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