danos/docs/device-driver-development/devices-csv.md

5.5 KiB

/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 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.)

  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.