Skip to main content

intel_vtd/spec/
root_context.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Root Table and Context Table entry types for the Intel VT-d IOMMU.
5//!
6//! Based on Intel VT-d Specification Rev 4.1, §9.1 (Root Entry) and §9.3
7//! (Context Entry). Legacy mode only.
8//!
9//! The VT-d uses a two-level device lookup:
10//! - Root Table: 4KB, 256 entries indexed by PCI bus number.
11//! - Context Table: 4KB per bus, 256 entries indexed by devfn (device:function).
12
13use bitfield_struct::bitfield;
14use inspect::Inspect;
15use open_enum::open_enum;
16use zerocopy::FromBytes;
17use zerocopy::Immutable;
18use zerocopy::IntoBytes;
19use zerocopy::KnownLayout;
20
21/// Root Table Entry (128-bit, §9.1).
22///
23/// Each entry maps a PCI bus number to a context table. The root table has 256
24/// entries (one per bus), totaling 4KB.
25///
26/// Layout (legacy mode):
27/// - Bits 0: P (present)
28/// - Bits 11:1: reserved
29/// - Bits 63:12: CTP (context table pointer, 4KB-aligned)
30/// - Bits 127:64: reserved (upper 64 bits unused in legacy mode)
31#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, KnownLayout)]
32#[repr(C)]
33pub struct RootEntry {
34    /// Low 64 bits: present bit and context table pointer.
35    pub lo: RootEntryLo,
36    /// High 64 bits: reserved in legacy mode.
37    pub hi: u64,
38}
39
40/// Number of entries in the root table (one per PCI bus).
41pub const ROOT_TABLE_ENTRIES: usize = 256;
42
43/// Root Entry — low 64 bits.
44#[bitfield(u64)]
45#[derive(IntoBytes, Immutable, KnownLayout, FromBytes, Inspect)]
46#[rustfmt::skip]
47pub struct RootEntryLo {
48    /// Present — 1 = this entry is valid.
49    pub p: bool,
50    #[bits(11)]
51    _reserved: u64,
52    /// Context Table Pointer, bits [63:12]. 4KB-aligned.
53    #[bits(52)]
54    pub ctp: u64,
55}
56
57impl RootEntryLo {
58    /// Get the full context table physical address (bits 63:12 shifted).
59    pub fn context_table_address(&self) -> u64 {
60        self.ctp() << 12
61    }
62}
63
64/// Context Table Entry (128-bit, §9.3).
65///
66/// Each entry maps a PCI device/function to a second-level page table and
67/// domain. The context table has 256 entries (32 devices × 8 functions = 256
68/// devfn combinations per bus), totaling 4KB.
69///
70/// Layout:
71/// - Low 64 bits:
72///   - Bit 0: P (present)
73///   - Bit 1: FPD (fault processing disable)
74///   - Bits 3:2: TT (translation type)
75///   - Bits 11:4: reserved
76///   - Bits 63:12: SSPTPTR (second-stage page table pointer, 4KB-aligned)
77/// - High 64 bits:
78///   - Bits 66:64 (2:0): AW (address width)
79///   - Bits 71:67 (7:3): reserved
80///   - Bits 87:72 (23:8): DID (domain ID)
81///   - Bits 127:88 (63:24): reserved
82#[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, KnownLayout)]
83#[repr(C)]
84pub struct ContextEntry {
85    /// Low 64 bits: present, FPD, translation type, page table pointer.
86    pub lo: ContextEntryLo,
87    /// High 64 bits: address width, domain ID.
88    pub hi: ContextEntryHi,
89}
90
91/// Number of entries per context table.
92pub const CONTEXT_TABLE_ENTRIES: usize = 256;
93
94/// Context Entry — low 64 bits.
95#[bitfield(u64)]
96#[derive(IntoBytes, Immutable, KnownLayout, FromBytes, Inspect)]
97#[rustfmt::skip]
98pub struct ContextEntryLo {
99    /// Present — 1 = this entry is valid.
100    pub p: bool,
101    /// Fault Processing Disable — 1 = suppress fault recording for this device.
102    pub fpd: bool,
103    /// Translation Type.
104    /// 00 = untranslated requests only (second-level translation).
105    /// 01 = all requests (both translated and untranslated, second-level).
106    /// 10 = pass-through (IOVA = GPA, identity mapping).
107    /// 11 = reserved.
108    #[bits(2)]
109    pub tt: u8,
110    #[bits(8)]
111    _reserved: u64,
112    /// Second-Stage Page Translation Pointer, bits [63:12]. 4KB-aligned.
113    /// Points to the root of the second-level page table hierarchy.
114    #[bits(52)]
115    pub ssptptr: u64,
116}
117
118impl ContextEntryLo {
119    /// Get the full second-stage page table pointer address (bits 63:12 shifted).
120    pub fn page_table_address(&self) -> u64 {
121        self.ssptptr() << 12
122    }
123}
124
125/// Context Entry — high 64 bits.
126#[bitfield(u64)]
127#[derive(IntoBytes, Immutable, KnownLayout, FromBytes, Inspect)]
128#[rustfmt::skip]
129pub struct ContextEntryHi {
130    /// Address Width — number of levels in the second-level page table.
131    /// 001 = 39-bit / 3-level, 010 = 48-bit / 4-level, 011 = 57-bit / 5-level.
132    #[bits(3)]
133    pub aw: u8,
134    #[bits(5)]
135    _reserved1: u64,
136    /// Domain ID (16-bit). Identifies the domain for TLB invalidation.
137    #[bits(16)]
138    pub did: u16,
139    #[bits(40)]
140    _reserved2: u64,
141}
142
143open_enum! {
144    /// Translation type for context entries (§9.3).
145    #[derive(Inspect)]
146    #[inspect(debug)]
147    pub enum TranslationType: u8 {
148        /// Untranslated requests only — second-level translation applied.
149        UNTRANSLATED_ONLY       = 0b00,
150        /// All requests — second-level translation applied to both translated
151        /// and untranslated requests.
152        ALL                     = 0b01,
153        /// Pass-through — IOVA = GPA (identity mapping).
154        PASS_THROUGH            = 0b10,
155    }
156}
157
158open_enum! {
159    /// Address width for second-level page tables (§9.3).
160    ///
161    /// Determines the number of page table levels walked.
162    #[derive(Inspect)]
163    #[inspect(debug)]
164    pub enum AddressWidth: u8 {
165        /// 39-bit (3-level page table). AGAW = 39 bits.
166        AW_39BIT    = 0b001,
167        /// 48-bit (4-level page table). AGAW = 48 bits.
168        AW_48BIT    = 0b010,
169        /// 57-bit (5-level page table). AGAW = 57 bits.
170        AW_57BIT    = 0b011,
171    }
172}
173
174impl AddressWidth {
175    /// Get the number of page table levels for this address width.
176    pub fn levels(&self) -> Option<u8> {
177        match *self {
178            AddressWidth::AW_39BIT => Some(3),
179            AddressWidth::AW_48BIT => Some(4),
180            AddressWidth::AW_57BIT => Some(5),
181            _ => None,
182        }
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn test_address_width_levels() {
192        assert_eq!(AddressWidth::AW_39BIT.levels(), Some(3));
193        assert_eq!(AddressWidth::AW_48BIT.levels(), Some(4));
194        assert_eq!(AddressWidth::AW_57BIT.levels(), Some(5));
195        assert_eq!(AddressWidth(0).levels(), None);
196        assert_eq!(AddressWidth(0b100).levels(), None);
197    }
198}