[SeaBIOS] [RFC PATCH 2/2] serial console, input

Kevin O'Connor kevin at koconnor.net
Fri Jul 1 19:07:39 CEST 2016


On Fri, Jul 01, 2016 at 12:54:31PM +0200, Gerd Hoffmann wrote:
> Signed-off-by: Gerd Hoffmann <kraxel at redhat.com>
> ---
>  src/clock.c  |   1 +
>  src/serial.c | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  src/util.h   |   1 +
>  3 files changed, 257 insertions(+)
> 
> 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/serial.c b/src/serial.c
> index 74b91bb..d72dd01 100644
> --- a/src/serial.c
> +++ b/src/serial.c
> @@ -655,3 +655,258 @@ void sercon_enable(void)
>      outb(0x01, addr + 0x02);       // enable fifo
>      enable_vga_console();
>  }
> +
> +/****************************************************************
> + * serial input
> + ****************************************************************/
> +
> +VARLOW u8 rx_buf[16];
> +VARLOW u8 rx_bytes;
> +
> +VARLOW struct {
> +    char seq[4];
> +    u8 len;
> +    u8 scancode;
> +} termseq[] = {
> +    { .seq = "OP", .len = 2, .scancode = 0x3b },    // F1
> +    { .seq = "OQ", .len = 2, .scancode = 0x3c },    // F2
> +    { .seq = "OR", .len = 2, .scancode = 0x3d },    // F3
> +    { .seq = "OS", .len = 2, .scancode = 0x3e },    // F4
> +    { .seq = "[A", .len = 2, .scancode = 0xc8 },    // up
> +    { .seq = "[B", .len = 2, .scancode = 0xd0 },    // down
> +    { .seq = "[C", .len = 2, .scancode = 0xcd },    // right
> +    { .seq = "[D", .len = 2, .scancode = 0xcb },    // left
> +};

It would be preferable to mark constant data with "static VAR16"
instead of VARLOW.

> +
> +#define FLAG_CTRL  (1<<0)
> +#define FLAG_SHIFT (1<<1)
> +
> +VARLOW struct {
> +    u8 flags;
> +    u8 scancode;
> +} termchr[256] = {
> +    [ '1'        ] = { .scancode = 0x02,                      },

I think this table should be generated at runtime from
kbd.c:scan_to_keycode[].  Since it doesn't change at runtime,
malloc_fseg() / GET_GLOBAL() could be used instead of VARLOW.

[...]
> +static void sercon_sendkey(u8 scancode, u8 flags)
> +{
> +    if (flags & FLAG_CTRL)
> +        process_key(0x1d);
> +    if (flags & FLAG_SHIFT)
> +        process_key(0x2a);
> +
> +    if (scancode & 0x80) {
> +        process_key(0xe0);
> +        process_key(scancode & ~0x80);
> +        process_key(0xe0);
> +        process_key(scancode);
> +    } else {
> +        process_key(scancode);
> +        process_key(scancode | 0x80);
> +    }
> +
> +    if (flags & FLAG_SHIFT)
> +        process_key(0x2a | 0x80);
> +    if (flags & FLAG_CTRL)
> +        process_key(0x1d | 0x80);
> +}

Is it necessary to use process_key() here instead of injecting the
keycode directly with enqueue_key()?  I think the only difference is
the CONFIG_KBD_CALL_INT15_4F stuff and I'm not sure if anything
interesting needs that.

> +
> +void VISIBLE16
> +sercon_check_event(void)

Does this need VISIBLE16?

> +{
> +    u16 addr = GET_LOW(sercon_port);
> +    u8 byte, scancode, flags, 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_flush_lazy();
> +
> +    // 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_LOW(termseq[seq].len);
> +            if (GET_LOW(rx_bytes) < len + 1)
> +                continue;
> +            for (chr = 0; chr < len; chr++) {
> +                if (GET_LOW(termseq[seq].seq[chr]) != GET_LOW(rx_buf[chr + 1]))
> +                    break;
> +            }
> +            if (chr == len) {
> +                scancode = GET_LOW(termseq[seq].scancode);
> +                sercon_sendkey(scancode, 0);
> +                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]);
> +    scancode = GET_LOW(termchr[chr].scancode);
> +    flags = GET_LOW(termchr[chr].flags);
> +    if (scancode)
> +        sercon_sendkey(scancode, flags);
> +    shiftbuf(1);
> +    goto next_char;
> +}

If I understand correctly, most keys are sent on the serial port as
single bytes, but there are a few keys that are sent as multi-byte
sequences.  There's a lot of complexity to implement buffering for
that unusual case.  I wonder if the buffer could be avoided - I played
with it a little and came up with the below (totally untested).  I'm
not sure if it's an improvement.

-Kevin


u8 multibyte_read_pos VARLOW;
u8 multibyte_read_count VARLOW;

void
sercon_check_event(void)
{
    u16 addr = GET_LOW(sercon_port);
    ...

    u8 mb_pos = GET_LOW(multibyte_read_pos);
    u8 mb_count = GET_LOW(multibyte_read_count);
    u8 mustflush = mb_count != 0;

    // read and process data
    while (inb(addr + SEROFF_LSR) & 0x01) {
        u8 byte = inb(addr + SEROFF_DATA);
        if (mb_count) {
            // In a multi-byte sequence
            while (GET_GLOBAL(termseq[mb_pos].seq[mb_count-1]) != byte) {
                // Byte didn't match this sequence - find one that does
                mb_pos++;
                if (mb_pos >= ARRAY_SIZE(termseq)
                    || memcmp_far(GLOBAL_SEG, termseq[mb_pos-1].seq
                                  , GLOBAL_SEG, termseq[mb_pos].seq
                                  , mb_count-1) != 0)
                    // No match - must flush previusly queued keys
                    dump_multibyte_sequence(mb_pos, mb_count);
                    mb_pos = mb_count = mustflush = 0;
                    break;
                }
            }
            if (mb_count) {
                if (!GET_GLOBAL(termseq[mb_pos].seq[mb_count])) {
                    // sequence complete
                    sercon_sendkey(GET_GLOBAL(termseq[seq].scancode), 0);
                    mb_pos = mb_count = mustflush = 0;
                } else {
                    // Got another key in this sequence - continue checking
                    mb_count++;
                }
                continue;
            }
        }
        if (byte == 0x1b) {
            // Start multi-byte sequence check;
            mb_pos = 0;
            mb_count = 1;
            continue;
        }
        // Send normal key
        sercon_sendkey(GET_LOW(termchr[chr].scancode), GET_LOW(termchr[chr].flags));
        mustflush = 0;
    }

    if (mustflush && mb_count) {
        // Too long to read multi-byte sequence - must flush
        dump_multibyte_sequence(mb_pos, mb_count);
        mb_count = mb_pos = 0;
    }
    SET_LOW(multibyte_read_count, mb_count);
    SET_LOW(multibyte_read_pos, mb_pos);
}

static void
dump_multibyte_sequence(u8 mb_pos, u8 mb_count)
{
    sercon_sendkey(GET_LOW(termchr[0x1b].scancode), GET_LOW(termchr[0x1b].flags));
    int i;
    for (i=0; i<mb_count-1; i++) {
        u8 key = GET_GLOBAL(termseq[mb_pos].seq[i]);
        sercon_sendkey(GET_LOW(termchr[key].scancode), GET_LOW(termchr[key].flags));
    }
}



More information about the SeaBIOS mailing list