iommu: enable Intel VT-d translation with per-claim device confinement
First enforcement step of the IOVA track. A vendor-neutral IOMMU core (iommu.zig) drives an Intel VT-d backend (iommu-intel.zig) to give DMA a real translation layer instead of the fail-open free-for-all M16 left. - Boot posture is now stated explicitly: "iommu online (Intel VT-d)" with version/agaw/rmrr, or "none present - DMA fail-open (unisolated)". - DMAR parsing extended to select the INCLUDE_PCI_ALL unit (real Intel PCs put an iGPU-scoped unit first) and record single-path-endpoint RMRRs; multi-hop scopes and extra DRHDs are counted and warned, never silently dropped. - Translation is enabled at boot into a blanket identity domain (all RAM + RMRRs, 2 MiB leaves). PCI functions are enumerated post-boot by the ring-3 pci-bus driver, so a device is attached to the domain when its driver claims it (confineDevice, with claim rollback if confinement fails) and detached on driver death, before broker release and DMA frame teardown. Unclaimed devices are non-present: their DMA faults. - Interrupt remapping stays off, so MSI writes to 0xFEE00000 bypass translation and the interrupt-driven xHC keeps working. - devices-broker gains pciAddressOf (derives BDF from the config-space ECAM offset), unclaim, and forEachPciFunction. Faults are drained and logged rate-limited as DANOS-IOMMU-FAULT. Cases: iommu extended (translation on, scratch-domain map/resolve/unmap, zero idle faults); new iommu-usb-storage and iommu-usb-hid run the full storage + input stacks through translated DMA with MSI intact. 103/103.
This commit is contained in:
+31
-3
@@ -18,6 +18,7 @@ const wall_clock = @import("wall-clock.zig");
|
||||
const devices_broker = @import("devices-broker.zig");
|
||||
const platform = @import("platform");
|
||||
const pmm = @import("pmm.zig");
|
||||
const iommu = @import("iommu.zig");
|
||||
const heap = @import("heap.zig");
|
||||
const scheduler = @import("scheduler.zig");
|
||||
const ipc = @import("ipc.zig");
|
||||
@@ -1166,6 +1167,11 @@ fn dmaTest() void {
|
||||
log("DANOS-TEST-BEGIN: dma\n", .{});
|
||||
const base_free = pmm.stats().free_frames;
|
||||
|
||||
// This case boots WITHOUT an IOMMU device, so it is the explicit witness of the
|
||||
// fail-open posture: no unit found, and the kernel said so at boot (the harness
|
||||
// asserts the boot line; this check pins the recorded state to it).
|
||||
check("no IOMMU present: DMA runs fail-open", !platform.platformInformation().iommu_present);
|
||||
|
||||
// A contiguous run: aligned, and it consumed exactly that many frames.
|
||||
const frames = 4;
|
||||
const phys = pmm.allocContiguous(frames, ~@as(u64, 0)) orelse {
|
||||
@@ -1236,9 +1242,10 @@ fn msiTest() void {
|
||||
|
||||
/// IOMMU (M16): with an emulated VT-d unit present (the harness boots this case with
|
||||
/// `-device intel-iommu`), danos must find it in the ACPI DMAR table, map its register
|
||||
/// block, and read back a real version. This is *detection*, the honest first step —
|
||||
/// no translation domains are programmed yet, so DMA is still unprotected; enforcement
|
||||
/// lands with the first DMA driver (docs/driver-model.md M16).
|
||||
/// block, and read back a real version. Detection was M16's honest first step; the
|
||||
/// IOVA-enforcement track extends this case milestone by milestone (translation
|
||||
/// enabled, then per-device domains) — see the plan in docs and the fail-open witness
|
||||
/// in dmaTest.
|
||||
fn iommuTest() void {
|
||||
log("DANOS-TEST-BEGIN: iommu\n", .{});
|
||||
const pinfo = platform.platformInformation();
|
||||
@@ -1246,6 +1253,27 @@ fn iommuTest() void {
|
||||
check("VT-d unit has a register base", pinfo.iommu_base != 0);
|
||||
check("VT-d version register reads back nonzero (real, mappable unit)", pinfo.iommu_version != 0);
|
||||
log("DANOS-IOMMU: base=0x{x} version=0x{x} capabilities=0x{x}\n", .{ pinfo.iommu_base, pinfo.iommu_version, pinfo.iommu_capabilities });
|
||||
|
||||
// Translation was enabled at boot (kernel.zig: iommu.init before any driver claims
|
||||
// a device). The blanket domain keeps every device identity-mapped, so DMA still
|
||||
// works, but the unit is live — and with only the boot-time mappings present, no
|
||||
// device should have faulted yet.
|
||||
check("IOMMU enabled (translation on)", iommu.enabled());
|
||||
check("no spurious translation faults at idle", iommu.faultDrain() == 0);
|
||||
|
||||
// A scratch domain proves the walker + invalidation path end to end: create it,
|
||||
// identity-map a page, and confirm the mapping resolves; then unmap and destroy.
|
||||
if (iommu.domainCreate(0, 0)) |scratch| {
|
||||
const scratch_phys: u64 = 0x0010_0000; // 1 MiB, page-aligned
|
||||
check("map into a scratch domain succeeds", iommu.map(scratch, scratch_phys, abi.page_size));
|
||||
check("scratch domain resolves the mapping", iommu.translationOf(scratch, scratch_phys) == scratch_phys);
|
||||
iommu.unmap(scratch, scratch_phys, abi.page_size);
|
||||
check("scratch domain drops the mapping", iommu.translationOf(scratch, scratch_phys) == null);
|
||||
iommu.domainDestroy(scratch);
|
||||
} else {
|
||||
check("scratch domain allocated", false);
|
||||
}
|
||||
log("DANOS-IOMMU: enabled base=0x{x} domains active\n", .{pinfo.iommu_base});
|
||||
result();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user