blob: ec5c6dc0b8b48f57b07d9996d830be37622be8a2 [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
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010017use core::fmt;
18use core::mem;
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +000019use core::ops::Range;
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010020use core::result;
Alice Wangeacb7382023-06-05 12:53:54 +000021use vmbase::util::unchecked_align_up;
Alan Stokesa0e42962023-04-14 17:59:50 +010022use zerocopy::{FromBytes, LayoutVerified};
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010023
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000024/// Configuration data header.
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010025#[repr(C, packed)]
Alan Stokesa0e42962023-04-14 17:59:50 +010026#[derive(Clone, Copy, Debug, FromBytes)]
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010027struct Header {
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000028 /// Magic number; must be `Header::MAGIC`.
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010029 magic: u32,
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000030 /// Version of the header format.
Jaewan Kim6b7fb3c2023-08-08 18:00:43 +090031 version: Version,
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000032 /// Total size of the configuration data.
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010033 total_size: u32,
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000034 /// Feature flags; currently reserved and must be zero.
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010035 flags: u32,
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000036 /// (offset, size) pairs used to locate individual entries appended to the header.
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010037 entries: [HeaderEntry; Entry::COUNT],
38}
39
40#[derive(Debug)]
41pub enum Error {
42 /// Reserved region can't fit configuration header.
43 BufferTooSmall,
Alan Stokesa0e42962023-04-14 17:59:50 +010044 /// Header has the wrong alignment
45 HeaderMisaligned,
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010046 /// Header doesn't contain the expect magic value.
47 InvalidMagic,
48 /// Version of the header isn't supported.
Jaewan Kim6b7fb3c2023-08-08 18:00:43 +090049 UnsupportedVersion(Version),
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010050 /// Header sets flags incorrectly or uses reserved flags.
51 InvalidFlags(u32),
52 /// Header describes configuration data that doesn't fit in the expected buffer.
53 InvalidSize(usize),
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +000054 /// Header entry is missing.
55 MissingEntry(Entry),
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010056 /// Header entry is invalid.
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +000057 InvalidEntry(Entry, EntryError),
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010058}
59
60impl fmt::Display for Error {
61 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
62 match self {
63 Self::BufferTooSmall => write!(f, "Reserved region is smaller than config header"),
Alan Stokesa0e42962023-04-14 17:59:50 +010064 Self::HeaderMisaligned => write!(f, "Reserved region is misaligned"),
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010065 Self::InvalidMagic => write!(f, "Wrong magic number"),
Jaewan Kim6b7fb3c2023-08-08 18:00:43 +090066 Self::UnsupportedVersion(v) => write!(f, "Version {v} not supported"),
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010067 Self::InvalidFlags(v) => write!(f, "Flags value {v:#x} is incorrect or reserved"),
68 Self::InvalidSize(sz) => write!(f, "Total size ({sz:#x}) overflows reserved region"),
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +000069 Self::MissingEntry(entry) => write!(f, "Mandatory {entry:?} entry is missing"),
70 Self::InvalidEntry(entry, e) => write!(f, "Invalid {entry:?} entry: {e}"),
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010071 }
72 }
73}
74
75pub type Result<T> = result::Result<T, Error>;
76
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +000077#[derive(Debug)]
78pub enum EntryError {
79 /// Offset isn't between the fixed minimum value and size of configuration data.
80 InvalidOffset(usize),
81 /// Size must be zero when offset is and not be when it isn't.
82 InvalidSize(usize),
83 /// Entry isn't fully within the configuration data structure.
84 OutOfBounds { offset: usize, size: usize, limit: usize },
85}
86
87impl fmt::Display for EntryError {
88 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
89 match self {
90 Self::InvalidOffset(offset) => write!(f, "Invalid offset: {offset:#x?}"),
91 Self::InvalidSize(sz) => write!(f, "Invalid size: {sz:#x?}"),
92 Self::OutOfBounds { offset, size, limit } => {
93 let range = Header::PADDED_SIZE..*limit;
94 let entry = *offset..(*offset + *size);
95 write!(f, "Out of bounds: {entry:#x?} must be within range {range:#x?}")
96 }
97 }
98 }
99}
100
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100101impl Header {
102 const MAGIC: u32 = u32::from_ne_bytes(*b"pvmf");
Jaewan Kim6b7fb3c2023-08-08 18:00:43 +0900103 const VERSION_1_0: Version = Version { major: 1, minor: 0 };
Alice Wangeacb7382023-06-05 12:53:54 +0000104 const PADDED_SIZE: usize = unchecked_align_up(mem::size_of::<Self>(), mem::size_of::<u64>());
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100105
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100106 pub fn total_size(&self) -> usize {
107 self.total_size as usize
108 }
109
110 pub fn body_size(&self) -> usize {
111 self.total_size() - Self::PADDED_SIZE
112 }
113
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000114 fn get_body_range(&self, entry: Entry) -> Result<Option<Range<usize>>> {
115 let e = self.entries[entry as usize];
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +0000116 let offset = e.offset as usize;
117 let size = e.size as usize;
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000118
Pierre-Clément Tosi706a10c2022-12-14 10:33:24 +0000119 match self._get_body_range(offset, size) {
120 Ok(r) => Ok(r),
121 Err(EntryError::InvalidSize(0)) => {
122 // As our bootloader currently uses this (non-compliant) case, permit it for now.
123 log::warn!("Config entry {entry:?} uses non-zero offset with zero size");
124 // TODO(b/262181812): Either make this case valid or fix the bootloader.
125 Ok(None)
126 }
127 Err(e) => Err(Error::InvalidEntry(entry, e)),
128 }
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +0000129 }
130
131 fn _get_body_range(
132 &self,
133 offset: usize,
134 size: usize,
135 ) -> result::Result<Option<Range<usize>>, EntryError> {
136 match (offset, size) {
137 (0, 0) => Ok(None),
138 (0, size) | (_, size @ 0) => Err(EntryError::InvalidSize(size)),
139 _ => {
140 let start = offset
141 .checked_sub(Header::PADDED_SIZE)
142 .ok_or(EntryError::InvalidOffset(offset))?;
143 let end = start
144 .checked_add(size)
145 .filter(|x| *x <= self.body_size())
146 .ok_or(EntryError::OutOfBounds { offset, size, limit: self.total_size() })?;
147
148 Ok(Some(start..end))
149 }
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000150 }
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100151 }
152}
153
154#[derive(Clone, Copy, Debug)]
155pub enum Entry {
156 Bcc = 0,
157 DebugPolicy = 1,
158}
159
160impl Entry {
161 const COUNT: usize = 2;
162}
163
164#[repr(packed)]
Alan Stokesa0e42962023-04-14 17:59:50 +0100165#[derive(Clone, Copy, Debug, FromBytes)]
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100166struct HeaderEntry {
167 offset: u32,
168 size: u32,
169}
170
Jaewan Kim6b7fb3c2023-08-08 18:00:43 +0900171#[repr(C, packed)]
172#[derive(Clone, Copy, Debug, Eq, FromBytes, PartialEq)]
173pub struct Version {
174 minor: u16,
175 major: u16,
176}
177
178impl fmt::Display for Version {
179 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
180 // Copy the fields to local variables to prevent unaligned access.
181 let (major, minor) = (self.major, self.minor);
182 write!(f, "{}.{}", major, minor)
183 }
184}
185
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100186#[derive(Debug)]
187pub struct Config<'a> {
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100188 body: &'a mut [u8],
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000189 bcc_range: Range<usize>,
190 dp_range: Option<Range<usize>>,
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100191}
192
193impl<'a> Config<'a> {
194 /// Take ownership of a pvmfw configuration consisting of its header and following entries.
Alan Stokesc3829f12023-06-02 15:02:23 +0100195 pub fn new(data: &'a mut [u8]) -> Result<Self> {
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100196 let header = data.get(..Header::PADDED_SIZE).ok_or(Error::BufferTooSmall)?;
197
Alan Stokesa0e42962023-04-14 17:59:50 +0100198 let (header, _) =
199 LayoutVerified::<_, Header>::new_from_prefix(header).ok_or(Error::HeaderMisaligned)?;
200 let header = header.into_ref();
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100201
202 if header.magic != Header::MAGIC {
203 return Err(Error::InvalidMagic);
204 }
205
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +0000206 if header.version != Header::VERSION_1_0 {
Jaewan Kim6b7fb3c2023-08-08 18:00:43 +0900207 return Err(Error::UnsupportedVersion(header.version));
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100208 }
209
210 if header.flags != 0 {
211 return Err(Error::InvalidFlags(header.flags));
212 }
213
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000214 let bcc_range =
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +0000215 header.get_body_range(Entry::Bcc)?.ok_or(Error::MissingEntry(Entry::Bcc))?;
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000216 let dp_range = header.get_body_range(Entry::DebugPolicy)?;
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100217
Alan Stokesa0e42962023-04-14 17:59:50 +0100218 let body_size = header.body_size();
219 let total_size = header.total_size();
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100220 let body = data
221 .get_mut(Header::PADDED_SIZE..)
222 .ok_or(Error::BufferTooSmall)?
Alan Stokesa0e42962023-04-14 17:59:50 +0100223 .get_mut(..body_size)
224 .ok_or(Error::InvalidSize(total_size))?;
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100225
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000226 Ok(Self { body, bcc_range, dp_range })
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100227 }
228
229 /// Get slice containing the platform BCC.
Pierre-Clément Tosiefe780c2023-02-21 21:36:30 +0000230 pub fn get_entries(&mut self) -> (&mut [u8], Option<&mut [u8]>) {
231 let bcc_start = self.bcc_range.start;
232 let bcc_end = self.bcc_range.len();
233 let (_, rest) = self.body.split_at_mut(bcc_start);
234 let (bcc, rest) = rest.split_at_mut(bcc_end);
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100235
Pierre-Clément Tosiefe780c2023-02-21 21:36:30 +0000236 let dp = if let Some(dp_range) = &self.dp_range {
237 let dp_start = dp_range.start.checked_sub(self.bcc_range.end).unwrap();
238 let dp_end = dp_range.len();
239 let (_, rest) = rest.split_at_mut(dp_start);
240 let (dp, _) = rest.split_at_mut(dp_end);
241 Some(dp)
242 } else {
243 None
244 };
245
246 (bcc, dp)
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100247 }
248}