Skip to main content

acpi/
cedt.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! CXL Early Discovery Table (CEDT) builder.
5
6use core::mem::size_of;
7use cxl_spec::spec::CEDT_STRUCTURE_TYPE_CFMWS;
8use cxl_spec::spec::CEDT_STRUCTURE_TYPE_CHBS;
9use cxl_spec::spec::CXL_HOST_BRIDGE_COMPONENT_REGISTERS_SIZE_BYTES;
10use cxl_spec::spec::CXL_HPA_ALIGNMENT;
11use cxl_spec::spec::InterleaveArithmetic;
12use cxl_spec::spec::InterleaveGranularity;
13use cxl_spec::spec::InterleaveWays;
14use memory_range::MemoryRange;
15use thiserror::Error;
16use zerocopy::FromBytes;
17use zerocopy::Immutable;
18use zerocopy::IntoBytes;
19use zerocopy::KnownLayout;
20
21/// Validation errors when adding CHBS/CFMWS CXL host-bridge entries.
22#[derive(Debug, Clone, PartialEq, Eq, Error)]
23pub enum CedtHostBridgeError {
24    /// HDM range is empty.
25    #[error("HDM range must be non-empty")]
26    EmptyHdmRange,
27    /// CHBCR range is empty.
28    #[error("CHBCR range must be non-empty")]
29    EmptyChbcrRange,
30    /// CHBCR range length does not match the spec-defined size.
31    #[error("CHBCR range has invalid length {actual:#x}, expected {expected:#x}")]
32    InvalidChbcrRangeLength { actual: u64, expected: u64 },
33    /// CHBCR base is not aligned to the CHBCR aperture size.
34    #[error("CHBCR base {base:#x} is not aligned to {alignment:#x}")]
35    InvalidChbcrBaseAlignment { base: u64, alignment: u64 },
36    /// HDM base is not aligned to CXL HPA alignment.
37    #[error("HDM base {base:#x} is not aligned to {alignment:#x}")]
38    InvalidHdmBaseAlignment { base: u64, alignment: u64 },
39    /// HDM size is not aligned to CXL HPA alignment.
40    #[error("HDM size {size:#x} is not aligned to {alignment:#x}")]
41    InvalidHdmSizeAlignment { size: u64, alignment: u64 },
42}
43
44/// Errors while serializing CEDT bytes.
45#[derive(Debug, Clone, PartialEq, Eq, Error)]
46pub enum CedtSerializeError {
47    /// Serialized CEDT size exceeded ACPI header u32 length field capacity.
48    #[error("CEDT length {actual:#x} exceeds u32 length field")]
49    LengthOverflow { actual: usize },
50}
51
52#[repr(C, packed)]
53#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
54pub struct DescriptionHeader {
55    pub signature: u32,
56    _length: u32, // placeholder, filled in during serialization to bytes
57    pub revision: u8,
58    _checksum: u8, // placeholder, filled in during serialization to bytes
59    pub oem_id: [u8; 6],
60    pub oem_table_id: u64,
61    pub oem_revision: u32,
62    pub creator_id: u32,
63    pub creator_rev: u32,
64}
65
66#[repr(C, packed)]
67#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
68struct ChbsStructure {
69    structure_type: u8,
70    reserved0: u8,
71    record_length: u16,
72    uid: u32,
73    cxl_version: u32,
74    reserved1: u32,
75    base: u64,
76    length: u64,
77}
78
79#[repr(C, packed)]
80#[derive(Copy, Clone, Debug, IntoBytes, Immutable, KnownLayout, FromBytes)]
81struct CfmwsStructure {
82    structure_type: u8,
83    reserved0: u8,
84    record_length: u16,
85    reserved1: u32,
86    base_hpa: u64,
87    window_size: u64,
88    eniw: u8,
89    interleave_arithmetic: u8,
90    reserved2: u16,
91    hbig: u32,
92    window_restrictions: u16,
93    qtg_id: u16,
94}
95
96/// CXL Early Discovery Table (CEDT).
97pub struct Cedt {
98    description_header: DescriptionHeader,
99    structures: Vec<u8>,
100}
101
102impl Cedt {
103    /// Create an empty CEDT.
104    pub fn new() -> Self {
105        Self {
106            description_header: DescriptionHeader {
107                signature: u32::from_le_bytes(*b"CEDT"),
108                _length: 0,
109                revision: 2,
110                _checksum: 0,
111                oem_id: *b"MSFTVM",
112                oem_table_id: u64::from_le_bytes(*b"CEDT01\0\0"),
113                oem_revision: 1,
114                creator_id: u32::from_le_bytes(*b"MSFT"),
115                creator_rev: 0x01000000,
116            },
117            structures: Vec::new(),
118        }
119    }
120
121    /// Add one CXL host bridge to CEDT.
122    ///
123    /// This appends:
124    /// 1) One CHBS structure.
125    /// 2) One CFMWS structure where only base HPA and window size are set,
126    ///    with one Interleave Target List entry for ENIW=0 (1-way interleave, default).
127    pub fn add_cxl_host_bridge(
128        &mut self,
129        uid: u32,
130        hdm_range: MemoryRange,
131        chbcr_range: MemoryRange,
132        hdm_window_restrictions: u16,
133    ) -> Result<(), CedtHostBridgeError> {
134        if hdm_range.is_empty() {
135            return Err(CedtHostBridgeError::EmptyHdmRange);
136        }
137        if chbcr_range.is_empty() {
138            return Err(CedtHostBridgeError::EmptyChbcrRange);
139        }
140        if chbcr_range.len() != CXL_HOST_BRIDGE_COMPONENT_REGISTERS_SIZE_BYTES {
141            return Err(CedtHostBridgeError::InvalidChbcrRangeLength {
142                actual: chbcr_range.len(),
143                expected: CXL_HOST_BRIDGE_COMPONENT_REGISTERS_SIZE_BYTES,
144            });
145        }
146        if !chbcr_range
147            .start()
148            .is_multiple_of(CXL_HOST_BRIDGE_COMPONENT_REGISTERS_SIZE_BYTES)
149        {
150            return Err(CedtHostBridgeError::InvalidChbcrBaseAlignment {
151                base: chbcr_range.start(),
152                alignment: CXL_HOST_BRIDGE_COMPONENT_REGISTERS_SIZE_BYTES,
153            });
154        }
155        if !hdm_range.start().is_multiple_of(CXL_HPA_ALIGNMENT) {
156            return Err(CedtHostBridgeError::InvalidHdmBaseAlignment {
157                base: hdm_range.start(),
158                alignment: CXL_HPA_ALIGNMENT,
159            });
160        }
161        if !hdm_range.len().is_multiple_of(CXL_HPA_ALIGNMENT) {
162            return Err(CedtHostBridgeError::InvalidHdmSizeAlignment {
163                size: hdm_range.len(),
164                alignment: CXL_HPA_ALIGNMENT,
165            });
166        }
167
168        let chbs = ChbsStructure {
169            structure_type: CEDT_STRUCTURE_TYPE_CHBS,
170            reserved0: 0,
171            record_length: size_of::<ChbsStructure>() as u16,
172            uid,
173            // Host bridge associated with one or more CXL root ports.
174            cxl_version: 1,
175            reserved1: 0,
176            base: chbcr_range.start(),
177            length: chbcr_range.len(),
178        };
179        self.structures.extend_from_slice(chbs.as_bytes());
180
181        let cfmws = CfmwsStructure {
182            structure_type: CEDT_STRUCTURE_TYPE_CFMWS,
183            reserved0: 0,
184            // ENIW=0 means 1-way interleave, so include one 32-bit target entry.
185            record_length: (size_of::<CfmwsStructure>() + size_of::<u32>()) as u16,
186            reserved1: 0,
187            base_hpa: hdm_range.start(),
188            window_size: hdm_range.len(),
189            eniw: InterleaveWays::WAY_1,
190            interleave_arithmetic: InterleaveArithmetic::STANDARD_MODULO,
191            reserved2: 0,
192            hbig: InterleaveGranularity::BYTES_256 as u32,
193            window_restrictions: hdm_window_restrictions,
194            qtg_id: 0,
195        };
196        self.structures.extend_from_slice(cfmws.as_bytes());
197        self.structures.extend_from_slice(&uid.to_le_bytes());
198
199        Ok(())
200    }
201
202    /// Serialize CEDT to bytes.
203    pub fn to_bytes(&self) -> Result<Vec<u8>, CedtSerializeError> {
204        let mut byte_stream = Vec::new();
205        byte_stream.extend_from_slice(self.description_header.as_bytes());
206        byte_stream.extend_from_slice(&self.structures);
207
208        let length = byte_stream.len();
209        let length = u32::try_from(length).map_err(|_| CedtSerializeError::LengthOverflow {
210            actual: byte_stream.len(),
211        })?;
212
213        let (header, _) = DescriptionHeader::mut_from_prefix(byte_stream.as_mut_slice())
214            .expect("serialized CEDT must start with a complete description header");
215        header._length = length;
216        // Checksum is computed with this field set to zero.
217        header._checksum = 0;
218
219        let mut checksum: u8 = 0;
220        for byte in &byte_stream {
221            checksum = checksum.wrapping_add(*byte);
222        }
223        DescriptionHeader::mut_from_prefix(byte_stream.as_mut_slice())
224            .expect("serialized CEDT must start with a complete description header")
225            .0
226            ._checksum = (!checksum).wrapping_add(1);
227
228        Ok(byte_stream)
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    #[test]
237    fn cedt_header_signature_and_revision() {
238        let cedt = Cedt::new();
239        let bytes = cedt.to_bytes().expect("serialize CEDT");
240        assert_eq!(&bytes[0..4], b"CEDT");
241        assert_eq!(bytes[8], 2);
242    }
243
244    #[test]
245    fn add_cxl_host_bridge_adds_chbs_and_cfmws() {
246        let mut cedt = Cedt::new();
247        cedt.add_cxl_host_bridge(
248            7,
249            MemoryRange::new(0x4000_0000..0x8000_0000),
250            MemoryRange::new(0x1000_0000..0x1001_0000),
251            cxl_spec::spec::CfmwsWindowRestrictions::DEVICE_COHERENT.bits(),
252        )
253        .expect("valid CXL host bridge ranges");
254        let bytes = cedt.to_bytes().expect("serialize CEDT");
255
256        // Header is 36 bytes. First structure should be CHBS type 0.
257        assert_eq!(bytes[36], CEDT_STRUCTURE_TYPE_CHBS);
258        // CHBS record length should be 0x20.
259        assert_eq!(u16::from_le_bytes([bytes[38], bytes[39]]), 0x20);
260
261        // Second structure should be CFMWS type 1 at offset 36 + 0x20.
262        assert_eq!(bytes[36 + 0x20], CEDT_STRUCTURE_TYPE_CFMWS);
263        // CFMWS record length should be 0x28 (base structure + one target entry).
264        assert_eq!(
265            u16::from_le_bytes([bytes[36 + 0x20 + 2], bytes[36 + 0x20 + 3]]),
266            0x28
267        );
268
269        // Interleave Target List[0] should contain the CHBS UID for ENIW=0.
270        assert_eq!(
271            u32::from_le_bytes([
272                bytes[36 + 0x20 + 0x24],
273                bytes[36 + 0x20 + 0x25],
274                bytes[36 + 0x20 + 0x26],
275                bytes[36 + 0x20 + 0x27],
276            ]),
277            7
278        );
279    }
280
281    #[test]
282    fn add_cxl_host_bridge_rejects_unaligned_chbcr_base() {
283        let mut cedt = Cedt::new();
284        let err = cedt
285            .add_cxl_host_bridge(
286                0,
287                MemoryRange::new(0x4000_0000..0x5000_0000),
288                // 4K-aligned but not 64K-aligned.
289                MemoryRange::new(0x1000_1000..0x1001_1000),
290                cxl_spec::spec::CfmwsWindowRestrictions::DEVICE_COHERENT.bits(),
291            )
292            .expect_err("unaligned CHBCR base should be rejected");
293
294        assert_eq!(
295            err,
296            CedtHostBridgeError::InvalidChbcrBaseAlignment {
297                base: 0x1000_1000,
298                alignment: CXL_HOST_BRIDGE_COMPONENT_REGISTERS_SIZE_BYTES,
299            }
300        );
301    }
302
303    #[test]
304    fn add_cxl_host_bridge_rejects_unaligned_hdm_base() {
305        let mut cedt = Cedt::new();
306        let err = cedt
307            .add_cxl_host_bridge(
308                0,
309                // 4K-aligned but not 256MiB-aligned.
310                MemoryRange::new(0x4000_1000..0x5000_1000),
311                MemoryRange::new(0x1000_0000..0x1001_0000),
312                cxl_spec::spec::CfmwsWindowRestrictions::DEVICE_COHERENT.bits(),
313            )
314            .expect_err("unaligned HDM base should be rejected");
315
316        assert_eq!(
317            err,
318            CedtHostBridgeError::InvalidHdmBaseAlignment {
319                base: 0x4000_1000,
320                alignment: CXL_HPA_ALIGNMENT,
321            }
322        );
323    }
324
325    #[test]
326    fn add_cxl_host_bridge_rejects_unaligned_hdm_size() {
327        let mut cedt = Cedt::new();
328        let err = cedt
329            .add_cxl_host_bridge(
330                0,
331                // Base is 256MiB-aligned, but size is not.
332                MemoryRange::new(0x4000_0000..0x5000_1000),
333                MemoryRange::new(0x1000_0000..0x1001_0000),
334                cxl_spec::spec::CfmwsWindowRestrictions::DEVICE_COHERENT.bits(),
335            )
336            .expect_err("unaligned HDM size should be rejected");
337
338        assert_eq!(
339            err,
340            CedtHostBridgeError::InvalidHdmSizeAlignment {
341                size: 0x1000_1000,
342                alignment: CXL_HPA_ALIGNMENT,
343            }
344        );
345    }
346}