use crate::arch::Architecture;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BootError {
Fatal(&'static str),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BootState {
NotStarted,
Running { current_stage: usize },
Completed { stages_run: usize },
Failed { failed_stage: usize },
}
pub struct BootContext<'a, A: Architecture> {
arch: &'a mut A,
state: BootState,
}
impl<'a, A: Architecture> BootContext<'a, A> {
pub fn new(arch: &'a mut A) -> Self {
Self {
arch,
state: BootState::NotStarted,
}
}
pub fn arch(&mut self) -> &mut A {
self.arch
}
pub fn state(&self) -> BootState {
self.state
}
fn set_state(&mut self, state: BootState) {
self.state = state;
}
}
pub trait BootStage<A: Architecture> {
fn name(&self) -> &'static str;
fn run(&self, ctx: &mut BootContext<'_, A>) -> Result<(), BootError>;
}
pub fn run_boot_sequence<A: Architecture>(
arch: &mut A,
stages: &[&dyn BootStage<A>],
) -> Result<BootState, BootError> {
let mut ctx = BootContext::new(arch);
for (index, stage) in stages.iter().enumerate() {
ctx.set_state(BootState::Running {
current_stage: index,
});
stage.run(&mut ctx).map_err(|err| {
ctx.set_state(BootState::Failed {
failed_stage: index,
});
err
})?;
}
ctx.set_state(BootState::Completed {
stages_run: stages.len(),
});
Ok(ctx.state())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::arch::{
Architecture, MockAddressTranslator, MockInterruptController, MockTimer, Timer,
};
#[derive(Debug)]
struct TestArch {
timer: MockTimer,
ic: MockInterruptController,
translator: MockAddressTranslator,
}
impl TestArch {
fn new() -> Self {
Self {
timer: MockTimer::new(),
ic: MockInterruptController::default(),
translator: MockAddressTranslator::new(),
}
}
}
impl Architecture for TestArch {
type Timer = MockTimer;
type InterruptController = MockInterruptController;
type AddressTranslator = MockAddressTranslator;
fn timer(&self) -> &Self::Timer {
&self.timer
}
fn interrupt_controller(&mut self) -> &mut Self::InterruptController {
&mut self.ic
}
fn address_translator(&self) -> &Self::AddressTranslator {
&self.translator
}
}
struct TickStage;
impl<A> BootStage<A> for TickStage
where
A: Architecture,
A::Timer: Timer<Tick = u64>,
{
fn name(&self) -> &'static str {
"tick"
}
fn run(&self, ctx: &mut BootContext<'_, A>) -> Result<(), BootError> {
let current = ctx.arch().timer().now();
if current > u64::MAX - 1 {
return Err(BootError::Fatal("tick overflow"));
}
Ok(())
}
}
struct FailingStage;
impl<A: Architecture> BootStage<A> for FailingStage {
fn name(&self) -> &'static str {
"failing"
}
fn run(&self, _ctx: &mut BootContext<'_, A>) -> Result<(), BootError> {
Err(BootError::Fatal("expected failure"))
}
}
#[test]
fn boot_sequence_succeeds() {
let mut arch = TestArch::new();
let stages: [&dyn BootStage<TestArch>; 1] = [&TickStage];
let state = run_boot_sequence(&mut arch, &stages).expect("boot should succeed");
assert_eq!(
state,
BootState::Completed {
stages_run: stages.len()
}
);
}
#[test]
fn boot_sequence_stops_on_failure() {
let mut arch = TestArch::new();
let stages: [&dyn BootStage<TestArch>; 2] = [&TickStage, &FailingStage];
let result = run_boot_sequence(&mut arch, &stages);
assert!(result.is_err());
}
}