@@ -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" ) ]
255309fn dsb_st ( ) {
0 commit comments