| 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/core/qdev-properties.h" |
| 19 | #include "hw/core/qdev-properties-system.h" |
| 20 | #include "hw/s390x/3270-ccw.h" |
| 21 | #include "qom/object.h" |
| 22 | |
| 23 | /* Enough spaces for different window sizes. */ |
| 24 | #define INPUT_BUFFER_SIZE 1000 |
| 25 | /* |
| 26 | * 1 for header, 1024*2 for datastream, 2 for tail |
| 27 | * Reserve enough spaces for telnet IAC escape. |
| 28 | */ |
| 29 | #define OUTPUT_BUFFER_SIZE 2051 |
| 30 | |
| 31 | struct Terminal3270 { |
| 32 | EmulatedCcw3270Device cdev; |
| 33 | CharFrontend chr; |
| 34 | uint8_t inv[INPUT_BUFFER_SIZE]; |
| 35 | uint8_t outv[OUTPUT_BUFFER_SIZE]; |
| 36 | int in_len; |
| 37 | bool handshake_done; |
| 38 | guint timer_tag; |
| 39 | }; |
| 40 | typedef struct Terminal3270 Terminal3270; |
| 41 | |
| 42 | #define TYPE_TERMINAL_3270 "x-terminal3270" |
| 43 | DECLARE_INSTANCE_CHECKER(Terminal3270, TERMINAL_3270, |
| 44 | TYPE_TERMINAL_3270) |
| 45 | |
| 46 | static int terminal_can_read(void *opaque) |
| 47 | { |
| 48 | Terminal3270 *t = opaque; |
| 49 | |
| 50 | return INPUT_BUFFER_SIZE - t->in_len; |
| 51 | } |
| 52 | |
| 53 | static void terminal_timer_cancel(Terminal3270 *t) |
| 54 | { |
| 55 | g_clear_handle_id(&t->timer_tag, g_source_remove); |
| 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, QEMUChrEvent 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 | case CHR_EVENT_BREAK: |
| 170 | case CHR_EVENT_MUX_IN: |
| 171 | case CHR_EVENT_MUX_OUT: |
| 172 | /* Ignore */ |
| 173 | break; |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | static void terminal_init(EmulatedCcw3270Device *dev, Error **errp) |
| 178 | { |
| 179 | Terminal3270 *t = TERMINAL_3270(dev); |
| 180 | static bool terminal_available; |
| 181 | |
| 182 | if (terminal_available) { |
| 183 | error_setg(errp, "Multiple 3270 terminals are not supported."); |
| 184 | return; |
| 185 | } |
| 186 | terminal_available = true; |
| 187 | qemu_chr_fe_set_handlers(&t->chr, terminal_can_read, |
| 188 | terminal_read, chr_event, NULL, t, NULL, true); |
| 189 | } |
| 190 | |
| 191 | static inline CcwDataStream *get_cds(Terminal3270 *t) |
| 192 | { |
| 193 | return &(CCW_DEVICE(&t->cdev)->sch->cds); |
| 194 | } |
| 195 | |
| 196 | static int read_payload_3270(EmulatedCcw3270Device *dev) |
| 197 | { |
| 198 | Terminal3270 *t = TERMINAL_3270(dev); |
| 199 | int len; |
| 200 | int ret; |
| 201 | |
| 202 | len = MIN(ccw_dstream_avail(get_cds(t)), t->in_len); |
| 203 | ret = ccw_dstream_write_buf(get_cds(t), t->inv, len); |
| 204 | if (ret < 0) { |
| 205 | return ret; |
| 206 | } |
| 207 | t->in_len -= len; |
| 208 | |
| 209 | return len; |
| 210 | } |
| 211 | |
| 212 | /* TN3270 uses binary transmission, which needs escape IAC to IAC IAC */ |
| 213 | static int insert_IAC_escape_char(uint8_t *outv, int out_len) |
| 214 | { |
| 215 | int IAC_num = 0, new_out_len, i, j; |
| 216 | |
| 217 | for (i = 0; i < out_len; i++) { |
| 218 | if (outv[i] == IAC) { |
| 219 | IAC_num++; |
| 220 | } |
| 221 | } |
| 222 | if (IAC_num == 0) { |
| 223 | return out_len; |
| 224 | } |
| 225 | new_out_len = out_len + IAC_num; |
| 226 | for (i = out_len - 1, j = new_out_len - 1; j > i && i >= 0; i--, j--) { |
| 227 | outv[j] = outv[i]; |
| 228 | if (outv[i] == IAC) { |
| 229 | outv[--j] = IAC; |
| 230 | } |
| 231 | } |
| 232 | return new_out_len; |
| 233 | } |
| 234 | |
| 235 | /* |
| 236 | * Write 3270 outbound to socket. |
| 237 | * Return the count of 3270 data field if succeeded, zero if failed. |
| 238 | */ |
| 239 | static int write_payload_3270(EmulatedCcw3270Device *dev, uint8_t cmd) |
| 240 | { |
| 241 | Terminal3270 *t = TERMINAL_3270(dev); |
| 242 | int retval = 0; |
| 243 | int count = ccw_dstream_avail(get_cds(t)); |
| 244 | int bound = (OUTPUT_BUFFER_SIZE - 3) / 2; |
| 245 | int len = MIN(count, bound); |
| 246 | int out_len = 0; |
| 247 | |
| 248 | if (!t->handshake_done) { |
| 249 | if (!(t->outv[0] == IAC && t->outv[1] != IAC)) { |
| 250 | /* |
| 251 | * Before having finished 3270 negotiation, |
| 252 | * sending outbound data except protocol options is prohibited. |
| 253 | */ |
| 254 | return 0; |
| 255 | } |
| 256 | } |
| 257 | if (!qemu_chr_fe_backend_connected(&t->chr)) { |
| 258 | /* We just say we consumed all data if there's no backend. */ |
| 259 | return count; |
| 260 | } |
| 261 | |
| 262 | t->outv[out_len++] = cmd; |
| 263 | do { |
| 264 | retval = ccw_dstream_read_buf(get_cds(t), &t->outv[out_len], len); |
| 265 | if (retval < 0) { |
| 266 | return retval; |
| 267 | } |
| 268 | count = ccw_dstream_avail(get_cds(t)); |
| 269 | out_len += len; |
| 270 | |
| 271 | out_len = insert_IAC_escape_char(t->outv, out_len); |
| 272 | if (!count) { |
| 273 | t->outv[out_len++] = IAC; |
| 274 | t->outv[out_len++] = IAC_EOR; |
| 275 | } |
| 276 | retval = qemu_chr_fe_write_all(&t->chr, t->outv, out_len); |
| 277 | len = MIN(count, bound); |
| 278 | out_len = 0; |
| 279 | } while (len && retval >= 0); |
| 280 | return (retval <= 0) ? 0 : get_cds(t)->count; |
| 281 | } |
| 282 | |
| 283 | static const Property terminal_properties[] = { |
| 284 | DEFINE_PROP_CHR("chardev", Terminal3270, chr), |
| 285 | }; |
| 286 | |
| 287 | static const VMStateDescription terminal3270_vmstate = { |
| 288 | .name = TYPE_TERMINAL_3270, |
| 289 | .unmigratable = 1, |
| 290 | }; |
| 291 | |
| 292 | static void terminal_class_init(ObjectClass *klass, const void *data) |
| 293 | { |
| 294 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 295 | EmulatedCcw3270Class *ck = EMULATED_CCW_3270_CLASS(klass); |
| 296 | |
| 297 | device_class_set_props(dc, terminal_properties); |
| 298 | dc->vmsd = &terminal3270_vmstate; |
| 299 | ck->init = terminal_init; |
| 300 | ck->read_payload_3270 = read_payload_3270; |
| 301 | ck->write_payload_3270 = write_payload_3270; |
| 302 | } |
| 303 | |
| 304 | static const TypeInfo ccw_terminal_info = { |
| 305 | .name = TYPE_TERMINAL_3270, |
| 306 | .parent = TYPE_EMULATED_CCW_3270, |
| 307 | .instance_size = sizeof(Terminal3270), |
| 308 | .class_init = terminal_class_init, |
| 309 | .class_size = sizeof(EmulatedCcw3270Class), |
| 310 | }; |
| 311 | |
| 312 | static void register_types(void) |
| 313 | { |
| 314 | type_register_static(&ccw_terminal_info); |
| 315 | } |
| 316 | |
| 317 | type_init(register_types) |