assemblyarmboottrustzone

arm cortex-a53 switch from el3 secure to el1 non-secure problem


I am trying to switch from EL3 secure state to EL1 non-secure state.If I don't change the security state, and only perform a switch from EL3 to EL1, like this:

el1_entry_aarch64:
    NOP
    NOP
    NOP

    // we can use the same vector table in this example, but in general
    // each combination of Exception level, Security state, and Execution state
    // will need a new vector table
    LDR      x0, =vectors
    MSR      VBAR_EL1, x0

    //we must ensure that floating point register accesses are not trapped
    //since the c library for AArch64-v8A uses them
    MOV      x0, #(0x3 << 20)
    MSR      CPACR_EL1, x0

    B        __main

secure_to_ns:
    MOV      w1, #0              // Initial value of register is unknown
    ORR      w1, w1, #(1 << 11)  // set ST bit (disable trapping of timer control registers)
    ORR      w1, w1, #(1 << 10)  // set RW bit (next lower EL in aarch64)
    ORR      w1, w1, #(1 << 3)   // Set EA bit (SError routed to EL3)
    ORR      w1, w1, #(1 << 2)   // Set FIQ bit (FIQs routed to EL3)
    ORR      w1, w1, #(1 << 1)   // Set IRQ bit (IRQs routed to EL3)
    MSR      SCR_EL3, x1

    MSR      SCTLR_EL1, xzr

    LDR      x0, =el1_entry_aarch64
    MOV      x1, #0x5
    MSR      ELR_EL3, x0 // where to branch to when exception completes
    MSR      SPSR_EL3, x1 // set the program state for this point to a known value

    ERET

After executing ERET, the PC will jump to el1_entry_aarch64, and from there it will proceed to the main function. Apart from this, there are also some basic stack initialization operations which I have omitted here.

But if I add the assembly code to switch the security state, when executing in el1_entry_aarch64, the program enters an exception. Changes of secure_to_ns are as follows:

secure_to_ns:
    MOV      w1, #0              // Initial value of register is unknown
    ORR      w1, w1, #(1 << 11)  // set ST bit (disable trapping of timer control registers)
    ORR      w1, w1, #(1 << 10)  // set RW bit (next lower EL in aarch64)
    ORR      w1, w1, #(1 << 3)   // Set EA bit (SError routed to EL3)
    ORR      w1, w1, #(1 << 2)   // Set FIQ bit (FIQs routed to EL3)
    ORR      w1, w1, #(1 << 1)   // Set IRQ bit (IRQs routed to EL3)
    ORR      w1, w1, #1        **// Set NS bit (lower EL in non Secure state)**

    MSR      SCR_EL3, x1

    MSR      SCTLR_EL1, xzr

    LDR      x0, =el1_entry_aarch64
    MOV      x1, #0x5
    MSR      ELR_EL3, x0 // where to branch to when exception completes
    MSR      SPSR_EL3, x1 // set the program state for this point to a known value

    ERET

Code demo code demo

When executing the NOP instruction within the el1_entry_aarch64 function, the program enters an exception.

My program is running within the memory range 0xF8040000 to 0xF80AFFFF, which is a 448KB memory space. Through certain configurations, I have set the range 0xF8090000 to 0xF80B0000 as a non-secure access region.

At the same time, I have placed the relevant assembly code in the non-secure region, which means that all code executed after the eret instruction is within the non-secure area.

At first, I did not include the NOP assembly instruction, but still encountered the issue. I initially thought it might be related to jumps between different segments, so I added the NOP instruction. Now it appears that the issue is not related to the jump itself.


Solution

  • I tried "Johannes Krottmayer" solution and found the key issue.
    ARM allows direct switching from EL3 to EL1, or it can switch from EL3 to EL2 and then to EL1. This is mainly controlled by the SPSR register.
    The reason is that before EL3 switches to EL1, it needs to determine whether EL1 is in AArch32 or AArch64 mode. This requires modifying the HCR_EL2 register to control this.
    Therefore, the key lies in the configuration of HCR_EL2 in el2_setup_el1. This part was missing in my original code, which caused issues after the jump. Adding the following code snippet to secure_to_ns should solve the problem:

    secure_to_ns:
        mrs x0, HCR_EL2
        orr x0, x0, #(1 << 31) /* EL1 is AArch64 */
        msr HCR_EL2, x0
        MOV      w1, #0              // Initial value of register is unknown
        ORR      w1, w1, #(1 << 11)  // set ST bit (disable trapping of timer control registers)
        ORR      w1, w1, #(1 << 10)  // set RW bit (next lower EL in aarch64)
        ORR      w1, w1, #(1 << 3)   // Set EA bit (SError routed to EL3)
        ORR      w1, w1, #(1 << 2)   // Set FIQ bit (FIQs routed to EL3)
        ORR      w1, w1, #(1 << 1)   // Set IRQ bit (IRQs routed to EL3)
        ORR      w1, w1, #1        **// Set NS bit (lower EL in non Secure state)**
    
        MSR      SCR_EL3, x1
    
        MSR      SCTLR_EL1, xzr
    
        LDR      x0, =el1_entry_aarch64
        MOV      x1, #0x5
        MSR      ELR_EL3, x0 // where to branch to when exception completes
        MSR      SPSR_EL3, x1 // set the program state for this point to a known value
    
        ERET