The SPIMonitor driver now supports basic functionalities. It is ready for basic hardware testing, but not production-complete. Region filtering, permanent policy locking, and board-level external-mux GPIO switching/delay still need implementation.
diff --git a/target/ast10x0/board/src/lib.rs b/target/ast10x0/board/src/lib.rs index 4e7ff1b..d1005b0 100644 --- a/target/ast10x0/board/src/lib.rs +++ b/target/ast10x0/board/src/lib.rs
@@ -19,7 +19,9 @@ pub mod spim_wiring; pub use monitor::Ast1060Monitor; -pub use spim_wiring::{apply_spim_wiring, presets, SpimWiring, SpimWiringError}; +pub use spim_wiring::{ + apply_spim_pinctrl, apply_spim_wiring, presets, 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 d33289d..8768562 100644 --- a/target/ast10x0/board/src/spim_wiring.rs +++ b/target/ast10x0/board/src/spim_wiring.rs
@@ -11,6 +11,10 @@ //! "configure early, validate, lock, and operate under that locked policy." use ast10x0_peripherals::scu::{ + pinctrl::{ + PINCTRL_SPIM1_DEFAULT, PINCTRL_SPIM2_DEFAULT, PINCTRL_SPIM3_DEFAULT, + PINCTRL_SPIM4_DEFAULT, + }, ScuError, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource, }; @@ -92,10 +96,24 @@ } } +/// Apply the default SCU pinctrl group for a SPI monitor instance. +/// +/// The instance numbering follows the hardware SPIPF blocks: `Spim0` uses +/// the device-tree `spim1` pins, through `Spim3` using the `spim4` pins. +pub fn apply_spim_pinctrl(scu: &ScuRegisters, instance: SpiMonitorInstance) { + let group = match instance { + SpiMonitorInstance::Spim0 => PINCTRL_SPIM1_DEFAULT, + SpiMonitorInstance::Spim1 => PINCTRL_SPIM2_DEFAULT, + SpiMonitorInstance::Spim2 => PINCTRL_SPIM3_DEFAULT, + SpiMonitorInstance::Spim3 => PINCTRL_SPIM4_DEFAULT, + }; + scu.apply_pinctrl_group(group); +} + /// Apply static SPIM wiring at controller-init time. /// -/// Order: validate → SCU route → passthrough → ext-mux → MISO multi-func → -/// SPIPF policy → SPIPF lock. The lock is one-way; an empty +/// Order: validate → pinctrl → 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 /// reset, so callers should pass a vetted preset (see [`presets`]). /// @@ -111,6 +129,7 @@ validate_controller_for_source(controller_id, wiring.source)?; scu.validate_spim_instance(wiring.instance)?; + apply_spim_pinctrl(scu, wiring.instance); scu.set_spim_internal_master_route(wiring.instance, wiring.source); scu.set_spim_passthrough(wiring.instance, wiring.passthrough); scu.set_spim_ext_mux(wiring.instance, wiring.ext_mux);
diff --git a/target/ast10x0/peripherals/BUILD.bazel b/target/ast10x0/peripherals/BUILD.bazel index cd069bc..68813df 100644 --- a/target/ast10x0/peripherals/BUILD.bazel +++ b/target/ast10x0/peripherals/BUILD.bazel
@@ -1,7 +1,7 @@ # Licensed under the Apache-2.0 license # SPDX-License-Identifier: Apache-2.0 -load("@rules_rust//rust:defs.bzl", "rust_library") +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") load("//target/ast10x0:defs.bzl", "TARGET_COMPATIBLE_WITH") package(default_visibility = ["//visibility:public"]) @@ -63,6 +63,7 @@ "smc/spi/spi_transaction.rs", "smc/types.rs", "spimonitor/controller.rs", + "spimonitor/commands.rs", "spimonitor/mod.rs", "spimonitor/policy.rs", "spimonitor/profile.rs", @@ -91,3 +92,10 @@ "@rust_crates//:zerocopy", ], ) + +rust_test( + name = "spimonitor_commands_test", + srcs = ["spimonitor/commands.rs"], + crate_name = "spimonitor_commands_test", + edition = "2024", +)
diff --git a/target/ast10x0/peripherals/spimonitor/commands.rs b/target/ast10x0/peripherals/spimonitor/commands.rs new file mode 100644 index 0000000..3f528d9 --- /dev/null +++ b/target/ast10x0/peripherals/spimonitor/commands.rs
@@ -0,0 +1,139 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Hardware command-table descriptors for the AST10x0 SPI monitor. + +/// A decoded command descriptor before the valid/lock state is applied. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct CommandDescriptor { + pub opcode: u8, + pub generic: bool, + pub write: bool, + pub read: bool, + pub memory: bool, + pub data_width: u8, + pub dummy_cycles: u8, + pub program_size: u8, + pub address_len: u8, + pub address_width: u8, +} + +impl CommandDescriptor { + #[must_use] + pub const fn encode(self) -> u32 { + ((self.generic as u32) << 29) + | ((self.write as u32) << 28) + | ((self.read as u32) << 27) + | ((self.memory as u32) << 26) + | ((self.data_width as u32) << 24) + | ((self.dummy_cycles as u32) << 16) + | ((self.program_size as u32) << 13) + | ((self.address_len as u32) << 10) + | ((self.address_width as u32) << 8) + | self.opcode as u32 + } +} + +const fn command( + opcode: u8, + generic: bool, + write: bool, + read: bool, + memory: bool, + data_width: u8, + dummy_cycles: u8, + program_size: u8, + address_len: u8, + address_width: u8, +) -> CommandDescriptor { + CommandDescriptor { + opcode, + generic, + write, + read, + memory, + data_width, + dummy_cycles, + program_size, + address_len, + address_width, + } +} + +/// Return the descriptor used by the Zephyr AST10x0 SPI monitor driver. +#[must_use] +pub const fn descriptor(opcode: u8) -> Option<CommandDescriptor> { + let entry = match opcode { + 0x03 => command(opcode, true, false, true, true, 1, 0, 0, 3, 1), + 0x13 => command(opcode, true, false, true, true, 1, 0, 0, 4, 1), + 0x0b => command(opcode, true, false, true, true, 1, 8, 0, 3, 1), + 0x0c => command(opcode, true, false, true, true, 1, 8, 0, 4, 1), + 0x3b => command(opcode, true, false, true, true, 2, 8, 0, 3, 1), + 0x3c => command(opcode, true, false, true, true, 2, 8, 0, 4, 1), + 0xbb => command(opcode, true, false, true, true, 2, 4, 0, 3, 2), + 0xbc => command(opcode, true, false, true, true, 2, 4, 0, 4, 2), + 0x6b => command(opcode, true, false, true, true, 3, 8, 0, 3, 1), + 0x6c => command(opcode, true, false, true, true, 3, 8, 0, 4, 1), + 0xeb => command(opcode, true, false, true, true, 3, 6, 0, 3, 3), + 0xec => command(opcode, true, false, true, true, 3, 6, 0, 4, 3), + 0x02 => command(opcode, true, true, false, true, 1, 0, 1, 3, 1), + 0x12 => command(opcode, true, true, false, true, 1, 0, 1, 4, 1), + 0x32 => command(opcode, true, true, false, true, 3, 0, 1, 3, 1), + 0x34 => command(opcode, true, true, false, true, 3, 0, 1, 4, 1), + 0x20 => command(opcode, true, true, false, true, 0, 0, 1, 3, 1), + 0x21 => command(opcode, true, true, false, true, 0, 0, 1, 4, 1), + 0xd8 => command(opcode, true, true, false, true, 0, 0, 5, 3, 1), + 0xdc => command(opcode, true, true, false, true, 0, 0, 5, 4, 1), + 0x06 | 0x04 | 0x50 | 0x66 | 0x99 => { + command(opcode, true, false, false, false, 0, 0, 0, 0, 0) + } + 0x05 | 0x35 | 0x15 | 0x70 | 0x9f => { + command(opcode, true, false, true, false, 1, 0, 0, 0, 0) + } + 0x01 | 0x31 => command(opcode, true, true, false, false, 1, 0, 0, 0, 0), + 0x5a => command(opcode, true, false, true, false, 1, 8, 0, 3, 1), + 0xb7 | 0xe9 => command(opcode, false, false, false, false, 0, 0, 0, 0, 0), + 0xc5 => command(opcode, false, true, false, false, 1, 0, 0, 0, 0), + _ => return None, + }; + Some(entry) +} + +pub const VALID: u32 = 1 << 30; +pub const VALID_ONCE: u32 = 1 << 31; +pub const LOCKED: u32 = 1 << 23; + +#[must_use] +pub const fn table_value(opcode: u8, valid_once: bool) -> Option<u32> { + match descriptor(opcode) { + Some(entry) => Some(entry.encode() | if valid_once { VALID_ONCE } else { VALID }), + None => None, + } +} + +#[must_use] +pub const fn fixed_slot(opcode: u8) -> Option<usize> { + match opcode { + 0xb7 => Some(0), + 0xe9 => Some(1), + 0xc5 => Some(31), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::{fixed_slot, table_value}; + + #[test] + fn fast_read_4b_matches_zephyr_encoding() { + assert_eq!(table_value(0x0c, false), Some(0x6d08_110c)); + } + + #[test] + fn reserved_commands_have_fixed_slots() { + assert_eq!(fixed_slot(0xb7), Some(0)); + assert_eq!(fixed_slot(0xe9), Some(1)); + assert_eq!(fixed_slot(0xc5), Some(31)); + } +}
diff --git a/target/ast10x0/peripherals/spimonitor/controller.rs b/target/ast10x0/peripherals/spimonitor/controller.rs index 39b1a1e..7b7e9e5 100644 --- a/target/ast10x0/peripherals/spimonitor/controller.rs +++ b/target/ast10x0/peripherals/spimonitor/controller.rs
@@ -7,6 +7,7 @@ use crate::scu::registers::ScuRegisters; use crate::scu::types::{ScuExtMuxSelect, SpiMonitorInstance}; +use crate::spimonitor::commands::{fixed_slot, table_value}; use crate::spimonitor::policy::{MonitorPolicy, MAX_REGION_SLOTS}; use crate::spimonitor::registers::{SpiMonitorController, SpiMonitorRegisters}; use crate::spimonitor::types::{ @@ -100,10 +101,26 @@ return Err(SpiMonitorError::InvalidRegion); } - // Program command allow-list table. + let mut next_slot = 2usize; + + // Slots 0 and 1 are reserved for EN4B and EX4B; slot 31 is reserved + // for WREAR. Other commands occupy slots 2 through 30. for i in 0..policy.allow_command_count { - let cmd = policy.allow_commands[i] as u32; - self.regs.write_allow_cmd_slot(i, cmd); + let opcode = policy.allow_commands[i]; + let value = + table_value(opcode, false).ok_or(SpiMonitorError::UnsupportedCommand(opcode))?; + let slot = match fixed_slot(opcode) { + Some(slot) => slot, + None => { + if next_slot >= 31 { + return Err(SpiMonitorError::NoCommandSlot); + } + let slot = next_slot; + next_slot += 1; + slot + } + }; + self.regs.write_allow_cmd_slot(slot, value); } // Program address filter table. @@ -160,8 +177,10 @@ /// Mirrors Zephyr's `spim_passthrough_config`. pub fn set_passthrough(&self, mode: PassthroughMode) { self.regs.modify_ctrl(|bits| match mode { - PassthroughMode::Enabled => *bits |= CTRL_PASSTHROUGH_BIT, - PassthroughMode::Disabled => *bits &= !CTRL_PASSTHROUGH_BIT, + PassthroughMode::Enabled => { + *bits = (*bits & !CTRL_PASSTHROUGH_MASK) | CTRL_SINGLE_PASSTHROUGH_BIT + } + PassthroughMode::Disabled => *bits &= !CTRL_PASSTHROUGH_MASK, }); } @@ -249,8 +268,10 @@ /// during mux ownership transitions at runtime (e.g., BMC boot-hold/release). pub fn set_passthrough(&self, mode: PassthroughMode) { self.regs.modify_ctrl(|bits| match mode { - PassthroughMode::Enabled => *bits |= CTRL_PASSTHROUGH_BIT, - PassthroughMode::Disabled => *bits &= !CTRL_PASSTHROUGH_BIT, + PassthroughMode::Enabled => { + *bits = (*bits & !CTRL_PASSTHROUGH_MASK) | CTRL_SINGLE_PASSTHROUGH_BIT + } + PassthroughMode::Disabled => *bits &= !CTRL_PASSTHROUGH_MASK, }); } @@ -329,12 +350,11 @@ /// /// Confirmed from aspeed-rust implementation (src/spimonitor/hardware.rs). /// Register field names from ast1060_pac provide safe typed accessors. -const CTRL_MONITOR_ENABLE_BIT: u32 = 1 << 0; // enbl_filter_fn() in SPIPF000[0] -const CTRL_PASSTHROUGH_BIT: u32 = 1 << 1; // enbl_single_bit_passthrough() in SPIPF000[1] +const CTRL_SINGLE_PASSTHROUGH_BIT: u32 = 1 << 0; +const CTRL_PASSTHROUGH_MASK: u32 = (1 << 0) | (1 << 1); +const CTRL_MONITOR_ENABLE_BIT: u32 = 1 << 2; #[allow(dead_code)] -const CTRL_SW_RESET_BIT: u32 = 1 << 0; // sweng_rst() in SPIPF000[?] - uses PAC field -#[allow(dead_code)] -const CTRL_EXT_MUX_SEL_BIT: u32 = 1 << 2; // PLACEHOLDER - NOT in SPIPF000! See note below. +const CTRL_SW_RESET_BIT: u32 = 1 << 15; #[allow(dead_code)] const CTRL_LOCK_BIT: u32 = 1 << 31; // PLACEHOLDER - NOT in SPIPF000! See note below. //
diff --git a/target/ast10x0/peripherals/spimonitor/mod.rs b/target/ast10x0/peripherals/spimonitor/mod.rs index 8f649e1..91e5540 100644 --- a/target/ast10x0/peripherals/spimonitor/mod.rs +++ b/target/ast10x0/peripherals/spimonitor/mod.rs
@@ -3,6 +3,7 @@ //! AST10x0 SPI monitor (SPIPF) module. +pub mod commands; pub mod controller; pub mod policy; pub mod profile; @@ -14,6 +15,7 @@ Configured, ConfiguredSpiMonitor, Locked, LockedSpiMonitor, SpiMonitor, UninitSpiMonitor, Uninitialized, }; +pub use commands::{descriptor as command_descriptor, table_value as command_table_value}; pub use policy::{MonitorPolicy, MAX_CMD_SLOTS, MAX_REGION_SLOTS}; pub use registers::{ SpiMonitorController, SpiMonitorRegisters, SPIPF1_BASE, SPIPF2_BASE, SPIPF3_BASE, SPIPF4_BASE,
diff --git a/target/ast10x0/peripherals/spimonitor/registers.rs b/target/ast10x0/peripherals/spimonitor/registers.rs index 3fcf02e..b0a6252 100644 --- a/target/ast10x0/peripherals/spimonitor/registers.rs +++ b/target/ast10x0/peripherals/spimonitor/registers.rs
@@ -118,6 +118,17 @@ self.regs().spipf004().write(|w| unsafe { w.bits(value) }); } + pub fn modify_ctrl2<F>(&self, f: F) + where + F: FnOnce(&mut u32), + { + self.regs().spipf004().modify(|r, w| { + let mut bits = r.bits(); + f(&mut bits); + unsafe { w.bits(bits) } + }); + } + /// SPIPF07C: Lock/status register. pub fn read_lock_status(&self) -> u32 { self.regs().spipf07c().read().bits() @@ -151,43 +162,24 @@ // ----------------------------------------------------------------------- // Violation log registers - // - // TODO: confirm SPIPF register offsets for log control from the AST10x0 - // datasheet once available. Offsets below are placeholders consistent with - // known Aspeed SPIPF register map patterns. // ----------------------------------------------------------------------- /// Current violation log write index (number of entries written so far). /// - /// Maps to the SPIPF log index register (placeholder offset 0x080). pub fn read_log_idx_reg(&self) -> u32 { - // SAFETY: raw offset read within the known SPIPF register block page. - unsafe { - let ptr = (self.base as *const u8).add(0x080) as *const u32; - core::ptr::read_volatile(ptr) - } + self.regs().spipf018().read().bits() } /// Maximum violation log capacity in bytes. /// - /// Maps to the SPIPF log size register (placeholder offset 0x084). pub fn read_log_max_sz(&self) -> u32 { - // SAFETY: same as above. - unsafe { - let ptr = (self.base as *const u8).add(0x084) as *const u32; - core::ptr::read_volatile(ptr) - } + self.regs().spipf014().read().bits() & 0x0007_ffff } /// Base address of the violation log RAM region. /// /// Returns a `usize` suitable for casting to `*const u32` by the caller. - /// Maps to the SPIPF log RAM address register (placeholder offset 0x088). pub fn log_ram_base_addr(&self) -> usize { - // SAFETY: same as above. - unsafe { - let ptr = (self.base as *const u8).add(0x088) as *const u32; - core::ptr::read_volatile(ptr) as usize - } + (self.regs().spipf010().read().bits() & !0x3) as usize } }
diff --git a/target/ast10x0/peripherals/spimonitor/types.rs b/target/ast10x0/peripherals/spimonitor/types.rs index 4be2d82..6471407 100644 --- a/target/ast10x0/peripherals/spimonitor/types.rs +++ b/target/ast10x0/peripherals/spimonitor/types.rs
@@ -128,6 +128,8 @@ pub enum SpiMonitorError { InvalidRegion, InvalidSlot, + UnsupportedCommand(u8), + NoCommandSlot, Locked, InvalidTransition, }
diff --git a/target/ast10x0/tests/spimonitor/BUILD.bazel b/target/ast10x0/tests/spimonitor/BUILD.bazel new file mode 100644 index 0000000..5d961b1 --- /dev/null +++ b/target/ast10x0/tests/spimonitor/BUILD.bazel
@@ -0,0 +1,75 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image", "system_image_test") +load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen") +load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script") +load("@pigweed//pw_kernel/tooling/panic_detector:rust_binary_no_panics_test.bzl", "rust_binary_no_panics_test") +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("//target/ast10x0:defs.bzl", "TARGET_COMPATIBLE_WITH") + +filegroup( + name = "system_config", + srcs = ["system.json5"], +) + +target_codegen( + name = "codegen", + arch = "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", + system_config = ":system_config", + target_compatible_with = TARGET_COMPATIBLE_WITH, +) + +target_linker_script( + name = "linker_script", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + template = "//target/ast10x0:linker_script_template", +) + +rust_binary( + name = "target", + srcs = ["target.rs"], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/ast10x0:entry", + "//target/ast10x0/board:ast10x0_board", + "//target/ast10x0/peripherals", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_log/rust:pw_log", + ], +) + +system_image( + name = "spimonitor_test", + kernel = ":target", + platform = "//target/ast10x0", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + userspace = False, + visibility = ["//visibility:public"], +) + +system_image_test( + name = "spimonitor_evb_test", + image = ":spimonitor_test", + tags = ["hardware"], + target_compatible_with = select({ + "//target/ast10x0:qemu_enabled": ["@platforms//:incompatible"], + "//conditions:default": [], + }), + visibility = ["//visibility:public"], +) + +rust_binary_no_panics_test( + name = "no_panics_test", + binary = ":spimonitor_test", + tags = ["kernel"], +)
diff --git a/target/ast10x0/tests/spimonitor/system.json5 b/target/ast10x0/tests/spimonitor/system.json5 new file mode 100644 index 0000000..669c9c5 --- /dev/null +++ b/target/ast10x0/tests/spimonitor/system.json5
@@ -0,0 +1,17 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +// AST10x0 kernel-only SPI monitor smoke test configuration. +{ + arch: { + type: "armv7m", + vector_table_start_address: 0x00000000, + vector_table_size_bytes: 1280, + }, + kernel: { + flash_start_address: 0x00000500, + flash_size_bytes: 262144, + ram_start_address: 0x00040500, + ram_size_bytes: 393216, + }, +}
diff --git a/target/ast10x0/tests/spimonitor/target.rs b/target/ast10x0/tests/spimonitor/target.rs new file mode 100644 index 0000000..f91d6be --- /dev/null +++ b/target/ast10x0/tests/spimonitor/target.rs
@@ -0,0 +1,144 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! AST10x0 SPI monitor register and routing smoke test. + +#![no_std] +#![no_main] + +use ast10x0_board::apply_spim_pinctrl; +use ast10x0_peripherals::scu::{ScuRegisters, SpiMonitorInstance}; +use ast10x0_peripherals::spimonitor::{ + command_table_value, ExtMuxSel, MonitorPolicy, MonitorState, PassthroughMode, SpiMonitor, + SpiMonitorController, SpiMonitorError, Uninitialized, +}; +use console_backend::console_backend_write_all; +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +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; + +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); + } + 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), + ]; + + let mut policy = MonitorPolicy::empty(); + policy.allow_commands[..TEST_COMMANDS.len()].copy_from_slice(&TEST_COMMANDS); + policy.allow_command_count = TEST_COMMANDS.len(); + + let configured = monitor.apply_policy(&policy)?; + if configured.state() != MonitorState::Configured { + return Err(SpiMonitorError::InvalidTransition); + } + + 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"); + + configured.enable(); + check_register( + configured.regs().read_ctrl() & CTRL_MONITOR_ENABLE, + CTRL_MONITOR_ENABLE, + )?; + + configured.set_passthrough(PassthroughMode::Enabled); + check_register( + configured.regs().read_ctrl() & CTRL_SINGLE_BIT_PASSTHROUGH, + CTRL_SINGLE_BIT_PASSTHROUGH, + )?; + + configured.set_passthrough(PassthroughMode::Disabled); + check_register( + configured.regs().read_ctrl() & CTRL_SINGLE_BIT_PASSTHROUGH, + 0, + )?; + + 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(()) + })(); + + // 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); + } + + result +} + +impl TargetInterface for Target { + const NAME: &'static str = "AST10x0 SPI Monitor Smoke Test"; + + fn main() -> ! { + let sentinel = if run_spimonitor_test().is_ok() { + b"TEST_RESULT:PASS\n" + } else { + b"TEST_RESULT:FAIL\n" + }; + let _ = console_backend_write_all(sentinel); + + #[expect(clippy::empty_loop)] + loop {} + } +} + +declare_target!(Target);