/* * 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 "hw/qdev.h" #include "qemu/thread.h" #include "qemu/error-report.h" #include "sysemu/char.h" #include "hw/s390x/sclp.h" #include "hw/s390x/event-facility.h" #include "hw/s390x/ebcdic.h" #define SIZE_BUFFER 4096 #define NEWLINE "\n" typedef struct OprtnsCommand { EventBufferHeader header; MDMSU message_unit; char data[0]; } QEMU_PACKED OprtnsCommand; /* max size for line-mode data in 4K SCCB page */ #define SIZE_CONSOLE_BUFFER (SCCB_DATA_LEN - sizeof(OprtnsCommand)) typedef struct SCLPConsoleLM { SCLPEvent event; CharDriverState *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]; qemu_irq irq_console_read; } SCLPConsoleLM; /* * 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 */ static int chr_can_read(void *opaque) { SCLPConsoleLM *scon = opaque; if (scon->event.event_pending) { return 0; } else if (SIZE_CONSOLE_BUFFER - scon->length) { return 1; } return 0; } static void receive_from_chr_layer(SCLPConsoleLM *scon, const uint8_t *buf, int size) { assert(size == 1); if (*buf == '\r' || *buf == '\n') { scon->event.event_pending = true; return; } scon->buf[scon->length] = *buf; scon->length += 1; if (scon->echo) { qemu_chr_fe_write(scon->chr, buf, size); } } /* * Send data from a char device over to the guest */ static void chr_read(void *opaque, const uint8_t *buf, int size) { SCLPConsoleLM *scon = opaque; receive_from_chr_layer(scon, buf, size); if (scon->event.event_pending) { /* trigger SCLP read operation */ qemu_irq_raise(scon->irq_console_read); } } /* 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 unsigned int send_mask(void) { return SCLP_EVENT_MASK_OP_CMD | SCLP_EVENT_MASK_PMSGCMD; } static unsigned int 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 = DO_UPCAST(SCLPConsoleLM, event, 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; 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) { int ret = 0; const uint8_t *buf_offset; SCLPConsoleLM *scon = DO_UPCAST(SCLPConsoleLM, event, event); if (!scon->chr) { /* If there's no backend, we can just say we consumed all data. */ return len; } buf_offset = buf; while (len > 0) { ret = qemu_chr_fe_write(scon->chr, buf, len); if (ret == 0) { /* a pty doesn't seem to be connected - no error */ len = 0; } else if (ret == -EAGAIN || (ret > 0 && ret < len)) { len -= ret; buf_offset += ret; } else { len = 0; } } return ret; } 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 = DO_UPCAST(SCLPConsoleLM, event, 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; } static void trigger_console_data(void *opaque, int n, int level) { sclp_service_interrupt(0); } /* functions for live migration */ static const VMStateDescription vmstate_sclplmconsole = { .name = "sclplmconsole", .version_id = 0, .minimum_version_id = 0, .minimum_version_id_old = 0, .fields = (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 = DO_UPCAST(SCLPConsoleLM, event, event); if (console_available) { error_report("Multiple line-mode operator consoles are not supported"); return -1; } console_available = true; if (scon->chr) { qemu_chr_add_handlers(scon->chr, chr_can_read, chr_read, NULL, scon); } scon->irq_console_read = *qemu_allocate_irqs(trigger_console_data, NULL, 1); return 0; } static int console_exit(SCLPEvent *event) { return 0; } static void console_reset(DeviceState *dev) { SCLPEvent *event = SCLP_EVENT(dev); SCLPConsoleLM *scon = DO_UPCAST(SCLPConsoleLM, event, event); event->event_pending = false; scon->length = 0; scon->write_errors = 0; } static 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), DEFINE_PROP_END_OF_LIST(), }; static void console_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); SCLPEventClass *ec = SCLP_EVENT_CLASS(klass); dc->props = console_properties; dc->reset = console_reset; dc->vmsd = &vmstate_sclplmconsole; ec->init = console_init; ec->exit = console_exit; 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; } static const TypeInfo sclp_console_info = { .name = "sclplmconsole", .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)