Files
danos/library/device/pci/pci.zig
Daniel Samson 23bcd77c58 C2: migrate consumers off the runtime shim to direct concern-module imports
Every user binary and the two device-logic library modules (pci, usb) now
`@import` the concern modules directly instead of aliasing through `runtime`:

  runtime.ipc/process/time/service/input/block/display  -> @import("<module>")
  runtime.device / runtime.device_manager               -> @import("driver")
  runtime.fs                                             -> @import("file-system")
  runtime.Thread                                         -> @import("thread").Thread
  runtime.system.{write,writeRecord,klog*}              -> logging.*
  runtime.system.{sleep,timerOnce,wallClock,clock}     -> time.*
  runtime.system.{spawn*,kill,exit,yield,processes,...}-> process.*
  runtime.system.{mmap,munmap,PROT_*}                  -> memory.*
  runtime.dma.* / runtime.shared_memory.* / runtime.allocator -> memory.*

Each consumer keeps its own alias name (e.g. `const device = @import("driver")`),
so call sites are unchanged and there are no collisions with local `driver`
variables. build.zig now injects the concern modules into every user binary via
`default_imports`; pci/usb module import lists were updated to match.

The `runtime` and `system` shims remain for one more step (root.zig still uses
runtime); they are deleted in C5. Nothing but root.zig imports `runtime` now.

Verified: zig build, zig build test, and 17 QEMU cases (smoke, device-manager,
logger, fat-mount, fat-mutations, usb-storage, usb-hid, display-native,
virtio-gpu, input, thread-spawn, thread-mutex, process-kill, shared-memory,
driver-restart, acpi-ps2, pci-scan).
2026-07-22 23:28:34 +01:00

109 lines
5.4 KiB
Zig

//! library/device/pci/pci.zig — a device driver's view of the ONE PCI function it has
//! claimed. Config space is mapped as resource 0; this gives header-field accessors, BAR
//! decode + map, and a capability-list iterator, so a driver never re-derives the
//! config-space layout by hand.
//!
//! This is the *device-owned* view: read my own function's live config, map my own BARs.
//! The bus enumerator's view — probing arbitrary, not-yet-claimed functions and sizing
//! their BARs — is a different mechanism and lives in the pci-bus driver. The pure
//! config-space layout both need (offsets, BAR bit fields) is named once in the `pci-class`
//! data module; this logic module adds the parts that need `mmio` + the `driver` client.
const std = @import("std");
const mmio = @import("mmio");
const pci_class = @import("pci-class");
const device = @import("driver");
/// A claimed PCI function whose configuration space is mapped (resource 0). `descriptor`
/// must outlive the Function — the driver's `device.enumerate` buffer does, for the whole
/// bring-up. Header reads and the capability walk hit live config space; `mapBar` caches.
pub const Function = struct {
device_id: u64,
descriptor: *const device.DeviceDescriptor,
config: usize, // virtual base of mapped resource 0
bar_virtual: [6]usize = .{ 0, 0, 0, 0, 0, 0 }, // per-BAR mmio_map cache
/// Map config space (resource 0) of the already-claimed `device_id`. null if the map
/// fails (not claimed, or no config resource).
pub fn map(device_id: u64, descriptor: *const device.DeviceDescriptor) ?Function {
const base = device.mmioMap(device_id, 0) orelse return null;
return .{ .device_id = device_id, .descriptor = descriptor, .config = base };
}
pub fn vendorId(self: *const Function) u16 {
return mmio.readRegister(u16, self.config + pci_class.config_vendor_id);
}
pub fn deviceId(self: *const Function) u16 {
return mmio.readRegister(u16, self.config + pci_class.config_device_id);
}
pub fn command(self: *const Function) u16 {
return mmio.readRegister(u16, self.config + pci_class.config_command);
}
pub fn status(self: *const Function) u16 {
return mmio.readRegister(u16, self.config + pci_class.config_status);
}
/// Set Memory-Space + Bus-Master enable in the command register. Firmware often leaves
/// a secondary display's decode off; a bus-mastering device must enable both.
pub fn enableMemoryAndBusMaster(self: *const Function) void {
const at = self.config + pci_class.config_command;
mmio.writeRegister(u16, at, mmio.readRegister(u16, at) | pci_class.command_memory_and_bus_master);
}
/// Decode BAR `bar` (0..5) and map it: read the BAR register, reject I/O-space BARs,
/// combine the high dword for a 64-bit BAR, mask the base, then correlate that physical
/// base with one of the descriptor's memory resources and `mmio_map` it — a BAR names a
/// *number*, while `mmio_map` takes a *resource index*, and gaps/config-space shift the
/// numbering. Cached per BAR. null if the BAR is I/O-space or is not a mapped resource.
pub fn mapBar(self: *Function, bar: u8) ?usize {
if (bar >= 6) return null;
if (self.bar_virtual[bar] != 0) return self.bar_virtual[bar];
const low = mmio.readRegister(u32, self.config + pci_class.config_bar0 + @as(usize, bar) * 4);
if (low & pci_class.bar_io_space != 0) return null; // an I/O-space BAR
var base: u64 = low & pci_class.bar_memory_base_mask;
if ((low & pci_class.bar_type_mask) == pci_class.bar_type_64bit) { // 64-bit: high half is the next dword
const high = mmio.readRegister(u32, self.config + pci_class.config_bar0 + (@as(usize, bar) + 1) * 4);
base |= @as(u64, high) << 32;
}
for (self.descriptor.resources[0..@intCast(self.descriptor.resource_count)], 0..) |resource, index| {
if (resource.kind == @intFromEnum(device.ResourceKind.memory) and resource.start == base) {
const v = device.mmioMap(self.device_id, index) orelse return null;
self.bar_virtual[bar] = v;
return v;
}
}
return null;
}
/// Iterate the capability list. Empty when the function advertises none.
pub fn capabilities(self: *const Function) CapabilityIterator {
const present = self.status() & pci_class.status_capabilities_list != 0;
const first = if (present)
mmio.readRegister(u8, self.config + pci_class.config_capabilities_pointer) & pci_class.capability_pointer_mask
else
0;
return .{ .config = self.config, .cursor = first };
}
};
/// One capability header. `offset` is the ABSOLUTE virtual address of the header, so the
/// caller reads its body with `mmio.readRegister(T, cap.offset + n)`.
pub const Capability = struct { id: u8, offset: usize };
pub const CapabilityIterator = struct {
config: usize,
cursor: u8,
guard: u32 = 0, // bounds a malformed/looping chain (48 = the 256-byte space in dwords)
pub fn next(self: *CapabilityIterator) ?Capability {
if (self.cursor == 0 or self.guard >= 48) return null;
self.guard += 1;
const at = self.config + self.cursor;
const id = mmio.readRegister(u8, at + 0);
self.cursor = mmio.readRegister(u8, at + 1) & pci_class.capability_pointer_mask;
return .{ .id = id, .offset = at };
}
};