← Bootloader DUJINLABS.COM

U-Boot · source analysis

RISC-V relocation, trap frame과 secondary hart

relocate_code가 U-Boot image와 `.rela.dyn`, BSS, tvec를 새 주소로 옮기고 secondary hart와 cache를 동기화한 뒤 board_init_r로 넘어가는 경계를 읽습니다.

Series
22 / 22
Baseline
v2026.07
Commit
ece349ade297
Source
arch/riscv/cpu/start.S:269

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에서 실행 중이지 않아야 한다. 이 문장을 기준으로 코드를 위에서 아래로 읽으면, 함수 이름을 외우는 대신 어느 시점에 어떤 상태를 신뢰할 수 있는지 판단할 수 있다.

객체와 주소가 놓이는 구조

그림 1. RISC-V relocation, trap frame과 secondary hart에서 입력, 내부 상태, 출력의 경계
입력과 전제내부 상태외부로 공개되는 결과
01s2 new SP
02s3 new gd
03s4 destination
04rela entries
05new trap vector
06secondary IPI
07relocated runtime
INVARIANT

board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다.

relocated image / .rela.dyn / trap frame를 중심에 놓고 왼쪽의 입력이 어떤 검사를 거쳐 오른쪽 결과로 공개되는지 표시했다. 실제 디버깅에서는 각 블록의 주소와 크기를 로그에 대입한다.

실행 흐름

그림 2. 정상 경로의 주요 호출과 상태 전달
new stack gd destination
copy text and data
apply .rela.dyn
relocate tvec
clear BSS
relocate secondary harts
cache maintenance
board_init_r

화살표는 단순 호출 순서만 뜻하지 않는다. 각 단계가 성공을 반환할 때 다음 단계가 읽을 수 있는 상태가 무엇인지 함께 확인한다. 오류 반환이 발생하면 바로 다음 화살표로 진행하지 않고 해당 단계의 정리 경로를 따라간다.

단계별 입력과 출력

호출 순서를 함수 이름으로만 외우지 않고, 각 단계가 무엇을 받아 무엇을 공개하는지 적은 표다. 실제 소스에서 생산 필드가 다르면 표를 고치는 방식으로 사용한다.

#단계진입 시 신뢰할 상태성공 뒤 남아야 할 상태다음 소비자
01new stack gd destinationboot hart relocation과 software-interrupt 기반 secondary 대기 문맥s2 new SPcopy text and data
02copy text and datanew stack gd destination 완료 상태s3 new gdapply .rela.dyn
03apply .rela.dyncopy text and data 완료 상태s4 destinationrelocate tvec
04relocate tvecapply .rela.dyn 완료 상태rela entriesclear BSS
05clear BSSrelocate tvec 완료 상태new trap vectorrelocate secondary harts
06relocate secondary hartsclear BSS 완료 상태secondary IPIcache maintenance
07cache maintenancerelocate secondary harts 완료 상태relocated runtimeboard_init_r
08board_init_rcache 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에서 실행 중이지 않아야 한다. 한 단계의 출력이 다음 단계의 입력 조건을 만족하지 않으면 오류가 실제로 드러난 위치보다 앞의 생산 단계부터 조사한다.

주소와 객체의 이동을 그림으로 읽기

아래 그림은 호출 이름보다 주소, 객체 수명과 handoff 경계를 먼저 볼 수 있도록 구성했다. 실제 주소와 크기는 사용 중인 보드의 bdinfo, map과 linker symbol을 대입한다.

그림 3. RISC-V image relocation의 source와 destination

크기는 개념 비율이다. 실제 분석에서는 map, symbol과 runtime 출력의 시작·끝 주소를 대입한다.

높은 주소↓ 주소 감소
new stack + new gds2/s3에 보존된 runtime state
destination text/rodata/data_start부터 __bss_start까지 copy
.rela.dyn targetsdestination base로 pointer 보정
new BSSdestination에서 zero clear
old image + old tvec새 tvec 게시 뒤 수명 종료
낮은 주소예약 cursor 진행 방향
그림 4. relocate_code가 board_init_r까지 닫는 순서

호출 이름만 나열하지 않고, 각 단계가 다음 단계에 넘기는 상태를 기준으로 실행 순서를 표시한다.

01s2/s3/s4 인자 보존
02image copy
03.rela.dyn 순회
04tvec + reloc_off
05BSS clear
06secondary IPI
07cache fence
08jr board_init_r
그림 5. trap 전후 register frame

