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
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.
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