M16: IOMMU detection (DMAR parsing)

Detect the IOMMU: discovery now parses the ACPI DMAR table, finds the first VT-d
DMA-remapping unit (DRHD), maps its register block, and records its version and
capabilities (iommu_present/base/version/capabilities in the platform info). On QEMU's
emulated intel-iommu this reads back a real unit (base 0xfed90000, version 1.0).

This is detection only, and deliberately so. A full VT-d bring-up — per-device
translation domains that confine a driver's DMA to the buffers it dma_alloc'd — is the
real device-side safety guarantee, but it cannot be verified without a DMA-capable
device driver (none exist yet) and QEMU's intel-iommu to fault against. Writing that
enforcement now would be a large body of unverifiable page-table code; it belongs with
the first DMA driver, which is both the natural order and the only way to test it. Until
then the caveat stands in full: device_claim on a DMA-capable device is still equivalent
to granting ring 0. The docs say so plainly.

New `iommu` test (harness boots it with -device intel-iommu via a new per-case qemu_extra
hook) confirms the DMAR is parsed and the unit's registers read. Suite 40/40 plus host
tests.
This commit is contained in:
Daniel Samson
2026-07-10 20:07:01 +01:00
parent 4ef21fa083
commit e612d948d2
4 changed files with 94 additions and 1 deletions
+55
View File
@@ -89,6 +89,20 @@ pub const PlatformInformation = struct {
/// ISA-IRQ-to-GSI remappings from the MADT (for future IOAPIC routing).
overrides: [16]IsoEntry = undefined,
override_count: usize = 0,
/// Whether an IOMMU (VT-d DMA-remapping unit) was found in the ACPI DMAR table.
/// When false, `device_claim` on a DMA-capable device is equivalent to granting
/// ring 0 — a device can DMA to any physical address (docs/driver-model.md M16).
/// Detection is the first step; per-device domain enforcement lands with the first
/// DMA driver.
iommu_present: bool = false,
/// MMIO base of the first DMA-remapping hardware unit (DMAR DRHD), when present.
iommu_base: u64 = 0,
/// The unit's Version register (offset 0x00) — its low byte is major.minor;
/// reading it back nonzero confirms a real, mappable VT-d unit.
iommu_version: u32 = 0,
/// The unit's Capability register (offset 0x08): supported address widths, number
/// of domains, etc. Recorded now; consumed when enforcement is built.
iommu_capabilities: u64 = 0,
};
/// Filled in by `discover`; the architecture layer reads it during bring-up.
@@ -233,6 +247,7 @@ const SLIT: [4]u8 = "SLIT".*;
/// System Resource Affinity Table (SRAT)
const SRAT: [4]u8 = "SRAT".*;
/// Secondary System Description Table (SSDT)
const DMAR: [4]u8 = "DMAR".*;
const SSDT: [4]u8 = "SSDT".*;
/// Serial Port Console Redirection table (SPCR) — the firmware's console UART.
const SPCR: [4]u8 = "SPCR".*;
@@ -440,6 +455,8 @@ fn handleTable(device_tree: *DeviceTree, hal: Hal, sdt_physical: u64) !void {
parseFadt(header);
} else if (std.mem.eql(u8, &sig, &SPCR)) {
parseSpcr(header);
} else if (std.mem.eql(u8, &sig, &DMAR)) {
parseDmar(hal, header);
} else if (std.mem.eql(u8, &sig, &SSDT)) {
// Secondary namespace bytecode — collect for the sleep-state (`_Sx`) scan.
addAmlBlock(sdt_physical);
@@ -750,6 +767,44 @@ fn parseSpcr(header: *const SystemDescriptorTableHeader) void {
platform_information.spcr_kind = fadt(u8, base, len, spcr_interface_type) orelse 0;
}
// DMAR remapping-structure layout (Intel VT-d spec §8): the DMAR-specific header is 12
// bytes (host-address-width, flags, 10 reserved), then a list of {type u16, length u16}
// structures. Type 0 is a DRHD (DMA Remapping Hardware Unit Definition), whose 64-bit
// register base sits at offset 8 within it.
const dmar_structures_offset = 48; // 36-byte ACPI header + 12-byte DMAR header
const dmar_type_drhd: u16 = 0;
const drhd_register_base_offset = 8;
/// DMAR -> detect the IOMMU. Find the first DMA-remapping hardware unit, map its
/// register block, and record its version and capabilities. This is *detection only*:
/// it tells the system an IOMMU exists (so `device_claim` on a DMA device could one day
/// be gated by a per-device translation domain), but no domains are programmed yet —
/// enforcement is built with the first DMA driver, which is what there is to protect and
/// test against. See docs/driver-model.md (M16), the honest caveat.
fn parseDmar(hal: Hal, header: *const SystemDescriptorTableHeader) void {
const base: [*]align(1) const u8 = @ptrCast(header);
const total: usize = header.length;
var off: usize = dmar_structures_offset;
while (off + 4 <= total) {
const kind = fadt(u16, base, total, off) orelse break;
const length = fadt(u16, base, total, off + 2) orelse break;
if (length < 4 or off + length > total) break; // malformed; stop rather than loop
if (kind == dmar_type_drhd) {
const register_base = fadt(u64, base, total, off + drhd_register_base_offset) orelse 0;
if (register_base != 0) {
const regs = hal.mapMmio(register_base, abi.page_size, true);
platform_information.iommu_present = true;
platform_information.iommu_base = register_base;
platform_information.iommu_version = @as(*const volatile u32, @ptrFromInt(regs + 0x00)).*;
platform_information.iommu_capabilities = @as(*const volatile u64, @ptrFromInt(regs + 0x08)).*;
return; // first unit is enough for detection; multi-unit is future
}
}
off += length;
}
}
// --- AML namespace -> generic device tree -----------------------------------
/// The PCI bus context while descending the ACPI namespace: the generic host