blob: f62a580d6ab1d64cf0ea33c9a16cea26ea5ada53 [file] [log] [blame]
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +01001// Copyright 2022, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Support for the pvmfw configuration data format.
16
17use crate::helpers;
18use core::fmt;
19use core::mem;
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +000020use core::ops::Range;
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010021use core::result;
22
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000023/// Configuration data header.
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010024#[repr(C, packed)]
25#[derive(Clone, Copy, Debug)]
26struct Header {
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000027 /// Magic number; must be `Header::MAGIC`.
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010028 magic: u32,
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000029 /// Version of the header format.
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010030 version: u32,
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000031 /// Total size of the configuration data.
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010032 total_size: u32,
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000033 /// Feature flags; currently reserved and must be zero.
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010034 flags: u32,
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000035 /// (offset, size) pairs used to locate individual entries appended to the header.
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010036 entries: [HeaderEntry; Entry::COUNT],
37}
38
39#[derive(Debug)]
40pub enum Error {
41 /// Reserved region can't fit configuration header.
42 BufferTooSmall,
43 /// Header doesn't contain the expect magic value.
44 InvalidMagic,
45 /// Version of the header isn't supported.
46 UnsupportedVersion(u16, u16),
47 /// Header sets flags incorrectly or uses reserved flags.
48 InvalidFlags(u32),
49 /// Header describes configuration data that doesn't fit in the expected buffer.
50 InvalidSize(usize),
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +000051 /// Header entry is missing.
52 MissingEntry(Entry),
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010053 /// Header entry is invalid.
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +000054 InvalidEntry(Entry, EntryError),
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010055}
56
57impl fmt::Display for Error {
58 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
59 match self {
60 Self::BufferTooSmall => write!(f, "Reserved region is smaller than config header"),
61 Self::InvalidMagic => write!(f, "Wrong magic number"),
62 Self::UnsupportedVersion(x, y) => write!(f, "Version {x}.{y} not supported"),
63 Self::InvalidFlags(v) => write!(f, "Flags value {v:#x} is incorrect or reserved"),
64 Self::InvalidSize(sz) => write!(f, "Total size ({sz:#x}) overflows reserved region"),
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +000065 Self::MissingEntry(entry) => write!(f, "Mandatory {entry:?} entry is missing"),
66 Self::InvalidEntry(entry, e) => write!(f, "Invalid {entry:?} entry: {e}"),
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010067 }
68 }
69}
70
71pub type Result<T> = result::Result<T, Error>;
72
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +000073#[derive(Debug)]
74pub enum EntryError {
75 /// Offset isn't between the fixed minimum value and size of configuration data.
76 InvalidOffset(usize),
77 /// Size must be zero when offset is and not be when it isn't.
78 InvalidSize(usize),
79 /// Entry isn't fully within the configuration data structure.
80 OutOfBounds { offset: usize, size: usize, limit: usize },
81}
82
83impl fmt::Display for EntryError {
84 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85 match self {
86 Self::InvalidOffset(offset) => write!(f, "Invalid offset: {offset:#x?}"),
87 Self::InvalidSize(sz) => write!(f, "Invalid size: {sz:#x?}"),
88 Self::OutOfBounds { offset, size, limit } => {
89 let range = Header::PADDED_SIZE..*limit;
90 let entry = *offset..(*offset + *size);
91 write!(f, "Out of bounds: {entry:#x?} must be within range {range:#x?}")
92 }
93 }
94 }
95}
96
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010097impl Header {
98 const MAGIC: u32 = u32::from_ne_bytes(*b"pvmf");
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000099 const VERSION_1_0: u32 = Self::version(1, 0);
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100100 const PADDED_SIZE: usize =
101 helpers::unchecked_align_up(mem::size_of::<Self>(), mem::size_of::<u64>());
102
103 pub const fn version(major: u16, minor: u16) -> u32 {
104 ((major as u32) << 16) | (minor as u32)
105 }
106
107 pub const fn version_tuple(&self) -> (u16, u16) {
108 ((self.version >> 16) as u16, self.version as u16)
109 }
110
111 pub fn total_size(&self) -> usize {
112 self.total_size as usize
113 }
114
115 pub fn body_size(&self) -> usize {
116 self.total_size() - Self::PADDED_SIZE
117 }
118
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000119 fn get_body_range(&self, entry: Entry) -> Result<Option<Range<usize>>> {
120 let e = self.entries[entry as usize];
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +0000121 let offset = e.offset as usize;
122 let size = e.size as usize;
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000123
Pierre-Clément Tosi706a10c2022-12-14 10:33:24 +0000124 match self._get_body_range(offset, size) {
125 Ok(r) => Ok(r),
126 Err(EntryError::InvalidSize(0)) => {
127 // As our bootloader currently uses this (non-compliant) case, permit it for now.
128 log::warn!("Config entry {entry:?} uses non-zero offset with zero size");
129 // TODO(b/262181812): Either make this case valid or fix the bootloader.
130 Ok(None)
131 }
132 Err(e) => Err(Error::InvalidEntry(entry, e)),
133 }
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +0000134 }
135
136 fn _get_body_range(
137 &self,
138 offset: usize,
139 size: usize,
140 ) -> result::Result<Option<Range<usize>>, EntryError> {
141 match (offset, size) {
142 (0, 0) => Ok(None),
143 (0, size) | (_, size @ 0) => Err(EntryError::InvalidSize(size)),
144 _ => {
145 let start = offset
146 .checked_sub(Header::PADDED_SIZE)
147 .ok_or(EntryError::InvalidOffset(offset))?;
148 let end = start
149 .checked_add(size)
150 .filter(|x| *x <= self.body_size())
151 .ok_or(EntryError::OutOfBounds { offset, size, limit: self.total_size() })?;
152
153 Ok(Some(start..end))
154 }
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000155 }
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100156 }
157}
158
159#[derive(Clone, Copy, Debug)]
160pub enum Entry {
161 Bcc = 0,
162 DebugPolicy = 1,
163}
164
165impl Entry {
166 const COUNT: usize = 2;
167}
168
169#[repr(packed)]
170#[derive(Clone, Copy, Debug)]
171struct HeaderEntry {
172 offset: u32,
173 size: u32,
174}
175
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100176#[derive(Debug)]
177pub struct Config<'a> {
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100178 body: &'a mut [u8],
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000179 bcc_range: Range<usize>,
180 dp_range: Option<Range<usize>>,
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100181}
182
183impl<'a> Config<'a> {
184 /// Take ownership of a pvmfw configuration consisting of its header and following entries.
185 ///
186 /// SAFETY - 'data' should respect the alignment of Header.
187 pub unsafe fn new(data: &'a mut [u8]) -> Result<Self> {
188 let header = data.get(..Header::PADDED_SIZE).ok_or(Error::BufferTooSmall)?;
189
190 let header = &*(header.as_ptr() as *const Header);
191
192 if header.magic != Header::MAGIC {
193 return Err(Error::InvalidMagic);
194 }
195
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +0000196 if header.version != Header::VERSION_1_0 {
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100197 let (major, minor) = header.version_tuple();
198 return Err(Error::UnsupportedVersion(major, minor));
199 }
200
201 if header.flags != 0 {
202 return Err(Error::InvalidFlags(header.flags));
203 }
204
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000205 let bcc_range =
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +0000206 header.get_body_range(Entry::Bcc)?.ok_or(Error::MissingEntry(Entry::Bcc))?;
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000207 let dp_range = header.get_body_range(Entry::DebugPolicy)?;
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100208
209 let body = data
210 .get_mut(Header::PADDED_SIZE..)
211 .ok_or(Error::BufferTooSmall)?
212 .get_mut(..header.body_size())
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000213 .ok_or_else(|| Error::InvalidSize(header.total_size()))?;
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100214
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000215 Ok(Self { body, bcc_range, dp_range })
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100216 }
217
218 /// Get slice containing the platform BCC.
Pierre-Clément Tosiefe780c2023-02-21 21:36:30 +0000219 pub fn get_entries(&mut self) -> (&mut [u8], Option<&mut [u8]>) {
220 let bcc_start = self.bcc_range.start;
221 let bcc_end = self.bcc_range.len();
222 let (_, rest) = self.body.split_at_mut(bcc_start);
223 let (bcc, rest) = rest.split_at_mut(bcc_end);
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100224
Pierre-Clément Tosiefe780c2023-02-21 21:36:30 +0000225 let dp = if let Some(dp_range) = &self.dp_range {
226 let dp_start = dp_range.start.checked_sub(self.bcc_range.end).unwrap();
227 let dp_end = dp_range.len();
228 let (_, rest) = rest.split_at_mut(dp_start);
229 let (dp, _) = rest.split_at_mut(dp_end);
230 Some(dp)
231 } else {
232 None
233 };
234
235 (bcc, dp)
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100236 }
237}