1use 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#[derive(Debug, Clone, PartialEq, Eq, Error)]
23pub enum CedtHostBridgeError {
24 #[error("HDM range must be non-empty")]
26 EmptyHdmRange,
27 #[error("CHBCR range must be non-empty")]
29 EmptyChbcrRange,
30 #[error("CHBCR range has invalid length {actual:#x}, expected {expected:#x}")]
32 InvalidChbcrRangeLength { actual: u64, expected: u64 },
33 #[error("CHBCR base {base:#x} is not aligned to {alignment:#x}")]
35 InvalidChbcrBaseAlignment { base: u64, alignment: u64 },
36 #[error("HDM base {base:#x} is not aligned to {alignment:#x}")]
38 InvalidHdmBaseAlignment { base: u64, alignment: u64 },
39 #[error("HDM size {size:#x} is not aligned to {alignment:#x}")]
41 InvalidHdmSizeAlignment { size: u64, alignment: u64 },
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Error)]
46pub enum CedtSerializeError {
47 #[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, pub revision: u8,
58 _checksum: u8, 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
96pub struct Cedt {
98 description_header: DescriptionHeader,
99 structures: Vec<u8>,
100}
101
102impl Cedt {
103 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 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 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 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 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 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 assert_eq!(bytes[36], CEDT_STRUCTURE_TYPE_CHBS);
258 assert_eq!(u16::from_le_bytes([bytes[38], bytes[39]]), 0x20);
260
261 assert_eq!(bytes[36 + 0x20], CEDT_STRUCTURE_TYPE_CFMWS);
263 assert_eq!(
265 u16::from_le_bytes([bytes[36 + 0x20 + 2], bytes[36 + 0x20 + 3]]),
266 0x28
267 );
268
269 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 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 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 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}