From 4f36c10a7f974461acb79624a38560e982d8e778 Mon Sep 17 00:00:00 2001 From: Gerzain Mata Date: Mon, 20 Apr 2026 21:55:38 -0700 Subject: [PATCH 1/4] peripheral: add SAU init helpers and jump_to_nonsecure for ARMv8-M MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Derive Copy, Clone, PartialEq, Eq on SauRegion and SauRegionAttribute - SAU::disable_allns(): set CTRL.ALLNS=1, ENABLE=0 (all memory Non-Secure) - SAU::init(regions): disable SAU, program up to 8 regions, re-enable - jump_to_nonsecure(ns_vtor): Secure→Non-Secure boot handoff via BXNS These cover the remaining ARMv8-M TrustZone boot sequence after SAU region programming: disabling the SAU for NS-only systems, bulk-initialising regions without manually looping set_region, and transferring control to the NS image. --- cortex-m/src/peripheral/sau.rs | 97 +++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/cortex-m/src/peripheral/sau.rs b/cortex-m/src/peripheral/sau.rs index da91aca9..a308e3dd 100644 --- a/cortex-m/src/peripheral/sau.rs +++ b/cortex-m/src/peripheral/sau.rs @@ -103,7 +103,7 @@ bitfield! { } /// Possible attribute of a SAU region. -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SauRegionAttribute { /// SAU region is Secure Secure, @@ -114,7 +114,7 @@ pub enum SauRegionAttribute { } /// Description of a SAU region. -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SauRegion { /// First address of the region, its 5 least significant bits must be set to zero. pub base_address: u32, @@ -143,6 +143,48 @@ impl SAU { self._type.read().sregion() } + /// Disable the SAU and mark all memory Non-Secure (ALLNS mode). + /// + /// Sets `CTRL.ALLNS = 1`, `CTRL.ENABLE = 0`. When the SAU is disabled with ALLNS set, the + /// entire address space is treated as Non-Secure (subject to any IDAU overrides). Use this + /// when running entirely in Non-Secure mode with no security boundary enforcement. + /// + /// To re-enable security boundaries, call [`init`] or [`enable`] after programming regions. + #[inline] + pub fn disable_allns(&mut self) { + unsafe { + self.ctrl.write(Ctrl(0b10)); // ALLNS=1, ENABLE=0 + } + } + + /// Program SAU regions and enable the SAU. + /// + /// This is a convenience wrapper around [`set_region`] + [`enable`]: + /// 1. Disables the SAU temporarily. + /// 2. Programs up to 8 regions from `regions` (extras silently ignored). + /// 3. Re-enables the SAU. + /// + /// Memory not covered by any enabled region is treated as Secure once the SAU is enabled. + /// + /// To also enable the `SecureFault` exception so TrustZone violations surface as a dedicated + /// fault rather than escalating to `HardFault`, call + /// `scb.enable(cortex_m::peripheral::scb::Exception::SecureFault)` after this. + /// + /// # Errors + /// Region-programming errors (bad alignment, region number out of range) are silently ignored; + /// `take(8)` already bounds the region count to the hardware maximum. + #[inline] + pub fn init(&mut self, regions: &[SauRegion]) { + // Disable while reprogramming to avoid partial-update windows. + unsafe { + self.ctrl.write(Ctrl(0)); + } + for (i, ®ion) in regions.iter().enumerate().take(8) { + let _ = self.set_region(i as u8, region); + } + self.enable(); + } + /// Enable the SAU. #[inline] pub fn enable(&mut self) { @@ -241,3 +283,54 @@ impl SAU { }) } } + +/// 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 Non-Secure world finds its vector table. +/// 2. Loads `MSP_NS` from the first word of the NS vector table (the initial NS stack pointer). +/// 3. Reads the NS reset handler address from the second word of the NS 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 SAU/GTZC setup is complete. +/// - `ns_vtor` must point to a valid Non-Secure vector table. The Cortex-M33 requires the VTOR +/// to be at least 32-byte aligned; in practice 128-byte or 256-byte alignment is typical. +/// - The NS reset handler at `*(ns_vtor + 4)` must be a valid Thumb function address (bit 0 set +/// in the vector table entry, as per the ARM ABI convention for vector tables). +/// - Available on ARMv8-M only (`thumbv8m.base` and `thumbv8m.main`). +#[cfg(armv8m)] +pub unsafe fn jump_to_nonsecure(ns_vtor: u32) -> ! { + // SCB_NS->VTOR is the Non-Secure alias of the SCB VTOR register (0xE002_ED08). + // Writing it tells the NS world where its vector table lives before we hand off. + const SCB_NS_VTOR: *mut u32 = 0xE002_ED08 as *mut u32; + unsafe { + SCB_NS_VTOR.write_volatile(ns_vtor); + } + + // Load the initial NS stack pointer from the first word of the NS vector table + // and write it into MSP_NS. + let ns_sp = unsafe { core::ptr::read_volatile(ns_vtor as *const u32) }; + unsafe { + 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 ABI: bit 0 is set in the stored value (Thumb mode marker). + // BXNS requires bit 0 = 0; if bit 0 is set, it raises SecureFault (SFSR.INVTRAN). + let ns_reset = unsafe { core::ptr::read_volatile((ns_vtor as *const u32).add(1)) }; + + // BXNS atomically clears bit 0, switches the processor to Non-Secure state, and + // branches to the NS reset handler. This instruction does not return. + unsafe { + core::arch::asm!( + "bxns {entry}", + entry = in(reg) ns_reset & !1u32, + options(noreturn), + ); + } +} From 21e730d771de88c28b23d35448370d2d09c9d627 Mon Sep 17 00:00:00 2001 From: Gerzain Mata Date: Mon, 20 Apr 2026 19:29:35 -0700 Subject: [PATCH 2/4] cortex-m-rt: fix stale compile-fail expected error string --- cortex-m-rt/tests/compile-fail/interrupt-not-reexported.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cortex-m-rt/tests/compile-fail/interrupt-not-reexported.rs b/cortex-m-rt/tests/compile-fail/interrupt-not-reexported.rs index 70eee1a1..bd5fd959 100644 --- a/cortex-m-rt/tests/compile-fail/interrupt-not-reexported.rs +++ b/cortex-m-rt/tests/compile-fail/interrupt-not-reexported.rs @@ -11,5 +11,5 @@ fn foo() -> ! { loop {} } -#[interrupt] //~ ERROR failed to resolve: use of unresolved module or unlinked crate `interrupt` +#[interrupt] //~ ERROR cannot find module or crate `interrupt` fn USART1() {} From ba7d159704e835fe62b800d9d1d0f4f0e1eda076 Mon Sep 17 00:00:00 2001 From: Gerzain Mata Date: Tue, 21 Apr 2026 10:27:50 -0700 Subject: [PATCH 3/4] peripheral/sau: return error from init instead of silently ignoring extras --- cortex-m/src/peripheral/sau.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/cortex-m/src/peripheral/sau.rs b/cortex-m/src/peripheral/sau.rs index a308e3dd..80914c51 100644 --- a/cortex-m/src/peripheral/sau.rs +++ b/cortex-m/src/peripheral/sau.rs @@ -134,6 +134,9 @@ pub enum SauError { WrongBaseAddress, /// Bits 0 to 4 of the limit address of a SAU region must be set to one. WrongLimitAddress, + /// The number of regions passed to [`SAU::init`] exceeds the number of regions implemented + /// in hardware (as reported by [`SAU::region_numbers`]). + TooManyRegions, } impl SAU { @@ -161,7 +164,7 @@ impl SAU { /// /// This is a convenience wrapper around [`set_region`] + [`enable`]: /// 1. Disables the SAU temporarily. - /// 2. Programs up to 8 regions from `regions` (extras silently ignored). + /// 2. Programs all regions from `regions`. /// 3. Re-enables the SAU. /// /// Memory not covered by any enabled region is treated as Secure once the SAU is enabled. @@ -171,18 +174,25 @@ impl SAU { /// `scb.enable(cortex_m::peripheral::scb::Exception::SecureFault)` after this. /// /// # Errors - /// Region-programming errors (bad alignment, region number out of range) are silently ignored; - /// `take(8)` already bounds the region count to the hardware maximum. + /// Returns [`SauError::TooManyRegions`] if `regions.len()` exceeds the number of regions + /// implemented in hardware (see [`region_numbers`]). Returns other [`SauError`] variants if + /// any region descriptor has a misaligned base or limit address. + /// + /// On error the SAU is left disabled (in the state set at step 1 above). #[inline] - pub fn init(&mut self, regions: &[SauRegion]) { + pub fn init(&mut self, regions: &[SauRegion]) -> Result<(), SauError> { + if regions.len() > self.region_numbers() as usize { + return Err(SauError::TooManyRegions); + } // Disable while reprogramming to avoid partial-update windows. unsafe { self.ctrl.write(Ctrl(0)); } - for (i, ®ion) in regions.iter().enumerate().take(8) { - let _ = self.set_region(i as u8, region); + for (i, ®ion) in regions.iter().enumerate() { + self.set_region(i as u8, region)?; } self.enable(); + Ok(()) } /// Enable the SAU. From f5af5a8de5e7519584dcb6ef41ba7c1489c73caf Mon Sep 17 00:00:00 2001 From: Gerzain Mata Date: Wed, 22 Apr 2026 16:11:21 -0700 Subject: [PATCH 4/4] peripheral/sau: use *const u32 for jump_to_nonsecure, add testsuite example --- cortex-m/src/peripheral/sau.rs | 10 +++++----- testsuite/src/main.rs | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/cortex-m/src/peripheral/sau.rs b/cortex-m/src/peripheral/sau.rs index 80914c51..9a3b66c1 100644 --- a/cortex-m/src/peripheral/sau.rs +++ b/cortex-m/src/peripheral/sau.rs @@ -306,21 +306,21 @@ impl SAU { /// - Must be called from the Secure world after all SAU/GTZC setup is complete. /// - `ns_vtor` must point to a valid Non-Secure vector table. The Cortex-M33 requires the VTOR /// to be at least 32-byte aligned; in practice 128-byte or 256-byte alignment is typical. -/// - The NS reset handler at `*(ns_vtor + 4)` must be a valid Thumb function address (bit 0 set +/// - The NS reset handler at `*(ns_vtor + 1)` must be a valid Thumb function address (bit 0 set /// in the vector table entry, as per the ARM ABI convention for vector tables). /// - Available on ARMv8-M only (`thumbv8m.base` and `thumbv8m.main`). #[cfg(armv8m)] -pub unsafe fn jump_to_nonsecure(ns_vtor: u32) -> ! { +pub unsafe fn jump_to_nonsecure(ns_vtor: *const u32) -> ! { // SCB_NS->VTOR is the Non-Secure alias of the SCB VTOR register (0xE002_ED08). // Writing it tells the NS world where its vector table lives before we hand off. const SCB_NS_VTOR: *mut u32 = 0xE002_ED08 as *mut u32; unsafe { - SCB_NS_VTOR.write_volatile(ns_vtor); + SCB_NS_VTOR.write_volatile(ns_vtor as usize as u32); } // Load the initial NS stack pointer from the first word of the NS vector table // and write it into MSP_NS. - let ns_sp = unsafe { core::ptr::read_volatile(ns_vtor as *const u32) }; + let ns_sp = unsafe { core::ptr::read_volatile(ns_vtor) }; unsafe { core::arch::asm!( "msr msp_ns, {sp}", @@ -332,7 +332,7 @@ pub unsafe fn jump_to_nonsecure(ns_vtor: u32) -> ! { // Read the NS reset handler address from the second word of the NS vector table. // ARM ABI: bit 0 is set in the stored value (Thumb mode marker). // BXNS requires bit 0 = 0; if bit 0 is set, it raises SecureFault (SFSR.INVTRAN). - let ns_reset = unsafe { core::ptr::read_volatile((ns_vtor as *const u32).add(1)) }; + let ns_reset = unsafe { core::ptr::read_volatile(ns_vtor.add(1)) }; // BXNS atomically clears bit 0, switches the processor to Non-Secure state, and // branches to the NS reset handler. This instruction does not return. diff --git a/testsuite/src/main.rs b/testsuite/src/main.rs index 259c2f3a..d2554f0e 100644 --- a/testsuite/src/main.rs +++ b/testsuite/src/main.rs @@ -151,6 +151,29 @@ mod tests { } } + #[cfg(armv8m)] + #[test] + fn sau_set_get_region(p: &mut cortex_m::Peripherals) { + use cortex_m::peripheral::sau::{SauRegion, SauRegionAttribute}; + + // The SAU must have at least one region on any ARMv8-M implementation. + let n = p.SAU.region_numbers(); + assert!(n > 0); + + // Program region 0 as a Non-Secure window and read it back to verify the + // register round-trip works correctly. + let region = SauRegion { + base_address: 0x2000_0000, + limit_address: 0x2001_FFFF, // bottom 5 bits already 1 (0x1F) + attribute: SauRegionAttribute::NonSecure, + }; + p.SAU.set_region(0, region).unwrap(); + let got = p.SAU.get_region(0).unwrap(); + assert_eq!(got.base_address, region.base_address); + assert_eq!(got.limit_address, region.limit_address); + assert_eq!(got.attribute, region.attribute); + } + // this test must be last! #[test] fn run_psp() {