diff options
| -rw-r--r-- | mubios/core/bda.c | 3 | ||||
| -rw-r--r-- | mubios/drivers/ata.c | 3 | ||||
| -rw-r--r-- | mubios/drivers/ioapic.c | 2 | ||||
| -rw-r--r-- | mubios/drivers/kbd.c | 12 | ||||
| -rw-r--r-- | mubios/drivers/serial.c | 113 | ||||
| -rw-r--r-- | mubios/drivers/vga.c | 27 | ||||
| -rw-r--r-- | mubios/include/bios.h | 6 | ||||
| -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 |
11 files changed, 586 insertions, 149 deletions
diff --git a/mubios/core/bda.c b/mubios/core/bda.c index 5aad85c..ddcedb7 100644 --- a/mubios/core/bda.c +++ b/mubios/core/bda.c @@ -24,6 +24,8 @@ extern u8 __ebda_start[]; #define BDA_COM1_OFFSET 0x0000u +#define BDA_COM_TIMEOUT_OFFSET 0x007Cu +#define BIOS_COM_TIMEOUT 0x0Au #define BDA_LPT1_OFFSET 0x0008u static void @@ -47,6 +49,7 @@ bda_init (void) store_u16 (bda + BDA_EQUIPMENT_OFFSET, 0x4227u); store_u16 (bda + BDA_COM1_OFFSET, 0x03F8u); + bda[BDA_COM_TIMEOUT_OFFSET] = BIOS_COM_TIMEOUT; store_u16 (bda + BDA_LPT1_OFFSET, 0x0378u); store_u16 (bda + BDA_CONVENTIONAL_MEMORY_OFFSET, (u16)(ebda_base >> 10)); diff --git a/mubios/drivers/ata.c b/mubios/drivers/ata.c index 86bab21..7d23a9c 100644 --- a/mubios/drivers/ata.c +++ b/mubios/drivers/ata.c @@ -96,8 +96,7 @@ ata_read_transfer28 (u32 lba, u16 *buffer, u16 count) for (sector = 0; sector < count; ++sector) { status = ata_wait_not_busy (); - if ((status & ATA_STATUS_ERR) != 0 - || (status & ATA_STATUS_DRQ) == 0) + if ((status & ATA_STATUS_ERR) != 0 || (status & ATA_STATUS_DRQ) == 0) return 1; for (index = 0; index < 256; ++index) diff --git a/mubios/drivers/ioapic.c b/mubios/drivers/ioapic.c index 7a0b6cb..552f497 100644 --- a/mubios/drivers/ioapic.c +++ b/mubios/drivers/ioapic.c @@ -84,7 +84,7 @@ ioapic_mask_all (void) for (pin = 0; pin <= maximum_pin; ++pin) { ioapic_write ((u8)(IOAPIC_REDIRECTION_BASE + 2u * pin), - IOAPIC_REDIRECTION_MASK); + IOAPIC_REDIRECTION_MASK); ioapic_write ((u8)(IOAPIC_REDIRECTION_BASE + 2u * pin + 1u), 0); } } diff --git a/mubios/drivers/kbd.c b/mubios/drivers/kbd.c index 4de0a96..eb99e70 100644 --- a/mubios/drivers/kbd.c +++ b/mubios/drivers/kbd.c @@ -33,9 +33,9 @@ /* * 8042 command byte bits. - * Bit 0 enables the keyboard IRQ, bit 1 the auxiliary port IRQ, bit 4 and bit 5 - * hold the keyboard and auxiliary clock, and bit 6 selects set 1 scan codes on - * the keyboard interface. + * Bit 0 enables the keyboard IRQ, bit 1 the auxiliary port IRQ, bit 4 and bit + * 5 hold the keyboard and auxiliary clock, and bit 6 selects set 1 scan codes + * on the keyboard interface. */ #define KBD_CONFIG_IRQ1 0x01u #define KBD_CONFIG_IRQ12 0x02u @@ -103,7 +103,8 @@ kbd_init (void) /* Disable both ports before the controller is reconfigured. */ kbd_write_command (KBD_CMD_DISABLE_KBD); - /* Drain the self-test and any pending scan code left in the output buffer. */ + /* Drain the self-test and any pending scan code left in the output buffer. + */ while ((io_in8 (KBD_STATUS_PORT) & KBD_STATUS_OBF) != 0u) (void)io_in8 (KBD_DATA_PORT); @@ -115,7 +116,8 @@ kbd_init (void) config = io_in8 (KBD_DATA_PORT); - config &= (u8)~(KBD_CONFIG_DISABLE_KBD_CLOCK | KBD_CONFIG_DISABLE_AUX_CLOCK); + config + &= (u8) ~(KBD_CONFIG_DISABLE_KBD_CLOCK | KBD_CONFIG_DISABLE_AUX_CLOCK); config |= KBD_CONFIG_IRQ1 | KBD_CONFIG_IRQ12 | KBD_CONFIG_TRANSLATE; if (kbd_write_command (KBD_CMD_WRITE_CONFIG) != 0) diff --git a/mubios/drivers/serial.c b/mubios/drivers/serial.c index 3da2093..c8b9adf 100644 --- a/mubios/drivers/serial.c +++ b/mubios/drivers/serial.c @@ -30,6 +30,7 @@ #define COM_LCR_OFFSET 0x03u #define COM_MCR_OFFSET 0x04u #define COM_LSR_OFFSET 0x05u +#define COM_MSR_OFFSET 0x06u #define COM_LCR_DLAB 0x80u #define COM_LCR_8N1 0x03u @@ -44,6 +45,12 @@ #define COM_MCR_OUT2 0x08u #define COM_LSR_THRE 0x20u +#define COM_LSR_IDLE 0x40u +#define COM_LSR_DATA_READY 0x01u +#define BIOS_SERIAL_TIMEOUT 0x80u + +#define PIT_COUNTER0 0x0040u +#define PIT_COMMAND 0x0043u static u16 com1_port (u16 offset) @@ -51,6 +58,112 @@ com1_port (u16 offset) return (u16)(COM1_BASE + offset); } +/* Latching channel 0 works while the protected-mode service has IRQs off. */ +u16 +serial_timer_counter (void) +{ + u16 counter; + + io_out8 (PIT_COMMAND, 0x00u); + counter = io_in8 (PIT_COUNTER0); + counter |= (u16)((u16)io_in8 (PIT_COUNTER0) << 8); + return counter; +} + +static int +serial_wait (u16 base, u8 mask, u8 timeout, u8 *status) +{ + u16 previous; + u16 current; + u32 elapsed; + u32 duration; + + previous = serial_timer_counter (); + elapsed = 0; + /* One BDA timeout unit is one 65536-clock BIOS timer tick. */ + duration = (u32)timeout << 16; + + for (;;) + { + *status = io_in8 ((u16)(base + COM_LSR_OFFSET)); + if ((*status & mask) == mask) + return 1; + + if (elapsed >= duration) + { + *status |= BIOS_SERIAL_TIMEOUT; + return 0; + } + + current = serial_timer_counter (); + elapsed += (u16)(previous - current); + previous = current; + } +} + +u16 +serial_status (u16 base) +{ + u8 line; + u8 modem; + + line = io_in8 ((u16)(base + COM_LSR_OFFSET)); + modem = io_in8 ((u16)(base + COM_MSR_OFFSET)); + return (u16)(((u16)line << 8) | modem); +} + +u16 +serial_configure (u16 base, u8 parameters) +{ + static const u16 divisors[] + = { 1047u, 768u, 384u, 192u, 96u, 48u, 24u, 12u }; + u16 divisor; + + /* INT 14h AL: bits 7:5 select baud; bits 4:0 are the UART format. */ + divisor = divisors[parameters >> 5]; + io_out8 ((u16)(base + COM_LCR_OFFSET), COM_LCR_DLAB); + io_out8 ((u16)(base + COM_DATA_OFFSET), (u8)divisor); + io_out8 ((u16)(base + COM_IER_OFFSET), (u8)(divisor >> 8)); + io_out8 ((u16)(base + COM_LCR_OFFSET), parameters & 0x1Fu); + /* Do not reset the FIFO here: pending terminal input must survive. */ + return serial_status (base); +} + +u8 +serial_transmit (u16 base, u8 value, u8 timeout) +{ + u8 status; + + if (serial_wait (base, COM_LSR_THRE | COM_LSR_IDLE, timeout, &status)) + io_out8 ((u16)(base + COM_DATA_OFFSET), value); + + return status; +} + +u8 +serial_receive (u16 base, u8 *value, u8 timeout) +{ + u8 status; + + if (serial_wait (base, COM_LSR_DATA_READY, timeout, &status)) + *value = io_in8 ((u16)(base + COM_DATA_OFFSET)); + + return status; +} + +int +serial_try_receive (u16 base, u8 *value) +{ + u8 status; + + status = io_in8 ((u16)(base + COM_LSR_OFFSET)); + if ((status & COM_LSR_DATA_READY) == 0u) + return 0; + + *value = io_in8 ((u16)(base + COM_DATA_OFFSET)); + return 1; +} + void serial_init (void) { diff --git a/mubios/drivers/vga.c b/mubios/drivers/vga.c index 57e1f8f..16f88e5 100644 --- a/mubios/drivers/vga.c +++ b/mubios/drivers/vga.c @@ -35,8 +35,7 @@ #define VGA_ATTR_INDEX_DATA 0x03C0u static void -vga_write_registers (u16 index_port, u16 data_port, const u8 *values, - u8 count) +vga_write_registers (u16 index_port, u16 data_port, const u8 *values, u8 count) { u8 index; @@ -50,19 +49,17 @@ vga_write_registers (u16 index_port, u16 data_port, const u8 *values, void vga_text_mode_init (void) { - static const u8 sequencer[] = {0x00u, 0x00u, 0x03u, 0x00u, 0x02u}; - static const u8 crtc[] = {0x5Fu, 0x4Fu, 0x50u, 0x82u, 0x55u, 0x81u, - 0xBFu, 0x1Fu, 0x00u, 0x4Fu, 0x0Du, 0x0Eu, - 0x00u, 0x00u, 0x00u, 0x00u, 0x9Cu, 0x8Eu, - 0x8Fu, 0x28u, 0x1Fu, 0x96u, 0xB9u, 0xA3u, - 0xFFu}; - static const u8 graphics[] = {0x00u, 0x00u, 0x00u, 0x00u, 0x00u, - 0x10u, 0x0Fu, 0x00u, 0xFFu}; - static const u8 attributes[] = {0x00u, 0x01u, 0x02u, 0x03u, 0x04u, - 0x05u, 0x06u, 0x07u, 0x08u, 0x09u, - 0x0Au, 0x0Bu, 0x0Cu, 0x0Du, 0x0Eu, - 0x0Fu, 0x0Cu, 0x00u, 0x0Fu, 0x08u, - 0x00u}; + static const u8 sequencer[] = { 0x00u, 0x00u, 0x03u, 0x00u, 0x02u }; + static const u8 crtc[] + = { 0x5Fu, 0x4Fu, 0x50u, 0x82u, 0x55u, 0x81u, 0xBFu, 0x1Fu, 0x00u, + 0x4Fu, 0x0Du, 0x0Eu, 0x00u, 0x00u, 0x00u, 0x00u, 0x9Cu, 0x8Eu, + 0x8Fu, 0x28u, 0x1Fu, 0x96u, 0xB9u, 0xA3u, 0xFFu }; + static const u8 graphics[] + = { 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x10u, 0x0Fu, 0x00u, 0xFFu }; + static const u8 attributes[] + = { 0x00u, 0x01u, 0x02u, 0x03u, 0x04u, 0x05u, 0x06u, + 0x07u, 0x08u, 0x09u, 0x0Au, 0x0Bu, 0x0Cu, 0x0Du, + 0x0Eu, 0x0Fu, 0x0Cu, 0x00u, 0x0Fu, 0x08u, 0x00u }; volatile u16 *video; volatile u8 *bda; u16 cell; diff --git a/mubios/include/bios.h b/mubios/include/bios.h index 3c8b0dc..979e445 100644 --- a/mubios/include/bios.h +++ b/mubios/include/bios.h @@ -43,6 +43,12 @@ void bda_init (void); void serial_init (void); void serial_putc (u8 value); void serial_write (const char *text); +u16 serial_timer_counter (void); +u16 serial_status (u16 base); +u16 serial_configure (u16 base, u8 parameters); +u8 serial_transmit (u16 base, u8 value, u8 timeout); +u8 serial_receive (u16 base, u8 *value, u8 timeout); +int serial_try_receive (u16 base, u8 *value); u8 cmos_read8 (u8 index); 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. + */ } } |
