From e01c4e33e48a16a2eaead183ba28f179b1ddb5b0 Mon Sep 17 00:00:00 2001 From: Robert Allan James Date: Sat, 8 Aug 2026 00:47:31 -0400 Subject: [PATCH] FABRIC.md: insert 4.3.5a -- riscv64 has no software-controlled paging Discovered live while implementing what was 4.3.5a (PLIC bring-up, renumbered 4.3.5b): riscv64 never activates its own page table (load_cr3() is a no-op outside __x86_64__, arch.c's Sv39/SATP setup is an explicit stub), so firmware's own mapping -- which doesn't cover the PLIC's MMIO window -- is what's live for the kernel's whole lifetime. Confirmed via a live store-page-fault, not inferred. Per Captain Bob's ruling: stop, scope as its own item, decide the fix (Bare mode vs. real Sv39) in a future session rather than embedded in the PLIC item. 4.3.5b-4.3.5e renumbered accordingly (4.3.5f was 4.3.5e); no items were executed under the old numbering, so this is free to do cleanly now. Documentation only. PLIC driver code (plic.c/plic.h, interrupts.c/ apic.c wiring, Makefile.starkernel) is written and reviewed but stays uncommitted -- it cannot be verified until 4.3.5a unblocks it, and riscv64 currently hangs in the fatal exception handler with it applied. Co-Authored-By: Claude Sonnet 5 --- FABRIC.md | 72 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/FABRIC.md b/FABRIC.md index 3f0301b..465ae05 100644 --- a/FABRIC.md +++ b/FABRIC.md @@ -3919,30 +3919,59 @@ document and committing that amendment as its own item.* > 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 +- [ ] **4.3.5a — riscv64: minimal paging/addressing bring-up (prerequisite, blocks + 4.3.5b).** Discovered live 2026-08-08 while implementing 4.3.5b, not anticipated when + 4.3.5a-4.3.5f were originally scoped. **riscv64 has no software-controlled paging at + all.** `arch/riscv64/arch.c`'s own comment is explicit: "Full Sv39/Sv48 page-table setup + (SATP, PMP, etc.) is deferred to a later milestone" — it is a stub, `satp` is never + written by this kernel. Separately, `memory/vmm.c` (header comment: "4-level paging + Virtual Memory Manager (x86_64)") runs its `vmm_init()` unconditionally on all three + architectures and builds a real x86-64-style page table in memory, but its activation + step (`load_cr3()`) compiles to a no-op outside `__x86_64__` — so on riscv64 that table + is dead, unused data. Whatever `satp` firmware (EDK2 RISC-V) left active at + `ExitBootServices()` is what's live for the kernel's entire lifetime, and there is no + documented or safe way for us to add entries to firmware's own page table post-handoff. + This surfaced because 4.3.5b's PLIC threshold register (`PLIC_BASE + 0x201000`, deep + into the PLIC's 0x600000-byte MMIO window) is not covered by firmware's mapping — + confirmed via a live store-page-fault (`scause=0xF`) at exactly that address, not + inferred. **Not fixed as part of surfacing it** — Captain Bob's ruling 2026-08-08: stop, + scope as its own item, decide the approach (Bare-mode `satp=0` vs. real Sv39 bring-up) + in a future session with fresh context, rather than deciding it embedded inside a PLIC + item. 4.3.5b's driver code (`plic.c`/`plic.h`, `interrupts.c`/`apic.c` wiring, + `Makefile.starkernel`) exists, written and reviewed, but **uncommitted** — it cannot be + verified until this item unblocks it, and 4.3.5b's own acceptance ("three-arch QEMU boot + unaffected") is currently violated (riscv64 hangs in the fatal exception handler). + *Done when:* riscv64 has some working address-translation story (either genuinely + activated Sv39 paging with a kernel-owned page table, or an explicit, deliberate switch + to Bare mode) sufficient for `plic_init()`'s existing MMIO writes to succeed without + faulting, three-arch QEMU boot clean, log committed. + *Refs:* §27.5. + +- [ ] **4.3.5b — 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 `interrupts.c`'s own comment ("the PLIC replaces both") describes the architecture, not anything implemented. This item enables `sie.SEIE`, programs the PLIC's per-source priority, the hart context's threshold and enable bits, and wires the trap handler's external-interrupt case to `PLIC claim` → dispatch → `PLIC complete`. Prerequisite for - 4.3.5b; no keyboard code in this item. + 4.3.5c; no keyboard code in this item. **Blocked on 4.3.5a** (see that item) — driver + code already written, not yet verifiable. *Done when:* a synthetic/known external interrupt source claims and completes correctly through the PLIC on riscv64, three-arch QEMU boot unaffected, log committed. *Refs:* §27.5. -- [ ] **4.3.5b — riscv64: virtio-keyboard-device, interrupt-driven.** Add +- [ ] **4.3.5c — riscv64: virtio-keyboard-device, interrupt-driven.** Add `virtio-keyboard-device` (MMIO virtio, matching the existing MMIO virtio-blk pattern on this board) to the riscv64 `qemu`/`qemu-esp` targets in `Makefile.starkernel`. New driver under `src/starkernel/virtio/` for the virtio-input device class — decode `EV_KEY` events off the input event virtqueue. Unlike `virtio_blk.c`'s synchronous poll-the-used-ring pattern, this device must be interrupt-driven end to end: the used-ring notification - arrives via the PLIC path from 4.3.5a, not a poll loop. Built on 4.3.5a. + arrives via the PLIC path from 4.3.5b, not a poll loop. Built on 4.3.5b. *Done when:* a keypress in QEMU on riscv64 produces a captured `EV_KEY` event via the interrupt path with no polling loop, three-arch QEMU boot unaffected, log committed. *Refs:* §27.5. -- [ ] **4.3.5c — aarch64: GIC SPI wiring for virtio-input.** Item 0.6 scoped the GIC to +- [ ] **4.3.5d — aarch64: GIC SPI wiring for virtio-input.** Item 0.6 scoped the GIC to "one interrupt" (the timer PPI) on purpose and explicitly called a general GIC driver out of scope. A PCI-attached `virtio-keyboard-pci` device signals via legacy INTx, which is an SPI, not a PPI — a different GIC distributor path (SPI target-CPU and priority @@ -3953,18 +3982,18 @@ document and committing that amendment as its own item.* PCI device, three-arch QEMU boot unaffected, log committed. *Refs:* §27.5. -- [ ] **4.3.5d — aarch64: virtio-keyboard-pci, interrupt-driven.** Add `virtio-keyboard-pci` +- [ ] **4.3.5e — aarch64: virtio-keyboard-pci, interrupt-driven.** Add `virtio-keyboard-pci` to the aarch64 `qemu`/`qemu-esp` targets — the existing `virtio-blk-pci` device proves the PCI bus is already enumerated (`pci.c`) on this board, so this rides the same bus, a new - device class. Same virtio-input driver shape as 4.3.5b (shared code where the transport + device class. Same virtio-input driver shape as 4.3.5c (shared code where the transport allows — MMIO vs. PCI config-space discovery differs, the event-queue/`EV_KEY` decode - should not). Built on 4.3.5c. + should not). Built on 4.3.5d. *Done when:* a keypress in QEMU on aarch64 produces a captured `EV_KEY` event via the interrupt path with no polling loop, three-arch QEMU boot unaffected, log committed. *Refs:* §27.5. -- [ ] **4.3.5e — Checkpoint: one keyboard abstraction, three architectures, no polling.** - Converge 4.3.5/4.3.5b/4.3.5d behind a single scancode/keycode interface so the REPL +- [ ] **4.3.5f — Checkpoint: one keyboard abstraction, three architectures, no polling.** + Converge 4.3.5/4.3.5c/4.3.5e behind a single scancode/keycode interface so the REPL keyboard-input work (M8, outside Stadium) has one thing to call, not three. Stop and review here — same posture as 4.3.4 — before scoping REPL wiring. *Done when:* a keypress on all three architectures produces the same shape of event at @@ -3973,7 +4002,7 @@ document and committing that amendment as its own item.* three-arch QEMU boot, logs committed. *Refs:* §27.5. - > **Sequencing noted 2026-08-07, not yet scoped:** keyboard input (4.3.5–4.3.5e) → + > **Sequencing noted 2026-08-07, not yet scoped:** keyboard input (4.3.5–4.3.5f) → > glyph rendering (stroke-based font capsules) → REPL — in that order, before the 4.4 > Artemis boundary. Recorded so the order is not lost between sessions; neither glyph > rendering nor REPL has *Done when* criteria yet. Per §25.0 rule 2, each gets scoped in @@ -4352,7 +4381,7 @@ not a quick add. Stop and review here before scoping the next 4.3.x item. Out-of for 4.3.1–4.3.4 is recorded once, at the 4.3 punch-list entry itself (§25.5), not repeated per sub-item. -### 27.5 Keyboard input, interrupt-driven (4.3.5–4.3.5e) +### 27.5 Keyboard input, interrupt-driven (4.3.5–4.3.5f) Scoped 2026-08-07, next after the 4.3.4 checkpoint. Purpose: get a real keypress into the kernel with no polling anywhere in the path, so the REPL keyboard-input work (M8, tracked @@ -4360,7 +4389,7 @@ outside Stadium — see `.claude/CLAUDE.md`'s roadmap section) has something to section is groundwork only, same as 27.1–27.4 was for the framebuffer — it stops at "a keypress produces an event at a shared interface," not REPL line-editing, not a scancode- to-ASCII layout, not repeat/modifier-key semantics. Those are later items, scoped once -4.3.5e is reviewed. +4.3.5f is reviewed. **Why this split is bigger than 4.3.3's three-way split.** 4.3.3/4.3.3a/4.3.3b all built on interrupt/timer substrate Phase 0 already finished. Keyboard input does not have that @@ -4373,11 +4402,16 @@ luxury on two of three architectures: - **riscv64** has never enabled external interrupts at all — Phase 0 (0.2/0.3) scoped strictly to the S-mode timer interrupt (`sie.STIE`). The PLIC (`sie.SEIE`, bit 9) is unimplemented; `interrupts.c`'s existing comment naming the PLIC describes the - architecture, not working code. 4.3.5a is that bring-up, split out as its own item because - it is substrate, not keyboard-specific — same shape as Phase 0's per-ISA items. + architecture, not working code. 4.3.5b is that bring-up, split out as its own item because + it is substrate, not keyboard-specific — same shape as Phase 0's per-ISA items. **Found + 2026-08-08 while implementing 4.3.5b: that substrate assumption itself had a substrate + gap underneath it** — riscv64 has no software-controlled paging at all (see 4.3.5a, + inserted ahead of the PLIC item once this surfaced). Phase 0 never needed to touch memory + translation because the timer only ever touches CSRs and SBI calls, never MMIO; PLIC + bring-up is the first riscv64 item to need a real MMIO write, and that is what exposed it. - **aarch64**'s GIC (item 0.6) was deliberately scoped to one PPI (the timer) and explicitly ruled a general GIC driver out of scope. A PCI `virtio-keyboard-pci` device signals via an - SPI, a distinct GIC path 0.6 never touched. 4.3.5c is the minimal SPI extension, held to + SPI, a distinct GIC path 0.6 never touched. 4.3.5d is the minimal SPI extension, held to the same narrow-scope discipline 0.6 used. **Hardware path differs by architecture, same as the framebuffer did.** amd64's `q35` @@ -4385,13 +4419,13 @@ machine has a real i8042 PS/2 controller (QEMU default, IRQ1) — 4.3.5. Neither (aarch64, riscv64) has PS/2 hardware; both get a `virtio-keyboard` device added to their QEMU command lines and a new virtio-input driver (this tree has none — `virtio_blk.c` is the only existing virtio driver, and it is fully synchronous/polled, so it is a transport -reference only, not an interrupt-handling one) — 4.3.5b (riscv64, MMIO transport) and 4.3.5d +reference only, not an interrupt-handling one) — 4.3.5c (riscv64, MMIO transport) and 4.3.5e (aarch64, PCI transport, riding the bus `pci.c` already enumerates for `virtio-blk-pci`). -**No polling, anywhere, in any of 4.3.5/4.3.5b/4.3.5d.** This is the one hard constraint +**No polling, anywhere, in any of 4.3.5/4.3.5c/4.3.5e.** This is the one hard constraint carried through every sub-item — restated per item because it is the actual point of doing interrupt bring-up first rather than reading `0x64`'s status bit or spinning on a used-ring index, which would have been the fast/wrong way to get a key on screen quickly. -4.3.5e is the checkpoint, same posture as 4.3.4: converge the three architecture-specific +4.3.5f is the checkpoint, same posture as 4.3.4: converge the three architecture-specific paths behind one interface before anything scopes REPL line input on top of it.