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 
video_rows(void)17 static u8 video_rows(void)
18 {
19     return GET_BDA(video_rows)+1;
20 }
21 
video_cols(void)22 static u8 video_cols(void)
23 {
24     return GET_BDA(video_cols);
25 }
26 
cursor_pos_col(void)27 static u8 cursor_pos_col(void)
28 {
29     u16 pos = GET_BDA(cursor_pos[0]);
30     return pos & 0xff;
31 }
32 
cursor_pos_row(void)33 static u8 cursor_pos_row(void)
34 {
35     u16 pos = GET_BDA(cursor_pos[0]);
36     return (pos >> 8) & 0xff;
37 }
38 
cursor_pos_set(u8 row,u8 col)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 
sercon_splitmode(void)72 static int sercon_splitmode(void)
73 {
74     return GET_LOW(sercon_split);
75 }
76 
sercon_putchar(u8 chr)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 
sercon_term_reset(void)102 static void sercon_term_reset(void)
103 {
104     sercon_putchar('\x1b');
105     sercon_putchar('c');
106 }
107 
sercon_term_clear_screen(void)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 
sercon_term_no_linewrap(void)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 
sercon_term_cursor_goto(u8 row,u8 col)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 
sercon_term_set_color(u8 fg,u8 bg,u8 bold)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 
sercon_set_attr(u8 attr)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 
sercon_print_utf8(u8 chr)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 
sercon_cursor_pos_set(u8 row,u8 col)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 
sercon_lazy_cursor_sync(void)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 
sercon_lazy_flush(void)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 
sercon_lazy_cursor_update(u8 row,u8 col)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 
sercon_lazy_backspace(void)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 
sercon_lazy_cr(void)261 static void sercon_lazy_cr(void)
262 {
263     sercon_cursor_pos_set(cursor_pos_row(), 0);
264 }
265 
sercon_lazy_lf(void)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 
sercon_lazy_move_cursor(void)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 
sercon_lazy_putchar(u8 chr,u8 attr,u8 teletype)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 */
sercon_1000(struct bregs * regs)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 */
sercon_1001(struct bregs * regs)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 */
sercon_1002(struct bregs * regs)363 static void sercon_1002(struct bregs *regs)
364 {
365     sercon_cursor_pos_set(regs->dh, regs->dl);
366 }
367 
368 /* Get cursor position */
sercon_1003(struct bregs * regs)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 */
sercon_1006(struct bregs * regs)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 */
sercon_1008(struct bregs * regs)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 */
sercon_1009(struct bregs * regs)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 */
sercon_100e(struct bregs * regs)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 */
sercon_100f(struct bregs * regs)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 */
sercon_104f(struct bregs * regs)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 == 0x02)
468             SET_LOW(sercon_enable, 0);
469     }
470 }
471 
sercon_10XX(struct bregs * regs)472 static void sercon_10XX(struct bregs *regs)
473 {
474     warn_unimplemented(regs);
475 }
476 
477 void VISIBLE16
handle_sercon(struct bregs * regs)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 
sercon_setup(void)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 
524     if (CONFIG_DEBUG_SERIAL)
525         if (addr == CONFIG_DEBUG_SERIAL_PORT)
526             ScreenAndDebug = 0;
527 
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 
shiftbuf(int remove)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 
cmpbuf(int seq)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 
findseq(void)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
sercon_check_event(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 }
678