두 열의 객체는 이름이 비슷해도 생성 시점, 사용 가능한 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 재개

원본 코드

아래 코드는 U-Boot v2026.07의 arch/riscv/cpu/start.S에서 269-460줄을 그대로 가져온 것이다. 설명을 위해 실제 코드를 가짜 의사 코드로 바꾸지 않았다.

commitece349ade2973e220f524ce59e59711cc919263fsymbolrelocate_code:
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

269-460줄 해설

원본에 보이는 모든 줄을 순서대로 설명한다. 빈 줄도 block 경계로 남겨, 코드와 설명의 위치가 어긋나지 않게 했다.

269mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

270mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

271j 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

272#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

273(빈 줄)

#endif까지의 동작과 #if !defined(CONFIG_XPL_BUILD)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 new trap vector 상태가 아래 블록의 입력으로 사용되는 경계다.

274#if !defined(CONFIG_XPL_BUILD)

#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_XPL_BUILD이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

275/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

276* void relocate_code(addr_sp, gd, addr_moni)

원본 주석이 'void relocate_code(addr_sp, gd, addr_moni)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

277*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

278* This "function" does not return, instead it continues in RAM

원본 주석이 'This "function" does not return, instead it continues in RAM'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

279* after relocating the monitor code.

원본 주석이 'after relocating the monitor code.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

280*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

281*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

282.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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

283relocate_code:

a0=new SP, a1=new gd, a2=destination을 받아 RISC-V image copy와 ELF relocation을 시작한다.

284mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

285mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

286mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

287(빈 줄)

mv s4, a2 /* save addr of destination */까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

288/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

289*Set up the stack

원본 주석이 'Set up the stack'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

290*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

291stack_setup:

'stack_setup' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

292#if CONFIG_IS_ENABLED(SMP)

#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

293/* tp: hart id */

원본 주석이 'tp: hart id'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

294slli 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

295sub sp, s2, t0

sub sp, s2, t0 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

296#else

#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

297mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

298#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

299(빈 줄)

#endif까지의 동작과 la t0, _start에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

300la 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

301sub 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로 확인한다.

302beq 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

303(빈 줄)

beq t0, s4, clear_bss /* skip relocation */까지의 동작과 mv t1, s4 /* t1 <- scratch for copy_loop */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

304mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

305la 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

306(빈 줄)

la t2, __bss_start /* t2 <- source end address */까지의 동작과 copy_loop:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

307copy_loop:

'copy_loop' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

308LREG 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

309addi 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

310SREG 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

311addi 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

312blt 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

313(빈 줄)

blt t0, t2, copy_loop까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

314/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

315* Update dynamic relocations after board_init_f

원본 주석이 'Update dynamic relocations after board_init_f'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

316*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

317fix_rela_dyn:

'fix_rela_dyn' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

318la 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

319la 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

320beq 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

321add 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로 확인한다.

322add 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로 확인한다.

323(빈 줄)

add t2, t2, t6 /* t2 <- rela_dyn_end in RAM */까지의 동작과 6:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

3246:

원본 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

325LREG t5, REGBYTES(t1) /* t5 <-- relocation info:type */

REGBYTES(t1)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

326li 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

327bne 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

328LREG 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

329LREG 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

330add 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로 확인한다.

331add 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로 확인한다.

332SREG t5, 0(t3)

계산된 새 pointer를 destination image의 relocation target slot에 XLEN 폭으로 기록한다.

333j 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

334(빈 줄)

j 10f까지의 동작과 8:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

3358:

원본 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

336la 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

337add t4, t4, t6

add t4, t4, t6 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

338(빈 줄)

add t4, t4, t6까지의 동작과 9:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

3399:

원본 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

340srli 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

341andi 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

342li 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

343bne 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

344(빈 줄)

bne t5, t3, 10f /* skip non-addned entries */까지의 동작과 LREG t3, 0(t1)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

345LREG 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

346li 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

347mul 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

348add s5, t4, t0

add s5, t4, t0 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

349LREG 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

350LREG t5, REGBYTES(s5)

REGBYTES(s5)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

351add t5, t5, t0

add t5, t5, t0 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

352add 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로 확인한다.

353add 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로 확인한다.

354SREG t5, 0(t3)

