diff options
| author | Richard Qin <richard.qin.x@gmail.com> | 2026-11-10 01:16:40 +0800 |
|---|---|---|
| committer | Richard Qin <richard.qin.x@gmail.com> | 2026-11-10 01:16:40 +0800 |
| commit | 4a9624da0c7cf0c27a00e1ff497d13f2e4a0d03e (patch) | |
| tree | 91b95e736fa08a071e7814872fc822c2d3fcd682 /mubios/services | |
| parent | 9f45c30789b6133d4b5fb4ef07f0818a3ea4de48 (diff) | |
| download | rcc-project-4a9624da0c7cf0c27a00e1ff497d13f2e4a0d03e.tar.gz rcc-project-4a9624da0c7cf0c27a00e1ff497d13f2e4a0d03e.tar.xz | |
[mubios] Implement serial input and BIOS console key handling
Implement UART receive, transmit timeouts, configuration and status reporting for INT 14h functions 00h-03h. Initialize the BDA serial timeout and expose the driver interfaces.
Translate terminal characters and CSI/SS3 escape sequences into BIOS keyboard events for INT 16h. Add blocking reads, non-consuming status queries, enhanced key reads and keyboard buffer insertion.
Diffstat (limited to 'mubios/services')
| -rw-r--r-- | mubios/services/int13.c | 15 | ||||
| -rw-r--r-- | mubios/services/int14.c | 75 | ||||
| -rw-r--r-- | mubios/services/int15.c | 6 | ||||
| -rw-r--r-- | mubios/services/int16.c | 473 |
4 files changed, 443 insertions, 126 deletions
diff --git a/mubios/services/int13.c b/mubios/services/int13.c index 033a045..7186377 100644 --- a/mubios/services/int13.c +++ b/mubios/services/int13.c @@ -137,9 +137,9 @@ int13_service (struct bios_regs *regs) head = (u8)(regs->edx >> 8); if ((regs->edx & 0x00FFu) != BIOS_HARD_DISK || count == 0u - || get_ata_geometry (&geometry) != 0 - || sector == 0u || sector > geometry.sectors_per_track - || head >= geometry.heads || cylinder >= geometry.cylinders) + || get_ata_geometry (&geometry) != 0 || sector == 0u + || sector > geometry.sectors_per_track || head >= geometry.heads + || cylinder >= geometry.cylinders) { int13_fail (regs); return; @@ -152,9 +152,9 @@ int13_service (struct bios_regs *regs) } lba = (((u32)cylinder * geometry.heads) + head) - * geometry.sectors_per_track + (u32)sector - 1u; - buffer_address - = ((u32)regs->es << 4) + (u16)regs->ebx; + * geometry.sectors_per_track + + (u32)sector - 1u; + buffer_address = ((u32)regs->es << 4) + (u16)regs->ebx; if (ata_read_sectors28 (lba, (u16 *)buffer_address, count) != 0) { @@ -188,8 +188,7 @@ int13_service (struct bios_regs *regs) max_head = (u8)(geometry.heads - 1u); sectors_per_track = (u8)geometry.sectors_per_track; regs->eax &= 0xFFFF0000u; - regs->ecx = (regs->ecx & 0xFFFF0000u) - | (u32)(sectors_per_track & 0x3Fu) + regs->ecx = (regs->ecx & 0xFFFF0000u) | (u32)(sectors_per_track & 0x3Fu) | ((u32)(max_cylinder & 0x00FFu) << 8) | ((u32)(max_cylinder >> 8) << 14); regs->edx = (regs->edx & 0xFFFF0000u) | ((u32)max_head << 8) | 1u; diff --git a/mubios/services/int14.c b/mubios/services/int14.c index 86019b7..0cf25fb 100644 --- a/mubios/services/int14.c +++ b/mubios/services/int14.c @@ -7,55 +7,68 @@ */ #include "bios.h" -#include "io.h" #include "regs.h" -#define COM1_BASE 0x03F8u -#define COM_DATA 0x00u -#define COM_LSR 0x05u -#define COM_LSR_THRE 0x20u -#define COM_LSR_IDLE 0x40u - -static void -int14_putc (u8 value) -{ - while ((io_in8 ((u16)(COM1_BASE + COM_LSR)) & COM_LSR_THRE) == 0u) - { - } - - io_out8 ((u16)(COM1_BASE + COM_DATA), value); -} +#define BIOS_CF 0x0001u +#define BIOS_INVALID_FUNCTION 0x86u +#define BDA_COM_PORTS 0x0400u +#define BDA_COM_TIMEOUTS 0x047Cu +#define BIOS_COM_PORT_COUNT 4u void int14_service (struct bios_regs *regs) { u8 function; u8 status; + u8 value; + u8 timeout; + u16 port; + u16 base; + u16 result; + volatile u16 *ports; + volatile u8 *timeouts; function = (u8)(regs->eax >> 8); + port = (u16)regs->edx; + ports = (volatile u16 *)BDA_COM_PORTS; + timeouts = (volatile u8 *)BDA_COM_TIMEOUTS; - if (function == 0x00u) + /* BIOS numbers COM ports through DX and advertises their bases in BDA. */ + if (function > 0x03u || port >= BIOS_COM_PORT_COUNT || ports[port] == 0u) { - serial_init (); - regs->eax &= 0xFFFF0000u; + regs->eax + = (regs->eax & 0xFFFF00FFu) | ((u32)BIOS_INVALID_FUNCTION << 8); + regs->flags |= BIOS_CF; return; } - if (function == 0x01u) - { - int14_putc ((u8)regs->eax); - status = (u8)(io_in8 ((u16)(COM1_BASE + COM_LSR)) - | COM_LSR_THRE | COM_LSR_IDLE); - regs->eax = (regs->eax & 0xFFFF00FFu) | ((u32)status << 8); - return; - } + base = ports[port]; + timeout = timeouts[port]; - if (function == 0x03u) + switch (function) { - status = io_in8 ((u16)(COM1_BASE + COM_LSR)); + case 0x00u: + result = serial_configure (base, (u8)regs->eax); + regs->eax = (regs->eax & 0xFFFF0000u) | result; + break; + + case 0x01u: + status = serial_transmit (base, (u8)regs->eax, timeout); regs->eax = (regs->eax & 0xFFFF00FFu) | ((u32)status << 8); - return; + break; + + case 0x02u: + value = (u8)regs->eax; + status = serial_receive (base, &value, timeout); + regs->eax = (regs->eax & 0xFFFF0000u) | ((u32)status << 8) | value; + break; + + case 0x03u: + result = serial_status (base); + regs->eax = (regs->eax & 0xFFFF0000u) | result; + break; } - regs->eax = 0x0100u; + /* As in SeaBIOS, timeouts use AH bit 7; CF denotes an invalid call. */ + regs->flags &= (u16)~BIOS_CF; } diff --git a/mubios/services/int15.c b/mubios/services/int15.c index b9edb76..4100935 100644 --- a/mubios/services/int15.c +++ b/mubios/services/int15.c @@ -62,9 +62,9 @@ get_memory_end (void) } else { - extended_kib = (u16)cmos_read8 (CMOS_EXTENDED_MEMORY_LOW_16M) - | (u16)((u16)cmos_read8 (CMOS_EXTENDED_MEMORY_HIGH_16M) - << 8); + extended_kib + = (u16)cmos_read8 (CMOS_EXTENDED_MEMORY_LOW_16M) + | (u16)((u16)cmos_read8 (CMOS_EXTENDED_MEMORY_HIGH_16M) << 8); if (extended_kib != 0u) memory_end = E820_MEMORY_BASE + ((u32)extended_kib << 10); else diff --git a/mubios/services/int16.c b/mubios/services/int16.c index cadc1ff..7e480a1 100644 --- a/mubios/services/int16.c +++ b/mubios/services/int16.c @@ -23,74 +23,332 @@ #define BIOS_ZF 0x0040u #define BDA_KBD_HEAD 0x041Au #define BDA_KBD_TAIL 0x041Cu -#define BDA_KBD_BUFFER 0x041Eu #define BDA_KBD_BUFFER_START 0x001Eu #define BDA_KBD_BUFFER_END 0x003Eu +#define BDA_KBD_FLAGS 0x0417u +#define BDA_KBD_EXTENDED_FLAGS 0x0496u +#define BDA_COM1 0x0400u +#define KBD_STATUS_PORT 0x0064u +#define KBD_DATA_PORT 0x0060u +#define KBD_STATUS_OBF 0x01u +#define KBD_STATUS_AUX 0x20u +#define KBD_RELEASE 0x80u +#define KBD_IGNORED 0xFFFFu +#define TERMINAL_ESCAPE 0x1Bu +#define TERMINAL_ESCAPE_CLOCKS 119319u /* 100 ms at the PIT's 1193182 Hz. */ -static u8 -scancode_to_ascii (u8 scancode) +struct ascii_key { - switch (scancode) + u8 normal; + u8 shifted; +}; + +/* Set 1 positions, also used to give terminal ASCII its BIOS scan code. */ +static const struct ascii_key ascii_keys[] = { + [0x01] = { 0x1B, 0x1B }, [0x02] = { '1', '!' }, [0x03] = { '2', '@' }, + [0x04] = { '3', '#' }, [0x05] = { '4', '$' }, [0x06] = { '5', '%' }, + [0x07] = { '6', '^' }, [0x08] = { '7', '&' }, [0x09] = { '8', '*' }, + [0x0A] = { '9', '(' }, [0x0B] = { '0', ')' }, [0x0C] = { '-', '_' }, + [0x0D] = { '=', '+' }, [0x0E] = { '\b', '\b' }, [0x0F] = { '\t', '\t' }, + [0x10] = { 'q', 'Q' }, [0x11] = { 'w', 'W' }, [0x12] = { 'e', 'E' }, + [0x13] = { 'r', 'R' }, [0x14] = { 't', 'T' }, [0x15] = { 'y', 'Y' }, + [0x16] = { 'u', 'U' }, [0x17] = { 'i', 'I' }, [0x18] = { 'o', 'O' }, + [0x19] = { 'p', 'P' }, [0x1A] = { '[', '{' }, [0x1B] = { ']', '}' }, + [0x1C] = { '\r', '\r' }, [0x1E] = { 'a', 'A' }, [0x1F] = { 's', 'S' }, + [0x20] = { 'd', 'D' }, [0x21] = { 'f', 'F' }, [0x22] = { 'g', 'G' }, + [0x23] = { 'h', 'H' }, [0x24] = { 'j', 'J' }, [0x25] = { 'k', 'K' }, + [0x26] = { 'l', 'L' }, [0x27] = { ';', ':' }, [0x28] = { '\'', '"' }, + [0x29] = { '`', '~' }, [0x2B] = { '\\', '|' }, [0x2C] = { 'z', 'Z' }, + [0x2D] = { 'x', 'X' }, [0x2E] = { 'c', 'C' }, [0x2F] = { 'v', 'V' }, + [0x30] = { 'b', 'B' }, [0x31] = { 'n', 'N' }, [0x32] = { 'm', 'M' }, + [0x33] = { ',', '<' }, [0x34] = { '.', '>' }, [0x35] = { '/', '?' }, + [0x37] = { '*', '*' }, [0x39] = { ' ', ' ' }, [0x4A] = { '-', '-' }, + [0x4E] = { '+', '+' }, [0x56] = { '\\', '|' } +}; + +struct terminal_sequence +{ + char text[5]; + u16 key; +}; + +/* CSI and SS3 sequences used by terminals in normal/application mode. */ +static const struct terminal_sequence terminal_sequences[] + = { { "[A", 0x48E0u }, { "[B", 0x50E0u }, { "[C", 0x4DE0u }, + { "[D", 0x4BE0u }, { "OA", 0x48E0u }, { "OB", 0x50E0u }, + { "OC", 0x4DE0u }, { "OD", 0x4BE0u }, { "[H", 0x47E0u }, + { "[F", 0x4FE0u }, { "OH", 0x47E0u }, { "OF", 0x4FE0u }, + { "[1~", 0x47E0u }, { "[4~", 0x4FE0u }, { "[7~", 0x47E0u }, + { "[8~", 0x4FE0u }, { "[2~", 0x52E0u }, { "[3~", 0x53E0u }, + { "[5~", 0x49E0u }, { "[6~", 0x51E0u }, { "[Z", 0x0F00u }, + { "OP", 0x3B00u }, { "OQ", 0x3C00u }, { "OR", 0x3D00u }, + { "OS", 0x3E00u }, { "[11~", 0x3B00u }, { "[12~", 0x3C00u }, + { "[13~", 0x3D00u }, { "[14~", 0x3E00u }, { "[15~", 0x3F00u }, + { "[17~", 0x4000u }, { "[18~", 0x4100u }, { "[19~", 0x4200u }, + { "[20~", 0x4300u }, { "[21~", 0x4400u }, { "[23~", 0x8500u }, + { "[24~", 0x8600u } }; + +static u8 terminal_buffer[16]; +static u8 terminal_count; +static u8 terminal_escape_pending; +static u16 terminal_counter; +static u32 terminal_elapsed; +static u16 decoded_tail; + +static u16 +next_position (u16 position) +{ + position += 2u; + if (position >= BDA_KBD_BUFFER_END) + position = BDA_KBD_BUFFER_START; + return position; +} + +static int +keyboard_full (void) +{ + volatile u16 *head = (volatile u16 *)BDA_KBD_HEAD; + volatile u16 *tail = (volatile u16 *)BDA_KBD_TAIL; + + return next_position (*tail) == *head; +} + +static void +enqueue_key (u16 key) +{ + volatile u16 *tail = (volatile u16 *)BDA_KBD_TAIL; + volatile u16 *slot = (volatile u16 *)(MUBIOS_BDA_BASE + *tail); + + *slot = key; + *tail = next_position (*tail); + /* This producer stores a complete BIOS key, not an IRQ1 raw scan code. */ + decoded_tail = *tail; +} + +static u16 +scancode_to_key (u8 scancode) +{ + if (scancode < sizeof (ascii_keys) / sizeof (ascii_keys[0]) + && ascii_keys[scancode].normal != 0u) + return (u16)(((u16)scancode << 8) | ascii_keys[scancode].normal); + + if (scancode >= 0x3Bu && scancode <= 0x44u) + return (u16)((u16)scancode << 8); + if (scancode >= 0x47u && scancode <= 0x53u) + return (u16)(((u16)scancode << 8) | 0xE0u); + if (scancode == 0x57u || scancode == 0x58u) + return (u16)((u16)(scancode + 0x2Eu) << 8); + + /* Modifier make codes and controller responses are not character keys. */ + return KBD_IGNORED; +} + +static void +decode_irq_keys (void) +{ + volatile u16 *tail = (volatile u16 *)BDA_KBD_TAIL; + volatile u16 *slot; + u16 read_position; + u16 write_position; + u16 end; + u16 key; + + if (decoded_tail == 0u) + decoded_tail = BDA_KBD_BUFFER_START; + + /* IRQ1 currently queues raw set 1 codes; decode only its new entries. */ + read_position = decoded_tail; + write_position = decoded_tail; + end = *tail; + while (read_position != end) + { + slot = (volatile u16 *)(MUBIOS_BDA_BASE + read_position); + key = scancode_to_key ((u8)(*slot >> 8)); + if (key != KBD_IGNORED) + { + slot = (volatile u16 *)(MUBIOS_BDA_BASE + write_position); + *slot = key; + write_position = next_position (write_position); + } + read_position = next_position (read_position); + } + /* Compact away modifier codes before publishing the decoded tail. */ + *tail = write_position; + decoded_tail = write_position; +} + +static void +poll_keyboard_controller (void) +{ + u8 status; + u8 scancode; + u16 key; + + /* INT 16h C services run with IRQs off, so a blocking read must poll. */ + while (!keyboard_full ()) { + status = io_in8 (KBD_STATUS_PORT); + if ((status & KBD_STATUS_OBF) == 0u) + return; + + /* The auxiliary device shares the data port, not the keyboard IRQ. */ + if ((status & KBD_STATUS_AUX) != 0u) + return; + + scancode = io_in8 (KBD_DATA_PORT); + if ((scancode & KBD_RELEASE) != 0u) + continue; + + key = scancode_to_key (scancode); + if (key != KBD_IGNORED) + enqueue_key (key); + } +} + +static u16 +ascii_to_key (u8 character) +{ + u16 scan; + + /* Terminals commonly send DEL for the Backspace key. */ + if (character == 0x7Fu) + return 0x0E08u; + if (character == '\n') + return 0x1C0Au; + if (character == 0u) + return 0x0300u; + + for (scan = 1; scan < sizeof (ascii_keys) / sizeof (ascii_keys[0]); ++scan) + { + if (ascii_keys[scan].normal == character + || ascii_keys[scan].shifted == character) + return (u16)((scan << 8) | character); + } + + /* A terminal encodes Ctrl+A through Ctrl+Z as ASCII 1 through 26. */ + if (character >= 1u && character <= 26u) + { + for (scan = 1; scan < sizeof (ascii_keys) / sizeof (ascii_keys[0]); + ++scan) + { + if (ascii_keys[scan].normal == (u8)('a' + character - 1u)) + return (u16)((scan << 8) | character); + } + } + + switch (character) + { + case 0x1Cu: + return 0x2B1Cu; + case 0x1Du: + return 0x1B1Du; case 0x1Eu: - return 'a'; - case 0x30u: - return 'b'; - case 0x2Eu: - return 'c'; - case 0x20u: - return 'd'; - case 0x12u: - return 'e'; - case 0x21u: - return 'f'; - case 0x22u: - return 'g'; - case 0x23u: - return 'h'; - case 0x17u: - return 'i'; - case 0x24u: - return 'j'; - case 0x25u: - return 'k'; - case 0x26u: - return 'l'; - case 0x32u: - return 'm'; - case 0x31u: - return 'n'; - case 0x18u: - return 'o'; - case 0x19u: - return 'p'; - case 0x10u: - return 'q'; - case 0x13u: - return 'r'; + return 0x071Eu; case 0x1Fu: - return 's'; - case 0x14u: - return 't'; - case 0x16u: - return 'u'; - case 0x2Fu: - return 'v'; - case 0x11u: - return 'w'; - case 0x2Du: - return 'x'; - case 0x15u: - return 'y'; - case 0x2Cu: - return 'z'; - case 0x39u: - return ' '; - case 0x1Cu: - return '\r'; + return 0x0C1Fu; default: + /* Byte-oriented BIOS input has no scan code for other byte values. */ + return character; + } +} + +static void +consume_terminal (u8 length) +{ + u8 index; + + terminal_count -= length; + for (index = 0; index < terminal_count; ++index) + terminal_buffer[index] = terminal_buffer[index + length]; + terminal_escape_pending = 0; +} + +static int +escape_expired (void) +{ + u16 current; + + current = serial_timer_counter (); + if (!terminal_escape_pending) + { + terminal_counter = current; + terminal_elapsed = 0; + terminal_escape_pending = 1; return 0; } + + terminal_elapsed += (u16)(terminal_counter - current); + terminal_counter = current; + return terminal_elapsed >= TERMINAL_ESCAPE_CLOCKS; +} + +static void +poll_terminal (void) +{ + volatile u16 *com1 = (volatile u16 *)BDA_COM1; + u16 base; + u16 key; + u16 sequence; + u8 index; + u8 length; + u8 prefix; + u8 matched; + + base = *com1; + /* No console UART is advertised when the BDA COM1 entry is zero. */ + if (base == 0u) + return; + + while (terminal_count < sizeof (terminal_buffer) + && serial_try_receive (base, &terminal_buffer[terminal_count])) + ++terminal_count; + + while (terminal_count != 0u && !keyboard_full ()) + { + matched = 0; + prefix = 0; + if (terminal_buffer[0] == TERMINAL_ESCAPE) + { + for (sequence = 0; sequence < sizeof (terminal_sequences) + / sizeof (terminal_sequences[0]); + ++sequence) + { + length = 0; + while (terminal_sequences[sequence].text[length] != '\0') + ++length; + + for (index = 0; index < length && index + 1u < terminal_count; + ++index) + { + if (terminal_buffer[index + 1u] + != (u8)terminal_sequences[sequence].text[index]) + break; + } + + if (index == length) + { + key = terminal_sequences[sequence].key; + consume_terminal ((u8)(length + 1u)); + matched = 1; + break; + } + if (index + 1u == terminal_count) + prefix = 1; + } + + /* Keep fragmented sequences; a lone Escape must eventually return. + */ + if (!matched && prefix && !escape_expired ()) + return; + } + + if (!matched) + { + key = ascii_to_key (terminal_buffer[0]); + consume_terminal (1); + } + enqueue_key (key); + } } void @@ -98,48 +356,95 @@ int16_service (struct bios_regs *regs) { volatile u16 *head; volatile u16 *tail; - volatile u16 *buffer; + volatile u16 *slot; + volatile u8 *flags; u8 function; u16 position; u16 value; - u8 scancode; - u8 ascii; + u8 consume; + u8 extended; function = (u8)(regs->eax >> 8); - if (function != 0x00u && function != 0x01u) - { - regs->eax = 0; - regs->flags |= BIOS_ZF; - return; - } - head = (volatile u16 *)BDA_KBD_HEAD; tail = (volatile u16 *)BDA_KBD_TAIL; - buffer = (volatile u16 *)BDA_KBD_BUFFER; + flags = (volatile u8 *)BDA_KBD_FLAGS; + decode_irq_keys (); - if (*head == *tail) + switch (function) { - regs->eax = 0; - regs->flags |= BIOS_ZF; + case 0x02u: + regs->eax = (regs->eax & 0xFFFFFF00u) | flags[0]; + return; + case 0x05u: + /* AH=05h returns AL=1 when the BDA ring cannot accept another key. */ + value = (u16)keyboard_full (); + if (value == 0u) + enqueue_key ((u16)regs->ecx); + regs->eax = (regs->eax & 0xFFFFFF00u) | value; + return; + case 0x09u: + /* Advertise enhanced reads/status, not unimplemented keyboard-ID calls. + */ + regs->eax = (regs->eax & 0xFFFFFF00u) | 0x20u; + return; + case 0x12u: + regs->eax = (regs->eax & 0xFFFF0000u) + | ((u32)((flags[1] & 0xF3u) + | (*(volatile u8 *)BDA_KBD_EXTENDED_FLAGS & 0x0Cu)) + << 8) + | flags[0]; + return; + case 0x00u: + case 0x01u: + case 0x10u: + case 0x11u: + break; + default: + /* SeaBIOS leaves unsupported INT 16h function registers unchanged. */ return; } - position = *head; - value = buffer[(position - BDA_KBD_BUFFER_START) >> 1]; - scancode = (u8)(value >> 8); - ascii = scancode_to_ascii (scancode); + consume = (u8)((function & 1u) == 0u); + extended = function >= 0x10u; - regs->eax = ((u32)scancode << 8) | ascii; - regs->flags &= (u16)~BIOS_ZF; - - if (function == 0x00u) + for (;;) { - position += 2; + poll_terminal (); + poll_keyboard_controller (); + + if (*head != *tail) + { + position = *head; + slot = (volatile u16 *)(MUBIOS_BDA_BASE + position); + value = *slot; + if (!extended) + { + /* Legacy reads expose navigation keys with AL=0, not E0h. */ + if ((u8)value == 0xE0u && (value & 0xFF00u) != 0u) + value &= 0xFF00u; + else if (value == 0xE00Du || value == 0xE00Au) + value = (u16)(0x1C00u | (u8)value); + else if (value == 0xE02Fu) + value = 0x352Fu; + } + if ((u8)value == 0xF0u && (value & 0xFF00u) != 0u) + value &= 0xFF00u; - if (position >= BDA_KBD_BUFFER_END) - position = BDA_KBD_BUFFER_START; + regs->eax = (regs->eax & 0xFFFF0000u) | value; + if (consume) + *head = next_position (position); + else + regs->flags &= (u16)~BIOS_ZF; + return; + } - *head = position; + if (!consume) + { + regs->flags |= BIOS_ZF; + return; + } + /* IRQs stay disabled here; both input sources are polled on every pass. + */ } } |
