add doom, disable warnings as errors, massive speedups in the fat32 driver, HAL_KEYBOARD Poll now returns the key and added IsKeyDown used by GetAsyncKeyState, slight upgrade in hal_main drain and pray, more accurate QueryTicks and by that GetTickCount, add GetTickCount64, boot.ini now dictates what executable should init launch, not the kernel, LaunchUserProcess in kmain.c now takes an argument, rearrange syscall numbers, add SysKbIsKeyDown syscall, use include windows.h over manual definitions, add fictional win32 api ClearConsoleScreenBuffer and real FlushConsoleInputBuffer and a clear command to the shell, print exit codes as signed int, StretchDIBits now can stretch, runa.bat now gives 256 MB.

This commit is contained in:
maksw committed 2026-05-11 10:10:21 +02:00
1 parent cdf7ed4cea
commit 0c7f461c6d
24 files changed
+50236 -1310

No files matched your search

+2 -1
View File
@@ -21,11 +21,12 @@ typedef struct HAL_SCREEN {
} HAL_SCREEN;
typedef struct HAL_KEYBOARD {
bool (*Poll)(void);
uint8_t (*Poll)(void);
uint8_t (*ReadScancode)(void);
char (*ReadChar)(void);
uint32_t (*GetModifiers)(void);
void (*SetCallback)(void (*cb)(uint8_t scancode));
bool (*IsKeyDown)(uint8_t scancode, bool extended);
void (*Isr)(void);
uint8_t IrqNumber;
} HAL_KEYBOARD;
+71 -214
View File
@@ -1,50 +1,31 @@
// 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.
//
// hal_keyboard.c - PS/2 keyboard, interrupt-driven, with live state tracking
#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 &.
// -- Ring buffer (for ASCII/typing) -------------------------------------------
#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)
static volatile int gHead = 0;
static volatile int gTail = 0;
// -- Live Key State (for GetAsyncKeyState) ------------------------------------
// These arrays track whether a key is currently held down.
static volatile uint8_t gKeyState[128]; // Standard scan codes
static volatile uint8_t gKeyStateExt[128]; // Extended (E0) scan codes
// -- 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
#define SC1_BREAK_BIT 0x80u
#define SC1_MAKE_MASK 0x7Fu
#define SC1_EXT_PREFIX 0xE0u
// -- Modifier flags ------------------------------------------------------------
#define MOD_LSHIFT (1u << 0)
@@ -53,107 +34,38 @@ static volatile int gTail = 0; // producer write pointer (updated by ISR)
#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_CAPS (1u << 6)
#define MOD_NUMLOCK (1u << 7)
#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 volatile bool gNextExtended = false;
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.
// -- ASCII Translation Tables --------------------------------------------------
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
0, 0, '1','2','3','4','5','6','7','8','9','0','-','=','\b','\t',
'q','w','e','r','t','y','u','i','o','p','[',']','\n', 0, 'a','s',
'd','f','g','h','j','k','l',';','\'','`', 0, '\\','z','x','c','v',
'b','n','m',',','.','/', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, '7','8','9','-', '4','5','6','+', '1','2','3','0','.'
};
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,
0, 0, '!','@','#','$','%','^','&','*','(',')','_','+','\b','\t',
'Q','W','E','R','T','Y','U','I','O','P','{','}','\n', 0, 'A','S',
'D','F','G','H','J','K','L',':','"','~', 0, '|','Z','X','C','V',
'B','N','M','<','>','?', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, '7','8','9','-', '4','5','6','+', '1','2','3','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) {
// -- ISR: The Engine -----------------------------------------------------------
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;
@@ -162,142 +74,87 @@ static void KbdIsr(void) {
bool extended = gNextExtended;
bool is_break = (sc & SC1_BREAK_BIT) != 0;
uint8_t make = sc & SC1_MAKE_MASK;
gNextExtended = false;
// -- Update modifier state -------------------------------------------------
// Update the live state arrays
if (extended) {
gKeyStateExt[make] = is_break ? 0 : 1;
} else {
gKeyState[make] = is_break ? 0 : 1;
}
// Update modifiers
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;
case 0x2A: gModifiers = is_break ? (gModifiers & ~MOD_LSHIFT) : (gModifiers | MOD_LSHIFT); break;
case 0x36: gModifiers = is_break ? (gModifiers & ~MOD_RSHIFT) : (gModifiers | MOD_RSHIFT); break;
case 0x1D: gModifiers = is_break ? (gModifiers & ~MOD_LCTRL) : (gModifiers | MOD_LCTRL); break;
case 0x38: gModifiers = is_break ? (gModifiers & ~MOD_LALT) : (gModifiers | MOD_LALT); break;
case 0x3A: if (!is_break) gModifiers ^= MOD_CAPS; break;
case 0x45: if (!is_break) gModifiers ^= MOD_NUMLOCK; 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;
case 0x1D: gModifiers = is_break ? (gModifiers & ~MOD_RCTRL) : (gModifiers | MOD_RCTRL); break;
case 0x38: gModifiers = is_break ? (gModifiers & ~MOD_RALT) : (gModifiers | MOD_RALT); 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;
// Buffer management (only for 'make' codes)
if (!is_break) {
int next = (gTail + 1) & (int)KBD_BUF_MASK;
if (next != gHead) {
gBuf[gTail] = make;
gTail = next;
}
void (*cb)(uint8_t) = (void (*)(uint8_t))gCallback;
if (cb) cb(make);
}
// -- 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 --------------------------------------------------------------
// -- Public API ----------------------------------------------------------------
static bool KbdPoll(void) {
return gHead != gTail;
// Use this inside your GetAsyncKeyState implementation!
bool KbdIsKeyDown(uint8_t scancode, bool extended) {
if (extended) return gKeyStateExt[scancode & 0x7F] != 0;
return gKeyState[scancode & 0x7F] != 0;
}
static uint8_t KbdPoll(void) {
return (gHead == gTail) ? 0 : gBuf[gHead];
}
// 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();
}
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;
uint32_t mods = gModifiers;
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;
}
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);
}
while (__inbyte(PS2_STAT) & 2);
__outbyte(PS2_CMD, 0xA8);
while (__inbyte(PS2_STAT) & 1) { __inbyte(PS2_DATA); }
out->Poll = KbdPoll;
out->ReadScancode = KbdReadScancode;
out->ReadChar = KbdReadChar;
out->SetCallback = KbdSetCallback;
out->GetModifiers = KbdGetModifiers;
out->IsKeyDown = KbdIsKeyDown;
out->Isr = KbdIsr;
out->IrqNumber = 1;
}
+2 -3
View File
@@ -365,11 +365,10 @@ __declspec(dllexport) KSTATUS HalInitialize(const HAL_INIT_PARAMS* initParams) {
// and because the CPU missed the IRQ 12 edge transition (cli) later when we expect it to fire, it doesn't
// leaving us with no mouse and no keyboard. The dreaded 8042 will hold the CLOCK line low forever.
// To combat this we just go about our day executing the rest of the init and just before the end
// of init we use a decaying poll to grab whatever the controller left for us ("Drain and Pray").
for (int i = 0; i < 1000; i++) {
// of init we just iterate 256 times to grab whatever the controller left for us ("Drain and Pray").
for (int i = 0; i < 256; i++) {
if (__inbyte(0x64) & 0x01) {
__inbyte(0x60); // Consume
i = 0; // Reset timeout if we are still getting data
}
gApi.Timer.Stall(1); // Micro-stalls
}
+22 -2
View File
@@ -21,6 +21,7 @@
extern HAL_API gApi;
static volatile uint32_t* gLapic = NULL;
static uint32_t gApicTicksPerUs = 0;
static uint32_t gApicTicksPerQuantum = 0;
static uint64_t gHpetTicksPerUs = 0;
static volatile uint64_t* gHpet = NULL;
#define LAPIC_REG(off) (*(volatile uint32_t *)((uint8_t *)gLapic + (off)))
@@ -37,7 +38,25 @@ static void TimerIsr(void) {
// EOI is sent by KeIrqDispatch after this returns
}
static uint64_t QueryTicks(void) { return gTicks; }
static uint64_t QueryTicks(void) {
uint64_t baseTicks = gTicks; // The 10ms "coarse" ticks
// The LAPIC timer counts DOWN.
// Ticks elapsed = (Initial Value) - (Current Value)
uint32_t current = LAPIC_REG(LAPIC_TIMER_CUR);
uint32_t initial = gApicTicksPerQuantum;
uint32_t elapsedApicTicks = 0;
if (current < initial) {
elapsedApicTicks = initial - current;
}
// Convert base 10ms ticks to microseconds + the remainder
// gTicks * 10,000 (ms to us) + (elapsed / ticks_per_us)
uint64_t totalUs = (baseTicks * QUANTUM_MS * 1000) + (elapsedApicTicks / gApicTicksPerUs);
return totalUs;
}
static void Stall(uint32_t us) {
// Priority 1: Use HPET (Highest Precision)
@@ -157,6 +176,7 @@ KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER *o
}
gApicTicksPerUs = ticksPerMs / 1000;
gApicTicksPerQuantum = ticksPerMs * QUANTUM_MS;
if (hpetPhys) {
gHpet = (volatile uint64_t*)gApi.PhysToVirt(hpetPhys);
@@ -169,7 +189,7 @@ KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER *o
// Configure LAPIC timer in periodic mode
LAPIC_REG(LAPIC_TIMER_DIV) = TIMER_DIV16;
LAPIC_REG(LAPIC_TIMER_LVT) = TIMER_PERIODIC | TIMER_VECTOR;
LAPIC_REG(LAPIC_TIMER_INIT) = ticksPerMs * QUANTUM_MS;
LAPIC_REG(LAPIC_TIMER_INIT) = gApicTicksPerQuantum;
out->QueryTicks = QueryTicks;
out->Stall = Stall;