[SeaBIOS] [RFC PATCH 1/2] serial console, output

Kevin O'Connor kevin at koconnor.net
Fri Jul 1 17:47:32 CEST 2016


On Fri, Jul 01, 2016 at 12:54:30PM +0200, Gerd Hoffmann wrote:
> Signed-off-by: Gerd Hoffmann <kraxel at redhat.com>

Thanks.  See my comments below.

[...]
> --- 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);
>  }

Might as well remove handle_10 and call sercon_10 directly from
romlayout.S.

[...]
> --- a/src/serial.c
> +++ b/src/serial.c
> @@ -315,3 +315,343 @@ handle_17(struct bregs *regs)
>      default:   handle_17XX(regs); break;
>      }
>  }
> +
> +/****************************************************************
> + * serial console output
> + ****************************************************************/

I think this code should go in a new c file and not modify serial.c.

[...]
> +VARLOW u8 sercon_mode;
> +VARLOW u8 sercon_cols;
> +VARLOW u8 sercon_rows;
> +
> +VARLOW u8 sercon_row;
> +VARLOW u8 sercon_col;

I think the code should use the BDA equivalents of the above instead
of declaring them in the private VARLOW space.  Some old programs may
rely on the equivalents in the BDA being updated.

> +
> +/*
> + * 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_col_lazy   is the column of the terminal's cursor, typically
> + *                   a few positions left of sercon_col.
> + * sercon_attr_last  is the most recent attribute sent to the terminal.
> + */
> +VARLOW u8 sercon_attr_last;
> +VARLOW u8 sercon_col_lazy;
> +VARLOW u8 sercon_char[8];
> +VARLOW u8 sercon_attr[8];
> +
> +VARLOW u8 sercon_cmap[8] = { '0', '4', '2', '6', '1', '5', '3', '7' };

For constant data (sercon_cmap) it would be preferable to use "static
VAR16" (see kbd.c:scan_to_keycode[] and mouse.c:sample_rates[] as
examples) and use GET_GLOBAL() instead of GET_LOW().

[...]
> +static void sercon_print_char(u8 chr, u8 attr)
> +{
> +    if (chr != 0x00) {
> +        sercon_set_attr(attr);
> +        sercon_putchar(chr);
> +    } else {
> +        /* move cursor right */
> +        sercon_putchar('\x1b');
> +        sercon_putchar('[');
> +        sercon_putchar('C');
> +    }
> +}
> +
> +static void sercon_flush_lazy(void)
> +{
> +    u8 count = GET_LOW(sercon_col) - GET_LOW(sercon_col_lazy);
> +    u8 pos = 0;
> +
> +    if (!count && !GET_LOW(sercon_attr[0]))
> +        return;
> +
> +    while (count) {
> +        sercon_print_char(GET_LOW(sercon_char[pos]),
> +                          GET_LOW(sercon_attr[pos]));
> +        count--;
> +        pos++;
> +    }
> +
> +    if (pos < ARRAY_SIZE(sercon_char) && GET_LOW(sercon_char[pos])) {
> +        sercon_print_char(GET_LOW(sercon_char[pos]),
> +                          GET_LOW(sercon_attr[pos]));
> +        /* move cursor left */
> +        sercon_putchar('\x1b');
> +        sercon_putchar('[');
> +        sercon_putchar('D');
> +    }
> +
> +    SET_LOW(sercon_col_lazy, GET_LOW(sercon_col));
> +    for (pos = 0; pos < ARRAY_SIZE(sercon_char); pos++) {
> +        SET_LOW(sercon_attr[pos], 0x07);
> +        SET_LOW(sercon_char[pos], 0x00);
> +    }
> +}

So, if I understand the above correctly, it's a buffer of recent
updates used to reduce serial bandwidth (and unclutter serial logs) in
the case where the application code issues a bunch of cursor moves /
cell updates that are mostly redundant.

> +/* Set video mode */
> +static void sercon_1000(struct bregs *regs)
> +{
> +    SET_LOW(sercon_mode, regs->al);
> +    switch (regs->al) {
> +    case 0x03:
> +    default:
> +        SET_LOW(sercon_cols, 80);
> +        SET_LOW(sercon_rows, 25);
> +        regs->al = 0x30;
> +    }
> +    SET_LOW(sercon_row, 0);
> +    SET_LOW(sercon_col, 0);
> +    SET_LOW(sercon_col_lazy, 0);
> +    sercon_init();
> +}

FYI, the screen should only be cleared if the high bit of the mode is
not set.  I do wonder if more of the vgabios interface code from
vgasrc/vgabios.c could be reused here.

