starkernel: item 4.3.5 -- amd64 I/O APIC + i8042 keyboard, interrupt-driven
Punch list §25 item 4.3.5 complete. New ioapic.c/i8042.c drivers (MADT-derived I/O APIC base, no hardcoded constants) plus a KBD-SCAN/KBD-DEBUG diagnostic word pair. Three real bugs found and fixed en route, all blocking this item's own acceptance: a fatal LAPIC spurious-vector crash (nothing had driven a real external interrupt through the I/O APIC before), OVMF leaving the keyboard device itself scanning-disabled (0xF4 fix), and isr.S's stub table only having individually-numbered stubs through vector 32 -- everything above that, including our IRQ1 vector 33, silently reported as vector 255 regardless of which IDT slot actually fired. Verified live via QEMU sendkey against KBD-SCAN: correct XT Set-1 make/break codes for two different keys. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
78ff335b97
commit
88eb73cfe8
@@ -3843,7 +3843,7 @@ document and committing that amendment as its own item.*
|
||||
> **This is the checkpoint** — 4.3.x groundwork stops here for review per this item's own
|
||||
> acceptance criterion, before scoping whatever comes next.
|
||||
|
||||
- [ ] **4.3.5 — amd64: I/O APIC bring-up + i8042 keyboard, interrupt-driven.** No I/O APIC
|
||||
- [x] **4.3.5 — amd64: I/O APIC bring-up + i8042 keyboard, interrupt-driven.** No I/O APIC
|
||||
driver exists in this tree today (checked: `apic.c` is Local-APIC-only, and the timer
|
||||
needs no routing because it self-interrupts) and `pic_disable()` masks the legacy 8259
|
||||
permanently — so no legacy IRQ, IRQ1 included, currently has any path to the CPU. This
|
||||
@@ -3856,6 +3856,69 @@ document and committing that amendment as its own item.*
|
||||
path with no polling loop in the code, three-arch QEMU boot unaffected, log committed.
|
||||
*Refs:* §27.5.
|
||||
|
||||
> **Done, 2026-08-08.** New `src/starkernel/arch/amd64/ioapic.c`/`include/starkernel/
|
||||
> ioapic.h`: parses the real ACPI MADT (RSDP → XSDT → APIC table, same walk `pci.c`
|
||||
> already uses for MCFG, not shared code but the same technique) for the I/O APIC's MMIO
|
||||
> base and any Interrupt Source Override entries — nothing hardcoded. New `i8042.c`/
|
||||
> `i8042.h`: minimal controller init, a trivial ISR (read `0x60`, push to a small ring
|
||||
> buffer, nothing else), and `i8042_pop_scancode()` for non-interrupt-context draining.
|
||||
> New diagnostic word `KBD-SCAN ( -- c -1 | 0 )` in `src/word_source/keyboard_words.c`
|
||||
> (raw hardware-boundary primitive, same relationship to future Console policy that
|
||||
> `PLOT` has to `capsules/fabric.4th`) plus a standing `KBD-DEBUG ( -- isr_count
|
||||
> spurious_count )` diagnostic. `apic_id()` added to `apic.h`/`apic.c` (LAPIC ID getter,
|
||||
> needed for the redirection entry's destination field).
|
||||
>
|
||||
> **Three real bugs found and fixed, all blocking this item's own acceptance test, not
|
||||
> scope creep:**
|
||||
>
|
||||
> 1. **LAPIC spurious-vector interrupts (`0xFF`) were previously fatal.** `pic_disable()`
|
||||
> means the I/O APIC is the *first* real external-interrupt source this kernel has ever
|
||||
> driven — the timer self-interrupts and never needed one. The very first live keypress
|
||||
> test crashed with `Vector : 255 (0xff)` / "Fault: Unhandled vector". Per Intel SDM
|
||||
> Vol.3 §10.9, a spurious-vector interrupt is a normal occasional hardware race, not a
|
||||
> fault, and must be silently ignored with no EOI. Fixed with a dedicated case in
|
||||
> `isr_common_handler()` (`interrupts.c`) plus a permanent `g_spurious_count` diagnostic
|
||||
> counter — cheap enough to keep, and this item is exactly why it's worth having.
|
||||
>
|
||||
> 2. **OVMF's own PS/2 driver leaves the keyboard *device* (not just the controller) with
|
||||
> scanning disabled.** After the crash was fixed, `KBD-SCAN` still came back empty on
|
||||
> every keypress. QEMU's own PS/2 tracepoints (`-trace enable='ps2_*,pckbd_*'`) showed
|
||||
> the device correctly generating and queuing the real make/break scancode sequence on
|
||||
> `sendkey`, so the device model itself was never the problem. `i8042_init()` only
|
||||
> programmed the *controller's* config byte (IRQ1-enable); it never told the *device*
|
||||
> to resume scanning. Fixed by sending `0xF4` (enable scanning) to the device via the
|
||||
> data port after the controller config write, with a bounded wait for the `0xFA` ACK —
|
||||
> a one-time init handshake, not the forbidden steady-state polling. (Ancillary,
|
||||
> reported not fixed: the controller's IRQ1-enable bit was already set before this
|
||||
> kernel ever touched it on this QEMU/OVMF combination, per the trace — that write is a
|
||||
> no-op here but stays correct for hardware that doesn't pre-enable it.)
|
||||
>
|
||||
> 3. **The real bug, found via `-d int` + a live LAPIC ISR/PPR register dump: `isr.S`'s
|
||||
> stub table only ever had individually-numbered stubs for vectors 0–32.** Vectors
|
||||
> 33–255 all shared `isr_stub_default`, which unconditionally pushes `255` as "the
|
||||
> vector" regardless of which IDT slot actually fired — nothing before this item had
|
||||
> ever needed a real interrupt source above vector 32. The I/O APIC correctly delivered
|
||||
> IRQ1 to vector 33 (0x21) — confirmed directly by reading the LAPIC's own ISR register
|
||||
> at the moment of the spurious firing, which showed vector 33 genuinely in-service —
|
||||
> but `isr_common_handler()` only ever saw vector 255, treated a real keyboard IRQ as
|
||||
> spurious, and (correctly, per finding 1) skipped its EOI, permanently stranding
|
||||
> vector 33's ISR bit and silently blocking that interrupt class forever after. A
|
||||
> priority-class theory (moving the keyboard vector to `0x31`, a different class from
|
||||
> the timer's `0x20`) was tested and ruled out first — the direct ISR-register readback
|
||||
> is what actually found it. Fixed by adding a dedicated `isr_stub33` (same `ISR_NOERR`
|
||||
> macro pattern as every other named stub) and pointing `isr_stub_table[33]` at it
|
||||
> instead of the shared default.
|
||||
>
|
||||
> **Verified live on amd64** via QEMU's HMP `sendkey` monitor command against a real
|
||||
> interactive serial session (same manual-injection technique used throughout 4.3.x):
|
||||
> `sendkey a` → `KBD-SCAN .S` showed `<2> 30 -1` (30 = `0x1E`, the correct XT Set-1 make
|
||||
> code for 'A'); `sendkey b` → two `KBD-SCAN` calls showed `48 -1` then `176 -1` (`0x30`
|
||||
> make / `0x30|0x80` break for 'B', confirming the release-code bit-7 pattern). `KBD-DEBUG`
|
||||
> read `2 0` (two real IRQs serviced, zero spurious) after each keypress. Three-architecture
|
||||
> acceptance boot clean on all three (`logs/20260808-001508` amd64, `logs/20260808-001546`
|
||||
> aarch64, `logs/20260808-001637` riscv64) — the feature is amd64-only this item, so
|
||||
> aarch64/riscv64 are a regression check, not a keyboard test.
|
||||
|
||||
- [ ] **4.3.5a — riscv64: PLIC bring-up (external interrupt controller).** Does not exist
|
||||
anywhere in this tree — Phase 0 (0.2/0.3) only ever enabled the S-mode *timer* interrupt
|
||||
(`sie.STIE`); external interrupts (`sie.SEIE`, bit 9) were never touched, and
|
||||
|
||||
Reference in New Issue
Block a user