Hi Gerd,
I was curious to see what the "sercon" stuff would look like in SeaVGABIOS instead of in SeaBIOS. So, I put together a quick prototype. The code is also at:
https://github.com/KevinOConnor/seabios/tree/testing
This is just a proof-of-concept thing - I didn't implement any of the useful features you have in your series. Specifically, it doesn't unclutter the serial output, doesn't implement cp437 translations, and doesn't handle multi-byte input. It does does have basic input, output, and split-output handling though.
I'm not sure if this is better than a SeaBIOS implementation. I suspect it will be easier to handle vga quirks this way. However, handling exotic serial outputs (eg, mmio and usb based) would be better suited in SeaBIOS.
Thoughts?
-Kevin
Kevin O'Connor (3): sercon: Initial support for VGA emulation over serial port sercon: Add basic keyboard input support sercon: Support "split output" mode
Makefile | 2 +- vgasrc/Kconfig | 6 + vgasrc/sercon.c | 356 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ vgasrc/vgabios.c | 15 ++- vgasrc/vgabios.h | 5 + vgasrc/vgaentry.S | 7 ++ vgasrc/vgahw.h | 72 ++++++++++- vgasrc/vgainit.c | 12 +- vgasrc/vgautil.h | 24 ++++ 9 files changed, 482 insertions(+), 17 deletions(-) create mode 100644 vgasrc/sercon.c
Signed-off-by: Kevin O'Connor kevin@koconnor.net --- Makefile | 2 +- vgasrc/Kconfig | 6 ++ vgasrc/sercon.c | 244 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ vgasrc/vgabios.c | 13 ++- vgasrc/vgahw.h | 72 +++++++++++++++- vgasrc/vgautil.h | 23 ++++++ 6 files changed, 348 insertions(+), 12 deletions(-) create mode 100644 vgasrc/sercon.c
diff --git a/Makefile b/Makefile index 3b94ee0..2715662 100644 --- a/Makefile +++ b/Makefile @@ -211,7 +211,7 @@ SRCVGA=src/output.c src/string.c src/hw/pci.c src/hw/serialio.c \ vgasrc/vgainit.c vgasrc/vgabios.c vgasrc/vgafb.c vgasrc/swcursor.c \ vgasrc/vgafonts.c vgasrc/vbe.c \ vgasrc/stdvga.c vgasrc/stdvgamodes.c vgasrc/stdvgaio.c \ - vgasrc/clext.c vgasrc/bochsvga.c vgasrc/geodevga.c \ + vgasrc/clext.c vgasrc/bochsvga.c vgasrc/geodevga.c vgasrc/sercon.c \ src/fw/coreboot.c vgasrc/cbvga.c
ifeq "$(CONFIG_VGA_FIXUP_ASM)" "y" diff --git a/vgasrc/Kconfig b/vgasrc/Kconfig index f5098a4..d017176 100644 --- a/vgasrc/Kconfig +++ b/vgasrc/Kconfig @@ -55,6 +55,12 @@ menu "VGA ROM" Build support for a vgabios wrapper around video devices initialized using coreboot native vga init.
+ config VGA_SERCON + bool "VGA emulation over serial port" + help + Build support for a vga option rom that forwards text + updates over a serial port. + endchoice
choice diff --git a/vgasrc/sercon.c b/vgasrc/sercon.c new file mode 100644 index 0000000..35b60a5 --- /dev/null +++ b/vgasrc/sercon.c @@ -0,0 +1,244 @@ +// Video emulation over serial port support +// +// Copyright (C) 2016 Kevin O'Connor kevin@koconnor.net +// +// This file may be distributed under the terms of the GNU LGPLv3 license. + +#include <stdarg.h> // va_list + +#include "biosvar.h" // GET_GLOBAL +#include "output.h" // dprintf +#include "vgabios.h" // struct vgamode_s +#include "vgafb.h" // struct cursorpos +#include "vgautil.h" // sercon_write_pixel +#include "hw/serialio.h" // SEROFF_LSR +#include "x86.h" // inb + +#define SERCON_PORT 0x3f8 // XXX - should be configurable +#define SERCON_TIMEOUT 100000 + + +/**************************************************************** + * Serial port writing + ****************************************************************/ + +// Write a character to the serial port +static void +sercon_putc(char c) +{ + int timeout = SERCON_TIMEOUT; + while ((inb(SERCON_PORT+SEROFF_LSR) & 0x20) != 0x20) + if (!timeout--) + // Ran out of time. + return; + outb(c, SERCON_PORT+SEROFF_DATA); +} + +// Write a small unsigned integer in ascii to the serial port +static void +sercon_puti(int v) +{ + switch (v) { + default: sercon_putc('0' + ((v / 100) % 10)); + case 10 ... 99: sercon_putc('0' + ((v / 10) % 10)); + case 0 ... 9: sercon_putc('0' + ( v % 10)); + } +} + +// Write a multi-byte escape sequence to the serial port +static void +sercon_send_esc(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + sercon_putc('\x1b'); + const char *s = fmt; + for (;;) { + char c = GET_GLOBAL(*(u8*)s); + s++; + if (!c) + break; + if (c == '*') { + sercon_puti(va_arg(args, int)); + continue; + } + sercon_putc(c); + } + va_end(args); +} + +static void +sercon_set_cursor(struct cursorpos pos) +{ + // XXX - check if already at given position + sercon_send_esc("[*;*H", pos.y+1, pos.x+1); +} + +static void +sercon_set_attr(struct carattr ca) +{ + if (!ca.use_attr) + // Assume last sent attribute is still valid + return; + // XXX - check if new attribute matches last sent attribute + static VAR16 u8 sercon_cmap[8] = { 0, 4, 2, 6, 1, 5, 3, 7 }; + u8 fg = 30 + GET_GLOBAL(sercon_cmap[ca.attr & 7]); + u8 bg = 40 + GET_GLOBAL(sercon_cmap[(ca.attr >> 4) & 7]); + u8 bold = !!(ca.attr & 0x08); + if (bold) + sercon_send_esc("[0;*;*;1m", fg, bg); + else + sercon_send_esc("[0;*;*m", fg, bg); +} + +static void +sercon_clear_screen(void) +{ + sercon_send_esc("[2J"); +} + + +/**************************************************************** + * Interface functions + ****************************************************************/ + +void +sercon_scroll(struct cursorpos win, struct cursorpos winsize + , int lines, struct carattr ca) +{ + if (winsize.x != GET_BDA(video_cols) || winsize.y != GET_BDA(video_rows)+1) + // XXX - handle window scrolling? + return; + sercon_set_attr(ca); + if (!lines) { + sercon_clear_screen(); + } else if (lines > 0) { + // XXX - send '\n' if lines==1 and on last line + sercon_send_esc("[*S", lines); + } else { + sercon_send_esc("[*T", -lines); + } +} + +void +sercon_write_char(struct cursorpos cp, struct carattr ca) +{ + sercon_set_cursor(cp); + sercon_set_attr(ca); + u8 c = ca.car; + if (c <= 0x1f || c >= 0x7f) + // XXX - map value to UTF8 code + c = '?'; + sercon_putc(c); +} + +struct carattr +sercon_read_char(struct cursorpos cp) +{ + return (struct carattr){0, 0, 0}; +} + +void +sercon_write_pixel(u8 color, u16 x, u16 y) +{ +} + +u8 +sercon_read_pixel(u16 x, u16 y) +{ + return 0; +} + +#define SERCON_MODE 0x03 +static struct vgamode_s sercon_mode VAR16 = { + MM_TEXT, 80, 25, 4, 9, 16, 0 +}; + +struct vgamode_s *sercon_find_mode(int mode) +{ + if (mode == SERCON_MODE) + return &sercon_mode; + return NULL; +} + +void +sercon_list_modes(u16 seg, u16 *dest, u16 *last) +{ + if (dest<last) { + SET_FARVAR(seg, *dest, SERCON_MODE); + dest++; + } + SET_FARVAR(seg, *dest, 0xffff); +} + +int +sercon_get_window(struct vgamode_s *vmode_g, int window) +{ + return -1; +} + +int +sercon_set_window(struct vgamode_s *vmode_g, int window, int val) +{ + return -1; +} + +int +sercon_get_linelength(struct vgamode_s *vmode_g) +{ + return GET_GLOBAL(sercon_mode.width) * 2; +} + +int +sercon_set_linelength(struct vgamode_s *vmode_g, int val) +{ + return -1; +} + +int +sercon_get_displaystart(struct vgamode_s *vmode_g) +{ + return 0; +} + +int +sercon_set_displaystart(struct vgamode_s *vmode_g, int val) +{ + return -1; +} + +int +sercon_get_dacformat(struct vgamode_s *vmode_g) +{ + return -1; +} + +int +sercon_set_dacformat(struct vgamode_s *vmode_g, int val) +{ + return -1; +} + +int +sercon_save_restore(int cmd, u16 seg, void *data) +{ + if (cmd & (SR_HARDWARE|SR_DAC|SR_REGISTERS)) + return -1; + return bda_save_restore(cmd, seg, data); +} + +int +sercon_set_mode(struct vgamode_s *vmode_g, int flags) +{ + if (!(flags & MF_NOCLEARMEM)) + sercon_clear_screen(); + return 0; +} + +int +sercon_setup(void) +{ + outb(0x03, SERCON_PORT + SEROFF_LCR); // 8N1 + outb(0x01, SERCON_PORT + 0x02); // enable fifo + return 0; +} diff --git a/vgasrc/vgabios.c b/vgasrc/vgabios.c index 3b9694c..5b08022 100644 --- a/vgasrc/vgabios.c +++ b/vgasrc/vgabios.c @@ -14,7 +14,6 @@ #include "stdvga.h" // stdvga_set_cursor_shape #include "string.h" // memset_far #include "vgabios.h" // calc_page_size -#include "vgafb.h" // vgafb_write_char #include "vgahw.h" // vgahw_set_mode #include "vgautil.h" // swcursor_pre_handle10
@@ -158,7 +157,7 @@ set_scan_lines(u8 lines) static void write_char(struct cursorpos *pcp, struct carattr ca) { - vgafb_write_char(*pcp, ca); + vgahw_write_char(*pcp, ca); pcp->x++; // Do we need to wrap ? if (pcp->x == GET_BDA(video_cols)) { @@ -199,7 +198,7 @@ write_teletype(struct cursorpos *pcp, struct carattr ca) struct cursorpos win = {0, 0, pcp->page}; struct cursorpos winsize = {GET_BDA(video_cols), nbrows+1}; struct carattr attr = {' ', 0, 0}; - vgafb_scroll(win, winsize, 1, attr); + vgahw_scroll(win, winsize, 1, attr); } }
@@ -411,7 +410,7 @@ verify_scroll(struct bregs *regs, int dir) struct cursorpos win = {ulx, uly, GET_BDA(video_page)}; struct cursorpos winsize = {wincols, winrows}; struct carattr attr = {' ', regs->bh, 1}; - vgafb_scroll(win, winsize, lines, attr); + vgahw_scroll(win, winsize, lines, attr); }
static void @@ -429,7 +428,7 @@ handle_1007(struct bregs *regs) static void handle_1008(struct bregs *regs) { - struct carattr ca = vgafb_read_char(get_cursor_pos(regs->bh)); + struct carattr ca = vgahw_read_char(get_cursor_pos(regs->bh)); regs->al = ca.car; regs->ah = ca.attr; } @@ -492,14 +491,14 @@ static void handle_100c(struct bregs *regs) { // XXX - page (regs->bh) is unused - vgafb_write_pixel(regs->al, regs->cx, regs->dx); + vgahw_write_pixel(regs->al, regs->cx, regs->dx); }
static void handle_100d(struct bregs *regs) { // XXX - page (regs->bh) is unused - regs->al = vgafb_read_pixel(regs->cx, regs->dx); + regs->al = vgahw_read_pixel(regs->cx, regs->dx); }
static void noinline diff --git a/vgasrc/vgahw.h b/vgasrc/vgahw.h index dab2b4d..5918f3f 100644 --- a/vgasrc/vgahw.h +++ b/vgasrc/vgahw.h @@ -1,12 +1,11 @@ #ifndef __VGAHW_H #define __VGAHW_H
-#include "types.h" // u8 -#include "config.h" // CONFIG_* - #include "bochsvga.h" // bochsvga_set_mode -#include "stdvga.h" // stdvga_set_mode +#include "config.h" // CONFIG_* #include "geodevga.h" // geodevga_setup +#include "stdvga.h" // stdvga_set_mode +#include "vgafb.h" // vgafb_scroll #include "vgautil.h" // stdvga_list_modes
static inline struct vgamode_s *vgahw_find_mode(int mode) { @@ -16,6 +15,8 @@ static inline struct vgamode_s *vgahw_find_mode(int mode) { return bochsvga_find_mode(mode); if (CONFIG_VGA_COREBOOT) return cbvga_find_mode(mode); + if (CONFIG_VGA_SERCON) + return sercon_find_mode(mode); return stdvga_find_mode(mode); }
@@ -26,6 +27,8 @@ static inline int vgahw_set_mode(struct vgamode_s *vmode_g, int flags) { return bochsvga_set_mode(vmode_g, flags); if (CONFIG_VGA_COREBOOT) return cbvga_set_mode(vmode_g, flags); + if (CONFIG_VGA_SERCON) + return sercon_set_mode(vmode_g, flags); return stdvga_set_mode(vmode_g, flags); }
@@ -36,6 +39,8 @@ static inline void vgahw_list_modes(u16 seg, u16 *dest, u16 *last) { bochsvga_list_modes(seg, dest, last); else if (CONFIG_VGA_COREBOOT) cbvga_list_modes(seg, dest, last); + else if (CONFIG_VGA_SERCON) + sercon_list_modes(seg, dest, last); else stdvga_list_modes(seg, dest, last); } @@ -49,6 +54,8 @@ static inline int vgahw_setup(void) { return geodevga_setup(); if (CONFIG_VGA_COREBOOT) return cbvga_setup(); + if (CONFIG_VGA_SERCON) + return sercon_setup(); return stdvga_setup(); }
@@ -59,6 +66,8 @@ static inline int vgahw_get_window(struct vgamode_s *vmode_g, int window) { return bochsvga_get_window(vmode_g, window); if (CONFIG_VGA_COREBOOT) return cbvga_get_window(vmode_g, window); + if (CONFIG_VGA_SERCON) + return sercon_get_window(vmode_g, window); return stdvga_get_window(vmode_g, window); }
@@ -70,6 +79,8 @@ static inline int vgahw_set_window(struct vgamode_s *vmode_g, int window return bochsvga_set_window(vmode_g, window, val); if (CONFIG_VGA_COREBOOT) return cbvga_set_window(vmode_g, window, val); + if (CONFIG_VGA_SERCON) + return sercon_set_window(vmode_g, window, val); return stdvga_set_window(vmode_g, window, val); }
@@ -80,6 +91,8 @@ static inline int vgahw_get_linelength(struct vgamode_s *vmode_g) { return bochsvga_get_linelength(vmode_g); if (CONFIG_VGA_COREBOOT) return cbvga_get_linelength(vmode_g); + if (CONFIG_VGA_SERCON) + return sercon_get_linelength(vmode_g); return stdvga_get_linelength(vmode_g); }
@@ -90,6 +103,8 @@ static inline int vgahw_set_linelength(struct vgamode_s *vmode_g, int val) { return bochsvga_set_linelength(vmode_g, val); if (CONFIG_VGA_COREBOOT) return cbvga_set_linelength(vmode_g, val); + if (CONFIG_VGA_SERCON) + return sercon_set_linelength(vmode_g, val); return stdvga_set_linelength(vmode_g, val); }
@@ -100,6 +115,8 @@ static inline int vgahw_get_displaystart(struct vgamode_s *vmode_g) { return bochsvga_get_displaystart(vmode_g); if (CONFIG_VGA_COREBOOT) return cbvga_get_displaystart(vmode_g); + if (CONFIG_VGA_SERCON) + return sercon_get_displaystart(vmode_g); return stdvga_get_displaystart(vmode_g); }
@@ -110,6 +127,8 @@ static inline int vgahw_set_displaystart(struct vgamode_s *vmode_g, int val) { return bochsvga_set_displaystart(vmode_g, val); if (CONFIG_VGA_COREBOOT) return cbvga_set_displaystart(vmode_g, val); + if (CONFIG_VGA_SERCON) + return sercon_set_displaystart(vmode_g, val); return stdvga_set_displaystart(vmode_g, val); }
@@ -118,6 +137,8 @@ static inline int vgahw_get_dacformat(struct vgamode_s *vmode_g) { return bochsvga_get_dacformat(vmode_g); if (CONFIG_VGA_COREBOOT) return cbvga_get_dacformat(vmode_g); + if (CONFIG_VGA_SERCON) + return sercon_get_dacformat(vmode_g); return stdvga_get_dacformat(vmode_g); }
@@ -126,6 +147,8 @@ static inline int vgahw_set_dacformat(struct vgamode_s *vmode_g, int val) { return bochsvga_set_dacformat(vmode_g, val); if (CONFIG_VGA_COREBOOT) return cbvga_set_dacformat(vmode_g, val); + if (CONFIG_VGA_SERCON) + return sercon_set_dacformat(vmode_g, val); return stdvga_set_dacformat(vmode_g, val); }
@@ -136,7 +159,48 @@ static inline int vgahw_save_restore(int cmd, u16 seg, void *data) { return bochsvga_save_restore(cmd, seg, data); if (CONFIG_VGA_COREBOOT) return cbvga_save_restore(cmd, seg, data); + if (CONFIG_VGA_SERCON) + return sercon_save_restore(cmd, seg, data); return stdvga_save_restore(cmd, seg, data); }
+static inline void vgahw_scroll(struct cursorpos win, struct cursorpos winsize + , int lines, struct carattr ca) +{ + if (CONFIG_VGA_SERCON) + sercon_scroll(win, winsize, lines, ca); + else + vgafb_scroll(win, winsize, lines, ca); +} + +static inline void vgahw_write_char(struct cursorpos cp, struct carattr ca) +{ + if (CONFIG_VGA_SERCON) + sercon_write_char(cp, ca); + else + vgafb_write_char(cp, ca); +} + +static inline struct carattr vgahw_read_char(struct cursorpos cp) +{ + if (CONFIG_VGA_SERCON) + return sercon_read_char(cp); + return vgafb_read_char(cp); +} + +static inline void vgahw_write_pixel(u8 color, u16 x, u16 y) +{ + if (CONFIG_VGA_SERCON) + sercon_write_pixel(color, x, y); + else + vgafb_write_pixel(color, x, y); +} + +static inline u8 vgahw_read_pixel(u16 x, u16 y) +{ + if (CONFIG_VGA_SERCON) + return sercon_read_pixel(x, y); + return vgafb_read_pixel(x, y); +} + #endif // vgahw.h diff --git a/vgasrc/vgautil.h b/vgasrc/vgautil.h index 08c4e8d..8436e60 100644 --- a/vgasrc/vgautil.h +++ b/vgasrc/vgautil.h @@ -34,6 +34,29 @@ struct bregs; void clext_1012(struct bregs *regs); int clext_setup(void);
+// sercon.c +void sercon_write_pixel(u8 color, u16 x, u16 y); +u8 sercon_read_pixel(u16 x, u16 y); +struct cursorpos; +struct carattr; +void sercon_scroll(struct cursorpos win, struct cursorpos winsize + , int lines, struct carattr ca); +void sercon_write_char(struct cursorpos cp, struct carattr ca); +struct carattr sercon_read_char(struct cursorpos cp); +struct vgamode_s *sercon_find_mode(int mode); +void sercon_list_modes(u16 seg, u16 *dest, u16 *last); +int sercon_get_window(struct vgamode_s *vmode_g, int window); +int sercon_set_window(struct vgamode_s *vmode_g, int window, int val); +int sercon_get_linelength(struct vgamode_s *vmode_g); +int sercon_set_linelength(struct vgamode_s *vmode_g, int val); +int sercon_get_displaystart(struct vgamode_s *vmode_g); +int sercon_set_displaystart(struct vgamode_s *vmode_g, int val); +int sercon_get_dacformat(struct vgamode_s *vmode_g); +int sercon_set_dacformat(struct vgamode_s *vmode_g, int val); +int sercon_save_restore(int cmd, u16 seg, void *data); +int sercon_set_mode(struct vgamode_s *vmode_g, int flags); +int sercon_setup(void); + // stdvgaio.c u8 stdvga_pelmask_read(void); void stdvga_pelmask_write(u8 val);
Signed-off-by: Kevin O'Connor kevin@koconnor.net --- vgasrc/sercon.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ vgasrc/vgainit.c | 3 ++- vgasrc/vgautil.h | 1 + 3 files changed, 50 insertions(+), 1 deletion(-)
diff --git a/vgasrc/sercon.c b/vgasrc/sercon.c index 35b60a5..11df1dc 100644 --- a/vgasrc/sercon.c +++ b/vgasrc/sercon.c @@ -99,6 +99,53 @@ sercon_clear_screen(void)
/**************************************************************** + * Serial port input + ****************************************************************/ + +static u8 +enqueue_key(u16 keycode) +{ + u16 buffer_start = GET_BDA(kbd_buf_start_offset); + u16 buffer_end = GET_BDA(kbd_buf_end_offset); + + u16 buffer_head = GET_BDA(kbd_buf_head); + u16 buffer_tail = GET_BDA(kbd_buf_tail); + + u16 temp_tail = buffer_tail; + buffer_tail += 2; + if (buffer_tail >= buffer_end) + buffer_tail = buffer_start; + + if (buffer_tail == buffer_head) + return 0; + + SET_FARVAR(SEG_BDA, *(u16*)(temp_tail+0), keycode); + SET_BDA(kbd_buf_tail, buffer_tail); + return 1; +} + +void +sercon_check_event(void) +{ + if (!CONFIG_VGA_SERCON) + return; + + // XXX - move cursor if current position doesn't match last sent location + + if (!(inb(SERCON_PORT + SEROFF_LSR) & 0x01)) + // No input data + return; + + u8 in = inb(SERCON_PORT + SEROFF_DATA); + + // XXX - check for multi-byte input sequence + + u16 keycode = in; // XXX - lookup real keycode + enqueue_key(keycode); +} + + +/**************************************************************** * Interface functions ****************************************************************/
diff --git a/vgasrc/vgainit.c b/vgasrc/vgainit.c index c6c8149..ec3996f 100644 --- a/vgasrc/vgainit.c +++ b/vgasrc/vgainit.c @@ -93,12 +93,13 @@ void VISIBLE16 handle_timer_hook(void) { swcursor_check_event(); + sercon_check_event(); }
static void hook_timer_irq(void) { - if (!CONFIG_VGA_EMULATE_TEXT) + if (!CONFIG_VGA_EMULATE_TEXT && !CONFIG_VGA_SERCON) return; extern void entry_timer_hook(void); extern void entry_timer_hook_extrastack(void); diff --git a/vgasrc/vgautil.h b/vgasrc/vgautil.h index 8436e60..7f42920 100644 --- a/vgasrc/vgautil.h +++ b/vgasrc/vgautil.h @@ -35,6 +35,7 @@ void clext_1012(struct bregs *regs); int clext_setup(void);
// sercon.c +void sercon_check_event(void); void sercon_write_pixel(u8 color, u16 x, u16 y); u8 sercon_read_pixel(u16 x, u16 y); struct cursorpos;
Signed-off-by: Kevin O'Connor kevin@koconnor.net --- vgasrc/sercon.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ vgasrc/vgabios.c | 2 +- vgasrc/vgabios.h | 5 +++++ vgasrc/vgaentry.S | 7 ++++++ vgasrc/vgainit.c | 9 +++++--- 5 files changed, 84 insertions(+), 4 deletions(-)
diff --git a/vgasrc/sercon.c b/vgasrc/sercon.c index 11df1dc..609a05e 100644 --- a/vgasrc/sercon.c +++ b/vgasrc/sercon.c @@ -7,6 +7,7 @@ #include <stdarg.h> // va_list
#include "biosvar.h" // GET_GLOBAL +#include "bregs.h" // struct bregs #include "output.h" // dprintf #include "vgabios.h" // struct vgamode_s #include "vgafb.h" // struct cursorpos @@ -146,6 +147,61 @@ sercon_check_event(void)
/**************************************************************** + * Hooked 0x10 handling + ****************************************************************/ + +struct segoff_s sercon_entry_hook_resume VAR16 VISIBLE16; + +static void +sercon_1000(struct bregs *regs) +{ + if (!(regs->al & 0x80)) + sercon_clear_screen(); +} + +static void +sercon_100e(struct bregs *regs) +{ + // Just like handle_100e, but don't update the cursor position + struct carattr ca = {regs->al, regs->bl, 0}; + struct cursorpos cp = get_cursor_pos(GET_BDA(video_page)); + write_teletype(&cp, ca); +} + +static void +sercon_1013(struct bregs *regs) +{ + // Use the main handle_10 routine, but don't update the cursor position + u8 oldal = regs->al; + regs->al &= ~0x01; + handle_10(regs); + regs->al = oldal; +} + +void VISIBLE16 +sercon_10(struct bregs *regs) +{ + if (!CONFIG_VGA_SERCON) + return; + + switch (regs->ah) { + case 0x00: sercon_1000(regs); break; + case 0x0e: sercon_100e(regs); break; + case 0x13: sercon_1013(regs); break; + + case 0x06: + case 0x07: + case 0x09: + case 0x0a: + handle_10(regs); + break; + default: + break; + } +} + + +/**************************************************************** * Interface functions ****************************************************************/
@@ -287,5 +343,14 @@ sercon_setup(void) { outb(0x03, SERCON_PORT + SEROFF_LCR); // 8N1 outb(0x01, SERCON_PORT + 0x02); // enable fifo + + struct segoff_s cur_entry = GET_IVT(0x10); + if (cur_entry.seg != SEG_BIOS) { + // Looks like a VGA BIOS has already been installed - hook it. + SET_VGA(sercon_entry_hook_resume, cur_entry); + extern void entry_sercon_10(void); + SET_IVT(0x10, SEGOFF(get_global_seg(), (u32)entry_sercon_10)); + } + return 0; } diff --git a/vgasrc/vgabios.c b/vgasrc/vgabios.c index 5b08022..dc7ea9c 100644 --- a/vgasrc/vgabios.c +++ b/vgasrc/vgabios.c @@ -168,7 +168,7 @@ write_char(struct cursorpos *pcp, struct carattr ca)
// Write a character to the screen at a given position. Implement // special characters and scroll the screen if necessary. -static void +void write_teletype(struct cursorpos *pcp, struct carattr ca) { switch (ca.car) { diff --git a/vgasrc/vgabios.h b/vgasrc/vgabios.h index 3d5bbfe..5e727bb 100644 --- a/vgasrc/vgabios.h +++ b/vgasrc/vgabios.h @@ -84,5 +84,10 @@ int bda_save_restore(int cmd, u16 seg, void *data); struct vgamode_s *get_current_mode(void); int vga_set_mode(int mode, int flags); extern struct video_func_static static_functionality; +struct cursorpos; +struct carattr; +void write_teletype(struct cursorpos *pcp, struct carattr ca); +struct bregs; +void handle_10(struct bregs *regs);
#endif // vgabios.h diff --git a/vgasrc/vgaentry.S b/vgasrc/vgaentry.S index 53be2b3..f064681 100644 --- a/vgasrc/vgaentry.S +++ b/vgasrc/vgaentry.S @@ -159,3 +159,10 @@ entry_timer_hook_extrastack: movl PUSHBREGS_size(%eax), %esp RESTOREBREGS_DSEAX ljmpw *%cs:Timer_Hook_Resume + + // Serial console irq hooking + DECLFUNC entry_sercon_10 +entry_sercon_10: + ENTRY_ARG sercon_10 + ljmpw *%cs:sercon_entry_hook_resume + // XXX - support extra stack on sercon entry? diff --git a/vgasrc/vgainit.c b/vgasrc/vgainit.c index ec3996f..3df1824 100644 --- a/vgasrc/vgainit.c +++ b/vgasrc/vgainit.c @@ -155,6 +155,7 @@ vga_post(struct bregs *regs) SET_VGA(VgaBDF, bdf); }
+ struct segoff_s orig_entry = GET_IVT(0x10); int ret = vgahw_setup(); if (ret) { dprintf(1, "Failed to initialize VGA hardware. Exiting.\n"); @@ -169,10 +170,12 @@ vga_post(struct bregs *regs) if (CONFIG_VGA_STDVGA_PORTS) stdvga_build_video_param();
- extern void entry_10(void); - SET_IVT(0x10, SEGOFF(get_global_seg(), (u32)entry_10)); + if (orig_entry.segoff == GET_IVT(0x10).segoff) { + extern void entry_10(void); + SET_IVT(0x10, SEGOFF(get_global_seg(), (u32)entry_10));
- allocate_extra_stack(); + allocate_extra_stack(); + }
hook_timer_irq();
Hi,
Finally, after a looong time being busy with other stuff, coming back to this one.
I was curious to see what the "sercon" stuff would look like in SeaVGABIOS instead of in SeaBIOS. So, I put together a quick prototype. The code is also at:
https://github.com/KevinOConnor/seabios/tree/testing
This is just a proof-of-concept thing - I didn't implement any of the useful features you have in your series. Specifically, it doesn't unclutter the serial output, doesn't implement cp437 translations, and doesn't handle multi-byte input. It does does have basic input, output, and split-output handling though.
Cool, that saves me the work trying this, to compare.
I'm not sure if this is better than a SeaBIOS implementation. I suspect it will be easier to handle vga quirks this way.
Works fine for me. Advantages of this approach are (a) vga quirks and (b) it works fine as drop-in replacement of the current sgabios.bin in qemu.
I'm not sure how important the vga quirks are though. I'd guess the use cases where the quirks are essential so the guest can do proper graphics output (such as old xorg server in the guest) and the use cases where you want serial console support for the bios (headless server) have little to no overlap.
However, handling exotic serial outputs (eg, mmio and usb based) would be better suited in SeaBIOS.
Indeed. Extending this for other hardware is alot easier when it is in seabios and can re-use the code there.
cheers, Gerd
On Tue, Jan 17, 2017 at 11:33:54AM +0100, Gerd Hoffmann wrote:
I was curious to see what the "sercon" stuff would look like in SeaVGABIOS instead of in SeaBIOS.
[...]
I'm not sure how important the vga quirks are though. I'd guess the use cases where the quirks are essential so the guest can do proper graphics output (such as old xorg server in the guest) and the use cases where you want serial console support for the bios (headless server) have little to no overlap.
I agree on no overlap. My big worry with the quirks is that the symptoms are just a mysterious crash. Should someone turn it on then some guests (or perhaps even some applications) will stop running with no indication of the root cause of the problem. It's also a significant regression from sgabios.
However, handling exotic serial outputs (eg, mmio and usb based) would be better suited in SeaBIOS.
Indeed. Extending this for other hardware is alot easier when it is in seabios and can re-use the code there.
After I posted the seavgabios stuff above, it occurred to me that another way to tackle this would be to keep the code in seabios and harden the sercon assembler entry point. Specifically, the assembler code could check that the last mode set wasn't from a vesa mode set call and it could check that calll (vs callw) works. It's a bit ugly to do that in assembler, but I think that would prevent all the nasty crashes that the quirks cause. Alas, I didn't get a chance to prototype it.
-Kevin
Hi,
After I posted the seavgabios stuff above, it occurred to me that another way to tackle this would be to keep the code in seabios and harden the sercon assembler entry point. Specifically, the assembler code could check that the last mode set wasn't from a vesa mode set call and it could check that calll (vs callw) works.
Additionally we need to hook the sercon code only into a few subcalls, and with the exception of 00h (aka set-mode) none of them are useful for software which wants program vesa modes. So, yes, I think that could work.
I'll go have a look, starting with old linux xorg+x86emu. Do you remember which windows versions have problems too?
cheers, Gerd
Hi,
I'll go have a look, starting with old linux xorg+x86emu.
That one looks easy, uses vesa calls only, even for switching back to text mode. So simply filtering out the ah=4fh cases in assembler code should do the trick here.
cheers, Gerd
On Wed, Jan 18, 2017 at 12:40:24PM +0100, Gerd Hoffmann wrote:
After I posted the seavgabios stuff above, it occurred to me that another way to tackle this would be to keep the code in seabios and harden the sercon assembler entry point. Specifically, the assembler code could check that the last mode set wasn't from a vesa mode set call and it could check that calll (vs callw) works.
Additionally we need to hook the sercon code only into a few subcalls, and with the exception of 00h (aka set-mode) none of them are useful for software which wants program vesa modes. So, yes, I think that could work.
One of the BSDs (I think freebsd) uses the x86emu code to emulate the vgabios at kernel startup. I'm not sure if it only makes vesa calls.
I was thinking a few instructions to test if calll worked would be sufficient to catch the x86emu case - something like:
pushl $0xffffffff calll 1f 1:popl %eax cmpl %eax, $1b jne running_on_broken_x86emu
I'll go have a look, starting with old linux xorg+x86emu. Do you remember which windows versions have problems too?
The notes at the top of scripts/vgafixup.py have:
# The x86emu code widely used in Linux distributions when running Xorg # in vesamode is known to have issues with "retl", "leavel", "entryl", # "leal", and some variants of "calll". This code modifies those # instructions that are known to be generated by gcc to avoid # triggering the x86emu bugs.
# It is also known that the Windows vgabios emulator has issues with # addressing negative offsets to the %esp register. That has been # worked around by not using the gcc parameter "-fomit-frame-pointer" # when compiling.
I believe at least WinXP had the esp problem. In addition to the above at least Vista has the skifree bug (stack can't be in high memory).
-Kevin
Hi,
I was thinking a few instructions to test if calll worked would be sufficient to catch the x86emu case - something like:
pushl $0xffffffff calll 1f 1:popl %eax cmpl %eax, $1b jne running_on_broken_x86emu
Ok, finally picked this up again.
Rebased the branch, added a patch for x86emu detection. https://www.kraxel.org/cgit/seabios/log/?h=serial
--- a/src/romlayout.S +++ b/src/romlayout.S @@ -531,12 +531,31 @@ entry_10_hooked: pushfw pushl %cs:sercon_int10_hook_resume + // Detect broken x86emu versions + pushl %eax + pushl %ebx + pushl $0xffffffff + calll 1f +1: popl %eax + movl $1b, %ebx + cmpl %eax, %ebx + jne running_on_broken_x86emu + popl %ebx // $0xffffffff + popl %ebx + popl %eax + pushl $sercon_10_splitmode #if CONFIG_ENTRY_EXTRASTACK jmp irqentry_arg_extrastack #else jmp irqentry_arg #endif +running_on_broken_x86emu: + popw %bx + popl %ebx + popl %eax + // skip sercon code, jump straight to vgabios + iretw // int 18/19 are special - they reset stack and call into 32bit mode. DECLFUNC entry_19
How is x86emu broken? I'm assuming it ignores the prefix for call, so it'll push a 16bit return address instead of a 32bit return address to the stack?
Patch tested on RHEL-7 and RHEL-5 (known-broken x86emu in xorg) so far.
cheers, Gerd
On Mon, Sep 04, 2017 at 04:02:08PM +0200, Gerd Hoffmann wrote:
I was thinking a few instructions to test if calll worked would be sufficient to catch the x86emu case - something like:
pushl $0xffffffff calll 1f 1:popl %eax cmpl %eax, $1b jne running_on_broken_x86emu
Ok, finally picked this up again.
Rebased the branch, added a patch for x86emu detection. https://www.kraxel.org/cgit/seabios/log/?h=serial
--- a/src/romlayout.S +++ b/src/romlayout.S @@ -531,12 +531,31 @@ entry_10_hooked: pushfw pushl %cs:sercon_int10_hook_resume + // Detect broken x86emu versions + pushl %eax + pushl %ebx + pushl $0xffffffff + calll 1f +1: popl %eax + movl $1b, %ebx + cmpl %eax, %ebx + jne running_on_broken_x86emu + popl %ebx // $0xffffffff + popl %ebx + popl %eax
pushl $sercon_10_splitmode #if CONFIG_ENTRY_EXTRASTACK jmp irqentry_arg_extrastack #else jmp irqentry_arg #endif +running_on_broken_x86emu: + popw %bx + popl %ebx + popl %eax + // skip sercon code, jump straight to vgabios + iretw // int 18/19 are special - they reset stack and call into 32bit mode. DECLFUNC entry_19
How is x86emu broken? I'm assuming it ignores the prefix for call, so it'll push a 16bit return address instead of a 32bit return address to the stack?
I think so. Might be safer to save/restore %esp though. This sequence might be simpler:
pushl %eax movl %esp, %eax pushl $1f retl 1: cmpl %esp, %eax jne broken_x86emu popl %eax
...
broken_x86emu: movl %eax, %esp popl %eax iretw
Note, though, in addition to checking for broken x86emu, the assembler entry code would also have to check that the last mode was not set via a vesa call (to avoid the "skifree" bug).
-Kevin
Hi,
How is x86emu broken? I'm assuming it ignores the prefix for call, so it'll push a 16bit return address instead of a 32bit return address to the stack?
I think so. Might be safer to save/restore %esp though. This sequence might be simpler:
pushl %eax movl %esp, %eax pushl $1f retl 1: cmpl %esp, %eax jne broken_x86emu popl %eax
...
broken_x86emu: movl %eax, %esp popl %eax iretw
Ah, fixup the stack without assuming specific behavior. Neat trick.
Note, though, in addition to checking for broken x86emu, the assembler entry code would also have to check that the last mode was not set via a vesa call (to avoid the "skifree" bug).
Ok, Is there some easy way to access the BDA from assembler code (specifically the video_mode field)? Guess I must set a segment register for that. Maybe it's easier to just place an additional flag in the fseg which we can easily reach via cs override ...
cheers, Gerd
On Tue, Sep 05, 2017 at 10:01:41AM +0200, Gerd Hoffmann wrote:
Note, though, in addition to checking for broken x86emu, the assembler entry code would also have to check that the last mode was not set via a vesa call (to avoid the "skifree" bug).
Ok, Is there some easy way to access the BDA from assembler code (specifically the video_mode field)? Guess I must set a segment register for that. Maybe it's easier to just place an additional flag in the fseg which we can easily reach via cs override ...
The flag can't be in the f-segment because it needs to be read/writable at runtime. I ran some tests - it looks like it is okay to access the varlow segment - one just can't write to it. Unfortunately, it's not enough to simply test for a legacy modeset call (int 0x10 ah=0x00) as it looks like Vista calls that in its emulation mode during startup. However, it looks like it is okay if one verifies that the legacy modeset call is for a text mode (eg, int 0x10 ax=0x0003).
I put together the patch below (based off of current seabios master). It doesn't do anything except test that one can safely enter the C code. It survives winxp, winvista, winvista+skifree, and xorg on fedora 13 (these were my typical trouble spots with vgabios testing).
-Kevin
From ad37047c3bbd7b5d92b849acfee3844a64ac1a28 Mon Sep 17 00:00:00 2001
From: Kevin O'Connor kevin@koconnor.net Date: Tue, 5 Sep 2017 11:45:08 -0400 Subject: [PATCH] sercon: Add support for hooking vga handler
Signed-off-by: Gerd Hoffmann kraxel@redhat.com Signed-off-by: Kevin O'Connor kevin@koconnor.net --- Makefile | 1 + src/Kconfig | 5 +++++ src/config.h | 1 + src/optionroms.c | 6 +++++- src/romlayout.S | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/sercon.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/util.h | 3 +++ 7 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/sercon.c
diff --git a/Makefile b/Makefile index 946df7e..07ac747 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,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 \ + sercon.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/Kconfig b/src/Kconfig index 77ec9c7..55a87cb 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -306,6 +306,11 @@ menu "Hardware support" default y help Support serial ports. This also enables int 14 serial port calls. + config SERCON + bool "Serial console" + default y + help + Support redirecting vga output to the serial console. config LPT bool "Parallel port" default y diff --git a/src/config.h b/src/config.h index baca029..e56d4a7 100644 --- a/src/config.h +++ b/src/config.h @@ -100,6 +100,7 @@ #define DEBUG_HDL_pmm 1 #define DEBUG_HDL_pcibios 9 #define DEBUG_HDL_apm 9 +#define DEBUG_HDL_sercon 9
#define DEBUG_unimplemented 2 #define DEBUG_invalid 3 diff --git a/src/optionroms.c b/src/optionroms.c index 65f7fe0..4f6611e 100644 --- a/src/optionroms.c +++ b/src/optionroms.c @@ -404,8 +404,10 @@ struct rom_header *VgaROM; void vgarom_setup(void) { - if (! CONFIG_OPTIONROMS) + if (! CONFIG_OPTIONROMS) { + sercon_setup(); return; + }
dprintf(1, "Scan for VGA option rom\n");
@@ -432,6 +434,8 @@ vgarom_setup(void) run_file_roms("vgaroms/", 1, NULL); rom_reserve(0);
+ sercon_setup(); + if (rom_get_last() == BUILD_ROM_START) // No VGA rom found return; diff --git a/src/romlayout.S b/src/romlayout.S index 89b3784..8ed53dc 100644 --- a/src/romlayout.S +++ b/src/romlayout.S @@ -414,6 +414,61 @@ __csm_return: popfw lretw
+// Serial console "hooked vga" entry point + DECLFUNC entry_sercon +entry_sercon: + // Setup for chain loading to real vga handler + pushfw + pushl %cs:sercon_real_vga_handler + + // Set %ds to varlow segment + cli + cld + pushw %ds + pushl %eax + movl $_zonelow_seg, %eax + movl %eax, %ds + + // Test if the sercon handler can be called + movl %esp, %eax // Test for broken x86emu + pushl $1f + retl +1: cmpl %esp, %eax + jne 4f + cmpb $0, sercon_enable // Test that sercon is enabled + je 3f + + // Call handle_sercon() on the extra stack +2: movl StackPos, %eax + subl $PUSHBREGS_size+8, %eax + SAVEBREGS_POP_DSEAX + movl %esp, PUSHBREGS_size(%eax) + movw %ss, PUSHBREGS_size+4(%eax) + + movw %ds, %dx // Setup %ss/%esp and call function + movw %dx, %ss + movl %eax, %esp + calll handle_sercon + + movl %esp, %eax // Restore registers and return + movw PUSHBREGS_size+4(%eax), %ss + movl PUSHBREGS_size(%eax), %esp + RESTOREBREGS_DSEAX + iretw + + // sercon disabled - verify not 0x03 modeset and otherwise exit +3: popl %eax + cmpw $0x0003, %ax + jne 5f + pushl %eax + jmp 2b + + // Running on broken x86emu - restore stack and exit +4: movl %eax, %esp + popl %eax +5: popw %ds + iretw +
/**************************************************************** * Interrupt entry points diff --git a/src/sercon.c b/src/sercon.c new file mode 100644 index 0000000..7307772 --- /dev/null +++ b/src/sercon.c @@ -0,0 +1,54 @@ +// Serial console support +// +// Copyright (C) 2017 Kevin O'Connor kevin@koconnor.net +// +// This file may be distributed under the terms of the GNU LGPLv3 license. + +#include "biosvar.h" // GET_IVT +#include "bregs.h" // struct bregs +#include "output.h" // dprintf +#include "romfile.h" // romfile_loadint +#include "util.h" // sercon_setup + +struct segoff_s sercon_real_vga_handler VARFSEG; +u8 sercon_enable VARLOW; + +void +sercon_setup(void) +{ + ASSERT32FLAT(); + if (!CONFIG_SERCON || (GET_IVT(0x10).segoff != FUNC16(entry_10).segoff + && !romfile_loadint("etc/sercon-hook", 0))) + return; + + dprintf(3, "init sercon\n"); + sercon_real_vga_handler = GET_IVT(0x10); + SET_IVT(0x10, FUNC16(entry_sercon)); +} + +static void +sercon_1000(struct bregs *regs) +{ + int mode = regs->al & 0x7f; + SET_LOW(sercon_enable, (mode == 0x03)); +} + +static void +sercon_104f(struct bregs *regs) +{ + // Disable sercon entry point on any vesa modeset + if (regs->al == 0x00) + SET_LOW(sercon_enable, 0); +} + +void VISIBLE16 +handle_sercon(struct bregs *regs) +{ + if (!CONFIG_SERCON) + return; + debug_enter(regs, DEBUG_HDL_sercon); + switch (regs->ah) { + case 0x00: sercon_1000(regs); break; + case 0x4f: sercon_104f(regs); break; + } +} diff --git a/src/util.h b/src/util.h index 8269057..8b747b3 100644 --- a/src/util.h +++ b/src/util.h @@ -229,6 +229,9 @@ void startBoot(void); void reloc_preinit(void *f, void *arg); void code_mutable_preinit(void);
+// sercon.c +void sercon_setup(void); + // serial.c void serial_setup(void); void lpt_setup(void);