qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 3/3] add serial console support


From: Gerd Hoffmann
Subject: [Qemu-devel] [PATCH 3/3] add serial console support
Date: Mon, 4 Jul 2016 22:39:54 +0200

Signed-off-by: Gerd Hoffmann <address@hidden>
---
 Makefile         |   2 +-
 src/clock.c      |   1 +
 src/misc.c       |   2 +
 src/optionroms.c |   4 +-
 src/sercon.c     | 545 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/util.h       |   3 +
 6 files changed, 555 insertions(+), 2 deletions(-)
 create mode 100644 src/sercon.c

diff --git a/Makefile b/Makefile
index 4930b3a..3f6248d 100644
--- a/Makefile
+++ b/Makefile
@@ -29,7 +29,7 @@ LD32BIT_FLAG:=-melf_i386
 
 # Source files
 SRCBOTH=misc.c stacks.c output.c string.c block.c cdrom.c disk.c mouse.c kbd.c 
\
-    system.c serial.c clock.c resume.c pnpbios.c vgahooks.c pcibios.c apm.c \
+    system.c serial.c sercon.c clock.c resume.c pnpbios.c vgahooks.c pcibios.c 
apm.c \
     hw/pci.c hw/timer.c hw/rtc.c hw/dma.c hw/pic.c hw/ps2port.c hw/serialio.c \
     hw/usb.c hw/usb-uhci.c hw/usb-ohci.c hw/usb-ehci.c \
     hw/usb-hid.c hw/usb-msc.c hw/usb-uas.c \
diff --git a/src/clock.c b/src/clock.c
index e83e0f3..e44e112 100644
--- a/src/clock.c
+++ b/src/clock.c
@@ -295,6 +295,7 @@ clock_update(void)
     floppy_tick();
     usb_check_event();
     ps2_check_event();
+    sercon_check_event();
 }
 
 // INT 08h System Timer ISR Entry Point
diff --git a/src/misc.c b/src/misc.c
index f02237c..f4b656d 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -11,6 +11,7 @@
 #include "output.h" // debug_enter
 #include "stacks.h" // call16_int
 #include "string.h" // memset
+#include "util.h" // serial_10
 
 #define PORT_MATH_CLEAR        0x00f0
 
@@ -57,6 +58,7 @@ handle_10(struct bregs *regs)
 {
     debug_enter(regs, DEBUG_HDL_10);
     // don't do anything, since the VGA BIOS handles int10h requests
+    sercon_10(regs);
 }
 
 // NMI handler
diff --git a/src/optionroms.c b/src/optionroms.c
index 65f7fe0..e6b308c 100644
--- a/src/optionroms.c
+++ b/src/optionroms.c
@@ -432,9 +432,11 @@ vgarom_setup(void)
     run_file_roms("vgaroms/", 1, NULL);
     rom_reserve(0);
 
-    if (rom_get_last() == BUILD_ROM_START)
+    if (rom_get_last() == BUILD_ROM_START) {
         // No VGA rom found
+        sercon_enable();
         return;
+    }
 
     VgaROM = (void*)BUILD_ROM_START;
     enable_vga_console();
