299 lines
11 KiB
C
299 lines
11 KiB
C
// 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;
|
||
}
|