blob: 2451ec941c256cb728cbe299b9f2197544f12861 [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;
Jaewan Kim3dfc0ab2023-09-27 14:00:59 +090019use core::num::NonZeroUsize;
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;
Pierre-Clément Tosice3e3132023-08-17 10:24:22 +010022use log::{info, warn};
Pierre-Clément Tosifd694c72023-08-14 14:24:07 +010023use static_assertions::const_assert_eq;
Pierre-Clément Tosi33001492023-08-14 17:57:30 +010024use vmbase::util::RangeExt;
Alan Stokesa0e42962023-04-14 17:59:50 +010025use zerocopy::{FromBytes, LayoutVerified};
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010026
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000027/// Configuration data header.
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010028#[repr(C, packed)]
Alan Stokesa0e42962023-04-14 17:59:50 +010029#[derive(Clone, Copy, Debug, FromBytes)]
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010030struct Header {
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000031 /// Magic number; must be `Header::MAGIC`.
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010032 magic: u32,
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000033 /// Version of the header format.
Jaewan Kim6b7fb3c2023-08-08 18:00:43 +090034 version: Version,
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000035 /// Total size of the configuration data.
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010036 total_size: u32,
Pierre-Clément Tosi43592a42022-12-19 14:17:38 +000037 /// Feature flags; currently reserved and must be zero.
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010038 flags: u32,
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010039}
40
41#[derive(Debug)]
42pub enum Error {
43 /// Reserved region can't fit configuration header.
44 BufferTooSmall,
Alan Stokesa0e42962023-04-14 17:59:50 +010045 /// Header has the wrong alignment
46 HeaderMisaligned,
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010047 /// Header doesn't contain the expect magic value.
48 InvalidMagic,
49 /// Version of the header isn't supported.
Jaewan Kim6b7fb3c2023-08-08 18:00:43 +090050 UnsupportedVersion(Version),
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010051 /// Header describes configuration data that doesn't fit in the expected buffer.
52 InvalidSize(usize),
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +000053 /// Header entry is missing.
54 MissingEntry(Entry),
Pierre-Clément Tosifd694c72023-08-14 14:24:07 +010055 /// Range described by entry does not fit within config data.
56 EntryOutOfBounds(Entry, Range<usize>, Range<usize>),
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010057}
58
59impl fmt::Display for Error {
60 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
61 match self {
62 Self::BufferTooSmall => write!(f, "Reserved region is smaller than config header"),
Alan Stokesa0e42962023-04-14 17:59:50 +010063 Self::HeaderMisaligned => write!(f, "Reserved region is misaligned"),
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010064 Self::InvalidMagic => write!(f, "Wrong magic number"),
Jaewan Kim6b7fb3c2023-08-08 18:00:43 +090065 Self::UnsupportedVersion(v) => write!(f, "Version {v} not supported"),
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010066 Self::InvalidSize(sz) => write!(f, "Total size ({sz:#x}) overflows reserved region"),
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +000067 Self::MissingEntry(entry) => write!(f, "Mandatory {entry:?} entry is missing"),
Pierre-Clément Tosifd694c72023-08-14 14:24:07 +010068 Self::EntryOutOfBounds(entry, range, limits) => {
69 write!(
70 f,
71 "Entry {entry:?} out of bounds: {range:#x?} must be within range {limits:#x?}"
72 )
73 }
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010074 }
75 }
76}
77
78pub type Result<T> = result::Result<T, Error>;
79
80impl Header {
81 const MAGIC: u32 = u32::from_ne_bytes(*b"pvmf");
Jaewan Kim6b7fb3c2023-08-08 18:00:43 +090082 const VERSION_1_0: Version = Version { major: 1, minor: 0 };
Jaewan Kimbf5978c2023-07-28 11:30:54 +000083 const VERSION_1_1: Version = Version { major: 1, minor: 1 };
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010084
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010085 pub fn total_size(&self) -> usize {
86 self.total_size as usize
87 }
88
Pierre-Clément Tosice3e3132023-08-17 10:24:22 +010089 pub fn body_lowest_bound(&self) -> Result<usize> {
Pierre-Clément Tosi33001492023-08-14 17:57:30 +010090 let entries_offset = mem::size_of::<Self>();
91
92 // Ensure that the entries are properly aligned and do not require padding.
93 const_assert_eq!(mem::align_of::<Header>() % mem::align_of::<HeaderEntry>(), 0);
94 const_assert_eq!(mem::size_of::<Header>() % mem::align_of::<HeaderEntry>(), 0);
95
96 let entries_size = self.entry_count()?.checked_mul(mem::size_of::<HeaderEntry>()).unwrap();
97
98 Ok(entries_offset.checked_add(entries_size).unwrap())
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010099 }
100
Pierre-Clément Tosi33001492023-08-14 17:57:30 +0100101 pub fn entry_count(&self) -> Result<usize> {
102 let last_entry = match self.version {
103 Self::VERSION_1_0 => Entry::DebugPolicy,
Jaewan Kimbf5978c2023-07-28 11:30:54 +0000104 Self::VERSION_1_1 => Entry::VmDtbo,
Pierre-Clément Tosice3e3132023-08-17 10:24:22 +0100105 v @ Version { major: 1, .. } => {
106 const LATEST: Version = Header::VERSION_1_1;
107 warn!("Parsing unknown config data version {v} as version {LATEST}");
108 return Ok(Entry::COUNT);
109 }
Pierre-Clément Tosi33001492023-08-14 17:57:30 +0100110 v => return Err(Error::UnsupportedVersion(v)),
Pierre-Clément Tosifd694c72023-08-14 14:24:07 +0100111 };
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000112
Pierre-Clément Tosi33001492023-08-14 17:57:30 +0100113 Ok(last_entry as usize + 1)
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100114 }
115}
116
117#[derive(Clone, Copy, Debug)]
118pub enum Entry {
Pierre-Clément Tosi33001492023-08-14 17:57:30 +0100119 Bcc,
120 DebugPolicy,
Jaewan Kimbf5978c2023-07-28 11:30:54 +0000121 VmDtbo,
Pierre-Clément Tosi33001492023-08-14 17:57:30 +0100122 #[allow(non_camel_case_types)] // TODO: Use mem::variant_count once stable.
123 _VARIANT_COUNT,
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100124}
125
126impl Entry {
Pierre-Clément Tosi33001492023-08-14 17:57:30 +0100127 const COUNT: usize = Self::_VARIANT_COUNT as usize;
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100128}
129
130#[repr(packed)]
Alan Stokesa0e42962023-04-14 17:59:50 +0100131#[derive(Clone, Copy, Debug, FromBytes)]
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100132struct HeaderEntry {
133 offset: u32,
134 size: u32,
135}
136
Jaewan Kim6b7fb3c2023-08-08 18:00:43 +0900137#[repr(C, packed)]
138#[derive(Clone, Copy, Debug, Eq, FromBytes, PartialEq)]
139pub struct Version {
140 minor: u16,
141 major: u16,
142}
143
144impl fmt::Display for Version {
145 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146 // Copy the fields to local variables to prevent unaligned access.
147 let (major, minor) = (self.major, self.minor);
148 write!(f, "{}.{}", major, minor)
149 }
150}
151
Jaewan Kim3dfc0ab2023-09-27 14:00:59 +0900152/// Range with non-empty length.
153#[derive(Debug, Copy, Clone)]
154struct NonEmptyRange {
155 start: usize,
156 size: NonZeroUsize,
157}
158
159impl NonEmptyRange {
160 pub fn new(start: usize, size: usize) -> Option<Self> {
161 // Ensure end() is safe.
162 start.checked_add(size).unwrap();
163
164 Some(Self { start, size: NonZeroUsize::new(size)? })
165 }
166
167 fn end(&self) -> usize {
168 self.start + self.len()
169 }
170
171 fn len(&self) -> usize {
172 self.size.into()
173 }
174
175 fn as_range(&self) -> Range<usize> {
176 self.start..self.end()
177 }
178}
179
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100180#[derive(Debug)]
181pub struct Config<'a> {
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100182 body: &'a mut [u8],
Jaewan Kim3dfc0ab2023-09-27 14:00:59 +0900183 ranges: [Option<NonEmptyRange>; Entry::COUNT],
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100184}
185
186impl<'a> Config<'a> {
187 /// Take ownership of a pvmfw configuration consisting of its header and following entries.
Pierre-Clément Tosifd694c72023-08-14 14:24:07 +0100188 pub fn new(bytes: &'a mut [u8]) -> Result<Self> {
189 const HEADER_SIZE: usize = mem::size_of::<Header>();
190 if bytes.len() < HEADER_SIZE {
191 return Err(Error::BufferTooSmall);
192 }
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100193
Pierre-Clément Tosifd694c72023-08-14 14:24:07 +0100194 let (header, rest) =
195 LayoutVerified::<_, Header>::new_from_prefix(bytes).ok_or(Error::HeaderMisaligned)?;
Alan Stokesa0e42962023-04-14 17:59:50 +0100196 let header = header.into_ref();
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100197
198 if header.magic != Header::MAGIC {
199 return Err(Error::InvalidMagic);
200 }
201
Pierre-Clément Tosice3e3132023-08-17 10:24:22 +0100202 let header_flags = header.flags;
203 if header_flags != 0 {
204 warn!("Ignoring unknown config flags: {header_flags:#x}");
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100205 }
206
Jaewan Kimbf5978c2023-07-28 11:30:54 +0000207 info!("pvmfw config version: {}", header.version);
208
Pierre-Clément Tosifd694c72023-08-14 14:24:07 +0100209 // Validate that we won't get an invalid alignment in the following due to padding to u64.
210 const_assert_eq!(HEADER_SIZE % mem::size_of::<u64>(), 0);
211
212 // Ensure that Header::total_size isn't larger than anticipated by the caller and resize
213 // the &[u8] to catch OOB accesses to entries/blobs.
214 let total_size = header.total_size();
215 let rest = if let Some(rest_size) = total_size.checked_sub(HEADER_SIZE) {
216 rest.get_mut(..rest_size).ok_or(Error::InvalidSize(total_size))?
217 } else {
218 return Err(Error::InvalidSize(total_size));
219 };
220
Pierre-Clément Tosi33001492023-08-14 17:57:30 +0100221 let (header_entries, body) =
222 LayoutVerified::<_, [HeaderEntry]>::new_slice_from_prefix(rest, header.entry_count()?)
223 .ok_or(Error::BufferTooSmall)?;
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100224
Pierre-Clément Tosi33001492023-08-14 17:57:30 +0100225 // Validate that we won't get an invalid alignment in the following due to padding to u64.
226 const_assert_eq!(mem::size_of::<HeaderEntry>() % mem::size_of::<u64>(), 0);
227
Jaewan Kim3dfc0ab2023-09-27 14:00:59 +0900228 // Ensure entries are in the body.
Pierre-Clément Tosice3e3132023-08-17 10:24:22 +0100229 let limits = header.body_lowest_bound()?..total_size;
Jaewan Kim3dfc0ab2023-09-27 14:00:59 +0900230 let mut ranges: [Option<NonEmptyRange>; Entry::COUNT] = [None; Entry::COUNT];
231 for entry in [Entry::Bcc, Entry::DebugPolicy, Entry::VmDtbo] {
232 let Some(header_entry) = header_entries.get(entry as usize) else { continue };
233 let entry_offset = header_entry.offset.try_into().unwrap();
234 let entry_size = header_entry.size.try_into().unwrap();
235 let Some(range) = NonEmptyRange::new(entry_offset, entry_size) else { continue };
236 let range = range.as_range();
237 if !range.is_within(&limits) {
238 return Err(Error::EntryOutOfBounds(entry, range, limits));
239 }
240
241 ranges[entry as usize] = NonEmptyRange::new(
242 entry_offset - limits.start, // is_within() validates safety of this.
243 entry_size,
244 );
245 }
Pierre-Clément Tosi33001492023-08-14 17:57:30 +0100246
247 Ok(Self { body, ranges })
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100248 }
249
250 /// Get slice containing the platform BCC.
Pierre-Clément Tosi33001492023-08-14 17:57:30 +0100251 pub fn get_entries(&mut self) -> Result<(&mut [u8], Option<&mut [u8]>)> {
252 // This assumes that the blobs are in-order w.r.t. the entries.
253 let bcc_range = self.get_entry_range(Entry::Bcc).ok_or(Error::MissingEntry(Entry::Bcc))?;
254 let dp_range = self.get_entry_range(Entry::DebugPolicy);
Jaewan Kimbf5978c2023-07-28 11:30:54 +0000255 let vm_dtbo_range = self.get_entry_range(Entry::VmDtbo);
256 // TODO(b/291191157): Provision device assignment with this.
257 if let Some(vm_dtbo_range) = vm_dtbo_range {
258 info!("Found VM DTBO at {:?}", vm_dtbo_range);
259 }
Pierre-Clément Tosi33001492023-08-14 17:57:30 +0100260 let bcc_start = bcc_range.start;
261 let bcc_end = bcc_range.len();
Pierre-Clément Tosiefe780c2023-02-21 21:36:30 +0000262 let (_, rest) = self.body.split_at_mut(bcc_start);
263 let (bcc, rest) = rest.split_at_mut(bcc_end);
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100264
Pierre-Clément Tosi33001492023-08-14 17:57:30 +0100265 let dp = if let Some(dp_range) = dp_range {
Jaewan Kim3dfc0ab2023-09-27 14:00:59 +0900266 let dp_start = dp_range.start.checked_sub(bcc_range.end()).unwrap();
Pierre-Clément Tosiefe780c2023-02-21 21:36:30 +0000267 let dp_end = dp_range.len();
268 let (_, rest) = rest.split_at_mut(dp_start);
269 let (dp, _) = rest.split_at_mut(dp_end);
270 Some(dp)
271 } else {
272 None
273 };
274
Pierre-Clément Tosi33001492023-08-14 17:57:30 +0100275 Ok((bcc, dp))
276 }
277
Jaewan Kim3dfc0ab2023-09-27 14:00:59 +0900278 fn get_entry_range(&self, entry: Entry) -> Option<NonEmptyRange> {
279 self.ranges[entry as usize]
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100280 }
281}