From 470f93a83d29a8529144698ea361ba128d725e33 Mon Sep 17 00:00:00 2001 From: Daniel Samson <12231216+daniel-samson@users.noreply.github.com> Date: Mon, 13 Jul 2026 01:15:17 +0100 Subject: [PATCH] Plan the discovery migration (M19 pci-bus, M20 acpi service) --- docs/m19-m20-plan.md | 140 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 docs/m19-m20-plan.md diff --git a/docs/m19-m20-plan.md b/docs/m19-m20-plan.md new file mode 100644 index 0000000..277fa96 --- /dev/null +++ b/docs/m19-m20-plan.md @@ -0,0 +1,140 @@ +# M19–M20 execution plan: discovery migration + +The operational plan for [device-manager.md](device-manager.md)'s increment 8: +discovery leaves the kernel — a **pci-bus driver** (M19) and an **acpi service** +(M20), with the kernel's device enumeration retired behind them. Same rules as +[m17-m18-plan.md](m17-m18-plan.md): one phase at a time, each green before the +next; this file is the build order and the checklist. + +**Definition of green, every phase:** `zig build` clean, `zig build test` clean, +`python3 test/qemu_test.py` passes (existing scenarios plus the phase's new +one), and the relevant design doc updated. Commit per green phase (no co-author +trailers). The full suite is the regression net — the existing +`driver-restart` / `usb-report` / `device-list` / `input` scenarios must stay +green *through* the migration, which is the whole point: the system must not be +able to tell who enumerated it. + +**Workflow:** dedicated worktree; branches off `main` — `feat/pci-bus` +(M19.0–19.3), `feat/acpi-service` (M20.1–20.3); auto-merge to main when a +branch is green; keep branches; push everything. + +## Settled decisions (2026-07-13 — veto before the loop starts) + +1. **What "retiring the kernel scan" means.** The kernel keeps, forever, the + parses it needs before user space exists: RSDP/XSDT location, MADT (SMP), + the HPET table (the tick), FADT + the AML `\_S5` evaluation (poweroff — the + power tests prove it), and MCFG (the host bridge node). What retires is + **device enumeration**: the ECAM function walk (M19.3) and the DSDT/SSDT + namespace walk that builds device nodes (M20.3). The AML module stays a + shared build module compiled into both the kernel (for `\_S5`) and the acpi + service (for everything else) — same source, two builds, no fork. +2. **Bridge apertures come from the firmware memory map, not AML.** Registered + PCI functions carry BAR resources, and containment demands the bridge own + windows that cover them. The apertures are derived kernel-side from the + boot memory map's MMIO holes (regions that are neither RAM nor tables) — + mechanical, AML-free, and available at boot regardless of what later moved + to user space. (The bridge today carries only ECAM + bus range; this is the + prerequisite M19.0 exists for.) +3. **`device_register` becomes idempotent on exact match.** A re-registration + with identical (parent, class, resources) returns the existing id instead + of appending. The kernel table has no unregister, so without this a + restarted registering bus would duplicate its children on every respawn — + idempotence makes restart-and-re-report safe for every future bus, not just + PCI. +4. **The manager matches from reports.** `ChildAdded` gains a `device_id` + field (the kernel-registered id, `no_device` for unregistered leaves like + USB ports). After the M19.3 flip, PCI driver matching keys off reported + identity (the class triple) instead of the manager's boot-time snapshot — + the snapshot match remains only for what the kernel still seeds. One flip + phase changes both sides at once so no device is ever matched twice. +5. **The acpi service's authority is one node.** The kernel publishes an + `acpi-tables` device: memory resources covering the table blobs plus a + broad `io_port` resource — the documented trust grant to exactly one + process (AML OperationRegions reach EC/PM ports; the claim-gated + io_read/io_write calls already exist). The service claims it, maps the + tables, and runs the shared AML module in ring 3 behind a `Hal` backed by + `mmio_map` + `io_read`/`io_write`. +6. **Both new processes are protocol drivers** under the manager: hello, + supervision, restart with backoff — all inherited from M18.1 for free. + Registration idempotence (decision 3) is what makes their restarts sound. + +## Status + +- [ ] **M19.0** — prerequisites on `feat/pci-bus`: bridge MMIO apertures from + the memory-map holes; `device_register` idempotence (+ kernel unit + checks); `ChildAdded.device_id`; archive note on m17-m18-plan.md. +- [ ] **M19.1** — pci-bus driver, scan only: claim the host bridge, map the + ECAM window, walk bus/device/function headers, log what it finds. + Scenario `pci-scan`: the kernel test compares the driver's reported count + against the broker table's `pci_device` count — equivalence, per class. +- [ ] **M19.2** — register + report: each function registered under the bridge + (config-space slice + BARs, `pci_class` in the descriptor), reported with + `child_added { device_id, identity = class triple }`. Manager mirrors; + spawn-from-reports stays **off**. Scenario extends `pci-scan`: + registered ids resolve, no duplicates after a forced driver restart + (idempotence proven end to end). +- [ ] **M19.3** — the flip: kernel `enumeratePci` call removed (bridge node + stays); manager matches PCI drivers from reports. One commit. The + existing xHCI scenarios (`driver-restart`, `usb-report`, `device-list`) + are the assertion — xhci must come up spawned off a pci-bus report, and + the suite must not be able to tell the difference. discovery.md updated. +- [ ] **merge** `feat/pci-bus` → main, push. +- [ ] **M20.1** — acpi service, parse only: kernel publishes `acpi-tables` + (decision 5); the service claims it, maps the blobs, runs the shared AML + module in ring 3, logs the namespace device count and `_HID`s. Scenario + `acpi-parse`: user-space count equals the kernel walk's count. +- [ ] **M20.2** — register + report: namespace devices with `_HID` + `_CRS` + resources registered under `acpi-tables` (its io_port + the memory-map + holes give containment), reported to the manager. Spawn-from-reports for + ACPI matches stays off. Scenario: the reported set includes the PS/2 + keyboard and mouse nodes with their IRQ resources. +- [ ] **M20.3** — the flip: kernel DSDT device-node building removed (static + tables + `\_S5` stay, decision 1); manager matches ACPI-hid drivers + (ps2-bus) from reports. The `input` and `device-manager` scenarios are + the assertion. discovery.md + acpi.md + device-manager.md updated; + device-manager.md increment 8 closed. +- [ ] **merge** `feat/acpi-service` → main, push — **loop ends here**. + +--- + +## Phase notes + +**M19.0 apertures:** the boot memory map already crosses the handoff +([boot-handoff]); the holes computation belongs where the bridge node is built +(`parseMcfg`). Sanity-check on QEMU q35: the xHCI BAR (`0xc0000000`-region +values seen in the M18 logs) must land inside a derived aperture, asserted in +the kernel unit test. + +**M19.1 scanning without owning config access twice:** the driver reads config +space through its ECAM mmio_map grant of the *bridge* window — the same bytes +the kernel walk read. Vendor-id `0xFFFF` skip, header-type multifunction rule, +no bridge recursion (matches the kernel's current single-segment walk). + +**M19.2 BAR sizing:** the classic size probe (write all-ones, read mask, +restore) is deferred — the BARs' current programmed values and types are +enough for containment-checked registration at bring-up; sizing lands with the +first driver that needs to *move* a BAR. Log what is registered so the +scenario can assert it. + +**M19.3 what the manager still seeds from the snapshot:** everything the +kernel still enumerates (timers, ACPI nodes until M20.3). The PCI arm of +`pciDriverFor` switches source; `driverFor` doesn't move until M20.3. + +**M20.1 Hal in ring 3:** `mapMmio` → `device.mmioMap` over the claimed +acpi-tables node (plus a table-offset map for blobs); `pioRead`/`pioWrite` → +`device.ioRead`/`ioWrite` against its io_port resource. The interpreter cannot +tell it moved — that is the assertion of `acpi-parse`. + +**M20.2 containment for `_CRS`:** io ports fall inside the node's broad +io_port resource; MMIO windows (HPET, LAPIC ranges some firmwares list) fall +inside the memory-map holes added to the node in M20.1. Anything that doesn't +fit is logged and skipped, loudly — bring-up honesty over silent drops. + +**M20.3 ps2 ordering:** ps2-bus binds nodes the acpi service now reports, so +its spawn moves behind the report (the manager's matching handles this once +the source flips); the `input` scenario proves the keyboard still types. + +**Explicitly out of scope:** PCI bridge recursion (single segment, flat bus +walk stays); BAR reprogramming/sizing; hotplug (PCIe native or ACPI); +interrupt routing changes (`_PRT` stays wherever it is today); the USB +descriptor track; multi-segment ECAM.