본문 바로가기

RISC-V/익셉션 핸들링

[RISC-V] u-boot 익셉션 핸들러

startup 코드 code-walkthrough

_start 심벌에서 스타트업 코드가 확인된다. Trap vector entry 주소를 설정한다.

0000000040200000 <_start>:
    40200000:   822a                    mv      tp,a0
    40200002:   84ae                    mv      s1,a1
    40200004:   00000193                li      gp,0
    40200008:   00074297                auipc   t0,0x74
    4020000c:   4402b283                ld      t0,1088(t0) # 40274448 <trap_entry+0x7352c>
    40200010:   10529073                csrw    stvec,t0
    40200014:   10401073                csrw    sie,zero

u-boot에서 익셉션이 유발되면 trap_entry 주소로 RISC-V Hart는 프로그램 카운터를 점프할 것이다.

이번에는 trap_entry 심벌의 코드를 보자.

0000000040200f1c <trap_entry>:
    40200f1c:   7111                    add     sp,sp,-256
    40200f1e:   e406                    sd      ra,8(sp)
    40200f20:   e80a                    sd      sp,16(sp)
    40200f22:   ec0e                    sd      gp,24(sp)
    40200f24:   f012                    sd      tp,32(sp)
...
   40200f5c:   14202573                csrr    a0,scause
    40200f60:   141025f3                csrr    a1,sepc
    40200f64:   14302673                csrr    a2,stval
    40200f68:   868a                    mv      a3,sp
    40200f6a:   398000ef                jal     40201302 <handle_trap>
    40200f6e:   14151073                csrw    sepc,a0
    40200f72:   60a2                    ld      ra,8(sp)
    40200f74:   61e2                    ld      gp,24(sp)
    40200f76:   7202                    ld      tp,32(sp)
    40200f78:   72a2                    ld      t0,40(sp)

위 코드에서 하는 일은 3가지다:

 1. General-purpose register를 스택에 저장한다 
 2. a0에 scause a1에 sepc 그리고 a2에 stval를 저장한다 
 3. handle_trap 함수를 호출한다 

위 어셈블리 루틴에 대응되는 *.S 소스 파일은 mtrap.S 이며, 위치는 아래와 같다: 

arch/riscv/cpu/mtrap.S
trap_entry:
        addi sp, sp, -32 * REGBYTES
        SREG x1,   1 * REGBYTES(sp)
        SREG x2,   2 * REGBYTES(sp)
        SREG x3,   3 * REGBYTES(sp)
        SREG x4,   4 * REGBYTES(sp)
...
       mv a3, sp
        jal handle_trap
        csrw MODE_PREFIX(epc), a0

        LREG x1,   1 * REGBYTES(sp)
        LREG x3,   3 * REGBYTES(sp)

Exception handler: handle_trap 함수 code-walkthrough

handle_trap 함수의 구현부는 아래와 같다.

