Add SPI monitor peripheral support
diff --git a/target/ast10x0/peripherals/BUILD.bazel b/target/ast10x0/peripherals/BUILD.bazel
index 5da2edc..d418dac 100644
--- a/target/ast10x0/peripherals/BUILD.bazel
+++ b/target/ast10x0/peripherals/BUILD.bazel
@@ -28,6 +28,7 @@
         "scu/pinctrl.rs",
         "scu/registers.rs",
         "scu/reset.rs",
+        "scu/routing.rs",
         "scu/status.rs",
         "scu/types.rs",
         "smc/controller.rs",
@@ -41,6 +42,13 @@
         "smc/registers.rs",
         "smc/spi.rs",
         "smc/types.rs",
+        "spimonitor/controller.rs",
+        "spimonitor/mod.rs",
+        "spimonitor/policy.rs",
+        "spimonitor/profile.rs",
+        "spimonitor/registers.rs",
+        "spimonitor/traits.rs",
+        "spimonitor/types.rs",
         "uart/mod.rs",
     ],
     crate_name = "ast10x0_peripherals",
diff --git a/target/ast10x0/peripherals/lib.rs b/target/ast10x0/peripherals/lib.rs
index 4ae0fac..be02f13 100644
--- a/target/ast10x0/peripherals/lib.rs
+++ b/target/ast10x0/peripherals/lib.rs
@@ -6,4 +6,5 @@
 pub mod i2c;
 pub mod scu;
 pub mod smc;
+pub mod spimonitor;
 pub mod uart;
diff --git a/target/ast10x0/peripherals/scu/mod.rs b/target/ast10x0/peripherals/scu/mod.rs
index fdcd40c..31fae65 100644
--- a/target/ast10x0/peripherals/scu/mod.rs
+++ b/target/ast10x0/peripherals/scu/mod.rs
@@ -7,9 +7,13 @@
 pub mod pinctrl;
 pub mod registers;
 pub mod reset;
+pub mod routing;
 pub mod status;
 pub mod types;
 
 pub use pinctrl::PinctrlPin;
 pub use registers::ScuRegisters;
-pub use types::{ClockRegisterHalf, ScuRegisterHalf};
+pub use types::{
+    ClockRegisterHalf, ScuError, ScuExtMuxSelect, ScuRegisterHalf, SpiMonitorInstance,
+    SpiMonitorPassthrough, SpiMonitorSource,
+};
diff --git a/target/ast10x0/peripherals/scu/pinctrl.rs b/target/ast10x0/peripherals/scu/pinctrl.rs
index a3a0d0d..a9d7b4c 100644
--- a/target/ast10x0/peripherals/scu/pinctrl.rs
+++ b/target/ast10x0/peripherals/scu/pinctrl.rs
@@ -547,6 +547,12 @@
 /// FMC quad-SPI pin group: mux selection on SCU430[10:11].
 pub const PINCTRL_FMC_QUAD: &[PinctrlPin] = &[PIN_SCU430_10, PIN_SCU430_11];
 
+/// SPI1 quad.
+pub const PINCTRL_SPI1_QUAD: &[PinctrlPin] = &[PIN_SCU430_17, PIN_SCU430_18];
+
+/// SPI2 quad.
+pub const PINCTRL_SPI2_QUAD: &[PinctrlPin] = &[PIN_SCU41C_30, PIN_SCU41C_31, PIN_SCU430_0, PIN_SCU430_1, PIN_SCU430_2, PIN_SCU430_3, PIN_SCU430_4];
+
 /// I2C2 pin group: SCL3/SDA3 mux selection on SCU418[0:1].
 ///
 /// The SVD names these EnblSCL3FnPin/EnblSDA3FnPin, corresponding to
diff --git a/target/ast10x0/peripherals/scu/registers.rs b/target/ast10x0/peripherals/scu/registers.rs
index 9414ce9..3444b72 100644
--- a/target/ast10x0/peripherals/scu/registers.rs
+++ b/target/ast10x0/peripherals/scu/registers.rs
@@ -33,7 +33,7 @@
     ///
     /// # Safety
     /// Caller must ensure access to the singleton SCU is coordinated.
