[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:
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:
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
Firmware Engineering Section Manager
3 周非常有幫助!