Skip to main content

intel_vtd/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Intel VT-d (Virtualization Technology for Directed I/O) IOMMU emulator.
5//!
6//! Provides emulated DMA address translation (IOVA → GPA via root/context
7//! tables and second-level page table walking) and interrupt remapping for
8//! emulated PCI devices.
9//!
10//! Unlike the AMD IOMMU (which is a PCI device), VT-d is a pure MMIO platform
11//! device discovered via the ACPI DMAR table. It has no PCI config space.
12//!
13//! Legacy and scalable second-stage translation support 39/48-bit addresses,
14//! 4KB/2MB/1GB pages, and pass-through. ECAP advertises SMTS, SSTS, SMPWCS,
15//! SSADS and RPS together. Scalable requests use the context's implied RID_PASID;
16//! explicitly PASID-tagged requests, first-stage/nested translation, ATS, PRI,
17//! and supervisor requests are not supported.
18//!
19//! Tracking is optional per PASID entry: SSADE-enabled second-stage walks
20//! atomically set accessed flags and, for accepted writes, the leaf dirty flag
21//! before the DMA operation, even if that operation subsequently fails
22//! (§§3.7.2, 9.8). Updates require PWSNP=1; a required non-snooping update faults
23//! with reason 0x7c, and a failed atomic update faults with 0x7d (§7.1.3).
24//! Legacy translation and pass-through never update A/D flags.
25//!
26//! Walks are uncached, so cleared A/D flags can be set again immediately. This
27//! is an implementation property, not a portable software guarantee: software
28//! must use the invalidation protocol in §6.5.3.3. The state lock drains walks
29//! and DMA operations against invalidation, not guest page-table writes.
30//! Unsynchronized clearing does not provide a race-free dirty snapshot. There
31//! is no migration bitmap API. Interrupt remapping supports MSI and IOAPIC
32//! routes; queued invalidation uses 128/256-bit descriptors in legacy mode and
33//! 256-bit descriptors in scalable mode.
34
35#![forbid(unsafe_code)]
36
37pub mod spec;
38
39use chipset_device::ChipsetDevice;
40use chipset_device::io::IoError;
41use chipset_device::io::IoResult;
42use chipset_device::mmio::MmioIntercept;
43use guestmem::GuestMemory;
44use guestmem::GuestMemoryError;
45use inspect::InspectMut;
46use parking_lot::RwLock;
47use parking_lot::RwLockWriteGuard;
48use pci_core::msi::SignalMsi;
49use spec::invalidation::DescriptorType;
50use spec::invalidation::DescriptorWidth;
51use spec::invalidation::InvalidationDescriptor256;
52use spec::invalidation::InvalidationError;
53use spec::invalidation::InvalidationWaitDw0Dw1;
54use spec::invalidation::InvalidationWaitDw2Dw3;
55use spec::irte::Irte;
56use spec::irte::IrteLo;
57use spec::irte::SourceValidationType;
58use spec::pte::SlPte;
59use spec::registers::CapReg;
60use spec::registers::CcmdReg;
61use spec::registers::EcapReg;
62use spec::registers::FaultReason;
63use spec::registers::FectlReg;
64use spec::registers::FrcdHi;
65use spec::registers::FrcdLo;
66use spec::registers::FstsReg;
67use spec::registers::GcmdReg;
68use spec::registers::GstsReg;
69use spec::registers::IcsReg;
70use spec::registers::IectlReg;
71use spec::registers::IotlbReg;
72use spec::registers::IqaReg;
73use spec::registers::IqhReg;
74use spec::registers::IqtReg;
75use spec::registers::IrtaReg;
76use spec::registers::MmioRegister as Reg;
77use spec::registers::NumDomains;
78use spec::registers::RtaddrReg;
79use spec::registers::TranslationTableMode;
80use spec::registers::VersionReg;
81use spec::root_context::AddressWidth;
82use spec::root_context::ContextEntry;
83use spec::root_context::RootEntry;
84use spec::root_context::TranslationType;
85use spec::scalable::PasidDirectoryEntry;
86use spec::scalable::PasidTableEntry;
87use spec::scalable::PasidTranslationType;
88use spec::scalable::ScalableContextEntry;
89use spec::scalable::ScalableRootEntry;
90use spec::scalable::ScalableValidationError;
91use std::ops::RangeInclusive;
92use std::sync::Arc;
93use zerocopy::FromBytes;
94use zerocopy::FromZeros;
95use zerocopy::Immutable;
96use zerocopy::IntoBytes;
97use zerocopy::KnownLayout;
98
99/// MMIO region size (4KB).
100pub const MMIO_REGION_SIZE: u64 = spec::registers::MMIO_REGION_SIZE;
101
102// =============================================================================
103// Hardcoded Capability Values (1B.3)
104// =============================================================================
105
106/// Value returned by the VT-d version register (major = 1, minor = 0).
107const VER_VALUE: u32 = VersionReg::new().with_max(1).with_min(0).into_bits();
108
109/// Maximum guest address width in bits.
110const MGAW_BITS: u8 = 48;
111
112/// Host width advertised by OpenVMM's IntelVtdDmarConfig, independently of
113/// second-stage input MGAW/AGAW. SlPte can encode up to 52 address bits.
114const HAW_BITS: u8 = 48;
115const HOST_ADDRESS_MASK: u64 = (1 << HAW_BITS) - 1;
116
117/// Bounds the entire walk, including all CAS-conflict restarts (at most 64 PTEs).
118const MAX_AD_WALK_ATTEMPTS: usize = 16;
119
120/// SAGAW bitmask: supported address widths (39-bit + 48-bit).
121const SAGAW_MASK: u8 = (1 << AddressWidth::AW_39BIT.0) | (1 << AddressWidth::AW_48BIT.0);
122
123/// Largest advertised AGAW, which bounds DMA fault information (§11.4.7.6).
124const MAX_AGAW_BITS: u32 = 30 + 9 * SAGAW_MASK.ilog2();
125
126/// Number of fault recording registers.
127const NUM_FAULT_RECORDS: u8 = 1;
128
129/// Capability Register value.
130const CAP_VALUE: u64 = CapReg::new()
131    .with_nd(NumDomains::ND_64K.0)
132    .with_afl(false)
133    .with_rwbf(false)
134    .with_cm(false)
135    .with_sagaw(SAGAW_MASK)
136    .with_mgaw(MGAW_BITS - 1)
137    .with_zlr(true)
138    .with_fro(Reg::FRCD_DW0.0 / 16)
139    .with_sllps(spec::registers::SLLPS_2MB | spec::registers::SLLPS_1GB)
140    .with_psi(true)
141    .with_nfr(NUM_FAULT_RECORDS - 1)
142    .with_mamv(30 - 12) // max invalidation range = 1GB (2^30) / 4KB page (2^12)
143    .with_dwd(true)
144    .with_drd(true)
145    .into_bits();
146
147/// Extended Capability Register value.
148const ECAP_VALUE: u64 = EcapReg::new()
149    .with_c(true)
150    .with_qi(true)
151    .with_ir(true)
152    .with_eim(true)
153    .with_pt(true)
154    .with_sc(true)
155    .with_smts(true)
156    .with_ssts(true)
157    .with_smpwcs(true)
158    .with_ssads(true)
159    .with_rps(true)
160    .with_iro(Reg::IVA.0 / 16)
161    .with_mhmv(15) // max IM field in interrupt cache invalidation (4-bit max)
162    .into_bits();
163
164// =============================================================================
165// Configuration
166// =============================================================================
167
168/// Configuration for constructing an [`IntelVtdDevice`].
169#[derive(Debug, Clone)]
170pub struct IntelVtdConfig {
171    /// MMIO base address for the VT-d register file.
172    pub mmio_base: u64,
173}
174
175// =============================================================================
176// Internal Register State
177// =============================================================================
178
179/// Internal mutable state of the VT-d IOMMU.
180///
181/// All fields are behind `RwLock<VtdState>` in [`VtdSharedState`].
182/// DMA translations take a read lock; MMIO writes take a write lock.
183#[derive(Debug, inspect::Inspect)]
184struct VtdState {
185    // -- Global status (mirrors GCMD operations) --
186    /// Global Status Register value.
187    gsts: GstsReg,
188
189    // -- Root table --
190    /// Root Table Address Register (raw value, written freely).
191    rtaddr: RtaddrReg,
192    /// Latched root table address (set when SRTP is processed).
193    latched_rtaddr: RtaddrReg,
194
195    // -- Interrupt remapping table --
196    /// Interrupt Remapping Table Address Register (raw value).
197    irta: IrtaReg,
198    /// Latched IRTA (set when SIRTP is processed).
199    latched_irta: IrtaReg,
200
201    // -- Context command register (register-based invalidation) --
202    ccmd: CcmdReg,
203
204    // -- Fault recording --
205    /// Fault Status Register (RW1C bits: PFO, IQE, ICE, ITE).
206    fsts: FstsReg,
207    /// Fault Event Control Register.
208    fectl: FectlReg,
209    /// Fault Event Data Register.
210    #[inspect(hex)]
211    fedata: u32,
212    /// Fault Event Address Register.
213    #[inspect(hex)]
214    feaddr: u32,
215    /// Fault Event Upper Address Register.
216    #[inspect(hex)]
217    feuaddr: u32,
218    /// Fault Recording Register — low 64 bits.
219    frcd_lo: FrcdLo,
220    /// Fault Recording Register — high 64 bits.
221    frcd_hi: FrcdHi,
222
223    // -- Invalidation queue --
224    /// Invalidation Queue Address Register.
225    iqa: IqaReg,
226    /// Invalidation Queue Head.
227    iqh: IqhReg,
228    /// Invalidation Queue Tail.
229    iqt: IqtReg,
230    /// Invalidation Completion Status Register (IWC bit).
231    ics: IcsReg,
232    /// Invalidation Event Control Register.
233    iectl: IectlReg,
234    /// Invalidation Event Data Register.
235    #[inspect(hex)]
236    iedata: u32,
237    /// Invalidation Event Address Register.
238    #[inspect(hex)]
239    ieaddr: u32,
240    /// Invalidation Event Upper Address Register.
241    #[inspect(hex)]
242    ieuaddr: u32,
243
244    // -- IOTLB registers (register-based invalidation, pre-QI) --
245    /// Invalidate Address Register (IVA_REG at 0x100).
246    #[inspect(hex)]
247    iva: u64,
248    /// IOTLB Invalidate Register (IOTLB_REG at 0x108).
249    iotlb: IotlbReg,
250}
251
252impl VtdState {
253    fn new() -> Self {
254        Self {
255            // Per VT-d spec §10.4.10 and §10.4.21, FECTL.IM and IECTL.IM
256            // default to 1 (masked) at power-up reset. Without this, the
257            // IOMMU may deliver spurious MSIs with uninitialized FEADDR/IEADDR
258            // registers (address=0, data=0) during early initialization, which
259            // can inject vector 0 (#DE) into the guest and crash it.
260            fectl: FectlReg::new().with_im(true),
261            iectl: IectlReg::new().with_im(true),
262            gsts: GstsReg::new(),
263            rtaddr: RtaddrReg::new(),
264            latched_rtaddr: RtaddrReg::new(),
265            irta: IrtaReg::new(),
266            latched_irta: IrtaReg::new(),
267            ccmd: CcmdReg::new(),
268            fsts: FstsReg::new(),
269            fedata: 0,
270            feaddr: 0,
271            feuaddr: 0,
272            frcd_lo: FrcdLo::new(),
273            frcd_hi: FrcdHi::new(),
274            iqa: IqaReg::new(),
275            iqh: IqhReg::new(),
276            iqt: IqtReg::new(),
277            ics: IcsReg::new(),
278            iedata: 0,
279            ieaddr: 0,
280            ieuaddr: 0,
281            iva: 0,
282            iotlb: IotlbReg::new(),
283        }
284    }
285}
286
287/// Validated queue geometry. Offsets are bytes, not descriptor indices.
288struct InvalidationQueue {
289    base: u64,
290    size: u64,
291    width: DescriptorWidth,
292    head: u64,
293    pending: u64,
294}
295
296impl InvalidationQueue {
297    fn new(state: &VtdState, ecap: EcapReg) -> Result<Self, InvalidationError> {
298        let iqa = state.iqa;
299        let width = DescriptorWidth::checked(
300            iqa.dw(),
301            TranslationTableMode(state.latched_rtaddr.ttm()),
302            ecap,
303        )?;
304        let base = iqa.queue_base_address();
305        let size = iqa.queue_size_bytes();
306        // Check the last byte, allowing a queue ending exactly at u64::MAX.
307        if iqa.into_bits() & 0x7f8 != 0 || base.checked_add(size - 1).is_none() {
308            return Err(InvalidationError::QueueAddress);
309        }
310        let head = state.iqh.into_bits();
311        let tail = state.iqt.into_bits();
312        let stride = width.bytes() as u64;
313        if head >= size || !head.is_multiple_of(stride) {
314            return Err(InvalidationError::QueueHead(head));
315        }
316        if tail >= size || !tail.is_multiple_of(stride) {
317            return Err(InvalidationError::QueueTail(tail));
318        }
319        Ok(Self {
320            base,
321            size,
322            width,
323            head,
324            pending: (tail + size - head) % size / stride,
325        })
326    }
327}
328
329// =============================================================================
330// Shared IOMMU State
331// =============================================================================
332
333/// Original, untagged DMA request and the fault policy accumulated during lookup.
334#[derive(Debug, Clone, Copy)]
335struct TranslationContext {
336    source_id: u16,
337    iova: u64,
338    mode: TranslationTableMode,
339    fpd: bool,
340}
341
342impl TranslationContext {
343    fn ad_fault(self, error: SecondStageAdError) -> VtdFault {
344        VtdFault::AdUpdate {
345            source_id: self.source_id,
346            iova: self.iova,
347            fpd: self.fpd,
348            error,
349        }
350    }
351
352    fn reason(self, legacy: FaultReason, scalable: FaultReason) -> FaultReason {
353        if self.mode == TranslationTableMode::LEGACY {
354            legacy
355        } else {
356            scalable
357        }
358    }
359
360    fn fault(self, reason: FaultReason) -> VtdFault {
361        let Self {
362            source_id,
363            iova,
364            fpd,
365            ..
366        } = self;
367        match reason {
368            FaultReason::ROOT_NOT_PRESENT => VtdFault::RootNotPresent { source_id, iova },
369            FaultReason::CONTEXT_NOT_PRESENT => VtdFault::ContextNotPresent {
370                source_id,
371                iova,
372                fpd,
373            },
374            FaultReason::INVALID_CONTEXT_ENTRY => VtdFault::InvalidContextEntry {
375                source_id,
376                iova,
377                fpd,
378            },
379            FaultReason::ADDRESS_BEYOND_MGAW => VtdFault::AddressBeyondMgaw {
380                source_id,
381                iova,
382                fpd,
383            },
384            FaultReason::WRITE_ACCESS_DENIED | FaultReason::READ_ACCESS_DENIED => {
385                VtdFault::AccessDenied {
386                    source_id,
387                    iova,
388                    fpd,
389                    write: reason == FaultReason::WRITE_ACCESS_DENIED,
390                }
391            }
392            FaultReason::ROOT_ENTRY_ACCESS_ERROR => {
393                VtdFault::RootEntryAccessError { source_id, iova }
394            }
395            FaultReason::CONTEXT_ENTRY_ACCESS_ERROR => {
396                VtdFault::ContextEntryAccessError { source_id, iova }
397            }
398            FaultReason::SL_PTE_ACCESS_ERROR => VtdFault::PageTableAccessError {
399                source_id,
400                iova,
401                fpd,
402            },
403            _ => VtdFault::Dma {
404                source_id,
405                iova,
406                reason,
407                fpd,
408            },
409        }
410    }
411}
412
413/// Validated lookup result; pass-through has no paging/A-D configuration.
414#[derive(Debug)]
415enum TranslationDescriptor {
416    PassThrough {
417        context: TranslationContext,
418        domain_id: u16,
419    },
420    SecondStage(SecondStageTranslation),
421}
422
423#[derive(Debug)]
424struct SecondStageTranslation {
425    context: TranslationContext,
426    root: u64,
427    levels: u8,
428    domain_id: u16,
429    ssade: bool,
430    pwsnp: bool,
431}
432
433/// Shared VT-d IOMMU state accessible by per-device wrappers.
434///
435/// This struct holds the MMIO register state and guest memory reference
436/// behind a `RwLock`, allowing concurrent reads from per-device translation
437/// wrappers while the `IntelVtdDevice` performs exclusive writes via MMIO.
438pub struct VtdSharedState {
439    /// Guest memory for reading root/context/page tables and IRT.
440    guest_memory: GuestMemory,
441    /// MMIO register state, protected by a RwLock.
442    state: RwLock<VtdState>,
443    /// MSI delivery handle for the IOMMU's own interrupts (fault events
444    /// and invalidation completion events). VT-d is a platform device
445    /// with no PCI MSI capability — it programs MSI address/data directly
446    /// into MMIO registers (FEADDR/FEDATA, IEADDR/IEDATA).
447    signal_msi: Arc<dyn SignalMsi>,
448    /// Registered interrupt routes for invalidation callbacks.
449    retranslate_interrupts: iommu_common::RetranslateInterruptsList,
450}
451
452impl VtdSharedState {
453    /// Create new shared state.
454    fn new(guest_memory: GuestMemory, signal_msi: Arc<dyn SignalMsi>) -> Self {
455        Self {
456            guest_memory,
457            state: RwLock::new(VtdState::new()),
458            signal_msi,
459            retranslate_interrupts: iommu_common::RetranslateInterruptsList::new(),
460        }
461    }
462
463    /// Returns whether translation is currently enabled (GSTS.TES).
464    pub fn is_enabled(&self) -> bool {
465        let state = self.state.read();
466        state.gsts.tes()
467    }
468
469    /// Returns whether interrupt remapping is currently enabled (GSTS.IRES).
470    pub fn is_ir_enabled(&self) -> bool {
471        let state = self.state.read();
472        state.gsts.ires()
473    }
474
475    /// Create a per-device IOVA→GPA translator.
476    pub fn translator(self: &Arc<Self>) -> VtdTranslator {
477        VtdTranslator {
478            shared: self.clone(),
479        }
480    }
481
482    /// Create a per-device MSI remapping wrapper.
483    pub fn wrap_signal_msi(self: &Arc<Self>, inner: Arc<dyn SignalMsi>) -> Arc<VtdSignalMsi> {
484        Arc::new(VtdSignalMsi {
485            shared: self.clone(),
486            inner,
487        })
488    }
489
490    /// Deliver the IOMMU's own fault event MSI.
491    ///
492    /// VT-d delivers its own MSIs directly (not through its own interrupt
493    /// remapping). Uses `signal_msi(None, ...)` — the `None` devid means
494    /// if this MSI were to pass through a VtdSignalMsi wrapper it would be
495    /// dropped, which is correct (IOMMU MSIs must not loop through IR).
496    fn deliver_fault_interrupt(&self, state: &VtdState) {
497        let fectl = state.fectl;
498        if fectl.im() {
499            // Masked — don't deliver, IP will be set by caller.
500            return;
501        }
502        let addr = (state.feuaddr as u64) << 32 | (state.feaddr as u64);
503        let data = state.fedata;
504        self.signal_msi.signal_msi(None, addr, data);
505    }
506
507    /// Deliver the IOMMU's own invalidation completion event MSI.
508    fn deliver_invalidation_interrupt(&self, state: &VtdState) {
509        let iectl = state.iectl;
510        if iectl.im() {
511            return;
512        }
513        let addr = (state.ieuaddr as u64) << 32 | (state.ieaddr as u64);
514        let data = state.iedata;
515        self.signal_msi.signal_msi(None, addr, data);
516    }
517
518    // =========================================================================
519    // Fault Recording (1D)
520    // =========================================================================
521
522    /// Record a translation fault in the Fault Recording Register.
523    ///
524    /// Must be called under a **write lock** on `self.state`.
525    /// `fpd` must already account for the fault's qualification (Table 26).
526    fn record_fault_locked(
527        &self,
528        state: &mut VtdState,
529        source_id: u16,
530        fault_reason: FaultReason,
531        fault_addr: u64,
532        is_read: bool,
533        fpd: bool,
534    ) {
535        if fpd {
536            return;
537        }
538
539        // Check if FRCD[0].F is already set (fault record occupied).
540        if state.frcd_hi.f() {
541            // Overflow — set PFO, discard new fault.
542            let mut fsts = state.fsts;
543            fsts.set_pfo(true);
544            state.fsts = fsts;
545            return;
546        }
547
548        // Write fault recording register.
549        let frcd_lo = FrcdLo::new().with_fi((fault_addr & ((1 << MAX_AGAW_BITS) - 1)) >> 12);
550        let frcd_hi = FrcdHi::new()
551            .with_sid(source_id)
552            .with_fr(fault_reason.0)
553            .with_t(is_read)
554            .with_f(true);
555
556        state.frcd_lo = frcd_lo;
557        state.frcd_hi = frcd_hi;
558
559        // Signal fault event MSI if unmasked.
560        // PPF transitions 0→1 since we just set F=1 and it was 0 before.
561        let fectl = state.fectl;
562        if !fectl.im() {
563            self.deliver_fault_interrupt(state);
564        } else {
565            // Masked — set IP (interrupt pending).
566            state.fectl = state.fectl.with_ip(true);
567        }
568    }
569
570    // =========================================================================
571    // DMA Translation (1E)
572    // =========================================================================
573
574    /// Translate an IOVA to a GPA while holding the read lock.
575    ///
576    /// Production callers use ECAP_VALUE; tests also exercise reduced profiles.
577    /// Returns `Ok(gpa)` on success or `Err(VtdFault)` on failure.
578    fn translate_locked(
579        &self,
580        state: &VtdState,
581        bus: u8,
582        devfn: u8,
583        iova: u64,
584        write: bool,
585        ecap: EcapReg,
586    ) -> Result<u64, VtdFault> {
587        if !state.gsts.tes() {
588            return Ok(iova);
589        }
590
591        let context = TranslationContext {
592            source_id: bdf(bus, devfn),
593            iova,
594            mode: TranslationTableMode(state.latched_rtaddr.ttm()),
595            fpd: false,
596        };
597        let root = state.latched_rtaddr.root_table_address();
598        let translation = match context.mode {
599            TranslationTableMode::LEGACY => self.resolve_legacy(root, context, ecap)?,
600            TranslationTableMode::SCALABLE if ecap.smts() => {
601                self.resolve_scalable(root, context, ecap)?
602            }
603            _ => return Err(context.fault(FaultReason::INVALID_ROOT_TABLE_MODE)),
604        };
605        match translation {
606            TranslationDescriptor::PassThrough { context, domain_id } => {
607                tracing::trace!(domain_id, "vtd: pass-through DMA");
608                if iova > HOST_ADDRESS_MASK {
609                    return Err(context.fault(context.reason(
610                        FaultReason::ADDRESS_BEYOND_MGAW,
611                        FaultReason::SCALABLE_ADDRESS_BEYOND_HAW,
612                    )));
613                }
614                Self::check_dma_output(context, iova)
615            }
616            TranslationDescriptor::SecondStage(translation) => {
617                self.walk_sl_page_table(&translation, write, ecap)
618            }
619        }
620    }
621
622    fn resolve_legacy(
623        &self,
624        root: u64,
625        mut context: TranslationContext,
626        ecap: EcapReg,
627    ) -> Result<TranslationDescriptor, VtdFault> {
628        let entry: RootEntry = self.read_translation_entry(
629            root,
630            u64::from(context.source_id >> 8),
631            context,
632            FaultReason::ROOT_ENTRY_ACCESS_ERROR,
633        )?;
634        if !entry.lo.p() {
635            return Err(context.fault(FaultReason::ROOT_NOT_PRESENT));
636        }
637        if entry.lo.into_bits() & (0xffe | !HOST_ADDRESS_MASK) != 0 || entry.hi != 0 {
638            return Err(context.fault(FaultReason::ROOT_ENTRY_RESERVED_BIT));
639        }
640        let entry: ContextEntry = self.read_translation_entry(
641            entry.lo.context_table_address(),
642            u64::from(context.source_id & 0xff),
643            context,
644            FaultReason::CONTEXT_ENTRY_ACCESS_ERROR,
645        )?;
646        context.fpd = entry.lo.fpd();
647        if !entry.lo.p() {
648            return Err(context.fault(FaultReason::CONTEXT_NOT_PRESENT));
649        }
650        // Context bits 70:67 are ignored, not reserved (§9.3).
651        if entry.lo.into_bits() & 0xff0 != 0 || entry.hi.into_bits() & 0xffff_ffff_ff00_0080 != 0 {
652            return Err(context.fault(FaultReason::CONTEXT_ENTRY_RESERVED_BIT));
653        }
654        let aw = AddressWidth(entry.hi.aw());
655        let domain_id = entry.hi.did();
656        match TranslationType(entry.lo.tt()) {
657            TranslationType::PASS_THROUGH if ecap.pt() => {
658                // §9.3 requires the largest supported AW. For compatibility,
659                // tolerate any supported AW, but not invalid encodings.
660                // Identity addresses still use HAW (§3.9), not paging AGAW.
661                if Self::supported_levels(aw).is_none() {
662                    return Err(context.fault(FaultReason::INVALID_CONTEXT_ENTRY));
663                }
664                Ok(TranslationDescriptor::PassThrough { context, domain_id })
665            }
666            TranslationType::UNTRANSLATED_ONLY | TranslationType::ALL => {
667                if entry.lo.page_table_address() > HOST_ADDRESS_MASK {
668                    return Err(context.fault(FaultReason::CONTEXT_ENTRY_RESERVED_BIT));
669                }
670                let levels = Self::supported_levels(aw)
671                    .ok_or_else(|| context.fault(FaultReason::INVALID_CONTEXT_ENTRY))?;
672                Ok(TranslationDescriptor::SecondStage(SecondStageTranslation {
673                    context,
674                    root: entry.lo.page_table_address(),
675                    levels,
676                    domain_id,
677                    ssade: false,
678                    pwsnp: ecap.c(),
679                }))
680            }
681            _ => Err(context.fault(FaultReason::INVALID_CONTEXT_ENTRY)),
682        }
683    }
684
685    /// Resolve an untagged RID through the scalable tables (§§3.4.3, 9.2, 9.4-9.6).
686    fn resolve_scalable(
687        &self,
688        root: u64,
689        mut context: TranslationContext,
690        ecap: EcapReg,
691    ) -> Result<TranslationDescriptor, VtdFault> {
692        let devfn = context.source_id as u8;
693        let entry: ScalableRootEntry = self.read_translation_entry(
694            root,
695            u64::from(context.source_id >> 8),
696            context,
697            FaultReason::SCALABLE_ROOT_ACCESS_ERROR,
698        )?;
699        let half = entry.half(devfn);
700        half.validate(HAW_BITS).map_err(|error| {
701            context.fault(match error {
702                ScalableValidationError::NotPresent => FaultReason::SCALABLE_ROOT_NOT_PRESENT,
703                _ => FaultReason::SCALABLE_ROOT_RESERVED_BIT,
704            })
705        })?;
706
707        let entry: ScalableContextEntry = self.read_translation_entry(
708            half.context_table_address(),
709            ScalableContextEntry::table_index(devfn) as u64,
710            context,
711            FaultReason::SCALABLE_CONTEXT_ACCESS_ERROR,
712        )?;
713        context.fpd |= entry.lo.fpd();
714        // Apply capability-dependent reserved-zero rules before profile validation,
715        // but ignore all fields except FPD when P=0 (§9.4).
716        if entry.lo.p() && !ecap.rps() && entry.hi.rid_pasid() != 0 {
717            return Err(context.fault(FaultReason::SCALABLE_CONTEXT_RESERVED_BIT));
718        }
719        entry.validate(HAW_BITS).map_err(|error| {
720            context.fault(match error {
721                ScalableValidationError::NotPresent => FaultReason::SCALABLE_CONTEXT_NOT_PRESENT,
722                ScalableValidationError::PasidOutOfRange { .. } => {
723                    FaultReason::SCALABLE_INVALID_CONTEXT
724                }
725                _ => FaultReason::SCALABLE_CONTEXT_RESERVED_BIT,
726            })
727        })?;
728        let (directory_index, table_index) = entry
729            .pasid_indices(entry.hi.rid_pasid())
730            .map_err(|_| context.fault(FaultReason::SCALABLE_INVALID_CONTEXT))?;
731
732        let entry: PasidDirectoryEntry = self.read_translation_entry(
733            entry.directory_address(),
734            u64::from(directory_index),
735            context,
736            FaultReason::PASID_DIRECTORY_ACCESS_ERROR,
737        )?;
738        context.fpd |= entry.fpd();
739        entry.validate(HAW_BITS).map_err(|error| {
740            context.fault(match error {
741                ScalableValidationError::NotPresent => FaultReason::PASID_DIRECTORY_NOT_PRESENT,
742                _ => FaultReason::PASID_DIRECTORY_RESERVED_BIT,
743            })
744        })?;
745
746        let entry: PasidTableEntry = self.read_translation_entry(
747            entry.table_address(),
748            u64::from(table_index),
749            context,
750            FaultReason::PASID_TABLE_ACCESS_ERROR,
751        )?;
752        context.fpd |= entry.lo.fpd();
753        // Capability-dependent reserved fields must not reach PGTT/AW validation;
754        // not-present entries still ignore everything except FPD (§9.6).
755        if entry.lo.p()
756            && ((!ecap.ssts()
757                && (entry.lo.aw() != 0 || entry.lo.ssptptr() != 0 || entry.lo.ssade()))
758                || (!ecap.ssads() && entry.lo.ssade())
759                || (!ecap.smpwcs() && entry.hi.pwsnp())
760                || (!ecap.sc() && entry.hi.pgsnp()))
761        {
762            return Err(context.fault(FaultReason::PASID_TABLE_RESERVED_BIT));
763        }
764        entry.validate(HAW_BITS).map_err(|error| {
765            context.fault(match error {
766                ScalableValidationError::NotPresent => FaultReason::PASID_TABLE_NOT_PRESENT,
767                ScalableValidationError::UnsupportedTranslationType(_)
768                | ScalableValidationError::UnsupportedAddressWidth(_) => {
769                    FaultReason::PASID_TABLE_INVALID_ENTRY
770                }
771                _ => FaultReason::PASID_TABLE_RESERVED_BIT,
772            })
773        })?;
774        let domain_id = entry.hi.did();
775        match PasidTranslationType(entry.lo.pgtt()) {
776            PasidTranslationType::PASS_THROUGH if ecap.pt() => {
777                Ok(TranslationDescriptor::PassThrough { context, domain_id })
778            }
779            PasidTranslationType::SECOND_STAGE if ecap.ssts() => {
780                let levels = Self::supported_levels(AddressWidth(entry.lo.aw()))
781                    .ok_or_else(|| context.fault(FaultReason::PASID_TABLE_INVALID_ENTRY))?;
782                Ok(TranslationDescriptor::SecondStage(SecondStageTranslation {
783                    context,
784                    root: entry.page_table_address(),
785                    levels,
786                    domain_id,
787                    ssade: entry.lo.ssade(),
788                    pwsnp: entry.hi.pwsnp(),
789                }))
790            }
791            _ => Err(context.fault(FaultReason::PASID_TABLE_INVALID_ENTRY)),
792        }
793    }
794
795    fn supported_levels(aw: AddressWidth) -> Option<u8> {
796        match aw {
797            AddressWidth::AW_39BIT => Some(3),
798            AddressWidth::AW_48BIT => Some(4),
799            _ => None,
800        }
801    }
802
803    fn read_translation_entry<T: FromBytes + KnownLayout + Immutable>(
804        &self,
805        base: u64,
806        index: u64,
807        context: TranslationContext,
808        reason: FaultReason,
809    ) -> Result<T, VtdFault> {
810        let address = Self::translation_entry_address::<T>(base, index)
811            .ok_or_else(|| context.fault(reason))?;
812        self.guest_memory
813            .read_plain(address)
814            .map_err(|_| context.fault(reason))
815    }
816
817    fn translation_entry_address<T>(base: u64, index: u64) -> Option<u64> {
818        let size = size_of::<T>() as u64;
819        index
820            .checked_mul(size)
821            .and_then(|offset| base.checked_add(offset))
822            .filter(|address| {
823                address
824                    .checked_add(size - 1)
825                    .is_some_and(|end| end <= HOST_ADDRESS_MASK)
826            })
827    }
828
829    fn check_dma_output(context: TranslationContext, gpa: u64) -> Result<u64, VtdFault> {
830        if gpa & !0xf_ffff == 0xfee0_0000 {
831            return Err(context.fault(context.reason(
832                FaultReason::OUTPUT_ADDR_IN_INTR_RANGE,
833                FaultReason::SCALABLE_OUTPUT_ADDR_IN_INTR_RANGE,
834            )));
835        }
836        Ok(gpa)
837    }
838
839    /// Shared second-stage walk (§§3.7, 3.7.1, 3.7.2, 9.8).
840    fn walk_sl_page_table(
841        &self,
842        translation: &SecondStageTranslation,
843        write: bool,
844        ecap: EcapReg,
845    ) -> Result<u64, VtdFault> {
846        self.walk_sl_page_table_with_cas(translation, write, ecap, |address, current, new| {
847            self.guest_memory
848                .compare_exchange::<u64>(address, current.to_le(), new.to_le())
849                .map(|result| result.is_ok())
850        })
851    }
852
853    /// The callable operates on decoded little-endian words and reports whether
854    /// the CAS succeeded. A conflict always requires a fresh, validated walk.
855    fn walk_sl_page_table_with_cas(
856        &self,
857        translation: &SecondStageTranslation,
858        write: bool,
859        ecap: EcapReg,
860        mut compare_exchange: impl FnMut(u64, u64, u64) -> Result<bool, GuestMemoryError>,
861    ) -> Result<u64, VtdFault> {
862        let &SecondStageTranslation {
863            context,
864            root,
865            levels,
866            domain_id,
867            ssade,
868            pwsnp,
869        } = translation;
870        tracing::trace!(domain_id, ssade, pwsnp, "vtd: second-stage DMA");
871        if !matches!(levels, 3 | 4) {
872            return Err(context.fault(context.reason(
873                FaultReason::INVALID_CONTEXT_ENTRY,
874                FaultReason::PASID_TABLE_INVALID_ENTRY,
875            )));
876        }
877        let iova = context.iova;
878        if iova >> MGAW_BITS.min(12 + 9 * levels) != 0 {
879            return Err(context.fault(context.reason(
880                FaultReason::ADDRESS_BEYOND_MGAW,
881                FaultReason::SCALABLE_ADDRESS_BEYOND_AGAW,
882            )));
883        }
884        let track_ad = ssade && context.mode == TranslationTableMode::SCALABLE;
885
886        'walk: for _ in 0..MAX_AD_WALK_ATTEMPTS {
887            let mut table_addr = root;
888            let mut can_read = true;
889            let mut can_write = true;
890            for level in (1..=levels).rev() {
891                // Table 26 distinguishes SSPTPTR access (LCT.4.3/SSS.4) from
892                // access through a preceding paging entry's ADDR (LSS.1/SSS.1).
893                let reason = if level == levels {
894                    context.reason(
895                        FaultReason::INVALID_CONTEXT_ENTRY,
896                        FaultReason::SCALABLE_SL_ROOT_ACCESS_ERROR,
897                    )
898                } else {
899                    context.reason(
900                        FaultReason::SL_PTE_ACCESS_ERROR,
901                        FaultReason::SCALABLE_SL_PTE_ACCESS_ERROR,
902                    )
903                };
904                let address = Self::translation_entry_address::<u64>(
905                    table_addr,
906                    SlPte::iova_index(iova, level) as u64,
907                )
908                .ok_or_else(|| context.fault(reason))?;
909                let raw = self
910                    .guest_memory
911                    .read_plain::<u64>(address)
912                    .map(u64::from_le)
913                    .map_err(|_| context.fault(reason))?;
914                let pte = SlPte::from(raw);
915                can_read &= pte.r();
916                can_write &= pte.w();
917                let denied = context.reason(
918                    if write {
919                        FaultReason::WRITE_ACCESS_DENIED
920                    } else {
921                        FaultReason::READ_ACCESS_DENIED
922                    },
923                    if write {
924                        FaultReason::SCALABLE_WRITE_ACCESS_DENIED
925                    } else {
926                        FaultReason::SCALABLE_READ_ACCESS_DENIED
927                    },
928                );
929                if !pte.is_present() || (!can_read && !can_write) {
930                    return Err(context
931                        .fault(context.reason(denied, FaultReason::SCALABLE_SL_PTE_NOT_PRESENT)));
932                }
933
934                let leaf = level == 1 || (pte.ps() && matches!(level, 2 | 3));
935                let offset_mask = SlPte::page_size_at_level(level) - 1;
936                let reserved = (1 << 62)
937                    | (((1u64 << 52) - 1) & !HOST_ADDRESS_MASK)
938                    | if !leaf || !ecap.sc() { 1 << 11 } else { 0 }
939                    | if level == 4 { 1 << 7 } else { 0 }
940                    | if leaf { offset_mask & !0xfff } else { 0 };
941                if raw & reserved != 0 {
942                    return Err(context.fault(context.reason(
943                        FaultReason::SL_PTE_RESERVED_BIT,
944                        FaultReason::SCALABLE_SL_PTE_RESERVED_BIT,
945                    )));
946                }
947                let output = if leaf {
948                    if (write && !can_write) || (!write && !can_read) {
949                        return Err(context.fault(denied));
950                    }
951                    Some(Self::check_dma_output(
952                        context,
953                        pte.phys_address() | (iova & offset_mask),
954                    )?)
955                } else {
956                    None
957                };
958                // Only validated entries acquire A; D additionally requires an
959                // accepted write translation, including the output-range check.
960                let updated = pte
961                    .with_a(true)
962                    .with_d(pte.d() || (leaf && write))
963                    .into_bits();
964                if track_ad && updated != raw {
965                    if !ecap.smpwcs() || !pwsnp {
966                        return Err(context.ad_fault(SecondStageAdError::NonSnooping));
967                    }
968                    if !compare_exchange(address, raw, updated)
969                        .map_err(|error| context.ad_fault(SecondStageAdError::Memory(error)))?
970                    {
971                        // Restart permissions and geometry from the root, even
972                        // for A/D-only conflicts. Never OR into a stale mapping.
973                        // The attempt budget is shared across every level.
974                        continue 'walk;
975                    }
976                }
977                if let Some(gpa) = output {
978                    return Ok(gpa);
979                }
980                table_addr = pte.phys_address();
981            }
982        }
983        Err(context.ad_fault(SecondStageAdError::RetryExhausted))
984    }
985
986    // =========================================================================
987    // Interrupt Remapping (1F)
988    // =========================================================================
989
990    /// Remap an MSI using the Interrupt Remapping Table.
991    ///
992    /// Called under the read lock. On fault, returns `Err(VtdFault)`.
993    fn remap_msi_locked(
994        &self,
995        state: &VtdState,
996        source_id: u16,
997        address: u64,
998        data: u32,
999    ) -> Result<(u64, u32), VtdFault> {
1000        let gsts = state.gsts;
1001        if !gsts.ires() {
1002            // IR disabled — pass through.
1003            return Ok((address, data));
1004        }
1005
1006        // Check for compatibility-format MSI (bit 4 of address = 0).
1007        let is_remappable = (address >> 4) & 1 != 0;
1008        if !is_remappable {
1009            return self.handle_compat_format(state, source_id, address, data);
1010        }
1011
1012        // Extract IRTE index from remappable-format MSI.
1013        let irte_index = Self::extract_irte_index(address, data);
1014
1015        // Validate index against IRT size.
1016        let irta = state.latched_irta;
1017        let max_entries = irta.entry_count();
1018        if irte_index as u32 >= max_entries {
1019            return Err(VtdFault::IrteIndexExceedsSize {
1020                source_id,
1021                irte_index,
1022            });
1023        }
1024
1025        // Look up IRTE.
1026        let irt_base = irta.irt_base_address();
1027        let irte = self.lookup_irte(irt_base, irte_index, source_id)?;
1028
1029        // Validate source ID.
1030        self.validate_irte_source(source_id, &irte, irte_index)?;
1031
1032        // Check IRTE mode (IM=0 for remapped, IM=1 for posted — not supported).
1033        let irte_lo = irte.lo;
1034        if irte_lo.im() {
1035            return Err(VtdFault::IrteReservedField {
1036                source_id,
1037                irte_index,
1038                fpd: irte_lo.fpd(),
1039            });
1040        }
1041
1042        // Construct remapped MSI address and data.
1043        let eime = irta.eime();
1044        let (new_address, new_data) = Self::construct_remapped_msi(irte_lo, eime);
1045
1046        Ok((new_address, new_data))
1047    }
1048
1049    /// Handle compatibility-format MSI when IR is enabled.
1050    fn handle_compat_format(
1051        &self,
1052        state: &VtdState,
1053        source_id: u16,
1054        address: u64,
1055        data: u32,
1056    ) -> Result<(u64, u32), VtdFault> {
1057        let irta = state.latched_irta;
1058        let gsts = state.gsts;
1059
1060        // EIME=1 → always block compatibility-format interrupts.
1061        if irta.eime() {
1062            return Err(VtdFault::CompatibilityFormatBlocked { source_id });
1063        }
1064
1065        // CFIS=1 → pass through.
1066        if gsts.cfis() {
1067            return Ok((address, data));
1068        }
1069
1070        // CFIS=0 → block.
1071        Err(VtdFault::CompatibilityFormatBlocked { source_id })
1072    }
1073
1074    /// Extract the IRTE index from remappable-format MSI address/data.
1075    ///
1076    /// Handle = {address[2], address[19:5]} (16-bit).
1077    /// SHV = address[3].
1078    /// If SHV=0: index = handle.
1079    /// If SHV=1: index = handle + data[15:0].
1080    fn extract_irte_index(address: u64, data: u32) -> u16 {
1081        let handle_lo = ((address >> 5) & 0x7FFF) as u16; // address[19:5] = 15 bits
1082        let handle_hi = ((address >> 2) & 1) as u16; // address[2] = 1 bit
1083        let handle = (handle_hi << 15) | handle_lo;
1084
1085        let shv = (address >> 3) & 1 != 0;
1086        if shv {
1087            handle.wrapping_add(data as u16)
1088        } else {
1089            handle
1090        }
1091    }
1092
1093    /// Look up an IRTE by index.
1094    fn lookup_irte(
1095        &self,
1096        irt_base: u64,
1097        irte_index: u16,
1098        source_id: u16,
1099    ) -> Result<Irte, VtdFault> {
1100        let entry_addr = irt_base + (irte_index as u64) * 16;
1101        let irte: Irte =
1102            self.guest_memory
1103                .read_plain(entry_addr)
1104                .map_err(|_| VtdFault::IrteAccessError {
1105                    source_id,
1106                    irte_index,
1107                })?;
1108
1109        if !irte.lo.p() {
1110            return Err(VtdFault::IrteNotPresent {
1111                source_id,
1112                irte_index,
1113                fpd: irte.lo.fpd(),
1114            });
1115        }
1116
1117        Ok(irte)
1118    }
1119
1120    /// Validate the interrupt source against the IRTE's SVT/SID/SQ fields.
1121    fn validate_irte_source(
1122        &self,
1123        source_id: u16,
1124        irte: &Irte,
1125        irte_index: u16,
1126    ) -> Result<(), VtdFault> {
1127        let svt = SourceValidationType(irte.hi.svt());
1128        let irte_sid = irte.hi.sid();
1129        let sq = irte.hi.sq();
1130        let fpd = irte.lo.fpd();
1131
1132        match svt {
1133            SourceValidationType::NONE => Ok(()),
1134            SourceValidationType::VERIFY_SID => {
1135                // Mask based on SQ: SQ=00 all 16 bits, SQ=01 ignore bit 2,
1136                // SQ=10 ignore bits 2:1, SQ=11 ignore bits 2:0.
1137                let mask: u16 = match sq {
1138                    0b00 => 0xFFFF,
1139                    0b01 => !0x04,
1140                    0b10 => !0x06,
1141                    0b11 => !0x07,
1142                    _ => 0xFFFF,
1143                };
1144                if (source_id & mask) != (irte_sid & mask) {
1145                    Err(VtdFault::SourceValidationFailed {
1146                        source_id,
1147                        irte_index,
1148                        fpd,
1149                    })
1150                } else {
1151                    Ok(())
1152                }
1153            }
1154            SourceValidationType::VERIFY_BUS_RANGE => {
1155                // SID[7:0] = start bus, SID[15:8] = end bus.
1156                let start_bus = (irte_sid & 0xFF) as u8;
1157                let end_bus = ((irte_sid >> 8) & 0xFF) as u8;
1158                let source_bus = ((source_id >> 8) & 0xFF) as u8;
1159                if source_bus < start_bus || source_bus > end_bus {
1160                    Err(VtdFault::SourceValidationFailed {
1161                        source_id,
1162                        irte_index,
1163                        fpd,
1164                    })
1165                } else {
1166                    Ok(())
1167                }
1168            }
1169            _ => {
1170                // Unknown SVT — treat as no validation.
1171                Ok(())
1172            }
1173        }
1174    }
1175
1176    /// Construct remapped MSI address and data from an IRTE.
1177    fn construct_remapped_msi(irte_lo: IrteLo, eime: bool) -> (u64, u32) {
1178        let dst = if eime {
1179            // x2APIC: full 32-bit destination.
1180            irte_lo.dst()
1181        } else {
1182            // xAPIC: 8-bit destination from bits 15:8 of DST field.
1183            irte_lo.xapic_destination() as u32
1184        };
1185
1186        let new_address = x86defs::msi::MsiAddress::new()
1187            .with_address(x86defs::msi::MSI_ADDRESS)
1188            .with_virt_destination(dst as u16)
1189            .with_destination_mode_logical(irte_lo.dm())
1190            .with_redirection_hint(irte_lo.rh());
1191
1192        let new_data = x86defs::msi::MsiData::new()
1193            .with_vector(irte_lo.vector())
1194            .with_delivery_mode(irte_lo.dlm())
1195            .with_trigger_mode_level(irte_lo.tm());
1196
1197        (new_address.into_bits() as u64, new_data.into_bits())
1198    }
1199}
1200
1201// =============================================================================
1202// VtdFault — IOMMU-specific translation/remapping error
1203// =============================================================================
1204
1205/// Failure to atomically mark a second-stage paging entry (§7.1.3, SSS.5/6).
1206#[derive(Debug, thiserror::Error)]
1207pub enum SecondStageAdError {
1208    /// A required update cannot be performed without page-walk snooping.
1209    #[error("second-stage A/D update requires page-walk snooping")]
1210    NonSnooping,
1211    /// Guest memory rejected the atomic access.
1212    #[error("second-stage A/D atomic memory access failed")]
1213    Memory(#[source] GuestMemoryError),
1214    /// Conflicting page-table changes exhausted the per-translation budget.
1215    #[error("second-stage A/D walk retry budget exhausted")]
1216    RetryExhausted,
1217}
1218
1219/// VT-d translation or interrupt remapping fault.
1220///
1221/// Each variant carries the source ID and faulting address/index,
1222/// and maps to a specific VT-d fault reason code.
1223#[derive(Debug, thiserror::Error)]
1224#[expect(missing_docs)]
1225pub enum VtdFault {
1226    #[error("{error} (source_id={source_id:#06x}, iova={iova:#x})")]
1227    AdUpdate {
1228        source_id: u16,
1229        iova: u64,
1230        fpd: bool,
1231        #[source]
1232        error: SecondStageAdError,
1233    },
1234
1235    #[error("DMA fault {reason:?} (source_id={source_id:#06x}, iova={iova:#x})")]
1236    Dma {
1237        source_id: u16,
1238        iova: u64,
1239        reason: FaultReason,
1240        fpd: bool,
1241    },
1242
1243    #[error("root entry not present (source_id={source_id:#06x})")]
1244    RootNotPresent { source_id: u16, iova: u64 },
1245
1246    #[error("context entry not present (source_id={source_id:#06x})")]
1247    ContextNotPresent {
1248        source_id: u16,
1249        iova: u64,
1250        fpd: bool,
1251    },
1252
1253    #[error("invalid context entry (source_id={source_id:#06x}, iova={iova:#x})")]
1254    InvalidContextEntry {
1255        source_id: u16,
1256        iova: u64,
1257        fpd: bool,
1258    },
1259
1260    #[error("IOVA beyond MGAW (source_id={source_id:#06x}, iova={iova:#x})")]
1261    AddressBeyondMgaw {
1262        source_id: u16,
1263        iova: u64,
1264        fpd: bool,
1265    },
1266
1267    #[error("{} access denied (source_id={source_id:#06x}, iova={iova:#x})", if *write { "write" } else { "read" })]
1268    AccessDenied {
1269        source_id: u16,
1270        iova: u64,
1271        fpd: bool,
1272        write: bool,
1273    },
1274
1275    #[error("root entry access error (source_id={source_id:#06x})")]
1276    RootEntryAccessError { source_id: u16, iova: u64 },
1277
1278    #[error("context entry access error (source_id={source_id:#06x})")]
1279    ContextEntryAccessError { source_id: u16, iova: u64 },
1280
1281    #[error("page table access error (source_id={source_id:#06x}, iova={iova:#x})")]
1282    PageTableAccessError {
1283        source_id: u16,
1284        iova: u64,
1285        fpd: bool,
1286    },
1287
1288    #[error("IRTE not present (source_id={source_id:#06x}, index={irte_index})")]
1289    IrteNotPresent {
1290        source_id: u16,
1291        irte_index: u16,
1292        fpd: bool,
1293    },
1294
1295    #[error("IRTE access error (source_id={source_id:#06x}, index={irte_index})")]
1296    IrteAccessError { source_id: u16, irte_index: u16 },
1297
1298    #[error("IRTE index exceeds IRT size (source_id={source_id:#06x}, index={irte_index})")]
1299    IrteIndexExceedsSize { source_id: u16, irte_index: u16 },
1300
1301    #[error("IRTE reserved field set (source_id={source_id:#06x}, index={irte_index})")]
1302    IrteReservedField {
1303        source_id: u16,
1304        irte_index: u16,
1305        fpd: bool,
1306    },
1307
1308    #[error("source validation failed (source_id={source_id:#06x})")]
1309    SourceValidationFailed {
1310        source_id: u16,
1311        irte_index: u16,
1312        fpd: bool,
1313    },
1314
1315    #[error("compatibility-format interrupt blocked (source_id={source_id:#06x})")]
1316    CompatibilityFormatBlocked { source_id: u16 },
1317}
1318
1319impl VtdFault {
1320    /// Get the fault reason code for this fault.
1321    fn fault_reason(&self) -> FaultReason {
1322        match self {
1323            Self::AdUpdate { error, .. } => match error {
1324                SecondStageAdError::NonSnooping => FaultReason::SL_AD_UPDATE_NON_SNOOP,
1325                SecondStageAdError::Memory(_) | SecondStageAdError::RetryExhausted => {
1326                    FaultReason::SL_AD_UPDATE_FAILED
1327                }
1328            },
1329            Self::Dma { reason, .. } => *reason,
1330            Self::RootNotPresent { .. } => FaultReason::ROOT_NOT_PRESENT,
1331            Self::ContextNotPresent { .. } => FaultReason::CONTEXT_NOT_PRESENT,
1332            Self::InvalidContextEntry { .. } => FaultReason::INVALID_CONTEXT_ENTRY,
1333            Self::AddressBeyondMgaw { .. } => FaultReason::ADDRESS_BEYOND_MGAW,
1334            Self::AccessDenied { write: true, .. } => FaultReason::WRITE_ACCESS_DENIED,
1335            Self::AccessDenied { write: false, .. } => FaultReason::READ_ACCESS_DENIED,
1336            Self::RootEntryAccessError { .. } => FaultReason::ROOT_ENTRY_ACCESS_ERROR,
1337            Self::ContextEntryAccessError { .. } => FaultReason::CONTEXT_ENTRY_ACCESS_ERROR,
1338            Self::PageTableAccessError { .. } => FaultReason::SL_PTE_ACCESS_ERROR,
1339            Self::IrteNotPresent { .. } => FaultReason::IRTE_NOT_PRESENT,
1340            Self::IrteAccessError { .. } => FaultReason::IRTE_ACCESS_ERROR,
1341            Self::IrteIndexExceedsSize { .. } => FaultReason::IR_INDEX_EXCEEDS_SIZE,
1342            Self::IrteReservedField { .. } => FaultReason::IRTE_RESERVED_FIELD,
1343            Self::SourceValidationFailed { .. } => FaultReason::SOURCE_ID_VERIFICATION_FAIL,
1344            Self::CompatibilityFormatBlocked { .. } => FaultReason::COMPAT_FORMAT_BLOCKED,
1345        }
1346    }
1347
1348    /// Get the source ID from this fault.
1349    fn source_id(&self) -> u16 {
1350        match self {
1351            Self::AdUpdate { source_id, .. }
1352            | Self::Dma { source_id, .. }
1353            | Self::RootNotPresent { source_id, .. }
1354            | Self::ContextNotPresent { source_id, .. }
1355            | Self::InvalidContextEntry { source_id, .. }
1356            | Self::AddressBeyondMgaw { source_id, .. }
1357            | Self::AccessDenied { source_id, .. }
1358            | Self::RootEntryAccessError { source_id, .. }
1359            | Self::ContextEntryAccessError { source_id, .. }
1360            | Self::PageTableAccessError { source_id, .. }
1361            | Self::IrteNotPresent { source_id, .. }
1362            | Self::IrteAccessError { source_id, .. }
1363            | Self::IrteIndexExceedsSize { source_id, .. }
1364            | Self::IrteReservedField { source_id, .. }
1365            | Self::SourceValidationFailed { source_id, .. }
1366            | Self::CompatibilityFormatBlocked { source_id, .. } => *source_id,
1367        }
1368    }
1369
1370    /// Get the faulting address (IOVA for DMA, 0 for IR faults).
1371    fn fault_address(&self) -> u64 {
1372        match self {
1373            Self::AdUpdate { iova, .. }
1374            | Self::Dma { iova, .. }
1375            | Self::RootNotPresent { iova, .. }
1376            | Self::ContextNotPresent { iova, .. }
1377            | Self::InvalidContextEntry { iova, .. }
1378            | Self::AddressBeyondMgaw { iova, .. }
1379            | Self::AccessDenied { iova, .. }
1380            | Self::RootEntryAccessError { iova, .. }
1381            | Self::ContextEntryAccessError { iova, .. }
1382            | Self::PageTableAccessError { iova, .. } => *iova,
1383            // Interrupt remapping faults don't have an IOVA.
1384            Self::IrteNotPresent { .. }
1385            | Self::IrteAccessError { .. }
1386            | Self::IrteIndexExceedsSize { .. }
1387            | Self::IrteReservedField { .. }
1388            | Self::SourceValidationFailed { .. }
1389            | Self::CompatibilityFormatBlocked { .. } => 0,
1390        }
1391    }
1392
1393    /// Whether fault processing is disabled for this fault.
1394    fn fpd(&self) -> bool {
1395        match self {
1396            // Table 26: lookup access errors are unqualified even after a
1397            // parent FPD has been observed. Page-walk faults are qualified.
1398            Self::Dma { reason, fpd, .. } => {
1399                *fpd && !matches!(
1400                    *reason,
1401                    FaultReason::INVALID_ROOT_TABLE_MODE
1402                        | FaultReason::SCALABLE_ROOT_ACCESS_ERROR
1403                        | FaultReason::SCALABLE_ROOT_NOT_PRESENT
1404                        | FaultReason::SCALABLE_ROOT_RESERVED_BIT
1405                        | FaultReason::SCALABLE_CONTEXT_ACCESS_ERROR
1406                        | FaultReason::PASID_DIRECTORY_ACCESS_ERROR
1407                        | FaultReason::PASID_TABLE_ACCESS_ERROR
1408                )
1409            }
1410            Self::AdUpdate { fpd, .. }
1411            | Self::ContextNotPresent { fpd, .. }
1412            | Self::AddressBeyondMgaw { fpd, .. }
1413            | Self::InvalidContextEntry { fpd, .. }
1414            | Self::AccessDenied { fpd, .. }
1415            | Self::PageTableAccessError { fpd, .. }
1416            | Self::IrteNotPresent { fpd, .. }
1417            | Self::IrteReservedField { fpd, .. }
1418            | Self::SourceValidationFailed { fpd, .. } => *fpd,
1419            // Faults without FPD context are always recorded.
1420            Self::RootNotPresent { .. }
1421            | Self::RootEntryAccessError { .. }
1422            | Self::ContextEntryAccessError { .. }
1423            | Self::IrteAccessError { .. }
1424            | Self::IrteIndexExceedsSize { .. }
1425            | Self::CompatibilityFormatBlocked { .. } => false,
1426        }
1427    }
1428
1429    /// Record this fault in the IOMMU's fault recording registers.
1430    ///
1431    /// `is_write` is the access type of the faulting request. This sets
1432    /// FRCD.T (0=write, 1=read/non-write) per §7.1. The access type is a
1433    /// property of the DMA/interrupt request, not of the fault itself.
1434    ///
1435    /// Takes a write lock on the shared state.
1436    fn record(&self, shared: &VtdSharedState, is_write: bool) {
1437        let mut state = shared.state.write();
1438        shared.record_fault_locked(
1439            &mut state,
1440            self.source_id(),
1441            self.fault_reason(),
1442            self.fault_address(),
1443            !is_write, // FRCD.T: 1=read, 0=write
1444            self.fpd(),
1445        );
1446    }
1447}
1448
1449/// Combine bus and devfn into a 16-bit BDF source ID.
1450fn bdf(bus: u8, devfn: u8) -> u16 {
1451    ((bus as u16) << 8) | devfn as u16
1452}
1453
1454// =============================================================================
1455// VtdTranslator — IommuTranslator implementation (1G.2)
1456// =============================================================================
1457
1458/// Per-device IOVA→GPA translator for Intel VT-d.
1459///
1460/// Implements [`iommu_common::IommuTranslator`] using the closure-based API
1461/// that holds the read lock across translation and the memory access operation.
1462#[derive(Clone)]
1463pub struct VtdTranslator {
1464    shared: Arc<VtdSharedState>,
1465}
1466
1467impl iommu_common::IommuTranslator for VtdTranslator {
1468    type Error = VtdFault;
1469
1470    fn max_iova(&self) -> u64 {
1471        // 48-bit address space.
1472        (1u64 << 48) - 1
1473    }
1474
1475    fn translate<R>(
1476        &self,
1477        rid: u16,
1478        iova: u64,
1479        write: bool,
1480        op: impl FnOnce(u64) -> R,
1481    ) -> Result<R, iommu_common::TranslationFault<VtdFault>> {
1482        self.translate_with_capabilities(rid, iova, write, EcapReg::from(ECAP_VALUE), op)
1483    }
1484}
1485
1486impl VtdTranslator {
1487    fn translate_with_capabilities<R>(
1488        &self,
1489        rid: u16,
1490        iova: u64,
1491        write: bool,
1492        ecap: EcapReg,
1493        op: impl FnOnce(u64) -> R,
1494    ) -> Result<R, iommu_common::TranslationFault<VtdFault>> {
1495        let bus = (rid >> 8) as u8;
1496        let devfn = rid as u8;
1497        self.translate_with(
1498            iova,
1499            write,
1500            |state| {
1501                self.shared
1502                    .translate_locked(state, bus, devfn, iova, write, ecap)
1503            },
1504            op,
1505        )
1506    }
1507
1508    fn translate_with<R>(
1509        &self,
1510        iova: u64,
1511        write: bool,
1512        translate: impl FnOnce(&VtdState) -> Result<u64, VtdFault>,
1513        op: impl FnOnce(u64) -> R,
1514    ) -> Result<R, iommu_common::TranslationFault<VtdFault>> {
1515        // Serialize translate + op against invalidation, not guest PTE writes.
1516        let state = self.shared.state.read();
1517        let gpa = match translate(&state) {
1518            Ok(gpa) => gpa,
1519            Err(fault) => {
1520                // Drop the read lock before acquiring write lock for fault recording.
1521                drop(state);
1522                fault.record(&self.shared, write);
1523                return Err(iommu_common::TranslationFault { iova, error: fault });
1524            }
1525        };
1526
1527        let result = op(gpa);
1528        drop(state);
1529        Ok(result)
1530    }
1531}
1532
1533// =============================================================================
1534// VtdSignalMsi — MSI remapping wrapper (1G.3)
1535// =============================================================================
1536
1537/// MSI remapping wrapper for Intel VT-d interrupt remapping.
1538///
1539/// Wraps an inner `SignalMsi` and intercepts MSI writes to remap them
1540/// through the Interrupt Remapping Table.
1541pub struct VtdSignalMsi {
1542    shared: Arc<VtdSharedState>,
1543    inner: Arc<dyn SignalMsi>,
1544}
1545
1546impl SignalMsi for VtdSignalMsi {
1547    fn signal_msi(&self, devid: Option<u32>, address: u64, data: u32) {
1548        let Some(device_id) = devid else {
1549            // No source ID — drop the MSI. Without a BDF, source validation
1550            // and IRTE lookup are impossible.
1551            return;
1552        };
1553        let source_id = device_id as u16;
1554
1555        let state = self.shared.state.read();
1556        match self
1557            .shared
1558            .remap_msi_locked(&state, source_id, address, data)
1559        {
1560            Ok((new_address, new_data)) => {
1561                drop(state);
1562                self.inner.signal_msi(devid, new_address, new_data);
1563            }
1564            Err(fault) => {
1565                drop(state);
1566                tracelimit::warn_ratelimited!(
1567                    source_id,
1568                    address,
1569                    data,
1570                    error = &fault as &dyn std::error::Error,
1571                    "vtd: MSI remapping fault, interrupt dropped"
1572                );
1573                // MSI is a posted write transaction, so is_write=true.
1574                fault.record(&self.shared, true);
1575            }
1576        }
1577    }
1578}
1579
1580impl iommu_common::InterruptRemapper for VtdSharedState {
1581    fn remap_msi(&self, device_id: u16, address: u64, data: u32) -> Option<(u64, u32)> {
1582        let state = self.state.read();
1583        match self.remap_msi_locked(&state, device_id, address, data) {
1584            Ok(result) => Some(result),
1585            Err(fault) => {
1586                drop(state);
1587                // This runs at interrupt delivery time (the IOAPIC wiring
1588                // translates its routes lazily, on assertion), so a fault here
1589                // corresponds to an interrupt actually firing through a bad
1590                // entry. Record it, matching hardware which performs the IRTE
1591                // lookup and records faults at delivery time.
1592                tracelimit::warn_ratelimited!(
1593                    device_id,
1594                    address,
1595                    data,
1596                    error = &fault as &dyn std::error::Error,
1597                    "vtd: interrupt remapping fault on registered-route delivery, dropping interrupt"
1598                );
1599                fault.record(self, true);
1600                None
1601            }
1602        }
1603    }
1604
1605    fn register_route(&self, route: &Arc<dyn iommu_common::RetranslateInterrupts>) {
1606        self.retranslate_interrupts.register(route);
1607    }
1608
1609    fn unregister_route(&self, route: &Arc<dyn iommu_common::RetranslateInterrupts>) {
1610        self.retranslate_interrupts.unregister(route);
1611    }
1612}
1613
1614// =============================================================================
1615// IntelVtdDevice
1616// =============================================================================
1617
1618/// Intel VT-d IOMMU emulator device.
1619///
1620/// A pure MMIO platform device (no PCI config space) discovered via the ACPI
1621/// DMAR table. Implements the VT-d register file for IOMMU control, DMA
1622/// translation, interrupt remapping, invalidation queue, and fault recording.
1623pub struct IntelVtdDevice {
1624    /// Fixed MMIO base address.
1625    mmio_base: u64,
1626    /// Static region descriptor for MmioIntercept.
1627    mmio_region: (&'static str, RangeInclusive<u64>),
1628    /// Shared IOMMU state (accessible by per-device wrappers).
1629    shared: Arc<VtdSharedState>,
1630}
1631
1632impl IntelVtdDevice {
1633    /// Create a new Intel VT-d IOMMU device.
1634    ///
1635    /// `guest_memory` is used for reading root/context/page tables and IRT.
1636    /// `signal_msi` is the partition's MSI delivery handle — used for the
1637    /// IOMMU's own fault event and invalidation completion interrupts.
1638    pub fn new(
1639        guest_memory: GuestMemory,
1640        config: IntelVtdConfig,
1641        signal_msi: Arc<dyn SignalMsi>,
1642    ) -> (Self, Arc<VtdSharedState>) {
1643        let mmio_base = config.mmio_base;
1644        let shared = Arc::new(VtdSharedState::new(guest_memory, signal_msi));
1645
1646        let device = Self {
1647            mmio_base,
1648            mmio_region: (
1649                "intel-vtd-mmio",
1650                mmio_base..=mmio_base + MMIO_REGION_SIZE - 1,
1651            ),
1652            shared: shared.clone(),
1653        };
1654
1655        (device, shared)
1656    }
1657
1658    /// Returns the shared IOMMU state for creating per-device wrappers.
1659    pub fn shared_state(&self) -> &Arc<VtdSharedState> {
1660        &self.shared
1661    }
1662
1663    // =========================================================================
1664    // MMIO Register Read (DWORD granularity)
1665    // =========================================================================
1666
1667    /// Read a 32-bit register value at a DWORD-aligned MMIO offset.
1668    ///
1669    /// All register reads go through this function. 64-bit reads are composed
1670    /// from two DWORD reads. This avoids alignment issues with 32-bit registers
1671    /// at non-8-byte-aligned offsets (e.g. GSTS at 0x01C, FSTS at 0x034).
1672    fn read_register_dword(&self, offset: u16) -> u32 {
1673        let state = self.shared.state.read();
1674        self.read_register_dword_locked(&state, offset)
1675    }
1676
1677    /// Read a DWORD register while already holding the state lock.
1678    fn read_register_dword_locked(&self, state: &VtdState, offset: u16) -> u32 {
1679        /// Extract the lo DWORD from a 64-bit value.
1680        fn lo(val: u64) -> u32 {
1681            val as u32
1682        }
1683        /// Extract the hi DWORD from a 64-bit value.
1684        fn hi(val: u64) -> u32 {
1685            (val >> 32) as u32
1686        }
1687
1688        match Reg(offset) {
1689            Reg::VER => VER_VALUE,
1690            Reg::CAP => lo(CAP_VALUE),
1691            Reg::CAP_HI => hi(CAP_VALUE),
1692            Reg::ECAP => lo(ECAP_VALUE),
1693            Reg::ECAP_HI => hi(ECAP_VALUE),
1694            Reg::GCMD => 0, // write-only
1695            Reg::GSTS => state.gsts.into_bits(),
1696            Reg::RTADDR => lo(state.rtaddr.into_bits()),
1697            Reg::RTADDR_HI => hi(state.rtaddr.into_bits()),
1698            Reg::CCMD => lo(state.ccmd.into_bits()),
1699            Reg::CCMD_HI => hi(state.ccmd.into_bits()),
1700            Reg::FSTS => self.read_fsts(state),
1701            Reg::FECTL => state.fectl.into_bits(),
1702            Reg::FEDATA => state.fedata,
1703            Reg::FEADDR => state.feaddr,
1704            Reg::FEUADDR => state.feuaddr,
1705            Reg::IQH => lo(state.iqh.into_bits()),
1706            Reg::IQH_HI => hi(state.iqh.into_bits()),
1707            Reg::IQT => lo(state.iqt.into_bits()),
1708            Reg::IQT_HI => hi(state.iqt.into_bits()),
1709            Reg::IQA => lo(state.iqa.into_bits()),
1710            Reg::IQA_HI => hi(state.iqa.into_bits()),
1711            Reg::ICS => state.ics.into_bits(),
1712            Reg::IECTL => state.iectl.into_bits(),
1713            Reg::IEDATA => state.iedata,
1714            Reg::IEADDR => state.ieaddr,
1715            Reg::IEUADDR => state.ieuaddr,
1716            Reg::IRTA => lo(state.irta.into_bits()),
1717            Reg::IRTA_HI => hi(state.irta.into_bits()),
1718            Reg::IVA => lo(state.iva),
1719            Reg::IVA_HI => hi(state.iva),
1720            Reg::IOTLB => lo(state.iotlb.into_bits()),
1721            Reg::IOTLB_HI => hi(state.iotlb.into_bits()),
1722            Reg::FRCD_DW0 => lo(state.frcd_lo.into_bits()),
1723            Reg::FRCD_DW1 => hi(state.frcd_lo.into_bits()),
1724            Reg::FRCD_DW2 => lo(state.frcd_hi.into_bits()),
1725            Reg::FRCD_DW3 => hi(state.frcd_hi.into_bits()),
1726            _ => 0,
1727        }
1728    }
1729
1730    /// Read FSTS with PPF dynamically computed as OR of FRCD[n].F bits.
1731    fn read_fsts(&self, state: &VtdState) -> u32 {
1732        let mut fsts = state.fsts;
1733        // PPF = OR of all FRCD[n].F bits. With NFR=0 (1 record), this
1734        // is just FRCD[0].F.
1735        fsts.set_ppf(state.frcd_hi.f());
1736        fsts.into_bits()
1737    }
1738
1739    /// Cancel an undelivered fault interrupt once all sources are serviced
1740    /// (§§7.3 and 11.4.7.2). Residual status must not re-latch a delivered event.
1741    fn reconcile_fault_interrupt(&self, state: &mut VtdState) {
1742        let fsts = FstsReg::from(self.read_fsts(state));
1743        if !fsts.ppf() && !fsts.pfo() && !fsts.iqe() && !fsts.ice() && !fsts.ite() {
1744            state.fectl.set_ip(false);
1745        }
1746    }
1747
1748    // =========================================================================
1749    // MMIO Register Write (DWORD granularity)
1750    // =========================================================================
1751
1752    /// Write a 32-bit value at a DWORD-aligned MMIO offset.
1753    ///
1754    /// Acquires the write lock, performs the register write, and releases it.
1755    /// 64-bit MMIO writes other than IQT call this twice. Command registers
1756    /// trigger on one DWORD and configuration registers are separately latched.
1757    /// IQT is handled atomically so an invalid upper half cannot be overlooked.
1758    fn write_register_dword(&mut self, offset: u16, value: u32) {
1759        let mut state = self.shared.state.write();
1760        let mut process_queue = false;
1761        tracing::trace!(offset, value, "vtd mmio_write_dword");
1762
1763        /// Merge a DWORD write into the lo or hi half of a 64-bit value.
1764        fn write_lo(old: u64, value: u32) -> u64 {
1765            (old & !0xFFFF_FFFF) | value as u64
1766        }
1767        fn write_hi(old: u64, value: u32) -> u64 {
1768            (old & 0xFFFF_FFFF) | ((value as u64) << 32)
1769        }
1770
1771        match Reg(offset) {
1772            // Read-only registers.
1773            Reg::VER
1774            | Reg::CAP
1775            | Reg::CAP_HI
1776            | Reg::ECAP
1777            | Reg::ECAP_HI
1778            | Reg::GSTS
1779            | Reg::IQH
1780            | Reg::IQH_HI
1781            | Reg::FRCD_DW0
1782            | Reg::FRCD_DW1
1783            | Reg::FRCD_DW2 => {}
1784
1785            Reg::GCMD => {
1786                self.process_gcmd(&mut state, value);
1787                process_queue = true;
1788            }
1789
1790            Reg::RTADDR => {
1791                state.rtaddr = RtaddrReg::from(write_lo(state.rtaddr.into_bits(), value));
1792            }
1793            Reg::RTADDR_HI => {
1794                state.rtaddr = RtaddrReg::from(write_hi(state.rtaddr.into_bits(), value));
1795            }
1796
1797            Reg::CCMD => {
1798                state.ccmd = CcmdReg::from(write_lo(state.ccmd.into_bits(), value));
1799            }
1800            Reg::CCMD_HI => {
1801                // ICC is bit 63 — process on hi DWORD write.
1802                let full = write_hi(state.ccmd.into_bits(), value);
1803                self.process_ccmd(&mut state, full);
1804            }
1805
1806            Reg::FSTS => {
1807                let write_val = FstsReg::from(value);
1808                let mut fsts = state.fsts;
1809                if write_val.pfo() {
1810                    fsts.set_pfo(false);
1811                }
1812                if write_val.iqe() {
1813                    fsts.set_iqe(false);
1814                }
1815                if write_val.ice() {
1816                    fsts.set_ice(false);
1817                }
1818                if write_val.ite() {
1819                    fsts.set_ite(false);
1820                }
1821                state.fsts = fsts;
1822                self.reconcile_fault_interrupt(&mut state);
1823                process_queue = write_val.iqe() || write_val.ite();
1824            }
1825
1826            Reg::FECTL => {
1827                let new = FectlReg::from(value);
1828                let old = state.fectl;
1829                state.fectl = FectlReg::new().with_im(new.im()).with_ip(old.ip());
1830                if old.im() && !new.im() && old.ip() {
1831                    state.fectl = state.fectl.with_ip(false);
1832                    self.shared.deliver_fault_interrupt(&state);
1833                }
1834            }
1835
1836            Reg::FEDATA => state.fedata = value,
1837            Reg::FEADDR => state.feaddr = value,
1838            Reg::FEUADDR => state.feuaddr = value,
1839
1840            Reg::IQT => {
1841                state.iqt = IqtReg::from(write_lo(state.iqt.into_bits(), value));
1842                process_queue = true;
1843            }
1844            Reg::IQT_HI => {
1845                state.iqt = IqtReg::from(write_hi(state.iqt.into_bits(), value));
1846                process_queue = true;
1847            }
1848
1849            Reg::IQA => {
1850                if !state.gsts.qies() {
1851                    state.iqa = IqaReg::from(write_lo(state.iqa.into_bits(), value));
1852                } else {
1853                    tracelimit::warn_ratelimited!("vtd: write to IQA while QIE=1, ignored");
1854                }
1855            }
1856            Reg::IQA_HI => {
1857                if !state.gsts.qies() {
1858                    state.iqa = IqaReg::from(write_hi(state.iqa.into_bits(), value));
1859                } else {
1860                    tracelimit::warn_ratelimited!("vtd: write to IQA while QIE=1, ignored");
1861                }
1862            }
1863
1864            Reg::ICS => {
1865                let write_val = IcsReg::from(value);
1866                let mut ics = state.ics;
1867                if write_val.iwc() {
1868                    ics.set_iwc(false);
1869                    state.iectl.set_ip(false);
1870                }
1871                state.ics = ics;
1872            }
1873
1874            Reg::IECTL => {
1875                let new = IectlReg::from(value);
1876                let old = state.iectl;
1877                state.iectl = IectlReg::new().with_im(new.im()).with_ip(old.ip());
1878                if old.im() && !new.im() && old.ip() {
1879                    state.iectl = state.iectl.with_ip(false);
1880                    self.shared.deliver_invalidation_interrupt(&state);
1881                }
1882            }
1883
1884            Reg::IEDATA => state.iedata = value,
1885            Reg::IEADDR => state.ieaddr = value,
1886            Reg::IEUADDR => state.ieuaddr = value,
1887
1888            Reg::IRTA => {
1889                if !state.gsts.ires() {
1890                    state.irta = IrtaReg::from(write_lo(state.irta.into_bits(), value));
1891                } else {
1892                    tracelimit::warn_ratelimited!("vtd: write to IRTA while IRE=1, ignored");
1893                }
1894            }
1895            Reg::IRTA_HI => {
1896                if !state.gsts.ires() {
1897                    state.irta = IrtaReg::from(write_hi(state.irta.into_bits(), value));
1898                } else {
1899                    tracelimit::warn_ratelimited!("vtd: write to IRTA while IRE=1, ignored");
1900                }
1901            }
1902
1903            Reg::IVA => state.iva = write_lo(state.iva, value),
1904            Reg::IVA_HI => state.iva = write_hi(state.iva, value),
1905
1906            Reg::IOTLB => {
1907                state.iotlb = IotlbReg::from(write_lo(state.iotlb.into_bits(), value));
1908            }
1909            Reg::IOTLB_HI => {
1910                // IVT is bit 63 — process on hi DWORD write.
1911                let full = write_hi(state.iotlb.into_bits(), value);
1912                self.process_iotlb_reg(&mut state, full);
1913            }
1914
1915            Reg::FRCD_DW3 => {
1916                // F bit (bit 31 of this DWORD = bit 63 of FRCD_HI) is RW1C.
1917                if (value >> 31) & 1 != 0 {
1918                    state.frcd_hi = state.frcd_hi.with_f(false);
1919                    self.reconcile_fault_interrupt(&mut state);
1920                }
1921            }
1922
1923            _ => {} // Unmapped offsets: silently ignored.
1924        }
1925
1926        drop(state);
1927        if process_queue {
1928            self.process_invalidation_queue();
1929        }
1930    }
1931
1932    // =========================================================================
1933    // GCMD Processing (1B.2)
1934    // =========================================================================
1935
1936    /// Process a write to the Global Command Register (GCMD).
1937    ///
1938    /// GCMD is write-only. Each bit triggers an action; status is reflected
1939    /// in GSTS. Toggle bits (TE, QIE, IRE, CFI) compare against current GSTS.
1940    /// One-shot bits (SRTP, SIRTP, WBF) fire if set.
1941    fn process_gcmd(&self, state: &mut VtdState, value: u32) {
1942        let gcmd = GcmdReg::from(value);
1943        let mut gsts = state.gsts;
1944
1945        // -- One-shot: Set Root Table Pointer (SRTP) --
1946        if gcmd.srtp() {
1947            state.latched_rtaddr = state.rtaddr;
1948            gsts.set_rtps(true);
1949        }
1950
1951        // -- One-shot: Set Interrupt Remapping Table Pointer (SIRTP) --
1952        if gcmd.sirtp() {
1953            state.latched_irta = state.irta;
1954            gsts.set_irtps(true);
1955        }
1956
1957        // -- One-shot: Write Buffer Flush (WBF) --
1958        if gcmd.wbf() {
1959            // No write buffer in emulator — set status immediately.
1960            gsts.set_wbfs(true);
1961        }
1962
1963        // -- One-shot: Set Fault Log / Enable Advanced Fault Logging --
1964        // AFL=0 in CAP, so these are no-ops, but set status for compatibility.
1965        if gcmd.sfl() {
1966            gsts.set_fls(true);
1967        }
1968        if gcmd.eafl() {
1969            gsts.set_afls(true);
1970        }
1971
1972        // -- Toggle: Queued Invalidation Enable (QIE) --
1973        if gcmd.qie() != gsts.qies() {
1974            if gcmd.qie() {
1975                // Enable QI.
1976                gsts.set_qies(true);
1977                // Reset head on enable.
1978                state.iqh = IqhReg::new();
1979            } else {
1980                // Disable QI — reject if TE or IRE is still enabled.
1981                if gsts.tes() || gsts.ires() {
1982                    tracelimit::warn_ratelimited!(
1983                        "vtd: cannot disable QIE while TE or IRE is enabled"
1984                    );
1985                } else {
1986                    gsts.set_qies(false);
1987                    state.iqh = IqhReg::new();
1988                }
1989            }
1990        }
1991
1992        // -- Toggle: Translation Enable (TE) --
1993        if gcmd.te() != gsts.tes() {
1994            if gcmd.te() {
1995                // Enable TE — reject if RTPS=0.
1996                if !gsts.rtps() {
1997                    tracelimit::warn_ratelimited!(
1998                        "vtd: cannot enable TE without root table pointer set (RTPS=0)"
1999                    );
2000                } else {
2001                    gsts.set_tes(true);
2002                }
2003            } else {
2004                gsts.set_tes(false);
2005            }
2006        }
2007
2008        // -- Toggle: Interrupt Remapping Enable (IRE) --
2009        if gcmd.ire() != gsts.ires() {
2010            if gcmd.ire() {
2011                // Enable IRE — reject if IRTPS=0.
2012                if !gsts.irtps() {
2013                    tracelimit::warn_ratelimited!(
2014                        "vtd: cannot enable IRE without IRT pointer set (IRTPS=0)"
2015                    );
2016                } else {
2017                    gsts.set_ires(true);
2018                }
2019            } else {
2020                gsts.set_ires(false);
2021            }
2022        }
2023
2024        // -- Toggle: Compatibility Format Interrupt (CFI) --
2025        if gcmd.cfi() != gsts.cfis() {
2026            gsts.set_cfis(gcmd.cfi());
2027        }
2028
2029        state.gsts = gsts;
2030    }
2031
2032    // =========================================================================
2033    // Register-based invalidation (pre-QI)
2034    // =========================================================================
2035
2036    /// Process a write to the Context Command Register (CCMD).
2037    ///
2038    /// Register-based context-cache invalidation. Linux writes this during
2039    /// early init before QI is enabled. No-op since we don't cache.
2040    fn process_ccmd(&self, state: &mut VtdState, value: u64) {
2041        let ccmd = CcmdReg::from(value);
2042        if ccmd.icc() {
2043            // Clear ICC, set CAIG = CIRG (echo back granularity).
2044            state.ccmd = ccmd.with_icc(false).with_caig(ccmd.cirg());
2045        } else {
2046            state.ccmd = CcmdReg::from(value);
2047        }
2048    }
2049
2050    /// Process a write to the IOTLB Invalidate Register (0x108).
2051    ///
2052    /// Register-based IOTLB invalidation. No-op since we don't cache.
2053    fn process_iotlb_reg(&self, state: &mut VtdState, value: u64) {
2054        let reg = IotlbReg::from(value);
2055        if reg.ivt() {
2056            // Clear IVT and echo IAIG = IIRG.
2057            state.iotlb = reg.with_ivt(false).with_iaig(reg.iirg());
2058        } else {
2059            state.iotlb = IotlbReg::from(value);
2060        }
2061    }
2062
2063    // =========================================================================
2064    // Invalidation Queue Processing (1C)
2065    // =========================================================================
2066
2067    /// Process the invalidation queue.
2068    ///
2069    /// Queue execution is serialized by exclusive access to the device. The
2070    /// state lock is also a DMA drain point: translators hold it across the
2071    /// translation and the entire memory operation.
2072    fn process_invalidation_queue(&mut self) {
2073        let cap = CapReg::from(CAP_VALUE);
2074        let ecap = EcapReg::from(ECAP_VALUE);
2075        let mut state = self.shared.state.write();
2076        if !state.gsts.qies() || state.fsts.iqe() || state.fsts.ite() {
2077            return;
2078        }
2079        let mut queue = match InvalidationQueue::new(&state, ecap) {
2080            Ok(queue) => queue,
2081            Err(err) => {
2082                tracelimit::warn_ratelimited!(
2083                    error = &err as &dyn std::error::Error,
2084                    "vtd: invalid queue"
2085                );
2086                self.invalidation_queue_error(&mut state);
2087                return;
2088            }
2089        };
2090        let mode = TranslationTableMode(state.latched_rtaddr.ttm());
2091        // Both offsets have been checked against the size and stride. Bound
2092        // the work explicitly rather than chasing an unchecked guest tail.
2093        for _ in 0..queue.pending {
2094            let entry_addr = queue.base + queue.head;
2095            let mut desc = InvalidationDescriptor256::new_zeroed();
2096            if let Err(err) = self
2097                .shared
2098                .guest_memory
2099                .read_at(entry_addr, &mut desc.as_mut_bytes()[..queue.width.bytes()])
2100            {
2101                tracelimit::warn_ratelimited!(
2102                    error = &err as &dyn std::error::Error,
2103                    addr = entry_addr,
2104                    "vtd: failed to read invalidation queue descriptor"
2105                );
2106                self.invalidation_queue_error(&mut state);
2107                break;
2108            }
2109            if let Err(err) = desc.validate(queue.width, mode, cap, ecap) {
2110                tracelimit::warn_ratelimited!(
2111                    error = &err as &dyn std::error::Error,
2112                    addr = entry_addr,
2113                    "vtd: invalid invalidation descriptor"
2114                );
2115                self.invalidation_queue_error(&mut state);
2116                break;
2117            }
2118            match desc.lower.descriptor_type() {
2119                DescriptorType::CONTEXT_CACHE_INVALIDATE
2120                | DescriptorType::IOTLB_INVALIDATE
2121                | DescriptorType::PASID_CACHE_INVALIDATE
2122                | DescriptorType::PASID_IOTLB_INVALIDATE => {
2123                    // No translation caches. Acquiring the exclusive state
2124                    // lock drains all earlier DMA reads and writes.
2125                }
2126                DescriptorType::INTERRUPT_ENTRY_CACHE_INVALIDATE => {
2127                    let desc = spec::invalidation::parse_interrupt_cache_invalidate(&desc.lower);
2128                    tracing::trace!(
2129                        granularity = desc.granularity(),
2130                        im = desc.im(),
2131                        iidx = desc.iidx(),
2132                        "vtd: interrupt entry cache invalidate"
2133                    );
2134                    // Callbacks may read translation state or record faults.
2135                    // Finish them before advancing to a wait, without holding
2136                    // the state lock they may reacquire. &mut self prevents a
2137                    // second MMIO writer/queue executor from overtaking us.
2138                    RwLockWriteGuard::unlocked(&mut state, || {
2139                        self.shared.retranslate_interrupts.invalidate(None);
2140                    });
2141                }
2142                DescriptorType::INVALIDATION_WAIT => {
2143                    let (lo, hi) = spec::invalidation::parse_invalidation_wait(&desc.lower);
2144                    if !self.process_invalidation_wait(&mut state, lo, hi) {
2145                        break;
2146                    }
2147                }
2148                dt => {
2149                    tracelimit::warn_ratelimited!(?dt, "vtd: unknown invalidation descriptor type");
2150                    self.invalidation_queue_error(&mut state);
2151                    break;
2152                }
2153            }
2154
2155            // Advance head with wrap-around.
2156            queue.head = (queue.head + queue.width.bytes() as u64) % queue.size;
2157            state.iqh = IqhReg::from(queue.head);
2158        }
2159    }
2160
2161    fn invalidation_queue_error(&self, state: &mut VtdState) {
2162        state.fsts.set_iqe(true);
2163        if state.fectl.im() {
2164            state.fectl.set_ip(true);
2165        } else {
2166            self.shared.deliver_fault_interrupt(state);
2167        }
2168    }
2169
2170    /// Process an INVALIDATION_WAIT descriptor (type 0x05).
2171    fn process_invalidation_wait(
2172        &self,
2173        state: &mut VtdState,
2174        lo: InvalidationWaitDw0Dw1,
2175        hi: InvalidationWaitDw2Dw3,
2176    ) -> bool {
2177        if lo.sw() {
2178            let status_address = hi.status_address();
2179
2180            if let Err(e) = self
2181                .shared
2182                .guest_memory
2183                .write_at(status_address, &lo.status_data().to_le_bytes())
2184            {
2185                tracelimit::warn_ratelimited!(
2186                    error = &e as &dyn std::error::Error,
2187                    addr = status_address,
2188                    "vtd: failed to write invalidation wait status"
2189                );
2190                // Do not manufacture a completion when its status write failed.
2191                self.invalidation_queue_error(state);
2192                return false;
2193            }
2194        }
2195
2196        if lo.iflag() && !state.ics.iwc() {
2197            // Set IWC in ICS.
2198            let mut ics = state.ics;
2199            ics.set_iwc(true);
2200            state.ics = ics;
2201
2202            // Signal invalidation completion interrupt.
2203            let iectl = state.iectl;
2204            if !iectl.im() {
2205                self.shared.deliver_invalidation_interrupt(state);
2206            } else {
2207                // Masked — set IP.
2208                state.iectl = state.iectl.with_ip(true);
2209            }
2210        }
2211        // Sequential execution is stronger than FN=0 requires, and satisfies
2212        // FN=1 without letting later descriptors pass this completion.
2213        true
2214    }
2215}
2216
2217// =============================================================================
2218// ChipsetDevice trait implementation
2219// =============================================================================
2220
2221impl ChipsetDevice for IntelVtdDevice {
2222    fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
2223        Some(self)
2224    }
2225}
2226
2227// =============================================================================
2228// MMIO Register Access
2229// =============================================================================
2230
2231impl MmioIntercept for IntelVtdDevice {
2232    fn mmio_read(&mut self, addr: u64, data: &mut [u8]) -> IoResult {
2233        let offset = addr - self.mmio_base;
2234
2235        // VT-d supports 4-byte and 8-byte naturally aligned accesses.
2236        match data.len() {
2237            8 => {
2238                // Acquire the read lock once for both DWORD reads to avoid
2239                // a torn read if a writer intervenes between the two halves.
2240                let state = self.shared.state.read();
2241                let lo = self.read_register_dword_locked(&state, offset as u16);
2242                let hi = self.read_register_dword_locked(&state, (offset + 4) as u16);
2243                let val = lo as u64 | ((hi as u64) << 32);
2244                data.copy_from_slice(&val.to_le_bytes());
2245            }
2246            4 => {
2247                let val = self.read_register_dword(offset as u16);
2248                data.copy_from_slice(&val.to_le_bytes());
2249            }
2250            _ => {
2251                tracelimit::warn_ratelimited!(
2252                    addr,
2253                    len = data.len(),
2254                    "vtd: unsupported MMIO read size"
2255                );
2256                data.fill(0xff);
2257                return IoResult::Err(IoError::InvalidAccessSize);
2258            }
2259        }
2260
2261        IoResult::Ok
2262    }
2263
2264    fn mmio_write(&mut self, addr: u64, data: &[u8]) -> IoResult {
2265        let offset = addr - self.mmio_base;
2266
2267        match data.len() {
2268            8 => {
2269                let val = u64::from_le_bytes(data.try_into().unwrap());
2270                if offset == u64::from(Reg::IQT.0) {
2271                    // Validate the complete tail before fetching anything.
2272                    self.shared.state.write().iqt = IqtReg::from(val);
2273                    self.process_invalidation_queue();
2274                } else {
2275                    self.write_register_dword(offset as u16, val as u32);
2276                    self.write_register_dword((offset + 4) as u16, (val >> 32) as u32);
2277                }
2278            }
2279            4 => {
2280                let val = u32::from_le_bytes(data.try_into().unwrap());
2281                self.write_register_dword(offset as u16, val);
2282            }
2283            _ => {
2284                tracelimit::warn_ratelimited!(
2285                    addr,
2286                    len = data.len(),
2287                    "vtd: unsupported MMIO write size"
2288                );
2289                return IoResult::Err(IoError::InvalidAccessSize);
2290            }
2291        }
2292
2293        IoResult::Ok
2294    }
2295
2296    fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u64>)] {
2297        std::slice::from_ref(&self.mmio_region)
2298    }
2299}
2300
2301// =============================================================================
2302// ChangeDeviceState
2303// =============================================================================
2304
2305impl vmcore::device_state::ChangeDeviceState for IntelVtdDevice {
2306    fn start(&mut self) {}
2307
2308    async fn stop(&mut self) {}
2309
2310    async fn reset(&mut self) {
2311        let mut state = self.shared.state.write();
2312        *state = VtdState::new();
2313    }
2314}
2315
2316// =============================================================================
2317// SaveRestore (stub)
2318// =============================================================================
2319
2320impl vmcore::save_restore::SaveRestore for IntelVtdDevice {
2321    type SavedState = vmcore::save_restore::SavedStateNotSupported;
2322
2323    fn save(&mut self) -> Result<Self::SavedState, vmcore::save_restore::SaveError> {
2324        Err(vmcore::save_restore::SaveError::NotSupported)
2325    }
2326
2327    fn restore(
2328        &mut self,
2329        state: Self::SavedState,
2330    ) -> Result<(), vmcore::save_restore::RestoreError> {
2331        match state {}
2332    }
2333}
2334
2335// =============================================================================
2336// InspectMut
2337// =============================================================================
2338
2339impl InspectMut for IntelVtdDevice {
2340    fn inspect_mut(&mut self, req: inspect::Request<'_>) {
2341        let state = self.shared.state.read();
2342        let gsts = state.gsts;
2343        req.respond()
2344            .hex("mmio_base", self.mmio_base)
2345            .field("translation_enabled", gsts.tes())
2346            .field("ir_enabled", gsts.ires())
2347            .field("qi_enabled", gsts.qies())
2348            .field("fault_status", self.read_fsts(&state))
2349            .field("translation_table_mode", state.latched_rtaddr.ttm())
2350            .field(
2351                "queue_descriptor_bits",
2352                if state.iqa.dw() { 256u16 } else { 128 },
2353            )
2354            .hex("root_table_addr", state.latched_rtaddr.root_table_address())
2355            .hex("irt_addr", state.latched_irta.irt_base_address())
2356            .field("state", &*state);
2357    }
2358}
2359
2360// =============================================================================
2361// Tests
2362// =============================================================================
2363
2364#[cfg(test)]
2365mod tests {
2366    mod accessed_dirty;
2367    mod queued_invalidation;
2368    mod scalable_fixture;
2369    mod scalable_translation;
2370
2371    use super::*;
2372    use guestmem::GuestMemory;
2373    use std::sync::atomic::AtomicU32;
2374    use std::sync::atomic::Ordering;
2375    use test_with_tracing::test;
2376
2377    const TEST_MMIO_BASE: u64 = 0xFED9_0000;
2378
2379    struct TestSignalMsi;
2380    impl SignalMsi for TestSignalMsi {
2381        fn signal_msi(&self, _devid: Option<u32>, _address: u64, _data: u32) {}
2382    }
2383
2384    fn create_test_device() -> IntelVtdDevice {
2385        let gm = GuestMemory::empty();
2386        let signal_msi = Arc::new(TestSignalMsi);
2387        let (device, _shared) = IntelVtdDevice::new(
2388            gm,
2389            IntelVtdConfig {
2390                mmio_base: TEST_MMIO_BASE,
2391            },
2392            signal_msi,
2393        );
2394        device
2395    }
2396
2397    /// Helper to read a 32-bit register.
2398    fn read32(dev: &mut IntelVtdDevice, reg_offset: u16) -> u32 {
2399        let mut data = [0u8; 4];
2400        let result = dev.mmio_read(TEST_MMIO_BASE + reg_offset as u64, &mut data);
2401        assert!(matches!(result, IoResult::Ok));
2402        u32::from_le_bytes(data)
2403    }
2404
2405    /// Helper to write a 32-bit register.
2406    fn write32(dev: &mut IntelVtdDevice, reg_offset: u16, value: u32) {
2407        let data = value.to_le_bytes();
2408        let result = dev.mmio_write(TEST_MMIO_BASE + reg_offset as u64, &data);
2409        assert!(matches!(result, IoResult::Ok));
2410    }
2411
2412    /// Helper to read a 64-bit register.
2413    fn read64(dev: &mut IntelVtdDevice, reg_offset: u16) -> u64 {
2414        let mut data = [0u8; 8];
2415        let result = dev.mmio_read(TEST_MMIO_BASE + reg_offset as u64, &mut data);
2416        assert!(matches!(result, IoResult::Ok));
2417        u64::from_le_bytes(data)
2418    }
2419
2420    /// Helper to write a 64-bit register.
2421    fn write64(dev: &mut IntelVtdDevice, reg_offset: u16, value: u64) {
2422        let data = value.to_le_bytes();
2423        let result = dev.mmio_write(TEST_MMIO_BASE + reg_offset as u64, &data);
2424        assert!(matches!(result, IoResult::Ok));
2425    }
2426
2427    #[test]
2428    fn test_ver_register() {
2429        let mut dev = create_test_device();
2430        let ver = read32(&mut dev, 0x000);
2431        let ver_reg = VersionReg::from(ver);
2432        assert_eq!(ver_reg.max(), 1);
2433        assert_eq!(ver_reg.min(), 0);
2434    }
2435
2436    #[test]
2437    fn test_inspect_reports_latched_mode_not_pending_rtaddr() {
2438        let mut dev = create_test_device();
2439        let mode = |dev: &mut IntelVtdDevice| {
2440            inspect::inspect("translation_table_mode", dev)
2441                .results()
2442                .to_string()
2443        };
2444        assert_eq!(mode(&mut dev), "0");
2445        write64(&mut dev, 0x020, 0x1000 | (1 << 10));
2446        assert_eq!(mode(&mut dev), "0");
2447        write32(&mut dev, 0x018, 1 << 30);
2448        assert_eq!(mode(&mut dev), "1");
2449        write64(&mut dev, 0x020, 0x2000);
2450        assert_eq!(mode(&mut dev), "1");
2451        write32(&mut dev, 0x018, 1 << 30);
2452        assert_eq!(mode(&mut dev), "0");
2453    }
2454
2455    #[test]
2456    fn test_cap_register() {
2457        let mut dev = create_test_device();
2458        let cap = read64(&mut dev, 0x008);
2459        let cap_reg = CapReg::from(cap);
2460        assert_eq!(cap_reg.mgaw(), 47); // 48-bit
2461        assert_eq!(cap_reg.sagaw(), 0x6); // 39-bit + 48-bit
2462        assert_eq!(cap_reg.nfr(), 0); // 1 fault record
2463        assert_eq!(cap_reg.fro(), 0x12); // 0x120
2464        assert_eq!(cap_reg.sllps(), 0x3); // 2MB + 1GB
2465        assert!(cap_reg.dwd());
2466        assert!(cap_reg.drd());
2467        assert!(!cap_reg.cm());
2468        assert_eq!(cap_reg.nd(), 6);
2469    }
2470
2471    #[test]
2472    fn test_ecap_register() {
2473        let mut dev = create_test_device();
2474        let ecap = read64(&mut dev, 0x010);
2475        let ecap_reg = EcapReg::from(ecap);
2476        assert!(ecap_reg.c());
2477        assert!(ecap_reg.qi());
2478        assert!(ecap_reg.ir());
2479        assert!(ecap_reg.eim());
2480        assert_eq!(ecap_reg.iro(), 0x10); // 0x100
2481        assert_eq!(ecap_reg.mhmv(), 0xF);
2482        assert!(ecap_reg.smts());
2483        assert!(ecap_reg.ssts());
2484        assert!(ecap_reg.ssads());
2485        assert!(ecap_reg.smpwcs());
2486        assert!(ecap_reg.rps());
2487        assert!(!ecap_reg.flts());
2488        assert!(!ecap_reg.eafs());
2489        assert!(!ecap_reg.nest());
2490        assert!(!ecap_reg.pasid());
2491        assert!(!ecap_reg.dt());
2492        assert!(!ecap_reg.prs());
2493        assert!(!ecap_reg.srs());
2494        assert_eq!(ecap, 0x0003_6800_00f0_10db);
2495    }
2496
2497    #[test]
2498    fn test_gcmd_read_returns_zero() {
2499        let mut dev = create_test_device();
2500        assert_eq!(read32(&mut dev, 0x018), 0);
2501    }
2502
2503    #[test]
2504    fn test_gsts_initial() {
2505        let mut dev = create_test_device();
2506        let gsts = read32(&mut dev, 0x01C);
2507        assert_eq!(gsts, 0); // All disabled initially
2508    }
2509
2510    #[test]
2511    fn test_srtp_and_te_enable() {
2512        let mut dev = create_test_device();
2513
2514        // Write RTADDR.
2515        let root_table_addr = 0x1000_0000u64;
2516        write64(&mut dev, 0x020, root_table_addr);
2517        assert_eq!(read64(&mut dev, 0x020), root_table_addr);
2518
2519        // GCMD: SRTP (bit 30).
2520        write32(&mut dev, 0x018, GcmdReg::new().with_srtp(true).into_bits());
2521        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
2522        assert!(gsts.rtps());
2523
2524        // GCMD: TE (bit 31).
2525        write32(&mut dev, 0x018, GcmdReg::new().with_te(true).into_bits());
2526        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
2527        assert!(gsts.tes());
2528    }
2529
2530    #[test]
2531    fn test_te_rejected_without_rtps() {
2532        let mut dev = create_test_device();
2533
2534        // Try to enable TE without setting root table pointer.
2535        write32(&mut dev, 0x018, GcmdReg::new().with_te(true).into_bits());
2536        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
2537        assert!(!gsts.tes()); // Should be rejected
2538    }
2539
2540    #[test]
2541    fn test_ire_rejected_without_irtps() {
2542        let mut dev = create_test_device();
2543
2544        // Try to enable IRE without setting IRT pointer.
2545        write32(&mut dev, 0x018, GcmdReg::new().with_ire(true).into_bits());
2546        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
2547        assert!(!gsts.ires()); // Should be rejected
2548    }
2549
2550    #[test]
2551    fn test_sirtp_and_ire_enable() {
2552        let mut dev = create_test_device();
2553
2554        // Write IRTA.
2555        let irt_addr = 0x2000_0000u64;
2556        write64(&mut dev, 0x0B8, irt_addr);
2557        assert_eq!(read64(&mut dev, 0x0B8), irt_addr);
2558
2559        // GCMD: SIRTP.
2560        write32(&mut dev, 0x018, GcmdReg::new().with_sirtp(true).into_bits());
2561        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
2562        assert!(gsts.irtps());
2563
2564        // GCMD: IRE.
2565        write32(&mut dev, 0x018, GcmdReg::new().with_ire(true).into_bits());
2566        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
2567        assert!(gsts.ires());
2568    }
2569
2570    #[test]
2571    fn test_wbf() {
2572        let mut dev = create_test_device();
2573
2574        // GCMD: WBF (bit 27).
2575        write32(&mut dev, 0x018, GcmdReg::new().with_wbf(true).into_bits());
2576        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
2577        assert!(gsts.wbfs());
2578    }
2579
2580    #[test]
2581    fn test_qie_enable_disable() {
2582        let mut dev = create_test_device();
2583
2584        // Write IQA.
2585        write64(&mut dev, 0x090, 0x3000_0000u64);
2586
2587        // Enable QIE.
2588        write32(&mut dev, 0x018, GcmdReg::new().with_qie(true).into_bits());
2589        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
2590        assert!(gsts.qies());
2591
2592        // Disable QIE (no TE or IRE active).
2593        write32(&mut dev, 0x018, GcmdReg::new().with_qie(false).into_bits());
2594        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
2595        assert!(!gsts.qies());
2596    }
2597
2598    #[test]
2599    fn test_ccmd_register_invalidation() {
2600        let mut dev = create_test_device();
2601
2602        // Write CCMD with ICC=1, CIRG=01 (global).
2603        let ccmd = CcmdReg::new().with_icc(true).with_cirg(1);
2604        write64(&mut dev, 0x028, ccmd.into_bits());
2605
2606        // Read back — ICC should be cleared, CAIG should equal CIRG.
2607        let result = CcmdReg::from(read64(&mut dev, 0x028));
2608        assert!(!result.icc());
2609        assert_eq!(result.caig(), 1);
2610    }
2611
2612    #[test]
2613    fn test_fsts_rw1c() {
2614        let mut dev = create_test_device();
2615
2616        // Manually set fault status bits via shared state.
2617        {
2618            let mut state = dev.shared.state.write();
2619            state.fsts = FstsReg::new().with_pfo(true).with_iqe(true);
2620        }
2621
2622        let fsts = FstsReg::from(read32(&mut dev, 0x034));
2623        assert!(fsts.pfo());
2624        assert!(fsts.iqe());
2625
2626        // Clear PFO by writing 1.
2627        write32(&mut dev, 0x034, FstsReg::new().with_pfo(true).into_bits());
2628        let fsts = FstsReg::from(read32(&mut dev, 0x034));
2629        assert!(!fsts.pfo());
2630        assert!(fsts.iqe()); // IQE should still be set
2631    }
2632
2633    #[test]
2634    fn test_ppf_dynamic_computation() {
2635        let mut dev = create_test_device();
2636
2637        // Initially PPF should be 0.
2638        let fsts = FstsReg::from(read32(&mut dev, 0x034));
2639        assert!(!fsts.ppf());
2640
2641        // Set FRCD[0].F (bit 63 of frcd_hi).
2642        {
2643            let mut state = dev.shared.state.write();
2644            state.frcd_hi = FrcdHi::new().with_f(true);
2645        }
2646
2647        // Now PPF should be 1.
2648        let fsts = FstsReg::from(read32(&mut dev, 0x034));
2649        assert!(fsts.ppf());
2650
2651        // Clear F bit via RW1C on FRCD_HI.
2652        write64(&mut dev, Reg::FRCD_DW2.0, 1u64 << 63);
2653
2654        // PPF should be 0 again.
2655        let fsts = FstsReg::from(read32(&mut dev, 0x034));
2656        assert!(!fsts.ppf());
2657    }
2658
2659    #[test]
2660    fn test_ics_rw1c() {
2661        let mut dev = create_test_device();
2662
2663        // Set IWC.
2664        {
2665            let mut state = dev.shared.state.write();
2666            state.ics = IcsReg::new().with_iwc(true);
2667        }
2668
2669        let ics = IcsReg::from(read32(&mut dev, 0x09C));
2670        assert!(ics.iwc());
2671
2672        // Clear IWC.
2673        write32(&mut dev, 0x09C, IcsReg::new().with_iwc(true).into_bits());
2674        let ics = IcsReg::from(read32(&mut dev, 0x09C));
2675        assert!(!ics.iwc());
2676    }
2677
2678    #[test]
2679    fn test_iqa_write_guard() {
2680        let mut dev = create_test_device();
2681
2682        // Write IQA before QIE is enabled — should succeed.
2683        write64(&mut dev, 0x090, 0x5000_0000u64);
2684        assert_eq!(read64(&mut dev, 0x090), 0x5000_0000u64);
2685
2686        // Enable QIE.
2687        write32(&mut dev, 0x018, GcmdReg::new().with_qie(true).into_bits());
2688
2689        // Write IQA while QIE=1 — should be ignored.
2690        write64(&mut dev, 0x090, 0x6000_0000u64);
2691        assert_eq!(read64(&mut dev, 0x090), 0x5000_0000u64);
2692    }
2693
2694    #[test]
2695    fn test_irta_write_guard() {
2696        let mut dev = create_test_device();
2697
2698        // Write IRTA before IRE is enabled — should succeed.
2699        write64(&mut dev, 0x0B8, 0x7000_0000u64);
2700        assert_eq!(read64(&mut dev, 0x0B8), 0x7000_0000u64);
2701
2702        // Enable IRE (need SIRTP first).
2703        write32(&mut dev, 0x018, GcmdReg::new().with_sirtp(true).into_bits());
2704        write32(&mut dev, 0x018, GcmdReg::new().with_ire(true).into_bits());
2705
2706        // Write IRTA while IRE=1 — should be ignored.
2707        write64(&mut dev, 0x0B8, 0x8000_0000u64);
2708        assert_eq!(read64(&mut dev, 0x0B8), 0x7000_0000u64);
2709    }
2710
2711    #[test]
2712    fn test_unmapped_offset_returns_zero() {
2713        let mut dev = create_test_device();
2714        // Read an offset that doesn't correspond to any register.
2715        assert_eq!(read32(&mut dev, 0x050), 0);
2716        assert_eq!(read64(&mut dev, 0x060), 0);
2717    }
2718
2719    #[test]
2720    fn test_unsupported_access_size() {
2721        let mut dev = create_test_device();
2722        let mut data = [0u8; 2];
2723        let result = dev.mmio_read(TEST_MMIO_BASE, &mut data);
2724        assert!(matches!(result, IoResult::Err(IoError::InvalidAccessSize)));
2725    }
2726
2727    #[test]
2728    fn test_32bit_access_to_64bit_register() {
2729        let mut dev = create_test_device();
2730
2731        // CAP is 64-bit at offset 0x008.
2732        let cap_lo = read32(&mut dev, 0x008);
2733        let cap_hi = read32(&mut dev, 0x00C);
2734        let cap_full = read64(&mut dev, 0x008);
2735
2736        assert_eq!(cap_full as u32, cap_lo);
2737        assert_eq!((cap_full >> 32) as u32, cap_hi);
2738    }
2739
2740    #[test]
2741    fn test_iotlb_register_invalidation() {
2742        let mut dev = create_test_device();
2743
2744        // Write IOTLB_REG with IVT=1, IIRG=01 (global).
2745        let iotlb_val = IotlbReg::new().with_ivt(true).with_iirg(1).into_bits();
2746        write64(&mut dev, Reg::IOTLB.0, iotlb_val);
2747
2748        // Read back — IVT should be cleared, IAIG should match IIRG.
2749        let result = IotlbReg::from(read64(&mut dev, Reg::IOTLB.0));
2750        assert!(!result.ivt()); // IVT cleared
2751        assert_eq!(result.iaig(), 1); // IAIG = IIRG = 01
2752    }
2753
2754    #[test]
2755    fn test_cfi_toggle() {
2756        let mut dev = create_test_device();
2757
2758        // Enable CFI.
2759        write32(&mut dev, 0x018, GcmdReg::new().with_cfi(true).into_bits());
2760        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
2761        assert!(gsts.cfis());
2762
2763        // Disable CFI.
2764        write32(&mut dev, 0x018, GcmdReg::new().with_cfi(false).into_bits());
2765        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
2766        assert!(!gsts.cfis());
2767    }
2768
2769    #[test]
2770    fn test_frcd_hi_rw1c() {
2771        let mut dev = create_test_device();
2772
2773        // Set F bit (bit 63) in FRCD_HI.
2774        {
2775            let mut state = dev.shared.state.write();
2776            state.frcd_hi = FrcdHi::new().with_f(true);
2777        }
2778
2779        // Verify it reads back.
2780        let frcd_hi = read64(&mut dev, Reg::FRCD_DW2.0);
2781        assert_eq!((frcd_hi >> 63) & 1, 1);
2782
2783        // Clear F via RW1C.
2784        write64(&mut dev, Reg::FRCD_DW2.0, 1u64 << 63);
2785        let frcd_hi = read64(&mut dev, Reg::FRCD_DW2.0);
2786        assert_eq!((frcd_hi >> 63) & 1, 0);
2787    }
2788
2789    // =========================================================================
2790    // Translation tests (1E)
2791    // =========================================================================
2792
2793    use spec::pte::SlPte;
2794    use spec::root_context::AddressWidth;
2795    use spec::root_context::ContextEntry;
2796    use spec::root_context::ContextEntryHi;
2797    use spec::root_context::ContextEntryLo;
2798    use spec::root_context::RootEntry;
2799    use spec::root_context::RootEntryLo;
2800    use zerocopy::IntoBytes;
2801
2802    const ROOT_TABLE_ADDR: u64 = 0x10_0000; // 1 MiB
2803    const CONTEXT_TABLE_ADDR: u64 = 0x11_0000;
2804    const PAGE_TABLE_L4_ADDR: u64 = 0x12_0000;
2805    const PAGE_TABLE_L3_ADDR: u64 = 0x13_0000;
2806    const PAGE_TABLE_L2_ADDR: u64 = 0x14_0000;
2807    const PAGE_TABLE_L1_ADDR: u64 = 0x15_0000;
2808    const TARGET_GPA: u64 = 0x20_0000; // Where IOVA 0 maps to
2809
2810    /// Create a device with guest memory and pre-populate page tables
2811    /// for a 4-level walk mapping IOVA 0 → TARGET_GPA.
2812    fn create_test_device_with_translation() -> (IntelVtdDevice, Arc<VtdSharedState>) {
2813        // 4 MiB of guest memory.
2814        let gm = GuestMemory::allocate(0x40_0000);
2815        let signal_msi = Arc::new(TestSignalMsi);
2816
2817        // Set up root entry: bus 0 → CONTEXT_TABLE_ADDR.
2818        let root_entry = RootEntry {
2819            lo: RootEntryLo::new()
2820                .with_p(true)
2821                .with_ctp(CONTEXT_TABLE_ADDR >> 12),
2822            hi: 0,
2823        };
2824        gm.write_at(ROOT_TABLE_ADDR, root_entry.as_bytes()).unwrap();
2825
2826        // Set up context entry: devfn 0 → page table at L4, AW=2 (48-bit/4-level).
2827        let context_entry = ContextEntry {
2828            lo: ContextEntryLo::new()
2829                .with_p(true)
2830                .with_tt(TranslationType::UNTRANSLATED_ONLY.0)
2831                .with_ssptptr(PAGE_TABLE_L4_ADDR >> 12),
2832            hi: ContextEntryHi::new()
2833                .with_aw(AddressWidth::AW_48BIT.0)
2834                .with_did(1), // 48-bit / 4-level
2835        };
2836        gm.write_at(CONTEXT_TABLE_ADDR, context_entry.as_bytes())
2837            .unwrap();
2838
2839        // Build 4-level page tables: L4 → L3 → L2 → L1 → TARGET_GPA.
2840        // Level 4 entry 0 → L3
2841        let pte_l4 = SlPte::new()
2842            .with_r(true)
2843            .with_w(true)
2844            .with_address(PAGE_TABLE_L3_ADDR >> 12);
2845        gm.write_at(PAGE_TABLE_L4_ADDR, pte_l4.into_bits().as_bytes())
2846            .unwrap();
2847
2848        // Level 3 entry 0 → L2
2849        let pte_l3 = SlPte::new()
2850            .with_r(true)
2851            .with_w(true)
2852            .with_address(PAGE_TABLE_L2_ADDR >> 12);
2853        gm.write_at(PAGE_TABLE_L3_ADDR, pte_l3.into_bits().as_bytes())
2854            .unwrap();
2855
2856        // Level 2 entry 0 → L1
2857        let pte_l2 = SlPte::new()
2858            .with_r(true)
2859            .with_w(true)
2860            .with_address(PAGE_TABLE_L1_ADDR >> 12);
2861        gm.write_at(PAGE_TABLE_L2_ADDR, pte_l2.into_bits().as_bytes())
2862            .unwrap();
2863
2864        // Level 1 entry 0 → TARGET_GPA (4KB page)
2865        let pte_l1 = SlPte::new()
2866            .with_r(true)
2867            .with_w(true)
2868            .with_address(TARGET_GPA >> 12);
2869        gm.write_at(PAGE_TABLE_L1_ADDR, pte_l1.into_bits().as_bytes())
2870            .unwrap();
2871
2872        let (dev, shared) = IntelVtdDevice::new(
2873            gm,
2874            IntelVtdConfig {
2875                mmio_base: TEST_MMIO_BASE,
2876            },
2877            signal_msi,
2878        );
2879
2880        // Enable translation: set root table, SRTP, TE.
2881        {
2882            let mut state = shared.state.write();
2883            state.rtaddr = RtaddrReg::from(ROOT_TABLE_ADDR);
2884            state.latched_rtaddr = RtaddrReg::from(ROOT_TABLE_ADDR);
2885            state.gsts = GstsReg::new().with_rtps(true).with_tes(true);
2886        }
2887
2888        (dev, shared)
2889    }
2890
2891    #[test]
2892    fn test_translate_4level_4kb() {
2893        let (_dev, shared) = create_test_device_with_translation();
2894        let state = shared.state.read();
2895        let gpa = shared
2896            .translate_locked(&state, 0, 0, 0x0000, false, EcapReg::from(ECAP_VALUE))
2897            .unwrap();
2898        assert_eq!(gpa, TARGET_GPA);
2899    }
2900
2901    #[test]
2902    fn test_translate_4level_4kb_with_offset() {
2903        let (_dev, shared) = create_test_device_with_translation();
2904        let state = shared.state.read();
2905        // IOVA 0x123 → TARGET_GPA + 0x123
2906        let gpa = shared
2907            .translate_locked(&state, 0, 0, 0x0123, false, EcapReg::from(ECAP_VALUE))
2908            .unwrap();
2909        assert_eq!(gpa, TARGET_GPA + 0x123);
2910    }
2911
2912    #[test]
2913    fn test_translate_disabled_is_identity() {
2914        let (_dev, shared) = create_test_device_with_translation();
2915        // Disable translation.
2916        {
2917            let mut state = shared.state.write();
2918            state.gsts = GstsReg::new().with_rtps(true);
2919        }
2920        let state = shared.state.read();
2921        let gpa = shared
2922            .translate_locked(&state, 0, 0, 0xDEAD_BEEF, false, EcapReg::from(ECAP_VALUE))
2923            .unwrap();
2924        assert_eq!(gpa, 0xDEAD_BEEF);
2925    }
2926
2927    #[test]
2928    fn test_translate_3level() {
2929        let gm = GuestMemory::allocate(0x40_0000);
2930        let signal_msi = Arc::new(TestSignalMsi);
2931
2932        // Set up root entry and context entry with AW=001 (39-bit/3-level).
2933        let root_entry = RootEntry {
2934            lo: RootEntryLo::new()
2935                .with_p(true)
2936                .with_ctp(CONTEXT_TABLE_ADDR >> 12),
2937            hi: 0,
2938        };
2939        gm.write_at(ROOT_TABLE_ADDR, root_entry.as_bytes()).unwrap();
2940
2941        let context_entry = ContextEntry {
2942            lo: ContextEntryLo::new()
2943                .with_p(true)
2944                .with_tt(TranslationType::UNTRANSLATED_ONLY.0)
2945                .with_ssptptr(PAGE_TABLE_L3_ADDR >> 12),
2946            hi: ContextEntryHi::new()
2947                .with_aw(AddressWidth::AW_39BIT.0)
2948                .with_did(1), // 39-bit / 3-level
2949        };
2950        gm.write_at(CONTEXT_TABLE_ADDR, context_entry.as_bytes())
2951            .unwrap();
2952
2953        // 3-level walk: L3 → L2 → L1 → TARGET_GPA.
2954        let pte_l3 = SlPte::new()
2955            .with_r(true)
2956            .with_w(true)
2957            .with_address(PAGE_TABLE_L2_ADDR >> 12);
2958        gm.write_at(PAGE_TABLE_L3_ADDR, pte_l3.into_bits().as_bytes())
2959            .unwrap();
2960
2961        let pte_l2 = SlPte::new()
2962            .with_r(true)
2963            .with_w(true)
2964            .with_address(PAGE_TABLE_L1_ADDR >> 12);
2965        gm.write_at(PAGE_TABLE_L2_ADDR, pte_l2.into_bits().as_bytes())
2966            .unwrap();
2967
2968        let pte_l1 = SlPte::new()
2969            .with_r(true)
2970            .with_w(true)
2971            .with_address(TARGET_GPA >> 12);
2972        gm.write_at(PAGE_TABLE_L1_ADDR, pte_l1.into_bits().as_bytes())
2973            .unwrap();
2974
2975        let (_dev, shared) = IntelVtdDevice::new(
2976            gm,
2977            IntelVtdConfig {
2978                mmio_base: TEST_MMIO_BASE,
2979            },
2980            signal_msi,
2981        );
2982        {
2983            let mut state = shared.state.write();
2984            state.rtaddr = RtaddrReg::from(ROOT_TABLE_ADDR);
2985            state.latched_rtaddr = RtaddrReg::from(ROOT_TABLE_ADDR);
2986            state.gsts = GstsReg::new().with_rtps(true).with_tes(true);
2987        }
2988
2989        let state = shared.state.read();
2990        let gpa = shared
2991            .translate_locked(&state, 0, 0, 0x0000, false, EcapReg::from(ECAP_VALUE))
2992            .unwrap();
2993        assert_eq!(gpa, TARGET_GPA);
2994    }
2995
2996    #[test]
2997    fn test_translate_2mb_large_page() {
2998        let gm = GuestMemory::allocate(0x40_0000);
2999        let signal_msi = Arc::new(TestSignalMsi);
3000
3001        let root_entry = RootEntry {
3002            lo: RootEntryLo::new()
3003                .with_p(true)
3004                .with_ctp(CONTEXT_TABLE_ADDR >> 12),
3005            hi: 0,
3006        };
3007        gm.write_at(ROOT_TABLE_ADDR, root_entry.as_bytes()).unwrap();
3008
3009        let context_entry = ContextEntry {
3010            lo: ContextEntryLo::new()
3011                .with_p(true)
3012                .with_tt(TranslationType::UNTRANSLATED_ONLY.0)
3013                .with_ssptptr(PAGE_TABLE_L4_ADDR >> 12),
3014            hi: ContextEntryHi::new()
3015                .with_aw(AddressWidth::AW_48BIT.0)
3016                .with_did(1),
3017        };
3018        gm.write_at(CONTEXT_TABLE_ADDR, context_entry.as_bytes())
3019            .unwrap();
3020
3021        // L4 → L3 → L2 (PS=1, 2MB page)
3022        let pte_l4 = SlPte::new()
3023            .with_r(true)
3024            .with_w(true)
3025            .with_address(PAGE_TABLE_L3_ADDR >> 12);
3026        gm.write_at(PAGE_TABLE_L4_ADDR, pte_l4.into_bits().as_bytes())
3027            .unwrap();
3028
3029        let pte_l3 = SlPte::new()
3030            .with_r(true)
3031            .with_w(true)
3032            .with_address(PAGE_TABLE_L2_ADDR >> 12);
3033        gm.write_at(PAGE_TABLE_L3_ADDR, pte_l3.into_bits().as_bytes())
3034            .unwrap();
3035
3036        // 2MB page at 0x200000
3037        let pte_l2 = SlPte::new()
3038            .with_r(true)
3039            .with_w(true)
3040            .with_ps(true)
3041            .with_address(0x20_0000 >> 12); // Base GPA = 0x200000
3042        gm.write_at(PAGE_TABLE_L2_ADDR, pte_l2.into_bits().as_bytes())
3043            .unwrap();
3044
3045        let (_dev, shared) = IntelVtdDevice::new(
3046            gm,
3047            IntelVtdConfig {
3048                mmio_base: TEST_MMIO_BASE,
3049            },
3050            signal_msi,
3051        );
3052        {
3053            let mut state = shared.state.write();
3054            state.rtaddr = RtaddrReg::from(ROOT_TABLE_ADDR);
3055            state.latched_rtaddr = RtaddrReg::from(ROOT_TABLE_ADDR);
3056            state.gsts = GstsReg::new().with_rtps(true).with_tes(true);
3057        }
3058
3059        let state = shared.state.read();
3060        // IOVA 0x1234 within the 2MB page → GPA 0x201234.
3061        let gpa = shared
3062            .translate_locked(&state, 0, 0, 0x1234, false, EcapReg::from(ECAP_VALUE))
3063            .unwrap();
3064        assert_eq!(gpa, 0x20_1234);
3065    }
3066
3067    #[test]
3068    fn test_translate_passthrough() {
3069        let gm = GuestMemory::allocate(0x40_0000);
3070        let signal_msi = Arc::new(TestSignalMsi);
3071
3072        let root_entry = RootEntry {
3073            lo: RootEntryLo::new()
3074                .with_p(true)
3075                .with_ctp(CONTEXT_TABLE_ADDR >> 12),
3076            hi: 0,
3077        };
3078        gm.write_at(ROOT_TABLE_ADDR, root_entry.as_bytes()).unwrap();
3079
3080        // Context entry with TT=10 (pass-through).
3081        let context_entry = ContextEntry {
3082            lo: ContextEntryLo::new()
3083                .with_p(true)
3084                .with_tt(TranslationType::PASS_THROUGH.0)
3085                .with_ssptptr(0),
3086            hi: ContextEntryHi::new()
3087                .with_aw(AddressWidth::AW_48BIT.0)
3088                .with_did(1),
3089        };
3090        gm.write_at(CONTEXT_TABLE_ADDR, context_entry.as_bytes())
3091            .unwrap();
3092
3093        let (_dev, shared) = IntelVtdDevice::new(
3094            gm,
3095            IntelVtdConfig {
3096                mmio_base: TEST_MMIO_BASE,
3097            },
3098            signal_msi,
3099        );
3100        {
3101            let mut state = shared.state.write();
3102            state.rtaddr = RtaddrReg::from(ROOT_TABLE_ADDR);
3103            state.latched_rtaddr = RtaddrReg::from(ROOT_TABLE_ADDR);
3104            state.gsts = GstsReg::new().with_rtps(true).with_tes(true);
3105        }
3106
3107        let state = shared.state.read();
3108        let gpa = shared
3109            .translate_locked(&state, 0, 0, 0xABCD_0000, false, EcapReg::from(ECAP_VALUE))
3110            .unwrap();
3111        assert_eq!(gpa, 0xABCD_0000);
3112    }
3113
3114    #[test]
3115    fn test_translate_root_not_present() {
3116        let gm = GuestMemory::allocate(0x40_0000);
3117        let signal_msi = Arc::new(TestSignalMsi);
3118
3119        // Root entry with P=0.
3120        let root_entry = RootEntry {
3121            lo: RootEntryLo::new().with_p(false),
3122            hi: 0,
3123        };
3124        gm.write_at(ROOT_TABLE_ADDR, root_entry.as_bytes()).unwrap();
3125
3126        let (_dev, shared) = IntelVtdDevice::new(
3127            gm,
3128            IntelVtdConfig {
3129                mmio_base: TEST_MMIO_BASE,
3130            },
3131            signal_msi,
3132        );
3133        {
3134            let mut state = shared.state.write();
3135            state.rtaddr = RtaddrReg::from(ROOT_TABLE_ADDR);
3136            state.latched_rtaddr = RtaddrReg::from(ROOT_TABLE_ADDR);
3137            state.gsts = GstsReg::new().with_rtps(true).with_tes(true);
3138        }
3139
3140        let state = shared.state.read();
3141        let err = shared
3142            .translate_locked(&state, 0, 0, 0x1000, false, EcapReg::from(ECAP_VALUE))
3143            .unwrap_err();
3144        assert!(matches!(err, VtdFault::RootNotPresent { .. }));
3145    }
3146
3147    #[test]
3148    fn test_translate_context_not_present() {
3149        let gm = GuestMemory::allocate(0x40_0000);
3150        let signal_msi = Arc::new(TestSignalMsi);
3151
3152        let root_entry = RootEntry {
3153            lo: RootEntryLo::new()
3154                .with_p(true)
3155                .with_ctp(CONTEXT_TABLE_ADDR >> 12),
3156            hi: 0,
3157        };
3158        gm.write_at(ROOT_TABLE_ADDR, root_entry.as_bytes()).unwrap();
3159
3160        // Context entry with P=0.
3161        let context_entry = ContextEntry {
3162            lo: ContextEntryLo::new().with_p(false),
3163            hi: ContextEntryHi::new(),
3164        };
3165        gm.write_at(CONTEXT_TABLE_ADDR, context_entry.as_bytes())
3166            .unwrap();
3167
3168        let (_dev, shared) = IntelVtdDevice::new(
3169            gm,
3170            IntelVtdConfig {
3171                mmio_base: TEST_MMIO_BASE,
3172            },
3173            signal_msi,
3174        );
3175        {
3176            let mut state = shared.state.write();
3177            state.rtaddr = RtaddrReg::from(ROOT_TABLE_ADDR);
3178            state.latched_rtaddr = RtaddrReg::from(ROOT_TABLE_ADDR);
3179            state.gsts = GstsReg::new().with_rtps(true).with_tes(true);
3180        }
3181
3182        let state = shared.state.read();
3183        let err = shared
3184            .translate_locked(&state, 0, 0, 0x1000, false, EcapReg::from(ECAP_VALUE))
3185            .unwrap_err();
3186        assert!(matches!(err, VtdFault::ContextNotPresent { .. }));
3187    }
3188
3189    #[test]
3190    fn test_translate_pte_not_present() {
3191        let (_dev, shared) = create_test_device_with_translation();
3192
3193        // Write a non-present PTE at L1 entry 1 (IOVA 0x1000).
3194        let pte = SlPte::new(); // R=W=0
3195        shared
3196            .guest_memory
3197            .write_at(PAGE_TABLE_L1_ADDR + 8, pte.into_bits().as_bytes())
3198            .unwrap();
3199
3200        let state = shared.state.read();
3201        let err = shared
3202            .translate_locked(&state, 0, 0, 0x1000, false, EcapReg::from(ECAP_VALUE))
3203            .unwrap_err();
3204        // Non-present PTE (R=W=0) on a read → AccessDenied with write=false (fault 0x06).
3205        assert!(matches!(err, VtdFault::AccessDenied { write: false, .. }));
3206    }
3207
3208    #[test]
3209    fn test_translate_write_to_readonly() {
3210        let (_dev, shared) = create_test_device_with_translation();
3211
3212        // Write a read-only PTE at L1 entry 0 (R=1, W=0).
3213        let pte = SlPte::new().with_r(true).with_w(false);
3214        shared
3215            .guest_memory
3216            .write_at(PAGE_TABLE_L1_ADDR, pte.into_bits().as_bytes())
3217            .unwrap();
3218
3219        let state = shared.state.read();
3220        // Read should succeed.
3221        let gpa = shared
3222            .translate_locked(&state, 0, 0, 0x0000, false, EcapReg::from(ECAP_VALUE))
3223            .unwrap();
3224        assert_eq!(gpa, 0); // PTE address field is 0
3225        // Write should fail.
3226        let err = shared
3227            .translate_locked(&state, 0, 0, 0x0000, true, EcapReg::from(ECAP_VALUE))
3228            .unwrap_err();
3229        assert!(matches!(err, VtdFault::AccessDenied { write: true, .. }));
3230    }
3231
3232    #[test]
3233    fn test_translate_pde_permission_restricts() {
3234        let (_dev, shared) = create_test_device_with_translation();
3235
3236        // Make L2 entry 0 read-only (R=1, W=0), L1 entry 0 has W=1.
3237        // AND-accumulation means write should fail.
3238        let pte_l2 = SlPte::new()
3239            .with_r(true)
3240            .with_w(false) // No write at L2
3241            .with_address(PAGE_TABLE_L1_ADDR >> 12);
3242        shared
3243            .guest_memory
3244            .write_at(PAGE_TABLE_L2_ADDR, pte_l2.into_bits().as_bytes())
3245            .unwrap();
3246
3247        let state = shared.state.read();
3248        let err = shared
3249            .translate_locked(&state, 0, 0, 0x0000, true, EcapReg::from(ECAP_VALUE))
3250            .unwrap_err();
3251        assert!(matches!(err, VtdFault::AccessDenied { write: true, .. }));
3252    }
3253
3254    #[test]
3255    fn test_translate_fpd_suppresses_fault() {
3256        let gm = GuestMemory::allocate(0x40_0000);
3257        let signal_msi = Arc::new(TestSignalMsi);
3258
3259        let root_entry = RootEntry {
3260            lo: RootEntryLo::new()
3261                .with_p(true)
3262                .with_ctp(CONTEXT_TABLE_ADDR >> 12),
3263            hi: 0,
3264        };
3265        gm.write_at(ROOT_TABLE_ADDR, root_entry.as_bytes()).unwrap();
3266
3267        // Context entry with FPD=1, pointing to a non-existent page table.
3268        let context_entry = ContextEntry {
3269            lo: ContextEntryLo::new()
3270                .with_p(true)
3271                .with_fpd(true)
3272                .with_tt(TranslationType::UNTRANSLATED_ONLY.0)
3273                .with_ssptptr(0x30_0000 >> 12), // Points to zeroed memory
3274            hi: ContextEntryHi::new()
3275                .with_aw(AddressWidth::AW_48BIT.0)
3276                .with_did(1),
3277        };
3278        gm.write_at(CONTEXT_TABLE_ADDR, context_entry.as_bytes())
3279            .unwrap();
3280
3281        let (_dev, shared) = IntelVtdDevice::new(
3282            gm,
3283            IntelVtdConfig {
3284                mmio_base: TEST_MMIO_BASE,
3285            },
3286            signal_msi,
3287        );
3288        {
3289            let mut state = shared.state.write();
3290            state.rtaddr = RtaddrReg::from(ROOT_TABLE_ADDR);
3291            state.latched_rtaddr = RtaddrReg::from(ROOT_TABLE_ADDR);
3292            state.gsts = GstsReg::new().with_rtps(true).with_tes(true);
3293        }
3294
3295        // Translation should fail (PTE not present in zeroed memory).
3296        let state = shared.state.read();
3297        let err = shared
3298            .translate_locked(&state, 0, 0, 0x0000, false, EcapReg::from(ECAP_VALUE))
3299            .unwrap_err();
3300        assert!(err.fpd()); // FPD should be true
3301
3302        // Record the fault — it should be suppressed by FPD.
3303        drop(state);
3304        err.record(&shared, false); // read access
3305
3306        // FRCD.F should NOT be set (fault suppressed).
3307        let state = shared.state.read();
3308        assert!(!state.frcd_hi.f());
3309    }
3310
3311    // =========================================================================
3312    // Interrupt remapping tests (1F)
3313    // =========================================================================
3314
3315    use spec::irte::Irte;
3316    use spec::irte::IrteHi;
3317    use spec::irte::IrteLo;
3318
3319    const IRT_BASE_ADDR: u64 = 0x18_0000;
3320
3321    #[test]
3322    fn test_extract_irte_index_no_shv() {
3323        // Handle = {addr[2], addr[19:5]}. SHV = addr[3] = 0.
3324        // addr = 0xFEE0_0010 (bit 4=1, remappable format, handle=0x0000).
3325        let index = VtdSharedState::extract_irte_index(0xFEE0_0010, 0);
3326        assert_eq!(index, 0);
3327
3328        // addr[19:5] = 0x7 (handle[14:0] = 7), addr[2] = 0 (handle[15]=0).
3329        // addr = 0xFEE0_00F0 (bits 7:5 = 111).
3330        let addr = 0xFEE0_0010 | (0x7 << 5);
3331        let index = VtdSharedState::extract_irte_index(addr, 0);
3332        assert_eq!(index, 7);
3333    }
3334
3335    #[test]
3336    fn test_extract_irte_index_with_shv() {
3337        // SHV = addr[3] = 1. handle = {addr[2], addr[19:5]}.
3338        // index = handle + data[15:0].
3339        let addr = 0xFEE0_0018 | (0x5 << 5); // handle=5, SHV=1
3340        let index = VtdSharedState::extract_irte_index(addr, 3);
3341        assert_eq!(index, 8); // 5 + 3
3342    }
3343
3344    fn setup_ir_state(shared: &Arc<VtdSharedState>, eime: bool) {
3345        let mut state = shared.state.write();
3346        // Set up IRT: base at IRT_BASE_ADDR, size=0xF (65536 entries).
3347        state.irta = IrtaReg::new()
3348            .with_irta(IRT_BASE_ADDR >> 12)
3349            .with_s(0xF)
3350            .with_eime(eime);
3351        state.latched_irta = state.irta;
3352        // Enable IR.
3353        let mut gsts = state.gsts;
3354        gsts.set_ires(true);
3355        gsts.set_irtps(true);
3356        state.gsts = gsts;
3357    }
3358
3359    struct CountingRoute {
3360        device_id: u16,
3361        retranslate_count: AtomicU32,
3362    }
3363
3364    impl iommu_common::RetranslateInterrupts for CountingRoute {
3365        fn device_id(&self) -> u16 {
3366            self.device_id
3367        }
3368
3369        fn retranslate(&self) {
3370            self.retranslate_count.fetch_add(1, Ordering::SeqCst);
3371        }
3372    }
3373
3374    #[test]
3375    fn test_remap_msi_basic() {
3376        let gm = GuestMemory::allocate(0x40_0000);
3377        let signal_msi = Arc::new(TestSignalMsi);
3378        let (_dev, shared) = IntelVtdDevice::new(
3379            gm.clone(),
3380            IntelVtdConfig {
3381                mmio_base: TEST_MMIO_BASE,
3382            },
3383            signal_msi,
3384        );
3385
3386        setup_ir_state(&shared, false);
3387
3388        // Write IRTE at index 0: vector=0x42, DST xAPIC ID=3, fixed delivery.
3389        let irte = Irte {
3390            lo: IrteLo::new()
3391                .with_p(true)
3392                .with_vector(0x42)
3393                .with_dst(0x0300) // xAPIC: APIC ID in bits 15:8
3394                .with_dlm(0) // Fixed
3395                .with_dm(false), // Physical
3396            hi: IrteHi::new().with_svt(0), // No source validation
3397        };
3398        gm.write_at(IRT_BASE_ADDR, irte.as_bytes()).unwrap();
3399
3400        // Remappable MSI: addr bit4=1, handle=0, SHV=0 → IRTE index 0.
3401        let addr = 0xFEE0_0010u64;
3402        let data = 0u32;
3403        let state = shared.state.read();
3404        let (new_addr, new_data) = shared.remap_msi_locked(&state, 0x0000, addr, data).unwrap();
3405
3406        // Expected: addr = 0xFEE03000 (DST=3 in bits 19:12).
3407        assert_eq!(new_addr & 0xFFF0_0000, 0xFEE0_0000);
3408        assert_eq!((new_addr >> 12) & 0xFF, 3); // APIC ID = 3
3409        assert_eq!(new_data & 0xFF, 0x42); // Vector = 0x42
3410    }
3411
3412    #[test]
3413    fn test_remap_msi_x2apic_destination() {
3414        let gm = GuestMemory::allocate(0x40_0000);
3415        let signal_msi = Arc::new(TestSignalMsi);
3416        let (_dev, shared) = IntelVtdDevice::new(
3417            gm.clone(),
3418            IntelVtdConfig {
3419                mmio_base: TEST_MMIO_BASE,
3420            },
3421            signal_msi,
3422        );
3423
3424        // Enable IR with EIME=1 (x2APIC mode).
3425        setup_ir_state(&shared, true);
3426
3427        // Write IRTE with destination APIC ID = 300 (> 255).
3428        let apic_id: u32 = 300;
3429        let irte = Irte {
3430            lo: IrteLo::new()
3431                .with_p(true)
3432                .with_vector(0x50)
3433                .with_dst(apic_id)
3434                .with_dlm(0)
3435                .with_dm(false),
3436            hi: IrteHi::new().with_svt(0),
3437        };
3438        gm.write_at(IRT_BASE_ADDR, irte.as_bytes()).unwrap();
3439
3440        let addr = 0xFEE0_0010u64;
3441        let state = shared.state.read();
3442        let (new_addr, new_data) = shared.remap_msi_locked(&state, 0x0000, addr, 0).unwrap();
3443
3444        // Verify the destination is preserved via virt_destination encoding.
3445        let msi_addr = x86defs::msi::MsiAddress::from(new_addr as u32);
3446        assert_eq!(msi_addr.virt_destination(), apic_id as u16);
3447        assert_eq!(new_data & 0xFF, 0x50);
3448    }
3449
3450    #[test]
3451    fn test_remap_msi_ir_disabled_passthrough() {
3452        let gm = GuestMemory::allocate(0x40_0000);
3453        let signal_msi = Arc::new(TestSignalMsi);
3454        let (_dev, shared) = IntelVtdDevice::new(
3455            gm,
3456            IntelVtdConfig {
3457                mmio_base: TEST_MMIO_BASE,
3458            },
3459            signal_msi,
3460        );
3461        // IR not enabled — pass through.
3462        let state = shared.state.read();
3463        let (a, d) = shared
3464            .remap_msi_locked(&state, 0, 0xFEE0_1234, 0x42)
3465            .unwrap();
3466        assert_eq!(a, 0xFEE0_1234);
3467        assert_eq!(d, 0x42);
3468    }
3469
3470    #[test]
3471    fn test_interrupt_remapper_transitions_from_passthrough_to_remap() {
3472        let gm = GuestMemory::allocate(0x40_0000);
3473        let signal_msi = Arc::new(TestSignalMsi);
3474        let (_dev, shared) = IntelVtdDevice::new(
3475            gm.clone(),
3476            IntelVtdConfig {
3477                mmio_base: TEST_MMIO_BASE,
3478            },
3479            signal_msi,
3480        );
3481
3482        let compat_addr = 0xFEE0_0000u64;
3483        assert_eq!(
3484            iommu_common::InterruptRemapper::remap_msi(&*shared, 0x00A0, compat_addr, 0x31),
3485            Some((compat_addr, 0x31))
3486        );
3487
3488        setup_ir_state(&shared, false);
3489        let irte = Irte {
3490            lo: IrteLo::new()
3491                .with_p(true)
3492                .with_vector(0x45)
3493                .with_dst(0x0200)
3494                .with_dlm(0)
3495                .with_dm(false),
3496            hi: IrteHi::new().with_svt(0b01).with_sq(0).with_sid(0x00A0),
3497        };
3498        gm.write_at(IRT_BASE_ADDR, irte.as_bytes()).unwrap();
3499
3500        let (new_addr, new_data) =
3501            iommu_common::InterruptRemapper::remap_msi(&*shared, 0x00A0, 0xFEE0_0010, 0).unwrap();
3502        assert_eq!((new_addr >> 12) & 0xFF, 2);
3503        assert_eq!(new_data & 0xFF, 0x45);
3504    }
3505
3506    #[test]
3507    fn test_remap_msi_irte_not_present() {
3508        let gm = GuestMemory::allocate(0x40_0000);
3509        let signal_msi = Arc::new(TestSignalMsi);
3510        let (_dev, shared) = IntelVtdDevice::new(
3511            gm.clone(),
3512            IntelVtdConfig {
3513                mmio_base: TEST_MMIO_BASE,
3514            },
3515            signal_msi,
3516        );
3517
3518        setup_ir_state(&shared, false);
3519
3520        // IRTE at index 0 is all zeros (P=0).
3521        let addr = 0xFEE0_0010u64;
3522        let state = shared.state.read();
3523        let err = shared
3524            .remap_msi_locked(&state, 0x0000, addr, 0)
3525            .unwrap_err();
3526        assert!(matches!(err, VtdFault::IrteNotPresent { .. }));
3527    }
3528
3529    #[test]
3530    fn test_remap_msi_posted_irte_rejected() {
3531        let gm = GuestMemory::allocate(0x40_0000);
3532        let signal_msi = Arc::new(TestSignalMsi);
3533        let (_dev, shared) = IntelVtdDevice::new(
3534            gm.clone(),
3535            IntelVtdConfig {
3536                mmio_base: TEST_MMIO_BASE,
3537            },
3538            signal_msi,
3539        );
3540
3541        setup_ir_state(&shared, false);
3542
3543        let irte = Irte {
3544            lo: IrteLo::new().with_p(true).with_im(true).with_vector(0x40),
3545            hi: IrteHi::new().with_svt(0),
3546        };
3547        gm.write_at(IRT_BASE_ADDR, irte.as_bytes()).unwrap();
3548
3549        let state = shared.state.read();
3550        let err = shared
3551            .remap_msi_locked(&state, 0x0000, 0xFEE0_0010, 0)
3552            .unwrap_err();
3553        assert!(matches!(err, VtdFault::IrteReservedField { .. }));
3554    }
3555
3556    #[test]
3557    fn test_interrupt_entry_cache_invalidate_retranslates_routes() {
3558        const IQ_BASE: u64 = 0x20_0000;
3559
3560        let gm = GuestMemory::allocate(0x40_0000);
3561        let signal_msi = Arc::new(TestSignalMsi);
3562        let (mut dev, shared) = IntelVtdDevice::new(
3563            gm.clone(),
3564            IntelVtdConfig {
3565                mmio_base: TEST_MMIO_BASE,
3566            },
3567            signal_msi,
3568        );
3569
3570        let route = Arc::new(CountingRoute {
3571            device_id: 0x00A0,
3572            retranslate_count: AtomicU32::new(0),
3573        });
3574        let route_dyn: Arc<dyn iommu_common::RetranslateInterrupts> = route.clone();
3575        iommu_common::InterruptRemapper::register_route(&*shared, &route_dyn);
3576
3577        let iqa = IqaReg::new().with_qs(0).with_iqa(IQ_BASE >> 12);
3578        write64(&mut dev, Reg::IQA.0, iqa.into_bits());
3579        write32(
3580            &mut dev,
3581            Reg::GCMD.0,
3582            GcmdReg::new().with_qie(true).into_bits(),
3583        );
3584
3585        let desc = spec::invalidation::InvalidationDescriptor {
3586            dw0: DescriptorType::INTERRUPT_ENTRY_CACHE_INVALIDATE.0 as u32,
3587            dw1: 0,
3588            dw2: 0,
3589            dw3: 0,
3590        };
3591        gm.write_at(IQ_BASE, desc.as_bytes()).unwrap();
3592
3593        write64(&mut dev, Reg::IQT.0, IqtReg::new().with_qt(1).into_bits());
3594
3595        assert_eq!(route.retranslate_count.load(Ordering::SeqCst), 1);
3596        assert_eq!(IqhReg::from(read64(&mut dev, Reg::IQH.0)).head_offset(), 16);
3597    }
3598
3599    #[test]
3600    fn test_remap_msi_compat_format_blocked() {
3601        let gm = GuestMemory::allocate(0x40_0000);
3602        let signal_msi = Arc::new(TestSignalMsi);
3603        let (_dev, shared) = IntelVtdDevice::new(
3604            gm,
3605            IntelVtdConfig {
3606                mmio_base: TEST_MMIO_BASE,
3607            },
3608            signal_msi,
3609        );
3610
3611        setup_ir_state(&shared, false);
3612        // CFIS=0 (default) → compat-format blocked.
3613
3614        // Compatibility format: bit 4 = 0.
3615        let addr = 0xFEE0_0000u64; // bit 4 = 0
3616        let state = shared.state.read();
3617        let err = shared
3618            .remap_msi_locked(&state, 0x0000, addr, 0)
3619            .unwrap_err();
3620        assert!(matches!(err, VtdFault::CompatibilityFormatBlocked { .. }));
3621    }
3622
3623    #[test]
3624    fn test_remap_msi_compat_format_passthrough_cfis() {
3625        let gm = GuestMemory::allocate(0x40_0000);
3626        let signal_msi = Arc::new(TestSignalMsi);
3627        let (_dev, shared) = IntelVtdDevice::new(
3628            gm,
3629            IntelVtdConfig {
3630                mmio_base: TEST_MMIO_BASE,
3631            },
3632            signal_msi,
3633        );
3634
3635        setup_ir_state(&shared, false);
3636
3637        // Set CFIS=1 → pass-through for compat-format.
3638        {
3639            let mut state = shared.state.write();
3640            let mut gsts = state.gsts;
3641            gsts.set_cfis(true);
3642            state.gsts = gsts;
3643        }
3644
3645        let addr = 0xFEE0_0000u64; // bit 4 = 0
3646        let state = shared.state.read();
3647        let (a, d) = shared.remap_msi_locked(&state, 0x0000, addr, 0x42).unwrap();
3648        assert_eq!(a, addr);
3649        assert_eq!(d, 0x42);
3650    }
3651
3652    #[test]
3653    fn test_remap_msi_source_validation_verify_sid() {
3654        let gm = GuestMemory::allocate(0x40_0000);
3655        let signal_msi = Arc::new(TestSignalMsi);
3656        let (_dev, shared) = IntelVtdDevice::new(
3657            gm.clone(),
3658            IntelVtdConfig {
3659                mmio_base: TEST_MMIO_BASE,
3660            },
3661            signal_msi,
3662        );
3663
3664        setup_ir_state(&shared, false);
3665
3666        // IRTE with SVT=01 (verify SID), SQ=00 (exact match), SID=0x0100.
3667        let irte = Irte {
3668            lo: IrteLo::new()
3669                .with_p(true)
3670                .with_vector(0x30)
3671                .with_dst(0x0100)
3672                .with_dlm(0)
3673                .with_dm(false),
3674            hi: IrteHi::new().with_svt(0b01).with_sq(0b00).with_sid(0x0100),
3675        };
3676        gm.write_at(IRT_BASE_ADDR, irte.as_bytes()).unwrap();
3677
3678        let addr = 0xFEE0_0010u64;
3679        let state = shared.state.read();
3680
3681        // Matching SID should succeed.
3682        assert!(shared.remap_msi_locked(&state, 0x0100, addr, 0).is_ok());
3683
3684        // Non-matching SID should fail.
3685        let err = shared
3686            .remap_msi_locked(&state, 0x0200, addr, 0)
3687            .unwrap_err();
3688        assert!(matches!(err, VtdFault::SourceValidationFailed { .. }));
3689    }
3690
3691    #[test]
3692    fn test_record_fault() {
3693        let gm = GuestMemory::allocate(0x40_0000);
3694        let signal_msi = Arc::new(TestSignalMsi);
3695        let (_dev, shared) = IntelVtdDevice::new(
3696            gm,
3697            IntelVtdConfig {
3698                mmio_base: TEST_MMIO_BASE,
3699            },
3700            signal_msi,
3701        );
3702
3703        let fault = VtdFault::RootNotPresent {
3704            source_id: 0x0100,
3705            iova: 0x1000,
3706        };
3707        fault.record(&shared, false); // simulate a read fault
3708
3709        let state = shared.state.read();
3710        let frcd_hi = state.frcd_hi;
3711        assert!(frcd_hi.f());
3712        assert_eq!(frcd_hi.sid(), 0x0100);
3713        assert_eq!(frcd_hi.fr(), FaultReason::ROOT_NOT_PRESENT.0);
3714        assert!(frcd_hi.t()); // T=1 for read
3715
3716        let frcd_lo = state.frcd_lo;
3717        assert_eq!(frcd_lo.fault_address(), 0x1000);
3718    }
3719
3720    #[test]
3721    fn test_frcd_raw_mmio_reason_position() {
3722        let mut dev = create_test_device();
3723        for reason in 0..=u8::MAX {
3724            for is_read in [false, true] {
3725                {
3726                    let mut state = dev.shared.state.write();
3727                    dev.shared.record_fault_locked(
3728                        &mut state,
3729                        0xabcd,
3730                        FaultReason(reason),
3731                        0x1234_5678_abcd,
3732                        is_read,
3733                        false,
3734                    );
3735                }
3736                // §11.4.7.6: FR is full-register bits 103:96, hence the
3737                // low byte of DWORD 3, not bits 23:16 of that DWORD.
3738                let dw3 = 0x8000_0000 | ((is_read as u32) << 30) | u32::from(reason);
3739                assert_eq!(read32(&mut dev, 0x120), 0x5678_a000);
3740                assert_eq!(read32(&mut dev, 0x124), 0x1234);
3741                assert_eq!(read32(&mut dev, 0x128), 0xabcd);
3742                assert_eq!(read32(&mut dev, 0x12c), dw3);
3743                assert_eq!(read64(&mut dev, 0x128), (u64::from(dw3) << 32) | 0xabcd);
3744                // RW1C clears F without changing the reason or request metadata.
3745                write32(&mut dev, 0x12c, 1 << 31);
3746                assert_eq!(read32(&mut dev, 0x12c), dw3 & !(1 << 31));
3747            }
3748        }
3749    }
3750
3751    #[test]
3752    fn test_iqa_raw_mmio_layout() {
3753        let mut dev = create_test_device();
3754        for qs in 0..=7 {
3755            for dw in [0, 1] {
3756                let raw = 0x1234_5678_9000 | (dw << 11) | qs;
3757                write64(&mut dev, 0x090, raw);
3758                assert_eq!(read64(&mut dev, 0x090), raw);
3759                let state = dev.shared.state.read();
3760                assert_eq!(state.iqa.dw(), dw != 0);
3761                assert_eq!(state.iqa.qs(), qs as u8);
3762                assert_eq!(state.iqa.queue_size_bytes(), 4096 << qs);
3763            }
3764        }
3765    }
3766
3767    #[test]
3768    fn test_record_fault_overflow() {
3769        let gm = GuestMemory::allocate(0x40_0000);
3770        let signal_msi = Arc::new(TestSignalMsi);
3771        let (_dev, shared) = IntelVtdDevice::new(
3772            gm,
3773            IntelVtdConfig {
3774                mmio_base: TEST_MMIO_BASE,
3775            },
3776            signal_msi,
3777        );
3778
3779        // Record first fault.
3780        let fault1 = VtdFault::RootNotPresent {
3781            source_id: 0x0100,
3782            iova: 0x1000,
3783        };
3784        fault1.record(&shared, true); // write fault
3785
3786        // Record second fault — should overflow.
3787        let fault2 = VtdFault::ContextNotPresent {
3788            source_id: 0x0200,
3789            iova: 0x2000,
3790            fpd: false,
3791        };
3792        fault2.record(&shared, false);
3793
3794        let state = shared.state.read();
3795        // PFO should be set.
3796        let fsts = state.fsts;
3797        assert!(fsts.pfo());
3798        // FRCD should still have first fault.
3799        let frcd_hi = state.frcd_hi;
3800        assert_eq!(frcd_hi.sid(), 0x0100);
3801    }
3802
3803    #[test]
3804    fn test_vtd_translator() {
3805        let (_dev, shared) = create_test_device_with_translation();
3806        let translator = shared.translator();
3807
3808        // Test via IommuTranslator trait.
3809        let result = iommu_common::IommuTranslator::translate(
3810            &translator,
3811            0x0000, // RID: bus=0, devfn=0
3812            0x0000, // IOVA
3813            false,
3814            |gpa| gpa,
3815        )
3816        .unwrap();
3817        assert_eq!(result, TARGET_GPA);
3818    }
3819
3820    #[test]
3821    fn test_vtd_signal_msi() {
3822        use std::sync::atomic::{AtomicU64, Ordering};
3823
3824        struct RecordingMsi {
3825            last_addr: AtomicU64,
3826        }
3827        impl SignalMsi for RecordingMsi {
3828            fn signal_msi(&self, _devid: Option<u32>, address: u64, _data: u32) {
3829                self.last_addr.store(address, Ordering::SeqCst);
3830            }
3831        }
3832
3833        let gm = GuestMemory::allocate(0x40_0000);
3834        let inner_msi = Arc::new(RecordingMsi {
3835            last_addr: AtomicU64::new(0),
3836        });
3837        let signal_msi = Arc::new(TestSignalMsi);
3838
3839        let (_dev, shared) = IntelVtdDevice::new(
3840            gm.clone(),
3841            IntelVtdConfig {
3842                mmio_base: TEST_MMIO_BASE,
3843            },
3844            signal_msi,
3845        );
3846
3847        // No IR enabled → pass-through.
3848        let wrapper = shared.wrap_signal_msi(inner_msi.clone());
3849        wrapper.signal_msi(Some(0x0100), 0xFEE0_1234, 0x42);
3850        assert_eq!(inner_msi.last_addr.load(Ordering::SeqCst), 0xFEE0_1234);
3851    }
3852
3853    #[test]
3854    fn test_vtd_signal_msi_no_devid_dropped() {
3855        use std::sync::atomic::{AtomicU32, Ordering};
3856
3857        struct CountingMsi {
3858            count: AtomicU32,
3859        }
3860        impl SignalMsi for CountingMsi {
3861            fn signal_msi(&self, _devid: Option<u32>, _address: u64, _data: u32) {
3862                self.count.fetch_add(1, Ordering::SeqCst);
3863            }
3864        }
3865
3866        let gm = GuestMemory::allocate(0x40_0000);
3867        let inner_msi = Arc::new(CountingMsi {
3868            count: AtomicU32::new(0),
3869        });
3870        let signal_msi = Arc::new(TestSignalMsi);
3871
3872        let (_dev, shared) = IntelVtdDevice::new(
3873            gm,
3874            IntelVtdConfig {
3875                mmio_base: TEST_MMIO_BASE,
3876            },
3877            signal_msi,
3878        );
3879
3880        let wrapper = shared.wrap_signal_msi(inner_msi.clone());
3881
3882        // devid=None should be silently dropped.
3883        wrapper.signal_msi(None, 0xFEE0_1234, 0x42);
3884        assert_eq!(inner_msi.count.load(Ordering::SeqCst), 0);
3885    }
3886
3887    // =========================================================================
3888    // End-to-end integration test (1J.1)
3889    // =========================================================================
3890    //
3891    // This test constructs the full VT-d stack and programs it via MMIO,
3892    // mimicking a Linux `intel-iommu` driver init sequence.
3893
3894    use spec::invalidation::InvalidationWaitDw0Dw1;
3895    use spec::invalidation::InvalidationWaitDw2Dw3;
3896
3897    /// Guest memory layout for the end-to-end test.
3898    ///
3899    /// 8 MiB of guest memory. Addresses chosen to be well-separated.
3900    const E2E_MEM_SIZE: usize = 0x80_0000;
3901    const E2E_ROOT_TABLE: u64 = 0x10_0000; // 1 MiB
3902    const E2E_CONTEXT_TABLE: u64 = 0x11_0000; // 1 MiB + 64 KiB
3903    const E2E_PT_L4: u64 = 0x12_0000;
3904    const E2E_PT_L3: u64 = 0x13_0000;
3905    const E2E_PT_L2: u64 = 0x14_0000;
3906    const E2E_PT_L1: u64 = 0x15_0000;
3907    const E2E_TARGET_GPA: u64 = 0x20_0000; // 2 MiB — where IOVA 0 maps to
3908    const E2E_IQ_BASE: u64 = 0x30_0000; // Invalidation queue
3909    const E2E_IQ_STATUS: u64 = 0x31_0000; // Status write address for INVALIDATION_WAIT
3910    const E2E_IRT_BASE: u64 = 0x40_0000; // Interrupt remapping table
3911    /// Test device BDF: bus=1, dev=0, fn=0 → devfn=0x00, RID=0x0100.
3912    const E2E_TEST_BUS: u8 = 1;
3913    const E2E_TEST_DEVFN: u8 = 0x00;
3914    const E2E_TEST_RID: u16 = 0x0100;
3915
3916    /// End-to-end test mimicking a Linux intel-iommu driver init sequence.
3917    ///
3918    /// Steps:
3919    ///  1. Read CAP/ECAP, verify capabilities.
3920    ///  2. Allocate root table, context tables, page tables in guest memory.
3921    ///  3. Write RTADDR with root table GPA.
3922    ///  4. Write GCMD with SRTP=1, verify RTPS in GSTS.
3923    ///  5. Register-based invalidation (CCMD, IOTLB) before QI.
3924    ///  6. Allocate invalidation queue, write IQA.
3925    ///  7. Write GCMD with QIE=1, verify QIES.
3926    ///  8. Program context entries for test devices.
3927    ///  9. Build 4-level page table mapping IOVA 0x0 → E2E_TARGET_GPA.
3928    /// 10. Write GCMD with TE=1, verify TES.
3929    /// 11. Submit invalidation queue descriptors (context + IOTLB + wait).
3930    /// 12. DMA read/write via VtdTranslator, verify translation.
3931    /// 13. Allocate IRT, program IRTE entries.
3932    /// 14. Write IRTA with IRT base, GCMD with SIRTP=1 then IRE=1.
3933    /// 15. MSI via VtdSignalMsi, verify remapping.
3934    /// 16. Access unmapped IOVA, verify fault recording.
3935    #[test]
3936    fn test_end_to_end_linux_init_sequence() {
3937        for (scalable, dw) in [(false, false), (false, true), (true, true)] {
3938            linux_init_sequence(scalable, dw);
3939        }
3940    }
3941
3942    fn linux_init_sequence(scalable: bool, dw: bool) {
3943        use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
3944
3945        // -- Recording MSI sink to verify interrupt delivery --
3946        struct RecordingMsi {
3947            last_addr: AtomicU64,
3948            last_data: AtomicU32,
3949            count: AtomicU32,
3950        }
3951        impl RecordingMsi {
3952            fn new() -> Self {
3953                Self {
3954                    last_addr: AtomicU64::new(0),
3955                    last_data: AtomicU32::new(0),
3956                    count: AtomicU32::new(0),
3957                }
3958            }
3959        }
3960        impl SignalMsi for RecordingMsi {
3961            fn signal_msi(&self, _devid: Option<u32>, address: u64, data: u32) {
3962                self.last_addr.store(address, Ordering::SeqCst);
3963                self.last_data.store(data, Ordering::SeqCst);
3964                self.count.fetch_add(1, Ordering::SeqCst);
3965            }
3966        }
3967
3968        let gm = GuestMemory::allocate(E2E_MEM_SIZE);
3969        let iommu_msi = Arc::new(RecordingMsi::new());
3970        let (mut dev, shared) = IntelVtdDevice::new(
3971            gm.clone(),
3972            IntelVtdConfig {
3973                mmio_base: TEST_MMIO_BASE,
3974            },
3975            iommu_msi.clone(),
3976        );
3977
3978        // =====================================================================
3979        // Step 1: Read and verify capabilities
3980        // =====================================================================
3981        let ver = read32(&mut dev, 0x000);
3982        assert_eq!(VersionReg::from(ver).max(), 1);
3983        assert_eq!(VersionReg::from(ver).min(), 0);
3984
3985        let cap = read64(&mut dev, 0x008);
3986        let cap_reg = CapReg::from(cap);
3987        assert_eq!(cap_reg.mgaw(), 47); // 48-bit
3988        assert!(cap_reg.sagaw() & 0x4 != 0); // 48-bit/4-level supported
3989        assert!(cap_reg.sagaw() & 0x2 != 0); // 39-bit/3-level supported
3990        assert_eq!(cap_reg.nfr(), 0); // 1 fault record
3991
3992        let ecap = read64(&mut dev, 0x010);
3993        assert_eq!(ecap, 0x0003_6800_00f0_10db);
3994        let ecap_reg = EcapReg::from(ecap);
3995        assert!(ecap_reg.qi()); // Queued invalidation
3996        assert!(ecap_reg.ir()); // Interrupt remapping
3997        assert!(ecap_reg.eim()); // x2APIC
3998        assert!(ecap_reg.c()); // Page-walk coherency
3999
4000        // Initial GSTS should be all zeros.
4001        assert_eq!(read32(&mut dev, 0x01C), 0);
4002
4003        // =====================================================================
4004        // Step 2–4: Set root table pointer
4005        // =====================================================================
4006
4007        // Write root table, context table entries, and page tables into guest
4008        // memory for bus 1, devfn 0.
4009
4010        // Root table entry for bus 1 → context table.
4011        let root_entry = RootEntry {
4012            lo: RootEntryLo::new()
4013                .with_p(true)
4014                .with_ctp(E2E_CONTEXT_TABLE >> 12),
4015            hi: 0,
4016        };
4017        gm.write_at(
4018            E2E_ROOT_TABLE + (E2E_TEST_BUS as u64) * 16,
4019            root_entry.as_bytes(),
4020        )
4021        .unwrap();
4022
4023        // Context entry for devfn 0 → 4-level page table.
4024        let context_entry = ContextEntry {
4025            lo: ContextEntryLo::new()
4026                .with_p(true)
4027                .with_tt(TranslationType::UNTRANSLATED_ONLY.0)
4028                .with_ssptptr(E2E_PT_L4 >> 12),
4029            hi: ContextEntryHi::new()
4030                .with_aw(AddressWidth::AW_48BIT.0)
4031                .with_did(1), // 48-bit/4-level
4032        };
4033        gm.write_at(
4034            E2E_CONTEXT_TABLE + (E2E_TEST_DEVFN as u64) * 16,
4035            context_entry.as_bytes(),
4036        )
4037        .unwrap();
4038        if scalable {
4039            // RID_PASID=65 resolves directory entry 1 and PASID-table entry 1.
4040            let directory = 0x16_0000u64;
4041            let pasid_table = 0x17_0000u64;
4042            for (address, words) in [
4043                (E2E_CONTEXT_TABLE, vec![directory | 1, 65, 0, 0]),
4044                (directory + 8, vec![pasid_table | 1]),
4045                (
4046                    pasid_table + 64,
4047                    vec![
4048                        E2E_PT_L4 | 1 | (2 << 2) | (2 << 6) | (1 << 9),
4049                        1 | (1 << 23),
4050                        0,
4051                        0,
4052                        0,
4053                        0,
4054                        0,
4055                        0,
4056                    ],
4057                ),
4058            ] {
4059                for (index, word) in words.into_iter().enumerate() {
4060                    gm.write_at(address + index as u64 * 8, &word.to_le_bytes())
4061                        .unwrap();
4062                }
4063            }
4064        }
4065
4066        // Build 4-level page tables: L4 → L3 → L2 → L1 → TARGET_GPA.
4067        let pte_l4 = SlPte::new()
4068            .with_r(true)
4069            .with_w(true)
4070            .with_address(E2E_PT_L3 >> 12);
4071        gm.write_at(E2E_PT_L4, pte_l4.into_bits().as_bytes())
4072            .unwrap();
4073
4074        let pte_l3 = SlPte::new()
4075            .with_r(true)
4076            .with_w(true)
4077            .with_address(E2E_PT_L2 >> 12);
4078        gm.write_at(E2E_PT_L3, pte_l3.into_bits().as_bytes())
4079            .unwrap();
4080
4081        let pte_l2 = SlPte::new()
4082            .with_r(true)
4083            .with_w(true)
4084            .with_address(E2E_PT_L1 >> 12);
4085        gm.write_at(E2E_PT_L2, pte_l2.into_bits().as_bytes())
4086            .unwrap();
4087
4088        let pte_l1 = SlPte::new()
4089            .with_r(true)
4090            .with_w(true)
4091            .with_address(E2E_TARGET_GPA >> 12);
4092        gm.write_at(E2E_PT_L1, pte_l1.into_bits().as_bytes())
4093            .unwrap();
4094
4095        // Write RTADDR register.
4096        let rtaddr = E2E_ROOT_TABLE | (u64::from(scalable) << 10);
4097        write64(&mut dev, 0x020, rtaddr);
4098        assert_eq!(read64(&mut dev, 0x020), rtaddr);
4099
4100        // GCMD: SRTP (set root table pointer).
4101        write32(&mut dev, 0x018, GcmdReg::new().with_srtp(true).into_bits());
4102        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
4103        assert!(gsts.rtps(), "RTPS must be set after SRTP");
4104
4105        // =====================================================================
4106        // Step 5: Register-based invalidation (legacy only)
4107        // =====================================================================
4108
4109        if !scalable {
4110            // Context-cache invalidation: global.
4111            let ccmd = CcmdReg::new().with_icc(true).with_cirg(1); // global
4112            write64(&mut dev, 0x028, ccmd.into_bits());
4113            let ccmd_result = CcmdReg::from(read64(&mut dev, 0x028));
4114            assert!(!ccmd_result.icc(), "ICC must be cleared after invalidation");
4115            assert_eq!(ccmd_result.caig(), 1, "CAIG must echo CIRG");
4116
4117            // IOTLB invalidation: global.
4118            let iotlb_val = IotlbReg::new().with_ivt(true).with_iirg(1).into_bits();
4119            write64(&mut dev, Reg::IOTLB.0, iotlb_val);
4120            let iotlb_result = IotlbReg::from(read64(&mut dev, Reg::IOTLB.0));
4121            assert!(!iotlb_result.ivt(), "IVT must be cleared");
4122            assert_eq!(iotlb_result.iaig(), 1, "IAIG must echo IIRG");
4123        }
4124
4125        // =====================================================================
4126        // Step 6–7: Enable queued invalidation
4127        // =====================================================================
4128
4129        // QS=0: 4KB, with 256 narrow or 128 wide descriptors.
4130        let stride = if dw { 32 } else { 16 };
4131        let iqa = IqaReg::new()
4132            .with_qs(0)
4133            .with_dw(dw)
4134            .with_iqa(E2E_IQ_BASE >> 12);
4135        write64(&mut dev, 0x090, iqa.into_bits());
4136        assert_eq!(read64(&mut dev, 0x090), iqa.into_bits());
4137
4138        // Enable QI.
4139        write32(&mut dev, 0x018, GcmdReg::new().with_qie(true).into_bits());
4140        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
4141        assert!(gsts.qies(), "QIES must be set after QIE");
4142
4143        // Verify IQH starts at 0.
4144        assert_eq!(read64(&mut dev, 0x080), 0, "IQH must start at 0");
4145
4146        // =====================================================================
4147        // Step 10: Enable translation
4148        // =====================================================================
4149
4150        // GCMD is a write-only register where toggle bits are compared against
4151        // GSTS. Must include the current state of all toggle bits (QIE=1) plus
4152        // the new bit (TE=1) to avoid inadvertently disabling QI.
4153        write32(
4154            &mut dev,
4155            0x018,
4156            GcmdReg::new().with_te(true).with_qie(true).into_bits(),
4157        );
4158        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
4159        assert!(gsts.tes(), "TES must be set after TE");
4160        assert!(gsts.qies(), "QIES must remain set");
4161
4162        // =====================================================================
4163        // Step 11: Submit invalidation queue descriptors
4164        // =====================================================================
4165
4166        // Write 3 descriptors: context-cache invalidate, IOTLB invalidate,
4167        // invalidation-wait with status write.
4168
4169        // Descriptor 0: context-cache invalidation (global, type=0x01).
4170        let desc0 = spec::invalidation::InvalidationDescriptor {
4171            dw0: DescriptorType::CONTEXT_CACHE_INVALIDATE.0 as u32 | (1u32 << 4), // global granularity
4172            dw1: 0,
4173            dw2: 0,
4174            dw3: 0,
4175        };
4176        gm.write_at(E2E_IQ_BASE, desc0.as_bytes()).unwrap();
4177
4178        // Descriptor 1: IOTLB invalidation (global, type=0x02).
4179        let desc1 = spec::invalidation::InvalidationDescriptor {
4180            dw0: DescriptorType::IOTLB_INVALIDATE.0 as u32 | (1u32 << 4), // global
4181            dw1: 0,
4182            dw2: 0,
4183            dw3: 0,
4184        };
4185        gm.write_at(E2E_IQ_BASE + stride, desc1.as_bytes()).unwrap();
4186
4187        // Descriptor 2: invalidation-wait with status write (type=0x05).
4188        let iw_lo = InvalidationWaitDw0Dw1::new()
4189            .with_desc_type(DescriptorType::INVALIDATION_WAIT.0)
4190            .with_sw(true)
4191            .with_status_data(0xDEAD_BEEF);
4192        let iw_hi = InvalidationWaitDw2Dw3::new().with_sal(E2E_IQ_STATUS >> 2);
4193        let desc2 = spec::invalidation::InvalidationDescriptor {
4194            dw0: iw_lo.into_bits() as u32,
4195            dw1: (iw_lo.into_bits() >> 32) as u32,
4196            dw2: iw_hi.into_bits() as u32,
4197            dw3: (iw_hi.into_bits() >> 32) as u32,
4198        };
4199        gm.write_at(E2E_IQ_BASE + 2 * stride, desc2.as_bytes())
4200            .unwrap();
4201
4202        // Clear status location.
4203        gm.write_at(E2E_IQ_STATUS, &0u32.to_le_bytes()).unwrap();
4204
4205        write64(&mut dev, 0x088, 3 * stride);
4206
4207        // Verify head advanced to tail.
4208        let iqh = IqhReg::from(read64(&mut dev, 0x080));
4209        assert_eq!(
4210            iqh.head_offset(),
4211            3 * stride,
4212            "IQH must advance to tail after processing"
4213        );
4214
4215        // Verify status data was written by INVALIDATION_WAIT.
4216        let status: u32 = gm.read_plain(E2E_IQ_STATUS).unwrap();
4217        assert_eq!(
4218            status, 0xDEAD_BEEF,
4219            "INVALIDATION_WAIT must write status data"
4220        );
4221
4222        // =====================================================================
4223        // Step 12: DMA translation via VtdTranslator
4224        // =====================================================================
4225        let translator = shared.translator();
4226
4227        // Translate IOVA 0x0 → should map to E2E_TARGET_GPA.
4228        let gpa = iommu_common::IommuTranslator::translate(
4229            &translator,
4230            E2E_TEST_RID,
4231            0x0000,
4232            false, // read
4233            |gpa| {
4234                assert_eq!(gm.read_plain::<[u8; 2]>(gpa).unwrap(), [0, 0]);
4235                gpa
4236            },
4237        )
4238        .unwrap();
4239        assert_eq!(gpa, E2E_TARGET_GPA, "IOVA 0x0 must map to target GPA");
4240        assert_eq!(
4241            u64::from_le(gm.read_plain::<u64>(E2E_PT_L1).unwrap()),
4242            pte_l1.into_bits() | if scalable { 1 << 8 } else { 0 },
4243        );
4244
4245        // Translate IOVA 0x0 with write.
4246        let gpa = iommu_common::IommuTranslator::translate(
4247            &translator,
4248            E2E_TEST_RID,
4249            0x0000,
4250            true, // write
4251            |gpa| {
4252                gm.write_at(gpa, &[0x5a, 0xa5]).unwrap();
4253                gpa
4254            },
4255        )
4256        .unwrap();
4257        assert_eq!(gpa, E2E_TARGET_GPA, "IOVA 0x0 write must map to target GPA");
4258        let payload =
4259            iommu_common::IommuTranslator::translate(&translator, E2E_TEST_RID, 0, false, |gpa| {
4260                gm.read_plain::<[u8; 2]>(gpa).unwrap()
4261            })
4262            .unwrap();
4263        assert_eq!(payload, [0x5a, 0xa5]);
4264        for (address, original, leaf) in [
4265            (E2E_PT_L4, pte_l4, false),
4266            (E2E_PT_L3, pte_l3, false),
4267            (E2E_PT_L2, pte_l2, false),
4268            (E2E_PT_L1, pte_l1, true),
4269        ] {
4270            assert_eq!(
4271                u64::from_le(gm.read_plain::<u64>(address).unwrap()),
4272                original.into_bits()
4273                    | if scalable {
4274                        (1 << 8) | if leaf { 1 << 9 } else { 0 }
4275                    } else {
4276                        0
4277                    },
4278            );
4279        }
4280
4281        // Translate with page offset.
4282        let gpa = iommu_common::IommuTranslator::translate(
4283            &translator,
4284            E2E_TEST_RID,
4285            0x0ABC,
4286            false,
4287            |gpa| gpa,
4288        )
4289        .unwrap();
4290        assert_eq!(
4291            gpa,
4292            E2E_TARGET_GPA + 0xABC,
4293            "IOVA page offset must be preserved"
4294        );
4295
4296        // =====================================================================
4297        // Step 13–14: Set up interrupt remapping
4298        // =====================================================================
4299
4300        // Program IRTE at index 0: vector=0x42, DST=5 (xAPIC), fixed delivery.
4301        let irte = Irte {
4302            lo: IrteLo::new()
4303                .with_p(true)
4304                .with_vector(0x42)
4305                .with_dst(0x0500) // xAPIC: APIC ID in bits 15:8
4306                .with_dlm(0) // Fixed delivery
4307                .with_dm(false), // Physical
4308            hi: IrteHi::new()
4309                .with_svt(0b01) // Verify SID
4310                .with_sq(0b00) // Exact match
4311                .with_sid(E2E_TEST_RID),
4312        };
4313        gm.write_at(E2E_IRT_BASE, irte.as_bytes()).unwrap();
4314
4315        // Write IRTA: base at E2E_IRT_BASE, S=0 (2 entries), EIME=0.
4316        let irta = IrtaReg::new()
4317            .with_irta(E2E_IRT_BASE >> 12)
4318            .with_s(0) // 2 entries
4319            .with_eime(false);
4320        write64(&mut dev, 0x0B8, irta.into_bits());
4321
4322        // GCMD: SIRTP (set IRT pointer). Must preserve toggle bits.
4323        write32(
4324            &mut dev,
4325            0x018,
4326            GcmdReg::new()
4327                .with_sirtp(true)
4328                .with_te(true)
4329                .with_qie(true)
4330                .into_bits(),
4331        );
4332        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
4333        assert!(gsts.irtps(), "IRTPS must be set after SIRTP");
4334
4335        // GCMD: IRE (enable interrupt remapping). Must preserve toggle bits.
4336        write32(
4337            &mut dev,
4338            0x018,
4339            GcmdReg::new()
4340                .with_ire(true)
4341                .with_te(true)
4342                .with_qie(true)
4343                .into_bits(),
4344        );
4345        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
4346        assert!(gsts.ires(), "IRES must be set after IRE");
4347
4348        // =====================================================================
4349        // Step 15: MSI remapping via VtdSignalMsi
4350        // =====================================================================
4351        let inner_msi = Arc::new(RecordingMsi::new());
4352        let wrapper = shared.wrap_signal_msi(inner_msi.clone());
4353
4354        // Send remappable-format MSI: addr bit4=1, handle=0, SHV=0 → IRTE index 0.
4355        let msi_addr = 0xFEE0_0010u64;
4356        let msi_data = 0u32;
4357        wrapper.signal_msi(Some(E2E_TEST_RID as u32), msi_addr, msi_data);
4358
4359        // Verify remapped MSI was delivered.
4360        assert_eq!(
4361            inner_msi.count.load(Ordering::SeqCst),
4362            1,
4363            "Remapped MSI must be delivered"
4364        );
4365        let delivered_addr = inner_msi.last_addr.load(Ordering::SeqCst);
4366        let delivered_data = inner_msi.last_data.load(Ordering::SeqCst);
4367        // Destination should be APIC ID 5 in bits 19:12.
4368        assert_eq!(
4369            (delivered_addr >> 12) & 0xFF,
4370            5,
4371            "Remapped MSI dest must be APIC ID 5"
4372        );
4373        // Vector should be 0x42.
4374        assert_eq!(
4375            delivered_data & 0xFF,
4376            0x42,
4377            "Remapped MSI vector must be 0x42"
4378        );
4379
4380        // =====================================================================
4381        // Step 16: Fault recording — unmapped IOVA
4382        // =====================================================================
4383
4384        // IOVA 0x1000 maps to L1 entry index 1, which is not populated (zeros).
4385        let fault_result = iommu_common::IommuTranslator::translate(
4386            &translator,
4387            E2E_TEST_RID,
4388            0x1000,
4389            false,
4390            |gpa| gpa,
4391        );
4392        assert!(
4393            fault_result.is_err(),
4394            "Unmapped IOVA must produce a translation fault"
4395        );
4396
4397        // Verify fault was recorded in FRCD.
4398        let frcd_hi = read64(&mut dev, Reg::FRCD_DW2.0);
4399        let frcd = FrcdHi::from(frcd_hi);
4400        assert!(frcd.f(), "Fault must be recorded (F=1)");
4401        assert_eq!(
4402            frcd.sid(),
4403            E2E_TEST_RID,
4404            "Fault source ID must match test device"
4405        );
4406
4407        // Verify FSTS.PPF is set.
4408        let fsts = FstsReg::from(read32(&mut dev, 0x034));
4409        assert!(fsts.ppf(), "PPF must be set when fault is pending");
4410
4411        // Clear fault by writing 1 to F bit (RW1C).
4412        write64(&mut dev, Reg::FRCD_DW2.0, 1u64 << 63);
4413        let fsts = FstsReg::from(read32(&mut dev, 0x034));
4414        assert!(!fsts.ppf(), "PPF must clear when F is cleared");
4415
4416        // =====================================================================
4417        // Verify source validation: wrong BDF should be rejected
4418        // =====================================================================
4419        let wrong_rid_wrapper = shared.wrap_signal_msi(inner_msi.clone());
4420        let pre_count = inner_msi.count.load(Ordering::SeqCst);
4421        wrong_rid_wrapper.signal_msi(Some(0x0200), msi_addr, msi_data); // Wrong BDF
4422        assert_eq!(
4423            inner_msi.count.load(Ordering::SeqCst),
4424            pre_count,
4425            "MSI with wrong source ID must be dropped"
4426        );
4427
4428        // =====================================================================
4429        // Verify disable: turn off translation, check identity mapping
4430        // =====================================================================
4431        // Write GCMD with TE=0, preserving QIE and IRE.
4432        write32(
4433            &mut dev,
4434            0x018,
4435            GcmdReg::new()
4436                .with_te(false)
4437                .with_qie(true)
4438                .with_ire(true)
4439                .into_bits(),
4440        );
4441        let gsts = GstsReg::from(read32(&mut dev, 0x01C));
4442        assert!(!gsts.tes(), "TES must clear when TE=0");
4443        assert!(gsts.qies(), "QIES must remain set");
4444        assert!(gsts.ires(), "IRES must remain set");
4445
4446        // With translation disabled, IOVA = GPA (identity mapping).
4447        let gpa = iommu_common::IommuTranslator::translate(
4448            &translator,
4449            E2E_TEST_RID,
4450            0xDEAD_0000,
4451            false,
4452            |gpa| gpa,
4453        )
4454        .unwrap();
4455        assert_eq!(
4456            gpa, 0xDEAD_0000,
4457            "Disabled translation must return identity mapping"
4458        );
4459    }
4460}