EL2 to EL1 Transition Failure with Stack Usage in Bare-Metal Code

The core issue revolves around a failure in transitioning from Exception Level 2 (EL2) to Exception Level 1 (EL1) on an ARM Cortex-A53 processor, specifically on a Raspberry Pi 3B. The EL2 code successfully initializes the UART and prints a message, but when it attempts to switch to EL1, the EL1 code fails to execute correctly if it uses the stack. If the stack is not used, the EL1 code prints the message as expected. This behavior suggests a problem with the stack pointer (SP) setup, memory corruption, or cache coherency issues during the transition.

The EL2 code sets up the necessary registers to enable AArch64 execution in EL1, configures the stack pointer for EL1, and then executes an ERET instruction to transition to EL1. However, the EL1 code fails to execute properly when it attempts to use the stack, indicating that the stack pointer or memory configuration might be incorrect. Additionally, there are indications that the memory contents viewed from EL1 differ from those viewed from EL2, suggesting potential issues with memory mappings, cache coherency, or MMU settings.

Incorrect SPSR_EL2 Configuration and Potential MMU/Cache Issues

The primary cause of the issue lies in the configuration of the SPSR_EL2 register and the potential misconfiguration of the Memory Management Unit (MMU) or cache settings. The SPSR_EL2 register is used to set the processor state when transitioning from EL2 to EL1. In the provided code, the SPSR_EL2.M field is initially set to 0x4, which corresponds to EL1t (EL1 with SP_EL0 selected). However, the stack pointer is set to SP_EL1, leading to a mismatch between the stack pointer configuration and the exception level state.

Furthermore, the memory contents appear to differ when viewed from EL2 and EL1, suggesting that the MMU might be enabled at EL2, causing virtual-to-physical address translation issues. If the MMU is enabled at EL2, the virtual address used to load the EL1 code might not map to the expected physical address, leading to memory corruption or incorrect execution when transitioning to EL1. Additionally, cache coherency issues could arise if the data written at EL2 is not properly flushed to memory before execution at EL1.

Another potential cause is the lack of proper initialization of EL1 control registers, particularly SCTLR_EL1. In AArch64, only the system registers at the highest implemented exception level (EL3) have known reset values. All other registers, including those at EL1, contain unknown values and must be initialized by software before entering that exception level. If SCTLR_EL1 is not properly initialized, the MMU or caches might be enabled with unexpected configurations, leading to memory access issues.

Properly Configuring SPSR_EL2, MMU, and Cache Coherency

To resolve the issue, the following steps should be taken to ensure proper configuration of the SPSR_EL2 register, MMU, and cache coherency:

  1. Correct SPSR_EL2 Configuration: The SPSR_EL2.M field should be set to 0x5, which corresponds to EL1h (EL1 with SP_EL1 selected). This ensures that the stack pointer configured for EL1 is correctly used when transitioning to EL1. The modified code should look like this:

    mov x2, #0x3c5  // Set SPSR_EL2.M to EL1h
    msr spsr_el2, x2
    
  2. Verify MMU Configuration: Check the value of SCTLR_EL2.M to determine if the MMU is enabled at EL2. If the MMU is enabled, ensure that the virtual address used to load the EL1 code maps to the correct physical address. If necessary, disable the MMU at EL2 before transitioning to EL1:

    mrs x0, sctlr_el2
    bic x0, x0, #(1 << 0)  // Disable MMU
    msr sctlr_el2, x0
    
  3. Initialize EL1 Control Registers: Before transitioning to EL1, ensure that the EL1 control registers, particularly SCTLR_EL1, are properly initialized. This includes disabling the MMU and caches at EL1 to avoid unexpected memory mappings or cache behavior:

    mov x0, #0
    msr sctlr_el1, x0  // Disable MMU and caches at EL1
    
  4. Ensure Cache Coherency: If the EL2 code writes data to memory that will be executed as instructions at EL1, ensure that the data cache is cleaned and the instruction cache is invalidated before transitioning to EL1. This ensures that the data written at EL2 is visible to the instruction fetch at EL1:

    dc cvac, x0  // Clean data cache
    ic ivau, x0  // Invalidate instruction cache
    
  5. Debugging with OpenOCD and JTAG: Use OpenOCD and JTAG to debug the issue further. Set breakpoints at key locations in the EL2 and EL1 code to inspect the memory contents and register values before and after the transition. Verify that the memory contents are consistent between EL2 and EL1 and that the stack pointer is correctly configured.

By following these steps, the issue with the EL2 to EL1 transition and stack usage should be resolved. Proper configuration of the SPSR_EL2 register, MMU, and cache coherency ensures that the EL1 code executes correctly and that the stack is properly initialized and used.

Summary of Key Fixes

Issue Solution
Incorrect SPSR_EL2.M configuration Set SPSR_EL2.M to EL1h (0x5) to use SP_EL1 in EL1.
MMU enabled at EL2 Disable MMU at EL2 or ensure correct virtual-to-physical address mapping.
Uninitialized EL1 control registers Initialize SCTLR_EL1 to disable MMU and caches at EL1.
Cache coherency issues Clean data cache and invalidate instruction cache before transitioning to EL1.

By addressing these issues, the transition from EL2 to EL1 should proceed smoothly, and the EL1 code should execute correctly, even when using the stack.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *