Skip to main content

acpi_spec/
iort.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! IORT (IO Remapping Table) types for aarch64 PCIe topology.
5
6use super::Table;
7use crate::packed_nums::*;
8use core::mem::size_of;
9use static_assertions::const_assert_eq;
10use zerocopy::FromBytes;
11use zerocopy::Immutable;
12use zerocopy::IntoBytes;
13use zerocopy::KnownLayout;
14use zerocopy::Unaligned;
15
16pub const IORT_REVISION: u8 = 5;
17pub const IORT_NODE_OFFSET: u32 = size_of::<crate::Header>() as u32 + size_of::<Iort>() as u32;
18
19pub const IORT_NODE_TYPE_ITS_GROUP: u8 = 0x00;
20pub const IORT_NODE_TYPE_PCI_ROOT_COMPLEX: u8 = 0x02;
21pub const IORT_NODE_TYPE_SMMUV3: u8 = 0x04;
22pub const IORT_NODE_TYPE_RMR: u8 = 0x06;
23
24pub const IORT_PCI_ROOT_COMPLEX_REVISION: u8 = 3;
25pub const IORT_ITS_GROUP_REVISION: u8 = 1;
26pub const IORT_SMMUV3_REVISION: u8 = 5;
27
28pub const IORT_NODE_COHERENT: u32 = 0x00000001;
29pub const IORT_MEMORY_ACCESS_COHERENCY: u8 = 1 << 0;
30pub const IORT_MEMORY_ACCESS_ATTRIBUTES: u8 = 1 << 1;
31pub const IORT_ID_SINGLE_MAPPING: u32 = 1 << 0;
32
33#[repr(C)]
34#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
35pub struct Iort {
36    pub node_count: u32_ne,
37    pub node_offset: u32_ne,
38    pub reserved: u32_ne,
39}
40
41impl Iort {
42    pub fn new(node_count: u32) -> Self {
43        Self {
44            node_count: node_count.into(),
45            node_offset: IORT_NODE_OFFSET.into(),
46            reserved: 0.into(),
47        }
48    }
49}
50
51impl Table for Iort {
52    const SIGNATURE: [u8; 4] = *b"IORT";
53}
54
55const_assert_eq!(size_of::<Iort>(), 12);
56const_assert_eq!(IORT_NODE_OFFSET as usize, 48);
57
58#[repr(C, packed)]
59#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
60pub struct IortNodeHeader {
61    pub node_type: u8,
62    pub length: u16_ne,
63    pub revision: u8,
64    pub identifier: u32_ne,
65    pub mapping_count: u32_ne,
66    pub mapping_offset: u32_ne,
67}
68
69impl IortNodeHeader {
70    pub fn new<T>(node_type: u8, revision: u8, identifier: u32, mapping_count: u32) -> Self {
71        Self {
72            node_type,
73            length: (size_of::<T>() as u16).into(),
74            revision,
75            identifier: identifier.into(),
76            mapping_count: mapping_count.into(),
77            mapping_offset: if mapping_count == 0 {
78                0.into()
79            } else {
80                (size_of::<T>() as u32).into()
81            },
82        }
83    }
84}
85
86const_assert_eq!(size_of::<IortNodeHeader>(), 16);
87
88#[repr(C)]
89#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
90pub struct IortMemoryAccessProperties {
91    pub cache_coherency: u32_ne,
92    pub hints: u8,
93    pub reserved: u16_ne,
94    pub memory_flags: u8,
95}
96
97impl IortMemoryAccessProperties {
98    pub fn coherent() -> Self {
99        Self {
100            cache_coherency: IORT_NODE_COHERENT.into(),
101            hints: 0,
102            reserved: 0.into(),
103            memory_flags: IORT_MEMORY_ACCESS_COHERENCY | IORT_MEMORY_ACCESS_ATTRIBUTES,
104        }
105    }
106}
107
108const_assert_eq!(size_of::<IortMemoryAccessProperties>(), 8);
109
110#[repr(C, packed)]
111#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
112pub struct IortPciRootComplex {
113    pub header: IortNodeHeader,
114    pub memory_properties: IortMemoryAccessProperties,
115    pub ats_attribute: u32_ne,
116    pub pci_segment_number: u32_ne,
117    pub memory_address_limit: u8,
118    pub reserved: [u8; 3],
119}
120
121impl IortPciRootComplex {
122    /// Create a PCI Root Complex node. The `length` field in the header
123    /// includes space for `mapping_count` trailing `IortIdMapping` entries,
124    /// which must be appended separately after serializing this struct.
125    pub fn new(identifier: u32, pci_segment_number: u16, mapping_count: u32) -> Self {
126        let mut header = IortNodeHeader::new::<Self>(
127            IORT_NODE_TYPE_PCI_ROOT_COMPLEX,
128            IORT_PCI_ROOT_COMPLEX_REVISION,
129            identifier,
130            mapping_count,
131        );
132        // The node length must include the variable-length ID mapping array.
133        let total =
134            size_of::<Self>() as u16 + (mapping_count as u16) * size_of::<IortIdMapping>() as u16;
135        header.length = total.into();
136        Self {
137            header,
138            memory_properties: IortMemoryAccessProperties::coherent(),
139            ats_attribute: 0.into(),
140            pci_segment_number: u32::from(pci_segment_number).into(),
141            memory_address_limit: 64,
142            reserved: [0; 3],
143        }
144    }
145}
146
147const_assert_eq!(size_of::<IortPciRootComplex>(), 36);
148
149#[repr(C, packed)]
150#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
151pub struct IortIdMapping {
152    pub input_base: u32_ne,
153    pub id_count: u32_ne,
154    pub output_base: u32_ne,
155    pub output_reference: u32_ne,
156    pub flags: u32_ne,
157}
158
159impl IortIdMapping {
160    pub fn new(
161        input_base: u32,
162        id_count: u32,
163        output_base: u32,
164        output_reference: u32,
165        flags: u32,
166    ) -> Self {
167        Self {
168            input_base: input_base.into(),
169            id_count: id_count.into(),
170            output_base: output_base.into(),
171            output_reference: output_reference.into(),
172            flags: flags.into(),
173        }
174    }
175}
176
177const_assert_eq!(size_of::<IortIdMapping>(), 20);
178
179/// ITS Group node. Followed by `its_count` u32 ITS identifiers.
180#[repr(C, packed)]
181#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
182pub struct IortItsGroup {
183    pub header: IortNodeHeader,
184    pub its_count: u32_ne,
185}
186
187impl IortItsGroup {
188    /// Create an ITS Group node. The `length` field in the header includes
189    /// space for `its_count` trailing u32 ITS identifiers, which must be
190    /// appended separately after serializing this struct.
191    pub fn new(identifier: u32, its_count: u32) -> Self {
192        let mut header = IortNodeHeader::new::<Self>(
193            IORT_NODE_TYPE_ITS_GROUP,
194            IORT_ITS_GROUP_REVISION,
195            identifier,
196            0,
197        );
198        // The node length must include the variable-length ITS ID array.
199        let total = size_of::<Self>() as u16 + (its_count as u16) * 4;
200        header.length = total.into();
201        Self {
202            header,
203            its_count: its_count.into(),
204        }
205    }
206}
207
208const_assert_eq!(size_of::<IortItsGroup>(), 20);
209
210/// SMMUv3 node flags.
211pub const IORT_SMMUV3_FLAG_COHACC: u32 = 1 << 0;
212/// `device_id_mapping_index` is valid (IORT rev E.e / node rev 5+).
213pub const IORT_SMMUV3_FLAG_DEVICEID_VALID: u32 = 1 << 4;
214
215/// SMMUv3 model: generic SMMU-v3.
216pub const IORT_SMMUV3_MODEL_GENERIC: u32 = 0;
217
218/// SMMUv3 node per IORT spec DEN0049E §E.4.
219#[repr(C, packed)]
220#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
221pub struct IortSmmuV3 {
222    pub header: IortNodeHeader,
223    pub base_address: u64_ne,
224    pub flags: u32_ne,
225    pub reserved: u32_ne,
226    pub vatos_address: u64_ne,
227    pub model: u32_ne,
228    pub event_gsiv: u32_ne,
229    pub pri_gsiv: u32_ne,
230    pub gerr_gsiv: u32_ne,
231    pub sync_gsiv: u32_ne,
232    pub proximity_domain: u32_ne,
233    pub device_id_mapping_index: u32_ne,
234}
235
236impl IortSmmuV3 {
237    /// Create an SMMUv3 node with COHACC set, wired SPI interrupts (GSIVs),
238    /// and the specified number of ID mappings. The `length` field in the
239    /// header includes space for `mapping_count` trailing `IortIdMapping`
240    /// entries, which must be appended separately.
241    pub fn new(
242        identifier: u32,
243        base_address: u64,
244        mapping_count: u32,
245        event_gsiv: u32,
246        gerr_gsiv: u32,
247    ) -> Self {
248        Self::new_with_device_id_mapping(
249            identifier,
250            base_address,
251            mapping_count,
252            event_gsiv,
253            gerr_gsiv,
254            0,
255        )
256    }
257
258    /// Create an SMMUv3 node with an explicit `device_id_mapping_index`.
259    ///
260    /// `device_id_mapping_index` selects which ID mapping entry Linux uses
261    /// for the SMMU's own MSI domain lookup. That mapping must have the
262    /// `IORT_ID_SINGLE_MAPPING` flag set. When set, the `DEVICEID_VALID`
263    /// flag is automatically added to the node flags.
264    pub fn new_with_device_id_mapping(
265        identifier: u32,
266        base_address: u64,
267        mapping_count: u32,
268        event_gsiv: u32,
269        gerr_gsiv: u32,
270        device_id_mapping_index: u32,
271    ) -> Self {
272        let mut header = IortNodeHeader::new::<Self>(
273            IORT_NODE_TYPE_SMMUV3,
274            IORT_SMMUV3_REVISION,
275            identifier,
276            mapping_count,
277        );
278        let total =
279            size_of::<Self>() as u16 + (mapping_count as u16) * size_of::<IortIdMapping>() as u16;
280        header.length = total.into();
281        Self {
282            header,
283            base_address: base_address.into(),
284            flags: (IORT_SMMUV3_FLAG_COHACC
285                | if mapping_count > 0 {
286                    IORT_SMMUV3_FLAG_DEVICEID_VALID
287                } else {
288                    0
289                })
290            .into(),
291            reserved: 0.into(),
292            vatos_address: 0.into(),
293            model: IORT_SMMUV3_MODEL_GENERIC.into(),
294            event_gsiv: event_gsiv.into(),
295            pri_gsiv: 0.into(),
296            gerr_gsiv: gerr_gsiv.into(),
297            sync_gsiv: 0.into(),
298            proximity_domain: 0.into(),
299            device_id_mapping_index: device_id_mapping_index.into(),
300        }
301    }
302}
303
304const_assert_eq!(size_of::<IortSmmuV3>(), 68);
305
306// --- RMR (Reserved Memory Range) node (IORT spec DEN0049E §E.5) ---
307
308pub const IORT_RMR_REVISION: u8 = 3;
309
310/// RMR flags: access privilege hint. When set, the OS may map the region
311/// with elevated privileges (e.g., supervisor mode).
312pub const IORT_RMR_ACCESS_PRIVILEGE: u32 = 1 << 0;
313/// RMR flags: remap permitted. When clear, the region MUST be identity-
314/// mapped (IOVA == physical address).
315pub const IORT_RMR_REMAP_PERMITTED: u32 = 1 << 1;
316
317/// RMR node header. Followed by `rmr_count` [`IortRmrDescriptor`] entries,
318/// then `mapping_count` [`IortIdMapping`] entries.
319#[repr(C, packed)]
320#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
321pub struct IortRmr {
322    pub header: IortNodeHeader,
323    pub flags: u32_ne,
324    pub rmr_count: u32_ne,
325    pub rmr_offset: u32_ne,
326}
327
328impl IortRmr {
329    /// Create an RMR node with the given number of RMR descriptors and
330    /// ID mappings. The `length` field includes space for the trailing
331    /// descriptors and mappings, which must be appended separately.
332    pub fn new(identifier: u32, flags: u32, rmr_count: u32, mapping_count: u32) -> Self {
333        let mut header = IortNodeHeader::new::<Self>(
334            IORT_NODE_TYPE_RMR,
335            IORT_RMR_REVISION,
336            identifier,
337            mapping_count,
338        );
339        // Total node size: fixed header + ID mappings + RMR descriptors.
340        // Accumulate in `usize` and convert to the on-wire `u16` `length`
341        // field via `try_into`, so an oversized node fails loudly instead of
342        // silently wrapping to a too-small length.
343        let total = size_of::<Self>()
344            + mapping_count as usize * size_of::<IortIdMapping>()
345            + rmr_count as usize * size_of::<IortRmrDescriptor>();
346        header.length = u16::try_from(total)
347            .expect("IORT RMR node length exceeds u16")
348            .into();
349        // mapping_offset is immediately after the fixed header.
350        if mapping_count > 0 {
351            header.mapping_offset = (size_of::<Self>() as u32).into();
352        }
353        // rmr_offset comes after the ID mappings. Bounded by `total` (already
354        // checked to fit `u16`), so the `u32` accumulation cannot overflow.
355        let rmr_offset = if rmr_count > 0 {
356            u32::try_from(size_of::<Self>() + mapping_count as usize * size_of::<IortIdMapping>())
357                .expect("IORT RMR descriptor offset exceeds u32")
358        } else {
359            0
360        };
361        Self {
362            header,
363            flags: flags.into(),
364            rmr_count: rmr_count.into(),
365            rmr_offset: rmr_offset.into(),
366        }
367    }
368}
369
370const_assert_eq!(size_of::<IortRmr>(), 28);
371
372/// RMR memory range descriptor.
373#[repr(C, packed)]
374#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
375pub struct IortRmrDescriptor {
376    pub base_address: u64_ne,
377    pub length: u64_ne,
378    pub reserved: u32_ne,
379}
380
381impl IortRmrDescriptor {
382    pub fn new(base_address: u64, length: u64) -> Self {
383        Self {
384            base_address: base_address.into(),
385            length: length.into(),
386            reserved: 0.into(),
387        }
388    }
389}
390
391const_assert_eq!(size_of::<IortRmrDescriptor>(), 20);