blob: 4086af7035c50a22e14a45fbe12deaa1523a8a9e [file] [log] [blame]
// Copyright 2022, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Support for the pvmfw configuration data format.
use core::fmt;
use core::mem;
use core::ops::Range;
use core::result;
use vmbase::util::unchecked_align_up;
use zerocopy::{FromBytes, LayoutVerified};
/// Configuration data header.
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, FromBytes)]
struct Header {
/// Magic number; must be `Header::MAGIC`.
magic: u32,
/// Version of the header format.
version: u32,
/// Total size of the configuration data.
total_size: u32,
/// Feature flags; currently reserved and must be zero.
flags: u32,
/// (offset, size) pairs used to locate individual entries appended to the header.
entries: [HeaderEntry; Entry::COUNT],
}
#[derive(Debug)]
pub enum Error {
/// Reserved region can't fit configuration header.
BufferTooSmall,
/// Header has the wrong alignment
HeaderMisaligned,
/// Header doesn't contain the expect magic value.
InvalidMagic,
/// Version of the header isn't supported.
UnsupportedVersion(u16, u16),
/// Header sets flags incorrectly or uses reserved flags.
InvalidFlags(u32),
/// Header describes configuration data that doesn't fit in the expected buffer.
InvalidSize(usize),
/// Header entry is missing.
MissingEntry(Entry),
/// Header entry is invalid.
InvalidEntry(Entry, EntryError),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::BufferTooSmall => write!(f, "Reserved region is smaller than config header"),
Self::HeaderMisaligned => write!(f, "Reserved region is misaligned"),
Self::InvalidMagic => write!(f, "Wrong magic number"),
Self::UnsupportedVersion(x, y) => write!(f, "Version {x}.{y} not supported"),
Self::InvalidFlags(v) => write!(f, "Flags value {v:#x} is incorrect or reserved"),
Self::InvalidSize(sz) => write!(f, "Total size ({sz:#x}) overflows reserved region"),
Self::MissingEntry(entry) => write!(f, "Mandatory {entry:?} entry is missing"),
Self::InvalidEntry(entry, e) => write!(f, "Invalid {entry:?} entry: {e}"),
}
}
}
pub type Result<T> = result::Result<T, Error>;
#[derive(Debug)]
pub enum EntryError {
/// Offset isn't between the fixed minimum value and size of configuration data.
InvalidOffset(usize),
/// Size must be zero when offset is and not be when it isn't.
InvalidSize(usize),
/// Entry isn't fully within the configuration data structure.
OutOfBounds { offset: usize, size: usize, limit: usize },
}
impl fmt::Display for EntryError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::InvalidOffset(offset) => write!(f, "Invalid offset: {offset:#x?}"),
Self::InvalidSize(sz) => write!(f, "Invalid size: {sz:#x?}"),
Self::OutOfBounds { offset, size, limit } => {
let range = Header::PADDED_SIZE..*limit;
let entry = *offset..(*offset + *size);
write!(f, "Out of bounds: {entry:#x?} must be within range {range:#x?}")
}
}
}
}
impl Header {
const MAGIC: u32 = u32::from_ne_bytes(*b"pvmf");
const VERSION_1_0: u32 = Self::version(1, 0);
const PADDED_SIZE: usize = unchecked_align_up(mem::size_of::<Self>(), mem::size_of::<u64>());
pub const fn version(major: u16, minor: u16) -> u32 {
((major as u32) << 16) | (minor as u32)
}
pub const fn version_tuple(&self) -> (u16, u16) {
((self.version >> 16) as u16, self.version as u16)
}
pub fn total_size(&self) -> usize {
self.total_size as usize
}
pub fn body_size(&self) -> usize {
self.total_size() - Self::PADDED_SIZE
}
fn get_body_range(&self, entry: Entry) -> Result<Option<Range<usize>>> {
let e = self.entries[entry as usize];
let offset = e.offset as usize;
let size = e.size as usize;
match self._get_body_range(offset, size) {
Ok(r) => Ok(r),
Err(EntryError::InvalidSize(0)) => {
// As our bootloader currently uses this (non-compliant) case, permit it for now.
log::warn!("Config entry {entry:?} uses non-zero offset with zero size");
// TODO(b/262181812): Either make this case valid or fix the bootloader.
Ok(None)
}
Err(e) => Err(Error::InvalidEntry(entry, e)),
}
}
fn _get_body_range(
&self,
offset: usize,
size: usize,
) -> result::Result<Option<Range<usize>>, EntryError> {
match (offset, size) {
(0, 0) => Ok(None),
(0, size) | (_, size @ 0) => Err(EntryError::InvalidSize(size)),
_ => {
let start = offset
.checked_sub(Header::PADDED_SIZE)
.ok_or(EntryError::InvalidOffset(offset))?;
let end = start
.checked_add(size)
.filter(|x| *x <= self.body_size())
.ok_or(EntryError::OutOfBounds { offset, size, limit: self.total_size() })?;
Ok(Some(start..end))
}
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum Entry {
Bcc = 0,
DebugPolicy = 1,
}
impl Entry {
const COUNT: usize = 2;
}
#[repr(packed)]
#[derive(Clone, Copy, Debug, FromBytes)]
struct HeaderEntry {
offset: u32,
size: u32,
}
#[derive(Debug)]
pub struct Config<'a> {
body: &'a mut [u8],
bcc_range: Range<usize>,
dp_range: Option<Range<usize>>,
}
impl<'a> Config<'a> {
/// Take ownership of a pvmfw configuration consisting of its header and following entries.
pub fn new(data: &'a mut [u8]) -> Result<Self> {
let header = data.get(..Header::PADDED_SIZE).ok_or(Error::BufferTooSmall)?;
let (header, _) =
LayoutVerified::<_, Header>::new_from_prefix(header).ok_or(Error::HeaderMisaligned)?;
let header = header.into_ref();
if header.magic != Header::MAGIC {
return Err(Error::InvalidMagic);
}
if header.version != Header::VERSION_1_0 {
let (major, minor) = header.version_tuple();
return Err(Error::UnsupportedVersion(major, minor));
}
if header.flags != 0 {
return Err(Error::InvalidFlags(header.flags));
}
let bcc_range =
header.get_body_range(Entry::Bcc)?.ok_or(Error::MissingEntry(Entry::Bcc))?;
let dp_range = header.get_body_range(Entry::DebugPolicy)?;
let body_size = header.body_size();
let total_size = header.total_size();
let body = data
.get_mut(Header::PADDED_SIZE..)
.ok_or(Error::BufferTooSmall)?
.get_mut(..body_size)
.ok_or(Error::InvalidSize(total_size))?;
Ok(Self { body, bcc_range, dp_range })
}
/// Get slice containing the platform BCC.
pub fn get_entries(&mut self) -> (&mut [u8], Option<&mut [u8]>) {
let bcc_start = self.bcc_range.start;
let bcc_end = self.bcc_range.len();
let (_, rest) = self.body.split_at_mut(bcc_start);
let (bcc, rest) = rest.split_at_mut(bcc_end);
let dp = if let Some(dp_range) = &self.dp_range {
let dp_start = dp_range.start.checked_sub(self.bcc_range.end).unwrap();
let dp_end = dp_range.len();
let (_, rest) = rest.split_at_mut(dp_start);
let (dp, _) = rest.split_at_mut(dp_end);
Some(dp)
} else {
None
};
(bcc, dp)
}
}