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