From 504e12753b6d6e3f7f37242f2541523b507335ac Mon Sep 17 00:00:00 2001 From: Gerzain Mata Date: Mon, 20 Apr 2026 19:17:16 -0700 Subject: [PATCH 1/3] peripheral: add TrustZone support for SCB NSACR and NVIC ITNS (ARMv8-M) Add the missing ARMv8-M TrustZone registers and typed helper methods: - SCB: add `nsacr` field (Non-Secure Access Control Register, 0xE000_ED8C) gated behind `#[cfg(armv8m)]`. Add `enable_nonsecure_fpu()`, `disable_nonsecure_fpu()`, and `is_nonsecure_fpu_enabled()` helpers on SCB for controlling CP10/CP11 (FPU) access from Non-Secure code. - NVIC: add `route_to_nonsecure()`, `route_to_secure()`, and `is_routed_to_nonsecure()` typed methods on NVIC, using the existing `itns` register field (already present since #580). Gated behind `#[cfg(armv8m)]`. - test: add address assertions for `nvic.itns` (0xE000_E380) and `scb.nsacr` (0xE000_ED8C), both gated `#[cfg(armv8m)]`. --- cortex-m/src/peripheral/nvic.rs | 49 +++++++++++++++++++++++++++++++++ cortex-m/src/peripheral/scb.rs | 42 ++++++++++++++++++++++++++++ cortex-m/src/peripheral/test.rs | 6 ++++ 3 files changed, 97 insertions(+) diff --git a/cortex-m/src/peripheral/nvic.rs b/cortex-m/src/peripheral/nvic.rs index 60411f0e7..a837ccd65 100644 --- a/cortex-m/src/peripheral/nvic.rs +++ b/cortex-m/src/peripheral/nvic.rs @@ -257,6 +257,55 @@ impl NVIC { unsafe { (*Self::PTR).icpr[usize::from(nr / 32)].write(1 << (nr % 32)) } } + /// Route `interrupt` to the Non-Secure world (ARMv8-M only). + /// + /// Sets the corresponding ITNS bit so the interrupt is taken as Non-Secure + /// and will not preempt Secure execution. Call this for every peripheral + /// interrupt handled by the Non-Secure application before jumping to it. + /// + /// # Safety + /// Must be called from the Secure world. Routing an interrupt to Non-Secure + /// while Secure handlers depend on it can violate security invariants. + #[cfg(armv8m)] + #[inline] + pub unsafe fn route_to_nonsecure(interrupt: I) + where + I: InterruptNumber, + { + let nr = interrupt.number(); + unsafe { (*Self::PTR).itns[usize::from(nr / 32)].modify(|v| v | (1 << (nr % 32))) } + } + + /// Route `interrupt` back to the Secure world (ARMv8-M only). + /// + /// Clears the corresponding ITNS bit. After this call the interrupt + /// targets Secure state (the default after reset). + /// + /// # Safety + /// Must be called from the Secure world. + #[cfg(armv8m)] + #[inline] + pub unsafe fn route_to_secure(interrupt: I) + where + I: InterruptNumber, + { + let nr = interrupt.number(); + unsafe { (*Self::PTR).itns[usize::from(nr / 32)].modify(|v| v & !(1 << (nr % 32))) } + } + + /// Returns `true` if `interrupt` is routed to the Non-Secure world (ARMv8-M only). + #[cfg(armv8m)] + #[inline] + pub fn is_routed_to_nonsecure(interrupt: I) -> bool + where + I: InterruptNumber, + { + let nr = interrupt.number(); + let mask = 1 << (nr % 32); + // NOTE(unsafe) atomic read with no side effects + unsafe { ((*Self::PTR).itns[usize::from(nr / 32)].read() & mask) == mask } + } + #[cfg(armv6m)] #[inline] fn ipr_index(interrupt: I) -> usize diff --git a/cortex-m/src/peripheral/scb.rs b/cortex-m/src/peripheral/scb.rs index 6d9d47006..bf773d3dc 100644 --- a/cortex-m/src/peripheral/scb.rs +++ b/cortex-m/src/peripheral/scb.rs @@ -100,6 +100,14 @@ pub struct RegisterBlock { pub cpacr: RW, #[cfg(armv6m)] _reserved9: u32, + + /// Non-Secure Access Control (only present on ARMv8-M) + /// + /// Controls whether Non-Secure code can access coprocessors. Bits 10–11 + /// correspond to CP10 and CP11 (the FPU): setting them allows Non-Secure + /// code to use floating-point instructions. + #[cfg(armv8m)] + pub nsacr: RW, } /// FPU access mode @@ -171,6 +179,40 @@ impl SCB { } } +/// ARMv8-M TrustZone coprocessor access control. +#[cfg(armv8m)] +impl SCB { + const SCB_NSACR_CP10_CP11: u32 = 0b11 << 10; + + /// Allow Non-Secure code to use the FPU (CP10 and CP11). + /// + /// Sets NSACR bits 10–11 so that Non-Secure threads can execute + /// floating-point instructions. Without this, any NS FPU instruction + /// raises a UsageFault. + /// + /// Call this before jumping to Non-Secure code if the NS application + /// uses floating-point. + #[inline] + pub fn enable_nonsecure_fpu(&mut self) { + unsafe { self.nsacr.modify(|v| v | Self::SCB_NSACR_CP10_CP11) } + } + + /// Deny Non-Secure code from using the FPU. + /// + /// Clears NSACR bits 10–11. + #[inline] + pub fn disable_nonsecure_fpu(&mut self) { + unsafe { self.nsacr.modify(|v| v & !Self::SCB_NSACR_CP10_CP11) } + } + + /// Returns `true` if Non-Secure code is allowed to use the FPU. + #[inline] + pub fn is_nonsecure_fpu_enabled() -> bool { + // NOTE(unsafe) atomic read with no side effects + unsafe { ((*Self::PTR).nsacr.read() & Self::SCB_NSACR_CP10_CP11) != 0 } + } +} + impl SCB { /// Returns the active exception number #[inline] diff --git a/cortex-m/src/peripheral/test.rs b/cortex-m/src/peripheral/test.rs index cab064aad..f22ee4d56 100644 --- a/cortex-m/src/peripheral/test.rs +++ b/cortex-m/src/peripheral/test.rs @@ -116,6 +116,10 @@ fn nvic() { assert_eq!(address(&nvic.ispr), 0xE000E200); assert_eq!(address(&nvic.icpr), 0xE000E280); assert_eq!(address(&nvic.iabr), 0xE000E300); + #[cfg(armv8m)] + assert_eq!(address(&nvic.itns), 0xE000_E380); + #[cfg(armv8m)] + assert_eq!(address(&nvic.itns[1]), 0xE000_E384); assert_eq!(address(&nvic.ipr), 0xE000E400); #[cfg(not(armv6m))] assert_eq!(address(&nvic.stir), 0xE000EF00); @@ -139,6 +143,8 @@ fn scb() { assert_eq!(address(&scb.bfar), 0xE000_ED38); assert_eq!(address(&scb.afsr), 0xE000_ED3C); assert_eq!(address(&scb.cpacr), 0xE000_ED88); + #[cfg(armv8m)] + assert_eq!(address(&scb.nsacr), 0xE000_ED8C); } #[test] From 32aee19460426a8fed0acf9028e371b200ec5c21 Mon Sep 17 00:00:00 2001 From: Gerzain Mata Date: Mon, 20 Apr 2026 19:29:35 -0700 Subject: [PATCH 2/3] 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 70eee1a1c..bd5fd9595 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 4e875bd23568f1697352ca46f5020cf8c32874e3 Mon Sep 17 00:00:00 2001 From: Gerzain Mata Date: Wed, 22 Apr 2026 16:03:41 -0700 Subject: [PATCH 3/3] peripheral: extract group_idx/bit_mask variables in NVIC ITNS methods Improves readability of the ARMv8-M route_to_nonsecure, route_to_secure, and is_routed_to_nonsecure methods by naming the nr/32 and nr%32 sub- expressions as suggested in PR review. --- cortex-m/src/peripheral/nvic.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/cortex-m/src/peripheral/nvic.rs b/cortex-m/src/peripheral/nvic.rs index a837ccd65..7e444ef24 100644 --- a/cortex-m/src/peripheral/nvic.rs +++ b/cortex-m/src/peripheral/nvic.rs @@ -273,7 +273,9 @@ impl NVIC { I: InterruptNumber, { let nr = interrupt.number(); - unsafe { (*Self::PTR).itns[usize::from(nr / 32)].modify(|v| v | (1 << (nr % 32))) } + let group_idx = usize::from(nr / 32); + let bit_mask = 1 << (nr % 32); + unsafe { (*Self::PTR).itns[group_idx].modify(|v| v | bit_mask) } } /// Route `interrupt` back to the Secure world (ARMv8-M only). @@ -290,7 +292,9 @@ impl NVIC { I: InterruptNumber, { let nr = interrupt.number(); - unsafe { (*Self::PTR).itns[usize::from(nr / 32)].modify(|v| v & !(1 << (nr % 32))) } + let group_idx = usize::from(nr / 32); + let bit_mask = 1 << (nr % 32); + unsafe { (*Self::PTR).itns[group_idx].modify(|v| v & !bit_mask) } } /// Returns `true` if `interrupt` is routed to the Non-Secure world (ARMv8-M only). @@ -301,9 +305,10 @@ impl NVIC { I: InterruptNumber, { let nr = interrupt.number(); - let mask = 1 << (nr % 32); + let group_idx = usize::from(nr / 32); + let bit_mask = 1 << (nr % 32); // NOTE(unsafe) atomic read with no side effects - unsafe { ((*Self::PTR).itns[usize::from(nr / 32)].read() & mask) == mask } + unsafe { ((*Self::PTR).itns[group_idx].read() & bit_mask) == bit_mask } } #[cfg(armv6m)]