Conversation
|
Disclaimer: note that while the code is very similar to |
src/entry.rs
Outdated
| /// `stack_ptr` must be a valid stack pointer. | ||
| /// `entry_point` must be a valid function pointer taking one argument. | ||
| #[unsafe(naked)] | ||
| pub unsafe extern "C" fn warm_boot_entry<T>(context: *mut SuspendContext<T>) -> ! { |
There was a problem hiding this comment.
How is this intended to be used? Does it need to be separate from secondary_entry? Would it make sense to provide a wrapper around psci::cpu_suspend the same way as we have a wrapper around psci::cpu_on?
There was a problem hiding this comment.
The idea is that this is deliberately low-level so that the user can implement suspending however they like. In case of ritm, where this is going to be used, ritm is just a thin hypervisor which doesn't need to restore any internal state after resuming.
Sure, I think it would make sense to provide a wrapper around psci::cpu_suspend which could emulate returning from a function even after powerdown. It could store the SuspendContext on the stack, along with the state of the registers, and just restore everything after resuming and call ret. This wrapper would still use warm_boot_entry internally. However, because such wrapper would be useless for ritm, I don't want to contribute functionality I won't actively use, and therefore, most importantly, test.
I've updated the docs of the function with this info.
qwandor
left a comment
There was a problem hiding this comment.
Whoops, didn't mean to approve before, sorry.
Update: I was able to test this in google/ritm#8 by using Rusted Firmware-A inside QEMU, and it seems to be working just fine. |
src/entry.rs
Outdated
| #[repr(C)] | ||
| pub struct SuspendContext<T> { | ||
| pub stack_ptr: u64, | ||
| pub entry: extern "C" fn(&Self) -> !, |
There was a problem hiding this comment.
Would it be simpler for this function to take &T rather than &Self?
There was a problem hiding this comment.
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.
src/entry.rs
Outdated
| /// | ||
| /// 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). |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Ah, that's a very good point and indeed a very good reason to pass a pointer instead of a reference here.
| "ldr x0, [x19, #{stack_ptr_offset}]", | ||
| "ldr x1, [x19, #{entry_offset}]", | ||
| // Set the exception vector. | ||
| "bl {set_exception_vector}", |
There was a problem hiding this comment.
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.
src/entry.rs
Outdated
| /// | ||
| /// 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 means that the SuspendContext can be stored on the stack of the resuming core. Larger values can be passed via a pointer to some other location.
| /// want to handle resuming this could either be the bottom of the stack (if you want to treat | ||
| /// resuming like `CPU_ON`) or the top (if `entry` will restore register state and return from the | ||
| /// point where the suspend happened). | ||
| pub unsafe fn suspend_core<C: smccc::Call, T: Sized>( |
There was a problem hiding this comment.
We don't use T anywhere, I think it can be dropped.
| /// point where the suspend happened). | ||
| pub unsafe fn suspend_core<C: smccc::Call, T: Sized>( | ||
| power_state: u32, | ||
| stack_ptr: *mut u64, |
There was a problem hiding this comment.
nitpick: it feels like this should be *mut u8 (to match the internals of StackPage, and basically treat at as an untyped pointer), or perhaps some other type.
No description provided.