blob: 30850e5152fb698f3598e8630f66650599b16812 [file] [log] [blame]
Balint Dobszay5bf492f2024-07-29 17:21:32 +02001// SPDX-FileCopyrightText: Copyright 2023 Arm Limited and/or its affiliates <open-source-office@arm.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +01004use crate::{
5 ffa_v1_1::{boot_info_descriptor, boot_info_header},
6 Version,
7};
8use core::ffi::CStr;
9use thiserror::Error;
Balint Dobszay5bf492f2024-07-29 17:21:32 +020010use uuid::Uuid;
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +010011use zerocopy::{FromBytes, IntoBytes};
Balint Dobszay5bf492f2024-07-29 17:21:32 +020012
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +010013#[derive(Debug, Error)]
14pub enum Error {
15 #[error("Invalid standard type {0}")]
16 InvalidStdType(u8),
17 #[error("Invalid type {0}")]
18 InvalidType(u8),
19 #[error("Invalid contents format {0}")]
20 InvalidContentsFormat(u16),
21 #[error("Invalid name format {0}")]
22 InvalidNameFormat(u16),
23 #[error("Invalid name")]
24 InvalidName,
25 #[error("Invalid flags {0}")]
26 InvalidFlags(u16),
27 #[error("Invalid header size or alignment")]
28 InvalidHeader,
29 #[error("Invalid buffer size")]
30 InvalidBufferSize,
31 #[error("Invalid signature")]
32 InvalidSignature,
33 #[error("Invalid version {0}")]
34 InvalidVersion(Version),
35 #[error("Malformed descriptor")]
36 MalformedDescriptor,
37}
38
39impl From<Error> for crate::FfaError {
40 fn from(_value: Error) -> Self {
41 Self::InvalidParameters
42 }
43}
44
45#[derive(Clone, Debug, PartialEq, Eq)]
46pub enum BootInfoName<'a> {
47 NullTermString(&'a CStr),
Balint Dobszay5bf492f2024-07-29 17:21:32 +020048 Uuid(Uuid),
49}
50
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +010051#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52#[repr(u8)]
53pub enum BootInfoStdId {
54 Fdt = Self::FDT,
55 Hob = Self::HOB,
Balint Dobszay5bf492f2024-07-29 17:21:32 +020056}
57
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +010058impl TryFrom<u8> for BootInfoStdId {
59 type Error = Error;
60
61 fn try_from(value: u8) -> Result<Self, Self::Error> {
62 match value {
63 Self::FDT => Ok(BootInfoStdId::Fdt),
64 Self::HOB => Ok(BootInfoStdId::Hob),
65 _ => Err(Error::InvalidStdType(value)),
66 }
67 }
68}
69
70impl BootInfoStdId {
71 const FDT: u8 = 0;
72 const HOB: u8 = 1;
73}
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub struct BootInfoImpdefId(pub u8);
77
78impl From<u8> for BootInfoImpdefId {
79 fn from(value: u8) -> Self {
80 Self(value)
81 }
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
Balint Dobszay5bf492f2024-07-29 17:21:32 +020085pub enum BootInfoType {
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +010086 Std(BootInfoStdId),
87 Impdef(BootInfoImpdefId),
Balint Dobszay5bf492f2024-07-29 17:21:32 +020088}
89
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +010090impl TryFrom<u8> for BootInfoType {
91 type Error = Error;
92
93 fn try_from(value: u8) -> Result<Self, Self::Error> {
94 match (value >> Self::TYPE_SHIFT) & Self::TYPE_MASK {
95 Self::TYPE_STANDARD => Ok(BootInfoType::Std((value & Self::ID_MASK).try_into()?)),
96 Self::TYPE_IMPDEF => Ok(BootInfoType::Impdef((value & Self::ID_MASK).into())),
97 _ => Err(Error::InvalidType(value)),
98 }
99 }
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200100}
101
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100102impl From<BootInfoType> for u8 {
103 fn from(value: BootInfoType) -> Self {
104 match value {
105 BootInfoType::Std(std_type) => {
106 std_type as u8 | BootInfoType::TYPE_STANDARD << BootInfoType::TYPE_SHIFT
107 }
108 BootInfoType::Impdef(impdef_type) => {
109 impdef_type.0 | BootInfoType::TYPE_IMPDEF << BootInfoType::TYPE_SHIFT
110 }
111 }
112 }
113}
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200114
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100115impl BootInfoType {
116 // This field contains the boot info type at bit[7] and the boot info identifier in bits[6:0]
117 const TYPE_SHIFT: usize = 7;
118 const TYPE_MASK: u8 = 0b1;
119 const TYPE_STANDARD: u8 = 0b0;
120 const TYPE_IMPDEF: u8 = 0b1;
121 // Mask for boot info identifier in bits[6:0]
122 const ID_MASK: u8 = 0b0111_1111;
123}
124
125#[derive(Clone, Copy, Debug, PartialEq, Eq)]
126pub enum BootInfoContents<'a> {
127 Address { content_buf: &'a [u8] },
128 Value { val: u64, len: usize },
129}
130
131#[derive(Clone, Copy, Debug, PartialEq, Eq)]
132#[repr(u16)]
133enum BootInfoContentsFormat {
134 Address = Self::ADDRESS << Self::SHIFT,
135 Value = Self::VALUE << Self::SHIFT,
136}
137
138impl TryFrom<u16> for BootInfoContentsFormat {
139 type Error = Error;
140
141 fn try_from(value: u16) -> Result<Self, Self::Error> {
142 match (value >> Self::SHIFT) & Self::MASK {
143 Self::ADDRESS => Ok(BootInfoContentsFormat::Address),
144 Self::VALUE => Ok(BootInfoContentsFormat::Value),
145 _ => Err(Error::InvalidContentsFormat(value)),
146 }
147 }
148}
149
150impl BootInfoContentsFormat {
151 const SHIFT: usize = 2;
152 const MASK: u16 = 0b11;
153 const ADDRESS: u16 = 0b00;
154 const VALUE: u16 = 0b01;
155}
156
157#[derive(Clone, Copy, Debug, PartialEq, Eq)]
158#[repr(u16)]
159enum BootInfoNameFormat {
160 String = Self::STRING << Self::SHIFT,
161 Uuid = Self::UUID << Self::SHIFT,
162}
163
164impl TryFrom<u16> for BootInfoNameFormat {
165 type Error = Error;
166
167 fn try_from(value: u16) -> Result<Self, Self::Error> {
168 match (value >> Self::SHIFT) & Self::MASK {
169 Self::STRING => Ok(BootInfoNameFormat::String),
170 Self::UUID => Ok(BootInfoNameFormat::Uuid),
171 _ => Err(Error::InvalidNameFormat(value)),
172 }
173 }
174}
175
176impl BootInfoNameFormat {
177 const SHIFT: usize = 0;
178 const MASK: u16 = 0b11;
179 const STRING: u16 = 0b00;
180 const UUID: u16 = 0b01;
181}
182
183#[derive(Clone, Copy, Debug, PartialEq, Eq)]
184struct BootInfoFlags {
185 contents_format: BootInfoContentsFormat,
186 name_format: BootInfoNameFormat,
187}
188
189impl TryFrom<u16> for BootInfoFlags {
190 type Error = Error;
191
192 fn try_from(value: u16) -> Result<Self, Self::Error> {
193 // bits[15:4]: Reserved (MBZ)
194 if value >> 4 == 0 {
195 Ok(Self {
196 contents_format: BootInfoContentsFormat::try_from(value)?,
197 name_format: BootInfoNameFormat::try_from(value)?,
198 })
199 } else {
200 Err(Error::InvalidFlags(value))
201 }
202 }
203}
204
205impl From<BootInfoFlags> for u16 {
206 fn from(value: BootInfoFlags) -> Self {
207 value.contents_format as u16 | value.name_format as u16
208 }
209}
210
211#[derive(Clone, Debug, PartialEq, Eq)]
212pub struct BootInfo<'a> {
213 pub name: BootInfoName<'a>,
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200214 pub typ: BootInfoType,
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100215 pub contents: BootInfoContents<'a>,
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200216}
217
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100218impl BootInfo<'_> {
219 pub fn pack(descriptors: &[BootInfo], buf: &mut [u8], mapped_addr: Option<usize>) {
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200220 // Offset from the base of the header to the first element in the boot info descriptor array
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100221 // Must be 8 byte aligned, but otherwise we're free to choose any value here.
222 // Let's just pack the array right after the header.
223 const DESC_ARRAY_OFFSET: usize = size_of::<boot_info_header>().next_multiple_of(8);
224 const DESC_SIZE: usize = size_of::<boot_info_descriptor>();
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200225
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100226 assert!(buf.len() <= u32::MAX as usize);
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200227
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200228 let desc_cnt = descriptors.len();
229
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100230 // Add the already known fields, later we have to add the sizes referenced by the individual
231 // descriptors
232
233 // The Size of boot information blob field specifies the size of the blob that spans one or
234 // more contiguous 4K pages used by the producer to populate it. It is calculated by adding
235 // the following values:
236 // 1. Boot information descriptor array offset
237 // 2. Product of Boot information descriptor count and Boot information descriptor size.
238 // 3. Total size of all boot information referenced by boot information descriptors.
239 // This is determined by adding the values in the Size field of each boot information
240 // descriptor whose Contents field contains an address.
241 // 4. Any padding between,
242 // 1. The boot information descriptor array and the boot information referenced from it.
243 // 2. Distinct instances of boot information referenced from the boot information
244 // descriptor array.
245 let mut total_offset = 0usize;
246
247 // No. 1 from the "Size of boot information blob" list
248 total_offset = total_offset.checked_add(DESC_ARRAY_OFFSET).unwrap();
249
250 // No. 2 from the "Size of boot information blob" list
251 total_offset = total_offset
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200252 .checked_add(desc_cnt.checked_mul(DESC_SIZE).unwrap())
253 .unwrap();
254
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100255 // Fail early if the buffer is too small
256 assert!(total_offset <= buf.len());
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200257
258 // Fill the boot info descriptor array, all offset based from DESC_ARRAY_OFFSET
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100259 let mut desc_array_offset = DESC_ARRAY_OFFSET;
260
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200261 for desc in descriptors {
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100262 let mut desc_raw = boot_info_descriptor::default();
263
264 let name_format = match &desc.name {
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200265 BootInfoName::NullTermString(name) => {
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100266 // count_bytes() doesn't include nul terminator
267 let name_len = name.count_bytes().min(15);
268 desc_raw.name[..name_len].copy_from_slice(&name.to_bytes()[..name_len]);
269 // Add nul terminator and zero fill the rest
270 desc_raw.name[name_len..].fill(0);
271
272 BootInfoNameFormat::String
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200273 }
274 BootInfoName::Uuid(uuid) => {
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100275 desc_raw.name.copy_from_slice(&uuid.to_bytes_le());
276 BootInfoNameFormat::Uuid
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200277 }
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200278 };
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200279
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100280 let contents_format = match desc.contents {
281 BootInfoContents::Address { content_buf } => {
282 // We have to copy the contents referenced by the boot info descriptor into the
283 // boot info blob. At this offset we're after the boot info header and all of
284 // the boot info descriptors. The contents referenced from the individual boot
285 // info descriptors will get copied to this starting address. The 8 byte
286 // alignment is not explicitly mentioned by the spec, but it's better to have it
287 // anyway.
288 // No. 4 from the "Size of boot information blob" list
289 total_offset = total_offset.next_multiple_of(8);
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200290
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100291 // The mapped_addr argument contains the address where buf is mapped to in the
292 // consumer's translation regime. If it's None, we assume identity mapping is
293 // used, so the buffer's address stays the same.
294 let buf_addr = mapped_addr.unwrap_or(buf.as_ptr() as usize);
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200295
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100296 // The content's address in the consumer's translation regime will be the
297 // buffer's address in the consumer's translation regime plus the offset of the
298 // content within the boot info blob.
299 let content_addr = buf_addr.checked_add(total_offset).unwrap();
300
301 // Check if the content fits before copying
302 let content_len = content_buf.len();
303 total_offset.checked_add(content_len).unwrap();
304
305 // Do the copy and increase the total size
306 // No. 3 from the "Size of boot information blob" list
307 buf[total_offset..total_offset + content_len].copy_from_slice(content_buf);
308 total_offset += content_len;
309
310 desc_raw.contents = content_addr as u64;
311 desc_raw.size = content_len as u32;
312
313 BootInfoContentsFormat::Address
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200314 }
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100315 BootInfoContents::Value { val, len } => {
316 assert!((1..=8).contains(&len));
317 desc_raw.contents = val;
318 desc_raw.size = len as u32;
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200319
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100320 BootInfoContentsFormat::Value
321 }
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200322 };
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200323
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100324 let flags = BootInfoFlags {
325 contents_format,
326 name_format,
327 };
328
329 desc_raw.flags = flags.into();
330 desc_raw.typ = desc.typ.into();
331
332 desc_raw
333 .write_to_prefix(&mut buf[desc_array_offset..])
334 .unwrap();
335 desc_array_offset += DESC_SIZE;
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200336 }
337
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100338 let header_raw = boot_info_header {
339 signature: 0x0ffa,
340 version: Version(1, 1).into(),
341 boot_info_blob_size: total_offset as u32,
342 boot_info_desc_size: DESC_SIZE as u32,
343 boot_info_desc_count: desc_cnt as u32,
344 boot_info_array_offset: DESC_ARRAY_OFFSET as u32,
345 reserved: 0,
346 };
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200347
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100348 header_raw.write_to_prefix(buf).unwrap();
349 }
350
351 /// Validate and return the boot information header
352 fn get_header(buf: &[u8]) -> Result<&boot_info_header, Error> {
353 let (header_raw, _) =
354 boot_info_header::ref_from_prefix(buf).map_err(|_| Error::InvalidHeader)?;
355
356 if header_raw.signature != 0x0ffa {
357 return Err(Error::InvalidSignature);
358 }
359
360 let version = Version::from(header_raw.version);
361 if version != Version(1, 1) {
362 return Err(Error::InvalidVersion(version));
363 }
364
365 Ok(header_raw)
366 }
367
368 /// Get the size of the boot information blob spanning contiguous memory.
369 ///
370 /// This enables a consumer to map all of the boot information blob in its translation regime
371 /// or copy it to another memory location without parsing each element in the boot information
372 /// descriptor array.
373 pub fn get_blob_size(buf: &[u8]) -> Result<usize, Error> {
374 let header_raw = Self::get_header(buf)?;
375
376 Ok(header_raw.boot_info_blob_size as usize)
377 }
378}
379
380pub struct BootInfoIterator<'a> {
381 buf: &'a [u8],
382 offset: usize,
383 desc_count: usize,
384 desc_size: usize,
385}
386
387impl<'a> BootInfoIterator<'a> {
388 pub fn new(buf: &'a [u8]) -> Result<Self, Error> {
389 let header_raw = BootInfo::get_header(buf)?;
390
391 if buf.len() < header_raw.boot_info_blob_size as usize {
392 return Err(Error::InvalidBufferSize);
393 }
394
395 if header_raw.boot_info_desc_size as usize != size_of::<boot_info_descriptor>() {
396 return Err(Error::MalformedDescriptor);
397 }
398
399 let Some(total_desc_size) = header_raw
400 .boot_info_desc_count
401 .checked_mul(header_raw.boot_info_desc_size)
402 .and_then(|x| x.checked_add(header_raw.boot_info_array_offset))
403 else {
404 return Err(Error::InvalidBufferSize);
405 };
406
407 if buf.len() < total_desc_size as usize {
408 return Err(Error::InvalidBufferSize);
409 }
410
411 Ok(Self {
412 buf,
413 offset: header_raw.boot_info_array_offset as usize,
414 desc_count: header_raw.boot_info_desc_count as usize,
415 desc_size: header_raw.boot_info_desc_size as usize,
416 })
417 }
418}
419
420impl<'a> Iterator for BootInfoIterator<'a> {
421 type Item = Result<BootInfo<'a>, Error>;
422
423 fn next(&mut self) -> Option<Self::Item> {
424 if self.desc_count > 0 {
425 let desc_offset = self.offset;
426 self.offset += self.desc_size;
427 self.desc_count -= 1;
428
429 let Ok(desc_raw) = boot_info_descriptor::ref_from_bytes(
430 &self.buf[desc_offset..desc_offset + self.desc_size],
431 ) else {
432 return Some(Err(Error::MalformedDescriptor));
433 };
434
435 if desc_raw.reserved != 0 {
436 return Some(Err(Error::MalformedDescriptor));
437 }
438
439 let typ: BootInfoType = match desc_raw.typ.try_into() {
440 Ok(v) => v,
441 Err(e) => return Some(Err(e)),
442 };
443
444 let flags: BootInfoFlags = match desc_raw.flags.try_into() {
445 Ok(v) => v,
446 Err(e) => return Some(Err(e)),
447 };
448
449 let name = match flags.name_format {
450 BootInfoNameFormat::String => {
451 let Ok(name_str) = CStr::from_bytes_with_nul(desc_raw.name.as_bytes()) else {
452 return Some(Err(Error::InvalidName));
453 };
454 BootInfoName::NullTermString(name_str)
455 }
456 BootInfoNameFormat::Uuid => BootInfoName::Uuid(Uuid::from_bytes_le(desc_raw.name)),
457 };
458
459 let contents = match flags.contents_format {
460 BootInfoContentsFormat::Address => {
461 let contents = desc_raw.contents as usize;
462 let contents_size = desc_raw.size as usize;
463
464 let Some(offset) = contents.checked_sub(self.buf.as_ptr() as usize) else {
465 return Some(Err(Error::InvalidBufferSize));
466 };
467
468 let Some(offset_end) = offset.checked_add(contents_size) else {
469 return Some(Err(Error::InvalidBufferSize));
470 };
471
472 if self.buf.len() < offset_end {
473 return Some(Err(Error::InvalidBufferSize));
474 }
475
476 BootInfoContents::Address {
477 content_buf: &self.buf[offset..offset_end],
478 }
479 }
480
481 BootInfoContentsFormat::Value => BootInfoContents::Value {
482 val: desc_raw.contents,
483 len: desc_raw.size as usize,
484 },
485 };
486
487 return Some(Ok(BootInfo {
488 name,
489 typ,
490 contents,
491 }));
492 }
493
494 None
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200495 }
496}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200501 use uuid::uuid;
502
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100503 // TODO: add tests with a known correct boot info blob
504
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200505 #[test]
506 fn boot_info() {
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100507 let desc1 = BootInfo {
508 name: BootInfoName::NullTermString(c"test1234test123"),
509 typ: BootInfoType::Impdef(BootInfoImpdefId(0x2b)),
510 contents: BootInfoContents::Value {
511 val: 0xdeadbeef,
512 len: 4,
513 },
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200514 };
515
516 let fdt = [0u8; 0xff];
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100517 let desc2 = BootInfo {
518 name: BootInfoName::Uuid(uuid!("12345678-abcd-dcba-1234-123456789abc")),
519 typ: BootInfoType::Std(BootInfoStdId::Fdt),
520 contents: BootInfoContents::Address { content_buf: &fdt },
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200521 };
522
523 let mut buf = [0u8; 0x1ff];
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100524 let buf_addr = buf.as_ptr() as usize;
525 BootInfo::pack(&[desc1.clone(), desc2.clone()], &mut buf, Some(buf_addr));
526 let mut descriptors = BootInfoIterator::new(&buf).unwrap();
527 let desc1_check = descriptors.next().unwrap().unwrap();
528 let desc2_check = descriptors.next().unwrap().unwrap();
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200529
Balint Dobszayb2ff2bc2024-12-19 18:59:38 +0100530 assert_eq!(desc1, desc1_check);
531 assert_eq!(desc2, desc2_check);
Balint Dobszay5bf492f2024-07-29 17:21:32 +0200532 }
533}