/* * SCLP event types * Operations Command - Line Mode input * Message - Line Mode output * * Copyright IBM, Corp. 2013 * * Authors: * Heinz Graalfs * * 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 "qemu/thread.h" #include "qemu/error-report.h" #include "qemu/module.h" #include "chardev/char-fe.h" #include "hw/s390x/sclp.h" #include "migration/vmstate.h" #include "hw/s390x/event-facility.h" #include "hw/qdev-properties.h" #include "hw/qdev-properties-system.h" #include "hw/s390x/ebcdic.h" #include "qom/object.h" #define SIZE_BUFFER 4096 #define NEWLINE "\n" typedef struct OprtnsCommand { EventBufferHeader header; MDMSU message_unit; char data[]; } QEMU_PACKED OprtnsCommand; /* max size for line-mode data in 4K SCCB page */ #define SIZE_CONSOLE_BUFFER (SCCB_DATA_LEN - sizeof(OprtnsCommand)) struct SCLPConsoleLM { SCLPEvent event; CharBackend chr; bool echo; /* immediate echo of input if true */ uint32_t write_errors; /* errors writing to char layer */ uint32_t length; /* length of byte stream in buffer */ uint8_t buf[SIZE_CONSOLE_BUFFER]; }; typedef struct SCLPConsoleLM SCLPConsoleLM; #define TYPE_SCLPLM_CONSOLE "sclplmconsole" DECLARE_INSTANCE_CHECKER(SCLPConsoleLM, SCLPLM_CONSOLE, TYPE_SCLPLM_CONSOLE) /* * Character layer call-back functions * * Allow 1 character at a time * * Accumulate bytes from character layer in console buffer, * event_pending is set when a newline character is encountered * * The maximum command line length is limited by the maximum * space available in an SCCB. Line mode console input is sent * truncated to the guest in case it doesn't fit into the SCCB. */ static int chr_can_read(void *opaque) { SCLPConsoleLM *scon = opaque; if (scon->event.event_pending) { return 0; } return 1; } static void chr_read(void *opaque, const uint8_t *buf, int size) { SCLPConsoleLM *scon = opaque; assert(size == 1); if (*buf == '\r' || *buf == '\n') { scon->event.event_pending = true; sclp_service_interrupt(0); return; } if (scon->length == SIZE_CONSOLE_BUFFER) { /* Eat the character, but still process CR and LF. */ return; } scon->buf[scon->length] = *buf; scon->length += 1; if (scon->echo) { /* XXX this blocks entire thread. Rewrite to use * qemu_chr_fe_write and background I/O callbacks */ qemu_chr_fe_write_all(&scon->chr, buf, size); } } /* functions to be called by event facility */ static bool can_handle_event(uint8_t type) { return type == SCLP_EVENT_MESSAGE || type == SCLP_EVENT_PMSGCMD; } static sccb_mask_t send_mask(void) { return SCLP_EVENT_MASK_OP_CMD | SCLP_EVENT_MASK_PMSGCMD; } static sccb_mask_t receive_mask(void) { return SCLP_EVENT_MASK_MSG | SCLP_EVENT_MASK_PMSGCMD; } /* * Triggered by SCLP's read_event_data * - convert ASCII byte stream to EBCDIC and * - copy converted data into provided (SCLP) buffer */ static int get_console_data(SCLPEvent *event, uint8_t *buf, size_t *size, int avail) { int len; SCLPConsoleLM *cons = SCLPLM_CONSOLE(event); len = cons->length; /* data need to fit into provided SCLP buffer */ if (len > avail) { return 1; } ebcdic_put(buf, (char *)&cons->buf, len); *size = len; cons->length = 0; /* data provided and no more data pending */ event->event_pending = false; qemu_notify_event(); return 0; } static int read_event_data(SCLPEvent *event, EventBufferHeader *evt_buf_hdr, int *slen) { int avail, rc; size_t src_len; uint8_t *to; OprtnsCommand *oc = (OprtnsCommand *) evt_buf_hdr; if (!event->event_pending) { /* no data pending */ return 0; } to = (uint8_t *)&oc->data; avail = *slen - sizeof(OprtnsCommand); rc = get_console_data(event, to, &src_len, avail); if (rc) { /* data didn't fit, try next SCCB */ return 1; } oc->message_unit.mdmsu.gds_id = GDS_ID_MDSMU; oc->message_unit.mdmsu.length = cpu_to_be16(sizeof(struct MDMSU)); oc->message_unit.cpmsu.gds_id = GDS_ID_CPMSU; oc->message_unit.cpmsu.length = cpu_to_be16(sizeof(struct MDMSU) - sizeof(GdsVector)); oc->message_unit.text_command.gds_id = GDS_ID_TEXTCMD; oc->message_unit.text_command.length = cpu_to_be16(sizeof(struct MDMSU) - (2 * sizeof(GdsVector))); oc->message_unit.self_def_text_message.key = GDS_KEY_SELFDEFTEXTMSG; oc->message_unit.self_def_text_message.length = cpu_to_be16(sizeof(struct MDMSU) - (3 * sizeof(GdsVector))); oc->message_unit.text_message.key = GDS_KEY_TEXTMSG; oc->message_unit.text_message.length = cpu_to_be16(sizeof(GdsSubvector) + src_len); oc->header.length = cpu_to_be16(sizeof(OprtnsCommand) + src_len); oc->header.type = SCLP_EVENT_OPRTNS_COMMAND; *slen = avail - src_len; return 1; } /* * Triggered by SCLP's write_event_data * - write console data to character layer * returns < 0 if an error occurred */ static int write_console_data(SCLPEvent *event, const uint8_t *buf, int len) { SCLPConsoleLM *scon = SCLPLM_CONSOLE(event); if (!qemu_chr_fe_backend_connected(&scon->chr)) { /* If there's no backend, we can just say we consumed all data. */ return len; } /* XXX this blocks entire thread. Rewrite to use * qemu_chr_fe_write and background I/O callbacks */ return qemu_chr_fe_write_all(&scon->chr, buf, len); } static int process_mdb(SCLPEvent *event, MDBO *mdbo) { int rc; int len; uint8_t buffer[SIZE_BUFFER]; len = be16_to_cpu(mdbo->length); len -= sizeof(mdbo->length) + sizeof(mdbo->type) + sizeof(mdbo->mto.line_type_flags) + sizeof(mdbo->mto.alarm_control) + sizeof(mdbo->mto._reserved); assert(len <= SIZE_BUFFER); /* convert EBCDIC SCLP contents to ASCII console message */ ascii_put(buffer, mdbo->mto.message, len); rc = write_console_data(event, (uint8_t *)NEWLINE, 1); if (rc < 0) { return rc; } return write_console_data(event, buffer, len); } static int write_event_data(SCLPEvent *event, EventBufferHeader *ebh) { int len; int written; int errors = 0; MDBO *mdbo; SclpMsg *data = (SclpMsg *) ebh; SCLPConsoleLM *scon = SCLPLM_CONSOLE(event); len = be16_to_cpu(data->mdb.header.length); if (len < sizeof(data->mdb.header)) { return SCLP_RC_INCONSISTENT_LENGTHS; } len -= sizeof(data->mdb.header); /* first check message buffers */ mdbo = data->mdb.mdbo; while (len > 0) { if (be16_to_cpu(mdbo->length) > len || be16_to_cpu(mdbo->length) == 0) { return SCLP_RC_INCONSISTENT_LENGTHS; } len -= be16_to_cpu(mdbo->length); mdbo = (void *) mdbo + be16_to_cpu(mdbo->length); } /* then execute */ len = be16_to_cpu(data->mdb.header.length) - sizeof(data->mdb.header); mdbo = data->mdb.mdbo; while (len > 0) { switch (be16_to_cpu(mdbo->type)) { case MESSAGE_TEXT: /* message text object */ written = process_mdb(event, mdbo); if (written < 0) { /* character layer error */ errors++; } break; default: /* ignore */ break; } len -= be16_to_cpu(mdbo->length); mdbo = (void *) mdbo + be16_to_cpu(mdbo->length); } if (errors) { scon->write_errors += errors; } data->header.flags = SCLP_EVENT_BUFFER_ACCEPTED; return SCLP_RC_NORMAL_COMPLETION; } /* functions for live migration */ static const VMStateDescription vmstate_sclplmconsole = { .name = "sclplmconsole", .version_id = 0, .minimum_version_id = 0, .fields = (const VMStateField[]) { VMSTATE_BOOL(event.event_pending, SCLPConsoleLM), VMSTATE_UINT32(write_errors, SCLPConsoleLM), VMSTATE_UINT32(length, SCLPConsoleLM), VMSTATE_UINT8_ARRAY(buf, SCLPConsoleLM, SIZE_CONSOLE_BUFFER), VMSTATE_END_OF_LIST() } }; /* qemu object creation and initialization functions */ /* tell character layer our call-back functions */ static int console_init(SCLPEvent *event) { static bool console_available; SCLPConsoleLM *scon = SCLPLM_CONSOLE(event); if (console_available) { error_report("Multiple line-mode operator consoles are not supported"); return -1; } console_available = true; qemu_chr_fe_set_handlers(&scon->chr, chr_can_read, chr_read, NULL, NULL, scon, NULL, true); return 0; } static void console_reset(DeviceState *dev) { SCLPEvent *event = SCLP_EVENT(dev); SCLPConsoleLM *scon = SCLPLM_CONSOLE(event); event->event_pending = false; scon->length = 0; scon->write_errors = 0; } static const Property console_properties[] = { DEFINE_PROP_CHR("chardev", SCLPConsoleLM, chr), DEFINE_PROP_UINT32("write_errors", SCLPConsoleLM, write_errors, 0), DEFINE_PROP_BOOL("echo", SCLPConsoleLM, echo, true), }; static void console_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); SCLPEventClass *ec = SCLP_EVENT_CLASS(klass); device_class_set_props(dc, console_properties); device_class_set_legacy_reset(dc, console_reset); dc->vmsd = &vmstate_sclplmconsole; ec->init = console_init; ec->get_send_mask = send_mask; ec->get_receive_mask = receive_mask; ec->can_handle_event = can_handle_event; ec->read_event_data = read_event_data; ec->write_event_data = write_event_data; set_bit(DEVICE_CATEGORY_INPUT, dc->categories); } static const TypeInfo sclp_console_info = { .name = TYPE_SCLPLM_CONSOLE, .parent = TYPE_SCLP_EVENT, .instance_size = sizeof(SCLPConsoleLM), .class_init = console_class_init, .class_size = sizeof(SCLPEventClass), }; static void register_types(void) { type_register_static(&sclp_console_info); } type_init(register_types)