Skip to main content

acpi_spec/
srat.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(feature = "alloc")]
5pub use self::alloc_parse::*;
6
7use super::Table;
8use crate::packed_nums::*;
9use core::mem::size_of;
10use static_assertions::const_assert_eq;
11use zerocopy::FromBytes;
12use zerocopy::Immutable;
13use zerocopy::IntoBytes;
14use zerocopy::KnownLayout;
15use zerocopy::Ref;
16use zerocopy::Unaligned;
17
18#[repr(C)]
19#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
20pub struct SratHeader {
21    pub rsvd1: u32_ne,
22    pub rsvd2: u64_ne,
23}
24
25impl SratHeader {
26    pub fn new() -> SratHeader {
27        SratHeader {
28            rsvd1: 1.into(),
29            rsvd2: 0.into(),
30        }
31    }
32}
33
34impl Table for SratHeader {
35    const SIGNATURE: [u8; 4] = *b"SRAT";
36}
37
38pub const SRAT_REVISION: u8 = 3;
39
40open_enum::open_enum! {
41    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
42    pub enum SratType: u8 {
43        APIC = 0,
44        MEMORY = 1,
45        X2APIC = 2,
46        GICC = 3,
47        GENERIC_INITIATOR = 5,
48    }
49}
50
51open_enum::open_enum! {
52    /// Device Handle Type for a [`SratGenericInitiator`] structure.
53    #[derive(IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
54    pub enum SratDeviceHandleType: u8 {
55        ACPI = 0,
56        PCI = 1,
57    }
58}
59
60#[repr(C)]
61#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
62pub struct SratApic {
63    pub typ: SratType,
64    pub length: u8,
65    pub proximity_domain_byte1: u8,
66    pub apic_id: u8,
67    pub flags: u32_ne,
68    pub local_sapic_eid: u8,
69    pub proximity_domain_byte2: u8,
70    pub proximity_domain_byte3: u8,
71    pub proximity_domain_byte4: u8,
72    pub clock_domain: u32_ne,
73}
74
75const_assert_eq!(size_of::<SratApic>(), 16);
76
77pub const SRAT_APIC_ENABLED: u32 = 1 << 0;
78
79impl SratApic {
80    pub fn new(apic_id: u8, vnode: u32) -> Self {
81        let vnode = vnode.to_le_bytes();
82        Self {
83            typ: SratType::APIC,
84            length: size_of::<Self>() as u8,
85            proximity_domain_byte1: vnode[0],
86            apic_id,
87            flags: SRAT_APIC_ENABLED.into(),
88            local_sapic_eid: 0,
89            proximity_domain_byte2: vnode[1],
90            proximity_domain_byte3: vnode[2],
91            proximity_domain_byte4: vnode[3],
92            clock_domain: 0.into(),
93        }
94    }
95}
96
97#[repr(C)]
98#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
99pub struct SratX2Apic {
100    pub typ: SratType,
101    pub length: u8,
102    pub reserved: u16_ne,
103    pub proximity_domain: u32_ne,
104    pub x2_apic_id: u32_ne,
105    pub flags: u32_ne,
106    pub clock_domain: u32_ne,
107    pub reserved2: u32_ne,
108}
109
110const_assert_eq!(size_of::<SratX2Apic>(), 24);
111
112impl SratX2Apic {
113    pub fn new(x2_apic_id: u32, vnode: u32) -> Self {
114        Self {
115            typ: SratType::X2APIC,
116            length: size_of::<Self>() as u8,
117            x2_apic_id: x2_apic_id.into(),
118            flags: SRAT_APIC_ENABLED.into(),
119            clock_domain: 0.into(),
120            reserved: 0.into(),
121            proximity_domain: vnode.into(),
122            reserved2: 0.into(),
123        }
124    }
125}
126
127#[repr(C)]
128#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
129pub struct SratGicc {
130    pub typ: SratType,
131    pub length: u8,
132    pub proximity_domain: u32_ne,
133    pub acpi_processor_uid: u32_ne,
134    pub flags: u32_ne,
135    pub clock_domain: u32_ne,
136}
137
138const_assert_eq!(size_of::<SratGicc>(), 18);
139
140impl SratGicc {
141    pub fn new(acpi_processor_uid: u32, vnode: u32) -> Self {
142        Self {
143            typ: SratType::GICC,
144            length: size_of::<Self>() as u8,
145            acpi_processor_uid: acpi_processor_uid.into(),
146            flags: SRAT_APIC_ENABLED.into(),
147            clock_domain: 0.into(),
148            proximity_domain: vnode.into(),
149        }
150    }
151}
152
153/// SRAT Generic Initiator Affinity Structure (ACPI 6.3+, type 5).
154///
155/// Associates a non-CPU initiator (such as a PCI device) with a proximity
156/// domain. This is how a passthrough device's coherent/device memory is
157/// attached to a CPU-less NUMA node: the guest OS matches the device handle
158/// (PCI segment/bus/device/function) against the device and onlines the
159/// corresponding proximity domain.
160#[repr(C)]
161#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
162pub struct SratGenericInitiator {
163    pub typ: SratType,
164    pub length: u8,
165    pub reserved1: u8,
166    pub device_handle_type: SratDeviceHandleType,
167    pub proximity_domain: u32_ne,
168    pub device_handle: [u8; 16],
169    pub flags: u32_ne,
170    pub reserved2: u32_ne,
171}
172
173const_assert_eq!(size_of::<SratGenericInitiator>(), 32);
174
175open_enum::open_enum! {
176    pub enum SratGenericInitiatorFlags: u32 {
177        ENABLED                   = 1 << 0,
178        ARCHITECTURAL_TRANSACTIONS = 1 << 1,
179    }
180}
181
182impl SratGenericInitiator {
183    /// Creates a Generic Initiator Affinity structure for a PCI device,
184    /// associating the device at `segment:bus:device.function` with the
185    /// proximity domain `vnode`.
186    pub fn new_pci(segment: u16, bus: u8, device: u8, function: u8, vnode: u32) -> Self {
187        // PCI Device Handle layout (ACPI 6.3, Table "Device Handle - PCI"):
188        //   bytes [0..2] : PCI Segment (little endian)
189        //   byte   [2]   : PCI Bus
190        //   byte   [3]   : PCI Device (bits 7:3) | Function (bits 2:0)
191        //   bytes [4..16]: reserved (zero)
192        let mut device_handle = [0u8; 16];
193        device_handle[0..2].copy_from_slice(&segment.to_le_bytes());
194        device_handle[2] = bus;
195        device_handle[3] = ((device & 0x1f) << 3) | (function & 0x7);
196        Self {
197            typ: SratType::GENERIC_INITIATOR,
198            length: size_of::<Self>() as u8,
199            reserved1: 0,
200            device_handle_type: SratDeviceHandleType::PCI,
201            proximity_domain: vnode.into(),
202            device_handle,
203            flags: SratGenericInitiatorFlags::ENABLED.0.into(),
204            reserved2: 0.into(),
205        }
206    }
207}
208
209#[repr(C)]
210#[derive(Copy, Clone, IntoBytes, Immutable, KnownLayout, FromBytes, Unaligned)]
211pub struct SratMemory {
212    pub typ: SratType,
213    pub length: u8,
214    pub proximity_domain: u32_ne,
215    pub rsvd1: u16_ne,
216    pub low_address: u32_ne,
217    pub high_address: u32_ne,
218    pub low_length: u32_ne,
219    pub high_length: u32_ne,
220    pub rsvd2: u32_ne,
221    pub flags: u32_ne,
222    pub rsvd3: u64_ne,
223}
224
225impl core::fmt::Debug for SratMemory {
226    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
227        let address =
228            u64::read_from_bytes([self.low_address, self.high_address].as_bytes()).unwrap();
229        let length = u64::read_from_bytes([self.low_length, self.high_length].as_bytes()).unwrap();
230
231        f.debug_struct("SratMemory")
232            .field("typ", &self.typ)
233            .field("length", &self.length)
234            .field("proximity_domain", &self.proximity_domain)
235            .field("rsvd1", &self.rsvd1)
236            .field("address", &address)
237            .field("_end_address", &(address + length))
238            .field("length", &length)
239            .field("rsvd2", &self.rsvd2)
240            .field("flags", &self.flags)
241            .field("rsvd3", &self.rsvd3)
242            .finish()
243    }
244}
245
246const_assert_eq!(size_of::<SratMemory>(), 40);
247
248open_enum::open_enum! {
249    pub enum SratMemoryFlags: u32 {
250        ENABLED       = 1 << 0,
251        HOT_PLUGGABLE = 1 << 1,
252        NVRAM         = 1 << 2,
253    }
254}
255
256impl SratMemory {
257    pub fn new(addr: u64, len: u64, vnode: u32) -> Self {
258        Self {
259            typ: SratType::MEMORY,
260            length: size_of::<Self>() as u8,
261            proximity_domain: vnode.into(),
262            rsvd1: 0.into(),
263            low_address: (addr as u32).into(),
264            high_address: ((addr >> 32) as u32).into(),
265            low_length: (len as u32).into(),
266            high_length: ((len >> 32) as u32).into(),
267            rsvd2: 0.into(),
268            flags: SratMemoryFlags::ENABLED.0.into(),
269            rsvd3: 0.into(),
270        }
271    }
272}
273
274#[derive(Debug)]
275pub enum ParseSratError {
276    MissingAcpiHeader,
277    InvalidSignature([u8; 4]),
278    MismatchedLength { in_header: usize, actual: usize },
279    MissingFixedHeader,
280    BadApic,
281    BadMemory,
282    UnknownType(u8),
283}
284
285impl core::fmt::Display for ParseSratError {
286    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
287        match self {
288            Self::MissingAcpiHeader => write!(f, "could not read standard ACPI header"),
289            Self::InvalidSignature(sig) => {
290                write!(f, "invalid signature. expected b\"SRAT\", found {sig:?}")
291            }
292            Self::MismatchedLength { in_header, actual } => {
293                write!(f, "mismatched len. in_header: {in_header}, actual {actual}")
294            }
295            Self::MissingFixedHeader => write!(f, "missing fixed SRAT header"),
296            Self::BadApic => write!(f, "could not read APIC structure"),
297            Self::BadMemory => write!(f, "could not read MEMORY structure"),
298            Self::UnknownType(ty) => write!(f, "unknown SRAT structure type: {ty}"),
299        }
300    }
301}
302
303impl core::error::Error for ParseSratError {}
304
305pub fn parse_srat<'a>(
306    raw_srat: &'a [u8],
307    mut on_apic: impl FnMut(&'a SratApic),
308    mut on_memory: impl FnMut(&'a SratMemory),
309) -> Result<(&'a crate::Header, &'a SratHeader), ParseSratError> {
310    let raw_srat_len = raw_srat.len();
311    let (acpi_header, buf) = Ref::<_, crate::Header>::from_prefix(raw_srat)
312        .map_err(|_| ParseSratError::MissingAcpiHeader)?; // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
313
314    if acpi_header.signature != *b"SRAT" {
315        return Err(ParseSratError::InvalidSignature(acpi_header.signature));
316    }
317
318    if acpi_header.length.get() as usize != raw_srat_len {
319        return Err(ParseSratError::MismatchedLength {
320            in_header: acpi_header.length.get() as usize,
321            actual: raw_srat_len,
322        });
323    }
324
325    let (srat_header, mut buf) =
326        Ref::<_, SratHeader>::from_prefix(buf).map_err(|_| ParseSratError::MissingFixedHeader)?; // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
327
328    while !buf.is_empty() {
329        buf = match SratType(buf[0]) {
330            SratType::APIC => {
331                let (apic, rest) =
332                    Ref::<_, SratApic>::from_prefix(buf).map_err(|_| ParseSratError::BadApic)?; // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
333                on_apic(Ref::into_ref(apic));
334                rest
335            }
336            SratType::MEMORY => {
337                let (mem, rest) = Ref::<_, SratMemory>::from_prefix(buf)
338                    .map_err(|_| ParseSratError::BadMemory)?; // TODO: zerocopy: map_err (https://github.com/microsoft/openvmm/issues/759)
339                on_memory(Ref::into_ref(mem));
340                rest
341            }
342            _ => return Err(ParseSratError::UnknownType(buf[0])),
343        }
344    }
345
346    Ok((Ref::into_ref(acpi_header), Ref::into_ref(srat_header)))
347}
348
349#[cfg(feature = "alloc")]
350pub mod alloc_parse {
351    use super::*;
352    use alloc::vec::Vec;
353
354    #[derive(Debug)]
355    pub struct BorrowedSrat<'a> {
356        pub acpi_header: &'a crate::Header,
357        pub srat_header: &'a SratHeader,
358        pub apics: Vec<&'a SratApic>,
359        pub memory: Vec<&'a SratMemory>,
360    }
361
362    #[derive(Debug)]
363    pub struct OwnedSrat {
364        pub acpi_header: crate::Header,
365        pub srat_header: SratHeader,
366        pub apics: Vec<SratApic>,
367        pub memory: Vec<SratMemory>,
368    }
369
370    impl From<BorrowedSrat<'_>> for OwnedSrat {
371        fn from(b: BorrowedSrat<'_>) -> Self {
372            OwnedSrat {
373                acpi_header: *b.acpi_header,
374                srat_header: *b.srat_header,
375                apics: b.apics.into_iter().cloned().collect(),
376                memory: b.memory.into_iter().cloned().collect(),
377            }
378        }
379    }
380
381    impl BorrowedSrat<'_> {
382        pub fn new(raw_srat: &[u8]) -> Result<BorrowedSrat<'_>, ParseSratError> {
383            let mut apics = Vec::new();
384            let mut memory = Vec::new();
385            let (acpi_header, srat_header) =
386                parse_srat(raw_srat, |x| apics.push(x), |x| memory.push(x))?;
387
388            Ok(BorrowedSrat {
389                acpi_header,
390                srat_header,
391                apics,
392                memory,
393            })
394        }
395    }
396
397    impl OwnedSrat {
398        pub fn new(raw_srat: &[u8]) -> Result<OwnedSrat, ParseSratError> {
399            Ok(BorrowedSrat::new(raw_srat)?.into())
400        }
401    }
402}