blob: 66a1f2412e771b7222dd794d932d4c479d7935f4 [file] [log] [blame]
Gerd Hoffmannd6728f32017-09-18 10:47:23 +02001// 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
17static u8 video_rows(void)
18{
19 return GET_BDA(video_rows)+1;
20}
21
22static u8 video_cols(void)
23{
24 return GET_BDA(video_cols);
25}
26
27static u8 cursor_pos_col(void)
28{
29 u16 pos = GET_BDA(cursor_pos[0]);
30 return pos & 0xff;
31}
32
33static u8 cursor_pos_row(void)
34{
35 u16 pos = GET_BDA(cursor_pos[0]);
36 return (pos >> 8) & 0xff;
37}
38
39static 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
49VARLOW u16 sercon_port;
50VARLOW u8 sercon_split;
51VARLOW u8 sercon_enable;
52VARFSEG 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 */
64VARLOW u8 sercon_attr_last;
65VARLOW u8 sercon_col_last;
66VARLOW u8 sercon_row_last;
67VARLOW u8 sercon_char;
68VARLOW u8 sercon_attr = 0x07;
69
70static VAR16 u8 sercon_cmap[8] = { '0', '4', '2', '6', '1', '5', '3', '7' };
71
72static int sercon_splitmode(void)
73{
74 return GET_LOW(sercon_split);
75}
76
77static 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
102static void sercon_term_reset(void)
103{
104 sercon_putchar('\x1b');
105 sercon_putchar('c');
106}
107
108static void sercon_term_clear_screen(void)
109{
110 sercon_putchar('\x1b');
111 sercon_putchar('[');
112 sercon_putchar('2');
113 sercon_putchar('J');
114}
115
116static 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
125static 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
138static 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
160static 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
171static 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
187static 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
196static 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
224static 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
242static 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
249static 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
261static void sercon_lazy_cr(void)
262{
263 sercon_cursor_pos_set(cursor_pos_row(), 0);
264}
265
266static 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
281static 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
294static 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 */
309static 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 */
356static void sercon_1001(struct bregs *regs)
357{
358 /* show/hide cursor? */
359 SET_BDA(cursor_type, regs->cx);
360}
361
362/* Set cursor position */
363static void sercon_1002(struct bregs *regs)
364{
365 sercon_cursor_pos_set(regs->dh, regs->dl);
366}
367
368/* Get cursor position */
369static 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 */
377static 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 */
396static void sercon_1008(struct bregs *regs)
397{
398 regs->ah = 0x07;
399 regs->bh = ' ';
400}
401
402/* Write character and attribute at cursor position */
403static 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 */
432static 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 */
454static 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 */
461static 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
Gerd Hoffmann066a9952020-03-06 10:03:21 +0100467 if (regs->al == 0x02)
Gerd Hoffmannd6728f32017-09-18 10:47:23 +0200468 SET_LOW(sercon_enable, 0);
469 }
470}
471
472static void sercon_10XX(struct bregs *regs)
473{
474 warn_unimplemented(regs);
475}
476
477void VISIBLE16
478handle_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
511void 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 Hoffmann0ca6d622017-11-03 08:40:57 +0100524 if (CONFIG_DEBUG_SERIAL)
525 if (addr == CONFIG_DEBUG_SERIAL_PORT)
526 ScreenAndDebug = 0;
527
Gerd Hoffmannd6728f32017-09-18 10:47:23 +0200528 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
551VARLOW u8 rx_buf[16];
552VARLOW u8 rx_bytes;
553
554static 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
587static 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
597static 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
610static 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
620void
621sercon_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}