arm_ffa/
boot_info.rs

1// 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
4//! Implementation of the FF-A Boot information protocol.
5//!
6//! An SP or SPMC could rely on boot information for their initialization e.g. a flattened device
7//! tree with nodes to describe the devices and memory regions assigned to the SP or SPMC. FF-A
8//! specifies a protocol that can be used by a producer to pass boot information to a consumer at a
9//! Secure FF-A instance. The Framework assumes that the boot information protocol is used by a
10//! producer and consumer pair that reside at adjacent exception levels as listed below.
11//! - SPMD (producer) and an SPMC (consumer) in either S-EL1 or S-EL2.
12//! - An SPMC (producer) and SP (consumer) pair listed below.
13//!   - EL3 SPMC and a Logical S-EL1 SP.
14//!   - S-EL2 SPMC and Physical S-EL1 SP.
15//!   - EL3 SPMC and a S-EL0 SP.
16//!   - S-EL2 SPMC and a S-EL0 SP.
17//!   - S-EL1 SPMC and a S-EL0 SP.
18
19use core::ffi::CStr;
20use thiserror::Error;
21use uuid::Uuid;
22use zerocopy::{FromBytes, IntoBytes};
23
24// This module uses FF-A v1.1 types by default.
25// FF-A v1.2 didn't introduce any changes to the data stuctures used by this module.
26use crate::{
27    ffa_v1_1::{boot_info_descriptor, boot_info_header},
28    Version,
29};
30
31/// Rich error types returned by this module. Should be converted to [`crate::FfaError`] when used
32/// with the `FFA_ERROR` interface.
33#[derive(Debug, Error)]
34pub enum Error {
35    #[error("Invalid standard type {0}")]
36    InvalidStdType(u8),
37    #[error("Invalid type {0}")]
38    InvalidType(u8),
39    #[error("Invalid contents format {0}")]
40    InvalidContentsFormat(u16),
41    #[error("Invalid name format {0}")]
42    InvalidNameFormat(u16),
43    #[error("Invalid name")]
44    InvalidName,
45    #[error("Invalid flags {0}")]
46    InvalidFlags(u16),
47    #[error("Invalid header size or alignment")]
48    InvalidHeader,
49    #[error("Invalid buffer size")]
50    InvalidBufferSize,
51    #[error("Invalid signature")]
52    InvalidSignature,
53    #[error("Invalid version {0}")]
54    InvalidVersion(Version),
55    #[error("Malformed descriptor")]
56    MalformedDescriptor,
57}
58
59impl From<Error> for crate::FfaError {
60    fn from(_value: Error) -> Self {
61        Self::InvalidParameters
62    }
63}
64
65/// Name of boot information descriptor.
66#[derive(Clone, Debug, PartialEq, Eq)]
67pub enum BootInfoName<'a> {
68    NullTermString(&'a CStr),
69    Uuid(Uuid),
70}
71
72/// ID for supported standard boot information types.
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74#[repr(u8)]
75pub enum BootInfoStdId {
76    Fdt = Self::FDT,
77    Hob = Self::HOB,
78}
79
80impl TryFrom<u8> for BootInfoStdId {
81    type Error = Error;
82
83    fn try_from(value: u8) -> Result<Self, Self::Error> {
84        match value {
85            Self::FDT => Ok(BootInfoStdId::Fdt),
86            Self::HOB => Ok(BootInfoStdId::Hob),
87            _ => Err(Error::InvalidStdType(value)),
88        }
89    }
90}
91
92impl BootInfoStdId {
93    const FDT: u8 = 0;
94    const HOB: u8 = 1;
95}
96
97/// ID for implementation defined boot information type.
98#[derive(Clone, Copy, Debug, PartialEq, Eq)]
99pub struct BootInfoImpdefId(pub u8);
100
101impl From<u8> for BootInfoImpdefId {
102    fn from(value: u8) -> Self {
103        Self(value)
104    }
105}
106
107/// Boot information type.
108#[derive(Clone, Copy, Debug, PartialEq, Eq)]
109pub enum BootInfoType {
110    Std(BootInfoStdId),
111    Impdef(BootInfoImpdefId),
112}
113
114impl TryFrom<u8> for BootInfoType {
115    type Error = Error;
116
117    fn try_from(value: u8) -> Result<Self, Self::Error> {
118        match (value >> Self::TYPE_SHIFT) & Self::TYPE_MASK {
119            Self::TYPE_STANDARD => Ok(BootInfoType::Std((value & Self::ID_MASK).try_into()?)),
120            Self::TYPE_IMPDEF => Ok(BootInfoType::Impdef((value & Self::ID_MASK).into())),
121            _ => Err(Error::InvalidType(value)),
122        }
123    }
124}
125
126impl From<BootInfoType> for u8 {
127    fn from(value: BootInfoType) -> Self {
128        match value {
129            BootInfoType::Std(std_type) => {
130                std_type as u8 | (BootInfoType::TYPE_STANDARD << BootInfoType::TYPE_SHIFT)
131            }
132            BootInfoType::Impdef(impdef_type) => {
133                impdef_type.0 | (BootInfoType::TYPE_IMPDEF << BootInfoType::TYPE_SHIFT)
134            }
135        }
136    }
137}
138
139impl BootInfoType {
140    // This field contains the boot info type at bit[7] and the boot info identifier in bits[6:0]
141    const TYPE_SHIFT: usize = 7;
142    const TYPE_MASK: u8 = 0b1;
143    const TYPE_STANDARD: u8 = 0b0;
144    const TYPE_IMPDEF: u8 = 0b1;
145    // Mask for boot info identifier in bits[6:0]
146    const ID_MASK: u8 = 0b0111_1111;
147}
148
149/// Boot information contents.
150#[derive(Clone, Copy, Debug, PartialEq, Eq)]
151pub enum BootInfoContents<'a> {
152    Address { content_buf: &'a [u8] },
153    Value { val: u64, len: usize },
154}
155
156#[derive(Clone, Copy, Debug, PartialEq, Eq)]
157#[repr(u16)]
158enum BootInfoContentsFormat {
159    Address = Self::ADDRESS << Self::SHIFT,
160    Value = Self::VALUE << Self::SHIFT,
161}
162
163impl TryFrom<u16> for BootInfoContentsFormat {
164    type Error = Error;
165
166    fn try_from(value: u16) -> Result<Self, Self::Error> {
167        match (value >> Self::SHIFT) & Self::MASK {
168            Self::ADDRESS => Ok(BootInfoContentsFormat::Address),
169            Self::VALUE => Ok(BootInfoContentsFormat::Value),
170            _ => Err(Error::InvalidContentsFormat(value)),
171        }
172    }
173}
174
175impl BootInfoContentsFormat {
176    const SHIFT: usize = 2;
177    const MASK: u16 = 0b11;
178    const ADDRESS: u16 = 0b00;
179    const VALUE: u16 = 0b01;
180}
181
182#[derive(Clone, Copy, Debug, PartialEq, Eq)]
183#[repr(u16)]
184enum BootInfoNameFormat {
185    String = Self::STRING << Self::SHIFT,
186    Uuid = Self::UUID << Self::SHIFT,
187}
188
189impl TryFrom<u16> for BootInfoNameFormat {
190    type Error = Error;
191
192    fn try_from(value: u16) -> Result<Self, Self::Error> {
193        match (value >> Self::SHIFT) & Self::MASK {
194            Self::STRING => Ok(BootInfoNameFormat::String),
195            Self::UUID => Ok(BootInfoNameFormat::Uuid),
196            _ => Err(Error::InvalidNameFormat(value)),
197        }
198    }
199}
200
201impl BootInfoNameFormat {
202    const SHIFT: usize = 0;
203    const MASK: u16 = 0b11;
204    const STRING: u16 = 0b00;
205    const UUID: u16 = 0b01;
206}
207
208#[derive(Clone, Copy, Debug, PartialEq, Eq)]
209struct BootInfoFlags {
210    contents_format: BootInfoContentsFormat,
211    name_format: BootInfoNameFormat,
212}
213
214impl TryFrom<u16> for BootInfoFlags {
215    type Error = Error;
216
217    fn try_from(value: u16) -> Result<Self, Self::Error> {
218        // bits[15:4]: Reserved (MBZ)
219        if value >> 4 == 0 {
220            Ok(Self {
221                contents_format: BootInfoContentsFormat::try_from(value)?,
222                name_format: BootInfoNameFormat::try_from(value)?,
223            })
224        } else {
225            Err(Error::InvalidFlags(value))
226        }
227    }
228}
229
230impl From<BootInfoFlags> for u16 {
231    fn from(value: BootInfoFlags) -> Self {
232        value.contents_format as u16 | value.name_format as u16
233    }
234}
235
236/// Boot information descriptor.
237#[derive(Clone, Debug, PartialEq, Eq)]
238pub struct BootInfo<'a> {
239    pub name: BootInfoName<'a>,
240    pub typ: BootInfoType,
241    pub contents: BootInfoContents<'a>,
242}
243
244impl BootInfo<'_> {
245    /// Serialize a list of boot information descriptors into a buffer. The `mapped_addr` parameter
246    /// should contain the address of the buffer in the consumers translation regime (typically a
247    /// virtual address where the buffer is mapped to). This is necessary since there are
248    /// self-references within the serialized data structure which must be described with an
249    /// absolute address according to the FF-A spec.
250    pub fn pack(
251        version: Version,
252        descriptors: &[BootInfo],
253        buf: &mut [u8],
254        mapped_addr: Option<usize>,
255    ) {
256        assert!((Version(1, 1)..=Version(1, 2)).contains(&version));
257
258        // Offset from the base of the header to the first element in the boot info descriptor array
259        // Must be 8 byte aligned, but otherwise we're free to choose any value here.
260        // Let's just pack the array right after the header.
261        const DESC_ARRAY_OFFSET: usize = size_of::<boot_info_header>().next_multiple_of(8);
262        const DESC_SIZE: usize = size_of::<boot_info_descriptor>();
263
264        assert!(buf.len() <= u32::MAX as usize);
265
266        let desc_cnt = descriptors.len();
267
268        // Add the already known fields, later we have to add the sizes referenced by the individual
269        // descriptors
270
271        // The Size of boot information blob field specifies the size of the blob that spans one or
272        // more contiguous 4K pages used by the producer to populate it. It is calculated by adding
273        // the following values:
274        // 1. Boot information descriptor array offset
275        // 2. Product of Boot information descriptor count and Boot information descriptor size.
276        // 3. Total size of all boot information referenced by boot information descriptors.
277        //    This is determined by adding the values in the Size field of each boot information
278        //    descriptor whose Contents field contains an address.
279        // 4. Any padding between,
280        //    1. The boot information descriptor array and the boot information referenced from it.
281        //    2. Distinct instances of boot information referenced from the boot information
282        //       descriptor array.
283        let mut total_offset = 0usize;
284
285        // No. 1 from the "Size of boot information blob" list
286        total_offset = total_offset.checked_add(DESC_ARRAY_OFFSET).unwrap();
287
288        // No. 2 from the "Size of boot information blob" list
289        total_offset = total_offset
290            .checked_add(desc_cnt.checked_mul(DESC_SIZE).unwrap())
291            .unwrap();
292
293        // Fail early if the buffer is too small
294        assert!(total_offset <= buf.len());
295
296        // Fill the boot info descriptor array, all offset based from DESC_ARRAY_OFFSET
297        let mut desc_array_offset = DESC_ARRAY_OFFSET;
298
299        for desc in descriptors {
300            let mut desc_raw = boot_info_descriptor::default();
301
302            let name_format = match &desc.name {
303                BootInfoName::NullTermString(name) => {
304                    // count_bytes() doesn't include nul terminator
305                    let name_len = name.count_bytes().min(15);
306                    desc_raw.name[..name_len].copy_from_slice(&name.to_bytes()[..name_len]);
307                    // Add nul terminator and zero fill the rest
308                    desc_raw.name[name_len..].fill(0);
309
310                    BootInfoNameFormat::String
311                }
312                BootInfoName::Uuid(uuid) => {
313                    desc_raw.name.copy_from_slice(&uuid.to_bytes_le());
314                    BootInfoNameFormat::Uuid
315                }
316            };
317
318            let contents_format = match desc.contents {
319                BootInfoContents::Address { content_buf } => {
320                    // We have to copy the contents referenced by the boot info descriptor into the
321                    // boot info blob. At this offset we're after the boot info header and all of
322                    // the boot info descriptors. The contents referenced from the individual boot
323                    // info descriptors will get copied to this starting address. The 8 byte
324                    // alignment is not explicitly mentioned by the spec, but it's better to have it
325                    // anyway.
326                    // No. 4 from the "Size of boot information blob" list
327                    total_offset = total_offset.next_multiple_of(8);
328
329                    // The mapped_addr argument contains the address where buf is mapped to in the
330                    // consumer's translation regime. If it's None, we assume identity mapping is
331                    // used, so the buffer's address stays the same.
332                    let buf_addr = mapped_addr.unwrap_or(buf.as_ptr() as usize);
333
334                    // The content's address in the consumer's translation regime will be the
335                    // buffer's address in the consumer's translation regime plus the offset of the
336                    // content within the boot info blob.
337                    let content_addr = buf_addr.checked_add(total_offset).unwrap();
338
339                    // Check if the content fits before copying
340                    let content_len = content_buf.len();
341                    total_offset.checked_add(content_len).unwrap();
342
343                    // Do the copy and increase the total size
344                    // No. 3 from the "Size of boot information blob" list
345                    buf[total_offset..total_offset + content_len].copy_from_slice(content_buf);
346                    total_offset += content_len;
347
348                    desc_raw.contents = content_addr as u64;
349                    desc_raw.size = content_len as u32;
350
351                    BootInfoContentsFormat::Address
352                }
353                BootInfoContents::Value { val, len } => {
354                    assert!((1..=8).contains(&len));
355                    desc_raw.contents = val;
356                    desc_raw.size = len as u32;
357
358                    BootInfoContentsFormat::Value
359                }
360            };
361
362            let flags = BootInfoFlags {
363                contents_format,
364                name_format,
365            };
366
367            desc_raw.flags = flags.into();
368            desc_raw.typ = desc.typ.into();
369
370            desc_raw
371                .write_to_prefix(&mut buf[desc_array_offset..])
372                .unwrap();
373            desc_array_offset += DESC_SIZE;
374        }
375
376        let header_raw = boot_info_header {
377            signature: 0x0ffa,
378            version: version.into(),
379            boot_info_blob_size: total_offset as u32,
380            boot_info_desc_size: DESC_SIZE as u32,
381            boot_info_desc_count: desc_cnt as u32,
382            boot_info_array_offset: DESC_ARRAY_OFFSET as u32,
383            reserved: 0,
384        };
385
386        header_raw.write_to_prefix(buf).unwrap();
387    }
388
389    /// Validate and return the boot information header
390    fn get_header(version: Version, buf: &[u8]) -> Result<&boot_info_header, Error> {
391        let (header_raw, _) =
392            boot_info_header::ref_from_prefix(buf).map_err(|_| Error::InvalidHeader)?;
393
394        if header_raw.signature != 0x0ffa {
395            return Err(Error::InvalidSignature);
396        }
397
398        let header_version = header_raw
399            .version
400            .try_into()
401            .map_err(|_| Error::InvalidHeader)?;
402        if header_version != version {
403            return Err(Error::InvalidVersion(header_version));
404        }
405
406        Ok(header_raw)
407    }
408
409    /// Get the size of the boot information blob spanning contiguous memory. This enables a
410    /// consumer to map all of the boot information blob in its translation regime or copy it to
411    /// another memory location without parsing each element in the boot information descriptor
412    /// array.
413    pub fn get_blob_size(version: Version, buf: &[u8]) -> Result<usize, Error> {
414        if !(Version(1, 1)..=Version(1, 2)).contains(&version) {
415            return Err(Error::InvalidVersion(version));
416        }
417
418        let header_raw = Self::get_header(version, buf)?;
419
420        Ok(header_raw.boot_info_blob_size as usize)
421    }
422}
423
424/// Iterator of boot information descriptors.
425pub struct BootInfoIterator<'a> {
426    buf: &'a [u8],
427    offset: usize,
428    desc_count: usize,
429    desc_size: usize,
430}
431
432impl<'a> BootInfoIterator<'a> {
433    /// Create an iterator of boot information descriptors from a buffer.
434    pub fn new(version: Version, buf: &'a [u8]) -> Result<Self, Error> {
435        let header_raw = BootInfo::get_header(version, buf)?;
436
437        if buf.len() < header_raw.boot_info_blob_size as usize {
438            return Err(Error::InvalidBufferSize);
439        }
440
441        if header_raw.boot_info_desc_size as usize != size_of::<boot_info_descriptor>() {
442            return Err(Error::MalformedDescriptor);
443        }
444
445        let Some(total_desc_size) = header_raw
446            .boot_info_desc_count
447            .checked_mul(header_raw.boot_info_desc_size)
448            .and_then(|x| x.checked_add(header_raw.boot_info_array_offset))
449        else {
450            return Err(Error::InvalidBufferSize);
451        };
452
453        if buf.len() < total_desc_size as usize {
454            return Err(Error::InvalidBufferSize);
455        }
456
457        Ok(Self {
458            buf,
459            offset: header_raw.boot_info_array_offset as usize,
460            desc_count: header_raw.boot_info_desc_count as usize,
461            desc_size: header_raw.boot_info_desc_size as usize,
462        })
463    }
464}
465
466impl<'a> Iterator for BootInfoIterator<'a> {
467    type Item = Result<BootInfo<'a>, Error>;
468
469    fn next(&mut self) -> Option<Self::Item> {
470        if self.desc_count > 0 {
471            let desc_offset = self.offset;
472            self.offset += self.desc_size;
473            self.desc_count -= 1;
474
475            let Ok(desc_raw) = boot_info_descriptor::ref_from_bytes(
476                &self.buf[desc_offset..desc_offset + self.desc_size],
477            ) else {
478                return Some(Err(Error::MalformedDescriptor));
479            };
480
481            if desc_raw.reserved != 0 {
482                return Some(Err(Error::MalformedDescriptor));
483            }
484
485            let typ: BootInfoType = match desc_raw.typ.try_into() {
486                Ok(v) => v,
487                Err(e) => return Some(Err(e)),
488            };
489
490            let flags: BootInfoFlags = match desc_raw.flags.try_into() {
491                Ok(v) => v,
492                Err(e) => return Some(Err(e)),
493            };
494
495            let name = match flags.name_format {
496                BootInfoNameFormat::String => {
497                    let Ok(name_str) = CStr::from_bytes_until_nul(desc_raw.name.as_bytes()) else {
498                        return Some(Err(Error::InvalidName));
499                    };
500                    BootInfoName::NullTermString(name_str)
501                }
502                BootInfoNameFormat::Uuid => BootInfoName::Uuid(Uuid::from_bytes_le(desc_raw.name)),
503            };
504
505            let contents = match flags.contents_format {
506                BootInfoContentsFormat::Address => {
507                    let contents = desc_raw.contents as usize;
508                    let contents_size = desc_raw.size as usize;
509
510                    let Some(offset) = contents.checked_sub(self.buf.as_ptr() as usize) else {
511                        return Some(Err(Error::InvalidBufferSize));
512                    };
513
514                    let Some(offset_end) = offset.checked_add(contents_size) else {
515                        return Some(Err(Error::InvalidBufferSize));
516                    };
517
518                    if self.buf.len() < offset_end {
519                        return Some(Err(Error::InvalidBufferSize));
520                    }
521
522                    BootInfoContents::Address {
523                        content_buf: &self.buf[offset..offset_end],
524                    }
525                }
526
527                BootInfoContentsFormat::Value => {
528                    let len = desc_raw.size as usize;
529                    if (1..=8).contains(&len) {
530                        BootInfoContents::Value {
531                            val: desc_raw.contents,
532                            len,
533                        }
534                    } else {
535                        return Some(Err(Error::MalformedDescriptor));
536                    }
537                }
538            };
539
540            return Some(Ok(BootInfo {
541                name,
542                typ,
543                contents,
544            }));
545        }
546
547        None
548    }
549}
550
551#[cfg(test)]
552mod tests {
553    use super::*;
554    use uuid::uuid;
555
556    // TODO: add tests with a known correct boot info blob
557
558    #[test]
559    fn boot_info() {
560        let desc1 = BootInfo {
561            name: BootInfoName::NullTermString(c"test1234test123"),
562            typ: BootInfoType::Impdef(BootInfoImpdefId(0x2b)),
563            contents: BootInfoContents::Value {
564                val: 0xdeadbeef,
565                len: 4,
566            },
567        };
568
569        let fdt = [0u8; 0xff];
570        let desc2 = BootInfo {
571            name: BootInfoName::Uuid(uuid!("12345678-abcd-dcba-1234-123456789abc")),
572            typ: BootInfoType::Std(BootInfoStdId::Fdt),
573            contents: BootInfoContents::Address { content_buf: &fdt },
574        };
575
576        let mut buf = [0u8; 0x1ff];
577        let buf_addr = buf.as_ptr() as usize;
578        BootInfo::pack(
579            Version(1, 1),
580            &[desc1.clone(), desc2.clone()],
581            &mut buf,
582            Some(buf_addr),
583        );
584        let mut descriptors = BootInfoIterator::new(Version(1, 1), &buf).unwrap();
585        let desc1_check = descriptors.next().unwrap().unwrap();
586        let desc2_check = descriptors.next().unwrap().unwrap();
587
588        assert_eq!(desc1, desc1_check);
589        assert_eq!(desc2, desc2_check);
590    }
591}