From: Heinz Graalfs <graa...@linux.vnet.ibm.com> Adds console support (in vt220 mode). In order to run qemu exploiting the SCLP's console functionality in vt220 mode the user has to specify the following console related parameters:
-chardev stdio,id=charconsole0 -device sclpconsole,chardev=charconsole0,id=console0 Signed-off-by: Heinz Graalfs <graa...@linux.vnet.ibm.com> Signed-off-by: Jens Freimann <jf...@linux.vnet.ibm.com> --- hw/s390-event-facility.c | 209 ++++++++++++++++++++++++++++++++++++++++++++++ hw/s390-event-facility.h | 8 ++ hw/s390-sclp.c | 177 ++++++++++++++++++++++++++++++++++++++- hw/s390-sclp.h | 22 ++++- sysemu.h | 1 + target-s390x/op_helper.c | 6 ++ vl.c | 41 +++++++++ 7 files changed, 460 insertions(+), 4 deletions(-) diff --git a/hw/s390-event-facility.c b/hw/s390-event-facility.c index b8106a6..cfa5dd4 100644 --- a/hw/s390-event-facility.c +++ b/hw/s390-event-facility.c @@ -16,6 +16,11 @@ #include "s390-sclp.h" #include "s390-event-facility.h" +qemu_irq sclp_read_vt220; + +static int size_buffer = 4096; +static char *sclp_console_data_vt220; + struct SCLPDevice { const char *name; bool vm_running; @@ -224,9 +229,213 @@ static TypeInfo sclp_quiesce_info = { .class_init = sclpef_quiesce_class_init, }; +/* ----------- SCLP VT220 console ------------ */ + +static void s390_signal_read_vt220(void *opaque, int n, int level) +{ + sclp_enable_signal_read_vt220(); + sclp_service_interrupt(opaque, 0); +} + +static void sclpef_set_console(SCLPEvent *event) +{ + if (event->id == ID_VT220) { + sclp_read_vt220 = *qemu_allocate_irqs(s390_signal_read_vt220, + event->evt_fac->opaque, 1); + } +} + +void sclpef_write_console_vt220(SCLPDevice *sdev, char *buf) +{ + DeviceState *dev; + SCLPEventFacility *event_facility; + static SCLPEvent *event; + static SCLPEventClass *cons; + + event_facility = DO_UPCAST(SCLPEventFacility, sdev, sdev); + + if (!cons) { + QTAILQ_FOREACH(dev, &event_facility->sbus.qbus.children, sibling) { + event = (SCLPEvent *) dev; + if (event->id == ID_VT220) { + cons = SCLP_EVENT_GET_CLASS(event); + assert(cons->have_data); + break; + } + } + } + if (cons) { + cons->have_data(event, (const uint8_t *)buf, strlen(buf)); + } +} + +char *sclpef_get_console_data_vt220(SCLPDevice *sdev) +{ + DeviceState *dev; + SCLPEventFacility *event_facility; + SCLPEvent *event = NULL; + static SCLPEventClass *cons; + + event_facility = DO_UPCAST(SCLPEventFacility, sdev, sdev); + + if (!cons) { + QTAILQ_FOREACH(dev, &event_facility->sbus.qbus.children, sibling) { + event = (SCLPEvent *) dev; + if (event->id == ID_VT220) { + cons = SCLP_EVENT_GET_CLASS(event); + assert(cons->get_data); + break; + } + } + } + if (cons) { + return cons->get_data(); + } + return NULL; +} + +static char *console_data_vt220(void) +{ + return sclp_console_data_vt220; +} + +static ssize_t flush_buf(SCLPEvent *event, const uint8_t *buf, size_t len) +{ + SCLPConsole *scon = DO_UPCAST(SCLPConsole, event, event); + ssize_t ret; + + if (!scon->chr) { + /* If there's no backend, we can just say we consumed all data. */ + return len; + } + + ret = qemu_chr_fe_write(scon->chr, buf, len); + + if (ret < 0) { + /* see virtio-console comments */ + ret = 0; + } + + return ret; +} + +static void guest_open(SCLPEvent *event) +{ + SCLPConsole *scon = DO_UPCAST(SCLPConsole, event, event); + + if (!scon->chr) { + return; + } + qemu_chr_fe_open(scon->chr); +} + +static void guest_close(SCLPEvent *event) +{ + SCLPConsole *scon = DO_UPCAST(SCLPConsole, event, event); + + if (!scon->chr) { + return; + } + qemu_chr_fe_close(scon->chr); +} + +static int chr_can_read(void *opaque) +{ + return 1; +} + +static void chr_read_vt220(void *opaque, const uint8_t *buf, int size) +{ + char *offset; + + if (!sclp_console_data_vt220) { + size_buffer = 2 * size; + sclp_console_data_vt220 = malloc(size_buffer); + } + if (size_buffer < size + 1) { + free(sclp_console_data_vt220); + size_buffer = 2 * size; + sclp_console_data_vt220 = malloc(size_buffer); + } + offset = sclp_console_data_vt220; + if (offset) { + memcpy(offset, buf, size); + offset += size; + *offset = '\0'; + qemu_irq_raise(sclp_read_vt220); + } else { + size_buffer = 0; + } +} + +static void chr_event(void *opaque, int event) +{ + switch (event) { + case CHR_EVENT_OPENED: + if (!sclp_console_data_vt220) { + sclp_console_data_vt220 = malloc(size_buffer); + } + break; + case CHR_EVENT_CLOSED: + break; + } +} + +static unsigned int send_mask_vt220(void) +{ + return SCLP_EVENT_MASK_MSG_ASCII; +} + +static unsigned int receive_mask_vt220(void) +{ + return SCLP_EVENT_MASK_MSG_ASCII; +} + +static int sclpconsole_initfn_vt220(SCLPEvent *event) +{ + SCLPConsole *scon = DO_UPCAST(SCLPConsole, event, event); + + event->id = ID_VT220; + sclpef_set_console(event); + if (scon->chr) { + qemu_chr_add_handlers(scon->chr, chr_can_read, + chr_read_vt220, chr_event, scon); + } + + return 0; +} + +static Property sclpconsole_properties[] = { + DEFINE_PROP_CHR("chardev", SCLPConsole, chr), + DEFINE_PROP_END_OF_LIST(), +}; + +static void sclpconsole_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SCLPEventClass *k = SCLP_EVENT_CLASS(klass); + + k->init = sclpconsole_initfn_vt220; + k->have_data = flush_buf; + k->guest_open = guest_open; + k->guest_close = guest_close; + k->get_send_mask = send_mask_vt220; + k->get_receive_mask = receive_mask_vt220; + k->get_data = console_data_vt220; + dc->props = sclpconsole_properties; +} + +static TypeInfo sclpconsole_info = { + .name = "sclpconsole", + .parent = TYPE_SCLP_EVENT, + .instance_size = sizeof(SCLPConsole), + .class_init = sclpconsole_class_init, +}; + static void sclpef_register_types(void) { type_register_static(&sclp_event_facility_type_info); type_register_static(&sclp_quiesce_info); + type_register_static(&sclpconsole_info); } type_init(sclpef_register_types) diff --git a/hw/s390-event-facility.h b/hw/s390-event-facility.h index 40d4049..d6bde7d 100644 --- a/hw/s390-event-facility.h +++ b/hw/s390-event-facility.h @@ -14,6 +14,7 @@ #include "qemu-common.h" #define ID_QUIESCE SCLP_EVENT_SIGNAL_QUIESCE +#define ID_VT220 SCLP_EVENT_ASCII_CONSOLE_DATA #define TYPE_SCLP_EVENT "s390-sclp-event-type" #define SCLP_EVENT(obj) \ @@ -34,6 +35,11 @@ typedef struct SCLPEventClass { int (*exit)(SCLPEvent *event); unsigned int (*get_send_mask)(void); unsigned int (*get_receive_mask)(void); + void (*guest_open)(SCLPEvent *event); + void (*guest_close)(SCLPEvent *event); + void (*guest_ready)(SCLPEvent *event); + ssize_t (*have_data)(SCLPEvent *event, const uint8_t *buf, size_t len); + char *(*get_data)(void); } SCLPEventClass; struct SCLPEvent { @@ -50,5 +56,7 @@ void sclpef_enable_irqs(SCLPDevice *sdev, void *opaque); void sclpef_set_masks(void); unsigned int sclpef_send_mask(SCLPDevice *sdev); unsigned int sclpef_receive_mask(SCLPDevice *sdev); +void sclpef_write_console_vt220(SCLPDevice *sdev, char *buf); +char *sclpef_get_console_data_vt220(SCLPDevice *sdev); #endif diff --git a/hw/s390-sclp.c b/hw/s390-sclp.c index 683a709..8f45773 100644 --- a/hw/s390-sclp.c +++ b/hw/s390-sclp.c @@ -11,6 +11,11 @@ #include "hw/s390-sclp.h" #include "hw/s390-event-facility.h" +/* input buffer handling */ +#define INP_BUFFER_SIZE 4096 +static int sclp_curr_buf_size; +static char *sclp_input_vt220; + /* Host capabilites */ static unsigned int sclp_send_mask; static unsigned int sclp_receive_mask; @@ -21,6 +26,7 @@ static unsigned int sclp_cp_receive_mask; static int quiesce; static int event_pending; +static int vt220; int sclp_read_info(CPUS390XState *env, struct sccb *sccb) { @@ -66,6 +72,7 @@ void sclp_enable_signal_quiesce(void) { quiesce = 1; event_pending = 1; + vt220 = 0; } static void sclp_set_masks(void) @@ -81,7 +88,112 @@ static void sclp_set_masks(void) sclp_send_mask = sclpef_send_mask(evt_fac->sdev); sclp_receive_mask = sclpef_receive_mask(evt_fac->sdev); - } +} + +static int signal_vt220_event(struct sccb *sccb, int *slen) +{ + char *p; + + if (!sclp_input_vt220 || !vt220) { + return 0; + } + + int l = strlen(sclp_input_vt220); + + if (*slen < sizeof(struct ascii_cons_data_command) + l + 1) { + event_pending = 1; + return 0; + } + p = (char *)&sccb->c.read.acd_cmd.data; + /* first byte is hex 0 saying an ascii string follows */ + *p++ = '\0'; + memmove(p, sclp_input_vt220, l); + *sclp_input_vt220 = '\0'; + + sccb->c.read.acd_cmd.h.length = + cpu_to_be16(sizeof(struct ascii_cons_data_command) + l + 1); + sccb->c.read.acd_cmd.h.type = SCLP_EVENT_ASCII_CONSOLE_DATA; + + vt220 = 0; + *slen -= sizeof(struct ascii_cons_data_command) + l + 1; + return 1; +} + +static char *grow_buffer(int size) +{ + char *p = (char *) malloc(size); + + if (!p) { + sclp_curr_buf_size = 0; + return NULL; + } + memset(p, '\0', size); + sclp_curr_buf_size = size; + return p; +} + +static int sclp_write_vt220(struct event_buffer_header *event) +{ + int l; + char *msg; + SCLPS390EventFacility *evt_fac; + struct ascii_cons_data_command *ad = + (struct ascii_cons_data_command *) event; + + assert(sclp_bus); + + l = event->length - sizeof(struct event_buffer_header); + msg = (char *) malloc(l + 1); + assert(msg); + memset(msg, '\0', l + 1); + memmove(msg, ad->data, l); + + evt_fac = sclp_bus->event_facility; + sclpef_write_console_vt220(evt_fac->sdev, msg); + + free(msg); + + return SCLP_RC_NORMAL_COMPLETION; +} + +void sclp_enable_signal_read_vt220(void) +{ + int len; + char *input; + SCLPS390EventFacility *evt_fac; + + if (!sclp_bus) { + return; + } + evt_fac = sclp_bus->event_facility; + + assert(evt_fac); + + input = sclpef_get_console_data_vt220(evt_fac->sdev); + + if (!input) { + return; + } + + vt220 = 1; + quiesce = 0; + event_pending = 1; + len = strlen((char *) input); + if (sclp_input_vt220 == NULL) { + /* get new buffer */ + sclp_input_vt220 = grow_buffer(2 * len + 1); + } else { + if (len >= sclp_curr_buf_size) { + /* get larger buffer */ + char *p = grow_buffer(2 * len + 1); + free(sclp_input_vt220); + sclp_input_vt220 = p; + } + } + if (sclp_input_vt220) { + strcat(sclp_input_vt220, (char *)input); + } +} int sclp_read_event_data(CPUS390XState *env, struct sccb *sccb) { @@ -99,7 +211,8 @@ int sclp_read_event_data(CPUS390XState *env, struct sccb *sccb) break; case SCLP_SELECTIVE_READ: if (!(sclp_cp_receive_mask & be32_to_cpu(sccb->c.read.mask))) { - sccb->h.response_code = cpu_to_be16(SCLP_RC_INVALID_SELECTION_MASK); + sccb->h.response_code = + cpu_to_be16(SCLP_RC_INVALID_SELECTION_MASK); goto out; } sclp_active_selection_mask = be32_to_cpu(sccb->c.read.mask); @@ -117,7 +230,12 @@ int sclp_read_event_data(CPUS390XState *env, struct sccb *sccb) sccb->h.response_code = cpu_to_be16(SCLP_RC_NORMAL_COMPLETION); } } - + if (sclp_active_selection_mask & SCLP_EVENT_MASK_MSG_ASCII) { + if (signal_vt220_event(sccb, &slen)) { + sccb->h.response_code = cpu_to_be16(SCLP_RC_NORMAL_COMPLETION); + sccb->h.length = cpu_to_be16(SCCB_SIZE - slen); + } + } if (sccb->h.control_mask[2] & SCLP_VARIABLE_LENGTH_RESPONSE) { sccb->h.control_mask[2] &= ~SCLP_VARIABLE_LENGTH_RESPONSE; sccb->h.length = cpu_to_be16(SCCB_SIZE - slen); @@ -127,6 +245,59 @@ out: return 0; } +int sclp_write_event_data(CPUS390XState *env, struct sccb *sccb) +{ + struct event_buffer_header *event; + int slen; + unsigned elen = 0; + + 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 check the sum of all events */ + event = &sccb->c.event; + for (slen = be16_to_cpu(sccb->h.length) - sizeof(sccb->h); + slen > 0; slen -= elen) { + elen = be16_to_cpu(event->length); + if (elen < sizeof(*event) || elen > slen) { + sccb->h.response_code = + cpu_to_be16(SCLP_RC_EVENT_BUFFER_SYNTAX_ERROR); + goto out; + } + event = (void *) event + elen; + } + if (slen) { + sccb->h.response_code = cpu_to_be16(SCLP_RC_INCONSISTENT_LENGTHS); + goto out; + } + + /* the execute */ + event = &sccb->c.event; + for (slen = be16_to_cpu(sccb->h.length) - sizeof(sccb->h); + slen > 0; slen -= elen) { + elen = be16_to_cpu(event->length); + switch (event->type) { + case SCLP_EVENT_ASCII_CONSOLE_DATA: + sccb->h.response_code = cpu_to_be16(sclp_write_vt220(event)); + break; + default: + sccb->h.response_code = SCLP_RC_INVALID_FUNCTION; + break; + } + event = (void *) event + elen; + } + sccb->h.response_code = SCLP_RC_NORMAL_COMPLETION; + +out: + return 0; +} + int sclp_write_event_mask(CPUS390XState *env, struct sccb *sccb) { /* Attention: We assume that Linux uses 4-byte masks, what it actually diff --git a/hw/s390-sclp.h b/hw/s390-sclp.h index f61421b..c86bca8 100644 --- a/hw/s390-sclp.h +++ b/hw/s390-sclp.h @@ -7,6 +7,8 @@ /* 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_WRITE_EVENT_MASK 0x00780005 /* SCLP response codes */ @@ -20,11 +22,12 @@ #define SCLP_RC_INVALID_MASK_LENGTH 0x74f0 /* 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 0x40000000 +#define SCLP_EVENT_MASK_MSG_ASCII 0x00000040 #define SCLP_UNCONDITIONAL_READ 0x00 #define SCLP_SELECTIVE_READ 0x01 @@ -44,6 +47,13 @@ struct write_event_mask { uint32_t receive_mask; } __attribute__((packed)); +struct mdb_header { + uint16_t length; + uint16_t type; + uint32_t tag; + uint32_t revision_code; +} __attribute__((packed)); + struct event_buffer_header { uint16_t length; uint8_t type; @@ -58,9 +68,15 @@ struct signal_quiesce { uint8_t unit; } __attribute__((packed)); +struct ascii_cons_data_command { + struct event_buffer_header h; + char data[0]; +} __attribute__((packed)); + struct read_event_data { union { struct signal_quiesce quiesce; + struct ascii_cons_data_command acd_cmd; uint32_t mask; }; } __attribute__((packed)); @@ -84,15 +100,19 @@ struct sccb { struct sccb_header h; union { struct read_info_sccb read_info; + struct event_buffer_header event; struct read_event_data read; + struct ascii_cons_data_command acd_cmd; struct write_event_mask we_mask; char data[SCCB_DATA_LEN]; } c; } __attribute__((packed)); void sclp_enable_signal_quiesce(void); +void sclp_enable_signal_read_vt220(void); int sclp_read_info(CPUS390XState *env, struct sccb *sccb); int sclp_read_event_data(CPUS390XState *env, struct sccb *sccb); +int sclp_write_event_data(CPUS390XState *env, struct sccb *sccb); int sclp_write_event_mask(CPUS390XState *env, struct sccb *sccb); void sclp_service_interrupt(CPUS390XState *env, uint32_t sccb); diff --git a/sysemu.h b/sysemu.h index bc2c788..b4d399c 100644 --- a/sysemu.h +++ b/sysemu.h @@ -62,6 +62,7 @@ int qemu_powerdown_requested(void); void qemu_system_killed(int signal, pid_t pid); void qemu_kill_report(void); extern qemu_irq qemu_system_powerdown; +extern qemu_irq sclp_read_vt220; void qemu_system_reset(bool report); void qemu_add_exit_notifier(Notifier *notify); diff --git a/target-s390x/op_helper.c b/target-s390x/op_helper.c index 3e5eff4..4d49472 100644 --- a/target-s390x/op_helper.c +++ b/target-s390x/op_helper.c @@ -2391,6 +2391,12 @@ int sclp_service_call(CPUS390XState *env, uint32_t sccb, uint64_t code) case SCLP_CMDW_READ_SCP_INFO_FORCED: r = sclp_read_info(env, &work_sccb); break; + case SCLP_CMD_READ_EVENT_DATA: + r = sclp_read_event_data(env, &work_sccb); + break; + case SCLP_CMD_WRITE_EVENT_DATA: + r = sclp_write_event_data(env, &work_sccb); + break; case SCLP_CMD_WRITE_EVENT_MASK: r = sclp_write_event_mask(env, &work_sccb); break; diff --git a/vl.c b/vl.c index 23ab3a3..aba7ab0 100644 --- a/vl.c +++ b/vl.c @@ -174,6 +174,7 @@ int main(int argc, char **argv) #define DEFAULT_RAM_SIZE 128 #define MAX_VIRTIO_CONSOLES 1 +#define MAX_SCLP_CONSOLES 1 static const char *data_dir; const char *bios_name = NULL; @@ -201,6 +202,7 @@ int no_quit = 0; CharDriverState *serial_hds[MAX_SERIAL_PORTS]; CharDriverState *parallel_hds[MAX_PARALLEL_PORTS]; CharDriverState *virtcon_hds[MAX_VIRTIO_CONSOLES]; +CharDriverState *sclpcon_hds[MAX_SCLP_CONSOLES]; int win2k_install_hack = 0; int usb_enabled = 0; int singlestep = 0; @@ -274,6 +276,8 @@ static int default_floppy = 1; static int default_cdrom = 1; static int default_sdcard = 1; static int default_vga = 1; +static int default_sclpcon = 1; +static int default_loader = 1; static struct { const char *driver; @@ -295,6 +299,8 @@ static struct { { .driver = "isa-cirrus-vga", .flag = &default_vga }, { .driver = "vmware-svga", .flag = &default_vga }, { .driver = "qxl-vga", .flag = &default_vga }, + { .driver = "s390-sclp", .flag = &default_sclpcon }, + { .driver = "s390-ipl", .flag = &default_loader }, }; static void res_free(void) @@ -1942,6 +1948,7 @@ struct device_config { DEV_VIRTCON, /* -virtioconsole */ DEV_DEBUGCON, /* -debugcon */ DEV_GDB, /* -gdb, -s */ + DEV_SCLPCON, /* sclp console */ } type; const char *cmdline; Location loc; @@ -2058,6 +2065,36 @@ static int virtcon_parse(const char *devname) return 0; } +static int sclpcon_parse(const char *devname) +{ + QemuOptsList *device = qemu_find_opts("device"); + static int index; + char label[32]; + QemuOpts *dev_opts; + + if (strcmp(devname, "none") == 0) + return 0; + if (index == MAX_SCLP_CONSOLES) { + fprintf(stderr, "qemu: too many sclp consoles\n"); + exit(1); + } + + dev_opts = qemu_opts_create(device, NULL, 0); + qemu_opt_set(dev_opts, "driver", "sclpconsole"); + + snprintf(label, sizeof(label), "sclpcon%d", index); + sclpcon_hds[index] = qemu_chr_new(label, devname, NULL); + if (!sclpcon_hds[index]) { + fprintf(stderr, "qemu: could not open sclp console '%s': %s\n", + devname, strerror(errno)); + return -1; + } + qemu_opt_set(dev_opts, "chardev", label); + + index++; + return 0; +} + static int debugcon_parse(const char *devname) { QemuOpts *opts; @@ -3304,6 +3341,8 @@ int main(int argc, char **argv, char **envp) add_device_config(DEV_SERIAL, "mon:stdio"); } else if (default_virtcon && default_monitor) { add_device_config(DEV_VIRTCON, "mon:stdio"); + } else if (default_sclpcon && default_monitor) { + add_device_config(DEV_SCLPCON, "mon:stdio"); } else { if (default_serial) add_device_config(DEV_SERIAL, "stdio"); @@ -3491,6 +3530,8 @@ int main(int argc, char **argv, char **envp) exit(1); if (foreach_device_config(DEV_VIRTCON, virtcon_parse) < 0) exit(1); + if (foreach_device_config(DEV_SCLPCON, sclpcon_parse) < 0) + exit(1); if (foreach_device_config(DEV_DEBUGCON, debugcon_parse) < 0) exit(1); -- 1.7.10.4