iommu: AMD-Vi backend behind the vendor-neutral core
The second hardware backend. The IOMMU core, DMA-region capabilities, and per-device enforcement are unchanged; this adds AMD-Vi (IVRS) as an alternative to Intel VT-d (DMAR) under the same Backend vtable. - parseIvrs records the IOMMU control-register base from the first IVHD; the platform layer gains iommu_is_amd, and the core picks the backend by vendor at init. VT-d and AMD-Vi are mutually exclusive on real hardware. - iommu-amd.zig: a 2 MiB device table (every DTE zeroed = deny-all until a device is claimed), AMD native-format page tables (4 KiB leaves), a command buffer (INVALIDATE_DEVTAB_ENTRY / INVALIDATE_IOMMU_PAGES / COMPLETION_WAIT) and an event log for faults. The DTE forwards interrupts unmapped, so MSI passthrough works exactly as on VT-d. - The boot log and the iommu self-test are now vendor-aware. **UNTESTED on real AMD hardware** — danos is developed on Intel, so this is validated only against QEMU's amd-iommu, and every log line and doc says so. QEMU quirk handled: its amd-iommu does not observe the COMPLETION_WAIT store form, but consumes the command ring synchronously on the tail-register write, so invalidations are already applied by the time we poll — the backend warns once and proceeds. Cases: amd-iommu (detection + scratch-domain walker) and amd-iommu-usb-storage (full storage stack through AMD device-table translation with per-grant capabilities), both green. 106/106. This completes the IOVA/IOMMU-enforcement track: per-device DMA domains on both vendors, with buffers reachable only through delegated capabilities.
This commit is contained in:
+44
-3
@@ -99,9 +99,12 @@ pub const PlatformInformation = struct {
|
||||
/// and the kernel says so at every boot (the fail-open platform log line). When
|
||||
/// true, the IOMMU core builds per-device translation domains from this record.
|
||||
iommu_present: bool = false,
|
||||
/// MMIO base of the selected DMA-remapping hardware unit (the DRHD with
|
||||
/// INCLUDE_PCI_ALL — the catch-all unit covering every device not scoped to a
|
||||
/// more specific one; falls back to the first unit when none carries the flag).
|
||||
/// true when the present unit is AMD-Vi (from IVRS) rather than Intel VT-d (DMAR).
|
||||
/// The two are mutually exclusive on real hardware; the IOMMU core picks the backend.
|
||||
iommu_is_amd: bool = false,
|
||||
/// MMIO base of the selected DMA-remapping hardware unit — the VT-d DRHD with
|
||||
/// INCLUDE_PCI_ALL (the catch-all unit; falls back to the first), or the AMD-Vi
|
||||
/// IOMMU's control-register base from the first IVHD.
|
||||
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.
|
||||
@@ -279,6 +282,8 @@ const SLIT: [4]u8 = "SLIT".*;
|
||||
const SRAT: [4]u8 = "SRAT".*;
|
||||
/// Secondary System Description Table (SSDT)
|
||||
const DMAR: [4]u8 = "DMAR".*;
|
||||
/// I/O Virtualization Reporting Structure (IVRS) — the AMD-Vi analogue of DMAR.
|
||||
const IVRS: [4]u8 = "IVRS".*;
|
||||
const SSDT: [4]u8 = "SSDT".*;
|
||||
/// Serial Port Console Redirection table (SPCR) — the firmware's console UART.
|
||||
const SPCR: [4]u8 = "SPCR".*;
|
||||
@@ -498,6 +503,8 @@ fn handleTable(device_tree: *DeviceTree, hal: Hal, sdt_physical: u64) !void {
|
||||
parseSpcr(header);
|
||||
} else if (std.mem.eql(u8, &sig, &DMAR)) {
|
||||
parseDmar(hal, header);
|
||||
} else if (std.mem.eql(u8, &sig, &IVRS)) {
|
||||
parseIvrs(header);
|
||||
} else if (std.mem.eql(u8, &sig, &SSDT)) {
|
||||
// Secondary namespace bytecode — collect it to publish for the ring-3 parse.
|
||||
addAmlBlock(sdt_physical);
|
||||
@@ -885,6 +892,40 @@ fn parseRmrr(base: [*]align(1) const u8, total: usize, off: usize, length: usize
|
||||
}
|
||||
}
|
||||
|
||||
// IVRS layout (AMD I/O Virtualization spec): 36-byte ACPI header, IVinfo u32 @36,
|
||||
// 8 reserved @40, then IVHD/IVMD blocks from @48. An IVHD common header is type u8 @0,
|
||||
// flags u8 @1, length u16 @2, device id u16 @4, capability offset u16 @6, IOMMU base
|
||||
// address u64 @8, PCI segment u16 @16, IOMMU info u16 @18.
|
||||
const ivrs_blocks_offset = 48;
|
||||
const ivhd_type_10: u8 = 0x10;
|
||||
const ivhd_type_11: u8 = 0x11;
|
||||
const ivhd_base_offset = 8;
|
||||
|
||||
/// IVRS -> detect an AMD-Vi IOMMU. Record the control-register base from the first IVHD
|
||||
/// of type 0x10/0x11. Per-device entries and IVMD (the AMD analogue of RMRR) are ignored
|
||||
/// in v1 — the default-deny device table is what we build anyway, and QEMU emits no IVMD;
|
||||
/// a real machine that needs them is flagged untested on AMD regardless.
|
||||
fn parseIvrs(header: *const SystemDescriptorTableHeader) void {
|
||||
const base: [*]align(1) const u8 = @ptrCast(header);
|
||||
const total: usize = header.length;
|
||||
var off: usize = ivrs_blocks_offset;
|
||||
while (off + 4 <= total) {
|
||||
const kind = fadt(u8, base, total, off) orelse break;
|
||||
const length = fadt(u16, base, total, off + 2) orelse break;
|
||||
if (length < 4 or off + length > total) break;
|
||||
if (kind == ivhd_type_10 or kind == ivhd_type_11) {
|
||||
const iommu_base = fadt(u64, base, total, off + ivhd_base_offset) orelse 0;
|
||||
if (iommu_base != 0) {
|
||||
platform_information.iommu_present = true;
|
||||
platform_information.iommu_is_amd = true;
|
||||
platform_information.iommu_base = iommu_base;
|
||||
return; // first IVHD is enough; multi-unit is future work
|
||||
}
|
||||
}
|
||||
off += length;
|
||||
}
|
||||
}
|
||||
|
||||
// --- helpers ----------------------------------------------------------------
|
||||
|
||||
/// Sum `len` bytes; an ACPI table/pointer is valid when the low 8 bits are zero.
|
||||
|
||||
Reference in New Issue
Block a user