//! Intel VT-d backend for the IOMMU core, behind the architecture boundary. //! //! Provides the architecture-neutral core (system/kernel/iommu.zig) with the VT-d //! hardware specifics behind the `Backend` vtable (iommu.zig beside this file): second-level page-table entry bits, the root/context table structure, the //! translation-enable and invalidation register sequences, and the fault drain. The //! core owns the domain table and the page-table walk; this file owns the registers. //! //! Register model (VT-d spec §10-11): offsets from the DRHD register base. The unit is //! programmed once at enable (root table + Translation Enable), then touched only for //! per-device context changes, per-domain invalidations, and fault draining. Interrupt //! remapping is deliberately left OFF (GCMD.IRE stays 0): with it off, upstream writes //! to 0xFEE0_0000-0xFEEF_FFFF are treated as interrupt requests and bypass second-level //! translation, so the existing MSI contract survives unchanged. const std = @import("std"); const abi = @import("abi"); const boot_handoff = @import("boot-handoff"); const paging = @import("paging.zig"); const iommu = @import("iommu.zig"); const page_size = abi.page_size; // Register offsets from the unit's base. const reg_cap = 0x08; // Capability (64) const reg_ecap = 0x10; // Extended Capability (64) const reg_gcmd = 0x18; // Global Command (32, write-only) const reg_gsts = 0x1C; // Global Status (32, read-only) const reg_rtaddr = 0x20; // Root Table Address (64) const reg_ccmd = 0x28; // Context Command (64) const reg_fsts = 0x34; // Fault Status (32) const gcmd_te: u32 = 1 << 31; // Translation Enable const gcmd_srtp: u32 = 1 << 30; // Set Root Table Pointer const gsts_tes: u32 = 1 << 31; // Translation Enable Status const gsts_rtps: u32 = 1 << 30; // Root Table Pointer Status const cap_cm: u64 = 1 << 7; // Caching Mode const cap_sagaw_shift = 8; // Supported Adjusted Guest Address Widths, bits 12:8 const cap_sagaw_39bit: u64 = 1 << 9; // 3-level const cap_sagaw_48bit: u64 = 1 << 10; // 4-level const cap_fro_shift = 24; // Fault-Recording Register Offset, bits 33:24 (×16) const cap_nfr_shift = 40; // Number of Fault Recording regs, bits 47:40 (+1) const ecap_coherent: u64 = 1 << 0; // hardware snoops CPU caches reading its structures const ecap_iro_shift = 8; // IOTLB Register Offset, bits 17:8 (×16) const ccmd_icc: u64 = 1 << 63; // Invalidate Context-Cache const ccmd_cirg_global: u64 = @as(u64, 1) << 61; // global granularity const ccmd_cirg_device: u64 = @as(u64, 3) << 61; // device-selective const iotlb_ivt: u64 = 1 << 63; // Invalidate IOTLB const iotlb_iirg_global: u64 = @as(u64, 1) << 60; const iotlb_iirg_domain: u64 = @as(u64, 2) << 60; const iotlb_dr: u64 = 1 << 49; // drain reads const iotlb_dw: u64 = 1 << 48; // drain writes const fsts_ppf: u32 = 1 << 1; // Primary Pending Fault // Second-level PTE bits. const slpte_read: u64 = 1 << 0; const slpte_write: u64 = 1 << 1; const slpte_page_size: u64 = 1 << 7; // a 2 MiB leaf (== iommu.huge_leaf_bit) const address_mask: u64 = 0x000F_FFFF_FFFF_F000; var register_base: usize = 0; var version: u32 = 0; var capabilities: u64 = 0; var extended_capabilities: u64 = 0; var coherent: bool = true; // ECAP.C — whether clflush is unnecessary var levels: u8 = 4; var context_aw: u64 = 2; // context-entry AW field (001=3-level, 010=4-level) var gcmd_shadow: u32 = 0; // sticky GCMD bits (TE etc.), for the write-only register var root_table: u64 = 0; // physical base of the 256-entry root table var context_table: [256]u64 = .{0} ** 256; // per-bus context table physical, 0 = none var fault_log_budget: u32 = 32; // rate-limit: log this many faults, then just count var faults_suppressed: u64 = 0; fn read32(offset: usize) u32 { return @as(*const volatile u32, @ptrFromInt(register_base + offset)).*; } fn write32(offset: usize, value: u32) void { @as(*volatile u32, @ptrFromInt(register_base + offset)).* = value; } fn read64(offset: usize) u64 { return @as(*const volatile u64, @ptrFromInt(register_base + offset)).*; } fn write64(offset: usize, value: u64) void { @as(*volatile u64, @ptrFromInt(register_base + offset)).* = value; } fn tableAt(physical: u64) [*]volatile u64 { return @ptrFromInt(boot_handoff.physicalToVirtual(physical)); } /// Map the register window, read caps, pick the address width. Returns the vtable, or /// null when the unit is not live or advertises no address width danos can drive. pub fn detect(discovery: iommu.Discovery) ?iommu.Backend { // Map 16 KiB: FRCD and IOTLB registers can sit past the first page (CAP.FRO / // ECAP.IRO are 16-byte-unit offsets). register_base = paging.mapMmio(discovery.register_base, 16 * 1024, true); // The Version register's low byte is major.minor; reading it back nonzero is // the live-mappable-unit sanity check (previously a kernel-test assertion). version = read32(0x00); if (version == 0) return null; capabilities = read64(reg_cap); extended_capabilities = read64(reg_ecap); coherent = (extended_capabilities & ecap_coherent) != 0; const sagaw = capabilities >> cap_sagaw_shift; if (sagaw & cap_sagaw_48bit != 0) { levels = 4; context_aw = 2; // 010b } else if (sagaw & cap_sagaw_39bit != 0) { levels = 3; context_aw = 1; // 001b } else { return null; // no width we build tables for } root_table = allocZeroed() orelse return null; return iommu.Backend{ .levels = levels, .supports_huge_pages = true, .enable = enable, .makeLeaf = makeLeaf, .makeTable = makeTable, .isPresent = isPresent, .flushStructure = flushStructure, .attach = attach, .detach = detach, .invalidateDomain = invalidateDomain, .faultDrain = faultDrain, }; } /// Program the root table and turn Translation Enable on. The core has already created /// and populated the RMRR domains (their context entries are live via `attach`), so at /// this instant every OTHER device's context entry is not-present and will fault — which /// for stale firmware bus-mastering is the desired evidence, not a bug. fn enable() void { write64(reg_rtaddr, root_table); // legacy mode (bits 11:10 = 00) setGlobalCommand(gcmd_srtp); spinStatus(gsts_rtps); globalInvalidate(); setGlobalCommand(gcmd_te); spinStatus(gsts_tes); gcmd_shadow |= gcmd_te; iommu.environment.write("/system/kernel: iommu online (Intel VT-d)\n"); var buffer: [64]u8 = undefined; if (std.fmt.bufPrint(&buffer, " version : 0x{x}\n", .{version})) |line| iommu.environment.write(line) else |_| {} if (std.fmt.bufPrint(&buffer, " agaw : {d} levels\n", .{levels})) |line| iommu.environment.write(line) else |_| {} } // --- Backend vtable ------------------------------------------------------------------ fn makeLeaf(physical: u64, huge: bool) u64 { return (physical & address_mask) | slpte_read | slpte_write | (if (huge) slpte_page_size else 0); } fn makeTable(table_physical: u64, level: u8) u64 { _ = level; return (table_physical & address_mask) | slpte_read | slpte_write; } fn isPresent(entry: u64) bool { return (entry & (slpte_read | slpte_write)) != 0; } fn flushStructure(address: usize) void { if (coherent) return; // the unit snoops CPU caches; no flush needed (QEMU) asm volatile ("clflush (%[p])" : : [p] "r" (address), : .{ .memory = true }); } fn attach(bdf: u16, domain: u16, page_table_root: u64) void { const bus: u8 = @intCast(bdf >> 8); const devfn: u8 = @intCast(bdf & 0xFF); // Lazily allocate this bus's context table and link it into the root table. if (context_table[bus] == 0) { const table = allocZeroed() orelse return; context_table[bus] = table; const root_entry = &tableAt(root_table)[@as(usize, bus) * 2]; // 16-byte entries root_entry.* = (table & address_mask) | 1; // present flushStructure(@intFromPtr(root_entry)); } const context = tableAt(context_table[bus]); const low = &context[@as(usize, devfn) * 2]; const high = &context[@as(usize, devfn) * 2 + 1]; high.* = (context_aw & 0x7) | (@as(u64, domain) << 8); // AW + DID low.* = (page_table_root & address_mask) | 1; // present, TT=00 (use second-level) flushStructure(@intFromPtr(high)); flushStructure(@intFromPtr(low)); invalidateContextDevice(bdf, domain); invalidateDomain(domain); } fn detach(bdf: u16) void { const bus: u8 = @intCast(bdf >> 8); const devfn: u8 = @intCast(bdf & 0xFF); if (context_table[bus] == 0) return; const context = tableAt(context_table[bus]); context[@as(usize, devfn) * 2] = 0; // not present context[@as(usize, devfn) * 2 + 1] = 0; flushStructure(@intFromPtr(&context[@as(usize, devfn) * 2])); invalidateContextDevice(bdf, 0); globalIotlb(); } fn invalidateDomain(domain: u16) void { const iotlb_offset = iotlbOffset(); write64(iotlb_offset, iotlb_ivt | iotlb_iirg_domain | iotlb_dr | iotlb_dw | (@as(u64, domain) << 32)); spin64(iotlb_offset, iotlb_ivt); } fn faultDrain() usize { const fsts = read32(reg_fsts); if (fsts & fsts_ppf == 0) return 0; const fro = (capabilities >> cap_fro_shift) & 0x3FF; const nfr = ((capabilities >> cap_nfr_shift) & 0xFF) + 1; const frcd_base = @as(usize, @intCast(fro)) * 16; var seen: usize = 0; var i: usize = 0; while (i < nfr) : (i += 1) { const off = frcd_base + i * 16; const high = read64(off + 8); if (high & (@as(u64, 1) << 63) == 0) continue; // F: no fault recorded here const low = read64(off); const address = low & ~@as(u64, 0xFFF); const source: u16 = @intCast(high & 0xFFFF); const reason: u8 = @intCast((high >> 32) & 0xFF); const is_read = (high >> 62) & 1; // T: 1 = read request logFault(source, address, reason, is_read == 1); write64(off + 8, @as(u64, 1) << 63); // RW1C: clear F seen += 1; } write32(reg_fsts, fsts); // clear PPF/PFO (write-1-to-clear) return seen; } fn logFault(source: u16, address: u64, reason: u8, is_read: bool) void { if (fault_log_budget > 0) { fault_log_budget -= 1; var buffer: [128]u8 = undefined; if (std.fmt.bufPrint(&buffer, "DANOS-IOMMU-FAULT: bdf={x:0>2}:{x:0>2}.{d} addr=0x{x} reason=0x{x} write={d}\n", .{ source >> 8, (source >> 3) & 0x1F, source & 0x7, address, reason, @intFromBool(!is_read), })) |line| iommu.environment.write(line) else |_| {} if (fault_log_budget == 0) iommu.environment.write("DANOS-IOMMU-FAULT: (further faults suppressed)\n"); } else { faults_suppressed += 1; } } // --- register helpers ---------------------------------------------------------------- fn setGlobalCommand(one_shot: u32) void { // GCMD is write-only: every write must carry the full sticky state plus the one-shot // bit being requested, or a set sticky bit (TE) would be cleared as a side effect. write32(reg_gcmd, gcmd_shadow | one_shot); } fn spinStatus(bit: u32) void { var spins: u64 = 0; while (read32(reg_gsts) & bit == 0) { spins += 1; if (spins > 10_000_000) { iommu.environment.write("/system/kernel: WARNING VT-d status bit never set — translation may be incomplete\n"); return; } } } fn spin64(offset: usize, bit: u64) void { var spins: u64 = 0; while (read64(offset) & bit != 0) { spins += 1; if (spins > 10_000_000) return; } } fn globalInvalidate() void { write64(reg_ccmd, ccmd_icc | ccmd_cirg_global); spin64(reg_ccmd, ccmd_icc); globalIotlb(); } fn globalIotlb() void { const iotlb_offset = iotlbOffset(); write64(iotlb_offset, iotlb_ivt | iotlb_iirg_global | iotlb_dr | iotlb_dw); spin64(iotlb_offset, iotlb_ivt); } fn invalidateContextDevice(bdf: u16, domain: u16) void { write64(reg_ccmd, ccmd_icc | ccmd_cirg_device | (@as(u64, bdf) << 16) | domain); spin64(reg_ccmd, ccmd_icc); } fn iotlbOffset() usize { const iro = (extended_capabilities >> ecap_iro_shift) & 0x3FF; return @as(usize, @intCast(iro)) * 16 + 8; // IOTLB register sits at IRO*16 + 8 } fn allocZeroed() ?u64 { const frame = iommu.environment.allocateFrame() orelse return null; const table = tableAt(frame); var i: usize = 0; while (i < 512) : (i += 1) table[i] = 0; return frame; }