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