The schib region can be used to obtain the latest SCHIB from the host passthrough subchannel. Since the guest SCHIB is virtualized, we currently only update the path related information so that the guest is aware of any path related changes when it issues the 'stsch' instruction. Signed-off-by: Farhan Ali <alifm@linux.ibm.com> Signed-off-by: Eric Farman <farman@linux.ibm.com> Reviewed-by: Cornelia Huck <cohuck@redhat.com> Message-Id: <20200505125757.98209-4-farman@linux.ibm.com> Signed-off-by: Cornelia Huck <cohuck@redhat.com>
813 lines
22 KiB
C
813 lines
22 KiB
C
/*
|
|
* I/O instructions for S/390
|
|
*
|
|
* Copyright 2012, 2015 IBM Corp.
|
|
* Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2 or (at
|
|
* your option) any later version. See the COPYING file in the top-level
|
|
* directory.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
|
|
#include "cpu.h"
|
|
#include "internal.h"
|
|
#include "hw/s390x/ioinst.h"
|
|
#include "trace.h"
|
|
#include "hw/s390x/s390-pci-bus.h"
|
|
#include "hw/s390x/pv.h"
|
|
|
|
/* All I/O instructions but chsc use the s format */
|
|
static uint64_t get_address_from_regs(CPUS390XState *env, uint32_t ipb,
|
|
uint8_t *ar)
|
|
{
|
|
/*
|
|
* Addresses for protected guests are all offsets into the
|
|
* satellite block which holds the IO control structures. Those
|
|
* control structures are always starting at offset 0 and are
|
|
* always aligned and accessible. So we can return 0 here which
|
|
* will pass the following address checks.
|
|
*/
|
|
if (s390_is_pv()) {
|
|
*ar = 0;
|
|
return 0;
|
|
}
|
|
return decode_basedisp_s(env, ipb, ar);
|
|
}
|
|
|
|
int ioinst_disassemble_sch_ident(uint32_t value, int *m, int *cssid, int *ssid,
|
|
int *schid)
|
|
{
|
|
if (!IOINST_SCHID_ONE(value)) {
|
|
return -EINVAL;
|
|
}
|
|
if (!IOINST_SCHID_M(value)) {
|
|
if (IOINST_SCHID_CSSID(value)) {
|
|
return -EINVAL;
|
|
}
|
|
*cssid = 0;
|
|
*m = 0;
|
|
} else {
|
|
*cssid = IOINST_SCHID_CSSID(value);
|
|
*m = 1;
|
|
}
|
|
*ssid = IOINST_SCHID_SSID(value);
|
|
*schid = IOINST_SCHID_NR(value);
|
|
return 0;
|
|
}
|
|
|
|
void ioinst_handle_xsch(S390CPU *cpu, uint64_t reg1, uintptr_t ra)
|
|
{
|
|
int cssid, ssid, schid, m;
|
|
SubchDev *sch;
|
|
|
|
if (ioinst_disassemble_sch_ident(reg1, &m, &cssid, &ssid, &schid)) {
|
|
s390_program_interrupt(&cpu->env, PGM_OPERAND, ra);
|
|
return;
|
|
}
|
|
trace_ioinst_sch_id("xsch", cssid, ssid, schid);
|
|
sch = css_find_subch(m, cssid, ssid, schid);
|
|
if (!sch || !css_subch_visible(sch)) {
|
|
setcc(cpu, 3);
|
|
return;
|
|
}
|
|
setcc(cpu, css_do_xsch(sch));
|
|
}
|
|
|
|
void ioinst_handle_csch(S390CPU *cpu, uint64_t reg1, uintptr_t ra)
|
|
{
|
|
int cssid, ssid, schid, m;
|
|
SubchDev *sch;
|
|
|
|
if (ioinst_disassemble_sch_ident(reg1, &m, &cssid, &ssid, &schid)) {
|
|
s390_program_interrupt(&cpu->env, PGM_OPERAND, ra);
|
|
return;
|
|
}
|
|
trace_ioinst_sch_id("csch", cssid, ssid, schid);
|
|
sch = css_find_subch(m, cssid, ssid, schid);
|
|
if (!sch || !css_subch_visible(sch)) {
|
|
setcc(cpu, 3);
|
|
return;
|
|
}
|
|
setcc(cpu, css_do_csch(sch));
|
|
}
|
|
|
|
void ioinst_handle_hsch(S390CPU *cpu, uint64_t reg1, uintptr_t ra)
|
|
{
|
|
int cssid, ssid, schid, m;
|
|
SubchDev *sch;
|
|
|
|
if (ioinst_disassemble_sch_ident(reg1, &m, &cssid, &ssid, &schid)) {
|
|
s390_program_interrupt(&cpu->env, PGM_OPERAND, ra);
|
|
return;
|
|
}
|
|
trace_ioinst_sch_id("hsch", cssid, ssid, schid);
|
|
sch = css_find_subch(m, cssid, ssid, schid);
|
|
if (!sch || !css_subch_visible(sch)) {
|
|
setcc(cpu, 3);
|
|
return;
|
|
}
|
|
setcc(cpu, css_do_hsch(sch));
|
|
}
|
|
|
|
static int ioinst_schib_valid(SCHIB *schib)
|
|
{
|
|
if ((be16_to_cpu(schib->pmcw.flags) & PMCW_FLAGS_MASK_INVALID) ||
|
|
(be32_to_cpu(schib->pmcw.chars) & PMCW_CHARS_MASK_INVALID)) {
|
|
return 0;
|
|
}
|
|
/* Disallow extended measurements for now. */
|
|
if (be32_to_cpu(schib->pmcw.chars) & PMCW_CHARS_MASK_XMWME) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void ioinst_handle_msch(S390CPU *cpu, uint64_t reg1, uint32_t ipb, uintptr_t ra)
|
|
{
|
|
int cssid, ssid, schid, m;
|
|
SubchDev *sch;
|
|
SCHIB schib;
|
|
uint64_t addr;
|
|
CPUS390XState *env = &cpu->env;
|
|
uint8_t ar;
|
|
|
|
addr = get_address_from_regs(env, ipb, &ar);
|
|
if (addr & 3) {
|
|
s390_program_interrupt(env, PGM_SPECIFICATION, ra);
|
|
return;
|
|
}
|
|
if (s390_is_pv()) {
|
|
s390_cpu_pv_mem_read(cpu, addr, &schib, sizeof(schib));
|
|
} else if (s390_cpu_virt_mem_read(cpu, addr, ar, &schib, sizeof(schib))) {
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
return;
|
|
}
|
|
if (ioinst_disassemble_sch_ident(reg1, &m, &cssid, &ssid, &schid) ||
|
|
!ioinst_schib_valid(&schib)) {
|
|
s390_program_interrupt(env, PGM_OPERAND, ra);
|
|
return;
|
|
}
|
|
trace_ioinst_sch_id("msch", cssid, ssid, schid);
|
|
sch = css_find_subch(m, cssid, ssid, schid);
|
|
if (!sch || !css_subch_visible(sch)) {
|
|
setcc(cpu, 3);
|
|
return;
|
|
}
|
|
setcc(cpu, css_do_msch(sch, &schib));
|
|
}
|
|
|
|
static void copy_orb_from_guest(ORB *dest, const ORB *src)
|
|
{
|
|
dest->intparm = be32_to_cpu(src->intparm);
|
|
dest->ctrl0 = be16_to_cpu(src->ctrl0);
|
|
dest->lpm = src->lpm;
|
|
dest->ctrl1 = src->ctrl1;
|
|
dest->cpa = be32_to_cpu(src->cpa);
|
|
}
|
|
|
|
static int ioinst_orb_valid(ORB *orb)
|
|
{
|
|
if ((orb->ctrl0 & ORB_CTRL0_MASK_INVALID) ||
|
|
(orb->ctrl1 & ORB_CTRL1_MASK_INVALID)) {
|
|
return 0;
|
|
}
|
|
/* We don't support MIDA. */
|
|
if (orb->ctrl1 & ORB_CTRL1_MASK_MIDAW) {
|
|
return 0;
|
|
}
|
|
if ((orb->cpa & HIGH_ORDER_BIT) != 0) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void ioinst_handle_ssch(S390CPU *cpu, uint64_t reg1, uint32_t ipb, uintptr_t ra)
|
|
{
|
|
int cssid, ssid, schid, m;
|
|
SubchDev *sch;
|
|
ORB orig_orb, orb;
|
|
uint64_t addr;
|
|
CPUS390XState *env = &cpu->env;
|
|
uint8_t ar;
|
|
|
|
addr = get_address_from_regs(env, ipb, &ar);
|
|
if (addr & 3) {
|
|
s390_program_interrupt(env, PGM_SPECIFICATION, ra);
|
|
return;
|
|
}
|
|
if (s390_is_pv()) {
|
|
s390_cpu_pv_mem_read(cpu, addr, &orig_orb, sizeof(orb));
|
|
} else if (s390_cpu_virt_mem_read(cpu, addr, ar, &orig_orb, sizeof(orb))) {
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
return;
|
|
}
|
|
copy_orb_from_guest(&orb, &orig_orb);
|
|
if (ioinst_disassemble_sch_ident(reg1, &m, &cssid, &ssid, &schid) ||
|
|
!ioinst_orb_valid(&orb)) {
|
|
s390_program_interrupt(env, PGM_OPERAND, ra);
|
|
return;
|
|
}
|
|
trace_ioinst_sch_id("ssch", cssid, ssid, schid);
|
|
sch = css_find_subch(m, cssid, ssid, schid);
|
|
if (!sch || !css_subch_visible(sch)) {
|
|
setcc(cpu, 3);
|
|
return;
|
|
}
|
|
setcc(cpu, css_do_ssch(sch, &orb));
|
|
}
|
|
|
|
void ioinst_handle_stcrw(S390CPU *cpu, uint32_t ipb, uintptr_t ra)
|
|
{
|
|
CRW crw;
|
|
uint64_t addr;
|
|
int cc;
|
|
CPUS390XState *env = &cpu->env;
|
|
uint8_t ar;
|
|
|
|
addr = get_address_from_regs(env, ipb, &ar);
|
|
if (addr & 3) {
|
|
s390_program_interrupt(env, PGM_SPECIFICATION, ra);
|
|
return;
|
|
}
|
|
|
|
cc = css_do_stcrw(&crw);
|
|
/* 0 - crw stored, 1 - zeroes stored */
|
|
|
|
if (s390_is_pv()) {
|
|
s390_cpu_pv_mem_write(cpu, addr, &crw, sizeof(crw));
|
|
setcc(cpu, cc);
|
|
} else {
|
|
if (s390_cpu_virt_mem_write(cpu, addr, ar, &crw, sizeof(crw)) == 0) {
|
|
setcc(cpu, cc);
|
|
} else {
|
|
if (cc == 0) {
|
|
/* Write failed: requeue CRW since STCRW is suppressing */
|
|
css_undo_stcrw(&crw);
|
|
}
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ioinst_handle_stsch(S390CPU *cpu, uint64_t reg1, uint32_t ipb,
|
|
uintptr_t ra)
|
|
{
|
|
int cssid, ssid, schid, m;
|
|
SubchDev *sch;
|
|
uint64_t addr;
|
|
int cc;
|
|
SCHIB schib;
|
|
CPUS390XState *env = &cpu->env;
|
|
uint8_t ar;
|
|
|
|
addr = get_address_from_regs(env, ipb, &ar);
|
|
if (addr & 3) {
|
|
s390_program_interrupt(env, PGM_SPECIFICATION, ra);
|
|
return;
|
|
}
|
|
|
|
if (ioinst_disassemble_sch_ident(reg1, &m, &cssid, &ssid, &schid)) {
|
|
/*
|
|
* The Ultravisor checks schid bit 16 to be one and bits 0-12
|
|
* to be 0 and injects a operand exception itself.
|
|
*
|
|
* Hence we should never end up here.
|
|
*/
|
|
g_assert(!s390_is_pv());
|
|
/*
|
|
* As operand exceptions have a lower priority than access exceptions,
|
|
* we check whether the memory area is writeable (injecting the
|
|
* access execption if it is not) first.
|
|
*/
|
|
if (!s390_cpu_virt_mem_check_write(cpu, addr, ar, sizeof(schib))) {
|
|
s390_program_interrupt(env, PGM_OPERAND, ra);
|
|
} else {
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
}
|
|
return;
|
|
}
|
|
trace_ioinst_sch_id("stsch", cssid, ssid, schid);
|
|
sch = css_find_subch(m, cssid, ssid, schid);
|
|
if (sch) {
|
|
if (css_subch_visible(sch)) {
|
|
cc = css_do_stsch(sch, &schib);
|
|
} else {
|
|
/* Indicate no more subchannels in this css/ss */
|
|
cc = 3;
|
|
}
|
|
} else {
|
|
if (css_schid_final(m, cssid, ssid, schid)) {
|
|
cc = 3; /* No more subchannels in this css/ss */
|
|
} else {
|
|
/* Store an empty schib. */
|
|
memset(&schib, 0, sizeof(schib));
|
|
cc = 0;
|
|
}
|
|
}
|
|
if (cc != 3) {
|
|
if (s390_is_pv()) {
|
|
s390_cpu_pv_mem_write(cpu, addr, &schib, sizeof(schib));
|
|
} else if (s390_cpu_virt_mem_write(cpu, addr, ar, &schib,
|
|
sizeof(schib)) != 0) {
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
return;
|
|
}
|
|
} else {
|
|
/* Access exceptions have a higher priority than cc3 */
|
|
if (!s390_is_pv() &&
|
|
s390_cpu_virt_mem_check_write(cpu, addr, ar, sizeof(schib)) != 0) {
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
return;
|
|
}
|
|
}
|
|
setcc(cpu, cc);
|
|
}
|
|
|
|
int ioinst_handle_tsch(S390CPU *cpu, uint64_t reg1, uint32_t ipb, uintptr_t ra)
|
|
{
|
|
CPUS390XState *env = &cpu->env;
|
|
int cssid, ssid, schid, m;
|
|
SubchDev *sch;
|
|
IRB irb;
|
|
uint64_t addr;
|
|
int cc, irb_len;
|
|
uint8_t ar;
|
|
|
|
if (ioinst_disassemble_sch_ident(reg1, &m, &cssid, &ssid, &schid)) {
|
|
s390_program_interrupt(env, PGM_OPERAND, ra);
|
|
return -EIO;
|
|
}
|
|
trace_ioinst_sch_id("tsch", cssid, ssid, schid);
|
|
addr = get_address_from_regs(env, ipb, &ar);
|
|
if (addr & 3) {
|
|
s390_program_interrupt(env, PGM_SPECIFICATION, ra);
|
|
return -EIO;
|
|
}
|
|
|
|
sch = css_find_subch(m, cssid, ssid, schid);
|
|
if (sch && css_subch_visible(sch)) {
|
|
cc = css_do_tsch_get_irb(sch, &irb, &irb_len);
|
|
} else {
|
|
cc = 3;
|
|
}
|
|
/* 0 - status pending, 1 - not status pending, 3 - not operational */
|
|
if (cc != 3) {
|
|
if (s390_is_pv()) {
|
|
s390_cpu_pv_mem_write(cpu, addr, &irb, irb_len);
|
|
} else if (s390_cpu_virt_mem_write(cpu, addr, ar, &irb, irb_len) != 0) {
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
return -EFAULT;
|
|
}
|
|
css_do_tsch_update_subch(sch);
|
|
} else {
|
|
irb_len = sizeof(irb) - sizeof(irb.emw);
|
|
/* Access exceptions have a higher priority than cc3 */
|
|
if (!s390_is_pv() &&
|
|
s390_cpu_virt_mem_check_write(cpu, addr, ar, irb_len) != 0) {
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
|
|
setcc(cpu, cc);
|
|
return 0;
|
|
}
|
|
|
|
typedef struct ChscReq {
|
|
uint16_t len;
|
|
uint16_t command;
|
|
uint32_t param0;
|
|
uint32_t param1;
|
|
uint32_t param2;
|
|
} QEMU_PACKED ChscReq;
|
|
|
|
typedef struct ChscResp {
|
|
uint16_t len;
|
|
uint16_t code;
|
|
uint32_t param;
|
|
char data[];
|
|
} QEMU_PACKED ChscResp;
|
|
|
|
#define CHSC_MIN_RESP_LEN 0x0008
|
|
|
|
#define CHSC_SCPD 0x0002
|
|
#define CHSC_SCSC 0x0010
|
|
#define CHSC_SDA 0x0031
|
|
#define CHSC_SEI 0x000e
|
|
|
|
#define CHSC_SCPD_0_M 0x20000000
|
|
#define CHSC_SCPD_0_C 0x10000000
|
|
#define CHSC_SCPD_0_FMT 0x0f000000
|
|
#define CHSC_SCPD_0_CSSID 0x00ff0000
|
|
#define CHSC_SCPD_0_RFMT 0x00000f00
|
|
#define CHSC_SCPD_0_RES 0xc000f000
|
|
#define CHSC_SCPD_1_RES 0xffffff00
|
|
#define CHSC_SCPD_01_CHPID 0x000000ff
|
|
static void ioinst_handle_chsc_scpd(ChscReq *req, ChscResp *res)
|
|
{
|
|
uint16_t len = be16_to_cpu(req->len);
|
|
uint32_t param0 = be32_to_cpu(req->param0);
|
|
uint32_t param1 = be32_to_cpu(req->param1);
|
|
uint16_t resp_code;
|
|
int rfmt;
|
|
uint16_t cssid;
|
|
uint8_t f_chpid, l_chpid;
|
|
int desc_size;
|
|
int m;
|
|
|
|
rfmt = (param0 & CHSC_SCPD_0_RFMT) >> 8;
|
|
if ((rfmt == 0) || (rfmt == 1)) {
|
|
rfmt = !!(param0 & CHSC_SCPD_0_C);
|
|
}
|
|
if ((len != 0x0010) || (param0 & CHSC_SCPD_0_RES) ||
|
|
(param1 & CHSC_SCPD_1_RES) || req->param2) {
|
|
resp_code = 0x0003;
|
|
goto out_err;
|
|
}
|
|
if (param0 & CHSC_SCPD_0_FMT) {
|
|
resp_code = 0x0007;
|
|
goto out_err;
|
|
}
|
|
cssid = (param0 & CHSC_SCPD_0_CSSID) >> 16;
|
|
m = param0 & CHSC_SCPD_0_M;
|
|
if (cssid != 0) {
|
|
if (!m || !css_present(cssid)) {
|
|
resp_code = 0x0008;
|
|
goto out_err;
|
|
}
|
|
}
|
|
f_chpid = param0 & CHSC_SCPD_01_CHPID;
|
|
l_chpid = param1 & CHSC_SCPD_01_CHPID;
|
|
if (l_chpid < f_chpid) {
|
|
resp_code = 0x0003;
|
|
goto out_err;
|
|
}
|
|
/* css_collect_chp_desc() is endian-aware */
|
|
desc_size = css_collect_chp_desc(m, cssid, f_chpid, l_chpid, rfmt,
|
|
&res->data);
|
|
res->code = cpu_to_be16(0x0001);
|
|
res->len = cpu_to_be16(8 + desc_size);
|
|
res->param = cpu_to_be32(rfmt);
|
|
return;
|
|
|
|
out_err:
|
|
res->code = cpu_to_be16(resp_code);
|
|
res->len = cpu_to_be16(CHSC_MIN_RESP_LEN);
|
|
res->param = cpu_to_be32(rfmt);
|
|
}
|
|
|
|
#define CHSC_SCSC_0_M 0x20000000
|
|
#define CHSC_SCSC_0_FMT 0x000f0000
|
|
#define CHSC_SCSC_0_CSSID 0x0000ff00
|
|
#define CHSC_SCSC_0_RES 0xdff000ff
|
|
static void ioinst_handle_chsc_scsc(ChscReq *req, ChscResp *res)
|
|
{
|
|
uint16_t len = be16_to_cpu(req->len);
|
|
uint32_t param0 = be32_to_cpu(req->param0);
|
|
uint8_t cssid;
|
|
uint16_t resp_code;
|
|
uint32_t general_chars[510];
|
|
uint32_t chsc_chars[508];
|
|
|
|
if (len != 0x0010) {
|
|
resp_code = 0x0003;
|
|
goto out_err;
|
|
}
|
|
|
|
if (param0 & CHSC_SCSC_0_FMT) {
|
|
resp_code = 0x0007;
|
|
goto out_err;
|
|
}
|
|
cssid = (param0 & CHSC_SCSC_0_CSSID) >> 8;
|
|
if (cssid != 0) {
|
|
if (!(param0 & CHSC_SCSC_0_M) || !css_present(cssid)) {
|
|
resp_code = 0x0008;
|
|
goto out_err;
|
|
}
|
|
}
|
|
if ((param0 & CHSC_SCSC_0_RES) || req->param1 || req->param2) {
|
|
resp_code = 0x0003;
|
|
goto out_err;
|
|
}
|
|
res->code = cpu_to_be16(0x0001);
|
|
res->len = cpu_to_be16(4080);
|
|
res->param = 0;
|
|
|
|
memset(general_chars, 0, sizeof(general_chars));
|
|
memset(chsc_chars, 0, sizeof(chsc_chars));
|
|
|
|
general_chars[0] = cpu_to_be32(0x03000000);
|
|
general_chars[1] = cpu_to_be32(0x00079000);
|
|
general_chars[3] = cpu_to_be32(0x00080000);
|
|
|
|
chsc_chars[0] = cpu_to_be32(0x40000000);
|
|
chsc_chars[3] = cpu_to_be32(0x00040000);
|
|
|
|
memcpy(res->data, general_chars, sizeof(general_chars));
|
|
memcpy(res->data + sizeof(general_chars), chsc_chars, sizeof(chsc_chars));
|
|
return;
|
|
|
|
out_err:
|
|
res->code = cpu_to_be16(resp_code);
|
|
res->len = cpu_to_be16(CHSC_MIN_RESP_LEN);
|
|
res->param = 0;
|
|
}
|
|
|
|
#define CHSC_SDA_0_FMT 0x0f000000
|
|
#define CHSC_SDA_0_OC 0x0000ffff
|
|
#define CHSC_SDA_0_RES 0xf0ff0000
|
|
#define CHSC_SDA_OC_MCSSE 0x0
|
|
#define CHSC_SDA_OC_MSS 0x2
|
|
static void ioinst_handle_chsc_sda(ChscReq *req, ChscResp *res)
|
|
{
|
|
uint16_t resp_code = 0x0001;
|
|
uint16_t len = be16_to_cpu(req->len);
|
|
uint32_t param0 = be32_to_cpu(req->param0);
|
|
uint16_t oc;
|
|
int ret;
|
|
|
|
if ((len != 0x0400) || (param0 & CHSC_SDA_0_RES)) {
|
|
resp_code = 0x0003;
|
|
goto out;
|
|
}
|
|
|
|
if (param0 & CHSC_SDA_0_FMT) {
|
|
resp_code = 0x0007;
|
|
goto out;
|
|
}
|
|
|
|
oc = param0 & CHSC_SDA_0_OC;
|
|
switch (oc) {
|
|
case CHSC_SDA_OC_MCSSE:
|
|
ret = css_enable_mcsse();
|
|
if (ret == -EINVAL) {
|
|
resp_code = 0x0101;
|
|
goto out;
|
|
}
|
|
break;
|
|
case CHSC_SDA_OC_MSS:
|
|
ret = css_enable_mss();
|
|
if (ret == -EINVAL) {
|
|
resp_code = 0x0101;
|
|
goto out;
|
|
}
|
|
break;
|
|
default:
|
|
resp_code = 0x0003;
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
res->code = cpu_to_be16(resp_code);
|
|
res->len = cpu_to_be16(CHSC_MIN_RESP_LEN);
|
|
res->param = 0;
|
|
}
|
|
|
|
static int chsc_sei_nt0_get_event(void *res)
|
|
{
|
|
/* no events yet */
|
|
return 1;
|
|
}
|
|
|
|
static int chsc_sei_nt0_have_event(void)
|
|
{
|
|
/* no events yet */
|
|
return 0;
|
|
}
|
|
|
|
static int chsc_sei_nt2_get_event(void *res)
|
|
{
|
|
if (s390_has_feat(S390_FEAT_ZPCI)) {
|
|
return pci_chsc_sei_nt2_get_event(res);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int chsc_sei_nt2_have_event(void)
|
|
{
|
|
if (s390_has_feat(S390_FEAT_ZPCI)) {
|
|
return pci_chsc_sei_nt2_have_event();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define CHSC_SEI_NT0 (1ULL << 63)
|
|
#define CHSC_SEI_NT2 (1ULL << 61)
|
|
static void ioinst_handle_chsc_sei(ChscReq *req, ChscResp *res)
|
|
{
|
|
uint64_t selection_mask = ldq_p(&req->param1);
|
|
uint8_t *res_flags = (uint8_t *)res->data;
|
|
int have_event = 0;
|
|
int have_more = 0;
|
|
|
|
/* regarding architecture nt0 can not be masked */
|
|
have_event = !chsc_sei_nt0_get_event(res);
|
|
have_more = chsc_sei_nt0_have_event();
|
|
|
|
if (selection_mask & CHSC_SEI_NT2) {
|
|
if (!have_event) {
|
|
have_event = !chsc_sei_nt2_get_event(res);
|
|
}
|
|
|
|
if (!have_more) {
|
|
have_more = chsc_sei_nt2_have_event();
|
|
}
|
|
}
|
|
|
|
if (have_event) {
|
|
res->code = cpu_to_be16(0x0001);
|
|
if (have_more) {
|
|
(*res_flags) |= 0x80;
|
|
} else {
|
|
(*res_flags) &= ~0x80;
|
|
css_clear_sei_pending();
|
|
}
|
|
} else {
|
|
res->code = cpu_to_be16(0x0005);
|
|
res->len = cpu_to_be16(CHSC_MIN_RESP_LEN);
|
|
}
|
|
}
|
|
|
|
static void ioinst_handle_chsc_unimplemented(ChscResp *res)
|
|
{
|
|
res->len = cpu_to_be16(CHSC_MIN_RESP_LEN);
|
|
res->code = cpu_to_be16(0x0004);
|
|
res->param = 0;
|
|
}
|
|
|
|
void ioinst_handle_chsc(S390CPU *cpu, uint32_t ipb, uintptr_t ra)
|
|
{
|
|
ChscReq *req;
|
|
ChscResp *res;
|
|
uint64_t addr = 0;
|
|
int reg;
|
|
uint16_t len;
|
|
uint16_t command;
|
|
CPUS390XState *env = &cpu->env;
|
|
uint8_t buf[TARGET_PAGE_SIZE];
|
|
|
|
trace_ioinst("chsc");
|
|
reg = (ipb >> 20) & 0x00f;
|
|
if (!s390_is_pv()) {
|
|
addr = env->regs[reg];
|
|
}
|
|
/* Page boundary? */
|
|
if (addr & 0xfff) {
|
|
s390_program_interrupt(env, PGM_SPECIFICATION, ra);
|
|
return;
|
|
}
|
|
/*
|
|
* Reading sizeof(ChscReq) bytes is currently enough for all of our
|
|
* present CHSC sub-handlers ... if we ever need more, we should take
|
|
* care of req->len here first.
|
|
*/
|
|
if (s390_is_pv()) {
|
|
s390_cpu_pv_mem_read(cpu, addr, buf, sizeof(ChscReq));
|
|
} else if (s390_cpu_virt_mem_read(cpu, addr, reg, buf, sizeof(ChscReq))) {
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
return;
|
|
}
|
|
req = (ChscReq *)buf;
|
|
len = be16_to_cpu(req->len);
|
|
/* Length field valid? */
|
|
if ((len < 16) || (len > 4088) || (len & 7)) {
|
|
s390_program_interrupt(env, PGM_OPERAND, ra);
|
|
return;
|
|
}
|
|
memset((char *)req + len, 0, TARGET_PAGE_SIZE - len);
|
|
res = (void *)((char *)req + len);
|
|
command = be16_to_cpu(req->command);
|
|
trace_ioinst_chsc_cmd(command, len);
|
|
switch (command) {
|
|
case CHSC_SCSC:
|
|
ioinst_handle_chsc_scsc(req, res);
|
|
break;
|
|
case CHSC_SCPD:
|
|
ioinst_handle_chsc_scpd(req, res);
|
|
break;
|
|
case CHSC_SDA:
|
|
ioinst_handle_chsc_sda(req, res);
|
|
break;
|
|
case CHSC_SEI:
|
|
ioinst_handle_chsc_sei(req, res);
|
|
break;
|
|
default:
|
|
ioinst_handle_chsc_unimplemented(res);
|
|
break;
|
|
}
|
|
|
|
if (s390_is_pv()) {
|
|
s390_cpu_pv_mem_write(cpu, addr + len, res, be16_to_cpu(res->len));
|
|
setcc(cpu, 0); /* Command execution complete */
|
|
} else {
|
|
if (!s390_cpu_virt_mem_write(cpu, addr + len, reg, res,
|
|
be16_to_cpu(res->len))) {
|
|
setcc(cpu, 0); /* Command execution complete */
|
|
} else {
|
|
s390_cpu_virt_mem_handle_exc(cpu, ra);
|
|
}
|
|
}
|
|
}
|
|
|
|
#define SCHM_REG1_RES(_reg) (_reg & 0x000000000ffffffc)
|
|
#define SCHM_REG1_MBK(_reg) ((_reg & 0x00000000f0000000) >> 28)
|
|
#define SCHM_REG1_UPD(_reg) ((_reg & 0x0000000000000002) >> 1)
|
|
#define SCHM_REG1_DCT(_reg) (_reg & 0x0000000000000001)
|
|
|
|
void ioinst_handle_schm(S390CPU *cpu, uint64_t reg1, uint64_t reg2,
|
|
uint32_t ipb, uintptr_t ra)
|
|
{
|
|
uint8_t mbk;
|
|
int update;
|
|
int dct;
|
|
CPUS390XState *env = &cpu->env;
|
|
|
|
trace_ioinst("schm");
|
|
|
|
if (SCHM_REG1_RES(reg1)) {
|
|
s390_program_interrupt(env, PGM_OPERAND, ra);
|
|
return;
|
|
}
|
|
|
|
mbk = SCHM_REG1_MBK(reg1);
|
|
update = SCHM_REG1_UPD(reg1);
|
|
dct = SCHM_REG1_DCT(reg1);
|
|
|
|
if (update && (reg2 & 0x000000000000001f)) {
|
|
s390_program_interrupt(env, PGM_OPERAND, ra);
|
|
return;
|
|
}
|
|
|
|
css_do_schm(mbk, update, dct, update ? reg2 : 0);
|
|
}
|
|
|
|
void ioinst_handle_rsch(S390CPU *cpu, uint64_t reg1, uintptr_t ra)
|
|
{
|
|
int cssid, ssid, schid, m;
|
|
SubchDev *sch;
|
|
|
|
if (ioinst_disassemble_sch_ident(reg1, &m, &cssid, &ssid, &schid)) {
|
|
s390_program_interrupt(&cpu->env, PGM_OPERAND, ra);
|
|
return;
|
|
}
|
|
trace_ioinst_sch_id("rsch", cssid, ssid, schid);
|
|
sch = css_find_subch(m, cssid, ssid, schid);
|
|
if (!sch || !css_subch_visible(sch)) {
|
|
setcc(cpu, 3);
|
|
return;
|
|
}
|
|
setcc(cpu, css_do_rsch(sch));
|
|
}
|
|
|
|
#define RCHP_REG1_RES(_reg) (_reg & 0x00000000ff00ff00)
|
|
#define RCHP_REG1_CSSID(_reg) ((_reg & 0x0000000000ff0000) >> 16)
|
|
#define RCHP_REG1_CHPID(_reg) (_reg & 0x00000000000000ff)
|
|
void ioinst_handle_rchp(S390CPU *cpu, uint64_t reg1, uintptr_t ra)
|
|
{
|
|
int cc;
|
|
uint8_t cssid;
|
|
uint8_t chpid;
|
|
int ret;
|
|
CPUS390XState *env = &cpu->env;
|
|
|
|
if (RCHP_REG1_RES(reg1)) {
|
|
s390_program_interrupt(env, PGM_OPERAND, ra);
|
|
return;
|
|
}
|
|
|
|
cssid = RCHP_REG1_CSSID(reg1);
|
|
chpid = RCHP_REG1_CHPID(reg1);
|
|
|
|
trace_ioinst_chp_id("rchp", cssid, chpid);
|
|
|
|
ret = css_do_rchp(cssid, chpid);
|
|
|
|
switch (ret) {
|
|
case -ENODEV:
|
|
cc = 3;
|
|
break;
|
|
case -EBUSY:
|
|
cc = 2;
|
|
break;
|
|
case 0:
|
|
cc = 0;
|
|
break;
|
|
default:
|
|
/* Invalid channel subsystem. */
|
|
s390_program_interrupt(env, PGM_OPERAND, ra);
|
|
return;
|
|
}
|
|
setcc(cpu, cc);
|
|
}
|
|
|
|
#define SAL_REG1_INVALID(_reg) (_reg & 0x0000000080000000)
|
|
void ioinst_handle_sal(S390CPU *cpu, uint64_t reg1, uintptr_t ra)
|
|
{
|
|
/* We do not provide address limit checking, so let's suppress it. */
|
|
if (SAL_REG1_INVALID(reg1) || reg1 & 0x000000000000ffff) {
|
|
s390_program_interrupt(&cpu->env, PGM_OPERAND, ra);
|
|
}
|
|
}
|