QUESTION
RISC-V image를 새 주소로 복사한 뒤 pointer, trap vector와 다른 hart의 PC까지 같은 주소 체계로 맞추는 순서는 무엇인가?
image byte copy와 pointer relocation, trap-vector relocation, secondary hart relocation은 서로 다른 작업이다. 각 단계가 새 주소를 게시하는 순서와 cache/fence가 다른 hart의 instruction fetch보다 앞서는지 확인한다.
board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다. 이 문장을 기준으로 코드를 위에서 아래로 읽으면, 함수 이름을 외우는 대신 어느 시점에 어떤 상태를 신뢰할 수 있는지 판단할 수 있다.
STRUCTURE
객체와 주소가 놓이는 구조
board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다.
relocated image / .rela.dyn / trap frame를 중심에 놓고 왼쪽의 입력이 어떤 검사를 거쳐 오른쪽 결과로 공개되는지 표시했다. 실제 디버깅에서는 각 블록의 주소와 크기를 로그에 대입한다.
CALL PATH
실행 흐름
화살표는 단순 호출 순서만 뜻하지 않는다. 각 단계가 성공을 반환할 때 다음 단계가 읽을 수 있는 상태가 무엇인지 함께 확인한다. 오류 반환이 발생하면 바로 다음 화살표로 진행하지 않고 해당 단계의 정리 경로를 따라간다.
STATE LEDGER
단계별 입력과 출력
호출 순서를 함수 이름으로만 외우지 않고, 각 단계가 무엇을 받아 무엇을 공개하는지 적은 표다. 실제 소스에서 생산 필드가 다르면 표를 고치는 방식으로 사용한다.
| # | 단계 | 진입 시 신뢰할 상태 | 성공 뒤 남아야 할 상태 | 다음 소비자 |
|---|---|---|---|---|
| 01 | new stack gd destination | boot hart relocation과 software-interrupt 기반 secondary 대기 문맥 | s2 new SP | copy text and data |
| 02 | copy text and data | new stack gd destination 완료 상태 | s3 new gd | apply .rela.dyn |
| 03 | apply .rela.dyn | copy text and data 완료 상태 | s4 destination | relocate tvec |
| 04 | relocate tvec | apply .rela.dyn 완료 상태 | rela entries | clear BSS |
| 05 | clear BSS | relocate tvec 완료 상태 | new trap vector | relocate secondary harts |
| 06 | relocate secondary harts | clear BSS 완료 상태 | secondary IPI | cache maintenance |
| 07 | cache maintenance | relocate secondary harts 완료 상태 | relocated runtime | board_init_r |
| 08 | board_init_r | cache maintenance 완료 상태 | relocated runtime | 최종 DRAM의 relocated RISC-V U-Boot 또는 다음 stage |
공통 불변 조건: board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다. 한 단계의 출력이 다음 단계의 입력 조건을 만족하지 않으면 오류가 실제로 드러난 위치보다 앞의 생산 단계부터 조사한다.
VISUAL WALKTHROUGH
주소와 객체의 이동을 그림으로 읽기
아래 그림은 호출 이름보다 주소, 객체 수명과 handoff 경계를 먼저 볼 수 있도록 구성했다. 실제 주소와 크기는 사용 중인 보드의 bdinfo, map과 linker symbol을 대입한다.
크기는 개념 비율이다. 실제 분석에서는 map, symbol과 runtime 출력의 시작·끝 주소를 대입한다.
호출 이름만 나열하지 않고, 각 단계가 다음 단계에 넘기는 상태를 기준으로 실행 순서를 표시한다.
두 열의 객체는 이름이 비슷해도 생성 시점, 사용 가능한 API와 해제 책임이 다르다.
Trap entry
01x1-x31 stack 저장
02cause/epc/tval 읽기
03frame pointer를 a3로 전달
04handle_trap 호출
05새 epc 반환
Trap return
01epc CSR 갱신
02x1-x31 역순 복원
03stack frame 해제
04mret 또는 sret
05중단된 context 재개
UPSTREAM SOURCE
원본 코드
아래 코드는 U-Boot v2026.07의 arch/riscv/cpu/start.S에서 269-460줄을 그대로 가져온 것이다. 설명을 위해 실제 코드를 가짜 의사 코드로 바꾸지 않았다.
269 mv a0, zero
270 mv a1, zero
271 j board_init_r
272#endif
273
274#if !defined(CONFIG_XPL_BUILD)
275/*
276 * void relocate_code(addr_sp, gd, addr_moni)
277 *
278 * This "function" does not return, instead it continues in RAM
279 * after relocating the monitor code.
280 *
281 */
282.globl relocate_code
283relocate_code:
284 mv s2, a0 /* save addr_sp */
285 mv s3, a1 /* save addr of gd */
286 mv s4, a2 /* save addr of destination */
287
288/*
289 *Set up the stack
290 */
291stack_setup:
292#if CONFIG_IS_ENABLED(SMP)
293 /* tp: hart id */
294 slli t0, tp, CONFIG_STACK_SIZE_SHIFT
295 sub sp, s2, t0
296#else
297 mv sp, s2
298#endif
299
300 la t0, _start
301 sub t6, s4, t0 /* t6 <- relocation offset */
302 beq t0, s4, clear_bss /* skip relocation */
303
304 mv t1, s4 /* t1 <- scratch for copy_loop */
305 la t2, __bss_start /* t2 <- source end address */
306
307copy_loop:
308 LREG t5, 0(t0)
309 addi t0, t0, REGBYTES
310 SREG t5, 0(t1)
311 addi t1, t1, REGBYTES
312 blt t0, t2, copy_loop
313
314/*
315 * Update dynamic relocations after board_init_f
316 */
317fix_rela_dyn:
318 la t1, __rel_dyn_start
319 la t2, __rel_dyn_end
320 beq t1, t2, clear_bss
321 add t1, t1, t6 /* t1 <- rela_dyn_start in RAM */
322 add t2, t2, t6 /* t2 <- rela_dyn_end in RAM */
323
3246:
325 LREG t5, REGBYTES(t1) /* t5 <-- relocation info:type */
326 li t3, R_RISCV_RELATIVE /* reloc type R_RISCV_RELATIVE */
327 bne t5, t3, 8f /* skip non-RISCV_RELOC entries */
328 LREG t3, 0(t1)
329 LREG t5, (REGBYTES * 2)(t1) /* t5 <-- addend */
330 add t5, t5, t6 /* t5 <-- location to fix up in RAM */
331 add t3, t3, t6 /* t3 <-- location to fix up in RAM */
332 SREG t5, 0(t3)
333 j 10f
334
3358:
336 la t4, __dyn_sym_start
337 add t4, t4, t6
338
3399:
340 srli t0, t5, SYM_INDEX /* t0 <--- sym table index */
341 andi t5, t5, 0xFF /* t5 <--- relocation type */
342 li t3, RELOC_TYPE
343 bne t5, t3, 10f /* skip non-addned entries */
344
345 LREG t3, 0(t1)
346 li t5, SYM_SIZE
347 mul t0, t0, t5
348 add s5, t4, t0
349 LREG t0, (REGBYTES * 2)(t1) /* t0 <-- addend */
350 LREG t5, REGBYTES(s5)
351 add t5, t5, t0
352 add t5, t5, t6 /* t5 <-- location to fix up in RAM */
353 add t3, t3, t6 /* t3 <-- location to fix up in RAM */
354 SREG t5, 0(t3)
35510:
356 addi t1, t1, (REGBYTES * 3)
357 blt t1, t2, 6b
358
359/*
360 * trap update
361*/
362 la t0, trap_entry
363 add t0, t0, t6
364 csrw MODE_PREFIX(tvec), t0
365
366clear_bss:
367 la t0, __bss_start /* t0 <- rel __bss_start in FLASH */
368 add t0, t0, t6 /* t0 <- rel __bss_start in RAM */
369 la t1, __bss_end /* t1 <- rel __bss_end in FLASH */
370 add t1, t1, t6 /* t1 <- rel __bss_end in RAM */
371 beq t0, t1, relocate_secondary_harts
372
373clbss_l:
374 SREG zero, 0(t0) /* clear loop... */
375 addi t0, t0, REGBYTES
376 blt t0, t1, clbss_l
377
378relocate_secondary_harts:
379#if CONFIG_IS_ENABLED(SMP)
380 /* send relocation IPI */
381 la t0, secondary_hart_relocate
382 add a0, t0, t6
383
384 /* store relocation offset */
385 mv s5, t6
386
387 mv a1, s2
388 mv a2, s3
389 mv a3, zero
390 jal smp_call_function
391
392 /* hang if relocation of secondary harts has failed */
393 beqz a0, 1f
394 mv a1, a0
395 la a0, secondary_harts_relocation_error
396 jal printf
397 jal hang
398
399 /* restore relocation offset */
4001: mv t6, s5
401#endif
402
403/*
404 * We are done. Do not return, instead branch to second part of board
405 * initialization, now running from RAM.
406 */
407call_board_init_r:
408 jal invalidate_icache_all
409 jal flush_dcache_all
410 la t0, board_init_r /* offset of board_init_r() */
411 add t4, t0, t6 /* real address of board_init_r() */
412/*
413 * setup parameters for board_init_r
414 */
415 mv a0, s3 /* gd_t */
416 mv a1, s4 /* dest_addr */
417 mv s0, zero /* fp == NULL */
418
419/*
420 * jump to it ...
421 */
422 jr t4 /* jump to board_init_r() */
423#endif /* !defined(CONFIG_XPL_BUILD) */
424
425#if CONFIG_IS_ENABLED(SMP)
426hart_out_of_bounds_loop:
427 /* Harts in this loop are out of bounds, increase CONFIG_NR_CPUS. */
428 wfi
429 j hart_out_of_bounds_loop
430
431/* SMP relocation entry */
432secondary_hart_relocate:
433 /* a1: new sp */
434 /* a2: new gd */
435 /* tp: hart id */
436
437 /* setup stack */
438 slli t0, tp, CONFIG_STACK_SIZE_SHIFT
439 sub sp, a1, t0
440
441 /* update global data pointer */
442 mv gp, a2
443#endif
444
445/*
446 * Interrupts are disabled globally, but they can still be read from m/sip. The
447 * wfi function will wake us up if we get an IPI, even if we do not trap.
448 */
449secondary_hart_loop:
450 wfi
451
452#if CONFIG_IS_ENABLED(SMP)
453 csrr t0, MODE_PREFIX(ip)
454#if CONFIG_IS_ENABLED(RISCV_MMODE)
455 andi t0, t0, MIE_MSIE
456#else
457 andi t0, t0, SIE_SSIE
458#endif
459 beqz t0, secondary_hart_loop
460
LINE BY LINE
269-460줄 해설
원본에 보이는 모든 줄을 순서대로 설명한다. 빈 줄도 block 경계로 남겨, 코드와 설명의 위치가 어긋나지 않게 했다.
mv a0, zero원본 269번 줄의 mv a0, zero는 앞의 이전 block 경계 결과를 받아 다음 mv a1, zero로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
mv a1, zero원본 270번 줄의 mv a1, zero는 앞의 mv a0, zero 결과를 받아 다음 j board_init_r로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
j board_init_r원본 271번 줄의 j board_init_r는 앞의 mv a1, zero 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 #if !defined(CONFIG_XPL_BUILD)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 new trap vector 상태가 아래 블록의 입력으로 사용되는 경계다.
#if !defined(CONFIG_XPL_BUILD)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_XPL_BUILD이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* void relocate_code(addr_sp, gd, addr_moni)원본 주석이 'void relocate_code(addr_sp, gd, addr_moni)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* This "function" does not return, instead it continues in RAM원본 주석이 'This "function" does not return, instead it continues in RAM'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* after relocating the monitor code.원본 주석이 'after relocating the monitor code.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
.globl relocate_code원본 282번 줄의 .globl relocate_code는 앞의 */ 결과를 받아 다음 relocate_code:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
relocate_code:a0=new SP, a1=new gd, a2=destination을 받아 RISC-V image copy와 ELF relocation을 시작한다.
mv s2, a0 /* save addr_sp */원본 284번 줄의 mv s2, a0 /* save addr_sp */는 앞의 relocate_code: 결과를 받아 다음 mv s3, a1 /* save addr of gd */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
mv s3, a1 /* save addr of gd */원본 285번 줄의 mv s3, a1 /* save addr of gd */는 앞의 mv s2, a0 /* save addr_sp */ 결과를 받아 다음 mv s4, a2 /* save addr of destination */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
mv s4, a2 /* save addr of destination */원본 286번 줄의 mv s4, a2 /* save addr of destination */는 앞의 mv s3, a1 /* save addr of gd */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)mv s4, a2 /* save addr of destination */까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*Set up the stack원본 주석이 'Set up the stack'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
stack_setup:'stack_setup' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
#if CONFIG_IS_ENABLED(SMP)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
/* tp: hart id */원본 주석이 'tp: hart id'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
slli t0, tp, CONFIG_STACK_SIZE_SHIFT원본 294번 줄의 slli t0, tp, CONFIG_STACK_SIZE_SHIFT는 앞의 /* tp: hart id */ 결과를 받아 다음 sub sp, s2, t0로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
sub sp, s2, t0sub sp, s2, t0 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
#else#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
mv sp, s2원본 297번 줄의 mv sp, s2는 앞의 #else 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 la t0, _start에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
la t0, _start원본 300번 줄의 la t0, _start는 앞의 이전 block 경계 결과를 받아 다음 sub t6, s4, t0 /* t6 <- relocation offset */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
sub t6, s4, t0 /* t6 <- relocation offset */sub t6, s4, t0 /* t6 <- relocation offset */ 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
beq t0, s4, clear_bss /* skip relocation */원본 302번 줄의 beq t0, s4, clear_bss /* skip relocation */는 앞의 sub t6, s4, t0 /* t6 <- relocation offset */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)beq t0, s4, clear_bss /* skip relocation */까지의 동작과 mv t1, s4 /* t1 <- scratch for copy_loop */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
mv t1, s4 /* t1 <- scratch for copy_loop */원본 304번 줄의 mv t1, s4 /* t1 <- scratch for copy_loop */는 앞의 이전 block 경계 결과를 받아 다음 la t2, __bss_start /* t2 <- source end address */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
la t2, __bss_start /* t2 <- source end address */원본 305번 줄의 la t2, __bss_start /* t2 <- source end address */는 앞의 mv t1, s4 /* t1 <- scratch for copy_loop */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)la t2, __bss_start /* t2 <- source end address */까지의 동작과 copy_loop:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
copy_loop:'copy_loop' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
LREG t5, 0(t0)원본 308번 줄의 LREG t5, 0(t0)는 앞의 copy_loop: 결과를 받아 다음 addi t0, t0, REGBYTES로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
addi t0, t0, REGBYTES원본 309번 줄의 addi t0, t0, REGBYTES는 앞의 LREG t5, 0(t0) 결과를 받아 다음 SREG t5, 0(t1)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
SREG t5, 0(t1)원본 310번 줄의 SREG t5, 0(t1)는 앞의 addi t0, t0, REGBYTES 결과를 받아 다음 addi t1, t1, REGBYTES로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
addi t1, t1, REGBYTES원본 311번 줄의 addi t1, t1, REGBYTES는 앞의 SREG t5, 0(t1) 결과를 받아 다음 blt t0, t2, copy_loop로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
blt t0, t2, copy_loop원본 312번 줄의 blt t0, t2, copy_loop는 앞의 addi t1, t1, REGBYTES 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)blt t0, t2, copy_loop까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Update dynamic relocations after board_init_f원본 주석이 'Update dynamic relocations after board_init_f'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
fix_rela_dyn:'fix_rela_dyn' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
la t1, __rel_dyn_start원본 318번 줄의 la t1, __rel_dyn_start는 앞의 fix_rela_dyn: 결과를 받아 다음 la t2, __rel_dyn_end로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
la t2, __rel_dyn_end원본 319번 줄의 la t2, __rel_dyn_end는 앞의 la t1, __rel_dyn_start 결과를 받아 다음 beq t1, t2, clear_bss로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
beq t1, t2, clear_bss원본 320번 줄의 beq t1, t2, clear_bss는 앞의 la t2, __rel_dyn_end 결과를 받아 다음 add t1, t1, t6 /* t1 <- rela_dyn_start in RAM */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
add t1, t1, t6 /* t1 <- rela_dyn_start in RAM */add t1, t1, t6 /* t1 <- rela_dyn_start in RAM */ 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
add t2, t2, t6 /* t2 <- rela_dyn_end in RAM */add t2, t2, t6 /* t2 <- rela_dyn_end in RAM */ 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
(빈 줄)add t2, t2, t6 /* t2 <- rela_dyn_end in RAM */까지의 동작과 6:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
6:원본 324번 줄의 6:는 앞의 이전 block 경계 결과를 받아 다음 LREG t5, REGBYTES(t1) /* t5 <-- relocation info:type */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
LREG t5, REGBYTES(t1) /* t5 <-- relocation info:type */REGBYTES(t1)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
li t3, R_RISCV_RELATIVE /* reloc type R_RISCV_RELATIVE */원본 326번 줄의 li t3, R_RISCV_RELATIVE /* reloc type R_RISCV_RELATIVE */는 앞의 LREG t5, REGBYTES(t1) /* t5 <-- relocation info:type */ 결과를 받아 다음 bne t5, t3, 8f /* skip non-RISCV_RELOC entries */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
bne t5, t3, 8f /* skip non-RISCV_RELOC entries */원본 327번 줄의 bne t5, t3, 8f /* skip non-RISCV_RELOC entries */는 앞의 li t3, R_RISCV_RELATIVE /* reloc type R_RISCV_RELATIVE */ 결과를 받아 다음 LREG t3, 0(t1)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
LREG t3, 0(t1)원본 328번 줄의 LREG t3, 0(t1)는 앞의 bne t5, t3, 8f /* skip non-RISCV_RELOC entries */ 결과를 받아 다음 LREG t5, (REGBYTES * 2)(t1) /* t5 <-- addend */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
LREG t5, (REGBYTES * 2)(t1) /* t5 <-- addend */원본 329번 줄의 LREG t5, (REGBYTES * 2)(t1) /* t5 <-- addend */는 앞의 LREG t3, 0(t1) 결과를 받아 다음 add t5, t5, t6 /* t5 <-- location to fix up in RAM */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
add t5, t5, t6 /* t5 <-- location to fix up in RAM */add t5, t5, t6 /* t5 <-- location to fix up in RAM */ 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
add t3, t3, t6 /* t3 <-- location to fix up in RAM */add t3, t3, t6 /* t3 <-- location to fix up in RAM */ 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
SREG t5, 0(t3)계산된 새 pointer를 destination image의 relocation target slot에 XLEN 폭으로 기록한다.
j 10f원본 333번 줄의 j 10f는 앞의 SREG t5, 0(t3) 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)j 10f까지의 동작과 8:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
8:원본 335번 줄의 8:는 앞의 이전 block 경계 결과를 받아 다음 la t4, __dyn_sym_start로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
la t4, __dyn_sym_start원본 336번 줄의 la t4, __dyn_sym_start는 앞의 8: 결과를 받아 다음 add t4, t4, t6로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
add t4, t4, t6add t4, t4, t6 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
(빈 줄)add t4, t4, t6까지의 동작과 9:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
9:원본 339번 줄의 9:는 앞의 이전 block 경계 결과를 받아 다음 srli t0, t5, SYM_INDEX /* t0 <--- sym table index */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
srli t0, t5, SYM_INDEX /* t0 <--- sym table index */원본 340번 줄의 srli t0, t5, SYM_INDEX /* t0 <--- sym table index */는 앞의 9: 결과를 받아 다음 andi t5, t5, 0xFF /* t5 <--- relocation type */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
andi t5, t5, 0xFF /* t5 <--- relocation type */원본 341번 줄의 andi t5, t5, 0xFF /* t5 <--- relocation type */는 앞의 srli t0, t5, SYM_INDEX /* t0 <--- sym table index */ 결과를 받아 다음 li t3, RELOC_TYPE로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
li t3, RELOC_TYPE원본 342번 줄의 li t3, RELOC_TYPE는 앞의 andi t5, t5, 0xFF /* t5 <--- relocation type */ 결과를 받아 다음 bne t5, t3, 10f /* skip non-addned entries */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
bne t5, t3, 10f /* skip non-addned entries */원본 343번 줄의 bne t5, t3, 10f /* skip non-addned entries */는 앞의 li t3, RELOC_TYPE 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)bne t5, t3, 10f /* skip non-addned entries */까지의 동작과 LREG t3, 0(t1)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
LREG t3, 0(t1)원본 345번 줄의 LREG t3, 0(t1)는 앞의 이전 block 경계 결과를 받아 다음 li t5, SYM_SIZE로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
li t5, SYM_SIZE원본 346번 줄의 li t5, SYM_SIZE는 앞의 LREG t3, 0(t1) 결과를 받아 다음 mul t0, t0, t5로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
mul t0, t0, t5원본 347번 줄의 mul t0, t0, t5는 앞의 li t5, SYM_SIZE 결과를 받아 다음 add s5, t4, t0로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
add s5, t4, t0add s5, t4, t0 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
LREG t0, (REGBYTES * 2)(t1) /* t0 <-- addend */원본 349번 줄의 LREG t0, (REGBYTES * 2)(t1) /* t0 <-- addend */는 앞의 add s5, t4, t0 결과를 받아 다음 LREG t5, REGBYTES(s5)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
LREG t5, REGBYTES(s5)REGBYTES(s5)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
add t5, t5, t0add t5, t5, t0 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
add t5, t5, t6 /* t5 <-- location to fix up in RAM */add t5, t5, t6 /* t5 <-- location to fix up in RAM */ 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
add t3, t3, t6 /* t3 <-- location to fix up in RAM */add t3, t3, t6 /* t3 <-- location to fix up in RAM */ 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
SREG t5, 0(t3)계산된 새 pointer를 destination image의 relocation target slot에 XLEN 폭으로 기록한다.
10:원본 355번 줄의 10:는 앞의 SREG t5, 0(t3) 결과를 받아 다음 addi t1, t1, (REGBYTES * 3)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
addi t1, t1, (REGBYTES * 3)원본 356번 줄의 addi t1, t1, (REGBYTES * 3)는 앞의 10: 결과를 받아 다음 blt t1, t2, 6b로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
blt t1, t2, 6b원본 357번 줄의 blt t1, t2, 6b는 앞의 addi t1, t1, (REGBYTES * 3) 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)blt t1, t2, 6b까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* trap update원본 주석이 'trap update'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
la t0, trap_entry원본 362번 줄의 la t0, trap_entry는 앞의 */ 결과를 받아 다음 add t0, t0, t6로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
add t0, t0, t6old image의 trap_entry link address를 destination handler address로 바꾼다. 바로 뒤 CSR write 전에 mode bit 처리도 확인한다.
csrw MODE_PREFIX(tvec), t0trap vector에도 relocation offset을 반영해 새 image handler를 가리키게 한다.
(빈 줄)csrw MODE_PREFIX(tvec), t0까지의 동작과 clear_bss:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
clear_bss:'clear_bss' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
la t0, __bss_start /* t0 <- rel __bss_start in FLASH */원본 367번 줄의 la t0, __bss_start /* t0 <- rel __bss_start in FLASH */는 앞의 clear_bss: 결과를 받아 다음 add t0, t0, t6 /* t0 <- rel __bss_start in RAM */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
add t0, t0, t6 /* t0 <- rel __bss_start in RAM */add t0, t0, t6 /* t0 <- rel __bss_start in RAM */ 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
la t1, __bss_end /* t1 <- rel __bss_end in FLASH */원본 369번 줄의 la t1, __bss_end /* t1 <- rel __bss_end in FLASH */는 앞의 add t0, t0, t6 /* t0 <- rel __bss_start in RAM */ 결과를 받아 다음 add t1, t1, t6 /* t1 <- rel __bss_end in RAM */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
add t1, t1, t6 /* t1 <- rel __bss_end in RAM */add t1, t1, t6 /* t1 <- rel __bss_end in RAM */ 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
beq t0, t1, relocate_secondary_harts원본 371번 줄의 beq t0, t1, relocate_secondary_harts는 앞의 add t1, t1, t6 /* t1 <- rel __bss_end in RAM */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)beq t0, t1, relocate_secondary_harts까지의 동작과 clbss_l:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
clbss_l:'clbss_l' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
SREG zero, 0(t0) /* clear loop... */원본 374번 줄의 SREG zero, 0(t0) /* clear loop... */는 앞의 clbss_l: 결과를 받아 다음 addi t0, t0, REGBYTES로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
addi t0, t0, REGBYTES원본 375번 줄의 addi t0, t0, REGBYTES는 앞의 SREG zero, 0(t0) /* clear loop... */ 결과를 받아 다음 blt t0, t1, clbss_l로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
blt t0, t1, clbss_l원본 376번 줄의 blt t0, t1, clbss_l는 앞의 addi t0, t0, REGBYTES 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)blt t0, t1, clbss_l까지의 동작과 relocate_secondary_harts:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
relocate_secondary_harts:'relocate_secondary_harts' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
#if CONFIG_IS_ENABLED(SMP)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
/* send relocation IPI */원본 주석이 'send relocation IPI'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
la t0, secondary_hart_relocate원본 381번 줄의 la t0, secondary_hart_relocate는 앞의 /* send relocation IPI */ 결과를 받아 다음 add a0, t0, t6로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
add a0, t0, t6add a0, t0, t6 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
(빈 줄)add a0, t0, t6까지의 동작과 /* store relocation offset */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/* store relocation offset */원본 주석이 'store relocation offset'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
mv s5, t6원본 385번 줄의 mv s5, t6는 앞의 /* store relocation offset */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)mv s5, t6까지의 동작과 mv a1, s2에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
mv a1, s2원본 387번 줄의 mv a1, s2는 앞의 이전 block 경계 결과를 받아 다음 mv a2, s3로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
mv a2, s3원본 388번 줄의 mv a2, s3는 앞의 mv a1, s2 결과를 받아 다음 mv a3, zero로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
mv a3, zero원본 389번 줄의 mv a3, zero는 앞의 mv a2, s3 결과를 받아 다음 jal smp_call_function로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
jal smp_call_function원본 390번 줄의 jal smp_call_function는 앞의 mv a3, zero 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)jal smp_call_function까지의 동작과 /* hang if relocation of secondary harts has failed */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/* hang if relocation of secondary harts has failed */원본 주석이 'hang if relocation of secondary harts has failed'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
beqz a0, 1f원본 393번 줄의 beqz a0, 1f는 앞의 /* hang if relocation of secondary harts has failed */ 결과를 받아 다음 mv a1, a0로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
mv a1, a0원본 394번 줄의 mv a1, a0는 앞의 beqz a0, 1f 결과를 받아 다음 la a0, secondary_harts_relocation_error로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
la a0, secondary_harts_relocation_error원본 395번 줄의 la a0, secondary_harts_relocation_error는 앞의 mv a1, a0 결과를 받아 다음 jal printf로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
jal printf원본 396번 줄의 jal printf는 앞의 la a0, secondary_harts_relocation_error 결과를 받아 다음 jal hang로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
jal hang원본 397번 줄의 jal hang는 앞의 jal printf 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)jal hang까지의 동작과 /* restore relocation offset */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/* restore relocation offset */원본 주석이 'restore relocation offset'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
1: mv t6, s5원본 400번 줄의 1: mv t6, s5는 앞의 /* restore relocation offset */ 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* We are done. Do not return, instead branch to second part of board원본 주석이 'We are done. Do not return, instead branch to second part of board'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* initialization, now running from RAM.원본 주석이 'initialization, now running from RAM.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
call_board_init_r:'call_board_init_r' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
jal invalidate_icache_all원본 408번 줄의 jal invalidate_icache_all는 앞의 call_board_init_r: 결과를 받아 다음 jal flush_dcache_all로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
jal flush_dcache_all원본 409번 줄의 jal flush_dcache_all는 앞의 jal invalidate_icache_all 결과를 받아 다음 la t0, board_init_r /* offset of board_init_r() */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
la t0, board_init_r /* offset of board_init_r() */board_init_r(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
add t4, t0, t6 /* real address of board_init_r() */board_init_r(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* setup parameters for board_init_r원본 주석이 'setup parameters for board_init_r'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
mv a0, s3 /* gd_t */원본 415번 줄의 mv a0, s3 /* gd_t */는 앞의 */ 결과를 받아 다음 mv a1, s4 /* dest_addr */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
mv a1, s4 /* dest_addr */원본 416번 줄의 mv a1, s4 /* dest_addr */는 앞의 mv a0, s3 /* gd_t */ 결과를 받아 다음 mv s0, zero /* fp == NULL */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
mv s0, zero /* fp == NULL */원본 417번 줄의 mv s0, zero /* fp == NULL */는 앞의 mv a1, s4 /* dest_addr */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)mv s0, zero /* fp == NULL */까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* jump to it ...원본 주석이 'jump to it ...'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
jr t4 /* jump to board_init_r() */board_init_r(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
#endif /* !defined(CONFIG_XPL_BUILD) */#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_XPL_BUILD이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif /* !defined(CONFIG_XPL_BUILD) */까지의 동작과 #if CONFIG_IS_ENABLED(SMP)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
#if CONFIG_IS_ENABLED(SMP)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
hart_out_of_bounds_loop:'hart_out_of_bounds_loop' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
/* Harts in this loop are out of bounds, increase CONFIG_NR_CPUS. */원본 주석이 'Harts in this loop are out of bounds, increase CONFIG_NR_CPUS.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
wfi원본 428번 줄의 wfi는 앞의 /* Harts in this loop are out of bounds, increase CONFIG_NR_CPUS. */ 결과를 받아 다음 j hart_out_of_bounds_loop로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
j hart_out_of_bounds_loop원본 429번 줄의 j hart_out_of_bounds_loop는 앞의 wfi 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)j hart_out_of_bounds_loop까지의 동작과 /* SMP relocation entry */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/* SMP relocation entry */원본 주석이 'SMP relocation entry'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
secondary_hart_relocate:'secondary_hart_relocate' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
/* a1: new sp */원본 주석이 'a1: new sp'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
/* a2: new gd */원본 주석이 'a2: new gd'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
/* tp: hart id */원본 주석이 'tp: hart id'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
(빈 줄)/* tp: hart id */까지의 동작과 /* setup stack */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/* setup stack */원본 주석이 'setup stack'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
slli t0, tp, CONFIG_STACK_SIZE_SHIFT원본 438번 줄의 slli t0, tp, CONFIG_STACK_SIZE_SHIFT는 앞의 /* setup stack */ 결과를 받아 다음 sub sp, a1, t0로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
sub sp, a1, t0sub sp, a1, t0 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
(빈 줄)sub sp, a1, t0까지의 동작과 /* update global data pointer */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/* update global data pointer */원본 주석이 'update global data pointer'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
mv gp, a2원본 442번 줄의 mv gp, a2는 앞의 /* update global data pointer */ 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Interrupts are disabled globally, but they can still be read from m/sip. The원본 주석이 'Interrupts are disabled globally, but they can still be read from m/sip. The'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* wfi function will wake us up if we get an IPI, even if we do not trap.원본 주석이 'wfi function will wake us up if we get an IPI, even if we do not trap.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
secondary_hart_loop:'secondary_hart_loop' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
wfi원본 450번 줄의 wfi는 앞의 secondary_hart_loop: 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)wfi까지의 동작과 #if CONFIG_IS_ENABLED(SMP)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
#if CONFIG_IS_ENABLED(SMP)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
csrr t0, MODE_PREFIX(ip)MODE_PREFIX(ip)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
#if CONFIG_IS_ENABLED(RISCV_MMODE)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
andi t0, t0, MIE_MSIE원본 455번 줄의 andi t0, t0, MIE_MSIE는 앞의 #if CONFIG_IS_ENABLED(RISCV_MMODE) 결과를 받아 다음 #else로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
#else#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
andi t0, t0, SIE_SSIE원본 457번 줄의 andi t0, t0, SIE_SSIE는 앞의 #else 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
beqz t0, secondary_hart_loop원본 459번 줄의 beqz t0, secondary_hart_loop는 앞의 #endif 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)beqz t0, secondary_hart_loop까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
RELATED SOURCE
함께 읽어야 하는 원본 코드
첫 코드 조각만으로 동작이 완성되지 않는 경우 호출 매크로, 객체 정의와 실제 실행 목록을 같은 페이지에 묶었다. 각 조각은 같은 기준 commit에서 가져왔다.
01. trap frame을 저장하고 handle_trap으로 넘기는 entry
arch/riscv/cpu/mtrap.S 18-104줄이다.
18#define SREG sw
19#define REGBYTES 4
20#else
21#define LREG ld
22#define SREG sd
23#define REGBYTES 8
24#endif
25
26 .text
27
28 /* trap entry */
29 .align 6
30 .global trap_entry
31trap_entry:
32 addi sp, sp, -32 * REGBYTES
33 SREG x1, 1 * REGBYTES(sp)
34 SREG x2, 2 * REGBYTES(sp)
35 SREG x3, 3 * REGBYTES(sp)
36 SREG x4, 4 * REGBYTES(sp)
37 SREG x5, 5 * REGBYTES(sp)
38 SREG x6, 6 * REGBYTES(sp)
39 SREG x7, 7 * REGBYTES(sp)
40 SREG x8, 8 * REGBYTES(sp)
41 SREG x9, 9 * REGBYTES(sp)
42 SREG x10, 10 * REGBYTES(sp)
43 SREG x11, 11 * REGBYTES(sp)
44 SREG x12, 12 * REGBYTES(sp)
45 SREG x13, 13 * REGBYTES(sp)
46 SREG x14, 14 * REGBYTES(sp)
47 SREG x15, 15 * REGBYTES(sp)
48 SREG x16, 16 * REGBYTES(sp)
49 SREG x17, 17 * REGBYTES(sp)
50 SREG x18, 18 * REGBYTES(sp)
51 SREG x19, 19 * REGBYTES(sp)
52 SREG x20, 20 * REGBYTES(sp)
53 SREG x21, 21 * REGBYTES(sp)
54 SREG x22, 22 * REGBYTES(sp)
55 SREG x23, 23 * REGBYTES(sp)
56 SREG x24, 24 * REGBYTES(sp)
57 SREG x25, 25 * REGBYTES(sp)
58 SREG x26, 26 * REGBYTES(sp)
59 SREG x27, 27 * REGBYTES(sp)
60 SREG x28, 28 * REGBYTES(sp)
61 SREG x29, 29 * REGBYTES(sp)
62 SREG x30, 30 * REGBYTES(sp)
63 SREG x31, 31 * REGBYTES(sp)
64 csrr a0, MODE_PREFIX(cause)
65 csrr a1, MODE_PREFIX(epc)
66 csrr a2, MODE_PREFIX(tval)
67 mv a3, sp
68 jal handle_trap
69 csrw MODE_PREFIX(epc), a0
70
71 LREG x1, 1 * REGBYTES(sp)
72 LREG x3, 3 * REGBYTES(sp)
73 LREG x4, 4 * REGBYTES(sp)
74 LREG x5, 5 * REGBYTES(sp)
75 LREG x6, 6 * REGBYTES(sp)
76 LREG x7, 7 * REGBYTES(sp)
77 LREG x8, 8 * REGBYTES(sp)
78 LREG x9, 9 * REGBYTES(sp)
79 LREG x10, 10 * REGBYTES(sp)
80 LREG x11, 11 * REGBYTES(sp)
81 LREG x12, 12 * REGBYTES(sp)
82 LREG x13, 13 * REGBYTES(sp)
83 LREG x14, 14 * REGBYTES(sp)
84 LREG x15, 15 * REGBYTES(sp)
85 LREG x16, 16 * REGBYTES(sp)
86 LREG x17, 17 * REGBYTES(sp)
87 LREG x18, 18 * REGBYTES(sp)
88 LREG x19, 19 * REGBYTES(sp)
89 LREG x20, 20 * REGBYTES(sp)
90 LREG x21, 21 * REGBYTES(sp)
91 LREG x22, 22 * REGBYTES(sp)
92 LREG x23, 23 * REGBYTES(sp)
93 LREG x24, 24 * REGBYTES(sp)
94 LREG x25, 25 * REGBYTES(sp)
95 LREG x26, 26 * REGBYTES(sp)
96 LREG x27, 27 * REGBYTES(sp)
97 LREG x28, 28 * REGBYTES(sp)
98 LREG x29, 29 * REGBYTES(sp)
99 LREG x30, 30 * REGBYTES(sp)
100 LREG x31, 31 * REGBYTES(sp)
101 LREG x2, 2 * REGBYTES(sp)
102 addi sp, sp, 32 * REGBYTES
103 MODE_PREFIX(ret)
104
18-104줄 해설
#define SREG sw#define 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#define REGBYTES 4#define 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#else#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#define LREG ld#define 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#define SREG sd#define 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#define REGBYTES 8#define 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 .text에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
.text원본 26번 줄의 .text는 앞의 이전 block 경계 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄).text까지의 동작과 /* trap entry */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/* trap entry */원본 주석이 'trap entry'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
.align 6원본 29번 줄의 .align 6는 앞의 /* trap entry */ 결과를 받아 다음 .global trap_entry로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
.global trap_entry원본 30번 줄의 .global trap_entry는 앞의 .align 6 결과를 받아 다음 trap_entry:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
trap_entry:'trap_entry' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
addi sp, sp, -32 * REGBYTES원본 32번 줄의 addi sp, sp, -32 * REGBYTES는 앞의 trap_entry: 결과를 받아 다음 SREG x1, 1 * REGBYTES(sp)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
SREG x1, 1 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x2, 2 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x3, 3 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x4, 4 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x5, 5 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x6, 6 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x7, 7 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x8, 8 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x9, 9 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x10, 10 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x11, 11 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x12, 12 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x13, 13 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x14, 14 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x15, 15 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x16, 16 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x17, 17 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x18, 18 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x19, 19 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x20, 20 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x21, 21 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x22, 22 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x23, 23 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x24, 24 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x25, 25 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x26, 26 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x27, 27 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x28, 28 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x29, 29 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x30, 30 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
SREG x31, 31 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
csrr a0, MODE_PREFIX(cause)MODE_PREFIX(cause)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
csrr a1, MODE_PREFIX(epc)MODE_PREFIX(epc)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
csrr a2, MODE_PREFIX(tval)MODE_PREFIX(tval)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
mv a3, sp원본 67번 줄의 mv a3, sp는 앞의 csrr a2, MODE_PREFIX(tval) 결과를 받아 다음 jal handle_trap로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
jal handle_trap원본 68번 줄의 jal handle_trap는 앞의 mv a3, sp 결과를 받아 다음 csrw MODE_PREFIX(epc), a0로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
csrw MODE_PREFIX(epc), a0MODE_PREFIX(epc)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
(빈 줄)csrw MODE_PREFIX(epc), a0까지의 동작과 LREG x1, 1 * REGBYTES(sp)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
LREG x1, 1 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x3, 3 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x4, 4 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x5, 5 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x6, 6 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x7, 7 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x8, 8 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x9, 9 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x10, 10 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x11, 11 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x12, 12 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x13, 13 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x14, 14 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x15, 15 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x16, 16 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x17, 17 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x18, 18 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x19, 19 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x20, 20 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x21, 21 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x22, 22 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x23, 23 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x24, 24 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x25, 25 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x26, 26 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x27, 27 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x28, 28 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x29, 29 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x30, 30 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x31, 31 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
LREG x2, 2 * REGBYTES(sp)REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
addi sp, sp, 32 * REGBYTES원본 102번 줄의 addi sp, sp, 32 * REGBYTES는 앞의 LREG x2, 2 * REGBYTES(sp) 결과를 받아 다음 MODE_PREFIX(ret)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
MODE_PREFIX(ret)MODE_PREFIX(ret)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
(빈 줄)MODE_PREFIX(ret)까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
02. secondary hart의 IPI 대기와 dispatch
arch/riscv/cpu/start.S 437-466줄이다.
437 /* setup stack */
438 slli t0, tp, CONFIG_STACK_SIZE_SHIFT
439 sub sp, a1, t0
440
441 /* update global data pointer */
442 mv gp, a2
443#endif
444
445/*
446 * Interrupts are disabled globally, but they can still be read from m/sip. The
447 * wfi function will wake us up if we get an IPI, even if we do not trap.
448 */
449secondary_hart_loop:
450 wfi
451
452#if CONFIG_IS_ENABLED(SMP)
453 csrr t0, MODE_PREFIX(ip)
454#if CONFIG_IS_ENABLED(RISCV_MMODE)
455 andi t0, t0, MIE_MSIE
456#else
457 andi t0, t0, SIE_SSIE
458#endif
459 beqz t0, secondary_hart_loop
460
461 mv a0, tp
462 jal handle_ipi
463#endif
464
465 j secondary_hart_loop
466
437-466줄 해설
/* setup stack */원본 주석이 'setup stack'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
slli t0, tp, CONFIG_STACK_SIZE_SHIFT원본 438번 줄의 slli t0, tp, CONFIG_STACK_SIZE_SHIFT는 앞의 /* setup stack */ 결과를 받아 다음 sub sp, a1, t0로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
sub sp, a1, t0sub sp, a1, t0 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
(빈 줄)sub sp, a1, t0까지의 동작과 /* update global data pointer */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 rela entries 상태가 아래 블록의 입력으로 사용되는 경계다.
/* update global data pointer */원본 주석이 'update global data pointer'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
mv gp, a2원본 442번 줄의 mv gp, a2는 앞의 /* update global data pointer */ 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Interrupts are disabled globally, but they can still be read from m/sip. The원본 주석이 'Interrupts are disabled globally, but they can still be read from m/sip. The'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* wfi function will wake us up if we get an IPI, even if we do not trap.원본 주석이 'wfi function will wake us up if we get an IPI, even if we do not trap.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
secondary_hart_loop:'secondary_hart_loop' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
wfi원본 450번 줄의 wfi는 앞의 secondary_hart_loop: 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)wfi까지의 동작과 #if CONFIG_IS_ENABLED(SMP)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
#if CONFIG_IS_ENABLED(SMP)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
csrr t0, MODE_PREFIX(ip)MODE_PREFIX(ip)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
#if CONFIG_IS_ENABLED(RISCV_MMODE)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
andi t0, t0, MIE_MSIE원본 455번 줄의 andi t0, t0, MIE_MSIE는 앞의 #if CONFIG_IS_ENABLED(RISCV_MMODE) 결과를 받아 다음 #else로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
#else#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
andi t0, t0, SIE_SSIE원본 457번 줄의 andi t0, t0, SIE_SSIE는 앞의 #else 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
beqz t0, secondary_hart_loop원본 459번 줄의 beqz t0, secondary_hart_loop는 앞의 #endif 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)beqz t0, secondary_hart_loop까지의 동작과 mv a0, tp에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
mv a0, tp원본 461번 줄의 mv a0, tp는 앞의 이전 block 경계 결과를 받아 다음 jal handle_ipi로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
jal handle_ipi원본 462번 줄의 jal handle_ipi는 앞의 mv a0, tp 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 j secondary_hart_loop에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
j secondary_hart_loop원본 465번 줄의 j secondary_hart_loop는 앞의 이전 block 경계 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(빈 줄)j secondary_hart_loop까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
03. RISC-V U-Boot linker section과 relocation table 배치
arch/riscv/cpu/u-boot.lds 46-90줄이다.
46 . = ALIGN(4);
47
48 __u_boot_list : {
49 KEEP(*(SORT(__u_boot_list*)));
50 }
51
52 . = ALIGN(8);
53
54 .efi_runtime_rel : {
55 __efi_runtime_rel_start = .;
56 *(.rel*.efi_runtime)
57 *(.rel*.efi_runtime.*)
58 __efi_runtime_rel_stop = .;
59 }
60
61 __image_copy_end = .;
62
63 /DISCARD/ : { *(.rela.plt*) }
64 .rela.dyn : {
65 __rel_dyn_start = .;
66 *(.rela*)
67 __rel_dyn_end = .;
68 }
69
70 . = ALIGN(8);
71
72 .dynsym : {
73 __dyn_sym_start = .;
74 *(.dynsym)
75 __dyn_sym_end = .;
76 . = ALIGN(8);
77 }
78
79 _end = .;
80
81 .bss : {
82 __bss_start = .;
83 *(.bss*)
84 . = ALIGN(8);
85 __bss_end = .;
86 }
87}
88
89ASSERT(_end % 8 == 0, "_end must be 8-byte aligned for device tree");
90
46-90줄 해설
. = ALIGN(4);.에 ALIGN(4)를 = 연산으로 반영해 relocated image / .rela.dyn / trap frame와 연결된 field를 갱신한다. 바로 다음 다음 block 경계가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄). = ALIGN(4);까지의 동작과 __u_boot_list : {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 s3 new gd 상태가 아래 블록의 입력으로 사용되는 경계다.
__u_boot_list : {원본 48번 줄의 __u_boot_list : {는 앞의 이전 block 경계 결과를 받아 다음 KEEP(*(SORT(__u_boot_list*)));로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
KEEP(*(SORT(__u_boot_list*)));SORT(__u_boot_list*)))를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 rela entries 항목과 대조한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 . = ALIGN(8);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary IPI 상태가 아래 블록의 입력으로 사용되는 경계다.
. = ALIGN(8);.에 ALIGN(8)를 = 연산으로 반영해 relocated image / .rela.dyn / trap frame와 연결된 field를 갱신한다. 바로 다음 다음 block 경계가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄). = ALIGN(8);까지의 동작과 .efi_runtime_rel : {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
.efi_runtime_rel : {원본 54번 줄의 .efi_runtime_rel : {는 앞의 이전 block 경계 결과를 받아 다음 __efi_runtime_rel_start = .;로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
__efi_runtime_rel_start = .;__efi_runtime_rel_start에 .를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
*(.rel*.efi_runtime)원본 주석이 '(.rel*.efi_runtime)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*(.rel*.efi_runtime.*)원본 주석이 '(.rel*.efi_runtime.*)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
__efi_runtime_rel_stop = .;__efi_runtime_rel_stop에 .를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 __image_copy_end = .;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
__image_copy_end = .;__image_copy_end에 .를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)__image_copy_end = .;까지의 동작과 /DISCARD/ : { *(.rela.plt*) }에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/DISCARD/ : { *(.rela.plt*) }원본 63번 줄의 /DISCARD/ : { *(.rela.plt*) }는 앞의 이전 block 경계 결과를 받아 다음 .rela.dyn : {로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
.rela.dyn : {원본 64번 줄의 .rela.dyn : {는 앞의 /DISCARD/ : { *(.rela.plt*) } 결과를 받아 다음 __rel_dyn_start = .;로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
__rel_dyn_start = .;__rel_dyn_start에 .를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
*(.rela*)원본 주석이 '(.rela*)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
__rel_dyn_end = .;__rel_dyn_end에 .를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 . = ALIGN(8);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
. = ALIGN(8);.에 ALIGN(8)를 = 연산으로 반영해 relocated image / .rela.dyn / trap frame와 연결된 field를 갱신한다. 바로 다음 다음 block 경계가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄). = ALIGN(8);까지의 동작과 .dynsym : {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
.dynsym : {원본 72번 줄의 .dynsym : {는 앞의 이전 block 경계 결과를 받아 다음 __dyn_sym_start = .;로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
__dyn_sym_start = .;__dyn_sym_start에 .를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
*(.dynsym)원본 주석이 '(.dynsym)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
__dyn_sym_end = .;__dyn_sym_end에 .를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
. = ALIGN(8);.에 ALIGN(8)를 = 연산으로 반영해 relocated image / .rela.dyn / trap frame와 연결된 field를 갱신한다. 바로 다음 }가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 _end = .;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
_end = .;_end에 .를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)_end = .;까지의 동작과 .bss : {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
.bss : {원본 81번 줄의 .bss : {는 앞의 이전 block 경계 결과를 받아 다음 __bss_start = .;로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
__bss_start = .;__bss_start에 .를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
*(.bss*)원본 주석이 '(.bss*)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
. = ALIGN(8);.에 ALIGN(8)를 = 연산으로 반영해 relocated image / .rela.dyn / trap frame와 연결된 field를 갱신한다. 바로 다음 __bss_end = .;가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
__bss_end = .;__bss_end에 .를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 ASSERT(_end % 8 == 0, "_end must be 8-byte aligned for device tree");에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
ASSERT(_end % 8 == 0, "_end must be 8-byte aligned for device tree");ASSERT(_end % 8에 = 0, "_end must be 8-byte aligned for device tree")를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)ASSERT(_end % 8 == 0, "_end must be 8-byte aligned for device tree");까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
04. cause, epc, tval을 해석하는 C trap dispatcher
arch/riscv/lib/interrupts.c 177-249줄이다.
177/*
178 * disable interrupts
179 */
180int disable_interrupts(void)
181{
182 return 0;
183}
184
185ulong handle_trap(ulong cause, ulong epc, ulong tval, struct pt_regs *regs)
186{
187 ulong is_irq, irq;
188
189 /* An UEFI application may have changed gd. Restore U-Boot's gd. */
190 efi_restore_gd();
191
192 if (cause == CAUSE_BREAKPOINT &&
193 CONFIG_IS_ENABLED(SEMIHOSTING_FALLBACK)) {
194 ulong pre_addr = epc - 4, post_addr = epc + 4;
195
196 /* Check for prior and post addresses to be in same page. */
197 if ((pre_addr & ~(PAGE_SIZE - 1)) ==
198 (post_addr & ~(PAGE_SIZE - 1))) {
199 u32 pre = *(u32 *)pre_addr;
200 u32 post = *(u32 *)post_addr;
201
202 /* Check for semihosting, i.e.:
203 * slli zero,zero,0x1f
204 * ebreak
205 * srai zero,zero,0x7
206 */
207 if (pre == 0x01f01013 && post == 0x40705013) {
208 disable_semihosting();
209 epc += 4;
210 return epc;
211 }
212 }
213 }
214
215 is_irq = (cause & MCAUSE_INT);
216 irq = (cause & ~MCAUSE_INT);
217
218 if (is_irq) {
219 switch (irq) {
220 case IRQ_M_EXT:
221 case IRQ_S_EXT:
222 external_interrupt(0); /* handle external interrupt */
223 break;
224 case IRQ_M_TIMER:
225 case IRQ_S_TIMER:
226 timer_interrupt(0); /* handle timer interrupt */
227 break;
228 default:
229 _exit_trap(cause, epc, tval, regs);
230 break;
231 };
232 } else {
233 _exit_trap(cause, epc, tval, regs);
234 }
235
236 return epc;
237}
238
239/*
240 *Entry Point for PLIC Interrupt Handler
241 */
242__attribute__((weak)) void external_interrupt(struct pt_regs *regs)
243{
244}
245
246__attribute__((weak)) void timer_interrupt(struct pt_regs *regs)
247{
248}
249
177-249줄 해설
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* disable interrupts원본 주석이 'disable interrupts'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
int disable_interrupts(void)disable_interrupts 함수 정의가 시작된다. 입력은 void이며, boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 호출된다는 전제로 반환 전까지의 상태 변화를 읽는다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 relocated image / .rela.dyn / trap frame의 수명에 맞춰 묶어 읽는다.
return 0;0를 호출자에게 반환한다. caller가 이 값을 검사한 뒤 부분 초기화된 relocated image / .rela.dyn / trap frame를 정리하거나 다음 단계로 진행하는지 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 ulong handle_trap(ulong cause, ulong epc, ulong tval, struct pt_regs *regs)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
ulong handle_trap(ulong cause, ulong epc, ulong tval, struct pt_regs *regs)handle_trap 함수 정의가 시작된다. 입력은 ulong cause, ulong epc, ulong tval, struct pt_regs *regs이며, boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 호출된다는 전제로 반환 전까지의 상태 변화를 읽는다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 relocated image / .rela.dyn / trap frame의 수명에 맞춰 묶어 읽는다.
ulong is_irq, irq;ulong is_irq, irq를 선언한다. 함수 안 선언이면 현재 stack frame, file scope와 static이면 image의 data/BSS에 놓인다. 이 값이 relocated image / .rela.dyn / trap frame를 직접 소유하는지 pointer만 빌리는지, boot hart relocation과 software-interrupt 기반 secondary 대기 문맥를 벗어난 뒤에도 참조되는지 다음 대입과 callback 등록까지 따라간다.
(빈 줄)ulong is_irq, irq;까지의 동작과 /* An UEFI application may have changed gd. Restore U-Boot's gd. */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/* An UEFI application may have changed gd. Restore U-Boot's gd. */원본 주석이 'An UEFI application may have changed gd. Restore U-Boot's gd.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
efi_restore_gd();efi_restore_gd(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
(빈 줄)efi_restore_gd();까지의 동작과 if (cause == CAUSE_BREAKPOINT &&에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
if (cause == CAUSE_BREAKPOINT &&원본 192번 줄의 if (cause == CAUSE_BREAKPOINT &&는 앞의 이전 block 경계 결과를 받아 다음 CONFIG_IS_ENABLED(SEMIHOSTING_FALLBACK)) {로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
CONFIG_IS_ENABLED(SEMIHOSTING_FALLBACK)) {CONFIG_IS_ENABLED(SEMIHOSTING_FALLBACK))를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
ulong pre_addr = epc - 4, post_addr = epc + 4;ulong pre_addr = epc - 4, post_addr = epc + 4를 선언한다. 함수 안 선언이면 현재 stack frame, file scope와 static이면 image의 data/BSS에 놓인다. 이 값이 relocated image / .rela.dyn / trap frame를 직접 소유하는지 pointer만 빌리는지, boot hart relocation과 software-interrupt 기반 secondary 대기 문맥를 벗어난 뒤에도 참조되는지 다음 대입과 callback 등록까지 따라간다.
(빈 줄)ulong pre_addr = epc - 4, post_addr = epc + 4;까지의 동작과 /* Check for prior and post addresses to be in same page. */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Check for prior and post addresses to be in same page. */원본 주석이 'Check for prior and post addresses to be in same page.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
if ((pre_addr & ~(PAGE_SIZE - 1)) ==원본 197번 줄의 if ((pre_addr & ~(PAGE_SIZE - 1)) ==는 앞의 /* Check for prior and post addresses to be in same page. */ 결과를 받아 다음 (post_addr & ~(PAGE_SIZE - 1))) {로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
(post_addr & ~(PAGE_SIZE - 1))) {원본 198번 줄의 (post_addr & ~(PAGE_SIZE - 1))) {는 앞의 if ((pre_addr & ~(PAGE_SIZE - 1)) == 결과를 받아 다음 u32 pre = *(u32 *)pre_addr;로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
u32 pre = *(u32 *)pre_addr;u32 pre에 *(u32 *)pre_addr를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
u32 post = *(u32 *)post_addr;u32 post에 *(u32 *)post_addr를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)u32 post = *(u32 *)post_addr;까지의 동작과 /* Check for semihosting, i.e.:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Check for semihosting, i.e.:원본 주석이 'Check for semihosting, i.e.:'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* slli zero,zero,0x1f원본 주석이 'slli zero,zero,0x1f'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* ebreak원본 주석이 'ebreak'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* srai zero,zero,0x7원본 주석이 'srai zero,zero,0x7'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
if (pre == 0x01f01013 && post == 0x40705013) {pre == 0x01f01013 && post == 0x40705013를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
disable_semihosting();disable_semihosting(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
epc += 4;epc에 4를 += 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 바로 다음 return epc;가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
return epc;assembly 복귀 코드가 CSR EPC에 다시 쓸 재개 주소를 반환한다. breakpoint semihosting 경로는 이 값을 4만큼 전진시킬 수 있다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 is_irq = (cause & MCAUSE_INT);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
is_irq = (cause & MCAUSE_INT);cause 최상위 bit만 분리해 asynchronous interrupt 여부를 만든다. 나머지 bit는 독립적인 cause number다.
irq = (cause & ~MCAUSE_INT);interrupt marker를 제거해 external/timer interrupt switch에 사용할 작은 cause code를 얻는다.
(빈 줄)irq = (cause & ~MCAUSE_INT);까지의 동작과 if (is_irq) {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
if (is_irq) {is_irq를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.
switch (irq) {원본 219번 줄의 switch (irq) {는 앞의 if (is_irq) { 결과를 받아 다음 case IRQ_M_EXT:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
case IRQ_M_EXT:원본 220번 줄의 case IRQ_M_EXT:는 앞의 switch (irq) { 결과를 받아 다음 case IRQ_S_EXT:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
case IRQ_S_EXT:원본 221번 줄의 case IRQ_S_EXT:는 앞의 case IRQ_M_EXT: 결과를 받아 다음 external_interrupt(0); /* handle external interrupt */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
external_interrupt(0); /* handle external interrupt */external_interrupt(0)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
break;'break;'로 직선 경로를 벗어난다. 이동 대상에서 relocated image / .rela.dyn / trap frame에 걸린 lock, allocation, list 등록을 어디까지 되돌리는지 이어서 확인한다.
case IRQ_M_TIMER:원본 224번 줄의 case IRQ_M_TIMER:는 앞의 break; 결과를 받아 다음 case IRQ_S_TIMER:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
case IRQ_S_TIMER:원본 225번 줄의 case IRQ_S_TIMER:는 앞의 case IRQ_M_TIMER: 결과를 받아 다음 timer_interrupt(0); /* handle timer interrupt */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
timer_interrupt(0); /* handle timer interrupt */timer_interrupt(0)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
break;'break;'로 직선 경로를 벗어난다. 이동 대상에서 relocated image / .rela.dyn / trap frame에 걸린 lock, allocation, list 등록을 어디까지 되돌리는지 이어서 확인한다.
default:'default' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
_exit_trap(cause, epc, tval, regs);_exit_trap(cause, epc, tval, regs)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
break;'break;'로 직선 경로를 벗어난다. 이동 대상에서 relocated image / .rela.dyn / trap frame에 걸린 lock, allocation, list 등록을 어디까지 되돌리는지 이어서 확인한다.
};현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
} else {원본 232번 줄의 } else {는 앞의 }; 결과를 받아 다음 _exit_trap(cause, epc, tval, regs);로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.
_exit_trap(cause, epc, tval, regs);_exit_trap(cause, epc, tval, regs)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 return epc;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
return epc;assembly 복귀 코드가 CSR EPC에 다시 쓸 재개 주소를 반환한다. breakpoint semihosting 경로는 이 값을 4만큼 전진시킬 수 있다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*Entry Point for PLIC Interrupt Handler원본 주석이 'Entry Point for PLIC Interrupt Handler'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
__attribute__((weak)) void external_interrupt(struct pt_regs *regs)external_interrupt(struct pt_regs *regs)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 relocated image / .rela.dyn / trap frame의 수명에 맞춰 묶어 읽는다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 __attribute__((weak)) void timer_interrupt(struct pt_regs *regs)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
__attribute__((weak)) void timer_interrupt(struct pt_regs *regs)timer_interrupt(struct pt_regs *regs)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 relocated image / .rela.dyn / trap frame의 수명에 맞춰 묶어 읽는다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.
DETAILS
내부 동작을 더 깊게 읽기
진입 조건을 먼저 고정한다
new stack gd destination에서 들어온 실행은 boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에 놓여 있다. 이때 interrupt, MMU/cache, stack, heap 중 무엇이 이미 준비되었는지 소스의 호출자까지 올라가 확인한다. 같은 함수라도 SPL, relocation 전후, app thread처럼 호출 문맥이 달라지면 허용되는 API와 지연 시간이 달라진다.
image byte copy와 pointer relocation, trap-vector relocation, secondary hart relocation은 서로 다른 작업이다. 각 단계가 새 주소를 게시하는 순서와 cache/fence가 다른 hart의 instruction fetch보다 앞서는지 확인한다.
중심 객체의 생성과 공개를 나눈다
이 글의 중심 객체는 relocated image / .rela.dyn / trap frame다. 메모리를 확보한 시점, 필드를 채운 시점, 전역 list나 다른 subsystem에 공개한 시점을 구분한다. 공개 뒤 오류가 발생한다면 목록에서 제거하고 child, buffer, reference를 역순으로 정리하는지 확인한다.
빌드 산출물 관점에서는 DRAM의 relocated RISC-V U-Boot 안에 해당 symbol과 section이 실제로 포함되었는지도 map과 objdump로 검증한다.
주소, 크기와 정렬을 계산한다
부트 코드의 오류는 논리보다 주소 계산에서 먼저 드러나는 경우가 많다. source range, destination range, header가 말하는 payload size, block 또는 page 단위를 표로 적고 각 구간의 끝 주소를 직접 계산한다. 끝 주소는 start + size - 1인지 exclusive end인지 API 계약을 확인한다.
board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다.
성공 flag와 실제 완료 시점을 맞춘다
copy text and data → apply .rela.dyn → relocate tvec → clear BSS → relocate secondary harts → cache maintenance 구간에서는 부분 초기화 상태가 생길 수 있다. flag, list insertion, callback 등록, storage write 완료 중 무엇이 성공의 기준인지 찾는다. hardware write나 DMA가 포함되면 함수 반환과 장치 완료가 같은 시점인지도 확인한다.
다른 CPU, interrupt handler, USB completion 또는 shell command가 상태를 관찰할 수 있다면 memory ordering과 lock 범위도 함께 읽는다.
마지막 handoff의 계약을 적는다
정상 경로는 board_init_r에서 끝난다. 이 단계가 함수 반환인지, scheduler 전환인지, 다른 image로의 비복귀 분기인지 구분한다. 비복귀 handoff라면 cache clean/invalidate, interrupt disable, 장치 quiesce, argument register와 FDT 또는 image address가 최종 점검 항목이다.
반환하는 경로라면 caller가 오류와 부분 성공을 구분하고 다음 후보 또는 복구 경로를 선택하는지 확인한다.
IMPLEMENTATION NOTES
구현을 읽을 때 놓치기 쉬운 부분
relocate_code의 s2, s3, s4는 새 stack, gd, destination을 끝까지 보존한다
RISC-V ABI의 saved register를 사용해 new SP, new gd, destination을 copy와 relocation loop 사이에 보존한다. 함수 호출과 macro가 임시 register를 덮어도 최종 board_init_r argument를 복구할 수 있어야 한다.
GDB에서 entry 직후 s2/s3/s4와 gd->relocaddr, gd->start_addr_sp를 비교하면 caller argument 오류와 내부 copy 오류를 분리할 수 있다.
copy 범위는 _start부터 __bss_start까지이며 BSS는 별도로 0으로 만든다
linker image byte가 존재하는 text/rodata/data를 destination으로 옮기고, file payload가 없는 BSS는 copy 뒤 새 주소에서 clear한다. source/destination overlap과 copy 방향은 address 비교로 검증해야 한다.
SPL/TPL 또는 XIP 설정에서는 section 배치가 달라질 수 있으므로 symbol 차이를 고정 상수로 쓰지 않고 최종 ELF에서 읽는다.
.rela.dyn은 R_RISCV_RELATIVE와 absolute symbol relocation을 구분한다
relocation loop는 entry type을 읽어 relative relocation이면 addend에 destination base를 더하고, R_RISCV_32/64이면 symbol value와 relocation offset을 조합한다. 지원하지 않는 type을 조용히 같은 방식으로 처리하면 pointer 일부만 손상될 수 있다.
readelf -rW u-boot 출력의 offset, type, addend를 몇 개 골라 relocation 전 source word와 relocation 후 destination word를 계산해 본다. 이 계산이 source 분석의 가장 직접적인 검증이다.
tvec도 relocation offset만큼 옮겨야 새 image에서 trap을 받는다
copy와 fixup 도중 trap vector는 old image의 trap_entry를 가리킨다. old image를 더 이상 정상 runtime으로 사용하지 않기 전에 tvec에 relocation offset을 반영해 새 handler address로 바꾼다.
이 줄이 빠지면 relocation 후 fault가 발생했을 때 old flash/SRAM handler로 되돌아가 새 stack/gd와 섞인다. 정상 부팅에서는 숨고 오류 경로에서만 나타나는 전형적인 stale entry 문제다.
trap_entry의 register frame은 32개 GPR과 cause, epc, tval을 묶는다
mtrap.S는 stack에 32 register 폭의 frame을 잡고 x1부터 x31을 저장한다. cause, epc, tval과 frame pointer를 C handle_trap에 넘기며 반환된 epc를 CSR에 다시 쓴 뒤 register를 복원하고 mret/sret한다.
trap handler가 stack이 없는 시점에 호출될 가능성을 따져야 한다. 초기 tvec 설치 전후, per-hart stack 선택 전후의 exception은 같은 handler라도 frame을 놓을 안전한 memory가 다르다.
secondary relocation은 boot hart copy 완료 뒤 IPI와 fence로 이어진다
boot hart가 destination image와 BSS를 완성한 뒤 secondary hart에 relocation 정보를 게시하고 IPI를 보낸다. secondary는 secondary_hart_loop에서 software interrupt를 확인하고 handler가 제공한 새 실행 위치로 이동한다.
I-cache invalidate와 D-cache flush는 copy byte가 다른 hart의 instruction fetch에 보이기 전에 끝나야 한다. coherent system이라고 가정해 barrier를 빼지 말고 architecture cache helper의 실제 구현을 확인한다.
board_init_r로 가는 jr은 relocation 함수의 마지막 소유권 이전이다
새 board_init_r 주소를 destination offset으로 계산하고 a0에 new gd, a1에 destination을 넣은 뒤 jr로 비복귀 분기한다. 이후 old stack, old gd, source image를 runtime 객체처럼 참조해서는 안 된다.
분기 직전 PC target, SP, gp, tvec, gd, BSS sample, relocation target sample을 한 번에 기록하면 relocation 완료 여부를 객관적으로 판정할 수 있다.
R_RISCV_RELATIVE entry 한 건을 destination 기준으로 계산한다
source base가 0x80000000, destination base가 0x87e00000이면 t6=0x07e00000이다. relocation offset 0x8006c018의 patch address는 0x87e6c018, link-time addend 0x8002a430에 t6를 더한 기록 값은 0x87e2a430이다. RV64라면 8-byte word 전체를 기록하고 alignment와 endianness를 확인한다.
readelf -rW u-boot 결과에서 선택한 entry를 relocation loop 직전/직후 memory와 비교한다. source word가 아니라 destination word가 바뀌어야 하며, 같은 entry가 두 번 적용되면 base가 중복 가산된다.
trap frame offset은 XLEN과 pt_regs 정의로 검산한다
RV64에서 register slot이 8 byte이고 x1부터 x31까지 31개를 저장하면 GPR 영역만 248 byte다. cause, epc, tval과 helper argument slot이 추가되므로 실제 frame size는 mtrap.S의 PT_SIZE_ON_STACK과 C struct pt_regs offset을 기준으로 계산해야 한다.
예를 들어 epc slot을 잘못 8 byte 밀면 C handler는 cause를 epc로 읽고 return 시 잘못된 PC로 mret/sret한다. assembly macro를 펼친 offset, generated asm-offsets와 debugger memory dump 세 가지를 대조한다.
tvec와 I-cache publication을 secondary hart 관점에서 확인한다
old trap_entry=0x80008100, relocation offset=0x07e00000이면 새 tvec는 0x87e08100이다. 이 source는 정렬된 trap_entry + t6를 그대로 쓰므로 하위 2bit가 0인 direct mode를 새로 선택한다. vectored mode를 유지하도록 확장한다면 기존 mode bit를 주소 계산과 분리해 다시 합쳐야 한다.
boot hart의 data copy 완료, relocation fixup, D-cache clean, fence.i 또는 remote I-cache invalidation, new entry/tvec 게시, IPI의 순서를 표로 만든다. secondary가 IPI는 받았지만 old instruction을 실행하는 문제는 이 순서 중 cache visibility가 빠졌을 때 생긴다.
OBJECT LIFETIME
객체와 수명
| 대상 | 만들어지는 시점 | 유효 범위 | 확인할 조건 |
|---|---|---|---|
relocated image / .rela.dyn / trap frame | copy text and data | board_init_r 또는 오류 정리 완료까지 | board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다. |
| 입력 buffer / metadata | new stack gd destination | parse와 검증이 끝날 때까지 | 길이, 정렬, 소유권, 변조 가능성 |
| 등록된 list / descriptor | apply .rela.dyn | subsystem 종료 또는 image handoff까지 | 이중 등록, dangling pointer, 오류 unwind |
| hardware 또는 persistent state | 실제 write/probe가 완료된 뒤 | reset 또는 명시적 해제·갱신까지 | flush, timeout, 전원 차단, rollback |
DRAM의 relocated RISC-V U-Boot | link/image 생성 시점 | 다음 stage가 새 image로 교체할 때까지 | load address, entry, section과 header 일치 |
FAILURE PATH
실패 지점과 증상
| # | 조건 | 관찰되는 증상 | 먼저 볼 단계 |
|---|---|---|---|
| 01 | rela type 처리 오류 | 특정 global pointer만 old address | copy text and data |
| 02 | tvec 미보정 | relocation 후 fault가 old handler로 진입 | apply .rela.dyn |
| 03 | secondary IPI 순서 오류 | old code 또는 0 stack 실행 | relocate tvec |
| 04 | cache maintenance 누락 | hart별 stale instruction | clear BSS |
로그가 끊긴 마지막 함수만 고치지 않는다. 그 함수가 받은 주소, size, flag가 만들어진 앞 단계까지 올라가고, 오류 뒤 등록 객체와 hardware 상태가 남았는지도 확인한다.
EVIDENCE
소스 밖에서 확인할 증거
소스 해석은 실제 빌드 산출물과 target 로그로 닫아야 한다. 아래 명령의 보드 이름과 toolchain prefix는 사용 중인 빌드 환경에 맞게 바꾼다.
| # | 목적 | 명령 또는 계측 | 판정 기준 |
|---|---|---|---|
| 01 | 전처리 결과 | make <board>_defconfig && make V=1 | 선택된 CONFIG와 실제 compile/link 명령을 보관한다. |
| 02 | symbol과 section | ${CROSS_COMPILE}nm -n u-boot | grep 'relocate_code' | symbol 주소가 기대한 stage와 image에 들어갔는지 확인한다. |
| 03 | 어셈블리 대조 | ${CROSS_COMPILE}objdump -drS u-boot | C 한 줄이 실제 branch, relocation, memory access로 어떻게 바뀌었는지 본다. |
| 04 | 주소 배치 | bdinfo | relocaddr, malloc, FDT, stack과 DRAM bank 주소를 한 번에 기록한다. |
| 05 | runtime 관찰 | dm tree; dm uclass; printenv | driver model과 환경이 이 단계에서 실제로 공개되었는지 확인한다. |
LAB
직접 확인할 실험
- 01readelf -rW에서 RELATIVE entry 손계산
copy text and data진입 전후에 s2 new SP의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다. 조건이 처음 깨지는 줄을 기록한다. - 02trap_entry frame에서 cause/epc/tval과 x1-x31 확인
apply .rela.dyn진입 전후에 s3 new gd의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다. 조건이 처음 깨지는 줄을 기록한다. - 03분기 직전 PC/SP/gp/tvec/gd snapshot
relocate tvec진입 전후에 s4 destination의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다. 조건이 처음 깨지는 줄을 기록한다. - 04secondary_hart_loop와 IPI handler timestamp 대조
clear BSS진입 전후에 rela entries의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다. 조건이 처음 깨지는 줄을 기록한다.
PRIMARY REFERENCES