diff --git a/docs/discovery.md b/docs/discovery.md index 86499a6..6dad24b 100644 --- a/docs/discovery.md +++ b/docs/discovery.md @@ -167,3 +167,13 @@ free; discovery on x86 is partly about *finding* what ARM just tells you. - [ipc.md](ipc.md) — the channels that interrupts-as-messages and the device manager will ride on. - [vision.md](vision.md) — why drivers belong in isolated user space at all. + +## Update (M19.3, 2026-07-13): PCI enumeration left the kernel + +The kernel now seeds only the `pci_host_bridge` node (ECAM window, MMIO +apertures derived from the memory map's holes, bus range, and the 16-bit I/O +window). The per-function walk moved to the ring-3 `pci-bus` driver +([device-manager.md](device-manager.md)): it claims the bridge, repeats the +ECAM scan through its mmio grant, and `device_register`s what it finds, which +the device manager mirrors and matches. The ACPI namespace walk follows in M20; +the static tables (MADT, HPET, MCFG, FADT + `\\_S5`) stay kernel-side. diff --git a/docs/m19-m20-plan.md b/docs/m19-m20-plan.md index 56e4d76..881839d 100644 --- a/docs/m19-m20-plan.md +++ b/docs/m19-m20-plan.md @@ -96,11 +96,15 @@ branch is green; keep branches; push everything. and asserts the PCI node count never grows — plus harness hardening: a failing case now preserves its serial as -failed-serial.log, and the heavy scenarios run at 150s; suite 55/55). -- [ ] **M19.3** — the flip: kernel `enumeratePci` call removed (bridge node - stays); manager matches PCI drivers from reports. One commit. The - existing xHCI scenarios (`driver-restart`, `usb-report`, `device-list`) - are the assertion — xhci must come up spawned off a pci-bus report, and - the suite must not be able to tell the difference. discovery.md updated. +- [x] **M19.3** — the flip: kernel `enumeratePci`/`addBars`/`PciHeader` all + deleted (bridge node stays); manager matches PCI drivers from reported + identity, deduped by registered id. Surfaced and fixed a real SMP race the + flip created — ring-3 device_register made the broker table concurrent, so + mmio_map's lock-free read intermittently tore hpet's resource length + (user fault) and overflowed `r.len-1` into a kernel panic; now the broker + read is under the big lock and the arithmetic is guarded, and pci-bus + skips size-0 BARs. discovery.md updated; suite 55/55 (driver-restart + hammered 6×). - [ ] **merge** `feat/pci-bus` → main, push. - [ ] **M20.1** — acpi service, parse only (fills the existing placeholder at system/services/acpi/acpi.zig): kernel publishes `acpi-tables` diff --git a/system/devices/acpi.zig b/system/devices/acpi.zig index 8aa495d..8e008db 100644 --- a/system/devices/acpi.zig +++ b/system/devices/acpi.zig @@ -360,25 +360,6 @@ const Hpet = extern struct { page_protection: u8, }; -// --- PCI configuration-space header (first 64 bytes, common fields) --------- - -const PciHeader = extern struct { - vendor_id: u16 align(1), - device_id: u16 align(1), - command: u16 align(1), - status: u16 align(1), - revision_id: u8, - prog_if: u8, - subclass: u8, - class_code: u8, - cache_line_size: u8, - latency_timer: u8, - /// bit 7 set => multi-function device. - header_type: u8, - bist: u8, - // 0x10 onward (BARs, etc.) depends on header_type; read separately. -}; - // --- Entry point ------------------------------------------------------------ /// Discover hardware from the ACPI tables rooted at `rsdp_physical` and populate @@ -452,7 +433,7 @@ fn handleTable(device_tree: *DeviceTree, hal: Hal, sdt_physical: u64) !void { if (std.mem.eql(u8, &sig, &APIC)) { try parseMadt(device_tree, header); } else if (std.mem.eql(u8, &sig, &MCFG)) { - try parseMcfg(device_tree, hal, header); + try parseMcfg(device_tree, header); } else if (std.mem.eql(u8, &sig, &HPET)) { try parseHpet(device_tree, hal, header); } else if (std.mem.eql(u8, &sig, &FACP)) { @@ -537,7 +518,7 @@ fn parseMadt(device_tree: *DeviceTree, header: *const SystemDescriptorTableHeade } /// MCFG -> a pci_host_bridge per ECAM segment, then a PCI enumeration underneath. -fn parseMcfg(device_tree: *DeviceTree, hal: Hal, header: *const SystemDescriptorTableHeader) !void { +fn parseMcfg(device_tree: *DeviceTree, header: *const SystemDescriptorTableHeader) !void { const total: usize = header.length; const base: [*]const u8 = @ptrCast(header); @@ -557,7 +538,11 @@ fn parseMcfg(device_tree: *DeviceTree, hal: Hal, header: *const SystemDescriptor // window functions' I/O BARs must register-contain within (M19.2). _ = bridge.addResource(.io_port, 0, 1 << 16); - try enumeratePci(device_tree, bridge, hal, alloc.*); + // The function walk itself retired to ring 3 (M19.3): the pci-bus + // driver claims this bridge, repeats the scan through its ECAM grant, + // and device_registers what it finds — the kernel seeds only the + // bridge. The scan's equivalence was proven before the hand-off + // (pci-scan), and the walk's history is in git if archaeology calls. } } @@ -587,7 +572,13 @@ fn addBridgeApertures(bridge: *device_model.Device) void { var high_end: u64 = 1 << 32; for (boot_memory_regions) |region| { const end = region.base + region.pages * 4096; - if (end > high_end) high_end = end; + // Above 4 GiB only *usable RAM* blocks the aperture: OVMF describes + // its own 64-bit PCI window as a reserved region and then programs + // BARs inside it — honoring reserved there would exclude the very + // space BARs live in. Below 4 GiB every described region blocks (the + // kernel image, the tables, the ramdisk all live there). Bring-up + // trust: only the bridge's claimant can register into the aperture. + if (region.kind == .usable and end > high_end) high_end = end; if (region.base >= (1 << 32) or below_count == below.len) continue; below[below_count] = .{ .base = region.base, .end = @min(end, 1 << 32) }; below_count += 1; @@ -623,106 +614,6 @@ fn addBridgeApertures(bridge: *device_model.Device) void { _ = bridge.addResource(.memory, high_end, (@as(u64, 1) << 46) - high_end); } -/// Brute-force scan the ECAM window's bus range for present PCI functions. No -/// bridge recursion yet: on the ECAM path the host bridge decodes every bus in -/// the window, so scanning the declared range finds everything QEMU exposes. -fn enumeratePci( - device_tree: *DeviceTree, - bridge: *device_model.Device, - hal: Hal, - alloc: McfgAllocation, -) !void { - var bus: u16 = alloc.start_bus; - while (bus <= alloc.end_bus) : (bus += 1) { - var device: u8 = 0; - while (device < 32) : (device += 1) { - const h0: *align(1) const PciHeader = @ptrCast(pciConfigurationPtr(alloc, hal, @intCast(bus), device, 0)); - if (h0.vendor_id == 0xFFFF) continue; // no function 0 => slot empty - - const funcs: u8 = if (h0.header_type & 0x80 != 0) 8 else 1; - var function: u8 = 0; - while (function < funcs) : (function += 1) { - const configuration = pciConfigurationPtr(alloc, hal, @intCast(bus), device, function); - const h: *align(1) const PciHeader = @ptrCast(configuration); - if (h.vendor_id == 0xFFFF) continue; - - var nb: [24]u8 = undefined; - const nm = std.fmt.bufPrint(&nb, "{s}:{x:0>2}:{x:0>2}.{d}", .{ - bridge.name(), bus, device, function, - }) catch "pcidev"; - const node = try device_tree.addChild(bridge, .pci_device, nm); - // Resource 0 is the function's own 4 KiB ECAM configuration space. A - // claimed PCI driver mmio_maps this to reach its command register, - // BARs, and — the point — its capability list (MSI/MSI-X, PCIe - // extended caps), without any new syscall. Physical address per the - // ECAM formula (same as pciConfigurationPtr). - const config_physical = alloc.base_address + - (@as(u64, @as(u8, @intCast(bus)) - alloc.start_bus) << 20) + - (@as(u64, device) << 15) + (@as(u64, function) << 12); - _ = node.addResource(.memory, config_physical, abi.page_size); - node.ids.pci_vendor = h.vendor_id; - node.ids.pci_device = h.device_id; - node.ids.pci_class = (@as(u24, h.class_code) << 16) | - (@as(u24, h.subclass) << 8) | h.prog_if; - node.ids.pci_bdf = (@as(u16, @intCast(bus)) << 8) | (@as(u16, device) << 3) | function; - - // BARs only exist in header type 0 (normal devices), not bridges. - if (h.header_type & 0x7F == 0) addBars(node, configuration); - } - } - } -} - -/// Record and size the memory/IO windows named by a device's Base Address -/// Registers. Sizing is the standard probe: disable decode, write all-ones, read -/// back the writable (address) bits, restore. `size = ~mask + 1`. -fn addBars(node: *device_model.Device, configuration: [*]align(1) u8) void { - // Stop the device decoding its BARs while we transiently write all-ones. - const command = rd(u16, configuration, 0x04); - wr(u16, configuration, 0x04, command & ~@as(u16, 0b11)); - - var i: usize = 0; - while (i < 6) : (i += 1) { - const off = 0x10 + i * 4; - const orig = rd(u32, configuration, off); - if (orig == 0) continue; - - if (orig & 1 != 0) { - // I/O-space BAR (16-bit address space on x86). - wr(u32, configuration, off, 0xFFFF_FFFF); - const readback = rd(u32, configuration, off); - wr(u32, configuration, off, orig); - const mask = readback & 0xFFFF_FFFC; - const size: u32 = if (mask == 0) 0 else (~mask +% 1) & 0xFFFF; - _ = node.addResource(.io_port, orig & 0xFFFF_FFFC, size); - } else if ((orig >> 1) & 0x3 == 2) { - // 64-bit memory BAR: this BAR pair spans two configuration slots. - const orig_hi = rd(u32, configuration, off + 4); - wr(u32, configuration, off, 0xFFFF_FFFF); - wr(u32, configuration, off + 4, 0xFFFF_FFFF); - const lo = rd(u32, configuration, off); - const hi = rd(u32, configuration, off + 4); - wr(u32, configuration, off, orig); - wr(u32, configuration, off + 4, orig_hi); - const readback = (@as(u64, hi) << 32) | (lo & 0xFFFF_FFF0); - const size: u64 = if (readback == 0) 0 else ~readback +% 1; - const address = (@as(u64, orig_hi) << 32) | (orig & 0xFFFF_FFF0); - _ = node.addResource(.memory, address, size); - i += 1; // consumed the high half - } else { - // 32-bit memory BAR. - wr(u32, configuration, off, 0xFFFF_FFFF); - const readback = rd(u32, configuration, off); - wr(u32, configuration, off, orig); - const mask = readback & 0xFFFF_FFF0; - const size: u32 = if (mask == 0) 0 else ~mask +% 1; - _ = node.addResource(.memory, orig & 0xFFFF_FFF0, size); - } - } - - wr(u16, configuration, 0x04, command); // restore decode -} - /// HPET -> a timer node with its register block as an MMIO resource, plus the GSI /// its comparators can raise. /// @@ -1253,16 +1144,6 @@ fn readCntRegister(base: [*]align(1) const u8, len: usize, xoff: usize, legacy_o /// The mapped configuration space of one PCI function (its 4 KiB ECAM page). Mapped /// writable so BAR sizing can probe it; reads and writes both go through here. -fn pciConfigurationPtr(alloc: McfgAllocation, hal: Hal, bus: u8, device: u8, function: u8) [*]align(1) u8 { - const physical = alloc.base_address + - (@as(u64, bus - alloc.start_bus) << 20) + - (@as(u64, device) << 15) + - (@as(u64, function) << 12); - // Map the configuration page (writable, for BAR sizing) and use the virtual - // address the HAL hands back. - return @ptrFromInt(hal.mapMmio(physical, abi.page_size, true)); -} - /// Read a little-endian integer at `off` from a (possibly unaligned) byte pointer. /// x86 is little-endian and native, so an unaligned load suffices. fn rd(comptime T: type, bytes: [*]align(1) const u8, off: usize) T { @@ -1270,12 +1151,6 @@ fn rd(comptime T: type, bytes: [*]align(1) const u8, off: usize) T { return p.*; } -/// Write a little-endian integer at `off` through a (possibly unaligned) pointer. -fn wr(comptime T: type, bytes: [*]align(1) u8, off: usize, value: T) void { - const p: *align(1) T = @ptrCast(bytes + off); - p.* = value; -} - // --- tests ------------------------------------------------------------------ test "eisaIdToStr decodes a packed EISA id" { diff --git a/system/drivers/pci-bus/pci-bus.zig b/system/drivers/pci-bus/pci-bus.zig index d54aa3c..2c19c59 100644 --- a/system/drivers/pci-bus/pci-bus.zig +++ b/system/drivers/pci-bus/pci-bus.zig @@ -180,6 +180,7 @@ fn registerAndReport(bus: u64, dev: u64, function: u64, class_triple: u32) void configWrite(bus, dev, function, off, original); const mask = readback & 0xFFFF_FFFC; const size: u32 = if (mask == 0) 0 else (~mask +% 1) & 0xFFFF; + if (size == 0) continue; // unimplemented BAR — nothing to register descriptor.resources[slot] = .{ .kind = @intFromEnum(device.ResourceKind.io_port), .start = original & 0xFFFF_FFFC, .len = size }; descriptor.resource_count += 1; } else if ((original >> 1) & 0x3 == 2) { @@ -192,15 +193,17 @@ fn registerAndReport(bus: u64, dev: u64, function: u64, class_triple: u32) void configWrite(bus, dev, function, off + 4, original_high); const readback = (@as(u64, hi) << 32) | (lo & 0xFFFF_FFF0); const size: u64 = if (readback == 0) 0 else ~readback +% 1; + i += 1; // consumed the high half regardless + if (size == 0) continue; descriptor.resources[slot] = .{ .kind = @intFromEnum(device.ResourceKind.memory), .start = (@as(u64, original_high) << 32) | (original & 0xFFFF_FFF0), .len = size }; descriptor.resource_count += 1; - i += 1; // consumed the high half } else { configWrite(bus, dev, function, off, 0xFFFF_FFFF); const readback = configRead(bus, dev, function, off); configWrite(bus, dev, function, off, original); const mask = readback & 0xFFFF_FFF0; const size: u32 = if (mask == 0) 0 else ~mask +% 1; + if (size == 0) continue; descriptor.resources[slot] = .{ .kind = @intFromEnum(device.ResourceKind.memory), .start = original & 0xFFFF_FFF0, .len = size }; descriptor.resource_count += 1; } diff --git a/system/kernel/process.zig b/system/kernel/process.zig index d795ec4..a724862 100644 --- a/system/kernel/process.zig +++ b/system/kernel/process.zig @@ -298,6 +298,8 @@ fn systemDeviceEnumerate(state: *architecture.CpuState) void { /// device_claim(id) -> 0/-1: take exclusive ownership of a device for this process. fn systemDeviceClaim(state: *architecture.CpuState) void { + const claim_flags = sync.enter(); + defer sync.leave(claim_flags); if (devices_broker.claim(architecture.systemCallArg(state, 0), scheduler.current().id)) architecture.setSystemCallResult(state, 0) else @@ -312,10 +314,22 @@ fn systemMmioMap(state: *architecture.CpuState) void { const resource_index = architecture.systemCallArg(state, 1); const t = scheduler.current(); if (t.aspace == 0) return fail(state); - const owner = devices_broker.ownerOf(device_id) orelse return fail(state); - if (owner != t.id) return fail(state); // not claimed by this process - const r = devices_broker.resourceOf(device_id, resource_index) orelse return fail(state); + // Read the broker table under the lock: ring-3 device_register (M19) now + // mutates it concurrently on other cores, so a lock-free read here could + // see a torn resource (and a torn length used to panic the arithmetic + // below on integer overflow). + const r = blk: { + const flags = sync.enter(); + defer sync.leave(flags); + const owner = devices_broker.ownerOf(device_id) orelse return fail(state); + if (owner != t.id) return fail(state); // not claimed by this process + break :blk devices_broker.resourceOf(device_id, resource_index) orelse return fail(state); + }; if (r.kind != @intFromEnum(device_abi.ResourceKind.memory)) return fail(state); + // A zero-length or wrapping window is not mappable — fail cleanly rather + // than underflow `r.len - 1`. + if (r.len == 0) return fail(state); + if (@addWithOverflow(r.start, r.len)[1] != 0) return fail(state); if (t.device_map_next == 0) t.device_map_next = device_arena_base; const first = r.start & ~@as(u64, page_size - 1); @@ -451,6 +465,11 @@ fn systemDeviceRegister(state: *architecture.CpuState) void { var descriptor: device_abi.DeviceDescriptor = undefined; if (!ipc.copyFromUser(t.aspace, descriptor_ptr, std.mem.asBytes(&descriptor))) return fail(state); + // Under the big kernel lock: the broker's table is also mutated by the + // death sweep (releaseAllOwnedBy) and read by enumerate on other cores — + // ring-3 registration (M19) made those genuinely concurrent. + const flags = sync.enter(); + defer sync.leave(flags); const id = devices_broker.register(parent_id, t.id, &descriptor) catch return fail(state); architecture.setSystemCallResult(state, id); } diff --git a/system/kernel/tests.zig b/system/kernel/tests.zig index 2a3b497..47a6596 100644 --- a/system/kernel/tests.zig +++ b/system/kernel/tests.zig @@ -270,20 +270,28 @@ fn discoveryTest() void { // M15: every PCI function now carries its own 4 KiB ECAM configuration space as // resource 0 — the window a driver mmio_maps to walk its capability list (MSI etc). + // M19.3: the kernel seeds only the bridge; functions arrive by the ring-3 + // scan (proven equivalent in pci-scan before the walk retired). var buffer: [64]device_abi.DeviceDescriptor = undefined; const n = @min(devices_broker.enumerate(&buffer), buffer.len); - var pci_functions: u32 = 0; - var pci_config_ok = true; + var bridges: u32 = 0; + var bridge_shape_ok = false; for (buffer[0..n]) |d| { - if (d.class != @intFromEnum(device_abi.DeviceClass.pci_device)) continue; - pci_functions += 1; - const has_config = d.resource_count >= 1 and - d.resources[0].kind == @intFromEnum(device_abi.ResourceKind.memory) and - d.resources[0].len == abi.page_size; - if (!has_config) pci_config_ok = false; + if (d.class != @intFromEnum(device_abi.DeviceClass.pci_host_bridge)) continue; + bridges += 1; + var has_bus_range = false; + var has_io = false; + var memory_windows: u32 = 0; + for (d.resources[0..@intCast(d.resource_count)]) |resource| { + if (resource.kind == @intFromEnum(device_abi.ResourceKind.bus_range)) has_bus_range = true; + if (resource.kind == @intFromEnum(device_abi.ResourceKind.io_port)) has_io = true; + if (resource.kind == @intFromEnum(device_abi.ResourceKind.memory)) memory_windows += 1; + } + // ECAM plus at least one MMIO aperture, the bus range, the I/O window. + if (has_bus_range and has_io and memory_windows >= 2) bridge_shape_ok = true; } - check("PCI functions were enumerated (MCFG/ECAM)", pci_functions >= 1); - check("each PCI function exposes its ECAM config space as resource 0", pci_config_ok); + check("a PCI host bridge was seeded (MCFG)", bridges >= 1); + check("the bridge carries ECAM, apertures, bus range, and the I/O window", bridge_shape_ok); // M19.0: every PCI memory resource (config slice and BARs alike) must be // contained in one of its parent bridge's windows — the aperture derivation @@ -1814,20 +1822,16 @@ fn pciScanTest(boot_information: *const BootInformation) void { return; }; - // What the kernel found: the expected marker is built from its own count. + // Post-flip (M19.3) ground truth: the kernel no longer enumerates PCI + // functions, so equivalence inverts — the broker's function count after + // the scan must equal what the driver itself reported finding. var buffer: [64]device_abi.DeviceDescriptor = undefined; const n = @min(devices_broker.enumerate(&buffer), buffer.len); - var kernel_count: u32 = 0; + var boot_pci: u32 = 0; for (buffer[0..n]) |d| { - if (d.class == @intFromEnum(device_abi.DeviceClass.pci_device)) kernel_count += 1; + if (d.class == @intFromEnum(device_abi.DeviceClass.pci_device)) boot_pci += 1; } - check("the kernel enumerated PCI functions to compare against", kernel_count >= 1); - var marker_buffer: [48]u8 = undefined; - const marker = std.fmt.bufPrint(&marker_buffer, "pci-bus: {d} functions found", .{kernel_count}) catch { - check("marker formatted", false); - result(); - return; - }; + check("the kernel seeded no PCI functions (the walk retired)", boot_pci == 0); process.setInitialRamdisk(image); process.write_count = 0; @@ -1841,16 +1845,35 @@ fn pciScanTest(boot_information: *const BootInformation) void { } check("device-manager spawned (test-pci-restart mode)", manager != 0); - // First scan: the ring-3 count equals the kernel's. + // First scan: wait for the driver's count line and parse the number. + const count_prefix = "pci-bus: "; + const count_suffix = " functions found"; + var reported: u32 = 0; scheduler.setPriority(1); var deadline = architecture.millis() + 15000; - var seen = false; - while (architecture.millis() < deadline and !seen) { - if (process.write_len >= marker.len and eql(process.write_buffer[0..marker.len], marker)) seen = true; + while (architecture.millis() < deadline and reported == 0) { + if (process.write_len > count_prefix.len + count_suffix.len and eql(process.write_buffer[0..count_prefix.len], count_prefix)) { + const line = process.write_buffer[0..process.write_len]; + const digits_end = std.mem.indexOf(u8, line, count_suffix) orelse { + scheduler.yield(); + continue; + }; + reported = std.fmt.parseInt(u32, line[count_prefix.len..digits_end], 10) catch 0; + } scheduler.yield(); } scheduler.setPriority(4); - check("the ring-3 scan found exactly the kernel's function count", seen); + check("the ring-3 scan reported a function count", reported >= 1); + + // Every reported function was registered: the broker holds exactly them. + var registered: [64]device_abi.DeviceDescriptor = undefined; + const r = @min(devices_broker.enumerate(®istered), registered.len); + var registered_pci: u32 = 0; + for (registered[0..r]) |d| { + if (d.class == @intFromEnum(device_abi.DeviceClass.pci_device)) registered_pci += 1; + } + check("the broker holds exactly the reported functions", registered_pci == reported); + const kernel_count = reported; // the no-duplicate check below reuses it // The restart drill: the manager kills pci-bus after its reports; the // respawn re-claims, re-scans, and re-registers. @@ -1865,9 +1888,11 @@ fn pciScanTest(boot_information: *const BootInformation) void { scheduler.setPriority(4); check("the manager restarted pci-bus", restarted); + var marker_buffer: [48]u8 = undefined; + const marker = std.fmt.bufPrint(&marker_buffer, "pci-bus: {d} functions found", .{reported}) catch ""; scheduler.setPriority(1); deadline = architecture.millis() + 15000; - seen = false; + var seen = false; while (architecture.millis() < deadline and !seen) { if (process.write_len >= marker.len and eql(process.write_buffer[0..marker.len], marker)) seen = true; scheduler.yield(); diff --git a/system/services/device-manager/device-manager.zig b/system/services/device-manager/device-manager.zig index 178cef0..ed2b4cb 100644 --- a/system/services/device-manager/device-manager.zig +++ b/system/services/device-manager/device-manager.zig @@ -50,17 +50,26 @@ fn driverFor(d: device.DeviceDescriptor) ?[]const u8 { /// pci-class.zig decodes. const xhci_pci_class: u64 = 0x0C_03_30; -/// The bus driver that serves a PCI function, or null. A machine can carry -/// several identical controllers — one driver instance per device, the id as -/// argv[1]. These drivers speak the protocol: a hello is expected. -fn pciDriverFor(d: device.DeviceDescriptor) ?[]const u8 { - if (d.class != @intFromEnum(device.DeviceClass.pci_device)) return null; - return switch (d.pci_class) { +/// 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 => "usb-xhci-bus", else => null, }; } +/// Whether some driver entry already serves registered device `device_id` — +/// a re-report after a bus restart must not spawn a second instance. +fn driverForDevice(device_id: u64) bool { + for (&drivers) |*driver| { + if (driver.used and driver.device_id == device_id) return true; + } + return false; +} + // --- supervision ------------------------------------------------------------- /// How long a protocol driver has to hello after its spawn. @@ -326,11 +335,8 @@ fn initialise(endpoint: runtime.ipc.Handle) bool { addDriver("pci-bus", descriptor.id, true); continue; } - if (pciDriverFor(descriptor)) |driver_name| { - matched += 1; - addDriver(driver_name, descriptor.id, true); - continue; - } + // PCI functions no longer appear in the boot snapshot (M19.3): the + // pci-bus driver reports them, and onChildAdded matches from reports. const driver_name = driverFor(descriptor) orelse continue; matched += 1; // Skip a singleton that is already alive (the initial-ramdisk sweep test @@ -396,6 +402,14 @@ 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; writeLine("device-manager: child added (device {d} port {d}, identity {d}) by {s}\n", .{ report.parent, report.bus_address, report.identity, driver.name() }); if (status == 0) publishEvent(message[0..protocol.child_added_size]); + // Matching from reports (M19.3): a registered child whose identity + // names a driver gets one, once — re-reports after a bus restart + // dedupe on the registered id, exactly like the registrations do. + if (status == 0 and report.device_id != protocol.no_device) { + if (pciDriverForIdentity(report.identity)) |child_driver| { + if (!driverForDevice(report.device_id)) addDriver(child_driver, report.device_id, true); + } + } } else { status = -1; }