Files
danos/docs/m19-m20-plan.md
T

8.4 KiB
Raw Blame History

M19–M20 execution plan: discovery migration

The operational plan for 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: 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 _HIDs. 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.