계산된 새 pointer를 destination image의 relocation target slot에 XLEN 폭으로 기록한다.

35510:

원본 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

356addi 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

357blt 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

358(빈 줄)

blt t1, t2, 6b까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

359/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

360* trap update

원본 주석이 'trap update'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

361*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

362la 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

363add t0, t0, t6

old image의 trap_entry link address를 destination handler address로 바꾼다. 바로 뒤 CSR write 전에 mode bit 처리도 확인한다.

364csrw MODE_PREFIX(tvec), t0

trap vector에도 relocation offset을 반영해 새 image handler를 가리키게 한다.

365(빈 줄)

csrw MODE_PREFIX(tvec), t0까지의 동작과 clear_bss:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

366clear_bss:

'clear_bss' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

367la 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

368add 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로 확인한다.

369la 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

370add 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로 확인한다.

371beq 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

372(빈 줄)

beq t0, t1, relocate_secondary_harts까지의 동작과 clbss_l:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

373clbss_l:

'clbss_l' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

374SREG 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

375addi 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

376blt 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

377(빈 줄)

blt t0, t1, clbss_l까지의 동작과 relocate_secondary_harts:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

378relocate_secondary_harts:

'relocate_secondary_harts' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

379#if CONFIG_IS_ENABLED(SMP)

#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

380/* send relocation IPI */

원본 주석이 'send relocation IPI'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

381la 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

382add a0, t0, t6

add a0, t0, t6 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

383(빈 줄)

add a0, t0, t6까지의 동작과 /* store relocation offset */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

384/* store relocation offset */

원본 주석이 'store relocation offset'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

385mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

386(빈 줄)

mv s5, t6까지의 동작과 mv a1, s2에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

387mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

388mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

389mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

390jal 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

391(빈 줄)

jal smp_call_function까지의 동작과 /* hang if relocation of secondary harts has failed */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

392/* hang if relocation of secondary harts has failed */

원본 주석이 'hang if relocation of secondary harts has failed'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

393beqz 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

394mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

395la 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

396jal 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

397jal 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

398(빈 줄)

jal hang까지의 동작과 /* restore relocation offset */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

399/* restore relocation offset */

원본 주석이 'restore relocation offset'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

4001: 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

401#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

402(빈 줄)

#endif까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

403/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

404* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

405* initialization, now running from RAM.

원본 주석이 'initialization, now running from RAM.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

406*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

407call_board_init_r:

'call_board_init_r' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

408jal 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

409jal 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

410la t0, board_init_r /* offset of board_init_r() */

