diff --git a/cortex-m/Cargo.toml b/cortex-m/Cargo.toml index 5431f48b..358ba721 100644 --- a/cortex-m/Cargo.toml +++ b/cortex-m/Cargo.toml @@ -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 = [ diff --git a/cortex-m/src/asm.rs b/cortex-m/src/asm.rs index d322a371..f4090832 100644 --- a/cortex-m/src/asm.rs +++ b/cortex-m/src/asm.rs @@ -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 + "#, +); + /// 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`). diff --git a/cortex-m/src/peripheral/mod.rs b/cortex-m/src/peripheral/mod.rs index d65707c1..d69ea0aa 100644 --- a/cortex-m/src/peripheral/mod.rs +++ b/cortex-m/src/peripheral/mod.rs @@ -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, @@ -215,6 +222,10 @@ impl Peripherals { SCB: SCB { _marker: PhantomData, }, + #[cfg(feature = "secure-mode")] + SCBNS: SCBNS { + _marker: PhantomData, + }, SYST: SYST { _marker: PhantomData, }, @@ -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 ()>, diff --git a/cortex-m/src/peripheral/sau.rs b/cortex-m/src/peripheral/sau.rs index da91aca9..dc00e137 100644 --- a/cortex-m/src/peripheral/sau.rs +++ b/cortex-m/src/peripheral/sau.rs @@ -83,14 +83,23 @@ 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! { @@ -98,12 +107,16 @@ bitfield! { #[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, @@ -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, @@ -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 { @@ -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, ®ion) in regions.iter().enumerate() { + self.set_region(i as u8, region)?; + } + self.enable(); + Ok(()) + } + /// Enable the SAU. #[inline] pub fn enable(&mut self) { diff --git a/testsuite/src/lib.rs b/testsuite/src/lib.rs index 27deafa5..a684407f 100644 --- a/testsuite/src/lib.rs +++ b/testsuite/src/lib.rs @@ -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]