//! /system/drivers/pci-bus — the PCI bus driver: enumeration moved out of ring 0 //! (docs/discovery.md). The device manager matches the `pci_host_bridge` //! node and spawns one instance per bridge, the bridge's device id as argv[1] — //! the same per-device contract as usb-xhci-bus. //! //! M19.1 (this increment): claim the bridge, map its ECAM window (resource 0; //! the bus range and the MMIO apertures follow it), walk every //! bus/device/function config header, and log what the walk finds — ending //! with "/system/drivers/pci-bus: N functions found", which the `pci-scan` scenario compares //! against the kernel's own enumeration. Registration and reports (M19.2), and //! the kernel walk's retirement (M19.3), build on this proven-equivalent scan. 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 memory = @import("memory"); const logging = @import("logging"); const device_manager_protocol = @import("device-manager-protocol"); const pci_class = @import("pci-class"); /// Log a discovered function as its would-be /system/configuration/devices.csv columns (bus, base, /// class, prog_if, vendor, device, subsystem) followed by the human-readable /// class/subclass/prog-IF names — so a row for a new driver reads straight off the /// boot log. `subsystem` prints as `*` when the function has none, matching the CSV /// wildcard. All read unclaimed, through the bridge's ECAM: the enumerator never /// claims the functions it probes (pci.zig's header — the device-owned pci.Function /// view is what needs a claim, not this one). A wide buffer: the names are long. fn logFunction(bus: u64, dev: u64, function: u64, class_triple: u32, vendor_id: u16, product_id: u16, subsystem: u32) void { const cc = pci_class.ClassCode.unpack(@truncate(class_triple)); const pif = pci_class.progIfName(cc.base, cc.subclass, cc.prog_if); var sub_buffer: [8]u8 = undefined; const sub = if (subsystem == 0) "*" else std.fmt.bufPrint(&sub_buffer, "{X:0>8}", .{subsystem}) catch "*"; var line: [320]u8 = undefined; const text = std.fmt.bufPrint(&line, "/system/drivers/pci-bus: {d}:{d}.{d} bus=pci base={X:0>2} class={X:0>2} prog_if={X:0>2} vendor={X:0>4} device={X:0>4} subsystem={s} — {s} / {s}{s}{s}\n", .{ bus, dev, function, cc.base, cc.subclass, cc.prog_if, vendor_id, product_id, sub, pci_class.className(cc.base), pci_class.subclassName(cc.base, cc.subclass), if (pif.len != 0) " / " else "", pif }) catch return; _ = logging.write(text); } var bridge_id: u64 = device_manager_protocol.no_device; var ecam_base: usize = 0; var ecam_physical: u64 = 0; var start_bus: u64 = 0; var bus_count: u64 = 0; var manager_handle: ipc.Handle = 0; /// One aligned 32-bit read from a function's configuration space. fn configRead(bus: u64, dev: u64, function: u64, offset: u64) u32 { const address = ecam_base + (((bus - start_bus) << 20) | (dev << 15) | (function << 12) | offset); const register: *volatile u32 = @ptrFromInt(address); return register.*; } fn configWrite(bus: u64, dev: u64, function: u64, offset: u64, value: u32) void { const address = ecam_base + (((bus - start_bus) << 20) | (dev << 15) | (function << 12) | offset); const register: *volatile u32 = @ptrFromInt(address); register.* = value; } fn configRead16(bus: u64, dev: u64, function: u64, offset: u64) u16 { const word = configRead(bus, dev, function, offset & ~@as(u64, 3)); return @truncate(word >> @intCast((offset & 3) * 8)); } fn configWrite16(bus: u64, dev: u64, function: u64, offset: u64, value: u16) void { const aligned = offset & ~@as(u64, 3); const shift: u5 = @intCast((offset & 3) * 8); const word = configRead(bus, dev, function, aligned); const mask = @as(u32, 0xFFFF) << shift; configWrite(bus, dev, function, aligned, (word & ~mask) | (@as(u32, value) << shift)); } /// Claim the bridge, map the ECAM, hello the manager, then scan. fn initialise(endpoint: ipc.Handle) bool { _ = endpoint; if (!device.claim(bridge_id)) { std.log.info("unable to claim bridge device {d}", .{bridge_id}); return false; } const buffer = memory.allocator().alloc(device.DeviceDescriptor, 64) catch { _ = logging.write("/system/drivers/pci-bus: out of memory\n"); return false; }; const total = device.enumerate(buffer); const descriptor = for (buffer[0..@min(total, buffer.len)]) |d| { if (d.id == bridge_id) break d; } else { std.log.info("device {d} not in the device tree", .{bridge_id}); return false; }; // Resource 0 is the ECAM window (1 MiB of config space per bus); the bus // range rides beside it. The MMIO apertures (M19.0) come after both. if (descriptor.resource_count < 2 or descriptor.resources[0].kind != @intFromEnum(device.ResourceKind.memory)) { _ = logging.write("/system/drivers/pci-bus: bridge has no ECAM window\n"); return false; } const bus_range = for (descriptor.resources[0..@intCast(descriptor.resource_count)]) |resource| { if (resource.kind == @intFromEnum(device.ResourceKind.bus_range)) break resource; } else { _ = logging.write("/system/drivers/pci-bus: bridge has no bus range\n"); return false; }; start_bus = bus_range.start; bus_count = bus_range.len; ecam_physical = descriptor.resources[0].start; ecam_base = device.mmioMap(bridge_id, 0) orelse { _ = logging.write("/system/drivers/pci-bus: ECAM mmio_map failed\n"); return false; }; // The handshake (role: bus — we enumerate PCI and report the functions we // find), then the scan. Keep the manager handle to report children through; // a supervised bus that cannot reach its manager has nothing to serve. manager_handle = device_manager.hello(.bus, bridge_id) orelse return false; scan(); return true; } /// The brute-force walk the kernel does today, from ring 3: every bus in the /// range, 32 devices, 8 functions; vendor id FFFFh means nothing decodes there, /// and only multifunction devices get their functions 1..7 probed. fn scan() void { var found: u32 = 0; var bus: u64 = start_bus; while (bus < start_bus + bus_count) : (bus += 1) { var dev: u64 = 0; while (dev < 32) : (dev += 1) { const first = configRead(bus, dev, 0, 0); if (first & 0xFFFF == 0xFFFF) continue; const multifunction = (configRead(bus, dev, 0, 0x0C) >> 16) & 0x80 != 0; var function: u64 = 0; while (function < 8) : (function += 1) { if (function != 0 and !multifunction) break; const vendor_device = configRead(bus, dev, function, 0); if (vendor_device & 0xFFFF == 0xFFFF) continue; const class_revision = configRead(bus, dev, function, 0x08); found += 1; registerAndReport(bus, dev, function, class_revision >> 8); } } } std.log.info("{d} functions found", .{found}); } /// Register one function under the bridge and report it to the manager. The /// descriptor mirrors the kernel's own recording byte for byte — config slice /// as resource 0, then the sized BARs — so during coexistence the idempotent /// device_register (M19.0) returns the kernel's existing node id rather than /// growing a duplicate, and the report carries the id drivers already use. 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 /system/configuration/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)), .len = 4096, }; descriptor.resource_count = 1; // The standard BAR-sizing probe, exactly as the kernel does it: decode off, // 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; while (i < 6) : (i += 1) { if (descriptor.resource_count >= 8) break; const off = 0x10 + i * 4; const original = configRead(bus, dev, function, off); if (original == 0) continue; const slot: usize = @intCast(descriptor.resource_count); if (original & 1 != 0) { configWrite(bus, dev, function, off, 0xFFFF_FFFF); const readback = configRead(bus, dev, function, off); 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) { const original_high = configRead(bus, dev, function, off + 4); configWrite(bus, dev, function, off, 0xFFFF_FFFF); configWrite(bus, dev, function, off + 4, 0xFFFF_FFFF); const lo = configRead(bus, dev, function, off); const hi = configRead(bus, dev, function, off + 4); configWrite(bus, dev, function, off, original); 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; } 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; } } configWrite16(bus, dev, function, 0x04, command); } // The devices.csv-column + friendly-name breadcrumb, now that vendor/device/ // subsystem are read. Every discovered function is logged, matched or not. logFunction(bus, dev, function, class_triple, descriptor.vendor, descriptor.device, descriptor.subsystem); const registered = device.register(bridge_id, &descriptor) orelse { std.log.info("register refused for {d}:{d}.{d}", .{ bus, dev, function }); 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 { std.log.info("child report for {d}:{d}.{d} failed", .{ bus, dev, function }); }; } fn onMessage(message: []const u8, reply: []u8, sender: u32, arrived: *ipc.Arrival) usize { _ = message; _ = reply; _ = sender; _ = arrived; // nothing here takes a capability: the harness closes what arrives return 0; } pub fn main(init: process.Init) void { const argument = init.arguments.get(1) orelse return; // bare (ramdisk sweep): stay silent bridge_id = std.fmt.parseInt(u64, argument, 10) catch { std.log.info("malformed bridge device id '{s}'", .{argument}); return; }; service.run(device_manager_protocol.message_maximum, .{ .init = initialise, .on_message = onMessage, }); }