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);