FABRIC.md: aarch64 keyboard punch list -- GIC-SPI derivation, no landmine

Live-probed GICD_ITARGETSR before starting 4.3.5d/e; survived cleanly,
unlike riscv64's PLIC (4.3.5a). Decoded QEMU's own virt DTB for the PCI
slot->SPI interrupt-map routing table and closed-form formula. Amended
4.3.5d's circular acceptance criterion to a synthetic GICD_ISPENDR probe
(mirrors 4.3.5b's fix for the same problem), and wrote the full 4.3.5e
virtio-input execution plan: device ID 0x1052, the real virtio_input_event
struct, the mandatory ISR-status read, and the pci_enable() interrupt-
disable-bit gap.

Log: logs/20260808-093228/aarch64/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-08 09:56:20 -04:00
co-authored by Claude Sonnet 5
parent e01c4e33e4
commit 06283e5364
6 changed files with 18375 additions and 16 deletions
+152 -4
View File
@@ -3978,18 +3978,120 @@ document and committing that amendment as its own item.*
registers) that today's minimal init never touches. This item extends the GIC init from
0.6 just far enough to enable and route one SPI, still not a general driver — scope stays
as narrow as 0.6's did.
*Done when:* aarch64 can take and acknowledge (`IAR`/`EOIR`) an SPI from the virtio-input
PCI device, three-arch QEMU boot unaffected, log committed.
**Amended 2026-08-08 — original acceptance was circular.** "Take and acknowledge an SPI
from the virtio-input PCI device" can't close as its own commit: that device doesn't
exist until 4.3.5e, so this item could never be verified in isolation, breaking §25.0's
one-item-one-commit rule. Fixed the same way 4.3.5b already fixed the identical problem
for the PLIC — verify against a **synthetic/known** interrupt, not the real device.
Concrete steps:
1. Add `PCI_CFG_INT_PIN` (offset `0x3D`, byte — the Interrupt Pin register) to
`include/starkernel/pci.h`, alongside the existing `PCI_CFG_INT_LINE` (`0x3C`).
2. Compute the target SPI at **runtime**, not a hardcoded constant:
`pin = pci_read8(dev, PCI_CFG_INT_PIN)` (1=INTA…4=INTD), `slot = dev->device`
(already populated by `pci_find_first()`), `spi = 3 + ((slot + pin - 1) % 4)`
INTID `32 + spi`. Derived live from this host's QEMU 10.2.1 `virt` DTB, not recalled
— full decode and the table it produced are in §27.5.
3. Program, for the computed `intid`: `GICD_IPRIORITYR` at `0x400 + intid` (value `0x80`,
matching `TIMER_PRIORITY`); `GICD_ISENABLER` at `0x100 + 4*(intid/32)` (`0x104` for
every candidate INTID 3538), bit `intid % 32`; `GICD_ITARGETSR` at `0x800 + intid`
(`0x823``0x826`), value `0x01` (single vCPU — this target has no `-smp`). `GICD_ICFGR`:
read back first; §27.5's decode says this SPI should already default level-triggered,
write only if the readback disagrees — keep item 0.6's "don't touch ICFGR unless
forced to" posture.
4. Verify with **no device present**: software-pend the computed SPI via `GICD_ISPENDR`
(offset `0x200 + 4*(intid/32)`, bit `intid % 32` — standard GICv2 architecture
register, ARM IHI 0048B), confirm it is taken, read via `GICC_IAR`, and completed via
`GICC_EOIR` through `aarch64_irq_handler()`. This is the actual acceptance below.
*Done when:* a software-pended SPI (via `GICD_ISPENDR`, no device present) on the
computed target INTID is taken and acknowledged (`IAR`/`EOIR`), three-arch QEMU boot
unaffected, log committed.
*Refs:* §27.5.
> **Scoping check, 2026-08-08 — no landmine found (unlike 4.3.5a).** Before starting this
> item, checked whether aarch64 carries the same class of gap that blocked riscv64:
> `arch_mmu_init()` (`arch/aarch64/arch.c:169`) is an identical stub — "page-table setup...
> deferred to a later milestone" — so aarch64's MMIO likewise depends entirely on whatever
> EDK2 left mapped at `ExitBootServices()`, an assumption `apic.c`'s own header comment
> (lines 7679) already names. Live-tested rather than inferred, same rigor as 4.3.5a's
> discovery: added a temporary probe (read-modify-write to `GICD_ITARGETSR[32]`, offset
> `0x820`) immediately after 0.6's existing `GICD_CTLR`/`ISENABLER0`/`IPRIORITYR` writes,
> booted aarch64 alone in QEMU. Result: survived — `GICv2 probe: ITARGETSR[32] read+write
> survived` printed, boot proceeded clean to `ok>`, no exception (log:
> `logs/20260808-093228/aarch64/`). `0x820` isn't itself one of this item's real targets —
> those, per the DTB decode above, are INTID 3538 at `0x823``0x826` — but all of them sit
> in the same 4KB page as `0x820` and as the offsets item 0.6 already proves reachable on
> every boot (`0x000`/`0x100`/`0x400`), so the probe validates the page, not a coincidence.
> Unlike riscv64's PLIC threshold register, which sat 2MB deep in a part of its MMIO window
> nothing had ever touched, that page-adjacency is why this item doesn't carry a
> 4.3.5a-shaped prerequisite. 4.3.5e's PCI-BAR MMIO transport is likewise already proven
> live (`virtio-blk-pci` uses it today on this same board). Probe code reverted after
> recording this result — not committed; this item remains unstarted.
- [ ] **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.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.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.
Concrete steps:
1. Add `-device virtio-keyboard-pci` to the aarch64 `qemu`/`qemu-esp` targets in
`Makefile.starkernel`. Confirmed available in this host's QEMU 10.2.1
(`qemu-system-aarch64 -device help` lists it, bus PCI, alias `virtio-keyboard`).
2. New `include/starkernel/virtio_input.h` / `src/starkernel/virtio/virtio_input.c`.
Device identity: vendor `0x1AF4` (same as blk), device ID `0x1052` — the modern-ID
formula `0x1040 + VIRTIO_ID_INPUT`, `VIRTIO_ID_INPUT = 18` per this build host's
`/usr/include/linux/virtio_ids.h`. No legacy/transitional ID exists for virtio-input
(unlike blk's `0x1001` fallback — input postdates the legacy 0.9.5 spec), so
`pci_find_first()` only needs the one modern ID, no fallback branch.
3. Event structure, from this host's `/usr/include/linux/virtio_input.h` and
`/usr/include/linux/input-event-codes.h` (authoritative, not recalled): `struct
virtio_input_event { __le16 type; __le16 code; __le32 value; }` (8 bytes), `EV_KEY =
0x01`.
4. **Design decision, flagged not resolved here:** `walk_virtio_caps()` (the
capability-list walker `virtio_blk.c` uses for `COMMON_CFG`/`NOTIFY_CFG`) is `static`
inside that file. This item needs the same walk plus, new, `ISR_CFG` (step 6). Promote
it to a shared `virtio_pci.c`/`.h`, or duplicate it here? Get a ruling before writing
code — don't default to one silently (§25.0 rule 3).
5. `pci_enable()` (`pci.c:363`) sets only IO/MEM/BUS_MASTER in the PCI COMMAND register —
it never clears bit 10 (Interrupt Disable). If that bit is set at enumeration, INTx
never asserts and every earlier step passes while producing zero real interrupts —
same failure shape as 4.3.5's OVMF-scanning-disabled bug (finding 2 in that item's done
note). Read COMMAND back after `pci_enable()` and confirm/clear bit 10.
6. Device init follows `vblk_init_device()`'s sequence (`virtio_blk.c:361-499`) almost
exactly — reset → ACKNOWLEDGE → DRIVER → negotiate `VIRTIO_F_VERSION_1` → FEATURES_OK →
configure queue(s) → DRIVER_OK. Differences:
- Two virtqueues (eventq index 0, statusq index 1) — read `common->num_queues` back
from the device rather than hardcoding 2, same live-verification discipline as
everything else in this item.
- Unlike `vblk_io()`'s request/response pattern, the driver pre-posts N empty
`virtio_input_event` buffers (device-writable) into the eventq's avail ring at init
and does not wait synchronously — the device fills and posts a buffer to the used
ring asynchronously, signalled by the interrupt wired in 4.3.5d, not a poll loop.
Buffers must be re-posted after being drained in the ISR.
- statusq (index 1) is for driver→device reports (LED state) — not needed to read
keypresses; configure it (spec expects both queues set up) but leave it unused.
- **New for this tree, mandatory:** `virtio_blk.c` defines `VIRTIO_PCI_CAP_ISR_CFG = 3`
but never reads it — `vblk_io()` polls and never handles a real interrupt, so it
never needed to. This item is the first interrupt-driven virtio-pci device here.
Because the routed SPI is level-triggered (§27.5's decoded `interrupt-map` flags =
`0x4`), the ISR handler **must** read the ISR-status capability byte
(`walk_virtio_caps(pci, VIRTIO_PCI_CAP_ISR_CFG, NULL)`) — that read is what
deasserts the line. Skipping it leaves INTx asserted after the first event: an
interrupt storm or a permanent hang, not a subtle bug.
- Report, don't fix: `virtio_blk.c`'s `wmb()`/`rmb()` (`virtio_blk.c:221-226`) are
compiler barriers only (`asm volatile("" ::: "memory")`), no real memory fence.
Adequate for blk's synchronous polled loop; an interrupt-driven used-ring (device
writes concurrently with the driver's re-post loop) leans on this ordering harder.
Not this item's job to fix tree-wide — report if it manifests as a live symptom.
*Done when:* a keypress in QEMU on aarch64 (via `sendkey`, same manual-injection
technique as 4.3.5's amd64 verification) produces a captured `EV_KEY` event through the
interrupt path with no polling loop anywhere in the path, three-arch QEMU boot
unaffected, log committed.
*Refs:* §27.5.
- [ ] **4.3.5f — Checkpoint: one keyboard abstraction, three architectures, no polling.**
@@ -4429,3 +4531,49 @@ index, which would have been the fast/wrong way to get a key on screen quickly.
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.
### 27.5.1 aarch64 GIC-SPI derivation for 4.3.5d/4.3.5e (2026-08-08)
Two facts 4.3.5d/4.3.5e depend on were derived live rather than assumed, same discipline as
`apic.c`'s own file header uses for the timer PPI values (item 0.6).
**1. aarch64 has no 4.3.5a-shaped landmine.** `arch_mmu_init()` (`arch/aarch64/arch.c:169`)
is the same kind of stub as riscv64's Sv39 gap — MMU bring-up "deferred to a later
milestone" — so aarch64's MMIO also depends entirely on whatever EDK2 left mapped at
`ExitBootServices()`. Live-probed rather than inferred: a temporary read-modify-write to
`GICD_ITARGETSR` (offset `0x820`, within the same 4KB page as this item's real target
offsets `0x823``0x826`) survived cleanly on a solo aarch64 boot — no exception, boot
proceeded to `ok>` (log: `logs/20260808-093228/aarch64/`). Recorded at the 4.3.5d entry
itself (§25.5); this subsection is the supporting derivation, not a duplicate record.
**2. The PCI slot→SPI routing formula, decoded from QEMU's own DTB, not recalled.**
Dumped with `qemu-system-aarch64 -machine virt,dumpdtb=<file> -cpu cortex-a57` (QEMU
10.2.1, the exact binary and machine/cpu flags this tree's `qemu` target uses —
`Makefile.starkernel:822-824`) and decoded by hand-parsing the FDT struct block (no `dtc`
installed on this build host) for the `pcie@10000000` node's `interrupt-map` /
`interrupt-map-mask` properties. `interrupt-map-mask` = `0x1800 0 0 0x7` masks PCI device
number down to its low 2 bits (`slot mod 4`) and the full 3-bit INTx pin field — meaning
the table's 16 explicit entries (slots 03 × INTAD) cover **every** PCI slot QEMU assigns,
not just 03, because routing repeats every 4 slots. Decoded table (INTID = GIC SPI number
+ 32; flags `0x4` = `IRQ_TYPE_LEVEL_HIGH` throughout):
| slot mod 4 | INTA | INTB | INTC | INTD |
|---|---|---|---|---|
| 0 | INTID 35 (SPI 3) | INTID 36 (SPI 4) | INTID 37 (SPI 5) | INTID 38 (SPI 6) |
| 1 | INTID 36 (SPI 4) | INTID 37 (SPI 5) | INTID 38 (SPI 6) | INTID 35 (SPI 3) |
| 2 | INTID 37 (SPI 5) | INTID 38 (SPI 6) | INTID 35 (SPI 3) | INTID 36 (SPI 4) |
| 3 | INTID 38 (SPI 6) | INTID 35 (SPI 3) | INTID 36 (SPI 4) | INTID 37 (SPI 5) |
Closed form, used at runtime by 4.3.5d/e rather than a hardcoded constant (the aarch64
`qemu`/`qemu-esp` targets don't pin PCI slot addresses the way the riscv64 target's
`addr=0x1`/`addr=0x2` do, so the keyboard's slot is whatever QEMU assigns):
```
pin = pci_read8(dev, PCI_CFG_INT_PIN) /* config offset 0x3D; 1=INTA .. 4=INTD */
slot = dev->device /* already populated by pci_find_first() */
spi = 3 + ((slot + pin - 1) % 4)
intid = 32 + spi /* 35..38 */
```
Not yet decoded/needed: entries for PCI bridge-forwarded interrupts or multi-function
devices beyond function 0 — out of scope for a single virtio-keyboard-pci device on bus 0.