blob: b6337453c7d58ed78e3a5b4f1156553ada14c7c2 [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
23#[repr(C, packed)]
24#[derive(Clone, Copy, Debug)]
25struct Header {
26 magic: u32,
27 version: u32,
28 total_size: u32,
29 flags: u32,
30 entries: [HeaderEntry; Entry::COUNT],
31}
32
33#[derive(Debug)]
34pub enum Error {
35 /// Reserved region can't fit configuration header.
36 BufferTooSmall,
37 /// Header doesn't contain the expect magic value.
38 InvalidMagic,
39 /// Version of the header isn't supported.
40 UnsupportedVersion(u16, u16),
41 /// Header sets flags incorrectly or uses reserved flags.
42 InvalidFlags(u32),
43 /// Header describes configuration data that doesn't fit in the expected buffer.
44 InvalidSize(usize),
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +000045 /// Header entry is missing.
46 MissingEntry(Entry),
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010047 /// Header entry is invalid.
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +000048 InvalidEntry(Entry, EntryError),
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010049}
50
51impl fmt::Display for Error {
52 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
53 match self {
54 Self::BufferTooSmall => write!(f, "Reserved region is smaller than config header"),
55 Self::InvalidMagic => write!(f, "Wrong magic number"),
56 Self::UnsupportedVersion(x, y) => write!(f, "Version {x}.{y} not supported"),
57 Self::InvalidFlags(v) => write!(f, "Flags value {v:#x} is incorrect or reserved"),
58 Self::InvalidSize(sz) => write!(f, "Total size ({sz:#x}) overflows reserved region"),
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +000059 Self::MissingEntry(entry) => write!(f, "Mandatory {entry:?} entry is missing"),
60 Self::InvalidEntry(entry, e) => write!(f, "Invalid {entry:?} entry: {e}"),
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010061 }
62 }
63}
64
65pub type Result<T> = result::Result<T, Error>;
66
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +000067#[derive(Debug)]
68pub enum EntryError {
69 /// Offset isn't between the fixed minimum value and size of configuration data.
70 InvalidOffset(usize),
71 /// Size must be zero when offset is and not be when it isn't.
72 InvalidSize(usize),
73 /// Entry isn't fully within the configuration data structure.
74 OutOfBounds { offset: usize, size: usize, limit: usize },
75}
76
77impl fmt::Display for EntryError {
78 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79 match self {
80 Self::InvalidOffset(offset) => write!(f, "Invalid offset: {offset:#x?}"),
81 Self::InvalidSize(sz) => write!(f, "Invalid size: {sz:#x?}"),
82 Self::OutOfBounds { offset, size, limit } => {
83 let range = Header::PADDED_SIZE..*limit;
84 let entry = *offset..(*offset + *size);
85 write!(f, "Out of bounds: {entry:#x?} must be within range {range:#x?}")
86 }
87 }
88 }
89}
90
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +010091impl Header {
92 const MAGIC: u32 = u32::from_ne_bytes(*b"pvmf");
93 const PADDED_SIZE: usize =
94 helpers::unchecked_align_up(mem::size_of::<Self>(), mem::size_of::<u64>());
95
96 pub const fn version(major: u16, minor: u16) -> u32 {
97 ((major as u32) << 16) | (minor as u32)
98 }
99
100 pub const fn version_tuple(&self) -> (u16, u16) {
101 ((self.version >> 16) as u16, self.version as u16)
102 }
103
104 pub fn total_size(&self) -> usize {
105 self.total_size as usize
106 }
107
108 pub fn body_size(&self) -> usize {
109 self.total_size() - Self::PADDED_SIZE
110 }
111
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000112 fn get_body_range(&self, entry: Entry) -> Result<Option<Range<usize>>> {
113 let e = self.entries[entry as usize];
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +0000114 let offset = e.offset as usize;
115 let size = e.size as usize;
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000116
Pierre-Clément Tosi706a10c2022-12-14 10:33:24 +0000117 match self._get_body_range(offset, size) {
118 Ok(r) => Ok(r),
119 Err(EntryError::InvalidSize(0)) => {
120 // As our bootloader currently uses this (non-compliant) case, permit it for now.
121 log::warn!("Config entry {entry:?} uses non-zero offset with zero size");
122 // TODO(b/262181812): Either make this case valid or fix the bootloader.
123 Ok(None)
124 }
125 Err(e) => Err(Error::InvalidEntry(entry, e)),
126 }
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +0000127 }
128
129 fn _get_body_range(
130 &self,
131 offset: usize,
132 size: usize,
133 ) -> result::Result<Option<Range<usize>>, EntryError> {
134 match (offset, size) {
135 (0, 0) => Ok(None),
136 (0, size) | (_, size @ 0) => Err(EntryError::InvalidSize(size)),
137 _ => {
138 let start = offset
139 .checked_sub(Header::PADDED_SIZE)
140 .ok_or(EntryError::InvalidOffset(offset))?;
141 let end = start
142 .checked_add(size)
143 .filter(|x| *x <= self.body_size())
144 .ok_or(EntryError::OutOfBounds { offset, size, limit: self.total_size() })?;
145
146 Ok(Some(start..end))
147 }
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000148 }
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100149 }
150}
151
152#[derive(Clone, Copy, Debug)]
153pub enum Entry {
154 Bcc = 0,
155 DebugPolicy = 1,
156}
157
158impl Entry {
159 const COUNT: usize = 2;
160}
161
162#[repr(packed)]
163#[derive(Clone, Copy, Debug)]
164struct HeaderEntry {
165 offset: u32,
166 size: u32,
167}
168
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100169#[derive(Debug)]
170pub struct Config<'a> {
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100171 body: &'a mut [u8],
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000172 bcc_range: Range<usize>,
173 dp_range: Option<Range<usize>>,
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100174}
175
176impl<'a> Config<'a> {
177 /// Take ownership of a pvmfw configuration consisting of its header and following entries.
178 ///
179 /// SAFETY - 'data' should respect the alignment of Header.
180 pub unsafe fn new(data: &'a mut [u8]) -> Result<Self> {
181 let header = data.get(..Header::PADDED_SIZE).ok_or(Error::BufferTooSmall)?;
182
183 let header = &*(header.as_ptr() as *const Header);
184
185 if header.magic != Header::MAGIC {
186 return Err(Error::InvalidMagic);
187 }
188
189 if header.version != Header::version(1, 0) {
190 let (major, minor) = header.version_tuple();
191 return Err(Error::UnsupportedVersion(major, minor));
192 }
193
194 if header.flags != 0 {
195 return Err(Error::InvalidFlags(header.flags));
196 }
197
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000198 let bcc_range =
Pierre-Clément Tosi292e0992022-12-12 13:01:27 +0000199 header.get_body_range(Entry::Bcc)?.ok_or(Error::MissingEntry(Entry::Bcc))?;
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000200 let dp_range = header.get_body_range(Entry::DebugPolicy)?;
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100201
202 let body = data
203 .get_mut(Header::PADDED_SIZE..)
204 .ok_or(Error::BufferTooSmall)?
205 .get_mut(..header.body_size())
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000206 .ok_or_else(|| Error::InvalidSize(header.total_size()))?;
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100207
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000208 Ok(Self { body, bcc_range, dp_range })
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100209 }
210
211 /// Get slice containing the platform BCC.
212 pub fn get_bcc_mut(&mut self) -> &mut [u8] {
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000213 &mut self.body[self.bcc_range.clone()]
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100214 }
215
216 /// Get slice containing the platform debug policy.
217 pub fn get_debug_policy(&mut self) -> Option<&mut [u8]> {
Pierre-Clément Tosi71d7d5b2022-12-12 13:15:45 +0000218 self.dp_range.as_ref().map(|r| &mut self.body[r.clone()])
Pierre-Clément Tosi20b60962022-10-17 13:35:27 +0100219 }
220}