Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 37 additions & 23 deletions target/ast10x0/board/src/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,29 +89,31 @@ impl<'a> Ast1060Monitor<'a> {
}
}

/// Map MuxSelect to SCU external mux select value.
/// Map MuxSelect to SCU external mux select value (board ext-mux polarity).
///
/// `Mux1` = RoT SPI master path; `Mux0` = host (BMC) flash access.
fn mux_to_scu(mux: MuxSelect) -> ScuExtMuxSelect {
match mux {
MuxSelect::RotControl => ScuExtMuxSelect::Mux0,
MuxSelect::HostControl => ScuExtMuxSelect::Mux1,
MuxSelect::RotControl => ScuExtMuxSelect::Mux1,
MuxSelect::HostControl => ScuExtMuxSelect::Mux0,
}
}

/// Map SCU external mux select back to MuxSelect.
fn scu_to_mux(scu_mux: ScuExtMuxSelect) -> MuxSelect {
match scu_mux {
ScuExtMuxSelect::Mux0 => MuxSelect::RotControl,
ScuExtMuxSelect::Mux1 => MuxSelect::HostControl,
ScuExtMuxSelect::Mux0 => MuxSelect::HostControl,
ScuExtMuxSelect::Mux1 => MuxSelect::RotControl,
}
}

/// Extract enforcement active flag from CTRL register.
/// Enforcement is active when passthrough bits are NOT set.
/// - SPIPF000[1] = enbl_single_bit_passthrough
/// - SPIPF000[2] = enbl_multiple_bit_passthrough
/// - SPIPF000[0] = enbl_single_bit_passthrough
/// - SPIPF000[1] = enbl_multiple_bit_passthrough
/// When both bits are 0, enforcement is active and SPI commands are filtered.
fn is_enforcement_active(ctrl: u32) -> bool {
let pass_bits = (ctrl >> 1) & 0x3;
let pass_bits = ctrl & 0x3;
pass_bits == 0 // Enforcement active when passthrough disabled
}

Expand Down Expand Up @@ -143,17 +145,9 @@ impl<'a> Monitor for Ast1060Monitor<'a> {
}

fn soft_reset(&mut self, instance: MonitorInstance) -> BootResult<()> {
let regs = self.regs_mut(instance);
// Soft reset clears status/logs but preserves policy.
// NON-BLOCKING TODO 1: Verify soft reset bit position from AST10x0 datasheet.
// Current: uses bit 7 in SPIPF000 as placeholder. May need adjustment.
// NON-BLOCKING TODO 2: Implement polling/timeout after write.
// Pattern: poll SPIPF000 until reset bit clears or timeout (e.g., 100 microseconds).
// In aspeed-rust, hardware self-clears the bit after reset completes.
let mut ctrl = regs.read_ctrl();
ctrl |= 0x80; // Soft reset bit (placeholder - verify with datasheet)
regs.write_ctrl(ctrl);
// TODO: Poll until bit 7 clears, with timeout check
// Soft reset clears status/logs but preserves policy: pulse SPIPF000
// `sweng_rst` (bit 15).
self.regs_mut(instance).sw_reset();
Ok(())
}

