From 16c7e793f97f9b7a2b4e51385668237f7d274803 Mon Sep 17 00:00:00 2001 From: Christina Quast Date: Thu, 16 Jul 2026 11:07:39 +0200 Subject: [PATCH 1/4] board: rename monitor to spi_monitor The board-level monitor module orchestrates the SPI monitor (SPIPF) and SCU mux control for SPI flash protection. Rename the file monitor.rs -> spi_monitor.rs and the struct Ast1060Monitor -> Ast1060SpiMonitor, and update the module path/re-export in lib.rs and the srcs list in BUILD.bazel, to reflect that it is specifically an SPI monitor. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Christina Quast --- target/ast10x0/board/BUILD.bazel | 2 +- target/ast10x0/board/src/lib.rs | 4 ++-- .../board/src/{monitor.rs => spi_monitor.rs} | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) rename target/ast10x0/board/src/{monitor.rs => spi_monitor.rs} (95%) diff --git a/target/ast10x0/board/BUILD.bazel b/target/ast10x0/board/BUILD.bazel index e5baf596..ea878a69 100644 --- a/target/ast10x0/board/BUILD.bazel +++ b/target/ast10x0/board/BUILD.bazel @@ -8,7 +8,7 @@ rust_library( name = "ast10x0_board", srcs = [ "src/lib.rs", - "src/monitor.rs", + "src/spi_monitor.rs", "src/spim_wiring.rs", ], crate_name = "ast10x0_board", diff --git a/target/ast10x0/board/src/lib.rs b/target/ast10x0/board/src/lib.rs index e12c05d7..5db23555 100644 --- a/target/ast10x0/board/src/lib.rs +++ b/target/ast10x0/board/src/lib.rs @@ -14,10 +14,10 @@ use ast10x0_peripherals::scu::{ClockRegisterHalf, ScuRegisterHalf}; use ast10x0_peripherals::scu::{PinctrlPin, ScuRegisters}; -pub mod monitor; +pub mod spi_monitor; pub mod spim_wiring; -pub use monitor::Ast1060Monitor; +pub use spi_monitor::Ast1060SpiMonitor; pub use spim_wiring::{apply_spim_wiring, presets, SpimWiring, SpimWiringError}; pub use ast10x0_peripherals::i2c::{I2cConfig, I2cError}; diff --git a/target/ast10x0/board/src/monitor.rs b/target/ast10x0/board/src/spi_monitor.rs similarity index 95% rename from target/ast10x0/board/src/monitor.rs rename to target/ast10x0/board/src/spi_monitor.rs index ae48623b..62dfc721 100644 --- a/target/ast10x0/board/src/monitor.rs +++ b/target/ast10x0/board/src/spi_monitor.rs @@ -3,7 +3,7 @@ //! Board-level SPI Monitor orchestration. //! -//! `Ast1060Monitor` is the concrete implementation of the `Monitor` trait, orchestrating +//! `Ast1060SpiMonitor` is the concrete implementation of the `Monitor` trait, orchestrating //! both SCU (for external mux control) and SPIPF (for enforcement, filtering, and locks). //! //! This layer ensures no duplication: mux control delegates to SCU routing, and SPIPF @@ -33,7 +33,7 @@ use ast10x0_peripherals::spimonitor::types::{ /// monitor.set_address_privilege(/*...*/)?; /// monitor.lock_policy(MonitorInstance::Spim0)?; /// ``` -pub struct Ast1060Monitor<'a> { +pub struct Ast1060SpiMonitor<'a> { scu: &'a mut ScuRegisters, spipf: &'a mut [SpiMonitorRegisters; 4], read_blocked_region_count: u8, @@ -41,7 +41,7 @@ pub struct Ast1060Monitor<'a> { write_blocked_region_count: u8, } -impl<'a> Ast1060Monitor<'a> { +impl<'a> Ast1060SpiMonitor<'a> { /// Create a new board-level Monitor with access to both SCU and SPIPF. pub fn new( scu: &'a mut ScuRegisters, @@ -125,7 +125,7 @@ impl<'a> Ast1060Monitor<'a> { } } -impl<'a> Monitor for Ast1060Monitor<'a> { +impl<'a> Monitor for Ast1060SpiMonitor<'a> { fn set_mux(&mut self, instance: MonitorInstance, mux: MuxSelect) -> BootResult<()> { // External mux selection is controlled via SCU0F0 register. // Delegate to SCU routing layer which has the actual register access. @@ -267,13 +267,13 @@ mod tests { #[test] fn test_enforcement_flag() { - assert!(!Ast1060Monitor::is_enforcement_active(0)); - assert!(Ast1060Monitor::is_enforcement_active(1 << 4)); + assert!(!Ast1060SpiMonitor::is_enforcement_active(0)); + assert!(Ast1060SpiMonitor::is_enforcement_active(1 << 4)); } #[test] fn test_lock_flag() { - assert!(!Ast1060Monitor::is_policy_locked(0)); - assert!(Ast1060Monitor::is_policy_locked(1)); + assert!(!Ast1060SpiMonitor::is_policy_locked(0)); + assert!(Ast1060SpiMonitor::is_policy_locked(1)); } } From b7dc5545cb44a7626022a551feab2cd5b1bb7a76 Mon Sep 17 00:00:00 2001 From: Christina Quast Date: Thu, 16 Jul 2026 11:07:39 +0200 Subject: [PATCH 2/4] spimonitor: prefix bare Monitor* names with SpiMonitor Disambiguate the SPI monitor API from an upcoming BootMonitor subsystem so the two never collide. Rename in the spimonitor crate: Monitor (trait) -> SpiMonitorControl MonitorInstance -> SpiMonitorId (avoids clash with scu::SpiMonitorInstance) MonitorStatus -> SpiMonitorStatus MonitorState -> SpiMonitorState MonitorPolicy -> SpiMonitorPolicy The SpimWiringError::Monitor and SpiMonitorError::MonitorNotFound enum variants are left as-is; they are namespaced by their enum and cannot collide. Update the board impl accordingly. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Christina Quast --- target/ast10x0/board/src/spi_monitor.rs | 62 +++++++++---------- target/ast10x0/board/src/spim_wiring.rs | 14 ++--- .../peripherals/spimonitor/controller.rs | 18 +++--- target/ast10x0/peripherals/spimonitor/mod.rs | 8 +-- .../ast10x0/peripherals/spimonitor/policy.rs | 4 +- .../ast10x0/peripherals/spimonitor/profile.rs | 10 +-- .../ast10x0/peripherals/spimonitor/traits.rs | 22 +++---- .../ast10x0/peripherals/spimonitor/types.rs | 6 +- 8 files changed, 72 insertions(+), 72 deletions(-) diff --git a/target/ast10x0/board/src/spi_monitor.rs b/target/ast10x0/board/src/spi_monitor.rs index 62dfc721..c3aef110 100644 --- a/target/ast10x0/board/src/spi_monitor.rs +++ b/target/ast10x0/board/src/spi_monitor.rs @@ -12,9 +12,9 @@ use ast10x0_peripherals::scu::registers::ScuRegisters; use ast10x0_peripherals::scu::types::{ScuExtMuxSelect, SpiMonitorInstance}; use ast10x0_peripherals::spimonitor::registers::SpiMonitorRegisters; -use ast10x0_peripherals::spimonitor::traits::Monitor; +use ast10x0_peripherals::spimonitor::traits::SpiMonitorControl; use ast10x0_peripherals::spimonitor::types::{ - BootError, BootResult, MonitorInstance, MonitorStatus, MuxSelect, PrivilegeDirection, + BootError, BootResult, SpiMonitorId, SpiMonitorStatus, MuxSelect, PrivilegeDirection, PrivilegeOp, }; @@ -29,9 +29,9 @@ use ast10x0_peripherals::spimonitor::types::{ /// ```ignore /// let mut board = Ast1060Board::init(); /// let mut monitor = board.monitor(); -/// monitor.set_mux(MonitorInstance::Spim0, MuxSelect::RotControl)?; +/// monitor.set_mux(SpiMonitorId::Spim0, MuxSelect::RotControl)?; /// monitor.set_address_privilege(/*...*/)?; -/// monitor.lock_policy(MonitorInstance::Spim0)?; +/// monitor.lock_policy(SpiMonitorId::Spim0)?; /// ``` pub struct Ast1060SpiMonitor<'a> { scu: &'a mut ScuRegisters, @@ -59,33 +59,33 @@ impl<'a> Ast1060SpiMonitor<'a> { /// Get the register accessor for the specified monitor instance. #[inline] - fn regs(&self, instance: MonitorInstance) -> &SpiMonitorRegisters { + fn regs(&self, instance: SpiMonitorId) -> &SpiMonitorRegisters { match instance { - MonitorInstance::Spim0 => &self.spipf[0], - MonitorInstance::Spim1 => &self.spipf[1], - MonitorInstance::Spim2 => &self.spipf[2], - MonitorInstance::Spim3 => &self.spipf[3], + SpiMonitorId::Spim0 => &self.spipf[0], + SpiMonitorId::Spim1 => &self.spipf[1], + SpiMonitorId::Spim2 => &self.spipf[2], + SpiMonitorId::Spim3 => &self.spipf[3], } } /// Get mutable reference to register accessor (for write operations). #[inline] - fn regs_mut(&mut self, instance: MonitorInstance) -> &mut SpiMonitorRegisters { + fn regs_mut(&mut self, instance: SpiMonitorId) -> &mut SpiMonitorRegisters { match instance { - MonitorInstance::Spim0 => &mut self.spipf[0], - MonitorInstance::Spim1 => &mut self.spipf[1], - MonitorInstance::Spim2 => &mut self.spipf[2], - MonitorInstance::Spim3 => &mut self.spipf[3], + SpiMonitorId::Spim0 => &mut self.spipf[0], + SpiMonitorId::Spim1 => &mut self.spipf[1], + SpiMonitorId::Spim2 => &mut self.spipf[2], + SpiMonitorId::Spim3 => &mut self.spipf[3], } } - /// Map MonitorInstance to SCU SpiMonitorInstance for routing operations. - fn instance_to_scu(instance: MonitorInstance) -> SpiMonitorInstance { + /// Map SpiMonitorId to SCU SpiMonitorInstance for routing operations. + fn instance_to_scu(instance: SpiMonitorId) -> SpiMonitorInstance { match instance { - MonitorInstance::Spim0 => SpiMonitorInstance::Spim0, - MonitorInstance::Spim1 => SpiMonitorInstance::Spim1, - MonitorInstance::Spim2 => SpiMonitorInstance::Spim2, - MonitorInstance::Spim3 => SpiMonitorInstance::Spim3, + SpiMonitorId::Spim0 => SpiMonitorInstance::Spim0, + SpiMonitorId::Spim1 => SpiMonitorInstance::Spim1, + SpiMonitorId::Spim2 => SpiMonitorInstance::Spim2, + SpiMonitorId::Spim3 => SpiMonitorInstance::Spim3, } } @@ -125,8 +125,8 @@ impl<'a> Ast1060SpiMonitor<'a> { } } -impl<'a> Monitor for Ast1060SpiMonitor<'a> { - fn set_mux(&mut self, instance: MonitorInstance, mux: MuxSelect) -> BootResult<()> { +impl<'a> SpiMonitorControl for Ast1060SpiMonitor<'a> { + fn set_mux(&mut self, instance: SpiMonitorId, mux: MuxSelect) -> BootResult<()> { // External mux selection is controlled via SCU0F0 register. // Delegate to SCU routing layer which has the actual register access. let scu_instance = Self::instance_to_scu(instance); @@ -135,14 +135,14 @@ impl<'a> Monitor for Ast1060SpiMonitor<'a> { Ok(()) } - fn read_mux(&self, instance: MonitorInstance) -> BootResult { + fn read_mux(&self, instance: SpiMonitorId) -> BootResult { // Read from SCU0F0 register via SCU routing layer. let scu_instance = Self::instance_to_scu(instance); let scu_mux = self.scu.get_spim_ext_mux(scu_instance); Ok(Self::scu_to_mux(scu_mux)) } - fn soft_reset(&mut self, instance: MonitorInstance) -> BootResult<()> { + fn soft_reset(&mut self, instance: SpiMonitorId) -> BootResult<()> { let regs = self.regs_mut(instance); // Soft reset clears status/logs but preserves policy. // NON-BLOCKING TODO 1: Verify soft reset bit position from AST10x0 datasheet. @@ -157,7 +157,7 @@ impl<'a> Monitor for Ast1060SpiMonitor<'a> { Ok(()) } - fn hardware_reset(&mut self, instance: MonitorInstance) -> BootResult<()> { + fn hardware_reset(&mut self, instance: SpiMonitorId) -> BootResult<()> { let regs = self.regs_mut(instance); // Full hardware reset of all state (SPIPF and related SCU registers). // NON-BLOCKING TODO 1: Verify hardware reset bit and sequence from AST10x0 datasheet. @@ -173,7 +173,7 @@ impl<'a> Monitor for Ast1060SpiMonitor<'a> { fn set_address_privilege( &mut self, - instance: MonitorInstance, + instance: SpiMonitorId, start_addr: u32, end_addr: u32, _direction: PrivilegeDirection, @@ -200,14 +200,14 @@ impl<'a> Monitor for Ast1060SpiMonitor<'a> { Ok(()) } - fn read_region_count(&self, _instance: MonitorInstance) -> BootResult { + fn read_region_count(&self, _instance: SpiMonitorId) -> BootResult { // Region count is tracked in memory (following aspeed-rust pattern). // aspeed-rust stores read_blocked_region_num and write_blocked_region_num as struct fields. // We return the read-blocked region count for now; write-blocked can be exposed via separate method if needed. Ok(self.read_blocked_region_count as u32) } - fn read_status(&self, instance: MonitorInstance) -> BootResult { + fn read_status(&self, instance: SpiMonitorId) -> BootResult { let regs = self.regs(instance); let ctrl = regs.read_ctrl(); let lock_status = regs.read_lock_status(); @@ -217,7 +217,7 @@ impl<'a> Monitor for Ast1060SpiMonitor<'a> { let scu_mux = self.scu.get_spim_ext_mux(scu_instance); let mux = Self::scu_to_mux(scu_mux); - Ok(MonitorStatus { + Ok(SpiMonitorStatus { mux, policy_locked: Self::is_policy_locked(lock_status), enforcement_active: Self::is_enforcement_active(ctrl), @@ -232,7 +232,7 @@ impl<'a> Monitor for Ast1060SpiMonitor<'a> { true } - fn lock_policy(&mut self, instance: MonitorInstance) -> BootResult<()> { + fn lock_policy(&mut self, instance: SpiMonitorId) -> BootResult<()> { if !self.supports_policy_lock() { return Err(BootError::LockedOutFromMonitor); } @@ -246,7 +246,7 @@ impl<'a> Monitor for Ast1060SpiMonitor<'a> { Ok(()) } - fn verify_policy_locked(&self, instance: MonitorInstance) -> BootResult<()> { + fn verify_policy_locked(&self, instance: SpiMonitorId) -> BootResult<()> { if !self.supports_policy_lock() { return Ok(()); } diff --git a/target/ast10x0/board/src/spim_wiring.rs b/target/ast10x0/board/src/spim_wiring.rs index d33289db..5698acf1 100644 --- a/target/ast10x0/board/src/spim_wiring.rs +++ b/target/ast10x0/board/src/spim_wiring.rs @@ -16,7 +16,7 @@ use ast10x0_peripherals::scu::{ }; use ast10x0_peripherals::smc::SmcController; use ast10x0_peripherals::spimonitor::{ - LockedSpiMonitor, MonitorPolicy, SpiMonitor, SpiMonitorController, SpiMonitorError, + LockedSpiMonitor, SpiMonitorPolicy, SpiMonitor, SpiMonitorController, SpiMonitorError, Uninitialized, }; @@ -96,7 +96,7 @@ impl From for SpimWiringError { /// /// Order: validate → SCU route → passthrough → ext-mux → MISO multi-func → /// SPIPF policy → SPIPF lock. The lock is one-way; an empty -/// `MonitorPolicy::empty()` combined with lock will brick the SPI bus until +/// `SpiMonitorPolicy::empty()` combined with lock will brick the SPI bus until /// reset, so callers should pass a vetted preset (see [`presets`]). /// /// # Safety @@ -106,7 +106,7 @@ pub unsafe fn apply_spim_wiring( scu: &ScuRegisters, controller_id: SmcController, wiring: SpimWiring, - policy: &MonitorPolicy, + policy: &SpiMonitorPolicy, ) -> Result { validate_controller_for_source(controller_id, wiring.source)?; scu.validate_spim_instance(wiring.instance)?; @@ -144,9 +144,9 @@ fn validate_controller_for_source( } } -/// Built-in `MonitorPolicy` presets vetted against the BMC's flash opcode set. +/// Built-in `SpiMonitorPolicy` presets vetted against the BMC's flash opcode set. pub mod presets { - use ast10x0_peripherals::spimonitor::MonitorPolicy; + use ast10x0_peripherals::spimonitor::SpiMonitorPolicy; /// Allow-list for the BMC's normal flash opcodes covering both 3-byte and /// 4-byte addressing variants. Empty `regions` (no address-privilege @@ -159,8 +159,8 @@ pub mod presets { /// `RDSR` (`0x05`), `WREN` (`0x06`), `WRDI` (`0x04`), /// `RDID` (`0x9F`), `RSTEN` (`0x66`), `RST` (`0x99`). #[must_use] - pub const fn bmc_default_policy() -> MonitorPolicy { - let mut p = MonitorPolicy::empty(); + pub const fn bmc_default_policy() -> SpiMonitorPolicy { + let mut p = SpiMonitorPolicy::empty(); p.allow_commands[0] = 0x03; // READ p.allow_commands[1] = 0x0B; // FAST_READ p.allow_commands[2] = 0x0C; // FAST_READ_4B diff --git a/target/ast10x0/peripherals/spimonitor/controller.rs b/target/ast10x0/peripherals/spimonitor/controller.rs index 39b1a1ee..a3c66f28 100644 --- a/target/ast10x0/peripherals/spimonitor/controller.rs +++ b/target/ast10x0/peripherals/spimonitor/controller.rs @@ -7,10 +7,10 @@ use core::marker::PhantomData; use crate::scu::registers::ScuRegisters; use crate::scu::types::{ScuExtMuxSelect, SpiMonitorInstance}; -use crate::spimonitor::policy::{MonitorPolicy, MAX_REGION_SLOTS}; +use crate::spimonitor::policy::{SpiMonitorPolicy, MAX_REGION_SLOTS}; use crate::spimonitor::registers::{SpiMonitorController, SpiMonitorRegisters}; use crate::spimonitor::types::{ - ExtMuxSel, LockState, MonitorState, PassthroughMode, PrivilegeDirection, PrivilegeOp, Result, + ExtMuxSel, LockState, SpiMonitorState, PassthroughMode, PrivilegeDirection, PrivilegeOp, Result, SpiMonitorError, ViolationLogEntry, }; @@ -92,7 +92,7 @@ impl SpiMonitor { /// Returns `Err(InvalidSlot)` if `allow_command_count` exceeds the command /// table length. Returns `Err(InvalidRegion)` if `region_count` exceeds /// `MAX_REGION_SLOTS`. - pub fn apply_policy(self, policy: &MonitorPolicy) -> Result> { + pub fn apply_policy(self, policy: &SpiMonitorPolicy) -> Result> { if policy.allow_command_count > policy.allow_commands.len() { return Err(SpiMonitorError::InvalidSlot); } @@ -128,8 +128,8 @@ impl SpiMonitor { } #[must_use] - pub const fn state(&self) -> MonitorState { - MonitorState::Uninitialized + pub const fn state(&self) -> SpiMonitorState { + SpiMonitorState::Uninitialized } } @@ -233,8 +233,8 @@ impl SpiMonitor { } #[must_use] - pub const fn state(&self) -> MonitorState { - MonitorState::Configured + pub const fn state(&self) -> SpiMonitorState { + SpiMonitorState::Configured } } @@ -300,8 +300,8 @@ impl SpiMonitor { } #[must_use] - pub const fn state(&self) -> MonitorState { - MonitorState::Locked + pub const fn state(&self) -> SpiMonitorState { + SpiMonitorState::Locked } } diff --git a/target/ast10x0/peripherals/spimonitor/mod.rs b/target/ast10x0/peripherals/spimonitor/mod.rs index 8f649e14..5e289972 100644 --- a/target/ast10x0/peripherals/spimonitor/mod.rs +++ b/target/ast10x0/peripherals/spimonitor/mod.rs @@ -14,14 +14,14 @@ pub use controller::{ Configured, ConfiguredSpiMonitor, Locked, LockedSpiMonitor, SpiMonitor, UninitSpiMonitor, Uninitialized, }; -pub use policy::{MonitorPolicy, MAX_CMD_SLOTS, MAX_REGION_SLOTS}; +pub use policy::{SpiMonitorPolicy, MAX_CMD_SLOTS, MAX_REGION_SLOTS}; pub use registers::{ SpiMonitorController, SpiMonitorRegisters, SPIPF1_BASE, SPIPF2_BASE, SPIPF3_BASE, SPIPF4_BASE, SPIPF_REG_SIZE, }; -pub use traits::Monitor; +pub use traits::SpiMonitorControl; pub use types::{ - BootConfig, BootError, BootPhase, BootResult, ExtMuxSel, LockState, MonitorInstance, - MonitorState, MonitorStatus, MuxSelect, PassthroughMode, PrivilegeDirection, PrivilegeOp, + BootConfig, BootError, BootPhase, BootResult, ExtMuxSel, LockState, SpiMonitorId, + SpiMonitorState, SpiMonitorStatus, MuxSelect, PassthroughMode, PrivilegeDirection, PrivilegeOp, RegionPolicy, Result as SpiMonitorResult, SpiMonitorError, ViolationLogEntry, }; diff --git a/target/ast10x0/peripherals/spimonitor/policy.rs b/target/ast10x0/peripherals/spimonitor/policy.rs index fee1082c..2d969062 100644 --- a/target/ast10x0/peripherals/spimonitor/policy.rs +++ b/target/ast10x0/peripherals/spimonitor/policy.rs @@ -13,14 +13,14 @@ pub const MAX_CMD_SLOTS: usize = 32; /// Policy payload applied to a monitor instance. #[derive(Clone, Debug)] -pub struct MonitorPolicy { +pub struct SpiMonitorPolicy { pub allow_commands: [u8; MAX_CMD_SLOTS], pub allow_command_count: usize, pub regions: [Option; MAX_REGION_SLOTS], pub region_count: usize, } -impl MonitorPolicy { +impl SpiMonitorPolicy { #[must_use] pub const fn empty() -> Self { Self { diff --git a/target/ast10x0/peripherals/spimonitor/profile.rs b/target/ast10x0/peripherals/spimonitor/profile.rs index 4e498dfe..458fc9d9 100644 --- a/target/ast10x0/peripherals/spimonitor/profile.rs +++ b/target/ast10x0/peripherals/spimonitor/profile.rs @@ -4,15 +4,15 @@ //! Built-in SPI monitor policy profiles. //! //! Profiles provide command allow-lists only. Region entries are platform -//! policy and must be added by the caller via `MonitorPolicy::add_region` +//! policy and must be added by the caller via `SpiMonitorPolicy::add_region` //! using the PFM or provisioned manifest for the specific device. -use crate::spimonitor::policy::MonitorPolicy; +use crate::spimonitor::policy::SpiMonitorPolicy; /// Runtime profile: read-focused allow-list suitable for steady-state boot/runtime. #[must_use] -pub const fn runtime_read_only() -> MonitorPolicy { - let mut p = MonitorPolicy::empty(); +pub const fn runtime_read_only() -> SpiMonitorPolicy { + let mut p = SpiMonitorPolicy::empty(); p.allow_commands[0] = 0x03; // READ p.allow_commands[1] = 0x0B; // FAST_READ p.allow_commands[2] = 0x9F; // RDID @@ -22,7 +22,7 @@ pub const fn runtime_read_only() -> MonitorPolicy { /// Update profile: expands allow-list for controlled erase/program flows. #[must_use] -pub const fn firmware_update_window() -> MonitorPolicy { +pub const fn firmware_update_window() -> SpiMonitorPolicy { let mut p = runtime_read_only(); p.allow_commands[3] = 0x06; // WREN p.allow_commands[4] = 0x20; // SE diff --git a/target/ast10x0/peripherals/spimonitor/traits.rs b/target/ast10x0/peripherals/spimonitor/traits.rs index af455d6c..a427740d 100644 --- a/target/ast10x0/peripherals/spimonitor/traits.rs +++ b/target/ast10x0/peripherals/spimonitor/traits.rs @@ -7,7 +7,7 @@ //! with SPI monitor hardware during platform initialization. use super::types::{ - BootError, BootResult, MonitorInstance, MonitorStatus, MuxSelect, PrivilegeDirection, + BootError, BootResult, SpiMonitorId, SpiMonitorStatus, MuxSelect, PrivilegeDirection, PrivilegeOp, }; @@ -16,18 +16,18 @@ use super::types::{ /// Implementations of this trait provide register-level access to SPI monitor /// blocks via PAC or other register models. Boot code uses this trait to remain /// independent of the concrete register implementation. -pub trait Monitor { +pub trait SpiMonitorControl { /// Set monitor mux to ROT or Host control. - fn set_mux(&mut self, instance: MonitorInstance, mux: MuxSelect) -> BootResult<()>; + fn set_mux(&mut self, instance: SpiMonitorId, mux: MuxSelect) -> BootResult<()>; /// Read current mux setting. - fn read_mux(&self, instance: MonitorInstance) -> BootResult; + fn read_mux(&self, instance: SpiMonitorId) -> BootResult; /// Soft reset monitor (clears status/logs, preserves policy). - fn soft_reset(&mut self, instance: MonitorInstance) -> BootResult<()>; + fn soft_reset(&mut self, instance: SpiMonitorId) -> BootResult<()>; /// Hardware reset monitor (full reset of all state). - fn hardware_reset(&mut self, instance: MonitorInstance) -> BootResult<()>; + fn hardware_reset(&mut self, instance: SpiMonitorId) -> BootResult<()>; /// Configure an address privilege region. /// @@ -39,7 +39,7 @@ pub trait Monitor { /// * `op` - Allow or Block access fn set_address_privilege( &mut self, - instance: MonitorInstance, + instance: SpiMonitorId, start_addr: u32, end_addr: u32, direction: PrivilegeDirection, @@ -47,10 +47,10 @@ pub trait Monitor { ) -> BootResult<()>; /// Read number of configured address privilege regions. - fn read_region_count(&self, instance: MonitorInstance) -> BootResult; + fn read_region_count(&self, instance: SpiMonitorId) -> BootResult; /// Read monitor status snapshot. - fn read_status(&self, instance: MonitorInstance) -> BootResult; + fn read_status(&self, instance: SpiMonitorId) -> BootResult; /// Check if policy write-lock is supported by this monitor. fn supports_policy_lock(&self) -> bool { @@ -60,7 +60,7 @@ pub trait Monitor { /// Lock policy tables to prevent further modification. /// /// Returns error if not supported by hardware. - fn lock_policy(&mut self, _instance: MonitorInstance) -> BootResult<()> { + fn lock_policy(&mut self, _instance: SpiMonitorId) -> BootResult<()> { if !self.supports_policy_lock() { return Err(BootError::LockedOutFromMonitor); } @@ -70,7 +70,7 @@ pub trait Monitor { /// Verify that policy is locked (if supported). /// /// No-op if policy lock is not supported. - fn verify_policy_locked(&self, instance: MonitorInstance) -> BootResult<()> { + fn verify_policy_locked(&self, instance: SpiMonitorId) -> BootResult<()> { if !self.supports_policy_lock() { return Ok(()); } diff --git a/target/ast10x0/peripherals/spimonitor/types.rs b/target/ast10x0/peripherals/spimonitor/types.rs index 4be2d823..a74a8576 100644 --- a/target/ast10x0/peripherals/spimonitor/types.rs +++ b/target/ast10x0/peripherals/spimonitor/types.rs @@ -52,7 +52,7 @@ pub enum LockState { /// High-level monitor lifecycle stage. #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum MonitorState { +pub enum SpiMonitorState { Uninitialized, Configured, Locked, @@ -143,7 +143,7 @@ pub type Result = core::result::Result; /// /// Maps to SPIPF1-4 hardware blocks on AST10x0. #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum MonitorInstance { +pub enum SpiMonitorId { /// SPIPF1 (0x7E79_1000) - typically BMC/SMC flash Spim0, /// SPIPF2 (0x7E79_2000) - typically BMC dual flash @@ -186,7 +186,7 @@ impl From for MuxSelect { /// Monitor status snapshot at a point in time. #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct MonitorStatus { +pub struct SpiMonitorStatus { /// Current mux routing pub mux: MuxSelect, /// Whether policy tables are write-locked From 79135e1fd8e9295767b2d5f71df7ed7b3d1d6db2 Mon Sep 17 00:00:00 2001 From: Christina Quast Date: Thu, 16 Jul 2026 11:07:39 +0200 Subject: [PATCH 3/4] fwmanager: Add BootControl trait and HAL adapter Introduce the Boot Orchestrator's actuation capability: the BootControl trait (hold_in_reset / release) and HalBootControl, which binds one HAL ResetControl line to a managed device. Includes a host unit test verifying that holding a device in reset asserts exactly its configured line. Includes tests that release deasserts the device's configured line and that a controller error surfaces through BootControl unchanged. Extend the fake reset controller with opt-in failure injection to drive the error case. Closes: https://github.com/9elements/openprot/issues/2 Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Christina Quast --- services/fwmanager/api/BUILD.bazel | 22 +++ services/fwmanager/api/src/lib.rs | 213 +++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 services/fwmanager/api/BUILD.bazel create mode 100644 services/fwmanager/api/src/lib.rs diff --git a/services/fwmanager/api/BUILD.bazel b/services/fwmanager/api/BUILD.bazel new file mode 100644 index 00000000..2ce01c00 --- /dev/null +++ b/services/fwmanager/api/BUILD.bazel @@ -0,0 +1,22 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "fwmanager_api", + srcs = [ + "src/lib.rs", + ], + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "//hal/blocking", + ], +) + +# Host tests: build on the host platform, no kernel/QEMU. +rust_test( + name = "fwmanager_api_test", + crate = ":fwmanager_api", +) diff --git a/services/fwmanager/api/src/lib.rs b/services/fwmanager/api/src/lib.rs new file mode 100644 index 00000000..8269f14e --- /dev/null +++ b/services/fwmanager/api/src/lib.rs @@ -0,0 +1,213 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Device-facing capability traits for the Boot Orchestrator. +//! +//! `BootControl` is the actuation capability: the orchestrator drives a +//! single managed device's reset without knowing which controller line it +//! maps to. The binding of a HAL reset controller line to a device happens +//! once, in platform configuration, via [`HalBootControl`]. + +#![cfg_attr(not(test), no_std)] + +use openprot_hal_blocking::system_control::ResetControl; + +/// Actuation capability: hold a managed device in reset and release it. +/// +/// Stateless pass-through by design — sequencing discipline (hold before +/// release, release only after verification) belongs to the orchestrator +/// flows, where it is observable behavior. +/// +/// # How the orchestrator uses it +/// +/// During verified release the orchestrator parks a device in +/// reset, verifies its active slot while nothing is running, then releases +/// it to boot the image it just checked: +/// +/// ```ignore +/// // `dev` is this device's BootControl, obtained from the registry. +/// fn verified_release(dev: &mut D) -> Result<(), D::Error> { +/// dev.hold_in_reset()?; // freeze the device; its flash is now safe to inspect +/// verify_active_slot()?; // re-hash + signature check (a separate capability) +/// dev.release()?; // run the just-verified image +/// Ok(()) +/// } +/// ``` +/// +/// In a trial boot the same hold/release pair brackets a +/// watchdog-bounded window; the new slot is committed only if a good boot is +/// observed, otherwise the device falls back to the previous slot: +/// +/// ```ignore +/// dev.hold_in_reset()?; +/// store.set_trial(new_slot)?; // tentative boot selection — not yet committed +/// dev.release()?; // boot the trial image +/// match monitor.await_boot(window)? { +/// Booted => store.commit(new_slot)?, // observed good => make it active +/// Failed | Timeout => { /* nothing committed; previous slot still active */ } +/// } +/// ``` +pub trait BootControl { + /// The error type reported by this device's boot control. + type Error: core::fmt::Debug; + + /// Holds the device in reset. + fn hold_in_reset(&mut self) -> Result<(), Self::Error>; + + /// Releases the device from reset. + fn release(&mut self) -> Result<(), Self::Error>; +} + +/// Binds one reset line of a HAL reset controller to one managed device. +pub struct HalBootControl { + controller: C, + reset_id: C::ResetId, +} + +impl HalBootControl { + /// Creates the binding of `controller`'s line `reset_id` to a device. + pub fn new(controller: C, reset_id: C::ResetId) -> Self { + Self { + controller, + reset_id, + } + } + + /// Read access to the underlying controller. + pub fn controller(&self) -> &C { + &self.controller + } +} + +impl BootControl for HalBootControl { + type Error = C::Error; + + fn hold_in_reset(&mut self) -> Result<(), Self::Error> { + self.controller.reset_assert(&self.reset_id) + } + + fn release(&mut self) -> Result<(), Self::Error> { + self.controller.reset_deassert(&self.reset_id) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::time::Duration; + use openprot_hal_blocking::system_control::{Error, ErrorKind, ErrorType}; + + // Normally set in config.rs + const BMC_LINE: u8 = 7; + + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + enum Call { + Assert(u8), + Deassert(u8), + } + + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + struct MockError(ErrorKind); + + impl Error for MockError { + fn kind(&self) -> ErrorKind { + self.0 + } + } + + /// Mock HAL reset controller: records every call it receives. + struct MockResetController { + calls: Vec, + fail: Option, + } + + impl MockResetController { + fn new() -> Self { + Self { + calls: Vec::new(), + fail: None, + } + } + + fn failing(kind: ErrorKind) -> Self { + Self { + calls: Vec::new(), + fail: Some(kind), + } + } + + fn calls(&self) -> &[Call] { + &self.calls + } + } + + impl ErrorType for MockResetController { + type Error = MockError; + } + + impl ResetControl for MockResetController { + type ResetId = u8; // Reset line is GPIO here. Real driver should use Enum + + fn reset_assert(&mut self, reset_id: &u8) -> Result<(), MockError> { + if let Some(kind) = self.fail { + return Err(MockError(kind)); + } + self.calls.push(Call::Assert(*reset_id)); + Ok(()) + } + + fn reset_deassert(&mut self, reset_id: &u8) -> Result<(), MockError> { + if let Some(kind) = self.fail { + return Err(MockError(kind)); + } + self.calls.push(Call::Deassert(*reset_id)); + Ok(()) + } + + fn reset_pulse(&mut self, _: &u8, _: Duration) -> Result<(), MockError> { + panic!( + "BootControl must never pulse: hold and release are distinct orchestrator steps" + ); + } + + fn reset_is_asserted(&self, _: &u8) -> Result { + panic!("BootControl does not query line state"); + } + } + + // `hold_in_reset()` must assert exactly the configured line (BMC = 7) + // and nothing else. + #[test] + fn holding_a_device_in_reset_asserts_its_configured_line() { + let mut bmc = HalBootControl::new(MockResetController::new(), BMC_LINE); + + bmc.hold_in_reset().expect("hold_in_reset failed"); + + assert_eq!(bmc.controller().calls(), &[Call::Assert(BMC_LINE)]); + } + + #[test] + fn holding_a_device_in_reset_deasserts_its_configured_line() { + let mut bmc = HalBootControl::new(MockResetController::new(), BMC_LINE); + + bmc.hold_in_reset().expect("hold_in_reset failed"); + bmc.release().expect("release failed"); + + assert_eq!( + bmc.controller().calls(), + &[Call::Assert(BMC_LINE), Call::Deassert(BMC_LINE)] + ); + } + + #[test] + fn controller_error_propagates_through_boot_control() { + let mut bmc = HalBootControl::new( + MockResetController::failing(ErrorKind::InvalidResetId), + BMC_LINE, + ); + let err = bmc + .hold_in_reset() + .expect_err("expected the controller error to propagate"); + assert_eq!(err.kind(), ErrorKind::InvalidResetId); + } +} From 54e276485ecbed68df9260faabcacdeae981465f Mon Sep 17 00:00:00 2001 From: Christina Quast Date: Thu, 16 Jul 2026 17:52:36 +0200 Subject: [PATCH 4/4] fwmanager: Extract BootControl into its own module Move the BootControl trait and HalBootControl out of lib.rs into boot_control.rs, so that public lib.rs API stays flat (fwmanager_api::BootControl, ::HalBootControl). Pure move, no code change; the same BootControl host tests pass. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Christina Quast (cherry picked from commit 68665407fda86b98657e645bc8dda2e96f85ae0d) Signed-off-by: Christina Quast --- services/fwmanager/api/BUILD.bazel | 1 + services/fwmanager/api/src/boot_control.rs | 206 +++++++++++++++++++++ services/fwmanager/api/src/lib.rs | 202 +------------------- 3 files changed, 209 insertions(+), 200 deletions(-) create mode 100644 services/fwmanager/api/src/boot_control.rs diff --git a/services/fwmanager/api/BUILD.bazel b/services/fwmanager/api/BUILD.bazel index 2ce01c00..d78ebb6a 100644 --- a/services/fwmanager/api/BUILD.bazel +++ b/services/fwmanager/api/BUILD.bazel @@ -6,6 +6,7 @@ load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") rust_library( name = "fwmanager_api", srcs = [ + "src/boot_control.rs", "src/lib.rs", ], edition = "2024", diff --git a/services/fwmanager/api/src/boot_control.rs b/services/fwmanager/api/src/boot_control.rs new file mode 100644 index 00000000..5f33c810 --- /dev/null +++ b/services/fwmanager/api/src/boot_control.rs @@ -0,0 +1,206 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Actuation capability: hold a managed device in reset and release it. + +use openprot_hal_blocking::system_control::ResetControl; + +/// Actuation capability: hold a managed device in reset and release it. +/// +/// Stateless pass-through by design — sequencing discipline (hold before +/// release, release only after verification) belongs to the orchestrator +/// flows, where it is observable behavior. +/// +/// # How the orchestrator uses it +/// +/// During verified release the orchestrator parks a device in +/// reset, verifies its active slot while nothing is running, then releases +/// it to boot the image it just checked: +/// +/// ```ignore +/// // `dev` is this device's BootControl, obtained from the registry. +/// fn verified_release(dev: &mut D) -> Result<(), D::Error> { +/// dev.hold_in_reset()?; // freeze the device; its flash is now safe to inspect +/// verify_active_slot()?; // re-hash + signature check (a separate capability) +/// dev.release()?; // run the just-verified image +/// Ok(()) +/// } +/// ``` +/// +/// In a trial boot the same hold/release pair brackets a +/// watchdog-bounded window; the new slot is committed only if a good boot is +/// observed, otherwise the device falls back to the previous slot: +/// +/// ```ignore +/// dev.hold_in_reset()?; +/// store.set_trial(new_slot)?; // tentative boot selection — not yet committed +/// dev.release()?; // boot the trial image +/// match monitor.await_boot(window)? { +/// Booted => store.commit(new_slot)?, // observed good => make it active +/// Failed | Timeout => { /* nothing committed; previous slot still active */ } +/// } +/// ``` +pub trait BootControl { + /// The error type reported by this device's boot control. + type Error: core::fmt::Debug; + + /// Holds the device in reset. + fn hold_in_reset(&mut self) -> Result<(), Self::Error>; + + /// Releases the device from reset. + fn release(&mut self) -> Result<(), Self::Error>; +} + +/// Binds one reset line of a HAL reset controller to one managed device. +pub struct HalBootControl { + controller: C, + reset_id: C::ResetId, +} + +impl HalBootControl { + /// Creates the binding of `controller`'s line `reset_id` to a device. + pub fn new(controller: C, reset_id: C::ResetId) -> Self { + Self { + controller, + reset_id, + } + } + + /// Read access to the underlying controller. + pub fn controller(&self) -> &C { + &self.controller + } +} + +impl BootControl for HalBootControl { + type Error = C::Error; + + fn hold_in_reset(&mut self) -> Result<(), Self::Error> { + self.controller.reset_assert(&self.reset_id) + } + + fn release(&mut self) -> Result<(), Self::Error> { + self.controller.reset_deassert(&self.reset_id) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::time::Duration; + use openprot_hal_blocking::system_control::{Error, ErrorKind, ErrorType}; + + // Normally set in config.rs + const BMC_LINE: u8 = 7; + + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + enum Call { + Assert(u8), + Deassert(u8), + } + + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + struct FakeError(ErrorKind); + + impl Error for FakeError { + fn kind(&self) -> ErrorKind { + self.0 + } + } + + /// Fake HAL reset controller: records every call it receives. + struct FakeResetController { + calls: Vec, + fail: Option, + } + + impl FakeResetController { + fn new() -> Self { + Self { + calls: Vec::new(), + fail: None, + } + } + + fn failing(kind: ErrorKind) -> Self { + Self { + calls: Vec::new(), + fail: Some(kind), + } + } + + fn calls(&self) -> &[Call] { + &self.calls + } + } + + impl ErrorType for FakeResetController { + type Error = FakeError; + } + + impl ResetControl for FakeResetController { + type ResetId = u8; // Reset line is GPIO here. Real driver should use Enum + + fn reset_assert(&mut self, reset_id: &u8) -> Result<(), FakeError> { + if let Some(kind) = self.fail { + return Err(FakeError(kind)); + } + self.calls.push(Call::Assert(*reset_id)); + Ok(()) + } + + fn reset_deassert(&mut self, reset_id: &u8) -> Result<(), FakeError> { + if let Some(kind) = self.fail { + return Err(FakeError(kind)); + } + self.calls.push(Call::Deassert(*reset_id)); + Ok(()) + } + + fn reset_pulse(&mut self, _: &u8, _: Duration) -> Result<(), FakeError> { + panic!( + "BootControl must never pulse: hold and release are distinct orchestrator steps" + ); + } + + fn reset_is_asserted(&self, _: &u8) -> Result { + panic!("BootControl does not query line state"); + } + } + + // `hold_in_reset()` must assert exactly the configured line (BMC = 7) + // and nothing else. + #[test] + fn holding_a_device_in_reset_asserts_its_configured_line() { + let mut bmc = HalBootControl::new(FakeResetController::new(), BMC_LINE); + + bmc.hold_in_reset().expect("hold_in_reset failed"); + + assert_eq!(bmc.controller().calls(), &[Call::Assert(BMC_LINE)]); + } + + #[test] + fn holding_a_device_in_reset_deasserts_its_configured_line() { + let mut bmc = HalBootControl::new(FakeResetController::new(), BMC_LINE); + + bmc.hold_in_reset().expect("hold_in_reset failed"); + bmc.release().expect("release failed"); + + assert_eq!( + bmc.controller().calls(), + &[Call::Assert(BMC_LINE), Call::Deassert(BMC_LINE)] + ); + } + + #[test] + fn controller_error_propagates_through_boot_control() { + let mut bmc = HalBootControl::new( + FakeResetController::failing(ErrorKind::InvalidResetId), + BMC_LINE, + ); + let err = bmc + .hold_in_reset() + .expect_err("expected the controller error to propagate"); + assert_eq!(err.kind(), ErrorKind::InvalidResetId); + } +} diff --git a/services/fwmanager/api/src/lib.rs b/services/fwmanager/api/src/lib.rs index 8269f14e..8ab488a2 100644 --- a/services/fwmanager/api/src/lib.rs +++ b/services/fwmanager/api/src/lib.rs @@ -10,204 +10,6 @@ #![cfg_attr(not(test), no_std)] -use openprot_hal_blocking::system_control::ResetControl; +mod boot_control; -/// Actuation capability: hold a managed device in reset and release it. -/// -/// Stateless pass-through by design — sequencing discipline (hold before -/// release, release only after verification) belongs to the orchestrator -/// flows, where it is observable behavior. -/// -/// # How the orchestrator uses it -/// -/// During verified release the orchestrator parks a device in -/// reset, verifies its active slot while nothing is running, then releases -/// it to boot the image it just checked: -/// -/// ```ignore -/// // `dev` is this device's BootControl, obtained from the registry. -/// fn verified_release(dev: &mut D) -> Result<(), D::Error> { -/// dev.hold_in_reset()?; // freeze the device; its flash is now safe to inspect -/// verify_active_slot()?; // re-hash + signature check (a separate capability) -/// dev.release()?; // run the just-verified image -/// Ok(()) -/// } -/// ``` -/// -/// In a trial boot the same hold/release pair brackets a -/// watchdog-bounded window; the new slot is committed only if a good boot is -/// observed, otherwise the device falls back to the previous slot: -/// -/// ```ignore -/// dev.hold_in_reset()?; -/// store.set_trial(new_slot)?; // tentative boot selection — not yet committed -/// dev.release()?; // boot the trial image -/// match monitor.await_boot(window)? { -/// Booted => store.commit(new_slot)?, // observed good => make it active -/// Failed | Timeout => { /* nothing committed; previous slot still active */ } -/// } -/// ``` -pub trait BootControl { - /// The error type reported by this device's boot control. - type Error: core::fmt::Debug; - - /// Holds the device in reset. - fn hold_in_reset(&mut self) -> Result<(), Self::Error>; - - /// Releases the device from reset. - fn release(&mut self) -> Result<(), Self::Error>; -} - -/// Binds one reset line of a HAL reset controller to one managed device. -pub struct HalBootControl { - controller: C, - reset_id: C::ResetId, -} - -impl HalBootControl { - /// Creates the binding of `controller`'s line `reset_id` to a device. - pub fn new(controller: C, reset_id: C::ResetId) -> Self { - Self { - controller, - reset_id, - } - } - - /// Read access to the underlying controller. - pub fn controller(&self) -> &C { - &self.controller - } -} - -impl BootControl for HalBootControl { - type Error = C::Error; - - fn hold_in_reset(&mut self) -> Result<(), Self::Error> { - self.controller.reset_assert(&self.reset_id) - } - - fn release(&mut self) -> Result<(), Self::Error> { - self.controller.reset_deassert(&self.reset_id) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use core::time::Duration; - use openprot_hal_blocking::system_control::{Error, ErrorKind, ErrorType}; - - // Normally set in config.rs - const BMC_LINE: u8 = 7; - - #[derive(Debug, PartialEq, Eq, Clone, Copy)] - enum Call { - Assert(u8), - Deassert(u8), - } - - #[derive(Debug, PartialEq, Eq, Clone, Copy)] - struct MockError(ErrorKind); - - impl Error for MockError { - fn kind(&self) -> ErrorKind { - self.0 - } - } - - /// Mock HAL reset controller: records every call it receives. - struct MockResetController { - calls: Vec, - fail: Option, - } - - impl MockResetController { - fn new() -> Self { - Self { - calls: Vec::new(), - fail: None, - } - } - - fn failing(kind: ErrorKind) -> Self { - Self { - calls: Vec::new(), - fail: Some(kind), - } - } - - fn calls(&self) -> &[Call] { - &self.calls - } - } - - impl ErrorType for MockResetController { - type Error = MockError; - } - - impl ResetControl for MockResetController { - type ResetId = u8; // Reset line is GPIO here. Real driver should use Enum - - fn reset_assert(&mut self, reset_id: &u8) -> Result<(), MockError> { - if let Some(kind) = self.fail { - return Err(MockError(kind)); - } - self.calls.push(Call::Assert(*reset_id)); - Ok(()) - } - - fn reset_deassert(&mut self, reset_id: &u8) -> Result<(), MockError> { - if let Some(kind) = self.fail { - return Err(MockError(kind)); - } - self.calls.push(Call::Deassert(*reset_id)); - Ok(()) - } - - fn reset_pulse(&mut self, _: &u8, _: Duration) -> Result<(), MockError> { - panic!( - "BootControl must never pulse: hold and release are distinct orchestrator steps" - ); - } - - fn reset_is_asserted(&self, _: &u8) -> Result { - panic!("BootControl does not query line state"); - } - } - - // `hold_in_reset()` must assert exactly the configured line (BMC = 7) - // and nothing else. - #[test] - fn holding_a_device_in_reset_asserts_its_configured_line() { - let mut bmc = HalBootControl::new(MockResetController::new(), BMC_LINE); - - bmc.hold_in_reset().expect("hold_in_reset failed"); - - assert_eq!(bmc.controller().calls(), &[Call::Assert(BMC_LINE)]); - } - - #[test] - fn holding_a_device_in_reset_deasserts_its_configured_line() { - let mut bmc = HalBootControl::new(MockResetController::new(), BMC_LINE); - - bmc.hold_in_reset().expect("hold_in_reset failed"); - bmc.release().expect("release failed"); - - assert_eq!( - bmc.controller().calls(), - &[Call::Assert(BMC_LINE), Call::Deassert(BMC_LINE)] - ); - } - - #[test] - fn controller_error_propagates_through_boot_control() { - let mut bmc = HalBootControl::new( - MockResetController::failing(ErrorKind::InvalidResetId), - BMC_LINE, - ); - let err = bmc - .hold_in_reset() - .expect_err("expected the controller error to propagate"); - assert_eq!(err.kind(), ErrorKind::InvalidResetId); - } -} +pub use boot_control::{BootControl, HalBootControl};