//! 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 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)); }