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:
@@ -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
|
||||
|
||||
@@ -88,6 +88,8 @@ pub fn run(case: []const u8, boot_information: *const BootInformation) void {
|
||||
dmaTest();
|
||||
} else if (eql(case, "msi")) {
|
||||
msiTest();
|
||||
} else if (eql(case, "iommu")) {
|
||||
iommuTest();
|
||||
} else if (eql(case, "smp")) {
|
||||
smpTest();
|
||||
} else if (eql(case, "affinity")) {
|
||||
@@ -1034,6 +1036,21 @@ fn msiTest() void {
|
||||
result();
|
||||
}
|
||||
|
||||
/// 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).
|
||||
fn iommuTest() void {
|
||||
log("DANOS-TEST-BEGIN: iommu\n", .{});
|
||||
const pinfo = platform.platformInformation();
|
||||
check("IOMMU found in the DMAR table", pinfo.iommu_present);
|
||||
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 });
|
||||
result();
|
||||
}
|
||||
|
||||
var proc_worker_run: bool = true;
|
||||
var proc_worker_ran: bool = false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user