Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use TZ SMC call to do aarch32 -> aarch64 execution state switch
As mentioned in the previous commit, the (proprietary) PSCI implementation in Qualcomm's TZ firmware seems to have a bug which complicates booting aarch64 kernels, unless we invoke its SMC call to switch execution state from aarch32 to aarch64. If we bypass it, the secondary CPU cores will be brought up in aarch32 state instead of aarch64 later. Right now we work around that in the HYP firmware by ignoring if TZ tells us to boot the secondary CPU cores in aarch32 state instead of aarch64. We could probably adjust this further to record ourselves if EL1 should be running in aarch64 or aarch32, and use that to drop the remaining FIXMEs. But overall, I would argue that's it's better to avoid the PSCI bug in the first place. Who knows what other problems might be caused by not making TZ aware of the correct execution state of EL1? To avoid the bug, we need to invoke TZ's SMC call at least once to make it aware that EL1 will be running in aarch64 execution state from now on. Unfortunately, TZ does not involve the hypervisor when switching states... It seems to modify our HCR_EL2 register to enable aarch64 and always returns to EL1 even if we do the SMC call from EL2. So, somehow we need to jump back to EL2 immediately after TZ returns to EL1. We could change the EL1 entry point to some custom code and do a HVC call back into HYP from there. But then we also need to save registers etc etc. As a "hypervisor" in EL2, there must be some way to prevent EL1 from running, right? Looking at the available hypervisor configuration options available in HCR_EL2 I found "HCR_EL2.TGE" (Trap General Exceptions). It does much more than we need but the promising part was "An exception return to EL1 is treated as an illegal exception return." Unfortunately, setting it before doing the SMC call just results in a hang. Reading a bit further, the illegal exception return is sent to the exception handlers in EL3, not to EL2, because the exception happens during execution of the "eret" instruction in EL3. Oh well. What we need is a way to cause an exception to EL2 immediately *after* the "eret" has completed, so once the CPU attempts to execute the first instruction. After thinking about it for a while this is actually quite simple. Since EL2 is used to implement hypervisors, it has the "stage 2 address translation" mechanism to provide each virtual machine with its own view of memory. This isn't used in qhypstub because it does not implement a hypervisor, and we don't prevent EL1 from accessing EL2 memory. But actually, a simple way to cause an Instruction Abort from EL1 into EL2 is to enable stage 2 address translation without setting up any (valid) translation tables. This means that there is effectively no physical memory mapped, so once TZ returns to EL1 it is immediately forced back to EL2. We can then handle that in our exception handler, read the faulting instruction address, and jump to it from EL2. It turns out that overall this actually requires less assembly instructions than the previous approach. :D
- Loading branch information