33.10.1. ARM exception levels

ARM exception levels are analogous to x86 rings.

The current EL can be determined by reading from certain registers, which we do with bit disassembly at:

./run --arch arm --baremetal userland/arch/arm/dump_regs.c
./run --arch aarch64 --baremetal baremetal/arch/aarch64/dump_regs.c

The relevant bits are:

  • arm: CPSR.M

  • aarch64: CurrentEl.EL. This register is not accessible from EL0 for some weird reason however.

Sources:

The lower ELs are not mandated by the architecture, and can be controlled through command line options in QEMU and gem5.

./run --arch arm --baremetal userland/arch/arm/dump_regs.c | grep CPSR.M
./run --arch arm --baremetal userland/arch/arm/dump_regs.c -- -machine virtualization=on | grep CPSR.M
./run --arch arm --baremetal userland/arch/arm/dump_regs.c -- -machine secure=on | grep CPSR.M
./run --arch aarch64 --baremetal baremetal/arch/aarch64/dump_regs.c | grep CurrentEL.EL
./run --arch aarch64 --baremetal baremetal/arch/aarch64/dump_regs.c -- -machine virtualization=on | grep CurrentEL.EL
./run --arch aarch64 --baremetal baremetal/arch/aarch64/dump_regs.c -- -machine secure=on | grep CurrentEL.EL

outputs respectively:

CPSR.M 0x3
CPSR.M 0x3
CPSR.M 0x3
CurrentEL.EL 0x1
CurrentEL.EL 0x2
CurrentEL.EL 0x3

TODO: why is arm CPSR.M stuck at 0x3 which equals Supervisor mode?

In gem5, you can configure the lowest EL with:

./run --arch arm --baremetal userland/arch/arm/dump_regs.c --emulator gem5
grep CPSR.M "$(./getvar --arch arm --emulator gem5 gem5_guest_terminal_file)"
./run --arch arm --baremetal userland/arch/arm/dump_regs.c --emulator gem5 -- --param 'system.have_virtualization = True'
grep CPSR.M "$(./getvar --arch arm --emulator gem5 gem5_guest_terminal_file)"
./run --arch arm --baremetal userland/arch/arm/dump_regs.c --emulator gem5 -- --param 'system.have_security = True'
grep CPSR.M "$(./getvar --arch arm --emulator gem5 gem5_guest_terminal_file)"
./run --arch aarch64 --baremetal baremetal/arch/aarch64/dump_regs.c --emulator gem5
grep CurrentEL.EL "$(./getvar --arch aarch64 --emulator gem5 gem5_guest_terminal_file)"
./run --arch aarch64 --baremetal baremetal/arch/aarch64/dump_regs.c --emulator gem5 -- --param 'system.have_virtualization = True'
grep CurrentEL.EL "$(./getvar --arch aarch64 --emulator gem5 gem5_guest_terminal_file)"
./run --arch aarch64 --baremetal baremetal/arch/aarch64/dump_regs.c --emulator gem5 -- --param 'system.have_security = True'
grep CurrentEL.EL "$(./getvar --arch aarch64 --emulator gem5 gem5_guest_terminal_file)"

output:

CPSR.M 0x3
CPSR.M 0xA
CPSR.M 0x3
CurrentEL.EL 0x1
CurrentEL.EL 0x2
CurrentEL.EL 0x3

TODO: the call:

./run --arch arm --baremetal userland/arch/arm/dump_regs.c --emulator gem5 -- --param 'system.have_virtualization = True'
vmsr fpexc, r0

in baremetal/lib/arm.S. That patch however enables SIMD in baremetal, which I feel is more important.

According to ARMv7 architecture reference manual, access to that register is controlled by other registers NSACR.{CP11, CP10} and HCPTR so those must be turned off, but I’m lazy to investigate now, even just trying to dump those registers in userland/arch/arm/dump_regs.c also leads to exceptions…​