// hal_keyboard.c – PS/2 keyboard, interrupt-driven, 256-entry ring buffer // // Scan-code set 1 (XT-compatible), as output by the 8042's built-in // translation layer. Supports make/break for all standard keys, Shift / // Ctrl / Alt / CapsLock / NumLock modifiers, and the E0-prefixed extended // key set (arrows, Home/End/PgUp/PgDn/Ins/Del, RCtrl, RAlt, numpad-Enter, // numpad-divide). // // Threading model // --------------- // Single-producer (ISR on IRQ 1) / single-consumer ring buffer. All // ISR-written state is declared volatile. On a uniprocessor, volatile // plus the natural atomicity of aligned int reads/writes is sufficient. // On SMP, add acquire/release memory barriers around gHead and gTail. // // Caller responsibilities // ----------------------- // 1. Register KbdIsr with your interrupt dispatcher for IRQ 1. // 2. Send the APIC/PIC EOI *after* KbdIsr returns, in the surrounding // wrapper – not inside KbdIsr itself. // 3. Unmask IRQ 1 in the APIC or 8259 before expecting any interrupts. // #include #include #include #include #include #include // -- Port addresses ------------------------------------------------------------ #define PS2_DATA 0x60u #define PS2_STAT 0x64u #define PS2_CMD 0x64u // -- Ring buffer --------------------------------------------------------------- // KBD_BUF_SIZE must be a power of two; KBD_BUF_MASK replaces % with &. #define KBD_BUF_SIZE 256u #define KBD_BUF_MASK (KBD_BUF_SIZE - 1u) static uint8_t gBuf[KBD_BUF_SIZE]; static volatile int gHead = 0; // consumer read pointer (updated by reader) static volatile int gTail = 0; // producer write pointer (updated by ISR) // -- Scan-code set 1 constants ------------------------------------------------- #define SC1_BREAK_BIT 0x80u // OR'd into every break (key-up) scan code #define SC1_MAKE_MASK 0x7Fu // complement of SC1_BREAK_BIT; avoids ~uint cast #define SC1_EXT_PREFIX 0xE0u // one-byte prefix for extended keys // -- Modifier flags ------------------------------------------------------------ #define MOD_LSHIFT (1u << 0) #define MOD_RSHIFT (1u << 1) #define MOD_LCTRL (1u << 2) #define MOD_RCTRL (1u << 3) #define MOD_LALT (1u << 4) #define MOD_RALT (1u << 5) #define MOD_CAPS (1u << 6) // toggle, not a held key #define MOD_NUMLOCK (1u << 7) // toggle, not a held key #define MOD_SHIFT (MOD_LSHIFT | MOD_RSHIFT) #define MOD_CTRL (MOD_LCTRL | MOD_RCTRL) #define MOD_ALT (MOD_LALT | MOD_RALT) static volatile uint32_t gModifiers = 0; static volatile bool gNextExtended = false; // previous byte was 0xE0 // -- Raw-scancode callback (invoked from ISR context) -------------------------- // The pointer must be volatile so the compiler re-reads it on every ISR entry // rather than caching the value seen at startup. static void (* volatile gCallback)(uint8_t) = NULL; // -- ASCII translation tables – scan-code set 1, non-extended keys only -------- // Index = 7-bit make code (SC1_BREAK_BIT cleared). 0 = no printable glyph. // Numpad digits yield their digit characters; a NumLock-aware wrapper can // substitute cursor-movement codes (e.g. HAL_KEY_HOME) when NumLock is clear. // // Layout verified against the IBM PC/AT scan-code set 1 reference. static const char gUnshifted[128] = { /*00*/ 0, /*01*/ 0, // ESC /*02*/ '1','2','3','4','5','6','7','8','9','0','-','=', // 02–0D /*0E*/ '\b', /*0F*/ '\t', /*10*/ 'q','w','e','r','t','y','u','i','o','p','[',']', // 10–1B /*1C*/ '\n', /*1D*/ 0, // Left Ctrl /*1E*/ 'a','s','d','f','g','h','j','k','l',';','\'','`', // 1E–29 /*2A*/ 0, // Left Shift /*2B*/ '\\', /*2C*/ 'z','x','c','v','b','n','m',',','.','/', // 2C–35 /*36*/ 0, // Right Shift /*37*/ '*', // Numpad * (also PrtSc on some keyboards) /*38*/ 0, // Left Alt /*39*/ ' ', /*3A*/ 0, // Caps Lock /*3B*/ 0,0,0,0,0,0,0,0,0,0, // F1–F10 (3B–44) /*45*/ 0, // Num Lock /*46*/ 0, // Scroll Lock /*47*/ '7','8','9', // Numpad 7–9 /*4A*/ '-', // Numpad – /*4B*/ '4','5','6', // Numpad 4–6 /*4E*/ '+', // Numpad + /*4F*/ '1','2','3', // Numpad 1–3 /*52*/ '0', // Numpad 0 /*53*/ '.', // Numpad . /*54*/ 0,0,0, /*57*/ 0, // F11 /*58*/ 0, // F12 /*59*/ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 59–68 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 69–78 0,0,0,0,0,0,0, // 79–7F }; static const char gShifted[128] = { /*00*/ 0, /*01*/ 0, // ESC /*02*/ '!','@','#','$','%','^','&','*','(',')','_','+', // 02–0D /*0E*/ '\b', /*0F*/ '\t', /*10*/ 'Q','W','E','R','T','Y','U','I','O','P','{','}', // 10–1B /*1C*/ '\n', /*1D*/ 0, // Left Ctrl /*1E*/ 'A','S','D','F','G','H','J','K','L',':','"','~', // 1E–29 /*2A*/ 0, // Left Shift /*2B*/ '|', /*2C*/ 'Z','X','C','V','B','N','M','<','>','?', // 2C–35 /*36*/ 0, // Right Shift /*37*/ '*', /*38*/ 0, // Left Alt /*39*/ ' ', /*3A*/ 0, // Caps Lock /*3B*/ 0,0,0,0,0,0,0,0,0,0, // F1–F10 /*45*/ 0, /*46*/ 0, /*47*/ '7','8','9', /*4A*/ '-', /*4B*/ '4','5','6', /*4E*/ '+', /*4F*/ '1','2','3', /*52*/ '0', /*53*/ '.', /*54*/ 0,0,0, /*57*/ 0, // F11 /*58*/ 0, // F12 /*59*/ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0, }; // -- Interrupt service routine -------------------------------------------------- // Called with interrupts disabled (inside the hardware IRQ handler). // The caller's wrapper must send the APIC/PIC EOI after this returns. static void KbdIsr(void) { uint8_t sc = __inbyte(PS2_DATA); // E0 prefix: the following byte is an extended make/break code. if (sc == SC1_EXT_PREFIX) { gNextExtended = true; return; } bool extended = gNextExtended; bool is_break = (sc & SC1_BREAK_BIT) != 0; uint8_t make = sc & SC1_MAKE_MASK; gNextExtended = false; // -- Update modifier state ------------------------------------------------- if (!extended) { switch (make) { case 0x2A: // Left Shift gModifiers = is_break ? (gModifiers & ~MOD_LSHIFT) : (gModifiers | MOD_LSHIFT); return; case 0x36: // Right Shift gModifiers = is_break ? (gModifiers & ~MOD_RSHIFT) : (gModifiers | MOD_RSHIFT); return; case 0x1D: // Left Ctrl gModifiers = is_break ? (gModifiers & ~MOD_LCTRL) : (gModifiers | MOD_LCTRL); return; case 0x38: // Left Alt gModifiers = is_break ? (gModifiers & ~MOD_LALT) : (gModifiers | MOD_LALT); return; case 0x3A: // Caps Lock – toggle on make only if (!is_break) gModifiers ^= MOD_CAPS; return; case 0x45: // Num Lock – toggle on make only if (!is_break) gModifiers ^= MOD_NUMLOCK; return; default: break; } } else { // Extended make codes that map to modifier keys. switch (make) { case 0x1D: // Right Ctrl (E0 1D) gModifiers = is_break ? (gModifiers & ~MOD_RCTRL) : (gModifiers | MOD_RCTRL); return; case 0x38: // Right Alt / AltGr (E0 38) gModifiers = is_break ? (gModifiers & ~MOD_RALT) : (gModifiers | MOD_RALT); return; default: break; } // All other extended keys (arrows, Home/End, etc.) fall through // and are enqueued as make codes for consumer-side interpretation. } // -- Discard break codes for non-modifier keys ----------------------------- if (is_break) return; // -- Enqueue make code; silently drop if buffer is full -------------------- int next = (gTail + 1) & (int)KBD_BUF_MASK; if (next != gHead) { gBuf[gTail] = make; gTail = next; } // -- Notify optional callback with the raw make code ----------------------- // Take a local snapshot so the compiler cannot reorder the NULL check and // the call across a volatile barrier, and to avoid double-fetching. void (*cb)(uint8_t) = (void (*)(uint8_t))gCallback; if (cb) cb(make); } // -- Consumer API -------------------------------------------------------------- static bool KbdPoll(void) { return gHead != gTail; } // Blocking read. Spins with PAUSE until a make code is available. // On a preemptive kernel, replace the _mm_pause() loop with a // condition variable or sti/hlt to avoid wasting CPU cycles. static uint8_t KbdReadScancode(void) { while (gHead == gTail) { _mm_pause(); } uint8_t sc = gBuf[gHead]; gHead = (gHead + 1) & (int)KBD_BUF_MASK; return sc; } // Returns the ASCII character for the next keypress, applying the current // Shift and CapsLock state. Returns 0 for keys with no ASCII mapping // (function keys, arrows, unmodified Ctrl/Alt keys, etc.). // // CapsLock inverts shift only for alphabetic keys (a–z); digit and symbol // keys are always governed solely by the physical Shift state. static char KbdReadChar(void) { uint8_t make = KbdReadScancode(); if (make >= 128) return 0; uint32_t mods = gModifiers; // snapshot; safe outside ISR bool shifted = (mods & MOD_SHIFT) != 0; // CapsLock toggles the effective shift for alphabetic keys only. if (mods & MOD_CAPS) { char base = gUnshifted[make]; if (base >= 'a' && base <= 'z') { shifted = !shifted; } } return shifted ? gShifted[make] : gUnshifted[make]; } static void KbdSetCallback(void (*cb)(uint8_t)) { gCallback = cb; } // Returns the current modifier bitmask (OR of MOD_* flags above). // Snapshot is taken outside ISR and may lag by at most one interrupt. static uint32_t KbdGetModifiers(void) { return gModifiers; } // -- Initializer --------------------------------------------------------------- void HalKeyboardInitialize(HAL_KEYBOARD *out) { // Drain any stale bytes sitting in the PS/2 output buffer before // enabling interrupts, so the first ISR call sees a clean state. while (__inbyte(PS2_STAT) & 1) { __inbyte(PS2_DATA); } out->Poll = KbdPoll; out->ReadScancode = KbdReadScancode; out->ReadChar = KbdReadChar; out->SetCallback = KbdSetCallback; out->GetModifiers = KbdGetModifiers; out->Isr = KbdIsr; out->IrqNumber = 1; }