-    const unsafe fn new_global() -> Self {
+    pub(crate) const unsafe fn new_global() -> Self {
         // SAFETY: Caller upholds the singleton access contract.
         unsafe { Self::new(device::Scu::ptr()) }
     }
@@ -64,7 +64,7 @@
     /// Call this once before a sequence of SCU writes, following the aspeed-rust
     /// pattern of a single unlock per batch of register operations.
     #[inline]
-    fn unlock_write_protection(&self) {
+    pub(crate) fn unlock_write_protection(&self) {
         self.regs()
             .scu000()
             .write(|w| unsafe { w.bits(SCU_UNLOCK_KEY) });
diff --git a/target/ast10x0/peripherals/scu/routing.rs b/target/ast10x0/peripherals/scu/routing.rs
new file mode 100644
index 0000000..546eb7d
--- /dev/null
+++ b/target/ast10x0/peripherals/scu/routing.rs
@@ -0,0 +1,135 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+//! SCU routing and mux helpers for SPI monitor integration.
+
+use super::registers::ScuRegisters;
+use super::types::{
+    Result, ScuExtMuxSelect, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource,
+};
+
+impl ScuRegisters {
+    /// Enable or disable passthrough for a SPI monitor instance.
+    pub fn set_spim_passthrough(
+        &self,
+        instance: SpiMonitorInstance,
+        passthrough: SpiMonitorPassthrough,
+    ) {
+        self.unlock_write_protection();
+        let enable = passthrough.is_enabled();
+
+        self.regs().scu0f0().modify(|_, w| match instance {
+            SpiMonitorInstance::Spim0 => w.enbl_passthrough_of_spipf1().bit(enable),
+            SpiMonitorInstance::Spim1 => w.enbl_passthrough_of_spipf2().bit(enable),
+            SpiMonitorInstance::Spim2 => w.enbl_passthrough_of_spipf3().bit(enable),
+            SpiMonitorInstance::Spim3 => w.enbl_passthrough_of_spipf4().bit(enable),
+        });
+    }
+
+    /// Route an internal SPI master through the selected SPI monitor path. bit3
+    pub fn set_spim_internal_master_route(
+        &self,
+        instance: SpiMonitorInstance,
+        source: SpiMonitorSource,
+    ) {
+        self.unlock_write_protection();
+        self.regs().scu0f0().modify(|_, w| unsafe {
+            w.select_int_spimaster_connection()
+                .bits(instance as u8 + 1)
+        });
+
+        let select_spi2 = matches!(source, SpiMonitorSource::Spi2);
+        self.regs()
+            .scu0f0()
+            .modify(|_, w| w.int_spimaster_sel().bit(select_spi2));
+    }
+
+    /// Disable any internal SPI-master detour route.
+    pub fn clear_spim_internal_master_route(&self) {
+        self.unlock_write_protection();
+        let mut bits = self.regs().scu0f0().read().bits();
+        bits &= !0xF;
+        self.regs().scu0f0().write(|w| unsafe { w.bits(bits) });
+    }
+
+    /// Select the external mux signal for a SPI monitor instance.
+    pub fn set_spim_ext_mux(&self, instance: SpiMonitorInstance, mux: ScuExtMuxSelect) {
+        self.unlock_write_protection();
+        let bit = mux.as_bool();
+
+        self.regs().scu0f0().modify(|_, w| match instance {
+            SpiMonitorInstance::Spim0 => w.ext_mux_select_sig_of_spipf1().bit(bit),
+            SpiMonitorInstance::Spim1 => w.ext_mux_select_sig_of_spipf2().bit(bit),
+            SpiMonitorInstance::Spim2 => w.ext_mux_select_sig_of_spipf3().bit(bit),
+            SpiMonitorInstance::Spim3 => w.ext_mux_select_sig_of_spipf4().bit(bit),
+        });
+    }
+
+    /// Query the external mux signal for a SPI monitor instance.
+    #[must_use]
+    pub fn get_spim_ext_mux(&self, instance: SpiMonitorInstance) -> ScuExtMuxSelect {
+        let bit = self.regs().scu0f0().read();
+        let is_mux1 = match instance {
+            SpiMonitorInstance::Spim0 => bit.ext_mux_select_sig_of_spipf1().bit(),
+            SpiMonitorInstance::Spim1 => bit.ext_mux_select_sig_of_spipf2().bit(),
+            SpiMonitorInstance::Spim2 => bit.ext_mux_select_sig_of_spipf3().bit(),
+            SpiMonitorInstance::Spim3 => bit.ext_mux_select_sig_of_spipf4().bit(),
+        };
+        if is_mux1 {
+            ScuExtMuxSelect::Mux1
+        } else {
+            ScuExtMuxSelect::Mux0
+        }
+    }
+
+    /// Enable or disable the SCU-controlled MISO multi-function pin for a SPI
+    /// monitor instance.
+    pub fn set_spim_miso_multi_func(&self, instance: SpiMonitorInstance, enable: bool) {
+        self.unlock_write_protection();
+        match instance {
+            SpiMonitorInstance::Spim0 => {
+                self.regs()
+                    .scu690()
+                    .modify(|_, w| w.enbl_qspimonitor1misoin_fn_pin().bit(enable));
+            }
+            SpiMonitorInstance::Spim1 => {
+                self.regs()
+                    .scu690()
+                    .modify(|_, w| w.enbl_qspimonitor2misoin_fn_pin().bit(enable));
+            }
+            SpiMonitorInstance::Spim2 => {
+                self.regs()
+                    .scu690()
+                    .modify(|_, w| w.enbl_qspimonitor3misoin_fn_pin().bit(enable));
+            }
+            SpiMonitorInstance::Spim3 => {
+                self.regs()
+                    .scu694()
+                    .modify(|_, w| w.enbl_qspimonitor4misoin_fn_pin().bit(enable));
+            }
+        }
+    }
+
+    /// Read the raw SPI-monitor routing control register image.
+    #[must_use]
+    pub fn spim_route_ctrl(&self) -> u32 {
+        self.regs().scu0f0().read().bits()
+    }
+
+    /// Restore the raw SPI-monitor routing control register image.
+    pub fn restore_spim_route_ctrl(&self, route_ctrl: u32) {
+        self.unlock_write_protection();
+        self.regs().scu0f0().write(|w| unsafe { w.bits(route_ctrl) });
+    }
+
+    /// Check that a SPI monitor instance can be represented in SCU routing
+    /// operations.
+    pub fn validate_spim_instance(&self, instance: SpiMonitorInstance) -> Result<()> {
+        match instance {
+            SpiMonitorInstance::Spim0
+            | SpiMonitorInstance::Spim1
+            | SpiMonitorInstance::Spim2
+            | SpiMonitorInstance::Spim3 => Ok(()),
+        }
+    }
+}
\ No newline at end of file
diff --git a/target/ast10x0/peripherals/scu/types.rs b/target/ast10x0/peripherals/scu/types.rs
index 0a2d8c1..c01d5a4 100644
--- a/target/ast10x0/peripherals/scu/types.rs
+++ b/target/ast10x0/peripherals/scu/types.rs
@@ -3,6 +3,15 @@
 
 //! Public SCU types.
 
+/// Result type for SCU operations.
+pub type Result<T> = core::result::Result<T, ScuError>;
+
+/// SCU error conditions.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum ScuError {
+    InvalidMonitorInstance,
+}
+
 /// Selects the lower or upper 32-bit control register half for reset domains.
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub enum ScuRegisterHalf {
@@ -16,3 +25,50 @@
     Lower,
     Upper,
 }
+
+/// AST10x0 SPI monitor instance identifier.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum SpiMonitorInstance {
+    Spim0 = 0,
+    Spim1 = 1,
+    Spim2 = 2,
+    Spim3 = 3,
+}
+
+/// Select which internal SPI master is routed through a monitor path.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum SpiMonitorSource {
+    Spi1,
+    Spi2,
+}
+
+/// External mux selection for a SPI monitor instance.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum ScuExtMuxSelect {
+    Mux0,
+    Mux1,
+}
+
+impl ScuExtMuxSelect {
+    #[must_use]
+    pub const fn as_bool(self) -> bool {
+        match self {
+            Self::Mux0 => false,
+            Self::Mux1 => true,
+        }
+    }
+}
+
+/// SCU passthrough enable state for a SPI monitor instance.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum SpiMonitorPassthrough {
+    Disabled,
+    Enabled,
+}
+
+impl SpiMonitorPassthrough {
+    #[must_use]
+    pub const fn is_enabled(self) -> bool {
+        matches!(self, Self::Enabled)
+    }
+}
diff --git a/target/ast10x0/peripherals/spimonitor/controller.rs b/target/ast10x0/peripherals/spimonitor/controller.rs
new file mode 100644
index 0000000..777eee0
--- /dev/null
+++ b/target/ast10x0/peripherals/spimonitor/controller.rs
@@ -0,0 +1,367 @@
+// 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::policy::{MonitorPolicy, MAX_REGION_SLOTS};
+use crate::spimonitor::registers::{SpiMonitorController, SpiMonitorRegisters};
+use crate::spimonitor::types::{
+    ExtMuxSel, LockState, MonitorState, PassthroughMode, PrivilegeDirection, PrivilegeOp, 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>;
+
+// ---------------------------------------------------------------------------
+// Encoding helpers (hardware-format definitions)
+// ---------------------------------------------------------------------------
+
+/// Encode one address-filter slot word from a region policy entry.
+///
+/// Hardware slot format (pending datasheet confirmation):
+/// - bits[31:14] : region base address >> 14 (18-bit granule)
+/// - bit[13]     : direction (0 = read, 1 = write)
+/// - bit[12]     : op (0 = enable/allow, 1 = disable/block)
+/// - bits[11:0]  : length in 4 KiB units (length >> 12), clamped to 12 bits
+///
+/// TODO: replace with confirmed SPIPF register field encoding once available.
+fn encode_addr_filter_slot(
+    start: u32,
+    length: u32,
+    direction: PrivilegeDirection,
+    op: PrivilegeOp,
+) -> u32 {
+    let addr_field = (start >> 14) & 0x3_FFFF;
+    let dir_bit: u32 = match direction {
+        PrivilegeDirection::Read => 0,
+        PrivilegeDirection::Write => 1,
+    };
+    let op_bit: u32 = match op {
+        PrivilegeOp::Enable => 0,
+        PrivilegeOp::Disable => 1,
+    };
+    let len_field = (length >> 12) & 0xFFF;
+    (addr_field << 14) | (dir_bit << 13) | (op_bit << 12) | len_field
+}
+
+// ---------------------------------------------------------------------------
+// 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.
+        for i in 0..policy.allow_command_count {
+            let cmd = policy.allow_commands[i] as u32;
+            self.regs.write_allow_cmd_slot(i, cmd);
+        }
+
+        // Program address filter table.
+        for i in 0..policy.region_count {
+            if let Some(region) = policy.regions[i] {
+                let word = encode_addr_filter_slot(
+                    region.start,
+                    region.length,
+                    region.direction,
+                    region.op,
+                );
+                self.regs.write_addr_filter_slot(i, word);
+            }
+        }
+
+        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> {
+    /// Enable the monitor filter (SPIPF000 bit 0).
+    ///
+    /// Mirrors Zephyr's `spim_monitor_enable(dev, true)`.
+    pub fn enable(&self) {
+        self.regs.modify_ctrl(|bits| *bits |= CTRL_MONITOR_ENABLE_BIT);
+    }
+
+    /// Disable the monitor filter (SPIPF000 bit 0).
+    ///
+    /// Mirrors Zephyr's `spim_monitor_enable(dev, false)`.
+    pub fn disable(&self) {
+        self.regs.modify_ctrl(|bits| *bits &= !CTRL_MONITOR_ENABLE_BIT);
+    }
+
+    /// Configure passthrough mode (SPIPF000 passthrough bit).
+    ///
+    /// When `PassthroughMode::Enabled`, SPI traffic bypasses the filter.
+    /// 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,
+        });
+    }
+
+    /// Select the external SPI mux routing.
+    ///
+    /// Mirrors Zephyr's `spim_ext_mux_config`. 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, following the aspeed-rust pattern.
+    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.
+    /// See aspeed-rust::spim_lock_common() for 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 per aspeed-rust pattern.
+        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.modify_ctrl(|bits| match mode {
+            PassthroughMode::Enabled => *bits |= CTRL_PASSTHROUGH_BIT,
+            PassthroughMode::Disabled => *bits &= !CTRL_PASSTHROUGH_BIT,
+        });
+    }
+
+    /// 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 following the aspeed-rust pattern.
+    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.
+///
+/// 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]
+#[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.
+#[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)
+//   See aspeed-rust: spim_ext_mux_config()
+// - Lock is controlled via SPIPF07C write-disable bits and individual command
+//   table entry lock bits. See aspeed-rust: spim_lock_common(), spim_lock_rw_priv_table()
+
+/// 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]
+}
diff --git a/target/ast10x0/peripherals/spimonitor/mod.rs b/target/ast10x0/peripherals/spimonitor/mod.rs
new file mode 100644
index 0000000..049a4a8
--- /dev/null
+++ b/target/ast10x0/peripherals/spimonitor/mod.rs
@@ -0,0 +1,27 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+//! AST10x0 SPI monitor (SPIPF) module.
+
+pub mod registers;
+pub mod types;
+pub mod policy;
+pub mod profile;
+pub mod controller;
+pub mod traits;
+
+pub use registers::{
+    SpiMonitorController, SpiMonitorRegisters, SPIPF1_BASE, SPIPF2_BASE, SPIPF3_BASE, SPIPF4_BASE,
+    SPIPF_REG_SIZE,
+};
+pub use types::{
+    BootConfig, BootError, BootPhase, BootResult, ExtMuxSel, LockState, MonitorInstance,
+    MonitorState, MonitorStatus, MuxSelect, PassthroughMode, PrivilegeDirection, PrivilegeOp,
+    RegionPolicy, Result as SpiMonitorResult, SpiMonitorError, ViolationLogEntry,
+};
+pub use policy::{MonitorPolicy, MAX_CMD_SLOTS, MAX_REGION_SLOTS};
+pub use controller::{
+    Configured, ConfiguredSpiMonitor, Locked, LockedSpiMonitor, SpiMonitor, Uninitialized,
+    UninitSpiMonitor,
+};
+pub use traits::Monitor;
diff --git a/target/ast10x0/peripherals/spimonitor/policy.rs b/target/ast10x0/peripherals/spimonitor/policy.rs
new file mode 100644
index 0000000..f6e5117
--- /dev/null
+++ b/target/ast10x0/peripherals/spimonitor/policy.rs
@@ -0,0 +1,49 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+//! Declarative policy model for SPI monitor configuration.
+
+use crate::spimonitor::types::{PrivilegeDirection, PrivilegeOp, RegionPolicy};
+
+/// Maximum number of address filter regions per monitor instance.
+pub const MAX_REGION_SLOTS: usize = 16;
+
+/// Maximum number of command allow-list entries per monitor instance.
+pub const MAX_CMD_SLOTS: usize = 32;
+
+/// Policy payload applied to a monitor instance.
+#[derive(Clone, Debug)]
+pub struct MonitorPolicy {
+    pub allow_commands: [u8; MAX_CMD_SLOTS],
+    pub allow_command_count: usize,
+    pub regions: [Option<RegionPolicy>; MAX_REGION_SLOTS],
+    pub region_count: usize,
+}
+
+impl MonitorPolicy {
+    #[must_use]
+    pub const fn empty() -> Self {
+        Self {
+            allow_commands: [0; MAX_CMD_SLOTS],
+            allow_command_count: 0,
+            regions: [None; MAX_REGION_SLOTS],
+            region_count: 0,
+        }
+    }
+
+    /// Add an address region entry. Returns `false` if the table is full.
+    pub fn add_region(
+        &mut self,
+        start: u32,
+        length: u32,
+        direction: PrivilegeDirection,
+        op: PrivilegeOp,
+    ) -> bool {
+        if self.region_count >= MAX_REGION_SLOTS {
+            return false;
+        }
+        self.regions[self.region_count] = Some(RegionPolicy { start, length, direction, op });
+        self.region_count += 1;
+        true
+    }
+}
diff --git a/target/ast10x0/peripherals/spimonitor/profile.rs b/target/ast10x0/peripherals/spimonitor/profile.rs
new file mode 100644
index 0000000..4e498df
--- /dev/null
+++ b/target/ast10x0/peripherals/spimonitor/profile.rs
@@ -0,0 +1,32 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+//! Built-in SPI monitor policy profiles.
+//!
+//! Profiles provide command allow-lists only. Region entries are platform
+//! policy and must be added by the caller via `MonitorPolicy::add_region`
+//! using the PFM or provisioned manifest for the specific device.
+
+use crate::spimonitor::policy::MonitorPolicy;
+
+/// Runtime profile: read-focused allow-list suitable for steady-state boot/runtime.
+#[must_use]
+pub const fn runtime_read_only() -> MonitorPolicy {
+    let mut p = MonitorPolicy::empty();
+    p.allow_commands[0] = 0x03; // READ
+    p.allow_commands[1] = 0x0B; // FAST_READ
+    p.allow_commands[2] = 0x9F; // RDID
+    p.allow_command_count = 3;
+    p
+}
+
+/// Update profile: expands allow-list for controlled erase/program flows.
+#[must_use]
+pub const fn firmware_update_window() -> MonitorPolicy {
+    let mut p = runtime_read_only();
+    p.allow_commands[3] = 0x06; // WREN
+    p.allow_commands[4] = 0x20; // SE
+    p.allow_commands[5] = 0x02; // PP
+    p.allow_command_count = 6;
+    p
+}
diff --git a/target/ast10x0/peripherals/spimonitor/registers.rs b/target/ast10x0/peripherals/spimonitor/registers.rs
new file mode 100644
index 0000000..3fcf02e
--- /dev/null
+++ b/target/ast10x0/peripherals/spimonitor/registers.rs
@@ -0,0 +1,193 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+//! AST10x0 SPI monitor (SPIPF) low-level register access.
+//!
+
+use ast1060_pac as device;
+use core::cell::UnsafeCell;
+use core::marker::PhantomData;
+
+/// SPI monitor controller identifier.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum SpiMonitorController {
+    Spim0 = 0,
+    Spim1 = 1,
+    Spim2 = 2,
+    Spim3 = 3,
+}
+
+impl SpiMonitorController {
+    /// Returns the SPIPF register block base address for the controller.
+    #[must_use]
+    pub const fn base_address(self) -> usize {
+        match self {
+            Self::Spim0 => SPIPF1_BASE,
+            Self::Spim1 => SPIPF2_BASE,
+            Self::Spim2 => SPIPF3_BASE,
+            Self::Spim3 => SPIPF4_BASE,
+        }
+    }
+
+    /// Returns the PAC register block pointer for this controller.
+    #[must_use]
+    pub const fn ptr(self) -> *const device::spipf::RegisterBlock {
+        match self {
+            Self::Spim0 => device::Spipf::ptr(),
+            Self::Spim1 => device::Spipf1::ptr(),
+            Self::Spim2 => device::Spipf2::ptr(),
+            Self::Spim3 => device::Spipf3::ptr(),
+        }
+    }
+}
+
+/// SPIPF1 base address (used by SPIM0).
+pub const SPIPF1_BASE: usize = 0x7E79_1000;
+/// SPIPF2 base address (used by SPIM1).
+pub const SPIPF2_BASE: usize = 0x7E79_2000;
+/// SPIPF3 base address (used by SPIM2).
+pub const SPIPF3_BASE: usize = 0x7E79_3000;
+/// SPIPF4 base address (used by SPIM3).
+pub const SPIPF4_BASE: usize = 0x7E79_4000;
+
+/// Size of each SPIPF register block.
+pub const SPIPF_REG_SIZE: usize = 0x1000;
+
+/// Safe wrapper around a SPI monitor register block.
+pub struct SpiMonitorRegisters {
+    base: *const device::spipf::RegisterBlock,
+    _not_sync: PhantomData<UnsafeCell<()>>, // Prevent Sync, allow Send.
+}
+
+impl SpiMonitorRegisters {
+    /// Create a register accessor from a raw register block pointer.
+    ///
+    /// # Safety
+    /// Caller must ensure:
+    /// - `base` points to a valid SPIPF register block.
+    /// - Only one mutable owner accesses this hardware instance.
+    pub const unsafe fn new(base: *const device::spipf::RegisterBlock) -> Self {
+        Self {
+            base,
+            _not_sync: PhantomData,
+        }
+    }
+
+    /// Create a register accessor for a specific SPI monitor controller.
+    ///
+    /// # Safety
+    /// Caller must ensure exclusive ownership of the selected controller.
+    pub const unsafe fn new_for_controller(controller: SpiMonitorController) -> Self {
+        // SAFETY: Caller upholds exclusive-access requirement.
+        unsafe { Self::new(controller.ptr()) }
+    }
+
+    #[inline]
+    fn regs(&self) -> &device::spipf::RegisterBlock {
+        // SAFETY: Constructor ensures pointer validity and ownership discipline.
+        unsafe { &*self.base }
+    }
+
+    /// SPIPF000: Common control register.
+    pub fn read_ctrl(&self) -> u32 {
+        self.regs().spipf000().read().bits()
+    }
+
+    pub fn write_ctrl(&self, value: u32) {
+        self.regs().spipf000().write(|w| unsafe { w.bits(value) });
+    }
+
+    pub fn modify_ctrl<F>(&self, f: F)
+    where
+        F: FnOnce(&mut u32),
+    {
+        self.regs().spipf000().modify(|r, w| {
+            let mut bits = r.bits();
+            f(&mut bits);
+            // SAFETY: Callback is responsible for valid register image.
+            unsafe { w.bits(bits) }
+        });
+    }
+
+    /// SPIPF004: Secondary control/config register.
+    pub fn read_ctrl2(&self) -> u32 {
+        self.regs().spipf004().read().bits()
+    }
+
+    pub fn write_ctrl2(&self, value: u32) {
+        self.regs().spipf004().write(|w| unsafe { w.bits(value) });
+    }
+
+    /// SPIPF07C: Lock/status register.
+    pub fn read_lock_status(&self) -> u32 {
+        self.regs().spipf07c().read().bits()
+    }
+
+    pub fn write_lock_status(&self, value: u32) {
+        self.regs().spipf07c().write(|w| unsafe { w.bits(value) });
+    }
+
+    /// SPIPFWT[n]: Allow-command table entry.
+    pub fn read_allow_cmd_slot(&self, index: usize) -> u32 {
+        self.regs().spipfwt(index).read().bits()
+    }
+
+    pub fn write_allow_cmd_slot(&self, index: usize, value: u32) {
+        self.regs()
+            .spipfwt(index)
+            .write(|w| unsafe { w.bits(value) });
+    }
+
+    /// SPIPFWA[n]: Address filter table entry.
+    pub fn read_addr_filter_slot(&self, index: usize) -> u32 {
+        self.regs().spipfwa(index).read().bits()
+    }
+
+    pub fn write_addr_filter_slot(&self, index: usize, value: u32) {
+        self.regs()
+            .spipfwa(index)
+            .write(|w| unsafe { w.bits(value) });
+    }
+
+    // -----------------------------------------------------------------------
+    // 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)
+        }
+    }
+
+    /// 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)
+        }
+    }
+
+    /// 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
+        }
+    }
+}
diff --git a/target/ast10x0/peripherals/spimonitor/traits.rs b/target/ast10x0/peripherals/spimonitor/traits.rs
new file mode 100644
index 0000000..ca95e57
--- /dev/null
+++ b/target/ast10x0/peripherals/spimonitor/traits.rs
@@ -0,0 +1,109 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+//! Boot sequence traits for SPI monitor.
+//!
+//! This module provides the trait-based interface used by boot code to interact
+//! with SPI monitor hardware during platform initialization.
+
+use super::types::{
+    BootError, BootResult, MuxSelect, MonitorInstance, MonitorStatus,
+    PrivilegeDirection, PrivilegeOp,
+};
+
+/// Abstract hardware interface for SPI monitor boot operations.
+///
+/// Implementations of this trait provide register-level access to SPI monitor
+/// blocks via PAC or other register models. Boot code uses this trait to remain
+/// independent of the concrete register implementation.
+pub trait Monitor {
+    /// Set monitor mux to ROT or Host control.
+    fn set_mux(&mut self, instance: MonitorInstance, mux: MuxSelect) -> BootResult<()>;
+
+    /// Read current mux setting.
+    fn read_mux(&self, instance: MonitorInstance) -> BootResult<MuxSelect>;
+
+    /// Soft reset monitor (clears status/logs, preserves policy).
+    fn soft_reset(&mut self, instance: MonitorInstance) -> BootResult<()>;
+
+    /// Hardware reset monitor (full reset of all state).
+    fn hardware_reset(&mut self, instance: MonitorInstance) -> BootResult<()>;
+
+    /// Configure an address privilege region.
+    ///
+    /// # Arguments
+    /// * `instance` - Monitor instance to configure
+    /// * `start_addr` - Start address (inclusive)
+    /// * `end_addr` - End address (inclusive)
+    /// * `direction` - Read or Write direction
+    /// * `op` - Allow or Block access
+    fn set_address_privilege(
+        &mut self,
+        instance: MonitorInstance,
+        start_addr: u32,
+        end_addr: u32,
+        direction: PrivilegeDirection,
+        op: PrivilegeOp,
+    ) -> BootResult<()>;
+
+    /// Read number of configured address privilege regions.
+    fn read_region_count(&self, instance: MonitorInstance) -> BootResult<u32>;
+
+    /// Read monitor status snapshot.
+    fn read_status(&self, instance: MonitorInstance) -> BootResult<MonitorStatus>;
+
+    /// Check if policy write-lock is supported by this monitor.
+    fn supports_policy_lock(&self) -> bool {
+        false
+    }
+
+    /// Lock policy tables to prevent further modification.
+    ///
+    /// Returns error if not supported by hardware.
+    fn lock_policy(&mut self, _instance: MonitorInstance) -> BootResult<()> {
+        if !self.supports_policy_lock() {
+            return Err(BootError::LockedOutFromMonitor);
+        }
+        Ok(())
+    }
+
+    /// Verify that policy is locked (if supported).
+    ///
+    /// No-op if policy lock is not supported.
+    fn verify_policy_locked(&self, instance: MonitorInstance) -> BootResult<()> {
+        if !self.supports_policy_lock() {
+            return Ok(());
+        }
+        let status = self.read_status(instance)?;
+        if status.policy_locked {
+            Ok(())
+        } else {
+            Err(BootError::PolicyVerificationFailed)
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_mux_select_conversion() {
+        assert_eq!(
+            ExtMuxSel::from(MuxSelect::RotControl),
+            ExtMuxSel::Sel0
+        );
+        assert_eq!(
+            ExtMuxSel::from(MuxSelect::HostControl),
+            ExtMuxSel::Sel1
+        );
+    }
+
+    #[test]
+    fn test_mux_select_round_trip() {
+        let orig = MuxSelect::RotControl;
+        let ext = ExtMuxSel::from(orig);
+        let restored: MuxSelect = ext.into();
+        assert_eq!(orig, restored);
+    }
+}
diff --git a/target/ast10x0/peripherals/spimonitor/types.rs b/target/ast10x0/peripherals/spimonitor/types.rs
new file mode 100644
index 0000000..43150c0
--- /dev/null
+++ b/target/ast10x0/peripherals/spimonitor/types.rs
@@ -0,0 +1,253 @@
+// Licensed under the Apache-2.0 license
+// SPDX-License-Identifier: Apache-2.0
+
+//! SPI monitor public types.
+
+/// Direction selector for address privilege programming.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum PrivilegeDirection {
+    Read,
+    Write,
+}
+
+/// Whether a privilege region grants or denies access.
+///
+/// Mirrors Zephyr's `SPI_FILTER_PRIV_ENABLE` / `SPI_FILTER_PRIV_DISABLE`.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum PrivilegeOp {
+    /// Grant access: the region is permitted for the given direction.
+    Enable,
+    /// Deny access: the region is blocked for the given direction.
+    Disable,
+}
+
+/// Monitor filter passthrough mode.
+///
+/// When `Enabled`, SPI traffic bypasses the filter (used during mux ownership
+/// transitions). When `Disabled`, the programmed policy is enforced.
+/// Mirrors Zephyr's `spim_passthrough_config`.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum PassthroughMode {
+    Enabled,
+    Disabled,
+}
+
+/// External mux selection for SPI path routing.
+///
+/// `Sel0` and `Sel1` are hardware-level values. Platform code is responsible
+/// for mapping these to ROT vs BMC/PCH roles (with optional polarity inversion).
+/// Mirrors Zephyr's `spim_ext_mux_sel`.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum ExtMuxSel {
+    Sel0,
+    Sel1,
+}
+
+/// Lock state observed from hardware policy tables.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum LockState {
+    Unlocked,
+    Locked,
+}
+
+/// High-level monitor lifecycle stage.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum MonitorState {
+    Uninitialized,
+    Configured,
+    Locked,
+}
+
+/// Region policy entry used by higher-level policy/controller code.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct RegionPolicy {
+    pub start: u32,
+    pub length: u32,
+    pub direction: PrivilegeDirection,
+    /// Whether this region grants (`Enable`) or denies (`Disable`) access.
+    pub op: PrivilegeOp,
+}
+
+impl RegionPolicy {
+    /// Construct a region that grants access in the given direction.
+    #[must_use]
+    pub const fn allow(start: u32, length: u32, direction: PrivilegeDirection) -> Self {
+        Self { start, length, direction, op: PrivilegeOp::Enable }
+    }
+
+    /// Construct a region that denies access in the given direction.
+    #[must_use]
+    pub const fn deny(start: u32, length: u32, direction: PrivilegeDirection) -> Self {
+        Self { start, length, direction, op: PrivilegeOp::Disable }
+    }
+}
+
+/// Decoded entry from the SPIPF violation log RAM.
+///
+/// The hardware log word encoding (bits[19:18]):
+/// - `0b00` → blocked command opcode in bits[7:0]
+/// - `0b01` → blocked write address: bits[17:0] << 14
+/// - `0b10` → blocked read address:  bits[17:0] << 14
+/// - other  → invalid/reserved
+///
+/// ISR callback installation, workqueue deferral, and log-pointer reset are
+/// caller / platform responsibilities and are not part of this crate.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum ViolationLogEntry {
+    BlockedCommand(u8),
+    BlockedWriteAddr(u32),
+    BlockedReadAddr(u32),
+    Invalid(u32),
+}
+
+impl ViolationLogEntry {
+    /// Decode a raw 32-bit hardware log word.
+    #[must_use]
+    pub fn parse(word: u32) -> Self {
+        match (word >> 18) & 0x3 {
+            0x0 => Self::BlockedCommand((word & 0xFF) as u8),
+            0x1 => Self::BlockedWriteAddr((word & 0x3_FFFF) << 14),
+            0x2 => Self::BlockedReadAddr((word & 0x3_FFFF) << 14),
+            _ => Self::Invalid(word),
+        }
+    }
+}
+
+/// SPI monitor module error type.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum SpiMonitorError {
+    InvalidRegion,
+    InvalidSlot,
+    Locked,
+    InvalidTransition,
+}
+
+/// Result alias for SPI monitor APIs.
+pub type Result<T> = core::result::Result<T, SpiMonitorError>;
+
+// ============================================================================
+// Boot-level types and abstractions
+// ============================================================================
+
+/// SPI Monitor instance identifier.
+///
+/// Maps to SPIPF1-4 hardware blocks on AST10x0.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum MonitorInstance {
+    /// SPIPF1 (0x7E79_1000) - typically BMC/SMC flash
+    Spim0,
+    /// SPIPF2 (0x7E79_2000) - typically BMC dual flash
+    Spim1,
+    /// SPIPF3 (0x7E79_3000) - typically PCH/FMC flash
+    Spim2,
+    /// SPIPF4 (0x7E79_4000) - typically PCH dual flash
+    Spim3,
+}
+
+/// Mux routing selector for monitor path control.
+///
+/// Controls which SPI master (ROT or BMC/PCH) owns the flash access path
+/// through the monitor.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum MuxSelect {
+    /// Root of Trust has exclusive control
+    RotControl,
+    /// Host (BMC/PCH) has control
+    HostControl,
+}
+
+impl From<MuxSelect> for ExtMuxSel {
+    fn from(mux: MuxSelect) -> Self {
+        match mux {
+            MuxSelect::RotControl => ExtMuxSel::Sel0,
+            MuxSelect::HostControl => ExtMuxSel::Sel1,
+        }
+    }
+}
+
+impl From<ExtMuxSel> for MuxSelect {
+    fn from(ext: ExtMuxSel) -> Self {
+        match ext {
+            ExtMuxSel::Sel0 => MuxSelect::RotControl,
+            ExtMuxSel::Sel1 => MuxSelect::HostControl,
+        }
+    }
+}
+
+/// Monitor status snapshot at a point in time.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub struct MonitorStatus {
+    /// Current mux routing
+    pub mux: MuxSelect,
+    /// Whether policy tables are write-locked
+    pub policy_locked: bool,
+    /// Whether enforcement is actively filtering
+    pub enforcement_active: bool,
+    /// Number of policy violations logged
+    pub violation_count: u32,
+}
+
+/// Boot-level error types.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum BootError {
+    /// Monitor instance not found or unavailable
+    MonitorNotFound,
+    /// Mux switch operation failed
+    MuxSwitchFailed,
+    /// Flash reset/control operation failed
+    FlashResetFailed,
+    /// Policy load failed (region conflict, out-of-range, etc.)
+    PolicyLoadFailed,
+    /// Policy verification failed (readback mismatch)
+    PolicyVerificationFailed,
+    /// Region overlap detected
+    RegionOverlap,
+    /// Invalid address or size
+    InvalidAddress,
+    /// Timeout waiting for flash operation
+    TimeoutWaitingForFlash,
+    /// Verification failed (state not as expected)
+    VerificationFailed,
+    /// Attempted to modify locked policy
+    LockedOutFromMonitor,
+    /// Hardware error
+    HardwareError,
+}
+
+/// Result type for boot operations.
+pub type BootResult<T> = core::result::Result<T, BootError>;
+
+/// Boot phase enumeration for state tracking.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum BootPhase {
+    /// Initial state
+    Start,
+    /// Hold phase - ROT exclusive access
+    Hold,
+    /// Policy configuration phase
+    ConfigurePolicy,
+    /// Release phase - host control
+    Release,
+    /// Runtime monitoring phase
+    RuntimeMonitoring,
+}
+
+/// Boot configuration options.
+#[derive(Clone, Copy, Debug)]
+pub struct BootConfig {
+    pub enable_hold: bool,
+    pub enable_policy_config: bool,
+    pub enable_release: bool,
+    pub enable_verification: bool,
+}
+
+impl Default for BootConfig {
+    fn default() -> Self {
+        Self {
+            enable_hold: true,
+            enable_policy_config: true,
+            enable_release: true,
+            enable_verification: true,
+        }
+    }
+}