From 0fe6c4c9304bfd2e57128fca936b61afc17c3c9a Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 27 Jul 2025 16:36:43 +0100 Subject: [PATCH 01/11] Put asm in unsafe block. Avoids future unsafe-in-unsafe warning,. --- cortex-m/src/asm.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/cortex-m/src/asm.rs b/cortex-m/src/asm.rs index 3bdea1fe..f5acf3cb 100644 --- a/cortex-m/src/asm.rs +++ b/cortex-m/src/asm.rs @@ -195,18 +195,20 @@ pub unsafe fn semihosting_syscall(nr: u32, arg: u32) -> u32 { #[cfg(cortex_m)] #[inline(always)] pub unsafe fn enter_unprivileged(psp: *const u32, entry: fn() -> !) -> ! { - core::arch::asm!( - "mrs {tmp}, CONTROL", - "orr {tmp}, #3", - "msr PSP, {psp}", - "msr CONTROL, {tmp}", - "isb", - "bx {ent}", - tmp = in(reg) 0, - psp = in(reg) psp, - ent = in(reg) entry, - options(noreturn, nomem, nostack) - ); + unsafe { + core::arch::asm!( + "mrs {tmp}, CONTROL", + "orr {tmp}, #3", + "msr PSP, {psp}", + "msr CONTROL, {tmp}", + "isb", + "bx {ent}", + tmp = in(reg) 0, + psp = in(reg) psp, + ent = in(reg) entry, + options(noreturn, nostack) + ); + } } /// Bootstrap. From 3acafb3835e493f156b217b0f7945b327250c1fb Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 27 Jul 2025 16:37:23 +0100 Subject: [PATCH 02/11] Enter Unprivileged needs an extern "C" fn The function pointer is branched to by assembly, so we're relying on a certain ABI. --- cortex-m/src/asm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cortex-m/src/asm.rs b/cortex-m/src/asm.rs index f5acf3cb..fa5bb520 100644 --- a/cortex-m/src/asm.rs +++ b/cortex-m/src/asm.rs @@ -194,7 +194,7 @@ pub unsafe fn semihosting_syscall(nr: u32, arg: u32) -> u32 { /// it, you may wish to set the `PSPLIM` register to guard against this. #[cfg(cortex_m)] #[inline(always)] -pub unsafe fn enter_unprivileged(psp: *const u32, entry: fn() -> !) -> ! { +pub unsafe fn enter_unprivileged(psp: *const u32, entry: extern "C" fn() -> !) -> ! { unsafe { core::arch::asm!( "mrs {tmp}, CONTROL", From 27e4745d9c4774451b7291a3b396f041bafabcd1 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sun, 27 Jul 2025 16:37:34 +0100 Subject: [PATCH 03/11] Add an object you can use to hold the PSP stack. --- cortex-m/src/lib.rs | 1 + cortex-m/src/psp.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 cortex-m/src/psp.rs diff --git a/cortex-m/src/lib.rs b/cortex-m/src/lib.rs index 147c210b..c103322b 100644 --- a/cortex-m/src/lib.rs +++ b/cortex-m/src/lib.rs @@ -111,6 +111,7 @@ pub mod interrupt; pub mod itm; pub mod peripheral; pub mod prelude; +pub mod psp; pub mod register; pub use crate::peripheral::Peripherals; diff --git a/cortex-m/src/psp.rs b/cortex-m/src/psp.rs new file mode 100644 index 00000000..303f507b --- /dev/null +++ b/cortex-m/src/psp.rs @@ -0,0 +1,49 @@ +//! Process Stack Pointer support + +#![allow(clippy::missing_inline_in_public_items)] + +use core::cell::UnsafeCell; + +/// A stack you can use as your Process Stack (PSP) +/// +/// The const-param N is the size **in 32-bit words** +#[repr(align(8), C)] +pub struct Stack { + space: UnsafeCell<[u32; N]>, +} + +impl Stack { + /// Const-initialise a Stack + /// + /// Use a turbofish to specify the size, like: + /// + /// ```rust + /// static PSP_STACK: Stack::<4096> = Stack::new(); + /// ``` + pub const fn new() -> Stack { + Stack { + space: UnsafeCell::new([0; N]), + } + } + + /// Return the top of the stack + pub fn get_top(&self) -> *mut u32 { + let start = self.space.get() as *mut u32; + unsafe { start.add(N) } + } +} + +unsafe impl Sync for Stack {} + +impl core::default::Default for Stack { + fn default() -> Self { + Stack::new() + } +} + +/// Switch to running on the PSP +#[cfg(cortex_m)] +pub fn switch_to_psp(psp_stack: &Stack, function: extern "C" fn() -> !) -> ! { + let stack_top = psp_stack.get_top(); + unsafe { crate::asm::enter_unprivileged(stack_top, function) } +} From 4233ce419a831abd4f416f100fc30bed246326a5 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 13 Sep 2025 17:25:04 +0100 Subject: [PATCH 04/11] Explain the lint --- cortex-m/src/psp.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cortex-m/src/psp.rs b/cortex-m/src/psp.rs index 303f507b..a7be8859 100644 --- a/cortex-m/src/psp.rs +++ b/cortex-m/src/psp.rs @@ -1,5 +1,7 @@ //! Process Stack Pointer support +// This is a useful lint for functions like 'asm::wfi()' but it's not a useful +// lint here. #![allow(clippy::missing_inline_in_public_items)] use core::cell::UnsafeCell; From 176c74d31ac649af4b83986b6443bd069082e715 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 13 Sep 2025 17:48:56 +0100 Subject: [PATCH 05/11] Fix PSP example code. --- cortex-m/src/psp.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cortex-m/src/psp.rs b/cortex-m/src/psp.rs index a7be8859..e5f8559b 100644 --- a/cortex-m/src/psp.rs +++ b/cortex-m/src/psp.rs @@ -20,6 +20,7 @@ impl Stack { /// Use a turbofish to specify the size, like: /// /// ```rust + /// # use cortex_m::psp::Stack; /// static PSP_STACK: Stack::<4096> = Stack::new(); /// ``` pub const fn new() -> Stack { From 7699eef31386e22c7bf5d976498b0a03be4f4d74 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 27 Sep 2025 15:48:10 +0100 Subject: [PATCH 06/11] Update the functions for using the PSP Now offers both Priv and Unpriv modes, and has a handle to represent ownership of a static Stack object. The load/store check on `Stack::taken` is not perfectly thread safe, but it's probably good enough and doing better requires a critical-section or CAS atomics. --- cortex-m/src/asm.rs | 36 ++++++++++++++++++++++++++--- cortex-m/src/psp.rs | 53 +++++++++++++++++++++++++++++++++++++------ testsuite/src/main.rs | 13 +++++++++++ 3 files changed, 92 insertions(+), 10 deletions(-) diff --git a/cortex-m/src/asm.rs b/cortex-m/src/asm.rs index fa5bb520..b7353482 100644 --- a/cortex-m/src/asm.rs +++ b/cortex-m/src/asm.rs @@ -176,9 +176,9 @@ pub unsafe fn semihosting_syscall(nr: u32, arg: u32) -> u32 { call_asm!(__sh_syscall(nr: u32, arg: u32) -> u32) } -/// Switch to unprivileged mode. +/// Switch to unprivileged mode using the Process Stack /// -/// Sets CONTROL.SPSEL (setting the program stack to be the active +/// Sets CONTROL.SPSEL (setting the Process Stack to be the active /// stack) and CONTROL.nPRIV (setting unprivileged mode), updates the /// program stack pointer to the address in `psp`, then jumps to the /// address in `entry`. @@ -194,7 +194,7 @@ pub unsafe fn semihosting_syscall(nr: u32, arg: u32) -> u32 { /// it, you may wish to set the `PSPLIM` register to guard against this. #[cfg(cortex_m)] #[inline(always)] -pub unsafe fn enter_unprivileged(psp: *const u32, entry: extern "C" fn() -> !) -> ! { +pub unsafe fn enter_unprivileged_psp(psp: *const u32, entry: extern "C" fn() -> !) -> ! { unsafe { core::arch::asm!( "mrs {tmp}, CONTROL", @@ -211,6 +211,36 @@ pub unsafe fn enter_unprivileged(psp: *const u32, entry: extern "C" fn() -> !) - } } +/// Switch to using the Process Stack, but remain in Privileged Mode +/// +/// Sets CONTROL.SPSEL (setting the Process Stack to be the active stack) but +/// leaves CONTROL.nPRIV alone, updates the program stack pointer to the +/// address in `psp`, then jumps to the address in `entry`. +/// +/// # Safety +/// +/// * `psp` and `entry` must point to valid stack memory and executable code, +/// respectively. +/// * `psp` must be 8 bytes aligned and point to stack top as stack grows +/// towards lower addresses. +/// * The size of the stack provided here must be large enough for your +/// program - stack overflows are obviously UB. If your processor supports +/// it, you may wish to set the `PSPLIM` register to guard against this. +#[cfg(cortex_m)] +#[inline(always)] +pub unsafe fn enter_privileged_psp(psp: *const u32, entry: extern "C" fn() -> !) -> ! { + unsafe { + core::arch::asm!( + "msr PSP, {psp}", + "isb", + "bx {ent}", + psp = in(reg) psp, + ent = in(reg) entry, + options(noreturn, nostack) + ); + } +} + /// Bootstrap. /// /// Clears CONTROL.SPSEL (setting the main stack to be the active stack), diff --git a/cortex-m/src/psp.rs b/cortex-m/src/psp.rs index e5f8559b..6c09af13 100644 --- a/cortex-m/src/psp.rs +++ b/cortex-m/src/psp.rs @@ -4,7 +4,26 @@ // lint here. #![allow(clippy::missing_inline_in_public_items)] -use core::cell::UnsafeCell; +use core::{ + cell::UnsafeCell, + sync::atomic::{AtomicBool, Ordering}, +}; + +/// Represents access to a [`Stack`] +pub struct StackHandle(*mut u32, usize); + +impl StackHandle { + /// Get the pointer to the top of the stack + pub const fn top(&mut self) -> *mut u32 { + // SAFETY: The stack was this big when we constructed the handle + unsafe { self.0.add(self.1) } + } + + /// Get the pointer to the top of the stack + pub const fn bottom(&mut self) -> *mut u32 { + self.0 + } +} /// A stack you can use as your Process Stack (PSP) /// @@ -12,6 +31,7 @@ use core::cell::UnsafeCell; #[repr(align(8), C)] pub struct Stack { space: UnsafeCell<[u32; N]>, + taken: AtomicBool, } impl Stack { @@ -22,17 +42,27 @@ impl Stack { /// ```rust /// # use cortex_m::psp::Stack; /// static PSP_STACK: Stack::<4096> = Stack::new(); + /// fn example() { + /// let handle = PSP_STACK.take_handle(); + /// // ... + /// } /// ``` pub const fn new() -> Stack { Stack { space: UnsafeCell::new([0; N]), + taken: AtomicBool::new(false), } } /// Return the top of the stack - pub fn get_top(&self) -> *mut u32 { + pub fn take_handle(&self) -> StackHandle { + if self.taken.load(Ordering::Acquire) { + panic!("Cannot get two handles to one stack!"); + } + self.taken.store(true, Ordering::Release); + let start = self.space.get() as *mut u32; - unsafe { start.add(N) } + StackHandle(start, N) } } @@ -44,9 +74,18 @@ impl core::default::Default for Stack { } } -/// Switch to running on the PSP +/// Switch to unprivileged mode running on the Process Stack Pointer (PSP) +/// +/// In Unprivileged Mode, code can no longer perform privileged operations, +/// such as disabling interrupts. +/// +#[cfg(cortex_m)] +pub fn switch_to_unprivileged_psp(mut psp_stack: StackHandle, function: extern "C" fn() -> !) -> ! { + unsafe { crate::asm::enter_unprivileged_psp(psp_stack.top(), function) } +} + +/// Switch to running on the Process Stack Pointer (PSP), but remain in privileged mode #[cfg(cortex_m)] -pub fn switch_to_psp(psp_stack: &Stack, function: extern "C" fn() -> !) -> ! { - let stack_top = psp_stack.get_top(); - unsafe { crate::asm::enter_unprivileged(stack_top, function) } +pub fn switch_to_privileged_psp(mut psp_stack: StackHandle, function: extern "C" fn() -> !) -> ! { + unsafe { crate::asm::enter_privileged_psp(psp_stack.top(), function) } } diff --git a/testsuite/src/main.rs b/testsuite/src/main.rs index ed2b1588..37d8a3fa 100644 --- a/testsuite/src/main.rs +++ b/testsuite/src/main.rs @@ -14,6 +14,10 @@ fn panic(info: &core::panic::PanicInfo) -> ! { static EXCEPTION_FLAG: AtomicBool = AtomicBool::new(false); +const STACK_SIZE_WORDS: usize = 1024; + +static STACK: cortex_m::psp::Stack = cortex_m::psp::Stack::new(); + #[cortex_m_rt::exception] fn PendSV() { EXCEPTION_FLAG.store(true, Ordering::SeqCst); @@ -86,4 +90,13 @@ mod tests { }); assert!(EXCEPTION_FLAG.load(Ordering::SeqCst)); } + + #[test] + fn check_stack_handles() { + let mut handle = super::STACK.take_handle(); + let top = handle.top(); + let bottom = handle.bottom(); + let delta = unsafe { top.byte_offset_from(bottom) }; + assert_eq!(delta as usize, super::STACK_SIZE_WORDS * 4); + } } From 521ebcb7e10124ed6323e923af30196ddee2710a Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 27 Sep 2025 16:03:29 +0100 Subject: [PATCH 07/11] Fixes for Rust 1.61 (the MSRV) --- cortex-m/src/psp.rs | 4 ++-- testsuite/src/main.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cortex-m/src/psp.rs b/cortex-m/src/psp.rs index 6c09af13..45510f6b 100644 --- a/cortex-m/src/psp.rs +++ b/cortex-m/src/psp.rs @@ -14,13 +14,13 @@ pub struct StackHandle(*mut u32, usize); impl StackHandle { /// Get the pointer to the top of the stack - pub const fn top(&mut self) -> *mut u32 { + pub fn top(&mut self) -> *mut u32 { // SAFETY: The stack was this big when we constructed the handle unsafe { self.0.add(self.1) } } /// Get the pointer to the top of the stack - pub const fn bottom(&mut self) -> *mut u32 { + pub fn bottom(&mut self) -> *mut u32 { self.0 } } diff --git a/testsuite/src/main.rs b/testsuite/src/main.rs index 37d8a3fa..143706b5 100644 --- a/testsuite/src/main.rs +++ b/testsuite/src/main.rs @@ -96,7 +96,7 @@ mod tests { let mut handle = super::STACK.take_handle(); let top = handle.top(); let bottom = handle.bottom(); - let delta = unsafe { top.byte_offset_from(bottom) }; - assert_eq!(delta as usize, super::STACK_SIZE_WORDS * 4); + let delta = unsafe { top.offset_from(bottom) }; + assert_eq!(delta as usize, super::STACK_SIZE_WORDS); } } From a951c1fa82a4a3887020630bbc01b39f9fbdc69c Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Sat, 27 Sep 2025 18:35:42 +0100 Subject: [PATCH 08/11] Fixed the assembly to work on ARMv6-M too. Turns out we don't check the assembly inside inline(asm) functions until the function is actually called (or referenced). --- cortex-m/src/asm.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cortex-m/src/asm.rs b/cortex-m/src/asm.rs index b7353482..3352e6b4 100644 --- a/cortex-m/src/asm.rs +++ b/cortex-m/src/asm.rs @@ -197,13 +197,14 @@ pub unsafe fn semihosting_syscall(nr: u32, arg: u32) -> u32 { pub unsafe fn enter_unprivileged_psp(psp: *const u32, entry: extern "C" fn() -> !) -> ! { unsafe { core::arch::asm!( - "mrs {tmp}, CONTROL", - "orr {tmp}, #3", - "msr PSP, {psp}", - "msr CONTROL, {tmp}", + "msr PSP, {psp}", + "mrs {tmp}, CONTROL", + "orrs {tmp}, {flags}", + "msr CONTROL, {tmp}", "isb", - "bx {ent}", + "bx {ent}", tmp = in(reg) 0, + flags = in(reg) 3, psp = in(reg) psp, ent = in(reg) entry, options(noreturn, nostack) From 14e748f1788c1bb93a9013415191e2fe2c31dc05 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 3 Oct 2025 17:49:42 +0100 Subject: [PATCH 09/11] Use const control register code instead of magic numbers. Also fixes the enter_privilged_function, which forgot to the change the mode so we didn't actually start using the PSP. --- cortex-m/src/asm.rs | 25 ++++++++++++++++++++++--- cortex-m/src/register/control.rs | 26 ++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/cortex-m/src/asm.rs b/cortex-m/src/asm.rs index 3352e6b4..3a5695b0 100644 --- a/cortex-m/src/asm.rs +++ b/cortex-m/src/asm.rs @@ -195,6 +195,13 @@ pub unsafe fn semihosting_syscall(nr: u32, arg: u32) -> u32 { #[cfg(cortex_m)] #[inline(always)] pub unsafe fn enter_unprivileged_psp(psp: *const u32, entry: extern "C" fn() -> !) -> ! { + use crate::register::control::{Control, Npriv, Spsel}; + const CONTROL_FLAGS: u32 = { + Control::from_bits(0) + .with_npriv(Npriv::Unprivileged) + .with_spsel(Spsel::Psp) + .bits() + }; unsafe { core::arch::asm!( "msr PSP, {psp}", @@ -204,7 +211,7 @@ pub unsafe fn enter_unprivileged_psp(psp: *const u32, entry: extern "C" fn() -> "isb", "bx {ent}", tmp = in(reg) 0, - flags = in(reg) 3, + flags = in(reg) CONTROL_FLAGS, psp = in(reg) psp, ent = in(reg) entry, options(noreturn, nostack) @@ -230,11 +237,23 @@ pub unsafe fn enter_unprivileged_psp(psp: *const u32, entry: extern "C" fn() -> #[cfg(cortex_m)] #[inline(always)] pub unsafe fn enter_privileged_psp(psp: *const u32, entry: extern "C" fn() -> !) -> ! { + use crate::register::control::{Control, Npriv, Spsel}; + const CONTROL_FLAGS: u32 = { + Control::from_bits(0) + .with_npriv(Npriv::Privileged) + .with_spsel(Spsel::Psp) + .bits() + }; unsafe { core::arch::asm!( - "msr PSP, {psp}", + "msr PSP, {psp}", + "mrs {tmp}, CONTROL", + "orrs {tmp}, {flags}", + "msr CONTROL, {tmp}", "isb", - "bx {ent}", + "bx {ent}", + tmp = in(reg) 0, + flags = in(reg) CONTROL_FLAGS, psp = in(reg) psp, ent = in(reg) entry, options(noreturn, nostack) diff --git a/cortex-m/src/register/control.rs b/cortex-m/src/register/control.rs index a991625b..ceca042e 100644 --- a/cortex-m/src/register/control.rs +++ b/cortex-m/src/register/control.rs @@ -9,13 +9,13 @@ pub struct Control { impl Control { /// Creates a `Control` value from raw bits. #[inline] - pub fn from_bits(bits: u32) -> Self { + pub const fn from_bits(bits: u32) -> Self { Self { bits } } /// Returns the contents of the register as raw bits #[inline] - pub fn bits(self) -> u32 { + pub const fn bits(self) -> u32 { self.bits } @@ -39,6 +39,17 @@ impl Control { } } + /// Sets the thread mode privilege level value (nPRIV). + #[inline] + pub const fn with_npriv(self, npriv: Npriv) -> Self { + let mask = 1 << 0; + let bits = match npriv { + Npriv::Unprivileged => self.bits | mask, + Npriv::Privileged => self.bits & !mask, + }; + Self { bits } + } + /// Currently active stack pointer #[inline] pub fn spsel(self) -> Spsel { @@ -59,6 +70,17 @@ impl Control { } } + /// Sets the SPSEL value. + #[inline] + pub const fn with_spsel(self, spsel: Spsel) -> Self { + let mask = 1 << 1; + let bits = match spsel { + Spsel::Psp => self.bits | mask, + Spsel::Msp => self.bits & !mask, + }; + Self { bits } + } + /// Whether context floating-point is currently active #[inline] pub fn fpca(self) -> Fpca { From ecf5ddc4553dada24cd47830fde1833ba9c68ff7 Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Fri, 3 Oct 2025 17:58:09 +0100 Subject: [PATCH 10/11] Set the PSPLIM when switching to the PSP on Armv8-M Mainline. --- cortex-m/src/psp.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/cortex-m/src/psp.rs b/cortex-m/src/psp.rs index 45510f6b..b7cc65ae 100644 --- a/cortex-m/src/psp.rs +++ b/cortex-m/src/psp.rs @@ -81,11 +81,27 @@ impl core::default::Default for Stack { /// #[cfg(cortex_m)] pub fn switch_to_unprivileged_psp(mut psp_stack: StackHandle, function: extern "C" fn() -> !) -> ! { - unsafe { crate::asm::enter_unprivileged_psp(psp_stack.top(), function) } + // set the stack limit + #[cfg(armv8m_main)] + unsafe { + crate::register::psplim::write(psp_stack.bottom() as u32); + } + // do the switch + unsafe { + crate::asm::enter_unprivileged_psp(psp_stack.top(), function); + } } /// Switch to running on the Process Stack Pointer (PSP), but remain in privileged mode #[cfg(cortex_m)] pub fn switch_to_privileged_psp(mut psp_stack: StackHandle, function: extern "C" fn() -> !) -> ! { - unsafe { crate::asm::enter_privileged_psp(psp_stack.top(), function) } + // set the stack limit + #[cfg(armv8m_main)] + unsafe { + crate::register::psplim::write(psp_stack.bottom() as u32); + } + // do the switch + unsafe { + crate::asm::enter_privileged_psp(psp_stack.top(), function); + } } From 155ab852ef39147ff4c7d56900868e994f48cb19 Mon Sep 17 00:00:00 2001 From: Adam Greig Date: Mon, 6 Oct 2025 01:56:54 +0100 Subject: [PATCH 11/11] Update cortex-m/src/psp.rs --- cortex-m/src/psp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cortex-m/src/psp.rs b/cortex-m/src/psp.rs index b7cc65ae..36b663f2 100644 --- a/cortex-m/src/psp.rs +++ b/cortex-m/src/psp.rs @@ -19,7 +19,7 @@ impl StackHandle { unsafe { self.0.add(self.1) } } - /// Get the pointer to the top of the stack + /// Get the pointer to the bottom of the stack pub fn bottom(&mut self) -> *mut u32 { self.0 }