[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