Kore ABI
Devices
Userland drivers on Kore: claiming a device, registers, IOMMU-mediated DMA and remapped interrupts, and proving a device stopped, with no physical address ever reaching a process.
Source: device.rsDrivers run in user space
Kore's drivers are ordinary processes, so the device contract exists to make a faulty or hostile driver harmless to everything it wasn't handed. Three rules carry it:
- No physical address crosses the boundary. The wire carries stamped capabilities and op-codes, never a physical address, kernel pointer or borrow. A device-facing address is its own type,
IovaAddr: an I/O virtual address in the device's IOMMU domain, never a physical one. - The IOMMU is mandatory. Every DMA is translated by the IOMMU (Intel VT-d, AMD-Vi or Arm SMMU). A device's translation starts closed, so its DMA is blocked until a driver binds it, and there is no identity-mapped fallback.
- A driver gets exactly what it declares. Register windows, interrupt routes and DMA windows are derived from the device capability one at a time, and each is its own capability.
Where devices come from
The kernel enumerates the hardware; drivers claim it. Inventory has two kinds of root:
- One
RootComplexcapability per PCIe root, carrying its segment, bus range and NUMA node. Up to eight roots are provisioned; a machine with more enumerates the rest but can't hand their devices out, and says so. - One
PlatformBusfor devices firmware describes outside PCI.
A device broker holds these. Holding one grants no register, DMA or interrupt authority of its own, only the right to hand one of that root's devices to a driver, so authority across roots is refused by construction. InventoryReq names an entry by its index, which is local to the root presented, and either reads it or claims it. A claim hands the driver one affine device capability.
Devices come in two species, and the broker knows which before it derives anything:
| Species | Capability | Grants |
|---|---|---|
| PCI function | PciDevice | BARs, MSI and MSI-X routes, DMA windows |
| Platform device | PlatformDevice | Its declared I/O ports, one firmware-owned MMIO window, an optional interrupt line |
What a record tells you
DeviceRecord describes an entry before anyone claims it. It is data, never authority:
| Field | Meaning |
|---|---|
species, index | PCI or platform, and the entry's index, stable for the boot |
vendor, device | PCI ids, or a platform device's compressed ACPI id |
class, subclass, progif | The PCI class triple, or a platform role |
locator, segment | Bus, device and function (bus << 8 | device << 3 | function), or a platform device's interrupt line |
flags | The settled facts below |
msix_table_size | MSI-X table entries; zero means no usable table |
diag | Geometry facts a driver can quote when bring-up refuses: which BARs exist and which are mappable, where the MSI-X table lives |
revision | The PCI revision id |
| Flag | Set when |
|---|---|
HAS_BINDING | The device performs DMA through a live IOMMU binding |
HAS_REMAPPED_MSI | It can be given a remapped MSI or MSI-X route |
HAS_REMAPPED_MSIX | It can be given a remapped MSI-X route specifically |
HAS_BAR0 | BAR 0 exists and has a mappable range |
HAS_LINE | Firmware resolved its interrupt line |
DRIVER_ENDPOINT | The kernel's admission policy classed it as assignable to a driver |
IS_BRIDGE | It is PCI fabric, a host or PCI-to-PCI bridge |
Admission is positive: a device without DRIVER_ENDPOINT isn't claimable, so a display controller whose firmware ownership is unresolved stays withheld even though it isn't a bridge. There is deliberately no "live" flag. Whether a controller responds, and whether anything is attached to it, are questions only its class registers answer, and reading them is the driver's job once it holds the device.
Registers
- PCI.
BarReqclaims one of a function's six BARs as anMmioRegion; the uncached attributes come from the hardware claim, not the caller. The region is then mapped into an address space like any lease, withShareReq. - Platform devices. An
IoPortGrantcovers exactly the I/O ports the device declares: holding it means the kernel admits the task'sinandoutto those ports through the per-CPU I/O permission bitmap, and to no others. A platform device's one MMIO window is claimed and mapped like a BAR.
DMA
A DMA buffer is a lease, allocated with the device's DMA class and attributes, then mapped into the device's IOMMU domain with DeviceShareReq (24 bytes):
| Field | Meaning |
|---|---|
device | The arena slot of the device capability whose domain is the target |
iova | Where the device sees the window |
bytes | The window's length |
access | ACCESS_READ, ACCESS_READ_WRITE, or ACCESS_WRITE (posted writes only, no reads) |
The same frames can be mapped for the CPU too, so the driver and the device share the buffer without a copy. A driver's own DMA allocation owns both mappings through one affine sub-lease, so revoking that one name tears down the CPU and the IOMMU mapping together.
- Bus mastering.
DEVICE_ACTIVATEopens PCI bus mastering, and refuses until the driver holds a live DMA window for the device. Retiring that window closes bus mastering before it unmaps, so a device never masters into memory that's gone. - Device to device.
DERIVE_KIND_PEER_IOVAmaps an exact range of one device's BAR into another device's IOMMU domain. It needs three capabilities: the BAR, the requesting device, and aDeviceMmusponsor. A driver holds that sponsor once it has programmed its device's own translation (a GPU's page tables, an NPU's SMMU), and it vouches that the device is contained by it. Revoking the sponsor retires every peer window it vouched for. There is no whole-BAR shortcut and no list of trusted device classes. - Faults. The IOMMU's fault records are readable by tools that hold the right capability. They name IOVAs, never physical addresses, and grant no device authority.
Interrupts
IrqReq (8 bytes) derives an IrqRoute:
| Field | Meaning |
|---|---|
first_entry | The first MSI-X table entry; zero for MSI |
count | How many consecutive vectors, a non-zero power of two |
affinity | A preferred CPU, or IRQ_AFFINITY_ANY to let the kernel place it |
The route prefers MSI-X and falls back to MSI, always remapped through the IOMMU: the only interrupt posture offered to a user-space driver. IRQ_ROUTE_MSIX insists on MSI-X, and IRQ_ROUTE_LINE takes the firmware's line (INTx, or a platform device's interrupt).
A driver waits by parking with WAIT_IRQ; the interrupt wakes it, and the kernel is otherwise off the path. A driver with several routes adds WAIT_IRQ_SELECTED and names the one to wait on by its capability slot, never by a raw vector.
Stopping a device
DEVICE_QUIESCE asks the kernel to prove a device has stopped. It masks MSI and MSI-X, clears bus mastering and reads it back, and points the device's IOMMU context at Abort, then answers with a QuiesceReport:
| Verdict | Meaning |
|---|---|
| Proven | Bus mastering reads back clear and the device is bound to Abort: it provably issues no DMA |
| Contained | The device refused to drop bus mastering, but it's bound to Abort, so no DMA reaches memory; it stays quarantined |
The report's flags say which parts closed: interrupts masked, Abort bound, peer windows drained. It is the only way a quiesced state comes to exist: a driver asserts what it stopped, and the kernel proves what the fabric saw. It needs MODIFY on the device, so a holder narrowed to ACCESS can't drive it. It changes no power state; it is the DMA half of a suspend.
Observing devices
Two read-only surfaces describe hardware without granting access to it:
- The device census. One row per PCI function: identity, class, revision and which root owns it, for system tools such as a hardware list. It holds up to 120 rows and marks itself truncated beyond that rather than shortening silently. It is stamped into the process context of an executable that declares it; nothing on the way gets configuration-space access.
- I2C admission. Evidence pages that let an input driver confirm an I2C controller and its HID devices before claiming it.
Writing a driver
libkore's driver feature exposes kore-driver: submission and completion rings, cookie tables, typed register access, DMA pools, the common device host and bounded completion waits. The host contract is the same for every driver: consume one claimed device and a DMA supply, derive the declared windows and routes, bind an owner, quiesce through a class hook, and retire children first.