diff options
Diffstat (limited to 'qemu/hw/s390x/event-facility.c')
-rw-r--r-- | qemu/hw/s390x/event-facility.c | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/qemu/hw/s390x/event-facility.c b/qemu/hw/s390x/event-facility.c new file mode 100644 index 000000000..0c700effb --- /dev/null +++ b/qemu/hw/s390x/event-facility.c @@ -0,0 +1,430 @@ +/* + * SCLP + * Event Facility + * handles SCLP event types + * - Signal Quiesce - system power down + * - ASCII Console Data - VT220 read and write + * + * Copyright IBM, Corp. 2012 + * + * Authors: + * Heinz Graalfs <graalfs@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 "sysemu/sysemu.h" + +#include "hw/s390x/sclp.h" +#include "hw/s390x/event-facility.h" + +typedef struct SCLPEventsBus { + BusState qbus; +} SCLPEventsBus; + +struct SCLPEventFacility { + SysBusDevice parent_obj; + SCLPEventsBus sbus; + /* guest' receive mask */ + unsigned int receive_mask; +}; + +static SCLPEvent cpu_hotplug; + +/* return true if any child has event pending set */ +static bool event_pending(SCLPEventFacility *ef) +{ + BusChild *kid; + SCLPEvent *event; + SCLPEventClass *event_class; + + QTAILQ_FOREACH(kid, &ef->sbus.qbus.children, sibling) { + DeviceState *qdev = kid->child; + event = DO_UPCAST(SCLPEvent, qdev, qdev); + event_class = SCLP_EVENT_GET_CLASS(event); + if (event->event_pending && + event_class->get_send_mask() & ef->receive_mask) { + return true; + } + } + return false; +} + +static unsigned int get_host_send_mask(SCLPEventFacility *ef) +{ + unsigned int mask; + BusChild *kid; + SCLPEventClass *child; + + mask = 0; + + QTAILQ_FOREACH(kid, &ef->sbus.qbus.children, sibling) { + DeviceState *qdev = kid->child; + child = SCLP_EVENT_GET_CLASS((SCLPEvent *) qdev); + mask |= child->get_send_mask(); + } + return mask; +} + +static unsigned int get_host_receive_mask(SCLPEventFacility *ef) +{ + unsigned int mask; + BusChild *kid; + SCLPEventClass *child; + + mask = 0; + + QTAILQ_FOREACH(kid, &ef->sbus.qbus.children, sibling) { + DeviceState *qdev = kid->child; + child = SCLP_EVENT_GET_CLASS((SCLPEvent *) qdev); + mask |= child->get_receive_mask(); + } + return mask; +} + +static uint16_t write_event_length_check(SCCB *sccb) +{ + int slen; + unsigned elen = 0; + EventBufferHeader *event; + WriteEventData *wed = (WriteEventData *) sccb; + + event = (EventBufferHeader *) &wed->ebh; + for (slen = sccb_data_len(sccb); slen > 0; slen -= elen) { + elen = be16_to_cpu(event->length); + if (elen < sizeof(*event) || elen > slen) { + return SCLP_RC_EVENT_BUFFER_SYNTAX_ERROR; + } + event = (void *) event + elen; + } + if (slen) { + return SCLP_RC_INCONSISTENT_LENGTHS; + } + return SCLP_RC_NORMAL_COMPLETION; +} + +static uint16_t handle_write_event_buf(SCLPEventFacility *ef, + EventBufferHeader *event_buf, SCCB *sccb) +{ + uint16_t rc; + BusChild *kid; + SCLPEvent *event; + SCLPEventClass *ec; + + rc = SCLP_RC_INVALID_FUNCTION; + + QTAILQ_FOREACH(kid, &ef->sbus.qbus.children, sibling) { + DeviceState *qdev = kid->child; + event = (SCLPEvent *) qdev; + ec = SCLP_EVENT_GET_CLASS(event); + + if (ec->write_event_data && + ec->can_handle_event(event_buf->type)) { + rc = ec->write_event_data(event, event_buf); + break; + } + } + return rc; +} + +static uint16_t handle_sccb_write_events(SCLPEventFacility *ef, SCCB *sccb) +{ + uint16_t rc; + int slen; + unsigned elen = 0; + EventBufferHeader *event_buf; + WriteEventData *wed = (WriteEventData *) sccb; + + event_buf = &wed->ebh; + rc = SCLP_RC_NORMAL_COMPLETION; + + /* loop over all contained event buffers */ + for (slen = sccb_data_len(sccb); slen > 0; slen -= elen) { + elen = be16_to_cpu(event_buf->length); + + /* in case of a previous error mark all trailing buffers + * as not accepted */ + if (rc != SCLP_RC_NORMAL_COMPLETION) { + event_buf->flags &= ~(SCLP_EVENT_BUFFER_ACCEPTED); + } else { + rc = handle_write_event_buf(ef, event_buf, sccb); + } + event_buf = (void *) event_buf + elen; + } + return rc; +} + +static void write_event_data(SCLPEventFacility *ef, SCCB *sccb) +{ + if (sccb->h.function_code != SCLP_FC_NORMAL_WRITE) { + sccb->h.response_code = cpu_to_be16(SCLP_RC_INVALID_FUNCTION); + goto out; + } + if (be16_to_cpu(sccb->h.length) < 8) { + sccb->h.response_code = cpu_to_be16(SCLP_RC_INSUFFICIENT_SCCB_LENGTH); + goto out; + } + /* first do a sanity check of the write events */ + sccb->h.response_code = cpu_to_be16(write_event_length_check(sccb)); + + /* if no early error, then execute */ + if (sccb->h.response_code == be16_to_cpu(SCLP_RC_NORMAL_COMPLETION)) { + sccb->h.response_code = + cpu_to_be16(handle_sccb_write_events(ef, sccb)); + } + +out: + return; +} + +static uint16_t handle_sccb_read_events(SCLPEventFacility *ef, SCCB *sccb, + unsigned int mask) +{ + uint16_t rc; + int slen; + unsigned elen; + BusChild *kid; + SCLPEvent *event; + SCLPEventClass *ec; + EventBufferHeader *event_buf; + ReadEventData *red = (ReadEventData *) sccb; + + event_buf = &red->ebh; + event_buf->length = 0; + slen = sizeof(sccb->data); + + rc = SCLP_RC_NO_EVENT_BUFFERS_STORED; + + QTAILQ_FOREACH(kid, &ef->sbus.qbus.children, sibling) { + DeviceState *qdev = kid->child; + event = (SCLPEvent *) qdev; + ec = SCLP_EVENT_GET_CLASS(event); + + if (mask & ec->get_send_mask()) { + if (ec->read_event_data(event, event_buf, &slen)) { + elen = be16_to_cpu(event_buf->length); + event_buf = (EventBufferHeader *) ((char *)event_buf + elen); + rc = SCLP_RC_NORMAL_COMPLETION; + } + } + } + + if (sccb->h.control_mask[2] & SCLP_VARIABLE_LENGTH_RESPONSE) { + /* architecture suggests to reset variable-length-response bit */ + sccb->h.control_mask[2] &= ~SCLP_VARIABLE_LENGTH_RESPONSE; + /* with a new length value */ + sccb->h.length = cpu_to_be16(SCCB_SIZE - slen); + } + return rc; +} + +static void read_event_data(SCLPEventFacility *ef, SCCB *sccb) +{ + unsigned int sclp_active_selection_mask; + unsigned int sclp_cp_receive_mask; + + ReadEventData *red = (ReadEventData *) sccb; + + if (be16_to_cpu(sccb->h.length) != SCCB_SIZE) { + sccb->h.response_code = cpu_to_be16(SCLP_RC_INSUFFICIENT_SCCB_LENGTH); + goto out; + } + + sclp_cp_receive_mask = ef->receive_mask; + + /* get active selection mask */ + switch (sccb->h.function_code) { + case SCLP_UNCONDITIONAL_READ: + sclp_active_selection_mask = sclp_cp_receive_mask; + break; + case SCLP_SELECTIVE_READ: + if (!(sclp_cp_receive_mask & be32_to_cpu(red->mask))) { + sccb->h.response_code = + cpu_to_be16(SCLP_RC_INVALID_SELECTION_MASK); + goto out; + } + sclp_active_selection_mask = be32_to_cpu(red->mask); + break; + default: + sccb->h.response_code = cpu_to_be16(SCLP_RC_INVALID_FUNCTION); + goto out; + } + sccb->h.response_code = cpu_to_be16( + handle_sccb_read_events(ef, sccb, sclp_active_selection_mask)); + +out: + return; +} + +static void write_event_mask(SCLPEventFacility *ef, SCCB *sccb) +{ + WriteEventMask *we_mask = (WriteEventMask *) sccb; + + /* Attention: We assume that Linux uses 4-byte masks, what it actually + does. Architecture allows for masks of variable size, though */ + if (be16_to_cpu(we_mask->mask_length) != 4) { + sccb->h.response_code = cpu_to_be16(SCLP_RC_INVALID_MASK_LENGTH); + goto out; + } + + /* keep track of the guest's capability masks */ + ef->receive_mask = be32_to_cpu(we_mask->cp_receive_mask); + + /* return the SCLP's capability masks to the guest */ + we_mask->send_mask = cpu_to_be32(get_host_send_mask(ef)); + we_mask->receive_mask = cpu_to_be32(get_host_receive_mask(ef)); + + sccb->h.response_code = cpu_to_be16(SCLP_RC_NORMAL_COMPLETION); + +out: + return; +} + +/* qemu object creation and initialization functions */ + +#define TYPE_SCLP_EVENTS_BUS "s390-sclp-events-bus" + +static void sclp_events_bus_class_init(ObjectClass *klass, void *data) +{ +} + +static const TypeInfo sclp_events_bus_info = { + .name = TYPE_SCLP_EVENTS_BUS, + .parent = TYPE_BUS, + .class_init = sclp_events_bus_class_init, +}; + +static void command_handler(SCLPEventFacility *ef, SCCB *sccb, uint64_t code) +{ + switch (code & SCLP_CMD_CODE_MASK) { + case SCLP_CMD_READ_EVENT_DATA: + read_event_data(ef, sccb); + break; + case SCLP_CMD_WRITE_EVENT_DATA: + write_event_data(ef, sccb); + break; + case SCLP_CMD_WRITE_EVENT_MASK: + write_event_mask(ef, sccb); + break; + default: + sccb->h.response_code = cpu_to_be16(SCLP_RC_INVALID_SCLP_COMMAND); + break; + } +} + +static const VMStateDescription vmstate_event_facility = { + .name = "vmstate-event-facility", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32(receive_mask, SCLPEventFacility), + VMSTATE_END_OF_LIST() + } +}; + +static int init_event_facility(SCLPEventFacility *event_facility) +{ + DeviceState *sdev = DEVICE(event_facility); + DeviceState *quiesce; + + /* Spawn a new bus for SCLP events */ + qbus_create_inplace(&event_facility->sbus, sizeof(event_facility->sbus), + TYPE_SCLP_EVENTS_BUS, sdev, NULL); + + quiesce = qdev_create(&event_facility->sbus.qbus, "sclpquiesce"); + if (!quiesce) { + return -1; + } + qdev_init_nofail(quiesce); + + object_initialize(&cpu_hotplug, sizeof(cpu_hotplug), TYPE_SCLP_CPU_HOTPLUG); + qdev_set_parent_bus(DEVICE(&cpu_hotplug), BUS(&event_facility->sbus)); + object_property_set_bool(OBJECT(&cpu_hotplug), true, "realized", NULL); + + return 0; +} + +static void reset_event_facility(DeviceState *dev) +{ + SCLPEventFacility *sdev = EVENT_FACILITY(dev); + + sdev->receive_mask = 0; +} + +static void init_event_facility_class(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sbdc = SYS_BUS_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(sbdc); + SCLPEventFacilityClass *k = EVENT_FACILITY_CLASS(dc); + + dc->reset = reset_event_facility; + dc->vmsd = &vmstate_event_facility; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); + k->init = init_event_facility; + k->command_handler = command_handler; + k->event_pending = event_pending; +} + +static const TypeInfo sclp_event_facility_info = { + .name = TYPE_SCLP_EVENT_FACILITY, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SCLPEventFacility), + .class_init = init_event_facility_class, + .class_size = sizeof(SCLPEventFacilityClass), +}; + +static void event_realize(DeviceState *qdev, Error **errp) +{ + SCLPEvent *event = SCLP_EVENT(qdev); + SCLPEventClass *child = SCLP_EVENT_GET_CLASS(event); + + if (child->init) { + int rc = child->init(event); + if (rc < 0) { + error_setg(errp, "SCLP event initialization failed."); + return; + } + } +} + +static void event_unrealize(DeviceState *qdev, Error **errp) +{ + SCLPEvent *event = SCLP_EVENT(qdev); + SCLPEventClass *child = SCLP_EVENT_GET_CLASS(event); + if (child->exit) { + int rc = child->exit(event); + if (rc < 0) { + error_setg(errp, "SCLP event exit failed."); + return; + } + } +} + +static void event_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->bus_type = TYPE_SCLP_EVENTS_BUS; + dc->realize = event_realize; + dc->unrealize = event_unrealize; +} + +static const TypeInfo sclp_event_type_info = { + .name = TYPE_SCLP_EVENT, + .parent = TYPE_DEVICE, + .instance_size = sizeof(SCLPEvent), + .class_init = event_class_init, + .class_size = sizeof(SCLPEventClass), + .abstract = true, +}; + +static void register_types(void) +{ + type_register_static(&sclp_events_bus_info); + type_register_static(&sclp_event_facility_info); + type_register_static(&sclp_event_type_info); +} + +type_init(register_types) |