add configuration of external-mux GPIOs and Implement policy locking
diff --git a/target/ast10x0/board/BUILD.bazel b/target/ast10x0/board/BUILD.bazel index e5baf59..34075da 100644 --- a/target/ast10x0/board/BUILD.bazel +++ b/target/ast10x0/board/BUILD.bazel
@@ -18,5 +18,6 @@ deps = [ "//target/ast10x0/backend/i2c:i2c_backend_ast10x0", "//target/ast10x0/peripherals", + "@ast1060_pac", ], )
diff --git a/target/ast10x0/board/src/lib.rs b/target/ast10x0/board/src/lib.rs index d1005b0..8e85720 100644 --- a/target/ast10x0/board/src/lib.rs +++ b/target/ast10x0/board/src/lib.rs
@@ -20,7 +20,8 @@ pub use monitor::Ast1060Monitor; pub use spim_wiring::{ - apply_spim_pinctrl, apply_spim_wiring, presets, SpimWiring, SpimWiringError, + apply_spim_external_mux, apply_spim_pinctrl, apply_spim_wiring, apply_spim_wiring_with_log, + presets, SpimWiring, SpimWiringError, }; pub use ast10x0_peripherals::i2c::{I2cConfig, I2cError}; @@ -133,7 +134,7 @@ /// This is a placeholder; production code should use a proper timer or delay provider. /// Spins for approximately `micros` microseconds. #[inline] -fn delay_us(micros: u32) { +pub(crate) fn delay_us(micros: u32) { // Very rough approximation: ~16 cycles per microsecond on Cortex-M4 @ ~50MHz // This is calibration-free but inaccurate; improve for production. for _ in 0..(micros * 16) {
diff --git a/target/ast10x0/board/src/spim_wiring.rs b/target/ast10x0/board/src/spim_wiring.rs index 8768562..ed247fd 100644 --- a/target/ast10x0/board/src/spim_wiring.rs +++ b/target/ast10x0/board/src/spim_wiring.rs
@@ -20,9 +20,10 @@ }; use ast10x0_peripherals::smc::SmcController; use ast10x0_peripherals::spimonitor::{ - LockedSpiMonitor, MonitorPolicy, SpiMonitor, SpiMonitorController, SpiMonitorError, - Uninitialized, + LockedSpiMonitor, MonitorPolicy, PassthroughMode, SpiMonitor, SpiMonitorController, + SpiMonitorError, Uninitialized, }; +use ast1060_pac as device; /// Static SPIM wiring for one SPI controller. /// @@ -110,6 +111,63 @@ scu.apply_pinctrl_group(group); } +/// Drive the external mux-select GPIO pair described by the AST1060 DTS. +/// +/// SPIM1/2 use GPIO A-D pin 12 and SGPIOM pin 0. SPIM3/4 use GPIO E-H +/// pin 8 and SGPIOM pin 2. Both signals carry the same mux selection. +pub fn apply_spim_external_mux(instance: SpiMonitorInstance, mux: ScuExtMuxSelect) { + let high = matches!(mux, ScuExtMuxSelect::Mux1); + let (gpio_group, gpio_mask, sgpio_mask) = match instance { + SpiMonitorInstance::Spim0 | SpiMonitorInstance::Spim1 => { + (ExternalMuxGpioGroup::Abcd, 1 << 12, 1 << 0) + } + SpiMonitorInstance::Spim2 | SpiMonitorInstance::Spim3 => { + (ExternalMuxGpioGroup::Efgh, 1 << 8, 1 << 2) + } + }; + + let gpio = unsafe { &*device::Gpio::ptr() }; + match gpio_group { + ExternalMuxGpioGroup::Abcd => { + gpio.gpio000().modify(|r, w| unsafe { + w.bits(update_bit(r.bits(), gpio_mask, high)) + }); + gpio.gpio004().modify(|r, w| unsafe { + w.bits(r.bits() | gpio_mask) + }); + } + ExternalMuxGpioGroup::Efgh => { + gpio.gpio020().modify(|r, w| unsafe { + w.bits(update_bit(r.bits(), gpio_mask, high)) + }); + gpio.gpio024().modify(|r, w| unsafe { + w.bits(r.bits() | gpio_mask) + }); + } + } + + let sgpio = unsafe { &*device::Sgpiom::ptr() }; + sgpio.gpio500().modify(|r, w| unsafe { + w.bits(update_bit(r.bits(), sgpio_mask, high)) + }); + + crate::delay_us(1_000); +} + +#[derive(Clone, Copy)] +enum ExternalMuxGpioGroup { + Abcd, + Efgh, +} + +const fn update_bit(value: u32, mask: u32, set: bool) -> u32 { + if set { + value | mask + } else { + value & !mask + } +} + /// Apply static SPIM wiring at controller-init time. /// /// Order: validate → pinctrl → SCU route → passthrough → ext-mux → @@ -126,14 +184,33 @@ wiring: SpimWiring, policy: &MonitorPolicy, ) -> Result<LockedSpiMonitor, SpimWiringError> { + unsafe { apply_spim_wiring_with_log(scu, controller_id, wiring, policy, None) } +} + +/// Apply static SPIM wiring and optionally configure violation-log DMA before +/// the policy registers are locked. +/// +/// # Safety +/// The caller must satisfy [`apply_spim_wiring`] ownership requirements. A +/// supplied log buffer must remain exclusively owned by the monitor forever. +pub unsafe fn apply_spim_wiring_with_log( + scu: &ScuRegisters, + controller_id: SmcController, + wiring: SpimWiring, + policy: &MonitorPolicy, + log_buffer: Option<&'static mut [u32]>, +) -> Result<LockedSpiMonitor, SpimWiringError> { validate_controller_for_source(controller_id, wiring.source)?; scu.validate_spim_instance(wiring.instance)?; apply_spim_pinctrl(scu, wiring.instance); + scu.disable_spim_cs_internal_pull_down(wiring.instance); scu.set_spim_internal_master_route(wiring.instance, wiring.source); scu.set_spim_passthrough(wiring.instance, wiring.passthrough); scu.set_spim_ext_mux(wiring.instance, wiring.ext_mux); + apply_spim_external_mux(wiring.instance, wiring.ext_mux); scu.set_spim_miso_multi_func(wiring.instance, wiring.miso_multi_func); + scu.set_spim_filter(wiring.instance, true); let monitor_controller = match wiring.instance { SpiMonitorInstance::Spim0 => SpiMonitorController::Spim0, @@ -146,6 +223,12 @@ // instance, mirroring the SCU exclusivity required above. let monitor = unsafe { SpiMonitor::<Uninitialized>::new(monitor_controller) }; let configured = monitor.apply_policy(policy)?; + if let Some(buffer) = log_buffer { + configured.configure_log(buffer)?; + } + configured.set_push_pull(true); + configured.set_passthrough(PassthroughMode::Disabled); + configured.enable(); let locked = configured.lock()?; Ok(locked) } @@ -165,7 +248,9 @@ /// Built-in `MonitorPolicy` presets vetted against the BMC's flash opcode set. pub mod presets { - use ast10x0_peripherals::spimonitor::MonitorPolicy; + use ast10x0_peripherals::spimonitor::{ + profile, MonitorPolicy, PrivilegeDirection, PrivilegeOp, + }; /// Allow-list for the BMC's normal flash opcodes covering both 3-byte and /// 4-byte addressing variants. Empty `regions` (no address-privilege @@ -196,4 +281,18 @@ p.allow_command_count = 13; p } + + /// Policy matching the supplied Zephyr SPIM nodes: full command list and + /// write protection over flash addresses `0x0000_0000..0x0800_0000`. + #[must_use] + pub fn zephyr_spim_policy() -> MonitorPolicy { + let mut policy = profile::zephyr_default(); + let _ = policy.add_region( + 0, + 0x0800_0000, + PrivilegeDirection::Write, + PrivilegeOp::Disable, + ); + policy + } }
diff --git a/target/ast10x0/peripherals/scu/routing.rs b/target/ast10x0/peripherals/scu/routing.rs index 2ebad91..b99d1bd 100644 --- a/target/ast10x0/peripherals/scu/routing.rs +++ b/target/ast10x0/peripherals/scu/routing.rs
@@ -197,6 +197,41 @@ }); } + /// Enable or disable the SCU-side SPI monitor filter path. Uses SCU0F0[11:8]. + pub fn set_spim_filter(&self, instance: SpiMonitorInstance, enable: bool) { + self.unlock_write_protection(); + self.regs().scu0f0().modify(|_, w| match instance { + SpiMonitorInstance::Spim0 => w.enbl_filter_of_spipf1().bit(enable), + SpiMonitorInstance::Spim1 => w.enbl_filter_of_spipf2().bit(enable), + SpiMonitorInstance::Spim2 => w.enbl_filter_of_spipf3().bit(enable), + SpiMonitorInstance::Spim3 => w.enbl_filter_of_spipf4().bit(enable), + }); + } + + /// Disable the internal pull-down on the monitor CS output pin. + pub fn disable_spim_cs_internal_pull_down(&self, instance: SpiMonitorInstance) { + self.unlock_write_protection(); + match instance { + SpiMonitorInstance::Spim0 => { + self.regs() + .scu610() + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << 6)) }); + } + SpiMonitorInstance::Spim1 => { + self.regs() + .scu610() + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << 20)) }); + } + // SPIM3's corresponding pin cannot have its pull-down disabled. + SpiMonitorInstance::Spim2 => {} + SpiMonitorInstance::Spim3 => { + self.regs() + .scu614() + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << 16)) }); + } + } + } + /// Route an internal SPI master through the selected SPI monitor path. pub fn set_spim_internal_master_route( &self,
diff --git a/target/ast10x0/peripherals/spimonitor/commands.rs b/target/ast10x0/peripherals/spimonitor/commands.rs index 3f528d9..034b7a3 100644 --- a/target/ast10x0/peripherals/spimonitor/commands.rs +++ b/target/ast10x0/peripherals/spimonitor/commands.rs
@@ -94,6 +94,7 @@ 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), + 0xc2 => command(opcode, true, true, false, false, 1, 0, 0, 0, 0), _ => return None, }; Some(entry) @@ -136,4 +137,9 @@ assert_eq!(fixed_slot(0xe9), Some(1)); assert_eq!(fixed_slot(0xc5), Some(31)); } + + #[test] + fn winbond_die_select_matches_zephyr_encoding() { + assert_eq!(table_value(0xc2, false), Some(0x7100_00c2)); + } }
diff --git a/target/ast10x0/peripherals/spimonitor/controller.rs b/target/ast10x0/peripherals/spimonitor/controller.rs index 7b7e9e5..123f1de 100644 --- a/target/ast10x0/peripherals/spimonitor/controller.rs +++ b/target/ast10x0/peripherals/spimonitor/controller.rs
@@ -7,7 +7,7 @@ use crate::scu::registers::ScuRegisters; use crate::scu::types::{ScuExtMuxSelect, SpiMonitorInstance}; -use crate::spimonitor::commands::{fixed_slot, table_value}; +use crate::spimonitor::commands::{fixed_slot, table_value, LOCKED as COMMAND_LOCKED}; use crate::spimonitor::policy::{MonitorPolicy, MAX_REGION_SLOTS}; use crate::spimonitor::registers::{SpiMonitorController, SpiMonitorRegisters}; use crate::spimonitor::types::{ @@ -38,38 +38,6 @@ 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 // --------------------------------------------------------------------------- @@ -100,11 +68,49 @@ if policy.region_count > MAX_REGION_SLOTS { return Err(SpiMonitorError::InvalidRegion); } + if self.regs.read_lock_status() & LOCK_STATUS_REQUIRED != 0 { + return Err(SpiMonitorError::Locked); + } + for slot in 0..COMMAND_TABLE_SLOTS { + if self.regs.read_allow_cmd_slot(slot) & COMMAND_LOCKED != 0 { + return Err(SpiMonitorError::Locked); + } + } - let mut next_slot = 2usize; + let general_slot_limit = if policy.allow_commands[..policy.allow_command_count] + .contains(&0xc5) + { + LAST_GENERAL_COMMAND_SLOT_EXCLUSIVE + } else { + COMMAND_TABLE_SLOTS + }; + let mut general_command_count = 0usize; + for opcode in policy.allow_commands[..policy.allow_command_count] + .iter() + .copied() + { + if table_value(opcode, false).is_none() { + return Err(SpiMonitorError::UnsupportedCommand(opcode)); + } + if fixed_slot(opcode).is_none() { + general_command_count += 1; + } + } + if general_command_count > general_slot_limit - FIRST_GENERAL_COMMAND_SLOT { + return Err(SpiMonitorError::NoCommandSlot); + } + for region in policy.regions[..policy.region_count].iter().flatten() { + validate_privilege_region(region.start, region.length)?; + } + for slot in 0..COMMAND_TABLE_SLOTS { + self.regs.write_allow_cmd_slot(slot, 0); + } + + let mut next_slot = FIRST_GENERAL_COMMAND_SLOT; // Slots 0 and 1 are reserved for EN4B and EX4B; slot 31 is reserved - // for WREAR. Other commands occupy slots 2 through 30. + // for WREAR when it is present. Otherwise slot 31 can hold a generic + // command, allowing the supplied 32-command Zephyr policy to fit. for i in 0..policy.allow_command_count { let opcode = policy.allow_commands[i]; let value = @@ -112,7 +118,7 @@ let slot = match fixed_slot(opcode) { Some(slot) => slot, None => { - if next_slot >= 31 { + if next_slot >= general_slot_limit { return Err(SpiMonitorError::NoCommandSlot); } let slot = next_slot; @@ -121,18 +127,26 @@ } }; self.regs.write_allow_cmd_slot(slot, value); + if self.regs.read_allow_cmd_slot(slot) != value { + return Err(SpiMonitorError::VerificationFailed); + } } - // Program address filter table. + // Empty policy means unrestricted access, matching the Zephyr driver: + // initialize both 256 MiB privilege maps to all-allowed, then apply + // the requested deny/allow regions. + initialize_privilege_table(&self.regs, PrivilegeDirection::Read)?; + initialize_privilege_table(&self.regs, PrivilegeDirection::Write)?; + for i in 0..policy.region_count { if let Some(region) = policy.regions[i] { - let word = encode_addr_filter_slot( + configure_privilege_region( + &self.regs, region.start, region.length, region.direction, region.op, - ); - self.regs.write_addr_filter_slot(i, word); + )?; } } @@ -148,6 +162,15 @@ pub const fn state(&self) -> MonitorState { MonitorState::Uninitialized } + + /// Pulse the SPIPF software-reset bit for at least 5 microseconds. + pub fn software_reset(&self) { + self.regs + .modify_ctrl(|bits| *bits |= CTRL_SW_RESET_BIT); + delay_cycles(SW_RESET_DELAY_CYCLES); + self.regs + .modify_ctrl(|bits| *bits &= !CTRL_SW_RESET_BIT); + } } // --------------------------------------------------------------------------- @@ -231,6 +254,58 @@ drain_log_impl(&self.regs, buf) } + /// Configure a caller-owned, static DMA buffer for violation logging. + /// + /// The hardware stores one 32-bit violation record per entry. + pub fn configure_log(&self, buffer: &'static mut [u32]) -> Result<()> { + if buffer.is_empty() || buffer.len() > MAX_LOG_ENTRIES { + return Err(SpiMonitorError::InvalidLogBuffer); + } + let address = buffer.as_mut_ptr() as usize; + if address & 0x3 != 0 || address > u32::MAX as usize { + return Err(SpiMonitorError::InvalidLogBuffer); + } + buffer.fill(0); + self.regs + .write_log_config(address as u32, buffer.len() as u32); + Ok(()) + } + + /// Use push-pull signaling for the monitor output path. + pub fn set_push_pull(&self, enable: bool) { + self.regs.modify_ctrl2(|bits| { + if enable { + *bits |= CTRL2_PUSH_PULL; + } else { + *bits &= !CTRL2_PUSH_PULL; + } + }); + } + + /// Enable command, write, and read violation interrupts in SPIPF004. + /// + /// Platform code must install and enable the corresponding NVIC handler. + pub fn enable_violation_interrupts(&self) { + self.regs + .modify_ctrl2(|bits| *bits |= CTRL2_VIOLATION_IRQ_ENABLE_MASK); + } + + /// Return currently pending command/write/read violation status bits. + #[must_use] + pub fn pending_violations(&self) -> u32 { + self.regs.read_ctrl2() & CTRL2_VIOLATION_STATUS_MASK + } + + /// Acknowledge all currently pending violation status bits. + pub fn acknowledge_violations(&self) -> u32 { + let pending = self.pending_violations(); + if pending != 0 { + self.regs + .modify_ctrl2(|bits| *bits |= pending); + } + pending + } + /// Lock monitor policy registers and transition to `Locked`. /// /// Activates all write-protection bits to prevent further policy changes. @@ -239,9 +314,32 @@ /// - 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); + for slot in 0..COMMAND_TABLE_SLOTS { + let value = self.regs.read_allow_cmd_slot(slot); + self.regs + .write_allow_cmd_slot(slot, value | COMMAND_LOCKED); + } + + self.regs + .modify_ctrl(|bits| *bits |= CTRL_BLOCK_FIFO_LOCK | CTRL_SW_RESET_LOCK); + self.regs.modify_lock_status(|bits| { + *bits |= LOCK_CTRL + | LOCK_IRQ_CTRL + | LOCK_LOG_BASE + | LOCK_LOG_CTRL + | LOCK_WRITE_PRIVILEGE + | LOCK_READ_PRIVILEGE; + }); + + let lock_status = self.regs.read_lock_status(); + if lock_status & LOCK_STATUS_REQUIRED != LOCK_STATUS_REQUIRED { + return Err(SpiMonitorError::LockFailed); + } + for slot in 0..COMMAND_TABLE_SLOTS { + if self.regs.read_allow_cmd_slot(slot) & COMMAND_LOCKED == 0 { + return Err(SpiMonitorError::LockFailed); + } + } Ok(SpiMonitor { regs: self.regs, @@ -262,19 +360,6 @@ // --------------------------------------------------------------------------- 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 = (*bits & !CTRL_PASSTHROUGH_MASK) | CTRL_SINGLE_PASSTHROUGH_BIT - } - PassthroughMode::Disabled => *bits &= !CTRL_PASSTHROUGH_MASK, - }); - } - /// Select the external SPI mux routing in locked state. /// /// Available post-lock for mux ownership transitions at runtime (e.g., BMC boot-hold/release). @@ -355,14 +440,123 @@ const CTRL_MONITOR_ENABLE_BIT: u32 = 1 << 2; #[allow(dead_code)] 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. - // - // 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() +const CTRL_BLOCK_FIFO_LOCK: u32 = 1 << 22; +const CTRL_SW_RESET_LOCK: u32 = 1 << 23; +const CTRL2_VIOLATION_STATUS_MASK: u32 = 0x7; +const CTRL2_VIOLATION_IRQ_ENABLE_MASK: u32 = 0x7 << 16; +const CTRL2_PUSH_PULL: u32 = 1 << 31; + +const COMMAND_TABLE_SLOTS: usize = 32; +const FIRST_GENERAL_COMMAND_SLOT: usize = 2; +const LAST_GENERAL_COMMAND_SLOT_EXCLUSIVE: usize = 31; +const PRIVILEGE_TABLE_WORDS: usize = 512; +const PRIVILEGE_BLOCK_SIZE: u32 = 16 * 1024; +const PRIVILEGE_ADDRESS_LIMIT: u32 = 256 * 1024 * 1024; +const PRIVILEGE_READ_SELECT: u32 = 0x5200_0000; +const PRIVILEGE_WRITE_SELECT: u32 = 0x5700_0000; +const MAX_LOG_ENTRIES: usize = 0x7_ffff; +const SW_RESET_DELAY_CYCLES: u32 = 1_000; + +const LOCK_CTRL: u32 = 1 << 0; +const LOCK_IRQ_CTRL: u32 = 1 << 1; +const LOCK_LOG_BASE: u32 = 1 << 4; +const LOCK_LOG_CTRL: u32 = 1 << 5; +const LOCK_WRITE_PRIVILEGE: u32 = 1 << 30; +const LOCK_READ_PRIVILEGE: u32 = 1 << 31; +const LOCK_STATUS_REQUIRED: u32 = LOCK_CTRL + | LOCK_IRQ_CTRL + | LOCK_LOG_BASE + | LOCK_LOG_CTRL + | LOCK_WRITE_PRIVILEGE + | LOCK_READ_PRIVILEGE; + +fn select_privilege_table(regs: &SpiMonitorRegisters, direction: PrivilegeDirection) { + let selection = match direction { + PrivilegeDirection::Read => PRIVILEGE_READ_SELECT, + PrivilegeDirection::Write => PRIVILEGE_WRITE_SELECT, + }; + regs.modify_ctrl(|bits| *bits = (*bits & 0x00ff_ffff) | selection); +} + +fn initialize_privilege_table( + regs: &SpiMonitorRegisters, + direction: PrivilegeDirection, +) -> Result<()> { + select_privilege_table(regs, direction); + for index in 0..PRIVILEGE_TABLE_WORDS { + regs.write_addr_filter_slot(index, u32::MAX); + } + if regs.read_addr_filter_slot(0) != u32::MAX + || regs.read_addr_filter_slot(PRIVILEGE_TABLE_WORDS - 1) != u32::MAX + { + return Err(SpiMonitorError::VerificationFailed); + } + Ok(()) +} + +fn configure_privilege_region( + regs: &SpiMonitorRegisters, + start: u32, + length: u32, + direction: PrivilegeDirection, + op: PrivilegeOp, +) -> Result<()> { + validate_privilege_region(start, length)?; + let end = start + .checked_add(length) + .ok_or(SpiMonitorError::InvalidLength)? + .min(PRIVILEGE_ADDRESS_LIMIT); + let aligned_start = start / PRIVILEGE_BLOCK_SIZE * PRIVILEGE_BLOCK_SIZE; + let aligned_end = end + .checked_add(PRIVILEGE_BLOCK_SIZE - 1) + .ok_or(SpiMonitorError::InvalidLength)? + / PRIVILEGE_BLOCK_SIZE + * PRIVILEGE_BLOCK_SIZE; + let mut block = aligned_start / PRIVILEGE_BLOCK_SIZE; + let end_block = aligned_end / PRIVILEGE_BLOCK_SIZE; + + select_privilege_table(regs, direction); + while block < end_block { + let word_index = (block / 32) as usize; + let bit_index = block % 32; + let remaining = end_block - block; + let updated = if bit_index == 0 && remaining >= 32 { + block += 32; + match op { + PrivilegeOp::Enable => u32::MAX, + PrivilegeOp::Disable => 0, + } + } else { + block += 1; + let value = regs.read_addr_filter_slot(word_index); + match op { + PrivilegeOp::Enable => value | (1 << bit_index), + PrivilegeOp::Disable => value & !(1 << bit_index), + } + }; + regs.write_addr_filter_slot(word_index, updated); + if regs.read_addr_filter_slot(word_index) != updated { + return Err(SpiMonitorError::VerificationFailed); + } + } + Ok(()) +} + +fn validate_privilege_region(start: u32, length: u32) -> Result<()> { + if start >= PRIVILEGE_ADDRESS_LIMIT { + return Err(SpiMonitorError::InvalidAddress); + } + if length == 0 || start.checked_add(length).is_none() { + return Err(SpiMonitorError::InvalidLength); + } + Ok(()) +} + +fn delay_cycles(cycles: u32) { + for _ in 0..cycles { + core::hint::spin_loop(); + } +} /// Shared drain-log implementation used by both `Configured` and `Locked`. fn drain_log_impl<'a>( @@ -370,7 +564,7 @@ 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 max_entries = regs.read_log_capacity_entries() as usize; let write_idx = regs.read_log_idx_reg() as usize; let available = write_idx.min(max_entries);
diff --git a/target/ast10x0/peripherals/spimonitor/profile.rs b/target/ast10x0/peripherals/spimonitor/profile.rs index 4e498df..dd6290e 100644 --- a/target/ast10x0/peripherals/spimonitor/profile.rs +++ b/target/ast10x0/peripherals/spimonitor/profile.rs
@@ -30,3 +30,16 @@ p.allow_command_count = 6; p } + +/// Full command allow-list used by the AST1060 Zephyr device tree. +#[must_use] +pub const fn zephyr_default() -> MonitorPolicy { + let mut p = MonitorPolicy::empty(); + p.allow_commands = [ + 0x03, 0x13, 0x0b, 0x0c, 0x6b, 0x6c, 0x01, 0x05, 0x35, 0x06, 0x04, 0x20, 0x21, 0x9f, + 0x5a, 0xb7, 0xe9, 0x32, 0x34, 0xd8, 0xdc, 0x02, 0x12, 0x3b, 0x3c, 0x70, 0xbb, 0xbc, + 0x50, 0xeb, 0xec, 0xc2, + ]; + p.allow_command_count = p.allow_commands.len(); + p +}
diff --git a/target/ast10x0/peripherals/spimonitor/registers.rs b/target/ast10x0/peripherals/spimonitor/registers.rs index b0a6252..a44e308 100644 --- a/target/ast10x0/peripherals/spimonitor/registers.rs +++ b/target/ast10x0/peripherals/spimonitor/registers.rs
@@ -138,6 +138,17 @@ self.regs().spipf07c().write(|w| unsafe { w.bits(value) }); } + pub fn modify_lock_status<F>(&self, f: F) + where + F: FnOnce(&mut u32), + { + self.regs().spipf07c().modify(|r, w| { + let mut bits = r.bits(); + f(&mut bits); + unsafe { w.bits(bits) } + }); + } + /// SPIPFWT[n]: Allow-command table entry. pub fn read_allow_cmd_slot(&self, index: usize) -> u32 { self.regs().spipfwt(index).read().bits() @@ -164,22 +175,38 @@ // Violation log registers // ----------------------------------------------------------------------- - /// Current violation log write index (number of entries written so far). - /// + /// Current violation log write index (number of 32-bit entries written). pub fn read_log_idx_reg(&self) -> u32 { - self.regs().spipf018().read().bits() + self.regs() + .spipf018() + .read() + .block_log_dmawr_pointer() + .bits() } - /// Maximum violation log capacity in bytes. - /// - pub fn read_log_max_sz(&self) -> u32 { - self.regs().spipf014().read().bits() & 0x0007_ffff + /// Maximum violation log capacity in 32-bit entries. + pub fn read_log_capacity_entries(&self) -> u32 { + self.regs() + .spipf014() + .read() + .size_of_block_log_dmabuffer() + .bits() } /// Base address of the violation log RAM region. - /// - /// Returns a `usize` suitable for casting to `*const u32` by the caller. pub fn log_ram_base_addr(&self) -> usize { (self.regs().spipf010().read().bits() & !0x3) as usize } + + /// Configure the violation-log DMA buffer. + pub fn write_log_config(&self, base_addr: u32, entries: u32) { + self.regs() + .spipf010() + .write(|w| unsafe { w.bits(base_addr & !0x3) }); + self.regs() + .spipf014() + .write(|w| unsafe { w.bits(LOG_DMA_ENABLE | entries) }); + } } + +const LOG_DMA_ENABLE: u32 = 1 << 31;
diff --git a/target/ast10x0/peripherals/spimonitor/types.rs b/target/ast10x0/peripherals/spimonitor/types.rs index 6471407..3be10bf 100644 --- a/target/ast10x0/peripherals/spimonitor/types.rs +++ b/target/ast10x0/peripherals/spimonitor/types.rs
@@ -127,10 +127,15 @@ #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum SpiMonitorError { InvalidRegion, + InvalidAddress, + InvalidLength, InvalidSlot, + InvalidLogBuffer, UnsupportedCommand(u8), NoCommandSlot, Locked, + LockFailed, + VerificationFailed, InvalidTransition, }