> +/* Set cursor position */
> +static void sercon_1002(struct bregs *regs)
> +{
> +    u8 row = regs->dh;
> +    u8 col = regs->dl;
> +
> +    if (row == GET_LOW(sercon_row) &&
> +        col >= GET_LOW(sercon_col_lazy) &&
> +        col <  GET_LOW(sercon_col_lazy) + ARRAY_SIZE(sercon_char)) {
> +        SET_LOW(sercon_col, col);
> +        if (col+1 == GET_LOW(sercon_col_lazy) + ARRAY_SIZE(sercon_char))
> +            sercon_flush_lazy();
> +    } else {
> +        sercon_flush_lazy();
> +        if (row == GET_LOW(sercon_row) && col == 0) {
> +            sercon_putchar('\r');
> +        } else {
> +            sercon_cursor_goto(row, col);
> +        }
> +        SET_LOW(sercon_row, row);
> +        SET_LOW(sercon_col, col);
> +        SET_LOW(sercon_col_lazy, col);
> +    }
> +}
> +
> +/* Get cursor position */
> +static void sercon_1003(struct bregs *regs)
> +{
> +    regs->ax = 0;
> +    regs->ch = 0;
> +    regs->cl = 7;
> +    regs->dh = GET_LOW(sercon_row);
> +    regs->dl = GET_LOW(sercon_col);
> +}
> +
> +/* Scroll up window */
> +static void sercon_1006(struct bregs *regs)
> +{
> +    sercon_flush_lazy();
> +    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 = ' ';
> +}

FYI, the sgabios code seems to indicate that sercon_1008() needs to be
implemented for some programs to work properly.  The sgabios code even
implements a cache of recent writes to try to get it to work.  It's
ugly.

> +/* Write character and attribute at cursor position */
> +static void sercon_1009(struct bregs *regs)
> +{
> +    u16 count = regs->cx;
> +    u8 pos;
> +
> +    if (count == 1) {
> +        pos = GET_LOW(sercon_col) - GET_LOW(sercon_col_lazy);
> +        if (pos < ARRAY_SIZE(sercon_char)) {
> +            sercon_char[pos] = regs->al;
> +            sercon_attr[pos] = regs->bl;
> +        }
> +    } else if (regs->al == 0x20 &&
> +               GET_LOW(sercon_rows) * GET_LOW(sercon_cols) == count &&
> +               GET_LOW(sercon_row) == 0 &&
> +               GET_LOW(sercon_col) == 0) {
> +        /* override everything with spaces -> this is clear screen */
> +        sercon_flush_lazy();
> +        sercon_putchar('\x1b');
> +        sercon_putchar('[');
> +        sercon_putchar('2');
> +        sercon_putchar('J');
> +    } else {
> +        sercon_flush_lazy();
> +        sercon_set_attr(regs->bl);
> +        while (count) {
> +            sercon_putchar(regs->al);
> +            count--;
> +        }
> +        sercon_cursor_goto(GET_LOW(sercon_row),
> +                           GET_LOW(sercon_col));
> +    }
> +}
> +
> +/* Teletype output */
> +static void sercon_100e(struct bregs *regs)
> +{
> +    u8 pos, row, col;
> +
> +    switch (regs->al) {
> +    case 7:
> +        // beep
> +        break;
> +    case 8:
> +        sercon_flush_lazy();
> +        sercon_putchar(regs->al);
> +        col = GET_LOW(sercon_col);
> +        if (col > 0) {
> +            col--;
> +            SET_LOW(sercon_col, col);
> +            SET_LOW(sercon_col_lazy, col);
> +        }
> +        break;
> +    case '\r':
> +        sercon_flush_lazy();
> +        sercon_putchar(regs->al);
> +        SET_LOW(sercon_col, 0);
> +        SET_LOW(sercon_col_lazy, 0);
> +        break;
> +    case '\n':
> +        sercon_flush_lazy();
> +        sercon_putchar(regs->al);
> +        row = GET_LOW(sercon_row);
> +        row++;
> +        if (row >= GET_LOW(sercon_rows))
> +            row = GET_LOW(sercon_rows)-1;
> +        SET_LOW(sercon_row, row);
> +        break;
> +    default:
> +        pos = GET_LOW(sercon_col) - GET_LOW(sercon_col_lazy);
> +        sercon_char[pos] = regs->al;
> +        SET_LOW(sercon_col, GET_LOW(sercon_col) + 1);
> +        if (pos+1 == ARRAY_SIZE(sercon_char))
> +            sercon_flush_lazy();
> +        break;
> +    }
> +}

I'm finding it hard to understand how the "uncluttering" works in the
above.  I think it would be easier to understand if the vgabios
interface code was separated from the "uncluttering" code.

In particular, I wonder if it would be simpler if the interface code
was more similar to vgasrc/vgabios.c and it just translated requests
to set_cursor_pos(cursorpos) and write_char(cursorpos, charattr) type
calls.  Then the write_char() code could check if the position was
near previously written characters and buffer it if so - flushing if
not.  Thus the "uncluttering" could be mostly done in write_char().
The set_cursor_pos() implementation could be very lazy - it only needs
to update the BDA with the new position.  The sercon_check_event()
code could send an explicit cursor move for the case where the cursor
is idling at some position not after the last written character.

[...]
> +void VISIBLE16
> +sercon_10(struct bregs *regs)
> +{
> +    if (!GET_LOW(sercon_port))
> +        return;
> +
> +    switch (regs->ah) {
> +    case 0x00: sercon_1000(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:
> +        dprintf(1, "%s: ah 0x%02x, not implemented\n",
> +                __func__, regs->ah);

There is a warn_unimplemented(regs) for this.  Also, would be nice to
implement a sercon_10XX(regs) to match other areas of the code.

-Kevin



More information about the SeaBIOS mailing list