Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,64 @@ pub unsafe extern "C" fn secondary_entry(stack_end: *mut u64) -> ! {
set_exception_vector = sym crate::set_exception_vector,
)
}

/// An assembly entry point for warm boot (e.g. resume from suspend).
///
/// It will enable the MMU, disable trapping of floating point instructions,
/// set up the exception vector, set the stack pointer to `stack_ptr`,
/// and then jump to `entry_point(arg)`.
///
/// The function expects to be passed a pointer to a `SuspendContext` instance
/// that will be valid after returning from suspend. It should therefore be
/// a static, or allocated on the heap, to avoid being deallocated before resuming.
Copy link
Collaborator

Choose a reason for hiding this comment

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

It would be nice if we could put the SuspendContext on the stack of the core being resumed. What about if we made the data field of SuspendContext a pointer rather than a value? Then the SuspendContext itself could go on the stack, as it isn't needed once its contents is loaded into registers, and only the value behind the data pointer would need to be allocated statically.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Alternatively, looking at how this is used in RITM, you could remove the data field from SuspendContext entirely, and the entry function could just find its own data based on the MPIDR. (In the case of RITM, SUSPEND_CONTEXTS could store pairs of SuspendContext and SuspendCoreData.)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Either way, I think it should work to always put the entry pointer and data pointer (if we keep it) on the stack of the core that is being resumed. That only requires that there is enough spare stack space to fit, and if the stack is so small or full that there isn't, then resuming isn't going to go very well anyway. The stack pointer itself doesn't need to go on the stack, as it can be passed directly as the context of the PSCI CPU_SUSPEND call. This would work with a wrapper function in this crate for CPU_SUSPEND to put them on the stack.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I've pushed a couple of commits implementing my suggestions here, see what you think.

I've changed data to a u64 rather than an arbitrary type. This means that SuspendContext can always go on the stack of the core being resumed, so there's no need to worry about deallocating it. I realised that we do still need to keep the stack pointer there too, as depending on how you want to handle resuming it may be the bottom of the stack or somewhere near the top, and won't necessarily be a constant offset from the SuspendContext. This also maintains the ability to put the SuspendContext somewhere else if you really want to.

If you want to pass more data than a u64 then you can cast a pointer to it, in which case you can handle the deallocation yourself (if you need to). But I don't think you need this for RITM anyway.

I've also added a suspend_core function which constructs a SuspendContext on the stack and passes it to the PSCI cpu_suspend. I think this should work for RITM.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks a lot for helping me with this! Yes, I think the current version is probably best of both worlds - still low level enough to be a good fit for RITM and easy to make it memory safe.

///
/// This is a low-level function that should be used as the entry point when
/// manually calling the `CPU_SUSPEND` PSCI call. It deliberately doesn't store any
/// data itself so that the caller has maximum flexibility over things such as
/// where the `SuspendContext` is stored. If you need to restore any state (such
/// as the registers), or you want to emulate returning from a function after
/// suspending the CPU, you need to implement this functionality yourself.
///
/// # Safety
///
/// The caller guarantees that the `SuspendContext` instance passed to the function
/// will be valid and safe to read when the CPU resumes (especially important when
/// the data is wrapped in a mutex, for instance).
Copy link
Collaborator

Choose a reason for hiding this comment

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

For how long does it need to remain valid? I guess as long as entry runs, which is potentially forever? This seems like a difficult requirement for the caller to meet. I wonder if it would make more sense for entry to take a pointer rather than a reference, then it would be up to the caller of warm_boot_entry to choose an appropriate lifetime to match what their entry function expects.

Copy link
Contributor Author

@m4tx m4tx Feb 18, 2026

Choose a reason for hiding this comment

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

Ah, that's a very good point and indeed a very good reason to pass a pointer instead of a reference here.

///
/// The `context` parameter has the following restrictions on its fields:
/// * `stack_ptr` must be a valid stack pointer.
/// * `entry` must be a valid function pointer taking `SuspendContext<T>`.
#[unsafe(naked)]
pub unsafe extern "C" fn warm_boot_entry<T>(context: *const SuspendContext<T>) -> ! {
naked_asm!(
"mov x19, x0",
"bl enable_mmu",
// Disable trapping floating point access in EL1.
"mrs x30, cpacr_el1",
"orr x30, x30, #(0x3 << 20)",
"msr cpacr_el1, x30",
"isb",
// Load data from SuspendContext
"ldr x0, [x19, #{stack_ptr_offset}]",
"ldr x1, [x19, #{entry_offset}]",
// Set the exception vector.
"bl {set_exception_vector}",
Copy link
Collaborator

Choose a reason for hiding this comment

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

The stack pointer needs to be set before calling set_exception_vector, as it is a Rust function. It may also clobber x0 and x1, so you need to get the entry function pointer after this.

// Set the stack pointer (x0).
"mov sp, x0",
// Jump to entry point (x1) with arg (x2).
"mov x0, x19",
"br x1",
set_exception_vector = sym crate::set_exception_vector,
stack_ptr_offset = const offset_of!(SuspendContext<T>, stack_ptr),
entry_offset = const offset_of!(SuspendContext<T>, entry),
)
}

/// Data used by [`warm_boot_entry`] to restore the CPU state after resuming.
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct SuspendContext<T> {
pub stack_ptr: u64,
pub entry: extern "C" fn(&Self) -> !,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would it be simpler for this function to take &T rather than &Self?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was initially thinking to do this, but then I realized if someone wants to allocate SuspendContext on the heap, there will be no easy way to deallocate it after unsuspending. Hence I went with passing Self here.

pub data: T,
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use core::arch::asm;
#[cfg(not(feature = "initial-pagetable"))]
use core::arch::naked_asm;
use core::mem::ManuallyDrop;
pub use entry::secondary_entry;
pub use entry::{SuspendContext, secondary_entry, warm_boot_entry};
#[cfg(feature = "exceptions")]
pub use exceptions::{ExceptionHandlers, RegisterState, RegisterStateRef};
#[cfg(all(feature = "initial-pagetable", feature = "el1"))]
Expand Down
Loading