| // Licensed under the Apache-2.0 license |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| //! SPI monitor controller facade. |
| |
| use core::marker::PhantomData; |
| |
| use crate::scu::registers::ScuRegisters; |
| use crate::scu::types::{ScuExtMuxSelect, SpiMonitorInstance}; |
| use crate::spimonitor::addr_priv; |
| use crate::spimonitor::cmd_table; |
| use crate::spimonitor::policy::{MonitorPolicy, MAX_REGION_SLOTS}; |
| use crate::spimonitor::registers::{SpiMonitorController, SpiMonitorRegisters}; |
| use crate::spimonitor::types::{ |
| ExtMuxSel, LockState, MonitorState, PassthroughMode, Result, |
| SpiMonitorError, ViolationLogEntry, |
| }; |
| |
| /// Typestate: monitor is created but policy is not yet applied. |
| pub struct Uninitialized; |
| /// Typestate: policy tables are programmed and can still be changed. |
| pub struct Configured; |
| /// Typestate: policy is locked and runtime-mutating APIs are unavailable. |
| pub struct Locked; |
| |
| /// Generic SPI monitor instance with typestate-enforced lifecycle. |
| pub struct SpiMonitor<Mode> { |
| regs: SpiMonitorRegisters, |
| controller: SpiMonitorController, |
| scu: ScuRegisters, |
| _mode: PhantomData<fn() -> Mode>, |
| } |
| |
| /// Ergonomic alias for an uninitialized SPI monitor handle. |
| pub type UninitSpiMonitor = SpiMonitor<Uninitialized>; |
| /// Ergonomic alias for a configured-but-unlocked SPI monitor handle. |
| pub type ConfiguredSpiMonitor = SpiMonitor<Configured>; |
| /// Ergonomic alias for a locked SPI monitor handle. |
| pub type LockedSpiMonitor = SpiMonitor<Locked>; |
| |
| // --------------------------------------------------------------------------- |
| // Uninitialized state |
| // --------------------------------------------------------------------------- |
| |
| impl SpiMonitor<Uninitialized> { |
| /// Construct a new controller facade for a specific monitor instance. |
| /// |
| /// # Safety |
| /// Caller must guarantee exclusive ownership of the target SPIPF block and SCU. |
| pub const unsafe fn new(controller: SpiMonitorController) -> Self { |
| Self { |
| regs: unsafe { SpiMonitorRegisters::new_for_controller(controller) }, |
| controller, |
| scu: unsafe { ScuRegisters::new_global() }, |
| _mode: PhantomData, |
| } |
| } |
| |
| /// Program command-table and address-filter policy, then transition to |
| /// `Configured`. |
| /// |
| /// 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<SpiMonitor<Configured>> { |
| if policy.allow_command_count > policy.allow_commands.len() { |
| return Err(SpiMonitorError::InvalidSlot); |
| } |
| if policy.region_count > MAX_REGION_SLOTS { |
| return Err(SpiMonitorError::InvalidRegion); |
| } |
| |
| // Program command allow-list table (encoded SPIPFWT words + VALID bit). |
| cmd_table::init_allow_cmd_table( |
| &self.regs, |
| &policy.allow_commands[..policy.allow_command_count], |
| ); |
| |
| // Program address privilege tables (bit-per-16KB SPIPFWA entries). |
| for i in 0..policy.region_count { |
| if let Some(region) = policy.regions[i] { |
| addr_priv::configure_address_privilege( |
| &self.regs, |
| region.direction, |
| region.op, |
| region.start, |
| region.length, |
| )?; |
| } |
| } |
| |
| Ok(SpiMonitor { |
| regs: self.regs, |
| controller: self.controller, |
| scu: self.scu, |
| _mode: PhantomData, |
| }) |
| } |
| |
| #[must_use] |
| pub const fn state(&self) -> MonitorState { |
| MonitorState::Uninitialized |
| } |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Configured state |
| // --------------------------------------------------------------------------- |
| |
| impl SpiMonitor<Configured> { |
| fn scu_instance(&self) -> SpiMonitorInstance { |
| match self.controller { |
| SpiMonitorController::Spim0 => SpiMonitorInstance::Spim0, |
| SpiMonitorController::Spim1 => SpiMonitorInstance::Spim1, |
| SpiMonitorController::Spim2 => SpiMonitorInstance::Spim2, |
| SpiMonitorController::Spim3 => SpiMonitorInstance::Spim3, |
| } |
| } |
| |
| /// Enable the monitor filter (SPIPF000 bit 2) and MISO multi-func pin. |
| pub fn enable(&self) { |
| self.scu |
| .set_spim_miso_multi_func(self.scu_instance(), true); |
| self.regs.set_filter_enable(true); |
| } |
| |
| /// Disable the monitor filter (SPIPF000 bit 2) and MISO multi-func pin. |
| pub fn disable(&self) { |
| self.regs.set_filter_enable(false); |
| self.scu |
| .set_spim_miso_multi_func(self.scu_instance(), false); |
| } |
| |
| /// Configure passthrough mode (SPIPF000 passthrough bit). |
| /// |
| /// When `PassthroughMode::Enabled`, SPI traffic bypasses the filter. |
| pub fn set_passthrough(&self, mode: PassthroughMode) { |
| self.regs |
| .set_single_passthrough(matches!(mode, PassthroughMode::Enabled)); |
| } |
| |
| /// Select the external SPI mux routing. |
| /// |
| /// Platform code maps `Sel0`/`Sel1` to ROT vs BMC/PCH roles. |
| /// |
| /// Correctly uses SCU0F0 register (ext_mux_select_sig_of_spipfN bits) |
| /// for each SPIPF instance. |
| pub fn set_ext_mux(&self, sel: ExtMuxSel) { |
| use crate::scu::types::{ScuExtMuxSelect, SpiMonitorInstance}; |
| |
| let mux_sel = match sel { |
| ExtMuxSel::Sel0 => ScuExtMuxSelect::Mux0, |
| ExtMuxSel::Sel1 => ScuExtMuxSelect::Mux1, |
| }; |
| |
| let instance = match self.controller { |
| SpiMonitorController::Spim0 => SpiMonitorInstance::Spim0, |
| SpiMonitorController::Spim1 => SpiMonitorInstance::Spim1, |
| SpiMonitorController::Spim2 => SpiMonitorInstance::Spim2, |
| SpiMonitorController::Spim3 => SpiMonitorInstance::Spim3, |
| }; |
| |
| self.scu.set_spim_ext_mux(instance, mux_sel); |
| } |
| |
| /// Query the current external SPI mux selection. |
| #[must_use] |
| pub fn get_ext_mux(&self) -> ExtMuxSel { |
| let instance = match self.controller { |
| SpiMonitorController::Spim0 => SpiMonitorInstance::Spim0, |
| SpiMonitorController::Spim1 => SpiMonitorInstance::Spim1, |
| SpiMonitorController::Spim2 => SpiMonitorInstance::Spim2, |
| SpiMonitorController::Spim3 => SpiMonitorInstance::Spim3, |
| }; |
| match self.scu.get_spim_ext_mux(instance) { |
| ScuExtMuxSelect::Mux0 => ExtMuxSel::Sel0, |
| ScuExtMuxSelect::Mux1 => ExtMuxSel::Sel1, |
| } |
| } |
| |
| /// Drain violation log entries into `buf`. Returns the filled slice. |
| /// |
| /// Available in `Configured` state for diagnostic use during bring-up. |
| pub fn drain_log<'a>(&self, buf: &'a mut [ViolationLogEntry]) -> &'a [ViolationLogEntry] { |
| drain_log_impl(&self.regs, buf) |
| } |
| |
| /// Lock monitor policy registers and transition to `Locked`. |
| /// |
| /// Activates all write-protection bits to prevent further policy changes. |
| /// Complete lock sequence: |
| /// - Write-disable SPIPFWA/SPIPFRA (address filter tables) |
| /// - Lock all command table entries |
| /// - Write-disable SPIPF000, SPIPF004, SPIPF010, SPIPF014 |
| pub fn lock(self) -> Result<SpiMonitor<Locked>> { |
| // Placeholder: This single bit write is incomplete. |
| // Full lock requires SPIPF07C write-disable bits. |
| self.regs.modify_ctrl(|bits| *bits |= CTRL_LOCK_BIT); |
| |
| Ok(SpiMonitor { |
| regs: self.regs, |
| controller: self.controller, |
| scu: self.scu, |
| _mode: PhantomData, |
| }) |
| } |
| |
| #[must_use] |
| pub const fn state(&self) -> MonitorState { |
| MonitorState::Configured |
| } |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Locked state |
| // --------------------------------------------------------------------------- |
| |
| impl SpiMonitor<Locked> { |
| /// Configure passthrough mode in locked state. |
| /// |
| /// Passthrough is intentionally available post-lock because it is used |
| /// during mux ownership transitions at runtime (e.g., BMC boot-hold/release). |
| pub fn set_passthrough(&self, mode: PassthroughMode) { |
| self.regs |
| .set_single_passthrough(matches!(mode, PassthroughMode::Enabled)); |
| } |
| |
| /// Select the external SPI mux routing in locked state. |
| /// |
| /// Available post-lock for mux ownership transitions at runtime (e.g., BMC boot-hold/release). |
| /// Uses SCU0F0 register. |
| pub fn set_ext_mux(&self, sel: ExtMuxSel) { |
| let mux = match sel { |
| ExtMuxSel::Sel0 => ScuExtMuxSelect::Mux0, |
| ExtMuxSel::Sel1 => ScuExtMuxSelect::Mux1, |
| }; |
| let instance = match self.controller { |
| SpiMonitorController::Spim0 => SpiMonitorInstance::Spim0, |
| SpiMonitorController::Spim1 => SpiMonitorInstance::Spim1, |
| SpiMonitorController::Spim2 => SpiMonitorInstance::Spim2, |
| SpiMonitorController::Spim3 => SpiMonitorInstance::Spim3, |
| }; |
| self.scu.set_spim_ext_mux(instance, mux); |
| } |
| |
| /// Query the current external SPI mux selection in locked state. |
| #[must_use] |
| pub fn get_ext_mux(&self) -> ExtMuxSel { |
| let instance = match self.controller { |
| SpiMonitorController::Spim0 => SpiMonitorInstance::Spim0, |
| SpiMonitorController::Spim1 => SpiMonitorInstance::Spim1, |
| SpiMonitorController::Spim2 => SpiMonitorInstance::Spim2, |
| SpiMonitorController::Spim3 => SpiMonitorInstance::Spim3, |
| }; |
| match self.scu.get_spim_ext_mux(instance) { |
| ScuExtMuxSelect::Mux0 => ExtMuxSel::Sel0, |
| ScuExtMuxSelect::Mux1 => ExtMuxSel::Sel1, |
| } |
| } |
| |
| /// Drain violation log entries into `buf`. Returns the filled slice. |
| /// |
| /// Caller is responsible for synchronization and log-pointer reset. |
| pub fn drain_log<'a>(&self, buf: &'a mut [ViolationLogEntry]) -> &'a [ViolationLogEntry] { |
| drain_log_impl(&self.regs, buf) |
| } |
| |
| #[must_use] |
| pub const fn lock_state(&self) -> LockState { |
| LockState::Locked |
| } |
| |
| #[must_use] |
| pub const fn state(&self) -> MonitorState { |
| MonitorState::Locked |
| } |
| } |
| |
| // --------------------------------------------------------------------------- |
| // State-independent accessors |
| // --------------------------------------------------------------------------- |
| |
| impl<Mode> SpiMonitor<Mode> { |
| #[must_use] |
| pub fn regs(&self) -> &SpiMonitorRegisters { |
| &self.regs |
| } |
| |
| #[must_use] |
| pub const fn controller(&self) -> SpiMonitorController { |
| self.controller |
| } |
| } |
| |
| // --------------------------------------------------------------------------- |
| // Internal helpers |
| // --------------------------------------------------------------------------- |
| |
| /// SPIPF000 bit positions (from ast1060_pac). |
| #[allow(dead_code)] |
| const CTRL_MULTI_PASSTHROUGH_BIT: u32 = 1 << 1; // enbl_multiple_bit_passthrough() in SPIPF000[1] |
| #[allow(dead_code)] |
| const CTRL_SW_RESET_BIT: u32 = 1 << 15; // sweng_rst() in SPIPF000[15] (SW Engine Reset) |
| #[allow(dead_code)] |
| const CTRL_EXT_MUX_SEL_BIT: u32 = 1 << 2; // PLACEHOLDER - NOT in SPIPF000! See note below. |
| #[allow(dead_code)] |
| const CTRL_LOCK_BIT: u32 = 1 << 31; // PLACEHOLDER - NOT in SPIPF000! See note below. |
| // |
| // NOTE: CTRL_EXT_MUX_SEL and CTRL_LOCK are NOT in SPIPF000 register: |
| // - ExtMux is controlled via SCU0F0 register (ext_mux_select_sig_of_spipfN bits) |
| // - Lock is controlled via SPIPF07C write-disable bits and individual command |
| // table entry lock bits. |
| |
| /// Shared drain-log implementation used by both `Configured` and `Locked`. |
| fn drain_log_impl<'a>( |
| regs: &SpiMonitorRegisters, |
| buf: &'a mut [ViolationLogEntry], |
| ) -> &'a [ViolationLogEntry] { |
| let log_base = regs.log_ram_base_addr(); |
| let max_entries = regs.read_log_max_sz() as usize / core::mem::size_of::<u32>(); |
| let write_idx = regs.read_log_idx_reg() as usize; |
| |
| let available = write_idx.min(max_entries); |
| let count = available.min(buf.len()); |
| |
| for i in 0..count { |
| // SAFETY: log_base is a hardware RAM address validated by the PAC |
| // base-address mapping. Offset stays within [0, max_entries) words. |
| let word = unsafe { core::ptr::read_volatile((log_base as *const u32).add(i)) }; |
| buf[i] = ViolationLogEntry::parse(word); |
| } |
| |
| &buf[..count] |
| } |