diff --git a/embassy-stm32/src/gtzc/mod.rs b/embassy-stm32/src/gtzc/mod.rs new file mode 100644 index 0000000000..e86584f8ea --- /dev/null +++ b/embassy-stm32/src/gtzc/mod.rs @@ -0,0 +1,392 @@ +//! Global TrustZone Controller (GTZC) driver. +//! +//! The GTZC provides hardware-enforced TrustZone security for STM32 microcontrollers. +//! It consists of three sub-units: +//! +//! - **TZSC** (TrustZone Security Controller): configures which peripherals are accessible +//! from the Non-Secure world and which require privileged access. +//! - **MPCBB** (Memory Protection Controller Block-Based): configures security and privilege +//! attributes for individual 512-byte SRAM blocks. +//! - **TZIC** (TrustZone Illegal access Controller): generates interrupts when a Non-Secure +//! or unprivileged access is made to a Secure or privileged-only resource. +//! +//! # Supported families +//! +//! | Variant | Family | TZSC | MPCBB | TZIC | MPCWM | +//! |-------------|---------------|--------|-------|------|-------| +//! | `gtzc_wba` | WBA | Full | Yes | Yes | No | +//! | `gtzc_v1` | U5 / H5 | Full | Yes | Yes | Yes | +//! | `gtzc_h503` | H503 | PrivOnly| Yes | No | BKPSRAM| +//! +//! # Usage +//! +//! All GTZC configuration must be performed from the Secure world. **Enable the GTZC +//! clock first** via [`enable_clock()`], then configure MPCBB/TZIC, and finally call +//! [`lock()`] to prevent Non-Secure code from modifying the configuration. +//! +//! MPCBB and TZIC instances are accessed via chip-specific PAC constants +//! (e.g., `pac::GTZC_MPCBB1`, `pac::GTZC_TZIC`). Consult your device reference +//! manual for which instances are present. +//! +//! ```no_run +//! use embassy_stm32::{gtzc, pac}; +//! +//! // Enable GTZC clock before any register access. +//! unsafe { gtzc::enable_clock(); } +//! +//! // Configure SRAM1 blocks as Non-Secure (wba or v1) +//! let mpcbb1 = unsafe { gtzc::Mpcbb::new(pac::GTZC_MPCBB1) }; +//! mpcbb1.set_all_secure(/*n_regs=*/14, false); // SRAM1 = 28 blocks × 512 B on WBA65 +//! +//! // Enable TZIC to generate IRQs on illegal accesses (wba or v1) +//! let tzic = unsafe { gtzc::Tzic::new(pac::GTZC_TZIC) }; +//! tzic.enable_all(/*n_regs=*/4); +//! +//! // Lock TZSC so the Non-Secure world cannot reconfigure it (wba or v1) +//! unsafe { gtzc::lock(); } +//! ``` + +#[cfg(any(gtzc_wba, gtzc_v1))] +use crate::pac; + +// ──────────────────────────────────────────────────────────────────────────── +// Clock enable +// ──────────────────────────────────────────────────────────────────────────── + +/// Enable the GTZC peripheral clock. +/// +/// Must be called before accessing any GTZC register (TZSC, MPCBB, TZIC). +/// Failing to enable the clock causes hard faults on GTZC register reads/writes. +/// +/// # Safety +/// Must be called from the Secure world. +// WBA and U5 use `gtzc1en`; H5 (rcc_h5) names the same bit `tzsc1en`. +// H503 (rcc_h50) has no software clock gate for GTZC — always-on. +#[cfg(any(gtzc_wba, all(gtzc_v1, not(stm32h5))))] +pub unsafe fn enable_clock() { + pac::RCC.ahb1enr().modify(|r| r.set_gtzc1en(true)); +} + +/// Enable the GTZC peripheral clock. +/// +/// Must be called before accessing any GTZC register (TZSC, MPCBB, TZIC). +/// Failing to enable the clock causes hard faults on GTZC register reads/writes. +/// +/// # Safety +/// Must be called from the Secure world. +#[cfg(all(gtzc_v1, stm32h5))] +pub unsafe fn enable_clock() { + pac::RCC.ahb1enr().modify(|r| r.set_tzsc1en(true)); +} + +// ──────────────────────────────────────────────────────────────────────────── +// MPCBB — Memory Protection Controller Block-Based +// ──────────────────────────────────────────────────────────────────────────── + +/// Memory Protection Controller Block-Based (MPCBB) wrapper. +/// +/// Controls security and privilege attributes of individual 512-byte SRAM blocks. +/// Each `seccfgr` / `privcfgr` register covers 32 contiguous blocks (= 16 KiB). +/// Every 32 blocks form one **superblock** that can be independently locked via +/// [`lock_superblock`]. +/// +/// Obtain a wrapper via [`Mpcbb::new`] with the chip-specific PAC constant for +/// the desired SRAM, e.g., `pac::GTZC_MPCBB1`, `pac::GTZC_MPCBB2`, etc. +#[cfg(any(gtzc_wba, gtzc_v1))] +pub struct Mpcbb { + inner: pac::gtzc::Mpcbb, +} + +#[cfg(any(gtzc_wba, gtzc_v1))] +impl Mpcbb { + /// Wrap a PAC MPCBB peripheral instance. + /// + /// # Safety + /// The caller must ensure no concurrent access to the same MPCBB instance and + /// that this code runs in the Secure world. + #[inline] + pub unsafe fn new(inner: pac::gtzc::Mpcbb) -> Self { + Self { inner } + } + + /// Globally lock this MPCBB's configuration. + /// + /// Once locked the security and privilege configuration cannot be changed until + /// the next hardware reset. + #[inline] + pub fn lock(&self) { + self.inner.cr().modify(|r| r.set_glock(true)); + } + + /// Returns `true` if the global lock has been set. + #[inline] + pub fn is_locked(&self) -> bool { + self.inner.cr().read().glock() + } + + /// Lock a single superblock (32 blocks = 16 KiB). + /// + /// `superblock` is a 0-based superblock index within this MPCBB. Each call locks + /// one bit in the CFGLOCK register; the lock is cleared only by hardware reset. + /// + /// Note: each bit in CFGLOCK corresponds to one 16 KiB superblock. On WBA, the + /// CFGLOCK register holds all superblock lock bits as a packed bitmask. + #[inline] + pub fn lock_superblock(&self, superblock: usize) { + self.inner + .cfglock() + .modify(|r| r.set_splck(r.splck() | (1u32 << superblock))); + } + + /// Lock the superblocks indicated by `mask`. + /// + /// Each bit `n` in `mask` corresponds to superblock `n`. Bits already set are + /// unaffected (lock is one-way). + #[inline] + pub fn lock_superblocks(&self, mask: u32) { + self.inner.cfglock().modify(|r| r.set_splck(r.splck() | mask)); + } + + /// Returns the current superblock lock bitmask from CFGLOCK. + #[inline] + pub fn superblock_lock_mask(&self) -> u32 { + self.inner.cfglock().read().splck() + } + + /// Configure the Secure Read/Write Illegal Access Disable (SRWILADIS) bit. + /// + /// When `true`, illegal accesses **from the Secure world** to Secure MPCBB blocks + /// do **not** generate a TZIC interrupt or flag. This is useful to suppress + /// spurious Secure-world self-access violations during initialization. + /// + /// Default after reset: `false` (all illegal accesses generate TZIC events). + #[inline] + pub fn set_srwiladis(&self, disabled: bool) { + self.inner.cr().modify(|r| r.set_srwiladis(disabled)); + } + + /// Returns the current SRWILADIS setting. + #[inline] + pub fn srwiladis(&self) -> bool { + self.inner.cr().read().srwiladis() + } + + /// Configure the Inverted Security State (INVSECSTATE) bit. + /// + /// When `true`, the security polarity of all blocks in this MPCBB is inverted: + /// a `seccfgr` bit of `0` means Secure, and `1` means Non-Secure. This is + /// an advanced option that is rarely needed; leave `false` for normal operation. + #[inline] + pub fn set_invsecstate(&self, inverted: bool) { + self.inner.cr().modify(|r| r.set_invsecstate(inverted)); + } + + /// Returns the current INVSECSTATE setting. + #[inline] + pub fn invsecstate(&self) -> bool { + self.inner.cr().read().invsecstate() + } + + /// Set the security attribute of a single 512-byte block. + /// + /// `block_idx` is a 0-based index into this SRAM region. + /// `secure = true` marks the block as Secure (Non-Secure accesses generate an IRQ); + /// `secure = false` makes it accessible from both worlds. + #[inline] + pub fn set_block_secure(&self, block_idx: usize, secure: bool) { + let reg = block_idx / 32; + let bit = block_idx % 32; + self.inner.seccfgr(reg).modify(|r| { + if secure { + r.set_sec(r.sec() | (1u32 << bit)); + } else { + r.set_sec(r.sec() & !(1u32 << bit)); + } + }); + } + + /// Set the privilege attribute of a single 512-byte block. + /// + /// `privileged = true` restricts access to privileged mode only. + #[inline] + pub fn set_block_privileged(&self, block_idx: usize, privileged: bool) { + let reg = block_idx / 32; + let bit = block_idx % 32; + self.inner.privcfgr(reg).modify(|r| { + if privileged { + r.set_priv_(r.priv_() | (1u32 << bit)); + } else { + r.set_priv_(r.priv_() & !(1u32 << bit)); + } + }); + } + + /// Write a raw security bitmap covering 32 contiguous blocks. + /// + /// `reg` selects which group of 32 blocks to configure (0-based). + /// Each bit in `bits` corresponds to one block: `1` = Secure, `0` = Non-Secure. + #[inline] + pub fn set_seccfgr(&self, reg: usize, bits: u32) { + self.inner.seccfgr(reg).write(|r| r.set_sec(bits)); + } + + /// Write a raw privilege bitmap covering 32 contiguous blocks. + /// + /// Each bit: `1` = Privileged-only, `0` = Unprivileged-accessible. + #[inline] + pub fn set_privcfgr(&self, reg: usize, bits: u32) { + self.inner.privcfgr(reg).write(|r| r.set_priv_(bits)); + } + + /// Read the raw security bitmap for a group of 32 blocks. + #[inline] + pub fn seccfgr(&self, reg: usize) -> u32 { + self.inner.seccfgr(reg).read().sec() + } + + /// Read the raw privilege bitmap for a group of 32 blocks. + #[inline] + pub fn privcfgr(&self, reg: usize) -> u32 { + self.inner.privcfgr(reg).read().priv_() + } + + /// Set all blocks as Secure or Non-Secure in one call. + /// + /// `n_regs` is the number of 32-block registers that cover this SRAM. + /// Calculate as `sram_size_bytes.div_ceil(16 * 1024)`. For example: + /// - 448 KiB SRAM (WBA65 SRAM1) → `n_regs = 28` + /// - 64 KiB SRAM (WBA65 SRAM2) → `n_regs = 4` + /// - 16 KiB SRAM (WBA65 SRAM6) → `n_regs = 1` + pub fn set_all_secure(&self, n_regs: usize, secure: bool) { + let bits = if secure { 0xFFFF_FFFF } else { 0 }; + for n in 0..n_regs { + self.inner.seccfgr(n).write(|r| r.set_sec(bits)); + } + } + + /// Set all blocks as Privileged-only or Unprivileged-accessible in one call. + /// + /// See [`set_all_secure`] for the `n_regs` calculation. + pub fn set_all_privileged(&self, n_regs: usize, privileged: bool) { + let bits = if privileged { 0xFFFF_FFFF } else { 0 }; + for n in 0..n_regs { + self.inner.privcfgr(n).write(|r| r.set_priv_(bits)); + } + } +} + +// ──────────────────────────────────────────────────────────────────────────── +// TZIC — TrustZone Illegal access Controller +// ──────────────────────────────────────────────────────────────────────────── + +/// TrustZone Illegal access Controller (TZIC) wrapper. +/// +/// Generates an interrupt when a Non-Secure or unprivileged access is attempted +/// to a resource marked Secure or Privileged-only by TZSC or MPCBB. +/// +/// Each register covers a group of peripherals/blocks. Consult your device +/// reference manual for the number of groups available on your chip. +/// +/// Obtain a wrapper via [`Tzic::new`] with `pac::GTZC_TZIC`. +#[cfg(any(gtzc_wba, gtzc_v1))] +pub struct Tzic { + inner: pac::gtzc::Tzic, +} + +#[cfg(any(gtzc_wba, gtzc_v1))] +impl Tzic { + /// Wrap a PAC TZIC peripheral instance. + /// + /// # Safety + /// The caller must ensure no concurrent access and that this runs in Secure world. + #[inline] + pub unsafe fn new(inner: pac::gtzc::Tzic) -> Self { + Self { inner } + } + + /// Enable all illegal-access IRQs for register group `reg`. + #[inline] + pub fn enable_irqs(&self, reg: usize) { + self.inner.ier(reg).write(|r| r.set_ie(0xFFFF_FFFF)); + } + + /// Disable all illegal-access IRQs for register group `reg`. + #[inline] + pub fn disable_irqs(&self, reg: usize) { + self.inner.ier(reg).write(|r| r.set_ie(0)); + } + + /// Clear all pending illegal-access flags for register group `reg`. + #[inline] + pub fn clear_flags(&self, reg: usize) { + self.inner.fcr(reg).write(|r| r.set_cf(0xFFFF_FFFF)); + } + + /// Read pending illegal-access flags for register group `reg`. + /// + /// Each bit corresponds to one peripheral or SRAM block; `1` = access violation pending. + #[inline] + pub fn status(&self, reg: usize) -> u32 { + self.inner.sr(reg).read().f() + } + + /// Clear any pending flags then enable IRQs for all `n_regs` register groups. + /// + /// Typical call: + /// - WBA65: `tzic.enable_all(4)` + /// - U585: `tzic.enable_all(3)` + pub fn enable_all(&self, n_regs: usize) { + for reg in 0..n_regs { + self.clear_flags(reg); + self.enable_irqs(reg); + } + } +} + +// ──────────────────────────────────────────────────────────────────────────── +// TZSC — TrustZone Security Controller (lock / raw access helpers) +// ──────────────────────────────────────────────────────────────────────────── + +/// Lock the TZSC security and privilege configuration until the next reset. +/// +/// After locking, no code (Secure or Non-Secure) can modify the TZSC register +/// contents. Typically called at the end of the Secure world's boot setup. +/// +/// For per-peripheral security/privilege configuration access the TZSC PAC registers +/// directly via `pac::GTZC_TZSC` — field names are chip-specific: +/// +/// - **WBA** (`gtzc_wba`): `pac::GTZC_TZSC.tzsc_seccfgr1().modify(|r| r.set_usart1sec(false))` +/// - **U5 / H5** (`gtzc_v1`): `pac::GTZC_TZSC.seccfgr1().modify(|r| r.set_usart1sec(false))` +/// +/// # Safety +/// Must be called from the Secure world. +#[cfg(any(gtzc_wba, gtzc_v1))] +pub unsafe fn lock() { + #[cfg(gtzc_wba)] + pac::GTZC_TZSC.tzsc_cr().modify(|r| r.set_lck(true)); + + #[cfg(gtzc_v1)] + pac::GTZC_TZSC.cr().modify(|r| r.set_lck(true)); +} + +/// Returns `true` if the TZSC configuration is locked. +#[cfg(any(gtzc_wba, gtzc_v1))] +pub fn is_locked() -> bool { + #[cfg(gtzc_wba)] + return pac::GTZC_TZSC.tzsc_cr().read().lck(); + + #[cfg(gtzc_v1)] + return pac::GTZC_TZSC.cr().read().lck(); +} + +// ──────────────────────────────────────────────────────────────────────────── +// MPCWM — Memory Protection Controller Watermark (gtzc_v1 only) +// ──────────────────────────────────────────────────────────────────────────── +// +// The MPCWM allows configuring security and privilege of sub-regions within +// external memories (FMC, OCTOSPI, BKPSRAM). Each watermark region A/B has: +// - CFGRx: SREN (sub-region enable), SRLOCK, SEC, PRIV +// - Rx: SUBA_START (granularity), SUBA_LENGTH +// +// Direct access via `pac::GTZC_TZSC.mpcwm1acfgr()` etc. is recommended until +// a higher-level MPCWM API is added. diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 05bf0416bd..841d86a40e 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -102,6 +102,8 @@ pub mod exti; pub mod flash; #[cfg(fmc)] pub mod fmc; +#[cfg(any(gtzc_wba, gtzc_v1, gtzc_h503, gtzc_l5))] +pub mod gtzc; #[cfg(hash)] pub mod hash; #[cfg(all(hrtim, feature = "stm32-hrtim"))] @@ -140,6 +142,8 @@ pub mod rtc; pub mod saes; #[cfg(sai)] pub mod sai; +#[cfg(any(gtzc_wba, gtzc_v1, gtzc_h503, gtzc_l5))] +pub mod sau; #[cfg(sdmmc)] pub mod sdmmc; #[cfg(spdifrx)] diff --git a/embassy-stm32/src/sau/mod.rs b/embassy-stm32/src/sau/mod.rs new file mode 100644 index 0000000000..6b2b7dd16e --- /dev/null +++ b/embassy-stm32/src/sau/mod.rs @@ -0,0 +1,209 @@ +//! Security Attribution Unit (SAU) driver. +//! +//! The SAU is a Cortex-M Security Extension core peripheral that defines which memory +//! regions are Secure, Non-Secure, or Non-Secure Callable. It is present on all +//! Cortex-M23 and Cortex-M33 processors that implement the TrustZone extension. +//! +//! Up to 8 configurable regions are supported. Memory not covered by any enabled region +//! is treated as **Secure** when the SAU is enabled. +//! +//! # Usage +//! +//! ```no_run +//! use embassy_stm32::sau::{Attribute, Region, init}; +//! +//! let regions = [ +//! Region { +//! base_address: 0x0810_0000, // Non-Secure Flash (must be 32-byte aligned) +//! end_address: 0x081F_FFFF, // Inclusive end (lower 5 bits must be 0x1F) +//! attribute: Attribute::NonSecure, +//! }, +//! Region { +//! base_address: 0x2007_0000, // Non-Secure SRAM2 +//! end_address: 0x2007_FFFF, +//! attribute: Attribute::NonSecure, +//! }, +//! Region { +//! base_address: 0x0C0F_E000, // Non-Secure Callable veneer region +//! end_address: 0x0C0F_FFFF, +//! attribute: Attribute::NonSecureCallable, +//! }, +//! ]; +//! +//! unsafe { init(®ions); } +//! ``` + +use cortex_m::peripheral::sau::{SauRegion, SauRegionAttribute}; +use cortex_m::peripheral::scb::Exception; + +/// SAU region security attribute. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum Attribute { + /// Non-Secure: accessible from both Secure and Non-Secure worlds. + NonSecure = 0, + /// Non-Secure Callable: enables calls from Non-Secure world into Secure code + /// through a veneer (gate) function at this address. The NSC bit is set in RLAR. + NonSecureCallable = 1, +} + +/// SAU region definition. +/// +/// Both `base_address` and `end_address` must be 32-byte aligned: +/// - `base_address & 0x1F == 0` (lower 5 bits must be zero) +/// - `end_address & 0x1F == 0x1F` (lower 5 bits must be one) +/// +/// For example, to cover 0x0810_0000–0x081F_FFFF both constraints are naturally satisfied. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Region { + /// Base address of the region (inclusive). Must be 32-byte aligned. + pub base_address: u32, + /// End address of the region (inclusive). The lower 5 bits must be `0x1F`. + pub end_address: u32, + /// Security attribute for this region. + pub attribute: Attribute, +} + +/// Initialize and enable the SAU with the given region definitions. +/// +/// This function: +/// 1. Disables the SAU. +/// 2. Programs up to 8 regions from `regions` (extras are silently ignored). +/// 3. Re-enables the SAU. +/// 4. Enables the `SecureFault` exception so illegal TrustZone accesses surface as +/// a debuggable fault rather than escalating silently to `HardFault`. +/// +/// Memory not covered by any configured region is treated as **Secure**. +/// +/// # Safety +/// Must be called from the Secure world before Non-Secure code is started. +/// Regions must satisfy the 32-byte alignment constraints documented on [`Region`]. +pub unsafe fn init(regions: &[Region]) { + let mut core = unsafe { cortex_m::Peripherals::steal() }; + + for (i, region) in regions.iter().enumerate().take(8) { + let sau_region = SauRegion { + base_address: region.base_address & !0x1F, + limit_address: region.end_address | 0x1F, + attribute: match region.attribute { + Attribute::NonSecure => SauRegionAttribute::NonSecure, + Attribute::NonSecureCallable => SauRegionAttribute::NonSecureCallable, + }, + }; + // Ignore errors: out-of-range region numbers are guarded by `take(8)` above. + let _ = core.SAU.set_region(i as u8, sau_region); + } + + core.SAU.enable(); + + // Enable SecureFault so TrustZone violations produce a dedicated exception. + core.SCB.enable(Exception::SecureFault); +} + +/// Disable the SAU, making all memory Non-Secure accessible. +/// +/// Sets SAU CTRL.ALLNS=1, ENABLE=0: the SAU is off and all memory is treated as +/// Non-Secure (unless overridden by the IDAU). Use this only when you intend to +/// run entirely in Non-Secure mode with no security boundary enforcement. +/// +/// # Safety +/// Must be called from the Secure world. +pub unsafe fn disable() { + let core = unsafe { cortex_m::Peripherals::steal() }; + // ALLNS=1 (bit 1), ENABLE=0 (bit 0): SAU disabled, all memory Non-Secure. + unsafe { core.SAU.ctrl.write(cortex_m::peripheral::sau::Ctrl(0b10)) }; +} + +/// Route an interrupt to the Non-Secure world via the NVIC ITNS registers. +/// +/// By default all interrupts target the Secure world. Call this for every interrupt +/// that the Non-Secure application handles (e.g. USART1, TIM2, DMA channels). +/// +/// `irq_number` is the peripheral interrupt number as defined in your device's +/// interrupt vector table (0-based, excluding the 16 CPU exceptions). +/// +/// # Safety +/// Must be called from the Secure world before jumping to Non-Secure code. +pub unsafe fn route_irq_to_nonsecure(irq_number: u16) { + // TODO: replace with NVIC::route_to_nonsecure() once cortex-m PR #647 lands + // (https://github.com/rust-embedded/cortex-m/pull/647). + // + // NVIC ITNS registers start at 0xE000_E380 (ARMv8-M only). + // itns[n] covers interrupts [n*32 .. n*32+31]; setting bit k routes interrupt + // (n*32 + k) to Non-Secure world. + const NVIC_ITNS_BASE: *mut u32 = 0xE000_E380 as *mut u32; + let reg = usize::from(irq_number / 32); + let bit = irq_number as u32 % 32; + unsafe { + let ptr = NVIC_ITNS_BASE.add(reg); + ptr.write_volatile(ptr.read_volatile() | (1 << bit)); + } +} + +/// Enable FPU access from the Non-Secure world. +/// +/// Sets SCB->NSACR bits 10–11 (CP10/CP11) so Non-Secure code can use the FPU. +/// Also clears the FPCCR.TS bit so that lazy FP state preservation does not +/// treat FP registers as Secure (prevents accidental FP-register leakage across +/// the security boundary). +/// +/// Must be called before jumping to Non-Secure code if the NS application uses +/// floating-point operations. Without this, NS FPU use will raise a UsageFault. +/// +/// # Safety +/// Must be called from the Secure world. +pub unsafe fn enable_nonsecure_fpu() { + // TODO: replace raw SCB NSACR write with SCB::enable_nonsecure_fpu() once + // cortex-m PR #647 lands (https://github.com/rust-embedded/cortex-m/pull/647). + // + // SCB NSACR is at 0xE000_ED8C. Bits 10-11 (CP10/CP11) grant NS access to the FPU. + const SCB_NSACR: *mut u32 = 0xE000_ED8C as *mut u32; + const CP10_CP11: u32 = 0b11 << 10; + unsafe { SCB_NSACR.write_volatile(SCB_NSACR.read_volatile() | CP10_CP11) }; + + // FPCCR.TS (bit 26) = 0 means FP state is Non-Secure, preventing Secure FP register + // contents from leaking to NS code on context switches. + let core = cortex_m::Peripherals::steal(); + core.FPU.fpccr.modify(|v| v & !(1 << 26)); +} + +/// Transfer control to the Non-Secure application. Does not return. +/// +/// This performs the standard Secure→Non-Secure boot handoff: +/// 1. Sets `SCB_NS->VTOR` to `ns_vtor` so the NS world knows its vector table. +/// 2. Loads `MSP_NS` from the first word of the NS vector table (initial NS stack pointer). +/// 3. Reads the NS reset handler address from the second word of the vector table. +/// 4. Executes `BXNS` to atomically switch to Non-Secure state and jump to the handler. +/// +/// # Safety +/// - Must be called from the Secure world after all GTZC/SAU setup is complete. +/// - `ns_vtor` must be a valid Non-Secure vector table address (32-byte aligned per +/// Cortex-M33 requirements; in practice 64-byte or 128-byte alignment is typical). +/// - The Non-Secure reset handler at `*(ns_vtor + 4)` must be a valid Thumb function +/// address (i.e., bit 0 set in the vector table entry, as per ARM ABI convention). +pub unsafe fn jump_to_nonsecure(ns_vtor: u32) -> ! { + // Configure the Non-Secure vector table. + // SCB_NS->VTOR is the NS alias of the SCB VTOR register (0xE002_ED08). + const SCB_NS_VTOR: *mut u32 = 0xE002_ED08 as *mut u32; + SCB_NS_VTOR.write_volatile(ns_vtor); + + // Load initial NS stack pointer from the first word of the NS vector table. + let ns_sp = core::ptr::read_volatile(ns_vtor as *const u32); + core::arch::asm!( + "msr msp_ns, {sp}", + sp = in(reg) ns_sp, + options(nomem, nostack, preserves_flags), + ); + + // Read the NS reset handler address from the second word of the NS vector table. + // ARM convention: bit 0 = 1 in the vector table (Thumb mode marker). + // BXNS requires bit 0 = 0 or it raises SecureFault (SFSR.INVTRAN). + let ns_reset = core::ptr::read_volatile((ns_vtor as *const u32).add(1)); + + // BXNS atomically clears bit 0, switches to Non-Secure state, and jumps. + core::arch::asm!( + "bxns {entry}", + entry = in(reg) ns_reset & !1u32, + options(noreturn), + ); +} diff --git a/examples/stm32wba6/src/bin/trustzone_setup.rs b/examples/stm32wba6/src/bin/trustzone_setup.rs new file mode 100644 index 0000000000..84530844bc --- /dev/null +++ b/examples/stm32wba6/src/bin/trustzone_setup.rs @@ -0,0 +1,181 @@ +//! TrustZone configuration example for STM32WBA65. +//! +//! This example shows how to use the GTZC and SAU drivers to configure TrustZone +//! security boundaries on the WBA65 family. It must run from the Secure world +//! (i.e., the linker script must place this code in Secure Flash). +//! +//! In a real dual-image firmware: +//! 1. This Secure application starts, configures SAU + GTZC, then jumps to the +//! Non-Secure application at a known Non-Secure Flash address. +//! 2. The Non-Secure application runs with restricted access as configured below. +//! +//! # WBA65RI Memory Layout +//! +//! Flash (2 MiB total, 0x0800_0000–0x081F_FFFF): +//! Secure Flash : 0x0800_0000 – 0x080F_FFFF (1 MiB) +//! Non-Secure Flash: 0x0810_0000 – 0x081F_FFFF (1 MiB) +//! NSC veneer alias: 0x0C0F_E000 – 0x0C0F_FFFF (8 KiB, in Secure alias space) +//! OTP/Info pages : 0x0BF9_0000 – 0x0BFB_7FFF (non-secure) +//! +//! SRAM: +//! SRAM1 (Secure) : 0x2000_0000 – 0x206F_FFFF (448 KiB, MPCBB1 28 regs) +//! SRAM2 (NS) : 0x2007_0000 – 0x2007_FFFF (64 KiB, MPCBB2 4 regs) +//! SRAM6 (Radio) : 0x4802_8000 – 0x4802_BFFF (16 KiB, MPCBB6 1 reg) +//! +//! Peripherals (Non-Secure): 0x4000_0000 – 0x4FFF_FFFF + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gtzc::{self, Mpcbb, Tzic}; +use embassy_stm32::{Config, pac}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +/// Non-Secure application entry point address (start of NS Flash on WBA65RI). +const NS_VTOR: u32 = 0x0810_0000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let config = Config::default(); + let _p = embassy_stm32::init(config); + + info!("TrustZone setup example (Secure world)"); + + // ── 0. Enable GTZC clock ──────────────────────────────────────────────── + // + // GTZC registers are behind the AHB1 bus clock; accessing them without + // enabling the clock causes a hard fault. + unsafe { gtzc::enable_clock() }; + info!("GTZC clock enabled"); + + // ── 1. Configure the SAU ──────────────────────────────────────────────── + // + // Define which memory regions are accessible from Non-Secure code. + // Memory NOT covered by any SAU region is Secure by default (when SAU is + // enabled). Five regions are configured here following the ST reference + // partition for WBA65: + // 0 – Non-Secure Flash + // 1 – OTP / Information pages (NS) + // 2 – Non-Secure Callable veneer (Secure alias, NSC) + // 3 – Non-Secure SRAM2 + // 4 – All peripherals (NS, so the NS app can use them) + + let sau_regions = [ + // Region 0: Non-Secure Flash (1 MiB, upper half of WBA65RI 2 MiB flash) + embassy_stm32::sau::Region { + base_address: 0x0810_0000, + end_address: 0x081F_FFFF, + attribute: embassy_stm32::sau::Attribute::NonSecure, + }, + // Region 1: OTP / option bytes in NS — required for NS read of device info + embassy_stm32::sau::Region { + base_address: 0x0BF9_0000, + end_address: 0x0BFB_7FFF, + attribute: embassy_stm32::sau::Attribute::NonSecure, + }, + // Region 2: Non-Secure Callable veneer — Secure code callable from NS world + embassy_stm32::sau::Region { + base_address: 0x0C0F_E000, + end_address: 0x0C0F_FFFF, + attribute: embassy_stm32::sau::Attribute::NonSecureCallable, + }, + // Region 3: Non-Secure SRAM2 (64 KiB) + embassy_stm32::sau::Region { + base_address: 0x2007_0000, + end_address: 0x2007_FFFF, + attribute: embassy_stm32::sau::Attribute::NonSecure, + }, + // Region 4: All peripherals — NS app must access its peripherals + embassy_stm32::sau::Region { + base_address: 0x4000_0000, + end_address: 0x4FFF_FFFF, + attribute: embassy_stm32::sau::Attribute::NonSecure, + }, + ]; + + unsafe { + embassy_stm32::sau::init(&sau_regions); + } + info!("SAU configured: {} regions", sau_regions.len()); + + // ── 2. Configure MPCBB1 (SRAM1 = 448 KiB = 28 × 16 KiB groups) ──────── + // + // Keep all SRAM1 blocks Secure so NS code cannot read Secure data. + let mpcbb1 = unsafe { Mpcbb::new(pac::GTZC_MPCBB1) }; + mpcbb1.set_all_secure(/*n_regs=*/ 28, true); + mpcbb1.set_all_privileged(/*n_regs=*/ 28, false); + // Suppress TZIC interrupts for Secure-world self-accesses to Secure blocks. + mpcbb1.set_srwiladis(true); + info!("MPCBB1 (SRAM1, 448 KiB): all blocks Secure"); + + // ── 3. Configure MPCBB2 (SRAM2 = 64 KiB = 4 × 16 KiB groups) ────────── + // + // Make SRAM2 fully Non-Secure so the Non-Secure application can use it. + let mpcbb2 = unsafe { Mpcbb::new(pac::GTZC_MPCBB2) }; + mpcbb2.set_all_secure(/*n_regs=*/ 4, false); + mpcbb2.set_all_privileged(/*n_regs=*/ 4, false); + info!("MPCBB2 (SRAM2, 64 KiB): all blocks Non-Secure"); + + // ── 4. Configure MPCBB6 (Radio SRAM = 16 KiB = 1 × 16 KiB group) ────── + // + // SRAM6 is the 2.4 GHz radio TX/RX buffer at 0x4802_8000. + // Keep it Secure so NS code cannot tamper with radio payloads. + let mpcbb6 = unsafe { Mpcbb::new(pac::GTZC_MPCBB6) }; + mpcbb6.set_all_secure(/*n_regs=*/ 1, true); + mpcbb6.set_all_privileged(/*n_regs=*/ 1, false); + info!("MPCBB6 (SRAM6 radio, 16 KiB): all blocks Secure"); + + // ── 5. Configure TZSC peripheral security ─────────────────────────────── + // + // Mark specific peripherals as Non-Secure so the NS application can use them. + // PAC field names are WBA-specific (gtzc_wba variant). + pac::GTZC_TZSC.tzsc_seccfgr2().modify(|r| r.set_usart1sec(false)); + info!("TZSC: USART1 → Non-Secure"); + + // ── 6. Enable TZIC (illegal access interrupts) ────────────────────────── + // + // WBA65RI has 4 TZIC register groups. Clear any stale flags first, then + // enable IRQs so violations are reported via the GTZC interrupt. + let tzic = unsafe { Tzic::new(pac::GTZC_TZIC) }; + tzic.enable_all(4); + info!("TZIC: enabled for all 4 register groups"); + + // ── 7. Lock the TZSC configuration ────────────────────────────────────── + // + // After locking, neither Secure nor Non-Secure code can modify the TZSC + // security/privilege registers until the next reset. + unsafe { gtzc::lock() }; + info!("TZSC locked. is_locked = {}", gtzc::is_locked()); + + // ── 8. Route selected interrupts to the Non-Secure world ──────────────── + // + // By default all interrupts target Secure world. Route the interrupts that + // the NS application handles. IRQ numbers come from the device interrupt + // vector table (see stm32wba65ri PAC or reference manual). + // + // Example: route USART1 global interrupt (IRQ #37 on WBA65) to NS. + // unsafe { embassy_stm32::sau::route_irq_to_nonsecure(37); } + + // ── 9. Enable FPU access from Non-Secure world ────────────────────────── + // + // Allow NS code to use the Cortex-M33 FPU. Without this, any NS floating- + // point operation raises a UsageFault. + unsafe { embassy_stm32::sau::enable_nonsecure_fpu() }; + info!("FPU enabled for Non-Secure world"); + + // ── 10. Jump to the Non-Secure application ────────────────────────────── + // + // In a real dual-image build, uncomment the line below. The NS app must be + // linked to start at NS_VTOR (0x0810_0000) with a valid vector table. + // + // unsafe { embassy_stm32::sau::jump_to_nonsecure(NS_VTOR) }; + + info!("TrustZone setup complete — would jump to NS app at {:#010x}.", NS_VTOR); + + loop { + Timer::after_secs(1).await; + } +}