Skip to content
Open
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
2 changes: 1 addition & 1 deletion cortex-m-rt/tests/compile-fail/interrupt-not-reexported.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
107 changes: 105 additions & 2 deletions cortex-m/src/peripheral/sau.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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 {
Expand All @@ -143,6 +146,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)?;
}
Comment on lines +191 to +193
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we clear the other regions as well (by setting them to Secure)? This could be the second invocation of this function with a smaller amount of regions.

self.enable();
Ok(())
}

/// Enable the SAU.
#[inline]
pub fn enable(&mut self) {
Expand Down Expand Up @@ -241,3 +293,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 + 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: *const u32) -> ! {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function would be analogous to asm::bootstrap, so perhaps we should put it there. Or we could put it in cmse. SAU is definitely the wrong location.

// 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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would love to see this added to the peripherals.

unsafe {
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) };
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.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),
);
Comment on lines +340 to +344
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are awaiting the creation of an RFC for cmse-nonsecure-call. When using nightly I get the following:

 40017f8:       f024 0401       bic.w   r4, r4, #1
 40017fc:       b0a2            sub     sp, #136        @ 0x88
 40017fe:       ec2d 0a00       vlstm   sp
 4001802:       4620            mov     r0, r4 ; <- All unused registers get cleared, to not leak information
 4001804:       4621            mov     r1, r4
 4001806:       4622            mov     r2, r4
 4001808:       4623            mov     r3, r4
 400180a:       4625            mov     r5, r4
 400180c:       4626            mov     r6, r4
 400180e:       4627            mov     r7, r4
 4001810:       46a0            mov     r8, r4
 4001812:       46a1            mov     r9, r4
 4001814:       46a2            mov     sl, r4
 4001816:       46a3            mov     fp, r4
 4001818:       46a4            mov     ip, r4
 400181a:       f384 8800       msr     CPSR_f, r4
 400181e:       47a4            blxns   r4 ; <- the expected non-secure call instruction

This function should perform a similar operation. Then that RFC is not as critical, but perhaps this implementation undermines that effort a little bit.

It will not be possible to do the same assembly-trick for cmse-nonsecure-entry I think.

}
}
23 changes: 23 additions & 0 deletions testsuite/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Loading