Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
203528c8a7 |
@@ -284,6 +284,13 @@ pub fn build(b: *std.Build) void {
|
|||||||
const pci_class_module = b.addModule("pci-class", .{
|
const pci_class_module = b.addModule("pci-class", .{
|
||||||
.root_source_file = b.path("library/device/pci/pci-class.zig"),
|
.root_source_file = b.path("library/device/pci/pci-class.zig"),
|
||||||
});
|
});
|
||||||
|
// The device registry: parse /etc/devices.csv into match rules and bind a
|
||||||
|
// reported device to a driver — the data-driven, authoritative replacement for
|
||||||
|
// the manager's hand-written switch tables. Pure logic (no hardware, no
|
||||||
|
// syscalls), so it unit-tests with plain `zig test`; the manager imports it.
|
||||||
|
const device_registry_module = b.addModule("device-registry", .{
|
||||||
|
.root_source_file = b.path("library/device/registry/device-registry.zig"),
|
||||||
|
});
|
||||||
// ACPI/PnP hardware-ID (_HID) names — the flat analog of pci-class for acpi_device
|
// ACPI/PnP hardware-ID (_HID) names — the flat analog of pci-class for acpi_device
|
||||||
// nodes. Also shared reference data.
|
// nodes. Also shared reference data.
|
||||||
// The AML interpreter, a build module so the ring-3 acpi service can run the
|
// The AML interpreter, a build module so the ring-3 acpi service can run the
|
||||||
@@ -729,12 +736,10 @@ pub fn build(b: *std.Build) void {
|
|||||||
if (discovery == .acpi) programModule(discovery_exe).addImport("device-manager-protocol", device_manager_protocol_module);
|
if (discovery == .acpi) programModule(discovery_exe).addImport("device-manager-protocol", device_manager_protocol_module);
|
||||||
if (discovery == .acpi) programModule(discovery_exe).addImport("power-protocol", power_protocol_module);
|
if (discovery == .acpi) programModule(discovery_exe).addImport("power-protocol", power_protocol_module);
|
||||||
const device_manager_exe = addUserBinary(b, kernel_target, &default_imports, "device-manager", "system/services/device-manager/device-manager.zig");
|
const device_manager_exe = addUserBinary(b, kernel_target, &default_imports, "device-manager", "system/services/device-manager/device-manager.zig");
|
||||||
// Names the xHCI PCI class triple from the shared taxonomy instead of a bare 0x0C0330.
|
|
||||||
programModule(device_manager_exe).addImport("pci-class", pci_class_module);
|
|
||||||
programModule(device_manager_exe).addImport("device-manager-protocol", device_manager_protocol_module);
|
programModule(device_manager_exe).addImport("device-manager-protocol", device_manager_protocol_module);
|
||||||
// The manager matches reported USB interfaces by their (class,subclass,protocol)
|
// Driver matching is data-driven: the manager parses /etc/devices.csv into this
|
||||||
// triple (usbDriverForIdentity), built from the named usb-ids codes.
|
// module's rules and binds each reported device by most-specific match.
|
||||||
programModule(device_manager_exe).addImport("usb-ids", usb_ids_module);
|
programModule(device_manager_exe).addImport("device-registry", device_registry_module);
|
||||||
// The input service and its exercisers: the fan-out server, a hardware-free synthetic
|
// The input service and its exercisers: the fan-out server, a hardware-free synthetic
|
||||||
// source, and a subscriber that doubles as the `input` test's oracle. See docs/input.md.
|
// source, and a subscriber that doubles as the `input` test's oracle. See docs/input.md.
|
||||||
const input_exe = addUserBinary(b, kernel_target, &default_imports, "input", "system/services/input/input.zig");
|
const input_exe = addUserBinary(b, kernel_target, &default_imports, "input", "system/services/input/input.zig");
|
||||||
@@ -764,6 +769,11 @@ pub fn build(b: *std.Build) void {
|
|||||||
.{ .path = "system/services/input", .binary = input_exe.getEmittedBin() },
|
.{ .path = "system/services/input", .binary = input_exe.getEmittedBin() },
|
||||||
.{ .path = "system/services/discovery", .binary = discovery_exe.getEmittedBin() },
|
.{ .path = "system/services/discovery", .binary = discovery_exe.getEmittedBin() },
|
||||||
.{ .path = "system/services/logger", .binary = logger_exe.getEmittedBin() },
|
.{ .path = "system/services/logger", .binary = logger_exe.getEmittedBin() },
|
||||||
|
// A data file, not a binary: the device registry the manager reads at boot.
|
||||||
|
// Packing it under /etc makes the kernel auto-mount /etc as a read-only
|
||||||
|
// initrd tree (system/kernel/vfs.zig setInitialRamdisk), so the manager can
|
||||||
|
// fs.open("/etc/devices.csv") with no filesystem service running.
|
||||||
|
.{ .path = "etc/devices.csv", .binary = b.path("etc/devices.csv") },
|
||||||
.{ .path = "system/drivers/ps2-bus", .binary = ps2_bus_exe.getEmittedBin() },
|
.{ .path = "system/drivers/ps2-bus", .binary = ps2_bus_exe.getEmittedBin() },
|
||||||
.{ .path = "system/drivers/ps2-keyboard", .binary = ps2_keyboard_exe.getEmittedBin() },
|
.{ .path = "system/drivers/ps2-keyboard", .binary = ps2_keyboard_exe.getEmittedBin() },
|
||||||
.{ .path = "system/drivers/ps2-mouse", .binary = ps2_mouse_exe.getEmittedBin() },
|
.{ .path = "system/drivers/ps2-mouse", .binary = ps2_mouse_exe.getEmittedBin() },
|
||||||
@@ -1078,6 +1088,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
"library/device/acpi/aml/aml.zig", // AML parse + interpret, incl. Notify dispatch (M21)
|
"library/device/acpi/aml/aml.zig", // AML parse + interpret, incl. Notify dispatch (M21)
|
||||||
"library/device/usb/usb-abi.zig", // wire sizes + bit packings + set-up packet encodings
|
"library/device/usb/usb-abi.zig", // wire sizes + bit packings + set-up packet encodings
|
||||||
"library/device/usb/usb-ids.zig", // class/subclass/protocol code assignments
|
"library/device/usb/usb-ids.zig", // class/subclass/protocol code assignments
|
||||||
|
"library/device/registry/device-registry.zig", // /etc/devices.csv parse + most-specific driver match
|
||||||
"library/device/mmio/mmio.zig", // barriers assemble + registers round-trip
|
"library/device/mmio/mmio.zig", // barriers assemble + registers round-trip
|
||||||
"system/drivers/ps2-bus/scancode.zig", // set-2 decode + keyboard state machine
|
"system/drivers/ps2-bus/scancode.zig", // set-2 decode + keyboard state machine
|
||||||
"system/drivers/ps2-bus/mouse-packet.zig", // 3-byte mouse packet assembly
|
"system/drivers/ps2-bus/mouse-packet.zig", // 3-byte mouse packet assembly
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ mechanism), replacing first-come-first-served `device_claim` with policy. Identi
|
|||||||
`child_added` is per-bus: PCI children carry the class triple (`pci_class`, as the
|
`child_added` is per-bus: PCI children carry the class triple (`pci_class`, as the
|
||||||
xHCI match already uses); USB children carry the (class, subclass, protocol) triple
|
xHCI match already uses); USB children carry the (class, subclass, protocol) triple
|
||||||
from usb-ids.zig — each bus's native language, decoded by the shared ids modules.
|
from usb-ids.zig — each bus's native language, decoded by the shared ids modules.
|
||||||
|
(Since the registry landed, `child_added` also carries a `bus` discriminator and
|
||||||
|
the numeric `vendor`/`device`/`subsystem` ids the finer match levels need —
|
||||||
|
see [/etc/devices.csv](devices-csv.md).)
|
||||||
|
|
||||||
## Supervision and restart
|
## Supervision and restart
|
||||||
|
|
||||||
@@ -169,9 +172,15 @@ published exit events, signals + `process`). On top of those:
|
|||||||
- **Manager death**: drivers survive the manager; the restarted manager re-learns
|
- **Manager death**: drivers survive the manager; the restarted manager re-learns
|
||||||
the world (above). Checkpointing driver state with the manager is deferred until
|
the world (above). Checkpointing driver state with the manager is deferred until
|
||||||
something demonstrates the need.
|
something demonstrates the need.
|
||||||
- **Matching stays code until the third bus.** `driverFor`/`pciDriverFor` were
|
- **Matching is a registry, not code (resolved 2026-07-26).** `driverFor`/
|
||||||
honest at two bus types; the third was expected to trigger the manifest (a driver
|
`pciDriverFor` were honest at two bus types; the third (USB) was matched in code
|
||||||
declares what it binds: a PCI class triple, a USB class triple, an ACPI `_HID`).
|
too, and then the switch tables started to hurt — they keyed PCI matches on the
|
||||||
(Since then: the third bus — USB — arrived and is matched in code too. Today's
|
class triple alone, so a virtio-gpu could only be matched as a generic display
|
||||||
matchers are `pciDriverForIdentity`, `hidDriverFor`, and `usbDriverForIdentity`;
|
function and the driver had to re-confirm its `1AF4:1050` identity from config
|
||||||
the manifest waits until code matching actually hurts.)
|
space after being spawned. The manifest the earlier note anticipated landed as a
|
||||||
|
human-readable registry: **[/etc/devices.csv](devices-csv.md)**, parsed by the
|
||||||
|
pure `device-registry` module and read by the manager at boot. A row binds a
|
||||||
|
driver to a device by any of base / subclass / prog-IF / vendor / device /
|
||||||
|
subsystem / `_HID`, most-specific match winning; it is authoritative (no
|
||||||
|
compiled-in fallback — an unmatched device is logged, never guessed).
|
||||||
|
`pciDriverForIdentity`, `hidDriverFor`, and `usbDriverForIdentity` are gone.
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
# /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
|
||||||
|
|
||||||
|
1. Build the driver binary and bundle it at `/system/drivers/<name>` (build.zig).
|
||||||
|
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.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# /etc/devices.csv — the device→driver registry.
|
||||||
|
#
|
||||||
|
# The device manager reads this at boot and binds each device a bus driver
|
||||||
|
# reports to the driver named here. It is AUTHORITATIVE: a device that no row
|
||||||
|
# matches goes unbound (logged), never guessed. Edit this file to teach the
|
||||||
|
# system new hardware — no recompile of the device manager required.
|
||||||
|
#
|
||||||
|
# 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 which bus reported the device: pci | usb | acpi
|
||||||
|
# base PCI base class / USB class (hex)
|
||||||
|
# class PCI subclass / USB subclass (hex)
|
||||||
|
# prog_if PCI prog-IF / USB protocol (hex)
|
||||||
|
# vendor PCI vendor id / USB idVendor (hex)
|
||||||
|
# device PCI device id / USB idProduct (hex)
|
||||||
|
# subsystem PCI subsystem, packed (ssvid<<16)|ssid (hex)
|
||||||
|
# hid ACPI _HID string (e.g. PNP0303); blank for pci/usb
|
||||||
|
# driver full ramdisk path of the driver to spawn
|
||||||
|
#
|
||||||
|
# '*' or an empty field is a wildcard. When several rows match one device the
|
||||||
|
# MOST SPECIFIC wins (pinning vendor/device/hid beats pinning only a class), so
|
||||||
|
# a generic class rule and a precise vendor:device rule can coexist.
|
||||||
|
#
|
||||||
|
# bus base class prog_if vendor device subsystem hid driver
|
||||||
|
pci, 0C, 03, 30, *, *, *, *, /system/drivers/usb-xhci-bus
|
||||||
|
pci, 03, 80, *, 1AF4, 1050, *, *, /system/drivers/virtio-gpu
|
||||||
|
usb, 03, 01, 01, *, *, *, *, /system/drivers/usb-hid-keyboard
|
||||||
|
usb, 03, 01, 02, *, *, *, *, /system/drivers/usb-hid-mouse
|
||||||
|
usb, 08, 06, 50, *, *, *, *, /system/drivers/usb-storage
|
||||||
|
acpi, *, *, *, *, *, *, PNP0303, /system/drivers/ps2-bus
|
||||||
|
acpi, *, *, *, *, *, *, PNP0F13, /system/drivers/ps2-bus
|
||||||
|
@@ -125,6 +125,15 @@ pub const DeviceDescriptor = extern struct {
|
|||||||
// `pci_device` is (an xHCI controller, an AHCI controller) — decode the triple into
|
// `pci_device` is (an xHCI controller, an AHCI controller) — decode the triple into
|
||||||
// names with the pci-class module.
|
// names with the pci-class module.
|
||||||
pci_class: u64,
|
pci_class: u64,
|
||||||
|
// Numeric identity beyond the class triple, mirrored in the bus report's
|
||||||
|
// ChildAdded so /etc/devices.csv can bind on it: `vendor`/`device` are the PCI
|
||||||
|
// vendor/device (or USB idVendor/idProduct), `subsystem` is the PCI subsystem id
|
||||||
|
// packed `(subsystem_vendor << 16) | subsystem_device`. Zero where the bus has no
|
||||||
|
// such concept. Defaulted so existing descriptor literals keep compiling and lay
|
||||||
|
// out identically until they choose to set them.
|
||||||
|
vendor: u16 = 0,
|
||||||
|
device: u16 = 0,
|
||||||
|
subsystem: u32 = 0,
|
||||||
hid_len: u64,
|
hid_len: u64,
|
||||||
resource_count: u64,
|
resource_count: u64,
|
||||||
hid: [8]u8,
|
hid: [8]u8,
|
||||||
|
|||||||
@@ -0,0 +1,348 @@
|
|||||||
|
//! The device registry: parse `/etc/devices.csv` into match rules and bind a
|
||||||
|
//! reported device to a driver. This is the data-driven replacement for the
|
||||||
|
//! device manager's three hand-written `switch` tables (`pciDriverForIdentity`,
|
||||||
|
//! `hidDriverFor`, `usbDriverForIdentity`); the registry is now **authoritative**
|
||||||
|
//! — a device that no row matches goes unbound (logged), never guessed.
|
||||||
|
//!
|
||||||
|
//! Pure logic: no hardware access, no syscalls, no allocator. `parse` fills a
|
||||||
|
//! caller-provided `[]Rule` whose string fields (`hid`, `driver`) are slices
|
||||||
|
//! *into the CSV source*, so the source buffer must outlive the rules (the
|
||||||
|
//! manager holds it in a static buffer for the life of the process — zero-copy).
|
||||||
|
//! That keeps this module freestanding and unit-testable with plain `zig test`.
|
||||||
|
//!
|
||||||
|
//! The file format (docs/device-driver-development/device-manager.md, and the
|
||||||
|
//! `/etc/devices.csv` header itself): one rule per line, nine comma-separated
|
||||||
|
//! fields, `#` starts a comment (whole-line or trailing), blank lines ignored.
|
||||||
|
//!
|
||||||
|
//! bus, base, class, prog_if, vendor, device, subsystem, hid, driver
|
||||||
|
//!
|
||||||
|
//! `bus` is `pci`/`usb`/`acpi`; the numeric fields are hex (with or without a
|
||||||
|
//! `0x` prefix); `*` or an empty field is a wildcard (matches anything). For PCI
|
||||||
|
//! the class triple is base/subclass/prog-IF; for USB it is class/subclass/
|
||||||
|
//! protocol with vendor/device the idVendor/idProduct; ACPI matches on `hid`
|
||||||
|
//! (e.g. "PNP0303") with the triple left blank. `driver` is a full ramdisk path.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// Which bus a rule or a reported device belongs to. `unknown` is what an
|
||||||
|
/// unrecognised `bus` token parses to — such a rule never matches (its bus
|
||||||
|
/// equals no real device's), so a typo fails safe rather than binding wrongly.
|
||||||
|
pub const Bus = enum {
|
||||||
|
pci,
|
||||||
|
usb,
|
||||||
|
acpi,
|
||||||
|
unknown,
|
||||||
|
|
||||||
|
pub fn fromToken(token: []const u8) Bus {
|
||||||
|
if (std.mem.eql(u8, token, "pci")) return .pci;
|
||||||
|
if (std.mem.eql(u8, token, "usb")) return .usb;
|
||||||
|
if (std.mem.eql(u8, token, "acpi")) return .acpi;
|
||||||
|
return .unknown;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A reported device's full identity, as the manager assembles it from a
|
||||||
|
/// `child_added`: the bus-native class triple plus the numeric ids the widened
|
||||||
|
/// ABI now carries, or the ACPI `_HID` string. Fields a given bus does not have
|
||||||
|
/// are zero / empty (a PCI function has no `hid`; an ACPI device has no vendor).
|
||||||
|
pub const Identity = struct {
|
||||||
|
bus: Bus,
|
||||||
|
base: u8 = 0,
|
||||||
|
subclass: u8 = 0,
|
||||||
|
prog_if: u8 = 0,
|
||||||
|
vendor: u16 = 0,
|
||||||
|
device: u16 = 0,
|
||||||
|
subsystem: u32 = 0,
|
||||||
|
hid: []const u8 = "",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// One parsed registry row. A `null` field is a wildcard — it matches any value
|
||||||
|
/// and contributes nothing to specificity. String fields point into the CSV
|
||||||
|
/// source that was parsed (see the module doc).
|
||||||
|
pub const Rule = struct {
|
||||||
|
bus: Bus,
|
||||||
|
base: ?u8 = null,
|
||||||
|
subclass: ?u8 = null,
|
||||||
|
prog_if: ?u8 = null,
|
||||||
|
vendor: ?u16 = null,
|
||||||
|
device: ?u16 = null,
|
||||||
|
subsystem: ?u32 = null,
|
||||||
|
hid: ?[]const u8 = null,
|
||||||
|
driver: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Specificity weights: how much each pinned field counts toward "most specific
|
||||||
|
/// wins". Doubling from the coarsest (`base`) so that each level outweighs *all*
|
||||||
|
/// coarser levels combined (1+2+4+8+16 = 31 < 32) — a rule that pins `device`
|
||||||
|
/// always beats any rule that does not, no matter how many coarse fields the
|
||||||
|
/// latter pins. `hid` and `device` share the top tier (the user's "hid and
|
||||||
|
/// device weigh heaviest"); they never co-occur, since `hid` is ACPI-only and
|
||||||
|
/// `device` is a PCI/USB numeric id.
|
||||||
|
const weight_base: u32 = 1;
|
||||||
|
const weight_subclass: u32 = 2;
|
||||||
|
const weight_prog_if: u32 = 4;
|
||||||
|
const weight_vendor: u32 = 8;
|
||||||
|
const weight_subsystem: u32 = 16;
|
||||||
|
const weight_device: u32 = 32;
|
||||||
|
const weight_hid: u32 = 32;
|
||||||
|
|
||||||
|
/// The outcome of `matchDriver`: the winning rule's driver path, its specificity,
|
||||||
|
/// and whether another rule tied it at that specificity. `ambiguous` is a
|
||||||
|
/// registry authoring error (two equally-specific rules claiming one device); the
|
||||||
|
/// manager logs it loudly and binds the first, so a shadowed rule is visible
|
||||||
|
/// rather than silently dropped.
|
||||||
|
pub const Match = struct {
|
||||||
|
driver: []const u8,
|
||||||
|
specificity: u32,
|
||||||
|
ambiguous: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Whether `rule` matches `id`: same bus, and every pinned (non-wildcard) field
|
||||||
|
/// equal. `hid` compares as a string; the rest as integers.
|
||||||
|
fn matches(rule: Rule, id: Identity) bool {
|
||||||
|
if (rule.bus != id.bus) return false;
|
||||||
|
if (rule.base) |b| if (b != id.base) return false;
|
||||||
|
if (rule.subclass) |s| if (s != id.subclass) return false;
|
||||||
|
if (rule.prog_if) |p| if (p != id.prog_if) return false;
|
||||||
|
if (rule.vendor) |v| if (v != id.vendor) return false;
|
||||||
|
if (rule.device) |d| if (d != id.device) return false;
|
||||||
|
if (rule.subsystem) |s| if (s != id.subsystem) return false;
|
||||||
|
if (rule.hid) |h| if (!std.mem.eql(u8, h, id.hid)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The specificity score of a rule — the sum of the weights of its pinned fields.
|
||||||
|
fn specificity(rule: Rule) u32 {
|
||||||
|
var score: u32 = 0;
|
||||||
|
if (rule.base != null) score += weight_base;
|
||||||
|
if (rule.subclass != null) score += weight_subclass;
|
||||||
|
if (rule.prog_if != null) score += weight_prog_if;
|
||||||
|
if (rule.vendor != null) score += weight_vendor;
|
||||||
|
if (rule.device != null) score += weight_device;
|
||||||
|
if (rule.subsystem != null) score += weight_subsystem;
|
||||||
|
if (rule.hid != null) score += weight_hid;
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bind a reported device to a driver: of every rule that matches `id`, return
|
||||||
|
/// the most specific. `null` when nothing matches (the device goes unbound —
|
||||||
|
/// the authoritative registry does not guess). On an exact specificity tie the
|
||||||
|
/// first such rule in file order wins and `ambiguous` is set.
|
||||||
|
pub fn matchDriver(rules: []const Rule, id: Identity) ?Match {
|
||||||
|
var best: ?Match = null;
|
||||||
|
for (rules) |rule| {
|
||||||
|
if (!matches(rule, id)) continue;
|
||||||
|
const score = specificity(rule);
|
||||||
|
if (best) |current| {
|
||||||
|
if (score > current.specificity) {
|
||||||
|
best = .{ .driver = rule.driver, .specificity = score, .ambiguous = false };
|
||||||
|
} else if (score == current.specificity) {
|
||||||
|
// Two equally-specific rules claim this device — keep the first,
|
||||||
|
// flag the ambiguity for the manager to log.
|
||||||
|
best.?.ambiguous = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
best = .{ .driver = rule.driver, .specificity = score, .ambiguous = false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- parsing -----------------------------------------------------------------
|
||||||
|
|
||||||
|
/// What one CSV line parsed to. `malformed` is a non-comment, non-blank line the
|
||||||
|
/// parser could not read (wrong field count, unparsable number, empty driver) —
|
||||||
|
/// the manager counts these and logs, so a broken registry is loud, not silent.
|
||||||
|
const Line = union(enum) {
|
||||||
|
rule: Rule,
|
||||||
|
ignorable, // blank or comment
|
||||||
|
malformed,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The result of `parse`: how many rules landed in the caller's buffer, and how
|
||||||
|
/// many non-ignorable lines were malformed (for the manager to log). `truncated`
|
||||||
|
/// is set if there were more valid rules than the buffer could hold.
|
||||||
|
pub const ParseResult = struct {
|
||||||
|
count: usize,
|
||||||
|
malformed: usize,
|
||||||
|
truncated: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Strip a trailing `#` comment and surrounding whitespace from one raw line.
|
||||||
|
fn stripComment(raw: []const u8) []const u8 {
|
||||||
|
const body = if (std.mem.indexOfScalar(u8, raw, '#')) |hash| raw[0..hash] else raw;
|
||||||
|
return std.mem.trim(u8, body, " \t\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse one hex field into `T`, honouring `*`/empty as a wildcard (`null`) and
|
||||||
|
/// an optional `0x` prefix. Returns an error only for a genuinely unparsable
|
||||||
|
/// non-wildcard token, so the caller can mark the whole line malformed.
|
||||||
|
fn parseHexField(comptime T: type, field: []const u8) !?T {
|
||||||
|
const token = std.mem.trim(u8, field, " \t");
|
||||||
|
if (token.len == 0 or std.mem.eql(u8, token, "*")) return null;
|
||||||
|
const digits = if (std.mem.startsWith(u8, token, "0x") or std.mem.startsWith(u8, token, "0X"))
|
||||||
|
token[2..]
|
||||||
|
else
|
||||||
|
token;
|
||||||
|
return try std.fmt.parseInt(T, digits, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a wildcard-or-string field (the `hid` column): `*`/empty → wildcard.
|
||||||
|
fn parseStringField(field: []const u8) ?[]const u8 {
|
||||||
|
const token = std.mem.trim(u8, field, " \t");
|
||||||
|
if (token.len == 0 or std.mem.eql(u8, token, "*")) return null;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Classify and (if a rule) parse one line. Split out from `parse` so it can be
|
||||||
|
/// unit-tested directly. `line` is the raw line including no newline.
|
||||||
|
fn parseLine(line: []const u8) Line {
|
||||||
|
const body = stripComment(line);
|
||||||
|
if (body.len == 0) return .ignorable;
|
||||||
|
|
||||||
|
// Nine comma-separated fields: bus, base, class, prog_if, vendor, device,
|
||||||
|
// subsystem, hid, driver.
|
||||||
|
var fields: [9][]const u8 = undefined;
|
||||||
|
var count: usize = 0;
|
||||||
|
var it = std.mem.splitScalar(u8, body, ',');
|
||||||
|
while (it.next()) |field| {
|
||||||
|
if (count >= fields.len) return .malformed; // too many columns
|
||||||
|
fields[count] = field;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
if (count != fields.len) return .malformed; // too few columns
|
||||||
|
|
||||||
|
const bus = Bus.fromToken(std.mem.trim(u8, fields[0], " \t"));
|
||||||
|
if (bus == .unknown) return .malformed;
|
||||||
|
|
||||||
|
const driver = std.mem.trim(u8, fields[8], " \t");
|
||||||
|
if (driver.len == 0) return .malformed;
|
||||||
|
|
||||||
|
return .{ .rule = .{
|
||||||
|
.bus = bus,
|
||||||
|
.base = parseHexField(u8, fields[1]) catch return .malformed,
|
||||||
|
.subclass = parseHexField(u8, fields[2]) catch return .malformed,
|
||||||
|
.prog_if = parseHexField(u8, fields[3]) catch return .malformed,
|
||||||
|
.vendor = parseHexField(u16, fields[4]) catch return .malformed,
|
||||||
|
.device = parseHexField(u16, fields[5]) catch return .malformed,
|
||||||
|
.subsystem = parseHexField(u32, fields[6]) catch return .malformed,
|
||||||
|
.hid = parseStringField(fields[7]),
|
||||||
|
.driver = driver,
|
||||||
|
} };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a whole `/etc/devices.csv` into `out_rules`. The string fields of the
|
||||||
|
/// returned rules point into `source`, which must outlive them.
|
||||||
|
pub fn parse(source: []const u8, out_rules: []Rule) ParseResult {
|
||||||
|
var result: ParseResult = .{ .count = 0, .malformed = 0, .truncated = false };
|
||||||
|
var lines = std.mem.splitScalar(u8, source, '\n');
|
||||||
|
while (lines.next()) |line| {
|
||||||
|
switch (parseLine(line)) {
|
||||||
|
.ignorable => {},
|
||||||
|
.malformed => result.malformed += 1,
|
||||||
|
.rule => |rule| {
|
||||||
|
if (result.count >= out_rules.len) {
|
||||||
|
result.truncated = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out_rules[result.count] = rule;
|
||||||
|
result.count += 1;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- tests -------------------------------------------------------------------
|
||||||
|
|
||||||
|
const testing = std.testing;
|
||||||
|
|
||||||
|
// The worked example from the design: a specific virtio-gpu rule (pins vendor +
|
||||||
|
// device) and a generic display rule (class only) both match the virtio card;
|
||||||
|
// the specific one must win. And a plain VGA adapter still falls to the generic
|
||||||
|
// rule. This is the whole point of widening the ABI to carry vendor/device.
|
||||||
|
test "virtio device rule beats the generic display rule" {
|
||||||
|
const csv =
|
||||||
|
\\# bus, base, class, prog_if, vendor, device, subsystem, hid, driver
|
||||||
|
\\pci, 03, 00, 00, *, *, *, *, /system/drivers/display
|
||||||
|
\\pci, 03, 80, *, 1AF4, 1050, *, *, /system/drivers/virtio-gpu
|
||||||
|
;
|
||||||
|
var rules: [8]Rule = undefined;
|
||||||
|
const parsed = parse(csv, &rules);
|
||||||
|
try testing.expectEqual(@as(usize, 2), parsed.count);
|
||||||
|
try testing.expectEqual(@as(usize, 0), parsed.malformed);
|
||||||
|
|
||||||
|
// The virtio-gpu function: display / other, vendor 1AF4 device 1050.
|
||||||
|
const virtio = matchDriver(rules[0..parsed.count], .{
|
||||||
|
.bus = .pci, .base = 0x03, .subclass = 0x80, .prog_if = 0x00,
|
||||||
|
.vendor = 0x1AF4, .device = 0x1050,
|
||||||
|
}).?;
|
||||||
|
try testing.expect(!virtio.ambiguous);
|
||||||
|
try testing.expectEqualStrings("/system/drivers/virtio-gpu", virtio.driver);
|
||||||
|
|
||||||
|
// A plain VGA adapter (display / VGA) still binds the generic display driver.
|
||||||
|
const vga = matchDriver(rules[0..parsed.count], .{
|
||||||
|
.bus = .pci, .base = 0x03, .subclass = 0x00, .prog_if = 0x00,
|
||||||
|
.vendor = 0x1234, .device = 0x1111,
|
||||||
|
}).?;
|
||||||
|
try testing.expectEqualStrings("/system/drivers/display", vga.driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "no matching row leaves the device unbound" {
|
||||||
|
const csv = "pci, 0C, 03, 30, *, *, *, *, /system/drivers/usb-xhci-bus\n";
|
||||||
|
var rules: [8]Rule = undefined;
|
||||||
|
const parsed = parse(csv, &rules);
|
||||||
|
try testing.expectEqual(@as(usize, 1), parsed.count);
|
||||||
|
|
||||||
|
// An AHCI controller (mass storage / SATA / AHCI) has no row — unbound.
|
||||||
|
const unmatched = matchDriver(rules[0..parsed.count], .{
|
||||||
|
.bus = .pci, .base = 0x01, .subclass = 0x06, .prog_if = 0x01,
|
||||||
|
});
|
||||||
|
try testing.expect(unmatched == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "acpi rows match on hid" {
|
||||||
|
const csv =
|
||||||
|
\\acpi, *, *, *, *, *, *, PNP0303, /system/drivers/ps2-bus
|
||||||
|
\\acpi, *, *, *, *, *, *, PNP0F13, /system/drivers/ps2-bus
|
||||||
|
;
|
||||||
|
var rules: [8]Rule = undefined;
|
||||||
|
const parsed = parse(csv, &rules);
|
||||||
|
try testing.expectEqual(@as(usize, 2), parsed.count);
|
||||||
|
|
||||||
|
const keyboard = matchDriver(rules[0..parsed.count], .{ .bus = .acpi, .hid = "PNP0303" }).?;
|
||||||
|
try testing.expectEqualStrings("/system/drivers/ps2-bus", keyboard.driver);
|
||||||
|
const nothing = matchDriver(rules[0..parsed.count], .{ .bus = .acpi, .hid = "PNP0A03" });
|
||||||
|
try testing.expect(nothing == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "equally specific rules flag ambiguity" {
|
||||||
|
const csv =
|
||||||
|
\\pci, 03, 00, 00, *, *, *, *, /system/drivers/display-a
|
||||||
|
\\pci, 03, 00, 00, *, *, *, *, /system/drivers/display-b
|
||||||
|
;
|
||||||
|
var rules: [8]Rule = undefined;
|
||||||
|
const parsed = parse(csv, &rules);
|
||||||
|
const hit = matchDriver(rules[0..parsed.count], .{
|
||||||
|
.bus = .pci, .base = 0x03, .subclass = 0x00, .prog_if = 0x00,
|
||||||
|
}).?;
|
||||||
|
try testing.expect(hit.ambiguous);
|
||||||
|
try testing.expectEqualStrings("/system/drivers/display-a", hit.driver); // first wins
|
||||||
|
}
|
||||||
|
|
||||||
|
test "comments, blanks, and malformed lines" {
|
||||||
|
const csv =
|
||||||
|
\\# a header comment
|
||||||
|
\\
|
||||||
|
\\pci, 0C, 03, 30, *, *, *, *, /system/drivers/usb-xhci-bus # trailing comment
|
||||||
|
\\pci, ZZ, 03, 30, *, *, *, *, /system/drivers/broken
|
||||||
|
\\pci, 03, 00, 00, *, *, *, *,
|
||||||
|
\\bogus-bus, *, *, *, *, *, *, *, /system/drivers/x
|
||||||
|
;
|
||||||
|
var rules: [8]Rule = undefined;
|
||||||
|
const parsed = parse(csv, &rules);
|
||||||
|
try testing.expectEqual(@as(usize, 1), parsed.count); // only the xhci row is valid
|
||||||
|
try testing.expectEqual(@as(usize, 3), parsed.malformed); // bad hex, empty driver, bad bus
|
||||||
|
try testing.expectEqualStrings("/system/drivers/usb-xhci-bus", rules[0].driver);
|
||||||
|
try testing.expect(rules[0].hid == null); // trailing comment stripped, hid still wildcard
|
||||||
|
}
|
||||||
@@ -11,6 +11,19 @@
|
|||||||
/// startup instead of quiet corruption later.
|
/// startup instead of quiet corruption later.
|
||||||
pub const version: u16 = 1;
|
pub const version: u16 = 1;
|
||||||
|
|
||||||
|
/// Which bus a `child_added` came from — stated by the reporting bus driver so
|
||||||
|
/// the manager's /etc/devices.csv matcher knows how to read the report's identity
|
||||||
|
/// (a PCI class triple vs a USB class triple are the same 24 bits but different
|
||||||
|
/// namespaces) and which `bus` column a rule must name to bind it. `unknown` is
|
||||||
|
/// the zero default, so an un-upgraded reporter fails to match rather than
|
||||||
|
/// binding to the wrong bus's rule.
|
||||||
|
pub const BusKind = enum(u8) {
|
||||||
|
unknown = 0,
|
||||||
|
pci = 1,
|
||||||
|
usb = 2,
|
||||||
|
acpi = 3,
|
||||||
|
};
|
||||||
|
|
||||||
/// What kind of driver is talking (docs/driver-model.md's shapes).
|
/// What kind of driver is talking (docs/driver-model.md's shapes).
|
||||||
pub const Role = enum(u8) {
|
pub const Role = enum(u8) {
|
||||||
/// Owns a controller and reports the devices behind it (`child_added`).
|
/// Owns a controller and reports the devices behind it (`child_added`).
|
||||||
@@ -66,7 +79,9 @@ pub const reply_size = @sizeOf(HelloReply);
|
|||||||
/// restarted instance rediscovers and re-reports.
|
/// restarted instance rediscovers and re-reports.
|
||||||
pub const ChildAdded = extern struct {
|
pub const ChildAdded = extern struct {
|
||||||
operation: u8 = @intFromEnum(Operation.child_added),
|
operation: u8 = @intFromEnum(Operation.child_added),
|
||||||
reserved0: u8 = 0,
|
/// A `BusKind` value: which bus reported this child, so the manager reads the
|
||||||
|
/// identity in the right namespace and matches against the right `bus` column.
|
||||||
|
bus: u8 = @intFromEnum(BusKind.unknown),
|
||||||
reserved1: u16 = 0,
|
reserved1: u16 = 0,
|
||||||
reserved2: u32 = 0,
|
reserved2: u32 = 0,
|
||||||
/// The reporting driver's own device (the controller) — the child's parent.
|
/// The reporting driver's own device (the controller) — the child's parent.
|
||||||
@@ -80,6 +95,18 @@ pub const ChildAdded = extern struct {
|
|||||||
/// manager hands a matched driver as its argv assignment — or `no_device`
|
/// manager hands a matched driver as its argv assignment — or `no_device`
|
||||||
/// for an unregistered leaf (a USB port before the descriptor track).
|
/// for an unregistered leaf (a USB port before the descriptor track).
|
||||||
device_id: u64 = no_device,
|
device_id: u64 = no_device,
|
||||||
|
/// The vendor id (PCI vendor / USB idVendor), or 0 when the bus has no such
|
||||||
|
/// concept (ACPI). Carried so the manager's /etc/devices.csv matcher can bind
|
||||||
|
/// on vendor — a level the bus-native `identity` (a class triple) cannot express.
|
||||||
|
vendor: u16 = 0,
|
||||||
|
/// The device id (PCI device / USB idProduct), or 0. The most specific numeric
|
||||||
|
/// level: this is what lets one virtio-gpu (1AF4:1050) be told from any other
|
||||||
|
/// virtio display function without the driver re-confirming after it is spawned.
|
||||||
|
device: u16 = 0,
|
||||||
|
/// The PCI subsystem id, packed `(subsystem_vendor << 16) | subsystem_device`
|
||||||
|
/// (so it reads vendor-first, matching the CSV's `ssvid:ssid`), or 0 when the
|
||||||
|
/// device has no subsystem id (a bridge, or a non-PCI bus).
|
||||||
|
subsystem: u32 = 0,
|
||||||
/// The ACPI hardware id (`_HID`), EISA-decoded (e.g. "PNP0303"), for devices
|
/// The ACPI hardware id (`_HID`), EISA-decoded (e.g. "PNP0303"), for devices
|
||||||
/// discovered by firmware string rather than a numeric bus identity. Empty
|
/// discovered by firmware string rather than a numeric bus identity. Empty
|
||||||
/// (all zero) otherwise. Widens for FDT `compatible` strings later.
|
/// (all zero) otherwise. Widens for FDT `compatible` strings later.
|
||||||
|
|||||||
@@ -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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -153,6 +153,12 @@ fn registerAndReport(bus: u64, dev: u64, function: u64, class_triple: u32) void
|
|||||||
var descriptor = std.mem.zeroes(device.DeviceDescriptor);
|
var descriptor = std.mem.zeroes(device.DeviceDescriptor);
|
||||||
descriptor.class = @intFromEnum(device.DeviceClass.pci_device);
|
descriptor.class = @intFromEnum(device.DeviceClass.pci_device);
|
||||||
descriptor.pci_class = class_triple;
|
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] = .{
|
descriptor.resources[0] = .{
|
||||||
.kind = @intFromEnum(device.ResourceKind.memory),
|
.kind = @intFromEnum(device.ResourceKind.memory),
|
||||||
.start = ecam_physical + (((bus - start_bus) << 20) | (dev << 15) | (function << 12)),
|
.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.
|
// write all-ones, read the writable mask back, restore. Header type 0 only.
|
||||||
const header_type = (configRead(bus, dev, function, 0x0C) >> 16) & 0x7F;
|
const header_type = (configRead(bus, dev, function, 0x0C) >> 16) & 0x7F;
|
||||||
if (header_type == 0) {
|
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);
|
const command = configRead16(bus, dev, function, 0x04);
|
||||||
configWrite16(bus, dev, function, 0x04, command & ~@as(u16, 0b11));
|
configWrite16(bus, dev, function, 0x04, command & ~@as(u16, 0b11));
|
||||||
var i: u64 = 0;
|
var i: u64 = 0;
|
||||||
@@ -215,10 +226,14 @@ fn registerAndReport(bus: u64, dev: u64, function: u64, class_triple: u32) void
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
const report = device_manager_protocol.ChildAdded{
|
const report = device_manager_protocol.ChildAdded{
|
||||||
|
.bus = @intFromEnum(device_manager_protocol.BusKind.pci),
|
||||||
.parent = bridge_id,
|
.parent = bridge_id,
|
||||||
.bus_address = (bus << 8) | (dev << 3) | function,
|
.bus_address = (bus << 8) | (dev << 3) | function,
|
||||||
.identity = class_triple,
|
.identity = class_triple,
|
||||||
.device_id = registered,
|
.device_id = registered,
|
||||||
|
.vendor = descriptor.vendor,
|
||||||
|
.device = descriptor.device,
|
||||||
|
.subsystem = descriptor.subsystem,
|
||||||
};
|
};
|
||||||
var reply: [device_manager_protocol.message_maximum]u8 = undefined;
|
var reply: [device_manager_protocol.message_maximum]u8 = undefined;
|
||||||
_ = ipc.call(manager_handle, std.mem.asBytes(&report), &reply) catch {
|
_ = 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{
|
const report = device_manager_protocol.ChildAdded{
|
||||||
|
.bus = @intFromEnum(device_manager_protocol.BusKind.usb),
|
||||||
.parent = controller_id,
|
.parent = controller_id,
|
||||||
.bus_address = (@as(u64, port) << 8) | interface.number,
|
.bus_address = (@as(u64, port) << 8) | interface.number,
|
||||||
.identity = identity,
|
.identity = identity,
|
||||||
|
|||||||
@@ -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.
|
/// the compositor in the announce so it packs colours in the surface's byte order.
|
||||||
const display_format_bgrx: u32 = 1;
|
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
|
/// 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
|
/// 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
|
/// 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;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Config space is resource 0. Confirm it really is a virtio-gpu, then enable memory-space
|
// Config space is resource 0. The registry (/etc/devices.csv) bound this driver by the
|
||||||
// decode + bus mastering (the device DMAs the ring and backing out of RAM); pci-bus only
|
// exact virtio-gpu identity (vendor 0x1AF4 / device 0x1050), so there is no re-confirm to
|
||||||
// preserves whatever the firmware left, and a secondary display is often left disabled.
|
// 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 {
|
var function = pci.Function.map(device_id, descriptor) orelse {
|
||||||
std.log.info("config-space map failed", .{});
|
std.log.info("config-space map failed", .{});
|
||||||
return false;
|
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();
|
function.enableMemoryAndBusMaster();
|
||||||
|
|
||||||
// Walk the capability list for the virtio common-config and notify structures (V3 needs
|
// Walk the capability list for the virtio common-config and notify structures (V3 needs
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ fn onInit(endpoint: ipc.Handle) bool {
|
|||||||
else
|
else
|
||||||
std.log.info("reported {s} (device {d}, {d} resources)", .{ hid, entry.device_id, entry.resource_count });
|
std.log.info("reported {s} (device {d}, {d} resources)", .{ hid, entry.device_id, entry.resource_count });
|
||||||
if (manager) |h| {
|
if (manager) |h| {
|
||||||
var report = device_manager_protocol.ChildAdded{ .parent = node_id, .bus_address = entry.device_id, .identity = 0, .device_id = entry.device_id };
|
var report = device_manager_protocol.ChildAdded{ .bus = @intFromEnum(device_manager_protocol.BusKind.acpi), .parent = node_id, .bus_address = entry.device_id, .identity = 0, .device_id = entry.device_id };
|
||||||
@memcpy(report.hid[0..entry.hid_len], entry.hid[0..entry.hid_len]);
|
@memcpy(report.hid[0..entry.hid_len], entry.hid[0..entry.hid_len]);
|
||||||
var reply: [device_manager_protocol.message_maximum]u8 = undefined;
|
var reply: [device_manager_protocol.message_maximum]u8 = undefined;
|
||||||
_ = ipc.call(h, std.mem.asBytes(&report), &reply) catch {};
|
_ = ipc.call(h, std.mem.asBytes(&report), &reply) catch {};
|
||||||
|
|||||||
@@ -23,86 +23,66 @@ const service = @import("service");
|
|||||||
const time = @import("time");
|
const time = @import("time");
|
||||||
const memory = @import("memory");
|
const memory = @import("memory");
|
||||||
const logging = @import("logging");
|
const logging = @import("logging");
|
||||||
const acpi_ids = @import("acpi-ids");
|
|
||||||
const pci_class = @import("pci-class");
|
|
||||||
const usb_ids = @import("usb-ids");
|
|
||||||
const device_manager_protocol = @import("device-manager-protocol");
|
const device_manager_protocol = @import("device-manager-protocol");
|
||||||
|
const registry = @import("device-registry");
|
||||||
|
const fs = @import("file-system");
|
||||||
|
|
||||||
/// The PCI class/subclass/prog-IF triple of an xHCI (USB 3) host controller —
|
// --- the device registry ------------------------------------------------------
|
||||||
/// Serial Bus Controller / USB Controller / XHCI — named from pci-class.zig rather
|
// Driver matching is data-driven and authoritative: /etc/devices.csv (parsed by
|
||||||
/// than written as the bare 0x0C0330 (docs/coding-standards.md, "Named values").
|
// the device-registry module) names, per bus, which driver binds a reported
|
||||||
const xhci_pci_class: u64 = pci_class.ClassCode.pack(.{
|
// device, the most-specific match winning. There is no compiled-in fallback — a
|
||||||
.base = @intFromEnum(pci_class.BaseClass.serial_bus),
|
// device no row matches goes unbound and is logged. This retired the hand-kept
|
||||||
.subclass = @intFromEnum(pci_class.serial_bus.SubClass.usb),
|
// pciDriverForIdentity / hidDriverFor / usbDriverForIdentity switch tables
|
||||||
.prog_if = @intFromEnum(pci_class.serial_bus.usb.ProgIf.xhci),
|
// (docs/device-manager.md: "matching stays code until the third bus").
|
||||||
});
|
|
||||||
|
|
||||||
/// The PCI class triple of a virtio-gpu — Display Controller / Other (0x80) / 0. The class
|
/// The CSV bytes, held for the life of the process because the parsed rules'
|
||||||
/// alone cannot tell it from any other display/other function, so the driver re-confirms
|
/// string fields (hid, driver) slice into this buffer.
|
||||||
/// vendor 0x1AF4 / device 0x1050 from config space once spawned; this only gets it spawned.
|
var registry_source: [8192]u8 = undefined;
|
||||||
const virtio_gpu_pci_class: u64 = pci_class.ClassCode.pack(.{
|
var registry_rules: [64]registry.Rule = undefined;
|
||||||
.base = @intFromEnum(pci_class.BaseClass.display),
|
var registry_count: usize = 0;
|
||||||
.subclass = 0x80, // "Other" — no named SubClass member (PCI convention)
|
|
||||||
.prog_if = 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
/// Read and parse /etc/devices.csv once at boot. The file lives in the initial
|
||||||
const vga_compatible_gpu_pci_class: u64 = pci_class.ClassCode.pack(.{
|
/// ramdisk, which the kernel serves directly — no filesystem service need be up
|
||||||
.base = @intFromEnum(pci_class.BaseClass.display),
|
/// (fat is spawned after the manager), so this is a plain fs.open + read.
|
||||||
.subclass = @intFromEnum(pci_class.display.SubClass.vga_compatible),
|
fn loadRegistry() void {
|
||||||
.prog_if = 0,
|
var file = fs.open("/etc/devices.csv", .{}) orelse {
|
||||||
});
|
_ = logging.write("/system/services/device-manager: /etc/devices.csv missing — nothing will match\n");
|
||||||
|
return;
|
||||||
/// The driver that serves a *reported* PCI function (M19.3: matching moved
|
|
||||||
/// from the boot snapshot to the bus reports), or null. A machine can carry
|
|
||||||
/// several identical controllers — one driver instance per reported device,
|
|
||||||
/// its registered id as argv[1].
|
|
||||||
fn pciDriverForIdentity(identity: u64) ?[]const u8 {
|
|
||||||
return switch (identity) {
|
|
||||||
xhci_pci_class => "/system/drivers/usb-xhci-bus",
|
|
||||||
vga_compatible_gpu_pci_class => "/system/drivers/display",
|
|
||||||
virtio_gpu_pci_class => "/system/drivers/virtio-gpu",
|
|
||||||
else => null,
|
|
||||||
};
|
};
|
||||||
|
defer file.close();
|
||||||
|
var used: usize = 0;
|
||||||
|
while (used < registry_source.len) {
|
||||||
|
const n = file.read(registry_source[used..]) orelse break;
|
||||||
|
if (n == 0) break;
|
||||||
|
used += n;
|
||||||
|
}
|
||||||
|
const result = registry.parse(registry_source[0..used], ®istry_rules);
|
||||||
|
registry_count = result.count;
|
||||||
|
if (result.malformed != 0) std.log.info("/etc/devices.csv: {d} malformed line(s) skipped", .{result.malformed});
|
||||||
|
if (result.truncated) _ = logging.write("/system/services/device-manager: /etc/devices.csv has more rules than the table holds\n");
|
||||||
|
std.log.info("/etc/devices.csv: {d} rule(s) loaded", .{registry_count});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The driver that serves a *reported* ACPI device by its `_HID` (M20.3:
|
/// Build a registry Identity from a bus driver's report: the bus it named, the
|
||||||
/// ps2-bus now binds the PS/2 nodes the acpi service reports, not boot-snapshot
|
/// class triple unpacked from `identity` (0xCCSSPP — the same packing for a PCI
|
||||||
/// nodes the kernel used to build). ps2-bus is a singleton that finds both its
|
/// class code and a USB class triple), the widened numeric ids, and the ACPI hid.
|
||||||
/// devices by hid once spawned, so keyboard and mouse map to the same name.
|
fn identityFromReport(report: device_manager_protocol.ChildAdded) registry.Identity {
|
||||||
fn hidDriverFor(hid: []const u8) ?[]const u8 {
|
const bus: registry.Bus = switch (report.bus) {
|
||||||
if (std.mem.eql(u8, hid, "PNP0303")) return "/system/drivers/ps2-bus"; // PS/2 keyboard
|
@intFromEnum(device_manager_protocol.BusKind.pci) => .pci,
|
||||||
if (std.mem.eql(u8, hid, "PNP0F13")) return "/system/drivers/ps2-bus"; // PS/2 mouse
|
@intFromEnum(device_manager_protocol.BusKind.usb) => .usb,
|
||||||
return null;
|
@intFromEnum(device_manager_protocol.BusKind.acpi) => .acpi,
|
||||||
}
|
else => .unknown,
|
||||||
|
};
|
||||||
/// The driver that serves a *reported* USB interface by its (class, subclass,
|
const hid_len = std.mem.indexOfScalar(u8, &report.hid, 0) orelse report.hid.len;
|
||||||
/// protocol) triple — the third bus after PCI and ACPI (docs/device-manager.md:
|
return .{
|
||||||
/// matching stays code until the third bus). The xHCI bus driver reports each
|
.bus = bus,
|
||||||
/// interface with this packed triple as its identity; the matched class driver is
|
.base = @truncate(report.identity >> 16),
|
||||||
/// spawned with the interface's registered id as argv[1], which it presents to the
|
.subclass = @truncate(report.identity >> 8),
|
||||||
/// bus driver to open the device.
|
.prog_if = @truncate(report.identity),
|
||||||
fn usbDriverForIdentity(identity: u64) ?[]const u8 {
|
.vendor = report.vendor,
|
||||||
const keyboard = comptime usb_ids.packTriple(
|
.device = report.device,
|
||||||
@intFromEnum(usb_ids.Class.hid),
|
.subsystem = report.subsystem,
|
||||||
@intFromEnum(usb_ids.hid.SubClass.boot),
|
.hid = report.hid[0..hid_len],
|
||||||
@intFromEnum(usb_ids.hid.Protocol.keyboard),
|
|
||||||
);
|
|
||||||
const mouse = comptime usb_ids.packTriple(
|
|
||||||
@intFromEnum(usb_ids.Class.hid),
|
|
||||||
@intFromEnum(usb_ids.hid.SubClass.boot),
|
|
||||||
@intFromEnum(usb_ids.hid.Protocol.mouse),
|
|
||||||
);
|
|
||||||
const storage = comptime usb_ids.packTriple(
|
|
||||||
@intFromEnum(usb_ids.Class.mass_storage),
|
|
||||||
@intFromEnum(usb_ids.mass_storage.SubClass.scsi),
|
|
||||||
@intFromEnum(usb_ids.mass_storage.Protocol.bulk_only),
|
|
||||||
);
|
|
||||||
return switch (identity) {
|
|
||||||
keyboard => "/system/drivers/usb-hid-keyboard",
|
|
||||||
mouse => "/system/drivers/usb-hid-mouse",
|
|
||||||
storage => "/system/drivers/usb-storage",
|
|
||||||
else => null,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,6 +345,10 @@ fn sweepDeadlines() void {
|
|||||||
fn initialise(endpoint: ipc.Handle) bool {
|
fn initialise(endpoint: ipc.Handle) bool {
|
||||||
manager_endpoint = endpoint;
|
manager_endpoint = endpoint;
|
||||||
|
|
||||||
|
// Load the authoritative driver-match registry before any bus driver can
|
||||||
|
// report a device to match against it.
|
||||||
|
loadRegistry();
|
||||||
|
|
||||||
// Enumerate into a heap buffer (too big for the one-page user stack).
|
// Enumerate into a heap buffer (too big for the one-page user stack).
|
||||||
const buffer = memory.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
const buffer = memory.allocator().alloc(device.DeviceDescriptor, 64) catch {
|
||||||
_ = logging.write("/system/services/device-manager: out of memory\n");
|
_ = logging.write("/system/services/device-manager: out of memory\n");
|
||||||
@@ -459,24 +443,22 @@ fn onChildAdded(message: []const u8, reply: []u8, sender: u32) usize {
|
|||||||
if (!addChild(report.parent, report.bus_address, report.identity, report.device_id, sender)) status = -1;
|
if (!addChild(report.parent, report.bus_address, report.identity, report.device_id, sender)) status = -1;
|
||||||
std.log.info("child added (device {d} port {d}, identity {d}) by {s}", .{ report.parent, report.bus_address, report.identity, driver.name() });
|
std.log.info("child added (device {d} port {d}, identity {d}) by {s}", .{ report.parent, report.bus_address, report.identity, driver.name() });
|
||||||
if (status == 0) publishEvent(message[0..device_manager_protocol.child_added_size]);
|
if (status == 0) publishEvent(message[0..device_manager_protocol.child_added_size]);
|
||||||
// Matching from reports (M19.3): a registered child whose identity
|
// Matching from reports (M19.3), now data-driven via the /etc/devices.csv
|
||||||
// names a driver gets one, once — re-reports after a bus restart
|
// registry: a registered child gets the most-specific driver its identity
|
||||||
// dedupe on the registered id, exactly like the registrations do.
|
// matches, once — re-reports after a bus restart dedupe on the registered
|
||||||
|
// id, exactly like the registrations do.
|
||||||
if (status == 0 and report.device_id != device_manager_protocol.no_device) {
|
if (status == 0 and report.device_id != device_manager_protocol.no_device) {
|
||||||
if (pciDriverForIdentity(report.identity)) |child_driver| {
|
const id = identityFromReport(report);
|
||||||
if (!driverForDevice(report.device_id)) addDriver(child_driver, report.device_id, true);
|
if (registry.matchDriver(registry_rules[0..registry_count], id)) |match| {
|
||||||
}
|
if (match.ambiguous)
|
||||||
// USB interface match: the reported identity is the packed class triple,
|
std.log.info("/etc/devices.csv: multiple equally-specific rules match the device {s} reported; binding {s}", .{ driver.name(), match.driver });
|
||||||
// and the class driver is spawned with the interface's registered id.
|
if (id.bus == .acpi) {
|
||||||
if (usbDriverForIdentity(report.identity)) |usb_driver| {
|
// An hid-matched driver (ps2-bus) is a singleton that finds its
|
||||||
if (!driverForDevice(report.device_id)) addDriver(usb_driver, report.device_id, true);
|
// own devices once spawned — spawn it once, no device assignment.
|
||||||
}
|
if (!alreadySupervised(match.driver)) addDriver(match.driver, device_manager_protocol.no_device, false);
|
||||||
// ACPI _HID match (M20.3): ps2-bus is a singleton that finds its own
|
} else {
|
||||||
// devices by hid, so spawn it once, without a device assignment.
|
// A per-device driver: one instance, the registered id as argv[1].
|
||||||
const hid_len = std.mem.indexOfScalar(u8, &report.hid, 0) orelse report.hid.len;
|
if (!driverForDevice(report.device_id)) addDriver(match.driver, report.device_id, true);
|
||||||
if (hid_len != 0) {
|
|
||||||
if (hidDriverFor(report.hid[0..hid_len])) |hid_driver| {
|
|
||||||
if (!alreadySupervised(hid_driver)) addDriver(hid_driver, device_manager_protocol.no_device, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user