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");
'RISC-V > 익셉션 핸들링' 카테고리의 다른 글
[RISC-V] 메모리 어보트 익셉션(Exception)의 실행 흐름 (RISC-V 강의) (0) | 2025.01.23 |
---|---|
[RISC-V] 인터럽트 타입 익셉션(Exception) 소개 (RISC-V 강의) (0) | 2025.01.22 |
[RISC-V] 인터럽트 타입 익셉션(Exception)의 실행 흐름 (RISC-V 강의) (0) | 2025.01.22 |
[RISC-V] 익셉션(Exception)의 기본 동작 원리 (RISC-V 강의) (0) | 2025.01.15 |
[RISC-V] 익셉션(Exception)의 종류 (RISC-V 강의) (0) | 2025.01.13 |