s390: sclp event support

Several SCLP features are considered to be events. Those events don't
provide SCLP commands on their own, instead they are all based on
Read Event Data, Write Event Data, Write Event Mask and the service
interrupt. Follow-on patches will provide SCLP's Signal Quiesce (via
system_powerdown) and the ASCII console.
Further down the road the sclp line mode console and configuration
change events (e.g. cpu hotplug) can be implemented.

Signed-off-by: Heinz Graalfs <graalfs@linux.vnet.ibm.com>
Signed-off-by: Christian Borntraeger <borntraeger@de.ibm.com>
Signed-off-by: Jens Freimann <jfrei@linux.vnet.ibm.com>
Signed-off-by: Alexander Graf <agraf@suse.de>
This commit is contained in:
Heinz Graalfs 2012-10-29 02:13:23 +00:00 committed by Alexander Graf
parent f6c98f9286
commit 559a17a143
6 changed files with 579 additions and 2 deletions

View File

@ -32,6 +32,7 @@
#include "exec-memory.h"
#include "hw/s390-virtio-bus.h"
#include "hw/s390x/sclp.h"
//#define DEBUG_S390
@ -184,6 +185,7 @@ static void s390_init(QEMUMachineInitArgs *args)
/* get a BUS */
s390_bus = s390_virtio_bus_init(&my_ram_size);
s390_sclp_init();
/* allocate RAM */
memory_region_init_ram(ram, "s390.ram", my_ram_size);

View File

@ -2,3 +2,4 @@ obj-y = s390-virtio-bus.o s390-virtio.o
obj-y := $(addprefix ../,$(obj-y))
obj-y += sclp.o
obj-y += event-facility.o

391
hw/s390x/event-facility.c Normal file
View File

@ -0,0 +1,391 @@
/*
* 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 "monitor.h"
#include "sysemu.h"
#include "sclp.h"
#include "event-facility.h"
typedef struct EventTypesBus {
BusState qbus;
} EventTypesBus;
struct SCLPEventFacility {
EventTypesBus sbus;
DeviceState *qdev;
/* guest' receive mask */
unsigned int receive_mask;
};
/* 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;
QTAILQ_FOREACH(kid, &ef->sbus.qbus.children, sibling) {
DeviceState *qdev = kid->child;
event = (SCLPEvent *) qdev;
ec = SCLP_EVENT_GET_CLASS(event);
rc = SCLP_RC_INVALID_FUNCTION;
if (ec->write_event_data &&
ec->event_type() == 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 = 0;
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)) {
rc = SCLP_RC_NORMAL_COMPLETION;
}
}
elen = be16_to_cpu(event_buf->length);
event_buf = (void *) event_buf + elen;
}
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 s390_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) {
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 int init_event_facility(S390SCLPDevice *sdev)
{
SCLPEventFacility *event_facility;
event_facility = g_malloc0(sizeof(SCLPEventFacility));
sdev->ef = event_facility;
sdev->sclp_command_handler = command_handler;
sdev->event_pending = event_pending;
/* Spawn a new sclp-events facility */
qbus_create_inplace(&event_facility->sbus.qbus,
TYPE_SCLP_EVENTS_BUS, (DeviceState *)sdev, NULL);
event_facility->sbus.qbus.allow_hotplug = 0;
event_facility->qdev = (DeviceState *) sdev;
return 0;
}
static void init_event_facility_class(ObjectClass *klass, void *data)
{
S390SCLPDeviceClass *k = SCLP_S390_DEVICE_CLASS(klass);
k->init = init_event_facility;
}
static TypeInfo s390_sclp_event_facility_info = {
.name = "s390-sclp-event-facility",
.parent = TYPE_DEVICE_S390_SCLP,
.instance_size = sizeof(S390SCLPDevice),
.class_init = init_event_facility_class,
};
static int event_qdev_init(DeviceState *qdev)
{
SCLPEvent *event = DO_UPCAST(SCLPEvent, qdev, qdev);
SCLPEventClass *child = SCLP_EVENT_GET_CLASS(event);
return child->init(event);
}
static int event_qdev_exit(DeviceState *qdev)
{
SCLPEvent *event = DO_UPCAST(SCLPEvent, qdev, qdev);
SCLPEventClass *child = SCLP_EVENT_GET_CLASS(event);
if (child->exit) {
child->exit(event);
}
return 0;
}
static void event_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->bus_type = TYPE_SCLP_EVENTS_BUS;
dc->unplug = qdev_simple_unplug_cb;
dc->init = event_qdev_init;
dc->exit = event_qdev_exit;
}
static TypeInfo s390_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(&s390_sclp_events_bus_info);
type_register_static(&s390_sclp_event_facility_info);
type_register_static(&s390_sclp_event_type_info);
}
type_init(register_types)

