ARMv8 Exception Level and Execution State Transition Challenges
The ARMv8 architecture introduces a sophisticated exception level (EL) model and execution state (AArch32 and AArch64) framework, which provides flexibility but also complexity when transitioning between different privilege levels and execution states. A common scenario in baremetal systems involves starting execution in AArch64 at EL3 during reset and then dropping down to AArch32 at EL0 for application execution. However, transitioning back from AArch32 at EL0 to AArch64 at EL1 or higher presents several architectural and implementation challenges.
The ARMv8 architecture defines four exception levels: EL0 (user mode), EL1 (OS kernel mode), EL2 (hypervisor mode), and EL3 (secure monitor mode). Each exception level can operate in either AArch32 or AArch64 execution state, depending on the system configuration and the state of specific control registers. The execution state determines the instruction set (32-bit or 64-bit) and the register width. Transitioning between exception levels and execution states requires careful handling of the architectural state, including the program counter, stack pointers, and system registers.
In the context of baremetal systems, the boot code typically initializes the core in AArch64 state at EL3 and then drops down to AArch32 at EL0 to execute the bootable application. However, the bootable application may need to transition back to AArch64 at EL1 or higher to perform more privileged operations. This transition is not straightforward due to the architectural constraints and the need to preserve the system state across the transition.
Architectural Constraints and Bootloader Configuration
The primary architectural constraints when transitioning from AArch32 at EL0 to AArch64 at EL1 or higher involve the handling of the execution state and the exception level. The ARMv8 architecture does not provide a direct mechanism to transition from AArch32 at EL0 to AArch64 at EL1. Instead, the transition must be mediated through an exception or a system call that changes both the execution state and the exception level.
The bootloader plays a critical role in configuring the system for such transitions. The bootloader must install the appropriate exception handlers in EL3, EL2, and EL1 to handle the transitions between exception levels and execution states. Specifically, the bootloader must configure the exception vectors and the system control registers to ensure that the transitions are handled correctly.
The SVC (Supervisor Call) instruction in AArch32 can be used to generate an exception that transitions from EL0 to EL1. However, this transition alone does not change the execution state from AArch32 to AArch64. To achieve the execution state change, the exception handler in EL1 must explicitly switch the execution state by modifying the appropriate system control registers, such as the PSTATE (Process State) register and the SCTLR (System Control Register).
The HVC (Hypervisor Call) instruction in AArch64 can be used to transition from EL1 to EL2, and the SMC (Secure Monitor Call) instruction can be used to transition from EL1 or EL2 to EL3. These instructions are typically used in conjunction with the appropriate exception handlers to manage the transitions between exception levels and execution states.
Implementing Exception Handlers and State Transitions
To implement the transition from AArch32 at EL0 to AArch64 at EL1, the bootloader must configure the exception vectors and the system control registers to handle the SVC exception and the execution state change. The following steps outline the process:
-
Configure Exception Vectors: The bootloader must set up the exception vectors in EL1 to handle the SVC exception. The exception vectors must be configured to point to the appropriate exception handler that will perform the execution state change.
-
Install SVC Handler: The SVC handler in EL1 must be implemented to handle the transition from AArch32 at EL0 to AArch64 at EL1. The handler must save the AArch32 state, switch the execution state to AArch64, and then restore the AArch64 state.
-
Modify System Control Registers: The SVC handler must modify the system control registers to switch the execution state from AArch32 to AArch64. This involves setting the appropriate bits in the PSTATE register and the SCTLR register to enable AArch64 execution.
-
Preserve Architectural State: The SVC handler must preserve the architectural state during the transition. This includes saving the AArch32 general-purpose registers, stack pointers, and program counter, and then restoring the corresponding AArch64 state.
-
Return to AArch64 Execution: After switching the execution state to AArch64, the SVC handler must return to the AArch64 execution context. This involves setting the program counter to the appropriate address in the AArch64 address space and resuming execution in AArch64 mode.
The following table summarizes the key steps and the corresponding system registers that need to be modified during the transition:
Step | Description | System Registers |
---|---|---|
1 | Configure Exception Vectors | VBAR_EL1 (Vector Base Address Register) |
2 | Install SVC Handler | ELR_EL1 (Exception Link Register), SPSR_EL1 (Saved Program Status Register) |
3 | Modify System Control Registers | PSTATE, SCTLR_EL1 (System Control Register) |
4 | Preserve Architectural State | General-purpose registers (R0-R12), Stack pointers (SP_EL0, SP_EL1) |
5 | Return to AArch64 Execution | ELR_EL1, SPSR_EL1 |
The implementation of the SVC handler must be carefully crafted to ensure that the transition is seamless and that the architectural state is preserved. The handler must also handle any potential exceptions or errors that may occur during the transition.
In addition to the SVC handler, the bootloader must also configure the exception handlers for HVC and SMC instructions to handle transitions between EL1, EL2, and EL3. These handlers must be implemented to handle the execution state changes and to preserve the architectural state during the transitions.
The following code snippet provides an example of how the SVC handler might be implemented to transition from AArch32 at EL0 to AArch64 at EL1:
// SVC Handler in EL1
svc_handler:
// Save AArch32 state
STP X0, X1, [SP, #-16]!
STP X2, X3, [SP, #-16]!
// Switch to AArch64 execution state
MRS X0, SCTLR_EL1
ORR X0, X0, #(1 << 0) // Set SCTLR_EL1.M bit to enable AArch64
MSR SCTLR_EL1, X0
// Restore AArch64 state
LDP X2, X3, [SP], #16
LDP X0, X1, [SP], #16
// Return to AArch64 execution
ERET
This code snippet demonstrates the basic steps involved in the SVC handler. The handler saves the AArch32 state, switches the execution state to AArch64, and then restores the AArch64 state before returning to AArch64 execution.
In conclusion, transitioning from AArch32 at EL0 to AArch64 at EL1 in ARMv8 baremetal systems requires careful handling of the architectural state and the system control registers. The bootloader must configure the exception vectors and install the appropriate exception handlers to manage the transitions between exception levels and execution states. By following the outlined steps and implementing the necessary exception handlers, it is possible to achieve a seamless transition from AArch32 at EL0 to AArch64 at EL1.