111 lines
5.5 KiB
Markdown
111 lines
5.5 KiB
Markdown
# /etc/devices.csv — the device registry
|
|
|
|
**Status: built (2026-07-26).** The device manager reads `/etc/devices.csv` at
|
|
boot and binds every device a bus driver reports to the driver the registry
|
|
names. It replaces the three hand-written `switch` tables that used to live in
|
|
the manager (`pciDriverForIdentity`, `hidDriverFor`, `usbDriverForIdentity`) —
|
|
the "manifest" [device-manager.md](device-manager.md) anticipated once code
|
|
matching started to hurt. The parser and matcher are the pure, unit-tested
|
|
`device-registry` module (`library/device/registry/device-registry.zig`).
|
|
|
|
## Why a registry
|
|
|
|
The switch tables keyed PCI matches on the 24-bit class/subclass/prog-IF triple
|
|
alone. That is too coarse: a virtio-gpu is just "display / other" by class, so it
|
|
could only be *class-matched* and the driver had to re-confirm its real
|
|
`1AF4:1050` identity from config space **after** the manager had already spawned
|
|
it. The registry lets a rule bind on the full identity — down to vendor, device,
|
|
and subsystem — so the manager makes the precise decision itself, and the driver
|
|
comes up already knowing it is the right one.
|
|
|
|
It is also **data, not code**: teaching the system new hardware is a line in a
|
|
file, not an edit-and-recompile of the manager. And it is **greppable** — one
|
|
place to read "what binds what," the same idea as Linux's `modules.alias`.
|
|
|
|
## The file
|
|
|
|
One rule per line, nine comma-separated fields; `#` starts a comment (whole-line
|
|
or trailing); blank lines are ignored. Whitespace around a field is trimmed, so
|
|
columns may be padded for readability.
|
|
|
|
```
|
|
# bus base class prog_if vendor device subsystem hid driver
|
|
pci, 0C, 03, 30, *, *, *, *, /system/drivers/usb-xhci-bus
|
|
pci, 03, 00, 00, *, *, *, *, /system/drivers/display
|
|
pci, 03, 80, *, 1AF4, 1050, *, *, /system/drivers/virtio-gpu
|
|
usb, 03, 01, 01, *, *, *, *, /system/drivers/usb-hid-keyboard
|
|
acpi, *, *, *, *, *, *, PNP0303, /system/drivers/ps2-bus
|
|
```
|
|
|
|
| Field | Meaning | Notes |
|
|
|---|---|---|
|
|
| `bus` | `pci` \| `usb` \| `acpi` | which bus reported the device; picks the namespace for the id columns |
|
|
| `base` | PCI base class / USB class | hex |
|
|
| `class` | PCI subclass / USB subclass | hex |
|
|
| `prog_if` | PCI prog-IF / USB protocol | hex |
|
|
| `vendor` | PCI vendor / USB idVendor | hex |
|
|
| `device` | PCI device / USB idProduct | hex |
|
|
| `subsystem` | PCI subsystem, `(ssvid<<16)\|ssid` | hex; blank for usb/acpi |
|
|
| `hid` | ACPI `_HID` (e.g. `PNP0303`) | blank for pci/usb |
|
|
| `driver` | full ramdisk path to spawn | e.g. `/system/drivers/virtio-gpu` |
|
|
|
|
`*` or an empty field is a **wildcard** — it matches anything and adds nothing to
|
|
a rule's specificity.
|
|
|
|
## Levels of detection: most-specific-wins
|
|
|
|
Several rows may match one device. The manager picks the **most specific** — the
|
|
one that pins the finest-grained fields. Specificity weights double from the
|
|
coarsest level so each outweighs all coarser levels combined:
|
|
|
|
```
|
|
base(1) < class(2) < prog_if(4) < vendor(8) < subsystem(16) < device(32) ≈ hid(32)
|
|
```
|
|
|
|
So the generic `pci, 03, 00, 00, …/display` rule and the precise
|
|
`pci, 03, 80, *, 1AF4, 1050, …/virtio-gpu` rule coexist: the virtio card
|
|
(vendor 1AF4, device 1050) takes the specific rule; a plain VGA adapter still
|
|
falls to the generic one. Two rules that match a device with the *same*
|
|
specificity are a registry authoring error — the manager logs it loudly and binds
|
|
the first, so the shadowed rule is visible rather than silently dropped.
|
|
|
|
## Authoritative — no code fallback
|
|
|
|
There is no compiled-in default table behind the registry. A device that no row
|
|
matches goes **unbound** and is logged; the manager never guesses. A missing or
|
|
empty `/etc/devices.csv` therefore means nothing matches — which is loud at boot,
|
|
not a silent half-working system.
|
|
|
|
## How the manager reads it
|
|
|
|
`/etc/devices.csv` is bundled into the initial ramdisk (`build.zig`'s `bundled`
|
|
list). The kernel serves the initrd's `/etc` tree directly — the `fat` service is
|
|
spawned *after* the device manager and is irrelevant to `/etc` — so the manager
|
|
reads the file with a plain `fs.open("/etc/devices.csv")` + `read`, with no
|
|
filesystem service running and no boot-ordering dependency. It parses the bytes
|
|
once in `initialise`, before any bus driver can report a device to match.
|
|
|
|
## Feeding the matcher: the widened report
|
|
|
|
Finer-grained matching needs identity the old ABI threw away. Two things carry it
|
|
now: `child_added` (and `DeviceDescriptor`) grew `vendor` / `device` /
|
|
`subsystem` fields, filled by the PCI bus driver from config space (offsets
|
|
0x00 and 0x2C); and each bus driver states its `bus` in the report (a `BusKind`),
|
|
so the manager reads a PCI class triple and a USB class triple — the same 24 bits
|
|
in different namespaces — against the right `bus` column.
|
|
|
|
## Adding a driver
|
|
|
|
(The step-by-step walkthrough with a worked example is
|
|
[new-driver-checklist.md](new-driver-checklist.md).)
|
|
|
|
1. Create `system/drivers/<name>/` with the driver source plus a ~15-line
|
|
package `build.zig` + `build.zig.zon` (copy an existing driver package,
|
|
e.g. `system/drivers/pci-bus/`; per-driver extras go through
|
|
`build_support.programModule`). Then bundle it at `/system/drivers/<name>`:
|
|
one dependency + one bundled entry in the root `build.zig`, one line in the
|
|
root `build.zig.zon`.
|
|
2. Add a row to `etc/devices.csv` naming the identity it binds and its full path.
|
|
|
|
No device-manager change is required — the registry is the seam.
|