96
hw/s390x/event-facility.h Normal file
View File

@ -0,0 +1,96 @@
/*
* SCLP
* Event Facility definitions
*
* 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.
*
*/
#ifndef HW_S390_SCLP_EVENT_FACILITY_H
#define HW_S390_SCLP_EVENT_FACILITY_H
#include <hw/qdev.h>
#include "qemu-thread.h"
/* SCLP event types */
#define SCLP_EVENT_ASCII_CONSOLE_DATA 0x1a
#define SCLP_EVENT_SIGNAL_QUIESCE 0x1d
/* SCLP event masks */
#define SCLP_EVENT_MASK_SIGNAL_QUIESCE 0x00000008
#define SCLP_EVENT_MASK_MSG_ASCII 0x00000040
#define SCLP_UNCONDITIONAL_READ 0x00
#define SCLP_SELECTIVE_READ 0x01
#define TYPE_SCLP_EVENT "s390-sclp-event-type"
#define SCLP_EVENT(obj) \
OBJECT_CHECK(SCLPEvent, (obj), TYPE_SCLP_EVENT)
#define SCLP_EVENT_CLASS(klass) \
OBJECT_CLASS_CHECK(SCLPEventClass, (klass), TYPE_SCLP_EVENT)
#define SCLP_EVENT_GET_CLASS(obj) \
OBJECT_GET_CLASS(SCLPEventClass, (obj), TYPE_SCLP_EVENT)
typedef struct WriteEventMask {
SCCBHeader h;
uint16_t _reserved;
uint16_t mask_length;
uint32_t cp_receive_mask;
uint32_t cp_send_mask;
uint32_t send_mask;
uint32_t receive_mask;
} QEMU_PACKED WriteEventMask;
typedef struct EventBufferHeader {
uint16_t length;
uint8_t type;
uint8_t flags;
uint16_t _reserved;
} QEMU_PACKED EventBufferHeader;
typedef struct WriteEventData {
SCCBHeader h;
EventBufferHeader ebh;
} QEMU_PACKED WriteEventData;
typedef struct ReadEventData {
SCCBHeader h;
EventBufferHeader ebh;
uint32_t mask;
} QEMU_PACKED ReadEventData;
typedef struct SCLPEvent {
DeviceState qdev;
bool event_pending;
uint32_t event_type;
char *name;
} SCLPEvent;
typedef struct SCLPEventClass {
DeviceClass parent_class;
int (*init)(SCLPEvent *event);
int (*exit)(SCLPEvent *event);
/* get SCLP's send mask */
unsigned int (*get_send_mask)(void);
/* get SCLP's receive mask */
unsigned int (*get_receive_mask)(void);
int (*read_event_data)(SCLPEvent *event, EventBufferHeader *evt_buf_hdr,
int *slen);
int (*write_event_data)(SCLPEvent *event, EventBufferHeader *evt_buf_hdr);
/* returns the supported event type */
int (*event_type)(void);
} SCLPEventClass;
#endif

View File