board_init_r(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

411add t4, t0, t6 /* real address of board_init_r() */

board_init_r(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

412/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

413* setup parameters for board_init_r

원본 주석이 'setup parameters for board_init_r'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

414*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

415mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

416mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

417mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

418(빈 줄)

mv s0, zero /* fp == NULL */까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

419/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

420* jump to it ...

원본 주석이 'jump to it ...'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

421*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

422jr t4 /* jump to board_init_r() */

board_init_r(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

423#endif /* !defined(CONFIG_XPL_BUILD) */

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_XPL_BUILD이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

424(빈 줄)

#endif /* !defined(CONFIG_XPL_BUILD) */까지의 동작과 #if CONFIG_IS_ENABLED(SMP)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

425#if CONFIG_IS_ENABLED(SMP)

#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

426hart_out_of_bounds_loop:

'hart_out_of_bounds_loop' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

427/* Harts in this loop are out of bounds, increase CONFIG_NR_CPUS. */

원본 주석이 'Harts in this loop are out of bounds, increase CONFIG_NR_CPUS.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

428wfi

원본 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

429j 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

430(빈 줄)

j hart_out_of_bounds_loop까지의 동작과 /* SMP relocation entry */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

431/* SMP relocation entry */

원본 주석이 'SMP relocation entry'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

432secondary_hart_relocate:

'secondary_hart_relocate' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

433/* a1: new sp */

원본 주석이 'a1: new sp'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

434/* a2: new gd */

원본 주석이 'a2: new gd'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

435/* tp: hart id */

원본 주석이 'tp: hart id'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

436(빈 줄)

/* tp: hart id */까지의 동작과 /* setup stack */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

437/* setup stack */

원본 주석이 'setup stack'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

438slli 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

439sub sp, a1, t0

sub sp, a1, t0 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

440(빈 줄)

sub sp, a1, t0까지의 동작과 /* update global data pointer */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

441/* update global data pointer */

원본 주석이 'update global data pointer'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

442mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

443#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

444(빈 줄)

#endif까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

445/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

446* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

447* 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.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

448*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

449secondary_hart_loop:

'secondary_hart_loop' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

450wfi

원본 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

451(빈 줄)

wfi까지의 동작과 #if CONFIG_IS_ENABLED(SMP)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

452#if CONFIG_IS_ENABLED(SMP)

#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

453csrr t0, MODE_PREFIX(ip)

MODE_PREFIX(ip)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

454#if CONFIG_IS_ENABLED(RISCV_MMODE)

#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

455andi 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

456#else

#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

457andi 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

458#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

459beqz 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

460(빈 줄)

beqz t0, secondary_hart_loop까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

함께 읽어야 하는 원본 코드

첫 코드 조각만으로 동작이 완성되지 않는 경우 호출 매크로, 객체 정의와 실제 실행 목록을 같은 페이지에 묶었다. 각 조각은 같은 기준 commit에서 가져왔다.

01. trap frame을 저장하고 handle_trap으로 넘기는 entry

arch/riscv/cpu/mtrap.S 18-104줄이다.

filearch/riscv/cpu/mtrap.Sanchortrap_entry:
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줄 해설

18#define SREG sw

#define 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

19#define REGBYTES 4

#define 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

20#else

#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

21#define LREG ld

#define 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

22#define SREG sd

#define 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

23#define REGBYTES 8

#define 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

24#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

25(빈 줄)

#endif까지의 동작과 .text에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

26.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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

27(빈 줄)

.text까지의 동작과 /* trap entry */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

28/* trap entry */

원본 주석이 'trap entry'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

29.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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

30.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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

31trap_entry:

'trap_entry' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

32addi 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

33SREG x1, 1 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

34SREG x2, 2 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

35SREG x3, 3 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

36SREG x4, 4 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

37SREG x5, 5 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

38SREG x6, 6 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

39SREG x7, 7 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

40SREG x8, 8 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

41SREG x9, 9 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

42SREG x10, 10 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

43SREG x11, 11 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

44SREG x12, 12 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

45SREG x13, 13 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

46SREG x14, 14 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

47SREG x15, 15 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

48SREG x16, 16 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

49SREG x17, 17 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

50SREG x18, 18 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

51SREG x19, 19 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

52SREG x20, 20 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

53SREG x21, 21 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

54SREG x22, 22 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

55SREG x23, 23 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

56SREG x24, 24 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

57SREG x25, 25 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

58SREG x26, 26 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

59SREG x27, 27 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

60SREG x28, 28 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

61SREG x29, 29 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

62SREG x30, 30 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

63SREG x31, 31 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

64csrr a0, MODE_PREFIX(cause)

MODE_PREFIX(cause)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

65csrr a1, MODE_PREFIX(epc)

MODE_PREFIX(epc)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

66csrr a2, MODE_PREFIX(tval)

MODE_PREFIX(tval)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

67mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

68jal 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

69csrw MODE_PREFIX(epc), a0

MODE_PREFIX(epc)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

70(빈 줄)

csrw MODE_PREFIX(epc), a0까지의 동작과 LREG x1, 1 * REGBYTES(sp)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

71LREG x1, 1 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

72LREG x3, 3 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

73LREG x4, 4 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

74LREG x5, 5 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

75LREG x6, 6 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

76LREG x7, 7 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

77LREG x8, 8 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

78LREG x9, 9 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

79LREG x10, 10 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

80LREG x11, 11 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

81LREG x12, 12 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

82LREG x13, 13 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

83LREG x14, 14 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

84LREG x15, 15 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

85LREG x16, 16 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

86LREG x17, 17 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

87LREG x18, 18 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

88LREG x19, 19 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

89LREG x20, 20 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

90LREG x21, 21 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

91LREG x22, 22 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

92LREG x23, 23 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

93LREG x24, 24 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

94LREG x25, 25 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

95LREG x26, 26 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

96LREG x27, 27 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

97LREG x28, 28 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

98LREG x29, 29 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

99LREG x30, 30 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

100LREG x31, 31 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

101LREG x2, 2 * REGBYTES(sp)

REGBYTES(sp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

102addi 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

103MODE_PREFIX(ret)

MODE_PREFIX(ret)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

104(빈 줄)

MODE_PREFIX(ret)까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

02. secondary hart의 IPI 대기와 dispatch

arch/riscv/cpu/start.S 437-466줄이다.

filearch/riscv/cpu/start.Sanchorsecondary_hart_loop:
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줄 해설

437/* setup stack */

원본 주석이 'setup stack'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

438slli 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

439sub sp, a1, t0

sub sp, a1, t0 명령이 register, memory 또는 PC를 직접 바꾼다. boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

440(빈 줄)

sub sp, a1, t0까지의 동작과 /* update global data pointer */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 rela entries 상태가 아래 블록의 입력으로 사용되는 경계다.

441/* update global data pointer */

원본 주석이 'update global data pointer'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

442mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

443#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

444(빈 줄)

#endif까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

445/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

446* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

447* 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.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

448*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

449secondary_hart_loop:

'secondary_hart_loop' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

450wfi

원본 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

451(빈 줄)

wfi까지의 동작과 #if CONFIG_IS_ENABLED(SMP)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

452#if CONFIG_IS_ENABLED(SMP)

#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

453csrr t0, MODE_PREFIX(ip)

MODE_PREFIX(ip)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

454#if CONFIG_IS_ENABLED(RISCV_MMODE)

#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

455andi 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

456#else

#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

457andi 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

458#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

459beqz 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

460(빈 줄)

beqz t0, secondary_hart_loop까지의 동작과 mv a0, tp에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

461mv 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

462jal 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

463#endif

#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.

464(빈 줄)

#endif까지의 동작과 j secondary_hart_loop에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

465j 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

466(빈 줄)

j secondary_hart_loop까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

03. RISC-V U-Boot linker section과 relocation table 배치

arch/riscv/cpu/u-boot.lds 46-90줄이다.

filearch/riscv/cpu/u-boot.ldsanchor.rela.dyn
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줄 해설

46. = ALIGN(4);

.ALIGN(4)를 = 연산으로 반영해 relocated image / .rela.dyn / trap frame와 연결된 field를 갱신한다. 바로 다음 다음 block 경계가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

47(빈 줄)

. = ALIGN(4);까지의 동작과 __u_boot_list : {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 s3 new gd 상태가 아래 블록의 입력으로 사용되는 경계다.

48__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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

49KEEP(*(SORT(__u_boot_list*)));

SORT(__u_boot_list*)))를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 rela entries 항목과 대조한다.

50}

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

51(빈 줄)

}까지의 동작과 . = ALIGN(8);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary IPI 상태가 아래 블록의 입력으로 사용되는 경계다.

52. = ALIGN(8);

.ALIGN(8)를 = 연산으로 반영해 relocated image / .rela.dyn / trap frame와 연결된 field를 갱신한다. 바로 다음 다음 block 경계가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

53(빈 줄)

. = ALIGN(8);까지의 동작과 .efi_runtime_rel : {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

54.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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

55__efi_runtime_rel_start = .;

__efi_runtime_rel_start.를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

56*(.rel*.efi_runtime)

원본 주석이 '(.rel*.efi_runtime)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

57*(.rel*.efi_runtime.*)

원본 주석이 '(.rel*.efi_runtime.*)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

58__efi_runtime_rel_stop = .;

__efi_runtime_rel_stop.를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

59}

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

60(빈 줄)

}까지의 동작과 __image_copy_end = .;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

