Gerd Hoffmann | d6728f3 | 2017-09-18 10:47:23 +0200 | [diff] [blame] | 1 | // serial console support |
| 2 | // |
| 3 | // Copyright (C) 2016 Gerd Hoffmann <kraxel@redhat.com> |
| 4 | // |
| 5 | // This file may be distributed under the terms of the GNU LGPLv3 license. |
| 6 | |
| 7 | #include "biosvar.h" // SET_BDA |
| 8 | #include "bregs.h" // struct bregs |
| 9 | #include "stacks.h" // yield |
| 10 | #include "output.h" // dprintf |
| 11 | #include "util.h" // irqtimer_calc_ticks |
| 12 | #include "string.h" // memcpy |
| 13 | #include "romfile.h" // romfile_loadint |
| 14 | #include "hw/serialio.h" // SEROFF_IER |
| 15 | #include "cp437.h" |
| 16 | |
| 17 | static u8 video_rows(void) |
| 18 | { |
| 19 | return GET_BDA(video_rows)+1; |
| 20 | } |
| 21 | |
| 22 | static u8 video_cols(void) |
| 23 | { |
| 24 | return GET_BDA(video_cols); |
| 25 | } |
| 26 | |
| 27 | static u8 cursor_pos_col(void) |
| 28 | { |
| 29 | u16 pos = GET_BDA(cursor_pos[0]); |
| 30 | return pos & 0xff; |
| 31 | } |
| 32 | |
| 33 | static u8 cursor_pos_row(void) |
| 34 | { |
| 35 | u16 pos = GET_BDA(cursor_pos[0]); |
| 36 | return (pos >> 8) & 0xff; |
| 37 | } |
| 38 | |
| 39 | static void cursor_pos_set(u8 row, u8 col) |
| 40 | { |
| 41 | u16 pos = ((u16)row << 8) | col; |
| 42 | SET_BDA(cursor_pos[0], pos); |
| 43 | } |
| 44 | |
| 45 | /**************************************************************** |
| 46 | * serial console output |
| 47 | ****************************************************************/ |
| 48 | |
| 49 | VARLOW u16 sercon_port; |
| 50 | VARLOW u8 sercon_split; |
| 51 | VARLOW u8 sercon_enable; |
| 52 | VARFSEG struct segoff_s sercon_real_vga_handler; |
| 53 | |
| 54 | /* |
| 55 | * We have a small output buffer here, for lazy output. That allows |
| 56 | * to avoid a whole bunch of control sequences for pointless cursor |
| 57 | * moves, so when logging the output it'll be *alot* less cluttered. |
| 58 | * |
| 59 | * sercon_char/attr is the actual output buffer. |
| 60 | * sercon_attr_last is the most recent attribute sent to the terminal. |
| 61 | * sercon_col_last is the most recent column sent to the terminal. |
| 62 | * sercon_row_last is the most recent row sent to the terminal. |
| 63 | */ |
| 64 | VARLOW u8 sercon_attr_last; |
| 65 | VARLOW u8 sercon_col_last; |
| 66 | VARLOW u8 sercon_row_last; |
| 67 | VARLOW u8 sercon_char; |
| 68 | VARLOW u8 sercon_attr = 0x07; |
| 69 | |
| 70 | static VAR16 u8 sercon_cmap[8] = { '0', '4', '2', '6', '1', '5', '3', '7' }; |
| 71 | |
| 72 | static int sercon_splitmode(void) |
| 73 | { |
| 74 | return GET_LOW(sercon_split); |
| 75 | } |
| 76 | |
| 77 | static void sercon_putchar(u8 chr) |
| 78 | { |
| 79 | u16 addr = GET_LOW(sercon_port); |
| 80 | u32 end = irqtimer_calc_ticks(0x0a); |
| 81 | |
| 82 | #if 0 |
| 83 | /* for visual control sequence debugging */ |
| 84 | if (chr == '\x1b') |
| 85 | chr = '*'; |
| 86 | #endif |
| 87 | |
| 88 | for (;;) { |
| 89 | u8 lsr = inb(addr+SEROFF_LSR); |
| 90 | if ((lsr & 0x60) == 0x60) { |
| 91 | // Success - can write data |
| 92 | outb(chr, addr+SEROFF_DATA); |
| 93 | break; |
| 94 | } |
| 95 | if (irqtimer_check(end)) { |
| 96 | break; |
| 97 | } |
| 98 | yield(); |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | static void sercon_term_reset(void) |
| 103 | { |
| 104 | sercon_putchar('\x1b'); |
| 105 | sercon_putchar('c'); |
| 106 | } |
| 107 | |
| 108 | static void sercon_term_clear_screen(void) |
| 109 | { |
| 110 | sercon_putchar('\x1b'); |
| 111 | sercon_putchar('['); |
| 112 | sercon_putchar('2'); |
| 113 | sercon_putchar('J'); |
| 114 | } |
| 115 | |
| 116 | static void sercon_term_no_linewrap(void) |
| 117 | { |
| 118 | sercon_putchar('\x1b'); |
| 119 | sercon_putchar('['); |
| 120 | sercon_putchar('?'); |
| 121 | sercon_putchar('7'); |
| 122 | sercon_putchar('l'); |
| 123 | } |
| 124 | |
| 125 | static void sercon_term_cursor_goto(u8 row, u8 col) |
| 126 | { |
| 127 | row++; col++; |
| 128 | sercon_putchar('\x1b'); |
| 129 | sercon_putchar('['); |
| 130 | sercon_putchar('0' + row / 10); |
| 131 | sercon_putchar('0' + row % 10); |
| 132 | sercon_putchar(';'); |
| 133 | sercon_putchar('0' + col / 10); |
| 134 | sercon_putchar('0' + col % 10); |
| 135 | sercon_putchar('H'); |
| 136 | } |
| 137 | |
| 138 | static void sercon_term_set_color(u8 fg, u8 bg, u8 bold) |
| 139 | { |
| 140 | sercon_putchar('\x1b'); |
| 141 | sercon_putchar('['); |
| 142 | sercon_putchar('0'); |
| 143 | if (fg != 7) { |
| 144 | sercon_putchar(';'); |
| 145 | sercon_putchar('3'); |
| 146 | sercon_putchar(GET_GLOBAL(sercon_cmap[fg & 7])); |
| 147 | } |
| 148 | if (bg != 0) { |
| 149 | sercon_putchar(';'); |
| 150 | sercon_putchar('4'); |
| 151 | sercon_putchar(GET_GLOBAL(sercon_cmap[bg & 7])); |
| 152 | } |
| 153 | if (bold) { |
| 154 | sercon_putchar(';'); |
| 155 | sercon_putchar('1'); |
| 156 | } |
| 157 | sercon_putchar('m'); |
| 158 | } |
| 159 | |
| 160 | static void sercon_set_attr(u8 attr) |
| 161 | { |
| 162 | if (attr == GET_LOW(sercon_attr_last)) |
| 163 | return; |
| 164 | |
| 165 | SET_LOW(sercon_attr_last, attr); |
| 166 | sercon_term_set_color((attr >> 0) & 7, |
| 167 | (attr >> 4) & 7, |
| 168 | attr & 0x08); |
| 169 | } |
| 170 | |
| 171 | static void sercon_print_utf8(u8 chr) |
| 172 | { |
| 173 | u16 unicode = cp437_to_unicode(chr); |
| 174 | |
| 175 | if (unicode < 0x7f) { |
| 176 | sercon_putchar(unicode); |
| 177 | } else if (unicode < 0x7ff) { |
| 178 | sercon_putchar(0xc0 | ((unicode >> 6) & 0x1f)); |
| 179 | sercon_putchar(0x80 | ((unicode >> 0) & 0x3f)); |
| 180 | } else { |
| 181 | sercon_putchar(0xe0 | ((unicode >> 12) & 0x0f)); |
| 182 | sercon_putchar(0x80 | ((unicode >> 6) & 0x3f)); |
| 183 | sercon_putchar(0x80 | ((unicode >> 0) & 0x3f)); |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | static void sercon_cursor_pos_set(u8 row, u8 col) |
| 188 | { |
| 189 | if (!sercon_splitmode()) { |
| 190 | cursor_pos_set(row, col); |
| 191 | } else { |
| 192 | /* let vgabios update cursor */ |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | static void sercon_lazy_cursor_sync(void) |
| 197 | { |
| 198 | u8 row = cursor_pos_row(); |
| 199 | u8 col = cursor_pos_col(); |
| 200 | |
| 201 | if (GET_LOW(sercon_row_last) == row && |
| 202 | GET_LOW(sercon_col_last) == col) |
| 203 | return; |
| 204 | |
| 205 | if (col == 0 && GET_LOW(sercon_row_last) <= row) { |
| 206 | if (GET_LOW(sercon_col_last) != 0) { |
| 207 | sercon_putchar('\r'); |
| 208 | SET_LOW(sercon_col_last, 0); |
| 209 | } |
| 210 | while (GET_LOW(sercon_row_last) < row) { |
| 211 | sercon_putchar('\n'); |
| 212 | SET_LOW(sercon_row_last, GET_LOW(sercon_row_last)+1); |
| 213 | } |
| 214 | if (GET_LOW(sercon_row_last) == row && |
| 215 | GET_LOW(sercon_col_last) == col) |
| 216 | return; |
| 217 | } |
| 218 | |
| 219 | sercon_term_cursor_goto(row, col); |
| 220 | SET_LOW(sercon_row_last, row); |
| 221 | SET_LOW(sercon_col_last, col); |
| 222 | } |
| 223 | |
| 224 | static void sercon_lazy_flush(void) |
| 225 | { |
| 226 | u8 chr, attr; |
| 227 | |
| 228 | chr = GET_LOW(sercon_char); |
| 229 | attr = GET_LOW(sercon_attr); |
| 230 | if (chr) { |
| 231 | sercon_set_attr(attr); |
| 232 | sercon_print_utf8(chr); |
| 233 | SET_LOW(sercon_col_last, GET_LOW(sercon_col_last) + 1); |
| 234 | } |
| 235 | |
| 236 | sercon_lazy_cursor_sync(); |
| 237 | |
| 238 | SET_LOW(sercon_attr, 0x07); |
| 239 | SET_LOW(sercon_char, 0x00); |
| 240 | } |
| 241 | |
| 242 | static void sercon_lazy_cursor_update(u8 row, u8 col) |
| 243 | { |
| 244 | sercon_cursor_pos_set(row, col); |
| 245 | SET_LOW(sercon_row_last, row); |
| 246 | SET_LOW(sercon_col_last, col); |
| 247 | } |
| 248 | |
| 249 | static void sercon_lazy_backspace(void) |
| 250 | { |
| 251 | u8 col; |
| 252 | |
| 253 | sercon_lazy_flush(); |
| 254 | col = cursor_pos_col(); |
| 255 | if (col > 0) { |
| 256 | sercon_putchar(8); |
| 257 | sercon_lazy_cursor_update(cursor_pos_row(), col-1); |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | static void sercon_lazy_cr(void) |
| 262 | { |
| 263 | sercon_cursor_pos_set(cursor_pos_row(), 0); |
| 264 | } |
| 265 | |
| 266 | static void sercon_lazy_lf(void) |
| 267 | { |
| 268 | u8 row; |
| 269 | |
| 270 | row = cursor_pos_row() + 1; |
| 271 | if (row >= video_rows()) { |
| 272 | /* scrolling up */ |
| 273 | row = video_rows()-1; |
| 274 | if (GET_LOW(sercon_row_last) > 0) { |
| 275 | SET_LOW(sercon_row_last, GET_LOW(sercon_row_last) - 1); |
| 276 | } |
| 277 | } |
| 278 | sercon_cursor_pos_set(row, cursor_pos_col()); |
| 279 | } |
| 280 | |
| 281 | static void sercon_lazy_move_cursor(void) |
| 282 | { |
| 283 | u8 col; |
| 284 | |
| 285 | col = cursor_pos_col() + 1; |
| 286 | if (col >= video_cols()) { |
| 287 | sercon_lazy_cr(); |
| 288 | sercon_lazy_lf(); |
| 289 | } else { |
| 290 | sercon_cursor_pos_set(cursor_pos_row(), col); |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | static void sercon_lazy_putchar(u8 chr, u8 attr, u8 teletype) |
| 295 | { |
| 296 | if (cursor_pos_row() != GET_LOW(sercon_row_last) || |
| 297 | cursor_pos_col() != GET_LOW(sercon_col_last)) { |
| 298 | sercon_lazy_flush(); |
| 299 | } |
| 300 | |
| 301 | SET_LOW(sercon_char, chr); |
| 302 | if (teletype) |
| 303 | sercon_lazy_move_cursor(); |
| 304 | else |
| 305 | SET_LOW(sercon_attr, attr); |
| 306 | } |
| 307 | |
| 308 | /* Set video mode */ |
| 309 | static void sercon_1000(struct bregs *regs) |
| 310 | { |
| 311 | u8 clearscreen = !(regs->al & 0x80); |
| 312 | u8 mode = regs->al & 0x7f; |
| 313 | u8 rows, cols; |
| 314 | |
| 315 | if (!sercon_splitmode()) { |
| 316 | switch (mode) { |
| 317 | case 0x00: |
| 318 | case 0x01: |
| 319 | case 0x04: /* 320x200 */ |
| 320 | case 0x05: /* 320x200 */ |
| 321 | cols = 40; |
| 322 | rows = 25; |
| 323 | regs->al = 0x30; |
| 324 | break; |
| 325 | case 0x02: |
| 326 | case 0x03: |
| 327 | case 0x06: /* 640x200 */ |
| 328 | case 0x07: |
| 329 | default: |
| 330 | cols = 80; |
| 331 | rows = 25; |
| 332 | regs->al = 0x30; |
| 333 | break; |
| 334 | } |
| 335 | cursor_pos_set(0, 0); |
| 336 | SET_BDA(video_mode, mode); |
| 337 | SET_BDA(video_cols, cols); |
| 338 | SET_BDA(video_rows, rows-1); |
| 339 | SET_BDA(cursor_type, 0x0007); |
| 340 | } else { |
| 341 | /* let vgabios handle mode init */ |
| 342 | } |
| 343 | |
| 344 | SET_LOW(sercon_enable, mode <= 0x07); |
| 345 | SET_LOW(sercon_col_last, 0); |
| 346 | SET_LOW(sercon_row_last, 0); |
| 347 | SET_LOW(sercon_attr_last, 0); |
| 348 | |
| 349 | sercon_term_reset(); |
| 350 | sercon_term_no_linewrap(); |
| 351 | if (clearscreen) |
| 352 | sercon_term_clear_screen(); |
| 353 | } |
| 354 | |
| 355 | /* Set text-mode cursor shape */ |
| 356 | static void sercon_1001(struct bregs *regs) |
| 357 | { |
| 358 | /* show/hide cursor? */ |
| 359 | SET_BDA(cursor_type, regs->cx); |
| 360 | } |
| 361 | |
| 362 | /* Set cursor position */ |
| 363 | static void sercon_1002(struct bregs *regs) |
| 364 | { |
| 365 | sercon_cursor_pos_set(regs->dh, regs->dl); |
| 366 | } |
| 367 | |
| 368 | /* Get cursor position */ |
| 369 | static void sercon_1003(struct bregs *regs) |
| 370 | { |
| 371 | regs->cx = GET_BDA(cursor_type); |
| 372 | regs->dh = cursor_pos_row(); |
| 373 | regs->dl = cursor_pos_col(); |
| 374 | } |
| 375 | |
| 376 | /* Scroll up window */ |
| 377 | static void sercon_1006(struct bregs *regs) |
| 378 | { |
| 379 | sercon_lazy_flush(); |
| 380 | if (regs->al == 0) { |
| 381 | /* clear rect, do only in case this looks like a fullscreen clear */ |
| 382 | if (regs->ch == 0 && |
| 383 | regs->cl == 0 && |
| 384 | regs->dh == video_rows()-1 && |
| 385 | regs->dl == video_cols()-1) { |
| 386 | sercon_set_attr(regs->bh); |
| 387 | sercon_term_clear_screen(); |
| 388 | } |
| 389 | } else { |
| 390 | sercon_putchar('\r'); |
| 391 | sercon_putchar('\n'); |
| 392 | } |
| 393 | } |
| 394 | |
| 395 | /* Read character and attribute at cursor position */ |
| 396 | static void sercon_1008(struct bregs *regs) |
| 397 | { |
| 398 | regs->ah = 0x07; |
| 399 | regs->bh = ' '; |
| 400 | } |
| 401 | |
| 402 | /* Write character and attribute at cursor position */ |
| 403 | static void sercon_1009(struct bregs *regs) |
| 404 | { |
| 405 | u16 count = regs->cx; |
| 406 | |
| 407 | if (count == 1) { |
| 408 | sercon_lazy_putchar(regs->al, regs->bl, 0); |
| 409 | |
| 410 | } else if (regs->al == 0x20 && |
| 411 | video_rows() * video_cols() == count && |
| 412 | cursor_pos_row() == 0 && |
| 413 | cursor_pos_col() == 0) { |
| 414 | /* override everything with spaces -> this is clear screen */ |
| 415 | sercon_lazy_flush(); |
| 416 | sercon_set_attr(regs->bl); |
| 417 | sercon_term_clear_screen(); |
| 418 | |
| 419 | } else { |
| 420 | sercon_lazy_flush(); |
| 421 | sercon_set_attr(regs->bl); |
| 422 | while (count) { |
| 423 | sercon_print_utf8(regs->al); |
| 424 | count--; |
| 425 | } |
| 426 | sercon_term_cursor_goto(cursor_pos_row(), |
| 427 | cursor_pos_col()); |
| 428 | } |
| 429 | } |
| 430 | |
| 431 | /* Teletype output */ |
| 432 | static void sercon_100e(struct bregs *regs) |
| 433 | { |
| 434 | switch (regs->al) { |
| 435 | case 7: |
| 436 | sercon_putchar(0x07); |
| 437 | break; |
| 438 | case 8: |
| 439 | sercon_lazy_backspace(); |
| 440 | break; |
| 441 | case '\r': |
| 442 | sercon_lazy_cr(); |
| 443 | break; |
| 444 | case '\n': |
| 445 | sercon_lazy_lf(); |
| 446 | break; |
| 447 | default: |
| 448 | sercon_lazy_putchar(regs->al, 0, 1); |
| 449 | break; |
| 450 | } |
| 451 | } |
| 452 | |
| 453 | /* Get current video mode */ |
| 454 | static void sercon_100f(struct bregs *regs) |
| 455 | { |
| 456 | regs->al = GET_BDA(video_mode); |
| 457 | regs->ah = GET_BDA(video_cols); |
| 458 | } |
| 459 | |
| 460 | /* VBE 2.0 */ |
| 461 | static void sercon_104f(struct bregs *regs) |
| 462 | { |
| 463 | if (!sercon_splitmode()) { |
| 464 | regs->ax = 0x0100; |
| 465 | } else { |
| 466 | // Disable sercon entry point on any vesa modeset |
| 467 | if (regs->al == 0x00) |
| 468 | SET_LOW(sercon_enable, 0); |
| 469 | } |
| 470 | } |
| 471 | |
| 472 | static void sercon_10XX(struct bregs *regs) |
| 473 | { |
| 474 | warn_unimplemented(regs); |
| 475 | } |
| 476 | |
| 477 | void VISIBLE16 |
| 478 | handle_sercon(struct bregs *regs) |
| 479 | { |
| 480 | if (!CONFIG_SERCON) |
| 481 | return; |
| 482 | if (!GET_LOW(sercon_port)) |
| 483 | return; |
| 484 | |
| 485 | switch (regs->ah) { |
| 486 | case 0x01: |
| 487 | case 0x02: |
| 488 | case 0x03: |
| 489 | case 0x08: |
| 490 | case 0x0f: |
| 491 | if (sercon_splitmode()) |
| 492 | /* nothing, vgabios handles it */ |
| 493 | return; |
| 494 | } |
| 495 | |
| 496 | switch (regs->ah) { |
| 497 | case 0x00: sercon_1000(regs); break; |
| 498 | case 0x01: sercon_1001(regs); break; |
| 499 | case 0x02: sercon_1002(regs); break; |
| 500 | case 0x03: sercon_1003(regs); break; |
| 501 | case 0x06: sercon_1006(regs); break; |
| 502 | case 0x08: sercon_1008(regs); break; |
| 503 | case 0x09: sercon_1009(regs); break; |
| 504 | case 0x0e: sercon_100e(regs); break; |
| 505 | case 0x0f: sercon_100f(regs); break; |
| 506 | case 0x4f: sercon_104f(regs); break; |
| 507 | default: sercon_10XX(regs); break; |
| 508 | } |
| 509 | } |
| 510 | |
| 511 | void sercon_setup(void) |
| 512 | { |
| 513 | if (!CONFIG_SERCON) |
| 514 | return; |
| 515 | |
| 516 | struct segoff_s seabios, vgabios; |
| 517 | u16 addr; |
| 518 | |
| 519 | addr = romfile_loadint("etc/sercon-port", 0); |
| 520 | if (!addr) |
| 521 | return; |
| 522 | dprintf(1, "sercon: using ioport 0x%x\n", addr); |
| 523 | |
Gerd Hoffmann | 0ca6d62 | 2017-11-03 08:40:57 +0100 | [diff] [blame] | 524 | if (CONFIG_DEBUG_SERIAL) |
| 525 | if (addr == CONFIG_DEBUG_SERIAL_PORT) |
| 526 | ScreenAndDebug = 0; |
| 527 | |
Gerd Hoffmann | d6728f3 | 2017-09-18 10:47:23 +0200 | [diff] [blame] | 528 | vgabios = GET_IVT(0x10); |
| 529 | seabios = FUNC16(entry_10); |
| 530 | if (vgabios.seg != seabios.seg || |
| 531 | vgabios.offset != seabios.offset) { |
| 532 | dprintf(1, "sercon: configuring in splitmode (vgabios %04x:%04x)\n", |
| 533 | vgabios.seg, vgabios.offset); |
| 534 | sercon_real_vga_handler = vgabios; |
| 535 | SET_LOW(sercon_split, 1); |
| 536 | } else { |
| 537 | dprintf(1, "sercon: configuring as primary display\n"); |
| 538 | sercon_real_vga_handler = seabios; |
| 539 | } |
| 540 | |
| 541 | SET_IVT(0x10, FUNC16(entry_sercon)); |
| 542 | SET_LOW(sercon_port, addr); |
| 543 | outb(0x03, addr + SEROFF_LCR); // 8N1 |
| 544 | outb(0x01, addr + 0x02); // enable fifo |
| 545 | } |
| 546 | |
| 547 | /**************************************************************** |
| 548 | * serial input |
| 549 | ****************************************************************/ |
| 550 | |
| 551 | VARLOW u8 rx_buf[16]; |
| 552 | VARLOW u8 rx_bytes; |
| 553 | |
| 554 | static VAR16 struct { |
| 555 | char seq[4]; |
| 556 | u8 len; |
| 557 | u16 keycode; |
| 558 | } termseq[] = { |
| 559 | { .seq = "OP", .len = 2, .keycode = 0x3b00 }, // F1 |
| 560 | { .seq = "OQ", .len = 2, .keycode = 0x3c00 }, // F2 |
| 561 | { .seq = "OR", .len = 2, .keycode = 0x3d00 }, // F3 |
| 562 | { .seq = "OS", .len = 2, .keycode = 0x3e00 }, // F4 |
| 563 | |
| 564 | { .seq = "[15~", .len = 4, .keycode = 0x3f00 }, // F5 |
| 565 | { .seq = "[17~", .len = 4, .keycode = 0x4000 }, // F6 |
| 566 | { .seq = "[18~", .len = 4, .keycode = 0x4100 }, // F7 |
| 567 | { .seq = "[19~", .len = 4, .keycode = 0x4200 }, // F8 |
| 568 | { .seq = "[20~", .len = 4, .keycode = 0x4300 }, // F9 |
| 569 | { .seq = "[21~", .len = 4, .keycode = 0x4400 }, // F10 |
| 570 | { .seq = "[23~", .len = 4, .keycode = 0x5700 }, // F11 |
| 571 | { .seq = "[24~", .len = 4, .keycode = 0x5800 }, // F12 |
| 572 | |
| 573 | { .seq = "[2~", .len = 3, .keycode = 0x52e0 }, // insert |
| 574 | { .seq = "[3~", .len = 3, .keycode = 0x53e0 }, // delete |
| 575 | { .seq = "[5~", .len = 3, .keycode = 0x49e0 }, // page up |
| 576 | { .seq = "[6~", .len = 3, .keycode = 0x51e0 }, // page down |
| 577 | |
| 578 | { .seq = "[A", .len = 2, .keycode = 0x48e0 }, // up |
| 579 | { .seq = "[B", .len = 2, .keycode = 0x50e0 }, // down |
| 580 | { .seq = "[C", .len = 2, .keycode = 0x4de0 }, // right |
| 581 | { .seq = "[D", .len = 2, .keycode = 0x4be0 }, // left |
| 582 | |
| 583 | { .seq = "[H", .len = 2, .keycode = 0x47e0 }, // home |
| 584 | { .seq = "[F", .len = 2, .keycode = 0x4fe0 }, // end |
| 585 | }; |
| 586 | |
| 587 | static void shiftbuf(int remove) |
| 588 | { |
| 589 | int i, remaining; |
| 590 | |
| 591 | remaining = GET_LOW(rx_bytes) - remove; |
| 592 | SET_LOW(rx_bytes, remaining); |
| 593 | for (i = 0; i < remaining; i++) |
| 594 | SET_LOW(rx_buf[i], GET_LOW(rx_buf[i + remove])); |
| 595 | } |
| 596 | |
| 597 | static int cmpbuf(int seq) |
| 598 | { |
| 599 | int chr, len; |
| 600 | |
| 601 | len = GET_GLOBAL(termseq[seq].len); |
| 602 | if (GET_LOW(rx_bytes) < len + 1) |
| 603 | return 0; |
| 604 | for (chr = 0; chr < len; chr++) |
| 605 | if (GET_GLOBAL(termseq[seq].seq[chr]) != GET_LOW(rx_buf[chr + 1])) |
| 606 | return 0; |
| 607 | return 1; |
| 608 | } |
| 609 | |
| 610 | static int findseq(void) |
| 611 | { |
| 612 | int seq; |
| 613 | |
| 614 | for (seq = 0; seq < ARRAY_SIZE(termseq); seq++) |
| 615 | if (cmpbuf(seq)) |
| 616 | return seq; |
| 617 | return -1; |
| 618 | } |
| 619 | |
| 620 | void |
| 621 | sercon_check_event(void) |
| 622 | { |
| 623 | if (!CONFIG_SERCON) |
| 624 | return; |
| 625 | |
| 626 | u16 addr = GET_LOW(sercon_port); |
| 627 | u16 keycode; |
| 628 | u8 byte, count = 0; |
| 629 | int seq, chr; |
| 630 | |
| 631 | // check to see if there is a active serial port |
| 632 | if (!addr) |
| 633 | return; |
| 634 | if (inb(addr + SEROFF_LSR) == 0xFF) |
| 635 | return; |
| 636 | |
| 637 | // flush pending output |
| 638 | sercon_lazy_flush(); |
| 639 | |
| 640 | // read all available data |
| 641 | while (inb(addr + SEROFF_LSR) & 0x01) { |
| 642 | byte = inb(addr + SEROFF_DATA); |
| 643 | if (GET_LOW(rx_bytes) < sizeof(rx_buf)) { |
| 644 | SET_LOW(rx_buf[rx_bytes], byte); |
| 645 | SET_LOW(rx_bytes, GET_LOW(rx_bytes) + 1); |
| 646 | count++; |
| 647 | } |
| 648 | } |
| 649 | |
| 650 | for (;;) { |
| 651 | // no (more) input data |
| 652 | if (!GET_LOW(rx_bytes)) |
| 653 | return; |
| 654 | |
| 655 | // lookup escape sequences |
| 656 | if (GET_LOW(rx_bytes) > 1 && GET_LOW(rx_buf[0]) == 0x1b) { |
| 657 | seq = findseq(); |
| 658 | if (seq >= 0) { |
| 659 | enqueue_key(GET_GLOBAL(termseq[seq].keycode)); |
| 660 | shiftbuf(GET_GLOBAL(termseq[seq].len) + 1); |
| 661 | continue; |
| 662 | } |
| 663 | } |
| 664 | |
| 665 | // Seems we got a escape sequence we didn't recognise. |
| 666 | // -> If we received data wait for more, maybe it is just incomplete. |
| 667 | if (GET_LOW(rx_buf[0]) == 0x1b && count) |
| 668 | return; |
| 669 | |
| 670 | // Handle input as individual char. |
| 671 | chr = GET_LOW(rx_buf[0]); |
| 672 | keycode = ascii_to_keycode(chr); |
| 673 | if (keycode) |
| 674 | enqueue_key(keycode); |
| 675 | shiftbuf(1); |
| 676 | } |
| 677 | } |