pvmfw: Add support for variable config entry count
Refactor Header::entries out of the struct so that it keeps a constant
size and parse the variable-sized (version-dependent) entry array from
Config, which will store a validated list of ranges describing the
blobs. This should make adding support for new blobs considerably
easier.
Derive Entry variant count (Entry::COUNT) programatically, using the
implementation suggested by jaewan@.
Bug: 291191157
Test: atest DebugPolicyHostTests
Change-Id: I7e7ad7d249a4463b3979c22b3ce10b00b97ad492
diff --git a/pvmfw/src/config.rs b/pvmfw/src/config.rs
index 47a06df..8a24347 100644
--- a/pvmfw/src/config.rs
+++ b/pvmfw/src/config.rs
@@ -19,7 +19,7 @@
use core::ops::Range;
use core::result;
use static_assertions::const_assert_eq;
-use vmbase::util::{unchecked_align_up, RangeExt};
+use vmbase::util::RangeExt;
use zerocopy::{FromBytes, LayoutVerified};
/// Configuration data header.
@@ -34,8 +34,6 @@
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)]
@@ -88,35 +86,38 @@
self.total_size as usize
}
- pub fn body_offset(&self) -> usize {
- unchecked_align_up(mem::size_of::<Self>(), mem::size_of::<u64>())
+ pub fn body_offset(&self) -> Result<usize> {
+ let entries_offset = mem::size_of::<Self>();
+
+ // Ensure that the entries are properly aligned and do not require padding.
+ const_assert_eq!(mem::align_of::<Header>() % mem::align_of::<HeaderEntry>(), 0);
+ const_assert_eq!(mem::size_of::<Header>() % mem::align_of::<HeaderEntry>(), 0);
+
+ let entries_size = self.entry_count()?.checked_mul(mem::size_of::<HeaderEntry>()).unwrap();
+
+ Ok(entries_offset.checked_add(entries_size).unwrap())
}
- fn get_body_range(&self, entry: Entry) -> Result<Option<Range<usize>>> {
- let Some(range) = self.entries[entry as usize].as_range() else {
- return Ok(None);
+ pub fn entry_count(&self) -> Result<usize> {
+ let last_entry = match self.version {
+ Self::VERSION_1_0 => Entry::DebugPolicy,
+ v => return Err(Error::UnsupportedVersion(v)),
};
- let limits = self.body_offset()..self.total_size();
- if range.start <= range.end && range.is_within(&limits) {
- let start = range.start - limits.start;
- let end = range.end - limits.start;
-
- Ok(Some(start..end))
- } else {
- Err(Error::EntryOutOfBounds(entry, range, limits))
- }
+ Ok(last_entry as usize + 1)
}
}
#[derive(Clone, Copy, Debug)]
pub enum Entry {
- Bcc = 0,
- DebugPolicy = 1,
+ Bcc,
+ DebugPolicy,
+ #[allow(non_camel_case_types)] // TODO: Use mem::variant_count once stable.
+ _VARIANT_COUNT,
}
impl Entry {
- const COUNT: usize = 2;
+ const COUNT: usize = Self::_VARIANT_COUNT as usize;
}
#[repr(packed)]
@@ -157,8 +158,7 @@
#[derive(Debug)]
pub struct Config<'a> {
body: &'a mut [u8],
- bcc_range: Range<usize>,
- dp_range: Option<Range<usize>>,
+ ranges: [Option<Range<usize>>; Entry::COUNT],
}
impl<'a> Config<'a> {
@@ -177,10 +177,6 @@
return Err(Error::InvalidMagic);
}
- if header.version != Header::VERSION_1_0 {
- return Err(Error::UnsupportedVersion(header.version));
- }
-
if header.flags != 0 {
return Err(Error::InvalidFlags(header.flags));
}
@@ -197,22 +193,36 @@
return Err(Error::InvalidSize(total_size));
};
- 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 (header_entries, body) =
+ LayoutVerified::<_, [HeaderEntry]>::new_slice_from_prefix(rest, header.entry_count()?)
+ .ok_or(Error::BufferTooSmall)?;
- Ok(Self { body: rest, bcc_range, dp_range })
+ // Validate that we won't get an invalid alignment in the following due to padding to u64.
+ const_assert_eq!(mem::size_of::<HeaderEntry>() % mem::size_of::<u64>(), 0);
+
+ let limits = header.body_offset()?..total_size;
+ let ranges = [
+ // TODO: Find a way to do this programmatically even if the trait
+ // `core::marker::Copy` is not implemented for `core::ops::Range<usize>`.
+ Self::validated_body_range(Entry::Bcc, &header_entries, &limits)?,
+ Self::validated_body_range(Entry::DebugPolicy, &header_entries, &limits)?,
+ ];
+
+ Ok(Self { body, ranges })
}
/// 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();
+ pub fn get_entries(&mut self) -> Result<(&mut [u8], Option<&mut [u8]>)> {
+ // This assumes that the blobs are in-order w.r.t. the entries.
+ let bcc_range = self.get_entry_range(Entry::Bcc).ok_or(Error::MissingEntry(Entry::Bcc))?;
+ let dp_range = self.get_entry_range(Entry::DebugPolicy);
+ let bcc_start = bcc_range.start;
+ let bcc_end = 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 = if let Some(dp_range) = dp_range {
+ let dp_start = dp_range.start.checked_sub(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);
@@ -221,6 +231,31 @@
None
};
- (bcc, dp)
+ Ok((bcc, dp))
+ }
+
+ pub fn get_entry_range(&self, entry: Entry) -> Option<Range<usize>> {
+ self.ranges[entry as usize].clone()
+ }
+
+ fn validated_body_range(
+ entry: Entry,
+ header_entries: &[HeaderEntry],
+ limits: &Range<usize>,
+ ) -> Result<Option<Range<usize>>> {
+ if let Some(header_entry) = header_entries.get(entry as usize) {
+ if let Some(r) = header_entry.as_range() {
+ return if r.start <= r.end && r.is_within(limits) {
+ let start = r.start - limits.start;
+ let end = r.end - limits.start;
+
+ Ok(Some(start..end))
+ } else {
+ Err(Error::EntryOutOfBounds(entry, r, limits.clone()))
+ };
+ }
+ }
+
+ Ok(None)
}
}
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 9c929a9..3efa61e 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -207,7 +207,10 @@
RebootReason::InvalidConfig
})?;
- let (bcc_slice, debug_policy) = appended.get_entries();
+ let (bcc_slice, debug_policy) = appended.get_entries().map_err(|e| {
+ error!("Failed to obtained the config entries: {e}");
+ RebootReason::InvalidConfig
+ })?;
// Up to this point, we were using the built-in static (from .rodata) page tables.
MEMORY.lock().replace(MemoryTracker::new(
@@ -427,10 +430,10 @@
}
}
- fn get_entries(&mut self) -> (&mut [u8], Option<&mut [u8]>) {
+ fn get_entries(&mut self) -> config::Result<(&mut [u8], Option<&mut [u8]>)> {
match self {
Self::Config(ref mut cfg) => cfg.get_entries(),
- Self::LegacyBcc(ref mut bcc) => (bcc, None),
+ Self::LegacyBcc(ref mut bcc) => Ok((bcc, None)),
}
}
}