Expand Down Expand Up @@ -185,7 +179,7 @@ impl<'a> Monitor for Ast1060Monitor<'a> {

let regs = self.regs_mut(instance);

// Address filtering in AST10x0 uses 16KB blocks (ACCESS_BLOCK_UNIT from aspeed-rust).
// Address filtering in AST10x0 uses 16KB blocks (ACCESS_BLOCK_UNIT).
// Each SPIPFWA register controls 32 blocks per register (32 * 16KB = 512KB per register).
// NON-BLOCKING TODO: Implement dynamic slot allocation instead of fixed slot 0.
// Phase C work: currently allocates all regions to slot 0 for simplicity.
Expand All @@ -201,8 +195,8 @@ impl<'a> Monitor for Ast1060Monitor<'a> {
}

fn read_region_count(&self, _instance: MonitorInstance) -> BootResult<u32> {
// Region count is tracked in memory (following aspeed-rust pattern).
// aspeed-rust stores read_blocked_region_num and write_blocked_region_num as struct fields.
// Region count is tracked in memory via the read_blocked_region_count
// and write_blocked_region_count struct fields.
// We return the read-blocked region count for now; write-blocked can be exposed via separate method if needed.
Ok(self.read_blocked_region_count as u32)
}
Expand Down Expand Up @@ -239,7 +233,7 @@ impl<'a> Monitor for Ast1060Monitor<'a> {

let regs = self.regs_mut(instance);
// Policy lock is controlled by SPIPF07C register.
// From aspeed-rust: bit 0 is `wr_dis_of_spipfwa` (write disable for address tables).
// Bit 0 is `wr_dis_of_spipfwa` (write disable for address tables).
let mut lock_status = regs.read_lock_status();
lock_status |= 0x1; // Set wr_dis_of_spipfwa bit
regs.write_lock_status(lock_status);
Expand All @@ -265,6 +259,26 @@ impl<'a> Monitor for Ast1060Monitor<'a> {
mod tests {
use super::*;

#[test]
fn test_mux_mapping() {
assert_eq!(
Ast1060Monitor::mux_to_scu(MuxSelect::RotControl),
ScuExtMuxSelect::Mux1
);
assert_eq!(
Ast1060Monitor::mux_to_scu(MuxSelect::HostControl),
ScuExtMuxSelect::Mux0
);
assert_eq!(
Ast1060Monitor::scu_to_mux(ScuExtMuxSelect::Mux1),
MuxSelect::RotControl
);
assert_eq!(
Ast1060Monitor::scu_to_mux(ScuExtMuxSelect::Mux0),
MuxSelect::HostControl
);
}

#[test]
fn test_enforcement_flag() {
assert!(!Ast1060Monitor::is_enforcement_active(0));
Expand Down
40 changes: 30 additions & 10 deletions target/ast10x0/board/src/spim_wiring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
//! Composes the `scu::routing` mux helpers with the `spimonitor::controller`
//! typestate to apply once-per-process SPIM routing and SPIPF policy at
//! backend init time. Per-transaction reroutes are explicitly out of scope:
//! the SPIPF lock is one-way, and the design doc
//! (`peripherals/spimonitor/planning/overview-and-usage-model.md`) calls for
//! "configure early, validate, lock, and operate under that locked policy."
//! the SPIPF lock is one-way, so the design is to configure early, validate,
//! lock, and operate under that locked policy.

use ast10x0_peripherals::scu::{
ScuError, ScuExtMuxSelect, ScuRegisters, SpiMonitorInstance, SpiMonitorPassthrough,
Expand Down Expand Up @@ -40,8 +39,8 @@ pub struct SpimWiring {
}

impl SpimWiring {
/// Default wiring for `SmcController::Spi1` (aspeed-rust SPI0,
/// `master_idx = 0`) routed through `Spim0` (SPIPF1 @ `0x7E79_1000`).
/// Default wiring for `SmcController::Spi1` (`master_idx = 0`) routed
/// through `Spim0` (SPIPF1 @ `0x7E79_1000`).
#[must_use]
pub const fn default_spi1_via_spim0() -> Self {
Self {
Expand All @@ -53,8 +52,8 @@ impl SpimWiring {
}
}

/// Default wiring for `SmcController::Spi2` (aspeed-rust SPI1,
/// `master_idx = 2`) routed through `Spim2` (SPIPF3 @ `0x7E79_3000`).
/// Default wiring for `SmcController::Spi2` (`master_idx = 2`) routed
/// through `Spim2` (SPIPF3 @ `0x7E79_3000`).
#[must_use]
pub const fn default_spi2_via_spim2() -> Self {
Self {
Expand Down Expand Up @@ -116,7 +115,7 @@ pub unsafe fn apply_spim_wiring(
scu.set_spim_ext_mux(wiring.instance, wiring.ext_mux);
scu.set_spim_miso_multi_func(wiring.instance, wiring.miso_multi_func);

let monitor_controller = match wiring.instance {
let monitor_controller: SpiMonitorController = match wiring.instance {
SpiMonitorInstance::Spim0 => SpiMonitorController::Spim0,
SpiMonitorInstance::Spim1 => SpiMonitorController::Spim1,
SpiMonitorInstance::Spim2 => SpiMonitorController::Spim2,
Expand Down Expand Up @@ -144,9 +143,30 @@ fn validate_controller_for_source(
}
}

/// Built-in `MonitorPolicy` presets vetted against the BMC's flash opcode set.
/// Built-in `MonitorPolicy` presets for **provisioned** filter handover.
pub mod presets {
use ast10x0_peripherals::spimonitor::MonitorPolicy;
use ast10x0_peripherals::spimonitor::{
MonitorPolicy, PrivilegeDirection, PrivilegeOp,
};

/// Allow-cmd whitelist for the BMC (32 opcodes).
const BMC_ALLOW_CMDS: [u8; 32] = [
0x03, 0x13, 0x0b, 0x0c, 0x6b, 0x6c, 0x01, 0x05, 0x35, 0x06, 0x04, 0x20, 0x21, 0x9f,
0x5a, 0xb7, 0xe9, 0x32, 0x34, 0xd8, 0xdc, 0x02, 0x12, 0x3b, 0x3c, 0x70, 0xbb, 0xbc,
0x50, 0xeb, 0xec, 0xc2,
];

/// Filter policy for the BMC SPIM: allow-cmd table plus full-flash
/// read/write regions.
#[must_use]
pub fn bmc_filter_policy() -> MonitorPolicy {
let mut p = MonitorPolicy::empty();
p.allow_commands = BMC_ALLOW_CMDS;
p.allow_command_count = BMC_ALLOW_CMDS.len();
let _ = p.add_region(0, 0x1000_0000, PrivilegeDirection::Read, PrivilegeOp::Enable);
let _ = p.add_region(0, 0x1000_0000, PrivilegeDirection::Write, PrivilegeOp::Enable);
p
}

/// Allow-list for the BMC's normal flash opcodes covering both 3-byte and
/// 4-byte addressing variants. Empty `regions` (no address-privilege
Expand Down
2 changes: 2 additions & 0 deletions target/ast10x0/peripherals/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ rust_library(
"smc/spi/spi.rs",
"smc/spi/spi_transaction.rs",
"smc/types.rs",
"spimonitor/addr_priv.rs",
"spimonitor/cmd_table.rs",
"spimonitor/controller.rs",
"spimonitor/mod.rs",
"spimonitor/policy.rs",
Expand Down
40 changes: 40 additions & 0 deletions target/ast10x0/peripherals/scu/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,46 @@ impl ScuRegisters {
}
}

/// Enable or disable the SCU-side SPI monitor block for an instance.
///
/// Uses SCU0F0[11:8] (`enbl_filter_of_spipfN`). Required together with
/// SPIPF000 single passthrough for push-pull IO.
pub fn set_spim_scu_monitor_enable(&self, instance: SpiMonitorInstance, enable: bool) {
self.unlock_write_protection();
self.regs().scu0f0().modify(|_, w| match instance {
SpiMonitorInstance::Spim0 => w.enbl_filter_of_spipf1().bit(enable),
SpiMonitorInstance::Spim1 => w.enbl_filter_of_spipf2().bit(enable),
SpiMonitorInstance::Spim2 => w.enbl_filter_of_spipf3().bit(enable),
SpiMonitorInstance::Spim3 => w.enbl_filter_of_spipf4().bit(enable),
});
}

/// Disable the SPI master CS internal pull-down for a monitor instance.
///
/// SPI_M1 (SPIM0): GPIOA6 / SCU610[6]; SPI_M2 (SPIM1): GPIOC0 / SCU610[20];
/// SPI_M4 (SPIM3): GPIOG0 / SCU614[16]. SPIM2 has no disable bit.
pub fn spim_disable_cs_internal_pd(&self, instance: SpiMonitorInstance) {
self.unlock_write_protection();
match instance {
SpiMonitorInstance::Spim0 => {
self.regs()
.scu610()
.modify(|_, w| w.dis_gpioa6int_pull_down().bit(true));
}
SpiMonitorInstance::Spim1 => {
self.regs()
.scu610()
.modify(|_, w| w.dis_gpioc0int_pull_down().bit(true));
}
SpiMonitorInstance::Spim2 => {}
SpiMonitorInstance::Spim3 => {
self.regs()
.scu614()
.modify(|_, w| w.dis_gpiog0int_pull_down().bit(true));
}
}
}

/// Enable or disable the SCU-controlled MISO multi-function pin for a SPI
/// monitor instance.
pub fn set_spim_miso_multi_func(&self, instance: SpiMonitorInstance, enable: bool) {
Expand Down
118 changes: 118 additions & 0 deletions target/ast10x0/peripherals/spimonitor/addr_priv.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0

//! SPIPF address-privilege table programming (SPIPFWA @ 0x0100).
//!
//! Each bit in SPIPFWA[n] grants one 16 KiB block for the selected read/write
//! table (selected via SPIPF000[31:24] magic bytes).

use crate::spimonitor::registers::SpiMonitorRegisters;
use crate::spimonitor::types::{PrivilegeDirection, PrivilegeOp, Result, SpiMonitorError};

/// 16 KiB address-privilege granule.
pub const ACCESS_BLOCK_UNIT: u32 = 16 * 1024;
/// One SPIPFWA register covers 32 × 16 KiB = 512 KiB.
pub const ACCESS_BLOCK_PER_REG: u32 = 32 * ACCESS_BLOCK_UNIT;
/// Maximum flash address space covered by the privilege tables.
pub const ADDR_LIMIT: u32 = 256 * 1024 * 1024;

const SEL_READ_TBL_MAGIC: u32 = 0x52 << 24;
const SEL_WRITE_TBL_MAGIC: u32 = 0x57 << 24;

fn select_addr_priv_table(regs: &SpiMonitorRegisters, direction: PrivilegeDirection) {
regs.modify_ctrl(|bits| {
*bits &= 0x00FF_FFFF;
match direction {
PrivilegeDirection::Read => *bits |= SEL_READ_TBL_MAGIC,
PrivilegeDirection::Write => *bits |= SEL_WRITE_TBL_MAGIC,
}
});
}

fn adjusted_addr_len(addr: u32, len: u32) -> (u32, u32) {
if len == 0 {
return (addr, 0);
}
let mut adjusted_len = len;
let mut aligned_addr = addr;
if !addr.is_multiple_of(ACCESS_BLOCK_UNIT) {
adjusted_len += addr % ACCESS_BLOCK_UNIT;
aligned_addr = (addr / ACCESS_BLOCK_UNIT) * ACCESS_BLOCK_UNIT;
}
adjusted_len = adjusted_len.div_ceil(ACCESS_BLOCK_UNIT) * ACCESS_BLOCK_UNIT;
(aligned_addr, adjusted_len)
}

/// Program a contiguous address range in the read or write privilege table.
pub fn configure_address_privilege(
regs: &SpiMonitorRegisters,
direction: PrivilegeDirection,
op: PrivilegeOp,
addr: u32,
len: u32,
) -> Result<()> {
if addr >= ADDR_LIMIT {
return Err(SpiMonitorError::InvalidRegion);
}
if len == 0 || addr.saturating_add(len) > ADDR_LIMIT {
return Err(SpiMonitorError::InvalidRegion);
}

let (aligned_addr, adjusted_len) = adjusted_addr_len(addr, len);
if adjusted_len == 0 {
return Ok(());
}

let mut reg_off = (aligned_addr / ACCESS_BLOCK_PER_REG) as usize;
let mut bit_off = ((aligned_addr % ACCESS_BLOCK_PER_REG) / ACCESS_BLOCK_UNIT) as u32;
let mut total_bit_num = adjusted_len / ACCESS_BLOCK_UNIT;

select_addr_priv_table(regs, direction);

while total_bit_num > 0 {
if bit_off > 31 {
bit_off = 0;
reg_off += 1;
}

if bit_off == 0 && total_bit_num >= 32 {
let word = match op {
PrivilegeOp::Enable => 0xFFFF_FFFF,
PrivilegeOp::Disable => 0,
};
regs.write_addr_filter_slot(reg_off, word);
reg_off += 1;
total_bit_num -= 32;
} else {
let mut reg_val = regs.read_addr_filter_slot(reg_off);
match op {
PrivilegeOp::Enable => reg_val |= 1 << bit_off,
PrivilegeOp::Disable => reg_val &= !(1 << bit_off),
}
regs.write_addr_filter_slot(reg_off, reg_val);
bit_off += 1;
total_bit_num -= 1;
}
}

Ok(())
}

/// Enable full-flash read and write in the addr-priv tables.
pub fn configure_full_flash_rw(regs: &SpiMonitorRegisters) -> Result<()> {
configure_address_privilege(regs, PrivilegeDirection::Read, PrivilegeOp::Enable, 0, ADDR_LIMIT)?;
configure_address_privilege(regs, PrivilegeDirection::Write, PrivilegeOp::Enable, 0, ADDR_LIMIT)?;
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn adjusted_addr_len_aligns_to_16k() {
let (addr, len) = adjusted_addr_len(0x1000, 0x2000);
assert_eq!(addr, 0);
assert_eq!(len, 0x3000);
}
}
Loading
Loading