xref: /qemu/hw/char/terminal3270.c (revision d895d25a)
1b8476205SYang Chen /*
2b8476205SYang Chen  * Terminal 3270 implementation
3b8476205SYang Chen  *
4b8476205SYang Chen  * Copyright 2017 IBM Corp.
5b8476205SYang Chen  *
6b8476205SYang Chen  * Authors: Yang Chen <bjcyang@linux.vnet.ibm.com>
7b8476205SYang Chen  *          Jing Liu <liujbjl@linux.vnet.ibm.com>
8b8476205SYang Chen  *
9b8476205SYang Chen  * This work is licensed under the terms of the GNU GPL, version 2 or (at
10b8476205SYang Chen  * your option) any later version. See the COPYING file in the top-level
11b8476205SYang Chen  * directory.
12b8476205SYang Chen  */
13b8476205SYang Chen 
14b8476205SYang Chen #include "qemu/osdep.h"
152dc95b4cSJing Liu #include "qapi/error.h"
160b8fa32fSMarkus Armbruster #include "qemu/module.h"
174d43a603SMarc-André Lureau #include "chardev/char-fe.h"
18a27bd6c7SMarkus Armbruster #include "hw/qdev-properties.h"
19ce35e229SEduardo Habkost #include "hw/qdev-properties-system.h"
20b8476205SYang Chen #include "hw/s390x/3270-ccw.h"
21db1015e9SEduardo Habkost #include "qom/object.h"
22b8476205SYang Chen 
232dc95b4cSJing Liu /* Enough spaces for different window sizes. */
242dc95b4cSJing Liu #define INPUT_BUFFER_SIZE  1000
252dc95b4cSJing Liu /*
262dc95b4cSJing Liu  * 1 for header, 1024*2 for datastream, 2 for tail
272dc95b4cSJing Liu  * Reserve enough spaces for telnet IAC escape.
282dc95b4cSJing Liu  */
292dc95b4cSJing Liu #define OUTPUT_BUFFER_SIZE 2051
302dc95b4cSJing Liu 
31db1015e9SEduardo Habkost struct Terminal3270 {
32b8476205SYang Chen     EmulatedCcw3270Device cdev;
332dc95b4cSJing Liu     CharBackend chr;
342dc95b4cSJing Liu     uint8_t inv[INPUT_BUFFER_SIZE];
352dc95b4cSJing Liu     uint8_t outv[OUTPUT_BUFFER_SIZE];
362dc95b4cSJing Liu     int in_len;
372dc95b4cSJing Liu     bool handshake_done;
38b1f1103dSMarc-André Lureau     guint timer_tag;
39db1015e9SEduardo Habkost };
40db1015e9SEduardo Habkost typedef struct Terminal3270 Terminal3270;
41b8476205SYang Chen 
42b8476205SYang Chen #define TYPE_TERMINAL_3270 "x-terminal3270"
DECLARE_INSTANCE_CHECKER(Terminal3270,TERMINAL_3270,TYPE_TERMINAL_3270)438110fa1dSEduardo Habkost DECLARE_INSTANCE_CHECKER(Terminal3270, TERMINAL_3270,
448110fa1dSEduardo Habkost                          TYPE_TERMINAL_3270)
452dc95b4cSJing Liu 
462dc95b4cSJing Liu static int terminal_can_read(void *opaque)
472dc95b4cSJing Liu {
482dc95b4cSJing Liu     Terminal3270 *t = opaque;
492dc95b4cSJing Liu 
502dc95b4cSJing Liu     return INPUT_BUFFER_SIZE - t->in_len;
512dc95b4cSJing Liu }
522dc95b4cSJing Liu 
terminal_timer_cancel(Terminal3270 * t)532c716ba1SPeter Xu static void terminal_timer_cancel(Terminal3270 *t)
542c716ba1SPeter Xu {
55b1f1103dSMarc-André Lureau     if (t->timer_tag) {
56b1f1103dSMarc-André Lureau         g_source_remove(t->timer_tag);
57b1f1103dSMarc-André Lureau         t->timer_tag = 0;
582c716ba1SPeter Xu     }
592c716ba1SPeter Xu }
602c716ba1SPeter Xu 
612dc95b4cSJing Liu /*
622dc95b4cSJing Liu  * Protocol handshake done,
632dc95b4cSJing Liu  * signal guest by an unsolicited DE irq.
642dc95b4cSJing Liu  */
TN3270_handshake_done(Terminal3270 * t)652dc95b4cSJing Liu static void TN3270_handshake_done(Terminal3270 *t)
662dc95b4cSJing Liu {
672dc95b4cSJing Liu     CcwDevice *ccw_dev = CCW_DEVICE(t);
682dc95b4cSJing Liu     SubchDev *sch = ccw_dev->sch;
692dc95b4cSJing Liu 
702dc95b4cSJing Liu     t->handshake_done = true;
712dc95b4cSJing Liu     sch->curr_status.scsw.dstat = SCSW_DSTAT_DEVICE_END;
722dc95b4cSJing Liu     css_conditional_io_interrupt(sch);
732dc95b4cSJing Liu }
742dc95b4cSJing Liu 
752dc95b4cSJing Liu /*
76e65a2720SJing Liu  * Called when the interval is timeout to detect
77e65a2720SJing Liu  * if the client is still alive by Timing Mark.
78e65a2720SJing Liu  */
send_timing_mark_cb(gpointer opaque)79e65a2720SJing Liu static gboolean send_timing_mark_cb(gpointer opaque)
80e65a2720SJing Liu {
81e65a2720SJing Liu     Terminal3270 *t = opaque;
82e65a2720SJing Liu     const uint8_t timing[] = {0xff, 0xfd, 0x06};
83e65a2720SJing Liu 
84e65a2720SJing Liu     qemu_chr_fe_write_all(&t->chr, timing, sizeof(timing));
85e65a2720SJing Liu     return true;
86e65a2720SJing Liu }
87e65a2720SJing Liu 
88e65a2720SJing Liu /*
892dc95b4cSJing Liu  * Receive inbound data from socket.
902dc95b4cSJing Liu  * For data given to guest, drop the data boundary IAC, IAC_EOR.
912dc95b4cSJing Liu  * TODO:
922dc95b4cSJing Liu  * Using "Reset" key on x3270 may result multiple commands in one packet.
932dc95b4cSJing Liu  * This usually happens when the user meets a poor traffic of the network.
942dc95b4cSJing Liu  * As of now, for such case, we simply terminate the connection,
952dc95b4cSJing Liu  * and we should come back here later with a better solution.
962dc95b4cSJing Liu  */
terminal_read(void * opaque,const uint8_t * buf,int size)972dc95b4cSJing Liu static void terminal_read(void *opaque, const uint8_t *buf, int size)
982dc95b4cSJing Liu {
992dc95b4cSJing Liu     Terminal3270 *t = opaque;
1002dc95b4cSJing Liu     CcwDevice *ccw_dev = CCW_DEVICE(t);
1012dc95b4cSJing Liu     SubchDev *sch = ccw_dev->sch;
1022dc95b4cSJing Liu     int end;
1032dc95b4cSJing Liu 
1042dc95b4cSJing Liu     assert(size <= (INPUT_BUFFER_SIZE - t->in_len));
1052dc95b4cSJing Liu 
1062c716ba1SPeter Xu     terminal_timer_cancel(t);
107b1f1103dSMarc-André Lureau     t->timer_tag = g_timeout_add_seconds(600, send_timing_mark_cb, t);
1082dc95b4cSJing Liu     memcpy(&t->inv[t->in_len], buf, size);
1092dc95b4cSJing Liu     t->in_len += size;
1102dc95b4cSJing Liu     if (t->in_len < 2) {
1112dc95b4cSJing Liu         return;
1122dc95b4cSJing Liu     }
1132dc95b4cSJing Liu 
1142dc95b4cSJing Liu     if (!t->handshake_done) {
1152dc95b4cSJing Liu         /*
1162dc95b4cSJing Liu          * Receiving Terminal Type is the last step of handshake.
1172dc95b4cSJing Liu          * The data format: IAC SB Terminal-Type IS <terminal type> IAC SE
1182dc95b4cSJing Liu          * The code for Terminal-Type is 0x18, for IS is 0.
1192dc95b4cSJing Liu          * Simply check the data format and mark handshake_done.
1202dc95b4cSJing Liu          */
1212dc95b4cSJing Liu         if (t->in_len > 6 && t->inv[2] == 0x18 && t->inv[3] == 0x0 &&
1222dc95b4cSJing Liu             t->inv[t->in_len - 2] == IAC && t->inv[t->in_len - 1] == IAC_SE) {
1232dc95b4cSJing Liu             TN3270_handshake_done(t);
1242dc95b4cSJing Liu             t->in_len = 0;
1252dc95b4cSJing Liu         }
1262dc95b4cSJing Liu         return;
1272dc95b4cSJing Liu     }
1282dc95b4cSJing Liu 
1292dc95b4cSJing Liu     for (end = 0; end < t->in_len - 1; end++) {
1302dc95b4cSJing Liu         if (t->inv[end] == IAC && t->inv[end + 1] == IAC_EOR) {
1312dc95b4cSJing Liu             break;
1322dc95b4cSJing Liu         }
1332dc95b4cSJing Liu     }
1342dc95b4cSJing Liu     if (end == t->in_len - 2) {
1352dc95b4cSJing Liu         /* Data is valid for consuming. */
1362dc95b4cSJing Liu         t->in_len -= 2;
1372dc95b4cSJing Liu         sch->curr_status.scsw.dstat = SCSW_DSTAT_ATTENTION;
1382dc95b4cSJing Liu         css_conditional_io_interrupt(sch);
1392dc95b4cSJing Liu     } else if (end < t->in_len - 2) {
1402dc95b4cSJing Liu         /* "Reset" key is used. */
1412dc95b4cSJing Liu         qemu_chr_fe_disconnect(&t->chr);
1422dc95b4cSJing Liu     } else {
1432dc95b4cSJing Liu         /* Gathering data. */
1442dc95b4cSJing Liu         return;
1452dc95b4cSJing Liu     }
1462dc95b4cSJing Liu }
147b8476205SYang Chen 
chr_event(void * opaque,QEMUChrEvent event)148083b266fSPhilippe Mathieu-Daudé static void chr_event(void *opaque, QEMUChrEvent event)
1494996241aSJing Liu {
1504996241aSJing Liu     Terminal3270 *t = opaque;
1514996241aSJing Liu     CcwDevice *ccw_dev = CCW_DEVICE(t);
1524996241aSJing Liu     SubchDev *sch = ccw_dev->sch;
1534996241aSJing Liu 
1544996241aSJing Liu     /* Ensure the initial status correct, always reset them. */
1554996241aSJing Liu     t->in_len = 0;
1564996241aSJing Liu     t->handshake_done = false;
1572c716ba1SPeter Xu     terminal_timer_cancel(t);
1584996241aSJing Liu 
1594996241aSJing Liu     switch (event) {
1604996241aSJing Liu     case CHR_EVENT_OPENED:
1614996241aSJing Liu         /*
1624996241aSJing Liu          * 3270 does handshake firstly by the negotiate options in
1634996241aSJing Liu          * char-socket.c. Once qemu receives the terminal-type of the
1644996241aSJing Liu          * client, mark handshake done and trigger everything rolling again.
1654996241aSJing Liu          */
166b1f1103dSMarc-André Lureau         t->timer_tag = g_timeout_add_seconds(600, send_timing_mark_cb, t);
1674996241aSJing Liu         break;
1684996241aSJing Liu     case CHR_EVENT_CLOSED:
1694996241aSJing Liu         sch->curr_status.scsw.dstat = SCSW_DSTAT_DEVICE_END;
1704996241aSJing Liu         css_conditional_io_interrupt(sch);
1714996241aSJing Liu         break;
17275c5bb0bSPhilippe Mathieu-Daudé     case CHR_EVENT_BREAK:
17375c5bb0bSPhilippe Mathieu-Daudé     case CHR_EVENT_MUX_IN:
17475c5bb0bSPhilippe Mathieu-Daudé     case CHR_EVENT_MUX_OUT:
17575c5bb0bSPhilippe Mathieu-Daudé         /* Ignore */
17675c5bb0bSPhilippe Mathieu-Daudé         break;
1774996241aSJing Liu     }
1784996241aSJing Liu }
1794996241aSJing Liu 
terminal_init(EmulatedCcw3270Device * dev,Error ** errp)180b8476205SYang Chen static void terminal_init(EmulatedCcw3270Device *dev, Error **errp)
181b8476205SYang Chen {
1822dc95b4cSJing Liu     Terminal3270 *t = TERMINAL_3270(dev);
183b8476205SYang Chen     static bool terminal_available;
184b8476205SYang Chen 
185b8476205SYang Chen     if (terminal_available) {
186b8476205SYang Chen         error_setg(errp, "Multiple 3270 terminals are not supported.");
187b8476205SYang Chen         return;
188b8476205SYang Chen     }
189b8476205SYang Chen     terminal_available = true;
1902dc95b4cSJing Liu     qemu_chr_fe_set_handlers(&t->chr, terminal_can_read,
19181517ba3SAnton Nefedov                              terminal_read, chr_event, NULL, t, NULL, true);
192b8476205SYang Chen }
193b8476205SYang Chen 
get_cds(Terminal3270 * t)1941baa2eb0SHalil Pasic static inline CcwDataStream *get_cds(Terminal3270 *t)
1951baa2eb0SHalil Pasic {
1961baa2eb0SHalil Pasic     return &(CCW_DEVICE(&t->cdev)->sch->cds);
1971baa2eb0SHalil Pasic }
1981baa2eb0SHalil Pasic 
read_payload_3270(EmulatedCcw3270Device * dev)1991baa2eb0SHalil Pasic static int read_payload_3270(EmulatedCcw3270Device *dev)
2002dc95b4cSJing Liu {
2012dc95b4cSJing Liu     Terminal3270 *t = TERMINAL_3270(dev);
2022dc95b4cSJing Liu     int len;
203*d895d25aSPierre Morel     int ret;
2042dc95b4cSJing Liu 
2051baa2eb0SHalil Pasic     len = MIN(ccw_dstream_avail(get_cds(t)), t->in_len);
206*d895d25aSPierre Morel     ret = ccw_dstream_write_buf(get_cds(t), t->inv, len);
207*d895d25aSPierre Morel     if (ret < 0) {
208*d895d25aSPierre Morel         return ret;
209*d895d25aSPierre Morel     }
2102dc95b4cSJing Liu     t->in_len -= len;
2112dc95b4cSJing Liu 
2122dc95b4cSJing Liu     return len;
2132dc95b4cSJing Liu }
2142dc95b4cSJing Liu 
2152dc95b4cSJing Liu /* TN3270 uses binary transmission, which needs escape IAC to IAC IAC */
insert_IAC_escape_char(uint8_t * outv,int out_len)2162dc95b4cSJing Liu static int insert_IAC_escape_char(uint8_t *outv, int out_len)
2172dc95b4cSJing Liu {
2182dc95b4cSJing Liu     int IAC_num = 0, new_out_len, i, j;
2192dc95b4cSJing Liu 
2202dc95b4cSJing Liu     for (i = 0; i < out_len; i++) {
2212dc95b4cSJing Liu         if (outv[i] == IAC) {
2222dc95b4cSJing Liu             IAC_num++;
2232dc95b4cSJing Liu         }
2242dc95b4cSJing Liu     }
2252dc95b4cSJing Liu     if (IAC_num == 0) {
2262dc95b4cSJing Liu         return out_len;
2272dc95b4cSJing Liu     }
2282dc95b4cSJing Liu     new_out_len = out_len + IAC_num;
2292dc95b4cSJing Liu     for (i = out_len - 1, j = new_out_len - 1; j > i && i >= 0; i--, j--) {
2302dc95b4cSJing Liu         outv[j] = outv[i];
2312dc95b4cSJing Liu         if (outv[i] == IAC) {
2322dc95b4cSJing Liu             outv[--j] = IAC;
2332dc95b4cSJing Liu         }
2342dc95b4cSJing Liu     }
2352dc95b4cSJing Liu     return new_out_len;
2362dc95b4cSJing Liu }
2372dc95b4cSJing Liu 
2382dc95b4cSJing Liu /*
2392dc95b4cSJing Liu  * Write 3270 outbound to socket.
2402dc95b4cSJing Liu  * Return the count of 3270 data field if succeeded, zero if failed.
2412dc95b4cSJing Liu  */
write_payload_3270(EmulatedCcw3270Device * dev,uint8_t cmd)2421baa2eb0SHalil Pasic static int write_payload_3270(EmulatedCcw3270Device *dev, uint8_t cmd)
2432dc95b4cSJing Liu {
2442dc95b4cSJing Liu     Terminal3270 *t = TERMINAL_3270(dev);
2452dc95b4cSJing Liu     int retval = 0;
2461baa2eb0SHalil Pasic     int count = ccw_dstream_avail(get_cds(t));
24717ec9921SHalil Pasic     int bound = (OUTPUT_BUFFER_SIZE - 3) / 2;
24817ec9921SHalil Pasic     int len = MIN(count, bound);
24917ec9921SHalil Pasic     int out_len = 0;
2502dc95b4cSJing Liu 
2512dc95b4cSJing Liu     if (!t->handshake_done) {
252e65a2720SJing Liu         if (!(t->outv[0] == IAC && t->outv[1] != IAC)) {
2532dc95b4cSJing Liu             /*
2542dc95b4cSJing Liu              * Before having finished 3270 negotiation,
255e65a2720SJing Liu              * sending outbound data except protocol options is prohibited.
2562dc95b4cSJing Liu              */
2572dc95b4cSJing Liu             return 0;
2582dc95b4cSJing Liu         }
259e65a2720SJing Liu     }
26030650701SAnton Nefedov     if (!qemu_chr_fe_backend_connected(&t->chr)) {
2612dc95b4cSJing Liu         /* We just say we consumed all data if there's no backend. */
2622dc95b4cSJing Liu         return count;
2632dc95b4cSJing Liu     }
2642dc95b4cSJing Liu 
26517ec9921SHalil Pasic     t->outv[out_len++] = cmd;
26617ec9921SHalil Pasic     do {
267*d895d25aSPierre Morel         retval = ccw_dstream_read_buf(get_cds(t), &t->outv[out_len], len);
268*d895d25aSPierre Morel         if (retval < 0) {
269*d895d25aSPierre Morel             return retval;
270*d895d25aSPierre Morel         }
27117ec9921SHalil Pasic         count = ccw_dstream_avail(get_cds(t));
27217ec9921SHalil Pasic         out_len += len;
2732dc95b4cSJing Liu 
27417ec9921SHalil Pasic         out_len = insert_IAC_escape_char(t->outv, out_len);
27517ec9921SHalil Pasic         if (!count) {
27617ec9921SHalil Pasic             t->outv[out_len++] = IAC;
27717ec9921SHalil Pasic             t->outv[out_len++] = IAC_EOR;
27817ec9921SHalil Pasic         }
27917ec9921SHalil Pasic         retval = qemu_chr_fe_write_all(&t->chr, t->outv, out_len);
28017ec9921SHalil Pasic         len = MIN(count, bound);
28117ec9921SHalil Pasic         out_len = 0;
28217ec9921SHalil Pasic     } while (len && retval >= 0);
28317ec9921SHalil Pasic     return (retval <= 0) ? 0 : get_cds(t)->count;
2842dc95b4cSJing Liu }
2852dc95b4cSJing Liu 
2862dc95b4cSJing Liu static Property terminal_properties[] = {
2872dc95b4cSJing Liu     DEFINE_PROP_CHR("chardev", Terminal3270, chr),
2882dc95b4cSJing Liu     DEFINE_PROP_END_OF_LIST(),
2892dc95b4cSJing Liu };
2902dc95b4cSJing Liu 
2919e8b3009SJing Liu static const VMStateDescription terminal3270_vmstate = {
2929e8b3009SJing Liu     .name = TYPE_TERMINAL_3270,
2939e8b3009SJing Liu     .unmigratable = 1,
2949e8b3009SJing Liu };
2959e8b3009SJing Liu 
terminal_class_init(ObjectClass * klass,void * data)296b8476205SYang Chen static void terminal_class_init(ObjectClass *klass, void *data)
297b8476205SYang Chen {
2982dc95b4cSJing Liu     DeviceClass *dc = DEVICE_CLASS(klass);
299b8476205SYang Chen     EmulatedCcw3270Class *ck = EMULATED_CCW_3270_CLASS(klass);
300b8476205SYang Chen 
3014f67d30bSMarc-André Lureau     device_class_set_props(dc, terminal_properties);
3029e8b3009SJing Liu     dc->vmsd = &terminal3270_vmstate;
303b8476205SYang Chen     ck->init = terminal_init;
3042dc95b4cSJing Liu     ck->read_payload_3270 = read_payload_3270;
3052dc95b4cSJing Liu     ck->write_payload_3270 = write_payload_3270;
306b8476205SYang Chen }
307b8476205SYang Chen 
308b8476205SYang Chen static const TypeInfo ccw_terminal_info = {
309b8476205SYang Chen     .name = TYPE_TERMINAL_3270,
310b8476205SYang Chen     .parent = TYPE_EMULATED_CCW_3270,
311b8476205SYang Chen     .instance_size = sizeof(Terminal3270),
312b8476205SYang Chen     .class_init = terminal_class_init,
313b8476205SYang Chen     .class_size = sizeof(EmulatedCcw3270Class),
314b8476205SYang Chen };
315b8476205SYang Chen 
register_types(void)316b8476205SYang Chen static void register_types(void)
317b8476205SYang Chen {
318b8476205SYang Chen     type_register_static(&ccw_terminal_info);
319b8476205SYang Chen }
320b8476205SYang Chen 
321b8476205SYang Chen type_init(register_types)
322