Exception switch from EL3 to non-secure EL1

The CPU succesfully switches to EL1h, non-secure. But the el1_entry is never called. I don't know why. If using secure el1, the el1_entry gets called. Compiled using clang -c boot.S -o boot.o, and linked with ld. Any help would be appreciated.

global _Entry

_Entry:

  mrs x0, scr_el3

  orr x0, x0, #(1 << 10)

  orr x0, x0, #(1 << 0)

  msr scr_el3, x0

  mov x0, #0b00101

  msr spsr_el3, x0

  adr x1, el1_entry

  msr elr_el3, x1

  eret

el1_entry:

  ldr x15, =0xdeadbeef

  b .

  • Also, the generated image is converted to binary file, and used qemu-system-aarch64 -M virt,secure=on -cpu cortex-a55 -m 128M -bios boot.bin

  • I can think of some possible answers it's worth checking:

    • Does the memory allow Non-secure accesses?

      • It looks like you have el1_entry in the same page as the EL3 code.  Unless you've set up the EL3 (and S_EL1) MMU to generate Non-secure accesses, the default will be to produce Secure accesses.  Depending on the memory system and the device, typically a memory would either Secure or Non-secure, but not both.  If it's something like the Arm FVP model, then you might have a switch to disable the memory system level checking of memory access security.  But be aware, strictly NS and S are different address spaces -  and allowing a given memory location to appear in both can lead to interesting cache coherency problems.
    • Have you initialised the _EL2 and _EL1 to safe values?
      • Most Arm system registers reset to an unknown value - which is safe because they don't effect execution immediately after reset.  Software is expected to initialise those registers before moving to lower ELs, where the registers do affect execution.
      • If the code works for moving to S_EL1, but not NS_EL1, then my starting point would be the EL2 registers (as the A55 doesn't have S_EL2).  But you should initialize the EL1 regs too.
  • No, im using qemu to emulate the cortex-a55. On reset, only EL3 registers are initialized to a know value. The MMU and cache is disabled by default. So secure accesses are allowed, the whole ram region is not MMU mapped and no protection rules are set. I think the secure access is denied if the MMU is enabled for the current exception level. EL2 is disabled by default on qemu unless i specify virtualization flag. Maybe i should check the ESR_EL1? Thanks

  • So secure accesses are allowed, the whole ram region is not MMU mapped and no protection rules are set. I think the secure access is denied if the MMU is enabled for the current exception level.

    That's not quite right. The Cortex-A55 supports two Physical Address Spaces (PAS); Secure and Non-secure.  All accesses by the core will resolve to a physical address in one of those two PASs.  In Non-secure state, the output PAS is always Non-secure (doesn't matter if the MMU is on or off).  In Secure state, when the MMU is disabled the output PAS is Secure, when the MMU is enabled the output PAS is set via the translation tables.

    On real hardware, it's important that the output PAS from the core matches whatever the thing in the memory is.  Typically you'd get a bus level fault if you tried to access a memory with the wrong PAS (as strictly, you asked for an address that doesn't exist).

    EL2 is disabled by default on qemu unless i specify virtualization flag.

    I'm not familiar with qemu... when you say it's disabled - what does that mean?

    On HW, the EL2 registers would still be there.  Qemu might have set them to a known value, but they might not be the ones you want.  For example, HCR_EL2.RW controls whether NS_EL1 uses AArch32 or AArch64.  You'd need to make sure that matches what you're putting into SPSR_EL3 before doing the ERET into EL1.

    Maybe i should check the ESR_EL1?

    The ESR is a good thing to check.  But also look at which EL you're in.  As in, did the ERET itself fail (illegal exception return), meaning you are still in EL3 and need to check ESR_EL3.  Or, did the ERET succeed, and something went wrong in EL1. 

  • The code started working if i use an ELF file as parameter to the qemu -kernel option. Using binary version, the code doesn't works. I'm not a pro at this, so Any help about what i need to initialize when the CPU is brought out of reset would be fine.