304 lines
11 KiB
C
304 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) {
|
|
// Enable PS/2 auxiliary port so the mouse driver can claim IRQ 12.
|
|
// The 8042 must have an empty input buffer before we can issue commands.
|
|
while (__inbyte(PS2_STAT) & 2) { /* wait for input buffer empty */ }
|
|
__outbyte(PS2_CMD, 0xA8); // enable aux port
|
|
|
|
// 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;
|
|
}
|