Skip to content

Add enter_unprivileged() function #594

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

kevinbube
Copy link

This adds a function to switch to program stack and unprivileged mode.

Fixes #583

@kevinbube kevinbube force-pushed the new/enter_unprivileged_fn branch from 93ae33a to 89152c5 Compare April 25, 2025 16:36
/// code, respectively.
#[cfg(cortex_m)]
#[inline(always)]
pub fn enter_unprivileged(psp: &u32, entry: fn() -> !) -> ! {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think PSP should be either a raw pointer, or a new type that requires appropriate alignment. Conceptually this is similar to https://docs.rs/rp2040-hal/latest/rp2040_hal/multicore/struct.Core.html#method.spawn and we could borrow the StackAllocation idea.

Unfortunately I don't immediately recall if PSP needs to be 4 or 8 byte aligned. We should check, and document.

The function also needs to be marked unsafe!

@kevinbube kevinbube force-pushed the new/enter_unprivileged_fn branch from 89152c5 to f8f611f Compare May 1, 2025 20:15
@kevinbube
Copy link
Author

kevinbube commented May 1, 2025 via email

@jonathanpallant
Copy link
Contributor

https://github.com/ARM-software/abi-aa/blob/main/aapcs32/aapcs32.rst#6212stack-constraints-at-a-public-interface

6.2.1.2 Stack constraints at a public interface

The stack must also conform to the following constraint at a public interface:

SP mod 8 = 0. The stack must be double-word aligned.

As this function will cause entry to the given function, the stack must have eight byte alignment - if I'm reading this correctly.

This adds a function to switch to program stack and unprivileged mode.

Fixes rust-embedded#583
@kevinbube kevinbube force-pushed the new/enter_unprivileged_fn branch from f8f611f to f26be34 Compare May 2, 2025 08:40
@kevinbube
Copy link
Author

kevinbube commented May 2, 2025 via email

@jonathanpallant
Copy link
Contributor

Well it must always be 4 byte aligned, and if it is 8 byte aligned it is still 4 byte aligned as well. The 4 byte alignment I think is an Arm processor requirement the 8 byte over-alignment is from the Arm Architecture Procedure Call Standard - which is a thing we agree to do, but the processor does not care whether we do or we don't.

Copy link
Contributor

@jonathanpallant jonathanpallant left a comment

Choose a reason for hiding this comment

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

LGTM, but we should write a test case (maybe using QEMU?)

@jonathanpallant
Copy link
Contributor

Some test code:

//! Check we can switch to the PSP.

#![no_std]
#![no_main]

use core::cell::UnsafeCell;

// Contains our panic handler
use qemu_thumbv7em as _;
// Does our demft output
use defmt_semihosting as _;

/// 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)]
struct PspStack<const N: usize> {
    space: UnsafeCell<[u32; N]>,
}

impl<const N: usize> PspStack<N> {
    /// Const-initialise a PspStack
    ///
    /// Use a turbofish to specify the size, like:
    ///
    /// ```rust
    /// static PSP_STACK: PspStack::<4096> = PspStack::new();
    /// ```
    pub const fn new() -> PspStack<N> {
        PspStack {
            space: UnsafeCell::new([0; N]),
        }
    }

    /// Return the top of the PSP stack
    pub fn get_top(&self) -> *mut u32 {
        let start = self.space.get() as *mut u32;
        let end = unsafe { start.add(N) };
        end
    }
}

unsafe impl<const N: usize> Sync for PspStack<N> {}

static PSP_STACK: PspStack<4096> = PspStack::new();

#[cortex_m_rt::entry]
fn main() -> ! {
    let x = 5;
    defmt::info!(
        "Using MSP. addr(x) = {=usize:08x}",
        core::ptr::addr_of!(x) as usize
    );
    unsafe {
        let psp_stack = PSP_STACK.get_top();
        defmt::info!("PSP stack is at {=usize:08x}", psp_stack as usize);
        enter_unprivileged(psp_stack, user_mode);
    }
}

fn user_mode() -> ! {
    let x = 5;
    defmt::info!(
        "Using PSP. addr(x) = {=usize:08x}",
        core::ptr::addr_of!(x) as usize
    );
    panic!("All finished");
}

/// Switch to unprivileged mode.
///
/// Sets CONTROL.SPSEL (setting the program 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`.
///
/// # Safety
///
/// `psp` and `entry` must point to valid stack memory and executable
/// code, respectively. `psp` must be 8 bytes aligned.
#[inline(always)]
pub unsafe fn enter_unprivileged(psp: *const u32, entry: fn() -> !) -> ! {
    unsafe {
        core::arch::asm!(
            "mrs {tmp}, CONTROL",
            "orr {tmp}, #2",
            "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)
        );
    }
}

Produces:

------------------------------------------------------------------------
[INFO ] Using MSP. addr(x) = 203fffe4 (bin/psp_test.rs:50)
[INFO ] PSP stack is at 20004000 (bin/psp_test.rs:56)
[INFO ] Using PSP. addr(x) = 20003fd8 (bin/psp_test.rs:63)
[ERROR] Panic! All finished (src/bin/psp_test.rs:67) (src/lib.rs:12)
------------------------------------------------------------------------

@jonathanpallant
Copy link
Contributor

jonathanpallant commented May 2, 2025

Having done this exercise, I now realise it's important to point out that the stack descends, and so they should pass a pointer to the top of the stack region. In fact, the word just above the stack region - because the SP is decremented before the first write. That is non-obvious.

We should probably just include the PspStack type I wrote, to make it easier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Hard fault on debug compilation
2 participants