diff --git a/src/sercon.c b/src/sercon.c
new file mode 100644
index 0000000..31e33f7
--- /dev/null
+++ b/src/sercon.c
@@ -0,0 +1,545 @@
+#include "biosvar.h" // SET_BDA
+#include "bregs.h" // struct bregs
+#include "stacks.h" // yield
+#include "output.h" // dprintf
+#include "util.h" // irqtimer_calc_ticks
+#include "hw/serialio.h" // SEROFF_IER
+
+static u8 video_rows(void)
+{
+    return GET_BDA(video_rows)+1;
+}
+
+static u8 video_cols(void)
+{
+    return GET_BDA(video_cols);
+}
+
+static u8 cursor_pos_col(void)
+{
+    u16 pos = GET_BDA(cursor_pos[0]);
+    return pos & 0xff;
+}
+
+static u8 cursor_pos_row(void)
+{
+    u16 pos = GET_BDA(cursor_pos[0]);
+    return (pos >> 8) & 0xff;
+}
+
+static void cursor_pos_set(u8 row, u8 col)
+{
+    u16 pos = ((u16)row << 8) | col;
+    SET_BDA(cursor_pos[0], pos);
+}
+
+/****************************************************************
+ * serial console output
+ ****************************************************************/
+
+VARLOW u16 sercon_port;
+
+/*
+ * We have a small output buffer here, for lazy output.  That allows
+ * to avoid a whole bunch of control sequences for pointless cursor
+ * moves, so when logging the output it'll be *alot* less cluttered.
+ *
+ * sercon_char/attr  is the actual output buffer.
+ * sercon_attr_last  is the most recent attribute sent to the terminal.
+ * sercon_col_last   is the most recent column sent to the terminal.
+ * sercon_row_last   is the most recent row sent to the terminal.
+ */
+VARLOW u8 sercon_attr_last;
+VARLOW u8 sercon_col_last;
+VARLOW u8 sercon_row_last;
+VARLOW u8 sercon_char[8];
+VARLOW u8 sercon_attr[8];
+
+static VAR16 u8 sercon_cmap[8] = { '0', '4', '2', '6', '1', '5', '3', '7' };
+static VAR16 u16 sercon_cp437[256] = {
+#include "std/cp437.h"
+};
+
+static void sercon_putchar(u8 chr)
+{
+    u16 addr = GET_LOW(sercon_port);
+    u32 end = irqtimer_calc_ticks(0x0a);
+
+    for (;;) {
+        u8 lsr = inb(addr+SEROFF_LSR);
+        if ((lsr & 0x60) == 0x60) {
+            // Success - can write data
+            outb(chr, addr+SEROFF_DATA);
+            break;
+        }
+        if (irqtimer_check(end)) {
+            break;
+        }
+        yield();
+    }
+}
+
+static void sercon_term_reset(void)
+{
+    sercon_putchar('\x1b');
+    sercon_putchar('c');
+}
+
+static void sercon_term_clear_screen(void)
+{
+    sercon_putchar('\x1b');
+    sercon_putchar('[');
+    sercon_putchar('2');
+    sercon_putchar('J');
+}
+
+static void sercon_term_cursor_right(void)
+{
+    sercon_putchar('\x1b');
+    sercon_putchar('[');
+    sercon_putchar('C');
+}
+
+static void sercon_term_cursor_goto(u8 row, u8 col)
+{
+    row++; col++;
+    sercon_putchar('\x1b');
+    sercon_putchar('[');
+    sercon_putchar('0' + row / 10);
+    sercon_putchar('0' + row % 10);
+    sercon_putchar(';');
+    sercon_putchar('0' + col / 10);
+    sercon_putchar('0' + col % 10);
+    sercon_putchar('H');
+}
+
+static void sercon_term_set_color(u8 fg, u8 bg, u8 bold)
+{
+    sercon_putchar('\x1b');
+    sercon_putchar('[');
+    sercon_putchar('0');
+    if (fg != 7) {
+        sercon_putchar(';');
+        sercon_putchar('3');
+        sercon_putchar(GET_GLOBAL(sercon_cmap[fg & 7]));
+    }
+    if (bg != 0) {
+        sercon_putchar(';');
+        sercon_putchar('4');
+        sercon_putchar(GET_GLOBAL(sercon_cmap[bg & 7]));
+    }
+    if (bold) {
+        sercon_putchar(';');
+        sercon_putchar('1');
+    }
+    sercon_putchar('m');
+}
+
+static void sercon_set_attr(u8 attr)
+{
+    if (attr == GET_LOW(sercon_attr_last))
+        return;
+
+    SET_LOW(sercon_attr_last, attr);
+    sercon_term_set_color((attr >> 0) & 7,
+                          (attr >> 4) & 7,
+                          attr & 0x08);
+}
+
+static void sercon_print_utf8(u8 chr)
+{
+    u16 unicode = GET_GLOBAL(sercon_cp437[chr]);
+
+    if (unicode < 0x7f) {
+        sercon_putchar(unicode);
+    } else if (unicode < 0x7ff) {
+        sercon_putchar(0xc0 | ((unicode >>  6) & 0x1f));
+        sercon_putchar(0x80 | ((unicode >>  0) & 0x3f));
+    } else {
+        sercon_putchar(0xe0 | ((unicode >> 12) & 0x0f));
+        sercon_putchar(0x80 | ((unicode >>  6) & 0x3f));
+        sercon_putchar(0x80 | ((unicode >>  0) & 0x3f));
+    }
+}
+
+static void sercon_lazy_flush(void)
+{
+    u8 idx, chr, attr;
+
+    for (idx = 0; idx < ARRAY_SIZE(sercon_char); idx++) {
+        chr = GET_LOW(sercon_char[idx]);
+        attr = GET_LOW(sercon_attr[idx]);
+        if (chr) {
+            sercon_set_attr(attr);
+            sercon_print_utf8(chr);
+            SET_LOW(sercon_col_last, GET_LOW(sercon_col_last) + 1);
+            continue;
+        }
+        if (GET_LOW(sercon_col_last) == cursor_pos_col() &&
+            GET_LOW(sercon_row_last) == cursor_pos_row()) {
+            break;
+        }
+        sercon_term_cursor_right();
+        SET_LOW(sercon_col_last, GET_LOW(sercon_col_last) + 1);
+    }
+
+    if (GET_LOW(sercon_col_last) != cursor_pos_col() ||
+        GET_LOW(sercon_row_last) != cursor_pos_row()) {
+        sercon_term_cursor_goto(cursor_pos_row(), cursor_pos_col());
+        SET_LOW(sercon_col_last, cursor_pos_col());
+        SET_LOW(sercon_row_last, cursor_pos_row());
+    }
+
+    for (idx = 0; idx < ARRAY_SIZE(sercon_char); idx++) {
+        SET_LOW(sercon_attr[idx], 0x07);
+        SET_LOW(sercon_char[idx], 0x00);
+    }
+}
+
+static void sercon_lazy_putchar(u8 chr, u8 attr, u8 teletype)
+{
+    u8 idx;
+
+    if (cursor_pos_row() != GET_LOW(sercon_row_last) ||
+        cursor_pos_col() <  GET_LOW(sercon_col_last) ||
+        cursor_pos_col() >= GET_LOW(sercon_col_last) + 
ARRAY_SIZE(sercon_char)) {
+        sercon_lazy_flush();
+    }
+
+    idx = cursor_pos_col() - GET_LOW(sercon_col_last);
+    SET_LOW(sercon_char[idx], chr);
+    if (teletype)
+        cursor_pos_set(cursor_pos_row(),
+                       cursor_pos_col()+1);
+    else
+        SET_LOW(sercon_attr[idx], attr);
+}
+
+static void sercon_lazy_cursor_update(u8 row, u8 col)
+{
+    cursor_pos_set(row, col);
+    SET_LOW(sercon_col_last, row);
+    SET_LOW(sercon_row_last, col);
+}
+
+static void sercon_lazy_backspace(void)
+{
+    u8 col;
+
+    sercon_lazy_flush();
+    col = cursor_pos_col();
+    if (col > 0) {
+        sercon_putchar(8);
+        sercon_lazy_cursor_update(cursor_pos_row(), col-1);
+    }
+}
+
+static void sercon_lazy_cr(void)
+{
+    sercon_lazy_flush();
+    sercon_putchar('\r');
+    sercon_lazy_cursor_update(cursor_pos_row(), 0);
+}
+
+static void sercon_lazy_lf(void)
+{
+    u8 row;
+
+    sercon_lazy_flush();
+    sercon_putchar('\n');
+    row = cursor_pos_row() + 1;
+    if (row >= video_rows())
+        row = video_rows()-1;
+    sercon_lazy_cursor_update(row, cursor_pos_col());
+}
+
+/* Set video mode */
+static void sercon_1000(struct bregs *regs)
+{
+    int mode, rows, cols;
+
+    mode = regs->al;
+    switch (mode) {
+    case 0x03:
+    default:
+        cols = 80;
+        rows = 25;
+        regs->al = 0x30;
+    }
+    SET_LOW(sercon_col_last, 0);
+    SET_LOW(sercon_row_last, 0);
+
+    cursor_pos_set(0, 0);
+    SET_BDA(video_mode, mode);
+    SET_BDA(video_cols, cols);
+    SET_BDA(video_rows, rows-1);
+    SET_BDA(cursor_type, 0x0007);
+
+    sercon_term_reset();
+    sercon_term_clear_screen();
+}
+
+/* Set text-mode cursor shape */
+static void sercon_1001(struct bregs *regs)
+{
+    /* dummy (add show/hide cursor?) */
+}
+
+/* Set cursor position */
+static void sercon_1002(struct bregs *regs)
+{
+    u8 row = regs->dh;
+    u8 col = regs->dl;
+
+    cursor_pos_set(row, col);
+}
+
+/* Get cursor position */
+static void sercon_1003(struct bregs *regs)
+{
+    regs->ax = 0;
+    regs->ch = 0;
+    regs->cl = 7;
+    regs->dh = cursor_pos_row();
+    regs->dl = cursor_pos_col();
+}
+
+/* Scroll up window */
+static void sercon_1006(struct bregs *regs)
+{
+    sercon_lazy_flush();
+    if (regs->al == 0) {
+        /* clear rect, do only in case this looks like a fullscreen clear */
+        if (regs->ch == 0 &&
+            regs->cl == 0 &&
+            regs->dh == video_rows()-1 &&
+            regs->dl == video_cols()-1) {
+            sercon_set_attr(regs->bh);
+            sercon_term_clear_screen();
+        }
+    } else {
+        sercon_putchar('\r');
+        sercon_putchar('\n');
+    }
+}
+
+/* Read character and attribute at cursor position */
+static void sercon_1008(struct bregs *regs)
+{
+    regs->ah = 0x07;
+    regs->bh = ' ';
+}
+
+/* Write character and attribute at cursor position */
+static void sercon_1009(struct bregs *regs)
+{
+    u16 count = regs->cx;
+
+    if (count == 1) {
+        sercon_lazy_putchar(regs->al, regs->bl, 0);
+
+    } else if (regs->al == 0x20 &&
+               video_rows() * video_cols() == count &&
+               cursor_pos_row() == 0 &&
+               cursor_pos_col() == 0) {
+        /* override everything with spaces -> this is clear screen */
+        sercon_lazy_flush();
+        sercon_set_attr(regs->bl);
+        sercon_term_clear_screen();
+
+    } else {
+        sercon_lazy_flush();
+        sercon_set_attr(regs->bl);
+        while (count) {
+            sercon_putchar(regs->al);
+            count--;
+        }
+        sercon_term_cursor_goto(cursor_pos_row(),
+                                cursor_pos_col());
+    }
+}
+
+/* Teletype output */
+static void sercon_100e(struct bregs *regs)
+{
+    switch (regs->al) {
+    case 7:
+        // beep
+        break;
+    case 8:
+        sercon_lazy_backspace();
+        break;
+    case '\r':
+        sercon_lazy_cr();
+        break;
+    case '\n':
+        sercon_lazy_lf();
+        break;
+    default:
+        sercon_lazy_putchar(regs->al, 0, 1);
+        break;
+    }
+}
+
+/* Get current video mode */
+static void sercon_100f(struct bregs *regs)
+{
+    regs->al = GET_BDA(video_mode);
+    regs->ah = GET_BDA(video_cols);
+}
+
+/* VBE 2.0 */
+static void sercon_104f(struct bregs *regs)
+{
+    regs->ax = 0x0100;
+}
+
+static void sercon_10XX(struct bregs *regs)
+{
+    warn_unimplemented(regs);
+}
+
+void VISIBLE16
+sercon_10(struct bregs *regs)
+{
+    if (!GET_LOW(sercon_port))
+        return;
+
+    switch (regs->ah) {
+    case 0x00: sercon_1000(regs); break;
+    case 0x01: sercon_1001(regs); break;
+    case 0x02: sercon_1002(regs); break;
+    case 0x03: sercon_1003(regs); break;
+    case 0x06: sercon_1006(regs); break;
+    case 0x08: sercon_1008(regs); break;
+    case 0x09: sercon_1009(regs); break;
+    case 0x0e: sercon_100e(regs); break;
+    case 0x0f: sercon_100f(regs); break;
+    case 0x4f: sercon_104f(regs); break;
+    default:   sercon_10XX(regs); break;
+    }
+}
+
+void sercon_enable(void)
+{
+    u16 addr = PORT_SERIAL1;
+
+    SET_LOW(sercon_port, addr);
+    outb(0x03, addr + SEROFF_LCR); // 8N1
+    outb(0x01, addr + 0x02);       // enable fifo
+    enable_vga_console();
+}
+
+/****************************************************************
+ * serial input
+ ****************************************************************/
+
+VARLOW u8 rx_buf[16];
+VARLOW u8 rx_bytes;
+
+static VAR16 struct {
+    char seq[4];
+    u8   len;
+    u16  keycode;
+} termseq[] = {
+    { .seq = "OP",   .len = 2, .keycode = 0x3b00 },    // F1
+    { .seq = "OQ",   .len = 2, .keycode = 0x3c00 },    // F2
+    { .seq = "OR",   .len = 2, .keycode = 0x3d00 },    // F3
+    { .seq = "OS",   .len = 2, .keycode = 0x3e00 },    // F4
+
+    { .seq = "[15~", .len = 4, .keycode = 0x3f00 },    // F5
+    { .seq = "[17~", .len = 4, .keycode = 0x4000 },    // F6
+    { .seq = "[18~", .len = 4, .keycode = 0x4100 },    // F7
+    { .seq = "[19~", .len = 4, .keycode = 0x4200 },    // F8
+    { .seq = "[20~", .len = 4, .keycode = 0x4300 },    // F9
+    { .seq = "[21~", .len = 4, .keycode = 0x4400 },    // F10
+    { .seq = "[23~", .len = 4, .keycode = 0x5700 },    // F11
+    { .seq = "[24~", .len = 4, .keycode = 0x5800 },    // F12
+
+    { .seq = "[2~",  .len = 3, .keycode = 0x52e0 },    // insert
+    { .seq = "[3~",  .len = 3, .keycode = 0x53e0 },    // delete
+    { .seq = "[5~",  .len = 3, .keycode = 0x49e0 },    // page up
+    { .seq = "[6~",  .len = 3, .keycode = 0x51e0 },    // page down
+
+    { .seq = "[A",   .len = 2, .keycode = 0x48e0 },    // up
+    { .seq = "[B",   .len = 2, .keycode = 0x50e0 },    // down
+    { .seq = "[C",   .len = 2, .keycode = 0x4de0 },    // right
+    { .seq = "[D",   .len = 2, .keycode = 0x4be0 },    // left
+
+    { .seq = "[H",   .len = 2, .keycode = 0x47e0 },    // home
+    { .seq = "[F",   .len = 2, .keycode = 0x4fe0 },    // end
+};
+
+static void shiftbuf(int remove)
+{
+    int i, remaining;
+
+    remaining = GET_LOW(rx_bytes) - remove;
+    SET_LOW(rx_bytes, remaining);
+    for (i = 0; i < remaining; i++)
+        SET_LOW(rx_buf[i], GET_LOW(rx_buf[i + remove]));
+}
+
+void VISIBLE16
+sercon_check_event(void)
+{
+    u16 addr = GET_LOW(sercon_port);
+    u16 keycode;
+    u8 byte, count = 0;
+    int seq, chr, len;
+
+    // check to see if there is a active serial port
+    if (!addr)
+        return;
+    if (inb(addr + SEROFF_LSR) == 0xFF)
+        return;
+
+    // flush pending output
+    sercon_lazy_flush();
+
+    // read all available data
+    while (inb(addr + SEROFF_LSR) & 0x01) {
+        byte = inb(addr + SEROFF_DATA);
+        if (GET_LOW(rx_bytes) < sizeof(rx_buf)) {
+            SET_LOW(rx_buf[rx_bytes], byte);
+            SET_LOW(rx_bytes, GET_LOW(rx_bytes) + 1);
+            count++;
+        }
+    }
+
+next_char:
+    // no (more) input data
+    if (!GET_LOW(rx_bytes))
+        return;
+
+    // lookup escape sequences
+    if (GET_LOW(rx_bytes) > 1 && GET_LOW(rx_buf[0]) == 0x1b) {
+        for (seq = 0; seq < ARRAY_SIZE(termseq); seq++) {
+            len = GET_GLOBAL(termseq[seq].len);
+            if (GET_LOW(rx_bytes) < len + 1)
+                continue;
+            for (chr = 0; chr < len; chr++) {
+                if (GET_GLOBAL(termseq[seq].seq[chr]) != GET_LOW(rx_buf[chr + 
1]))
+                    break;
+            }
+            if (chr == len) {
+                enqueue_key(GET_GLOBAL(termseq[seq].keycode));
+                shiftbuf(len + 1);
+                goto next_char;
+            }
+        }
+    }
+
+    // Seems we got a escape sequence we didn't recognise.
+    //  -> If we received data wait for more, maybe it is just incomplete.
+    if (GET_LOW(rx_buf[0]) == 0x1b && count)
+        return;
+
+    // Handle input as individual chars.
+    chr = GET_LOW(rx_buf[0]);
+    keycode = ascii_to_keycode(chr);
+    if (keycode)
+        enqueue_key(keycode);
+    shiftbuf(1);
+    goto next_char;
+}
diff --git a/src/util.h b/src/util.h
index cfcf4a3..1df8d0b 100644
--- a/src/util.h
+++ b/src/util.h
@@ -232,6 +232,9 @@ void code_mutable_preinit(void);
 // serial.c
 void serial_setup(void);
 void lpt_setup(void);
+void sercon_10(struct bregs *regs);
+void sercon_enable(void);
+void sercon_check_event(void);
 
 // vgahooks.c
 void handle_155f(struct bregs *regs);
-- 
1.8.3.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]