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