Skip to main content

intel_vtd/spec/
registers.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! MMIO register definitions for the Intel VT-d IOMMU.
5//!
6//! Based on the Intel VT-d Specification Rev 4.1, §10.4 (Register
7//! Descriptions). VT-d is a pure MMIO device — no PCI config space.
8
9use bitfield_struct::bitfield;
10use inspect::Inspect;
11use open_enum::open_enum;
12
13// =============================================================================
14// MMIO Register Offsets (§10.4)
15// =============================================================================
16
17open_enum! {
18    /// MMIO register offsets for the Intel VT-d IOMMU.
19    ///
20    /// 64-bit registers have separate `_HI` variants for the upper DWORD
21    /// (base + 4), enabling flat match dispatch without nested fallbacks.
22    #[derive(Inspect)]
23    #[inspect(debug)]
24    pub enum MmioRegister: u16 {
25        /// Version Register (32-bit, RO). §10.4.1.
26        VER             = 0x000,
27        /// Capability Register — lo DWORD (64-bit, RO). §10.4.2.
28        CAP             = 0x008,
29        /// Capability Register — hi DWORD.
30        CAP_HI          = 0x00C,
31        /// Extended Capability Register — lo DWORD (64-bit, RO). §10.4.3.
32        ECAP            = 0x010,
33        /// Extended Capability Register — hi DWORD.
34        ECAP_HI         = 0x014,
35        /// Global Command Register (32-bit, WO). §10.4.4.
36        GCMD            = 0x018,
37        /// Global Status Register (32-bit, RO). §10.4.5.
38        GSTS            = 0x01C,
39        /// Root Table Address Register — lo DWORD (64-bit, RW). §10.4.6.
40        RTADDR          = 0x020,
41        /// Root Table Address Register — hi DWORD.
42        RTADDR_HI       = 0x024,
43        /// Context Command Register — lo DWORD (64-bit, RW). §10.4.7.
44        CCMD            = 0x028,
45        /// Context Command Register — hi DWORD.
46        CCMD_HI         = 0x02C,
47        /// Fault Status Register (32-bit, RW1C). §10.4.9.
48        FSTS            = 0x034,
49        /// Fault Event Control Register (32-bit, RW). §10.4.10.
50        FECTL           = 0x038,
51        /// Fault Event Data Register (32-bit, RW). §10.4.11.
52        FEDATA          = 0x03C,
53        /// Fault Event Address Register (32-bit, RW). §10.4.12.
54        FEADDR          = 0x040,
55        /// Fault Event Upper Address Register (32-bit, RW). §10.4.13.
56        FEUADDR         = 0x044,
57        /// Invalidation Queue Head Register — lo DWORD (64-bit, RO). §10.4.17.
58        IQH             = 0x080,
59        /// Invalidation Queue Head Register — hi DWORD.
60        IQH_HI          = 0x084,
61        /// Invalidation Queue Tail Register — lo DWORD (64-bit, RW). §10.4.18.
62        IQT             = 0x088,
63        /// Invalidation Queue Tail Register — hi DWORD.
64        IQT_HI          = 0x08C,
65        /// Invalidation Queue Address Register — lo DWORD (64-bit, RW). §10.4.19.
66        IQA             = 0x090,
67        /// Invalidation Queue Address Register — hi DWORD.
68        IQA_HI          = 0x094,
69        /// Invalidation Completion Status Register (32-bit, RW1C). §10.4.20.
70        ICS             = 0x09C,
71        /// Invalidation Event Control Register (32-bit, RW). §10.4.21.
72        IECTL           = 0x0A0,
73        /// Invalidation Event Data Register (32-bit, RW). §10.4.22.
74        IEDATA          = 0x0A4,
75        /// Invalidation Event Address Register (32-bit, RW). §10.4.23.
76        IEADDR          = 0x0A8,
77        /// Invalidation Event Upper Address Register (32-bit, RW). §10.4.24.
78        IEUADDR         = 0x0AC,
79        /// Interrupt Remapping Table Address Register — lo DWORD (64-bit, RW). §10.4.29.
80        IRTA            = 0x0B8,
81        /// Interrupt Remapping Table Address Register — hi DWORD.
82        IRTA_HI         = 0x0BC,
83        /// Invalidate Address Register — lo DWORD (64-bit, RW). §10.4.15.
84        IVA             = 0x100,
85        /// Invalidate Address Register — hi DWORD.
86        IVA_HI          = 0x104,
87        /// IOTLB Invalidate Register — lo DWORD (64-bit, RW). §10.4.16.
88        IOTLB           = 0x108,
89        /// IOTLB Invalidate Register — hi DWORD.
90        IOTLB_HI        = 0x10C,
91        /// Fault Recording Register DWORD 0 (FrcdLo lo). §10.4.14.
92        FRCD_DW0        = 0x120,
93        /// Fault Recording Register DWORD 1 (FrcdLo hi).
94        FRCD_DW1        = 0x124,
95        /// Fault Recording Register DWORD 2 (FrcdHi lo).
96        FRCD_DW2        = 0x128,
97        /// Fault Recording Register DWORD 3 (FrcdHi hi, contains F bit).
98        FRCD_DW3        = 0x12C,
99    }
100}
101
102/// MMIO region size in bytes (4KB, one page).
103pub const MMIO_REGION_SIZE: u64 = 0x1000;
104
105// =============================================================================
106// Register Bitfield Definitions
107// =============================================================================
108
109/// Version Register (MMIO offset 0x000, 32-bit, RO). §10.4.1.
110///
111/// Reports the architecture version implemented by the hardware.
112#[bitfield(u32)]
113#[derive(Inspect)]
114#[rustfmt::skip]
115pub struct VersionReg {
116    /// Minor version number.
117    #[bits(4)]
118    pub min: u8,
119    /// Major version number.
120    #[bits(4)]
121    pub max: u8,
122    #[bits(24)]
123    _reserved: u32,
124}
125
126/// Capability Register (MMIO offset 0x008, 64-bit, RO). §10.4.2.
127///
128/// Reports hardware capabilities to software. Key fields control the
129/// supported address widths, large page sizes, fault recording, and
130/// domain count.
131#[bitfield(u64)]
132#[derive(Inspect)]
133#[rustfmt::skip]
134pub struct CapReg {
135    /// Number of domains supported (3 bits). See [`NumDomains`].
136    #[bits(3)]
137    pub nd: u8,
138    /// Advanced Fault Logging (not supported in emulator).
139    pub afl: bool,
140    /// Required Write-Buffer Flushing — 0 = not required.
141    pub rwbf: bool,
142    /// Protected Low-Memory Region support.
143    pub plmr: bool,
144    /// Protected High-Memory Region support.
145    pub phmr: bool,
146    /// Caching Mode — 0 = non-caching (walk tables on every access).
147    pub cm: bool,
148    /// Supported Adjusted Guest Address Widths (5 bits).
149    /// Bit 0 (reserved), bit 1 = 39-bit (3-level), bit 2 = 48-bit (4-level),
150    /// bit 3 = 57-bit (5-level), bit 4 (reserved).
151    #[bits(5)]
152    pub sagaw: u8,
153    #[bits(3)]
154    _reserved1: u64,
155    /// Maximum Guest Address Width (6 bits) — value is MGAW-1.
156    /// 47 = 48-bit address width.
157    #[bits(6)]
158    pub mgaw: u8,
159    /// Zero-Length Read — 1 = supports zero-length read requests.
160    pub zlr: bool,
161    /// Deprecated (was FLR — Fault Log Register, pre-spec 2.0).
162    #[bits(1)]
163    _deprecated: u64,
164    /// Fault Recording Register Offset (10 bits).
165    /// Offset in 16-byte units from register base. FRO=0x12 → offset 0x120.
166    #[bits(10)]
167    pub fro: u16,
168    /// Second-Level Large Page Support (4 bits).
169    /// Bit 0 = 2MB (21-bit), bit 1 = 1GB (30-bit).
170    #[bits(4)]
171    pub sllps: u8,
172    #[bits(1)]
173    _reserved_38: u64,
174    /// Page-Selective Invalidation support.
175    pub psi: bool,
176    /// Number of Fault Recording registers minus 1 (8 bits).
177    /// NFR=0 means 1 fault record.
178    #[bits(8)]
179    pub nfr: u8,
180    /// Maximum Address Mask Value (6 bits) for page-selective invalidation.
181    #[bits(6)]
182    pub mamv: u8,
183    /// DMA Write Draining — 1 = hardware drains write buffers.
184    pub dwd: bool,
185    /// DMA Read Draining — 1 = hardware drains read requests.
186    pub drd: bool,
187    /// First-Level 1GB page support (not used in legacy mode).
188    pub fl1gp: bool,
189    #[bits(2)]
190    _reserved2: u64,
191    /// Page-walk Incoherency — 1 = page walk results may be incoherent.
192    pub pi: bool,
193    #[bits(4)]
194    _reserved3: u64,
195}
196
197open_enum! {
198    /// CAP.ND encoding: number of supported domain IDs. §10.4.2, Table 10-2.
199    #[derive(Inspect)]
200    #[inspect(debug)]
201    pub enum NumDomains: u8 {
202        /// 16 domains (4-bit domain ID).
203        ND_16       = 0,
204        /// 64 domains (6-bit domain ID).
205        ND_64       = 1,
206        /// 256 domains (8-bit domain ID).
207        ND_256      = 2,
208        /// 1024 domains (10-bit domain ID).
209        ND_1K       = 3,
210        /// 4096 domains (12-bit domain ID).
211        ND_4K       = 4,
212        /// 16384 domains (14-bit domain ID).
213        ND_16K      = 5,
214        /// 65536 domains (16-bit domain ID).
215        ND_64K      = 6,
216    }
217}
218
219/// CAP.SLLPS bit: 2MB large page support (21-bit page offset).
220pub const SLLPS_2MB: u8 = 1 << 0;
221/// CAP.SLLPS bit: 1GB large page support (30-bit page offset).
222pub const SLLPS_1GB: u8 = 1 << 1;
223
224/// Extended Capability Register (MMIO offset 0x010, 64-bit, RO). §11.4.3.
225///
226/// Reports queued invalidation, interrupt remapping, scalable translation,
227/// and second-stage accessed/dirty capabilities.
228#[bitfield(u64)]
229#[derive(Inspect)]
230#[rustfmt::skip]
231pub struct EcapReg {
232    /// Page-walk Coherency — 1 = hardware ensures coherency.
233    pub c: bool,
234    /// Queued Invalidation support.
235    pub qi: bool,
236    /// Device-TLB support (not implemented).
237    pub dt: bool,
238    /// Interrupt Remapping support.
239    pub ir: bool,
240    /// Extended Interrupt Mode — 1 = x2APIC (32-bit destination IDs).
241    pub eim: bool,
242    #[bits(1)]
243    _reserved1: u64,
244    /// Pass Through support.
245    pub pt: bool,
246    /// Snoop Control support.
247    pub sc: bool,
248    /// IOTLB Register Offset (10 bits).
249    /// Offset in 16-byte units from register base. IRO=0x10 → offset 0x100.
250    #[bits(10)]
251    pub iro: u16,
252    #[bits(2)]
253    _reserved2: u64,
254    /// Maximum Handle Mask Value (4 bits) for interrupt remapping.
255    #[bits(4)]
256    pub mhmv: u8,
257    #[bits(1)]
258    _reserved3: u64,
259    /// Memory Type Support (not used).
260    pub mts: bool,
261    /// Nested Translation support (not implemented).
262    pub nest: bool,
263    #[bits(2)]
264    _reserved4: u64,
265    /// Page Request support (not implemented).
266    pub prs: bool,
267    /// Execute Request support (not implemented).
268    pub ers: bool,
269    /// Supervisor Request support (not implemented).
270    pub srs: bool,
271    #[bits(1)]
272    _reserved5: u64,
273    /// No Write Flag support.
274    pub nwfs: bool,
275    /// Extended Accessed Flag support.
276    pub eafs: bool,
277    /// Process Address Space ID Size (5 bits, not used).
278    #[bits(5)]
279    pub pss: u8,
280    /// PASID Translation support (not implemented).
281    pub pasid: bool,
282    /// Device-TLB Invalidation Throttle (not used).
283    pub dit: bool,
284    /// Page-request Drain Support (not implemented).
285    pub pds: bool,
286    /// Scalable-mode DMA remapping through context and PASID tables.
287    pub smts: bool,
288    /// Virtual Command support (not used).
289    pub vcs: bool,
290    /// Second-stage accessed/dirty flag support, enabled by PASID-entry SSADE.
291    pub ssads: bool,
292    /// Second-stage translation support in scalable mode.
293    pub ssts: bool,
294    /// First-stage Translation Support (scalable mode, not used).
295    pub flts: bool,
296    /// Scalable-mode page walks snoop according to PASID-entry PWSNP.
297    pub smpwcs: bool,
298    /// Context-entry RID_PASID support for requests without an explicit PASID.
299    pub rps: bool,
300    #[bits(1)]
301    _reserved6: u64,
302    /// Performance Monitoring Support (not used).
303    pub pms: bool,
304    /// Abort DMA Mode Support (not used).
305    pub adms: bool,
306    /// RID_PRIV Support (not used).
307    pub rprivs: bool,
308    #[bits(4)]
309    _reserved7: u64,
310    /// Stop Marker Support (not used).
311    pub sms: bool,
312    #[bits(5)]
313    _reserved8: u64,
314}
315
316/// Global Command Register (MMIO offset 0x018, 32-bit, WO). §10.4.4.
317///
318/// Write-only register for controlling IOMMU operation. Reads return 0.
319/// Each bit triggers a corresponding action; status is reflected in GSTS.
320#[bitfield(u32)]
321#[derive(Inspect)]
322#[rustfmt::skip]
323pub struct GcmdReg {
324    #[bits(23)]
325    _reserved: u32,
326    /// Compatibility Format Interrupt — pass through compatibility-format
327    /// MSIs when IR is enabled.
328    pub cfi: bool,
329    /// Set Interrupt Remapping Table Pointer — latch IRTA register.
330    pub sirtp: bool,
331    /// Interrupt Remapping Enable.
332    pub ire: bool,
333    /// Queued Invalidation Enable.
334    pub qie: bool,
335    /// Write Buffer Flush — flush internal write buffers.
336    pub wbf: bool,
337    /// Enable Advanced Fault Logging (not supported, treated as no-op).
338    pub eafl: bool,
339    /// Set Fault Log (not supported, treated as no-op).
340    pub sfl: bool,
341    /// Set Root Table Pointer — latch RTADDR register.
342    pub srtp: bool,
343    /// Translation Enable — enable/disable DMA translation.
344    pub te: bool,
345}
346
347/// Global Status Register (MMIO offset 0x01C, 32-bit, RO). §10.4.5.
348///
349/// Reports the status corresponding to GCMD operations.
350#[bitfield(u32)]
351#[derive(Inspect)]
352#[rustfmt::skip]
353pub struct GstsReg {
354    #[bits(23)]
355    _reserved: u32,
356    /// Compatibility Format Interrupt Status.
357    pub cfis: bool,
358    /// Interrupt Remapping Table Pointer Status — IRTA has been latched.
359    pub irtps: bool,
360    /// Interrupt Remapping Enable Status.
361    pub ires: bool,
362    /// Queued Invalidation Enable Status.
363    pub qies: bool,
364    /// Write Buffer Flush Status.
365    pub wbfs: bool,
366    /// Advanced Fault Logging Status.
367    pub afls: bool,
368    /// Fault Log Status.
369    pub fls: bool,
370    /// Root Table Pointer Status — RTADDR has been latched.
371    pub rtps: bool,
372    /// Translation Enable Status.
373    pub tes: bool,
374}
375
376/// Root Table Address Register (MMIO offset 0x020, 64-bit, RW). §10.4.6.
377///
378/// Holds the physical address of the root table. The value is consumed when
379/// GCMD.SRTP is written. Bits 11:10 select translation type (00=legacy).
380#[bitfield(u64)]
381#[derive(Inspect)]
382#[rustfmt::skip]
383pub struct RtaddrReg {
384    #[bits(10)]
385    _reserved: u64,
386    /// Translation table mode (bits 11:10).
387    /// 00 = legacy mode, 01 = scalable mode.
388    #[bits(2)]
389    pub ttm: u8,
390    /// Root table address, bits [63:12]. 4KB-aligned.
391    #[bits(52)]
392    pub rta: u64,
393}
394
395impl RtaddrReg {
396    /// Get the full root table physical address (bits 63:12 shifted).
397    pub fn root_table_address(&self) -> u64 {
398        self.rta() << 12
399    }
400}
401
402open_enum! {
403    /// Translation table mode in RTADDR.TTM (§11.4.5).
404    #[derive(Inspect)]
405    #[inspect(debug)]
406    pub enum TranslationTableMode: u8 {
407        /// Legacy root/context tables.
408        LEGACY = 0b00,
409        /// Scalable root/context and PASID tables.
410        SCALABLE = 0b01,
411        /// Abort DMA (not supported by the emulator).
412        ABORT_DMA = 0b11,
413    }
414}
415
416/// Context Command Register (MMIO offset 0x028, 64-bit, RW). §10.4.7.
417///
418/// Register-based context-cache invalidation (used before QI is enabled).
419#[bitfield(u64)]
420#[derive(Inspect)]
421#[rustfmt::skip]
422pub struct CcmdReg {
423    /// Domain ID for domain-selective invalidation.
424    #[bits(16)]
425    pub did: u16,
426    /// Source ID for device-selective invalidation.
427    #[bits(16)]
428    pub sid: u16,
429    /// Function Mask for device-selective invalidation.
430    #[bits(2)]
431    pub fm: u8,
432    #[bits(25)]
433    _reserved: u64,
434    /// Context Actual Invalidation Granularity (RO, set by HW).
435    #[bits(2)]
436    pub caig: u8,
437    /// Context Invalidation Request Granularity.
438    /// 01=global, 10=domain, 11=device.
439    #[bits(2)]
440    pub cirg: u8,
441    /// Invalidate Context-Cache — set by SW, cleared by HW when done.
442    pub icc: bool,
443}
444
445/// Fault Status Register (MMIO offset 0x034, 32-bit, RW1C). §10.4.9.
446///
447/// Reports fault recording status. PPF is dynamically computed as OR of all
448/// FRCD\[n\].F bits.
449#[bitfield(u32)]
450#[derive(Inspect)]
451#[rustfmt::skip]
452pub struct FstsReg {
453    /// Primary Fault Overflow — a fault was discarded because all records
454    /// were occupied.
455    pub pfo: bool,
456    /// Primary Fault Pending — dynamically computed as OR of FRCD[n].F.
457    /// Not independently stored; always reflects current FRCD state.
458    pub ppf: bool,
459    /// Advanced Fault Overflow (not used).
460    pub afo: bool,
461    /// Advanced Fault Pending (not used).
462    pub apf: bool,
463    /// Invalidation Queue Error — illegal descriptor in the queue.
464    pub iqe: bool,
465    /// Invalidation Completion Error (not used).
466    pub ice: bool,
467    /// Invalidation Timeout Error (not used).
468    pub ite: bool,
469    #[bits(1)]
470    _reserved1: u32,
471    /// Fault Record Index — index of the first pending fault record.
472    #[bits(8)]
473    pub fri: u8,
474    #[bits(16)]
475    _reserved2: u32,
476}
477
478/// Fault Event Control Register (MMIO offset 0x038, 32-bit, RW). §10.4.10.
479///
480/// Controls fault event MSI signaling.
481#[bitfield(u32)]
482#[derive(Inspect)]
483#[rustfmt::skip]
484pub struct FectlReg {
485    #[bits(30)]
486    _reserved: u32,
487    /// Interrupt Pending (RO) — set when fault interrupt is pending.
488    pub ip: bool,
489    /// Interrupt Mask — 1 = mask fault event interrupt.
490    pub im: bool,
491}
492
493/// Fault Event Data Register (MMIO offset 0x03C, 32-bit, RW). §10.4.11.
494#[bitfield(u32)]
495#[derive(Inspect)]
496#[rustfmt::skip]
497pub struct FedataReg {
498    /// Interrupt Message Data.
499    #[bits(16)]
500    pub imd: u16,
501    /// Extended Interrupt Message Data (for x2APIC).
502    #[bits(16)]
503    pub eimd: u16,
504}
505
506/// Fault Event Address Register (MMIO offset 0x040, 32-bit, RW). §10.4.12.
507#[bitfield(u32)]
508#[derive(Inspect)]
509#[rustfmt::skip]
510pub struct FeaddrReg {
511    #[bits(2)]
512    _reserved: u32,
513    /// Message Address (bits 31:2).
514    #[bits(30)]
515    pub ma: u32,
516}
517
518impl FeaddrReg {
519    /// Get the full message address (bits 31:2 shifted).
520    pub fn message_address(&self) -> u32 {
521        self.ma() << 2
522    }
523}
524
525/// Invalidation Queue Head Register (MMIO offset 0x080, 64-bit). §10.4.17.
526///
527/// Read-only from software. Points to the next descriptor to be fetched.
528#[bitfield(u64)]
529#[derive(Inspect)]
530#[rustfmt::skip]
531pub struct IqhReg {
532    #[bits(4)]
533    _reserved1: u64,
534    /// Queue head in 16-byte units (bits 18:4); bit 4 is zero for IQA.DW=1.
535    #[bits(15)]
536    pub qh: u32,
537    #[bits(45)]
538    _reserved2: u64,
539}
540
541impl IqhReg {
542    /// Get the byte offset of the head pointer.
543    pub fn head_offset(&self) -> u64 {
544        (self.qh() as u64) << 4
545    }
546}
547
548/// Invalidation Queue Tail Register (MMIO offset 0x088, 64-bit, RW). §10.4.18.
549///
550/// Software writes this to submit new descriptors.
551#[bitfield(u64)]
552#[derive(Inspect)]
553#[rustfmt::skip]
554pub struct IqtReg {
555    #[bits(4)]
556    _reserved1: u64,
557    /// Queue tail in 16-byte units (bits 18:4); bit 4 is reserved for IQA.DW=1.
558    #[bits(15)]
559    pub qt: u32,
560    #[bits(45)]
561    _reserved2: u64,
562}
563
564impl IqtReg {
565    /// Get the byte offset of the tail pointer.
566    pub fn tail_offset(&self) -> u64 {
567        (self.qt() as u64) << 4
568    }
569}
570
571/// Invalidation Queue Address Register (MMIO offset 0x090, 64-bit, RW). §11.4.9.3.
572///
573/// Holds the base address and size of the invalidation queue. Only writable
574/// when QIE=0.
575#[bitfield(u64)]
576#[derive(Inspect)]
577#[rustfmt::skip]
578pub struct IqaReg {
579    /// Queue size in 4KB pages = 2^QS, independent of descriptor width.
580    #[bits(3)]
581    pub qs: u8,
582    #[bits(8)]
583    _reserved: u64,
584    /// Descriptor width (bit 11): 0=128-bit, 1=256-bit.
585    /// 256-bit descriptors require scalable-mode or abort-DMA capability.
586    pub dw: bool,
587    /// Queue base address, bits [63:12]. 4KB-aligned.
588    #[bits(52)]
589    pub iqa: u64,
590}
591
592impl IqaReg {
593    /// Get the queue base physical address.
594    pub fn queue_base_address(&self) -> u64 {
595        self.iqa() << 12
596    }
597
598    /// Get the queue size in bytes: 4096 * 2^QS, for either descriptor width.
599    pub fn queue_size_bytes(&self) -> u64 {
600        4096u64 << self.qs()
601    }
602}
603
604/// Invalidation Completion Status Register (MMIO offset 0x09C, 32-bit, RW1C).
605/// §10.4.20.
606#[bitfield(u32)]
607#[derive(Inspect)]
608#[rustfmt::skip]
609pub struct IcsReg {
610    /// Invalidation Wait Descriptor Complete.
611    pub iwc: bool,
612    #[bits(31)]
613    _reserved: u32,
614}
615
616/// Invalidation Event Control Register (MMIO offset 0x0A0, 32-bit, RW).
617/// §10.4.21.
618///
619/// Controls invalidation completion event MSI signaling. Separate from the
620/// fault event registers (FECTL/FEDATA).
621#[bitfield(u32)]
622#[derive(Inspect)]
623#[rustfmt::skip]
624pub struct IectlReg {
625    #[bits(30)]
626    _reserved: u32,
627    /// Interrupt Pending (RO).
628    pub ip: bool,
629    /// Interrupt Mask — 1 = mask invalidation completion interrupt.
630    pub im: bool,
631}
632
633/// Interrupt Remapping Table Address Register (MMIO offset 0x0B8, 64-bit, RW).
634/// §10.4.29.
635///
636/// Holds the base address, size, and mode of the interrupt remapping table.
637/// Only writable when IRE=0.
638#[bitfield(u64)]
639#[derive(Inspect)]
640#[rustfmt::skip]
641pub struct IrtaReg {
642    /// Size: number of entries = 2^(S+1). 0=2, 15=65536.
643    #[bits(4)]
644    pub s: u8,
645    #[bits(7)]
646    _reserved: u64,
647    /// Extended Interrupt Mode Enable — 1 = x2APIC 32-bit destinations.
648    pub eime: bool,
649    /// IRT base address, bits [63:12]. 4KB-aligned.
650    #[bits(52)]
651    pub irta: u64,
652}
653
654impl IrtaReg {
655    /// Get the IRT base physical address.
656    pub fn irt_base_address(&self) -> u64 {
657        self.irta() << 12
658    }
659
660    /// Get the number of entries in the IRT: 2^(S+1).
661    pub fn entry_count(&self) -> u32 {
662        1u32 << (self.s() as u32 + 1)
663    }
664}
665
666/// IOTLB Invalidate Register (ECAP.IRO*16 + 0x08, 64-bit, RW). §10.4.16.
667///
668/// Register-based IOTLB invalidation (used before QI is enabled).
669#[bitfield(u64)]
670#[derive(Inspect)]
671#[rustfmt::skip]
672pub struct IotlbReg {
673    #[bits(32)]
674    _reserved1: u64,
675    /// Domain ID for domain-selective invalidation.
676    #[bits(16)]
677    pub did: u16,
678    /// Drain Writes.
679    pub dw: bool,
680    /// Drain Reads.
681    pub dr: bool,
682    #[bits(7)]
683    _reserved2: u64,
684    /// IOTLB Actual Invalidation Granularity (RO, set by HW).
685    #[bits(2)]
686    pub iaig: u8,
687    #[bits(1)]
688    _reserved3: u64,
689    /// IOTLB Invalidation Request Granularity.
690    /// 01=global, 10=domain, 11=page.
691    #[bits(2)]
692    pub iirg: u8,
693    #[bits(1)]
694    _reserved4: u64,
695    /// Invalidate IOTLB — set by SW, cleared by HW when done.
696    pub ivt: bool,
697}
698
699/// Fault Recording Register — high 64 bits (CAP.FRO*16 + 8). §11.4.7.6.
700///
701/// Contains fault metadata: source ID, fault reason, type, and the fault bit.
702#[bitfield(u64)]
703#[derive(Inspect)]
704#[rustfmt::skip]
705pub struct FrcdHi {
706    /// Source ID (BDF) of the faulting device.
707    #[bits(16)]
708    pub sid: u16,
709    // Includes T2=0 (no Page Request/AtomicOp reporting) and unsupported
710    // explicit-PASID fields PRIV, EXE, PP.
711    #[bits(16)]
712    _reserved1: u64,
713    /// Fault Reason code, full-register bits 103:96. See [`FaultReason`].
714    #[bits(8)]
715    pub fr: u8,
716    /// PASID value is reserved-zero without explicit PASID support.
717    #[bits(20)]
718    _reserved2: u64,
719    /// Address Type (2 bits).
720    #[bits(2)]
721    pub at: u8,
722    /// Type — 0 = write request, 1 = read request (or other non-write).
723    pub t: bool,
724    /// Fault — 1 = this record contains a valid fault. RW1C (write 1 to clear).
725    pub f: bool,
726}
727
728/// Fault Recording Register — low 64 bits (CAP.FRO*16). §11.4.7.6.
729///
730/// Contains the faulting address (bits 63:12) and reserved low bits.
731#[bitfield(u64)]
732#[derive(Inspect)]
733#[rustfmt::skip]
734pub struct FrcdLo {
735    #[bits(12)]
736    _reserved1: u64,
737    /// Fault Info — the faulting IOVA address, bits [63:12].
738    #[bits(52)]
739    pub fi: u64,
740}
741
742impl FrcdLo {
743    /// Get the full faulting address (bits 63:12 shifted).
744    pub fn fault_address(&self) -> u64 {
745        self.fi() << 12
746    }
747}
748
749// =============================================================================
750// Fault Reason Codes (§7.1.3, §5.1.4.1)
751// =============================================================================
752
753open_enum! {
754    /// VT-d fault reason codes.
755    #[derive(Inspect)]
756    #[inspect(debug)]
757    pub enum FaultReason: u8 {
758        // DMA remapping faults (§7.1.3)
759
760        /// Root entry not present (P=0).
761        ROOT_NOT_PRESENT            = 0x01,
762        /// Context entry not present (P=0).
763        CONTEXT_NOT_PRESENT         = 0x02,
764        /// Invalid context entry programming (reserved TT value).
765        INVALID_CONTEXT_ENTRY       = 0x03,
766        /// Address beyond MGAW (IOVA overflow).
767        ADDRESS_BEYOND_MGAW         = 0x04,
768        /// Write permission fault (W=0 in page table path).
769        WRITE_ACCESS_DENIED         = 0x05,
770        /// Read permission fault (R=0 in page table path).
771        READ_ACCESS_DENIED          = 0x06,
772        /// Error accessing second-level paging entry (GPA read failure).
773        SL_PTE_ACCESS_ERROR         = 0x07,
774        /// Error accessing root entry (GPA read failure).
775        ROOT_ENTRY_ACCESS_ERROR     = 0x08,
776        /// Error accessing context entry (GPA read failure).
777        CONTEXT_ENTRY_ACCESS_ERROR  = 0x09,
778        /// Reserved bit set in root entry.
779        ROOT_ENTRY_RESERVED_BIT     = 0x0A,
780        /// Reserved bit set in context entry.
781        CONTEXT_ENTRY_RESERVED_BIT  = 0x0B,
782        /// Reserved bit set in second-level paging entry.
783        SL_PTE_RESERVED_BIT         = 0x0C,
784        /// Context entry blocks translation/translated requests.
785        CONTEXT_ENTRY_TT_BLOCK      = 0x0D,
786        /// Output address in interrupt address range.
787        OUTPUT_ADDR_IN_INTR_RANGE   = 0x0E,
788
789        // Scalable translation faults (Table 26, §7.1.3). These definitions
790        // do not enable scalable translation or change legacy fault mappings.
791
792        /// RTA.1: invalid translation table mode.
793        INVALID_ROOT_TABLE_MODE     = 0x30,
794        /// SRT.1: scalable root-entry access error.
795        SCALABLE_ROOT_ACCESS_ERROR  = 0x38,
796        /// SRT.2: selected scalable root half not present.
797        SCALABLE_ROOT_NOT_PRESENT   = 0x39,
798        /// SRT.3: reserved field in a present root half.
799        SCALABLE_ROOT_RESERVED_BIT  = 0x3A,
800        /// SCT.1: scalable context-entry access error.
801        SCALABLE_CONTEXT_ACCESS_ERROR = 0x40,
802        /// SCT.2: scalable context not present.
803        SCALABLE_CONTEXT_NOT_PRESENT = 0x41,
804        /// SCT.3: reserved field in a present context.
805        SCALABLE_CONTEXT_RESERVED_BIT = 0x42,
806        /// SCT.4: invalid context programming, including RID_PASID > PDTS limit.
807        SCALABLE_INVALID_CONTEXT    = 0x43,
808        /// SPD.1: PASID-directory access error.
809        PASID_DIRECTORY_ACCESS_ERROR = 0x50,
810        /// SPD.2: PASID-directory entry not present.
811        PASID_DIRECTORY_NOT_PRESENT = 0x51,
812        /// SPD.3: reserved field in a present PASID-directory entry.
813        PASID_DIRECTORY_RESERVED_BIT = 0x52,
814        /// SPT.1: PASID-table access error.
815        PASID_TABLE_ACCESS_ERROR    = 0x58,
816        /// SPT.2: PASID-table entry not present.
817        PASID_TABLE_NOT_PRESENT     = 0x59,
818        /// SPT.3: reserved field in a present PASID-table entry.
819        PASID_TABLE_RESERVED_BIT    = 0x5A,
820        /// SPT.4: invalid PASID-table programming, including AW and PGTT.
821        PASID_TABLE_INVALID_ENTRY  = 0x5B,
822        /// SSS.1: access error through a preceding second-stage paging entry.
823        SCALABLE_SL_PTE_ACCESS_ERROR = 0x78,
824        /// SSS.2: R=W=0 in an entry or in accumulated second-stage permissions.
825        SCALABLE_SL_PTE_NOT_PRESENT = 0x79,
826        /// SSS.3: reserved field in a present second-stage paging entry.
827        SCALABLE_SL_PTE_RESERVED_BIT = 0x7A,
828        /// SSS.4: access error through the PASID entry's SSPTPTR.
829        SCALABLE_SL_ROOT_ACCESS_ERROR = 0x7B,
830        /// SSS.5: an A/D update is needed but page walks do not snoop caches.
831        SL_AD_UPDATE_NON_SNOOP      = 0x7C,
832        /// SSS.6: hardware failed to atomically update a second-stage A/D bit.
833        SL_AD_UPDATE_FAILED         = 0x7D,
834        /// SGN.4: host input address exceeds HAW.
835        SCALABLE_ADDRESS_BEYOND_HAW = 0x83,
836        /// SGN.5: second-stage input address exceeds min(MGAW, AGAW).
837        SCALABLE_ADDRESS_BEYOND_AGAW = 0x84,
838        /// SGN.6: write permission fault in scalable translation.
839        SCALABLE_WRITE_ACCESS_DENIED = 0x85,
840        /// SGN.7: read permission fault in scalable second-stage translation.
841        SCALABLE_READ_ACCESS_DENIED = 0x86,
842        /// SGN.8: scalable translation output is in the interrupt address range.
843        SCALABLE_OUTPUT_ADDR_IN_INTR_RANGE = 0x87,
844
845        // Interrupt remapping faults (§5.1.4.1)
846
847        /// Reserved field set in remappable interrupt request.
848        IR_RESERVED_FIELD           = 0x20,
849        /// Interrupt index exceeds IRT size.
850        IR_INDEX_EXCEEDS_SIZE       = 0x21,
851        /// IRTE not present (P=0).
852        IRTE_NOT_PRESENT            = 0x22,
853        /// Error accessing IRTE (GPA read failure).
854        IRTE_ACCESS_ERROR           = 0x23,
855        /// Reserved field set in present IRTE.
856        IRTE_RESERVED_FIELD         = 0x24,
857        /// Compatibility-format interrupt blocked.
858        COMPAT_FORMAT_BLOCKED       = 0x25,
859        /// Source-ID verification failure.
860        SOURCE_ID_VERIFICATION_FAIL = 0x26,
861    }
862}
863
864#[cfg(test)]
865mod tests {
866    use super::*;
867    use test_with_tracing::test;
868
869    #[test]
870    fn test_gcmd_gsts_bits_aligned() {
871        // GCMD and GSTS must have matching bit positions for the status bits.
872        let gcmd = GcmdReg::new()
873            .with_te(true)
874            .with_srtp(true)
875            .with_qie(true)
876            .with_ire(true)
877            .with_sirtp(true)
878            .with_cfi(true)
879            .with_wbf(true);
880
881        let gsts = GstsReg::new()
882            .with_tes(true)
883            .with_rtps(true)
884            .with_qies(true)
885            .with_ires(true)
886            .with_irtps(true)
887            .with_cfis(true)
888            .with_wbfs(true);
889
890        // The bit positions should match between command and status.
891        let gcmd_val = u32::from(gcmd);
892        let gsts_val = u32::from(gsts);
893        assert_eq!(gcmd_val, gsts_val);
894    }
895
896    #[test]
897    fn test_iqa_queue_size() {
898        for qs in 0..=7 {
899            for dw in [false, true] {
900                let raw = 0x1234_5678_9000 | ((dw as u64) << 11) | qs;
901                let iqa = IqaReg::from(raw);
902                assert_eq!(iqa.qs(), qs as u8);
903                assert_eq!(iqa.dw(), dw);
904                assert_eq!(iqa.queue_base_address(), 0x1234_5678_9000);
905                assert_eq!(iqa.queue_size_bytes(), 4096 << qs);
906            }
907        }
908        assert_eq!(IqaReg::new().with_dw(true).into_bits(), 0x800);
909        for bit in 3..=10 {
910            let iqa = IqaReg::from(1 << bit);
911            assert!(!iqa.dw());
912            assert_eq!(iqa.qs(), 0);
913            assert_eq!(iqa.queue_base_address(), 0);
914        }
915    }
916
917    #[test]
918    fn test_frcd_literal_layout() {
919        let raw = (1 << 63) | (1 << 62) | (2 << 60) | (0x7D << 32) | 0xFEDC;
920        let frcd = FrcdHi::from(raw);
921        assert_eq!(frcd.sid(), 0xFEDC);
922        assert_eq!(frcd.fr(), 0x7D);
923        assert_eq!(frcd.at(), 2);
924        assert!(frcd.t());
925        assert!(frcd.f());
926        assert_eq!(
927            FrcdHi::new()
928                .with_sid(0xFEDC)
929                .with_fr(0x7D)
930                .with_at(2)
931                .with_t(true)
932                .with_f(true)
933                .into_bits(),
934            raw
935        );
936        assert_eq!(FrcdHi::from(0xFF << 48).fr(), 0);
937    }
938
939    #[test]
940    fn test_translation_table_mode_encodings() {
941        assert_eq!(TranslationTableMode::LEGACY.0, 0);
942        assert_eq!(TranslationTableMode::SCALABLE.0, 1);
943        assert_eq!(TranslationTableMode::ABORT_DMA.0, 3);
944        for mode in 0..=3 {
945            let rtaddr = RtaddrReg::from(0x1234_5000 | ((mode as u64) << 10));
946            assert_eq!(TranslationTableMode(rtaddr.ttm()).0, mode);
947            assert_eq!(rtaddr.root_table_address(), 0x1234_5000);
948        }
949    }
950
951    #[test]
952    fn test_scalable_fault_reason_codes() {
953        // Independent literal encodings from §7.1.3, Table 26.
954        for (reason, expected) in [
955            (FaultReason::INVALID_ROOT_TABLE_MODE, 0x30),
956            (FaultReason::SCALABLE_ROOT_ACCESS_ERROR, 0x38),
957            (FaultReason::SCALABLE_ROOT_NOT_PRESENT, 0x39),
958            (FaultReason::SCALABLE_ROOT_RESERVED_BIT, 0x3a),
959            (FaultReason::SCALABLE_CONTEXT_ACCESS_ERROR, 0x40),
960            (FaultReason::SCALABLE_CONTEXT_NOT_PRESENT, 0x41),
961            (FaultReason::SCALABLE_CONTEXT_RESERVED_BIT, 0x42),
962            (FaultReason::SCALABLE_INVALID_CONTEXT, 0x43),
963            (FaultReason::PASID_DIRECTORY_ACCESS_ERROR, 0x50),
964            (FaultReason::PASID_DIRECTORY_NOT_PRESENT, 0x51),
965            (FaultReason::PASID_DIRECTORY_RESERVED_BIT, 0x52),
966            (FaultReason::PASID_TABLE_ACCESS_ERROR, 0x58),
967            (FaultReason::PASID_TABLE_NOT_PRESENT, 0x59),
968            (FaultReason::PASID_TABLE_RESERVED_BIT, 0x5a),
969            (FaultReason::PASID_TABLE_INVALID_ENTRY, 0x5b),
970            (FaultReason::SCALABLE_SL_PTE_ACCESS_ERROR, 0x78),
971            (FaultReason::SCALABLE_SL_PTE_NOT_PRESENT, 0x79),
972            (FaultReason::SCALABLE_SL_PTE_RESERVED_BIT, 0x7a),
973            (FaultReason::SCALABLE_SL_ROOT_ACCESS_ERROR, 0x7b),
974            (FaultReason::SL_AD_UPDATE_NON_SNOOP, 0x7c),
975            (FaultReason::SL_AD_UPDATE_FAILED, 0x7d),
976            (FaultReason::SCALABLE_ADDRESS_BEYOND_HAW, 0x83),
977            (FaultReason::SCALABLE_ADDRESS_BEYOND_AGAW, 0x84),
978            (FaultReason::SCALABLE_WRITE_ACCESS_DENIED, 0x85),
979            (FaultReason::SCALABLE_READ_ACCESS_DENIED, 0x86),
980            (FaultReason::SCALABLE_OUTPUT_ADDR_IN_INTR_RANGE, 0x87),
981        ] {
982            assert_eq!(reason.0, expected);
983        }
984        assert_eq!(FaultReason(0xff).0, 0xff);
985    }
986
987    #[test]
988    fn test_irta_entry_count() {
989        assert_eq!(IrtaReg::new().with_s(0).entry_count(), 2);
990        assert_eq!(IrtaReg::new().with_s(15).entry_count(), 65536);
991    }
992
993    #[test]
994    fn test_iqh_iqt_offsets() {
995        assert_eq!(IqhReg::new().with_qh(16).head_offset(), 256);
996        assert_eq!(IqtReg::new().with_qt(32).tail_offset(), 512);
997    }
998}