qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

terminal3270.c (8928B)


      1 /*
      2  * Terminal 3270 implementation
      3  *
      4  * Copyright 2017 IBM Corp.
      5  *
      6  * Authors: Yang Chen <bjcyang@linux.vnet.ibm.com>
      7  *          Jing Liu <liujbjl@linux.vnet.ibm.com>
      8  *
      9  * This work is licensed under the terms of the GNU GPL, version 2 or (at
     10  * your option) any later version. See the COPYING file in the top-level
     11  * directory.
     12  */
     13 
     14 #include "qemu/osdep.h"
     15 #include "qapi/error.h"
     16 #include "qemu/module.h"
     17 #include "chardev/char-fe.h"
     18 #include "hw/qdev-properties.h"
     19 #include "hw/qdev-properties-system.h"
     20 #include "hw/s390x/3270-ccw.h"
     21 #include "qom/object.h"
     22 
     23 /* Enough spaces for different window sizes. */
     24 #define INPUT_BUFFER_SIZE  1000
     25 /*
     26  * 1 for header, 1024*2 for datastream, 2 for tail
     27  * Reserve enough spaces for telnet IAC escape.
     28  */
     29 #define OUTPUT_BUFFER_SIZE 2051
     30 
     31 struct Terminal3270 {
     32     EmulatedCcw3270Device cdev;
     33     CharBackend chr;
     34     uint8_t inv[INPUT_BUFFER_SIZE];
     35     uint8_t outv[OUTPUT_BUFFER_SIZE];
     36     int in_len;
     37     bool handshake_done;
     38     guint timer_tag;
     39 };
     40 typedef struct Terminal3270 Terminal3270;
     41 
     42 #define TYPE_TERMINAL_3270 "x-terminal3270"
     43 DECLARE_INSTANCE_CHECKER(Terminal3270, TERMINAL_3270,
     44                          TYPE_TERMINAL_3270)
     45 
     46 static int terminal_can_read(void *opaque)
     47 {
     48     Terminal3270 *t = opaque;
     49 
     50     return INPUT_BUFFER_SIZE - t->in_len;
     51 }
     52 
     53 static void terminal_timer_cancel(Terminal3270 *t)
     54 {
     55     if (t->timer_tag) {
     56         g_source_remove(t->timer_tag);
     57         t->timer_tag = 0;
     58     }
     59 }
     60 
     61 /*
     62  * Protocol handshake done,
     63  * signal guest by an unsolicited DE irq.
     64  */
     65 static void TN3270_handshake_done(Terminal3270 *t)
     66 {
     67     CcwDevice *ccw_dev = CCW_DEVICE(t);
     68     SubchDev *sch = ccw_dev->sch;
     69 
     70     t->handshake_done = true;
     71     sch->curr_status.scsw.dstat = SCSW_DSTAT_DEVICE_END;
     72     css_conditional_io_interrupt(sch);
     73 }
     74 
     75 /*
     76  * Called when the interval is timeout to detect
     77  * if the client is still alive by Timing Mark.
     78  */
     79 static gboolean send_timing_mark_cb(gpointer opaque)
     80 {
     81     Terminal3270 *t = opaque;
     82     const uint8_t timing[] = {0xff, 0xfd, 0x06};
     83 
     84     qemu_chr_fe_write_all(&t->chr, timing, sizeof(timing));
     85     return true;
     86 }
     87 
     88 /*
     89  * Receive inbound data from socket.
     90  * For data given to guest, drop the data boundary IAC, IAC_EOR.
     91  * TODO:
     92  * Using "Reset" key on x3270 may result multiple commands in one packet.
     93  * This usually happens when the user meets a poor traffic of the network.
     94  * As of now, for such case, we simply terminate the connection,
     95  * and we should come back here later with a better solution.
     96  */
     97 static void terminal_read(void *opaque, const uint8_t *buf, int size)
     98 {
     99     Terminal3270 *t = opaque;
    100     CcwDevice *ccw_dev = CCW_DEVICE(t);
    101     SubchDev *sch = ccw_dev->sch;
    102     int end;
    103 
    104     assert(size <= (INPUT_BUFFER_SIZE - t->in_len));
    105 
    106     terminal_timer_cancel(t);
    107     t->timer_tag = g_timeout_add_seconds(600, send_timing_mark_cb, t);
    108     memcpy(&t->inv[t->in_len], buf, size);
    109     t->in_len += size;
    110     if (t->in_len < 2) {
    111         return;
    112     }
    113 
    114     if (!t->handshake_done) {
    115         /*
    116          * Receiving Terminal Type is the last step of handshake.
    117          * The data format: IAC SB Terminal-Type IS <terminal type> IAC SE
    118          * The code for Terminal-Type is 0x18, for IS is 0.
    119          * Simply check the data format and mark handshake_done.
    120          */
    121         if (t->in_len > 6 && t->inv[2] == 0x18 && t->inv[3] == 0x0 &&
    122             t->inv[t->in_len - 2] == IAC && t->inv[t->in_len - 1] == IAC_SE) {
    123             TN3270_handshake_done(t);
    124             t->in_len = 0;
    125         }
    126         return;
    127     }
    128 
    129     for (end = 0; end < t->in_len - 1; end++) {
    130         if (t->inv[end] == IAC && t->inv[end + 1] == IAC_EOR) {
    131             break;
    132         }
    133     }
    134     if (end == t->in_len - 2) {
    135         /* Data is valid for consuming. */
    136         t->in_len -= 2;
    137         sch->curr_status.scsw.dstat = SCSW_DSTAT_ATTENTION;
    138         css_conditional_io_interrupt(sch);
    139     } else if (end < t->in_len - 2) {
    140         /* "Reset" key is used. */
    141         qemu_chr_fe_disconnect(&t->chr);
    142     } else {
    143         /* Gathering data. */
    144         return;
    145     }
    146 }
    147 
    148 static void chr_event(void *opaque, QEMUChrEvent event)
    149 {
    150     Terminal3270 *t = opaque;
    151     CcwDevice *ccw_dev = CCW_DEVICE(t);
    152     SubchDev *sch = ccw_dev->sch;
    153 
    154     /* Ensure the initial status correct, always reset them. */
    155     t->in_len = 0;
    156     t->handshake_done = false;
    157     terminal_timer_cancel(t);
    158 
    159     switch (event) {
    160     case CHR_EVENT_OPENED:
    161         /*
    162          * 3270 does handshake firstly by the negotiate options in
    163          * char-socket.c. Once qemu receives the terminal-type of the
    164          * client, mark handshake done and trigger everything rolling again.
    165          */
    166         t->timer_tag = g_timeout_add_seconds(600, send_timing_mark_cb, t);
    167         break;
    168     case CHR_EVENT_CLOSED:
    169         sch->curr_status.scsw.dstat = SCSW_DSTAT_DEVICE_END;
    170         css_conditional_io_interrupt(sch);
    171         break;
    172     case CHR_EVENT_BREAK:
    173     case CHR_EVENT_MUX_IN:
    174     case CHR_EVENT_MUX_OUT:
    175         /* Ignore */
    176         break;
    177     }
    178 }
    179 
    180 static void terminal_init(EmulatedCcw3270Device *dev, Error **errp)
    181 {
    182     Terminal3270 *t = TERMINAL_3270(dev);
    183     static bool terminal_available;
    184 
    185     if (terminal_available) {
    186         error_setg(errp, "Multiple 3270 terminals are not supported.");
    187         return;
    188     }
    189     terminal_available = true;
    190     qemu_chr_fe_set_handlers(&t->chr, terminal_can_read,
    191                              terminal_read, chr_event, NULL, t, NULL, true);
    192 }
    193 
    194 static inline CcwDataStream *get_cds(Terminal3270 *t)
    195 {
    196     return &(CCW_DEVICE(&t->cdev)->sch->cds);
    197 }
    198 
    199 static int read_payload_3270(EmulatedCcw3270Device *dev)
    200 {
    201     Terminal3270 *t = TERMINAL_3270(dev);
    202     int len;
    203     int ret;
    204 
    205     len = MIN(ccw_dstream_avail(get_cds(t)), t->in_len);
    206     ret = ccw_dstream_write_buf(get_cds(t), t->inv, len);
    207     if (ret < 0) {
    208         return ret;
    209     }
    210     t->in_len -= len;
    211 
    212     return len;
    213 }
    214 
    215 /* TN3270 uses binary transmission, which needs escape IAC to IAC IAC */
    216 static int insert_IAC_escape_char(uint8_t *outv, int out_len)
    217 {
    218     int IAC_num = 0, new_out_len, i, j;
    219 
    220     for (i = 0; i < out_len; i++) {
    221         if (outv[i] == IAC) {
    222             IAC_num++;
    223         }
    224     }
    225     if (IAC_num == 0) {
    226         return out_len;
    227     }
    228     new_out_len = out_len + IAC_num;
    229     for (i = out_len - 1, j = new_out_len - 1; j > i && i >= 0; i--, j--) {
    230         outv[j] = outv[i];
    231         if (outv[i] == IAC) {
    232             outv[--j] = IAC;
    233         }
    234     }
    235     return new_out_len;
    236 }
    237 
    238 /*
    239  * Write 3270 outbound to socket.
    240  * Return the count of 3270 data field if succeeded, zero if failed.
    241  */
    242 static int write_payload_3270(EmulatedCcw3270Device *dev, uint8_t cmd)
    243 {
    244     Terminal3270 *t = TERMINAL_3270(dev);
    245     int retval = 0;
    246     int count = ccw_dstream_avail(get_cds(t));
    247     int bound = (OUTPUT_BUFFER_SIZE - 3) / 2;
    248     int len = MIN(count, bound);
    249     int out_len = 0;
    250 
    251     if (!t->handshake_done) {
    252         if (!(t->outv[0] == IAC && t->outv[1] != IAC)) {
    253             /*
    254              * Before having finished 3270 negotiation,
    255              * sending outbound data except protocol options is prohibited.
    256              */
    257             return 0;
    258         }
    259     }
    260     if (!qemu_chr_fe_backend_connected(&t->chr)) {
    261         /* We just say we consumed all data if there's no backend. */
    262         return count;
    263     }
    264 
    265     t->outv[out_len++] = cmd;
    266     do {
    267         retval = ccw_dstream_read_buf(get_cds(t), &t->outv[out_len], len);
    268         if (retval < 0) {
    269             return retval;
    270         }
    271         count = ccw_dstream_avail(get_cds(t));
    272         out_len += len;
    273 
    274         out_len = insert_IAC_escape_char(t->outv, out_len);
    275         if (!count) {
    276             t->outv[out_len++] = IAC;
    277             t->outv[out_len++] = IAC_EOR;
    278         }
    279         retval = qemu_chr_fe_write_all(&t->chr, t->outv, out_len);
    280         len = MIN(count, bound);
    281         out_len = 0;
    282     } while (len && retval >= 0);
    283     return (retval <= 0) ? 0 : get_cds(t)->count;
    284 }
    285 
    286 static Property terminal_properties[] = {
    287     DEFINE_PROP_CHR("chardev", Terminal3270, chr),
    288     DEFINE_PROP_END_OF_LIST(),
    289 };
    290 
    291 static const VMStateDescription terminal3270_vmstate = {
    292     .name = TYPE_TERMINAL_3270,
    293     .unmigratable = 1,
    294 };
    295 
    296 static void terminal_class_init(ObjectClass *klass, void *data)
    297 {
    298     DeviceClass *dc = DEVICE_CLASS(klass);
    299     EmulatedCcw3270Class *ck = EMULATED_CCW_3270_CLASS(klass);
    300 
    301     device_class_set_props(dc, terminal_properties);
    302     dc->vmsd = &terminal3270_vmstate;
    303     ck->init = terminal_init;
    304     ck->read_payload_3270 = read_payload_3270;
    305     ck->write_payload_3270 = write_payload_3270;
    306 }
    307 
    308 static const TypeInfo ccw_terminal_info = {
    309     .name = TYPE_TERMINAL_3270,
    310     .parent = TYPE_EMULATED_CCW_3270,
    311     .instance_size = sizeof(Terminal3270),
    312     .class_init = terminal_class_init,
    313     .class_size = sizeof(EmulatedCcw3270Class),
    314 };
    315 
    316 static void register_types(void)
    317 {
    318     type_register_static(&ccw_terminal_info);
    319 }
    320 
    321 type_init(register_types)