61__image_copy_end = .;

__image_copy_end.를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

62(빈 줄)

__image_copy_end = .;까지의 동작과 /DISCARD/ : { *(.rela.plt*) }에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

63/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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

64.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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

65__rel_dyn_start = .;

__rel_dyn_start.를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

66*(.rela*)

원본 주석이 '(.rela*)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

67__rel_dyn_end = .;

__rel_dyn_end.를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

68}

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

69(빈 줄)

}까지의 동작과 . = ALIGN(8);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

70. = ALIGN(8);

.ALIGN(8)를 = 연산으로 반영해 relocated image / .rela.dyn / trap frame와 연결된 field를 갱신한다. 바로 다음 다음 block 경계가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

71(빈 줄)

. = ALIGN(8);까지의 동작과 .dynsym : {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

72.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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

73__dyn_sym_start = .;

__dyn_sym_start.를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

74*(.dynsym)

원본 주석이 '(.dynsym)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

75__dyn_sym_end = .;

__dyn_sym_end.를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

76. = ALIGN(8);

.ALIGN(8)를 = 연산으로 반영해 relocated image / .rela.dyn / trap frame와 연결된 field를 갱신한다. 바로 다음 }가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

77}

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

78(빈 줄)

}까지의 동작과 _end = .;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

79_end = .;

