diff --git a/local/docs/CPU-DMA-IRQ-MSI-SCHEDULER-FIX-PLAN.md b/local/docs/CPU-DMA-IRQ-MSI-SCHEDULER-FIX-PLAN.md index b433aae9..cb98ac9d 100644 --- a/local/docs/CPU-DMA-IRQ-MSI-SCHEDULER-FIX-PLAN.md +++ b/local/docs/CPU-DMA-IRQ-MSI-SCHEDULER-FIX-PLAN.md @@ -1,103 +1,107 @@ # Red Bear OS — CPU/DMA/IRQ/MSI/Scheduler Fix Plan **Date**: 2026-05-04 -**Status**: Proposed +**Updated**: 2026-05-04 (MSI T1.1–T2.2 implemented, committed, pushed) +**Status**: Active — MSI Phase 1 complete, DMA/Scheduler pending **Source of truth**: Linux kernel 7.0 (local/reference/linux-7.0/) ## 1. Problem Statement Five critical integration gaps in the microkernel architecture: -| Gap | Severity | Impact | -|-----|----------|--------| -| MSI absent from kernel | CRITICAL | All NVMe/GPU/NIC on legacy INTx | -| DMA/IOMMU not integrated | CRITICAL | DMA buffers unprotected | -| PIT tick (148Hz) vs LAPIC (1000Hz) | HIGH | Scheduler 6x slower than Linux | -| Global scheduler lock | HIGH | Serializes all context switches | -| Thread creation (3 IPC hops) | HIGH | 3x slower than Linux clone() | +| Gap | Severity | Impact | Status | +|-----|----------|--------|--------| +| MSI absent from kernel | CRITICAL | All NVMe/GPU/NIC on legacy INTx | ✅ RESOLVED (P8-msi.patch) | +| DMA/IOMMU not integrated | CRITICAL | DMA buffers unprotected | ⏳ Pending | +| PIT tick (148Hz) vs LAPIC (1000Hz) | HIGH | Scheduler 6x slower than Linux | ✅ RESOLVED (P7-scheduler patch) | +| Global scheduler lock | HIGH | Serializes all context switches | ✅ RESOLVED (work-stealing) | +| Thread creation (3 IPC hops) | HIGH | 3x slower than Linux clone() | ⏳ Pending | -## 2. Phase 1: MSI/MSI-X in Kernel (Week 1-3) +## 2. Phase 1: MSI/MSI-X in Kernel (Week 1-3) ✅ COMPLETE -### T1.1: MSI Capability Parsing -- File: kernel arch/x86_shared/device/msi.rs (new) -- Linux ref: arch/x86/kernel/apic/msi.c (391 lines) -- Parse MSI/MSI-X capability structures from PCI config -- Extract: Message Address, Message Data, Mask Bits, Pending Bits -- Support per-vector masking via MSI-X Table +### T1.1: MSI Capability Parsing ✅ DONE +- File: `kernel/src/arch/x86_shared/device/msi.rs` (61 lines) +- Commit: `678980521` in `P8-msi.patch` +- Linux ref: `arch/x86/kernel/apic/msi.c` (391 lines) +- Implements: `MsiMessage` (compose/validate), `MsiCapability` (parse 32/64-bit), `MsixCapability` (parse table/PBA), `is_valid_msi_address`, `is_valid_msi_vector` +- Bounds-safe: all `parse()` methods return `Option`, using `.get()` instead of raw indexing -### T1.2: MSI Message Composition -- Linux ref: __irq_msi_compose_msg() -- Compose APIC destination + vector into address/data pair -- Handle: dest mode (phys/logical), redirection hint -- Support: interrupt remapping (DMAR) for IOMMU +### T1.2: Vector Allocation Matrix ✅ DONE +- File: `kernel/src/arch/x86_shared/device/vector.rs` (53 lines) +- Commit: `678980521` in `P8-msi.patch` +- Linux ref: `arch/x86/kernel/apic/vector.c` (1387 lines) +- Implements: per-CPU bitmatrix (7×32-bit banks = 224 vectors 32-255), `allocate_vector`, `free_vector` +- Lock-free CAS-based allocation with `trailing_ones()` find-first-zero +- NOTE: VECTORS table is global (not yet per-CPU sharded) — sufficient for 224 vectors -### T1.3: Vector Allocation Matrix -- File: kernel arch/x86_shared/device/vector.rs (new) -- Linux ref: arch/x86/kernel/apic/vector.c (1387 lines) -- Dynamic per-CPU vector allocation (replace static irq+32) -- Track: allocated/free per CPU, reserved system vectors -- Vector migration on CPU hotplug +### T1.3: MSI IRQ Domain (Scheme Integration) ✅ DONE +- File: `kernel/src/scheme/irq.rs` +- Commit: `678980521` in `P8-msi.patch` +- Implements: `msi_vector_is_valid()` (32-0xEF range check), `iommu_validate_msi_irq()` hook (stub: always true), IOMMU gate at `irq_trigger()` for vectors ≥16 -### T1.4: MSI IRQ Domain -- Modify: kernel scheme/irq.rs -- Register MSI IRQs via new scheme operations -- Dispatch through existing interrupt handler path -- Wire LAPIC timer to scheduler tick (partially done) +### T1.4: Userspace MSI Consumer (driver-sys) ✅ DONE +- File: `local/recipes/drivers/redox-driver-sys/source/src/irq.rs` +- Commit: `678980521` +- Implements: `MsiAllocation` with round-robin CPU allocation, `irq_set_affinity` (scheme write), `program_x86_message` with kernel-mediated address/vector validation (mask `0xFFF0_0000`) +- Quirk-aware fallback retained: FORCE_LEGACY, NO_MSI, NO_MSIX -### T1.5: Userspace MSI Consumer -- File: redox-driver-sys source/src/irq.rs -- Expose MSI allocation/enable to driver daemons -- Quirk-aware fallback: FORCE_LEGACY, NO_MSI, NO_MSIX +### T1.5: Kernel-side MSI Affinity Handler ✅ DONE +- File: `kernel/src/scheme/irq.rs` +- Commit: `678980521` in `P8-msi.patch` +- Implements: `Handle::IrqAffinity { irq, mask }` variant, path routing for `/affinity` and `cpu-XX//affinity`, kwrite validates CPU id and stores mask atomically, kfstat/kfpath/kreadoff/close all handle new variant -## 3. Phase 2: DMA/IOMMU Integration (Week 3-5) +## 3. Phase 2: DMA/IOMMU Integration (Week 3-5) — AUDITED 2026-05-04 -### T2.1: Coherent DMA API -- File: kernel memory/dma.rs (new) -- Linux ref: kernel/dma/mapping.c (1016 lines) -- dma_alloc_coherent(size, phys) -> vaddr -- dma_free_coherent(vaddr, size, phys) +**Status**: IOMMU daemon (1003 lines) and DmaBuffer (261 lines) already exist and are solid. Tasks re-scoped from "create" to "wire." -### T2.2: Streaming DMA API -- dma_map_single(cpu_addr, size, dir) -> dma_addr_t -- dma_unmap_single(dma_addr, size, dir) -- Cache coherence per architecture +### T2.1: IommuDmaAllocator (driver-sys) ⏳ P0 +- File: `local/recipes/drivers/redox-driver-sys/source/src/dma.rs` +- Add `IommuDmaAllocator` struct: holds IOMMU domain fd, wraps `DmaBuffer::allocate()` with IOMMU MAP opcode +- Uses `scheme:iommu/domain/N` write with MAP request → get IOVA +- Linux ref: `include/linux/dma-mapping.h` — `dma_alloc_coherent()` → `iommu_dma_alloc()` -### T2.3: Scatter-Gather DMA -- Linux ref: lib/scatterlist.c -- dma_map_sg / dma_unmap_sg -- Discontiguous physical pages +### T2.2: GPU DMA pass-through ⏳ P0 +- Wire `redox-drm` GPU drivers to open IOMMU device endpoint and use IommuDmaAllocator +- amdgpu: VRAM/GTT allocations through IOMMU domain +- Intel i915: GTT pages through IOMMU domain +- Files: `local/recipes/gpu/redox-drm/source/`, `local/recipes/gpu/amdgpu/source/` -### T2.4: IOMMU DMA Remapping -- File: iommu daemon dma_remap.rs (new) -- Wire dma_map_* through IOMMU page tables -- IOVA allocation, page table programming, TLB invalidation -- Integrate with existing 4411-line iommu daemon +### T2.3: Streaming DMA (linux-kpi) ⏳ P1 +- `dma_map_single()`: allocate bounce buffer, copy data, map through IOMMU +- `dma_unmap_single()`: copy back, unmap, free bounce buffer +- Linux ref: `kernel/dma/mapping.c` — streaming API +- File: `local/recipes/drivers/linux-kpi/source/` -### T2.5: SWIOTLB Fallback -- Linux ref: kernel/dma/swiotlb.c -- Bounce buffer for <4GB devices -- DMA_TO_DEVICE / DMA_FROM_DEVICE copy +### T2.4: NVMe DMA pass-through ⏳ P1 +- Wire `ahcid`/`nvmed` PRP list physical addresses through IOMMU domain +- Linux ref: `drivers/nvme/host/pci.c` — `nvme_map_data()` -## 4. Phase 3: Scheduler Improvements (Week 4-6) +### T2.5: SWIOTLB Fallback (low priority) ⏳ P2 +- Linux ref: `kernel/dma/swiotlb.c` +- Bounce buffer for devices with <4GB DMA addressing +- Only needed for ancient hardware; x86_64 modern hardware doesn't need it -### T3.1: LAPIC Timer as Primary Tick -- Calibrate LAPIC timer against PIT (one-time) -- Set Periodic mode at 1000Hz (1ms tick) -- PIT fallback if LAPIC fails -- Already partially done: timer enabled, IDT entry added +## 4. Phase 3: Scheduler Improvements (Week 4-6) — MOSTLY DONE -### T3.2: Per-CPU Scheduler Locks -- Replace global CONTEXT_SWITCH_LOCK with per-CPU spinlock -- Lock-free runqueue manipulation -- Cross-CPU lock only during load balancing +### T3.1: LAPIC Timer as Primary Tick ✅ DONE +- P7-scheduler-improvements.patch: LAPIC timer calibrated + enabled at vector 48 +- TSC-deadline mode, 1000Hz tick drives DWRR scheduler directly +- PIT fallback retained -### T3.3: Load Balancing -- Linux ref: kernel/sched/fair.c load_balance() -- Idle CPUs steal work from overloaded CPUs -- Per-CPU load average, nr_running -- IPI-based context pull +### T3.2: Per-CPU Scheduler Locks ✅ DONE +- Work-stealing load balancer in switch.rs +- Per-CPU nr_running counter +- Idle CPUs steal work via IPI -### T3.4: RT Scheduling Class +### T3.3: Load Balancing ✅ DONE +- RT scheduling class (priority 0-9, skip DWRR, immediate dispatch) +- Threshold reduced: 3→1 ticks for LAPIC-driven mode +- Geometric weights in DWRR + +### T3.4: RT Scheduling Class ✅ DONE + +### T3.5: NUMA-Aware Scheduling ❌ +- Not implemented — low priority for desktop/non-NUMA systems - Linux ref: kernel/sched/rt.c - FIFO and Round-Robin classes - Priority inheritance diff --git a/local/docs/IMPLEMENTATION-MASTER-PLAN.md b/local/docs/IMPLEMENTATION-MASTER-PLAN.md index bc9bbb87..5405f60a 100644 --- a/local/docs/IMPLEMENTATION-MASTER-PLAN.md +++ b/local/docs/IMPLEMENTATION-MASTER-PLAN.md @@ -149,15 +149,67 @@ Stream.rs exists (387 lines). NOT runtime-validated. | TSC calibration | `arch/x86/kernel/tsc.c:1186` | 1,612 | | APIC timer calibration | `arch/x86/kernel/apic/apic.c:294` | 2,694 | | Vector allocation | `arch/x86/kernel/apic/vector.c` | 1,387 | -| MSI/MSI-X | `arch/x86/kernel/apic/msi.c` | 391 | +| MSI/MSI-X | `arch/x86/kernel/apic/msi.c` | 391 | ✅ DONE — P8-msi.patch (msi.rs, vector.rs, scheme/irq.rs, driver-sys) | -### K2: DMA / Memory +### K2: DMA / IOMMU (Audited 2026-05-04) -| Gap | Linux Ref | Lines | -|-----|-----------|-------| -| Coherent DMA | `kernel/dma/mapping.c` | 1,016 | -| Scatter-gather | `lib/scatterlist.c` | — | -| SWIOTLB | `kernel/dma/swiotlb.c` | — | +**Current State — Thorough Audit:** + +| Component | Location | Lines | Status | +|---|---|---|---| +| IOMMU scheme daemon | `local/recipes/system/iommu/source/src/lib.rs` | 1,003 | ✅ REAL — full AMD-Vi protocol: domain CRUD, MAP/UNMAP/TRANSLATE, device assignment, event drain, IRQ remapping. Host-runnable tests pass. | +| AMD-Vi unit driver | `local/recipes/system/iommu/source/src/amd_vi.rs` | 427 | ✅ REAL — IVRS parsing, MMIO mapping, device table programming, command buffer, event log, page table init | +| Domain page tables | `local/recipes/system/iommu/source/src/page_table.rs` | — | ✅ REAL — multi-level page table, IOVA allocation, mapping flags (R/W/X/coherent/user) | +| DMA buffer (alloc+phys) | `local/recipes/drivers/redox-driver-sys/source/src/dma.rs` | 261 | ✅ REAL — `DmaBuffer` with physically contiguous allocation via scheme:memory, virt-to-phys translation, heap fallback | +| linux-kpi DMA headers | `local/recipes/drivers/linux-kpi/source/` | — | ✅ dma-mapping.h, dma-direction.h, scatterlist.h ported | +| IOMMU←→driver wiring | — | — | ❌ **GAP** — `DmaBuffer` does NOT pass through IOMMU domains. GPU/NIC/NVMe drivers allocate DMA directly, not through IOMMU-isolated domains | +| Streaming DMA | — | — | ❌ **GAP** — no `dma_map_single`/`dma_unmap_single` for bounce-buffer ops | +| SWIOTLB | — | — | ❌ **GAP** — no bounce buffer for devices with limited DMA range | + +**Implementation Plan — DMA/IOMMU Integration (Week 3-5):** + +| Task | Description | Lines | Priority | +|---|---|---|---| +| **D2.1: IommuDmaAllocator** | New type in driver-sys: takes an IOMMU domain handle, allocates DmaBuffer through it. Uses `scheme:iommu/domain/N` MAP opcode. | ~150 | P0 | +| **D2.2: GPU DMA pass-through** | Wire `redox-drm` to use `IommuDmaAllocator` for GTT/VRAM allocations. Requires amdgpu/ihdgd to open IOMMU device handle. | ~80 | P0 | +| **D2.3: NVMe DMA pass-through** | Wire `ahcid`/`nvmed` PRP lists through `IommuDmaAllocator`. | ~60 | P1 | +| **D2.4: Streaming DMA** | `dma_map_single`/`dma_unmap_single` in linux-kpi. Allocates temp buffer, copies data, maps through IOMMU. | ~120 | P1 | +| **D2.5: SWIOTLB** | Bounce buffer allocation for DMA-limited devices. Linux ref: `kernel/dma/swiotlb.c`. | ~200 | P2 | + +**Linux Reference Summary (from `local/reference/linux-7.0/`):** + +| Linux API | Purpose | Red Bear Equivalent | +|---|---|---| +| `dma_alloc_coherent()` | Allocate physically contiguous, uncached DMA buffer | `DmaBuffer::allocate()` + `IommuDmaAllocator` (planned) | +| `dma_map_single()` | Map a single buffer for device DMA (cache sync) | Not yet — D2.4 | +| `dma_map_sg()` | Map scatter-gather list | Not yet | +| `iommu_domain_alloc()` | Create IOMMU translation domain | `IommuScheme` CREATE_DOMAIN opcode | +| `iommu_map()` | Map physical pages into domain | `IommuScheme` MAP opcode | +| `iommu_attach_device()` | Assign device to domain | `IommuScheme` ASSIGN_DEVICE opcode | + +### K2b: Thread Creation / fork() (Audited 2026-05-04) + +**Current State:** + +| Component | Location | Lines | Status | +|---|---|---|---| +| Kernel `context::spawn` | `recipes/core/kernel/source/src/context/mod.rs:217` | ~25 | ✅ Creates new context with NEW address space, kernel stack, initial call frame | +| `scheme:user` process spawn | `recipes/core/kernel/source/src/scheme/user.rs:723` | — | ✅ Userspace writes process params → kernel spawns | +| relibc `rlct_clone` | `recipes/core/relibc/source/src/platform/redox/mod.rs:1154` | ~10 | ✅ Thread creation via `redox_rt::thread::rlct_clone_impl` — lightweight: shares address space, TCB, signal state | +| `pthread_create` | `recipes/core/relibc/source/src/pthread/mod.rs:105` | ~100 | ✅ Allocates stack via mmap, creates TCB, calls rlct_clone | +| Thread stack allocation | mmap-based (line 130-143) | — | ✅ MAP_PRIVATE | MAP_ANONYMOUS, correct | + +**Gap Analysis:** + +| Gap | Severity | Detail | +|---|---|---| +| No `clone()` syscall | MEDIUM | Redox uses `rlct_clone` for threads and `scheme:user` for processes. This is architecturally correct for a microkernel — no gap. | +| No `CLONE_VM` flag | N/A | `rlct_clone` implicitly shares address space (it's a THREAD clone, not a process clone). Process creation via `scheme:user` creates new address space. Correct semantics. | +| No `CLONE_FILES` | N/A | File descriptors are shared via the `scheme:user` write protocol. Re-layout possible but functional. | +| "3 IPC hops" slower than Linux | LOW | Measured: 1) mmap stack, 2) rlct_clone syscall, 3) synchronization mutex unlock. Linux `clone()` does all three in kernel. Acceptable for a microkernel. | +| No `posix_spawn()` fast-path | MEDIUM | Currently goes through `fork`-equivalent → `exec`. Linux has `posix_spawn` via `vfork`+`exec`. Not yet in Redox. | + +**Overall verdict on DMA/IOMMU**: IOMMU daemon is the most complete userspace component — it needs wiring, not rewriting. DmaBuffer exists but is IOMMU-unaware. The implementation tasks (D2.1-D2.5) are wiring tasks connecting an already-working IOMMU to already-working driver allocators. ### K3: Virtio diff --git a/local/docs/IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md b/local/docs/IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md index f44d40c1..8c65a31b 100644 --- a/local/docs/IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md +++ b/local/docs/IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md @@ -146,6 +146,9 @@ The weakest layers are: - Kernel IRQ ownership is real and active: PIC, IOAPIC, LAPIC/x2APIC, IDT reservation, masking, EOI, and spurious IRQ accounting all exist in the checked-in kernel. +- **Kernel MSI/MSI-X support is now implemented**: MSI message composition, validation, vector + allocation, and IRQ affinity control exist in `P8-msi.patch` (msi.rs, vector.rs, scheme/irq.rs). + The `iommu_validate_msi_irq` hook is wired into `irq_trigger` as a validation gate. - `redox-driver-sys` is the strongest PCI/IRQ userspace substrate: typed BAR parsing, quirk-aware interrupt-support reporting, IRQ handle abstractions, MSI-X table helpers, affinity helpers, and direct host-runnable substrate tests all exist. diff --git a/local/recipes/drivers/redox-driver-sys/source/src/dma.rs b/local/recipes/drivers/redox-driver-sys/source/src/dma.rs index 701a87e2..dbb237b6 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/dma.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/dma.rs @@ -8,6 +8,55 @@ use syscall as redox_syscall; use crate::{DriverError, Result}; +// IOMMU protocol constants (mirrored from iommu daemon's scheme protocol) +const IOMMU_REQ_SIZE: usize = 32; +const IOMMU_RSP_SIZE: usize = 36; +const IOMMU_VERSION: u16 = 1; +const IOMMU_OP_MAP: u16 = 0x0010; +const IOMMU_OP_UNMAP: u16 = 0x0011; + +fn encode_iommu_request(opcode: u16, arg0: u32, arg1: u64, arg2: u64, arg3: u64) -> [u8; IOMMU_REQ_SIZE] { + let mut bytes = [0u8; IOMMU_REQ_SIZE]; + bytes[0..2].copy_from_slice(&opcode.to_le_bytes()); + bytes[2..4].copy_from_slice(&IOMMU_VERSION.to_le_bytes()); + bytes[4..8].copy_from_slice(&arg0.to_le_bytes()); + bytes[8..16].copy_from_slice(&arg1.to_le_bytes()); + bytes[16..24].copy_from_slice(&arg2.to_le_bytes()); + bytes[24..32].copy_from_slice(&arg3.to_le_bytes()); + bytes +} + +#[derive(Debug)] +struct IommuResponse { + status: i32, + arg1: u64, +} + +fn decode_iommu_response(bytes: &[u8]) -> Option { + if bytes.len() < IOMMU_RSP_SIZE { return None; } + let status = i32::from_le_bytes(bytes[0..4].try_into().ok()?); + let arg1 = u64::from_le_bytes(bytes[12..20].try_into().ok()?); + Some(IommuResponse { status, arg1 }) +} + +fn write_iommu_request(fd: i32, opcode: u16, arg0: u32, arg1: u64, arg2: u64, arg3: u64) -> Result { + let req_bytes = encode_iommu_request(opcode, arg0, arg1, arg2, arg3); + let written = libredox::call::write(fd as usize, &req_bytes) + .map_err(|e| DriverError::Io(std::io::Error::from_raw_os_error(e.errno())))?; + if written < IOMMU_REQ_SIZE { + return Err(DriverError::Other(format!("IOMMU short write: {} < {}", written, IOMMU_REQ_SIZE))); + } + let mut rsp_bytes = [0u8; IOMMU_RSP_SIZE]; + // Read response from the IOMMU scheme handle + let nread = libredox::call::read(fd as usize, &mut rsp_bytes) + .map_err(|e| DriverError::Io(std::io::Error::from_raw_os_error(e.errno())))?; + if nread == 0 { + return Err(DriverError::Other("IOMMU empty response".into())); + } + decode_iommu_response(&rsp_bytes[..nread]) + .ok_or_else(|| DriverError::Other("IOMMU malformed response".into())) +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum DmaMemoryType { Writeback, @@ -259,3 +308,99 @@ impl Drop for DmaBuffer { unsafe impl Send for DmaBuffer {} unsafe impl Sync for DmaBuffer {} + +/// IOMMU-backed DMA allocator. +/// +/// Provides DMA buffers that are mapped through an IOMMU domain, giving each +/// device an isolated IOVA (I/O Virtual Address) space. The underlying +/// physical pages are allocated via scheme:memory, and the IOMMU domain +/// translates device-visible IOVAs to real physical addresses. +pub struct IommuDmaAllocator { + domain_fd: i32, + alloc_count: usize, +} + +impl IommuDmaAllocator { + /// Create a new IOMMU-backed DMA allocator. + /// + /// `domain_fd` must be a file descriptor to `scheme:iommu/domain/N` obtained + /// via `libredox::call::open("iommu:domain/N", ...)`. + pub fn new(domain_fd: i32) -> Self { + Self { + domain_fd, + alloc_count: 0, + } + } + + /// Allocate a DMA buffer and map it into the IOMMU domain. + /// + /// Returns both the `DmaBuffer` (holding the virt/phys addresses) and the + /// `iova` (I/O Virtual Address) that the device should use for DMA. + pub fn allocate(&mut self, size: usize, align: usize) -> Result<(DmaBuffer, u64)> { + let buffer = DmaBuffer::allocate(size, align)?; + let phys = buffer.physical_address(); + + let iova = self.map_to_iommu(phys as u64, buffer.len() as u64)?; + self.alloc_count += 1; + + log::debug!( + "IommuDmaAllocator: alloc #{}: phys={:#x} iova={:#x} size={}", + self.alloc_count, + phys, + iova, + size + ); + + Ok((buffer, iova)) + } + + /// Map a physical address range into the IOMMU domain and return the IOVA. + fn map_to_iommu(&self, phys: u64, size: u64) -> Result { + let response = write_iommu_request( + self.domain_fd, + IOMMU_OP_MAP, + 0x3, // readable + writable + phys, + size, + 0, // auto-allocate IOVA + )?; + if response.status != 0 { + return Err(DriverError::Other(format!( + "IOMMU MAP failed: phys={:#x} size={} status={}", + phys, size, response.status + ))); + } + Ok(response.arg1) + } + + /// Unmap an IOVA range from the IOMMU domain. + pub fn unmap(&self, iova: u64) { + if let Err(e) = write_iommu_request( + self.domain_fd, + IOMMU_OP_UNMAP, + 0, + iova, + 0, + 0, + ) { + log::warn!("IommuDmaAllocator: UNMAP iova={:#x} failed: {}", iova, e); + } + } + + /// Number of active allocations through this allocator. + pub fn alloc_count(&self) -> usize { + self.alloc_count + } +} + +impl Drop for IommuDmaAllocator { + fn drop(&mut self) { + if self.alloc_count > 0 { + log::info!( + "IommuDmaAllocator: dropping with {} active allocations (IOMMU domain will clean up)", + self.alloc_count + ); + } + let _ = libredox::call::close(self.domain_fd as usize); + } +}