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
|
||||
|
||||
Reference in New Issue
Block a user