Skip to content
2 changes: 2 additions & 0 deletions cortex-m/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ critical-section-single-core = ["critical-section/restore-state-u32"]
critical-section = []
# Deprecated feature from when inline-asm was optional (to preserve a lower MSRV)
inline-asm = []
# Add NS variants of various Cortex-M peripherals, which secure mode might want to configure
secure-mode = []

[package.metadata.docs.rs]
targets = [
Expand Down
74 changes: 74 additions & 0 deletions cortex-m/src/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,80 @@ pub unsafe fn bootload(vector_table: *const u32) -> ! {
}
}

/// 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 + 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(all(armv8m, feature = "secure-mode"))]
pub unsafe fn bootload_ns(ns_vtor: *const u32, scb_ns: crate::peripheral::SCBNS) -> ! {
// Set NS_VTOR, so nonsecure mode uses that vector table
unsafe {
scb_ns.vtor.write(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 { ns_vtor.read_volatile() };

// Set MSP_NS, so nonsecure mode uses that stack pointer
unsafe {
crate::register::msp::write_ns(ns_sp);
}

// 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 { ns_vtor.add(1).read_volatile() };

// BXNS switches the processor to the state given in the LSB
// so we must clear that bit.
unsafe extern "C" {
fn _bx_ns_trampoline(boot: u32) -> !;
}
unsafe {
_bx_ns_trampoline(ns_reset & 0xFFFF_FFFE);
}
}

#[cfg(all(armv8m, feature = "secure-mode"))]
core::arch::global_asm!(
r#"
.type _bx_ns_trampoline,%function
.global _bx_ns_trampoline
_bx_ns_trampoline:
vlstm sp // Push secure FPU state to stack, and zero secure FPU registers (nop if no FPU present)
mov lr, r0 // Put target address in LR
mov r0, 0 // Zero all the other registers
mov r1, 0 // Except secure MSP, as nonsecure has its own MSP, which we set
mov r2, 0
mov r3, 0
mov r4, 0
mov r5, 0
mov r6, 0
mov r7, 0
mov r8, 0
mov r8, 0
mov r9, 0
mov r10, 0
mov r11, 0
mov r12, 0
msr apsr_nzcvq, r0 // Also clear processor flags
bxns lr // Branch to nonsecure mode
.size _bx_ns_trampoline, . - _bx_ns_trampoline
"#,
);

Comment thread
jonathanpallant marked this conversation as resolved.
/// This instruction moves one Register to a Coprocessor Register.
/// This function generates inline assembly and needs the instruction configuration
/// during compilation time (i.e. as `const`).
Expand Down
39 changes: 39 additions & 0 deletions cortex-m/src/peripheral/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ pub struct Peripherals {
/// System Control Block
pub SCB: SCB,

/// Nonsecure alias for System Control Block
///
/// This lets a CPU running in Secure mode access the Nonsecure System Control
/// Block, without switching to Nonsecure mode to do so.
#[cfg(feature = "secure-mode")]
pub SCBNS: SCBNS,

/// SysTick: System Timer
pub SYST: SYST,

Expand Down Expand Up @@ -215,6 +222,10 @@ impl Peripherals {
SCB: SCB {
_marker: PhantomData,
},
#[cfg(feature = "secure-mode")]
SCBNS: SCBNS {
_marker: PhantomData,
},
SYST: SYST {
_marker: PhantomData,
},
Expand Down Expand Up @@ -621,6 +632,34 @@ impl ops::Deref for SCB {
}
}

/// Nonsecure alias for the System Control Block
///
/// This lets a CPU running in Secure mode access the Nonsecure System Control
/// Block, without switching to Nonsecure mode to do so.
#[cfg(feature = "secure-mode")]
pub struct SCBNS {
_marker: core::marker::PhantomData<*const ()>,
}

#[cfg(feature = "secure-mode")]
unsafe impl Send for SCBNS {}

#[cfg(feature = "secure-mode")]
impl SCBNS {
/// Pointer to the nonsecure alias for the register block
pub const PTR: *const scb::RegisterBlock = 0xE002_ED04 as *const _;
}

#[cfg(feature = "secure-mode")]
impl ops::Deref for SCBNS {
type Target = scb::RegisterBlock;

#[inline(always)]
fn deref(&self) -> &Self::Target {
unsafe { &*Self::PTR }
}
}

/// SysTick: System Timer
pub struct SYST {
_marker: PhantomData<*const ()>,
Expand Down
87 changes: 76 additions & 11 deletions cortex-m/src/peripheral/sau.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,27 +83,40 @@ bitfield! {
#[repr(C)]
#[derive(Copy, Clone)]
pub struct Sfsr(u32);
invep, _: 0;
invis, _: 1;
inver, _: 2;
auviol, _: 3;
invtran, _: 4;
lsperr, _: 5;
sfarvalid, _: 6;
lserr, _: 7;
impl Debug;
/// Invalid Entry Point
pub invep, _: 0;
/// Invalid Integrity Signature
pub invis, _: 1;
/// Invalid Exception Return
pub inver, _: 2;
/// Attribution Unit Violation
pub auviol, _: 3;
/// Invalid Transition
pub invtran, _: 4;
/// Lazy state preservation error
pub lsperr, _: 5;
/// SFAR is valid
pub sfarvalid, _: 6;
/// Lazy state error
pub lserr, _: 7;
}

bitfield! {
/// Secure Fault Address Register description
#[repr(C)]
#[derive(Copy, Clone)]
pub struct Sfar(u32);
impl Debug;
u32;
address, _: 31, 0;
/// Faulting memory address
///
/// Only valid if SFSR.SFARVALID = 1
pub address, _: 31, 0;
}

/// Possible attribute of a SAU region.
#[derive(Debug)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SauRegionAttribute {
/// SAU region is Secure
Secure,
Expand All @@ -114,7 +127,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,
Expand All @@ -134,6 +147,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 {
Expand All @@ -143,6 +159,55 @@ 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 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.
///
/// 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
/// 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]) -> 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, &region) in regions.iter().enumerate() {
self.set_region(i as u8, region)?;
}
self.enable();
Ok(())
}

/// Enable the SAU.
#[inline]
pub fn enable(&mut self) {
Expand Down
23 changes: 23 additions & 0 deletions testsuite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,29 @@ mod tests {
cortex_m::asm::semihosting_syscall(SYS_WRITE0, msg.as_ptr() as usize as u32);
}
}

#[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]
Expand Down