Plan v3.1 records the completed P0/LDR/P2 work program and the operator-ratified cutover (driver-manager owns the boot path in all redbear-* configs; pcid-spawner gated as fallback). Assessment § 11 documents the OS-services integration: init ConditionPathExists gate, live driver-params bridge, udev-shim driver-binding gap (P3), and the correct absence of D-Bus (bridge-on-demand pattern).
23 KiB
Driver-Manager Migration — Major Assessment (2026-07-22)
Scope: implementation quality, plan viability, correctness, robustness,
AI-introduced code patterns, adjacent technology (pcid, pcid_interface,
redox-driver-pci, linux-kpi, redox-driver-sys, firmware-loader, redox-drm,
redbear-iwlwifi), gaps/blockers/inconsistencies.
Cross-references: Linux 7.1 (local/reference/linux-7.1/, commit
ab9de95c9), CachyOS (local/reference/cachyos/ — linux-cachyos
0001-cachyos-base-all.patch 6.17.9 + desktop ISO 260628).
Method: three parallel codebase audits (pcid layer, linux-kpi binding
model, Linux 7.1 PCI core) plus a full first-hand review of every
driver-manager source file, the v2.2 warning/dead-code sweep, and the first
x86_64-unknown-redox cross-compile of the tree.
1. Executive verdict
The plan is viable. The implementation is compile-grade solid after v2.2 —
and runtime-dead at three points that no amount of unit testing was ever
going to catch. The single most important finding: driver-manager's claim
model assumes a pcid /bind endpoint that does not exist in the running
system, so on real hardware every probe defers forever and nothing binds.
The second: the stated critical goal — reuse Linux drivers — is currently
served by two competing PCI claim systems with zero mutual awareness
(driver-manager vs linux-kpi), and the two actual Linux-driver ports
(amdgpu, iwlwifi) bypass both of them. The third: the pciehp and AER
listeners poll files no producer creates.
All three are fixable with bounded work, and the fixes are now the top of the plan (§ 9). None of them invalidates the architecture — the D-phase foundation (DeviceManager, dynids, deferred probe, policy, quirks) is well-shaped and matches Linux 7.1 semantics where it matters.
2. Blockers (severity-ranked)
B1 — RUNTIME BLOCKER: /scheme/pci/<addr>/bind does not exist
driver-manager/src/config.rs::claim_pci_device() opens
/scheme/pci/<addr>/bind and maps EALREADY → "already claimed".
The pcid fork (local/sources/base/drivers/pcid/src/scheme.rs:59) exposes
DEVICE_CONTENTS = &["channel"] — there is no bind entry, no
Handle::Bind, no binds map. The three patches that add it
(local/patches/base/P3-pcid-bind-scheme.patch,
P3-pcid-aer-scheme.patch, P3-pcid-uevent-format-fix.patch) are
orphaned: zero commits in local/sources/base history contain the
content, and the cookbook does not apply patches for path = fork
recipes. This also violates orphan-patch governance (AGENTS.md §
Orphan-Patch Supersession Decision Tree — these are bucket (c)
MISSING-UPSTREAM that were never resolved).
Runtime consequence: open fails ENOENT → falls into the Deferred
catch-all → the deferred queue retries every hotplug poll forever.
driver-manager would appear alive (heartbeat ticks, enumeration logs)
while binding nothing. Worse than a crash.
Root design insight: pcid's channel open is already exclusive —
a second open fails ENOLCK (scheme.rs:396-398). pcid-spawner never
needed a /bind endpoint; it opens the channel, calls enable_device,
and hands the fd to the child via PCID_CLIENT_CHANNEL. The v2.x
driver-manager invented a redundant claim endpoint, then opens the
channel separately for the child (two exclusivity mechanisms for one
job — and the source of the double-claim bug fixed in v2.2).
Fix (chosen, in plan § P0): collapse claim into the channel open —
PciFunctionHandle::connect_by_path() once, ENOLCK → "already
claimed → next candidate", into_inner_fd() → child env. Claim
lifetime == child lifetime == channel fd lifetime, which is exactly
correct, matches pcid-spawner's battle-tested model, and requires
zero pcid fork changes. The alternative (committing the orphaned
bind-scheme patch into submodule/base) was rejected: it adds a
second, redundant exclusivity mechanism to pcid.
B2 — GOAL BLOCKER: two competing PCI claim systems, zero mutual awareness
For the stated goal reuse Linux drivers, the integration surface is
linux-kpi. Today (linux-kpi/src/rust_impl/pci.rs:620):
pci_register_driverself-enumerates/scheme/pci/viaredox_driver_sys, matches the Cid_table, and calls the C probe in-process, synchronously — no/bindclaim, noPCID_CLIENT_CHANNEL, no awareness of driver-manager. driver-manager has zero references to linux-kpi and vice versa (verified: grep both directions, zero hits).- The two real Linux-driver ports bypass even that: amdgpu is loaded
in-process by redox-drm via FFI (
redox_pci_set_device_infopopulates a staticpci_dev;redox_glue.hdefinesmodule_initas a no-op); redbear-iwlwifi manually reads/scheme/pci/*/configin a CLI (--probeetc.) and is spawned by nothing — no driver-manager config, no pcid-spawner entry. - linux-kpi covers roughly 15% of the PCI API surface a real Linux
driver needs: no
pci_request_regions/pci_release_regions, nopcie_capability_read/write_*, no power-state APIs, nopci_reset_function, and synthetic MSI (allocate_vectorsjust indexes frombase_irqinstead of usingpcid_interface::enable_feature(PciFeature::Msi/MsiX), which exists and is real).
Fix (plan § LDR — Linux-Driver-Reuse track): one ownership model.
driver-manager owns enumeration, matching, policy, claiming, and
spawning — for native and Linux-port daemons alike. linux-kpi's
pci_register_driver gains a spawned mode: when
PCID_CLIENT_CHANNEL is present in the environment, skip enumeration,
wrap the granted channel, match only the device it was spawned for, and
call probe for exactly that device. The standalone self-enumeration mode
remains for CLI tools. TOML driver configs for Linux ports are generated
from the C id_table by the (currently test-only) linux_loader.
B3 — SILENT DEAD FEATURES: no pciehp or AER producers
unified_events.rs polls /scheme/pci/pciehp and /scheme/acpi/aer
every 500 ms. Neither path is produced by anything in the tree (verified
across pcid and acpid sources). Both readers fail-soft on !path.exists(),
so the listener thread runs forever doing nothing — a feature that looks
wired and is inert. The orphaned P3-pcid-aer-scheme.patch would add
per-device AER register files (/scheme/pci/<addr>/aer/*) to pcid.
pciehp has no producer patch at all.
Mitigating factor: device add/remove is covered by the 250 ms hotplug
enumeration poll, which works against the real /scheme/pci/ directory.
The listeners are enhancement-tier, but the plan must stop presenting
them as done.
3. Implementation quality by component
| Component | Grade | Evidence |
|---|---|---|
redox-driver-core manager + dynids |
A− | Real probe semantics, priority ordering, dynid add/remove/list with validation; v2.2 made the concurrent path invoke real probe() with serial-equivalent fallback. Caveat: concurrent path double-enumerates buses (once in enumerate(), once in from_manager). |
driver-manager probe/spawn (config.rs) |
B+ (was D) | v2.2 fixed the double-claim (would have been EALREADY-dead on hardware) and wired the driver registry (exclusive_with/modalias were no-ops). Claim model itself is B1 — redesign in plan. |
Scheme server (scheme.rs) |
B | Correct trait impl after v2.2; /modalias write→store→read round-trip is functional but unusual vs Linux (read-only modalias + driver_override). Missing operator surface (§ G4). |
Policy/blacklist (policy.rs) |
A− | Real TOML loading, live SharedBlacklist::replace(), and — after v2.2 — a actually installed SIGHUP handler. |
| Signal handling (reaper/sighup) | B+ (was F) | v2.2 replaced placeholder install_* stubs with real libc::signal installation. Before that, the reaper and reload never fired in production. |
Hotplug (hotplug.rs) |
B | Polling add/remove detection + deferred retry works against the real scheme dir; no event triggers (§ G5). |
| unified_events / aer / pciehp | D | Parsers are real; producers don't exist (B3). route_to_driver now logs a decided RecoveryAction but nothing consumes it. |
| Heartbeat | B+ | Counters wired into enumerate/hotplug in v2.2; publishes real numbers now. |
modern_tech.rs |
D | Advisory theater — see § 5. |
linux_loader.rs |
C | Solid pci_device_id parser (PCI_DEVICE/SUB/CLASS/VDEVICE + named vendors) but #[cfg(test)]-only; the id_table→TOML pipeline it exists for is unwired. |
exec.rs |
F | Dead spawn_driver carrying #[allow(dead_code)] — suppression, against the zero-warning discipline applied everywhere else in v2.2. Remove. |
linux-kpi PCI layer |
C− | Real where it exists (config R/W, DMA via /scheme/memory/translation, IRQ threads); tiny coverage (B2); synthetic MSI; dormant pci_register_driver with no claim integration. |
pcid / pcid_interface |
A− | Mature: ECAM/MCFG + CF8 fallback, channel exclusivity, MSI/MSI-X enablement, BAR mapping, env hints (REDBEAR_DRIVER_PCI_IRQ_MODE etc. consumed end-to-end). The strongest layer in the stack. |
4. Plan viability
The D-phase/C-phase structure is sound and the § 0.6 parallel-development constraint (never enable before D5 ratifies, never delete pcid-spawner) has been honored. What the plan got wrong:
- It marked capabilities "✅ Done" at compile grade that are broken at
runtime — the
<addr>/bindclaim (plan lines 94/134/771/921/975), the pciehp listener, and the AER listener are the three cases. The D5 audit needs an explicit runtime bar per capability (see § 9, P0-4). - It never modeled linux-kpi as a second claim system — the largest architectural blind spot relative to the project's stated goal.
- It presented
exclusive_withas "the CachyOS pattern." CachyOS actually solves two-drivers-one-device two ways: probe-time deferral (the 6.17.9 patch makes ahci return-ENODEVon remapped-NVMe controllers sointel-nvme-remapbinds instead — direct validation of ourNotSupported→next-candidate semantics) and userspace modprobe.d blacklists. Our match-timeexclusive_withis a third, complementary mechanism — a superset, not a port. The plan now says so. - "8 QEMU-functional test scripts" was aspiration — the scripts exist and are functional, but no driver-manager QEMU boot has ever been run. Runtime validation is now a hard gate (P0-4).
5. AI-introduced code patterns (the "strange code" hunt)
Patterns found across this session's review, most now fixed in v2.2:
- Fake concurrency (fixed v2.2).
ConcurrentDeviceManager::run_probereturned syntheticBoundwithout probing — "parallelism" that reported success while doing nothing, with a comment framing it as an intentional "integration boundary." - Placeholder installers (fixed v2.2).
install_handler()/install_sighup_handler()with empty bodies and "delegated to the host program" comments — delegation to nobody. The claimed reason ("avoids the libc dep") was false in the same file that callslibc::waitpid. - Advisory theater (open — plan P0-3).
modern_tech.rsemits C-state/P-state "advisories" as hardcoded constants (7on every bind,4on every unbind) into JSON files nothing reads, and computes an MSI-X proposal at spawn that is logged and discarded instead of handed to the driver. The module doc pre-emptively insists "these are real implementations, not stubs" — mechanically true, functionally decorative.modern_tech_for_path(key)ignores its argument and returns a global. - Suppressed dead code (open — plan P0-3).
exec.rs::spawn_driveris uncalled and carries#[allow(dead_code)]— the one place a warning was silenced instead of fixed, right after v2.2 removed every other instance. - Unwired registries (fixed v2.2).
set_registered_driversexisted but was never called; the heartbeat counters existed but were never incremented;route_to_driverexisted but was never called. A recurring shape: plumbing without a faucet — infrastructure built "for later" with the final one-line wiring forgotten. - Redundant invention (open — plan P0-1). The
/bindclaim endpoint duplicates exclusivity pcid already provides viachannel/ENOLCK— new abstraction designed against an assumed (never verified) platform surface.
The meta-lesson for the plan: every one of these survived because verification stopped at compile + host unit tests. The audit gate (§ 0.5) catches stub-shaped text, not behavior-shaped fiction. P0-4 adds the missing gate.
6. Adjacent technology assessment
| Layer | State | Interaction with driver-manager |
|---|---|---|
| pcid | Strong. ECAM+CF8, channel exclusivity (ENOLCK), per-device config + channel. |
Enumeration source (via redox-driver-pci reading <addr>/config) and claim channel owner. Missing: /bind (unneeded after P0-1), AER/pciehp producers (P2). |
| pcid_interface | Strong. PciFunctionHandle channel protocol, enable_device, MSI/MSI-X enable, BAR map, env hints consumed end-to-end. |
The correct claim+channel vehicle (P0-1 builds on it). |
| redox-driver-pci | Adequate. Directory+config-file enumeration feeding DeviceInfo. |
driver-manager's bus. No hotplug push — polling only. |
| redox-driver-sys | Strong. Quirks tables (compiled + TOML + DMI), MMIO/IRQ wrappers. | Quirk flags flow into spawn env vars end-to-end. Two pre-existing warnings (unused Once, unused vendor). |
| linux-kpi | Partial (B2). Real DMA/IRQ/config; ~15% PCI API; synthetic MSI; dormant registration path. | None today. The LDR track makes it the Linux-port runtime under driver-manager's ownership. |
| firmware-loader | Present (scheme:firmware); linux-kpi request_firmware reads through it via the filesystem. |
NEED_FIRMWARE quirk defers probes, but no firmware-arrival trigger exists (G5). |
| redox-drm + amdgpu | Works via in-process FFI; bypasses both claim systems. | Spawned as a driver-manager child (class 0x03). LDR-4 migrates amdgpu to the spawned-mode path. |
| redbear-iwlwifi | CLI tool, manual PCI scan, spawned by nothing. | LDR-3 gives it a driver-manager config and proper claim. |
| pcid-spawner | The incumbent. Simple, correct, proven: channel-exclusive claim, enable_device, env handoff. |
The behavioral reference for P0-1; stays in-tree as the dormant fallback after cutover. |
7. Linux 7.1 cross-reference (divergence audit)
| Mechanism | Linux 7.1 (local/reference/linux-7.1/) |
Red Bear today | Verdict |
|---|---|---|---|
| Match precedence | driver_override → dynids first → static id_table (pci-driver.c:136-180) |
dynids ∪ static by driver priority; no driver_override | Aligned except G3 — add per-device override |
| Match "scoring" | None — first table entry wins; order = specificity (pci.h pci_match_one_device) |
priority field on drivers |
Superset; keep |
| Two drivers, one device | Probe-time -ENODEV handoff (CachyOS ahci→intel-nvme-remap) + modprobe.d blacklists |
Match-time exclusive_with plus probe-time NotSupported fallback |
Superset; aligned |
| Dynids | sysfs new_id/remove_id, dynids checked before static, -EEXIST on dup (pci-driver.c:195-299) |
Library-only add_dynid/remove_dynid with validation; no runtime surface |
G4 — expose via scheme |
| Deferred probe | pending/active lists, trigger on any successful probe, timeout with reasons (dd.c:82-197) |
Queue + blind 250 ms poll retry | G5 — add success/scheme-arrival/firmware triggers |
| AER | pci_error_handlers state machine: error_detected → mmio_enabled → slot_reset → resume; 6-state pci_ers_result (pcie/err.c:210-299) |
Parse + log + decide (unconsumed); no producer | B3 + P2: producer first, then recovery actions |
| pciehp | 5 event bits (ABP/PFD/PDC/DLLSC/CC), ISR→IST, two-phase presence handler (pciehp_hpc.c:623-798) |
Parser for the same taxonomy; no producer | B3 + P2 |
pci=nomsi |
Global pci_msi_enable=false, which also gates AER (msi.c:985, aer.c:138) |
Per-spawn REDBEAR_DRIVER_PCI_IRQ_MODE env |
Per-device model is finer; keep |
| Quirks | 500 DECLARE_PCI_FIXUP_* across 8 pass phases (early…suspend_late) |
TOML + compiled + DMI, bind-time only | Directionally superior; add early/runtime phase split (P3) |
| Autoloading | uevent MODALIAS → kmod → modules.alias (pci-driver.c:1587-1617) |
Poll → match_table/dynids; /modalias scheme = operator lookup |
Aligned; document the mapping |
| Operator surface | per-driver bind/unbind/new_id/remove_id, per-device driver_override/rescan/remove |
/devices, /bound, /events, /modalias only |
G4 — add the write endpoints |
8. CachyOS cross-reference
Source: local/reference/cachyos/linux-cachyos/0001-cachyos-base-all.patch
(6.17.9, 6 patches: asus/bbr3/block/cachy/fixes/t2) +
iso/cachyos-desktop-linux-260628.pkgs.txt.
- CachyOS does not redesign driver loading. Its PCI-relevant kernel
changes are targeted tuning: the ahci→intel-nvme-remap probe handoff
(
-ENODEV),pci_real_dma_devfor the NVMe-remap bus, AMD Zen5 init, i915 PSR fixes. This validates our approach: mirror Linux 7.1 semantics, deviate only where the microkernel forces it. - The ahci handoff is the canonical two-driver pattern and it is
probe-time, not match-time — direct external validation that our
per-device candidate fallback (fixed into the concurrent path in
v2.2) models reality.
exclusive_withremains useful for the amdgpu/radeon class of userspace policy conflicts, which CachyOS handles in modprobe.d — our/etc/driver-manager.dblacklist is the same layer. - Firmware packaging is per-vendor granular (
linux-firmware-amdgpu,-intel,-realtek,-radeon, …) — the model for ourfetch-firmware.shsubsets and future firmware-loader packaging (P3). - Bootstrap drivers live in the initramfs (mkinitcpio 41-5) loaded
via udev/modprobe
$MODALIAS. Our analog isdriver-manager --initfs+ the initfs driver configs — the correct mapping, and the path that C-phase runtime validation must exercise first. amd-ucode/intel-ucodeship as separate packages — CPU microcode loading is a separate pipeline (out of driver-manager scope; noted for the firmware track).- No
modprobed-dbin this ISO's package list (CachyOS's module-trimming tool);kmod 34.2present.
9. Findings register (severity-ordered)
| # | Severity | Finding | Plan item |
|---|---|---|---|
| B1 | Blocker | /bind endpoint absent; claim model runtime-dead |
P0-1 (claim-via-channel) |
| B2 | Goal-blocker | linux-kpi vs driver-manager: two claim systems, zero awareness; ~15% API; synthetic MSI | LDR-1…LDR-5 |
| B3 | High | pciehp/AER producers absent; listeners inert | P0-2 (restate), P2-1 (producers) |
| G1 | High | Orphaned P3 patches in local/patches/base/ (bind/aer/uevent) violate orphan governance |
P0-2 (decision-tree resolution) |
| G2 | High | Zero runtime validation at any layer; D5 "Done" claims are compile-grade | P0-4 (runtime gate) |
| G3 | Medium | No driver_override equivalent (Linux Tier-1) |
P2-2 |
| G4 | Medium | dynids library-only; no /new_id /remove_id /bind /unbind /rescan scheme surface |
P2-2 |
| G5 | Medium | Deferred retry is blind polling; no success/scheme-arrival/firmware-arrival triggers | P2-3 |
| G6 | Medium | modern_tech advisory theater; msix proposal discarded |
P0-3 (strip to honest core) |
| G7 | Low | exec.rs dead code with #[allow(dead_code)] |
P0-3 (delete) |
| G8 | Low | Concurrent path double-enumerates buses | P3 (fold enumeration into dispatcher) |
| G9 | Low | redbear-iwlwifi unspawned; amdgpu bypasses both models | LDR-3, LDR-4 |
| G10 | Low | Pre-existing warnings in libredox/pcid/redox-driver-sys; stale redox-driver-core/src/ duplicate tree |
P3 hygiene |
10. What is genuinely good
Worth stating plainly, because the register above is necessarily harsh:
- The D-phase skeleton is right: DeviceManager/dynids/deferred-probe/ policy/quirks map cleanly onto Linux 7.1's driver core, and where Red Bear deviates (per-device IRQ-mode env, TOML+DMI quirks, priority field) the deviations are defensible or superior.
- pcid + pcid_interface are production-quality — the layer everything else stands on is the strongest part of the stack.
- The v2.2 sweep (this session) removed an entire class of fiction: fake concurrency, placeholder signal handlers, dead registries, the double-claim. The tree after v2.2 is honest at compile grade.
- The § 0.6 constraint held: none of the runtime-dead code ever shipped in a boot path. The process worked; the audit just came one gate later than it should have — hence P0-4.
Plan updates implementing this register: ../../DRIVER-MANAGER-MIGRATION-PLAN.md v3.0.
11. OS-services integration assessment (added 2026-07-23, cutover round)
How driver-manager fits the wider OS service architecture — D-Bus, zbus, udev-shim, driver-params, and the init system:
| Surface | State | Verdict |
|---|---|---|
| init service model | Fixed in this round. init learned ConditionPathExists (with ! negation) — the C0 dormancy was previously a parser-rejection accident. Cutover wiring: driver-manager services run by default; pcid-spawner services are gated behind /etc/driver-manager.d/disabled as the operator fallback. |
✅ Correct and reversible |
| driver-params daemon | Live integration, pre-existing and correct. driver-params reads /scheme/driver-manager/bound (read_driver_manager_bound, tolerating ENODEV when the scheme is absent) and exposes enabled=true params per bound driver; driver-manager's notify_bind writes per-device params the other direction. |
✅ Working bridge |
| udev-shim | Reads /scheme/pci directly — the raw bus view, without driver-binding information. On Linux, libudev exposes DRIVER= and driver links; post-cutover udev-shim should also read /scheme/driver-manager/{bound,devices} so udisks/KDE-Solid-style consumers see bindings. |
🟡 Gap (P3, non-blocking) |
| D-Bus / zbus | driver-manager has no D-Bus presence, and that is correct today. The Linux analog: udev events feed domain daemons (udisks2, upower, NetworkManager) which own the D-Bus APIs — udev itself does not publish on the bus. Red Bear's scheme is the native device-event transport; a driver-manager D-Bus service now would duplicate the scheme for zero consumers. If a desktop consumer ever needs bind signals over D-Bus, the pattern is a thin zbus bridge daemon (same shape as redbear-sessiond for login1), not D-Bus code inside driver-manager. |
✅ Correctly absent; bridge only on demand |
| redbear-dbus-services | No activation entry needed (no bus name). | ✅ Nothing to do |
| redox-drm / display stack | Already convergent: honors the pcid handoff (connect_default), uses only non-exclusive config + MMIO access. |
✅ Verified |
| initfs storage path | 40_driver-manager-initfs.service (oneshot, --initfs) is default-on; pcid-spawner-initfs gated as fallback. The initfs driver-manager exits after enumeration (oneshot) — correct for the pre-switchroot phase. |
✅ Wired; first QEMU gate target |
Cutover commits: e30f2499 (init gate), c72d4247 (service gates), b338760111 + parent bumps (config cutover).