From f2e82539932f45a57e06fdbff229ab0d3cbfa386 Mon Sep 17 00:00:00 2001 From: hailin wu Date: Thu, 18 Jun 2026 10:26:43 -0700 Subject: [PATCH 01/13] add spi2 master controller test. now it can get jedec id from flash #3 resolved Conflicts: --- target/ast10x0/peripherals/scu/pinctrl.rs | 4 + target/ast10x0/peripherals/scu/routing.rs | 274 +++++++++++-------- target/ast10x0/tests/smc/read/target_spi2.rs | 242 ++++++++++++++++ 3 files changed, 407 insertions(+), 113 deletions(-) create mode 100644 target/ast10x0/tests/smc/read/target_spi2.rs diff --git a/target/ast10x0/peripherals/scu/pinctrl.rs b/target/ast10x0/peripherals/scu/pinctrl.rs index d3997168..9d90f544 100644 --- a/target/ast10x0/peripherals/scu/pinctrl.rs +++ b/target/ast10x0/peripherals/scu/pinctrl.rs @@ -537,6 +537,10 @@ paste! { gen_pin_pairs!(SCU6B0, 0x6B0, 31); } +// GPIO +pub const PINCTRL_GPIOL2: &[PinctrlPin] = &[CLR_PIN_SCU418_26]; +pub const PINCTRL_GPIOL3: &[PinctrlPin] = &[CLR_PIN_SCU418_27]; + /// I2C1 pin group: SCL2/SDA2 mux selection on SCU414[30:31]. /// /// The SVD names these EnblSCL2FnPin/EnblSDA2FnPin, but they correspond to diff --git a/target/ast10x0/peripherals/scu/routing.rs b/target/ast10x0/peripherals/scu/routing.rs index 0ed23f63..41a2868a 100644 --- a/target/ast10x0/peripherals/scu/routing.rs +++ b/target/ast10x0/peripherals/scu/routing.rs @@ -3,27 +3,92 @@ //! SCU routing and mux helpers for SPI monitor integration. +use core::ptr::{read_volatile, write_volatile}; + use super::registers::ScuRegisters; use super::types::{ Result, ScuExtMuxSelect, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource, }; -const PIN_SPIM0_CLK_OUT_BIT: u32 = 7; -const PIN_SPIM1_CLK_OUT_BIT: u32 = 21; -const PIN_SPIM2_CLK_OUT_BIT: u32 = 3; -const PIN_SPIM3_CLK_OUT_BIT: u32 = 17; - -pub type SpimGpioOriVal = [u32; 4]; - -macro_rules! modify_reg { - ($reg:expr, $bit:expr, $clear:expr) => {{ - let mut val: u32 = $reg.read().bits(); - if $clear { - val &= !(1 << $bit); - } else { - val |= 1 << $bit; - } - $reg.write(|w| unsafe { w.bits(val) }); - }}; + +#[derive(Clone, Copy)] +pub struct SpimGpioOriVal { + clk_gpio_ori_val: [u32; 4], +} + +#[derive(Clone, Copy)] +struct SpimGpioInfo { + scu_reg_addr: usize, + scu_bit_mask: u32, + gpio_addr: usize, + gpio_bit_mask: u32, +} + +// Literal translation of g_ast1060_spim_clk_gpio[] in spi_aspeed.c. +const AST1060_SPIM_CLK_GPIO: [SpimGpioInfo; 4] = [ + SpimGpioInfo { + scu_reg_addr: 0x7e6e_2690, + scu_bit_mask: 1 << 7, + gpio_addr: 0x7e78_0000, + gpio_bit_mask: 1 << 7, + }, + SpimGpioInfo { + scu_reg_addr: 0x7e6e_2690, + scu_bit_mask: 1 << 21, + gpio_addr: 0x7e78_0000, + gpio_bit_mask: 1 << 21, + }, + SpimGpioInfo { + scu_reg_addr: 0x7e6e_2694, + scu_bit_mask: 1 << 3, + gpio_addr: 0x7e78_0020, + gpio_bit_mask: 1 << 3, + }, + SpimGpioInfo { + scu_reg_addr: 0x7e6e_2694, + scu_bit_mask: 1 << 17, + gpio_addr: 0x7e78_0020, + gpio_bit_mask: 1 << 17, + }, +]; + +// Literal translation of g_ast1060_spim_cs_gpio[] in spi_aspeed.c. +const AST1060_SPIM_CS_GPIO: [SpimGpioInfo; 4] = [ + SpimGpioInfo { + scu_reg_addr: 0x7e6e_2690, + scu_bit_mask: 1 << 1, + gpio_addr: 0x7e78_0000, + gpio_bit_mask: 1 << 6, + }, + SpimGpioInfo { + scu_reg_addr: 0x7e6e_2690, + scu_bit_mask: 1 << 20, + gpio_addr: 0x7e78_0000, + gpio_bit_mask: 1 << 20, + }, + SpimGpioInfo { + scu_reg_addr: 0x7e6e_2694, + scu_bit_mask: 1 << 2, + gpio_addr: 0x7e78_0020, + gpio_bit_mask: 1 << 2, + }, + SpimGpioInfo { + scu_reg_addr: 0x7e6e_2694, + scu_bit_mask: 1 << 16, + gpio_addr: 0x7e78_0020, + gpio_bit_mask: 1 << 16, + }, +]; + +fn ast1060_spim_op_idx(scu0f0: u32) -> Option { + if scu0f0 & 0x7 == 0 { + return None; + } + + match (scu0f0 & 0x7) - 1 { + 2 => Some(3), + 3 => Some(2), + _ => None, + } } impl ScuRegisters { @@ -94,120 +159,103 @@ impl ScuRegisters { pub fn spim_proprietary_pre_config(&self) -> Option { self.unlock_write_protection(); - let scu = self.regs(); - let gpio = unsafe { &*ast1060_pac::Gpio::ptr() }; + let scu0f0 = self.regs().scu0f0().read().bits(); + pw_log::debug!("SPIM pre-config: SCU0F0=0x{:08x}", scu0f0 as u32); + let op_idx = match ast1060_spim_op_idx(scu0f0) { + Some(idx) => idx, + None => return None, + }; + pw_log::debug!( + "SPIM pre-config: active SPIM index {}, op index {}", + ((scu0f0 & 0x7) - 1) as u32, + op_idx as u32 + ); - let scu0f0 = scu.scu0f0().read().bits(); - if scu0f0 & 0x7 == 0 { - return None; - } + let clk = AST1060_SPIM_CLK_GPIO[op_idx]; + let cs = AST1060_SPIM_CS_GPIO[op_idx]; + let mut clk_gpio_ori_val = [0u32; 4]; - let spim_idx = (scu0f0 & 0x7) - 1; - if spim_idx > 3 { - return None; - } + unsafe { + // Change the paired SPIM CLKOUT pin to GPIO mode. + let mut reg_val = read_volatile(clk.scu_reg_addr as *const u32); + reg_val &= !clk.scu_bit_mask; + write_volatile(clk.scu_reg_addr as *mut u32, reg_val); - let mut gpio_ori_val = [0; 4]; + // Save its GPIO direction bit, then configure it as an input. + let clk_dir_addr = (clk.gpio_addr + 0x4) as *mut u32; + reg_val = read_volatile(clk_dir_addr); + clk_gpio_ori_val[op_idx] = reg_val & clk.gpio_bit_mask; + reg_val &= !clk.gpio_bit_mask; + write_volatile(clk_dir_addr, reg_val); - for idx in 0..4 { - if idx as u32 == spim_idx { - continue; - } + // Drive the paired SPIM CSOUT GPIO high and configure it as output. + let cs_data_addr = cs.gpio_addr as *mut u32; + reg_val = read_volatile(cs_data_addr); + reg_val |= cs.gpio_bit_mask; + write_volatile(cs_data_addr, reg_val); - match idx { - 0 => { - modify_reg!(scu.scu690(), PIN_SPIM0_CLK_OUT_BIT, true); - gpio_ori_val[0] = gpio.gpio004().read().bits(); - modify_reg!(gpio.gpio004(), PIN_SPIM0_CLK_OUT_BIT, true); - } - 1 => { - modify_reg!(scu.scu690(), PIN_SPIM1_CLK_OUT_BIT, true); - gpio_ori_val[1] = gpio.gpio004().read().bits(); - modify_reg!(gpio.gpio004(), PIN_SPIM1_CLK_OUT_BIT, true); - } - 2 => { - modify_reg!(scu.scu694(), PIN_SPIM2_CLK_OUT_BIT, true); - gpio_ori_val[2] = gpio.gpio024().read().bits(); - modify_reg!(gpio.gpio024(), PIN_SPIM2_CLK_OUT_BIT, true); - } - 3 => { - modify_reg!(scu.scu694(), PIN_SPIM3_CLK_OUT_BIT, true); - gpio_ori_val[3] = gpio.gpio024().read().bits(); - modify_reg!(gpio.gpio024(), PIN_SPIM3_CLK_OUT_BIT, true); - } - _ => {} - } + let cs_dir_addr = (cs.gpio_addr + 0x4) as *mut u32; + reg_val = read_volatile(cs_dir_addr); + reg_val |= cs.gpio_bit_mask; + write_volatile(cs_dir_addr, reg_val); + + // Change the paired SPIM CSOUT pin to GPIO mode. + let cs_scu_addr = cs.scu_reg_addr as *mut u32; + reg_val = read_volatile(cs_scu_addr); + reg_val &= !cs.scu_bit_mask; + write_volatile(cs_scu_addr, reg_val); } - Some(gpio_ori_val) + pw_log::debug!( + "SPIM pre-config: op index {}, saved CLK direction mask 0x{:08x}", + op_idx as u32, + clk_gpio_ori_val[op_idx] as u32 + ); + Some(SpimGpioOriVal { + clk_gpio_ori_val, + }) } /// Restore AST1060 SPIM proprietary pin state after a transaction. pub fn spim_proprietary_post_config(&self, gpio_ori_val: SpimGpioOriVal) { self.unlock_write_protection(); - let scu = self.regs(); - let gpio = unsafe { &*ast1060_pac::Gpio::ptr() }; + let scu0f0 = self.regs().scu0f0().read().bits(); + let op_idx = match ast1060_spim_op_idx(scu0f0) { + Some(idx) => idx, + None => return, + }; + let clk = AST1060_SPIM_CLK_GPIO[op_idx]; + let cs = AST1060_SPIM_CS_GPIO[op_idx]; - let bits = scu.scu0f0().read().bits(); - if bits.trailing_zeros() >= 3 { - return; - } + pw_log::debug!( + "SPIM post-config: SCU0F0=0x{:08x}, op index {}, saved CLK direction mask 0x{:08x}", + scu0f0 as u32, + op_idx as u32, + gpio_ori_val.clk_gpio_ori_val[op_idx] as u32 + ); - let spim_idx = (bits & 0x7) - 1; - if spim_idx > 3 { - return; - } + unsafe { + // Restore the paired CLKOUT GPIO direction bit. + let clk_dir_addr = (clk.gpio_addr + 0x4) as *mut u32; + let mut reg_val = read_volatile(clk_dir_addr); + reg_val &= !clk.gpio_bit_mask; + reg_val |= gpio_ori_val.clk_gpio_ori_val[op_idx]; + write_volatile(clk_dir_addr, reg_val); - for idx in 0..4 { - if idx as u32 == spim_idx { - continue; - } + // Return paired CLKOUT and CSOUT pins to SPIM mode. + let clk_scu_addr = clk.scu_reg_addr as *mut u32; + reg_val = read_volatile(clk_scu_addr); + reg_val |= clk.scu_bit_mask; + write_volatile(clk_scu_addr, reg_val); - match idx { - 0 => { - let ori_val = gpio_ori_val[0]; - gpio.gpio004().modify(|r, w| unsafe { - let mut current = r.bits(); - current &= !(1 << PIN_SPIM0_CLK_OUT_BIT); - current |= ori_val; - w.bits(current) - }); - modify_reg!(scu.scu690(), PIN_SPIM0_CLK_OUT_BIT, false); - } - 1 => { - let ori_val = gpio_ori_val[1]; - gpio.gpio004().modify(|r, w| unsafe { - let mut current = r.bits(); - current &= !(1 << PIN_SPIM1_CLK_OUT_BIT); - current |= ori_val; - w.bits(current) - }); - modify_reg!(gpio.gpio004(), PIN_SPIM1_CLK_OUT_BIT, false); - } - 2 => { - let ori_val = gpio_ori_val[2]; - gpio.gpio024().modify(|r, w| unsafe { - let mut current = r.bits(); - current &= !(1 << PIN_SPIM2_CLK_OUT_BIT); - current |= ori_val; - w.bits(current) - }); - modify_reg!(scu.scu694(), PIN_SPIM2_CLK_OUT_BIT, false); - } - 3 => { - let ori_val = gpio_ori_val[3]; - gpio.gpio024().modify(|r, w| unsafe { - let mut current = r.bits(); - current &= !(1 << PIN_SPIM3_CLK_OUT_BIT); - current |= ori_val; - w.bits(current) - }); - modify_reg!(scu.scu694(), PIN_SPIM3_CLK_OUT_BIT, false); - } - _ => {} - } + let cs_scu_addr = cs.scu_reg_addr as *mut u32; + reg_val = read_volatile(cs_scu_addr); + reg_val |= cs.scu_bit_mask; + write_volatile(cs_scu_addr, reg_val); } + + pw_log::debug!("SPIM post-config: restore complete"); } /// Select the external mux signal for a SPI monitor instance. Uses SCU0F0[15:12]. diff --git a/target/ast10x0/tests/smc/read/target_spi2.rs b/target/ast10x0/tests/smc/read/target_spi2.rs new file mode 100644 index 00000000..9b9f56eb --- /dev/null +++ b/target/ast10x0/tests/smc/read/target_spi2.rs @@ -0,0 +1,242 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! AST10x0 SMC SPI2 read smoke test target. + +#![no_std] +#![no_main] +#[allow(unused_imports)] +use ast10x0_peripherals::scu::{ + pinctrl::{ + PINCTRL_GPIOL2, PINCTRL_GPIOL3, PINCTRL_SPI2_QUAD, PINCTRL_SPIM3_DEFAULT, + PINCTRL_SPIM4_DEFAULT, + }, + ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource, ScuExtMuxSelect, +}; +use ast10x0_peripherals::smc::{ + ChipSelect, FlashConfig, SmcConfig, SmcController, SmcError, SmcTopology, SpiTransaction, + SpiUninit, TransferMode, +}; +use console_backend::console_backend_write_all; +use core::ptr::{read_volatile, write_volatile}; +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +#[path = "../target_debug.rs"] +mod target_debug; +use target_debug::{dump_smc_read, dump_smc_register}; + +const SPI_FLASH_CONFIG: FlashConfig = FlashConfig { + capacity_mb: 32, + page_size: 256, + sector_size: 4096, + block_size: 65536, + spi_clock_mhz: 25, +}; + +pub struct Target {} +/* +SCU418 = 0x7E6E_2418, clear bits 26, 27 +GPIO070 = 0x7E78_0070, set bits 26, 27 +GPIO074 = 0x7E78_0074, set bits 26, 27 +*/ +fn gpio_flash_power() { + // const SCU418: *mut u32 = 0x7E6E_2418 as *mut u32; + const GPIO_DATA: *mut u32 = 0x7E78_0070 as *mut u32; + const GPIO_DIR: *mut u32 = 0x7E78_0074 as *mut u32; + const GPIOL2_L3_MASK: u32 = (1 << 26) | (1 << 27); + + unsafe { + // Select GPIO function for GPIOL2/GPIOL3. + // write_volatile(SCU418, read_volatile(SCU418) & !GPIOL2_L3_MASK); + // Configure GPIOL2/GPIOL3 as outputs and drive them high. + write_volatile(GPIO_DIR, read_volatile(GPIO_DIR) | GPIOL2_L3_MASK); + write_volatile(GPIO_DATA, read_volatile(GPIO_DATA) | GPIOL2_L3_MASK); + } + + for _ in 0..1_000_000 { + core::hint::spin_loop(); + } +} + +// Temporary raw-MMIO implementation until GPIO and SGPIOM peripherals exist. +#[allow(dead_code)] +fn configure_spi2_external_mux(select_mux1: bool) { + const SCU41C: *mut u32 = 0x7E6E_241C as *mut u32; + const SGPIOM_PIN_MASK: u32 = 0xF << 8; + + const GPIO_E_H_DATA: *mut u32 = 0x7E78_0020 as *mut u32; + const GPIO_E_H_DIR: *mut u32 = 0x7E78_0024 as *mut u32; + const GPIO_E8: u32 = 1 << 8; + + const SGPIOM_DATA_A_D: *mut u32 = 0x7E78_0500 as *mut u32; + const SGPIOM_CONFIG: *mut u32 = 0x7E78_0554 as *mut u32; + const SGPIOM_WRITE_LATCH_A_D: *const u32 = 0x7E78_0570 as *const u32; + const SGPIOM_A_D_2: u32 = 1 << 2; + const SGPIOM_CONFIG_MASK: u32 = 1 | (0x1F << 6) | (0xFFFF << 16); + const SGPIOM_CONFIG_1MHZ_128_PINS: u32 = 1 | (16 << 6) | (24 << 16); + + unsafe { + // Select the SGPIOM physical pins and initialize the controller using + // the same 1 MHz / 128-pin settings as the Zephyr board configuration. + write_volatile(SCU41C, read_volatile(SCU41C) | SGPIOM_PIN_MASK); + let config = read_volatile(SGPIOM_CONFIG) & !SGPIOM_CONFIG_MASK; + write_volatile( + SGPIOM_CONFIG, + config | SGPIOM_CONFIG_1MHZ_128_PINS, + ); + + let mut gpio_data = read_volatile(GPIO_E_H_DATA); + let mut sgpio_data = read_volatile(SGPIOM_WRITE_LATCH_A_D); + if select_mux1 { + gpio_data |= GPIO_E8; + sgpio_data |= SGPIOM_A_D_2; + } else { + gpio_data &= !GPIO_E8; + sgpio_data &= !SGPIOM_A_D_2; + } + + write_volatile(GPIO_E_H_DATA, gpio_data); + write_volatile(GPIO_E_H_DIR, read_volatile(GPIO_E_H_DIR) | GPIO_E8); + write_volatile(SGPIOM_DATA_A_D, sgpio_data); + } + + // Match the overlay's ext-mux-sel-delay-us = <1000>. + for _ in 0..100_000 { + core::hint::spin_loop(); + } +} + +fn config_spi2_master_controller() -> Result<(), SmcError> { + let scu = unsafe { ScuRegisters::new_global_unlocked() }; + scu.apply_pinctrl_group(PINCTRL_SPIM3_DEFAULT); + scu.apply_pinctrl_group(PINCTRL_SPIM4_DEFAULT); + scu.apply_pinctrl_group(PINCTRL_SPI2_QUAD); + scu.apply_pinctrl_group(PINCTRL_GPIOL2); + scu.apply_pinctrl_group(PINCTRL_GPIOL3); + gpio_flash_power(); + //configure spi2 external mux through gpio pins + configure_spi2_external_mux(true); + + // Configure the mux for the SPI master controller path. + scu.set_spim_internal_master_route(SpiMonitorInstance::Spim2, SpiMonitorSource::Spi2); + scu.set_spim_passthrough(SpiMonitorInstance::Spim2, SpiMonitorPassthrough::Enabled); + scu.set_spim_ext_mux(SpiMonitorInstance::Spim2, ScuExtMuxSelect::Mux1); + pw_log::info!("SCU pinmux and SPIM routing configured for SPI2 monitoring"); + unsafe { + write_volatile(0x7E6E_20F0 as *mut u32, 0xfff0); + } + for _ in 0..1_000_000 { + core::hint::spin_loop(); + } + Ok(()) +} + +fn run_spi2_read_test() -> Result<(), SmcError> { + config_spi2_master_controller()?; + + let config = SmcConfig { + controller_id: SmcController::Spi2, + cs0: Some(SPI_FLASH_CONFIG), + cs1: None, + dma_enabled: true, + enable_interrupts: false, + topology: SmcTopology::NormalSpi { master_idx: 2 }, + }; + + pw_log::info!("=== AST10x0 SMC SPI2 read test ==="); + let spi = unsafe { SpiUninit::new(SmcController::Spi2, config)? }; + let mut spi = spi.init()?; + spi.spi_nor_read_init(ChipSelect::Cs0)?; + + if !spi.is_ready() { + return Err(SmcError::HardwareError); + } + + pw_log::info!("=== SPI2 controller register ==="); + dump_smc_register(0x7E64_0000, 16); + dump_smc_register(0x7E64_0080, 16); + pw_log::info!("=== SCU QSPI Mux routing register ==="); + dump_smc_register(0x7E6E_20F0, 2); + dump_smc_register(0x7E6E_2418, 2); + dump_smc_register(0x7e6e_2694, 2); + dump_smc_register(0x7E78_0020, 2); + dump_smc_register(0x7E78_0070, 2); + let mut jedec = [0u8; 3]; + SpiTransaction::transceive_user_with_spim( + &mut spi, + SpiMonitorInstance::Spim2, + ChipSelect::Cs0, + &[0x9f], + &[], + &mut jedec, + TransferMode::Mode111, + )?; + pw_log::info!( + "SPI2 CS0 JEDEC ID: {:02x} {:02x} {:02x}", + jedec[0] as u32, + jedec[1] as u32, + jedec[2] as u32 + ); + + if jedec[0] == 0xff { + pw_log::info!("SPI2 CS0 JEDEC manufacturer is 0xff; skipping read test"); + return Ok(()); + } + + pw_log::info!("=== SPI2 read ==="); + let mut buf = [0u8; 64]; + let n = SpiTransaction::read_with_spim( + &mut spi, + SpiMonitorInstance::Spim2, + ChipSelect::Cs0, + 0x0, + &mut buf, + )?; + if n != buf.len() { + return Err(SmcError::HardwareError); + } + dump_smc_read(&buf, buf.len() as u32); + + pw_log::info!("=== SPI2 DMA read @ 0x00000000 ==="); + let dma_buf = unsafe { core::slice::from_raw_parts_mut(0x41500 as *mut u8, 256) }; + let mut dma_txn = SpiTransaction::dma_read_with_spim( + &mut spi, + SpiMonitorInstance::Spim2, + ChipSelect::Cs0, + 0x0, + 0x41500usize, + dma_buf.len() as u32, + )?; + + loop { + match dma_txn.poll_dma_completion() { + core::task::Poll::Pending => {} + core::task::Poll::Ready(result) => { + result?; + break; + } + } + } + dump_smc_read(dma_buf, dma_buf.len() as u32); + + Ok(()) +} + +impl TargetInterface for Target { + const NAME: &'static str = "AST10x0 SMC SPI2 read Test"; + + fn main() -> ! { + let sentinel = if run_spi2_read_test().is_ok() { + b"TEST_RESULT:PASS\n" + } else { + b"TEST_RESULT:FAIL\n" + }; + let _ = console_backend_write_all(sentinel); + + #[expect(clippy::empty_loop)] + loop {} + } +} + +declare_target!(Target); From 95b87a2125f763ca7b69e04af42c1592618114dd Mon Sep 17 00:00:00 2001 From: hailin wu Date: Mon, 8 Jun 2026 16:37:16 -0700 Subject: [PATCH 02/13] add bazel build for spi2 --- target/ast10x0/peripherals/scu/routing.rs | 4 +- target/ast10x0/tests/smc/read/BUILD.bazel | 44 ++++++++++++++++++++ target/ast10x0/tests/smc/read/target_spi2.rs | 11 ++--- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/target/ast10x0/peripherals/scu/routing.rs b/target/ast10x0/peripherals/scu/routing.rs index 41a2868a..784c2065 100644 --- a/target/ast10x0/peripherals/scu/routing.rs +++ b/target/ast10x0/peripherals/scu/routing.rs @@ -211,9 +211,7 @@ impl ScuRegisters { op_idx as u32, clk_gpio_ori_val[op_idx] as u32 ); - Some(SpimGpioOriVal { - clk_gpio_ori_val, - }) + Some(SpimGpioOriVal { clk_gpio_ori_val }) } /// Restore AST1060 SPIM proprietary pin state after a transaction. diff --git a/target/ast10x0/tests/smc/read/BUILD.bazel b/target/ast10x0/tests/smc/read/BUILD.bazel index d740fafe..f10f3a0a 100644 --- a/target/ast10x0/tests/smc/read/BUILD.bazel +++ b/target/ast10x0/tests/smc/read/BUILD.bazel @@ -50,6 +50,28 @@ rust_binary( ], ) +rust_binary( + name = "target_spi2", + srcs = [ + "target_spi2.rs", + "//target/ast10x0/tests/smc:target_debug.rs", + ], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/ast10x0:entry", + "//target/ast10x0/peripherals", + "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", + "@pigweed//pw_kernel/kernel", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_log/rust:pw_log", + ], +) + rust_binary( name = "target", srcs = [ @@ -95,6 +117,28 @@ system_image_test( visibility = ["//visibility:public"], ) +system_image( + name = "smc_spi2_read_test", + kernel = ":target_spi2", + platform = "//target/ast10x0", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + userspace = False, + visibility = ["//visibility:public"], +) + +system_image_test( + name = "smc_spi2_read_evb_test", + image = ":smc_spi2_read_test", + tags = ["hardware"], + target_compatible_with = select({ + "//target/ast10x0:qemu_enabled": ["@platforms//:incompatible"], + "//conditions:default": [], + }), + visibility = ["//visibility:public"], +) + rust_binary_no_panics_test( name = "spi1_no_panics_test", binary = ":smc_spi1_read_test", diff --git a/target/ast10x0/tests/smc/read/target_spi2.rs b/target/ast10x0/tests/smc/read/target_spi2.rs index 9b9f56eb..1b67bb33 100644 --- a/target/ast10x0/tests/smc/read/target_spi2.rs +++ b/target/ast10x0/tests/smc/read/target_spi2.rs @@ -11,7 +11,7 @@ use ast10x0_peripherals::scu::{ PINCTRL_GPIOL2, PINCTRL_GPIOL3, PINCTRL_SPI2_QUAD, PINCTRL_SPIM3_DEFAULT, PINCTRL_SPIM4_DEFAULT, }, - ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource, ScuExtMuxSelect, + ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource, }; use ast10x0_peripherals::smc::{ ChipSelect, FlashConfig, SmcConfig, SmcController, SmcError, SmcTopology, SpiTransaction, @@ -81,10 +81,7 @@ fn configure_spi2_external_mux(select_mux1: bool) { // the same 1 MHz / 128-pin settings as the Zephyr board configuration. write_volatile(SCU41C, read_volatile(SCU41C) | SGPIOM_PIN_MASK); let config = read_volatile(SGPIOM_CONFIG) & !SGPIOM_CONFIG_MASK; - write_volatile( - SGPIOM_CONFIG, - config | SGPIOM_CONFIG_1MHZ_128_PINS, - ); + write_volatile(SGPIOM_CONFIG, config | SGPIOM_CONFIG_1MHZ_128_PINS); let mut gpio_data = read_volatile(GPIO_E_H_DATA); let mut sgpio_data = read_volatile(SGPIOM_WRITE_LATCH_A_D); @@ -123,9 +120,7 @@ fn config_spi2_master_controller() -> Result<(), SmcError> { scu.set_spim_passthrough(SpiMonitorInstance::Spim2, SpiMonitorPassthrough::Enabled); scu.set_spim_ext_mux(SpiMonitorInstance::Spim2, ScuExtMuxSelect::Mux1); pw_log::info!("SCU pinmux and SPIM routing configured for SPI2 monitoring"); - unsafe { - write_volatile(0x7E6E_20F0 as *mut u32, 0xfff0); - } + for _ in 0..1_000_000 { core::hint::spin_loop(); } From 623e64c6ccaa5c43241061cf0138a536448d939f Mon Sep 17 00:00:00 2001 From: HaiLin Wu Date: Tue, 9 Jun 2026 09:57:30 -0700 Subject: [PATCH 03/13] fixed clipping issue and use gpio pac instead of using volatile regiser read/write --- target/ast10x0/peripherals/scu/routing.rs | 194 ++++++++++++------- target/ast10x0/tests/smc/read/BUILD.bazel | 1 + target/ast10x0/tests/smc/read/target_spi2.rs | 94 ++++----- 3 files changed, 176 insertions(+), 113 deletions(-) diff --git a/target/ast10x0/peripherals/scu/routing.rs b/target/ast10x0/peripherals/scu/routing.rs index 784c2065..2ebad91d 100644 --- a/target/ast10x0/peripherals/scu/routing.rs +++ b/target/ast10x0/peripherals/scu/routing.rs @@ -3,7 +3,7 @@ //! SCU routing and mux helpers for SPI monitor integration. -use core::ptr::{read_volatile, write_volatile}; +use ast1060_pac as device; use super::registers::ScuRegisters; use super::types::{ @@ -17,36 +17,103 @@ pub struct SpimGpioOriVal { #[derive(Clone, Copy)] struct SpimGpioInfo { - scu_reg_addr: usize, + scu_register: SpimScuRegister, scu_bit_mask: u32, - gpio_addr: usize, + gpio_group: GpioGroup, gpio_bit_mask: u32, } +#[derive(Clone, Copy)] +enum SpimScuRegister { + Scu690, + Scu694, +} + +#[derive(Clone, Copy)] +enum GpioGroup { + Abcd, + Efgh, +} + +fn gpio_regs() -> &'static device::gpio::RegisterBlock { + // SAFETY: The PAC supplies the GPIO register block base address. + unsafe { &*device::Gpio::ptr() } +} + +fn gpio_direction(group: GpioGroup) -> u32 { + match group { + GpioGroup::Abcd => gpio_regs().gpio004().read().bits(), + GpioGroup::Efgh => gpio_regs().gpio024().read().bits(), + } +} + +fn gpio_set_direction(group: GpioGroup, mask: u32, output: bool) { + let update = |bits: u32| { + if output { + bits | mask + } else { + bits & !mask + } + }; + + match group { + GpioGroup::Abcd => gpio_regs() + .gpio004() + .modify(|r, w| unsafe { w.bits(update(r.bits())) }), + GpioGroup::Efgh => gpio_regs() + .gpio024() + .modify(|r, w| unsafe { w.bits(update(r.bits())) }), + }; +} + +fn gpio_set_data(group: GpioGroup, mask: u32, high: bool) { + let update = |bits: u32| { + if high { + bits | mask + } else { + bits & !mask + } + }; + + match group { + GpioGroup::Abcd => gpio_regs() + .gpio000() + .modify(|r, w| unsafe { w.bits(update(r.bits())) }), + GpioGroup::Efgh => gpio_regs() + .gpio020() + .modify(|r, w| unsafe { w.bits(update(r.bits())) }), + }; +} + +fn gpio_set_output(group: GpioGroup, mask: u32, high: bool) { + gpio_set_data(group, mask, high); + gpio_set_direction(group, mask, true); +} + // Literal translation of g_ast1060_spim_clk_gpio[] in spi_aspeed.c. const AST1060_SPIM_CLK_GPIO: [SpimGpioInfo; 4] = [ SpimGpioInfo { - scu_reg_addr: 0x7e6e_2690, + scu_register: SpimScuRegister::Scu690, scu_bit_mask: 1 << 7, - gpio_addr: 0x7e78_0000, + gpio_group: GpioGroup::Abcd, gpio_bit_mask: 1 << 7, }, SpimGpioInfo { - scu_reg_addr: 0x7e6e_2690, + scu_register: SpimScuRegister::Scu690, scu_bit_mask: 1 << 21, - gpio_addr: 0x7e78_0000, + gpio_group: GpioGroup::Abcd, gpio_bit_mask: 1 << 21, }, SpimGpioInfo { - scu_reg_addr: 0x7e6e_2694, + scu_register: SpimScuRegister::Scu694, scu_bit_mask: 1 << 3, - gpio_addr: 0x7e78_0020, + gpio_group: GpioGroup::Efgh, gpio_bit_mask: 1 << 3, }, SpimGpioInfo { - scu_reg_addr: 0x7e6e_2694, + scu_register: SpimScuRegister::Scu694, scu_bit_mask: 1 << 17, - gpio_addr: 0x7e78_0020, + gpio_group: GpioGroup::Efgh, gpio_bit_mask: 1 << 17, }, ]; @@ -54,27 +121,27 @@ const AST1060_SPIM_CLK_GPIO: [SpimGpioInfo; 4] = [ // Literal translation of g_ast1060_spim_cs_gpio[] in spi_aspeed.c. const AST1060_SPIM_CS_GPIO: [SpimGpioInfo; 4] = [ SpimGpioInfo { - scu_reg_addr: 0x7e6e_2690, + scu_register: SpimScuRegister::Scu690, scu_bit_mask: 1 << 1, - gpio_addr: 0x7e78_0000, + gpio_group: GpioGroup::Abcd, gpio_bit_mask: 1 << 6, }, SpimGpioInfo { - scu_reg_addr: 0x7e6e_2690, + scu_register: SpimScuRegister::Scu690, scu_bit_mask: 1 << 20, - gpio_addr: 0x7e78_0000, + gpio_group: GpioGroup::Abcd, gpio_bit_mask: 1 << 20, }, SpimGpioInfo { - scu_reg_addr: 0x7e6e_2694, + scu_register: SpimScuRegister::Scu694, scu_bit_mask: 1 << 2, - gpio_addr: 0x7e78_0020, + gpio_group: GpioGroup::Efgh, gpio_bit_mask: 1 << 2, }, SpimGpioInfo { - scu_reg_addr: 0x7e6e_2694, + scu_register: SpimScuRegister::Scu694, scu_bit_mask: 1 << 16, - gpio_addr: 0x7e78_0020, + gpio_group: GpioGroup::Efgh, gpio_bit_mask: 1 << 16, }, ]; @@ -92,6 +159,27 @@ fn ast1060_spim_op_idx(scu0f0: u32) -> Option { } impl ScuRegisters { + fn set_spim_pin_function(&self, pin: SpimGpioInfo, enabled: bool) { + let update = |bits: u32| { + if enabled { + bits | pin.scu_bit_mask + } else { + bits & !pin.scu_bit_mask + } + }; + + match pin.scu_register { + SpimScuRegister::Scu690 => self + .regs() + .scu690() + .modify(|r, w| unsafe { w.bits(update(r.bits())) }), + SpimScuRegister::Scu694 => self + .regs() + .scu694() + .modify(|r, w| unsafe { w.bits(update(r.bits())) }), + }; + } + /// Enable or disable passthrough for a SPI monitor instance. Uses SCU0F0[7:4]. pub fn set_spim_passthrough( &self, @@ -174,37 +262,18 @@ impl ScuRegisters { let clk = AST1060_SPIM_CLK_GPIO[op_idx]; let cs = AST1060_SPIM_CS_GPIO[op_idx]; let mut clk_gpio_ori_val = [0u32; 4]; + // Change the paired SPIM CLKOUT pin to GPIO mode. + self.set_spim_pin_function(clk, false); - unsafe { - // Change the paired SPIM CLKOUT pin to GPIO mode. - let mut reg_val = read_volatile(clk.scu_reg_addr as *const u32); - reg_val &= !clk.scu_bit_mask; - write_volatile(clk.scu_reg_addr as *mut u32, reg_val); - - // Save its GPIO direction bit, then configure it as an input. - let clk_dir_addr = (clk.gpio_addr + 0x4) as *mut u32; - reg_val = read_volatile(clk_dir_addr); - clk_gpio_ori_val[op_idx] = reg_val & clk.gpio_bit_mask; - reg_val &= !clk.gpio_bit_mask; - write_volatile(clk_dir_addr, reg_val); - - // Drive the paired SPIM CSOUT GPIO high and configure it as output. - let cs_data_addr = cs.gpio_addr as *mut u32; - reg_val = read_volatile(cs_data_addr); - reg_val |= cs.gpio_bit_mask; - write_volatile(cs_data_addr, reg_val); - - let cs_dir_addr = (cs.gpio_addr + 0x4) as *mut u32; - reg_val = read_volatile(cs_dir_addr); - reg_val |= cs.gpio_bit_mask; - write_volatile(cs_dir_addr, reg_val); - - // Change the paired SPIM CSOUT pin to GPIO mode. - let cs_scu_addr = cs.scu_reg_addr as *mut u32; - reg_val = read_volatile(cs_scu_addr); - reg_val &= !cs.scu_bit_mask; - write_volatile(cs_scu_addr, reg_val); - } + // Save its GPIO direction bit, then configure it as an input. + clk_gpio_ori_val[op_idx] = gpio_direction(clk.gpio_group) & clk.gpio_bit_mask; + gpio_set_direction(clk.gpio_group, clk.gpio_bit_mask, false); + + // Drive the paired SPIM CSOUT GPIO high and configure it as output. + gpio_set_output(cs.gpio_group, cs.gpio_bit_mask, true); + + // Change the paired SPIM CSOUT pin to GPIO mode. + self.set_spim_pin_function(cs, false); pw_log::debug!( "SPIM pre-config: op index {}, saved CLK direction mask 0x{:08x}", @@ -225,7 +294,6 @@ impl ScuRegisters { }; let clk = AST1060_SPIM_CLK_GPIO[op_idx]; let cs = AST1060_SPIM_CS_GPIO[op_idx]; - pw_log::debug!( "SPIM post-config: SCU0F0=0x{:08x}, op index {}, saved CLK direction mask 0x{:08x}", scu0f0 as u32, @@ -233,25 +301,13 @@ impl ScuRegisters { gpio_ori_val.clk_gpio_ori_val[op_idx] as u32 ); - unsafe { - // Restore the paired CLKOUT GPIO direction bit. - let clk_dir_addr = (clk.gpio_addr + 0x4) as *mut u32; - let mut reg_val = read_volatile(clk_dir_addr); - reg_val &= !clk.gpio_bit_mask; - reg_val |= gpio_ori_val.clk_gpio_ori_val[op_idx]; - write_volatile(clk_dir_addr, reg_val); - - // Return paired CLKOUT and CSOUT pins to SPIM mode. - let clk_scu_addr = clk.scu_reg_addr as *mut u32; - reg_val = read_volatile(clk_scu_addr); - reg_val |= clk.scu_bit_mask; - write_volatile(clk_scu_addr, reg_val); - - let cs_scu_addr = cs.scu_reg_addr as *mut u32; - reg_val = read_volatile(cs_scu_addr); - reg_val |= cs.scu_bit_mask; - write_volatile(cs_scu_addr, reg_val); - } + // Restore the paired CLKOUT GPIO direction bit. + let was_output = gpio_ori_val.clk_gpio_ori_val[op_idx] != 0; + gpio_set_direction(clk.gpio_group, clk.gpio_bit_mask, was_output); + + // Return paired CLKOUT and CSOUT pins to SPIM mode. + self.set_spim_pin_function(clk, true); + self.set_spim_pin_function(cs, true); pw_log::debug!("SPIM post-config: restore complete"); } diff --git a/target/ast10x0/tests/smc/read/BUILD.bazel b/target/ast10x0/tests/smc/read/BUILD.bazel index f10f3a0a..3caf6936 100644 --- a/target/ast10x0/tests/smc/read/BUILD.bazel +++ b/target/ast10x0/tests/smc/read/BUILD.bazel @@ -64,6 +64,7 @@ rust_binary( ":linker_script", "//target/ast10x0:entry", "//target/ast10x0/peripherals", + "@ast1060_pac", "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", "@pigweed//pw_kernel/kernel", "@pigweed//pw_kernel/subsys/console:console_backend", diff --git a/target/ast10x0/tests/smc/read/target_spi2.rs b/target/ast10x0/tests/smc/read/target_spi2.rs index 1b67bb33..43759a1d 100644 --- a/target/ast10x0/tests/smc/read/target_spi2.rs +++ b/target/ast10x0/tests/smc/read/target_spi2.rs @@ -5,6 +5,7 @@ #![no_std] #![no_main] +use ast1060_pac as device; #[allow(unused_imports)] use ast10x0_peripherals::scu::{ pinctrl::{ @@ -18,7 +19,6 @@ use ast10x0_peripherals::smc::{ SpiUninit, TransferMode, }; use console_backend::console_backend_write_all; -use core::ptr::{read_volatile, write_volatile}; use target_common::{declare_target, TargetInterface}; use {console_backend as _, entry as _}; @@ -41,62 +41,68 @@ GPIO070 = 0x7E78_0070, set bits 26, 27 GPIO074 = 0x7E78_0074, set bits 26, 27 */ fn gpio_flash_power() { - // const SCU418: *mut u32 = 0x7E6E_2418 as *mut u32; - const GPIO_DATA: *mut u32 = 0x7E78_0070 as *mut u32; - const GPIO_DIR: *mut u32 = 0x7E78_0074 as *mut u32; const GPIOL2_L3_MASK: u32 = (1 << 26) | (1 << 27); - unsafe { - // Select GPIO function for GPIOL2/GPIOL3. - // write_volatile(SCU418, read_volatile(SCU418) & !GPIOL2_L3_MASK); - // Configure GPIOL2/GPIOL3 as outputs and drive them high. - write_volatile(GPIO_DIR, read_volatile(GPIO_DIR) | GPIOL2_L3_MASK); - write_volatile(GPIO_DATA, read_volatile(GPIO_DATA) | GPIOL2_L3_MASK); - } + // SAFETY: Board initialization has exclusive access to the PAC singleton. + let gpio = unsafe { &*device::Gpio::ptr() }; + gpio.gpio070() + .modify(|r, w| unsafe { w.bits(r.bits() | GPIOL2_L3_MASK) }); + gpio.gpio074() + .modify(|r, w| unsafe { w.bits(r.bits() | GPIOL2_L3_MASK) }); for _ in 0..1_000_000 { core::hint::spin_loop(); } } -// Temporary raw-MMIO implementation until GPIO and SGPIOM peripherals exist. #[allow(dead_code)] fn configure_spi2_external_mux(select_mux1: bool) { - const SCU41C: *mut u32 = 0x7E6E_241C as *mut u32; - const SGPIOM_PIN_MASK: u32 = 0xF << 8; - - const GPIO_E_H_DATA: *mut u32 = 0x7E78_0020 as *mut u32; - const GPIO_E_H_DIR: *mut u32 = 0x7E78_0024 as *mut u32; const GPIO_E8: u32 = 1 << 8; - - const SGPIOM_DATA_A_D: *mut u32 = 0x7E78_0500 as *mut u32; - const SGPIOM_CONFIG: *mut u32 = 0x7E78_0554 as *mut u32; - const SGPIOM_WRITE_LATCH_A_D: *const u32 = 0x7E78_0570 as *const u32; const SGPIOM_A_D_2: u32 = 1 << 2; - const SGPIOM_CONFIG_MASK: u32 = 1 | (0x1F << 6) | (0xFFFF << 16); - const SGPIOM_CONFIG_1MHZ_128_PINS: u32 = 1 | (16 << 6) | (24 << 16); - - unsafe { - // Select the SGPIOM physical pins and initialize the controller using - // the same 1 MHz / 128-pin settings as the Zephyr board configuration. - write_volatile(SCU41C, read_volatile(SCU41C) | SGPIOM_PIN_MASK); - let config = read_volatile(SGPIOM_CONFIG) & !SGPIOM_CONFIG_MASK; - write_volatile(SGPIOM_CONFIG, config | SGPIOM_CONFIG_1MHZ_128_PINS); - - let mut gpio_data = read_volatile(GPIO_E_H_DATA); - let mut sgpio_data = read_volatile(SGPIOM_WRITE_LATCH_A_D); - if select_mux1 { - gpio_data |= GPIO_E8; - sgpio_data |= SGPIOM_A_D_2; - } else { - gpio_data &= !GPIO_E8; - sgpio_data &= !SGPIOM_A_D_2; - } - write_volatile(GPIO_E_H_DATA, gpio_data); - write_volatile(GPIO_E_H_DIR, read_volatile(GPIO_E_H_DIR) | GPIO_E8); - write_volatile(SGPIOM_DATA_A_D, sgpio_data); - } + // SAFETY: Board initialization has exclusive access to the PAC singletons. + let gpio = unsafe { &*device::Gpio::ptr() }; + let sgpio = unsafe { &*device::Sgpiom::ptr() }; + let _scu_unlocked = unsafe { ScuRegisters::new_global_unlocked() }; + let scu = unsafe { &*device::Scu::ptr() }; + + scu.scu41c().modify(|_, w| { + w.enbl_sgpiomaster_ckfn_pin() + .set_bit() + .enbl_sgpiomaster_ldfn_pin() + .set_bit() + .enbl_sgpiomaster_dofn_pin() + .set_bit() + .enbl_sgpiomaster_difn_pin() + .set_bit() + }); + sgpio.gpio554().modify(|_, w| unsafe { + w.enbl_of_serial_gpio() + .set_bit() + .numbers_of_serial_gpiopins() + .bits(16) + .serial_gpioclk_division() + .bits(24) + }); + + gpio.gpio020().modify(|r, w| unsafe { + let bits = if select_mux1 { + r.bits() | GPIO_E8 + } else { + r.bits() & !GPIO_E8 + }; + w.bits(bits) + }); + gpio.gpio024() + .modify(|r, w| unsafe { w.bits(r.bits() | GPIO_E8) }); + + let sgpio_latch = sgpio.gpio570().read().bits(); + let sgpio_data = if select_mux1 { + sgpio_latch | SGPIOM_A_D_2 + } else { + sgpio_latch & !SGPIOM_A_D_2 + }; + sgpio.gpio500().write(|w| unsafe { w.bits(sgpio_data) }); // Match the overlay's ext-mux-sel-delay-us = <1000>. for _ in 0..100_000 { From 8d74830c41a0976e3820ed93fae55b59f1c1d0c9 Mon Sep 17 00:00:00 2001 From: HaiLin Wu Date: Tue, 9 Jun 2026 16:37:48 -0700 Subject: [PATCH 04/13] The SPIMonitor driver now supports basic functionalities. It is ready for basic hardware testing, but not production-complete. Region filtering, permanent policy locking, and board-level external-mux GPIO switching/delay still need implementation. --- target/ast10x0/board/src/lib.rs | 4 +- target/ast10x0/board/src/spim_wiring.rs | 23 ++- target/ast10x0/peripherals/BUILD.bazel | 10 +- .../peripherals/spimonitor/commands.rs | 139 +++++++++++++++++ .../peripherals/spimonitor/controller.rs | 44 ++++-- target/ast10x0/peripherals/spimonitor/mod.rs | 2 + .../peripherals/spimonitor/registers.rs | 36 ++--- .../ast10x0/peripherals/spimonitor/types.rs | 2 + target/ast10x0/tests/spimonitor/BUILD.bazel | 75 +++++++++ target/ast10x0/tests/spimonitor/system.json5 | 17 +++ target/ast10x0/tests/spimonitor/target.rs | 144 ++++++++++++++++++ 11 files changed, 458 insertions(+), 38 deletions(-) create mode 100644 target/ast10x0/peripherals/spimonitor/commands.rs create mode 100644 target/ast10x0/tests/spimonitor/BUILD.bazel create mode 100644 target/ast10x0/tests/spimonitor/system.json5 create mode 100644 target/ast10x0/tests/spimonitor/target.rs diff --git a/target/ast10x0/board/src/lib.rs b/target/ast10x0/board/src/lib.rs index e12c05d7..3c675691 100644 --- a/target/ast10x0/board/src/lib.rs +++ b/target/ast10x0/board/src/lib.rs @@ -18,7 +18,9 @@ pub mod monitor; pub mod spim_wiring; pub use monitor::Ast1060Monitor; -pub use spim_wiring::{apply_spim_wiring, presets, SpimWiring, SpimWiringError}; +pub use spim_wiring::{ + apply_spim_pinctrl, apply_spim_wiring, presets, SpimWiring, SpimWiringError, +}; pub use ast10x0_peripherals::i2c::{I2cConfig, I2cError}; diff --git a/target/ast10x0/board/src/spim_wiring.rs b/target/ast10x0/board/src/spim_wiring.rs index d33289db..8768562c 100644 --- a/target/ast10x0/board/src/spim_wiring.rs +++ b/target/ast10x0/board/src/spim_wiring.rs @@ -11,6 +11,10 @@ //! "configure early, validate, lock, and operate under that locked policy." use ast10x0_peripherals::scu::{ + pinctrl::{ + PINCTRL_SPIM1_DEFAULT, PINCTRL_SPIM2_DEFAULT, PINCTRL_SPIM3_DEFAULT, + PINCTRL_SPIM4_DEFAULT, + }, ScuError, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource, }; @@ -92,10 +96,24 @@ impl From for SpimWiringError { } } +/// Apply the default SCU pinctrl group for a SPI monitor instance. +/// +/// The instance numbering follows the hardware SPIPF blocks: `Spim0` uses +/// the device-tree `spim1` pins, through `Spim3` using the `spim4` pins. +pub fn apply_spim_pinctrl(scu: &ScuRegisters, instance: SpiMonitorInstance) { + let group = match instance { + SpiMonitorInstance::Spim0 => PINCTRL_SPIM1_DEFAULT, + SpiMonitorInstance::Spim1 => PINCTRL_SPIM2_DEFAULT, + SpiMonitorInstance::Spim2 => PINCTRL_SPIM3_DEFAULT, + SpiMonitorInstance::Spim3 => PINCTRL_SPIM4_DEFAULT, + }; + scu.apply_pinctrl_group(group); +} + /// Apply static SPIM wiring at controller-init time. /// -/// Order: validate → SCU route → passthrough → ext-mux → MISO multi-func → -/// SPIPF policy → SPIPF lock. The lock is one-way; an empty +/// Order: validate → pinctrl → SCU route → passthrough → ext-mux → +/// MISO multi-func → SPIPF policy → SPIPF lock. The lock is one-way; an empty /// `MonitorPolicy::empty()` combined with lock will brick the SPI bus until /// reset, so callers should pass a vetted preset (see [`presets`]). /// @@ -111,6 +129,7 @@ pub unsafe fn apply_spim_wiring( validate_controller_for_source(controller_id, wiring.source)?; scu.validate_spim_instance(wiring.instance)?; + apply_spim_pinctrl(scu, wiring.instance); scu.set_spim_internal_master_route(wiring.instance, wiring.source); scu.set_spim_passthrough(wiring.instance, wiring.passthrough); scu.set_spim_ext_mux(wiring.instance, wiring.ext_mux); diff --git a/target/ast10x0/peripherals/BUILD.bazel b/target/ast10x0/peripherals/BUILD.bazel index e7318f26..509b5d54 100644 --- a/target/ast10x0/peripherals/BUILD.bazel +++ b/target/ast10x0/peripherals/BUILD.bazel @@ -1,7 +1,7 @@ # Licensed under the Apache-2.0 license # SPDX-License-Identifier: Apache-2.0 -load("@rules_rust//rust:defs.bzl", "rust_library") +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") load("//target/ast10x0:defs.bzl", "TARGET_COMPATIBLE_WITH") package(default_visibility = ["//visibility:public"]) @@ -52,6 +52,7 @@ rust_library( "smc/spi/spi_transaction.rs", "smc/types.rs", "spimonitor/controller.rs", + "spimonitor/commands.rs", "spimonitor/mod.rs", "spimonitor/policy.rs", "spimonitor/profile.rs", @@ -79,3 +80,10 @@ rust_library( "@rust_crates//:nb", ], ) + +rust_test( + name = "spimonitor_commands_test", + srcs = ["spimonitor/commands.rs"], + crate_name = "spimonitor_commands_test", + edition = "2024", +) diff --git a/target/ast10x0/peripherals/spimonitor/commands.rs b/target/ast10x0/peripherals/spimonitor/commands.rs new file mode 100644 index 00000000..3f528d99 --- /dev/null +++ b/target/ast10x0/peripherals/spimonitor/commands.rs @@ -0,0 +1,139 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Hardware command-table descriptors for the AST10x0 SPI monitor. + +/// A decoded command descriptor before the valid/lock state is applied. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct CommandDescriptor { + pub opcode: u8, + pub generic: bool, + pub write: bool, + pub read: bool, + pub memory: bool, + pub data_width: u8, + pub dummy_cycles: u8, + pub program_size: u8, + pub address_len: u8, + pub address_width: u8, +} + +impl CommandDescriptor { + #[must_use] + pub const fn encode(self) -> u32 { + ((self.generic as u32) << 29) + | ((self.write as u32) << 28) + | ((self.read as u32) << 27) + | ((self.memory as u32) << 26) + | ((self.data_width as u32) << 24) + | ((self.dummy_cycles as u32) << 16) + | ((self.program_size as u32) << 13) + | ((self.address_len as u32) << 10) + | ((self.address_width as u32) << 8) + | self.opcode as u32 + } +} + +const fn command( + opcode: u8, + generic: bool, + write: bool, + read: bool, + memory: bool, + data_width: u8, + dummy_cycles: u8, + program_size: u8, + address_len: u8, + address_width: u8, +) -> CommandDescriptor { + CommandDescriptor { + opcode, + generic, + write, + read, + memory, + data_width, + dummy_cycles, + program_size, + address_len, + address_width, + } +} + +/// Return the descriptor used by the Zephyr AST10x0 SPI monitor driver. +#[must_use] +pub const fn descriptor(opcode: u8) -> Option { + let entry = match opcode { + 0x03 => command(opcode, true, false, true, true, 1, 0, 0, 3, 1), + 0x13 => command(opcode, true, false, true, true, 1, 0, 0, 4, 1), + 0x0b => command(opcode, true, false, true, true, 1, 8, 0, 3, 1), + 0x0c => command(opcode, true, false, true, true, 1, 8, 0, 4, 1), + 0x3b => command(opcode, true, false, true, true, 2, 8, 0, 3, 1), + 0x3c => command(opcode, true, false, true, true, 2, 8, 0, 4, 1), + 0xbb => command(opcode, true, false, true, true, 2, 4, 0, 3, 2), + 0xbc => command(opcode, true, false, true, true, 2, 4, 0, 4, 2), + 0x6b => command(opcode, true, false, true, true, 3, 8, 0, 3, 1), + 0x6c => command(opcode, true, false, true, true, 3, 8, 0, 4, 1), + 0xeb => command(opcode, true, false, true, true, 3, 6, 0, 3, 3), + 0xec => command(opcode, true, false, true, true, 3, 6, 0, 4, 3), + 0x02 => command(opcode, true, true, false, true, 1, 0, 1, 3, 1), + 0x12 => command(opcode, true, true, false, true, 1, 0, 1, 4, 1), + 0x32 => command(opcode, true, true, false, true, 3, 0, 1, 3, 1), + 0x34 => command(opcode, true, true, false, true, 3, 0, 1, 4, 1), + 0x20 => command(opcode, true, true, false, true, 0, 0, 1, 3, 1), + 0x21 => command(opcode, true, true, false, true, 0, 0, 1, 4, 1), + 0xd8 => command(opcode, true, true, false, true, 0, 0, 5, 3, 1), + 0xdc => command(opcode, true, true, false, true, 0, 0, 5, 4, 1), + 0x06 | 0x04 | 0x50 | 0x66 | 0x99 => { + command(opcode, true, false, false, false, 0, 0, 0, 0, 0) + } + 0x05 | 0x35 | 0x15 | 0x70 | 0x9f => { + command(opcode, true, false, true, false, 1, 0, 0, 0, 0) + } + 0x01 | 0x31 => command(opcode, true, true, false, false, 1, 0, 0, 0, 0), + 0x5a => command(opcode, true, false, true, false, 1, 8, 0, 3, 1), + 0xb7 | 0xe9 => command(opcode, false, false, false, false, 0, 0, 0, 0, 0), + 0xc5 => command(opcode, false, true, false, false, 1, 0, 0, 0, 0), + _ => return None, + }; + Some(entry) +} + +pub const VALID: u32 = 1 << 30; +pub const VALID_ONCE: u32 = 1 << 31; +pub const LOCKED: u32 = 1 << 23; + +#[must_use] +pub const fn table_value(opcode: u8, valid_once: bool) -> Option { + match descriptor(opcode) { + Some(entry) => Some(entry.encode() | if valid_once { VALID_ONCE } else { VALID }), + None => None, + } +} + +#[must_use] +pub const fn fixed_slot(opcode: u8) -> Option { + match opcode { + 0xb7 => Some(0), + 0xe9 => Some(1), + 0xc5 => Some(31), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::{fixed_slot, table_value}; + + #[test] + fn fast_read_4b_matches_zephyr_encoding() { + assert_eq!(table_value(0x0c, false), Some(0x6d08_110c)); + } + + #[test] + fn reserved_commands_have_fixed_slots() { + assert_eq!(fixed_slot(0xb7), Some(0)); + assert_eq!(fixed_slot(0xe9), Some(1)); + assert_eq!(fixed_slot(0xc5), Some(31)); + } +} diff --git a/target/ast10x0/peripherals/spimonitor/controller.rs b/target/ast10x0/peripherals/spimonitor/controller.rs index 39b1a1ee..7b7e9e55 100644 --- a/target/ast10x0/peripherals/spimonitor/controller.rs +++ b/target/ast10x0/peripherals/spimonitor/controller.rs @@ -7,6 +7,7 @@ use core::marker::PhantomData; use crate::scu::registers::ScuRegisters; use crate::scu::types::{ScuExtMuxSelect, SpiMonitorInstance}; +use crate::spimonitor::commands::{fixed_slot, table_value}; use crate::spimonitor::policy::{MonitorPolicy, MAX_REGION_SLOTS}; use crate::spimonitor::registers::{SpiMonitorController, SpiMonitorRegisters}; use crate::spimonitor::types::{ @@ -100,10 +101,26 @@ impl SpiMonitor { return Err(SpiMonitorError::InvalidRegion); } - // Program command allow-list table. + let mut next_slot = 2usize; + + // Slots 0 and 1 are reserved for EN4B and EX4B; slot 31 is reserved + // for WREAR. Other commands occupy slots 2 through 30. for i in 0..policy.allow_command_count { - let cmd = policy.allow_commands[i] as u32; - self.regs.write_allow_cmd_slot(i, cmd); + let opcode = policy.allow_commands[i]; + let value = + table_value(opcode, false).ok_or(SpiMonitorError::UnsupportedCommand(opcode))?; + let slot = match fixed_slot(opcode) { + Some(slot) => slot, + None => { + if next_slot >= 31 { + return Err(SpiMonitorError::NoCommandSlot); + } + let slot = next_slot; + next_slot += 1; + slot + } + }; + self.regs.write_allow_cmd_slot(slot, value); } // Program address filter table. @@ -160,8 +177,10 @@ impl SpiMonitor { /// Mirrors Zephyr's `spim_passthrough_config`. pub fn set_passthrough(&self, mode: PassthroughMode) { self.regs.modify_ctrl(|bits| match mode { - PassthroughMode::Enabled => *bits |= CTRL_PASSTHROUGH_BIT, - PassthroughMode::Disabled => *bits &= !CTRL_PASSTHROUGH_BIT, + PassthroughMode::Enabled => { + *bits = (*bits & !CTRL_PASSTHROUGH_MASK) | CTRL_SINGLE_PASSTHROUGH_BIT + } + PassthroughMode::Disabled => *bits &= !CTRL_PASSTHROUGH_MASK, }); } @@ -249,8 +268,10 @@ impl SpiMonitor { /// during mux ownership transitions at runtime (e.g., BMC boot-hold/release). pub fn set_passthrough(&self, mode: PassthroughMode) { self.regs.modify_ctrl(|bits| match mode { - PassthroughMode::Enabled => *bits |= CTRL_PASSTHROUGH_BIT, - PassthroughMode::Disabled => *bits &= !CTRL_PASSTHROUGH_BIT, + PassthroughMode::Enabled => { + *bits = (*bits & !CTRL_PASSTHROUGH_MASK) | CTRL_SINGLE_PASSTHROUGH_BIT + } + PassthroughMode::Disabled => *bits &= !CTRL_PASSTHROUGH_MASK, }); } @@ -329,12 +350,11 @@ impl SpiMonitor { /// /// 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 +const CTRL_SINGLE_PASSTHROUGH_BIT: u32 = 1 << 0; +const CTRL_PASSTHROUGH_MASK: u32 = (1 << 0) | (1 << 1); +const CTRL_MONITOR_ENABLE_BIT: u32 = 1 << 2; #[allow(dead_code)] -const CTRL_EXT_MUX_SEL_BIT: u32 = 1 << 2; // PLACEHOLDER - NOT in SPIPF000! See note below. +const CTRL_SW_RESET_BIT: u32 = 1 << 15; #[allow(dead_code)] const CTRL_LOCK_BIT: u32 = 1 << 31; // PLACEHOLDER - NOT in SPIPF000! See note below. // diff --git a/target/ast10x0/peripherals/spimonitor/mod.rs b/target/ast10x0/peripherals/spimonitor/mod.rs index 8f649e14..91e55400 100644 --- a/target/ast10x0/peripherals/spimonitor/mod.rs +++ b/target/ast10x0/peripherals/spimonitor/mod.rs @@ -3,6 +3,7 @@ //! AST10x0 SPI monitor (SPIPF) module. +pub mod commands; pub mod controller; pub mod policy; pub mod profile; @@ -14,6 +15,7 @@ pub use controller::{ Configured, ConfiguredSpiMonitor, Locked, LockedSpiMonitor, SpiMonitor, UninitSpiMonitor, Uninitialized, }; +pub use commands::{descriptor as command_descriptor, table_value as command_table_value}; pub use policy::{MonitorPolicy, MAX_CMD_SLOTS, MAX_REGION_SLOTS}; pub use registers::{ SpiMonitorController, SpiMonitorRegisters, SPIPF1_BASE, SPIPF2_BASE, SPIPF3_BASE, SPIPF4_BASE, diff --git a/target/ast10x0/peripherals/spimonitor/registers.rs b/target/ast10x0/peripherals/spimonitor/registers.rs index 3fcf02e7..b0a62522 100644 --- a/target/ast10x0/peripherals/spimonitor/registers.rs +++ b/target/ast10x0/peripherals/spimonitor/registers.rs @@ -118,6 +118,17 @@ impl SpiMonitorRegisters { self.regs().spipf004().write(|w| unsafe { w.bits(value) }); } + pub fn modify_ctrl2(&self, f: F) + where + F: FnOnce(&mut u32), + { + self.regs().spipf004().modify(|r, w| { + let mut bits = r.bits(); + f(&mut bits); + unsafe { w.bits(bits) } + }); + } + /// SPIPF07C: Lock/status register. pub fn read_lock_status(&self) -> u32 { self.regs().spipf07c().read().bits() @@ -151,43 +162,24 @@ impl SpiMonitorRegisters { // ----------------------------------------------------------------------- // Violation log registers - // - // TODO: confirm SPIPF register offsets for log control from the AST10x0 - // datasheet once available. Offsets below are placeholders consistent with - // known Aspeed SPIPF register map patterns. // ----------------------------------------------------------------------- /// Current violation log write index (number of entries written so far). /// - /// Maps to the SPIPF log index register (placeholder offset 0x080). pub fn read_log_idx_reg(&self) -> u32 { - // SAFETY: raw offset read within the known SPIPF register block page. - unsafe { - let ptr = (self.base as *const u8).add(0x080) as *const u32; - core::ptr::read_volatile(ptr) - } + self.regs().spipf018().read().bits() } /// Maximum violation log capacity in bytes. /// - /// Maps to the SPIPF log size register (placeholder offset 0x084). pub fn read_log_max_sz(&self) -> u32 { - // SAFETY: same as above. - unsafe { - let ptr = (self.base as *const u8).add(0x084) as *const u32; - core::ptr::read_volatile(ptr) - } + self.regs().spipf014().read().bits() & 0x0007_ffff } /// Base address of the violation log RAM region. /// /// Returns a `usize` suitable for casting to `*const u32` by the caller. - /// Maps to the SPIPF log RAM address register (placeholder offset 0x088). pub fn log_ram_base_addr(&self) -> usize { - // SAFETY: same as above. - unsafe { - let ptr = (self.base as *const u8).add(0x088) as *const u32; - core::ptr::read_volatile(ptr) as usize - } + (self.regs().spipf010().read().bits() & !0x3) as usize } } diff --git a/target/ast10x0/peripherals/spimonitor/types.rs b/target/ast10x0/peripherals/spimonitor/types.rs index 4be2d823..64714075 100644 --- a/target/ast10x0/peripherals/spimonitor/types.rs +++ b/target/ast10x0/peripherals/spimonitor/types.rs @@ -128,6 +128,8 @@ impl ViolationLogEntry { pub enum SpiMonitorError { InvalidRegion, InvalidSlot, + UnsupportedCommand(u8), + NoCommandSlot, Locked, InvalidTransition, } diff --git a/target/ast10x0/tests/spimonitor/BUILD.bazel b/target/ast10x0/tests/spimonitor/BUILD.bazel new file mode 100644 index 00000000..5d961b1f --- /dev/null +++ b/target/ast10x0/tests/spimonitor/BUILD.bazel @@ -0,0 +1,75 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image", "system_image_test") +load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen") +load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script") +load("@pigweed//pw_kernel/tooling/panic_detector:rust_binary_no_panics_test.bzl", "rust_binary_no_panics_test") +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("//target/ast10x0:defs.bzl", "TARGET_COMPATIBLE_WITH") + +filegroup( + name = "system_config", + srcs = ["system.json5"], +) + +target_codegen( + name = "codegen", + arch = "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", + system_config = ":system_config", + target_compatible_with = TARGET_COMPATIBLE_WITH, +) + +target_linker_script( + name = "linker_script", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + template = "//target/ast10x0:linker_script_template", +) + +rust_binary( + name = "target", + srcs = ["target.rs"], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/ast10x0:entry", + "//target/ast10x0/board:ast10x0_board", + "//target/ast10x0/peripherals", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_log/rust:pw_log", + ], +) + +system_image( + name = "spimonitor_test", + kernel = ":target", + platform = "//target/ast10x0", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + userspace = False, + visibility = ["//visibility:public"], +) + +system_image_test( + name = "spimonitor_evb_test", + image = ":spimonitor_test", + tags = ["hardware"], + target_compatible_with = select({ + "//target/ast10x0:qemu_enabled": ["@platforms//:incompatible"], + "//conditions:default": [], + }), + visibility = ["//visibility:public"], +) + +rust_binary_no_panics_test( + name = "no_panics_test", + binary = ":spimonitor_test", + tags = ["kernel"], +) diff --git a/target/ast10x0/tests/spimonitor/system.json5 b/target/ast10x0/tests/spimonitor/system.json5 new file mode 100644 index 00000000..669c9c50 --- /dev/null +++ b/target/ast10x0/tests/spimonitor/system.json5 @@ -0,0 +1,17 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +// AST10x0 kernel-only SPI monitor smoke test configuration. +{ + arch: { + type: "armv7m", + vector_table_start_address: 0x00000000, + vector_table_size_bytes: 1280, + }, + kernel: { + flash_start_address: 0x00000500, + flash_size_bytes: 262144, + ram_start_address: 0x00040500, + ram_size_bytes: 393216, + }, +} diff --git a/target/ast10x0/tests/spimonitor/target.rs b/target/ast10x0/tests/spimonitor/target.rs new file mode 100644 index 00000000..f91d6bee --- /dev/null +++ b/target/ast10x0/tests/spimonitor/target.rs @@ -0,0 +1,144 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! AST10x0 SPI monitor register and routing smoke test. + +#![no_std] +#![no_main] + +use ast10x0_board::apply_spim_pinctrl; +use ast10x0_peripherals::scu::{ScuRegisters, SpiMonitorInstance}; +use ast10x0_peripherals::spimonitor::{ + command_table_value, ExtMuxSel, MonitorPolicy, MonitorState, PassthroughMode, SpiMonitor, + SpiMonitorController, SpiMonitorError, Uninitialized, +}; +use console_backend::console_backend_write_all; +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +pub struct Target {} + +const CTRL_MONITOR_ENABLE: u32 = 1 << 2; +const CTRL_SINGLE_BIT_PASSTHROUGH: u32 = 1 << 0; +const TEST_COMMANDS: [u8; 3] = [0x9f, 0x05, 0x06]; +const FIRST_GENERAL_COMMAND_SLOT: usize = 2; + +fn check_register(actual: u32, expected: u32) -> Result<(), SpiMonitorError> { + if actual != expected { + pw_log::info!( + "register mismatch: expected=0x{:08x}, actual=0x{:08x}", + expected as u32, + actual as u32 + ); + return Err(SpiMonitorError::InvalidTransition); + } + Ok(()) +} + +fn run_spimonitor_test() -> Result<(), SpiMonitorError> { + pw_log::info!("=== AST10x0 SPI monitor smoke test ==="); + + // Match the device-tree pinctrl-0 setup for spim1/SPIPF1. + let scu = unsafe { ScuRegisters::new_global_unlocked() }; + apply_spim_pinctrl(&scu, SpiMonitorInstance::Spim0); + + // This target owns SPIM0/SPIPF1 for its complete lifetime. + let monitor = unsafe { + SpiMonitor::::new(SpiMonitorController::Spim0) + }; + if monitor.state() != MonitorState::Uninitialized { + return Err(SpiMonitorError::InvalidTransition); + } + + let original_ctrl = monitor.regs().read_ctrl(); + let original_slots = [ + monitor.regs().read_allow_cmd_slot(2), + monitor.regs().read_allow_cmd_slot(3), + monitor.regs().read_allow_cmd_slot(4), + ]; + + let mut policy = MonitorPolicy::empty(); + policy.allow_commands[..TEST_COMMANDS.len()].copy_from_slice(&TEST_COMMANDS); + policy.allow_command_count = TEST_COMMANDS.len(); + + let configured = monitor.apply_policy(&policy)?; + if configured.state() != MonitorState::Configured { + return Err(SpiMonitorError::InvalidTransition); + } + + let original_mux = configured.get_ext_mux(); + let result = (|| { + for (index, command) in TEST_COMMANDS.iter().copied().enumerate() { + let expected = + command_table_value(command, false).ok_or(SpiMonitorError::UnsupportedCommand( + command, + ))?; + check_register( + configured + .regs() + .read_allow_cmd_slot(FIRST_GENERAL_COMMAND_SLOT + index), + expected, + )?; + } + pw_log::info!("command table readback passed"); + + configured.enable(); + check_register( + configured.regs().read_ctrl() & CTRL_MONITOR_ENABLE, + CTRL_MONITOR_ENABLE, + )?; + + configured.set_passthrough(PassthroughMode::Enabled); + check_register( + configured.regs().read_ctrl() & CTRL_SINGLE_BIT_PASSTHROUGH, + CTRL_SINGLE_BIT_PASSTHROUGH, + )?; + + configured.set_passthrough(PassthroughMode::Disabled); + check_register( + configured.regs().read_ctrl() & CTRL_SINGLE_BIT_PASSTHROUGH, + 0, + )?; + + let test_mux = match original_mux { + ExtMuxSel::Sel0 => ExtMuxSel::Sel1, + ExtMuxSel::Sel1 => ExtMuxSel::Sel0, + }; + configured.set_ext_mux(test_mux); + if configured.get_ext_mux() != test_mux { + pw_log::info!("external mux readback failed"); + return Err(SpiMonitorError::InvalidTransition); + } + pw_log::info!("control and external mux readback passed"); + Ok(()) + })(); + + // Restore every register changed by this smoke test. + configured.set_ext_mux(original_mux); + configured.regs().write_ctrl(original_ctrl); + for (index, value) in original_slots.iter().copied().enumerate() { + configured + .regs() + .write_allow_cmd_slot(FIRST_GENERAL_COMMAND_SLOT + index, value); + } + + result +} + +impl TargetInterface for Target { + const NAME: &'static str = "AST10x0 SPI Monitor Smoke Test"; + + fn main() -> ! { + let sentinel = if run_spimonitor_test().is_ok() { + b"TEST_RESULT:PASS\n" + } else { + b"TEST_RESULT:FAIL\n" + }; + let _ = console_backend_write_all(sentinel); + + #[expect(clippy::empty_loop)] + loop {} + } +} + +declare_target!(Target); From eeed39843fdd3a00bea91f6f517fa34d88b1e55b Mon Sep 17 00:00:00 2001 From: hailin wu Date: Wed, 10 Jun 2026 10:58:30 -0700 Subject: [PATCH 05/13] add configuration of external-mux GPIOs and Implement policy locking --- target/ast10x0/board/BUILD.bazel | 1 + target/ast10x0/board/src/lib.rs | 5 +- target/ast10x0/board/src/spim_wiring.rs | 105 +++++- target/ast10x0/peripherals/scu/routing.rs | 35 ++ .../peripherals/spimonitor/commands.rs | 6 + .../peripherals/spimonitor/controller.rs | 324 ++++++++++++++---- .../ast10x0/peripherals/spimonitor/profile.rs | 13 + .../peripherals/spimonitor/registers.rs | 45 ++- .../ast10x0/peripherals/spimonitor/types.rs | 5 + 9 files changed, 460 insertions(+), 79 deletions(-) diff --git a/target/ast10x0/board/BUILD.bazel b/target/ast10x0/board/BUILD.bazel index e5baf596..34075dad 100644 --- a/target/ast10x0/board/BUILD.bazel +++ b/target/ast10x0/board/BUILD.bazel @@ -18,5 +18,6 @@ rust_library( 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 3c675691..2b360bcf 100644 --- a/target/ast10x0/board/src/lib.rs +++ b/target/ast10x0/board/src/lib.rs @@ -19,7 +19,8 @@ pub mod spim_wiring; 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}; @@ -117,7 +118,7 @@ impl Ast10x0Board { /// 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 8768562c..ed247fd7 100644 --- a/target/ast10x0/board/src/spim_wiring.rs +++ b/target/ast10x0/board/src/spim_wiring.rs @@ -20,9 +20,10 @@ use ast10x0_peripherals::scu::{ }; 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 @@ pub fn apply_spim_pinctrl(scu: &ScuRegisters, instance: SpiMonitorInstance) { 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 → @@ -125,15 +183,34 @@ pub unsafe fn apply_spim_wiring( controller_id: SmcController, wiring: SpimWiring, policy: &MonitorPolicy, +) -> Result { + 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 { 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 @@ pub unsafe fn apply_spim_wiring( // instance, mirroring the SCU exclusivity required above. let monitor = unsafe { SpiMonitor::::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 @@ fn validate_controller_for_source( /// 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 @@ pub mod presets { 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 2ebad91d..b99d1bd7 100644 --- a/target/ast10x0/peripherals/scu/routing.rs +++ b/target/ast10x0/peripherals/scu/routing.rs @@ -197,6 +197,41 @@ impl ScuRegisters { }); } + /// 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 3f528d99..034b7a35 100644 --- a/target/ast10x0/peripherals/spimonitor/commands.rs +++ b/target/ast10x0/peripherals/spimonitor/commands.rs @@ -94,6 +94,7 @@ pub const fn descriptor(opcode: u8) -> Option { 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 @@ mod tests { 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 7b7e9e55..123f1dea 100644 --- a/target/ast10x0/peripherals/spimonitor/controller.rs +++ b/target/ast10x0/peripherals/spimonitor/controller.rs @@ -7,7 +7,7 @@ use core::marker::PhantomData; 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::{ @@ -37,38 +37,6 @@ pub type ConfiguredSpiMonitor = SpiMonitor; /// Ergonomic alias for a locked SPI monitor handle. pub type LockedSpiMonitor = SpiMonitor; -// --------------------------------------------------------------------------- -// 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 @@ impl SpiMonitor { 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 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)?; + } - let mut next_slot = 2usize; + 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 @@ impl SpiMonitor { 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 @@ impl SpiMonitor { } }; 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 @@ impl SpiMonitor { 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 @@ impl SpiMonitor { 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 @@ impl SpiMonitor { /// - Lock all command table entries /// - Write-disable SPIPF000, SPIPF004, SPIPF010, SPIPF014 pub fn lock(self) -> Result> { - // 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 { // --------------------------------------------------------------------------- impl SpiMonitor { - /// 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_PASSTHROUGH_MASK: u32 = (1 << 0) | (1 << 1); const CTRL_MONITOR_ENABLE_BIT: u32 = 1 << 2; #[allow(dead_code)] const CTRL_SW_RESET_BIT: u32 = 1 << 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 @@ fn drain_log_impl<'a>( 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::(); + 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 4e498dfe..dd6290eb 100644 --- a/target/ast10x0/peripherals/spimonitor/profile.rs +++ b/target/ast10x0/peripherals/spimonitor/profile.rs @@ -30,3 +30,16 @@ pub const fn firmware_update_window() -> MonitorPolicy { 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 b0a62522..a44e3086 100644 --- a/target/ast10x0/peripherals/spimonitor/registers.rs +++ b/target/ast10x0/peripherals/spimonitor/registers.rs @@ -138,6 +138,17 @@ impl SpiMonitorRegisters { self.regs().spipf07c().write(|w| unsafe { w.bits(value) }); } + pub fn modify_lock_status(&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 @@ impl SpiMonitorRegisters { // 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 64714075..3be10bf2 100644 --- a/target/ast10x0/peripherals/spimonitor/types.rs +++ b/target/ast10x0/peripherals/spimonitor/types.rs @@ -127,10 +127,15 @@ impl ViolationLogEntry { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum SpiMonitorError { InvalidRegion, + InvalidAddress, + InvalidLength, InvalidSlot, + InvalidLogBuffer, UnsupportedCommand(u8), NoCommandSlot, Locked, + LockFailed, + VerificationFailed, InvalidTransition, } From d66ddb28601a0148aae2fe7a50cb8f96cfac6782 Mon Sep 17 00:00:00 2001 From: hailin wu Date: Wed, 10 Jun 2026 12:56:03 -0700 Subject: [PATCH 06/13] add spimonitor test. Spimonitor is not monitoring the traffic from spi controller. Spi monitor is only monitoring the traffic from either BMC or host. --- target/ast10x0/board/src/lib.rs | 2 +- target/ast10x0/board/src/spim_wiring.rs | 72 +++- .../peripherals/spimonitor/commands.rs | 12 + .../peripherals/spimonitor/controller.rs | 127 ++++++- .../ast10x0/peripherals/spimonitor/types.rs | 3 + target/ast10x0/tests/spimonitor/target.rs | 330 ++++++++++++------ 6 files changed, 433 insertions(+), 113 deletions(-) diff --git a/target/ast10x0/board/src/lib.rs b/target/ast10x0/board/src/lib.rs index 2b360bcf..61444397 100644 --- a/target/ast10x0/board/src/lib.rs +++ b/target/ast10x0/board/src/lib.rs @@ -20,7 +20,7 @@ pub mod spim_wiring; pub use monitor::Ast1060Monitor; pub use spim_wiring::{ apply_spim_external_mux, apply_spim_pinctrl, apply_spim_wiring, apply_spim_wiring_with_log, - presets, SpimWiring, SpimWiringError, + presets, spim_external_mux_state, SpimWiring, SpimWiringError, }; pub use ast10x0_peripherals::i2c::{I2cConfig, I2cError}; diff --git a/target/ast10x0/board/src/spim_wiring.rs b/target/ast10x0/board/src/spim_wiring.rs index ed247fd7..13489170 100644 --- a/target/ast10x0/board/src/spim_wiring.rs +++ b/target/ast10x0/board/src/spim_wiring.rs @@ -4,8 +4,8 @@ //! Static SPI-monitor wiring for AST10x0 boards. //! //! 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: +//! typestate to apply once-per-process external-monitor 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." @@ -25,16 +25,15 @@ use ast10x0_peripherals::spimonitor::{ }; use ast1060_pac as device; -/// Static SPIM wiring for one SPI controller. +/// Static wiring for one external SPI monitor path. /// -/// Captures the four SCU0F0 fields plus the MISO multi-function pin choice -/// that together determine which monitor instance a given SPI master is -/// routed through. +/// The `source` identifies the external flash bus associated with this policy. +/// It does not enable the AST1060 internal SPI-master detour. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct SpimWiring { - /// Monitor instance the master is routed through. + /// Monitor instance connected to the external bus. pub instance: SpiMonitorInstance, - /// Which SPI master is being routed. + /// External SPI bus associated with the monitor policy. pub source: SpiMonitorSource, /// Passthrough enable for the chosen instance. pub passthrough: SpiMonitorPassthrough, @@ -127,6 +126,17 @@ pub fn apply_spim_external_mux(instance: SpiMonitorInstance, mux: ScuExtMuxSelec }; let gpio = unsafe { &*device::Gpio::ptr() }; + let scu = unsafe { &*device::Scu::ptr() }; + scu.scu41c().modify(|_, w| { + w.enbl_sgpiomaster_ckfn_pin() + .set_bit() + .enbl_sgpiomaster_ldfn_pin() + .set_bit() + .enbl_sgpiomaster_dofn_pin() + .set_bit() + .enbl_sgpiomaster_difn_pin() + .set_bit() + }); match gpio_group { ExternalMuxGpioGroup::Abcd => { gpio.gpio000().modify(|r, w| unsafe { @@ -147,13 +157,51 @@ pub fn apply_spim_external_mux(instance: SpiMonitorInstance, mux: ScuExtMuxSelec } let sgpio = unsafe { &*device::Sgpiom::ptr() }; - sgpio.gpio500().modify(|r, w| unsafe { - w.bits(update_bit(r.bits(), sgpio_mask, high)) + sgpio.gpio554().modify(|_, w| unsafe { + w.enbl_of_serial_gpio() + .set_bit() + .numbers_of_serial_gpiopins() + .bits(16) + .serial_gpioclk_division() + .bits(24) }); + let latch = sgpio.gpio570().read().bits(); + sgpio + .gpio500() + .write(|w| unsafe { w.bits(update_bit(latch, sgpio_mask, high)) }); crate::delay_us(1_000); } +/// Read back the two board-level external mux-select outputs. +#[must_use] +pub fn spim_external_mux_state( + instance: SpiMonitorInstance, +) -> Option { + 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() }; + let gpio_high = match gpio_group { + ExternalMuxGpioGroup::Abcd => gpio.gpio000().read().bits() & gpio_mask != 0, + ExternalMuxGpioGroup::Efgh => gpio.gpio020().read().bits() & gpio_mask != 0, + }; + let sgpio = unsafe { &*device::Sgpiom::ptr() }; + let sgpio_high = sgpio.gpio570().read().bits() & sgpio_mask != 0; + if gpio_high != sgpio_high { + None + } else if gpio_high { + Some(ScuExtMuxSelect::Mux1) + } else { + Some(ScuExtMuxSelect::Mux0) + } +} + #[derive(Clone, Copy)] enum ExternalMuxGpioGroup { Abcd, @@ -205,7 +253,9 @@ pub unsafe fn apply_spim_wiring_with_log( 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); + // SPIPF monitors the external BMC/host pins. Keep SCU0F0[3:0] clear so + // neither AST1060 internal SPI controller is detoured into the monitor. + scu.set_spim_internal_mux(wiring.source, 0)?; 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); diff --git a/target/ast10x0/peripherals/spimonitor/commands.rs b/target/ast10x0/peripherals/spimonitor/commands.rs index 034b7a35..40dbb867 100644 --- a/target/ast10x0/peripherals/spimonitor/commands.rs +++ b/target/ast10x0/peripherals/spimonitor/commands.rs @@ -142,4 +142,16 @@ mod tests { fn winbond_die_select_matches_zephyr_encoding() { assert_eq!(table_value(0xc2, false), Some(0x7100_00c2)); } + + #[test] + fn complete_zephyr_allow_list_is_supported() { + let 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, + ]; + for command in commands { + assert!(table_value(command, false).is_some()); + } + } } diff --git a/target/ast10x0/peripherals/spimonitor/controller.rs b/target/ast10x0/peripherals/spimonitor/controller.rs index 123f1dea..a6f80e90 100644 --- a/target/ast10x0/peripherals/spimonitor/controller.rs +++ b/target/ast10x0/peripherals/spimonitor/controller.rs @@ -178,6 +178,117 @@ impl SpiMonitor { // --------------------------------------------------------------------------- impl SpiMonitor { + /// Add or re-enable one command, matching the Zephyr shell `cmd add`. + pub fn add_command(&self, opcode: u8, valid_once: bool) -> Result { + let value = + table_value(opcode, valid_once).ok_or(SpiMonitorError::UnsupportedCommand(opcode))?; + + for slot in 0..COMMAND_TABLE_SLOTS { + let current = self.regs.read_allow_cmd_slot(slot); + if current & 0xff == u32::from(opcode) && current & COMMAND_LOCKED == 0 { + self.regs.write_allow_cmd_slot(slot, value); + return verify_command_slot(&self.regs, slot, value); + } + } + + let slot = if let Some(slot) = fixed_slot(opcode) { + let current = self.regs.read_allow_cmd_slot(slot); + if current != 0 && current & 0xff != u32::from(opcode) { + return Err(SpiMonitorError::NoCommandSlot); + } + slot + } else { + (FIRST_GENERAL_COMMAND_SLOT..COMMAND_TABLE_SLOTS) + .find(|slot| self.regs.read_allow_cmd_slot(*slot) == 0) + .ok_or(SpiMonitorError::NoCommandSlot)? + }; + if self.regs.read_allow_cmd_slot(slot) & COMMAND_LOCKED != 0 { + return Err(SpiMonitorError::Locked); + } + self.regs.write_allow_cmd_slot(slot, value); + verify_command_slot(&self.regs, slot, value) + } + + /// Disable every unlocked entry matching an opcode. + pub fn remove_command(&self, opcode: u8) -> Result { + let mut count = 0; + for slot in 0..COMMAND_TABLE_SLOTS { + let current = self.regs.read_allow_cmd_slot(slot); + if current & 0xff == u32::from(opcode) { + if current & COMMAND_LOCKED != 0 { + return Err(SpiMonitorError::Locked); + } + self.regs.write_allow_cmd_slot(slot, 0); + verify_command_slot(&self.regs, slot, 0)?; + count += 1; + } + } + if count == 0 { + return Err(SpiMonitorError::UnsupportedCommand(opcode)); + } + Ok(count) + } + + /// Lock every command-table entry matching an opcode. + pub fn lock_command(&self, opcode: u8) -> Result { + let mut count = 0; + for slot in 0..COMMAND_TABLE_SLOTS { + let current = self.regs.read_allow_cmd_slot(slot); + if current & 0xff == u32::from(opcode) { + let updated = current | COMMAND_LOCKED; + self.regs.write_allow_cmd_slot(slot, updated); + verify_command_slot(&self.regs, slot, updated)?; + count += 1; + } + } + if count == 0 { + return Err(SpiMonitorError::UnsupportedCommand(opcode)); + } + Ok(count) + } + + /// Update an address privilege region, matching the Zephyr shell + /// `addr read/write enable/disable` operations. + pub fn configure_region( + &self, + start: u32, + length: u32, + direction: PrivilegeDirection, + op: PrivilegeOp, + ) -> Result<()> { + let lock = self.regs.read_lock_status(); + let locked = match direction { + PrivilegeDirection::Read => lock & LOCK_READ_PRIVILEGE != 0, + PrivilegeDirection::Write => lock & LOCK_WRITE_PRIVILEGE != 0, + }; + if locked { + return Err(SpiMonitorError::Locked); + } + configure_privilege_region(&self.regs, start, length, direction, op) + } + + /// Read one word from the selected privilege bitmap. + pub fn privilege_word(&self, direction: PrivilegeDirection, index: usize) -> Result { + if index >= PRIVILEGE_TABLE_WORDS { + return Err(SpiMonitorError::InvalidSlot); + } + select_privilege_table(&self.regs, direction); + Ok(self.regs.read_addr_filter_slot(index)) + } + + /// Lock one privilege bitmap, matching the shell `addr lock read/write`. + pub fn lock_privilege_table(&self, direction: PrivilegeDirection) -> Result<()> { + let mask = match direction { + PrivilegeDirection::Read => LOCK_READ_PRIVILEGE, + PrivilegeDirection::Write => LOCK_WRITE_PRIVILEGE, + }; + self.regs.modify_lock_status(|bits| *bits |= mask); + if self.regs.read_lock_status() & mask != mask { + return Err(SpiMonitorError::LockFailed); + } + Ok(()) + } + /// Enable the monitor filter (SPIPF000 bit 0). /// /// Mirrors Zephyr's `spim_monitor_enable(dev, true)`. @@ -203,6 +314,9 @@ impl SpiMonitor { PassthroughMode::Enabled => { *bits = (*bits & !CTRL_PASSTHROUGH_MASK) | CTRL_SINGLE_PASSTHROUGH_BIT } + PassthroughMode::MultiEnabled => { + *bits = (*bits & !CTRL_PASSTHROUGH_MASK) | CTRL_MULTI_PASSTHROUGH_BIT + } PassthroughMode::Disabled => *bits &= !CTRL_PASSTHROUGH_MASK, }); } @@ -436,6 +550,7 @@ impl SpiMonitor { /// Confirmed from aspeed-rust implementation (src/spimonitor/hardware.rs). /// Register field names from ast1060_pac provide safe typed accessors. const CTRL_SINGLE_PASSTHROUGH_BIT: u32 = 1 << 0; +const CTRL_MULTI_PASSTHROUGH_BIT: u32 = 1 << 1; const CTRL_PASSTHROUGH_MASK: u32 = (1 << 0) | (1 << 1); const CTRL_MONITOR_ENABLE_BIT: u32 = 1 << 2; #[allow(dead_code)] @@ -445,7 +560,6 @@ 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; @@ -478,6 +592,17 @@ fn select_privilege_table(regs: &SpiMonitorRegisters, direction: PrivilegeDirect regs.modify_ctrl(|bits| *bits = (*bits & 0x00ff_ffff) | selection); } +fn verify_command_slot( + regs: &SpiMonitorRegisters, + slot: usize, + expected: u32, +) -> Result { + if regs.read_allow_cmd_slot(slot) != expected { + return Err(SpiMonitorError::VerificationFailed); + } + Ok(slot) +} + fn initialize_privilege_table( regs: &SpiMonitorRegisters, direction: PrivilegeDirection, diff --git a/target/ast10x0/peripherals/spimonitor/types.rs b/target/ast10x0/peripherals/spimonitor/types.rs index 3be10bf2..5c00cec7 100644 --- a/target/ast10x0/peripherals/spimonitor/types.rs +++ b/target/ast10x0/peripherals/spimonitor/types.rs @@ -28,7 +28,10 @@ pub enum PrivilegeOp { /// Mirrors Zephyr's `spim_passthrough_config`. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PassthroughMode { + /// Enable single-bit passthrough. Enabled, + /// Enable multi-bit passthrough. + MultiEnabled, Disabled, } diff --git a/target/ast10x0/tests/spimonitor/target.rs b/target/ast10x0/tests/spimonitor/target.rs index f91d6bee..3792b231 100644 --- a/target/ast10x0/tests/spimonitor/target.rs +++ b/target/ast10x0/tests/spimonitor/target.rs @@ -1,16 +1,24 @@ // Licensed under the Apache-2.0 license // SPDX-License-Identifier: Apache-2.0 -//! AST10x0 SPI monitor register and routing smoke test. +//! AST10x0 SPI monitor configuration hardware test. #![no_std] #![no_main] -use ast10x0_board::apply_spim_pinctrl; -use ast10x0_peripherals::scu::{ScuRegisters, SpiMonitorInstance}; +use core::cell::UnsafeCell; + +use ast10x0_board::{ + apply_spim_external_mux, apply_spim_pinctrl, spim_external_mux_state, +}; +use ast10x0_peripherals::scu::{ + ScuError, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, + SpiMonitorSource, +}; use ast10x0_peripherals::spimonitor::{ - command_table_value, ExtMuxSel, MonitorPolicy, MonitorState, PassthroughMode, SpiMonitor, - SpiMonitorController, SpiMonitorError, Uninitialized, + ConfiguredSpiMonitor, LockState, MonitorPolicy, MonitorState, PassthroughMode, + PrivilegeDirection, PrivilegeOp, SpiMonitor, SpiMonitorController, SpiMonitorError, + Uninitialized, }; use console_backend::console_backend_write_all; use target_common::{declare_target, TargetInterface}; @@ -18,115 +26,237 @@ use {console_backend as _, entry as _}; pub struct Target {} -const CTRL_MONITOR_ENABLE: u32 = 1 << 2; -const CTRL_SINGLE_BIT_PASSTHROUGH: u32 = 1 << 0; -const TEST_COMMANDS: [u8; 3] = [0x9f, 0x05, 0x06]; -const FIRST_GENERAL_COMMAND_SLOT: usize = 2; - -fn check_register(actual: u32, expected: u32) -> Result<(), SpiMonitorError> { - if actual != expected { - pw_log::info!( - "register mismatch: expected=0x{:08x}, actual=0x{:08x}", - expected as u32, - actual as u32 - ); - return Err(SpiMonitorError::InvalidTransition); - } - Ok(()) +const SPIM: SpiMonitorInstance = SpiMonitorInstance::Spim0; +const PROTECTED_LENGTH: u32 = 0x0010_0000; +const LOG_RAM_BYTES: usize = 0x200; +const LOG_RAM_WORDS: usize = LOG_RAM_BYTES / core::mem::size_of::(); +const COMMAND_VALID_MASK: u32 = (1 << 30) | (1 << 31); +const COMMAND_LOCKED: u32 = 1 << 23; +const LOCK_REQUIRED: u32 = (1 << 0) | (1 << 1) | (1 << 4) | (1 << 5) | (1 << 30) | (1 << 31); + +#[repr(align(16))] +struct LogRam(UnsafeCell<[u32; LOG_RAM_WORDS]>); + +// SAFETY: This test is single-threaded and gives the buffer exclusively to SPIPF. +unsafe impl Sync for LogRam {} + +static LOG_RAM: LogRam = LogRam(UnsafeCell::new([0; LOG_RAM_WORDS])); + +#[derive(Clone, Copy)] +enum TestError { + Monitor, + Scu, + Check, } -fn run_spimonitor_test() -> Result<(), SpiMonitorError> { - pw_log::info!("=== AST10x0 SPI monitor smoke test ==="); +impl From for TestError { + fn from(_: SpiMonitorError) -> Self { + Self::Monitor + } +} - // Match the device-tree pinctrl-0 setup for spim1/SPIPF1. - let scu = unsafe { ScuRegisters::new_global_unlocked() }; - apply_spim_pinctrl(&scu, SpiMonitorInstance::Spim0); +impl From for TestError { + fn from(_: ScuError) -> Self { + Self::Scu + } +} - // This target owns SPIM0/SPIPF1 for its complete lifetime. - let monitor = unsafe { - SpiMonitor::::new(SpiMonitorController::Spim0) +macro_rules! test_check { + ($condition:expr, $message:literal) => { + if !$condition { + pw_log::info!($message); + return Err(TestError::Check); + } }; - if monitor.state() != MonitorState::Uninitialized { - return Err(SpiMonitorError::InvalidTransition); - } +} + +fn log_buffer() -> &'static mut [u32] { + // SAFETY: The test is single-threaded and calls this exactly once. + unsafe { &mut *LOG_RAM.0.get() } +} - let original_ctrl = monitor.regs().read_ctrl(); - let original_slots = [ - monitor.regs().read_allow_cmd_slot(2), - monitor.regs().read_allow_cmd_slot(3), - monitor.regs().read_allow_cmd_slot(4), - ]; +fn configure_wiring(scu: &ScuRegisters) -> Result<(), TestError> { + pw_log::info!("START: external monitor pinctrl, routing, and mux"); + apply_spim_pinctrl(scu, SPIM); + scu.disable_spim_cs_internal_pull_down(SPIM); + + // SPIPF observes external BMC/host traffic. Do not detour SPI1 or SPI2 + // internally into the monitor. + scu.set_spim_internal_mux(SpiMonitorSource::Spi1, 0)?; + test_check!( + scu.route_control_raw() & 0x0f == 0, + "FAIL: internal SPI master detour is enabled" + ); + + // This SCU bit enables the SPIPF signal path. Controller bypass/filtering + // is controlled independently by SPIPF000[1:0]. + scu.set_spim_passthrough(SPIM, SpiMonitorPassthrough::Enabled); + scu.set_spim_miso_multi_func(SPIM, true); + scu.set_spim_filter(SPIM, true); + + apply_spim_external_mux(SPIM, ScuExtMuxSelect::Mux0); + test_check!( + spim_external_mux_state(SPIM) == Some(ScuExtMuxSelect::Mux0), + "FAIL: external mux 0 GPIO readback" + ); + apply_spim_external_mux(SPIM, ScuExtMuxSelect::Mux1); + test_check!( + spim_external_mux_state(SPIM) == Some(ScuExtMuxSelect::Mux1), + "FAIL: external mux 1 GPIO readback" + ); + scu.set_spim_ext_mux(SPIM, ScuExtMuxSelect::Mux1); + + test_check!( + scu.route_control_raw() & 0x0f == 0, + "FAIL: external monitor setup changed internal SPI routing" + ); + pw_log::info!("PASS: external monitor pinctrl, routing, and mux"); + Ok(()) +} +fn build_policy() -> MonitorPolicy { let mut policy = MonitorPolicy::empty(); - policy.allow_commands[..TEST_COMMANDS.len()].copy_from_slice(&TEST_COMMANDS); - policy.allow_command_count = TEST_COMMANDS.len(); + policy.allow_commands[..9] + .copy_from_slice(&[0x9f, 0x05, 0x06, 0x04, 0x02, 0x12, 0x20, 0x21, 0x0c]); + policy.allow_command_count = 9; + let _ = policy.add_region( + 0, + PROTECTED_LENGTH, + PrivilegeDirection::Write, + PrivilegeOp::Disable, + ); + policy +} - let configured = monitor.apply_policy(&policy)?; - if configured.state() != MonitorState::Configured { - return Err(SpiMonitorError::InvalidTransition); - } +fn initialize_monitor() -> Result { + pw_log::info!("START: monitor reset, policy, and log RAM"); + let monitor = unsafe { SpiMonitor::::new(SpiMonitorController::Spim0) }; + monitor.software_reset(); + test_check!( + monitor.regs().read_ctrl() & (1 << 15) == 0, + "FAIL: software reset did not deassert" + ); - let original_mux = configured.get_ext_mux(); - let result = (|| { - for (index, command) in TEST_COMMANDS.iter().copied().enumerate() { - let expected = - command_table_value(command, false).ok_or(SpiMonitorError::UnsupportedCommand( - command, - ))?; - check_register( - configured - .regs() - .read_allow_cmd_slot(FIRST_GENERAL_COMMAND_SLOT + index), - expected, - )?; - } - pw_log::info!("command table readback passed"); - - configured.enable(); - check_register( - configured.regs().read_ctrl() & CTRL_MONITOR_ENABLE, - CTRL_MONITOR_ENABLE, - )?; - - configured.set_passthrough(PassthroughMode::Enabled); - check_register( - configured.regs().read_ctrl() & CTRL_SINGLE_BIT_PASSTHROUGH, - CTRL_SINGLE_BIT_PASSTHROUGH, - )?; - - configured.set_passthrough(PassthroughMode::Disabled); - check_register( - configured.regs().read_ctrl() & CTRL_SINGLE_BIT_PASSTHROUGH, - 0, - )?; - - let test_mux = match original_mux { - ExtMuxSel::Sel0 => ExtMuxSel::Sel1, - ExtMuxSel::Sel1 => ExtMuxSel::Sel0, - }; - configured.set_ext_mux(test_mux); - if configured.get_ext_mux() != test_mux { - pw_log::info!("external mux readback failed"); - return Err(SpiMonitorError::InvalidTransition); - } - pw_log::info!("control and external mux readback passed"); - Ok(()) - })(); - - // Restore every register changed by this smoke test. - configured.set_ext_mux(original_mux); - configured.regs().write_ctrl(original_ctrl); - for (index, value) in original_slots.iter().copied().enumerate() { - configured - .regs() - .write_allow_cmd_slot(FIRST_GENERAL_COMMAND_SLOT + index, value); - } + let configured = monitor.apply_policy(&build_policy())?; + test_check!( + configured.state() == MonitorState::Configured, + "FAIL: monitor did not enter configured state" + ); + + configured.configure_log(log_buffer())?; + test_check!( + configured.regs().read_log_capacity_entries() as usize == LOG_RAM_WORDS, + "FAIL: 0x200-byte log RAM configuration" + ); + test_check!( + configured.regs().read_log_idx_reg() == 0, + "FAIL: violation log pointer was not reset" + ); - result + configured.set_push_pull(true); + configured.set_passthrough(PassthroughMode::Disabled); + configured.enable(); + test_check!( + configured.regs().read_ctrl() & (1 << 2) != 0, + "FAIL: monitor filter is not enabled" + ); + pw_log::info!("PASS: monitor reset, policy, and log RAM"); + Ok(configured) +} + +fn test_passthrough_control(configured: &ConfiguredSpiMonitor) -> Result<(), TestError> { + pw_log::info!("START: passthrough control readback"); + configured.set_passthrough(PassthroughMode::Enabled); + test_check!( + configured.regs().read_ctrl() & 0x3 == 0x1, + "FAIL: single-bit passthrough readback" + ); + configured.set_passthrough(PassthroughMode::MultiEnabled); + test_check!( + configured.regs().read_ctrl() & 0x3 == 0x2, + "FAIL: multi-bit passthrough readback" + ); + configured.set_passthrough(PassthroughMode::Disabled); + test_check!( + configured.regs().read_ctrl() & 0x3 == 0, + "FAIL: passthrough disable readback" + ); + pw_log::info!("PASS: passthrough control readback"); + Ok(()) +} + +fn test_command_policy(configured: &ConfiguredSpiMonitor) -> Result<(), TestError> { + pw_log::info!("START: command table add and remove"); + configured.remove_command(0x05)?; + let status_slot = configured.add_command(0x05, false)?; + test_check!( + configured.regs().read_allow_cmd_slot(status_slot) & COMMAND_VALID_MASK != 0, + "FAIL: command add readback" + ); + configured.remove_command(0x05)?; + test_check!( + configured.regs().read_allow_cmd_slot(status_slot) == 0, + "FAIL: command remove did not clear slot" + ); + configured.add_command(0x05, false)?; + pw_log::info!("PASS: command table add and remove"); + Ok(()) +} + +fn test_address_policy(configured: &ConfiguredSpiMonitor) -> Result<(), TestError> { + pw_log::info!("START: protected and unprotected address policy"); + test_check!( + configured.privilege_word(PrivilegeDirection::Write, 0)? == 0, + "FAIL: protected write region programming" + ); + test_check!( + configured.privilege_word(PrivilegeDirection::Write, 2)? == u32::MAX, + "FAIL: unprotected write region programming" + ); + pw_log::info!("PASS: protected and unprotected address policy"); + Ok(()) +} + +fn test_policy_locking(configured: ConfiguredSpiMonitor) -> Result<(), TestError> { + pw_log::info!("START: policy locking and readback"); + configured.lock_command(0x9f)?; + let locked = configured.lock()?; + test_check!( + locked.lock_state() == LockState::Locked, + "FAIL: monitor lock state" + ); + test_check!( + locked.regs().read_lock_status() & LOCK_REQUIRED == LOCK_REQUIRED, + "FAIL: lock register readback" + ); + let slot = locked.regs().read_allow_cmd_slot(2); + locked.regs().write_allow_cmd_slot(2, 0); + test_check!( + locked.regs().read_allow_cmd_slot(2) == slot && slot & COMMAND_LOCKED != 0, + "FAIL: command lock did not prevent modification" + ); + pw_log::info!("PASS: policy locking and readback"); + Ok(()) +} + +fn run_spimonitor_test() -> Result<(), TestError> { + pw_log::info!("=== AST10x0 external SPI monitor configuration test ==="); + + let scu = unsafe { ScuRegisters::new_global_unlocked() }; + configure_wiring(&scu)?; + let configured = initialize_monitor()?; + test_passthrough_control(&configured)?; + test_command_policy(&configured)?; + test_address_policy(&configured)?; + test_policy_locking(configured)?; + + pw_log::info!("External traffic blocking requires a BMC/host stimulus"); + pw_log::info!("=== all SPI monitor configuration tests passed ==="); + Ok(()) } impl TargetInterface for Target { - const NAME: &'static str = "AST10x0 SPI Monitor Smoke Test"; + const NAME: &'static str = "AST10x0 External SPI Monitor Configuration Test"; fn main() -> ! { let sentinel = if run_spimonitor_test().is_ok() { From 128c9bd38cbf5e268d060a222844f1e079206880 Mon Sep 17 00:00:00 2001 From: hailin wu Date: Wed, 10 Jun 2026 17:10:48 -0700 Subject: [PATCH 07/13] Tested Spimonitor with Emulated BMC master for write protected region. --- target/ast10x0/board/src/spim_wiring.rs | 2 +- target/ast10x0/tests/spimonitor/BUILD.bazel | 54 ++----- .../tests/spimonitor/setup_all_spim.rs | 142 ++++++++++++++++++ .../tests/spimonitor/spimonitor_test.bzl | 100 ++++++++++++ .../spimonitor/{target.rs => test_common.rs} | 127 +++++++++------- target/ast10x0/tests/spimonitor/test_spim1.rs | 40 +++++ target/ast10x0/tests/spimonitor/test_spim2.rs | 40 +++++ target/ast10x0/tests/spimonitor/test_spim3.rs | 40 +++++ target/ast10x0/tests/spimonitor/test_spim4.rs | 40 +++++ 9 files changed, 488 insertions(+), 97 deletions(-) create mode 100644 target/ast10x0/tests/spimonitor/setup_all_spim.rs create mode 100644 target/ast10x0/tests/spimonitor/spimonitor_test.bzl rename target/ast10x0/tests/spimonitor/{target.rs => test_common.rs} (71%) create mode 100644 target/ast10x0/tests/spimonitor/test_spim1.rs create mode 100644 target/ast10x0/tests/spimonitor/test_spim2.rs create mode 100644 target/ast10x0/tests/spimonitor/test_spim3.rs create mode 100644 target/ast10x0/tests/spimonitor/test_spim4.rs diff --git a/target/ast10x0/board/src/spim_wiring.rs b/target/ast10x0/board/src/spim_wiring.rs index 13489170..f540d52b 100644 --- a/target/ast10x0/board/src/spim_wiring.rs +++ b/target/ast10x0/board/src/spim_wiring.rs @@ -339,7 +339,7 @@ pub mod presets { let mut policy = profile::zephyr_default(); let _ = policy.add_region( 0, - 0x0800_0000, + 0x0010_0000, PrivilegeDirection::Write, PrivilegeOp::Disable, ); diff --git a/target/ast10x0/tests/spimonitor/BUILD.bazel b/target/ast10x0/tests/spimonitor/BUILD.bazel index 5d961b1f..47f0d666 100644 --- a/target/ast10x0/tests/spimonitor/BUILD.bazel +++ b/target/ast10x0/tests/spimonitor/BUILD.bazel @@ -1,12 +1,10 @@ # Licensed under the Apache-2.0 license # SPDX-License-Identifier: Apache-2.0 -load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image", "system_image_test") load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen") load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script") -load("@pigweed//pw_kernel/tooling/panic_detector:rust_binary_no_panics_test.bzl", "rust_binary_no_panics_test") -load("@rules_rust//rust:defs.bzl", "rust_binary") load("//target/ast10x0:defs.bzl", "TARGET_COMPATIBLE_WITH") +load(":spimonitor_test.bzl", "spimonitor_setup_all", "spimonitor_test") filegroup( name = "system_config", @@ -28,48 +26,20 @@ target_linker_script( template = "//target/ast10x0:linker_script_template", ) -rust_binary( - name = "target", - srcs = ["target.rs"], - edition = "2024", - tags = ["kernel"], - target_compatible_with = TARGET_COMPATIBLE_WITH, - deps = [ - ":codegen", - ":linker_script", - "//target/ast10x0:entry", - "//target/ast10x0/board:ast10x0_board", - "//target/ast10x0/peripherals", - "@pigweed//pw_kernel/subsys/console:console_backend", - "@pigweed//pw_kernel/target:target_common", - "@pigweed//pw_log/rust:pw_log", - ], -) +spimonitor_test(1, TARGET_COMPATIBLE_WITH) +spimonitor_test(2, TARGET_COMPATIBLE_WITH) +spimonitor_test(3, TARGET_COMPATIBLE_WITH) +spimonitor_test(4, TARGET_COMPATIBLE_WITH) +spimonitor_setup_all(TARGET_COMPATIBLE_WITH) -system_image( - name = "spimonitor_test", - kernel = ":target", - platform = "//target/ast10x0", - system_config = ":system_config", - tags = ["kernel"], - target_compatible_with = TARGET_COMPATIBLE_WITH, - userspace = False, +alias( + name = "target", + actual = ":target_spim1", visibility = ["//visibility:public"], ) -system_image_test( - name = "spimonitor_evb_test", - image = ":spimonitor_test", - tags = ["hardware"], - target_compatible_with = select({ - "//target/ast10x0:qemu_enabled": ["@platforms//:incompatible"], - "//conditions:default": [], - }), +alias( + name = "spimonitor_test", + actual = ":spimonitor1_test", visibility = ["//visibility:public"], ) - -rust_binary_no_panics_test( - name = "no_panics_test", - binary = ":spimonitor_test", - tags = ["kernel"], -) diff --git a/target/ast10x0/tests/spimonitor/setup_all_spim.rs b/target/ast10x0/tests/spimonitor/setup_all_spim.rs new file mode 100644 index 00000000..a0419598 --- /dev/null +++ b/target/ast10x0/tests/spimonitor/setup_all_spim.rs @@ -0,0 +1,142 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Configure SPIM1 through SPIM4 and keep all monitors active. + +#![no_std] +#![no_main] + +use core::cell::UnsafeCell; + +#[path = "test_common.rs"] +mod test_common; + +use ast10x0_peripherals::scu::{ScuRegisters, SpiMonitorInstance}; +use ast10x0_peripherals::spimonitor::{ + MonitorPolicy, PrivilegeDirection, PrivilegeOp, SpiMonitorController, +}; +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +struct Spim1Config; +struct Spim2Config; +struct Spim3Config; +struct Spim4Config; + +impl test_common::TestConfig for Spim1Config { + const INSTANCE: SpiMonitorInstance = SpiMonitorInstance::Spim0; + const CONTROLLER: SpiMonitorController = SpiMonitorController::Spim0; +} + +impl test_common::TestConfig for Spim2Config { + const INSTANCE: SpiMonitorInstance = SpiMonitorInstance::Spim1; + const CONTROLLER: SpiMonitorController = SpiMonitorController::Spim1; +} + +impl test_common::TestConfig for Spim3Config { + const INSTANCE: SpiMonitorInstance = SpiMonitorInstance::Spim2; + const CONTROLLER: SpiMonitorController = SpiMonitorController::Spim2; +} + +impl test_common::TestConfig for Spim4Config { + const INSTANCE: SpiMonitorInstance = SpiMonitorInstance::Spim3; + const CONTROLLER: SpiMonitorController = SpiMonitorController::Spim3; +} + +#[repr(align(16))] +struct LogRam(UnsafeCell<[u32; test_common::LOG_RAM_WORDS]>); + +// SAFETY: Each buffer is exclusively assigned to one SPIPF instance. +unsafe impl Sync for LogRam {} + +static SPIM1_LOG: LogRam = LogRam(UnsafeCell::new([0; test_common::LOG_RAM_WORDS])); +static SPIM2_LOG: LogRam = LogRam(UnsafeCell::new([0; test_common::LOG_RAM_WORDS])); +static SPIM3_LOG: LogRam = LogRam(UnsafeCell::new([0; test_common::LOG_RAM_WORDS])); +static SPIM4_LOG: LogRam = LogRam(UnsafeCell::new([0; test_common::LOG_RAM_WORDS])); + +const WRITE_PROTECTED_LENGTH: u32 = 0x0010_0000; +const ALLOW_COMMANDS: [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, +]; + +fn log_buffer(log: &'static LogRam) -> &'static mut [u32] { + // SAFETY: The caller assigns each static buffer to exactly one monitor. + unsafe { &mut *log.0.get() } +} + +fn production_policy() -> MonitorPolicy { + let mut policy = MonitorPolicy::empty(); + policy.allow_commands.copy_from_slice(&ALLOW_COMMANDS); + policy.allow_command_count = ALLOW_COMMANDS.len(); + let _ = policy.add_region( + 0, + WRITE_PROTECTED_LENGTH, + PrivilegeDirection::Write, + PrivilegeOp::Disable, + ); + policy +} + +fn setup_all_spim() -> Result<(), test_common::TestError> { + let scu = unsafe { ScuRegisters::new_global_unlocked() }; + let policy = production_policy(); + + pw_log::info!("=== SPIM1 ==="); + test_common::configure_wiring::(&scu)?; + let spim1 = test_common::initialize_monitor_with_policy::( + log_buffer(&SPIM1_LOG), + &policy, + )?; + test_common::dump_policy(&spim1, WRITE_PROTECTED_LENGTH)?; + let _spim1 = test_common::lock_monitor(spim1)?; + + pw_log::info!("=== SPIM2 ==="); + test_common::configure_wiring::(&scu)?; + let spim2 = test_common::initialize_monitor_with_policy::( + log_buffer(&SPIM2_LOG), + &policy, + )?; + test_common::dump_policy(&spim2, WRITE_PROTECTED_LENGTH)?; + let _spim2 = test_common::lock_monitor(spim2)?; + + pw_log::info!("=== SPIM3 ==="); + test_common::configure_wiring::(&scu)?; + let spim3 = test_common::initialize_monitor_with_policy::( + log_buffer(&SPIM3_LOG), + &policy, + )?; + test_common::dump_policy(&spim3, WRITE_PROTECTED_LENGTH)?; + let _spim3 = test_common::lock_monitor(spim3)?; + + pw_log::info!("=== SPIM4 ==="); + test_common::configure_wiring::(&scu)?; + let spim4 = test_common::initialize_monitor_with_policy::( + log_buffer(&SPIM4_LOG), + &policy, + )?; + test_common::dump_policy(&spim4, WRITE_PROTECTED_LENGTH)?; + let _spim4 = test_common::lock_monitor(spim4)?; + + pw_log::info!("All SPIM1-4 monitors are configured and locked"); + pw_log::info!("Firmware will remain active until the user stops or resets it"); + Ok(()) +} + +struct Target; + +impl TargetInterface for Target { + const NAME: &'static str = "AST10x0 Setup All External SPI Monitors"; + + fn main() -> ! { + if setup_all_spim().is_err() { + pw_log::info!("FAIL: setup all SPIM monitors"); + } + + #[expect(clippy::empty_loop)] + loop {} + } +} + +declare_target!(Target); diff --git a/target/ast10x0/tests/spimonitor/spimonitor_test.bzl b/target/ast10x0/tests/spimonitor/spimonitor_test.bzl new file mode 100644 index 00000000..e229ed10 --- /dev/null +++ b/target/ast10x0/tests/spimonitor/spimonitor_test.bzl @@ -0,0 +1,100 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image", "system_image_test") +load("@pigweed//pw_kernel/tooling/panic_detector:rust_binary_no_panics_test.bzl", "rust_binary_no_panics_test") +load("@rules_rust//rust:defs.bzl", "rust_binary") + +def spimonitor_test(index, target_compatible_with): + target_name = "target_spim{}".format(index) + image_name = "spimonitor{}_test".format(index) + + rust_binary( + name = target_name, + srcs = [ + "test_common.rs", + "test_spim{}.rs".format(index), + ], + crate_root = "test_spim{}.rs".format(index), + edition = "2024", + tags = ["kernel"], + target_compatible_with = target_compatible_with, + deps = [ + ":codegen", + ":linker_script", + "//target/ast10x0:entry", + "//target/ast10x0/board:ast10x0_board", + "//target/ast10x0/peripherals", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_log/rust:pw_log", + ], + ) + + system_image( + name = image_name, + kernel = ":" + target_name, + platform = "//target/ast10x0", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = target_compatible_with, + userspace = False, + visibility = ["//visibility:public"], + ) + + system_image_test( + name = "spimonitor{}_evb_test".format(index), + image = ":" + image_name, + tags = ["hardware"], + target_compatible_with = select({ + "//target/ast10x0:qemu_enabled": ["@platforms//:incompatible"], + "//conditions:default": [], + }), + visibility = ["//visibility:public"], + ) + + rust_binary_no_panics_test( + name = "spimonitor{}_no_panics_test".format(index), + binary = ":" + image_name, + tags = ["kernel"], + ) + +def spimonitor_setup_all(target_compatible_with): + rust_binary( + name = "setup_all_spim", + srcs = [ + "setup_all_spim.rs", + "test_common.rs", + ], + crate_root = "setup_all_spim.rs", + edition = "2024", + tags = ["kernel"], + target_compatible_with = target_compatible_with, + deps = [ + ":codegen", + ":linker_script", + "//target/ast10x0:entry", + "//target/ast10x0/board:ast10x0_board", + "//target/ast10x0/peripherals", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_log/rust:pw_log", + ], + ) + + system_image( + name = "setup_all_spim_image", + kernel = ":setup_all_spim", + platform = "//target/ast10x0", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = target_compatible_with, + userspace = False, + visibility = ["//visibility:public"], + ) + + rust_binary_no_panics_test( + name = "setup_all_spim_no_panics_test", + binary = ":setup_all_spim_image", + tags = ["kernel"], + ) diff --git a/target/ast10x0/tests/spimonitor/target.rs b/target/ast10x0/tests/spimonitor/test_common.rs similarity index 71% rename from target/ast10x0/tests/spimonitor/target.rs rename to target/ast10x0/tests/spimonitor/test_common.rs index 3792b231..d252f89c 100644 --- a/target/ast10x0/tests/spimonitor/target.rs +++ b/target/ast10x0/tests/spimonitor/test_common.rs @@ -1,10 +1,9 @@ // Licensed under the Apache-2.0 license // SPDX-License-Identifier: Apache-2.0 -//! AST10x0 SPI monitor configuration hardware test. +//! Shared AST10x0 external SPI monitor configuration tests. -#![no_std] -#![no_main] +#![allow(dead_code)] use core::cell::UnsafeCell; @@ -20,30 +19,29 @@ use ast10x0_peripherals::spimonitor::{ PrivilegeDirection, PrivilegeOp, SpiMonitor, SpiMonitorController, SpiMonitorError, Uninitialized, }; -use console_backend::console_backend_write_all; -use target_common::{declare_target, TargetInterface}; -use {console_backend as _, entry as _}; -pub struct Target {} - -const SPIM: SpiMonitorInstance = SpiMonitorInstance::Spim0; -const PROTECTED_LENGTH: u32 = 0x0010_0000; +pub const PROTECTED_LENGTH: u32 = 0x0010_0000; const LOG_RAM_BYTES: usize = 0x200; -const LOG_RAM_WORDS: usize = LOG_RAM_BYTES / core::mem::size_of::(); +pub const LOG_RAM_WORDS: usize = LOG_RAM_BYTES / core::mem::size_of::(); const COMMAND_VALID_MASK: u32 = (1 << 30) | (1 << 31); const COMMAND_LOCKED: u32 = 1 << 23; const LOCK_REQUIRED: u32 = (1 << 0) | (1 << 1) | (1 << 4) | (1 << 5) | (1 << 30) | (1 << 31); +pub trait TestConfig { + const INSTANCE: SpiMonitorInstance; + const CONTROLLER: SpiMonitorController; +} + #[repr(align(16))] struct LogRam(UnsafeCell<[u32; LOG_RAM_WORDS]>); -// SAFETY: This test is single-threaded and gives the buffer exclusively to SPIPF. +// SAFETY: Each test image is single-threaded and owns one SPIPF log buffer. unsafe impl Sync for LogRam {} static LOG_RAM: LogRam = LogRam(UnsafeCell::new([0; LOG_RAM_WORDS])); #[derive(Clone, Copy)] -enum TestError { +pub enum TestError { Monitor, Scu, Check, @@ -75,36 +73,34 @@ fn log_buffer() -> &'static mut [u32] { unsafe { &mut *LOG_RAM.0.get() } } -fn configure_wiring(scu: &ScuRegisters) -> Result<(), TestError> { +pub fn configure_wiring(scu: &ScuRegisters) -> Result<(), TestError> { pw_log::info!("START: external monitor pinctrl, routing, and mux"); - apply_spim_pinctrl(scu, SPIM); - scu.disable_spim_cs_internal_pull_down(SPIM); + apply_spim_pinctrl(scu, C::INSTANCE); + scu.disable_spim_cs_internal_pull_down(C::INSTANCE); - // SPIPF observes external BMC/host traffic. Do not detour SPI1 or SPI2 - // internally into the monitor. + // SPIPF observes external BMC/host traffic. Do not detour SPI1 or SPI2. scu.set_spim_internal_mux(SpiMonitorSource::Spi1, 0)?; test_check!( scu.route_control_raw() & 0x0f == 0, "FAIL: internal SPI master detour is enabled" ); - // This SCU bit enables the SPIPF signal path. Controller bypass/filtering - // is controlled independently by SPIPF000[1:0]. - scu.set_spim_passthrough(SPIM, SpiMonitorPassthrough::Enabled); - scu.set_spim_miso_multi_func(SPIM, true); - scu.set_spim_filter(SPIM, true); + // SCU passthrough enables the external signal path through SPIPF. + scu.set_spim_passthrough(C::INSTANCE, SpiMonitorPassthrough::Enabled); + scu.set_spim_miso_multi_func(C::INSTANCE, true); + scu.set_spim_filter(C::INSTANCE, true); - apply_spim_external_mux(SPIM, ScuExtMuxSelect::Mux0); + apply_spim_external_mux(C::INSTANCE, ScuExtMuxSelect::Mux0); test_check!( - spim_external_mux_state(SPIM) == Some(ScuExtMuxSelect::Mux0), + spim_external_mux_state(C::INSTANCE) == Some(ScuExtMuxSelect::Mux0), "FAIL: external mux 0 GPIO readback" ); - apply_spim_external_mux(SPIM, ScuExtMuxSelect::Mux1); + apply_spim_external_mux(C::INSTANCE, ScuExtMuxSelect::Mux1); test_check!( - spim_external_mux_state(SPIM) == Some(ScuExtMuxSelect::Mux1), + spim_external_mux_state(C::INSTANCE) == Some(ScuExtMuxSelect::Mux1), "FAIL: external mux 1 GPIO readback" ); - scu.set_spim_ext_mux(SPIM, ScuExtMuxSelect::Mux1); + scu.set_spim_ext_mux(C::INSTANCE, ScuExtMuxSelect::Mux1); test_check!( scu.route_control_raw() & 0x0f == 0, @@ -114,7 +110,7 @@ fn configure_wiring(scu: &ScuRegisters) -> Result<(), TestError> { Ok(()) } -fn build_policy() -> MonitorPolicy { +pub fn build_policy() -> MonitorPolicy { let mut policy = MonitorPolicy::empty(); policy.allow_commands[..9] .copy_from_slice(&[0x9f, 0x05, 0x06, 0x04, 0x02, 0x12, 0x20, 0x21, 0x0c]); @@ -128,22 +124,31 @@ fn build_policy() -> MonitorPolicy { policy } -fn initialize_monitor() -> Result { +pub fn initialize_monitor( + buffer: &'static mut [u32], +) -> Result { + initialize_monitor_with_policy::(buffer, &build_policy()) +} + +pub fn initialize_monitor_with_policy( + buffer: &'static mut [u32], + policy: &MonitorPolicy, +) -> Result { pw_log::info!("START: monitor reset, policy, and log RAM"); - let monitor = unsafe { SpiMonitor::::new(SpiMonitorController::Spim0) }; + let monitor = unsafe { SpiMonitor::::new(C::CONTROLLER) }; monitor.software_reset(); test_check!( monitor.regs().read_ctrl() & (1 << 15) == 0, "FAIL: software reset did not deassert" ); - let configured = monitor.apply_policy(&build_policy())?; + let configured = monitor.apply_policy(policy)?; test_check!( configured.state() == MonitorState::Configured, "FAIL: monitor did not enter configured state" ); - configured.configure_log(log_buffer())?; + configured.configure_log(buffer)?; test_check!( configured.regs().read_log_capacity_entries() as usize == LOG_RAM_WORDS, "FAIL: 0x200-byte log RAM configuration" @@ -164,6 +169,38 @@ fn initialize_monitor() -> Result { Ok(configured) } +#[allow(dead_code)] +pub fn dump_policy( + configured: &ConfiguredSpiMonitor, + write_protected_length: u32, +) -> Result<(), TestError> { + pw_log::info!("allow-command table:"); + for slot in 0..32 { + let value = configured.regs().read_allow_cmd_slot(slot); + if value != 0 { + pw_log::info!( + " slot {:02}: cmd=0x{:02x}, raw=0x{:08x}", + slot as u32, + (value & 0xff) as u32, + value as u32 + ); + } + } + + pw_log::info!( + "write-protected region: start=0x00000000, length=0x{:08x}", + write_protected_length as u32 + ); + Ok(()) +} + +#[allow(dead_code)] +pub fn lock_monitor( + configured: ConfiguredSpiMonitor, +) -> Result { + Ok(configured.lock()?) +} + fn test_passthrough_control(configured: &ConfiguredSpiMonitor) -> Result<(), TestError> { pw_log::info!("START: passthrough control readback"); configured.set_passthrough(PassthroughMode::Enabled); @@ -239,12 +276,12 @@ fn test_policy_locking(configured: ConfiguredSpiMonitor) -> Result<(), TestError Ok(()) } -fn run_spimonitor_test() -> Result<(), TestError> { +pub fn run() -> Result<(), TestError> { pw_log::info!("=== AST10x0 external SPI monitor configuration test ==="); let scu = unsafe { ScuRegisters::new_global_unlocked() }; - configure_wiring(&scu)?; - let configured = initialize_monitor()?; + configure_wiring::(&scu)?; + let configured = initialize_monitor::(log_buffer())?; test_passthrough_control(&configured)?; test_command_policy(&configured)?; test_address_policy(&configured)?; @@ -254,21 +291,3 @@ fn run_spimonitor_test() -> Result<(), TestError> { pw_log::info!("=== all SPI monitor configuration tests passed ==="); Ok(()) } - -impl TargetInterface for Target { - const NAME: &'static str = "AST10x0 External SPI Monitor Configuration Test"; - - fn main() -> ! { - let sentinel = if run_spimonitor_test().is_ok() { - b"TEST_RESULT:PASS\n" - } else { - b"TEST_RESULT:FAIL\n" - }; - let _ = console_backend_write_all(sentinel); - - #[expect(clippy::empty_loop)] - loop {} - } -} - -declare_target!(Target); diff --git a/target/ast10x0/tests/spimonitor/test_spim1.rs b/target/ast10x0/tests/spimonitor/test_spim1.rs new file mode 100644 index 00000000..0aa6a20b --- /dev/null +++ b/target/ast10x0/tests/spimonitor/test_spim1.rs @@ -0,0 +1,40 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] + +#[path = "test_common.rs"] +mod test_common; + +use ast10x0_peripherals::scu::SpiMonitorInstance; +use ast10x0_peripherals::spimonitor::SpiMonitorController; +use console_backend::console_backend_write_all; +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +struct Spim1Config; + +impl test_common::TestConfig for Spim1Config { + const INSTANCE: SpiMonitorInstance = SpiMonitorInstance::Spim0; + const CONTROLLER: SpiMonitorController = SpiMonitorController::Spim0; +} + +struct Target; + +impl TargetInterface for Target { + const NAME: &'static str = "AST10x0 External SPIM1 Configuration Test"; + + fn main() -> ! { + let sentinel = if test_common::run::().is_ok() { + b"TEST_RESULT:PASS\n" + } else { + b"TEST_RESULT:FAIL\n" + }; + let _ = console_backend_write_all(sentinel); + #[expect(clippy::empty_loop)] + loop {} + } +} + +declare_target!(Target); diff --git a/target/ast10x0/tests/spimonitor/test_spim2.rs b/target/ast10x0/tests/spimonitor/test_spim2.rs new file mode 100644 index 00000000..cae247c6 --- /dev/null +++ b/target/ast10x0/tests/spimonitor/test_spim2.rs @@ -0,0 +1,40 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] + +#[path = "test_common.rs"] +mod test_common; + +use ast10x0_peripherals::scu::SpiMonitorInstance; +use ast10x0_peripherals::spimonitor::SpiMonitorController; +use console_backend::console_backend_write_all; +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +struct Spim2Config; + +impl test_common::TestConfig for Spim2Config { + const INSTANCE: SpiMonitorInstance = SpiMonitorInstance::Spim1; + const CONTROLLER: SpiMonitorController = SpiMonitorController::Spim1; +} + +struct Target; + +impl TargetInterface for Target { + const NAME: &'static str = "AST10x0 External SPIM2 Configuration Test"; + + fn main() -> ! { + let sentinel = if test_common::run::().is_ok() { + b"TEST_RESULT:PASS\n" + } else { + b"TEST_RESULT:FAIL\n" + }; + let _ = console_backend_write_all(sentinel); + #[expect(clippy::empty_loop)] + loop {} + } +} + +declare_target!(Target); diff --git a/target/ast10x0/tests/spimonitor/test_spim3.rs b/target/ast10x0/tests/spimonitor/test_spim3.rs new file mode 100644 index 00000000..e6ce1597 --- /dev/null +++ b/target/ast10x0/tests/spimonitor/test_spim3.rs @@ -0,0 +1,40 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] + +#[path = "test_common.rs"] +mod test_common; + +use ast10x0_peripherals::scu::SpiMonitorInstance; +use ast10x0_peripherals::spimonitor::SpiMonitorController; +use console_backend::console_backend_write_all; +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +struct Spim3Config; + +impl test_common::TestConfig for Spim3Config { + const INSTANCE: SpiMonitorInstance = SpiMonitorInstance::Spim2; + const CONTROLLER: SpiMonitorController = SpiMonitorController::Spim2; +} + +struct Target; + +impl TargetInterface for Target { + const NAME: &'static str = "AST10x0 External SPIM3 Configuration Test"; + + fn main() -> ! { + let sentinel = if test_common::run::().is_ok() { + b"TEST_RESULT:PASS\n" + } else { + b"TEST_RESULT:FAIL\n" + }; + let _ = console_backend_write_all(sentinel); + #[expect(clippy::empty_loop)] + loop {} + } +} + +declare_target!(Target); diff --git a/target/ast10x0/tests/spimonitor/test_spim4.rs b/target/ast10x0/tests/spimonitor/test_spim4.rs new file mode 100644 index 00000000..3d437157 --- /dev/null +++ b/target/ast10x0/tests/spimonitor/test_spim4.rs @@ -0,0 +1,40 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] + +#[path = "test_common.rs"] +mod test_common; + +use ast10x0_peripherals::scu::SpiMonitorInstance; +use ast10x0_peripherals::spimonitor::SpiMonitorController; +use console_backend::console_backend_write_all; +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +struct Spim4Config; + +impl test_common::TestConfig for Spim4Config { + const INSTANCE: SpiMonitorInstance = SpiMonitorInstance::Spim3; + const CONTROLLER: SpiMonitorController = SpiMonitorController::Spim3; +} + +struct Target; + +impl TargetInterface for Target { + const NAME: &'static str = "AST10x0 External SPIM4 Configuration Test"; + + fn main() -> ! { + let sentinel = if test_common::run::().is_ok() { + b"TEST_RESULT:PASS\n" + } else { + b"TEST_RESULT:FAIL\n" + }; + let _ = console_backend_write_all(sentinel); + #[expect(clippy::empty_loop)] + loop {} + } +} + +declare_target!(Target); From 061a325b7dc47b3a6bf92eaea2c332523860d522 Mon Sep 17 00:00:00 2001 From: hailin wu Date: Thu, 11 Jun 2026 16:00:42 -0700 Subject: [PATCH 08/13] replaced the old demo-board GPIOM5/GPIOH2 pins with SGPIOM outputs 8/9 for pulse helper. and split it into explicit assert/release operations so monitor setup happens while the BMC is held in reset. --- target/ast10x0/board/src/lib.rs | 5 +- target/ast10x0/board/src/spim_wiring.rs | 80 ++++++++++++++++- target/ast10x0/peripherals/scu/pinctrl.rs | 6 ++ target/ast10x0/peripherals/smc/controller.rs | 3 +- target/ast10x0/tests/smc/read/BUILD.bazel | 2 +- target/ast10x0/tests/smc/read/target_spi2.rs | 89 ++----------------- .../tests/spimonitor/setup_all_spim.rs | 51 ++++++++++- .../ast10x0/tests/spimonitor/test_common.rs | 9 +- 8 files changed, 149 insertions(+), 96 deletions(-) diff --git a/target/ast10x0/board/src/lib.rs b/target/ast10x0/board/src/lib.rs index 61444397..5dae84a3 100644 --- a/target/ast10x0/board/src/lib.rs +++ b/target/ast10x0/board/src/lib.rs @@ -20,7 +20,8 @@ pub mod spim_wiring; pub use monitor::Ast1060Monitor; pub use spim_wiring::{ apply_spim_external_mux, apply_spim_pinctrl, apply_spim_wiring, apply_spim_wiring_with_log, - presets, spim_external_mux_state, SpimWiring, SpimWiringError, + enable_flash_power, presets, set_bmc_resets, spim_external_mux_state, SpimWiring, + SpimWiringError, }; pub use ast10x0_peripherals::i2c::{I2cConfig, I2cError}; @@ -118,7 +119,7 @@ impl Ast10x0Board { /// This is a placeholder; production code should use a proper timer or delay provider. /// Spins for approximately `micros` microseconds. #[inline] -pub(crate) fn delay_us(micros: u32) { +pub 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 f540d52b..1aa4fb18 100644 --- a/target/ast10x0/board/src/spim_wiring.rs +++ b/target/ast10x0/board/src/spim_wiring.rs @@ -12,8 +12,8 @@ use ast10x0_peripherals::scu::{ pinctrl::{ - PINCTRL_SPIM1_DEFAULT, PINCTRL_SPIM2_DEFAULT, PINCTRL_SPIM3_DEFAULT, - PINCTRL_SPIM4_DEFAULT, + PINCTRL_GPIOL2, PINCTRL_GPIOL3, PINCTRL_SPIM1_DEFAULT, PINCTRL_SPIM2_DEFAULT, + PINCTRL_SPIM3_DEFAULT, PINCTRL_SPIM4_DEFAULT, }, ScuError, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource, @@ -202,6 +202,82 @@ pub fn spim_external_mux_state( } } +/// Enable the flash-power outputs required by older AST1060 demo boards. +#[must_use] +pub fn enable_flash_power(scu: &ScuRegisters) -> bool { + scu.apply_pinctrl_group(PINCTRL_GPIOL2); + scu.apply_pinctrl_group(PINCTRL_GPIOL3); + + const FLASH_POWER_MASK: u32 = (1 << 26) | (1 << 27); + let gpio = unsafe { &*device::Gpio::ptr() }; + gpio.gpio070().modify(|r, w| unsafe { + w.bits(r.bits() | FLASH_POWER_MASK) + }); + gpio.gpio074().modify(|r, w| unsafe { + w.bits(r.bits() | FLASH_POWER_MASK) + }); + crate::delay_us(1_000); + + gpio.gpio0c8().read().bits() & FLASH_POWER_MASK == FLASH_POWER_MASK +} + +/// Assert or release the active-low BMC reset outputs. +/// +/// The prot board routes BMC_SRST to SGPIOM output 8 and BMC_EXTRST to +/// SGPIOM output 9. On assertion EXTRST is driven low first; on release SRST +/// is driven high first. +#[must_use] +pub fn set_bmc_resets(asserted: bool) -> bool { + const BMC_SRST_MASK: u32 = 1 << 8; + const BMC_EXTRST_MASK: u32 = 1 << 9; + + let scu = unsafe { &*device::Scu::ptr() }; + scu.scu41c().modify(|_, w| { + w.enbl_sgpiomaster_ckfn_pin() + .set_bit() + .enbl_sgpiomaster_ldfn_pin() + .set_bit() + .enbl_sgpiomaster_dofn_pin() + .set_bit() + .enbl_sgpiomaster_difn_pin() + .set_bit() + }); + + let sgpio = unsafe { &*device::Sgpiom::ptr() }; + sgpio.gpio554().modify(|_, w| unsafe { + w.enbl_of_serial_gpio() + .set_bit() + .numbers_of_serial_gpiopins() + .bits(16) + .serial_gpioclk_division() + .bits(24) + }); + + let output_high = !asserted; + let first_mask = if asserted { + BMC_EXTRST_MASK + } else { + BMC_SRST_MASK + }; + let second_mask = if asserted { + BMC_SRST_MASK + } else { + BMC_EXTRST_MASK + }; + + for mask in [first_mask, second_mask] { + let latch = sgpio.gpio570().read().bits(); + sgpio + .gpio500() + .write(|w| unsafe { w.bits(update_bit(latch, mask, output_high)) }); + crate::delay_us(10_000); + } + + let latch = sgpio.gpio570().read().bits(); + let reset_mask = BMC_SRST_MASK | BMC_EXTRST_MASK; + (latch & reset_mask == reset_mask) == output_high +} + #[derive(Clone, Copy)] enum ExternalMuxGpioGroup { Abcd, diff --git a/target/ast10x0/peripherals/scu/pinctrl.rs b/target/ast10x0/peripherals/scu/pinctrl.rs index 9d90f544..2b5f8721 100644 --- a/target/ast10x0/peripherals/scu/pinctrl.rs +++ b/target/ast10x0/peripherals/scu/pinctrl.rs @@ -538,8 +538,14 @@ paste! { } // GPIO +pub const PINCTRL_GPIOH2: &[PinctrlPin] = &[ + CLR_PIN_SCU414_26, + CLR_PIN_SCU4B4_26, + CLR_PIN_SCU694_26, +]; pub const PINCTRL_GPIOL2: &[PinctrlPin] = &[CLR_PIN_SCU418_26]; pub const PINCTRL_GPIOL3: &[PinctrlPin] = &[CLR_PIN_SCU418_27]; +pub const PINCTRL_GPIOM5: &[PinctrlPin] = &[CLR_PIN_SCU41C_5]; /// I2C1 pin group: SCL2/SDA2 mux selection on SCU414[30:31]. /// diff --git a/target/ast10x0/peripherals/smc/controller.rs b/target/ast10x0/peripherals/smc/controller.rs index eaf74dea..bef00038 100644 --- a/target/ast10x0/peripherals/smc/controller.rs +++ b/target/ast10x0/peripherals/smc/controller.rs @@ -618,8 +618,7 @@ impl Smc { let reg = self.regs.read_cs_ctrl(cs); self.regs .write_cs_ctrl(cs, (reg & !SPI_CTRL_FREQ_MASK) | encoded_div); - let val = self.normal_read_ctrl(cs) & ((!SPI_CTRL_FREQ_MASK) | encoded_div); - self.set_normal_read_ctrl(cs, val); + self.normal_read_ctrl[cs_idx] = self.regs.read_cs_ctrl(cs); Ok(()) } diff --git a/target/ast10x0/tests/smc/read/BUILD.bazel b/target/ast10x0/tests/smc/read/BUILD.bazel index 3caf6936..d437777e 100644 --- a/target/ast10x0/tests/smc/read/BUILD.bazel +++ b/target/ast10x0/tests/smc/read/BUILD.bazel @@ -63,8 +63,8 @@ rust_binary( ":codegen", ":linker_script", "//target/ast10x0:entry", + "//target/ast10x0/board:ast10x0_board", "//target/ast10x0/peripherals", - "@ast1060_pac", "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", "@pigweed//pw_kernel/kernel", "@pigweed//pw_kernel/subsys/console:console_backend", diff --git a/target/ast10x0/tests/smc/read/target_spi2.rs b/target/ast10x0/tests/smc/read/target_spi2.rs index 43759a1d..79018772 100644 --- a/target/ast10x0/tests/smc/read/target_spi2.rs +++ b/target/ast10x0/tests/smc/read/target_spi2.rs @@ -5,12 +5,11 @@ #![no_std] #![no_main] -use ast1060_pac as device; +use ast10x0_board::{apply_spim_external_mux, enable_flash_power}; #[allow(unused_imports)] use ast10x0_peripherals::scu::{ pinctrl::{ - PINCTRL_GPIOL2, PINCTRL_GPIOL3, PINCTRL_SPI2_QUAD, PINCTRL_SPIM3_DEFAULT, - PINCTRL_SPIM4_DEFAULT, + PINCTRL_SPI2_QUAD, PINCTRL_SPIM3_DEFAULT, PINCTRL_SPIM4_DEFAULT, }, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource, }; @@ -35,91 +34,17 @@ const SPI_FLASH_CONFIG: FlashConfig = FlashConfig { }; pub struct Target {} -/* -SCU418 = 0x7E6E_2418, clear bits 26, 27 -GPIO070 = 0x7E78_0070, set bits 26, 27 -GPIO074 = 0x7E78_0074, set bits 26, 27 -*/ -fn gpio_flash_power() { - const GPIOL2_L3_MASK: u32 = (1 << 26) | (1 << 27); - - // SAFETY: Board initialization has exclusive access to the PAC singleton. - let gpio = unsafe { &*device::Gpio::ptr() }; - gpio.gpio070() - .modify(|r, w| unsafe { w.bits(r.bits() | GPIOL2_L3_MASK) }); - gpio.gpio074() - .modify(|r, w| unsafe { w.bits(r.bits() | GPIOL2_L3_MASK) }); - - for _ in 0..1_000_000 { - core::hint::spin_loop(); - } -} - -#[allow(dead_code)] -fn configure_spi2_external_mux(select_mux1: bool) { - const GPIO_E8: u32 = 1 << 8; - const SGPIOM_A_D_2: u32 = 1 << 2; - - // SAFETY: Board initialization has exclusive access to the PAC singletons. - let gpio = unsafe { &*device::Gpio::ptr() }; - let sgpio = unsafe { &*device::Sgpiom::ptr() }; - let _scu_unlocked = unsafe { ScuRegisters::new_global_unlocked() }; - let scu = unsafe { &*device::Scu::ptr() }; - - scu.scu41c().modify(|_, w| { - w.enbl_sgpiomaster_ckfn_pin() - .set_bit() - .enbl_sgpiomaster_ldfn_pin() - .set_bit() - .enbl_sgpiomaster_dofn_pin() - .set_bit() - .enbl_sgpiomaster_difn_pin() - .set_bit() - }); - sgpio.gpio554().modify(|_, w| unsafe { - w.enbl_of_serial_gpio() - .set_bit() - .numbers_of_serial_gpiopins() - .bits(16) - .serial_gpioclk_division() - .bits(24) - }); - - gpio.gpio020().modify(|r, w| unsafe { - let bits = if select_mux1 { - r.bits() | GPIO_E8 - } else { - r.bits() & !GPIO_E8 - }; - w.bits(bits) - }); - gpio.gpio024() - .modify(|r, w| unsafe { w.bits(r.bits() | GPIO_E8) }); - - let sgpio_latch = sgpio.gpio570().read().bits(); - let sgpio_data = if select_mux1 { - sgpio_latch | SGPIOM_A_D_2 - } else { - sgpio_latch & !SGPIOM_A_D_2 - }; - sgpio.gpio500().write(|w| unsafe { w.bits(sgpio_data) }); - - // Match the overlay's ext-mux-sel-delay-us = <1000>. - for _ in 0..100_000 { - core::hint::spin_loop(); - } -} fn config_spi2_master_controller() -> Result<(), SmcError> { let scu = unsafe { ScuRegisters::new_global_unlocked() }; scu.apply_pinctrl_group(PINCTRL_SPIM3_DEFAULT); scu.apply_pinctrl_group(PINCTRL_SPIM4_DEFAULT); scu.apply_pinctrl_group(PINCTRL_SPI2_QUAD); - scu.apply_pinctrl_group(PINCTRL_GPIOL2); - scu.apply_pinctrl_group(PINCTRL_GPIOL3); - gpio_flash_power(); - //configure spi2 external mux through gpio pins - configure_spi2_external_mux(true); + if !enable_flash_power(&scu) { + pw_log::info!("GPIOL2/GPIOL3 flash power readback failed"); + return Err(SmcError::HardwareError); + } + apply_spim_external_mux(SpiMonitorInstance::Spim2, ScuExtMuxSelect::Mux1); // Configure the mux for the SPI master controller path. scu.set_spim_internal_master_route(SpiMonitorInstance::Spim2, SpiMonitorSource::Spi2); diff --git a/target/ast10x0/tests/spimonitor/setup_all_spim.rs b/target/ast10x0/tests/spimonitor/setup_all_spim.rs index a0419598..820dac60 100644 --- a/target/ast10x0/tests/spimonitor/setup_all_spim.rs +++ b/target/ast10x0/tests/spimonitor/setup_all_spim.rs @@ -11,6 +11,7 @@ use core::cell::UnsafeCell; #[path = "test_common.rs"] mod test_common; +use ast10x0_board::{delay_us, enable_flash_power, set_bmc_resets}; use ast10x0_peripherals::scu::{ScuRegisters, SpiMonitorInstance}; use ast10x0_peripherals::spimonitor::{ MonitorPolicy, PrivilegeDirection, PrivilegeOp, SpiMonitorController, @@ -60,6 +61,9 @@ const ALLOW_COMMANDS: [u8; 32] = [ 0xb7, 0xe9, 0x32, 0x34, 0xd8, 0xdc, 0x02, 0x12, 0x3b, 0x3c, 0x70, 0xbb, 0xbc, 0x50, 0xeb, 0xec, 0xc2, ]; +const SPIM4_ALLOW_COMMANDS: [u8; 15] = [ + 0x01, 0x06, 0x04, 0x20, 0x21, 0xb7, 0xe9, 0x32, 0x34, 0xd8, 0xdc, 0x02, 0x12, 0x50, 0xc2, +]; fn log_buffer(log: &'static LogRam) -> &'static mut [u32] { // SAFETY: The caller assigns each static buffer to exactly one monitor. @@ -79,9 +83,38 @@ fn production_policy() -> MonitorPolicy { policy } +fn spim4_test_policy() -> MonitorPolicy { + let mut policy = MonitorPolicy::empty(); + policy.allow_commands[..SPIM4_ALLOW_COMMANDS.len()] + .copy_from_slice(&SPIM4_ALLOW_COMMANDS); + policy.allow_command_count = SPIM4_ALLOW_COMMANDS.len(); + let _ = policy.add_region( + 0, + WRITE_PROTECTED_LENGTH, + PrivilegeDirection::Write, + PrivilegeOp::Disable, + ); + policy +} + fn setup_all_spim() -> Result<(), test_common::TestError> { let scu = unsafe { ScuRegisters::new_global_unlocked() }; let policy = production_policy(); + let spim4_policy = spim4_test_policy(); + + pw_log::info!("=== GPIO flash power ==="); + if !enable_flash_power(&scu) { + pw_log::info!("FAIL: GPIOL2/GPIOL3 flash power readback"); + return Err(test_common::TestError::Check); + } + pw_log::info!("PASS: GPIOL2 and GPIOL3 set high"); + + pw_log::info!("=== Hold BMC in reset ==="); + if !set_bmc_resets(true) { + pw_log::info!("FAIL: SGPIOM BMC reset outputs did not assert"); + return Err(test_common::TestError::Check); + } + pw_log::info!("PASS: SGPIOM outputs 8 and 9 asserted low"); pw_log::info!("=== SPIM1 ==="); test_common::configure_wiring::(&scu)?; @@ -111,15 +144,31 @@ fn setup_all_spim() -> Result<(), test_common::TestError> { let _spim3 = test_common::lock_monitor(spim3)?; pw_log::info!("=== SPIM4 ==="); + pw_log::info!("SPIM4 test policy blocks all read commands"); test_common::configure_wiring::(&scu)?; let spim4 = test_common::initialize_monitor_with_policy::( log_buffer(&SPIM4_LOG), - &policy, + &spim4_policy, )?; test_common::dump_policy(&spim4, WRITE_PROTECTED_LENGTH)?; let _spim4 = test_common::lock_monitor(spim4)?; pw_log::info!("All SPIM1-4 monitors are configured and locked"); + pw_log::info!( + "SCU0F0 after SPIM setup: 0x{:08x}", + scu.route_control_raw() as u32 + ); + + pw_log::info!("Waiting 60 ms for flash routing to settle"); + delay_us(60_000); + + pw_log::info!("=== Release BMC reset ==="); + if !set_bmc_resets(false) { + pw_log::info!("FAIL: SGPIOM BMC reset outputs did not release"); + return Err(test_common::TestError::Check); + } + pw_log::info!("PASS: SGPIOM outputs 8 and 9 released high"); + pw_log::info!("Firmware will remain active until the user stops or resets it"); Ok(()) } diff --git a/target/ast10x0/tests/spimonitor/test_common.rs b/target/ast10x0/tests/spimonitor/test_common.rs index d252f89c..837930ff 100644 --- a/target/ast10x0/tests/spimonitor/test_common.rs +++ b/target/ast10x0/tests/spimonitor/test_common.rs @@ -90,17 +90,14 @@ pub fn configure_wiring(scu: &ScuRegisters) -> Result<(), TestErr scu.set_spim_miso_multi_func(C::INSTANCE, true); scu.set_spim_filter(C::INSTANCE, true); - apply_spim_external_mux(C::INSTANCE, ScuExtMuxSelect::Mux0); - test_check!( - spim_external_mux_state(C::INSTANCE) == Some(ScuExtMuxSelect::Mux0), - "FAIL: external mux 0 GPIO readback" - ); + // This board provides ext-mux-sel-gpios. Match the Zephyr driver by + // driving those GPIOs only; SCU0F0[15:12] is used only when no external + // mux GPIOs are described. apply_spim_external_mux(C::INSTANCE, ScuExtMuxSelect::Mux1); test_check!( spim_external_mux_state(C::INSTANCE) == Some(ScuExtMuxSelect::Mux1), "FAIL: external mux 1 GPIO readback" ); - scu.set_spim_ext_mux(C::INSTANCE, ScuExtMuxSelect::Mux1); test_check!( scu.route_control_raw() & 0x0f == 0, From 8aaf9eac83b1f39e53ce298e075860825a588fac Mon Sep 17 00:00:00 2001 From: hailin wu Date: Fri, 12 Jun 2026 09:55:40 -0700 Subject: [PATCH 09/13] Complete the AST1060 SPI-monitor handoff from the RoT to the external BMC master and Host. Configure board-level GPIO: - BMC: mux select bit 0, active-low OE bit 1, reset bit 7 - Host: mux select bit 2, active-low OE bit 3, reset bit 6 Switch the shared SPIM1/2 BMC mux from RoT value 1 to BMC value 0. --- target/ast10x0/board/src/spim_wiring.rs | 56 +++++++++++++++---- target/ast10x0/peripherals/scu/routing.rs | 34 +++++++++++ .../tests/spimonitor/setup_all_spim.rs | 52 +++++++++-------- .../ast10x0/tests/spimonitor/test_common.rs | 29 +++++++++- 4 files changed, 135 insertions(+), 36 deletions(-) diff --git a/target/ast10x0/board/src/spim_wiring.rs b/target/ast10x0/board/src/spim_wiring.rs index 1aa4fb18..a22224ac 100644 --- a/target/ast10x0/board/src/spim_wiring.rs +++ b/target/ast10x0/board/src/spim_wiring.rs @@ -110,18 +110,30 @@ pub fn apply_spim_pinctrl(scu: &ScuRegisters, instance: SpiMonitorInstance) { scu.apply_pinctrl_group(group); } -/// Drive the external mux-select GPIO pair described by the AST1060 DTS. +/// Configure the external flash mux controls described by the board schematic. /// -/// 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. +/// SPIM1/2 use GPIO A-D pin 12 plus SGPIOM select/OE/reset bits 0/1/7. +/// SPIM3/4 use GPIO E-H pin 8 plus SGPIOM select/OE/reset bits 2/3/6. 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 { + let (gpio_group, gpio_mask, sgpio_select, sgpio_oe_n, sgpio_reset_n) = match instance { SpiMonitorInstance::Spim0 | SpiMonitorInstance::Spim1 => { - (ExternalMuxGpioGroup::Abcd, 1 << 12, 1 << 0) + ( + ExternalMuxGpioGroup::Abcd, + 1 << 12, + 1 << 0, + 1 << 1, + 1 << 7, + ) } SpiMonitorInstance::Spim2 | SpiMonitorInstance::Spim3 => { - (ExternalMuxGpioGroup::Efgh, 1 << 8, 1 << 2) + ( + ExternalMuxGpioGroup::Efgh, + 1 << 8, + 1 << 2, + 1 << 3, + 1 << 6, + ) } }; @@ -166,24 +178,38 @@ pub fn apply_spim_external_mux(instance: SpiMonitorInstance, mux: ScuExtMuxSelec .bits(24) }); let latch = sgpio.gpio570().read().bits(); + let control_mask = sgpio_select | sgpio_oe_n | sgpio_reset_n; + let control_value = if high { sgpio_select } else { 0 } | sgpio_reset_n; sgpio .gpio500() - .write(|w| unsafe { w.bits(update_bit(latch, sgpio_mask, high)) }); + .write(|w| unsafe { w.bits((latch & !control_mask) | control_value) }); crate::delay_us(1_000); } -/// Read back the two board-level external mux-select outputs. +/// Read back the board-level mux selection, output enable, and flash reset. #[must_use] pub fn spim_external_mux_state( instance: SpiMonitorInstance, ) -> Option { - let (gpio_group, gpio_mask, sgpio_mask) = match instance { + let (gpio_group, gpio_mask, sgpio_select, sgpio_oe_n, sgpio_reset_n) = match instance { SpiMonitorInstance::Spim0 | SpiMonitorInstance::Spim1 => { - (ExternalMuxGpioGroup::Abcd, 1 << 12, 1 << 0) + ( + ExternalMuxGpioGroup::Abcd, + 1 << 12, + 1 << 0, + 1 << 1, + 1 << 7, + ) } SpiMonitorInstance::Spim2 | SpiMonitorInstance::Spim3 => { - (ExternalMuxGpioGroup::Efgh, 1 << 8, 1 << 2) + ( + ExternalMuxGpioGroup::Efgh, + 1 << 8, + 1 << 2, + 1 << 3, + 1 << 6, + ) } }; let gpio = unsafe { &*device::Gpio::ptr() }; @@ -192,7 +218,13 @@ pub fn spim_external_mux_state( ExternalMuxGpioGroup::Efgh => gpio.gpio020().read().bits() & gpio_mask != 0, }; let sgpio = unsafe { &*device::Sgpiom::ptr() }; - let sgpio_high = sgpio.gpio570().read().bits() & sgpio_mask != 0; + let latch = sgpio.gpio570().read().bits(); + let sgpio_high = latch & sgpio_select != 0; + let mux_enabled = latch & sgpio_oe_n == 0; + let flash_reset_released = latch & sgpio_reset_n != 0; + if !mux_enabled || !flash_reset_released { + return None; + } if gpio_high != sgpio_high { None } else if gpio_high { diff --git a/target/ast10x0/peripherals/scu/routing.rs b/target/ast10x0/peripherals/scu/routing.rs index b99d1bd7..18a9a1fe 100644 --- a/target/ast10x0/peripherals/scu/routing.rs +++ b/target/ast10x0/peripherals/scu/routing.rs @@ -208,6 +208,40 @@ impl ScuRegisters { }); } + /// Route the external flash-reset input through one SPIPF. + /// + /// Clears the instance's reset signal and source-selection bits in + /// SCU0F0[19:16] and [23:20], and enables its reset output in [27:24]. + pub fn configure_spim_external_flash_reset(&self, instance: SpiMonitorInstance) { + self.unlock_write_protection(); + let instance_bit = match instance { + SpiMonitorInstance::Spim0 => 1 << 0, + SpiMonitorInstance::Spim1 => 1 << 1, + SpiMonitorInstance::Spim2 => 1 << 2, + SpiMonitorInstance::Spim3 => 1 << 3, + }; + let clear_mask = (instance_bit << 16) | (instance_bit << 20); + let output_enable = instance_bit << 24; + self.regs() + .scu0f0() + .modify(|r, w| unsafe { w.bits((r.bits() & !clear_mask) | output_enable) }); + } + + /// Check the external flash-reset source and output-enable configuration. + #[must_use] + pub fn is_spim_external_flash_reset_configured(&self, instance: SpiMonitorInstance) -> bool { + let instance_bit = match instance { + SpiMonitorInstance::Spim0 => 1 << 0, + SpiMonitorInstance::Spim1 => 1 << 1, + SpiMonitorInstance::Spim2 => 1 << 2, + SpiMonitorInstance::Spim3 => 1 << 3, + }; + let cleared_mask = (instance_bit << 16) | (instance_bit << 20); + let output_enable = instance_bit << 24; + let value = self.regs().scu0f0().read().bits(); + value & cleared_mask == 0 && value & output_enable == output_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(); diff --git a/target/ast10x0/tests/spimonitor/setup_all_spim.rs b/target/ast10x0/tests/spimonitor/setup_all_spim.rs index 820dac60..c94200e0 100644 --- a/target/ast10x0/tests/spimonitor/setup_all_spim.rs +++ b/target/ast10x0/tests/spimonitor/setup_all_spim.rs @@ -11,8 +11,12 @@ use core::cell::UnsafeCell; #[path = "test_common.rs"] mod test_common; -use ast10x0_board::{delay_us, enable_flash_power, set_bmc_resets}; -use ast10x0_peripherals::scu::{ScuRegisters, SpiMonitorInstance}; +use test_common::TestConfig; +use ast10x0_board::{ + apply_spim_external_mux, delay_us, enable_flash_power, set_bmc_resets, + spim_external_mux_state, +}; +use ast10x0_peripherals::scu::{ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance}; use ast10x0_peripherals::spimonitor::{ MonitorPolicy, PrivilegeDirection, PrivilegeOp, SpiMonitorController, }; @@ -61,9 +65,6 @@ const ALLOW_COMMANDS: [u8; 32] = [ 0xb7, 0xe9, 0x32, 0x34, 0xd8, 0xdc, 0x02, 0x12, 0x3b, 0x3c, 0x70, 0xbb, 0xbc, 0x50, 0xeb, 0xec, 0xc2, ]; -const SPIM4_ALLOW_COMMANDS: [u8; 15] = [ - 0x01, 0x06, 0x04, 0x20, 0x21, 0xb7, 0xe9, 0x32, 0x34, 0xd8, 0xdc, 0x02, 0x12, 0x50, 0xc2, -]; fn log_buffer(log: &'static LogRam) -> &'static mut [u32] { // SAFETY: The caller assigns each static buffer to exactly one monitor. @@ -83,24 +84,10 @@ fn production_policy() -> MonitorPolicy { policy } -fn spim4_test_policy() -> MonitorPolicy { - let mut policy = MonitorPolicy::empty(); - policy.allow_commands[..SPIM4_ALLOW_COMMANDS.len()] - .copy_from_slice(&SPIM4_ALLOW_COMMANDS); - policy.allow_command_count = SPIM4_ALLOW_COMMANDS.len(); - let _ = policy.add_region( - 0, - WRITE_PROTECTED_LENGTH, - PrivilegeDirection::Write, - PrivilegeOp::Disable, - ); - policy -} - fn setup_all_spim() -> Result<(), test_common::TestError> { let scu = unsafe { ScuRegisters::new_global_unlocked() }; let policy = production_policy(); - let spim4_policy = spim4_test_policy(); + //let spim4_policy = spim4_test_policy(); pw_log::info!("=== GPIO flash power ==="); if !enable_flash_power(&scu) { @@ -123,6 +110,7 @@ fn setup_all_spim() -> Result<(), test_common::TestError> { &policy, )?; test_common::dump_policy(&spim1, WRITE_PROTECTED_LENGTH)?; + test_common::configure_passthrough::(&spim1)?; let _spim1 = test_common::lock_monitor(spim1)?; pw_log::info!("=== SPIM2 ==="); @@ -132,6 +120,7 @@ fn setup_all_spim() -> Result<(), test_common::TestError> { &policy, )?; test_common::dump_policy(&spim2, WRITE_PROTECTED_LENGTH)?; + test_common::configure_passthrough::(&spim2)?; let _spim2 = test_common::lock_monitor(spim2)?; pw_log::info!("=== SPIM3 ==="); @@ -141,24 +130,41 @@ fn setup_all_spim() -> Result<(), test_common::TestError> { &policy, )?; test_common::dump_policy(&spim3, WRITE_PROTECTED_LENGTH)?; + test_common::configure_passthrough::(&spim3)?; let _spim3 = test_common::lock_monitor(spim3)?; pw_log::info!("=== SPIM4 ==="); - pw_log::info!("SPIM4 test policy blocks all read commands"); test_common::configure_wiring::(&scu)?; let spim4 = test_common::initialize_monitor_with_policy::( log_buffer(&SPIM4_LOG), - &spim4_policy, + &policy, )?; test_common::dump_policy(&spim4, WRITE_PROTECTED_LENGTH)?; + test_common::configure_passthrough::(&spim4)?; let _spim4 = test_common::lock_monitor(spim4)?; - pw_log::info!("All SPIM1-4 monitors are configured and locked"); + if scu.route_control_raw() & 0x0fff_0000 != 0x0f00_0000 { + pw_log::info!( + "FAIL: final SPIPF reset routing: 0x{:08x}", + scu.route_control_raw() as u32 + ); + return Err(test_common::TestError::Check); + } + pw_log::info!("All SPIM1-4 paths are configured in passthrough mode and locked"); + pw_log::info!("SPI clock frequency must be configured to 25 MHz by the external master"); pw_log::info!( "SCU0F0 after SPIM setup: 0x{:08x}", scu.route_control_raw() as u32 ); + pw_log::info!("=== Hand BMC flash mux to external BMC master ==="); + apply_spim_external_mux(Spim1Config::INSTANCE, ScuExtMuxSelect::Mux0); + if spim_external_mux_state(Spim1Config::INSTANCE) != Some(ScuExtMuxSelect::Mux0) { + pw_log::info!("FAIL: SPIM1/2 external BMC mux handoff"); + return Err(test_common::TestError::Check); + } + pw_log::info!("PASS: SPIM1/2 mux switched from RoT=1 to BMC=0"); + pw_log::info!("Waiting 60 ms for flash routing to settle"); delay_us(60_000); diff --git a/target/ast10x0/tests/spimonitor/test_common.rs b/target/ast10x0/tests/spimonitor/test_common.rs index 837930ff..9286ca4e 100644 --- a/target/ast10x0/tests/spimonitor/test_common.rs +++ b/target/ast10x0/tests/spimonitor/test_common.rs @@ -8,7 +8,7 @@ use core::cell::UnsafeCell; use ast10x0_board::{ - apply_spim_external_mux, apply_spim_pinctrl, spim_external_mux_state, + apply_spim_external_mux, apply_spim_pinctrl, delay_us, spim_external_mux_state, }; use ast10x0_peripherals::scu::{ ScuError, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, @@ -139,6 +139,14 @@ pub fn initialize_monitor_with_policy( "FAIL: software reset did not deassert" ); + let scu = unsafe { ScuRegisters::new_global_unlocked() }; + scu.configure_spim_external_flash_reset(C::INSTANCE); + test_check!( + scu.is_spim_external_flash_reset_configured(C::INSTANCE), + "FAIL: SPIPF external flash reset routing readback" + ); + delay_us(5_000); + let configured = monitor.apply_policy(policy)?; test_check!( configured.state() == MonitorState::Configured, @@ -191,6 +199,25 @@ pub fn dump_policy( Ok(()) } +#[allow(dead_code)] +pub fn configure_passthrough( + configured: &ConfiguredSpiMonitor, +) -> Result<(), TestError> { + configured.disable(); + configured.set_passthrough(PassthroughMode::Enabled); + + let scu = unsafe { ScuRegisters::new_global_unlocked() }; + scu.set_spim_passthrough(C::INSTANCE, SpiMonitorPassthrough::Enabled); + scu.set_spim_miso_multi_func(C::INSTANCE, false); + + test_check!( + configured.regs().read_ctrl() & 0x7 == 0x1, + "FAIL: SPIPF passthrough mode readback" + ); + pw_log::info!("PASS: SPIPF single-bit passthrough mode"); + Ok(()) +} + #[allow(dead_code)] pub fn lock_monitor( configured: ConfiguredSpiMonitor, From a359ee865f1487b8326259f4e7adabb0e2b81cad Mon Sep 17 00:00:00 2001 From: hailin wu Date: Fri, 12 Jun 2026 10:46:29 -0700 Subject: [PATCH 10/13] now we can see bmc spi and host spi on Master side. HOST spimonitor works for spi2@0 and spi2@1. --- target/ast10x0/board/src/spim_wiring.rs | 68 +++++-------------- target/ast10x0/peripherals/scu/pinctrl.rs | 7 +- target/ast10x0/peripherals/scu/routing.rs | 34 ---------- .../peripherals/spimonitor/commands.rs | 6 +- .../peripherals/spimonitor/controller.rs | 31 +++------ target/ast10x0/peripherals/spimonitor/mod.rs | 2 +- .../ast10x0/peripherals/spimonitor/profile.rs | 6 +- target/ast10x0/tests/smc/read/target_spi2.rs | 4 +- .../tests/spimonitor/setup_all_spim.rs | 29 ++++---- .../ast10x0/tests/spimonitor/test_common.rs | 49 +++++++------ 10 files changed, 81 insertions(+), 155 deletions(-) diff --git a/target/ast10x0/board/src/spim_wiring.rs b/target/ast10x0/board/src/spim_wiring.rs index a22224ac..701b26d6 100644 --- a/target/ast10x0/board/src/spim_wiring.rs +++ b/target/ast10x0/board/src/spim_wiring.rs @@ -10,6 +10,7 @@ //! (`peripherals/spimonitor/planning/overview-and-usage-model.md`) calls for //! "configure early, validate, lock, and operate under that locked policy." +use ast1060_pac as device; use ast10x0_peripherals::scu::{ pinctrl::{ PINCTRL_GPIOL2, PINCTRL_GPIOL3, PINCTRL_SPIM1_DEFAULT, PINCTRL_SPIM2_DEFAULT, @@ -23,7 +24,6 @@ use ast10x0_peripherals::spimonitor::{ LockedSpiMonitor, MonitorPolicy, PassthroughMode, SpiMonitor, SpiMonitorController, SpiMonitorError, Uninitialized, }; -use ast1060_pac as device; /// Static wiring for one external SPI monitor path. /// @@ -118,22 +118,10 @@ pub fn apply_spim_external_mux(instance: SpiMonitorInstance, mux: ScuExtMuxSelec let high = matches!(mux, ScuExtMuxSelect::Mux1); let (gpio_group, gpio_mask, sgpio_select, sgpio_oe_n, sgpio_reset_n) = match instance { SpiMonitorInstance::Spim0 | SpiMonitorInstance::Spim1 => { - ( - ExternalMuxGpioGroup::Abcd, - 1 << 12, - 1 << 0, - 1 << 1, - 1 << 7, - ) + (ExternalMuxGpioGroup::Abcd, 1 << 12, 1 << 0, 1 << 1, 1 << 7) } SpiMonitorInstance::Spim2 | SpiMonitorInstance::Spim3 => { - ( - ExternalMuxGpioGroup::Efgh, - 1 << 8, - 1 << 2, - 1 << 3, - 1 << 6, - ) + (ExternalMuxGpioGroup::Efgh, 1 << 8, 1 << 2, 1 << 3, 1 << 6) } }; @@ -151,20 +139,16 @@ pub fn apply_spim_external_mux(instance: SpiMonitorInstance, mux: ScuExtMuxSelec }); 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) - }); + 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) - }); + 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) }); } } @@ -189,27 +173,13 @@ pub fn apply_spim_external_mux(instance: SpiMonitorInstance, mux: ScuExtMuxSelec /// Read back the board-level mux selection, output enable, and flash reset. #[must_use] -pub fn spim_external_mux_state( - instance: SpiMonitorInstance, -) -> Option { +pub fn spim_external_mux_state(instance: SpiMonitorInstance) -> Option { let (gpio_group, gpio_mask, sgpio_select, sgpio_oe_n, sgpio_reset_n) = match instance { SpiMonitorInstance::Spim0 | SpiMonitorInstance::Spim1 => { - ( - ExternalMuxGpioGroup::Abcd, - 1 << 12, - 1 << 0, - 1 << 1, - 1 << 7, - ) + (ExternalMuxGpioGroup::Abcd, 1 << 12, 1 << 0, 1 << 1, 1 << 7) } SpiMonitorInstance::Spim2 | SpiMonitorInstance::Spim3 => { - ( - ExternalMuxGpioGroup::Efgh, - 1 << 8, - 1 << 2, - 1 << 3, - 1 << 6, - ) + (ExternalMuxGpioGroup::Efgh, 1 << 8, 1 << 2, 1 << 3, 1 << 6) } }; let gpio = unsafe { &*device::Gpio::ptr() }; @@ -242,12 +212,10 @@ pub fn enable_flash_power(scu: &ScuRegisters) -> bool { const FLASH_POWER_MASK: u32 = (1 << 26) | (1 << 27); let gpio = unsafe { &*device::Gpio::ptr() }; - gpio.gpio070().modify(|r, w| unsafe { - w.bits(r.bits() | FLASH_POWER_MASK) - }); - gpio.gpio074().modify(|r, w| unsafe { - w.bits(r.bits() | FLASH_POWER_MASK) - }); + gpio.gpio070() + .modify(|r, w| unsafe { w.bits(r.bits() | FLASH_POWER_MASK) }); + gpio.gpio074() + .modify(|r, w| unsafe { w.bits(r.bits() | FLASH_POWER_MASK) }); crate::delay_us(1_000); gpio.gpio0c8().read().bits() & FLASH_POWER_MASK == FLASH_POWER_MASK diff --git a/target/ast10x0/peripherals/scu/pinctrl.rs b/target/ast10x0/peripherals/scu/pinctrl.rs index 2b5f8721..203fe2ac 100644 --- a/target/ast10x0/peripherals/scu/pinctrl.rs +++ b/target/ast10x0/peripherals/scu/pinctrl.rs @@ -538,11 +538,8 @@ paste! { } // GPIO -pub const PINCTRL_GPIOH2: &[PinctrlPin] = &[ - CLR_PIN_SCU414_26, - CLR_PIN_SCU4B4_26, - CLR_PIN_SCU694_26, -]; +pub const PINCTRL_GPIOH2: &[PinctrlPin] = + &[CLR_PIN_SCU414_26, CLR_PIN_SCU4B4_26, CLR_PIN_SCU694_26]; pub const PINCTRL_GPIOL2: &[PinctrlPin] = &[CLR_PIN_SCU418_26]; pub const PINCTRL_GPIOL3: &[PinctrlPin] = &[CLR_PIN_SCU418_27]; pub const PINCTRL_GPIOM5: &[PinctrlPin] = &[CLR_PIN_SCU41C_5]; diff --git a/target/ast10x0/peripherals/scu/routing.rs b/target/ast10x0/peripherals/scu/routing.rs index 18a9a1fe..b99d1bd7 100644 --- a/target/ast10x0/peripherals/scu/routing.rs +++ b/target/ast10x0/peripherals/scu/routing.rs @@ -208,40 +208,6 @@ impl ScuRegisters { }); } - /// Route the external flash-reset input through one SPIPF. - /// - /// Clears the instance's reset signal and source-selection bits in - /// SCU0F0[19:16] and [23:20], and enables its reset output in [27:24]. - pub fn configure_spim_external_flash_reset(&self, instance: SpiMonitorInstance) { - self.unlock_write_protection(); - let instance_bit = match instance { - SpiMonitorInstance::Spim0 => 1 << 0, - SpiMonitorInstance::Spim1 => 1 << 1, - SpiMonitorInstance::Spim2 => 1 << 2, - SpiMonitorInstance::Spim3 => 1 << 3, - }; - let clear_mask = (instance_bit << 16) | (instance_bit << 20); - let output_enable = instance_bit << 24; - self.regs() - .scu0f0() - .modify(|r, w| unsafe { w.bits((r.bits() & !clear_mask) | output_enable) }); - } - - /// Check the external flash-reset source and output-enable configuration. - #[must_use] - pub fn is_spim_external_flash_reset_configured(&self, instance: SpiMonitorInstance) -> bool { - let instance_bit = match instance { - SpiMonitorInstance::Spim0 => 1 << 0, - SpiMonitorInstance::Spim1 => 1 << 1, - SpiMonitorInstance::Spim2 => 1 << 2, - SpiMonitorInstance::Spim3 => 1 << 3, - }; - let cleared_mask = (instance_bit << 16) | (instance_bit << 20); - let output_enable = instance_bit << 24; - let value = self.regs().scu0f0().read().bits(); - value & cleared_mask == 0 && value & output_enable == output_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(); diff --git a/target/ast10x0/peripherals/spimonitor/commands.rs b/target/ast10x0/peripherals/spimonitor/commands.rs index 40dbb867..1024cbf0 100644 --- a/target/ast10x0/peripherals/spimonitor/commands.rs +++ b/target/ast10x0/peripherals/spimonitor/commands.rs @@ -146,9 +146,9 @@ mod tests { #[test] fn complete_zephyr_allow_list_is_supported() { let 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, + 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, ]; for command in commands { assert!(table_value(command, false).is_some()); diff --git a/target/ast10x0/peripherals/spimonitor/controller.rs b/target/ast10x0/peripherals/spimonitor/controller.rs index a6f80e90..e6fbbfdc 100644 --- a/target/ast10x0/peripherals/spimonitor/controller.rs +++ b/target/ast10x0/peripherals/spimonitor/controller.rs @@ -77,13 +77,12 @@ impl SpiMonitor { } } - let general_slot_limit = if policy.allow_commands[..policy.allow_command_count] - .contains(&0xc5) - { - LAST_GENERAL_COMMAND_SLOT_EXCLUSIVE - } else { - COMMAND_TABLE_SLOTS - }; + 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() @@ -165,11 +164,9 @@ impl SpiMonitor { /// 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); + 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); + self.regs.modify_ctrl(|bits| *bits &= !CTRL_SW_RESET_BIT); } } @@ -414,8 +411,7 @@ impl SpiMonitor { pub fn acknowledge_violations(&self) -> u32 { let pending = self.pending_violations(); if pending != 0 { - self.regs - .modify_ctrl2(|bits| *bits |= pending); + self.regs.modify_ctrl2(|bits| *bits |= pending); } pending } @@ -430,8 +426,7 @@ impl SpiMonitor { pub fn lock(self) -> Result> { 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.write_allow_cmd_slot(slot, value | COMMAND_LOCKED); } self.regs @@ -592,11 +587,7 @@ fn select_privilege_table(regs: &SpiMonitorRegisters, direction: PrivilegeDirect regs.modify_ctrl(|bits| *bits = (*bits & 0x00ff_ffff) | selection); } -fn verify_command_slot( - regs: &SpiMonitorRegisters, - slot: usize, - expected: u32, -) -> Result { +fn verify_command_slot(regs: &SpiMonitorRegisters, slot: usize, expected: u32) -> Result { if regs.read_allow_cmd_slot(slot) != expected { return Err(SpiMonitorError::VerificationFailed); } diff --git a/target/ast10x0/peripherals/spimonitor/mod.rs b/target/ast10x0/peripherals/spimonitor/mod.rs index 91e55400..4fb11754 100644 --- a/target/ast10x0/peripherals/spimonitor/mod.rs +++ b/target/ast10x0/peripherals/spimonitor/mod.rs @@ -11,11 +11,11 @@ pub mod registers; pub mod traits; pub mod types; +pub use commands::{descriptor as command_descriptor, table_value as command_table_value}; pub use controller::{ Configured, ConfiguredSpiMonitor, Locked, LockedSpiMonitor, SpiMonitor, UninitSpiMonitor, Uninitialized, }; -pub use commands::{descriptor as command_descriptor, table_value as command_table_value}; pub use policy::{MonitorPolicy, MAX_CMD_SLOTS, MAX_REGION_SLOTS}; pub use registers::{ SpiMonitorController, SpiMonitorRegisters, SPIPF1_BASE, SPIPF2_BASE, SPIPF3_BASE, SPIPF4_BASE, diff --git a/target/ast10x0/peripherals/spimonitor/profile.rs b/target/ast10x0/peripherals/spimonitor/profile.rs index dd6290eb..e3f35e33 100644 --- a/target/ast10x0/peripherals/spimonitor/profile.rs +++ b/target/ast10x0/peripherals/spimonitor/profile.rs @@ -36,9 +36,9 @@ pub const fn firmware_update_window() -> MonitorPolicy { 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, + 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/tests/smc/read/target_spi2.rs b/target/ast10x0/tests/smc/read/target_spi2.rs index 79018772..e3407062 100644 --- a/target/ast10x0/tests/smc/read/target_spi2.rs +++ b/target/ast10x0/tests/smc/read/target_spi2.rs @@ -8,9 +8,7 @@ use ast10x0_board::{apply_spim_external_mux, enable_flash_power}; #[allow(unused_imports)] use ast10x0_peripherals::scu::{ - pinctrl::{ - PINCTRL_SPI2_QUAD, PINCTRL_SPIM3_DEFAULT, PINCTRL_SPIM4_DEFAULT, - }, + pinctrl::{PINCTRL_SPI2_QUAD, PINCTRL_SPIM3_DEFAULT, PINCTRL_SPIM4_DEFAULT}, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource, }; use ast10x0_peripherals::smc::{ diff --git a/target/ast10x0/tests/spimonitor/setup_all_spim.rs b/target/ast10x0/tests/spimonitor/setup_all_spim.rs index c94200e0..75fed1bd 100644 --- a/target/ast10x0/tests/spimonitor/setup_all_spim.rs +++ b/target/ast10x0/tests/spimonitor/setup_all_spim.rs @@ -11,16 +11,15 @@ use core::cell::UnsafeCell; #[path = "test_common.rs"] mod test_common; -use test_common::TestConfig; use ast10x0_board::{ - apply_spim_external_mux, delay_us, enable_flash_power, set_bmc_resets, - spim_external_mux_state, + apply_spim_external_mux, delay_us, enable_flash_power, set_bmc_resets, spim_external_mux_state, }; use ast10x0_peripherals::scu::{ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance}; use ast10x0_peripherals::spimonitor::{ MonitorPolicy, PrivilegeDirection, PrivilegeOp, SpiMonitorController, }; use target_common::{declare_target, TargetInterface}; +use test_common::TestConfig; use {console_backend as _, entry as _}; struct Spim1Config; @@ -61,9 +60,8 @@ static SPIM4_LOG: LogRam = LogRam(UnsafeCell::new([0; test_common::LOG_RAM_WORDS const WRITE_PROTECTED_LENGTH: u32 = 0x0010_0000; const ALLOW_COMMANDS: [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, + 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, ]; fn log_buffer(log: &'static LogRam) -> &'static mut [u32] { @@ -110,7 +108,8 @@ fn setup_all_spim() -> Result<(), test_common::TestError> { &policy, )?; test_common::dump_policy(&spim1, WRITE_PROTECTED_LENGTH)?; - test_common::configure_passthrough::(&spim1)?; + test_common::validate_one_mib_write_protection(&spim1)?; + test_common::validate_filtering(&spim1)?; let _spim1 = test_common::lock_monitor(spim1)?; pw_log::info!("=== SPIM2 ==="); @@ -120,7 +119,8 @@ fn setup_all_spim() -> Result<(), test_common::TestError> { &policy, )?; test_common::dump_policy(&spim2, WRITE_PROTECTED_LENGTH)?; - test_common::configure_passthrough::(&spim2)?; + test_common::validate_one_mib_write_protection(&spim2)?; + test_common::validate_filtering(&spim2)?; let _spim2 = test_common::lock_monitor(spim2)?; pw_log::info!("=== SPIM3 ==="); @@ -130,7 +130,8 @@ fn setup_all_spim() -> Result<(), test_common::TestError> { &policy, )?; test_common::dump_policy(&spim3, WRITE_PROTECTED_LENGTH)?; - test_common::configure_passthrough::(&spim3)?; + test_common::validate_one_mib_write_protection(&spim3)?; + test_common::validate_filtering(&spim3)?; let _spim3 = test_common::lock_monitor(spim3)?; pw_log::info!("=== SPIM4 ==="); @@ -140,18 +141,18 @@ fn setup_all_spim() -> Result<(), test_common::TestError> { &policy, )?; test_common::dump_policy(&spim4, WRITE_PROTECTED_LENGTH)?; - test_common::configure_passthrough::(&spim4)?; + test_common::validate_one_mib_write_protection(&spim4)?; + test_common::validate_filtering(&spim4)?; let _spim4 = test_common::lock_monitor(spim4)?; - if scu.route_control_raw() & 0x0fff_0000 != 0x0f00_0000 { + if scu.route_control_raw() & 0x0fff_0000 != 0 { pw_log::info!( - "FAIL: final SPIPF reset routing: 0x{:08x}", + "FAIL: unexpected SCU flash-reset routing: 0x{:08x}", scu.route_control_raw() as u32 ); return Err(test_common::TestError::Check); } - pw_log::info!("All SPIM1-4 paths are configured in passthrough mode and locked"); - pw_log::info!("SPI clock frequency must be configured to 25 MHz by the external master"); + pw_log::info!("All SPIM1-4 monitors are filtering traffic and locked"); pw_log::info!( "SCU0F0 after SPIM setup: 0x{:08x}", scu.route_control_raw() as u32 diff --git a/target/ast10x0/tests/spimonitor/test_common.rs b/target/ast10x0/tests/spimonitor/test_common.rs index 9286ca4e..aa850515 100644 --- a/target/ast10x0/tests/spimonitor/test_common.rs +++ b/target/ast10x0/tests/spimonitor/test_common.rs @@ -7,9 +7,7 @@ use core::cell::UnsafeCell; -use ast10x0_board::{ - apply_spim_external_mux, apply_spim_pinctrl, delay_us, spim_external_mux_state, -}; +use ast10x0_board::{apply_spim_external_mux, apply_spim_pinctrl, spim_external_mux_state}; use ast10x0_peripherals::scu::{ ScuError, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, SpiMonitorSource, @@ -139,14 +137,6 @@ pub fn initialize_monitor_with_policy( "FAIL: software reset did not deassert" ); - let scu = unsafe { ScuRegisters::new_global_unlocked() }; - scu.configure_spim_external_flash_reset(C::INSTANCE); - test_check!( - scu.is_spim_external_flash_reset_configured(C::INSTANCE), - "FAIL: SPIPF external flash reset routing readback" - ); - delay_us(5_000); - let configured = monitor.apply_policy(policy)?; test_check!( configured.state() == MonitorState::Configured, @@ -200,21 +190,36 @@ pub fn dump_policy( } #[allow(dead_code)] -pub fn configure_passthrough( +pub fn validate_filtering(configured: &ConfiguredSpiMonitor) -> Result<(), TestError> { + test_check!( + configured.regs().read_ctrl() & 0x7 == 0x4, + "FAIL: SPIPF filtering mode readback" + ); + pw_log::info!("PASS: SPIPF filtering mode"); + Ok(()) +} + +#[allow(dead_code)] +pub fn validate_one_mib_write_protection( configured: &ConfiguredSpiMonitor, ) -> Result<(), TestError> { - configured.disable(); - configured.set_passthrough(PassthroughMode::Enabled); - - let scu = unsafe { ScuRegisters::new_global_unlocked() }; - scu.set_spim_passthrough(C::INSTANCE, SpiMonitorPassthrough::Enabled); - scu.set_spim_miso_multi_func(C::INSTANCE, false); - test_check!( - configured.regs().read_ctrl() & 0x7 == 0x1, - "FAIL: SPIPF passthrough mode readback" + configured.privilege_word(PrivilegeDirection::Read, 0)? == u32::MAX + && configured.privilege_word(PrivilegeDirection::Read, 1)? == u32::MAX + && configured.privilege_word(PrivilegeDirection::Read, 2)? == u32::MAX, + "FAIL: read privilege table is not unrestricted" + ); + test_check!( + configured.privilege_word(PrivilegeDirection::Write, 0)? == 0 + && configured.privilege_word(PrivilegeDirection::Write, 1)? == 0, + "FAIL: first 1 MiB is not write protected" + ); + test_check!( + configured.privilege_word(PrivilegeDirection::Write, 2)? == u32::MAX, + "FAIL: writes at and above 0x00100000 are not enabled" ); - pw_log::info!("PASS: SPIPF single-bit passthrough mode"); + pw_log::info!("PASS: reads allowed; writes below 0x00100000 blocked"); + pw_log::info!("PASS: writes at 0x00110000 allowed"); Ok(()) } From ff9331b0dc3200a73cd0618fba4f5833b144320e Mon Sep 17 00:00:00 2001 From: hailin wu Date: Mon, 15 Jun 2026 12:14:38 -0700 Subject: [PATCH 11/13] fixed SPIM1/2 CSNIN stuck low(asserted) problem by: Adding a PROT recovery timeout: if BMC CSIN stays low, assert BMC reset, route to RoT, reset flashes and monitors, then retry --- target/ast10x0/board/src/lib.rs | 6 +- target/ast10x0/board/src/spim_wiring.rs | 107 +++-- .../peripherals/spimonitor/controller.rs | 20 +- .../tests/spimonitor/setup_all_spim.rs | 398 +++++++++++++++++- 4 files changed, 488 insertions(+), 43 deletions(-) diff --git a/target/ast10x0/board/src/lib.rs b/target/ast10x0/board/src/lib.rs index 5dae84a3..cf5b011f 100644 --- a/target/ast10x0/board/src/lib.rs +++ b/target/ast10x0/board/src/lib.rs @@ -19,9 +19,9 @@ pub mod spim_wiring; pub use monitor::Ast1060Monitor; pub use spim_wiring::{ - apply_spim_external_mux, apply_spim_pinctrl, apply_spim_wiring, apply_spim_wiring_with_log, - enable_flash_power, presets, set_bmc_resets, spim_external_mux_state, SpimWiring, - SpimWiringError, + BmcSpimPathDebug, SpimWiring, SpimWiringError, apply_spim_external_mux, apply_spim_pinctrl, + apply_spim_wiring, apply_spim_wiring_with_log, bmc_spim_csin_levels, bmc_spim_path_debug, + enable_flash_power, presets, release_spi_flash_resets, set_bmc_resets, spim_external_mux_state, }; pub use ast10x0_peripherals::i2c::{I2cConfig, I2cError}; diff --git a/target/ast10x0/board/src/spim_wiring.rs b/target/ast10x0/board/src/spim_wiring.rs index 701b26d6..26bc7fed 100644 --- a/target/ast10x0/board/src/spim_wiring.rs +++ b/target/ast10x0/board/src/spim_wiring.rs @@ -10,20 +10,20 @@ //! (`peripherals/spimonitor/planning/overview-and-usage-model.md`) calls for //! "configure early, validate, lock, and operate under that locked policy." -use ast1060_pac as device; use ast10x0_peripherals::scu::{ + ScuError, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, + SpiMonitorSource, pinctrl::{ PINCTRL_GPIOL2, PINCTRL_GPIOL3, PINCTRL_SPIM1_DEFAULT, PINCTRL_SPIM2_DEFAULT, PINCTRL_SPIM3_DEFAULT, PINCTRL_SPIM4_DEFAULT, }, - ScuError, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, - SpiMonitorSource, }; use ast10x0_peripherals::smc::SmcController; use ast10x0_peripherals::spimonitor::{ LockedSpiMonitor, MonitorPolicy, PassthroughMode, SpiMonitor, SpiMonitorController, SpiMonitorError, Uninitialized, }; +use ast1060_pac as device; /// Static wiring for one external SPI monitor path. /// @@ -112,16 +112,16 @@ pub fn apply_spim_pinctrl(scu: &ScuRegisters, instance: SpiMonitorInstance) { /// Configure the external flash mux controls described by the board schematic. /// -/// SPIM1/2 use GPIO A-D pin 12 plus SGPIOM select/OE/reset bits 0/1/7. -/// SPIM3/4 use GPIO E-H pin 8 plus SGPIOM select/OE/reset bits 2/3/6. +/// SPIM1/2 use GPIO A-D pin 12 plus SGPIOM output 0. +/// SPIM3/4 use GPIO E-H pin 8 plus SGPIOM output 2. pub fn apply_spim_external_mux(instance: SpiMonitorInstance, mux: ScuExtMuxSelect) { let high = matches!(mux, ScuExtMuxSelect::Mux1); - let (gpio_group, gpio_mask, sgpio_select, sgpio_oe_n, sgpio_reset_n) = match instance { + let (gpio_group, gpio_mask, sgpio_select) = match instance { SpiMonitorInstance::Spim0 | SpiMonitorInstance::Spim1 => { - (ExternalMuxGpioGroup::Abcd, 1 << 12, 1 << 0, 1 << 1, 1 << 7) + (ExternalMuxGpioGroup::Abcd, 1 << 12, 1 << 0) } SpiMonitorInstance::Spim2 | SpiMonitorInstance::Spim3 => { - (ExternalMuxGpioGroup::Efgh, 1 << 8, 1 << 2, 1 << 3, 1 << 6) + (ExternalMuxGpioGroup::Efgh, 1 << 8, 1 << 2) } }; @@ -162,24 +162,22 @@ pub fn apply_spim_external_mux(instance: SpiMonitorInstance, mux: ScuExtMuxSelec .bits(24) }); let latch = sgpio.gpio570().read().bits(); - let control_mask = sgpio_select | sgpio_oe_n | sgpio_reset_n; - let control_value = if high { sgpio_select } else { 0 } | sgpio_reset_n; sgpio .gpio500() - .write(|w| unsafe { w.bits((latch & !control_mask) | control_value) }); + .write(|w| unsafe { w.bits(update_bit(latch, sgpio_select, high)) }); crate::delay_us(1_000); } -/// Read back the board-level mux selection, output enable, and flash reset. +/// Read back the board-level external mux selection. #[must_use] pub fn spim_external_mux_state(instance: SpiMonitorInstance) -> Option { - let (gpio_group, gpio_mask, sgpio_select, sgpio_oe_n, sgpio_reset_n) = match instance { + let (gpio_group, gpio_mask, sgpio_select) = match instance { SpiMonitorInstance::Spim0 | SpiMonitorInstance::Spim1 => { - (ExternalMuxGpioGroup::Abcd, 1 << 12, 1 << 0, 1 << 1, 1 << 7) + (ExternalMuxGpioGroup::Abcd, 1 << 12, 1 << 0) } SpiMonitorInstance::Spim2 | SpiMonitorInstance::Spim3 => { - (ExternalMuxGpioGroup::Efgh, 1 << 8, 1 << 2, 1 << 3, 1 << 6) + (ExternalMuxGpioGroup::Efgh, 1 << 8, 1 << 2) } }; let gpio = unsafe { &*device::Gpio::ptr() }; @@ -190,11 +188,6 @@ pub fn spim_external_mux_state(instance: SpiMonitorInstance) -> Option Option bool { + const CPU_SPI_RESET_N: u32 = 1 << 6; + const BMC_SPI_RESET_N: u32 = 1 << 7; + const RESET_MASK: u32 = CPU_SPI_RESET_N | BMC_SPI_RESET_N; + + let sgpio = unsafe { &*device::Sgpiom::ptr() }; + let latch = sgpio.gpio570().read().bits(); + sgpio + .gpio500() + .write(|w| unsafe { w.bits(latch | RESET_MASK) }); + crate::delay_us(1_000); + + sgpio.gpio570().read().bits() & RESET_MASK == RESET_MASK +} + +/// Raw board routing state for the external BMC SPI1/2 paths. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct BmcSpimPathDebug { + pub scu410: u32, + pub scu4b0: u32, + pub scu690: u32, + pub scu610: u32, + pub gpio_data: u32, + pub gpio_direction: u32, + pub sgpio_latch: u32, + pub sgpio_config: u32, +} + +/// Capture the pinmux and external-mux state used by BMC SPI1/2. +#[must_use] +pub fn bmc_spim_path_debug() -> BmcSpimPathDebug { + let scu = unsafe { &*device::Scu::ptr() }; + let gpio = unsafe { &*device::Gpio::ptr() }; + let sgpio = unsafe { &*device::Sgpiom::ptr() }; + + BmcSpimPathDebug { + scu410: scu.scu410().read().bits(), + scu4b0: scu.scu4b0().read().bits(), + scu690: scu.scu690().read().bits(), + scu610: scu.scu610().read().bits(), + gpio_data: gpio.gpio000().read().bits(), + gpio_direction: gpio.gpio004().read().bits(), + sgpio_latch: sgpio.gpio570().read().bits(), + sgpio_config: sgpio.gpio554().read().bits(), + } +} + +/// Sample the active-low BMC SPI chip-select inputs. +/// +/// SPIM1 CSIN is GPIOA0 and SPIM2 CSIN is GPIOB6. The GPIO data-read +/// value register reflects the physical input levels while the pins are +/// assigned to their SPIM alternate functions. GPIO0C0 is the output-latch +/// readback and must not be used for these input signals. +#[must_use] +pub fn bmc_spim_csin_levels() -> u32 { + const SPIM1_CSIN: u32 = 1 << 0; + const SPIM2_CSIN: u32 = 1 << 14; + + let gpio = unsafe { &*device::Gpio::ptr() }; + gpio.gpio000().read().bits() & (SPIM1_CSIN | SPIM2_CSIN) +} + /// Enable the flash-power outputs required by older AST1060 demo boards. #[must_use] pub fn enable_flash_power(scu: &ScuRegisters) -> bool { @@ -285,11 +344,7 @@ enum ExternalMuxGpioGroup { } const fn update_bit(value: u32, mask: u32, set: bool) -> u32 { - if set { - value | mask - } else { - value & !mask - } + if set { value | mask } else { value & !mask } } /// Apply static SPIM wiring at controller-init time. @@ -375,7 +430,7 @@ fn validate_controller_for_source( /// Built-in `MonitorPolicy` presets vetted against the BMC's flash opcode set. pub mod presets { use ast10x0_peripherals::spimonitor::{ - profile, MonitorPolicy, PrivilegeDirection, PrivilegeOp, + MonitorPolicy, PrivilegeDirection, PrivilegeOp, profile, }; /// Allow-list for the BMC's normal flash opcodes covering both 3-byte and diff --git a/target/ast10x0/peripherals/spimonitor/controller.rs b/target/ast10x0/peripherals/spimonitor/controller.rs index e6fbbfdc..8c13a18f 100644 --- a/target/ast10x0/peripherals/spimonitor/controller.rs +++ b/target/ast10x0/peripherals/spimonitor/controller.rs @@ -7,8 +7,8 @@ use core::marker::PhantomData; use crate::scu::registers::ScuRegisters; use crate::scu::types::{ScuExtMuxSelect, SpiMonitorInstance}; -use crate::spimonitor::commands::{fixed_slot, table_value, LOCKED as COMMAND_LOCKED}; -use crate::spimonitor::policy::{MonitorPolicy, MAX_REGION_SLOTS}; +use crate::spimonitor::commands::{LOCKED as COMMAND_LOCKED, fixed_slot, table_value}; +use crate::spimonitor::policy::{MAX_REGION_SLOTS, MonitorPolicy}; use crate::spimonitor::registers::{SpiMonitorController, SpiMonitorRegisters}; use crate::spimonitor::types::{ ExtMuxSel, LockState, MonitorState, PassthroughMode, PrivilegeDirection, PrivilegeOp, Result, @@ -164,9 +164,10 @@ impl SpiMonitor { /// 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); + let ctrl = self.regs.read_ctrl() & !CTRL_SW_RESET_BIT; + self.regs.write_ctrl(ctrl | CTRL_SW_RESET_BIT); delay_cycles(SW_RESET_DELAY_CYCLES); - self.regs.modify_ctrl(|bits| *bits &= !CTRL_SW_RESET_BIT); + self.regs.write_ctrl(ctrl); } } @@ -175,6 +176,17 @@ impl SpiMonitor { // --------------------------------------------------------------------------- impl SpiMonitor { + /// Reset the SPIPF transaction parser while preserving programmed policy. + /// + /// Zephyr pulses this reset immediately before handing flash ownership + /// from the RoT master to the external BMC/PCH master. + pub fn reset_filter_state(&self) { + let ctrl = self.regs.read_ctrl() & !CTRL_SW_RESET_BIT; + self.regs.write_ctrl(ctrl | CTRL_SW_RESET_BIT); + delay_cycles(SW_RESET_DELAY_CYCLES); + self.regs.write_ctrl(ctrl); + } + /// Add or re-enable one command, matching the Zephyr shell `cmd add`. pub fn add_command(&self, opcode: u8, valid_once: bool) -> Result { let value = diff --git a/target/ast10x0/tests/spimonitor/setup_all_spim.rs b/target/ast10x0/tests/spimonitor/setup_all_spim.rs index 75fed1bd..c7206268 100644 --- a/target/ast10x0/tests/spimonitor/setup_all_spim.rs +++ b/target/ast10x0/tests/spimonitor/setup_all_spim.rs @@ -12,13 +12,22 @@ use core::cell::UnsafeCell; mod test_common; use ast10x0_board::{ - apply_spim_external_mux, delay_us, enable_flash_power, set_bmc_resets, spim_external_mux_state, + apply_spim_external_mux, bmc_spim_csin_levels, bmc_spim_path_debug, delay_us, + enable_flash_power, release_spi_flash_resets, set_bmc_resets, spim_external_mux_state, }; -use ast10x0_peripherals::scu::{ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance}; +use ast10x0_peripherals::scu::{ + ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorSource, pinctrl::PINCTRL_SPI1_QUAD, +}; +use ast10x0_peripherals::smc::{ + ChipSelect, FlashConfig, SmcConfig, SmcController, SmcError, SmcTopology, SpiReady, SpiUninit, + TransferMode, +}; +use ast10x0_peripherals::spimonitor::registers::SpiMonitorRegisters; use ast10x0_peripherals::spimonitor::{ - MonitorPolicy, PrivilegeDirection, PrivilegeOp, SpiMonitorController, + ConfiguredSpiMonitor, LockedSpiMonitor, MonitorPolicy, PrivilegeDirection, PrivilegeOp, + SpiMonitorController, ViolationLogEntry, }; -use target_common::{declare_target, TargetInterface}; +use target_common::{TargetInterface, declare_target}; use test_common::TestConfig; use {console_backend as _, entry as _}; @@ -59,6 +68,18 @@ static SPIM3_LOG: LogRam = LogRam(UnsafeCell::new([0; test_common::LOG_RAM_WORDS static SPIM4_LOG: LogRam = LogRam(UnsafeCell::new([0; test_common::LOG_RAM_WORDS])); const WRITE_PROTECTED_LENGTH: u32 = 0x0010_0000; +const MONITOR_POLL_INTERVAL_US: u32 = 10_000; +const BMC_CSIN_RECOVERY_TIMEOUT_US: u32 = 500_000; +const BMC_RECOVERY_RETRY_DELAY_US: u32 = 1_000_000; +const BMC_CSIN_MASK: u32 = (1 << 0) | (1 << 14); +const ENABLE_RUNTIME_DEBUG_LOGS: bool = false; +const BMC_FLASH_CONFIG: FlashConfig = FlashConfig { + capacity_mb: 128, + page_size: 256, + sector_size: 4096, + block_size: 65536, + spi_clock_mhz: 25, +}; const ALLOW_COMMANDS: [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, @@ -69,6 +90,340 @@ fn log_buffer(log: &'static LogRam) -> &'static mut [u32] { unsafe { &mut *log.0.get() } } +fn raw_log_word(log: &'static LogRam, index: usize) -> u32 { + // SAFETY: The index comes from the bounded SPIPF log pointer. + unsafe { core::ptr::read_volatile((*log.0.get()).as_ptr().add(index)) } +} + +fn log_violation(id: u32, index: u32, word: u32) { + match ViolationLogEntry::parse(word) { + ViolationLogEntry::BlockedCommand(command) => pw_log::info!( + "SPIM{} BLOCKED COMMAND log[{}]: cmd=0x{:02x}, raw=0x{:08x}", + id as u32, + index as u32, + command as u32, + word as u32 + ), + ViolationLogEntry::BlockedWriteAddr(address) => pw_log::info!( + "SPIM{} BLOCKED WRITE log[{}]: address=0x{:08x}, raw=0x{:08x}", + id as u32, + index as u32, + address as u32, + word as u32 + ), + ViolationLogEntry::BlockedReadAddr(address) => pw_log::info!( + "SPIM{} BLOCKED READ log[{}]: address=0x{:08x}, raw=0x{:08x}", + id as u32, + index as u32, + address as u32, + word as u32 + ), + ViolationLogEntry::Invalid(raw) => pw_log::info!( + "SPIM{} INVALID LOG log[{}]: raw=0x{:08x}", + id as u32, + index as u32, + raw as u32 + ), + } +} + +fn log_violation_status(id: u32, status: u32) { + pw_log::info!( + "SPIM{} status=0x{:08x}: command_blocked={}, write_blocked={}, read_blocked={}", + id as u32, + status as u32, + (status & (1 << 0) != 0) as u32, + (status & (1 << 1) != 0) as u32, + (status & (1 << 2) != 0) as u32 + ); +} + +fn acknowledge_violations(regs: &SpiMonitorRegisters) -> u32 { + let pending = regs.read_ctrl2() & 0x7; + if pending != 0 { + regs.modify_ctrl2(|bits| *bits |= pending); + } + pending +} + +macro_rules! runtime_debug { + ($($arg:tt)*) => { + if ENABLE_RUNTIME_DEBUG_LOGS { + pw_log::info!($($arg)*); + } + }; +} + +fn recover_bmc_path( + scu: &ScuRegisters, + spim1: &ConfiguredSpiMonitor, + spim2: &ConfiguredSpiMonitor, +) { + runtime_debug!("BMC recovery: asserting BMC reset"); + if !set_bmc_resets(true) { + runtime_debug!("BMC recovery: reset assertion readback failed"); + } + + runtime_debug!("BMC recovery: routing SPIM1/2 flashes to RoT"); + apply_spim_external_mux(Spim1Config::INSTANCE, ScuExtMuxSelect::Mux1); + delay_us(1_000); + + loop { + if !set_bmc_resets(true) { + runtime_debug!("BMC recovery: reset assertion readback failed"); + } + if spim_external_mux_state(Spim1Config::INSTANCE) != Some(ScuExtMuxSelect::Mux1) { + runtime_debug!( + "BMC recovery: RoT mux readback failed; retrying in {} us", + BMC_RECOVERY_RETRY_DELAY_US as u32 + ); + apply_spim_external_mux(Spim1Config::INSTANCE, ScuExtMuxSelect::Mux1); + delay_us(BMC_RECOVERY_RETRY_DELAY_US); + continue; + } + spim1.reset_filter_state(); + spim2.reset_filter_state(); + + runtime_debug!("BMC recovery: resetting and probing spi1@0 and spi1@1"); + if reset_bmc_flashes(scu, false).is_err() { + runtime_debug!( + "BMC recovery: flash reset/probe failed; retrying in {} us", + BMC_RECOVERY_RETRY_DELAY_US as u32 + ); + delay_us(BMC_RECOVERY_RETRY_DELAY_US); + continue; + } + + spim1.reset_filter_state(); + spim2.reset_filter_state(); + acknowledge_violations(spim1.regs()); + acknowledge_violations(spim2.regs()); + + runtime_debug!("BMC recovery: handing SPIM1/2 flashes back to BMC"); + apply_spim_external_mux(Spim1Config::INSTANCE, ScuExtMuxSelect::Mux0); + delay_us(60_000); + if spim_external_mux_state(Spim1Config::INSTANCE) != Some(ScuExtMuxSelect::Mux0) { + runtime_debug!( + "BMC recovery: BMC mux readback failed; retrying in {} us", + BMC_RECOVERY_RETRY_DELAY_US as u32 + ); + apply_spim_external_mux(Spim1Config::INSTANCE, ScuExtMuxSelect::Mux1); + delay_us(BMC_RECOVERY_RETRY_DELAY_US); + continue; + } + + if !set_bmc_resets(false) { + runtime_debug!( + "BMC recovery: reset release readback failed; retrying in {} us", + BMC_RECOVERY_RETRY_DELAY_US as u32 + ); + apply_spim_external_mux(Spim1Config::INSTANCE, ScuExtMuxSelect::Mux1); + delay_us(BMC_RECOVERY_RETRY_DELAY_US); + continue; + } + + runtime_debug!("BMC recovery: complete"); + return; + } +} + +fn monitor_spim_violations( + spim1: &ConfiguredSpiMonitor, + spim2: &ConfiguredSpiMonitor, + spim3: &LockedSpiMonitor, + spim4: &LockedSpiMonitor, +) -> ! { + let monitors = [ + (1u32, spim1.regs(), &SPIM1_LOG), + (2u32, spim2.regs(), &SPIM2_LOG), + (3u32, spim3.regs(), &SPIM3_LOG), + (4u32, spim4.regs(), &SPIM4_LOG), + ]; + let mut log_indices = [ + spim1.regs().read_log_idx_reg(), + spim2.regs().read_log_idx_reg(), + spim3.regs().read_log_idx_reg(), + spim4.regs().read_log_idx_reg(), + ]; + let mut poll_count = 0u32; + let mut bmc_csin_low_us = 0u32; + + if ENABLE_RUNTIME_DEBUG_LOGS { + for (id, regs, _) in monitors { + pw_log::info!( + "SPIM{} ctrl/status/lock=0x{:08x}/0x{:08x}/0x{:08x}", + id as u32, + regs.read_ctrl() as u32, + regs.read_ctrl2() as u32, + regs.read_lock_status() as u32 + ); + } + pw_log::info!("Monitoring BMC and host traffic; SPIPF logs blocked transactions only"); + } + + loop { + for (slot, (id, regs, log)) in monitors.iter().enumerate() { + let next_index = regs.read_log_idx_reg(); + if next_index != log_indices[slot] { + let status = regs.read_ctrl2(); + pw_log::info!( + "SPIM{} NEW VIOLATION: log {} -> {}", + *id as u32, + log_indices[slot] as u32, + next_index as u32 + ); + log_violation_status(*id, status); + + let end = next_index.min(test_common::LOG_RAM_WORDS as u32); + for index in log_indices[slot]..end { + log_violation(*id, index, raw_log_word(log, index as usize)); + } + + let acknowledged = acknowledge_violations(regs); + pw_log::info!( + "SPIM{} acknowledged status 0x{:08x}", + *id as u32, + acknowledged as u32 + ); + log_indices[slot] = next_index; + } + } + + let csin_levels = bmc_spim_csin_levels(); + if csin_levels != BMC_CSIN_MASK { + bmc_csin_low_us = bmc_csin_low_us.saturating_add(MONITOR_POLL_INTERVAL_US); + if bmc_csin_low_us >= BMC_CSIN_RECOVERY_TIMEOUT_US { + runtime_debug!( + "BMC CSIN timeout: levels=0x{:08x}, low for {} us", + csin_levels as u32, + bmc_csin_low_us as u32 + ); + let scu = unsafe { ScuRegisters::new_global_unlocked() }; + recover_bmc_path(&scu, spim1, spim2); + log_indices[0] = spim1.regs().read_log_idx_reg(); + log_indices[1] = spim2.regs().read_log_idx_reg(); + bmc_csin_low_us = 0; + } + } else { + bmc_csin_low_us = 0; + } + + poll_count = poll_count.wrapping_add(1); + if ENABLE_RUNTIME_DEBUG_LOGS && poll_count % 100 == 0 { + let scu = unsafe { ScuRegisters::new_global_unlocked() }; + let mux = match spim_external_mux_state(Spim1Config::INSTANCE) { + Some(ScuExtMuxSelect::Mux0) => 0u32, + Some(ScuExtMuxSelect::Mux1) => 1u32, + None => 0xffff_ffff, + }; + let host_mux = match spim_external_mux_state(Spim3Config::INSTANCE) { + Some(ScuExtMuxSelect::Mux0) => 0u32, + Some(ScuExtMuxSelect::Mux1) => 1u32, + None => 0xffff_ffff, + }; + let path = bmc_spim_path_debug(); + pw_log::info!( + "SPIM health: SCU0F0=0x{:08x}, mux BMC/host={}/{}, CSIN=0x{:08x}", + scu.route_control_raw() as u32, + mux as u32, + host_mux as u32, + bmc_spim_csin_levels() as u32 + ); + pw_log::info!( + "SPIM log_idx 1/2/3/4={}/{}/{}/{}", + monitors[0].1.read_log_idx_reg() as u32, + monitors[1].1.read_log_idx_reg() as u32, + monitors[2].1.read_log_idx_reg() as u32, + monitors[3].1.read_log_idx_reg() as u32 + ); + pw_log::info!( + "SPIM status 1/2/3/4=0x{:08x}/0x{:08x}/0x{:08x}/0x{:08x}", + monitors[0].1.read_ctrl2() as u32, + monitors[1].1.read_ctrl2() as u32, + monitors[2].1.read_ctrl2() as u32, + monitors[3].1.read_ctrl2() as u32 + ); + pw_log::info!( + "BMC path: SCU410/4B0/690=0x{:08x}/0x{:08x}/0x{:08x}, SCU610=0x{:08x}", + path.scu410 as u32, + path.scu4b0 as u32, + path.scu690 as u32, + path.scu610 as u32 + ); + pw_log::info!( + "BMC path: GPIO000/004=0x{:08x}/0x{:08x}, SGPIOM570/554=0x{:08x}/0x{:08x}", + path.gpio_data as u32, + path.gpio_direction as u32, + path.sgpio_latch as u32, + path.sgpio_config as u32 + ); + } + delay_us(MONITOR_POLL_INTERVAL_US); + } +} + +fn reset_one_bmc_flash( + scu: &ScuRegisters, + spi: &SpiReady, + monitor: SpiMonitorInstance, + chip_select: ChipSelect, +) -> Result<[u8; 3], SmcError> { + scu.set_spim_internal_mux(SpiMonitorSource::Spi1, monitor as u8 + 1) + .map_err(|_| SmcError::HardwareError)?; + let proprietary_state = scu.spim_proprietary_pre_config(); + + let result = (|| { + spi.transceive_user(chip_select, &[0x66], &[], &mut [], TransferMode::Mode111)?; + delay_us(10_000); + spi.transceive_user(chip_select, &[0x99], &[], &mut [], TransferMode::Mode111)?; + delay_us(50_000); + spi.transceive_user(chip_select, &[0xe9], &[], &mut [], TransferMode::Mode111)?; + + let mut jedec = [0u8; 3]; + spi.transceive_user(chip_select, &[0x9f], &[], &mut jedec, TransferMode::Mode111)?; + Ok(jedec) + })(); + + if let Some(state) = proprietary_state { + scu.spim_proprietary_post_config(state); + } + scu.clear_spim_internal_master_route(); + result +} + +fn reset_bmc_flashes(scu: &ScuRegisters, log_jedec: bool) -> Result<(), SmcError> { + scu.apply_pinctrl_group(PINCTRL_SPI1_QUAD); + let config = SmcConfig { + controller_id: SmcController::Spi1, + cs0: Some(BMC_FLASH_CONFIG), + cs1: Some(BMC_FLASH_CONFIG), + dma_enabled: false, + enable_interrupts: false, + topology: SmcTopology::HostSpi { master_idx: 0 }, + }; + let spi = unsafe { SpiUninit::new(SmcController::Spi1, config)? }.init()?; + + for (index, monitor, chip_select) in [ + (0u32, SpiMonitorInstance::Spim0, ChipSelect::Cs0), + (1u32, SpiMonitorInstance::Spim1, ChipSelect::Cs1), + ] { + let jedec = reset_one_bmc_flash(scu, &spi, monitor, chip_select)?; + if log_jedec { + pw_log::info!( + "spi1@{} reset complete, JEDEC ID: {:02x} {:02x} {:02x}", + index as u32, + jedec[0] as u32, + jedec[1] as u32, + jedec[2] as u32 + ); + } + if matches!(jedec, [0x00, 0x00, 0x00] | [0xff, 0xff, 0xff]) { + return Err(SmcError::HardwareError); + } + } + Ok(()) +} + fn production_policy() -> MonitorPolicy { let mut policy = MonitorPolicy::empty(); policy.allow_commands.copy_from_slice(&ALLOW_COMMANDS); @@ -94,6 +449,13 @@ fn setup_all_spim() -> Result<(), test_common::TestError> { } pw_log::info!("PASS: GPIOL2 and GPIOL3 set high"); + pw_log::info!("=== Release SPI flash reset outputs ==="); + if !release_spi_flash_resets() { + pw_log::info!("FAIL: SGPIOM CPU/BMC SPI reset outputs did not release"); + return Err(test_common::TestError::Check); + } + pw_log::info!("PASS: SGPIOM outputs 6 and 7 released high"); + pw_log::info!("=== Hold BMC in reset ==="); if !set_bmc_resets(true) { pw_log::info!("FAIL: SGPIOM BMC reset outputs did not assert"); @@ -110,7 +472,6 @@ fn setup_all_spim() -> Result<(), test_common::TestError> { test_common::dump_policy(&spim1, WRITE_PROTECTED_LENGTH)?; test_common::validate_one_mib_write_protection(&spim1)?; test_common::validate_filtering(&spim1)?; - let _spim1 = test_common::lock_monitor(spim1)?; pw_log::info!("=== SPIM2 ==="); test_common::configure_wiring::(&scu)?; @@ -121,7 +482,6 @@ fn setup_all_spim() -> Result<(), test_common::TestError> { test_common::dump_policy(&spim2, WRITE_PROTECTED_LENGTH)?; test_common::validate_one_mib_write_protection(&spim2)?; test_common::validate_filtering(&spim2)?; - let _spim2 = test_common::lock_monitor(spim2)?; pw_log::info!("=== SPIM3 ==="); test_common::configure_wiring::(&scu)?; @@ -132,7 +492,7 @@ fn setup_all_spim() -> Result<(), test_common::TestError> { test_common::dump_policy(&spim3, WRITE_PROTECTED_LENGTH)?; test_common::validate_one_mib_write_protection(&spim3)?; test_common::validate_filtering(&spim3)?; - let _spim3 = test_common::lock_monitor(spim3)?; + let spim3 = test_common::lock_monitor(spim3)?; pw_log::info!("=== SPIM4 ==="); test_common::configure_wiring::(&scu)?; @@ -143,7 +503,7 @@ fn setup_all_spim() -> Result<(), test_common::TestError> { test_common::dump_policy(&spim4, WRITE_PROTECTED_LENGTH)?; test_common::validate_one_mib_write_protection(&spim4)?; test_common::validate_filtering(&spim4)?; - let _spim4 = test_common::lock_monitor(spim4)?; + let spim4 = test_common::lock_monitor(spim4)?; if scu.route_control_raw() & 0x0fff_0000 != 0 { pw_log::info!( @@ -152,13 +512,31 @@ fn setup_all_spim() -> Result<(), test_common::TestError> { ); return Err(test_common::TestError::Check); } - pw_log::info!("All SPIM1-4 monitors are filtering traffic and locked"); + pw_log::info!("All SPIM1-4 monitors are filtering traffic"); + pw_log::info!("SPIM3/4 policies are locked; SPIM1/2 match unlocked Zephyr runtime"); pw_log::info!( "SCU0F0 after SPIM setup: 0x{:08x}", scu.route_control_raw() as u32 ); + pw_log::info!("=== Reset BMC flashes through internal SPI1 ==="); + if reset_bmc_flashes(&scu, true).is_err() { + pw_log::info!("FAIL: BMC flash reset/readback through SPI1"); + return Err(test_common::TestError::Check); + } + pw_log::info!("PASS: spi1@0 and spi1@1 are in a known SPI state"); + pw_log::info!("=== Hand BMC flash mux to external BMC master ==="); + spim1.reset_filter_state(); + spim2.reset_filter_state(); + test_common::validate_filtering(&spim1)?; + test_common::validate_filtering(&spim2)?; + pw_log::info!( + "SPIM1/2 pre-release ctrl=0x{:08x}/0x{:08x}", + spim1.regs().read_ctrl() as u32, + spim2.regs().read_ctrl() as u32 + ); + apply_spim_external_mux(Spim1Config::INSTANCE, ScuExtMuxSelect::Mux0); if spim_external_mux_state(Spim1Config::INSTANCE) != Some(ScuExtMuxSelect::Mux0) { pw_log::info!("FAIL: SPIM1/2 external BMC mux handoff"); @@ -177,7 +555,7 @@ fn setup_all_spim() -> Result<(), test_common::TestError> { pw_log::info!("PASS: SGPIOM outputs 8 and 9 released high"); pw_log::info!("Firmware will remain active until the user stops or resets it"); - Ok(()) + monitor_spim_violations(&spim1, &spim2, &spim3, &spim4) } struct Target; From ff6a8c8b2644eef97f62e5ba8eaf76f022e99142 Mon Sep 17 00:00:00 2001 From: HaiLin Wu Date: Wed, 17 Jun 2026 10:49:33 -0700 Subject: [PATCH 12/13] fixed clipping issues --- target/ast10x0/board/src/lib.rs | 7 ++++--- target/ast10x0/board/src/spim_wiring.rs | 14 +++++++++----- .../ast10x0/peripherals/spimonitor/controller.rs | 4 ++-- target/ast10x0/tests/spimonitor/setup_all_spim.rs | 4 ++-- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/target/ast10x0/board/src/lib.rs b/target/ast10x0/board/src/lib.rs index cf5b011f..f9924eab 100644 --- a/target/ast10x0/board/src/lib.rs +++ b/target/ast10x0/board/src/lib.rs @@ -19,9 +19,10 @@ pub mod spim_wiring; pub use monitor::Ast1060Monitor; pub use spim_wiring::{ - BmcSpimPathDebug, SpimWiring, SpimWiringError, apply_spim_external_mux, apply_spim_pinctrl, - apply_spim_wiring, apply_spim_wiring_with_log, bmc_spim_csin_levels, bmc_spim_path_debug, - enable_flash_power, presets, release_spi_flash_resets, set_bmc_resets, spim_external_mux_state, + apply_spim_external_mux, apply_spim_pinctrl, apply_spim_wiring, apply_spim_wiring_with_log, + bmc_spim_csin_levels, bmc_spim_path_debug, enable_flash_power, presets, + release_spi_flash_resets, set_bmc_resets, spim_external_mux_state, BmcSpimPathDebug, + SpimWiring, SpimWiringError, }; pub use ast10x0_peripherals::i2c::{I2cConfig, I2cError}; diff --git a/target/ast10x0/board/src/spim_wiring.rs b/target/ast10x0/board/src/spim_wiring.rs index 26bc7fed..5ad45869 100644 --- a/target/ast10x0/board/src/spim_wiring.rs +++ b/target/ast10x0/board/src/spim_wiring.rs @@ -10,20 +10,20 @@ //! (`peripherals/spimonitor/planning/overview-and-usage-model.md`) calls for //! "configure early, validate, lock, and operate under that locked policy." +use ast1060_pac as device; use ast10x0_peripherals::scu::{ - ScuError, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, - SpiMonitorSource, pinctrl::{ PINCTRL_GPIOL2, PINCTRL_GPIOL3, PINCTRL_SPIM1_DEFAULT, PINCTRL_SPIM2_DEFAULT, PINCTRL_SPIM3_DEFAULT, PINCTRL_SPIM4_DEFAULT, }, + ScuError, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough, + SpiMonitorSource, }; use ast10x0_peripherals::smc::SmcController; use ast10x0_peripherals::spimonitor::{ LockedSpiMonitor, MonitorPolicy, PassthroughMode, SpiMonitor, SpiMonitorController, SpiMonitorError, Uninitialized, }; -use ast1060_pac as device; /// Static wiring for one external SPI monitor path. /// @@ -344,7 +344,11 @@ enum ExternalMuxGpioGroup { } const fn update_bit(value: u32, mask: u32, set: bool) -> u32 { - if set { value | mask } else { value & !mask } + if set { + value | mask + } else { + value & !mask + } } /// Apply static SPIM wiring at controller-init time. @@ -430,7 +434,7 @@ fn validate_controller_for_source( /// Built-in `MonitorPolicy` presets vetted against the BMC's flash opcode set. pub mod presets { use ast10x0_peripherals::spimonitor::{ - MonitorPolicy, PrivilegeDirection, PrivilegeOp, profile, + profile, MonitorPolicy, PrivilegeDirection, PrivilegeOp, }; /// Allow-list for the BMC's normal flash opcodes covering both 3-byte and diff --git a/target/ast10x0/peripherals/spimonitor/controller.rs b/target/ast10x0/peripherals/spimonitor/controller.rs index 8c13a18f..e38a4c72 100644 --- a/target/ast10x0/peripherals/spimonitor/controller.rs +++ b/target/ast10x0/peripherals/spimonitor/controller.rs @@ -7,8 +7,8 @@ use core::marker::PhantomData; use crate::scu::registers::ScuRegisters; use crate::scu::types::{ScuExtMuxSelect, SpiMonitorInstance}; -use crate::spimonitor::commands::{LOCKED as COMMAND_LOCKED, fixed_slot, table_value}; -use crate::spimonitor::policy::{MAX_REGION_SLOTS, MonitorPolicy}; +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::{ ExtMuxSel, LockState, MonitorState, PassthroughMode, PrivilegeDirection, PrivilegeOp, Result, diff --git a/target/ast10x0/tests/spimonitor/setup_all_spim.rs b/target/ast10x0/tests/spimonitor/setup_all_spim.rs index c7206268..b6ba08cc 100644 --- a/target/ast10x0/tests/spimonitor/setup_all_spim.rs +++ b/target/ast10x0/tests/spimonitor/setup_all_spim.rs @@ -16,7 +16,7 @@ use ast10x0_board::{ enable_flash_power, release_spi_flash_resets, set_bmc_resets, spim_external_mux_state, }; use ast10x0_peripherals::scu::{ - ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorSource, pinctrl::PINCTRL_SPI1_QUAD, + pinctrl::PINCTRL_SPI1_QUAD, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorSource, }; use ast10x0_peripherals::smc::{ ChipSelect, FlashConfig, SmcConfig, SmcController, SmcError, SmcTopology, SpiReady, SpiUninit, @@ -27,7 +27,7 @@ use ast10x0_peripherals::spimonitor::{ ConfiguredSpiMonitor, LockedSpiMonitor, MonitorPolicy, PrivilegeDirection, PrivilegeOp, SpiMonitorController, ViolationLogEntry, }; -use target_common::{TargetInterface, declare_target}; +use target_common::{declare_target, TargetInterface}; use test_common::TestConfig; use {console_backend as _, entry as _}; From cea67d1d717f884dc3b8022b79266c0fc418b84e Mon Sep 17 00:00:00 2001 From: HaiLin Wu Date: Thu, 18 Jun 2026 12:30:17 -0700 Subject: [PATCH 13/13] fixed merge clipping issue --- target/ast10x0/peripherals/BUILD.bazel | 7 +- target/ast10x0/peripherals/smc/controller.rs | 3 +- .../peripherals/spimonitor/commands.rs | 118 ++++++++++++------ .../peripherals/spimonitor/controller.rs | 10 +- .../peripherals/spimonitor/registers.rs | 26 ++++ target/ast10x0/tests/spimonitor/BUILD.bazel | 4 + .../ast10x0/tests/spimonitor/test_common.rs | 15 ++- 7 files changed, 135 insertions(+), 48 deletions(-) diff --git a/target/ast10x0/peripherals/BUILD.bazel b/target/ast10x0/peripherals/BUILD.bazel index 509b5d54..08a0b726 100644 --- a/target/ast10x0/peripherals/BUILD.bazel +++ b/target/ast10x0/peripherals/BUILD.bazel @@ -51,8 +51,8 @@ rust_library( "smc/spi/spi.rs", "smc/spi/spi_transaction.rs", "smc/types.rs", - "spimonitor/controller.rs", "spimonitor/commands.rs", + "spimonitor/controller.rs", "spimonitor/mod.rs", "spimonitor/policy.rs", "spimonitor/profile.rs", @@ -86,4 +86,9 @@ rust_test( srcs = ["spimonitor/commands.rs"], crate_name = "spimonitor_commands_test", edition = "2024", + tags = ["do_not_build"], + target_compatible_with = [ + "@platforms//cpu:x86_64", + "@platforms//os:linux", + ], ) diff --git a/target/ast10x0/peripherals/smc/controller.rs b/target/ast10x0/peripherals/smc/controller.rs index bef00038..545b0280 100644 --- a/target/ast10x0/peripherals/smc/controller.rs +++ b/target/ast10x0/peripherals/smc/controller.rs @@ -618,8 +618,7 @@ impl Smc { let reg = self.regs.read_cs_ctrl(cs); self.regs .write_cs_ctrl(cs, (reg & !SPI_CTRL_FREQ_MASK) | encoded_div); - self.normal_read_ctrl[cs_idx] = self.regs.read_cs_ctrl(cs); - + self.set_normal_read_ctrl(cs, self.regs.read_cs_ctrl(cs)); Ok(()) } diff --git a/target/ast10x0/peripherals/spimonitor/commands.rs b/target/ast10x0/peripherals/spimonitor/commands.rs index 1024cbf0..aa71f779 100644 --- a/target/ast10x0/peripherals/spimonitor/commands.rs +++ b/target/ast10x0/peripherals/spimonitor/commands.rs @@ -34,12 +34,60 @@ impl CommandDescriptor { } } -const fn command( - opcode: u8, +#[derive(Clone, Copy)] +struct CommandFlags { generic: bool, write: bool, read: bool, memory: bool, +} + +const GENERIC_READ_MEMORY: CommandFlags = CommandFlags { + generic: true, + write: false, + read: true, + memory: true, +}; +const GENERIC_WRITE_MEMORY: CommandFlags = CommandFlags { + generic: true, + write: true, + read: false, + memory: true, +}; +const GENERIC_NONE: CommandFlags = CommandFlags { + generic: true, + write: false, + read: false, + memory: false, +}; +const GENERIC_READ: CommandFlags = CommandFlags { + generic: true, + write: false, + read: true, + memory: false, +}; +const GENERIC_WRITE: CommandFlags = CommandFlags { + generic: true, + write: true, + read: false, + memory: false, +}; +const RESERVED_NONE: CommandFlags = CommandFlags { + generic: false, + write: false, + read: false, + memory: false, +}; +const RESERVED_WRITE: CommandFlags = CommandFlags { + generic: false, + write: true, + read: false, + memory: false, +}; + +const fn command( + opcode: u8, + flags: CommandFlags, data_width: u8, dummy_cycles: u8, program_size: u8, @@ -48,10 +96,10 @@ const fn command( ) -> CommandDescriptor { CommandDescriptor { opcode, - generic, - write, - read, - memory, + generic: flags.generic, + write: flags.write, + read: flags.read, + memory: flags.memory, data_width, dummy_cycles, program_size, @@ -64,37 +112,33 @@ const fn command( #[must_use] pub const fn descriptor(opcode: u8) -> Option { let entry = match opcode { - 0x03 => command(opcode, true, false, true, true, 1, 0, 0, 3, 1), - 0x13 => command(opcode, true, false, true, true, 1, 0, 0, 4, 1), - 0x0b => command(opcode, true, false, true, true, 1, 8, 0, 3, 1), - 0x0c => command(opcode, true, false, true, true, 1, 8, 0, 4, 1), - 0x3b => command(opcode, true, false, true, true, 2, 8, 0, 3, 1), - 0x3c => command(opcode, true, false, true, true, 2, 8, 0, 4, 1), - 0xbb => command(opcode, true, false, true, true, 2, 4, 0, 3, 2), - 0xbc => command(opcode, true, false, true, true, 2, 4, 0, 4, 2), - 0x6b => command(opcode, true, false, true, true, 3, 8, 0, 3, 1), - 0x6c => command(opcode, true, false, true, true, 3, 8, 0, 4, 1), - 0xeb => command(opcode, true, false, true, true, 3, 6, 0, 3, 3), - 0xec => command(opcode, true, false, true, true, 3, 6, 0, 4, 3), - 0x02 => command(opcode, true, true, false, true, 1, 0, 1, 3, 1), - 0x12 => command(opcode, true, true, false, true, 1, 0, 1, 4, 1), - 0x32 => command(opcode, true, true, false, true, 3, 0, 1, 3, 1), - 0x34 => command(opcode, true, true, false, true, 3, 0, 1, 4, 1), - 0x20 => command(opcode, true, true, false, true, 0, 0, 1, 3, 1), - 0x21 => command(opcode, true, true, false, true, 0, 0, 1, 4, 1), - 0xd8 => command(opcode, true, true, false, true, 0, 0, 5, 3, 1), - 0xdc => command(opcode, true, true, false, true, 0, 0, 5, 4, 1), - 0x06 | 0x04 | 0x50 | 0x66 | 0x99 => { - command(opcode, true, false, false, false, 0, 0, 0, 0, 0) - } - 0x05 | 0x35 | 0x15 | 0x70 | 0x9f => { - command(opcode, true, false, true, false, 1, 0, 0, 0, 0) - } - 0x01 | 0x31 => command(opcode, true, true, false, false, 1, 0, 0, 0, 0), - 0x5a => command(opcode, true, false, true, false, 1, 8, 0, 3, 1), - 0xb7 | 0xe9 => command(opcode, false, false, false, false, 0, 0, 0, 0, 0), - 0xc5 => command(opcode, false, true, false, false, 1, 0, 0, 0, 0), - 0xc2 => command(opcode, true, true, false, false, 1, 0, 0, 0, 0), + 0x03 => command(opcode, GENERIC_READ_MEMORY, 1, 0, 0, 3, 1), + 0x13 => command(opcode, GENERIC_READ_MEMORY, 1, 0, 0, 4, 1), + 0x0b => command(opcode, GENERIC_READ_MEMORY, 1, 8, 0, 3, 1), + 0x0c => command(opcode, GENERIC_READ_MEMORY, 1, 8, 0, 4, 1), + 0x3b => command(opcode, GENERIC_READ_MEMORY, 2, 8, 0, 3, 1), + 0x3c => command(opcode, GENERIC_READ_MEMORY, 2, 8, 0, 4, 1), + 0xbb => command(opcode, GENERIC_READ_MEMORY, 2, 4, 0, 3, 2), + 0xbc => command(opcode, GENERIC_READ_MEMORY, 2, 4, 0, 4, 2), + 0x6b => command(opcode, GENERIC_READ_MEMORY, 3, 8, 0, 3, 1), + 0x6c => command(opcode, GENERIC_READ_MEMORY, 3, 8, 0, 4, 1), + 0xeb => command(opcode, GENERIC_READ_MEMORY, 3, 6, 0, 3, 3), + 0xec => command(opcode, GENERIC_READ_MEMORY, 3, 6, 0, 4, 3), + 0x02 => command(opcode, GENERIC_WRITE_MEMORY, 1, 0, 1, 3, 1), + 0x12 => command(opcode, GENERIC_WRITE_MEMORY, 1, 0, 1, 4, 1), + 0x32 => command(opcode, GENERIC_WRITE_MEMORY, 3, 0, 1, 3, 1), + 0x34 => command(opcode, GENERIC_WRITE_MEMORY, 3, 0, 1, 4, 1), + 0x20 => command(opcode, GENERIC_WRITE_MEMORY, 0, 0, 1, 3, 1), + 0x21 => command(opcode, GENERIC_WRITE_MEMORY, 0, 0, 1, 4, 1), + 0xd8 => command(opcode, GENERIC_WRITE_MEMORY, 0, 0, 5, 3, 1), + 0xdc => command(opcode, GENERIC_WRITE_MEMORY, 0, 0, 5, 4, 1), + 0x06 | 0x04 | 0x50 | 0x66 | 0x99 => command(opcode, GENERIC_NONE, 0, 0, 0, 0, 0), + 0x05 | 0x35 | 0x15 | 0x70 | 0x9f => command(opcode, GENERIC_READ, 1, 0, 0, 0, 0), + 0x01 | 0x31 => command(opcode, GENERIC_WRITE, 1, 0, 0, 0, 0), + 0x5a => command(opcode, GENERIC_READ, 1, 8, 0, 3, 1), + 0xb7 | 0xe9 => command(opcode, RESERVED_NONE, 0, 0, 0, 0, 0), + 0xc5 => command(opcode, RESERVED_WRITE, 1, 0, 0, 0, 0), + 0xc2 => command(opcode, GENERIC_WRITE, 1, 0, 0, 0, 0), _ => return None, }; Some(entry) diff --git a/target/ast10x0/peripherals/spimonitor/controller.rs b/target/ast10x0/peripherals/spimonitor/controller.rs index e38a4c72..252f8f38 100644 --- a/target/ast10x0/peripherals/spimonitor/controller.rs +++ b/target/ast10x0/peripherals/spimonitor/controller.rs @@ -656,14 +656,18 @@ fn configure_privilege_region( } } else { block += 1; - let value = regs.read_addr_filter_slot(word_index); + let value = regs + .read_addr_filter_slot_checked(word_index) + .ok_or(SpiMonitorError::InvalidAddress)?; 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 { + if !regs.write_addr_filter_slot_checked(word_index, updated) { + return Err(SpiMonitorError::InvalidAddress); + } + if regs.read_addr_filter_slot_checked(word_index) != Some(updated) { return Err(SpiMonitorError::VerificationFailed); } } diff --git a/target/ast10x0/peripherals/spimonitor/registers.rs b/target/ast10x0/peripherals/spimonitor/registers.rs index a44e3086..01fd6151 100644 --- a/target/ast10x0/peripherals/spimonitor/registers.rs +++ b/target/ast10x0/peripherals/spimonitor/registers.rs @@ -160,6 +160,14 @@ impl SpiMonitorRegisters { .write(|w| unsafe { w.bits(value) }); } + /// Read an allow-command slot without panicking for an invalid index. + pub fn read_allow_cmd_slot_checked(&self, index: usize) -> Option { + self.regs() + .spipfwt_iter() + .nth(index) + .map(|register| register.read().bits()) + } + /// SPIPFWA[n]: Address filter table entry. pub fn read_addr_filter_slot(&self, index: usize) -> u32 { self.regs().spipfwa(index).read().bits() @@ -171,6 +179,24 @@ impl SpiMonitorRegisters { .write(|w| unsafe { w.bits(value) }); } + /// Read an address-filter slot without panicking for an invalid index. + pub fn read_addr_filter_slot_checked(&self, index: usize) -> Option { + self.regs() + .spipfwa_iter() + .nth(index) + .map(|register| register.read().bits()) + } + + /// Write an address-filter slot, returning false for an invalid index. + #[must_use] + pub fn write_addr_filter_slot_checked(&self, index: usize, value: u32) -> bool { + let Some(register) = self.regs().spipfwa_iter().nth(index) else { + return false; + }; + register.write(|w| unsafe { w.bits(value) }); + true + } + // ----------------------------------------------------------------------- // Violation log registers // ----------------------------------------------------------------------- diff --git a/target/ast10x0/tests/spimonitor/BUILD.bazel b/target/ast10x0/tests/spimonitor/BUILD.bazel index 47f0d666..8618bbf1 100644 --- a/target/ast10x0/tests/spimonitor/BUILD.bazel +++ b/target/ast10x0/tests/spimonitor/BUILD.bazel @@ -27,9 +27,13 @@ target_linker_script( ) spimonitor_test(1, TARGET_COMPATIBLE_WITH) + spimonitor_test(2, TARGET_COMPATIBLE_WITH) + spimonitor_test(3, TARGET_COMPATIBLE_WITH) + spimonitor_test(4, TARGET_COMPATIBLE_WITH) + spimonitor_setup_all(TARGET_COMPATIBLE_WITH) alias( diff --git a/target/ast10x0/tests/spimonitor/test_common.rs b/target/ast10x0/tests/spimonitor/test_common.rs index aa850515..4b57a1cc 100644 --- a/target/ast10x0/tests/spimonitor/test_common.rs +++ b/target/ast10x0/tests/spimonitor/test_common.rs @@ -255,15 +255,20 @@ fn test_command_policy(configured: &ConfiguredSpiMonitor) -> Result<(), TestErro pw_log::info!("START: command table add and remove"); configured.remove_command(0x05)?; let status_slot = configured.add_command(0x05, false)?; + let status_value = configured + .regs() + .read_allow_cmd_slot_checked(status_slot) + .ok_or(TestError::Check)?; test_check!( - configured.regs().read_allow_cmd_slot(status_slot) & COMMAND_VALID_MASK != 0, + status_value & COMMAND_VALID_MASK != 0, "FAIL: command add readback" ); configured.remove_command(0x05)?; - test_check!( - configured.regs().read_allow_cmd_slot(status_slot) == 0, - "FAIL: command remove did not clear slot" - ); + let status_value = configured + .regs() + .read_allow_cmd_slot_checked(status_slot) + .ok_or(TestError::Check)?; + test_check!(status_value == 0, "FAIL: command remove did not clear slot"); configured.add_command(0x05, false)?; pw_log::info!("PASS: command table add and remove"); Ok(())