_end.를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

80(빈 줄)

_end = .;까지의 동작과 .bss : {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

81.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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

82__bss_start = .;

__bss_start.를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

83*(.bss*)

원본 주석이 '(.bss*)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

84. = ALIGN(8);

.ALIGN(8)를 = 연산으로 반영해 relocated image / .rela.dyn / trap frame와 연결된 field를 갱신한다. 바로 다음 __bss_end = .;가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

85__bss_end = .;

__bss_end.를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 주소·크기 값이면 단위와 정렬, 덧셈 overflow를 함께 검산한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

86}

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

87}

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

88(빈 줄)

}까지의 동작과 ASSERT(_end % 8 == 0, "_end must be 8-byte aligned for device tree");에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

89ASSERT(_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를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

90(빈 줄)

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줄이다.

filearch/riscv/lib/interrupts.canchorulong handle_trap(ulong cause
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줄 해설

177/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

178* disable interrupts

원본 주석이 'disable interrupts'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

179*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

180int disable_interrupts(void)

disable_interrupts 함수 정의가 시작된다. 입력은 void이며, boot hart relocation과 software-interrupt 기반 secondary 대기 문맥에서 호출된다는 전제로 반환 전까지의 상태 변화를 읽는다.

181{

바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 relocated image / .rela.dyn / trap frame의 수명에 맞춰 묶어 읽는다.

182return 0;

0를 호출자에게 반환한다. caller가 이 값을 검사한 뒤 부분 초기화된 relocated image / .rela.dyn / trap frame를 정리하거나 다음 단계로 진행하는지 확인한다.

183}

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

184(빈 줄)

}까지의 동작과 ulong handle_trap(ulong cause, ulong epc, ulong tval, struct pt_regs *regs)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

185ulong 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 대기 문맥에서 호출된다는 전제로 반환 전까지의 상태 변화를 읽는다.

186{

바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 relocated image / .rela.dyn / trap frame의 수명에 맞춰 묶어 읽는다.

187ulong 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 등록까지 따라간다.

188(빈 줄)

ulong is_irq, irq;까지의 동작과 /* An UEFI application may have changed gd. Restore U-Boot's gd. */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

189/* An UEFI application may have changed gd. Restore U-Boot's gd. */

원본 주석이 'An UEFI application may have changed gd. Restore U-Boot's gd.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

190efi_restore_gd();

efi_restore_gd(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

191(빈 줄)

efi_restore_gd();까지의 동작과 if (cause == CAUSE_BREAKPOINT &&에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

192if (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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

193CONFIG_IS_ENABLED(SEMIHOSTING_FALLBACK)) {

CONFIG_IS_ENABLED(SEMIHOSTING_FALLBACK))를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

194ulong 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 등록까지 따라간다.

195(빈 줄)

ulong pre_addr = epc - 4, post_addr = epc + 4;까지의 동작과 /* Check for prior and post addresses to be in same page. */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

196/* Check for prior and post addresses to be in same page. */

원본 주석이 'Check for prior and post addresses to be in same page.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

197if ((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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

198(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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

199u32 pre = *(u32 *)pre_addr;

u32 pre*(u32 *)pre_addr를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

200u32 post = *(u32 *)post_addr;

u32 post*(u32 *)post_addr를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 board_init_r 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

201(빈 줄)

u32 post = *(u32 *)post_addr;까지의 동작과 /* Check for semihosting, i.e.:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

202/* Check for semihosting, i.e.:

원본 주석이 'Check for semihosting, i.e.:'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

203* slli zero,zero,0x1f

원본 주석이 'slli zero,zero,0x1f'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

204* ebreak

원본 주석이 'ebreak'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

205* srai zero,zero,0x7

원본 주석이 'srai zero,zero,0x7'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

206*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

207if (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까지 따라간다.

208disable_semihosting();

disable_semihosting(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

209epc += 4;

epc4를 += 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 바로 다음 return epc;가 이 값을 다시 읽으므로 그 전까지 완성된 값이어야 한다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

210return epc;

assembly 복귀 코드가 CSR EPC에 다시 쓸 재개 주소를 반환한다. breakpoint semihosting 경로는 이 값을 4만큼 전진시킬 수 있다.

211}

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

212}

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

213}

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

214(빈 줄)

}까지의 동작과 is_irq = (cause & MCAUSE_INT);에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

215is_irq = (cause & MCAUSE_INT);

cause 최상위 bit만 분리해 asynchronous interrupt 여부를 만든다. 나머지 bit는 독립적인 cause number다.

216irq = (cause & ~MCAUSE_INT);

interrupt marker를 제거해 external/timer interrupt switch에 사용할 작은 cause code를 얻는다.

217(빈 줄)

irq = (cause & ~MCAUSE_INT);까지의 동작과 if (is_irq) {에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

218if (is_irq) {

is_irq를 검사해 진행 여부를 가른다. 거짓 경로와 참 경로 중 어느 쪽이 'board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다' 조건을 보존하는지 다음 return 또는 goto까지 따라간다.

219switch (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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

220case 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

221case 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

222external_interrupt(0); /* handle external interrupt */

external_interrupt(0)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

223break;

'break;'로 직선 경로를 벗어난다. 이동 대상에서 relocated image / .rela.dyn / trap frame에 걸린 lock, allocation, list 등록을 어디까지 되돌리는지 이어서 확인한다.

224case 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

225case 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

226timer_interrupt(0); /* handle timer interrupt */

timer_interrupt(0)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

227break;

'break;'로 직선 경로를 벗어난다. 이동 대상에서 relocated image / .rela.dyn / trap frame에 걸린 lock, allocation, list 등록을 어디까지 되돌리는지 이어서 확인한다.

228default:

'default' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.

229_exit_trap(cause, epc, tval, regs);

_exit_trap(cause, epc, tval, regs)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

230break;

'break;'로 직선 경로를 벗어난다. 이동 대상에서 relocated image / .rela.dyn / trap frame에 걸린 lock, allocation, list 등록을 어디까지 되돌리는지 이어서 확인한다.

231};

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

232} 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에서 실행 중이지 않아야 한다' 조건이 유지되는지 확인한다.

233_exit_trap(cause, epc, tval, regs);

_exit_trap(cause, epc, tval, regs)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

234}

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

235(빈 줄)

}까지의 동작과 return epc;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

236return epc;

assembly 복귀 코드가 CSR EPC에 다시 쓸 재개 주소를 반환한다. breakpoint semihosting 경로는 이 값을 4만큼 전진시킬 수 있다.

237}

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

238(빈 줄)

}까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

239/*

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

240*Entry Point for PLIC Interrupt Handler

원본 주석이 'Entry Point for PLIC Interrupt Handler'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

241*/

원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

242__attribute__((weak)) void external_interrupt(struct pt_regs *regs)

external_interrupt(struct pt_regs *regs)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

243{

바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 relocated image / .rela.dyn / trap frame의 수명에 맞춰 묶어 읽는다.

244}

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

245(빈 줄)

}까지의 동작과 __attribute__((weak)) void timer_interrupt(struct pt_regs *regs)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

