Skip to content

Commit 30b06d7

Browse files
authored
Merge pull request #31 from m4tx/start_core_arg
Allow arbitrary closures in `start_core`
2 parents 17e58a0 + bb3a41b commit 30b06d7

File tree

2 files changed

+77
-16
lines changed

2 files changed

+77
-16
lines changed

src/entry.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
//! Entrypoint code
66
7-
use core::arch::naked_asm;
7+
use core::{arch::naked_asm, mem::offset_of};
8+
9+
use crate::StartCoreStack;
810

911
/// This is a generic entry point for an image. It carries out the operations required to prepare the
1012
/// loaded image to be run. Specifically, it zeroes the bss section using registers x25 and above,
@@ -51,8 +53,8 @@ unsafe extern "C" fn entry() -> ! {
5153
/// An assembly entry point for secondary cores.
5254
///
5355
/// It will enable the MMU, disable trapping of floating point instructions, initialise the
54-
/// stack pointer to `stack_end` and then jump to the function pointer at the bottom of the
55-
/// stack with the u64 value second on the stack as a parameter.
56+
/// stack pointer to `stack_end` and then jump to the trampoline function pointer at the bottom
57+
/// of the stack with the closure pointer second on the stack as a parameter.
5658
///
5759
/// # Safety
5860
///
@@ -69,15 +71,20 @@ pub unsafe extern "C" fn secondary_entry(stack_end: *mut u64) -> ! {
6971
"isb",
7072
// Set the stack pointer which was passed.
7173
"mov sp, x0",
72-
// Load Rust entry point address and argument from the bottom of the stack into
73-
// callee-saved registers.
74-
"ldp x19, x20, [sp, #-16]",
74+
// Load the closure address into x19 and the trampoline address into x20.
75+
// This is loaded from StartCoreStack.
76+
"ldr x19, [sp, #{entry_ptr_offset}]",
77+
"ldr x20, [sp, #{trampoline_ptr_offset}]",
7578
// Set the exception vector.
7679
"bl {set_exception_vector}",
77-
// Pass argument to Rust entry point.
80+
// Pass the entry point (closure) address to the trampoline function.
7881
"mov x0, x19",
79-
// Call into Rust code.
82+
// Call into Rust trampoline.
8083
"br x20",
84+
entry_ptr_offset = const offset_of!(StartCoreStack<()>, entry_ptr) as isize
85+
- size_of::<StartCoreStack<()>>() as isize,
86+
trampoline_ptr_offset = const offset_of!(StartCoreStack<()>, trampoline_ptr) as isize
87+
- size_of::<StartCoreStack<()>>() as isize,
8188
set_exception_vector = sym crate::set_exception_vector,
8289
)
8390
}

src/lib.rs

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ pub use pagetable::{
4646
InitialPagetable,
4747
};
4848

49+
use core::mem::ManuallyDrop;
50+
4951
#[cfg(not(feature = "initial-pagetable"))]
5052
#[unsafe(naked)]
5153
#[unsafe(link_section = ".init")]
@@ -209,37 +211,71 @@ impl StackPage {
209211
}
210212
}
211213

