Skip to main content

intel_vtd/spec/
invalidation.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Invalidation queue descriptor types for the Intel VT-d IOMMU.
5//!
6//! Based on Intel VT-d Specification Rev 4.1, §§6.5.2 and 11.4.9.
7//! Queue entries are 128 or 256 bits. Type is the concatenation of bits
8//! 11:9 and 3:0, not just the low nibble. Legacy descriptors in a 256-bit
9//! queue have zero upper padding.
10
11use super::registers::CapReg;
12use super::registers::EcapReg;
13use super::registers::TranslationTableMode;
14use bitfield_struct::bitfield;
15use inspect::Inspect;
16use open_enum::open_enum;
17use zerocopy::FromBytes;
18use zerocopy::Immutable;
19use zerocopy::IntoBytes;
20use zerocopy::KnownLayout;
21
22/// A raw 128-bit invalidation queue descriptor (16 bytes).
23#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, KnownLayout)]
24#[repr(C)]
25pub struct InvalidationDescriptor {
26    /// First dword: bits 11:9 and 3:0 = type, rest is type-dependent.
27    pub dw0: u32,
28    /// Second dword: type-dependent fields.
29    pub dw1: u32,
30    /// Third dword: type-dependent fields.
31    pub dw2: u32,
32    /// Fourth dword: type-dependent fields.
33    pub dw3: u32,
34}
35
36impl InvalidationDescriptor {
37    /// Extract `Type[6:0]` from the two noncontiguous fields.
38    pub fn descriptor_type(&self) -> DescriptorType {
39        DescriptorType(((self.dw0 & 0xf) | ((self.dw0 >> 5) & 0x70)) as u8)
40    }
41
42    /// Bits 63:0 of the descriptor.
43    pub fn low(&self) -> u64 {
44        u64::from(self.dw0) | (u64::from(self.dw1) << 32)
45    }
46
47    /// Bits 127:64 of the descriptor.
48    pub fn high(&self) -> u64 {
49        u64::from(self.dw2) | (u64::from(self.dw3) << 32)
50    }
51}
52
53/// A 256-bit descriptor, including the reserved upper half (§6.5.2).
54#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, KnownLayout)]
55#[repr(C)]
56pub struct InvalidationDescriptor256 {
57    /// Common header and type-specific fields.
58    pub lower: InvalidationDescriptor,
59    /// Reserved for all descriptor types implemented here.
60    pub upper: [u64; 2],
61}
62
63/// Width of an entry, distinct from IQH/IQT's fixed 16-byte register units.
64#[derive(Debug, Copy, Clone, PartialEq, Eq)]
65pub enum DescriptorWidth {
66    /// IQA.DW=0.
67    Bits128,
68    /// IQA.DW=1.
69    Bits256,
70}
71
72impl DescriptorWidth {
73    /// Entry size in bytes.
74    pub const fn bytes(self) -> usize {
75        match self {
76            Self::Bits128 => 16,
77            Self::Bits256 => 32,
78        }
79    }
80
81    pub(crate) fn checked(
82        dw: bool,
83        mode: TranslationTableMode,
84        ecap: EcapReg,
85    ) -> Result<Self, InvalidationError> {
86        match mode {
87            TranslationTableMode::LEGACY => {}
88            TranslationTableMode::SCALABLE if ecap.smts() => {}
89            TranslationTableMode::ABORT_DMA if ecap.adms() => {}
90            _ => return Err(InvalidationError::InvalidMode(mode)),
91        }
92        if dw {
93            if !ecap.smts() && !ecap.adms() {
94                return Err(InvalidationError::InvalidWidth);
95            }
96            Ok(Self::Bits256)
97        } else if mode == TranslationTableMode::LEGACY {
98            Ok(Self::Bits128)
99        } else {
100            Err(InvalidationError::InvalidWidth)
101        }
102    }
103}
104
105#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
106pub(crate) enum InvalidationError {
107    #[error("invalid descriptor width for the translation mode/capabilities")]
108    InvalidWidth,
109    #[error("invalid translation table mode {0:?}")]
110    InvalidMode(TranslationTableMode),
111    #[error("invalid or unsupported descriptor type {0:?}")]
112    InvalidType(DescriptorType),
113    #[error("reserved bits {bits:#x} in descriptor word {word}")]
114    ReservedBits { word: usize, bits: u64 },
115    #[error("invalid invalidation granularity {0}")]
116    InvalidGranularity(u8),
117    #[error("page-selective IOTLB invalidation is unsupported")]
118    PageInvalidationUnsupported,
119    #[error("unsupported IOTLB address mask {0}")]
120    AddressMask(u8),
121    #[error("unsupported interrupt index mask {0}")]
122    IndexMask(u8),
123    #[error("invalid queue base or reserved IQA bits")]
124    QueueAddress,
125    #[error("invalid queue head offset {0:#x}")]
126    QueueHead(u64),
127    #[error("invalid queue tail offset {0:#x}")]
128    QueueTail(u64),
129}
130
131impl InvalidationDescriptor256 {
132    /// Validate the uncached emulator's descriptor subset. Capability arguments
133    /// are internal policy inputs, not an externally selectable feature profile.
134    pub(crate) fn validate(
135        &self,
136        width: DescriptorWidth,
137        mode: TranslationTableMode,
138        cap: CapReg,
139        ecap: EcapReg,
140    ) -> Result<(), InvalidationError> {
141        DescriptorWidth::checked(width == DescriptorWidth::Bits256, mode, ecap)?;
142        let dt = self.lower.descriptor_type();
143        // Table 23: only 1..=5 in legacy mode, 1..=9 in scalable/abort mode.
144        if !(1..=9).contains(&dt.0) || (mode == TranslationTableMode::LEGACY && dt.0 > 5) {
145            return Err(InvalidationError::InvalidType(dt));
146        }
147        // ATS and PRI are not implemented or advertised. In particular,
148        // §§6.5.2.5-.6 require an invalid-type error for Device-TLB commands
149        // when ECAP.DT=0; these must not complete as cache no-ops.
150        if matches!(
151            dt,
152            DescriptorType::DEVICE_TLB_INVALIDATE
153                | DescriptorType::PASID_DEVICE_TLB_INVALIDATE
154                | DescriptorType::PAGE_GROUP_RESPONSE
155        ) || (dt == DescriptorType::INTERRUPT_ENTRY_CACHE_INVALIDATE && !ecap.ir())
156        {
157            return Err(InvalidationError::InvalidType(dt));
158        }
159
160        let lo = self.lower.low();
161        let hi = self.lower.high();
162        let (lo_mask, hi_mask) = match dt {
163            DescriptorType::CONTEXT_CACHE_INVALIDATE => (0x0003_ffff_ffff_003f, 0),
164            DescriptorType::IOTLB_INVALIDATE => (0x0000_0000_ffff_00ff, 0xffff_ffff_ffff_f07f),
165            DescriptorType::INTERRUPT_ENTRY_CACHE_INVALIDATE => (0x0000_ffff_f800_001f, 0),
166            DescriptorType::INVALIDATION_WAIT => {
167                // SW/IF independently select notifications; FN controls ordering
168                // (§§6.5.2.8 and 6.5.2.11). We accept Type 5 with all three
169                // clear: no notification or fence, not an invalid Type 0.
170                // §7.10 gives a fence-only example, not a minimum-flag rule.
171                let mask = if ecap.pds() { 0xff } else { 0x7f };
172                (0xffff_ffff_0000_0000 | mask, 0xffff_ffff_ffff_fffc)
173            }
174            DescriptorType::PASID_IOTLB_INVALIDATE => {
175                (0x000f_ffff_ffff_003f, 0xffff_ffff_ffff_f07f)
176            }
177            DescriptorType::PASID_CACHE_INVALIDATE => (0x000f_ffff_ffff_003f, 0),
178            _ => return Err(InvalidationError::InvalidType(dt)),
179        };
180        for (word, (value, allowed)) in [
181            (lo, lo_mask),
182            (hi, hi_mask),
183            (self.upper[0], 0),
184            (self.upper[1], 0),
185        ]
186        .into_iter()
187        .enumerate()
188        {
189            let bits = value & !allowed;
190            if bits != 0 {
191                return Err(InvalidationError::ReservedBits { word, bits });
192            }
193        }
194
195        let granularity = ((lo >> 4) & 3) as u8;
196        match dt {
197            DescriptorType::CONTEXT_CACHE_INVALIDATE | DescriptorType::IOTLB_INVALIDATE
198                if granularity == 0 =>
199            {
200                return Err(InvalidationError::InvalidGranularity(granularity));
201            }
202            DescriptorType::PASID_CACHE_INVALIDATE if granularity == 2 => {
203                return Err(InvalidationError::InvalidGranularity(granularity));
204            }
205            DescriptorType::PASID_IOTLB_INVALIDATE if granularity < 2 => {
206                return Err(InvalidationError::InvalidGranularity(granularity));
207            }
208            _ => {}
209        }
210        if dt == DescriptorType::IOTLB_INVALIDATE && granularity == 3 {
211            if !cap.psi() {
212                return Err(InvalidationError::PageInvalidationUnsupported);
213            }
214            let am = IotlbInvalidateAddress::from(hi).am();
215            if am > cap.mamv() {
216                return Err(InvalidationError::AddressMask(am));
217            }
218        }
219        // CAP.PSI/MAMV do not restrict P_IOTLB (§11.4.2). Nonmatching
220        // first-stage invalidations remain valid in a second-stage-only profile.
221        if dt == DescriptorType::INTERRUPT_ENTRY_CACHE_INVALIDATE {
222            let iec = InterruptCacheInvalidateDw0Dw1::from(lo);
223            if iec.granularity() && iec.im() > ecap.mhmv() {
224                return Err(InvalidationError::IndexMask(iec.im()));
225            }
226        }
227        Ok(())
228    }
229}
230
231open_enum! {
232    /// Invalidation descriptor types (§6.5).
233    #[derive(Inspect)]
234    #[inspect(debug)]
235    pub enum DescriptorType: u8 {
236        /// Context-Cache Invalidation Descriptor (§6.5.2.1).
237        CONTEXT_CACHE_INVALIDATE        = 0x01,
238        /// IOTLB Invalidation Descriptor (§6.5.2.3).
239        IOTLB_INVALIDATE                = 0x02,
240        /// Device-TLB Invalidation Descriptor (§6.5.2.5, not supported).
241        DEVICE_TLB_INVALIDATE           = 0x03,
242        /// Interrupt Entry Cache Invalidation Descriptor (§6.5.2.7).
243        INTERRUPT_ENTRY_CACHE_INVALIDATE = 0x04,
244        /// Invalidation Wait Descriptor (§6.5.2.8).
245        INVALIDATION_WAIT               = 0x05,
246        /// PASID-based IOTLB Invalidation Descriptor (§6.5.2.4).
247        PASID_IOTLB_INVALIDATE           = 0x06,
248        /// PASID-cache Invalidation Descriptor (§6.5.2.2).
249        PASID_CACHE_INVALIDATE           = 0x07,
250        /// PASID-based Device-TLB Invalidation Descriptor (§6.5.2.6, unsupported).
251        PASID_DEVICE_TLB_INVALIDATE      = 0x08,
252        /// Page Group Response Descriptor (§7.6.1, unsupported).
253        PAGE_GROUP_RESPONSE             = 0x09,
254    }
255}
256
257/// Invalidation Wait Descriptor (type 0x05, §6.5.2.8).
258///
259/// SW and IF independently request completion notifications; both may be
260/// disabled. FN controls following-descriptor ordering, not notification
261/// (§6.5.2.11). This implementation accepts SW=IF=FN=0 as a wait without
262/// notification or fence; §7.10 also gives an explicit fence-only example.
263///
264/// ```text
265/// Bits [3:0]   = Type (0x05)
266/// Bit  [4]     = IF (Interrupt Flag — generate invalidation completion event)
267/// Bit  [5]     = SW (Status Write — write status data to status address)
268/// Bit  [6]     = FN (Fence — complete this wait before following descriptors)
269/// Bit  [7]     = PD (Page-request Drain)
270/// Bit  [8]     = reserved
271/// Bits [11:9]  = Type[6:4] (zero)
272/// Bits [31:12] = reserved
273/// Bits [63:32] = Status Data (32-bit value to write)
274/// Bits [65:64] = reserved
275/// Bits [127:66]= Status Address [63:2] (DWORD-aligned)
276/// ```
277#[bitfield(u64)]
278#[derive(Inspect)]
279#[rustfmt::skip]
280pub struct InvalidationWaitDw0Dw1 {
281    /// Descriptor type (must be 0x05).
282    #[bits(4)]
283    pub desc_type: u8,
284    /// Interrupt Flag — 1 = generate invalidation completion event; 0 = no event.
285    pub iflag: bool,
286    /// Status Write — 1 = write status_data to status_address; 0 = ignore both.
287    pub sw: bool,
288    /// Fence — 1 = complete this wait before executing following descriptors.
289    /// If 0, following descriptors may execute before this wait completes.
290    pub fn_flag: bool,
291    /// Page-request Drain.
292    pub pd: bool,
293    #[bits(1)]
294    _reserved1: u64,
295    /// Upper three bits of the descriptor type (zero for this type).
296    #[bits(3)]
297    pub type_hi: u8,
298    #[bits(20)]
299    _reserved2: u64,
300    /// Status Data (32-bit value to write when SW=1).
301    #[bits(32)]
302    pub status_data: u32,
303}
304
305/// Invalidation Wait Descriptor — high 64 bits.
306///
307/// Contains the status address (bits 127:66 = address bits 63:2).
308#[bitfield(u64)]
309#[derive(Inspect)]
310#[rustfmt::skip]
311pub struct InvalidationWaitDw2Dw3 {
312    #[bits(2)]
313    _reserved: u64,
314    /// Status Address bits [63:2]. The full address is `sal << 2`.
315    #[bits(62)]
316    pub sal: u64,
317}
318
319impl InvalidationWaitDw2Dw3 {
320    /// Get the full status address (DWORD-aligned).
321    pub fn status_address(&self) -> u64 {
322        self.sal() << 2
323    }
324}
325
326/// Parse a `InvalidationDescriptor` as INVALIDATION_WAIT fields.
327pub fn parse_invalidation_wait(
328    desc: &InvalidationDescriptor,
329) -> (InvalidationWaitDw0Dw1, InvalidationWaitDw2Dw3) {
330    let lo = desc.low();
331    let hi = desc.high();
332    (
333        InvalidationWaitDw0Dw1::from(lo),
334        InvalidationWaitDw2Dw3::from(hi),
335    )
336}
337
338/// Context-Cache Invalidation Descriptor (type 0x01, §6.5.2.1).
339///
340/// ```text
341/// Bits [3:0]   = Type (0x01)
342/// Bits [5:4]   = Granularity (01=global, 10=domain, 11=device)
343/// Bits [8:6]   = reserved
344/// Bits [11:9]  = Type[6:4] (zero)
345/// Bits [15:12] = reserved
346/// Bits [31:16] = Domain ID (for domain/device granularity)
347/// Bits [47:32] = Source ID (for device granularity)
348/// Bits [49:48] = Function Mask (for device granularity)
349/// Bits [63:50] = reserved
350/// Bits [127:64]= reserved
351/// ```
352#[bitfield(u64)]
353#[derive(Inspect)]
354#[rustfmt::skip]
355pub struct ContextCacheInvalidateDw0Dw1 {
356    /// Descriptor type (must be 0x01).
357    #[bits(4)]
358    pub desc_type: u8,
359    /// Invalidation granularity: 01=global, 10=domain, 11=device.
360    #[bits(2)]
361    pub granularity: u8,
362    #[bits(3)]
363    _reserved1: u64,
364    /// Upper three bits of the descriptor type.
365    #[bits(3)]
366    pub type_hi: u8,
367    #[bits(4)]
368    _reserved_type: u64,
369    /// Domain ID (for domain-selective and device-selective invalidation).
370    #[bits(16)]
371    pub did: u16,
372    /// Source ID (for device-selective invalidation).
373    #[bits(16)]
374    pub sid: u16,
375    /// Function Mask for device-selective invalidation.
376    #[bits(2)]
377    pub fm: u8,
378    #[bits(14)]
379    _reserved2: u64,
380}
381
382/// IOTLB Invalidation Descriptor (type 0x02, §6.5.2.3).
383///
384/// ```text
385/// Bits [3:0]   = Type (0x02)
386/// Bits [5:4]   = Granularity (01=global, 10=domain, 11=page)
387/// Bit  [6]     = DW (Drain Writes)
388/// Bit  [7]     = DR (Drain Reads)
389/// Bit  [8]     = reserved
390/// Bits [11:9]  = Type[6:4] (zero)
391/// Bits [15:12] = reserved
392/// Bits [31:16] = Domain ID (for domain/page granularity)
393/// Bits [63:32] = reserved
394/// ```
395#[bitfield(u64)]
396#[derive(Inspect)]
397#[rustfmt::skip]
398pub struct IotlbInvalidateDw0Dw1 {
399    /// Descriptor type (must be 0x02).
400    #[bits(4)]
401    pub desc_type: u8,
402    /// Invalidation granularity: 01=global, 10=domain, 11=page.
403    #[bits(2)]
404    pub granularity: u8,
405    /// Drain Writes.
406    pub dw: bool,
407    /// Drain Reads.
408    pub dr: bool,
409    #[bits(1)]
410    _reserved1: u64,
411    /// Upper three bits of the descriptor type.
412    #[bits(3)]
413    pub type_hi: u8,
414    #[bits(4)]
415    _reserved_type: u64,
416    /// Domain ID (for domain-selective and page-selective invalidation).
417    #[bits(16)]
418    pub did: u16,
419    #[bits(32)]
420    _reserved2: u64,
421}
422
423/// Address fields shared by IOTLB and P_IOTLB invalidations (§§6.5.2.3-.4).
424#[bitfield(u64)]
425#[derive(Inspect)]
426#[rustfmt::skip]
427pub struct IotlbInvalidateAddress {
428    /// Number of low address bits (starting at bit 12) to mask.
429    #[bits(6)]
430    pub am: u8,
431    /// Invalidation hint, ignored for non-page-selective requests.
432    pub ih: bool,
433    #[bits(5)]
434    _reserved: u64,
435    /// Input address bits 63:12.
436    #[bits(52)]
437    pub address: u64,
438}
439
440/// Common low word of PASID-cache and P_IOTLB descriptors (§§6.5.2.2, 6.5.2.4).
441#[bitfield(u64)]
442#[derive(Inspect)]
443#[rustfmt::skip]
444pub struct PasidInvalidateDw0Dw1 {
445    /// Low four type bits (7 for PASID-cache, 6 for P_IOTLB).
446    #[bits(4)]
447    pub desc_type: u8,
448    /// PASID-cache: 0=domain, 1=PASID, 3=global; P_IOTLB: 2=PASID, 3=page.
449    #[bits(2)]
450    pub granularity: u8,
451    #[bits(3)]
452    _reserved1: u64,
453    /// Upper three type bits.
454    #[bits(3)]
455    pub type_hi: u8,
456    #[bits(4)]
457    _reserved2: u64,
458    /// Domain ID.
459    pub did: u16,
460    /// PASID, including an implied RID_PASID.
461    #[bits(20)]
462    pub pasid: u32,
463    #[bits(12)]
464    _reserved3: u64,
465}
466
467/// Interrupt Entry Cache Invalidation Descriptor (type 0x04, §6.5.2.7).
468///
469/// ```text
470/// Bits [3:0]   = Type (0x04)
471/// Bit  [4]     = Granularity (0=global, 1=index-selective)
472/// Bits [8:5]   = reserved
473/// Bits [11:9]  = Type[6:4] (zero)
474/// Bits [26:12] = reserved
475/// Bits [31:27] = IM (Index Mask, for index-selective)
476/// Bits [47:32] = IIDX (Interrupt Index, for index-selective)
477/// Bits [63:48] = reserved
478/// Bits [127:64]= reserved
479/// ```
480#[bitfield(u64)]
481#[derive(Inspect)]
482#[rustfmt::skip]
483pub struct InterruptCacheInvalidateDw0Dw1 {
484    /// Descriptor type (must be 0x04).
485    #[bits(4)]
486    pub desc_type: u8,
487    /// Invalidation granularity: 0=global, 1=index-selective.
488    pub granularity: bool,
489    #[bits(4)]
490    _reserved1: u64,
491    /// Upper three bits of the descriptor type.
492    #[bits(3)]
493    pub type_hi: u8,
494    #[bits(15)]
495    _reserved2: u64,
496    /// Index Mask (5 bits, for index-selective invalidation).
497    #[bits(5)]
498    pub im: u8,
499    /// Interrupt Index (16 bits, for index-selective invalidation).
500    #[bits(16)]
501    pub iidx: u16,
502    #[bits(16)]
503    _reserved3: u64,
504}
505
506/// Parse an `InvalidationDescriptor` as INTERRUPT_ENTRY_CACHE_INVALIDATE fields.
507pub fn parse_interrupt_cache_invalidate(
508    desc: &InvalidationDescriptor,
509) -> InterruptCacheInvalidateDw0Dw1 {
510    InterruptCacheInvalidateDw0Dw1::from(desc.low())
511}
512
513#[cfg(test)]
514mod tests {
515    use super::*;
516    use test_with_tracing::test;
517
518    fn raw(lo: u64, hi: u64) -> InvalidationDescriptor256 {
519        InvalidationDescriptor256 {
520            lower: InvalidationDescriptor {
521                dw0: lo as u32,
522                dw1: (lo >> 32) as u32,
523                dw2: hi as u32,
524                dw3: (hi >> 32) as u32,
525            },
526            upper: [0; 2],
527        }
528    }
529
530    fn cap() -> CapReg {
531        CapReg::new().with_psi(true).with_mamv(18)
532    }
533
534    fn ecap() -> EcapReg {
535        EcapReg::new().with_smts(true).with_ir(true).with_mhmv(15)
536    }
537
538    fn validate(desc: &InvalidationDescriptor256) -> Result<(), InvalidationError> {
539        desc.validate(
540            DescriptorWidth::Bits256,
541            TranslationTableMode::SCALABLE,
542            cap(),
543            ecap(),
544        )
545    }
546
547    #[test]
548    fn test_full_type_encoding() {
549        assert_eq!(size_of::<InvalidationDescriptor>(), 16);
550        assert_eq!(size_of::<InvalidationDescriptor256>(), 32);
551        for dt in 0..128 {
552            let lo = (dt & 15) | ((dt >> 4) << 9);
553            let desc = raw(lo, 0);
554            assert_eq!(desc.lower.descriptor_type().0, dt as u8);
555            for bit in [4, 5, 6, 7, 8, 12, 13, 14, 15, 31, 32, 63] {
556                assert_eq!(raw(lo | (1 << bit), 0).lower.descriptor_type().0, dt as u8);
557            }
558        }
559        for (dt, value) in [
560            (DescriptorType::CONTEXT_CACHE_INVALIDATE, 1),
561            (DescriptorType::IOTLB_INVALIDATE, 2),
562            (DescriptorType::DEVICE_TLB_INVALIDATE, 3),
563            (DescriptorType::INTERRUPT_ENTRY_CACHE_INVALIDATE, 4),
564            (DescriptorType::INVALIDATION_WAIT, 5),
565            (DescriptorType::PASID_IOTLB_INVALIDATE, 6),
566            (DescriptorType::PASID_CACHE_INVALIDATE, 7),
567            (DescriptorType::PASID_DEVICE_TLB_INVALIDATE, 8),
568            (DescriptorType::PAGE_GROUP_RESPONSE, 9),
569        ] {
570            assert_eq!(dt.0, value);
571        }
572    }
573
574    #[test]
575    fn test_literal_descriptor_bytes() {
576        let bytes = [
577            0x26, 0x00, 0x34, 0x12, 0xde, 0xbc, 0x0a, 0x00, 0x52, 0x10, 0x32, 0x54, 0x76, 0x98,
578            0xba, 0xdc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
579        ];
580        let desc = InvalidationDescriptor256::read_from_bytes(&bytes).unwrap();
581        assert_eq!(desc.lower.low(), 0x000a_bcde_1234_0026);
582        assert_eq!(desc.lower.high(), 0xdcba_9876_5432_1052);
583        assert_eq!(desc.upper, [0, 0]);
584        assert_eq!(desc.as_bytes(), bytes);
585        let short = InvalidationDescriptor::read_from_bytes(&bytes[..16]).unwrap();
586        assert_eq!(short.as_bytes(), &bytes[..16]);
587        assert_eq!(
588            short.descriptor_type(),
589            DescriptorType::PASID_IOTLB_INVALIDATE
590        );
591        assert!(validate(&desc).is_ok());
592    }
593
594    #[test]
595    fn test_context_raw_fields() {
596        let value = 0x0003_abcd_9876_0031;
597        let cc = ContextCacheInvalidateDw0Dw1::from(value);
598        assert_eq!((cc.desc_type(), cc.type_hi(), cc.granularity()), (1, 0, 3));
599        assert_eq!((cc.did(), cc.sid(), cc.fm()), (0x9876, 0xabcd, 3));
600        assert_eq!(
601            ContextCacheInvalidateDw0Dw1::new()
602                .with_desc_type(1)
603                .with_granularity(3)
604                .with_did(0x9876)
605                .with_sid(0xabcd)
606                .with_fm(3)
607                .into_bits(),
608            value
609        );
610    }
611
612    #[test]
613    fn test_iotlb_raw_fields() {
614        for g in 0..4 {
615            for drains in 0..4 {
616                let value = 0xabcd_0002 | (g << 4) | (drains << 6);
617                let iotlb = IotlbInvalidateDw0Dw1::from(value);
618                assert_eq!(iotlb.granularity(), g as u8);
619                assert_eq!(iotlb.dw(), drains & 1 != 0);
620                assert_eq!(iotlb.dr(), drains & 2 != 0);
621                assert_eq!(iotlb.did(), 0xabcd);
622                assert_eq!(
623                    IotlbInvalidateDw0Dw1::new()
624                        .with_desc_type(2)
625                        .with_granularity(g as u8)
626                        .with_dw(drains & 1 != 0)
627                        .with_dr(drains & 2 != 0)
628                        .with_did(0xabcd)
629                        .into_bits(),
630                    value
631                );
632            }
633        }
634        for am in 0..64 {
635            for ih in [false, true] {
636                let value = 0x1234_5678_9abc_d000 | am | ((ih as u64) << 6);
637                let address = IotlbInvalidateAddress::from(value);
638                assert_eq!(
639                    (address.am(), address.ih(), address.address()),
640                    (am as u8, ih, 0x0001_2345_6789_abcd)
641                );
642                assert_eq!(
643                    IotlbInvalidateAddress::new()
644                        .with_am(am as u8)
645                        .with_ih(ih)
646                        .with_address(0x0001_2345_6789_abcd)
647                        .into_bits(),
648                    value
649                );
650            }
651        }
652    }
653
654    #[test]
655    fn test_pasid_raw_fields() {
656        for dt in [6, 7] {
657            for g in 0..4 {
658                let value = 0x000a_bcde_1234_0000 | (g << 4) | dt;
659                let pasid = PasidInvalidateDw0Dw1::from(value);
660                assert_eq!(
661                    (pasid.desc_type(), pasid.type_hi(), pasid.granularity()),
662                    (dt as u8, 0, g as u8)
663                );
664                assert_eq!((pasid.pasid(), pasid.did()), (0xabcde, 0x1234));
665                assert_eq!(
666                    PasidInvalidateDw0Dw1::new()
667                        .with_desc_type(dt as u8)
668                        .with_granularity(g as u8)
669                        .with_pasid(0xabcde)
670                        .with_did(0x1234)
671                        .into_bits(),
672                    value
673                );
674            }
675        }
676    }
677
678    #[test]
679    fn test_interrupt_cache_raw_fields() {
680        for im in 0..32 {
681            for g in [false, true] {
682                let value = 0x0000_abcd_0000_0004 | (im << 27) | ((g as u64) << 4);
683                let iec = parse_interrupt_cache_invalidate(&raw(value, 0).lower);
684                assert_eq!(
685                    (iec.im(), iec.iidx(), iec.granularity()),
686                    (im as u8, 0xabcd, g)
687                );
688                assert_eq!(
689                    InterruptCacheInvalidateDw0Dw1::new()
690                        .with_desc_type(4)
691                        .with_granularity(g)
692                        .with_im(im as u8)
693                        .with_iidx(0xabcd)
694                        .into_bits(),
695                    value
696                );
697            }
698        }
699    }
700
701    #[test]
702    fn test_wait_raw_fields() {
703        for flags in 0..16 {
704            let value = 0xabcd_1234_0000_0005 | (flags << 4);
705            let (lo, hi) = parse_invalidation_wait(&raw(value, 0x1234_5678_9abc_defc).lower);
706            assert_eq!(lo.iflag(), flags & 1 != 0);
707            assert_eq!(lo.sw(), flags & 2 != 0);
708            assert_eq!(lo.fn_flag(), flags & 4 != 0);
709            assert_eq!(lo.pd(), flags & 8 != 0);
710            assert_eq!(lo.status_data(), 0xabcd_1234);
711            assert_eq!(hi.status_address(), 0x1234_5678_9abc_defc);
712            assert_eq!(
713                InvalidationWaitDw0Dw1::new()
714                    .with_desc_type(5)
715                    .with_iflag(flags & 1 != 0)
716                    .with_sw(flags & 2 != 0)
717                    .with_fn_flag(flags & 4 != 0)
718                    .with_pd(flags & 8 != 0)
719                    .with_status_data(0xabcd_1234)
720                    .into_bits(),
721                value
722            );
723            assert_eq!(
724                InvalidationWaitDw2Dw3::new()
725                    .with_sal(0x1234_5678_9abc_defc >> 2)
726                    .into_bits(),
727                0x1234_5678_9abc_defc
728            );
729        }
730    }
731
732    #[test]
733    fn test_mode_width_type_matrix() {
734        for smts in [false, true] {
735            for adms in [false, true] {
736                let ecap = ecap().with_smts(smts).with_adms(adms);
737                for mode in 0..4 {
738                    for dw in [false, true] {
739                        let mode_allowed = match mode {
740                            0 => true,
741                            1 => smts,
742                            3 => adms,
743                            _ => false,
744                        };
745                        let width_allowed = if dw { smts || adms } else { mode == 0 };
746                        let width = if dw {
747                            DescriptorWidth::Bits256
748                        } else {
749                            DescriptorWidth::Bits128
750                        };
751                        assert_eq!(
752                            DescriptorWidth::checked(dw, TranslationTableMode(mode), ecap).is_ok(),
753                            mode_allowed && width_allowed
754                        );
755                        for dt in 0..128 {
756                            let g = if dt == 6 { 2 } else { 1 };
757                            let lo = (dt & 15) | ((dt >> 4) << 9) | (g << 4);
758                            let expected = mode_allowed
759                                && width_allowed
760                                && (matches!(dt, 1 | 2 | 4 | 5)
761                                    || (mode != 0 && matches!(dt, 6 | 7)));
762                            assert_eq!(
763                                raw(lo, 0)
764                                    .validate(width, TranslationTableMode(mode), cap(), ecap)
765                                    .is_ok(),
766                                expected,
767                                "smts={smts} adms={adms} mode={mode} dw={dw} type={dt}"
768                            );
769                        }
770                    }
771                }
772            }
773        }
774    }
775
776    #[test]
777    fn test_granularity_and_ignored_fields() {
778        for (dt, valid) in [
779            (1, [false, true, true, true]),
780            (2, [false, true, true, true]),
781            (6, [false, false, true, true]),
782            (7, [true, true, false, true]),
783        ] {
784            for (g, expected) in valid.into_iter().enumerate() {
785                assert_eq!(validate(&raw(dt | ((g as u64) << 4), 0)).is_ok(), expected);
786            }
787        }
788        // Ignore DID/SID/FM for global CC; DID/PASID in global PC; the full
789        // 20-bit PASID is legal even though explicit requests-with-PASID are off.
790        for lo in [
791            0x0003_ffff_ffff_0011,
792            0x000f_ffff_ffff_0037,
793            0x000f_ffff_ffff_0007,
794        ] {
795            assert!(validate(&raw(lo, 0)).is_ok());
796        }
797        // Address, AM and IH are ignored for global/domain IOTLB and PASID-
798        // selective P_IOTLB. Address bits above MGAW are ignored, not reserved.
799        for lo in [0xffff_0012, 0xffff_0022, 0x000f_ffff_ffff_0026] {
800            assert!(validate(&raw(lo, 0xffff_ffff_ffff_f07f)).is_ok());
801        }
802        // SW=0 ignores status data/address, but not the reserved alignment bits.
803        assert!(validate(&raw(0xffff_ffff_0000_0005, 0xffff_ffff_ffff_fffc)).is_ok());
804        assert!(validate(&raw(5, 1)).is_err());
805    }
806
807    #[test]
808    fn test_capability_dependent_fields() {
809        for am in 0..64 {
810            let desc = raw(0x32, 0xffff_ffff_ffff_f000 | am);
811            assert_eq!(validate(&desc).is_ok(), am <= 18);
812            assert_eq!(
813                desc.validate(
814                    DescriptorWidth::Bits256,
815                    TranslationTableMode::SCALABLE,
816                    cap().with_psi(false),
817                    ecap()
818                ),
819                Err(InvalidationError::PageInvalidationUnsupported)
820            );
821            // Neither PSI nor MAMV restrict P_IOTLB; no shifts by AM are needed.
822            assert!(
823                raw(0x36, am)
824                    .validate(
825                        DescriptorWidth::Bits256,
826                        TranslationTableMode::SCALABLE,
827                        cap().with_psi(false).with_mamv(0),
828                        ecap()
829                    )
830                    .is_ok()
831            );
832        }
833        for im in 0..32 {
834            assert_eq!(validate(&raw(0x14 | (im << 27), 0)).is_ok(), im <= 15);
835            assert!(validate(&raw(4 | (im << 27), 0)).is_ok());
836        }
837        assert!(validate(&raw(0x85, 0)).is_err()); // PD without PDS.
838        assert!(
839            raw(0x85, 0)
840                .validate(
841                    DescriptorWidth::Bits256,
842                    TranslationTableMode::SCALABLE,
843                    cap(),
844                    ecap().with_pds(true)
845                )
846                .is_ok()
847        );
848        assert!(
849            raw(4, 0)
850                .validate(
851                    DescriptorWidth::Bits256,
852                    TranslationTableMode::SCALABLE,
853                    cap(),
854                    ecap().with_ir(false)
855                )
856                .is_err()
857        );
858    }
859
860    #[test]
861    fn test_every_reserved_bit_and_padding() {
862        // Independent bit positions from Figures 6-1 through 6-8.
863        for (lo, reserved) in [
864            (0x11, vec![6..9, 12..16, 50..128]),
865            (0x12, vec![8..9, 12..16, 32..64, 71..76]),
866            (0x04, vec![5..9, 12..27, 48..128]),
867            (0x05, vec![7..9, 12..32, 64..66]),
868            (0x26, vec![6..9, 12..16, 52..64, 71..76]),
869            (0x07, vec![6..9, 12..16, 52..128]),
870        ] {
871            for bit in reserved.into_iter().flatten().chain(128..256) {
872                let mut desc = raw(lo, 0);
873                match bit {
874                    0..64 => desc.lower = raw(lo | (1 << bit), 0).lower,
875                    64..128 => desc.lower = raw(lo, 1 << (bit - 64)).lower,
876                    _ => desc.upper[(bit - 128) / 64] = 1 << (bit % 64),
877                }
878                assert!(
879                    matches!(validate(&desc), Err(InvalidationError::ReservedBits { .. })),
880                    "type={} bit={bit}",
881                    lo & 15
882                );
883            }
884        }
885    }
886
887    #[test]
888    fn test_parse_invalidation_wait() {
889        let lo_val = InvalidationWaitDw0Dw1::new()
890            .with_desc_type(0x05)
891            .with_sw(true)
892            .with_status_data(0x42);
893        let hi_val = InvalidationWaitDw2Dw3::new().with_sal(0x1000);
894
895        let lo_raw = u64::from(lo_val);
896        let hi_raw = u64::from(hi_val);
897
898        let desc = InvalidationDescriptor {
899            dw0: lo_raw as u32,
900            dw1: (lo_raw >> 32) as u32,
901            dw2: hi_raw as u32,
902            dw3: (hi_raw >> 32) as u32,
903        };
904
905        let (parsed_lo, parsed_hi) = parse_invalidation_wait(&desc);
906        assert!(parsed_lo.sw());
907        assert_eq!(parsed_lo.status_data(), 0x42);
908        assert_eq!(parsed_hi.status_address(), 0x1000 << 2);
909    }
910
911    #[test]
912    fn test_parse_interrupt_cache_invalidate() {
913        let lo_val = InterruptCacheInvalidateDw0Dw1::new()
914            .with_desc_type(0x04)
915            .with_granularity(true)
916            .with_im(0x1f)
917            .with_iidx(0x1234);
918
919        let lo_raw = u64::from(lo_val);
920        let desc = InvalidationDescriptor {
921            dw0: lo_raw as u32,
922            dw1: (lo_raw >> 32) as u32,
923            dw2: 0,
924            dw3: 0,
925        };
926
927        let parsed = parse_interrupt_cache_invalidate(&desc);
928        assert!(parsed.granularity());
929        assert_eq!(parsed.im(), 0x1f);
930        assert_eq!(parsed.iidx(), 0x1234);
931    }
932}