246__attribute__((weak)) void timer_interrupt(struct pt_regs *regs)

timer_interrupt(struct pt_regs *regs)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 relocated image / .rela.dyn / trap frame의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 relocated runtime 항목과 대조한다.

247{

바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 relocated image / .rela.dyn / trap frame의 수명에 맞춰 묶어 읽는다.

248}

현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.

249(빈 줄)

}까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 relocated runtime 상태가 아래 블록의 입력으로 사용되는 경계다.

내부 동작을 더 깊게 읽기

01

진입 조건을 먼저 고정한다

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보다 앞서는지 확인한다.

02

중심 객체의 생성과 공개를 나눈다

이 글의 중심 객체는 relocated image / .rela.dyn / trap frame다. 메모리를 확보한 시점, 필드를 채운 시점, 전역 list나 다른 subsystem에 공개한 시점을 구분한다. 공개 뒤 오류가 발생한다면 목록에서 제거하고 child, buffer, reference를 역순으로 정리하는지 확인한다.

빌드 산출물 관점에서는 DRAM의 relocated RISC-V U-Boot 안에 해당 symbol과 section이 실제로 포함되었는지도 map과 objdump로 검증한다.

03

주소, 크기와 정렬을 계산한다

부트 코드의 오류는 논리보다 주소 계산에서 먼저 드러나는 경우가 많다. 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에서 실행 중이지 않아야 한다.

