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}