arch/riscv/lib/interrupts.c
ulong handle_trap(ulong cause, ulong epc, ulong tval, struct pt_regs *regs)
{
        ulong is_irq, irq;

        /* An UEFI application may have changed gd. Restore U-Boot's gd. */
        efi_restore_gd();

        if (cause == CAUSE_BREAKPOINT &&
            CONFIG_IS_ENABLED(SEMIHOSTING_FALLBACK)) {
                ulong pre_addr = epc - 4, post_addr = epc + 4;
...
       }

        is_irq = (cause & MCAUSE_INT);
        irq = (cause & ~MCAUSE_INT);

        if (is_irq) {
                switch (irq) {
                case IRQ_M_EXT:
                case IRQ_S_EXT:
                        external_interrupt(0);  /* handle external interrupt */
                        break;
                case IRQ_M_TIMER:
                case IRQ_S_TIMER:
                        timer_interrupt(0);     /* handle timer interrupt */
                        break;
                default:
                        _exit_trap(cause, epc, tval, regs);
                        break;
                };
        } else {
                _exit_trap(cause, epc, tval, regs);
        }

아래 코드는 브레이크 포인트를 설정했을 때 처리되는 루틴이다:

        if (cause == CAUSE_BREAKPOINT &&
            CONFIG_IS_ENABLED(SEMIHOSTING_FALLBACK)) {

scause 레지스터의 최상위 레지스터를 읽어서 인터럽트로 익셉션이 유발됐는지 체크한다: 

        is_irq = (cause & MCAUSE_INT);
        irq = (cause & ~MCAUSE_INT);

아래는 RISC-V 아키텍처에서 인터럽트를 처리하는 루틴이다. 

        if (is_irq) {
                switch (irq) {
                case IRQ_M_EXT:
                case IRQ_S_EXT:
                        external_interrupt(0);  /* handle external interrupt */
                        break;
                case IRQ_M_TIMER:
                case IRQ_S_TIMER:
                        timer_interrupt(0);     /* handle timer interrupt */
                        break;

아래는 is_irq가 true가 아닌 경우 처리되는 구문이다.

        } else {
                _exit_trap(cause, epc, tval, regs);
        }

_exit_trap 함수의 전체 소스 코드는 아래와 같다:

arch/riscv/lib/interrupts.c
static void _exit_trap(ulong code, ulong epc, ulong tval, struct pt_regs *regs)
{
        static const char * const exception_code[] = {
                "Instruction address misaligned",
                "Instruction access fault",
                "Illegal instruction",
                "Breakpoint",
                "Load address misaligned",
                "Load access fault",
                "Store/AMO address misaligned",
                "Store/AMO access fault",
                "Environment call from U-mode",
                "Environment call from S-mode",
                "Reserved",
                "Environment call from M-mode",
                "Instruction page fault",
                "Load page fault",
                "Reserved",
                "Store/AMO page fault",
        };

        if (code < ARRAY_SIZE(exception_code))
                printf("Unhandled exception: %s\n", exception_code[code]);
        else
                printf("Unhandled exception code: %ld\n", code);

        printf("EPC: " REG_FMT " RA: " REG_FMT " TVAL: " REG_FMT "\n",
               epc, regs->ra, tval);
        /* Print relocation adjustments, but only if gd is initialized */
        if (gd && gd->flags & GD_FLG_RELOC)
                printf("EPC: " REG_FMT " RA: " REG_FMT " reloc adjusted\n",
                       epc - gd->reloc_off, regs->ra - gd->reloc_off);

        show_regs(regs);
        show_code(epc);
        show_efi_loaded_images(epc);
        panic("\n");
}

위 함수의 구현부에서 중요한 부분을 보자. 아래는 익셉션 코드이다.
RISC-V 메뉴얼 문서를 보면 확인할 수 있다.

        static const char * const exception_code[] = {
                "Instruction address misaligned",
                "Instruction access fault",
                "Illegal instruction",
                "Breakpoint",
                "Load address misaligned",
                "Load access fault",
                "Store/AMO address misaligned",
                "Store/AMO access fault",
                "Environment call from U-mode",
                "Environment call from S-mode",
                "Reserved",
                "Environment call from M-mode",
                "Instruction page fault",
                "Load page fault",
                "Reserved",
                "Store/AMO page fault",
        };

UART 콘솔 로그로 에러 메시지를 출력해주는 코드다:

        if (code < ARRAY_SIZE(exception_code))
                printf("Unhandled exception: %s\n", exception_code[code]);
        else
                printf("Unhandled exception code: %ld\n", code);

        printf("EPC: " REG_FMT " RA: " REG_FMT " TVAL: " REG_FMT "\n",
               epc, regs->ra, tval);
        /* Print relocation adjustments, but only if gd is initialized */
        if (gd && gd->flags & GD_FLG_RELOC)
                printf("EPC: " REG_FMT " RA: " REG_FMT " reloc adjusted\n",
                       epc - gd->reloc_off, regs->ra - gd->reloc_off);

레지스터를 출력하고, 익셉션이 유발된 코드를 출력한 다음에 panic() 함수를 호출한다.
 
        show_regs(regs);
        show_code(epc);
        show_efi_loaded_images(epc);
        panic("\n");