Cortex-A53 EL2 to EL1 Transition Failure and System Hang
The Cortex-A53 processor, part of ARM’s Cortex-A series, is a widely used 64-bit ARMv8-A core that supports multiple exception levels (ELs). These exception levels provide a hierarchical privilege model, with EL3 being the most privileged (secure monitor), followed by EL2 (hypervisor), EL1 (operating system), and EL0 (user applications). Transitioning between these exception levels is a critical operation during system initialization, particularly when moving from EL2 to EL1, which is a common requirement for bootloaders and hypervisors.
The issue at hand involves a failure to successfully transition from EL2 to EL1 on a Cortex-A53 processor. The system hangs after executing the eret
instruction, which is intended to return execution to EL1. This behavior suggests that the processor is either encountering an unexpected state during the transition or is unable to properly handle the exception return. The problem is exacerbated by the lack of visible exceptions or debug output, making it challenging to diagnose the root cause.
Key symptoms of the issue include:
- The system hangs after executing the
eret
instruction in EL2. - No exceptions are raised, and the vector table handlers for EL2 are not invoked.
- Execution in EL1 is slower than in EL2, and certain C functions fail to execute correctly.
- The UART output, which works in EL2, ceases to function after transitioning to EL1.
These symptoms indicate that the transition to EL1 is not being handled correctly, potentially due to misconfigured system registers, improper stack setup, or issues with the state of the processor during the transition.
Misconfigured System Registers and Cache State
The root cause of the EL2 to EL1 transition failure can be traced to several potential issues related to system register configuration and cache management. The Cortex-A53 processor relies on a set of system registers to manage exception levels, interrupts, and memory attributes. Misconfiguration of these registers can lead to undefined behavior, including system hangs or incorrect execution states.
HCR_EL2 and SCTLR_EL1 Configuration
The Hypervisor Configuration Register (HCR_EL2) and the System Control Register (SCTLR_EL1) are critical for managing the transition between EL2 and EL1. HCR_EL2 controls the behavior of the hypervisor, including whether certain operations are trapped to EL2 or allowed to execute in EL1. SCTLR_EL1, on the other hand, controls the behavior of the system in EL1, including memory management, caching, and alignment checks.
In the provided code, the following configuration is applied to HCR_EL2:
mov x0, xzr
orr x0, x0, #(1 << 31)
msr HCR_EL2, x0
This sets bit 31 of HCR_EL2, which enables the Virtualization Host Extensions (VHE). While this is a valid configuration, it may not be necessary for all use cases, particularly if the system does not require virtualization support. Additionally, the configuration of SCTLR_EL1 is incomplete, as it does not enable critical features such as the MMU, instruction cache, or data cache. This can lead to performance degradation and unexpected behavior in EL1.
Cache Coherency and State Management
The Cortex-A53 processor features separate instruction and data caches, which must be properly managed during exception level transitions. Failure to invalidate or clean the caches can result in stale data being used, leading to incorrect execution or system hangs. The state of the caches is particularly important when transitioning from EL2 to EL1, as the processor may rely on cached data for critical operations.
In the provided code, there is no explicit cache management performed before or after the transition to EL1. This can result in the processor using stale or invalid cache entries, which may cause the system to hang or behave unpredictably. Proper cache management involves invalidating the instruction cache and cleaning the data cache to ensure that the processor has access to the most up-to-date data.
Stack Pointer and Exception Handling
The stack pointer (SP) is another critical component that must be correctly configured during exception level transitions. Each exception level has its own stack pointer, and failure to set the correct stack pointer can result in stack corruption or incorrect exception handling. In the provided code, the stack pointer for EL1 is set as follows:
__el1_stack:
ldr x0, =_stack_el1_e
mov sp, x0
While this sets the stack pointer to the correct address, there is no guarantee that the stack is properly initialized or that it has sufficient space for the execution environment. Additionally, the stack pointer for EL2 is set using the SPsel
register, which selects between the EL2 stack pointer and the shared stack pointer. This configuration may not be necessary for all use cases and can lead to confusion if not properly documented.
Implementing Proper Exception Level Transition and Cache Management
To resolve the EL2 to EL1 transition issue, a comprehensive approach is required, involving proper configuration of system registers, cache management, and stack initialization. The following steps outline the necessary actions to ensure a successful transition:
Step 1: Configure HCR_EL2 and SCTLR_EL1
The first step is to properly configure HCR_EL2 and SCTLR_EL1 to ensure that the processor is in the correct state for the transition. The following configuration is recommended for HCR_EL2:
mov x0, xzr
orr x0, x0, #(1 << 31) // Enable VHE if required
msr HCR_EL2, x0
For SCTLR_EL1, the following configuration is recommended:
mov x0, xzr
orr x0, x0, #(1 << 0) // Enable MMU
orr x0, x0, #(1 << 2) // Enable caches
orr x0, x0, #(1 << 12) // Enable instruction cache
msr SCTLR_EL1, x0
This configuration enables the MMU, instruction cache, and data cache, ensuring that the processor can efficiently execute code in EL1.
Step 2: Invalidate and Clean Caches
Before transitioning to EL1, it is essential to invalidate the instruction cache and clean the data cache. This ensures that the processor does not use stale data and that all cached data is written back to memory. The following code demonstrates how to perform these operations:
// Invalidate instruction cache
ic ialluis
dsb sy
// Clean data cache
dc cisw, x0
dsb sy
These instructions invalidate the entire instruction cache and clean the data cache, ensuring that the processor has access to the most up-to-date data.
Step 3: Set Up Stack Pointers and Exception Handling
Proper stack initialization is critical for successful exception handling and execution in EL1. The following code demonstrates how to set up the stack pointers for EL2 and EL1:
// Set up EL2 stack pointer
__el2_stack:
ldr x0, =_stack_el2_e
mov sp, x0
// Set up EL1 stack pointer
__el1_stack:
ldr x0, =_stack_el1_e
mov sp, x0
Additionally, ensure that the vector table for EL1 is properly set up and that exception handlers are correctly implemented. This includes setting the Vector Base Address Register (VBAR_EL1) to the address of the EL1 vector table:
ldr x0, =_el1_vector_table
msr VBAR_EL1, x0
Step 4: Perform the Exception Level Transition
Finally, perform the transition from EL2 to EL1 using the eret
instruction. Ensure that the Exception Link Register (ELR_EL2) and Saved Program Status Register (SPSR_EL2) are correctly set up before executing the eret
instruction:
// Set up ELR_EL2 and SPSR_EL2
adr x0, __el1
msr ELR_EL2, x0
mov x1, xzr
orr x1, x1, #(3 << 6) // Mask IRQ/FIQ
orr x1, x1, #(1 << 2) // Set EL1
orr x1, x1, #(1 << 0) // Set SPsel
msr SPSR_EL2, x1
// Perform the transition
eret
This configuration ensures that the processor transitions to EL1 with the correct execution state and that interrupts are masked during the transition.
Step 5: Verify Execution in EL1
After transitioning to EL1, verify that the processor is executing correctly by checking the Current Exception Level (CurrentEL) register and ensuring that critical functions, such as UART output, are working as expected. The following code demonstrates how to check the current exception level:
mrs x0, CurrentEL
and x0, x0, #0xC
cmp x0, #1
beq el1_execution
If the processor is not in EL1, revisit the configuration steps to identify any misconfigurations or issues.
Conclusion
Transitioning from EL2 to EL1 on a Cortex-A53 processor requires careful configuration of system registers, proper cache management, and correct stack initialization. By following the steps outlined above, you can ensure a successful transition and avoid common pitfalls such as system hangs or incorrect execution states. Proper debugging tools, such as a JTAG debugger, can also be invaluable for diagnosing and resolving issues during the transition process.