In `AArch64`, there are four so-called exception levels:
| Exception Level | Typically used for |
| ------------- | ------------- |
| EL0 | Userspace applications |
| EL1 | OS Kernel |
| EL2 | Hypervisor |
| EL3 | Low-Level Firmware |
If you are familiar with the `x86` architecture, `ELs` are the counterpart to [privilege rings](https://en.wikipedia.org/wiki/Protection_ring).
At this point, I strongly recommend that you glimpse over `Chapter 3` of the [Programmer’s Guide for ARMv8-A](http://infocenter.arm.com/help/topic/com.arm.doc.den0024a/DEN0024A_v8_architecture_PG.pdf) before you continue.
It gives a concise overview about the topic.
## Scope of this tutorial
If you set up your SD Card exactly like mentioned in the repository's [top-level README](../README.md#prerequisites),
our binary will start executing in `EL2`. Since we have an OS-focus, we will now write code that will cause a transition
into the more appropriate `EL1`.
## Checking for EL2 in the entrypoint
First of all, we need to ensure that we actually run in `EL2` before we can call respective code to transition to EL1:
```rust
/// Entrypoint of the processor.
///
/// Parks all cores except core0 and checks if we started in EL2. If
Next, we configure the [Hypervisor Configuration Register](https://docs.rs/cortex-a/2.4.0/src/cortex_a/regs/hcr_el2.rs.html) such that `EL1` should actually run in `AArch64` mode, and not in `AArch32`, which would also be possible.
```rust
// Set EL1 execution state to AArch64
HCR_EL2.write(HCR_EL2::RW::EL1IsAarch64;
```
## Returning from an exception that never happened
There is actually only one way to transition from a higher EL to a lower EL, which is by way of executing
the [ERET](https://docs.rs/cortex-a/2.4.0/src/cortex_a/asm.rs.html#49-62) instruction.
This instruction will copy the contents of the [Saved Program Status Register - EL2](https://docs.rs/cortex-a/2.4.0/src/cortex_a/regs/spsr_el2.rs.html)
to `Current Program Status Register - EL1` and jump to the instruction address that is stored in the [Exception Link Register - EL2](https://docs.rs/cortex-a/2.4.0/src/cortex_a/regs/elr_el2.rs.html).
This is basically the reverse of what is happening when an exception is taken. You'll learn about it in tutorial [10_exception_groundwork](../10_exceptions_groundwork).
```rust
// Set up a simulated exception return.
//
// First, fake a saved program status, where all interrupts were
// masked and SP_EL1 was used as a stack pointer.
SPSR_EL2.write(
SPSR_EL2::D::Masked
+ SPSR_EL2::A::Masked
+ SPSR_EL2::I::Masked
+ SPSR_EL2::F::Masked
+ SPSR_EL2::M::EL1h,
);
// Second, let the link register point to reset().
ELR_EL2.set(reset as *const () as u64);
```
As you can see, we are populating `ELR_EL2` with the address of the [reset()](raspi3_boot/src/lib.rs#L51) function that we earlier used to call directly from the entrypoint.
Finally, we set the stack pointer for `SP_EL1` and call `ERET`:
```rust
// Set up SP_EL1 (stack pointer), which will be used by EL1 once
// we "return" to it.
SP_EL1.set(STACK_START);
// Use `eret` to "return" to EL1. This will result in execution of
// `reset()` in EL1.
asm::eret()
```
## Are we stackless?
We just wrote a big rust function, `setup_and_enter_el1_from_el2()`, that is executed in a context where we
do not have a stack yet. We should double-check the generated machine code:
Looks good! Thanks zero-overhead abstractions in the [cortex-a](https://github.com/rust-embedded/cortex-a) crate! :heart_eyes:
## Testing
In `main.rs`, we added some tests to see if access to the counter timer registers is actually working, and if the mask bits in `SPSR_EL2` made it to `EL1` as well:
```console
ferris@box:~$ make raspboot
[0] UART is live!
[1] Press a key to continue booting... Greetings fellow Rustacean!