diff --git a/include/keyboard.h b/include/keyboard.h new file mode 100644 index 0000000..3559926 --- /dev/null +++ b/include/keyboard.h @@ -0,0 +1,42 @@ +/* Nadir PS/2 keyboard: scancode set-1 IRQ1 driver with ASCII queue. + * + * Runs in 64-bit long mode at Ring 0 (CPL 0); uses `inb` on ports + * 0x60 (data) / 0x64 (status), which fault with #GP above CPL 0, so + * this driver is kernel-only. QEMU/SeaBIOS provides a PS/2 controller + * in set-1 mode; the 8259 routes IRQ1 to vector 33 after the PIC remap. + * Fault behavior: port I/O cannot fault at CPL 0. Reading 0x60 when + * the controller has no byte returns stale data, so keyboard_on_irq() + * must run only from IRQ1 (data-ready guaranteed). + * + * Locking: single core, one producer (IRQ1 handler) and one consumer + * (polling kmain). `volatile` head/tail plus single-byte loads/stores + * suffice — no locks, no atomics. IF stays cleared; the queue is both + * polling-safe and IRQ-safe under that model. + * + * Preconditions: call keyboard_init() once before use. It registers + * keyboard_on_irq() for IRQ1 (via irq_register_handler) and unmasks + * the line (via pic_unmask); the PIC remap, IRQ-gate install, and + * `sti` stay integrator duty, and IF stays cleared until then. + * Failure modes: unknown/extended/release codes are dropped silently; + * a full queue drops the newest byte; dequeue on empty returns 0. + */ + +#ifndef NADIR_KEYBOARD_H +#define NADIR_KEYBOARD_H + +/* Clear the queue and modifier state, register keyboard_on_irq() for + * IRQ1, and unmask IRQ1. */ +void keyboard_init(void); + +/* IRQ1 entry point: read one scancode from 0x60, translate, enqueue. + * Runs in interrupt context. */ +void keyboard_on_irq(void); + +/* Nonzero if at least one translated byte waits in the queue. */ +int keyboard_has_key(void); + +/* Remove and return the oldest byte, or 0 when the queue is empty. + * Backspace arrives as '\b', Enter as '\n'. */ +char keyboard_dequeue(void); + +#endif /* NADIR_KEYBOARD_H */ diff --git a/kernel/arch/x86_64/keyboard.c b/kernel/arch/x86_64/keyboard.c new file mode 100644 index 0000000..3c462d9 --- /dev/null +++ b/kernel/arch/x86_64/keyboard.c @@ -0,0 +1,189 @@ +/* PS/2 scancode set-1 IRQ1 driver (see keyboard.h). + * + * IRQ wiring uses the live framework: keyboard_init() registers + * keyboard_on_irq() via irq_register_handler() (idt.h) and unmasks + * line 1 via pic_unmask() (pic.h), both feature-detected with + * __has_include so this file still compiles if either header is + * absent (registration then falls back to caller duty). The driver + * itself never executes `sti`: IF stays cleared and IRQs only fire + * after the integrator remaps the PIC, installs the IRQ gates, and + * enables interrupts. 8259 IRQ1 -> vector 33. + */ + +#include + +#include "keyboard.h" + +/* Port I/O owned by include/io.h; no local inb duplicate. */ +#include "io.h" + +#if defined(__has_include) +#if __has_include("idt.h") +#include "idt.h" +#define NADIR_HAS_IRQ_REG 1 +#endif +#if __has_include("pic.h") +#include "pic.h" +#define NADIR_HAS_PIC 1 +#endif +#endif + +#define KBD_DATA 0x60U /* PS/2 data port: one scancode per IRQ1 */ +#define KBD_BUF_SIZE 128U /* power of two so wrap is cheap */ + +#define SC_RELEASE 0x80U /* high bit set = key release, ignore */ +#define SC_EXTENDED 0xE0U /* prefix for arrows/extended keys, ignore */ +#define SC_LSHIFT 0x2AU +#define SC_RSHIFT 0x36U +#define SC_CAPS 0x3AU + +/* US set-1 codes without Shift (0 = no printable mapping). Backspace + * 0x0E stays '\b' and Enter 0x1C stays '\n' so editing works raw. */ +static const char k_unshifted[128] = { + [0x02] = '1', [0x03] = '2', [0x04] = '3', [0x05] = '4', + [0x06] = '5', [0x07] = '6', [0x08] = '7', [0x09] = '8', + [0x0A] = '9', [0x0B] = '0', [0x0C] = '-', [0x0D] = '=', + [0x0E] = '\b', [0x0F] = '\t', + [0x10] = 'q', [0x11] = 'w', [0x12] = 'e', [0x13] = 'r', + [0x14] = 't', [0x15] = 'y', [0x16] = 'u', [0x17] = 'i', + [0x18] = 'o', [0x19] = 'p', [0x1A] = '[', [0x1B] = ']', + [0x1C] = '\n', + [0x1E] = 'a', [0x1F] = 's', [0x20] = 'd', [0x21] = 'f', + [0x22] = 'g', [0x23] = 'h', [0x24] = 'j', [0x25] = 'k', + [0x26] = 'l', [0x27] = ';', [0x28] = '\'', [0x29] = '`', + [0x2B] = '\\', + [0x2C] = 'z', [0x2D] = 'x', [0x2E] = 'c', [0x2F] = 'v', + [0x30] = 'b', [0x31] = 'n', [0x32] = 'm', [0x33] = ',', + [0x34] = '.', [0x35] = '/', [0x39] = ' ', +}; + +/* Same codes with Shift held (letters stay lowercase here; the Shift + * + CapsLock XOR in keyboard_on_irq() uppercases them instead). */ +static const char k_shifted[128] = { + [0x02] = '!', [0x03] = '@', [0x04] = '#', [0x05] = '$', + [0x06] = '%', [0x07] = '^', [0x08] = '&', [0x09] = '*', + [0x0A] = '(', [0x0B] = ')', [0x0C] = '_', [0x0D] = '+', + [0x0E] = '\b', [0x0F] = '\t', + [0x10] = 'Q', [0x11] = 'W', [0x12] = 'E', [0x13] = 'R', + [0x14] = 'T', [0x15] = 'Y', [0x16] = 'U', [0x17] = 'I', + [0x18] = 'O', [0x19] = 'P', [0x1A] = '{', [0x1B] = '}', + [0x1C] = '\n', + [0x1E] = 'A', [0x1F] = 'S', [0x20] = 'D', [0x21] = 'F', + [0x22] = 'G', [0x23] = 'H', [0x24] = 'J', [0x25] = 'K', + [0x26] = 'L', [0x27] = ':', [0x28] = '"', [0x29] = '~', + [0x2B] = '|', + [0x2C] = 'Z', [0x2D] = 'X', [0x2E] = 'C', [0x2F] = 'V', + [0x30] = 'B', [0x31] = 'N', [0x32] = 'M', [0x33] = '<', + [0x34] = '>', [0x35] = '?', [0x39] = ' ', +}; + +/* Single-producer (IRQ) / single-consumer (poll) ring. `volatile` stops + * the compiler caching head/tail across the IRQ boundary; on one core + * with byte-sized index traffic no lock is needed because the producer + * only writes head (+buf slot) and the consumer only writes tail. */ +static char k_buf[KBD_BUF_SIZE]; +static volatile uint8_t k_head; +static volatile uint8_t k_tail; +/* IRQ-only state: touched solely in keyboard_on_irq(), so plain. */ +static uint8_t k_shift; +static uint8_t k_caps; +static uint8_t k_e0; + +/* Queue helpers below; port I/O comes from io.h. */ + +/* Drop-newest on full: keeps the consumer's prefix intact for editing. */ +static void enqueue(char c) +{ + uint8_t next = (uint8_t)((k_head + 1) % KBD_BUF_SIZE); + if (next == k_tail) { + return; + } + k_buf[k_head] = c; + k_head = next; +} + +void keyboard_init(void) +{ + k_head = 0; + k_tail = 0; + k_shift = 0; + k_caps = 0; + k_e0 = 0; +#ifdef NADIR_HAS_IRQ_REG + /* Route vector 33 -> keyboard_on_irq(); without idt.h this stays + * caller duty (manual irq_register_handler call). */ + irq_register_handler(1, keyboard_on_irq); +#endif +#ifdef NADIR_HAS_PIC + pic_unmask(1); +#endif +} + +void keyboard_on_irq(void) +{ + uint8_t code = inb(KBD_DATA); + + if (code == SC_EXTENDED) { + k_e0 = 1; + return; + } + if (k_e0 != 0) { + /* Extended make/break: swallow, maps to 0. */ + k_e0 = 0; + return; + } + /* only Shift release changes state the rest is ignored. */ + if ((code & SC_RELEASE) != 0) { + uint8_t make = (uint8_t)(code & (uint8_t)~SC_RELEASE); + if (make == SC_LSHIFT || make == SC_RSHIFT) { + k_shift = 0; + } + return; + } + if (code == SC_LSHIFT || code == SC_RSHIFT) { + k_shift = 1; + return; + } + /* CapsLock toggles on press only */ + if (code == SC_CAPS) { + k_caps = (uint8_t)(k_caps == 0 ? 1 : 0); + return; + } + if (code >= sizeof(k_unshifted)) { + return; + } + char base = k_unshifted[code]; + if (base == '\0') { + return; + } + char out = base; + if (base >= 'a' && base <= 'z') { + /* Shift XOR CapsLock uppercases letters. */ + if ((k_shift != 0) != (k_caps != 0)) { + out = (char)(base - 'a' + 'A'); + } + } else if (k_shift != 0) { + char shifted = k_shifted[code]; + if (shifted != '\0') { + out = shifted; + } + } else if (k_caps != 0) { + /* CapsLock alone must not shift digits/symbols */ + } + enqueue(out); +} + +int keyboard_has_key(void) +{ + return k_head != k_tail; +} + +char keyboard_dequeue(void) +{ + if (k_head == k_tail) { + return 0; + } + char c = k_buf[k_tail]; + k_tail = (uint8_t)((k_tail + 1) % KBD_BUF_SIZE); + return c; +}