//! 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` + `runtime.device`. const std = @import("std"); const runtime = @import("runtime"); const mmio = @import("mmio"); const pci_class = @import("pci-class"); const device = runtime.device; /// 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 }; } };