# Switching Exception Levels

## In this learning path

- [Introduction](https://learn.arm.com/learning-paths/embedded-and-microcontrollers/bare-metal/)
- [Create and build a Hello World example project](https://learn.arm.com/learning-paths/embedded-and-microcontrollers/bare-metal/hello/)
- [Write a reset handler](https://learn.arm.com/learning-paths/embedded-and-microcontrollers/bare-metal/reset/)
- [Modify the example to use the UART for printf output](https://learn.arm.com/learning-paths/embedded-and-microcontrollers/bare-metal/retarget/)
- [Create event-driven application (1)](https://learn.arm.com/learning-paths/embedded-and-microcontrollers/bare-metal/event-driven1/)
- [Create event-driven application (2)](https://learn.arm.com/learning-paths/embedded-and-microcontrollers/bare-metal/event-driven2/)
- [Switching Exception Levels](https://learn.arm.com/learning-paths/embedded-and-microcontrollers/bare-metal/exception-levels/)
- [Next Steps](https://learn.arm.com/learning-paths/embedded-and-microcontrollers/bare-metal/_next-steps/)

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.

## Changing exception levels

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()`.

**startup_el1.s**

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

## Modify EL3 initialization

You’ll also need to modify the `EL3` initialization from before:

- Create an `EL3` stack (as `__main` will now be called in `EL1`)
- Disable timer exceptions at `EL3`
- Execute any privileged code (`gicInit`)
- Set `EL1` execution state to be `Aarch64` (before entering `EL1`)
- Switch to `EL1` level

### EL3 stack

Create a new execution region `STACK_EL3` in the scatter file for the EL3 stack.

**scatter.txt**

```assembly
STACK_EL3 0x04020000 EMPTY 0x10000{}
```

The linker will generate a symbol `Image$$STACK_EL3$$ZI$$Limit` which can be referenced in code as follows:

**startup_el3.s**

```assembly
boot:
    // Set EL3 Stack pointer
    ADRP x0, Image$$STACK_EL3$$ZI$$Limit
    MOV  sp, x0
```

### SCR_EL3

In the code to set up [SCR_EL3](https://developer.arm.com/documentation/ddi0595/2021-06/AArch64-Registers/SCR-EL3--Secure-Configuration-Register), 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`.

**startup_el3.s**

```assembly
// 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
```

### Perform other EL3 tasks

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](https://developer.arm.com/documentation/den0024/a/Security), which is beyond the scope of this article.

**startup_el3.s**

```assembly
// 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.

**hello.c**

```c
// gicInit();
```

### Switch to EL1

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:

- Initialize [SCTLR_EL1, System Control Register (EL1)](https://developer.arm.com/documentation/ddi0601/latest/AArch64-Registers/SCTLR-EL1--System-Control-Register--EL1) so that `EL1` is in a known state
- Set [SPSR_EL3, Saved Program Status Register (EL3)](https://developer.arm.com/documentation/ddi0601/latest/AArch64-Registers/SPSR-EL3--Saved-Program-Status-Register--EL3) so that the code will return to `EL1` exception level.
- Set [ELR_EL3, Exception Link Register (EL3)](https://developer.arm.com/documentation/ddi0601/latest/AArch64-Registers/ELR-EL3--Exception-Link-Register--EL3-?lang=en) with the address of the `EL1` entry point.

**startup_el3.s**

```assembly
// 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`.

```assembly
.global  __main
B        __main
```

Save your `startup_el3.s` file.

> **Tip**: The complete source for this file is provided as an [appendix](https://learn.arm.com/learning-paths/embedded-and-microcontrollers/bare-metal/exception-levels/#appendix) below for reference.

## Build and run the example

You are now ready to rebuild the complete example as before:

```bash
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
```

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

```
__output__Hello World!
__output__Waiting for interrupt...
__output__Interrupt 29 occurred
__output__Returned from interrupt!
__output__In _sys_exit. Use Ctrl+C to quit.
```

## Appendix

**startup_el3.s**

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