So far, everything has been at the most privileged EL3
exception level, which the processor starts in at reset.
In general, you would want your application code to run at a lower level so that it cannot corrupt system settings, maliciously or otherwise.
Switching between levels is performed by the ERET
exception return instruction.
Extend the example to make use of EL1
.
Create startup_el1.s
as below. The el1_entry
function will be the EL1
entry point, and this function will call our application code starting from __main()
.
.section EL1_ENTRY,"ax"
.global el1_entry
.type el1_entry, "function"
el1_entry:
LDR x0, =vectors
MSR VBAR_EL1, x0 // Set EL1 Vector Table
MOV x0, #(0x3 << 20)
MSR CPACR_EL1, x0 // Disable instruction traps for EL1
ISB
.global __main
B __main
You will also need to modify the EL3
initialization from before:
EL3
stack (as __main
will now be called in EL1
)EL3
gicInit
)EL1
execution state to be Aarch64
(before entering EL1
)EL1
levelCreate a new execution region STACK_EL3
in the scatter file for the EL3 stack.
STACK_EL3 0x04020000 EMPTY 0x10000{}
The linker will generate a symbol Image$$STACK_EL3$$ZI$$Limit
which can be referenced in code as follows:
boot:
// Set EL3 Stack pointer
ADRP x0, Image$$STACK_EL3$$ZI$$Limit
MOV sp, x0
In the code to set up
SCR_EL3
, set the ST
bit to disable timer exceptions, and the RW
bit so that EL1
executes in Aarch64
state.
Also, remove the code to set the FIQ
bit, as you now want to trap this exception in EL1
.
// Configure SCR_EL3
MOV w1, #0
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)
MSR SCR_EL3, x1
The code to initialize the GIC
must be executed in EL3
. Call gicInit()
function before leaving EL3
. This function sets the NS
bit in SCR_EL3
. Clear it here to avoid issues with
security settings
, which is beyond the scope of this article.
// Initialize GIC
BL gicInit
MRS x1, SCR_EL3
BIC x1, x1, #1 // Clear NS bit
MSR SCR_EL3, x1
Remove the call to gicInit()
from your main()
function.
// gicInit();
Rather than branching to __main
, the EL3
reset handler must instead perform an exception return (ERET
) to EL1
. Set this up by configuring the appropriate registers:
EL1
is in a known stateEL1
exception level.EL1
entry point.
// Switch to EL1
MSR SCTLR_EL1, xzr // Initialize state of EL1
MOV x1, #0x5
MSR SPSR_EL3,x1 // Set return level to EL1
LDR x0, =el1_entry
MSR ELR_EL3, x0 // Set return address to EL1 entry point
ISB // Ensure all above fully executes before...
ERET // Returning to EL1
Delete the call to __main()
in EL3
.
.global __main
B __main
Save your startup_el3.s
file.
The complete source for this file is provided as an appendix below for reference.
You are now ready to rebuild the complete example as before:
armclang -c -g --target=aarch64-arm-none-eabi startup_el3.s
armclang -c -g --target=aarch64-arm-none-eabi startup_el1.s
armclang -c -g --target=aarch64-arm-none-eabi uart.c
armclang -c -g --target=aarch64-arm-none-eabi vectors.s
armclang -c -g --target=aarch64-arm-none-eabi gic.s
armclang -c -g --target=aarch64-arm-none-eabi timer.s
armclang -c -g --target=aarch64-arm-none-eabi hello.c
armlink --scatter=scatter.txt --entry=el3_entry startup_el3.o startup_el1.o uart.o vectors.o gic.o timer.o hello.o -o hello.axf
FVP_Base_AEMvA -C bp.refcounter.non_arch_start_at_default=1 -a hello.axf
The output is the same as before, though the exception level the code executes in is different.
Hello World!
Waiting for interrupt...
Interrupt 29 occurred
Returned from interrupt!
In _sys_exit. Use Ctrl+C to quit.
.section BOOT,"ax" // Define an executable ELF section, BOOT
.global el3_entry
.type el3_entry, "function"
el3_entry:
MRS x0, MPIDR_EL1 // Read Affinity register
AND x0, x0, #0xFFFF // Mask off Aff0/Aff1 fields
CBZ x0, boot // Branch to boot if Aff0/Aff1 are zero (Core 0 of Cluster 0)
sleep: // Else put processor to sleep
WFI
B sleep
boot:
// Set EL3 Stack pointer
ADRP x0, Image$$STACK_EL3$$ZI$$Limit
MOV sp, x0
// Clear all trap bits
MSR CPTR_EL3, xzr
// Configure SCR_EL3
MOV w1, #0
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)
MSR SCR_EL3, x1
// Install vector table
.global vectors
LDR x0, =vectors
MSR VBAR_EL3, x0
ISB
// Clear interrupt masks
MSR DAIFClr, #0xF
// Initialize GIC
BL gicInit
MRS x1, SCR_EL3
BIC x1, x1, #1 // Clear NS bit
MSR SCR_EL3, x1
// Switch to EL1
MSR SCTLR_EL1, xzr // Initialize state of EL1
MOV x1, #0x5
MSR SPSR_EL3,x1 // Set return level to EL1
LDR x0, =el1_entry
MSR ELR_EL3, x0 // Set return address to EL1 entry point
ISB // Ensure all above fully executes before...
ERET // Returning to EL1