device-manager: data-driven driver matching via /etc/devices.csv

Replace the three hardcoded switch tables (pciDriverForIdentity, hidDriverFor,
usbDriverForIdentity) with an authoritative, human-readable device registry the
manager reads at boot. Matching is most-specific-wins across
base/subclass/prog_if/vendor/device/subsystem/hid, so a precise vendor:device
rule and a generic class rule coexist; an unmatched device is logged, never
guessed. This resolves docs' "matching stays code until the third bus".

- ABI: child_added and DeviceDescriptor gain vendor/device/subsystem; child_added
  gains a bus discriminator (BusKind) so PCI and USB class triples match against
  the right namespace.
- pci-bus reads vendor/device (config 0x00) and subsystem (0x2C, type-0) and
  reports them.
- library/device/registry: freestanding CSV parser + matchDriver() with
  specificity scoring; 5 unit tests wired into `zig build test`.
- etc/devices.csv bundled into the initrd; the kernel serves /etc directly, so
  the manager reads it before any filesystem service is up (fat starts later).
- virtio-gpu: drop the now-redundant post-spawn 1AF4:1050 re-confirm, since the
  registry binds this driver by exact identity.
- Remove the orphaned system/drivers/display driver (unreferenced by build or
  registry).
- docs: new devices-csv.md; device-manager.md "matching stays code" resolved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KJqSiLLchDUUCoXn5jsiwd
This commit is contained in:
2026-07-26 16:43:13 +01:00
co-authored by Claude Opus 4.8
parent bf0c3fd3e0
commit 203528c8a7
14 changed files with 645 additions and 245 deletions
-59
View File
@@ -1,59 +0,0 @@
//! /system/drivers/display - the generic display engine driver.
//! This driver is a non official driver for GPU vendors like Intel, NVIDIA, AMD. It provides basic
//! display engine features to the display engine protocol used by the display server, compositor
//! and graphical user interface libraries like Zooeee.
//!
//! This driver is acts like BUS driver, in that it detects the GPU, its capabilities and loads
//! sub-drivers for each device detected. Similar The device manager
//! finds display adaptor e.g. over the PCI/ACPI, and passes the buck on to this driver to handle.
//!
//! The display driver provides the low level part of identifying the device and launching the
//! generic device driver for a GPU vendor.
//!
//! It takes over the framebuffer feature that was setup during system boot.
const std = @import("std");
const device = @import("driver");
const ipc = @import("ipc");
const process = @import("process");
const service = @import("service");
const device_manager = @import("driver");
const logging = @import("logging");
const mmio = @import("mmio");
const display_protocol = @import("display-protocol");
const scanout_protocol = @import("scanout-protocol");
var device_id: u64 = 0;
fn initialise(endpoint: ipc.Handle) bool {
_ = endpoint;
// Hello the device manager (role: device — we claim one GPU's PCI function
// and serve its display engine; we report no children). Best-effort: without a
// manager the driver still runs standalone; when present, the manager marks us
// up before the hello deadline and restarts us if we die.
_ = device_manager.hello(.device, device_id);
return true;
}
fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?ipc.Handle) usize {
_ = sender;
_ = capability;
_ = reply;
if (message.len < scanout_protocol.request_size) return 0;
return 0;
}
pub fn main(init: process.Init) void {
const argument = init.arguments.get(1) orelse {
_ = logging.write("display: missing device id (argv[1])\n");
return;
};
device_id = std.fmt.parseInt(u64, argument, 10) catch {
std.log.info("malformed device id '{s}'", .{argument});
return;
};
service.run(256, .{
.service = .scanout,
.init = initialise,
.on_message = onMessage,
});
}
@@ -1,69 +0,0 @@
//! /system/drivers/display/intel-integrated - the intel 985 family display engine driver.
const std = @import("std");
const device = @import("driver");
const ipc = @import("ipc");
const process = @import("process");
const service = @import("service");
const logging = @import("logging");
const mmio = @import("mmio");
const display_protocol = @import("display-protocol");
const scanout_protocol = @import("scanout-protocol");
const device_manager_protocol = @import("device-manager-protocol");
var device_id: u64 = 0;
fn initialise(endpoint: ipc.Handle) bool {
_ = endpoint;
return true;
}
fn onMessage(message: []const u8, reply: []u8, sender: u32, capability: ?ipc.Handle) usize {
_ = sender;
_ = capability;
_ = reply;
if (message.len < scanout_protocol.request_size) return 0;
const request = std.mem.bytesToValue(scanout_protocol.Request, message[0..scanout_protocol.request_size]);
switch (request.operation) {
_ => return 0,
// TODO:
// @intFromEnum(sp.Operation.present) => return scanoutStatus(reply, presentFull()),
// @intFromEnum(sp.Operation.get_modes) => {
// var response = sp.ModesReply{ .status = 0, .count = offered_modes.len, .modes = undefined };
// for (0..sp.max_modes) |i| {
// response.modes[i] = if (i < offered_modes.len)
// .{ .width = offered_modes[i].width, .height = offered_modes[i].height }
// else
// .{ .width = 0, .height = 0 };
// }
// @memcpy(reply[0..sp.modes_reply_size], std.mem.asBytes(&response));
// return sp.modes_reply_size;
// },
// @intFromEnum(sp.Operation.set_mode) => {
// const w = request.width;
// const h = request.height;
// if (w == 0 or h == 0 or w > max_width or h > max_height) return scanoutStatus(reply, false);
// current_width = w;
// current_height = h;
// return scanoutStatus(reply, setScanoutRect());
// },
else => return 0,
}
return 0;
}
pub fn main(init: process.Init) void {
const argument = init.arguments.get(1) orelse {
_ = logging.write("display/intel-985: missing device id (argv[1])\n");
return;
};
device_id = std.fmt.parseInt(u64, argument, 10) catch {
std.log.info("malformed device id '{s}'", .{argument});
return;
};
service.run(256, .{
.service = .scanout,
.init = initialise,
.on_message = onMessage,
});
}
+15
View File
@@ -153,6 +153,12 @@ fn registerAndReport(bus: u64, dev: u64, function: u64, class_triple: u32) void
var descriptor = std.mem.zeroes(device.DeviceDescriptor);
descriptor.class = @intFromEnum(device.DeviceClass.pci_device);
descriptor.pci_class = class_triple;
// Vendor/device from the first config dword (0x00): low half vendor, high half
// device. These carry to the manager's /etc/devices.csv matcher so a function
// can bind on its exact 1AF4:1050 identity, not just its class triple.
const vendor_device = configRead(bus, dev, function, 0x00);
descriptor.vendor = @truncate(vendor_device);
descriptor.device = @truncate(vendor_device >> 16);
descriptor.resources[0] = .{
.kind = @intFromEnum(device.ResourceKind.memory),
.start = ecam_physical + (((bus - start_bus) << 20) | (dev << 15) | (function << 12)),
@@ -164,6 +170,11 @@ fn registerAndReport(bus: u64, dev: u64, function: u64, class_triple: u32) void
// write all-ones, read the writable mask back, restore. Header type 0 only.
const header_type = (configRead(bus, dev, function, 0x0C) >> 16) & 0x7F;
if (header_type == 0) {
// Subsystem id lives at 0x2C only on type-0 (device) headers, not on
// bridges: dword low half is subsystem-vendor, high half subsystem-device.
// Repack vendor-first so it reads like the CSV's `ssvid:ssid`.
const subsystem_dword = configRead(bus, dev, function, 0x2C);
descriptor.subsystem = (@as(u32, @truncate(subsystem_dword)) << 16) | @as(u32, @truncate(subsystem_dword >> 16));
const command = configRead16(bus, dev, function, 0x04);
configWrite16(bus, dev, function, 0x04, command & ~@as(u16, 0b11));
var i: u64 = 0;
@@ -215,10 +226,14 @@ fn registerAndReport(bus: u64, dev: u64, function: u64, class_triple: u32) void
return;
};
const report = device_manager_protocol.ChildAdded{
.bus = @intFromEnum(device_manager_protocol.BusKind.pci),
.parent = bridge_id,
.bus_address = (bus << 8) | (dev << 3) | function,
.identity = class_triple,
.device_id = registered,
.vendor = descriptor.vendor,
.device = descriptor.device,
.subsystem = descriptor.subsystem,
};
var reply: [device_manager_protocol.message_maximum]u8 = undefined;
_ = ipc.call(manager_handle, std.mem.asBytes(&report), &reply) catch {
@@ -379,6 +379,7 @@ fn reportInterface(manager: ipc.Handle, port: u32, interface: library.InterfaceI
};
const report = device_manager_protocol.ChildAdded{
.bus = @intFromEnum(device_manager_protocol.BusKind.usb),
.parent = controller_id,
.bus_address = (@as(u64, port) << 8) | interface.number,
.identity = identity,
+5 -14
View File
@@ -33,11 +33,6 @@ const vg = @import("virtio-gpu-protocol.zig");
/// the compositor in the announce so it packs colours in the surface's byte order.
const display_format_bgrx: u32 = 1;
/// The PCI vendor/device ids of a modern virtio-gpu (Red Hat / virtio; GPU is a
/// virtio-1.0-only device, so the id is always the modern 0x1050 — no legacy variant).
const virtio_vendor: u16 = 0x1AF4;
const virtio_gpu_device: u16 = 0x1050;
/// The scanout resource + shared surface are sized to the *largest* mode we offer; a mode
/// change (V5) re-points the scanout rectangle within it, so the resource, its backing, and
/// the shared surface never churn — and the surface's row stride is always `max_width`, which
@@ -210,19 +205,15 @@ fn initialise(endpoint: ipc.Handle) bool {
return false;
};
// Config space is resource 0. Confirm it really is a virtio-gpu, then enable memory-space
// decode + bus mastering (the device DMAs the ring and backing out of RAM); pci-bus only
// preserves whatever the firmware left, and a secondary display is often left disabled.
// Config space is resource 0. The registry (/etc/devices.csv) bound this driver by the
// exact virtio-gpu identity (vendor 0x1AF4 / device 0x1050), so there is no re-confirm to
// do here any more — map config space and enable memory-space decode + bus mastering (the
// device DMAs the ring and backing out of RAM; pci-bus only preserves whatever the firmware
// left, and a secondary display is often left disabled).
var function = pci.Function.map(device_id, descriptor) orelse {
std.log.info("config-space map failed", .{});
return false;
};
const vendor = function.vendorId();
const dev = function.deviceId();
if (vendor != virtio_vendor or dev != virtio_gpu_device) {
std.log.info("not a virtio-gpu (vendor 0x{x} device 0x{x})", .{ vendor, dev });
return false;
}
function.enableMemoryAndBusMaster();
// Walk the capability list for the virtio common-config and notify structures (V3 needs