Files
nt-lite/code/hal/hal_keyboard.c
T

299 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 <stdint.h>
#include <stdbool.h>
#include <intrin.h>
#include <hal_api.h>
#include <hal_apic.h>
#include <hal_debug.h>
// -- 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;
}