@ -18,6 +18,15 @@
#include "sclp.h"
static inline S390SCLPDevice *get_event_facility(void)
{
ObjectProperty *op = object_property_find(qdev_get_machine(),
"s390-sclp-event-facility",
NULL);
assert(op);
return op->opaque;
}
/* Provide information about the configuration, CPUs and storage */
static void read_SCP_info(SCCB *sccb)
{
@ -34,13 +43,15 @@ static void read_SCP_info(SCCB *sccb)
static void sclp_execute(SCCB *sccb, uint64_t code)
{
S390SCLPDevice *sdev = get_event_facility();
switch (code) {
case SCLP_CMDW_READ_SCP_INFO:
case SCLP_CMDW_READ_SCP_INFO_FORCED:
read_SCP_info(sccb);
break;
default:
sccb->h.response_code = cpu_to_be16(SCLP_RC_INVALID_SCLP_COMMAND);
sdev->sclp_command_handler(sdev->ef, sccb, code);
break;
}
}
@ -89,11 +100,45 @@ out:
void sclp_service_interrupt(uint32_t sccb)
{
s390_sclp_extint(sccb & ~3);
S390SCLPDevice *sdev = get_event_facility();
uint32_t param = sccb & ~3;
/* Indicate whether an event is still pending */
param |= sdev->event_pending(sdev->ef) ? 1 : 0;
if (!param) {
/* No need to send an interrupt, there's nothing to be notified about */
return;
}
s390_sclp_extint(param);
}
/* qemu object creation and initialization functions */
void s390_sclp_init(void)
{
DeviceState *dev = qdev_create(NULL, "s390-sclp-event-facility");
object_property_add_child(qdev_get_machine(), "s390-sclp-event-facility",
OBJECT(dev), NULL);
qdev_init_nofail(dev);
}
static int s390_sclp_dev_init(SysBusDevice *dev)
{
int r;
S390SCLPDevice *sdev = (S390SCLPDevice *)dev;
S390SCLPDeviceClass *sclp = SCLP_S390_DEVICE_GET_CLASS(dev);
r = sclp->init(sdev);
if (!r) {
assert(sdev->event_pending);
assert(sdev->sclp_command_handler);
}
return r;
}
static void s390_sclp_device_class_init(ObjectClass *klass, void *data)
{
SysBusDeviceClass *dc = SYS_BUS_DEVICE_CLASS(klass);

View File

@ -20,15 +20,35 @@
/* SCLP command codes */
#define SCLP_CMDW_READ_SCP_INFO 0x00020001
#define SCLP_CMDW_READ_SCP_INFO_FORCED 0x00120001
#define SCLP_CMD_READ_EVENT_DATA 0x00770005
#define SCLP_CMD_WRITE_EVENT_DATA 0x00760005
#define SCLP_CMD_READ_EVENT_DATA 0x00770005
#define SCLP_CMD_WRITE_EVENT_DATA 0x00760005
#define SCLP_CMD_WRITE_EVENT_MASK 0x00780005
/* SCLP response codes */
#define SCLP_RC_NORMAL_READ_COMPLETION 0x0010
#define SCLP_RC_NORMAL_COMPLETION 0x0020
#define SCLP_RC_INVALID_SCLP_COMMAND 0x01f0
#define SCLP_RC_CONTAINED_EQUIPMENT_CHECK 0x0340
#define SCLP_RC_INSUFFICIENT_SCCB_LENGTH 0x0300
#define SCLP_RC_INVALID_FUNCTION 0x40f0
#define SCLP_RC_NO_EVENT_BUFFERS_STORED 0x60f0
#define SCLP_RC_INVALID_SELECTION_MASK 0x70f0
#define SCLP_RC_INCONSISTENT_LENGTHS 0x72f0
#define SCLP_RC_EVENT_BUFFER_SYNTAX_ERROR 0x73f0
#define SCLP_RC_INVALID_MASK_LENGTH 0x74f0
/* Service Call Control Block (SCCB) and its elements */
#define SCCB_SIZE 4096
#define SCLP_VARIABLE_LENGTH_RESPONSE 0x80
#define SCLP_EVENT_BUFFER_ACCEPTED 0x80
#define SCLP_FC_NORMAL_WRITE 0
/*
* Normally packed structures are not the right thing to do, since all code
* must take care of endianess. We cant use ldl_phys and friends for two
@ -62,8 +82,29 @@ typedef struct SCCB {
char data[SCCB_DATA_LEN];
} QEMU_PACKED SCCB;
static inline int sccb_data_len(SCCB *sccb)
{
return be16_to_cpu(sccb->h.length) - sizeof(sccb->h);
}
#define TYPE_DEVICE_S390_SCLP "s390-sclp-device"
#define SCLP_S390_DEVICE(obj) \
OBJECT_CHECK(S390SCLPDevice, (obj), TYPE_DEVICE_S390_SCLP)
#define SCLP_S390_DEVICE_CLASS(klass) \
OBJECT_CLASS_CHECK(S390SCLPDeviceClass, (klass), \
TYPE_DEVICE_S390_SCLP)
#define SCLP_S390_DEVICE_GET_CLASS(obj) \
OBJECT_GET_CLASS(S390SCLPDeviceClass, (obj), \
TYPE_DEVICE_S390_SCLP)
typedef struct SCLPEventFacility SCLPEventFacility;
typedef struct S390SCLPDevice {
SysBusDevice busdev;
SCLPEventFacility *ef;
void (*sclp_command_handler)(SCLPEventFacility *ef, SCCB *sccb,
uint64_t code);
bool (*event_pending)(SCLPEventFacility *ef);
} S390SCLPDevice;
typedef struct S390SCLPDeviceClass {
@ -71,6 +112,7 @@ typedef struct S390SCLPDeviceClass {
int (*init)(S390SCLPDevice *sdev);
} S390SCLPDeviceClass;
void s390_sclp_init(void);
void sclp_service_interrupt(uint32_t sccb);
#endif