214+
#[repr(C)]
215+
pub(crate) struct StartCoreStack<F> {
216+
entry_ptr: *mut ManuallyDrop<F>,
217+
trampoline_ptr: unsafe extern "C" fn(&mut ManuallyDrop<F>) -> !,
218+
}
219+
212220
#[cfg(feature = "psci")]
213221
/// Issues a PSCI CPU_ON call to start the CPU core with the given MPIDR.
214222
///
215223
/// This starts the core with an assembly entry point which will enable the MMU, disable trapping of
216224
/// floating point instructions, initialise the stack pointer to the given value, and then jump to
217225
/// the given Rust entry point function, passing it the given argument value.
218226
///
227+
/// The closure passed as `rust_entry` **should never return**. Because the
228+
/// [never type has not been stabilized](https://github.com/rust-lang/rust/issues/35121)), this
229+
/// cannot be enforced by the type system yet.
230+
///
219231
/// # Safety
220232
///
221233
/// `stack` must point to a region of memory which is reserved for this core's stack. It must remain
222234
/// valid as long as the core is running, and there must not be any other access to it during that
223235
/// time. It must be mapped both for the current core to write to it (to pass initial parameters)
224236
/// and in the initial page table which the core being started will used, with the same memory
225237
/// attributes for both.
226-
pub unsafe fn start_core<C: smccc::Call, const N: usize>(
238+
// TODO: change `F` generic bounds to `FnOnce() -> !` when the never type is stabilized:
239+
// https://github.com/rust-lang/rust/issues/35121
240+
pub unsafe fn start_core<C: smccc::Call, F: FnOnce() + Send + 'static, const N: usize>(
227241
mpidr: u64,
228242
stack: *mut Stack<N>,
229-
rust_entry: extern "C" fn(arg: u64) -> !,
230-
arg: u64,
243+
rust_entry: F,
231244
) -> Result<(), smccc::psci::Error> {
245+
const {
246+
assert!(
247+
core::mem::size_of::<StartCoreStack<F>>()
248+
+ 2 * core::mem::size_of::<F>()
249+
+ 2 * core::mem::align_of::<F>()
250+
+ 1024 // trampoline stack frame overhead
251+
<= core::mem::size_of::<Stack<N>>(),
252+
"the `rust_entry` closure is too big to fit in the core stack"
253+
);
254+
}
255+
256+
let rust_entry = ManuallyDrop::new(rust_entry);
257+
258+
let stack_start = stack.cast::<u8>();
259+
let align_offfset = stack_start.align_offset(core::mem::align_of::<F>());
260+
let entry_ptr = stack_start
261+
.wrapping_add(align_offfset)
262+
.cast::<ManuallyDrop<F>>();
263+
232264
assert!(stack.is_aligned());
233265
// The stack grows downwards on aarch64, so get a pointer to the end of the stack.
234266
let stack_end = stack.wrapping_add(1);
267+
let params = stack_end.cast::<StartCoreStack<F>>().wrapping_sub(1);
235268

236-
// Write Rust entry point to the stack, so the assembly entry point can jump to it.
237-
let params = stack_end as *mut u64;
269+
// Write the trampoline and entry closure, so the assembly entry point can jump to it.
238270
// SAFETY: Our caller promised that the stack is valid and nothing else will access it.
239271
unsafe {
240-
*params.wrapping_sub(1) = rust_entry as usize as _;
241-
*params.wrapping_sub(2) = arg;
242-
}
272+
entry_ptr.write(rust_entry);
273+
*params = StartCoreStack {
274+
entry_ptr,
275+
trampoline_ptr: trampoline::<F>,
276+
};
277+
};
278+
243279
// Wait for the stores above to complete before starting the secondary CPU core.
244280
dsb_st();
245281

@@ -250,6 +286,24 @@ pub unsafe fn start_core<C: smccc::Call, const N: usize>(
250286
)
251287
}
252288

289+
#[cfg(feature = "psci")]
290+
/// Used by [`start_core`] as an entry point for the secondary CPU core.
291+
///
292+
/// # Safety
293+
///
294+
/// This calls [`ManuallyDrop::take`] on the provided argument, so this function must be
295+
/// called at most once for a given instance of `F`.
296+
// TODO: change `F` generic bounds to `FnOnce() -> !` when the never type is stabilized:
297+
// https://github.com/rust-lang/rust/issues/35121
298+
unsafe extern "C" fn trampoline<F: FnOnce() + Send + 'static>(entry: &mut ManuallyDrop<F>) -> ! {
299+
// SAFETY: the trampoline function is only ever called once after creating ManuallyDrop
300+
// instance, so we won't call ManuallyDrop::take more than once.
301+
let entry = unsafe { ManuallyDrop::take(entry) };
302+
entry();
303+
304+
panic!("rust_entry function passed to start_core should never return");
305+
}
306+
253307
/// Data synchronisation barrier that waits for stores to complete, for the full system.
254308
#[cfg(feature = "psci")]
255309
fn dsb_st() {

0 commit comments

Comments
 (0)