| // Licensed under the Apache-2.0 license |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| //! Board-level SPI Monitor orchestration. |
| //! |
| //! `Ast1060Monitor` 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 |
| //! operations delegate to SPI monitor registers. |
| |
| 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::types::{ |
| BootError, BootResult, MonitorInstance, MonitorStatus, MuxSelect, PrivilegeDirection, |
| PrivilegeOp, |
| }; |
| |
| /// Board-level Monitor orchestrator. |
| /// |
| /// Holds mutable references to both SCU and SPIPF register blocks, allowing it to |
| /// orchestrate mux operations (via SCU) and enforcement/filtering (via SPIPF) from |
| /// a single unified interface. |
| /// |
| /// # Example |
| /// |
| /// ```ignore |
| /// let mut board = Ast1060Board::init(); |
| /// let mut monitor = board.monitor(); |
| /// monitor.set_mux(MonitorInstance::Spim0, MuxSelect::RotControl)?; |
| /// monitor.set_address_privilege(/*...*/)?; |
| /// monitor.lock_policy(MonitorInstance::Spim0)?; |
| /// ``` |
| pub struct Ast1060Monitor<'a> { |
| scu: &'a mut ScuRegisters, |
| spipf: &'a mut [SpiMonitorRegisters; 4], |
| read_blocked_region_count: u8, |
| #[allow(dead_code)] |
| write_blocked_region_count: u8, |
| } |
| |
| impl<'a> Ast1060Monitor<'a> { |
| /// Create a new board-level Monitor with access to both SCU and SPIPF. |
| pub fn new( |
| scu: &'a mut ScuRegisters, |
| spipf: &'a mut [SpiMonitorRegisters; 4], |
| read_blocked_region_count: u8, |
| write_blocked_region_count: u8, |
| ) -> Self { |
| Self { |
| scu, |
| spipf, |
| read_blocked_region_count, |
| write_blocked_region_count, |
| } |
| } |
| |
| /// Get the register accessor for the specified monitor instance. |
| #[inline] |
| fn regs(&self, instance: MonitorInstance) -> &SpiMonitorRegisters { |
| match instance { |
| MonitorInstance::Spim0 => &self.spipf[0], |
| MonitorInstance::Spim1 => &self.spipf[1], |
| MonitorInstance::Spim2 => &self.spipf[2], |
| MonitorInstance::Spim3 => &self.spipf[3], |
| } |
| } |
| |
| /// Get mutable reference to register accessor (for write operations). |
| #[inline] |
| fn regs_mut(&mut self, instance: MonitorInstance) -> &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], |
| } |
| } |
| |
| /// Map MonitorInstance to SCU SpiMonitorInstance for routing operations. |
| fn instance_to_scu(instance: MonitorInstance) -> SpiMonitorInstance { |
| match instance { |
| MonitorInstance::Spim0 => SpiMonitorInstance::Spim0, |
| MonitorInstance::Spim1 => SpiMonitorInstance::Spim1, |
| MonitorInstance::Spim2 => SpiMonitorInstance::Spim2, |
| MonitorInstance::Spim3 => SpiMonitorInstance::Spim3, |
| } |
| } |
| |
| /// Map MuxSelect to SCU external mux select value (board ext-mux polarity). |
| /// |
| /// `Mux1` = RoT SPI master path; `Mux0` = host (BMC) flash access. |
| fn mux_to_scu(mux: MuxSelect) -> ScuExtMuxSelect { |
| match mux { |
| MuxSelect::RotControl => ScuExtMuxSelect::Mux1, |
| MuxSelect::HostControl => ScuExtMuxSelect::Mux0, |
| } |
| } |
| |
| /// Map SCU external mux select back to MuxSelect. |
| fn scu_to_mux(scu_mux: ScuExtMuxSelect) -> MuxSelect { |
| match scu_mux { |
| ScuExtMuxSelect::Mux0 => MuxSelect::HostControl, |
| ScuExtMuxSelect::Mux1 => MuxSelect::RotControl, |
| } |
| } |
| |
| /// Extract enforcement active flag from CTRL register. |
| /// Enforcement is active when passthrough bits are NOT set. |
| /// - SPIPF000[0] = enbl_single_bit_passthrough |
| /// - SPIPF000[1] = enbl_multiple_bit_passthrough |
| /// When both bits are 0, enforcement is active and SPI commands are filtered. |
| fn is_enforcement_active(ctrl: u32) -> bool { |
| let pass_bits = ctrl & 0x3; |
| pass_bits == 0 // Enforcement active when passthrough disabled |
| } |
| |
| /// Extract policy lock flag from lock/status register. |
| /// Policy is locked when write-disable bits are set in SPIPF07C. |
| /// SPIPF07C bit 0: wr_dis_of_spipfwa (write disable for address privilege tables) |
| fn is_policy_locked(lock_status: u32) -> bool { |
| // Write-disable bit in SPIPF07C |
| // When this bit is set, policy tables cannot be modified |
| (lock_status >> 0) & 0x1 != 0 |
| } |
| } |
| |
| impl<'a> Monitor for Ast1060Monitor<'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. |
| let scu_instance = Self::instance_to_scu(instance); |
| let scu_mux = Self::mux_to_scu(mux); |
| self.scu.set_spim_ext_mux(scu_instance, scu_mux); |
| Ok(()) |
| } |
| |
| fn read_mux(&self, instance: MonitorInstance) -> BootResult<MuxSelect> { |
| // 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<()> { |
| // Soft reset clears status/logs but preserves policy: pulse SPIPF000 |
| // `sweng_rst` (bit 15). |
| self.regs_mut(instance).sw_reset(); |
| Ok(()) |
| } |
| |
| fn hardware_reset(&mut self, instance: MonitorInstance) -> 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. |
| // Current: uses bit 8 in SPIPF000 as placeholder. |
| // NON-BLOCKING TODO 2: Implement polling/timeout after write. |
| // Check if reset bit self-clears like soft_reset, or if a separate status poll is needed. |
| let mut ctrl = regs.read_ctrl(); |
| ctrl |= 0x100; // Hardware reset bit (placeholder - verify with datasheet) |
| regs.write_ctrl(ctrl); |
| // TODO: Poll until bit 8 clears or wait for timeout, similar to soft_reset |
| Ok(()) |
| } |
| |
| fn set_address_privilege( |
| &mut self, |
| instance: MonitorInstance, |
| start_addr: u32, |
| end_addr: u32, |
| _direction: PrivilegeDirection, |
| _op: PrivilegeOp, |
| ) -> BootResult<()> { |
| if end_addr < start_addr { |
| return Err(BootError::InvalidAddress); |
| } |
| |
| let regs = self.regs_mut(instance); |
| |
| // Address filtering in AST10x0 uses 16KB blocks (ACCESS_BLOCK_UNIT). |
| // Each SPIPFWA register controls 32 blocks per register (32 * 16KB = 512KB per register). |
| // NON-BLOCKING TODO: Implement dynamic slot allocation instead of fixed slot 0. |
| // Phase C work: currently allocates all regions to slot 0 for simplicity. |
| // Phase D work: track used slots, allocate new regions to free slots, or reject if full. |
| let slot = 0; |
| |
| // Address filter encoding: SPIPFWA stores address range in HW format. |
| // Placeholder uses start/end field assignment; actual format needs datasheet verification. |
| let entry = ((start_addr & 0xFFF0_0000) << 0) | ((end_addr & 0x0F_FFFF) << 0); |
| regs.write_addr_filter_slot(slot, entry); |
| |
| Ok(()) |
| } |
| |
| fn read_region_count(&self, _instance: MonitorInstance) -> BootResult<u32> { |
| // Region count is tracked in memory via the read_blocked_region_count |
| // and write_blocked_region_count 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<MonitorStatus> { |
| let regs = self.regs(instance); |
| let ctrl = regs.read_ctrl(); |
| let lock_status = regs.read_lock_status(); |
| |
| // Read actual mux state from SCU (not placeholder from SPIPF) |
| let scu_instance = Self::instance_to_scu(instance); |
| let scu_mux = self.scu.get_spim_ext_mux(scu_instance); |
| let mux = Self::scu_to_mux(scu_mux); |
| |
| Ok(MonitorStatus { |
| mux, |
| policy_locked: Self::is_policy_locked(lock_status), |
| enforcement_active: Self::is_enforcement_active(ctrl), |
| violation_count: 0, // NON-BLOCKING TODO: Implement violation log register reading |
| // Future: read from SPIPF violation count register if available |
| }) |
| } |
| |
| fn supports_policy_lock(&self) -> bool { |
| // Check hardware capability from a known register or constant. |
| // Placeholder: assume AST10x0 always supports policy lock. |
| true |
| } |
| |
| fn lock_policy(&mut self, instance: MonitorInstance) -> BootResult<()> { |
| if !self.supports_policy_lock() { |
| return Err(BootError::LockedOutFromMonitor); |
| } |
| |
| let regs = self.regs_mut(instance); |
| // Policy lock is controlled by SPIPF07C register. |
| // Bit 0 is `wr_dis_of_spipfwa` (write disable for address tables). |
| let mut lock_status = regs.read_lock_status(); |
| lock_status |= 0x1; // Set wr_dis_of_spipfwa bit |
| regs.write_lock_status(lock_status); |
| Ok(()) |
| } |
| |
| fn verify_policy_locked(&self, instance: MonitorInstance) -> BootResult<()> { |
| if !self.supports_policy_lock() { |
| return Ok(()); |
| } |
| |
| let regs = self.regs(instance); |
| let lock_status = regs.read_lock_status(); |
| if Self::is_policy_locked(lock_status) { |
| Ok(()) |
| } else { |
| Err(BootError::PolicyVerificationFailed) |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| #[test] |
| fn test_mux_mapping() { |
| assert_eq!( |
| Ast1060Monitor::mux_to_scu(MuxSelect::RotControl), |
| ScuExtMuxSelect::Mux1 |
| ); |
| assert_eq!( |
| Ast1060Monitor::mux_to_scu(MuxSelect::HostControl), |
| ScuExtMuxSelect::Mux0 |
| ); |
| assert_eq!( |
| Ast1060Monitor::scu_to_mux(ScuExtMuxSelect::Mux1), |
| MuxSelect::RotControl |
| ); |
| assert_eq!( |
| Ast1060Monitor::scu_to_mux(ScuExtMuxSelect::Mux0), |
| MuxSelect::HostControl |
| ); |
| } |
| |
| #[test] |
| fn test_enforcement_flag() { |
| assert!(!Ast1060Monitor::is_enforcement_active(0)); |
| assert!(Ast1060Monitor::is_enforcement_active(1 << 4)); |
| } |
| |
| #[test] |
| fn test_lock_flag() { |
| assert!(!Ast1060Monitor::is_policy_locked(0)); |
| assert!(Ast1060Monitor::is_policy_locked(1)); |
| } |
| } |