Two parts, both blockers for real PCI drivers. ECAM config space per function: enumeratePci now gives every pci_device its own 4 KiB configuration window as resource 0. A claimed PCI driver mmio_maps that to reach its command register, BARs, and — the point — its capability list (MSI/MSI-X, PCIe extended caps), with no new syscall. Verified in the discovery test (QEMU q35's functions each carry it). MSI: msi_bind(device_id, endpoint) -> address (rax), data (rdx) allocates a per-device edge-triggered vector, binds it to the endpoint, and returns the (address, data) the driver programs into its own MSI capability. Unlike irq_bind there's no GSI, no I/O APIC entry, no sharing, and no ack cycle — dispatch recognises an MSI vector (vector_gsi == none, msi_bound set), EOIs, and notifies. Owner-keyed release drops the binding on exit. Legacy INTx (_PRT parsing + shared lines) is deliberately skipped; MSI is the real answer. QEMU's HPET has no MSI, so the new `msi` test proves the vector-routing path with a self-IPI (new apic.selfIpi) standing in for the device's MSI write: bind a vector, fire it, the bound endpoint is notified. The msi_bind syscall wraps irq.msiBind with the claim check and lands its first real use with the first PCI driver. Suite 39/39 plus host tests.
93 lines
4.5 KiB
Zig
93 lines
4.5 KiB
Zig
//! User-space device access: enumerate the kernel's device table, claim a device,
|
|
//! map its MMIO, and bind its interrupt. A driver uses these to find and take
|
|
//! ownership of its hardware; the claim is the capability the kernel checks before
|
|
//! mapping registers or routing an IRQ.
|
|
|
|
const abi = @import("abi");
|
|
const device_abi = @import("device-abi");
|
|
const sc = @import("system-call.zig");
|
|
|
|
pub const DeviceDescriptor = device_abi.DeviceDescriptor;
|
|
pub const ResourceDescriptor = device_abi.ResourceDescriptor;
|
|
pub const DeviceClass = device_abi.DeviceClass;
|
|
pub const ResourceKind = device_abi.ResourceKind;
|
|
|
|
inline fn failed(r: usize) bool {
|
|
return r > ~@as(usize, 0) - 4095;
|
|
}
|
|
|
|
/// Copy up to `buffer.len` device descriptors into `buffer`; returns the total count.
|
|
pub fn enumerate(buffer: []DeviceDescriptor) usize {
|
|
return sc.systemCall2(.device_enumerate, @intFromPtr(buffer.ptr), buffer.len);
|
|
}
|
|
|
|
/// Take exclusive ownership of device `id`. Returns false if taken or invalid.
|
|
pub fn claim(id: u64) bool {
|
|
return !failed(sc.systemCall1(.device_claim, id));
|
|
}
|
|
|
|
/// Map resource `resource_index` (which must be an MMIO window) of claimed device
|
|
/// `device_id` into this address space; returns the register base virtual address.
|
|
pub fn mmioMap(device_id: u64, resource_index: u64) ?usize {
|
|
const r = sc.systemCall2(.mmio_map, device_id, resource_index);
|
|
return if (failed(r)) null else r;
|
|
}
|
|
|
|
/// `DeviceDescriptor.parent` for a device with no parent.
|
|
pub const no_parent = device_abi.no_parent;
|
|
|
|
/// Publish `descriptor` as a child of `parent_id`, which this process must have claimed.
|
|
/// Returns the new device id. The child is left unclaimed, so whichever driver owns
|
|
/// that class of device can `claim` it — that is how a bus hands off a device.
|
|
///
|
|
/// Every resource in `descriptor` must be **contained** in a parent resource of the same
|
|
/// kind: a sub-window of the parent's MMIO, or one of its IRQs. The kernel refuses
|
|
/// anything else, because a device descriptor is a licence to map physical memory and
|
|
/// a bus driver may only subdivide what it already owns. `descriptor.id` and `descriptor.parent`
|
|
/// are ignored. A device with no resources at all is fine — a USB device is reached
|
|
/// through its controller, not by MMIO.
|
|
pub fn register(parent_id: u64, descriptor: *const DeviceDescriptor) ?u64 {
|
|
const r = sc.systemCall2(.device_register, parent_id, @intFromPtr(descriptor));
|
|
return if (failed(r)) null else r;
|
|
}
|
|
|
|
/// Bind resource `resource_index` (which must be an IRQ) of claimed device `device_id` to
|
|
/// `endpoint`. From then on the interrupt arrives as an asynchronous notification:
|
|
/// `ipc.replyWait` on that endpoint returns with the high bit set in `badge` and the
|
|
/// low bits carrying the GSI. The kernel masks the line before waking you.
|
|
pub fn irqBind(device_id: u64, resource_index: u64, endpoint: usize) bool {
|
|
return !failed(sc.systemCall3(.irq_bind, device_id, resource_index, endpoint));
|
|
}
|
|
|
|
/// Re-arm a bound IRQ. Call this **after** quieting the device (clearing whatever
|
|
/// status register holds its line asserted) — the kernel left the line masked
|
|
/// precisely because it could not do that for you. Skip it and the interrupt never
|
|
/// fires again; call it before the device is quiet and a level-triggered line storms.
|
|
pub fn irqAck(device_id: u64, resource_index: u64) bool {
|
|
return !failed(sc.systemCall2(.irq_ack, device_id, resource_index));
|
|
}
|
|
|
|
/// The Message-Signalled Interrupt address/data a driver programs into its device's
|
|
/// MSI capability. The device raises the interrupt by writing `data` to `address`.
|
|
pub const Msi = struct { address: u64, data: u32 };
|
|
|
|
/// Set up MSI for a claimed device: the kernel allocates a per-device edge-triggered
|
|
/// vector, binds it to `endpoint` (delivered like `irqBind`, but with no mask and no
|
|
/// `irqAck` cycle), and returns the (address, data) to write into the device's MSI
|
|
/// capability — found by mmio_mapping the device's ECAM config space (resource 0) and
|
|
/// walking its capability list. Returns null on failure. Two return values (address in
|
|
/// rax, data in rdx), so a hand-written stub.
|
|
pub fn msiBind(device_id: u64, endpoint: usize) ?Msi {
|
|
var rax: usize = undefined;
|
|
var rdx: usize = undefined;
|
|
asm volatile ("syscall"
|
|
: [rax] "={rax}" (rax),
|
|
[rdx] "={rdx}" (rdx),
|
|
: [n] "{rax}" (@intFromEnum(abi.SystemCall.msi_bind)),
|
|
[a0] "{rdi}" (device_id),
|
|
[a1] "{rsi}" (endpoint),
|
|
: .{ .rcx = true, .r11 = true, .memory = true });
|
|
if (failed(rax)) return null;
|
|
return .{ .address = rax, .data = @intCast(rdx) };
|
|
}
|