[U-boot] Analyzing Trap vector hander (RISC-V)

[U-boot] Analyzing Trap vector hander (RISC-V)

In the previous post, we explored how to use binary utilities to inspect assembly instructions. Now, let’s dive into analyzing the trap vector handler (RISC-V) found in U-Boot, linked below:

https://github.com/u-boot/u-boot

Startup Code Walkthrough

We start by examining the startup code at the _start symbol, where the trap vector entry address is set.

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        

In U-Boot, if an exception occurs, the RISC-V Hart will jump to the trap_entry address.

Next, let’s examine the code for the trap_entry symbol.

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)        

This routine performs three main tasks:

  • Saves the general-purpose registers to the stack.
  • Loads scause into a0, sepc into a1, and stval into a2.
  • Calls the handle_trap function.

The assembly routine is reflected in the corresponding .S source file, mtrap.S, located at:

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

The implementation of the handle_trap function is as follows:

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);
    }
}        

The following routine is executed when a breakpoint is set:

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

The highest bit of the scause register is checked to see if the exception was caused by an interrupt:

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

The following is the routine for handling interrupts in 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;        

If is_irq is false, the following routine is executed:

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

The _exit_trap Function

Here is the complete source code for the exittrap function:

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");
}        

Let’s focus on the key parts of this function. The exception codes are listed as follows, which can also be found in the RISC-V manual:

        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",
        };        

The code prints the error message to the UART console:

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);        

After printing the register values and the code where the exception occurred, the panic() function is called:

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


10/20/2024

Chun-Cheng H.

Firmware Engineering Section Manager

3 周

非常有幫助!

回复

要查看或添加评论,请登录

社区洞察

其他会员也浏览了