reorg: extract library/device/pci — the claimed-function view
New pci logic module (library/device/pci/pci.zig): a device driver's view of the one PCI function it has claimed — Function.map (config space = resource 0), vendorId/deviceId/command/status, enableMemoryAndBusMaster, mapBar (BAR decode + resource correlation + mmio_map, cached), and a capabilities() iterator. The generic PCI mechanics every leaf PCI driver used to re-derive inline. The config-space layout it needs — header offsets, the command MEM|bus-master bits, the status capabilities-list bit, the capability-pointer mask, and the BAR bit fields — is named in the pci-class data module (a "Configuration-space layout" section), so the bus enumerator can share the same constants later. virtio-gpu is the first consumer: its inline mapBar + walkCapabilities + config header reads are gone, replaced by pci.Function; only the virtio-specific cfg_type dispatch (and the virtio common-config cfgRead/cfgWrite, which are NOT PCI config space) stay in the driver. The generic display driver will use the same module. pci-bus's enumerator (arbitrary-function probing) is untouched. zig build + test green; virtio-gpu, display-native, display-reattach, pci-scan pass.
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
const std = @import("std");
|
||||
const runtime = @import("runtime");
|
||||
const mmio = @import("mmio");
|
||||
const pci = @import("pci");
|
||||
const device = runtime.device;
|
||||
const dma = runtime.dma;
|
||||
const shared_memory = runtime.shared_memory;
|
||||
@@ -84,10 +85,6 @@ var notify_base: usize = 0;
|
||||
var notify_multiplier: u32 = 0;
|
||||
var notify_addr: usize = 0;
|
||||
|
||||
// Per-BAR mapping cache: several capabilities usually share one BAR, and mmio_map must not
|
||||
// be asked to map the same resource twice.
|
||||
var bar_virtual: [6]usize = .{ 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
// DMA memory: the virtqueue rings and the command scratch.
|
||||
var ring: dma.Region = undefined;
|
||||
var command: dma.Region = undefined;
|
||||
@@ -121,75 +118,6 @@ fn orStatus(bit: u8) void {
|
||||
cfgWrite(u8, "device_status", cfgRead(u8, "device_status") | bit);
|
||||
}
|
||||
|
||||
// --- PCI config-space capability walk (config space is resource 0) ---------------------
|
||||
|
||||
/// Map the BAR numbered `bar` (0..5) and return its virtual base, correlating the BAR's
|
||||
/// physical address (read from config space) with one of our device resources — because a
|
||||
/// virtio capability names a BAR *number*, while `mmio_map` takes a *resource index* (and
|
||||
/// resource 0 is config space, so BAR resources are re-numbered and gaps skipped).
|
||||
fn mapBar(config: usize, descriptor: *const device.DeviceDescriptor, bar: u8) ?usize {
|
||||
if (bar >= 6) return null;
|
||||
if (bar_virtual[bar] != 0) return bar_virtual[bar];
|
||||
|
||||
const low = mmio.read(u32, config + 0x10 + @as(usize, bar) * 4);
|
||||
if (low & 0x1 != 0) return null; // an I/O-space BAR — virtio structures are in memory BARs
|
||||
var base: u64 = low & 0xFFFF_FFF0;
|
||||
if ((low & 0x6) == 0x4) { // 64-bit memory BAR: the high half is the next dword
|
||||
const high = mmio.read(u32, config + 0x10 + (@as(usize, bar) + 1) * 4);
|
||||
base |= @as(u64, high) << 32;
|
||||
}
|
||||
|
||||
for (descriptor.resources[0..@intCast(descriptor.resource_count)], 0..) |resource, index| {
|
||||
if (resource.kind == @intFromEnum(device.ResourceKind.memory) and resource.start == base) {
|
||||
const v = device.mmioMap(device_id, index) orelse return null;
|
||||
bar_virtual[bar] = v;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
std.log.info("BAR {d} (physical 0x{x}) is not a mapped resource", .{ bar, base });
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Walk the PCI capability list from mapped config space, recording the common-config and
|
||||
/// notify structures (the only two V3 needs). Returns false if either is missing.
|
||||
fn walkCapabilities(config: usize, descriptor: *const device.DeviceDescriptor) bool {
|
||||
if (mmio.read(u16, config + 0x06) & 0x10 == 0) { // Status bit 4: capabilities list present
|
||||
std.log.info("device has no PCI capability list", .{});
|
||||
return false;
|
||||
}
|
||||
var cap: u8 = @as(u8, @truncate(mmio.read(u8, config + 0x34))) & 0xFC;
|
||||
var guard: u32 = 0;
|
||||
while (cap != 0 and guard < 48) : (guard += 1) {
|
||||
const at = config + cap;
|
||||
const id = mmio.read(u8, at + 0);
|
||||
const next = mmio.read(u8, at + 1) & 0xFC;
|
||||
// Only map BARs for the structures V3 uses (common + notify). The other virtio
|
||||
// capabilities (isr, device, and especially the cfg_pci back-door, which carries a
|
||||
// placeholder bar=0/offset=0) reference BARs we never touch, so mapping them would
|
||||
// just log spurious "not a mapped resource" noise.
|
||||
if (id == vp.pci_cap_vendor) {
|
||||
const cfg_type = mmio.read(u8, at + 3);
|
||||
if (cfg_type == vp.cfg_common or cfg_type == vp.cfg_notify) {
|
||||
const bar = mmio.read(u8, at + 4);
|
||||
const offset = mmio.read(u32, at + 8);
|
||||
if (mapBar(config, descriptor, bar)) |bar_base| {
|
||||
if (cfg_type == vp.cfg_common) {
|
||||
common_base = bar_base + offset;
|
||||
} else {
|
||||
notify_base = bar_base + offset;
|
||||
notify_multiplier = mmio.read(u32, at + 16); // virtio_pci_notify_cap tail
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cap = next;
|
||||
}
|
||||
if (common_base == 0 or notify_base == 0) {
|
||||
std.log.info("missing common-config or notify capability", .{});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- the control virtqueue -------------------------------------------------------------
|
||||
|
||||
@@ -283,19 +211,43 @@ fn initialise(endpoint: ipc.Handle) bool {
|
||||
// Config space is resource 0. Confirm it really is a virtio-gpu, then enable memory-space
|
||||
// decode + bus mastering (the device DMAs the ring and backing out of RAM); pci-bus only
|
||||
// preserves whatever the firmware left, and a secondary display is often left disabled.
|
||||
const config = device.mmioMap(device_id, 0) orelse {
|
||||
var function = pci.Function.map(device_id, descriptor) orelse {
|
||||
std.log.info("config-space map failed", .{});
|
||||
return false;
|
||||
};
|
||||
const vendor = mmio.read(u16, config + 0x00);
|
||||
const dev = mmio.read(u16, config + 0x02);
|
||||
const vendor = function.vendorId();
|
||||
const dev = function.deviceId();
|
||||
if (vendor != virtio_vendor or dev != virtio_gpu_device) {
|
||||
std.log.info("not a virtio-gpu (vendor 0x{x} device 0x{x})", .{ vendor, dev });
|
||||
return false;
|
||||
}
|
||||
mmio.write(u16, config + 0x04, mmio.read(u16, config + 0x04) | 0x06); // MEM + bus master
|
||||
function.enableMemoryAndBusMaster();
|
||||
|
||||
if (!walkCapabilities(config, descriptor)) return false;
|
||||
// Walk the capability list for the virtio common-config and notify structures (V3 needs
|
||||
// only those two). The generic PCI mechanics — header fields, BAR decode, the capability
|
||||
// chain — are library/device/pci; the virtio cfg_type dispatch stays here. We map only the
|
||||
// common/notify BARs (the other virtio caps, and the cfg_pci back-door's placeholder
|
||||
// bar=0/offset=0, reference BARs we never touch, so mapping them would only log noise).
|
||||
var caps = function.capabilities();
|
||||
while (caps.next()) |cap| {
|
||||
if (cap.id != vp.pci_cap_vendor) continue;
|
||||
const cfg_type = mmio.read(u8, cap.offset + 3);
|
||||
if (cfg_type != vp.cfg_common and cfg_type != vp.cfg_notify) continue;
|
||||
const bar = mmio.read(u8, cap.offset + 4);
|
||||
const offset = mmio.read(u32, cap.offset + 8);
|
||||
if (function.mapBar(bar)) |bar_base| {
|
||||
if (cfg_type == vp.cfg_common) {
|
||||
common_base = bar_base + offset;
|
||||
} else {
|
||||
notify_base = bar_base + offset;
|
||||
notify_multiplier = mmio.read(u32, cap.offset + 16); // virtio_pci_notify_cap tail
|
||||
}
|
||||
}
|
||||
}
|
||||
if (common_base == 0 or notify_base == 0) {
|
||||
std.log.info("missing common-config or notify capability", .{});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset, then the modern feature handshake: acknowledge, take driver ownership, require
|
||||
// VERSION_1 and offer nothing else, and confirm the device accepts that.
|
||||
|
||||
Reference in New Issue
Block a user