add spimonitor test. Spimonitor is not monitoring the traffic from spi controller. Spi monitor is only monitoring the traffic from either BMC or host.
diff --git a/target/ast10x0/board/src/lib.rs b/target/ast10x0/board/src/lib.rs index 8e85720..78c5ab8 100644 --- a/target/ast10x0/board/src/lib.rs +++ b/target/ast10x0/board/src/lib.rs
@@ -21,7 +21,7 @@ pub use monitor::Ast1060Monitor; pub use spim_wiring::{ apply_spim_external_mux, apply_spim_pinctrl, apply_spim_wiring, apply_spim_wiring_with_log, - presets, SpimWiring, SpimWiringError, + presets, spim_external_mux_state, SpimWiring, SpimWiringError, }; pub use ast10x0_peripherals::i2c::{I2cConfig, I2cError};
diff --git a/target/ast10x0/board/src/spim_wiring.rs b/target/ast10x0/board/src/spim_wiring.rs index ed247fd..1348917 100644 --- a/target/ast10x0/board/src/spim_wiring.rs +++ b/target/ast10x0/board/src/spim_wiring.rs
@@ -4,8 +4,8 @@ //! Static SPI-monitor wiring for AST10x0 boards. //! //! Composes the `scu::routing` mux helpers with the `spimonitor::controller` -//! typestate to apply once-per-process SPIM routing and SPIPF policy at -//! backend init time. Per-transaction reroutes are explicitly out of scope: +//! typestate to apply once-per-process external-monitor routing and SPIPF +//! policy at backend init time. Per-transaction reroutes are explicitly out of scope: //! the SPIPF lock is one-way, and the design doc //! (`peripherals/spimonitor/planning/overview-and-usage-model.md`) calls for //! "configure early, validate, lock, and operate under that locked policy." @@ -25,16 +25,15 @@ }; use ast1060_pac as device; -/// Static SPIM wiring for one SPI controller. +/// Static wiring for one external SPI monitor path. /// -/// Captures the four SCU0F0 fields plus the MISO multi-function pin choice -/// that together determine which monitor instance a given SPI master is -/// routed through. +/// The `source` identifies the external flash bus associated with this policy. +/// It does not enable the AST1060 internal SPI-master detour. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct SpimWiring { - /// Monitor instance the master is routed through. + /// Monitor instance connected to the external bus. pub instance: SpiMonitorInstance, - /// Which SPI master is being routed. + /// External SPI bus associated with the monitor policy. pub source: SpiMonitorSource, /// Passthrough enable for the chosen instance. pub passthrough: SpiMonitorPassthrough, @@ -127,6 +126,17 @@ }; let gpio = unsafe { &*device::Gpio::ptr() }; + let scu = unsafe { &*device::Scu::ptr() }; + scu.scu41c().modify(|_, w| { + w.enbl_sgpiomaster_ckfn_pin() + .set_bit() + .enbl_sgpiomaster_ldfn_pin() + .set_bit() + .enbl_sgpiomaster_dofn_pin() + .set_bit() + .enbl_sgpiomaster_difn_pin() + .set_bit() + }); match gpio_group { ExternalMuxGpioGroup::Abcd => { gpio.gpio000().modify(|r, w| unsafe { @@ -147,13 +157,51 @@ } let sgpio = unsafe { &*device::Sgpiom::ptr() }; - sgpio.gpio500().modify(|r, w| unsafe { - w.bits(update_bit(r.bits(), sgpio_mask, high)) + sgpio.gpio554().modify(|_, w| unsafe { + w.enbl_of_serial_gpio() + .set_bit() + .numbers_of_serial_gpiopins() + .bits(16) + .serial_gpioclk_division() + .bits(24) }); + let latch = sgpio.gpio570().read().bits(); + sgpio + .gpio500() + .write(|w| unsafe { w.bits(update_bit(latch, sgpio_mask, high)) }); crate::delay_us(1_000); } +/// Read back the two board-level external mux-select outputs. +#[must_use] +pub fn spim_external_mux_state( + instance: SpiMonitorInstance, +) -> Option<ScuExtMuxSelect> { + let (gpio_group, gpio_mask, sgpio_mask) = match instance { + SpiMonitorInstance::Spim0 | SpiMonitorInstance::Spim1 => { + (ExternalMuxGpioGroup::Abcd, 1 << 12, 1 << 0) + } + SpiMonitorInstance::Spim2 | SpiMonitorInstance::Spim3 => { + (ExternalMuxGpioGroup::Efgh, 1 << 8, 1 << 2) + } + }; + let gpio = unsafe { &*device::Gpio::ptr() }; + let gpio_high = match gpio_group { + ExternalMuxGpioGroup::Abcd => gpio.gpio000().read().bits() & gpio_mask != 0, + ExternalMuxGpioGroup::Efgh => gpio.gpio020().read().bits() & gpio_mask != 0, + }; + let sgpio = unsafe { &*device::Sgpiom::ptr() }; + let sgpio_high = sgpio.gpio570().read().bits() & sgpio_mask != 0; + if gpio_high != sgpio_high { + None + } else if gpio_high { + Some(ScuExtMuxSelect::Mux1) + } else { + Some(ScuExtMuxSelect::Mux0) + } +} + #[derive(Clone, Copy)] enum ExternalMuxGpioGroup { Abcd, @@ -205,7 +253,9 @@ apply_spim_pinctrl(scu, wiring.instance); scu.disable_spim_cs_internal_pull_down(wiring.instance); - scu.set_spim_internal_master_route(wiring.instance, wiring.source); + // SPIPF monitors the external BMC/host pins. Keep SCU0F0[3:0] clear so + // neither AST1060 internal SPI controller is detoured into the monitor. + scu.set_spim_internal_mux(wiring.source, 0)?; scu.set_spim_passthrough(wiring.instance, wiring.passthrough); scu.set_spim_ext_mux(wiring.instance, wiring.ext_mux); apply_spim_external_mux(wiring.instance, wiring.ext_mux);
diff --git a/target/ast10x0/peripherals/spimonitor/commands.rs b/target/ast10x0/peripherals/spimonitor/commands.rs index 034b7a3..40dbb86 100644 --- a/target/ast10x0/peripherals/spimonitor/commands.rs +++ b/target/ast10x0/peripherals/spimonitor/commands.rs
@@ -142,4 +142,16 @@ fn winbond_die_select_matches_zephyr_encoding() { assert_eq!(table_value(0xc2, false), Some(0x7100_00c2)); } + + #[test] + fn complete_zephyr_allow_list_is_supported() { + let commands = [ + 0x03, 0x13, 0x0b, 0x0c, 0x6b, 0x6c, 0x01, 0x05, 0x35, 0x06, 0x04, 0x20, 0x21, + 0x9f, 0x5a, 0xb7, 0xe9, 0x32, 0x34, 0xd8, 0xdc, 0x02, 0x12, 0x3b, 0x3c, 0x70, + 0xbb, 0xbc, 0x50, 0xeb, 0xec, 0xc2, + ]; + for command in commands { + assert!(table_value(command, false).is_some()); + } + } }
diff --git a/target/ast10x0/peripherals/spimonitor/controller.rs b/target/ast10x0/peripherals/spimonitor/controller.rs index 123f1de..a6f80e9 100644 --- a/target/ast10x0/peripherals/spimonitor/controller.rs +++ b/target/ast10x0/peripherals/spimonitor/controller.rs
@@ -178,6 +178,117 @@ // --------------------------------------------------------------------------- impl SpiMonitor<Configured> { + /// Add or re-enable one command, matching the Zephyr shell `cmd add`. + pub fn add_command(&self, opcode: u8, valid_once: bool) -> Result<usize> { + let value = + table_value(opcode, valid_once).ok_or(SpiMonitorError::UnsupportedCommand(opcode))?; + + for slot in 0..COMMAND_TABLE_SLOTS { + let current = self.regs.read_allow_cmd_slot(slot); + if current & 0xff == u32::from(opcode) && current & COMMAND_LOCKED == 0 { + self.regs.write_allow_cmd_slot(slot, value); + return verify_command_slot(&self.regs, slot, value); + } + } + + let slot = if let Some(slot) = fixed_slot(opcode) { + let current = self.regs.read_allow_cmd_slot(slot); + if current != 0 && current & 0xff != u32::from(opcode) { + return Err(SpiMonitorError::NoCommandSlot); + } + slot + } else { + (FIRST_GENERAL_COMMAND_SLOT..COMMAND_TABLE_SLOTS) + .find(|slot| self.regs.read_allow_cmd_slot(*slot) == 0) + .ok_or(SpiMonitorError::NoCommandSlot)? + }; + if self.regs.read_allow_cmd_slot(slot) & COMMAND_LOCKED != 0 { + return Err(SpiMonitorError::Locked); + } + self.regs.write_allow_cmd_slot(slot, value); + verify_command_slot(&self.regs, slot, value) + } + + /// Disable every unlocked entry matching an opcode. + pub fn remove_command(&self, opcode: u8) -> Result<usize> { + let mut count = 0; + for slot in 0..COMMAND_TABLE_SLOTS { + let current = self.regs.read_allow_cmd_slot(slot); + if current & 0xff == u32::from(opcode) { + if current & COMMAND_LOCKED != 0 { + return Err(SpiMonitorError::Locked); + } + self.regs.write_allow_cmd_slot(slot, 0); + verify_command_slot(&self.regs, slot, 0)?; + count += 1; + } + } + if count == 0 { + return Err(SpiMonitorError::UnsupportedCommand(opcode)); + } + Ok(count) + } + + /// Lock every command-table entry matching an opcode. + pub fn lock_command(&self, opcode: u8) -> Result<usize> { + let mut count = 0; + for slot in 0..COMMAND_TABLE_SLOTS { + let current = self.regs.read_allow_cmd_slot(slot); + if current & 0xff == u32::from(opcode) { + let updated = current | COMMAND_LOCKED; + self.regs.write_allow_cmd_slot(slot, updated); + verify_command_slot(&self.regs, slot, updated)?; + count += 1; + } + } + if count == 0 { + return Err(SpiMonitorError::UnsupportedCommand(opcode)); + } + Ok(count) + } + + /// Update an address privilege region, matching the Zephyr shell + /// `addr read/write enable/disable` operations. + pub fn configure_region( + &self, + start: u32, + length: u32, + direction: PrivilegeDirection, + op: PrivilegeOp, + ) -> Result<()> { + let lock = self.regs.read_lock_status(); + let locked = match direction { + PrivilegeDirection::Read => lock & LOCK_READ_PRIVILEGE != 0, + PrivilegeDirection::Write => lock & LOCK_WRITE_PRIVILEGE != 0, + }; + if locked { + return Err(SpiMonitorError::Locked); + } + configure_privilege_region(&self.regs, start, length, direction, op) + } + + /// Read one word from the selected privilege bitmap. + pub fn privilege_word(&self, direction: PrivilegeDirection, index: usize) -> Result<u32> { + if index >= PRIVILEGE_TABLE_WORDS { + return Err(SpiMonitorError::InvalidSlot); + } + select_privilege_table(&self.regs, direction); + Ok(self.regs.read_addr_filter_slot(index)) + } + + /// Lock one privilege bitmap, matching the shell `addr lock read/write`. + pub fn lock_privilege_table(&self, direction: PrivilegeDirection) -> Result<()> { + let mask = match direction { + PrivilegeDirection::Read => LOCK_READ_PRIVILEGE, + PrivilegeDirection::Write => LOCK_WRITE_PRIVILEGE, + }; + self.regs.modify_lock_status(|bits| *bits |= mask); + if self.regs.read_lock_status() & mask != mask { + return Err(SpiMonitorError::LockFailed); + } + Ok(()) + } + /// Enable the monitor filter (SPIPF000 bit 0). /// /// Mirrors Zephyr's `spim_monitor_enable(dev, true)`. @@ -203,6 +314,9 @@ PassthroughMode::Enabled => { *bits = (*bits & !CTRL_PASSTHROUGH_MASK) | CTRL_SINGLE_PASSTHROUGH_BIT } + PassthroughMode::MultiEnabled => { + *bits = (*bits & !CTRL_PASSTHROUGH_MASK) | CTRL_MULTI_PASSTHROUGH_BIT + } PassthroughMode::Disabled => *bits &= !CTRL_PASSTHROUGH_MASK, }); } @@ -436,6 +550,7 @@ /// Confirmed from aspeed-rust implementation (src/spimonitor/hardware.rs). /// Register field names from ast1060_pac provide safe typed accessors. const CTRL_SINGLE_PASSTHROUGH_BIT: u32 = 1 << 0; +const CTRL_MULTI_PASSTHROUGH_BIT: u32 = 1 << 1; const CTRL_PASSTHROUGH_MASK: u32 = (1 << 0) | (1 << 1); const CTRL_MONITOR_ENABLE_BIT: u32 = 1 << 2; #[allow(dead_code)] @@ -445,7 +560,6 @@ const CTRL2_VIOLATION_STATUS_MASK: u32 = 0x7; const CTRL2_VIOLATION_IRQ_ENABLE_MASK: u32 = 0x7 << 16; const CTRL2_PUSH_PULL: u32 = 1 << 31; - const COMMAND_TABLE_SLOTS: usize = 32; const FIRST_GENERAL_COMMAND_SLOT: usize = 2; const LAST_GENERAL_COMMAND_SLOT_EXCLUSIVE: usize = 31; @@ -478,6 +592,17 @@ regs.modify_ctrl(|bits| *bits = (*bits & 0x00ff_ffff) | selection); } +fn verify_command_slot( + regs: &SpiMonitorRegisters, + slot: usize, + expected: u32, +) -> Result<usize> { + if regs.read_allow_cmd_slot(slot) != expected { + return Err(SpiMonitorError::VerificationFailed); + } + Ok(slot) +} + fn initialize_privilege_table( regs: &SpiMonitorRegisters, direction: PrivilegeDirection,
diff --git a/target/ast10x0/peripherals/spimonitor/types.rs b/target/ast10x0/peripherals/spimonitor/types.rs index 3be10bf..5c00cec 100644 --- a/target/ast10x0/peripherals/spimonitor/types.rs +++ b/target/ast10x0/peripherals/spimonitor/types.rs
@@ -28,7 +28,10 @@ /// Mirrors Zephyr's `spim_passthrough_config`. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PassthroughMode { + /// Enable single-bit passthrough. Enabled, + /// Enable multi-bit passthrough. + MultiEnabled, Disabled, }
diff --git a/target/ast10x0/tests/spimonitor/target.rs b/target/ast10x0/tests/spimonitor/target.rs index f91d6be..3792b23 100644 --- a/target/ast10x0/tests/spimonitor/target.rs +++ b/target/ast10x0/tests/spimonitor/target.rs
@@ -1,16 +1,24 @@ // Licensed under the Apache-2.0 license // SPDX-License-Identifier: Apache-2.0 -//! AST10x0 SPI monitor register and routing smoke test. +//! AST10x0 SPI monitor configuration hardware test. #![no_std] #![no_main] -use ast10x0_board::apply_spim_pinctrl; -use ast10x0_peripherals::scu::{ScuRegisters, SpiMonitorInstance}; +use core::cell::UnsafeCell; + +use ast10x0_board::{ + apply_spim_external_mux, apply_spim_pinctrl, spim_external_mux_state, +}; +use ast10x0_peripherals::scu::{ + ScuError, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, + SpiMonitorSource, +}; use ast10x0_peripherals::spimonitor::{ - command_table_value, ExtMuxSel, MonitorPolicy, MonitorState, PassthroughMode, SpiMonitor, - SpiMonitorController, SpiMonitorError, Uninitialized, + ConfiguredSpiMonitor, LockState, MonitorPolicy, MonitorState, PassthroughMode, + PrivilegeDirection, PrivilegeOp, SpiMonitor, SpiMonitorController, SpiMonitorError, + Uninitialized, }; use console_backend::console_backend_write_all; use target_common::{declare_target, TargetInterface}; @@ -18,115 +26,237 @@ pub struct Target {} -const CTRL_MONITOR_ENABLE: u32 = 1 << 2; -const CTRL_SINGLE_BIT_PASSTHROUGH: u32 = 1 << 0; -const TEST_COMMANDS: [u8; 3] = [0x9f, 0x05, 0x06]; -const FIRST_GENERAL_COMMAND_SLOT: usize = 2; +const SPIM: SpiMonitorInstance = SpiMonitorInstance::Spim0; +const PROTECTED_LENGTH: u32 = 0x0010_0000; +const LOG_RAM_BYTES: usize = 0x200; +const LOG_RAM_WORDS: usize = LOG_RAM_BYTES / core::mem::size_of::<u32>(); +const COMMAND_VALID_MASK: u32 = (1 << 30) | (1 << 31); +const COMMAND_LOCKED: u32 = 1 << 23; +const LOCK_REQUIRED: u32 = (1 << 0) | (1 << 1) | (1 << 4) | (1 << 5) | (1 << 30) | (1 << 31); -fn check_register(actual: u32, expected: u32) -> Result<(), SpiMonitorError> { - if actual != expected { - pw_log::info!( - "register mismatch: expected=0x{:08x}, actual=0x{:08x}", - expected as u32, - actual as u32 - ); - return Err(SpiMonitorError::InvalidTransition); +#[repr(align(16))] +struct LogRam(UnsafeCell<[u32; LOG_RAM_WORDS]>); + +// SAFETY: This test is single-threaded and gives the buffer exclusively to SPIPF. +unsafe impl Sync for LogRam {} + +static LOG_RAM: LogRam = LogRam(UnsafeCell::new([0; LOG_RAM_WORDS])); + +#[derive(Clone, Copy)] +enum TestError { + Monitor, + Scu, + Check, +} + +impl From<SpiMonitorError> for TestError { + fn from(_: SpiMonitorError) -> Self { + Self::Monitor } +} + +impl From<ScuError> for TestError { + fn from(_: ScuError) -> Self { + Self::Scu + } +} + +macro_rules! test_check { + ($condition:expr, $message:literal) => { + if !$condition { + pw_log::info!($message); + return Err(TestError::Check); + } + }; +} + +fn log_buffer() -> &'static mut [u32] { + // SAFETY: The test is single-threaded and calls this exactly once. + unsafe { &mut *LOG_RAM.0.get() } +} + +fn configure_wiring(scu: &ScuRegisters) -> Result<(), TestError> { + pw_log::info!("START: external monitor pinctrl, routing, and mux"); + apply_spim_pinctrl(scu, SPIM); + scu.disable_spim_cs_internal_pull_down(SPIM); + + // SPIPF observes external BMC/host traffic. Do not detour SPI1 or SPI2 + // internally into the monitor. + scu.set_spim_internal_mux(SpiMonitorSource::Spi1, 0)?; + test_check!( + scu.route_control_raw() & 0x0f == 0, + "FAIL: internal SPI master detour is enabled" + ); + + // This SCU bit enables the SPIPF signal path. Controller bypass/filtering + // is controlled independently by SPIPF000[1:0]. + scu.set_spim_passthrough(SPIM, SpiMonitorPassthrough::Enabled); + scu.set_spim_miso_multi_func(SPIM, true); + scu.set_spim_filter(SPIM, true); + + apply_spim_external_mux(SPIM, ScuExtMuxSelect::Mux0); + test_check!( + spim_external_mux_state(SPIM) == Some(ScuExtMuxSelect::Mux0), + "FAIL: external mux 0 GPIO readback" + ); + apply_spim_external_mux(SPIM, ScuExtMuxSelect::Mux1); + test_check!( + spim_external_mux_state(SPIM) == Some(ScuExtMuxSelect::Mux1), + "FAIL: external mux 1 GPIO readback" + ); + scu.set_spim_ext_mux(SPIM, ScuExtMuxSelect::Mux1); + + test_check!( + scu.route_control_raw() & 0x0f == 0, + "FAIL: external monitor setup changed internal SPI routing" + ); + pw_log::info!("PASS: external monitor pinctrl, routing, and mux"); Ok(()) } -fn run_spimonitor_test() -> Result<(), SpiMonitorError> { - pw_log::info!("=== AST10x0 SPI monitor smoke test ==="); - - // Match the device-tree pinctrl-0 setup for spim1/SPIPF1. - let scu = unsafe { ScuRegisters::new_global_unlocked() }; - apply_spim_pinctrl(&scu, SpiMonitorInstance::Spim0); - - // This target owns SPIM0/SPIPF1 for its complete lifetime. - let monitor = unsafe { - SpiMonitor::<Uninitialized>::new(SpiMonitorController::Spim0) - }; - if monitor.state() != MonitorState::Uninitialized { - return Err(SpiMonitorError::InvalidTransition); - } - - let original_ctrl = monitor.regs().read_ctrl(); - let original_slots = [ - monitor.regs().read_allow_cmd_slot(2), - monitor.regs().read_allow_cmd_slot(3), - monitor.regs().read_allow_cmd_slot(4), - ]; - +fn build_policy() -> MonitorPolicy { let mut policy = MonitorPolicy::empty(); - policy.allow_commands[..TEST_COMMANDS.len()].copy_from_slice(&TEST_COMMANDS); - policy.allow_command_count = TEST_COMMANDS.len(); + policy.allow_commands[..9] + .copy_from_slice(&[0x9f, 0x05, 0x06, 0x04, 0x02, 0x12, 0x20, 0x21, 0x0c]); + policy.allow_command_count = 9; + let _ = policy.add_region( + 0, + PROTECTED_LENGTH, + PrivilegeDirection::Write, + PrivilegeOp::Disable, + ); + policy +} - let configured = monitor.apply_policy(&policy)?; - if configured.state() != MonitorState::Configured { - return Err(SpiMonitorError::InvalidTransition); - } +fn initialize_monitor() -> Result<ConfiguredSpiMonitor, TestError> { + pw_log::info!("START: monitor reset, policy, and log RAM"); + let monitor = unsafe { SpiMonitor::<Uninitialized>::new(SpiMonitorController::Spim0) }; + monitor.software_reset(); + test_check!( + monitor.regs().read_ctrl() & (1 << 15) == 0, + "FAIL: software reset did not deassert" + ); - let original_mux = configured.get_ext_mux(); - let result = (|| { - for (index, command) in TEST_COMMANDS.iter().copied().enumerate() { - let expected = - command_table_value(command, false).ok_or(SpiMonitorError::UnsupportedCommand( - command, - ))?; - check_register( - configured - .regs() - .read_allow_cmd_slot(FIRST_GENERAL_COMMAND_SLOT + index), - expected, - )?; - } - pw_log::info!("command table readback passed"); + let configured = monitor.apply_policy(&build_policy())?; + test_check!( + configured.state() == MonitorState::Configured, + "FAIL: monitor did not enter configured state" + ); - configured.enable(); - check_register( - configured.regs().read_ctrl() & CTRL_MONITOR_ENABLE, - CTRL_MONITOR_ENABLE, - )?; + configured.configure_log(log_buffer())?; + test_check!( + configured.regs().read_log_capacity_entries() as usize == LOG_RAM_WORDS, + "FAIL: 0x200-byte log RAM configuration" + ); + test_check!( + configured.regs().read_log_idx_reg() == 0, + "FAIL: violation log pointer was not reset" + ); - configured.set_passthrough(PassthroughMode::Enabled); - check_register( - configured.regs().read_ctrl() & CTRL_SINGLE_BIT_PASSTHROUGH, - CTRL_SINGLE_BIT_PASSTHROUGH, - )?; + configured.set_push_pull(true); + configured.set_passthrough(PassthroughMode::Disabled); + configured.enable(); + test_check!( + configured.regs().read_ctrl() & (1 << 2) != 0, + "FAIL: monitor filter is not enabled" + ); + pw_log::info!("PASS: monitor reset, policy, and log RAM"); + Ok(configured) +} - configured.set_passthrough(PassthroughMode::Disabled); - check_register( - configured.regs().read_ctrl() & CTRL_SINGLE_BIT_PASSTHROUGH, - 0, - )?; +fn test_passthrough_control(configured: &ConfiguredSpiMonitor) -> Result<(), TestError> { + pw_log::info!("START: passthrough control readback"); + configured.set_passthrough(PassthroughMode::Enabled); + test_check!( + configured.regs().read_ctrl() & 0x3 == 0x1, + "FAIL: single-bit passthrough readback" + ); + configured.set_passthrough(PassthroughMode::MultiEnabled); + test_check!( + configured.regs().read_ctrl() & 0x3 == 0x2, + "FAIL: multi-bit passthrough readback" + ); + configured.set_passthrough(PassthroughMode::Disabled); + test_check!( + configured.regs().read_ctrl() & 0x3 == 0, + "FAIL: passthrough disable readback" + ); + pw_log::info!("PASS: passthrough control readback"); + Ok(()) +} - let test_mux = match original_mux { - ExtMuxSel::Sel0 => ExtMuxSel::Sel1, - ExtMuxSel::Sel1 => ExtMuxSel::Sel0, - }; - configured.set_ext_mux(test_mux); - if configured.get_ext_mux() != test_mux { - pw_log::info!("external mux readback failed"); - return Err(SpiMonitorError::InvalidTransition); - } - pw_log::info!("control and external mux readback passed"); - Ok(()) - })(); +fn test_command_policy(configured: &ConfiguredSpiMonitor) -> Result<(), TestError> { + pw_log::info!("START: command table add and remove"); + configured.remove_command(0x05)?; + let status_slot = configured.add_command(0x05, false)?; + test_check!( + configured.regs().read_allow_cmd_slot(status_slot) & COMMAND_VALID_MASK != 0, + "FAIL: command add readback" + ); + configured.remove_command(0x05)?; + test_check!( + configured.regs().read_allow_cmd_slot(status_slot) == 0, + "FAIL: command remove did not clear slot" + ); + configured.add_command(0x05, false)?; + pw_log::info!("PASS: command table add and remove"); + Ok(()) +} - // Restore every register changed by this smoke test. - configured.set_ext_mux(original_mux); - configured.regs().write_ctrl(original_ctrl); - for (index, value) in original_slots.iter().copied().enumerate() { - configured - .regs() - .write_allow_cmd_slot(FIRST_GENERAL_COMMAND_SLOT + index, value); - } +fn test_address_policy(configured: &ConfiguredSpiMonitor) -> Result<(), TestError> { + pw_log::info!("START: protected and unprotected address policy"); + test_check!( + configured.privilege_word(PrivilegeDirection::Write, 0)? == 0, + "FAIL: protected write region programming" + ); + test_check!( + configured.privilege_word(PrivilegeDirection::Write, 2)? == u32::MAX, + "FAIL: unprotected write region programming" + ); + pw_log::info!("PASS: protected and unprotected address policy"); + Ok(()) +} - result +fn test_policy_locking(configured: ConfiguredSpiMonitor) -> Result<(), TestError> { + pw_log::info!("START: policy locking and readback"); + configured.lock_command(0x9f)?; + let locked = configured.lock()?; + test_check!( + locked.lock_state() == LockState::Locked, + "FAIL: monitor lock state" + ); + test_check!( + locked.regs().read_lock_status() & LOCK_REQUIRED == LOCK_REQUIRED, + "FAIL: lock register readback" + ); + let slot = locked.regs().read_allow_cmd_slot(2); + locked.regs().write_allow_cmd_slot(2, 0); + test_check!( + locked.regs().read_allow_cmd_slot(2) == slot && slot & COMMAND_LOCKED != 0, + "FAIL: command lock did not prevent modification" + ); + pw_log::info!("PASS: policy locking and readback"); + Ok(()) +} + +fn run_spimonitor_test() -> Result<(), TestError> { + pw_log::info!("=== AST10x0 external SPI monitor configuration test ==="); + + let scu = unsafe { ScuRegisters::new_global_unlocked() }; + configure_wiring(&scu)?; + let configured = initialize_monitor()?; + test_passthrough_control(&configured)?; + test_command_policy(&configured)?; + test_address_policy(&configured)?; + test_policy_locking(configured)?; + + pw_log::info!("External traffic blocking requires a BMC/host stimulus"); + pw_log::info!("=== all SPI monitor configuration tests passed ==="); + Ok(()) } impl TargetInterface for Target { - const NAME: &'static str = "AST10x0 SPI Monitor Smoke Test"; + const NAME: &'static str = "AST10x0 External SPI Monitor Configuration Test"; fn main() -> ! { let sentinel = if run_spimonitor_test().is_ok() {