//! K601–K604: distinguish QEMU direct Image from TurkOS UEFI BootInfo V1. //! //! Entry contract (specific to our existing boot.S/loader, not all firmware): //! //! | x1 | Header slice | Result before publishing runtime state | //! |---|---|---| //! | 0 | absent | Direct Image; x0 is the only DTB | //! | 0 | present | reject: contradictory ownership/source | //! | nonzero | absent or at a different physical address | reject | //! | nonzero | DTB, truncated or unsupported BootInfo | reject, never fall back | //! | nonzero | valid V1 handoff and nonoverlapping ranges | UEFI BootInfo | //! //! `LocatedBytes` is a readable Rust slice plus *claimed* physical provenance. //! It does NOT prove mapping, RAM ownership, or authenticity. A future small //! unsafe entry adapter must establish those facts before making slices. This //! module never dereferences register values, calls firmware, allocates pages, //! or reads map descriptors. BootInfo metadata is copied into the result; map //! ownership/copying and allocation authority belong to subsequent contracts. //! //! `resolve` is transactional: it accepts immutable inputs and returns the //! complete value only after every gate. An error publishes no partial state; //! it cannot acquire or leak any resources. There is no global origin variable //! and no fallback from failed UEFI validation to direct boot. use turkos_bootinfo::{ BootInfoHandoffError, BootInfoHeaderError, BootInfoPointerError, BootInfoV1, validate_boot_info_pointer, }; use turkos_kernel::dtb::{self, Hardware}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum BootOrigin { DirectImage, UefiBootInfoV1, } impl BootOrigin { /// Stable diagnostic tag; neither an authenticated identity nor a new ABI. pub const fn tag(self) -> &'static str { match self { Self::DirectImage => "direct-image", Self::UefiBootInfoV1 => "uefi-bootinfo-v1", } } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct EntryRegisters { pub dtb_address: u64, pub boot_info_address: u64, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RangeError { Empty, Overflow, } /// Checked, nonempty, half-open physical interval; it is not an allocation. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct PhysicalRange { start: u64, end: u64, } impl PhysicalRange { pub fn new(start: u64, bytes: u64) -> Result { if bytes == 0 { return Err(RangeError::Empty); } let end = start.checked_add(bytes).ok_or(RangeError::Overflow)?; Ok(Self { start, end }) } pub const fn start(self) -> u64 { self.start } pub const fn end(self) -> u64 { self.end } pub const fn bytes(self) -> u64 { self.end - self.start } fn contains(self, other: Self) -> bool { self.start <= other.start && other.end <= self.end } fn overlaps(self, other: Self) -> bool { self.start < other.end && other.start < self.end } } #[derive(Clone, Copy, Debug)] pub struct LocatedBytes<'a> { address: u64, bytes: &'a [u8], } impl<'a> LocatedBytes<'a> { pub fn new(address: u64, bytes: &'a [u8]) -> Result { PhysicalRange::new(address, bytes.len() as u64)?; Ok(Self { address, bytes }) } } #[derive(Debug, PartialEq, Eq)] pub enum BootError { DtbPointer, DtbAddressMismatch, UnexpectedBootInfo, MissingBootInfo, BootInfoAddressMismatch, BootInfoPointer(BootInfoPointerError), HeaderTruncated, Header(BootInfoHeaderError), Dtb(dtb::Error), RamRange(RangeError), ImageOutsideRam, DtbOutsideRam, ImageDtbOverlap, Handoff(BootInfoHandoffError), } /// Owned metadata only: no borrowed slice or callable firmware service escapes. /// `ram` is a discovered extent, NOT a list of allocatable UEFI page types. #[derive(Clone, Copy, Debug)] pub struct ResolvedBoot { pub origin: BootOrigin, pub ram: PhysicalRange, pub dtb: PhysicalRange, pub uart_base: u64, pub boot_info: Option, } /// Decode little-endian V1 bytes without a cast or unaligned/raw pointer read. fn decode_header(bytes: &[u8]) -> Result { let b = bytes .get(..core::mem::size_of::()) .ok_or(BootError::HeaderTruncated)?; // All fixed fields below fit the 72-byte bound established above. let word = |at| u32::from_le_bytes([b[at], b[at + 1], b[at + 2], b[at + 3]]); let long = |at| { u64::from_le_bytes([ b[at], b[at + 1], b[at + 2], b[at + 3], b[at + 4], b[at + 5], b[at + 6], b[at + 7], ]) }; let info = BootInfoV1 { magic: [b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]], version: word(8), struct_bytes: word(12), memory_map_ptr: long(16), memory_map_bytes: long(24), descriptor_bytes: word(32), descriptor_version: word(36), kernel_phys_start: long(40), kernel_file_bytes: long(48), dtb_phys_addr: long(56), flags: long(64), }; info.validate_header().map_err(BootError::Header)?; Ok(info) } /// Validate both paths through the same DTB reader, but never interpret the DTB /// as BootInfo. `image` must come from linker symbols in the future adapter. pub fn resolve( registers: EntryRegisters, dtb_bytes: LocatedBytes<'_>, payload: Option>, image: PhysicalRange, ) -> Result { let info = if registers.boot_info_address == 0 { if payload.is_some() { // MUTANT: ignore contradictory payload } None } else { validate_boot_info_pointer(registers.boot_info_address) .map_err(BootError::BootInfoPointer)?; let located = payload.ok_or(BootError::MissingBootInfo)?; if located.address != registers.boot_info_address { return Err(BootError::BootInfoAddressMismatch); } Some(decode_header(located.bytes)?) }; if registers.dtb_address == 0 || !registers.dtb_address.is_multiple_of(4) { return Err(BootError::DtbPointer); } if dtb_bytes.address != registers.dtb_address { return Err(BootError::DtbAddressMismatch); } let dtb_size = dtb::total_size(dtb_bytes.bytes).map_err(BootError::Dtb)?; let blob = dtb_bytes .bytes .get(..dtb_size) .ok_or(BootError::Dtb(dtb::Error::Invalid))?; let Hardware { uart_base, ram_base, ram_size, } = dtb::discover(blob).map_err(BootError::Dtb)?; let ram = PhysicalRange::new(ram_base, ram_size).map_err(BootError::RamRange)?; // LocatedBytes already checked its whole slice, so this prefix cannot wrap. let dtb = PhysicalRange::new(dtb_bytes.address, dtb_size as u64) .map_err(|_| BootError::DtbOutsideRam)?; if !ram.contains(image) { return Err(BootError::ImageOutsideRam); } if !ram.contains(dtb) { return Err(BootError::DtbOutsideRam); } if image.overlaps(dtb) { return Err(BootError::ImageDtbOverlap); } if let Some(header) = info { header .validate_handoff( registers.boot_info_address, ram.start(), ram.bytes(), image.start(), image.bytes(), dtb.start(), dtb.bytes(), ) .map_err(BootError::Handoff)?; } Ok(ResolvedBoot { origin: if info.is_some() { BootOrigin::UefiBootInfoV1 } else { BootOrigin::DirectImage }, ram, dtb, uart_base, boot_info: info, }) }