peripheral: add SAU init helpers and jump_to_nonsecure for ARMv8-M#648
peripheral: add SAU init helpers and jump_to_nonsecure for ARMv8-M#648leftger wants to merge 4 commits intorust-embedded:masterfrom
Conversation
- 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.
I think it should either panic or return an error if you pass too many regions. The aarch32-cpu MPU set-up code returns an error when the slice of region descriptors given is too long. |
|
@jonathanpallant I updated the code to address your concern. Thank you for the feedback 😄 |
jonathanpallant
left a comment
There was a problem hiding this comment.
One question, but otherwise this looks OK to me.
It would be great to see an example of it in action, ideally in the testsuite.
|
I have added it to the |
|
FYI, trustzone calls are actively being worked on: rust-lang/rfcs#3884 So we'll have a language feature to do trustzone (which should be the preferred way to do the calls in most cases). As it stands, the current |
| /// 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) -> ! { |
There was a problem hiding this comment.
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.
| 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; |
There was a problem hiding this comment.
I would love to see this added to the peripherals.
| core::arch::asm!( | ||
| "bxns {entry}", | ||
| entry = in(reg) ns_reset & !1u32, | ||
| options(noreturn), | ||
| ); |
There was a problem hiding this comment.
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.
| for (i, ®ion) in regions.iter().enumerate() { | ||
| self.set_region(i as u8, region)?; | ||
| } |
There was a problem hiding this comment.
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.
Summary
This PR extends the existing SAU peripheral module with higher-level helpers that cover the full ARMv8-M TrustZone boot sequence:
SauRegion/SauRegionAttribute: deriveCopy,Clone,PartialEq,Eq— these are plain data types and the missing impls made bulk operations unnecessarily verbose.SAU::disable_allns(): setsCTRL.ALLNS=1, ENABLE=0, making the entire address space Non-Secure. Useful for systems that run entirely in Non-Secure mode with no security boundary enforcement.SAU::init(regions: &[SauRegion]): convenience wrapper that disables the SAU, programs up to 8 regions (extras silently ignored, matching the hardware maximum), then re-enables it. Callers that wantSecureFaultenabled should follow up withscb.enable(Exception::SecureFault).jump_to_nonsecure(ns_vtor: u32) -> !(#[cfg(armv8m)]): performs the standard Secure→Non-Secure boot handoff — writesSCB_NS->VTOR, loadsMSP_NSfrom the NS vector table, and executesBXNSto atomically switch state and jump to the NS reset handler.Relationship to PR #647
This PR is independent of #647 (SCB NSACR / NVIC ITNS) and can be reviewed separately. Together they cover the complete ARMv8-M TrustZone setup: SAU region programming (this PR), interrupt routing and FPU access (#647), and NS boot handoff (this PR).
Motivation
The downstream motivating use-case is the embassy-stm32 TrustZone/SAU driver. Currently
jump_to_nonsecurelives in that vendor HAL using raw inline assembly. Once this lands it can be replaced with the typed API here, removing duplicated unsafe boot code from every ARMv8-M HAL that needs it.