diff options
author | Daniel Baumann <mail@daniel-baumann.ch> | 2025-06-06 10:05:23 +0000 |
---|---|---|
committer | Daniel Baumann <mail@daniel-baumann.ch> | 2025-06-06 10:05:23 +0000 |
commit | 755cc582a2473d06f3a2131d506d0311cc70e9f9 (patch) | |
tree | 3efb1ddb8d57bbb4539ac0d229b384871c57820f /hw/s390x/css.c | |
parent | Initial commit. (diff) | |
download | qemu-upstream.tar.xz qemu-upstream.zip |
Adding upstream version 1:7.2+dfsg.upstream/1%7.2+dfsgupstream
Signed-off-by: Daniel Baumann <mail@daniel-baumann.ch>
Diffstat (limited to 'hw/s390x/css.c')
-rw-r--r-- | hw/s390x/css.c | 2718 |
1 files changed, 2718 insertions, 0 deletions
diff --git a/hw/s390x/css.c b/hw/s390x/css.c new file mode 100644 index 00000000..95d1b3a3 --- /dev/null +++ b/hw/s390x/css.c @@ -0,0 +1,2718 @@ +/* + * Channel subsystem base support. + * + * Copyright 2012 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 "qapi/error.h" +#include "qapi/visitor.h" +#include "qemu/bitops.h" +#include "qemu/error-report.h" +#include "exec/address-spaces.h" +#include "hw/s390x/ioinst.h" +#include "hw/qdev-properties.h" +#include "hw/s390x/css.h" +#include "trace.h" +#include "hw/s390x/s390_flic.h" +#include "hw/s390x/s390-virtio-ccw.h" +#include "hw/s390x/s390-ccw.h" + +typedef struct CrwContainer { + CRW crw; + QTAILQ_ENTRY(CrwContainer) sibling; +} CrwContainer; + +static const VMStateDescription vmstate_crw = { + .name = "s390_crw", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT16(flags, CRW), + VMSTATE_UINT16(rsid, CRW), + VMSTATE_END_OF_LIST() + }, +}; + +static const VMStateDescription vmstate_crw_container = { + .name = "s390_crw_container", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(crw, CrwContainer, 0, vmstate_crw, CRW), + VMSTATE_END_OF_LIST() + }, +}; + +typedef struct ChpInfo { + uint8_t in_use; + uint8_t type; + uint8_t is_virtual; +} ChpInfo; + +static const VMStateDescription vmstate_chp_info = { + .name = "s390_chp_info", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(in_use, ChpInfo), + VMSTATE_UINT8(type, ChpInfo), + VMSTATE_UINT8(is_virtual, ChpInfo), + VMSTATE_END_OF_LIST() + } +}; + +typedef struct SubchSet { + SubchDev *sch[MAX_SCHID + 1]; + unsigned long schids_used[BITS_TO_LONGS(MAX_SCHID + 1)]; + unsigned long devnos_used[BITS_TO_LONGS(MAX_SCHID + 1)]; +} SubchSet; + +static const VMStateDescription vmstate_scsw = { + .name = "s390_scsw", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT16(flags, SCSW), + VMSTATE_UINT16(ctrl, SCSW), + VMSTATE_UINT32(cpa, SCSW), + VMSTATE_UINT8(dstat, SCSW), + VMSTATE_UINT8(cstat, SCSW), + VMSTATE_UINT16(count, SCSW), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_pmcw = { + .name = "s390_pmcw", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(intparm, PMCW), + VMSTATE_UINT16(flags, PMCW), + VMSTATE_UINT16(devno, PMCW), + VMSTATE_UINT8(lpm, PMCW), + VMSTATE_UINT8(pnom, PMCW), + VMSTATE_UINT8(lpum, PMCW), + VMSTATE_UINT8(pim, PMCW), + VMSTATE_UINT16(mbi, PMCW), + VMSTATE_UINT8(pom, PMCW), + VMSTATE_UINT8(pam, PMCW), + VMSTATE_UINT8_ARRAY(chpid, PMCW, 8), + VMSTATE_UINT32(chars, PMCW), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_schib = { + .name = "s390_schib", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(pmcw, SCHIB, 0, vmstate_pmcw, PMCW), + VMSTATE_STRUCT(scsw, SCHIB, 0, vmstate_scsw, SCSW), + VMSTATE_UINT64(mba, SCHIB), + VMSTATE_UINT8_ARRAY(mda, SCHIB, 4), + VMSTATE_END_OF_LIST() + } +}; + + +static const VMStateDescription vmstate_ccw1 = { + .name = "s390_ccw1", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(cmd_code, CCW1), + VMSTATE_UINT8(flags, CCW1), + VMSTATE_UINT16(count, CCW1), + VMSTATE_UINT32(cda, CCW1), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_ciw = { + .name = "s390_ciw", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(type, CIW), + VMSTATE_UINT8(command, CIW), + VMSTATE_UINT16(count, CIW), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_sense_id = { + .name = "s390_sense_id", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(reserved, SenseId), + VMSTATE_UINT16(cu_type, SenseId), + VMSTATE_UINT8(cu_model, SenseId), + VMSTATE_UINT16(dev_type, SenseId), + VMSTATE_UINT8(dev_model, SenseId), + VMSTATE_UINT8(unused, SenseId), + VMSTATE_STRUCT_ARRAY(ciw, SenseId, MAX_CIWS, 0, vmstate_ciw, CIW), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_orb = { + .name = "s390_orb", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(intparm, ORB), + VMSTATE_UINT16(ctrl0, ORB), + VMSTATE_UINT8(lpm, ORB), + VMSTATE_UINT8(ctrl1, ORB), + VMSTATE_UINT32(cpa, ORB), + VMSTATE_END_OF_LIST() + } +}; + +static bool vmstate_schdev_orb_needed(void *opaque) +{ + return css_migration_enabled(); +} + +static const VMStateDescription vmstate_schdev_orb = { + .name = "s390_subch_dev/orb", + .version_id = 1, + .minimum_version_id = 1, + .needed = vmstate_schdev_orb_needed, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(orb, SubchDev, 1, vmstate_orb, ORB), + VMSTATE_END_OF_LIST() + } +}; + +static int subch_dev_post_load(void *opaque, int version_id); +static int subch_dev_pre_save(void *opaque); + +const char err_hint_devno[] = "Devno mismatch, tried to load wrong section!" + " Likely reason: some sequences of plug and unplug can break" + " migration for machine versions prior to 2.7 (known design flaw)."; + +const VMStateDescription vmstate_subch_dev = { + .name = "s390_subch_dev", + .version_id = 1, + .minimum_version_id = 1, + .post_load = subch_dev_post_load, + .pre_save = subch_dev_pre_save, + .fields = (VMStateField[]) { + VMSTATE_UINT8_EQUAL(cssid, SubchDev, "Bug!"), + VMSTATE_UINT8_EQUAL(ssid, SubchDev, "Bug!"), + VMSTATE_UINT16(migrated_schid, SubchDev), + VMSTATE_UINT16_EQUAL(devno, SubchDev, err_hint_devno), + VMSTATE_BOOL(thinint_active, SubchDev), + VMSTATE_STRUCT(curr_status, SubchDev, 0, vmstate_schib, SCHIB), + VMSTATE_UINT8_ARRAY(sense_data, SubchDev, 32), + VMSTATE_UINT64(channel_prog, SubchDev), + VMSTATE_STRUCT(last_cmd, SubchDev, 0, vmstate_ccw1, CCW1), + VMSTATE_BOOL(last_cmd_valid, SubchDev), + VMSTATE_STRUCT(id, SubchDev, 0, vmstate_sense_id, SenseId), + VMSTATE_BOOL(ccw_fmt_1, SubchDev), + VMSTATE_UINT8(ccw_no_data_cnt, SubchDev), + VMSTATE_END_OF_LIST() + }, + .subsections = (const VMStateDescription * []) { + &vmstate_schdev_orb, + NULL + } +}; + +typedef struct IndAddrPtrTmp { + IndAddr **parent; + uint64_t addr; + int32_t len; +} IndAddrPtrTmp; + +static int post_load_ind_addr(void *opaque, int version_id) +{ + IndAddrPtrTmp *ptmp = opaque; + IndAddr **ind_addr = ptmp->parent; + + if (ptmp->len != 0) { + *ind_addr = get_indicator(ptmp->addr, ptmp->len); + } else { + *ind_addr = NULL; + } + return 0; +} + +static int pre_save_ind_addr(void *opaque) +{ + IndAddrPtrTmp *ptmp = opaque; + IndAddr *ind_addr = *(ptmp->parent); + + if (ind_addr != NULL) { + ptmp->len = ind_addr->len; + ptmp->addr = ind_addr->addr; + } else { + ptmp->len = 0; + ptmp->addr = 0L; + } + + return 0; +} + +const VMStateDescription vmstate_ind_addr_tmp = { + .name = "s390_ind_addr_tmp", + .pre_save = pre_save_ind_addr, + .post_load = post_load_ind_addr, + + .fields = (VMStateField[]) { + VMSTATE_INT32(len, IndAddrPtrTmp), + VMSTATE_UINT64(addr, IndAddrPtrTmp), + VMSTATE_END_OF_LIST() + } +}; + +const VMStateDescription vmstate_ind_addr = { + .name = "s390_ind_addr_tmp", + .fields = (VMStateField[]) { + VMSTATE_WITH_TMP(IndAddr*, IndAddrPtrTmp, vmstate_ind_addr_tmp), + VMSTATE_END_OF_LIST() + } +}; + +typedef struct CssImage { + SubchSet *sch_set[MAX_SSID + 1]; + ChpInfo chpids[MAX_CHPID + 1]; +} CssImage; + +static const VMStateDescription vmstate_css_img = { + .name = "s390_css_img", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + /* Subchannel sets have no relevant state. */ + VMSTATE_STRUCT_ARRAY(chpids, CssImage, MAX_CHPID + 1, 0, + vmstate_chp_info, ChpInfo), + VMSTATE_END_OF_LIST() + } + +}; + +typedef struct IoAdapter { + uint32_t id; + uint8_t type; + uint8_t isc; + uint8_t flags; +} IoAdapter; + +typedef struct ChannelSubSys { + QTAILQ_HEAD(, CrwContainer) pending_crws; + bool sei_pending; + bool do_crw_mchk; + bool crws_lost; + uint8_t max_cssid; + uint8_t max_ssid; + bool chnmon_active; + uint64_t chnmon_area; + CssImage *css[MAX_CSSID + 1]; + uint8_t default_cssid; + /* don't migrate, see css_register_io_adapters */ + IoAdapter *io_adapters[CSS_IO_ADAPTER_TYPE_NUMS][MAX_ISC + 1]; + /* don't migrate, see get_indicator and IndAddrPtrTmp */ + QTAILQ_HEAD(, IndAddr) indicator_addresses; +} ChannelSubSys; + +static const VMStateDescription vmstate_css = { + .name = "s390_css", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_QTAILQ_V(pending_crws, ChannelSubSys, 1, vmstate_crw_container, + CrwContainer, sibling), + VMSTATE_BOOL(sei_pending, ChannelSubSys), + VMSTATE_BOOL(do_crw_mchk, ChannelSubSys), + VMSTATE_BOOL(crws_lost, ChannelSubSys), + /* These were kind of migrated by virtio */ + VMSTATE_UINT8(max_cssid, ChannelSubSys), + VMSTATE_UINT8(max_ssid, ChannelSubSys), + VMSTATE_BOOL(chnmon_active, ChannelSubSys), + VMSTATE_UINT64(chnmon_area, ChannelSubSys), + VMSTATE_ARRAY_OF_POINTER_TO_STRUCT(css, ChannelSubSys, MAX_CSSID + 1, + 0, vmstate_css_img, CssImage), + VMSTATE_UINT8(default_cssid, ChannelSubSys), + VMSTATE_END_OF_LIST() + } +}; + +static ChannelSubSys channel_subsys = { + .pending_crws = QTAILQ_HEAD_INITIALIZER(channel_subsys.pending_crws), + .do_crw_mchk = true, + .sei_pending = false, + .crws_lost = false, + .chnmon_active = false, + .indicator_addresses = + QTAILQ_HEAD_INITIALIZER(channel_subsys.indicator_addresses), +}; + +static int subch_dev_pre_save(void *opaque) +{ + SubchDev *s = opaque; + + /* Prepare remote_schid for save */ + s->migrated_schid = s->schid; + + return 0; +} + +static int subch_dev_post_load(void *opaque, int version_id) +{ + + SubchDev *s = opaque; + + /* Re-assign the subchannel to remote_schid if necessary */ + if (s->migrated_schid != s->schid) { + if (css_find_subch(true, s->cssid, s->ssid, s->schid) == s) { + /* + * Cleanup the slot before moving to s->migrated_schid provided + * it still belongs to us, i.e. it was not changed by previous + * invocation of this function. + */ + css_subch_assign(s->cssid, s->ssid, s->schid, s->devno, NULL); + } + /* It's OK to re-assign without a prior de-assign. */ + s->schid = s->migrated_schid; + css_subch_assign(s->cssid, s->ssid, s->schid, s->devno, s); + } + + if (css_migration_enabled()) { + /* No compat voodoo to do ;) */ + return 0; + } + /* + * Hack alert. If we don't migrate the channel subsystem status + * we still need to find out if the guest enabled mss/mcss-e. + * If the subchannel is enabled, it certainly was able to access it, + * so adjust the max_ssid/max_cssid values for relevant ssid/cssid + * values. This is not watertight, but better than nothing. + */ + if (s->curr_status.pmcw.flags & PMCW_FLAGS_MASK_ENA) { + if (s->ssid) { + channel_subsys.max_ssid = MAX_SSID; + } + if (s->cssid != channel_subsys.default_cssid) { + channel_subsys.max_cssid = MAX_CSSID; + } + } + return 0; +} + +void css_register_vmstate(void) +{ + vmstate_register(NULL, 0, &vmstate_css, &channel_subsys); +} + +IndAddr *get_indicator(hwaddr ind_addr, int len) +{ + IndAddr *indicator; + + QTAILQ_FOREACH(indicator, &channel_subsys.indicator_addresses, sibling) { + if (indicator->addr == ind_addr) { + indicator->refcnt++; + return indicator; + } + } + indicator = g_new0(IndAddr, 1); + indicator->addr = ind_addr; + indicator->len = len; + indicator->refcnt = 1; + QTAILQ_INSERT_TAIL(&channel_subsys.indicator_addresses, + indicator, sibling); + return indicator; +} + +static int s390_io_adapter_map(AdapterInfo *adapter, uint64_t map_addr, + bool do_map) +{ + S390FLICState *fs = s390_get_flic(); + S390FLICStateClass *fsc = s390_get_flic_class(fs); + + return fsc->io_adapter_map(fs, adapter->adapter_id, map_addr, do_map); +} + +void release_indicator(AdapterInfo *adapter, IndAddr *indicator) +{ + assert(indicator->refcnt > 0); + indicator->refcnt--; + if (indicator->refcnt > 0) { + return; + } + QTAILQ_REMOVE(&channel_subsys.indicator_addresses, indicator, sibling); + if (indicator->map) { + s390_io_adapter_map(adapter, indicator->map, false); + } + g_free(indicator); +} + +int map_indicator(AdapterInfo *adapter, IndAddr *indicator) +{ + int ret; + + if (indicator->map) { + return 0; /* already mapped is not an error */ + } + indicator->map = indicator->addr; + ret = s390_io_adapter_map(adapter, indicator->map, true); + if ((ret != 0) && (ret != -ENOSYS)) { + goto out_err; + } + return 0; + +out_err: + indicator->map = 0; + return ret; +} + +int css_create_css_image(uint8_t cssid, bool default_image) +{ + trace_css_new_image(cssid, default_image ? "(default)" : ""); + /* 255 is reserved */ + if (cssid == 255) { + return -EINVAL; + } + if (channel_subsys.css[cssid]) { + return -EBUSY; + } + channel_subsys.css[cssid] = g_new0(CssImage, 1); + if (default_image) { + channel_subsys.default_cssid = cssid; + } + return 0; +} + +uint32_t css_get_adapter_id(CssIoAdapterType type, uint8_t isc) +{ + if (type >= CSS_IO_ADAPTER_TYPE_NUMS || isc > MAX_ISC || + !channel_subsys.io_adapters[type][isc]) { + return -1; + } + + return channel_subsys.io_adapters[type][isc]->id; +} + +/** + * css_register_io_adapters: Register I/O adapters per ISC during init + * + * @swap: an indication if byte swap is needed. + * @maskable: an indication if the adapter is subject to the mask operation. + * @flags: further characteristics of the adapter. + * e.g. suppressible, an indication if the adapter is subject to AIS. + * @errp: location to store error information. + */ +void css_register_io_adapters(CssIoAdapterType type, bool swap, bool maskable, + uint8_t flags, Error **errp) +{ + uint32_t id; + int ret, isc; + IoAdapter *adapter; + S390FLICState *fs = s390_get_flic(); + S390FLICStateClass *fsc = s390_get_flic_class(fs); + + /* + * Disallow multiple registrations for the same device type. + * Report an error if registering for an already registered type. + */ + if (channel_subsys.io_adapters[type][0]) { + error_setg(errp, "Adapters for type %d already registered", type); + } + + for (isc = 0; isc <= MAX_ISC; isc++) { + id = (type << 3) | isc; + ret = fsc->register_io_adapter(fs, id, isc, swap, maskable, flags); + if (ret == 0) { + adapter = g_new0(IoAdapter, 1); + adapter->id = id; + adapter->isc = isc; + adapter->type = type; + adapter->flags = flags; + channel_subsys.io_adapters[type][isc] = adapter; + } else { + error_setg_errno(errp, -ret, "Unexpected error %d when " + "registering adapter %d", ret, id); + break; + } + } + + /* + * No need to free registered adapters in kvm: kvm will clean up + * when the machine goes away. + */ + if (ret) { + for (isc--; isc >= 0; isc--) { + g_free(channel_subsys.io_adapters[type][isc]); + channel_subsys.io_adapters[type][isc] = NULL; + } + } + +} + +static void css_clear_io_interrupt(uint16_t subchannel_id, + uint16_t subchannel_nr) +{ + Error *err = NULL; + static bool no_clear_irq; + S390FLICState *fs = s390_get_flic(); + S390FLICStateClass *fsc = s390_get_flic_class(fs); + int r; + + if (unlikely(no_clear_irq)) { + return; + } + r = fsc->clear_io_irq(fs, subchannel_id, subchannel_nr); + switch (r) { + case 0: + break; + case -ENOSYS: + no_clear_irq = true; + /* + * Ignore unavailability, as the user can't do anything + * about it anyway. + */ + break; + default: + error_setg_errno(&err, -r, "unexpected error condition"); + error_propagate(&error_abort, err); + } +} + +static inline uint16_t css_do_build_subchannel_id(uint8_t cssid, uint8_t ssid) +{ + if (channel_subsys.max_cssid > 0) { + return (cssid << 8) | (1 << 3) | (ssid << 1) | 1; + } + return (ssid << 1) | 1; +} + +uint16_t css_build_subchannel_id(SubchDev *sch) +{ + return css_do_build_subchannel_id(sch->cssid, sch->ssid); +} + +void css_inject_io_interrupt(SubchDev *sch) +{ + uint8_t isc = (sch->curr_status.pmcw.flags & PMCW_FLAGS_MASK_ISC) >> 11; + + trace_css_io_interrupt(sch->cssid, sch->ssid, sch->schid, + sch->curr_status.pmcw.intparm, isc, ""); + s390_io_interrupt(css_build_subchannel_id(sch), + sch->schid, + sch->curr_status.pmcw.intparm, + isc << 27); +} + +void css_conditional_io_interrupt(SubchDev *sch) +{ + /* + * If the subchannel is not enabled, it is not made status pending + * (see PoP p. 16-17, "Status Control"). + */ + if (!(sch->curr_status.pmcw.flags & PMCW_FLAGS_MASK_ENA)) { + return; + } + + /* + * If the subchannel is not currently status pending, make it pending + * with alert status. + */ + if (!(sch->curr_status.scsw.ctrl & SCSW_STCTL_STATUS_PEND)) { + uint8_t isc = (sch->curr_status.pmcw.flags & PMCW_FLAGS_MASK_ISC) >> 11; + + trace_css_io_interrupt(sch->cssid, sch->ssid, sch->schid, + sch->curr_status.pmcw.intparm, isc, + "(unsolicited)"); + sch->curr_status.scsw.ctrl &= ~SCSW_CTRL_MASK_STCTL; + sch->curr_status.scsw.ctrl |= + SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND; + /* Inject an I/O interrupt. */ + s390_io_interrupt(css_build_subchannel_id(sch), + sch->schid, + sch->curr_status.pmcw.intparm, + isc << 27); + } +} + +int css_do_sic(CPUS390XState *env, uint8_t isc, uint16_t mode) +{ + S390FLICState *fs = s390_get_flic(); + S390FLICStateClass *fsc = s390_get_flic_class(fs); + int r; + + if (env->psw.mask & PSW_MASK_PSTATE) { + r = -PGM_PRIVILEGED; + goto out; + } + + trace_css_do_sic(mode, isc); + switch (mode) { + case SIC_IRQ_MODE_ALL: + case SIC_IRQ_MODE_SINGLE: + break; + default: + r = -PGM_OPERAND; + goto out; + } + + r = fsc->modify_ais_mode(fs, isc, mode) ? -PGM_OPERATION : 0; +out: + return r; +} + +void css_adapter_interrupt(CssIoAdapterType type, uint8_t isc) +{ + S390FLICState *fs = s390_get_flic(); + S390FLICStateClass *fsc = s390_get_flic_class(fs); + uint32_t io_int_word = (isc << 27) | IO_INT_WORD_AI; + IoAdapter *adapter = channel_subsys.io_adapters[type][isc]; + + if (!adapter) { + return; + } + + trace_css_adapter_interrupt(isc); + if (fs->ais_supported) { + if (fsc->inject_airq(fs, type, isc, adapter->flags)) { + error_report("Failed to inject airq with AIS supported"); + exit(1); + } + } else { + s390_io_interrupt(0, 0, 0, io_int_word); + } +} + +static void sch_handle_clear_func(SubchDev *sch) +{ + SCHIB *schib = &sch->curr_status; + int path; + + /* Path management: In our simple css, we always choose the only path. */ + path = 0x80; + + /* Reset values prior to 'issuing the clear signal'. */ + schib->pmcw.lpum = 0; + schib->pmcw.pom = 0xff; + schib->scsw.flags &= ~SCSW_FLAGS_MASK_PNO; + + /* We always 'attempt to issue the clear signal', and we always succeed. */ + sch->channel_prog = 0x0; + sch->last_cmd_valid = false; + schib->scsw.ctrl &= ~SCSW_ACTL_CLEAR_PEND; + schib->scsw.ctrl |= SCSW_STCTL_STATUS_PEND; + + schib->scsw.dstat = 0; + schib->scsw.cstat = 0; + schib->pmcw.lpum = path; + +} + +static void sch_handle_halt_func(SubchDev *sch) +{ + SCHIB *schib = &sch->curr_status; + hwaddr curr_ccw = sch->channel_prog; + int path; + + /* Path management: In our simple css, we always choose the only path. */ + path = 0x80; + + /* We always 'attempt to issue the halt signal', and we always succeed. */ + sch->channel_prog = 0x0; + sch->last_cmd_valid = false; + schib->scsw.ctrl &= ~SCSW_ACTL_HALT_PEND; + schib->scsw.ctrl |= SCSW_STCTL_STATUS_PEND; + + if ((schib->scsw.ctrl & (SCSW_ACTL_SUBCH_ACTIVE | + SCSW_ACTL_DEVICE_ACTIVE)) || + !((schib->scsw.ctrl & SCSW_ACTL_START_PEND) || + (schib->scsw.ctrl & SCSW_ACTL_SUSP))) { + schib->scsw.dstat = SCSW_DSTAT_DEVICE_END; + } + if ((schib->scsw.ctrl & (SCSW_ACTL_SUBCH_ACTIVE | + SCSW_ACTL_DEVICE_ACTIVE)) || + (schib->scsw.ctrl & SCSW_ACTL_SUSP)) { + schib->scsw.cpa = curr_ccw + 8; + } + schib->scsw.cstat = 0; + schib->pmcw.lpum = path; + +} + +/* + * As the SenseId struct cannot be packed (would cause unaligned accesses), we + * have to copy the individual fields to an unstructured area using the correct + * layout (see SA22-7204-01 "Common I/O-Device Commands"). + */ +static void copy_sense_id_to_guest(uint8_t *dest, SenseId *src) +{ + int i; + + dest[0] = src->reserved; + stw_be_p(dest + 1, src->cu_type); + dest[3] = src->cu_model; + stw_be_p(dest + 4, src->dev_type); + dest[6] = src->dev_model; + dest[7] = src->unused; + for (i = 0; i < ARRAY_SIZE(src->ciw); i++) { + dest[8 + i * 4] = src->ciw[i].type; + dest[9 + i * 4] = src->ciw[i].command; + stw_be_p(dest + 10 + i * 4, src->ciw[i].count); + } +} + +static CCW1 copy_ccw_from_guest(hwaddr addr, bool fmt1) +{ + CCW0 tmp0; + CCW1 tmp1; + CCW1 ret; + + if (fmt1) { + cpu_physical_memory_read(addr, &tmp1, sizeof(tmp1)); + ret.cmd_code = tmp1.cmd_code; + ret.flags = tmp1.flags; + ret.count = be16_to_cpu(tmp1.count); + ret.cda = be32_to_cpu(tmp1.cda); + } else { + cpu_physical_memory_read(addr, &tmp0, sizeof(tmp0)); + if ((tmp0.cmd_code & 0x0f) == CCW_CMD_TIC) { + ret.cmd_code = CCW_CMD_TIC; + ret.flags = 0; + ret.count = 0; + } else { + ret.cmd_code = tmp0.cmd_code; + ret.flags = tmp0.flags; + ret.count = be16_to_cpu(tmp0.count); + } + ret.cda = be16_to_cpu(tmp0.cda1) | (tmp0.cda0 << 16); + } + return ret; +} +/** + * If out of bounds marks the stream broken. If broken returns -EINVAL, + * otherwise the requested length (may be zero) + */ +static inline int cds_check_len(CcwDataStream *cds, int len) +{ + if (cds->at_byte + len > cds->count) { + cds->flags |= CDS_F_STREAM_BROKEN; + } + return cds->flags & CDS_F_STREAM_BROKEN ? -EINVAL : len; +} + +static inline bool cds_ccw_addrs_ok(hwaddr addr, int len, bool ccw_fmt1) +{ + return (addr + len) < (ccw_fmt1 ? (1UL << 31) : (1UL << 24)); +} + +static int ccw_dstream_rw_noflags(CcwDataStream *cds, void *buff, int len, + CcwDataStreamOp op) +{ + int ret; + + ret = cds_check_len(cds, len); + if (ret <= 0) { + return ret; + } + if (!cds_ccw_addrs_ok(cds->cda, len, cds->flags & CDS_F_FMT)) { + return -EINVAL; /* channel program check */ + } + if (op == CDS_OP_A) { + goto incr; + } + if (!cds->do_skip) { + ret = address_space_rw(&address_space_memory, cds->cda, + MEMTXATTRS_UNSPECIFIED, buff, len, op); + } else { + ret = MEMTX_OK; + } + if (ret != MEMTX_OK) { + cds->flags |= CDS_F_STREAM_BROKEN; + return -EINVAL; + } +incr: + cds->at_byte += len; + cds->cda += len; + return 0; +} + +/* returns values between 1 and bsz, where bsz is a power of 2 */ +static inline uint16_t ida_continuous_left(hwaddr cda, uint64_t bsz) +{ + return bsz - (cda & (bsz - 1)); +} + +static inline uint64_t ccw_ida_block_size(uint8_t flags) +{ + if ((flags & CDS_F_C64) && !(flags & CDS_F_I2K)) { + return 1ULL << 12; + } + return 1ULL << 11; +} + +static inline int ida_read_next_idaw(CcwDataStream *cds) +{ + union {uint64_t fmt2; uint32_t fmt1; } idaw; + int ret; + hwaddr idaw_addr; + bool idaw_fmt2 = cds->flags & CDS_F_C64; + bool ccw_fmt1 = cds->flags & CDS_F_FMT; + + if (idaw_fmt2) { + idaw_addr = cds->cda_orig + sizeof(idaw.fmt2) * cds->at_idaw; + if (idaw_addr & 0x07 || !cds_ccw_addrs_ok(idaw_addr, 0, ccw_fmt1)) { + return -EINVAL; /* channel program check */ + } + ret = address_space_read(&address_space_memory, idaw_addr, + MEMTXATTRS_UNSPECIFIED, &idaw.fmt2, + sizeof(idaw.fmt2)); + cds->cda = be64_to_cpu(idaw.fmt2); + } else { + idaw_addr = cds->cda_orig + sizeof(idaw.fmt1) * cds->at_idaw; + if (idaw_addr & 0x03 || !cds_ccw_addrs_ok(idaw_addr, 0, ccw_fmt1)) { + return -EINVAL; /* channel program check */ + } + ret = address_space_read(&address_space_memory, idaw_addr, + MEMTXATTRS_UNSPECIFIED, &idaw.fmt1, + sizeof(idaw.fmt1)); + cds->cda = be64_to_cpu(idaw.fmt1); + if (cds->cda & 0x80000000) { + return -EINVAL; /* channel program check */ + } + } + ++(cds->at_idaw); + if (ret != MEMTX_OK) { + /* assume inaccessible address */ + return -EINVAL; /* channel program check */ + } + return 0; +} + +static int ccw_dstream_rw_ida(CcwDataStream *cds, void *buff, int len, + CcwDataStreamOp op) +{ + uint64_t bsz = ccw_ida_block_size(cds->flags); + int ret = 0; + uint16_t cont_left, iter_len; + + ret = cds_check_len(cds, len); + if (ret <= 0) { + return ret; + } + if (!cds->at_idaw) { + /* read first idaw */ + ret = ida_read_next_idaw(cds); + if (ret) { + goto err; + } + cont_left = ida_continuous_left(cds->cda, bsz); + } else { + cont_left = ida_continuous_left(cds->cda, bsz); + if (cont_left == bsz) { + ret = ida_read_next_idaw(cds); + if (ret) { + goto err; + } + if (cds->cda & (bsz - 1)) { + ret = -EINVAL; /* channel program check */ + goto err; + } + } + } + do { + iter_len = MIN(len, cont_left); + if (op != CDS_OP_A) { + if (!cds->do_skip) { + ret = address_space_rw(&address_space_memory, cds->cda, + MEMTXATTRS_UNSPECIFIED, buff, iter_len, + op); + } else { + ret = MEMTX_OK; + } + if (ret != MEMTX_OK) { + /* assume inaccessible address */ + ret = -EINVAL; /* channel program check */ + goto err; + } + } + cds->at_byte += iter_len; + cds->cda += iter_len; + len -= iter_len; + if (!len) { + break; + } + ret = ida_read_next_idaw(cds); + if (ret) { + goto err; + } + cont_left = bsz; + } while (true); + return ret; +err: + cds->flags |= CDS_F_STREAM_BROKEN; + return ret; +} + +void ccw_dstream_init(CcwDataStream *cds, CCW1 const *ccw, ORB const *orb) +{ + /* + * We don't support MIDA (an optional facility) yet and we + * catch this earlier. Just for expressing the precondition. + */ + g_assert(!(orb->ctrl1 & ORB_CTRL1_MASK_MIDAW)); + cds->flags = (orb->ctrl0 & ORB_CTRL0_MASK_I2K ? CDS_F_I2K : 0) | + (orb->ctrl0 & ORB_CTRL0_MASK_C64 ? CDS_F_C64 : 0) | + (orb->ctrl0 & ORB_CTRL0_MASK_FMT ? CDS_F_FMT : 0) | + (ccw->flags & CCW_FLAG_IDA ? CDS_F_IDA : 0); + + cds->count = ccw->count; + cds->cda_orig = ccw->cda; + /* skip is only effective for read, read backwards, or sense commands */ + cds->do_skip = (ccw->flags & CCW_FLAG_SKIP) && + ((ccw->cmd_code & 0x0f) == CCW_CMD_BASIC_SENSE || + (ccw->cmd_code & 0x03) == 0x02 /* read */ || + (ccw->cmd_code & 0x0f) == 0x0c /* read backwards */); + ccw_dstream_rewind(cds); + if (!(cds->flags & CDS_F_IDA)) { + cds->op_handler = ccw_dstream_rw_noflags; + } else { + cds->op_handler = ccw_dstream_rw_ida; + } +} + +static int css_interpret_ccw(SubchDev *sch, hwaddr ccw_addr, + bool suspend_allowed) +{ + int ret; + bool check_len; + int len; + CCW1 ccw; + + if (!ccw_addr) { + return -EINVAL; /* channel-program check */ + } + /* Check doubleword aligned and 31 or 24 (fmt 0) bit addressable. */ + if (ccw_addr & (sch->ccw_fmt_1 ? 0x80000007 : 0xff000007)) { + return -EINVAL; + } + + /* Translate everything to format-1 ccws - the information is the same. */ + ccw = copy_ccw_from_guest(ccw_addr, sch->ccw_fmt_1); + + /* Check for invalid command codes. */ + if ((ccw.cmd_code & 0x0f) == 0) { + return -EINVAL; + } + if (((ccw.cmd_code & 0x0f) == CCW_CMD_TIC) && + ((ccw.cmd_code & 0xf0) != 0)) { + return -EINVAL; + } + if (!sch->ccw_fmt_1 && (ccw.count == 0) && + (ccw.cmd_code != CCW_CMD_TIC)) { + return -EINVAL; + } + + /* We don't support MIDA. */ + if (ccw.flags & CCW_FLAG_MIDA) { + return -EINVAL; + } + + if (ccw.flags & CCW_FLAG_SUSPEND) { + return suspend_allowed ? -EINPROGRESS : -EINVAL; + } + + check_len = !((ccw.flags & CCW_FLAG_SLI) && !(ccw.flags & CCW_FLAG_DC)); + + if (!ccw.cda) { + if (sch->ccw_no_data_cnt == 255) { + return -EINVAL; + } + sch->ccw_no_data_cnt++; + } + + /* Look at the command. */ + ccw_dstream_init(&sch->cds, &ccw, &(sch->orb)); + switch (ccw.cmd_code) { + case CCW_CMD_NOOP: + /* Nothing to do. */ + ret = 0; + break; + case CCW_CMD_BASIC_SENSE: + if (check_len) { + if (ccw.count != sizeof(sch->sense_data)) { + ret = -EINVAL; + break; + } + } + len = MIN(ccw.count, sizeof(sch->sense_data)); + ret = ccw_dstream_write_buf(&sch->cds, sch->sense_data, len); + sch->curr_status.scsw.count = ccw_dstream_residual_count(&sch->cds); + if (!ret) { + memset(sch->sense_data, 0, sizeof(sch->sense_data)); + } + break; + case CCW_CMD_SENSE_ID: + { + /* According to SA22-7204-01, Sense-ID can store up to 256 bytes */ + uint8_t sense_id[256]; + + copy_sense_id_to_guest(sense_id, &sch->id); + /* Sense ID information is device specific. */ + if (check_len) { + if (ccw.count != sizeof(sense_id)) { + ret = -EINVAL; + break; + } + } + len = MIN(ccw.count, sizeof(sense_id)); + /* + * Only indicate 0xff in the first sense byte if we actually + * have enough place to store at least bytes 0-3. + */ + if (len >= 4) { + sense_id[0] = 0xff; + } else { + sense_id[0] = 0; + } + ret = ccw_dstream_write_buf(&sch->cds, sense_id, len); + if (!ret) { + sch->curr_status.scsw.count = ccw_dstream_residual_count(&sch->cds); + } + break; + } + case CCW_CMD_TIC: + if (sch->last_cmd_valid && (sch->last_cmd.cmd_code == CCW_CMD_TIC)) { + ret = -EINVAL; + break; + } + if (ccw.flags || ccw.count) { + /* We have already sanitized these if converted from fmt 0. */ + ret = -EINVAL; + break; + } + sch->channel_prog = ccw.cda; + ret = -EAGAIN; + break; + default: + if (sch->ccw_cb) { + /* Handle device specific commands. */ + ret = sch->ccw_cb(sch, ccw); + } else { + ret = -ENOSYS; + } + break; + } + sch->last_cmd = ccw; + sch->last_cmd_valid = true; + if (ret == 0) { + if (ccw.flags & CCW_FLAG_CC) { + sch->channel_prog += 8; + ret = -EAGAIN; + } + } + + return ret; +} + +static void sch_handle_start_func_virtual(SubchDev *sch) +{ + SCHIB *schib = &sch->curr_status; + int path; + int ret; + bool suspend_allowed; + + /* Path management: In our simple css, we always choose the only path. */ + path = 0x80; + + if (!(schib->scsw.ctrl & SCSW_ACTL_SUSP)) { + /* Start Function triggered via ssch, i.e. we have an ORB */ + ORB *orb = &sch->orb; + schib->scsw.cstat = 0; + schib->scsw.dstat = 0; + /* Look at the orb and try to execute the channel program. */ + schib->pmcw.intparm = orb->intparm; + if (!(orb->lpm & path)) { + /* Generate a deferred cc 3 condition. */ + schib->scsw.flags |= SCSW_FLAGS_MASK_CC; + schib->scsw.ctrl &= ~SCSW_CTRL_MASK_STCTL; + schib->scsw.ctrl |= (SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND); + return; + } + sch->ccw_fmt_1 = !!(orb->ctrl0 & ORB_CTRL0_MASK_FMT); + schib->scsw.flags |= (sch->ccw_fmt_1) ? SCSW_FLAGS_MASK_FMT : 0; + sch->ccw_no_data_cnt = 0; + suspend_allowed = !!(orb->ctrl0 & ORB_CTRL0_MASK_SPND); + } else { + /* Start Function resumed via rsch */ + schib->scsw.ctrl &= ~(SCSW_ACTL_SUSP | SCSW_ACTL_RESUME_PEND); + /* The channel program had been suspended before. */ + suspend_allowed = true; + } + sch->last_cmd_valid = false; + do { + ret = css_interpret_ccw(sch, sch->channel_prog, suspend_allowed); + switch (ret) { + case -EAGAIN: + /* ccw chain, continue processing */ + break; + case 0: + /* success */ + schib->scsw.ctrl &= ~SCSW_ACTL_START_PEND; + schib->scsw.ctrl &= ~SCSW_CTRL_MASK_STCTL; + schib->scsw.ctrl |= SCSW_STCTL_PRIMARY | SCSW_STCTL_SECONDARY | + SCSW_STCTL_STATUS_PEND; + schib->scsw.dstat = SCSW_DSTAT_CHANNEL_END | SCSW_DSTAT_DEVICE_END; + schib->scsw.cpa = sch->channel_prog + 8; + break; + case -EIO: + /* I/O errors, status depends on specific devices */ + break; + case -ENOSYS: + /* unsupported command, generate unit check (command reject) */ + schib->scsw.ctrl &= ~SCSW_ACTL_START_PEND; + schib->scsw.dstat = SCSW_DSTAT_UNIT_CHECK; + /* Set sense bit 0 in ecw0. */ + sch->sense_data[0] = 0x80; + schib->scsw.ctrl &= ~SCSW_CTRL_MASK_STCTL; + schib->scsw.ctrl |= SCSW_STCTL_PRIMARY | SCSW_STCTL_SECONDARY | + SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND; + schib->scsw.cpa = sch->channel_prog + 8; + break; + case -EINPROGRESS: + /* channel program has been suspended */ + schib->scsw.ctrl &= ~SCSW_ACTL_START_PEND; + schib->scsw.ctrl |= SCSW_ACTL_SUSP; + break; + default: + /* error, generate channel program check */ + schib->scsw.ctrl &= ~SCSW_ACTL_START_PEND; + schib->scsw.cstat = SCSW_CSTAT_PROG_CHECK; + schib->scsw.ctrl &= ~SCSW_CTRL_MASK_STCTL; + schib->scsw.ctrl |= SCSW_STCTL_PRIMARY | SCSW_STCTL_SECONDARY | + SCSW_STCTL_ALERT | SCSW_STCTL_STATUS_PEND; + schib->scsw.cpa = sch->channel_prog + 8; + break; + } + } while (ret == -EAGAIN); + +} + +static IOInstEnding sch_handle_halt_func_passthrough(SubchDev *sch) +{ + int ret; + + ret = s390_ccw_halt(sch); + if (ret == -ENOSYS) { + sch_handle_halt_func(sch); + return IOINST_CC_EXPECTED; + } + /* + * Some conditions may have been detected prior to starting the halt + * function; map them to the correct cc. + * Note that we map both -ENODEV and -EACCES to cc 3 (there's not really + * anything else we can do.) + */ + switch (ret) { + case -EBUSY: + return IOINST_CC_BUSY; + case -ENODEV: + case -EACCES: + return IOINST_CC_NOT_OPERATIONAL; + default: + return IOINST_CC_EXPECTED; + } +} + +static IOInstEnding sch_handle_clear_func_passthrough(SubchDev *sch) +{ + int ret; + + ret = s390_ccw_clear(sch); + if (ret == -ENOSYS) { + sch_handle_clear_func(sch); + return IOINST_CC_EXPECTED; + } + /* + * Some conditions may have been detected prior to starting the clear + * function; map them to the correct cc. + * Note that we map both -ENODEV and -EACCES to cc 3 (there's not really + * anything else we can do.) + */ + switch (ret) { + case -ENODEV: + case -EACCES: + return IOINST_CC_NOT_OPERATIONAL; + default: + return IOINST_CC_EXPECTED; + } +} + +static IOInstEnding sch_handle_start_func_passthrough(SubchDev *sch) +{ + SCHIB *schib = &sch->curr_status; + ORB *orb = &sch->orb; + if (!(schib->scsw.ctrl & SCSW_ACTL_SUSP)) { + assert(orb != NULL); + schib->pmcw.intparm = orb->intparm; + } + return s390_ccw_cmd_request(sch); +} + +/* + * On real machines, this would run asynchronously to the main vcpus. + * We might want to make some parts of the ssch handling (interpreting + * read/writes) asynchronous later on if we start supporting more than + * our current very simple devices. + */ +IOInstEnding do_subchannel_work_virtual(SubchDev *sch) +{ + SCHIB *schib = &sch->curr_status; + + if (schib->scsw.ctrl & SCSW_FCTL_CLEAR_FUNC) { + sch_handle_clear_func(sch); + } else if (schib->scsw.ctrl & SCSW_FCTL_HALT_FUNC) { + sch_handle_halt_func(sch); + } else if (schib->scsw.ctrl & SCSW_FCTL_START_FUNC) { + /* Triggered by both ssch and rsch. */ + sch_handle_start_func_virtual(sch); + } + css_inject_io_interrupt(sch); + /* inst must succeed if this func is called */ + return IOINST_CC_EXPECTED; +} + +IOInstEnding do_subchannel_work_passthrough(SubchDev *sch) +{ + SCHIB *schib = &sch->curr_status; + + if (schib->scsw.ctrl & SCSW_FCTL_CLEAR_FUNC) { + return sch_handle_clear_func_passthrough(sch); + } else if (schib->scsw.ctrl & SCSW_FCTL_HALT_FUNC) { + return sch_handle_halt_func_passthrough(sch); + } else if (schib->scsw.ctrl & SCSW_FCTL_START_FUNC) { + return sch_handle_start_func_passthrough(sch); + } + return IOINST_CC_EXPECTED; +} + +static IOInstEnding do_subchannel_work(SubchDev *sch) +{ + if (!sch->do_subchannel_work) { + return IOINST_CC_STATUS_PRESENT; + } + g_assert(sch->curr_status.scsw.ctrl & SCSW_CTRL_MASK_FCTL); + return sch->do_subchannel_work(sch); +} + +static void copy_pmcw_to_guest(PMCW *dest, const PMCW *src) +{ + int i; + + dest->intparm = cpu_to_be32(src->intparm); + dest->flags = cpu_to_be16(src->flags); + dest->devno = cpu_to_be16(src->devno); + dest->lpm = src->lpm; + dest->pnom = src->pnom; + dest->lpum = src->lpum; + dest->pim = src->pim; + dest->mbi = cpu_to_be16(src->mbi); + dest->pom = src->pom; + dest->pam = src->pam; + for (i = 0; i < ARRAY_SIZE(dest->chpid); i++) { + dest->chpid[i] = src->chpid[i]; + } + dest->chars = cpu_to_be32(src->chars); +} + +void copy_scsw_to_guest(SCSW *dest, const SCSW *src) +{ + dest->flags = cpu_to_be16(src->flags); + dest->ctrl = cpu_to_be16(src->ctrl); + dest->cpa = cpu_to_be32(src->cpa); + dest->dstat = src->dstat; + dest->cstat = src->cstat; + dest->count = cpu_to_be16(src->count); +} + +static void copy_schib_to_guest(SCHIB *dest, const SCHIB *src) +{ + int i; + /* + * We copy the PMCW and SCSW in and out of local variables to + * avoid taking the address of members of a packed struct. + */ + PMCW src_pmcw, dest_pmcw; + SCSW src_scsw, dest_scsw; + + src_pmcw = src->pmcw; + copy_pmcw_to_guest(&dest_pmcw, &src_pmcw); + dest->pmcw = dest_pmcw; + src_scsw = src->scsw; + copy_scsw_to_guest(&dest_scsw, &src_scsw); + dest->scsw = dest_scsw; + dest->mba = cpu_to_be64(src->mba); + for (i = 0; i < ARRAY_SIZE(dest->mda); i++) { + dest->mda[i] = src->mda[i]; + } +} + +void copy_esw_to_guest(ESW *dest, const ESW *src) +{ + dest->word0 = cpu_to_be32(src->word0); + dest->erw = cpu_to_be32(src->erw); + dest->word2 = cpu_to_be64(src->word2); + dest->word4 = cpu_to_be32(src->word4); +} + +IOInstEnding css_do_stsch(SubchDev *sch, SCHIB *schib) +{ + int ret; + + /* + * For some subchannels, we may want to update parts of + * the schib (e.g., update path masks from the host device + * for passthrough subchannels). + */ + ret = s390_ccw_store(sch); + + /* Use current status. */ + copy_schib_to_guest(schib, &sch->curr_status); + return ret; +} + +static void copy_pmcw_from_guest(PMCW *dest, const PMCW *src) +{ + int i; + + dest->intparm = be32_to_cpu(src->intparm); + dest->flags = be16_to_cpu(src->flags); + dest->devno = be16_to_cpu(src->devno); + dest->lpm = src->lpm; + dest->pnom = src->pnom; + dest->lpum = src->lpum; + dest->pim = src->pim; + dest->mbi = be16_to_cpu(src->mbi); + dest->pom = src->pom; + dest->pam = src->pam; + for (i = 0; i < ARRAY_SIZE(dest->chpid); i++) { + dest->chpid[i] = src->chpid[i]; + } + dest->chars = be32_to_cpu(src->chars); +} + +static void copy_scsw_from_guest(SCSW *dest, const SCSW *src) +{ + dest->flags = be16_to_cpu(src->flags); + dest->ctrl = be16_to_cpu(src->ctrl); + dest->cpa = be32_to_cpu(src->cpa); + dest->dstat = src->dstat; + dest->cstat = src->cstat; + dest->count = be16_to_cpu(src->count); +} + +static void copy_schib_from_guest(SCHIB *dest, const SCHIB *src) +{ + int i; + /* + * We copy the PMCW and SCSW in and out of local variables to + * avoid taking the address of members of a packed struct. + */ + PMCW src_pmcw, dest_pmcw; + SCSW src_scsw, dest_scsw; + + src_pmcw = src->pmcw; + copy_pmcw_from_guest(&dest_pmcw, &src_pmcw); + dest->pmcw = dest_pmcw; + src_scsw = src->scsw; + copy_scsw_from_guest(&dest_scsw, &src_scsw); + dest->scsw = dest_scsw; + dest->mba = be64_to_cpu(src->mba); + for (i = 0; i < ARRAY_SIZE(dest->mda); i++) { + dest->mda[i] = src->mda[i]; + } +} + +IOInstEnding css_do_msch(SubchDev *sch, const SCHIB *orig_schib) +{ + SCHIB *schib = &sch->curr_status; + uint16_t oldflags; + SCHIB schib_copy; + + if (!(schib->pmcw.flags & PMCW_FLAGS_MASK_DNV)) { + return IOINST_CC_EXPECTED; + } + + if (schib->scsw.ctrl & SCSW_STCTL_STATUS_PEND) { + return IOINST_CC_STATUS_PRESENT; + } + + if (schib->scsw.ctrl & + (SCSW_FCTL_START_FUNC|SCSW_FCTL_HALT_FUNC|SCSW_FCTL_CLEAR_FUNC)) { + return IOINST_CC_BUSY; + } + + copy_schib_from_guest(&schib_copy, orig_schib); + /* Only update the program-modifiable fields. */ + schib->pmcw.intparm = schib_copy.pmcw.intparm; + oldflags = schib->pmcw.flags; + schib->pmcw.flags &= ~(PMCW_FLAGS_MASK_ISC | PMCW_FLAGS_MASK_ENA | + PMCW_FLAGS_MASK_LM | PMCW_FLAGS_MASK_MME | + PMCW_FLAGS_MASK_MP); + schib->pmcw.flags |= schib_copy.pmcw.flags & + (PMCW_FLAGS_MASK_ISC | PMCW_FLAGS_MASK_ENA | + PMCW_FLAGS_MASK_LM | PMCW_FLAGS_MASK_MME | + PMCW_FLAGS_MASK_MP); + schib->pmcw.lpm = schib_copy.pmcw.lpm; + schib->pmcw.mbi = schib_copy.pmcw.mbi; + schib->pmcw.pom = schib_copy.pmcw.pom; + schib->pmcw.chars &= ~(PMCW_CHARS_MASK_MBFC | PMCW_CHARS_MASK_CSENSE); + schib->pmcw.chars |= schib_copy.pmcw.chars & + (PMCW_CHARS_MASK_MBFC | PMCW_CHARS_MASK_CSENSE); + schib->mba = schib_copy.mba; + + /* Has the channel been disabled? */ + if (sch->disable_cb && (oldflags & PMCW_FLAGS_MASK_ENA) != 0 + && (schib->pmcw.flags & PMCW_FLAGS_MASK_ENA) == 0) { + sch->disable_cb(sch); + } + return IOINST_CC_EXPECTED; +} + +IOInstEnding css_do_xsch(SubchDev *sch) +{ + SCHIB *schib = &sch->curr_status; + + if (~(schib->pmcw.flags) & (PMCW_FLAGS_MASK_DNV | PMCW_FLAGS_MASK_ENA)) { + return IOINST_CC_NOT_OPERATIONAL; + } + + if (schib->scsw.ctrl & SCSW_CTRL_MASK_STCTL) { + return IOINST_CC_STATUS_PRESENT; + } + + if (!(schib->scsw.ctrl & SCSW_CTRL_MASK_FCTL) || + ((schib->scsw.ctrl & SCSW_CTRL_MASK_FCTL) != SCSW_FCTL_START_FUNC) || + (!(schib->scsw.ctrl & + (SCSW_ACTL_RESUME_PEND | SCSW_ACTL_START_PEND | SCSW_ACTL_SUSP))) || + (schib->scsw.ctrl & SCSW_ACTL_SUBCH_ACTIVE)) { + return IOINST_CC_BUSY; + } + + /* Cancel the current operation. */ + schib->scsw.ctrl &= ~(SCSW_FCTL_START_FUNC | + SCSW_ACTL_RESUME_PEND | + SCSW_ACTL_START_PEND | + SCSW_ACTL_SUSP); + sch->channel_prog = 0x0; + sch->last_cmd_valid = false; + schib->scsw.dstat = 0; + schib->scsw.cstat = 0; + return IOINST_CC_EXPECTED; +} + +IOInstEnding css_do_csch(SubchDev *sch) +{ + SCHIB *schib = &sch->curr_status; + uint16_t old_scsw_ctrl; + IOInstEnding ccode; + + if (~(schib->pmcw.flags) & (PMCW_FLAGS_MASK_DNV | PMCW_FLAGS_MASK_ENA)) { + return IOINST_CC_NOT_OPERATIONAL; + } + + /* + * Save the current scsw.ctrl in case CSCH fails and we need + * to revert the scsw to the status quo ante. + */ + old_scsw_ctrl = schib->scsw.ctrl; + + /* Trigger the clear function. */ + schib->scsw.ctrl &= ~(SCSW_CTRL_MASK_FCTL | SCSW_CTRL_MASK_ACTL); + schib->scsw.ctrl |= SCSW_FCTL_CLEAR_FUNC | SCSW_ACTL_CLEAR_PEND; + + ccode = do_subchannel_work(sch); + + if (ccode != IOINST_CC_EXPECTED) { + schib->scsw.ctrl = old_scsw_ctrl; + } + + return ccode; +} + +IOInstEnding css_do_hsch(SubchDev *sch) +{ + SCHIB *schib = &sch->curr_status; + uint16_t old_scsw_ctrl; + IOInstEnding ccode; + + if (~(schib->pmcw.flags) & (PMCW_FLAGS_MASK_DNV | PMCW_FLAGS_MASK_ENA)) { + return IOINST_CC_NOT_OPERATIONAL; + } + + if (((schib->scsw.ctrl & SCSW_CTRL_MASK_STCTL) == SCSW_STCTL_STATUS_PEND) || + (schib->scsw.ctrl & (SCSW_STCTL_PRIMARY | + SCSW_STCTL_SECONDARY | + SCSW_STCTL_ALERT))) { + return IOINST_CC_STATUS_PRESENT; + } + + if (schib->scsw.ctrl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) { + return IOINST_CC_BUSY; + } + + /* + * Save the current scsw.ctrl in case HSCH fails and we need + * to revert the scsw to the status quo ante. + */ + old_scsw_ctrl = schib->scsw.ctrl; + + /* Trigger the halt function. */ + schib->scsw.ctrl |= SCSW_FCTL_HALT_FUNC; + schib->scsw.ctrl &= ~SCSW_FCTL_START_FUNC; + if (((schib->scsw.ctrl & SCSW_CTRL_MASK_ACTL) == + (SCSW_ACTL_SUBCH_ACTIVE | SCSW_ACTL_DEVICE_ACTIVE)) && + ((schib->scsw.ctrl & SCSW_CTRL_MASK_STCTL) == + SCSW_STCTL_INTERMEDIATE)) { + schib->scsw.ctrl &= ~SCSW_STCTL_STATUS_PEND; + } + schib->scsw.ctrl |= SCSW_ACTL_HALT_PEND; + + ccode = do_subchannel_work(sch); + + if (ccode != IOINST_CC_EXPECTED) { + schib->scsw.ctrl = old_scsw_ctrl; + } + + return ccode; +} + +static void css_update_chnmon(SubchDev *sch) +{ + if (!(sch->curr_status.pmcw.flags & PMCW_FLAGS_MASK_MME)) { + /* Not active. */ + return; + } + /* The counter is conveniently located at the beginning of the struct. */ + if (sch->curr_status.pmcw.chars & PMCW_CHARS_MASK_MBFC) { + /* Format 1, per-subchannel area. */ + uint32_t count; + + count = address_space_ldl(&address_space_memory, + sch->curr_status.mba, + MEMTXATTRS_UNSPECIFIED, + NULL); + count++; + address_space_stl(&address_space_memory, sch->curr_status.mba, count, + MEMTXATTRS_UNSPECIFIED, NULL); + } else { + /* Format 0, global area. */ + uint32_t offset; + uint16_t count; + + offset = sch->curr_status.pmcw.mbi << 5; + count = address_space_lduw(&address_space_memory, + channel_subsys.chnmon_area + offset, + MEMTXATTRS_UNSPECIFIED, + NULL); + count++; + address_space_stw(&address_space_memory, + channel_subsys.chnmon_area + offset, count, + MEMTXATTRS_UNSPECIFIED, NULL); + } +} + +IOInstEnding css_do_ssch(SubchDev *sch, ORB *orb) +{ + SCHIB *schib = &sch->curr_status; + uint16_t old_scsw_ctrl, old_scsw_flags; + IOInstEnding ccode; + + if (~(schib->pmcw.flags) & (PMCW_FLAGS_MASK_DNV | PMCW_FLAGS_MASK_ENA)) { + return IOINST_CC_NOT_OPERATIONAL; + } + + if (schib->scsw.ctrl & SCSW_STCTL_STATUS_PEND) { + return IOINST_CC_STATUS_PRESENT; + } + + if (schib->scsw.ctrl & (SCSW_FCTL_START_FUNC | + SCSW_FCTL_HALT_FUNC | + SCSW_FCTL_CLEAR_FUNC)) { + return IOINST_CC_BUSY; + } + + /* If monitoring is active, update counter. */ + if (channel_subsys.chnmon_active) { + css_update_chnmon(sch); + } + sch->orb = *orb; + sch->channel_prog = orb->cpa; + + /* + * Save the current scsw.ctrl and scsw.flags in case SSCH fails and we need + * to revert the scsw to the status quo ante. + */ + old_scsw_ctrl = schib->scsw.ctrl; + old_scsw_flags = schib->scsw.flags; + + /* Trigger the start function. */ + schib->scsw.ctrl |= (SCSW_FCTL_START_FUNC | SCSW_ACTL_START_PEND); + schib->scsw.flags &= ~SCSW_FLAGS_MASK_PNO; + + ccode = do_subchannel_work(sch); + + if (ccode != IOINST_CC_EXPECTED) { + schib->scsw.ctrl = old_scsw_ctrl; + schib->scsw.flags = old_scsw_flags; + } + + return ccode; +} + +static void copy_irb_to_guest(IRB *dest, const IRB *src, const PMCW *pmcw, + int *irb_len) +{ + int i; + uint16_t stctl = src->scsw.ctrl & SCSW_CTRL_MASK_STCTL; + uint16_t actl = src->scsw.ctrl & SCSW_CTRL_MASK_ACTL; + + copy_scsw_to_guest(&dest->scsw, &src->scsw); + + copy_esw_to_guest(&dest->esw, &src->esw); + + for (i = 0; i < ARRAY_SIZE(dest->ecw); i++) { + dest->ecw[i] = cpu_to_be32(src->ecw[i]); + } + *irb_len = sizeof(*dest) - sizeof(dest->emw); + + /* extended measurements enabled? */ + if ((src->scsw.flags & SCSW_FLAGS_MASK_ESWF) || + !(pmcw->flags & PMCW_FLAGS_MASK_TF) || + !(pmcw->chars & PMCW_CHARS_MASK_XMWME)) { + return; + } + /* extended measurements pending? */ + if (!(stctl & SCSW_STCTL_STATUS_PEND)) { + return; + } + if ((stctl & SCSW_STCTL_PRIMARY) || + (stctl == SCSW_STCTL_SECONDARY) || + ((stctl & SCSW_STCTL_INTERMEDIATE) && (actl & SCSW_ACTL_SUSP))) { + for (i = 0; i < ARRAY_SIZE(dest->emw); i++) { + dest->emw[i] = cpu_to_be32(src->emw[i]); + } + } + *irb_len = sizeof(*dest); +} + +static void build_irb_sense_data(SubchDev *sch, IRB *irb) +{ + int i; + + /* Attention: sense_data is already BE! */ + memcpy(irb->ecw, sch->sense_data, sizeof(sch->sense_data)); + for (i = 0; i < ARRAY_SIZE(irb->ecw); i++) { + irb->ecw[i] = be32_to_cpu(irb->ecw[i]); + } +} + +void build_irb_passthrough(SubchDev *sch, IRB *irb) +{ + /* Copy ESW from hardware */ + irb->esw = sch->esw; + + /* + * If (irb->esw.erw & ESW_ERW_SENSE) is true, then the contents + * of the ECW is sense data. If false, then it is model-dependent + * information. Either way, copy it into the IRB for the guest to + * read/decide what to do with. + */ + build_irb_sense_data(sch, irb); +} + +void build_irb_virtual(SubchDev *sch, IRB *irb) +{ + SCHIB *schib = &sch->curr_status; + uint16_t stctl = schib->scsw.ctrl & SCSW_CTRL_MASK_STCTL; + + if (stctl & SCSW_STCTL_STATUS_PEND) { + if (schib->scsw.cstat & (SCSW_CSTAT_DATA_CHECK | + SCSW_CSTAT_CHN_CTRL_CHK | + SCSW_CSTAT_INTF_CTRL_CHK)) { + irb->scsw.flags |= SCSW_FLAGS_MASK_ESWF; + irb->esw.word0 = 0x04804000; + } else { + irb->esw.word0 = 0x00800000; + } + /* If a unit check is pending, copy sense data. */ + if ((schib->scsw.dstat & SCSW_DSTAT_UNIT_CHECK) && + (schib->pmcw.chars & PMCW_CHARS_MASK_CSENSE)) { + irb->scsw.flags |= SCSW_FLAGS_MASK_ESWF | SCSW_FLAGS_MASK_ECTL; + build_irb_sense_data(sch, irb); + irb->esw.erw = ESW_ERW_SENSE | (sizeof(sch->sense_data) << 8); + } + } +} + +int css_do_tsch_get_irb(SubchDev *sch, IRB *target_irb, int *irb_len) +{ + SCHIB *schib = &sch->curr_status; + PMCW p; + uint16_t stctl; + IRB irb; + + if (~(schib->pmcw.flags) & (PMCW_FLAGS_MASK_DNV | PMCW_FLAGS_MASK_ENA)) { + return 3; + } + + stctl = schib->scsw.ctrl & SCSW_CTRL_MASK_STCTL; + + /* Prepare the irb for the guest. */ + memset(&irb, 0, sizeof(IRB)); + + /* Copy scsw from current status. */ + irb.scsw = schib->scsw; + + /* Build other IRB data, if necessary */ + if (sch->irb_cb) { + sch->irb_cb(sch, &irb); + } + + /* Store the irb to the guest. */ + p = schib->pmcw; + copy_irb_to_guest(target_irb, &irb, &p, irb_len); + + return ((stctl & SCSW_STCTL_STATUS_PEND) == 0); +} + +void css_do_tsch_update_subch(SubchDev *sch) +{ + SCHIB *schib = &sch->curr_status; + uint16_t stctl; + uint16_t fctl; + uint16_t actl; + + stctl = schib->scsw.ctrl & SCSW_CTRL_MASK_STCTL; + fctl = schib->scsw.ctrl & SCSW_CTRL_MASK_FCTL; + actl = schib->scsw.ctrl & SCSW_CTRL_MASK_ACTL; + + /* Clear conditions on subchannel, if applicable. */ + if (stctl & SCSW_STCTL_STATUS_PEND) { + schib->scsw.ctrl &= ~SCSW_CTRL_MASK_STCTL; + if ((stctl != (SCSW_STCTL_INTERMEDIATE | SCSW_STCTL_STATUS_PEND)) || + ((fctl & SCSW_FCTL_HALT_FUNC) && + (actl & SCSW_ACTL_SUSP))) { + schib->scsw.ctrl &= ~SCSW_CTRL_MASK_FCTL; + } + if (stctl != (SCSW_STCTL_INTERMEDIATE | SCSW_STCTL_STATUS_PEND)) { + schib->scsw.flags &= ~SCSW_FLAGS_MASK_PNO; + schib->scsw.ctrl &= ~(SCSW_ACTL_RESUME_PEND | + SCSW_ACTL_START_PEND | + SCSW_ACTL_HALT_PEND | + SCSW_ACTL_CLEAR_PEND | + SCSW_ACTL_SUSP); + } else { + if ((actl & SCSW_ACTL_SUSP) && + (fctl & SCSW_FCTL_START_FUNC)) { + schib->scsw.flags &= ~SCSW_FLAGS_MASK_PNO; + if (fctl & SCSW_FCTL_HALT_FUNC) { + schib->scsw.ctrl &= ~(SCSW_ACTL_RESUME_PEND | + SCSW_ACTL_START_PEND | + SCSW_ACTL_HALT_PEND | + SCSW_ACTL_CLEAR_PEND | + SCSW_ACTL_SUSP); + } else { + schib->scsw.ctrl &= ~SCSW_ACTL_RESUME_PEND; + } + } + } + /* Clear pending sense data. */ + if (schib->pmcw.chars & PMCW_CHARS_MASK_CSENSE) { + memset(sch->sense_data, 0 , sizeof(sch->sense_data)); + } + } +} + +static void copy_crw_to_guest(CRW *dest, const CRW *src) +{ + dest->flags = cpu_to_be16(src->flags); + dest->rsid = cpu_to_be16(src->rsid); +} + +int css_do_stcrw(CRW *crw) +{ + CrwContainer *crw_cont; + int ret; + + crw_cont = QTAILQ_FIRST(&channel_subsys.pending_crws); + if (crw_cont) { + QTAILQ_REMOVE(&channel_subsys.pending_crws, crw_cont, sibling); + copy_crw_to_guest(crw, &crw_cont->crw); + g_free(crw_cont); + ret = 0; + } else { + /* List was empty, turn crw machine checks on again. */ + memset(crw, 0, sizeof(*crw)); + channel_subsys.do_crw_mchk = true; + ret = 1; + } + + return ret; +} + +static void copy_crw_from_guest(CRW *dest, const CRW *src) +{ + dest->flags = be16_to_cpu(src->flags); + dest->rsid = be16_to_cpu(src->rsid); +} + +void css_undo_stcrw(CRW *crw) +{ + CrwContainer *crw_cont; + + crw_cont = g_try_new0(CrwContainer, 1); + if (!crw_cont) { + channel_subsys.crws_lost = true; + return; + } + copy_crw_from_guest(&crw_cont->crw, crw); + + QTAILQ_INSERT_HEAD(&channel_subsys.pending_crws, crw_cont, sibling); +} + +int css_collect_chp_desc(int m, uint8_t cssid, uint8_t f_chpid, uint8_t l_chpid, + int rfmt, void *buf) +{ + int i, desc_size; + uint32_t words[8]; + uint32_t chpid_type_word; + CssImage *css; + + if (!m && !cssid) { + css = channel_subsys.css[channel_subsys.default_cssid]; + } else { + css = channel_subsys.css[cssid]; + } + if (!css) { + return 0; + } + desc_size = 0; + for (i = f_chpid; i <= l_chpid; i++) { + if (css->chpids[i].in_use) { + chpid_type_word = 0x80000000 | (css->chpids[i].type << 8) | i; + if (rfmt == 0) { + words[0] = cpu_to_be32(chpid_type_word); + words[1] = 0; + memcpy(buf + desc_size, words, 8); + desc_size += 8; + } else if (rfmt == 1) { + words[0] = cpu_to_be32(chpid_type_word); + words[1] = 0; + words[2] = 0; + words[3] = 0; + words[4] = 0; + words[5] = 0; + words[6] = 0; + words[7] = 0; + memcpy(buf + desc_size, words, 32); + desc_size += 32; + } + } + } + return desc_size; +} + +void css_do_schm(uint8_t mbk, int update, int dct, uint64_t mbo) +{ + /* dct is currently ignored (not really meaningful for our devices) */ + /* TODO: Don't ignore mbk. */ + if (update && !channel_subsys.chnmon_active) { + /* Enable measuring. */ + channel_subsys.chnmon_area = mbo; + channel_subsys.chnmon_active = true; + } + if (!update && channel_subsys.chnmon_active) { + /* Disable measuring. */ + channel_subsys.chnmon_area = 0; + channel_subsys.chnmon_active = false; + } +} + +IOInstEnding css_do_rsch(SubchDev *sch) +{ + SCHIB *schib = &sch->curr_status; + + if (~(schib->pmcw.flags) & (PMCW_FLAGS_MASK_DNV | PMCW_FLAGS_MASK_ENA)) { + return IOINST_CC_NOT_OPERATIONAL; + } + + if (schib->scsw.ctrl & SCSW_STCTL_STATUS_PEND) { + return IOINST_CC_STATUS_PRESENT; + } + + if (((schib->scsw.ctrl & SCSW_CTRL_MASK_FCTL) != SCSW_FCTL_START_FUNC) || + (schib->scsw.ctrl & SCSW_ACTL_RESUME_PEND) || + (!(schib->scsw.ctrl & SCSW_ACTL_SUSP))) { + return IOINST_CC_BUSY; + } + + /* If monitoring is active, update counter. */ + if (channel_subsys.chnmon_active) { + css_update_chnmon(sch); + } + + schib->scsw.ctrl |= SCSW_ACTL_RESUME_PEND; + return do_subchannel_work(sch); +} + +int css_do_rchp(uint8_t cssid, uint8_t chpid) +{ + uint8_t real_cssid; + + if (cssid > channel_subsys.max_cssid) { + return -EINVAL; + } + if (channel_subsys.max_cssid == 0) { + real_cssid = channel_subsys.default_cssid; + } else { + real_cssid = cssid; + } + if (!channel_subsys.css[real_cssid]) { + return -EINVAL; + } + + if (!channel_subsys.css[real_cssid]->chpids[chpid].in_use) { + return -ENODEV; + } + + if (!channel_subsys.css[real_cssid]->chpids[chpid].is_virtual) { + fprintf(stderr, + "rchp unsupported for non-virtual chpid %x.%02x!\n", + real_cssid, chpid); + return -ENODEV; + } + + /* We don't really use a channel path, so we're done here. */ + css_queue_crw(CRW_RSC_CHP, CRW_ERC_INIT, 1, + channel_subsys.max_cssid > 0 ? 1 : 0, chpid); + if (channel_subsys.max_cssid > 0) { + css_queue_crw(CRW_RSC_CHP, CRW_ERC_INIT, 1, 0, real_cssid << 8); + } + return 0; +} + +bool css_schid_final(int m, uint8_t cssid, uint8_t ssid, uint16_t schid) +{ + SubchSet *set; + uint8_t real_cssid; + + real_cssid = (!m && (cssid == 0)) ? channel_subsys.default_cssid : cssid; + if (ssid > MAX_SSID || + !channel_subsys.css[real_cssid] || + !channel_subsys.css[real_cssid]->sch_set[ssid]) { + return true; + } + set = channel_subsys.css[real_cssid]->sch_set[ssid]; + return schid > find_last_bit(set->schids_used, + (MAX_SCHID + 1) / sizeof(unsigned long)); +} + +unsigned int css_find_free_chpid(uint8_t cssid) +{ + CssImage *css = channel_subsys.css[cssid]; + unsigned int chpid; + + if (!css) { + return MAX_CHPID + 1; + } + + for (chpid = 0; chpid <= MAX_CHPID; chpid++) { + /* skip reserved chpid */ + if (chpid == VIRTIO_CCW_CHPID) { + continue; + } + if (!css->chpids[chpid].in_use) { + return chpid; + } + } + return MAX_CHPID + 1; +} + +static int css_add_chpid(uint8_t cssid, uint8_t chpid, uint8_t type, + bool is_virt) +{ + CssImage *css; + + trace_css_chpid_add(cssid, chpid, type); + css = channel_subsys.css[cssid]; + if (!css) { + return -EINVAL; + } + if (css->chpids[chpid].in_use) { + return -EEXIST; + } + css->chpids[chpid].in_use = 1; + css->chpids[chpid].type = type; + css->chpids[chpid].is_virtual = is_virt; + + css_generate_chp_crws(cssid, chpid); + + return 0; +} + +void css_sch_build_virtual_schib(SubchDev *sch, uint8_t chpid, uint8_t type) +{ + SCHIB *schib = &sch->curr_status; + int i; + CssImage *css = channel_subsys.css[sch->cssid]; + + assert(css != NULL); + memset(&schib->pmcw, 0, sizeof(PMCW)); + schib->pmcw.flags |= PMCW_FLAGS_MASK_DNV; + schib->pmcw.devno = sch->devno; + /* single path */ + schib->pmcw.pim = 0x80; + schib->pmcw.pom = 0xff; + schib->pmcw.pam = 0x80; + schib->pmcw.chpid[0] = chpid; + if (!css->chpids[chpid].in_use) { + css_add_chpid(sch->cssid, chpid, type, true); + } + + memset(&schib->scsw, 0, sizeof(SCSW)); + schib->mba = 0; + for (i = 0; i < ARRAY_SIZE(schib->mda); i++) { + schib->mda[i] = 0; + } +} + +SubchDev *css_find_subch(uint8_t m, uint8_t cssid, uint8_t ssid, uint16_t schid) +{ + uint8_t real_cssid; + + real_cssid = (!m && (cssid == 0)) ? channel_subsys.default_cssid : cssid; + + if (!channel_subsys.css[real_cssid]) { + return NULL; + } + + if (!channel_subsys.css[real_cssid]->sch_set[ssid]) { + return NULL; + } + + return channel_subsys.css[real_cssid]->sch_set[ssid]->sch[schid]; +} + +/** + * Return free device number in subchannel set. + * + * Return index of the first free device number in the subchannel set + * identified by @p cssid and @p ssid, beginning the search at @p + * start and wrapping around at MAX_DEVNO. Return a value exceeding + * MAX_SCHID if there are no free device numbers in the subchannel + * set. + */ +static uint32_t css_find_free_devno(uint8_t cssid, uint8_t ssid, + uint16_t start) +{ + uint32_t round; + + for (round = 0; round <= MAX_DEVNO; round++) { + uint16_t devno = (start + round) % MAX_DEVNO; + + if (!css_devno_used(cssid, ssid, devno)) { + return devno; + } + } + return MAX_DEVNO + 1; +} + +/** + * Return first free subchannel (id) in subchannel set. + * + * Return index of the first free subchannel in the subchannel set + * identified by @p cssid and @p ssid, if there is any. Return a value + * exceeding MAX_SCHID if there are no free subchannels in the + * subchannel set. + */ +static uint32_t css_find_free_subch(uint8_t cssid, uint8_t ssid) +{ + uint32_t schid; + + for (schid = 0; schid <= MAX_SCHID; schid++) { + if (!css_find_subch(1, cssid, ssid, schid)) { + return schid; + } + } + return MAX_SCHID + 1; +} + +/** + * Return first free subchannel (id) in subchannel set for a device number + * + * Verify the device number @p devno is not used yet in the subchannel + * set identified by @p cssid and @p ssid. Set @p schid to the index + * of the first free subchannel in the subchannel set, if there is + * any. Return true if everything succeeded and false otherwise. + */ +static bool css_find_free_subch_for_devno(uint8_t cssid, uint8_t ssid, + uint16_t devno, uint16_t *schid, + Error **errp) +{ + uint32_t free_schid; + + assert(schid); + if (css_devno_used(cssid, ssid, devno)) { + error_setg(errp, "Device %x.%x.%04x already exists", + cssid, ssid, devno); + return false; + } + free_schid = css_find_free_subch(cssid, ssid); + if (free_schid > MAX_SCHID) { + error_setg(errp, "No free subchannel found for %x.%x.%04x", + cssid, ssid, devno); + return false; + } + *schid = free_schid; + return true; +} + +/** + * Return first free subchannel (id) and device number + * + * Locate the first free subchannel and first free device number in + * any of the subchannel sets of the channel subsystem identified by + * @p cssid. Return false if no free subchannel / device number could + * be found. Otherwise set @p ssid, @p devno and @p schid to identify + * the available subchannel and device number and return true. + * + * May modify @p ssid, @p devno and / or @p schid even if no free + * subchannel / device number could be found. + */ +static bool css_find_free_subch_and_devno(uint8_t cssid, uint8_t *ssid, + uint16_t *devno, uint16_t *schid, + Error **errp) +{ + uint32_t free_schid, free_devno; + + assert(ssid && devno && schid); + for (*ssid = 0; *ssid <= MAX_SSID; (*ssid)++) { + free_schid = css_find_free_subch(cssid, *ssid); + if (free_schid > MAX_SCHID) { + continue; + } + free_devno = css_find_free_devno(cssid, *ssid, free_schid); + if (free_devno > MAX_DEVNO) { + continue; + } + *schid = free_schid; + *devno = free_devno; + return true; + } + error_setg(errp, "Virtual channel subsystem is full!"); + return false; +} + +bool css_subch_visible(SubchDev *sch) +{ + if (sch->ssid > channel_subsys.max_ssid) { + return false; + } + + if (sch->cssid != channel_subsys.default_cssid) { + return (channel_subsys.max_cssid > 0); + } + + return true; +} + +bool css_present(uint8_t cssid) +{ + return (channel_subsys.css[cssid] != NULL); +} + +bool css_devno_used(uint8_t cssid, uint8_t ssid, uint16_t devno) +{ + if (!channel_subsys.css[cssid]) { + return false; + } + if (!channel_subsys.css[cssid]->sch_set[ssid]) { + return false; + } + + return !!test_bit(devno, + channel_subsys.css[cssid]->sch_set[ssid]->devnos_used); +} + +void css_subch_assign(uint8_t cssid, uint8_t ssid, uint16_t schid, + uint16_t devno, SubchDev *sch) +{ + CssImage *css; + SubchSet *s_set; + + trace_css_assign_subch(sch ? "assign" : "deassign", cssid, ssid, schid, + devno); + if (!channel_subsys.css[cssid]) { + fprintf(stderr, + "Suspicious call to %s (%x.%x.%04x) for non-existing css!\n", + __func__, cssid, ssid, schid); + return; + } + css = channel_subsys.css[cssid]; + + if (!css->sch_set[ssid]) { + css->sch_set[ssid] = g_new0(SubchSet, 1); + } + s_set = css->sch_set[ssid]; + + s_set->sch[schid] = sch; + if (sch) { + set_bit(schid, s_set->schids_used); + set_bit(devno, s_set->devnos_used); + } else { + clear_bit(schid, s_set->schids_used); + clear_bit(devno, s_set->devnos_used); + } +} + +void css_crw_add_to_queue(CRW crw) +{ + CrwContainer *crw_cont; + + trace_css_crw((crw.flags & CRW_FLAGS_MASK_RSC) >> 8, + crw.flags & CRW_FLAGS_MASK_ERC, + crw.rsid, + (crw.flags & CRW_FLAGS_MASK_C) ? "(chained)" : ""); + + /* TODO: Maybe use a static crw pool? */ + crw_cont = g_try_new0(CrwContainer, 1); + if (!crw_cont) { + channel_subsys.crws_lost = true; + return; + } + + crw_cont->crw = crw; + + QTAILQ_INSERT_TAIL(&channel_subsys.pending_crws, crw_cont, sibling); + + if (channel_subsys.do_crw_mchk) { + channel_subsys.do_crw_mchk = false; + /* Inject crw pending machine check. */ + s390_crw_mchk(); + } +} + +void css_queue_crw(uint8_t rsc, uint8_t erc, int solicited, + int chain, uint16_t rsid) +{ + CRW crw; + + crw.flags = (rsc << 8) | erc; + if (solicited) { + crw.flags |= CRW_FLAGS_MASK_S; + } + if (chain) { + crw.flags |= CRW_FLAGS_MASK_C; + } + crw.rsid = rsid; + if (channel_subsys.crws_lost) { + crw.flags |= CRW_FLAGS_MASK_R; + channel_subsys.crws_lost = false; + } + + css_crw_add_to_queue(crw); +} + +void css_generate_sch_crws(uint8_t cssid, uint8_t ssid, uint16_t schid, + int hotplugged, int add) +{ + uint8_t guest_cssid; + bool chain_crw; + + if (add && !hotplugged) { + return; + } + if (channel_subsys.max_cssid == 0) { + /* Default cssid shows up as 0. */ + guest_cssid = (cssid == channel_subsys.default_cssid) ? 0 : cssid; + } else { + /* Show real cssid to the guest. */ + guest_cssid = cssid; + } + /* + * Only notify for higher subchannel sets/channel subsystems if the + * guest has enabled it. + */ + if ((ssid > channel_subsys.max_ssid) || + (guest_cssid > channel_subsys.max_cssid) || + ((channel_subsys.max_cssid == 0) && + (cssid != channel_subsys.default_cssid))) { + return; + } + chain_crw = (channel_subsys.max_ssid > 0) || + (channel_subsys.max_cssid > 0); + css_queue_crw(CRW_RSC_SUBCH, CRW_ERC_IPI, 0, chain_crw ? 1 : 0, schid); + if (chain_crw) { + css_queue_crw(CRW_RSC_SUBCH, CRW_ERC_IPI, 0, 0, + (guest_cssid << 8) | (ssid << 4)); + } + /* RW_ERC_IPI --> clear pending interrupts */ + css_clear_io_interrupt(css_do_build_subchannel_id(cssid, ssid), schid); +} + +void css_generate_chp_crws(uint8_t cssid, uint8_t chpid) +{ + /* TODO */ +} + +void css_generate_css_crws(uint8_t cssid) +{ + if (!channel_subsys.sei_pending) { + css_queue_crw(CRW_RSC_CSS, CRW_ERC_EVENT, 0, 0, cssid); + } + channel_subsys.sei_pending = true; +} + +void css_clear_sei_pending(void) +{ + channel_subsys.sei_pending = false; +} + +int css_enable_mcsse(void) +{ + trace_css_enable_facility("mcsse"); + channel_subsys.max_cssid = MAX_CSSID; + return 0; +} + +int css_enable_mss(void) +{ + trace_css_enable_facility("mss"); + channel_subsys.max_ssid = MAX_SSID; + return 0; +} + +void css_reset_sch(SubchDev *sch) +{ + SCHIB *schib = &sch->curr_status; + + if ((schib->pmcw.flags & PMCW_FLAGS_MASK_ENA) != 0 && sch->disable_cb) { + sch->disable_cb(sch); + } + + schib->pmcw.intparm = 0; + schib->pmcw.flags &= ~(PMCW_FLAGS_MASK_ISC | PMCW_FLAGS_MASK_ENA | + PMCW_FLAGS_MASK_LM | PMCW_FLAGS_MASK_MME | + PMCW_FLAGS_MASK_MP | PMCW_FLAGS_MASK_TF); + schib->pmcw.flags |= PMCW_FLAGS_MASK_DNV; + schib->pmcw.devno = sch->devno; + schib->pmcw.pim = 0x80; + schib->pmcw.lpm = schib->pmcw.pim; + schib->pmcw.pnom = 0; + schib->pmcw.lpum = 0; + schib->pmcw.mbi = 0; + schib->pmcw.pom = 0xff; + schib->pmcw.pam = 0x80; + schib->pmcw.chars &= ~(PMCW_CHARS_MASK_MBFC | PMCW_CHARS_MASK_XMWME | + PMCW_CHARS_MASK_CSENSE); + + memset(&schib->scsw, 0, sizeof(schib->scsw)); + schib->mba = 0; + + sch->channel_prog = 0x0; + sch->last_cmd_valid = false; + sch->thinint_active = false; +} + +void css_reset(void) +{ + CrwContainer *crw_cont; + + /* Clean up monitoring. */ + channel_subsys.chnmon_active = false; + channel_subsys.chnmon_area = 0; + + /* Clear pending CRWs. */ + while ((crw_cont = QTAILQ_FIRST(&channel_subsys.pending_crws))) { + QTAILQ_REMOVE(&channel_subsys.pending_crws, crw_cont, sibling); + g_free(crw_cont); + } + channel_subsys.sei_pending = false; + channel_subsys.do_crw_mchk = true; + channel_subsys.crws_lost = false; + + /* Reset maximum ids. */ + channel_subsys.max_cssid = 0; + channel_subsys.max_ssid = 0; +} + +static void get_css_devid(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + Property *prop = opaque; + CssDevId *dev_id = object_field_prop_ptr(obj, prop); + char buffer[] = "xx.x.xxxx"; + char *p = buffer; + int r; + + if (dev_id->valid) { + + r = snprintf(buffer, sizeof(buffer), "%02x.%1x.%04x", dev_id->cssid, + dev_id->ssid, dev_id->devid); + assert(r == sizeof(buffer) - 1); + + /* drop leading zero */ + if (dev_id->cssid <= 0xf) { + p++; + } + } else { + snprintf(buffer, sizeof(buffer), "<unset>"); + } + + visit_type_str(v, name, &p, errp); +} + +/* + * parse <cssid>.<ssid>.<devid> and assert valid range for cssid/ssid + */ +static void set_css_devid(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + Property *prop = opaque; + CssDevId *dev_id = object_field_prop_ptr(obj, prop); + char *str; + int num, n1, n2; + unsigned int cssid, ssid, devid; + + if (!visit_type_str(v, name, &str, errp)) { + return; + } + + num = sscanf(str, "%2x.%1x%n.%4x%n", &cssid, &ssid, &n1, &devid, &n2); + if (num != 3 || (n2 - n1) != 5 || strlen(str) != n2) { + error_set_from_qdev_prop_error(errp, EINVAL, obj, name, str); + goto out; + } + if ((cssid > MAX_CSSID) || (ssid > MAX_SSID)) { + error_setg(errp, "Invalid cssid or ssid: cssid %x, ssid %x", + cssid, ssid); + goto out; + } + + dev_id->cssid = cssid; + dev_id->ssid = ssid; + dev_id->devid = devid; + dev_id->valid = true; + +out: + g_free(str); +} + +const PropertyInfo css_devid_propinfo = { + .name = "str", + .description = "Identifier of an I/O device in the channel " + "subsystem, example: fe.1.23ab", + .get = get_css_devid, + .set = set_css_devid, +}; + +const PropertyInfo css_devid_ro_propinfo = { + .name = "str", + .description = "Read-only identifier of an I/O device in the channel " + "subsystem, example: fe.1.23ab", + .get = get_css_devid, +}; + +SubchDev *css_create_sch(CssDevId bus_id, Error **errp) +{ + uint16_t schid = 0; + SubchDev *sch; + + if (bus_id.valid) { + if (!channel_subsys.css[bus_id.cssid]) { + css_create_css_image(bus_id.cssid, false); + } + + if (!css_find_free_subch_for_devno(bus_id.cssid, bus_id.ssid, + bus_id.devid, &schid, errp)) { + return NULL; + } + } else { + for (bus_id.cssid = channel_subsys.default_cssid;;) { + if (!channel_subsys.css[bus_id.cssid]) { + css_create_css_image(bus_id.cssid, false); + } + + if (css_find_free_subch_and_devno(bus_id.cssid, &bus_id.ssid, + &bus_id.devid, &schid, + NULL)) { + break; + } + bus_id.cssid = (bus_id.cssid + 1) % MAX_CSSID; + if (bus_id.cssid == channel_subsys.default_cssid) { + error_setg(errp, "Virtual channel subsystem is full!"); + return NULL; + } + } + } + + sch = g_new0(SubchDev, 1); + sch->cssid = bus_id.cssid; + sch->ssid = bus_id.ssid; + sch->devno = bus_id.devid; + sch->schid = schid; + css_subch_assign(sch->cssid, sch->ssid, schid, sch->devno, sch); + return sch; +} + +static int css_sch_get_chpids(SubchDev *sch, CssDevId *dev_id) +{ + char *fid_path; + FILE *fd; + uint32_t chpid[8]; + int i; + SCHIB *schib = &sch->curr_status; + + fid_path = g_strdup_printf("/sys/bus/css/devices/%x.%x.%04x/chpids", + dev_id->cssid, dev_id->ssid, dev_id->devid); + fd = fopen(fid_path, "r"); + if (fd == NULL) { + error_report("%s: open %s failed", __func__, fid_path); + g_free(fid_path); + return -EINVAL; + } + + if (fscanf(fd, "%x %x %x %x %x %x %x %x", + &chpid[0], &chpid[1], &chpid[2], &chpid[3], + &chpid[4], &chpid[5], &chpid[6], &chpid[7]) != 8) { + fclose(fd); + g_free(fid_path); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(schib->pmcw.chpid); i++) { + schib->pmcw.chpid[i] = chpid[i]; + } + + fclose(fd); + g_free(fid_path); + + return 0; +} + +static int css_sch_get_path_masks(SubchDev *sch, CssDevId *dev_id) +{ + char *fid_path; + FILE *fd; + uint32_t pim, pam, pom; + SCHIB *schib = &sch->curr_status; + + fid_path = g_strdup_printf("/sys/bus/css/devices/%x.%x.%04x/pimpampom", + dev_id->cssid, dev_id->ssid, dev_id->devid); + fd = fopen(fid_path, "r"); + if (fd == NULL) { + error_report("%s: open %s failed", __func__, fid_path); + g_free(fid_path); + return -EINVAL; + } + + if (fscanf(fd, "%x %x %x", &pim, &pam, &pom) != 3) { + fclose(fd); + g_free(fid_path); + return -EINVAL; + } + + schib->pmcw.pim = pim; + schib->pmcw.pam = pam; + schib->pmcw.pom = pom; + fclose(fd); + g_free(fid_path); + + return 0; +} + +static int css_sch_get_chpid_type(uint8_t chpid, uint32_t *type, + CssDevId *dev_id) +{ + char *fid_path; + FILE *fd; + + fid_path = g_strdup_printf("/sys/devices/css%x/chp0.%02x/type", + dev_id->cssid, chpid); + fd = fopen(fid_path, "r"); + if (fd == NULL) { + error_report("%s: open %s failed", __func__, fid_path); + g_free(fid_path); + return -EINVAL; + } + + if (fscanf(fd, "%x", type) != 1) { + fclose(fd); + g_free(fid_path); + return -EINVAL; + } + + fclose(fd); + g_free(fid_path); + + return 0; +} + +/* + * We currently retrieve the real device information from sysfs to build the + * guest subchannel information block without considering the migration feature. + * We need to revisit this problem when we want to add migration support. + */ +int css_sch_build_schib(SubchDev *sch, CssDevId *dev_id) +{ + CssImage *css = channel_subsys.css[sch->cssid]; + SCHIB *schib = &sch->curr_status; + uint32_t type; + int i, ret; + + assert(css != NULL); + memset(&schib->pmcw, 0, sizeof(PMCW)); + schib->pmcw.flags |= PMCW_FLAGS_MASK_DNV; + /* We are dealing with I/O subchannels only. */ + schib->pmcw.devno = sch->devno; + + /* Grab path mask from sysfs. */ + ret = css_sch_get_path_masks(sch, dev_id); + if (ret) { + return ret; + } + + /* Grab chpids from sysfs. */ + ret = css_sch_get_chpids(sch, dev_id); + if (ret) { + return ret; + } + + /* Build chpid type. */ + for (i = 0; i < ARRAY_SIZE(schib->pmcw.chpid); i++) { + if (schib->pmcw.chpid[i] && !css->chpids[schib->pmcw.chpid[i]].in_use) { + ret = css_sch_get_chpid_type(schib->pmcw.chpid[i], &type, dev_id); + if (ret) { + return ret; + } + css_add_chpid(sch->cssid, schib->pmcw.chpid[i], type, false); + } + } + + memset(&schib->scsw, 0, sizeof(SCSW)); + schib->mba = 0; + for (i = 0; i < ARRAY_SIZE(schib->mda); i++) { + schib->mda[i] = 0; + } + + return 0; +} |