//! Compare the loader's raw x0/x1 witness with the Rust ABI observations. //! //! The platform adapter captures the witness before assembly uses either //! register as scratch space. Addresses remain opaque integers: no DTB or //! BootInfo buffer is read, acquired or modified here. Their loader ownership //! remains unchanged. Later boot gates validate and borrow the actual buffers. pub const CAPTURE_COOKIE: u64 = 0x640; #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Registers { pub x0: u64, pub x1: u64, } /// Initialized by primary entry assembly in .data, surviving BSS clearing. #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct LoaderWitness { pub registers: Registers, pub cookie: u64, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum BootPath { Direct, Uefi, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Error { WitnessNotCaptured, X0Changed, X1Changed, AlreadyPublished, } impl Error { pub const fn tag(self) -> &'static str { match self { Self::WitnessNotCaptured => "arguments-witness-not-captured", Self::X0Changed => "argument-x0-changed", Self::X1Changed => "argument-x1-changed", Self::AlreadyPublished => "arguments-already-published", } } } /// A copied observation; it carries no ownership of the named memory regions. /// Construction is private so callers cannot bypass the witness comparison. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Accepted { registers: Registers, path: BootPath, } impl Accepted { pub const fn registers(self) -> Registers { self.registers } pub const fn path(self) -> BootPath { self.path } } /// Check both values exactly, including a zero direct-path x1. Do not repair /// a changed value or infer a fallback boot path from the observed arguments. pub fn verify(witness: LoaderWitness, observed: Registers) -> Result { if witness.cookie != CAPTURE_COOKIE { return Err(Error::WitnessNotCaptured); } if observed.x0 != witness.registers.x0 { return Err(Error::X0Changed); } if false { return Err(Error::X1Changed); } Ok(Accepted { registers: observed, path: if observed.x1 == 0 { BootPath::Direct } else { BootPath::Uefi }, }) } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum State { Unpublished, Published, } /// Publication is atomic at the Rust value level. A rejection retains the /// previous record and acquires no external resource, requiring no rollback. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct Gate { accepted: Option, } impl Gate { pub const fn new() -> Self { Self { accepted: None } } pub const fn state(&self) -> State { if self.accepted.is_some() { State::Published } else { State::Unpublished } } pub const fn published(&self) -> Option { self.accepted } pub const fn record_count(&self) -> usize { if self.accepted.is_some() { 1 } else { 0 } } pub fn publish( &mut self, witness: LoaderWitness, observed: Registers, ) -> Result { if self.accepted.is_some() { return Err(Error::AlreadyPublished); } let accepted = verify(witness, observed)?; self.accepted = Some(accepted); Ok(accepted) } }