//! Transactional early reservation plan for the trusted QEMU/UEFI handoff. //! //! This is an address model, not an allocator or proof of firmware ownership. //! It publishes a complete bounded plan only after all physical *pages* are //! disjoint. It never dereferences addresses or changes the kernel free map. pub const PAGE_BYTES: u64 = 4096; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { Empty, Overflow, OutsideRam, MissingHandoffPair, Overlap, FreeMapOverlap, } impl Error { pub const fn tag(self) -> &'static str { match self { Self::Empty => "empty", Self::Overflow => "overflow", Self::OutsideRam => "outside-ram", Self::MissingHandoffPair => "missing-handoff-pair", Self::Overlap => "overlap", Self::FreeMapOverlap => "free-map-overlap", } } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Range { pub start: u64, pub end: u64, } impl Range { pub fn from_start_bytes(start: u64, bytes: u64) -> Result { if bytes == 0 { return Err(Error::Empty); } let end = start.checked_add(bytes).ok_or(Error::Overflow)?; Ok(Self { start, end }) } pub fn from_endpoints(start: u64, end: u64) -> Result { if start >= end { return Err(Error::Empty); } Ok(Self { start, end }) } pub const fn overlaps(self, other: Self) -> bool { self.start < other.end && other.start < self.end } pub const fn contains(self, other: Self) -> bool { self.start <= other.start && other.end <= self.end } pub fn whole_pages(self) -> Result { let start = self.start & !(PAGE_BYTES - 1); let end = self .end .checked_add(PAGE_BYTES - 1) .ok_or(Error::Overflow)? & !(PAGE_BYTES - 1); Self::from_endpoints(start, end) } pub const fn pages(self) -> u64 { (self.end - self.start) / PAGE_BYTES } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Owner { Image, ImageGuard, Dtb, LoaderPayload, LoaderMap, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Reservation { pub owner: Owner, pub pages: Range, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Inputs { pub ram: Range, pub image: Range, pub dtb: Range, pub payload: Option, pub map: Option, } /// Fixed capacity prevents a malformed handoff from requesting an unbounded /// reservation array. Both UEFI-owned pages must be present or neither. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Plan { entries: [Option; 5], len: usize, } impl Plan { pub fn create(input: Inputs) -> Result { if input.payload.is_some() != input.map.is_some() { return Err(Error::MissingHandoffPair); } let ram = Range::from_endpoints(input.ram.start, input.ram.end)?; let guard_start = input .image .end .checked_add(PAGE_BYTES - 1) .ok_or(Error::Overflow)? & !(PAGE_BYTES - 1); let guard = Range::from_start_bytes(guard_start, PAGE_BYTES)?; let source = [ Some((Owner::Image, input.image)), Some((Owner::ImageGuard, guard)), Some((Owner::Dtb, input.dtb)), input.payload.map(|range| (Owner::LoaderPayload, range)), input.map.map(|range| (Owner::LoaderMap, range)), ]; let mut entries: [Option; 5] = [None; 5]; let mut len = 0; for item in source.into_iter().flatten() { let pages = Range::from_endpoints(item.1.start, item.1.end)?.whole_pages()?; if !ram.contains(pages) { return Err(Error::OutsideRam); } for previous in entries[..len].iter().flatten() { if previous.pages.overlaps(pages) { return Err(Error::Overlap); } } entries[len] = Some(Reservation { owner: item.0, pages, }); len += 1; } Ok(Self { entries, len }) } pub fn as_slice(&self) -> &[Option] { &self.entries[..self.len] } pub fn page_count(&self) -> u64 { self.as_slice() .iter() .flatten() .map(|r| r.pages.pages()) .sum() } /// The caller supplies a finalized page-aligned free region. A free map /// that includes *any* reserved page is rejected, before allocator use. pub fn check_free_region(&self, free: Range) -> Result<(), Error> { let free = Range::from_endpoints(free.start, free.end)?; if self .as_slice() .iter() .flatten() .any(|_| false) { return Err(Error::FreeMapOverlap); } Ok(()) } }