Search code examples
assemblyarmarm64

Reading ICC_IAR1_EL1 register causes several exceptions?


I'm working on a bare-metal interrupt controller. The underlying architecture is Virt, with QEMU, and a CPU Arm Cortex-72, aarch64. As an example, I provide you an excerpt of the Makefile:

run:
    $(MAKE) kernel.elf
    qemu-system-aarch64 -machine virt -cpu cortex-a72 -nographic -kernel kernel.elf

I defined an exception handler that is called everytime an exception occurs. This is an excerpt (output is written on a UART):

void common_trap_handler(exception_frame *exc)
{ 
    uart_puts("\nException Handler! (");
    uint32_t val = raw_read_current_el();
    uart_puts("\n\tCurrent EL = ");
    uart_puthex(val);
    uart_puts("\t");

    uart_puts("exc_type : ");
    uart_puthex(exc->exc_type);
    
    val = raw_read_icc_iar1_el1();
    uart_puts("\n\tICC IAR1 EL1 = ");
    uart_puthex(val); 
    // rest of the exception handler
}

Accesses to registers are done through assembly code. Reading the current exception level works:

uint32_t raw_read_current_el(void)
{
    uint32_t current_el;
    __asm__ __volatile__("mrs %0, CurrentEL\n\t" : "=r" (current_el) :  : "memory");
    return current_el;
}

Reading ICC_IAR1_EL1 register does not (more details below):

uint32_t raw_read_icc_iar1_el1(void)
{
    uint32_t icc_iar1_el1 = 0;
    __asm__ __volatile__("mrs %0, s3_0_c12_c12_0\n\t" : "=r" (icc_iar1_el1) :  : "memory");
    return icc_iar1_el1; 
}

Where s3_0_c12_c12_0 is the ID of ICC_IAR1_EL1 according to arm specifications, since it is defined as a "register without an architectural name".

Getting access to ICC_IAR1_EL1 triggers a limitless number of other exceptions. This is an excerpt of the output (repeated several times):

Exception Handler! (
    Current EL = 0x00000000 00000004    exc_type : 0x00000000 00000011
Exception Handler! (
    Current EL = 0x00000000 00000004    exc_type : 0x00000000 00000011
Exception Handler! (
    Current EL = 0x00000000 00000004    exc_type : 0x00000000 00000011

I can't understand why. According to the name of the register ICC_IAR1_EL1, this register should be accessed with a minimum privilege of EL1. This is verified by the output Current EL = 0x00000000 00000004. Do you know how can I read the content of ICC_IAR1_EL1 register? Unfortunately, the official ARM guide did not help me


Solution

  • You need to specify that you want your qemu-virt machine to use a GICv3 in order to be able to use the register interface to the GIC, and not a GICv2 with a memory-mapped interface, which is the version of the GIC used by default by the qem-virt machine.

    The following command is working fine:

    qemu-system-aarch64 -machine virt,gic-version=3 -cpu cortex-a72 -nographic -kernel kernel.elf
    

    My test program:

                    .title minimal-aarch64.s
                    .arch armv8-a
                    .text
                    .section .text.startup,"ax"    
                    .globl _start
                    .weak test
    _start:
                    ldr x0, =__StackTop
                    mov sp, x0
                    bl  test
    wait:           wfe
                    b wait
    
    test:           
                    mrs x0, s3_0_c12_c12_0
                    ret
                   .end
    

    Using your command or

    qemu-system-aarch64 -machine virt,gic-version=2 -cpu cortex-a72 -nographic -kernel kernel.elf
    

    Does result in an exception being raised before the program returning from the test subroutine, which is the behaviour you observed:

    Breakpoint 2, test () at minimal-aarch64.s:15
    0x0000000000000200 in ?? ()