mmio is device-driver code, so it joins the other domains under
library/device/mmio/ (module name "mmio" unchanged — a pure relocation, only
the build paths move). And its abbreviated function names are spelled out per
docs/coding-standards.md:
read -> readRegister mb -> memoryBarrier
write -> writeRegister rmb -> readMemoryBarrier
wmb -> writeMemoryBarrier
All call sites updated (virtio-gpu, usb-xhci-library, pci.Function); the two
display-driver placeholders import mmio but use nothing, so they're untouched.
Docs (driver-model graph, README layout, drivers.md, the FHS note) follow the
new path and names.
zig build + test green; virtio-gpu, display-native, display-reattach, usb-hid,
usb-hub, usb-storage, pci-scan pass.
110 lines
5.5 KiB
Zig
110 lines
5.5 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` + `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 };
|
|
}
|
|
};
|