04

성공 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 범위도 함께 읽는다.

05

마지막 handoff의 계약을 적는다

정상 경로는 board_init_r에서 끝난다. 이 단계가 함수 반환인지, scheduler 전환인지, 다른 image로의 비복귀 분기인지 구분한다. 비복귀 handoff라면 cache clean/invalidate, interrupt disable, 장치 quiesce, argument register와 FDT 또는 image address가 최종 점검 항목이다.

반환하는 경로라면 caller가 오류와 부분 성공을 구분하고 다음 후보 또는 복구 경로를 선택하는지 확인한다.

구현을 읽을 때 놓치기 쉬운 부분

01

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 오류를 분리할 수 있다.

02

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에서 읽는다.

03

.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 분석의 가장 직접적인 검증이다.

04

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 문제다.

05

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가 다르다.

06

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의 실제 구현을 확인한다.

07

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 완료 여부를 객관적으로 판정할 수 있다.

08

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가 중복 가산된다.

09

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.SPT_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 세 가지를 대조한다.

10

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가 빠졌을 때 생긴다.

객체와 수명

대상만들어지는 시점유효 범위확인할 조건
relocated image / .rela.dyn / trap framecopy text and databoard_init_r 또는 오류 정리 완료까지board_init_r로 분기할 때 PC, SP, gp, tvec, gd와 모든 relocation target이 destination image를 가리키고 secondary hart가 old image에서 실행 중이지 않아야 한다.
입력 buffer / metadatanew stack gd destinationparse와 검증이 끝날 때까지길이, 정렬, 소유권, 변조 가능성
등록된 list / descriptorapply .rela.dynsubsystem 종료 또는 image handoff까지이중 등록, dangling pointer, 오류 unwind
hardware 또는 persistent state실제 write/probe가 완료된 뒤reset 또는 명시적 해제·갱신까지flush, timeout, 전원 차단, rollback
DRAM의 relocated RISC-V U-Bootlink/image 생성 시점다음 stage가 새 image로 교체할 때까지load address, entry, section과 header 일치

실패 지점과 증상

#조건관찰되는 증상먼저 볼 단계
01rela type 처리 오류특정 global pointer만 old addresscopy text and data
02tvec 미보정relocation 후 fault가 old handler로 진입apply .rela.dyn
03secondary IPI 순서 오류old code 또는 0 stack 실행relocate tvec
04cache maintenance 누락hart별 stale instructionclear BSS

로그가 끊긴 마지막 함수만 고치지 않는다. 그 함수가 받은 주소, size, flag가 만들어진 앞 단계까지 올라가고, 오류 뒤 등록 객체와 hardware 상태가 남았는지도 확인한다.

소스 밖에서 확인할 증거

소스 해석은 실제 빌드 산출물과 target 로그로 닫아야 한다. 아래 명령의 보드 이름과 toolchain prefix는 사용 중인 빌드 환경에 맞게 바꾼다.

#목적명령 또는 계측판정 기준
01전처리 결과make <board>_defconfig && make V=1선택된 CONFIG와 실제 compile/link 명령을 보관한다.
02symbol과 section${CROSS_COMPILE}nm -n u-boot | grep 'relocate_code'symbol 주소가 기대한 stage와 image에 들어갔는지 확인한다.
03어셈블리 대조${CROSS_COMPILE}objdump -drS u-bootC 한 줄이 실제 branch, relocation, memory access로 어떻게 바뀌었는지 본다.
04주소 배치bdinforelocaddr, malloc, FDT, stack과 DRAM bank 주소를 한 번에 기록한다.
05runtime 관찰dm tree; dm uclass; printenvdriver model과 환경이 이 단계에서 실제로 공개되었는지 확인한다.

직접 확인할 실험

  1. 01
    readelf -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에서 실행 중이지 않아야 한다. 조건이 처음 깨지는 줄을 기록한다.

  2. 02
    trap_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에서 실행 중이지 않아야 한다. 조건이 처음 깨지는 줄을 기록한다.

  3. 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에서 실행 중이지 않아야 한다. 조건이 처음 깨지는 줄을 기록한다.

  4. 04
    secondary_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에서 실행 중이지 않아야 한다. 조건이 처음 깨지는 줄을 기록한다.

원문과 다음 글