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, + } + } +}