fix(ast10x0-spim): correct control bits and mux polarity Correct SPI-monitor driver defects on the AST1060 flash-routing path. - Fix SPIPF000 control bits: enable [0]->[2], passthrough [1]->[0]. - Fix inverted RoT/host mux polarity in the board monitor (Cypress). - Mux the MISO pin on enable/disable; add sw_reset (sweng_rst). - Replace the guessed filter encoding with confirmed SPIPFWA (addr_priv) and SPIPFWT (cmd_table) tables. - Add set_spim_scu_monitor_enable and spim_disable_cs_internal_pd. Signed-off-by: PojuiLiu <pojui_liu@jabil.com>
diff --git a/target/ast10x0/board/src/monitor.rs b/target/ast10x0/board/src/monitor.rs index ae48623..87c40df 100644 --- a/target/ast10x0/board/src/monitor.rs +++ b/target/ast10x0/board/src/monitor.rs
@@ -89,29 +89,31 @@ } } - /// Map MuxSelect to SCU external mux select value. + /// Map MuxSelect to SCU external mux select value (board ext-mux polarity). + /// + /// `Mux1` = RoT SPI master path; `Mux0` = host (BMC) flash access. fn mux_to_scu(mux: MuxSelect) -> ScuExtMuxSelect { match mux { - MuxSelect::RotControl => ScuExtMuxSelect::Mux0, - MuxSelect::HostControl => ScuExtMuxSelect::Mux1, + MuxSelect::RotControl => ScuExtMuxSelect::Mux1, + MuxSelect::HostControl => ScuExtMuxSelect::Mux0, } } /// Map SCU external mux select back to MuxSelect. fn scu_to_mux(scu_mux: ScuExtMuxSelect) -> MuxSelect { match scu_mux { - ScuExtMuxSelect::Mux0 => MuxSelect::RotControl, - ScuExtMuxSelect::Mux1 => MuxSelect::HostControl, + ScuExtMuxSelect::Mux0 => MuxSelect::HostControl, + ScuExtMuxSelect::Mux1 => MuxSelect::RotControl, } } /// Extract enforcement active flag from CTRL register. /// Enforcement is active when passthrough bits are NOT set. - /// - SPIPF000[1] = enbl_single_bit_passthrough - /// - SPIPF000[2] = enbl_multiple_bit_passthrough + /// - SPIPF000[0] = enbl_single_bit_passthrough + /// - SPIPF000[1] = enbl_multiple_bit_passthrough /// When both bits are 0, enforcement is active and SPI commands are filtered. fn is_enforcement_active(ctrl: u32) -> bool { - let pass_bits = (ctrl >> 1) & 0x3; + let pass_bits = ctrl & 0x3; pass_bits == 0 // Enforcement active when passthrough disabled } @@ -143,17 +145,9 @@ } fn soft_reset(&mut self, instance: MonitorInstance) -> BootResult<()> { - let regs = self.regs_mut(instance); - // Soft reset clears status/logs but preserves policy. - // NON-BLOCKING TODO 1: Verify soft reset bit position from AST10x0 datasheet. - // Current: uses bit 7 in SPIPF000 as placeholder. May need adjustment. - // NON-BLOCKING TODO 2: Implement polling/timeout after write. - // Pattern: poll SPIPF000 until reset bit clears or timeout (e.g., 100 microseconds). - // In aspeed-rust, hardware self-clears the bit after reset completes. - let mut ctrl = regs.read_ctrl(); - ctrl |= 0x80; // Soft reset bit (placeholder - verify with datasheet) - regs.write_ctrl(ctrl); - // TODO: Poll until bit 7 clears, with timeout check + // Soft reset clears status/logs but preserves policy: pulse SPIPF000 + // `sweng_rst` (bit 15). + self.regs_mut(instance).sw_reset(); Ok(()) } @@ -185,7 +179,7 @@ let regs = self.regs_mut(instance); - // Address filtering in AST10x0 uses 16KB blocks (ACCESS_BLOCK_UNIT from aspeed-rust). + // Address filtering in AST10x0 uses 16KB blocks (ACCESS_BLOCK_UNIT). // Each SPIPFWA register controls 32 blocks per register (32 * 16KB = 512KB per register). // NON-BLOCKING TODO: Implement dynamic slot allocation instead of fixed slot 0. // Phase C work: currently allocates all regions to slot 0 for simplicity. @@ -201,8 +195,8 @@ } fn read_region_count(&self, _instance: MonitorInstance) -> BootResult<u32> { - // Region count is tracked in memory (following aspeed-rust pattern). - // aspeed-rust stores read_blocked_region_num and write_blocked_region_num as struct fields. + // Region count is tracked in memory via the read_blocked_region_count + // and write_blocked_region_count struct fields. // We return the read-blocked region count for now; write-blocked can be exposed via separate method if needed. Ok(self.read_blocked_region_count as u32) } @@ -239,7 +233,7 @@ let regs = self.regs_mut(instance); // Policy lock is controlled by SPIPF07C register. - // From aspeed-rust: bit 0 is `wr_dis_of_spipfwa` (write disable for address tables). + // Bit 0 is `wr_dis_of_spipfwa` (write disable for address tables). let mut lock_status = regs.read_lock_status(); lock_status |= 0x1; // Set wr_dis_of_spipfwa bit regs.write_lock_status(lock_status); @@ -266,6 +260,26 @@ use super::*; #[test] + fn test_mux_mapping() { + assert_eq!( + Ast1060Monitor::mux_to_scu(MuxSelect::RotControl), + ScuExtMuxSelect::Mux1 + ); + assert_eq!( + Ast1060Monitor::mux_to_scu(MuxSelect::HostControl), + ScuExtMuxSelect::Mux0 + ); + assert_eq!( + Ast1060Monitor::scu_to_mux(ScuExtMuxSelect::Mux1), + MuxSelect::RotControl + ); + assert_eq!( + Ast1060Monitor::scu_to_mux(ScuExtMuxSelect::Mux0), + MuxSelect::HostControl + ); + } + + #[test] fn test_enforcement_flag() { assert!(!Ast1060Monitor::is_enforcement_active(0)); assert!(Ast1060Monitor::is_enforcement_active(1 << 4));
diff --git a/target/ast10x0/board/src/spim_wiring.rs b/target/ast10x0/board/src/spim_wiring.rs index d33289d..14fed3f 100644 --- a/target/ast10x0/board/src/spim_wiring.rs +++ b/target/ast10x0/board/src/spim_wiring.rs
@@ -6,9 +6,8 @@ //! Composes the `scu::routing` mux helpers with the `spimonitor::controller` //! typestate to apply once-per-process SPIM routing and SPIPF policy at //! backend init time. Per-transaction reroutes are explicitly out of scope: -//! the SPIPF lock is one-way, and the design doc -//! (`peripherals/spimonitor/planning/overview-and-usage-model.md`) calls for -//! "configure early, validate, lock, and operate under that locked policy." +//! the SPIPF lock is one-way, so the design is to configure early, validate, +//! lock, and operate under that locked policy. use ast10x0_peripherals::scu::{ ScuError, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, @@ -40,8 +39,8 @@ } impl SpimWiring { - /// Default wiring for `SmcController::Spi1` (aspeed-rust SPI0, - /// `master_idx = 0`) routed through `Spim0` (SPIPF1 @ `0x7E79_1000`). + /// Default wiring for `SmcController::Spi1` (`master_idx = 0`) routed + /// through `Spim0` (SPIPF1 @ `0x7E79_1000`). #[must_use] pub const fn default_spi1_via_spim0() -> Self { Self { @@ -53,8 +52,8 @@ } } - /// Default wiring for `SmcController::Spi2` (aspeed-rust SPI1, - /// `master_idx = 2`) routed through `Spim2` (SPIPF3 @ `0x7E79_3000`). + /// Default wiring for `SmcController::Spi2` (`master_idx = 2`) routed + /// through `Spim2` (SPIPF3 @ `0x7E79_3000`). #[must_use] pub const fn default_spi2_via_spim2() -> Self { Self { @@ -116,7 +115,7 @@ scu.set_spim_ext_mux(wiring.instance, wiring.ext_mux); scu.set_spim_miso_multi_func(wiring.instance, wiring.miso_multi_func); - let monitor_controller = match wiring.instance { + let monitor_controller: SpiMonitorController = match wiring.instance { SpiMonitorInstance::Spim0 => SpiMonitorController::Spim0, SpiMonitorInstance::Spim1 => SpiMonitorController::Spim1, SpiMonitorInstance::Spim2 => SpiMonitorController::Spim2, @@ -144,9 +143,30 @@ } } -/// Built-in `MonitorPolicy` presets vetted against the BMC's flash opcode set. +/// Built-in `MonitorPolicy` presets for **provisioned** filter handover. pub mod presets { - use ast10x0_peripherals::spimonitor::MonitorPolicy; + use ast10x0_peripherals::spimonitor::{ + MonitorPolicy, PrivilegeDirection, PrivilegeOp, + }; + + /// Allow-cmd whitelist for the BMC (32 opcodes). + const BMC_ALLOW_CMDS: [u8; 32] = [ + 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, + ]; + + /// Filter policy for the BMC SPIM: allow-cmd table plus full-flash + /// read/write regions. + #[must_use] + pub fn bmc_filter_policy() -> MonitorPolicy { + let mut p = MonitorPolicy::empty(); + p.allow_commands = BMC_ALLOW_CMDS; + p.allow_command_count = BMC_ALLOW_CMDS.len(); + let _ = p.add_region(0, 0x1000_0000, PrivilegeDirection::Read, PrivilegeOp::Enable); + let _ = p.add_region(0, 0x1000_0000, PrivilegeDirection::Write, PrivilegeOp::Enable); + p + } /// Allow-list for the BMC's normal flash opcodes covering both 3-byte and /// 4-byte addressing variants. Empty `regions` (no address-privilege
diff --git a/target/ast10x0/peripherals/BUILD.bazel b/target/ast10x0/peripherals/BUILD.bazel index e7318f2..555c032 100644 --- a/target/ast10x0/peripherals/BUILD.bazel +++ b/target/ast10x0/peripherals/BUILD.bazel
@@ -51,6 +51,8 @@ "smc/spi/spi.rs", "smc/spi/spi_transaction.rs", "smc/types.rs", + "spimonitor/addr_priv.rs", + "spimonitor/cmd_table.rs", "spimonitor/controller.rs", "spimonitor/mod.rs", "spimonitor/policy.rs",
diff --git a/target/ast10x0/peripherals/scu/routing.rs b/target/ast10x0/peripherals/scu/routing.rs index 0ed23f6..5fde055 100644 --- a/target/ast10x0/peripherals/scu/routing.rs +++ b/target/ast10x0/peripherals/scu/routing.rs
@@ -240,6 +240,46 @@ } } + /// Enable or disable the SCU-side SPI monitor block for an instance. + /// + /// Uses SCU0F0[11:8] (`enbl_filter_of_spipfN`). Required together with + /// SPIPF000 single passthrough for push-pull IO. + pub fn set_spim_scu_monitor_enable(&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 SPI master CS internal pull-down for a monitor instance. + /// + /// SPI_M1 (SPIM0): GPIOA6 / SCU610[6]; SPI_M2 (SPIM1): GPIOC0 / SCU610[20]; + /// SPI_M4 (SPIM3): GPIOG0 / SCU614[16]. SPIM2 has no disable bit. + pub fn spim_disable_cs_internal_pd(&self, instance: SpiMonitorInstance) { + self.unlock_write_protection(); + match instance { + SpiMonitorInstance::Spim0 => { + self.regs() + .scu610() + .modify(|_, w| w.dis_gpioa6int_pull_down().bit(true)); + } + SpiMonitorInstance::Spim1 => { + self.regs() + .scu610() + .modify(|_, w| w.dis_gpioc0int_pull_down().bit(true)); + } + SpiMonitorInstance::Spim2 => {} + SpiMonitorInstance::Spim3 => { + self.regs() + .scu614() + .modify(|_, w| w.dis_gpiog0int_pull_down().bit(true)); + } + } + } + /// 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) {
diff --git a/target/ast10x0/peripherals/spimonitor/addr_priv.rs b/target/ast10x0/peripherals/spimonitor/addr_priv.rs new file mode 100644 index 0000000..c0587ce --- /dev/null +++ b/target/ast10x0/peripherals/spimonitor/addr_priv.rs
@@ -0,0 +1,118 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! SPIPF address-privilege table programming (SPIPFWA @ 0x0100). +//! +//! Each bit in SPIPFWA[n] grants one 16 KiB block for the selected read/write +//! table (selected via SPIPF000[31:24] magic bytes). + +use crate::spimonitor::registers::SpiMonitorRegisters; +use crate::spimonitor::types::{PrivilegeDirection, PrivilegeOp, Result, SpiMonitorError}; + +/// 16 KiB address-privilege granule. +pub const ACCESS_BLOCK_UNIT: u32 = 16 * 1024; +/// One SPIPFWA register covers 32 × 16 KiB = 512 KiB. +pub const ACCESS_BLOCK_PER_REG: u32 = 32 * ACCESS_BLOCK_UNIT; +/// Maximum flash address space covered by the privilege tables. +pub const ADDR_LIMIT: u32 = 256 * 1024 * 1024; + +const SEL_READ_TBL_MAGIC: u32 = 0x52 << 24; +const SEL_WRITE_TBL_MAGIC: u32 = 0x57 << 24; + +fn select_addr_priv_table(regs: &SpiMonitorRegisters, direction: PrivilegeDirection) { + regs.modify_ctrl(|bits| { + *bits &= 0x00FF_FFFF; + match direction { + PrivilegeDirection::Read => *bits |= SEL_READ_TBL_MAGIC, + PrivilegeDirection::Write => *bits |= SEL_WRITE_TBL_MAGIC, + } + }); +} + +fn adjusted_addr_len(addr: u32, len: u32) -> (u32, u32) { + if len == 0 { + return (addr, 0); + } + let mut adjusted_len = len; + let mut aligned_addr = addr; + if !addr.is_multiple_of(ACCESS_BLOCK_UNIT) { + adjusted_len += addr % ACCESS_BLOCK_UNIT; + aligned_addr = (addr / ACCESS_BLOCK_UNIT) * ACCESS_BLOCK_UNIT; + } + adjusted_len = adjusted_len.div_ceil(ACCESS_BLOCK_UNIT) * ACCESS_BLOCK_UNIT; + (aligned_addr, adjusted_len) +} + +/// Program a contiguous address range in the read or write privilege table. +pub fn configure_address_privilege( + regs: &SpiMonitorRegisters, + direction: PrivilegeDirection, + op: PrivilegeOp, + addr: u32, + len: u32, +) -> Result<()> { + if addr >= ADDR_LIMIT { + return Err(SpiMonitorError::InvalidRegion); + } + if len == 0 || addr.saturating_add(len) > ADDR_LIMIT { + return Err(SpiMonitorError::InvalidRegion); + } + + let (aligned_addr, adjusted_len) = adjusted_addr_len(addr, len); + if adjusted_len == 0 { + return Ok(()); + } + + let mut reg_off = (aligned_addr / ACCESS_BLOCK_PER_REG) as usize; + let mut bit_off = ((aligned_addr % ACCESS_BLOCK_PER_REG) / ACCESS_BLOCK_UNIT) as u32; + let mut total_bit_num = adjusted_len / ACCESS_BLOCK_UNIT; + + select_addr_priv_table(regs, direction); + + while total_bit_num > 0 { + if bit_off > 31 { + bit_off = 0; + reg_off += 1; + } + + if bit_off == 0 && total_bit_num >= 32 { + let word = match op { + PrivilegeOp::Enable => 0xFFFF_FFFF, + PrivilegeOp::Disable => 0, + }; + regs.write_addr_filter_slot(reg_off, word); + reg_off += 1; + total_bit_num -= 32; + } else { + let mut reg_val = regs.read_addr_filter_slot(reg_off); + match op { + PrivilegeOp::Enable => reg_val |= 1 << bit_off, + PrivilegeOp::Disable => reg_val &= !(1 << bit_off), + } + regs.write_addr_filter_slot(reg_off, reg_val); + bit_off += 1; + total_bit_num -= 1; + } + } + + Ok(()) +} + +/// Enable full-flash read and write in the addr-priv tables. +pub fn configure_full_flash_rw(regs: &SpiMonitorRegisters) -> Result<()> { + configure_address_privilege(regs, PrivilegeDirection::Read, PrivilegeOp::Enable, 0, ADDR_LIMIT)?; + configure_address_privilege(regs, PrivilegeDirection::Write, PrivilegeOp::Enable, 0, ADDR_LIMIT)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn adjusted_addr_len_aligns_to_16k() { + let (addr, len) = adjusted_addr_len(0x1000, 0x2000); + assert_eq!(addr, 0); + assert_eq!(len, 0x3000); + } +}
diff --git a/target/ast10x0/peripherals/spimonitor/cmd_table.rs b/target/ast10x0/peripherals/spimonitor/cmd_table.rs new file mode 100644 index 0000000..ac18511 --- /dev/null +++ b/target/ast10x0/peripherals/spimonitor/cmd_table.rs
@@ -0,0 +1,353 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! SPIPF allow-command table encoding. + +use crate::spimonitor::registers::SpiMonitorRegisters; + +/// Number of SPIPFWT slots (0..=31). +pub const SPIM_CMD_TABLE_NUM: usize = 32; +/// Last fixed/dynamic slot index. +pub const MAX_CMD_INDEX: usize = 31; + +/// general command 13 +pub const CMD_RDID: u8 = 0x9F; +pub const CMD_WREN: u8 = 0x06; +pub const CMD_WRDIS: u8 = 0x04; +pub const CMD_RDSR: u8 = 0x05; +pub const CMD_RDCR: u8 = 0x15; +pub const CMD_RDSR2: u8 = 0x35; +pub const CMD_WRSR: u8 = 0x01; +pub const CMD_WRSR2: u8 = 0x31; +pub const CMD_SFDP: u8 = 0x5A; +pub const CMD_EN4B: u8 = 0xB7; +pub const CMD_EX4B: u8 = 0xE9; +pub const CMD_RDFSR: u8 = 0x70; +pub const CMD_VSR_WREN: u8 = 0x50; + +/// read commands 12 +pub const CMD_READ_1_1_1_3B: u8 = 0x03; +pub const CMD_READ_1_1_1_4B: u8 = 0x13; +pub const CMD_FREAD_1_1_1_3B: u8 = 0x0B; +pub const CMD_FREAD_1_1_1_4B: u8 = 0x0C; +pub const CMD_READ_1_1_2_3B: u8 = 0x3B; +pub const CMD_READ_1_1_2_4B: u8 = 0x3C; +pub const CMD_READ_1_2_2_3B: u8 = 0xBB; +pub const CMD_READ_1_2_2_4B: u8 = 0xBC; +pub const CMD_READ_1_1_4_3B: u8 = 0x6B; +pub const CMD_READ_1_1_4_4B: u8 = 0x6C; +pub const CMD_READ_1_4_4_3B: u8 = 0xEB; +pub const CMD_READ_1_4_4_4B: u8 = 0xEC; + +// write command 6 +pub const CMD_PP_1_1_1_3B: u8 = 0x02; +pub const CMD_PP_1_1_1_4B: u8 = 0x12; +pub const CMD_PP_1_1_4_3B: u8 = 0x32; +pub const CMD_PP_1_1_4_4B: u8 = 0x34; +pub const CMD_PP_1_4_4_3B: u8 = 0x38; +pub const CMD_PP_1_4_4_4B: u8 = 0x3E; + +// sector erase command 4 +pub const CMD_SE_1_1_0_3B: u8 = 0x20; +pub const CMD_SE_1_1_0_4B: u8 = 0x21; +pub const CMD_SE_1_1_0_64_3B: u8 = 0xD8; +pub const CMD_SE_1_1_0_64_4B: u8 = 0xDC; + +// Write Extend Address Register +pub const CMD_WREAR: u8 = 0xC5; +/// Winbond die select +pub const CMD_WINBOND_DIE_SEL: u8 = 0xC2; + + +pub const CMD_TABLE_LOCK_MASK: u32 = 1 << 23; +pub const CMD_TABLE_VALID_ONCE_BIT: u32 = 1 << 31; +pub const CMD_TABLE_VALID_BIT: u32 = 1 << 30; +pub const CMD_TABLE_CMD_MASK: u32 = 0xFF; + + + + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy)] +pub struct CmdTableInfo { + cmd: u8, + reserved: [u8; 3], + cmd_table_val: u32, +} + +//#[derive(Debug, Clone, Copy)] +//pub struct GpioInfo { + +//} +//compile time - const fn +#[allow(clippy::too_many_arguments)] +#[must_use] +pub const fn cmd_table_value( + g: u32, + w: u32, + r: u32, + m: u32, + dat_mode: u32, + dummy: u32, + prog_sz: u32, + addr_len: u32, + addr_mode: u32, + cmd: u32, +) -> u32 { + (g << 29) + | (w << 28) + | (r << 27) + | (m << 26) + | (dat_mode << 24) + | (dummy << 16) + | (prog_sz << 13) + | (addr_len << 10) + | (addr_mode << 8) + | cmd +} + +//32 Allow Command Table Entries +//total commands: 36 +static CMDS_ARRAY: &[CmdTableInfo] = &[ + CmdTableInfo { + cmd: CMD_READ_1_1_1_3B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 1, 1, 0, 0, 3, 1, CMD_READ_1_1_1_3B as u32), + }, + CmdTableInfo { + cmd: CMD_READ_1_1_1_4B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 1, 1, 0, 0, 4, 1, CMD_READ_1_1_1_4B as u32), + }, + CmdTableInfo { + cmd: CMD_FREAD_1_1_1_3B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 1, 1, 8, 0, 3, 1, CMD_FREAD_1_1_1_3B as u32), + }, + CmdTableInfo { + cmd: CMD_FREAD_1_1_1_4B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 1, 1, 8, 0, 4, 1, CMD_FREAD_1_1_1_4B as u32), + }, + CmdTableInfo { + cmd: CMD_READ_1_1_2_3B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 1, 2, 8, 0, 3, 1, CMD_READ_1_1_2_3B as u32), + }, + CmdTableInfo { + cmd: CMD_READ_1_1_2_4B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 1, 2, 8, 0, 4, 1, CMD_READ_1_1_2_4B as u32), + }, + CmdTableInfo { + cmd: CMD_READ_1_2_2_3B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 1, 2, 4, 0, 3, 2, CMD_READ_1_2_2_3B as u32), + }, + CmdTableInfo { + cmd: CMD_READ_1_2_2_4B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 1, 2, 4, 0, 4, 2, CMD_READ_1_2_2_4B as u32), + }, + CmdTableInfo { + cmd: CMD_READ_1_1_4_3B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 1, 3, 8, 0, 3, 1, CMD_READ_1_1_4_3B as u32), + }, + CmdTableInfo { + cmd: CMD_READ_1_1_4_4B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 1, 3, 8, 0, 4, 1, CMD_READ_1_1_4_4B as u32), + }, + CmdTableInfo { + cmd: CMD_READ_1_4_4_3B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 1, 3, 6, 0, 3, 3, CMD_READ_1_4_4_3B as u32), + }, + CmdTableInfo { + cmd: CMD_READ_1_4_4_4B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 1, 3, 6, 0, 4, 3, CMD_READ_1_4_4_4B as u32), + }, + CmdTableInfo { + cmd: CMD_PP_1_1_1_3B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 1, 0, 1, 1, 0, 1, 3, 1, CMD_PP_1_1_1_3B as u32), + }, + CmdTableInfo { + cmd: CMD_PP_1_1_1_4B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 1, 0, 1, 1, 0, 1, 4, 1, CMD_PP_1_1_1_4B as u32), + }, + CmdTableInfo { + cmd: CMD_PP_1_1_4_3B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 1, 0, 1, 3, 0, 1, 3, 1, CMD_PP_1_1_4_3B as u32), + }, + CmdTableInfo { + cmd: CMD_PP_1_1_4_4B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 1, 0, 1, 3, 0, 1, 4, 1, CMD_PP_1_1_4_4B as u32), + }, + CmdTableInfo { + cmd: CMD_SE_1_1_0_3B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 1, 0, 1, 0, 0, 1, 3, 1, CMD_SE_1_1_0_3B as u32), + }, + CmdTableInfo { + cmd: CMD_SE_1_1_0_4B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 1, 0, 1, 0, 0, 1, 4, 1, CMD_SE_1_1_0_4B as u32), + }, + CmdTableInfo { + cmd: CMD_SE_1_1_0_64_3B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 1, 0, 1, 0, 0, 5, 3, 1, CMD_SE_1_1_0_64_3B as u32), + }, + CmdTableInfo { + cmd: CMD_SE_1_1_0_64_4B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 1, 0, 1, 0, 0, 5, 4, 1, CMD_SE_1_1_0_64_4B as u32), + }, + CmdTableInfo { + cmd: CMD_WREN, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 0, 0, 0, 0, 0, 0, 0, CMD_WREN as u32), + }, + CmdTableInfo { + cmd: CMD_WRDIS, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 0, 0, 0, 0, 0, 0, 0, CMD_WRDIS as u32), + }, + CmdTableInfo { + cmd: CMD_RDSR, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 0, 1, 0, 0, 0, 0, CMD_RDSR as u32), + }, + CmdTableInfo { + cmd: CMD_RDSR2, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 0, 1, 0, 0, 0, 0, CMD_RDSR2 as u32), + }, + CmdTableInfo { + cmd: CMD_WRSR, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 1, 0, 0, 1, 0, 0, 0, 0, CMD_WRSR as u32), + }, + CmdTableInfo { + cmd: CMD_WRSR2, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 1, 0, 0, 1, 0, 0, 0, 0, CMD_WRSR2 as u32), + }, + CmdTableInfo { + cmd: CMD_RDCR, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 0, 1, 0, 0, 0, 0, CMD_RDCR as u32), + }, + CmdTableInfo { + cmd: CMD_EN4B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(0, 0, 0, 0, 0, 0, 0, 0, 0, CMD_EN4B as u32), + }, + CmdTableInfo { + cmd: CMD_EX4B, + reserved: [0; 3], + cmd_table_val: cmd_table_value(0, 0, 0, 0, 0, 0, 0, 0, 0, CMD_EX4B as u32), + }, + CmdTableInfo { + cmd: CMD_SFDP, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 0, 1, 8, 0, 3, 1, CMD_SFDP as u32), + }, + CmdTableInfo { + cmd: CMD_RDID, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 0, 1, 0, 0, 0, 0, CMD_RDID as u32), + }, + CmdTableInfo { + cmd: CMD_RDFSR, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 1, 0, 1, 0, 0, 0, 0, CMD_RDFSR as u32), + }, + CmdTableInfo { + cmd: CMD_VSR_WREN, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 0, 0, 0, 0, 0, 0, 0, 0, CMD_VSR_WREN as u32), + }, + CmdTableInfo { + cmd: CMD_WREAR, + reserved: [0; 3], + cmd_table_val: cmd_table_value(0, 1, 0, 0, 1, 0, 0, 0, 0, CMD_WREAR as u32), + }, + CmdTableInfo { + cmd: CMD_WINBOND_DIE_SEL, + reserved: [0; 3], + cmd_table_val: cmd_table_value(1, 1, 0, 0, 1, 0, 0, 0, 0, CMD_WINBOND_DIE_SEL as u32), + }, +]; + +/// Look up the base encoded word for `cmd` (without VALID bit). +#[must_use] +pub fn lookup_cmd_table_val(cmd: u8) -> Option<u32> { + for entry in CMDS_ARRAY { + if entry.cmd == cmd { + return Some(entry.cmd_table_val); + } + } + None +} + +/// Program SPIPFWT from an opcode list. +/// +/// Fixed slots: `EN4B`→0, `EX4B`→1, `WREAR`→31. Dynamic opcodes fill slots 2..31. +/// Unknown opcodes are skipped. +pub fn init_allow_cmd_table(regs: &SpiMonitorRegisters, cmd_list: &[u8]) { + let mut idx = 1usize; + for &cmd in cmd_list { + let Some(mut reg_val) = lookup_cmd_table_val(cmd) else { + continue; + }; + reg_val |= CMD_TABLE_VALID_BIT; + + match cmd { + CMD_EN4B => { + regs.write_allow_cmd_slot(0, reg_val); + continue; + } + CMD_EX4B => { + regs.write_allow_cmd_slot(1, reg_val); + continue; + } + CMD_WREAR => { + regs.write_allow_cmd_slot(MAX_CMD_INDEX, reg_val); + continue; + } + _ => { + idx += 1; + } + } + + if idx > MAX_CMD_INDEX { + break; + } + regs.write_allow_cmd_slot(idx, reg_val); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn read_03_encodes_with_valid_bit() { + let base = lookup_cmd_table_val(CMD_READ_1_1_1_3B).expect("0x03 in table"); + let encoded = base | CMD_TABLE_VALID_BIT; + assert_eq!(encoded & 0xFF, 0x03); + assert_ne!(encoded & CMD_TABLE_VALID_BIT, 0); + } + + #[test] + fn winbond_die_sel_in_table() { + assert!(lookup_cmd_table_val(CMD_WINBOND_DIE_SEL).is_some()); + } +}
diff --git a/target/ast10x0/peripherals/spimonitor/controller.rs b/target/ast10x0/peripherals/spimonitor/controller.rs index 39b1a1e..c8da20e 100644 --- a/target/ast10x0/peripherals/spimonitor/controller.rs +++ b/target/ast10x0/peripherals/spimonitor/controller.rs
@@ -7,10 +7,12 @@ use crate::scu::registers::ScuRegisters; use crate::scu::types::{ScuExtMuxSelect, SpiMonitorInstance}; +use crate::spimonitor::addr_priv; +use crate::spimonitor::cmd_table; 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, + ExtMuxSel, LockState, MonitorState, PassthroughMode, Result, SpiMonitorError, ViolationLogEntry, }; @@ -37,38 +39,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,22 +70,22 @@ 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 command allow-list table (encoded SPIPFWT words + VALID bit). + cmd_table::init_allow_cmd_table( + &self.regs, + &policy.allow_commands[..policy.allow_command_count], + ); - // Program address filter table. + // Program address privilege tables (bit-per-16KB SPIPFWA entries). for i in 0..policy.region_count { if let Some(region) = policy.regions[i] { - let word = encode_addr_filter_slot( - region.start, - region.length, + addr_priv::configure_address_privilege( + &self.regs, region.direction, region.op, - ); - self.regs.write_addr_filter_slot(i, word); + region.start, + region.length, + )?; } } @@ -138,40 +108,43 @@ // --------------------------------------------------------------------------- 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); + fn scu_instance(&self) -> SpiMonitorInstance { + match self.controller { + SpiMonitorController::Spim0 => SpiMonitorInstance::Spim0, + SpiMonitorController::Spim1 => SpiMonitorInstance::Spim1, + SpiMonitorController::Spim2 => SpiMonitorInstance::Spim2, + SpiMonitorController::Spim3 => SpiMonitorInstance::Spim3, + } } - /// Disable the monitor filter (SPIPF000 bit 0). - /// - /// Mirrors Zephyr's `spim_monitor_enable(dev, false)`. + /// Enable the monitor filter (SPIPF000 bit 2) and MISO multi-func pin. + pub fn enable(&self) { + self.scu + .set_spim_miso_multi_func(self.scu_instance(), true); + self.regs.set_filter_enable(true); + } + + /// Disable the monitor filter (SPIPF000 bit 2) and MISO multi-func pin. pub fn disable(&self) { - self.regs - .modify_ctrl(|bits| *bits &= !CTRL_MONITOR_ENABLE_BIT); + self.regs.set_filter_enable(false); + self.scu + .set_spim_miso_multi_func(self.scu_instance(), false); } /// 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, - }); + self.regs + .set_single_passthrough(matches!(mode, PassthroughMode::Enabled)); } /// Select the external SPI mux routing. /// - /// Mirrors Zephyr's `spim_ext_mux_config`. Platform code maps `Sel0`/`Sel1` - /// to ROT vs BMC/PCH roles. + /// 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. + /// for each SPIPF instance. pub fn set_ext_mux(&self, sel: ExtMuxSel) { use crate::scu::types::{ScuExtMuxSelect, SpiMonitorInstance}; @@ -215,13 +188,13 @@ /// 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: + /// 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. + // Full lock requires SPIPF07C write-disable bits. self.regs.modify_ctrl(|bits| *bits |= CTRL_LOCK_BIT); Ok(SpiMonitor { @@ -248,16 +221,14 @@ /// 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, - }); + self.regs + .set_single_passthrough(matches!(mode, PassthroughMode::Enabled)); } /// 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. + /// Uses SCU0F0 register. pub fn set_ext_mux(&self, sel: ExtMuxSel) { let mux = match sel { ExtMuxSel::Sel0 => ScuExtMuxSelect::Mux0, @@ -325,14 +296,11 @@ // 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] +/// SPIPF000 bit positions (from ast1060_pac). #[allow(dead_code)] -const CTRL_SW_RESET_BIT: u32 = 1 << 0; // sweng_rst() in SPIPF000[?] - uses PAC field +const CTRL_MULTI_PASSTHROUGH_BIT: u32 = 1 << 1; // enbl_multiple_bit_passthrough() in SPIPF000[1] +#[allow(dead_code)] +const CTRL_SW_RESET_BIT: u32 = 1 << 15; // sweng_rst() in SPIPF000[15] (SW Engine Reset) #[allow(dead_code)] const CTRL_EXT_MUX_SEL_BIT: u32 = 1 << 2; // PLACEHOLDER - NOT in SPIPF000! See note below. #[allow(dead_code)] @@ -340,9 +308,8 @@ // // 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() + // table entry lock bits. /// Shared drain-log implementation used by both `Configured` and `Locked`. fn drain_log_impl<'a>(
diff --git a/target/ast10x0/peripherals/spimonitor/mod.rs b/target/ast10x0/peripherals/spimonitor/mod.rs index 8f649e1..1096560 100644 --- a/target/ast10x0/peripherals/spimonitor/mod.rs +++ b/target/ast10x0/peripherals/spimonitor/mod.rs
@@ -3,6 +3,8 @@ //! AST10x0 SPI monitor (SPIPF) module. +pub mod addr_priv; +pub mod cmd_table; pub mod controller; pub mod policy; pub mod profile;
diff --git a/target/ast10x0/peripherals/spimonitor/registers.rs b/target/ast10x0/peripherals/spimonitor/registers.rs index 3fcf02e..5febb8d 100644 --- a/target/ast10x0/peripherals/spimonitor/registers.rs +++ b/target/ast10x0/peripherals/spimonitor/registers.rs
@@ -53,6 +53,10 @@ /// Size of each SPIPF register block. pub const SPIPF_REG_SIZE: usize = 0x1000; +/// Spin iterations for the `sw_reset` pulse hold. Targets ~5 us at 200 MHz; +/// exact timing is uncritical for the reset to latch. +const SW_RESET_SPIN_COUNT: u32 = 5 * 200; + /// Safe wrapper around a SPI monitor register block. pub struct SpiMonitorRegisters { base: *const device::spipf::RegisterBlock, @@ -109,6 +113,37 @@ }); } + /// Enable or disable the monitor filter (SPIPF000 `enbl_filter_fn`, bit 2). + pub fn set_filter_enable(&self, enable: bool) { + self.regs() + .spipf000() + .modify(|_, w| w.enbl_filter_fn().bit(enable)); + } + + /// Enable or disable single-bit passthrough (SPIPF000 + /// `enbl_single_bit_passthrough`, bit 0); passthrough bypasses the filter. + pub fn set_single_passthrough(&self, enable: bool) { + self.regs() + .spipf000() + .modify(|_, w| w.enbl_single_bit_passthrough().bit(enable)); + } + + /// Software-reset the monitor: pulse SPIPF000 `sweng_rst` (bit 15) high then + /// low. Clears status and logs while preserving the programmed policy. + /// `sweng_rst` is not self-clearing, so it is driven low explicitly after a + /// brief hold. + pub fn sw_reset(&self) { + self.regs() + .spipf000() + .modify(|_, w| w.sweng_rst().bit(true)); + for _ in 0..SW_RESET_SPIN_COUNT { + core::hint::spin_loop(); + } + self.regs() + .spipf000() + .modify(|_, w| w.sweng_rst().bit(false)); + } + /// SPIPF004: Secondary control/config register. pub fn read_ctrl2(&self) -> u32 { self.regs().spipf004().read().bits() @@ -154,7 +189,7 @@ // // 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. + // known SPIPF register map patterns. // ----------------------------------------------------------------------- /// Current violation log write index (number of entries written so far).
diff --git a/target/ast10x0/peripherals/spimonitor/types.rs b/target/ast10x0/peripherals/spimonitor/types.rs index 4be2d82..68e08bd 100644 --- a/target/ast10x0/peripherals/spimonitor/types.rs +++ b/target/ast10x0/peripherals/spimonitor/types.rs
@@ -11,8 +11,6 @@ } /// 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. @@ -25,7 +23,6 @@ /// /// 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, @@ -36,7 +33,6 @@ /// /// `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,