← Bootloader DUJINLABS.COM

U-Boot · source analysis

RISC-V hart entry, boot lottery와 per-hart stack

여러 hart가 `_start`에 들어온 뒤 hart ID와 FDT를 보존하고 trap vector, per-hart stack, global data를 만든 다음 한 hart만 board_init_f를 실행하는 과정을 읽습니다.

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

동시에 깨어난 RISC-V hart 중 하나만 global 초기화를 수행하고 나머지가 안전한 stack에서 기다리게 하는 장치는 무엇인가?

RISC-V에서는 여러 hart가 동시에 같은 entry에 도착할 수 있다. stack을 hart별로 분리하고 atomic lottery의 winner만 global 초기화를 실행하며, loser는 winner가 게시한 상태를 기다린다.

어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다. 이 문장을 기준으로 코드를 위에서 아래로 읽으면, 함수 이름을 외우는 대신 어느 시점에 어떤 상태를 신뢰할 수 있는지 판단할 수 있다.

객체와 주소가 놓이는 구조

그림 1. RISC-V hart entry, boot lottery와 per-hart stack에서 입력, 내부 상태, 출력의 경계
입력과 전제내부 상태외부로 공개되는 결과
01a0 hartid and a1 FDT
02tp and s1 preserve
03per-hart stack slots
04hart_lottery
05gd publication
06secondary_hart_loop
INVARIANT

어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다.

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

실행 흐름

그림 2. 정상 경로의 주요 호출과 상태 전달
firmware or reset
hartid and FDT preserve
trap vector and interrupt mask
per-hart stack
hart lottery
boot hart gd
secondary wait
board_init_f

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

단계별 입력과 출력

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

#단계진입 시 신뢰할 상태성공 뒤 남아야 할 상태다음 소비자
01firmware or resetMMU와 일반 scheduler가 없는 multi-hart 초기 문맥a0 hartid and a1 FDThartid and FDT preserve
02hartid and FDT preservefirmware or reset 완료 상태tp and s1 preservetrap vector and interrupt mask
03trap vector and interrupt maskhartid and FDT preserve 완료 상태per-hart stack slotsper-hart stack
04per-hart stacktrap vector and interrupt mask 완료 상태hart_lotteryhart lottery
05hart lotteryper-hart stack 완료 상태gd publicationboot hart gd
06boot hart gdhart lottery 완료 상태secondary_hart_loopsecondary wait
07secondary waitboot hart gd 완료 상태secondary_hart_loopboard_init_f
08board_init_fsecondary wait 완료 상태secondary_hart_loop최종 RISC-V U-Boot pre-relocation image 또는 다음 stage

공통 불변 조건: 어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다. 한 단계의 출력이 다음 단계의 입력 조건을 만족하지 않으면 오류가 실제로 드러난 위치보다 앞의 생산 단계부터 조사한다.

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

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

그림 3. hart ID로 나뉘는 초기 stack slot

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

높은 주소↓ 주소 감소
boot hart stackwinner의 board_init_f call chain
hart 1 stackCONFIG_STACK_SIZE 고정 간격
hart 2 stackhart ID 기반 offset
all-hart reservationgd와 malloc_f가 침범하지 않음
gd + early mallocwinner만 만들고 게시
낮은 주소예약 cursor 진행 방향
그림 4. lottery winner와 secondary hart의 실행 경로

두 열의 객체는 이름이 비슷해도 생성 시점, 사용 가능한 API와 해제 책임이 다르다.

Winner hart

01atomic 이전 값으로 승리

02gd와 malloc_f 생성

03FDT와 boot hart 기록

04board_init_f 실행

05relocation 주도

Secondary hart

01자기 stack 유지

02global init 반복 금지

03gp와 trap 상태 준비

04shared pointer/IPI 대기

05relocation 뒤 합류

그림 5. 여러 hart가 하나의 runtime으로 모이는 순서

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

01all harts _start
02trap/IE 정리
03per-hart SP
04atomic lottery
05winner publishes gd
06secondary wait
07relocation handoff

원본 코드

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

commitece349ade2973e220f524ce59e59711cc919263fsymbol_start:
33#endif
34
35.section .data
36secondary_harts_relocation_error:
37	.ascii "Relocation of secondary harts has failed, error %d\n"
38
39.section .text
40.globl _start
41_start:
42#if CONFIG_IS_ENABLED(RISCV_MMODE)
43	csrr	a0, CSR_MHARTID
44#endif
45
46	/*
47	 * Save hart id and dtb pointer. The thread pointer register is not
48	 * modified by C code. It is used by secondary_hart_loop.
49	 */
50	mv	tp, a0
51	mv	s1, a1
52
53	/*
54	 * Set the global data pointer to a known value in case we get a very
55	 * early trap. The global data pointer will be set its actual value only
56	 * after it has been initialized.
57	 */
58	mv	gp, zero
59
60	/*
61	 * Set the trap handler. This must happen after initializing gp because
62	 * the handler may use it.
63	 */
64	la	t0, trap_entry
65	csrw	MODE_PREFIX(tvec), t0
66
67	/*
68	 * Mask all interrupts. Interrupts are disabled globally (in m/sstatus)
69	 * for U-Boot, but we will need to read m/sip to determine if we get an
70	 * IPI
71	 */
72	csrw	MODE_PREFIX(ie), zero
73
74#if CONFIG_IS_ENABLED(SMP)
75	/* check if hart is within range */
76	/* tp: hart id */
77	li	t0, CONFIG_NR_CPUS
78	bge	tp, t0, hart_out_of_bounds_loop
79
80	/* set xSIE bit to receive IPIs */
81#if CONFIG_IS_ENABLED(RISCV_MMODE)
82	li	t0, MIE_MSIE
83#else
84	li	t0, SIE_SSIE
85#endif
86	csrs	MODE_PREFIX(ie), t0
87#endif
88
89/*
90 * Set stackpointer in internal/ex RAM to call board_init_f
91 */
92call_board_init_f:
93#if CONFIG_IS_ENABLED(HAVE_INIT_STACK)
94	li	t0, CONFIG_VAL(STACK)
95#else
96	li	t0, SYS_INIT_SP_ADDR
97#endif
98	and	t0, t0, -16		/* force 16 byte alignment */
99
100	/* setup stack */
101#if CONFIG_IS_ENABLED(SMP)
102	/* tp: hart id */
103	slli	t1, tp, CONFIG_STACK_SIZE_SHIFT
104	sub	sp, t0, t1
105#else
106	mv	sp, t0
107#endif
108/*
109 * Now sp points to the right stack belonging to current CPU.
110 * It's essential before any function call, otherwise, we get data-race.
111 */
112
113/* clear stack if necessary */
114#if CONFIG_IS_ENABLED(ZERO_MEM_BEFORE_USE)
115clear_stack:
116	li	t1, 1
117	slli	t1, t1, CONFIG_STACK_SIZE_SHIFT
118	sub	t1, sp, t1
119clear_stack_loop:
120	SREG	zero, 0(t1)		/* t1 is always 16 byte aligned */
121	addi	t1, t1, REGBYTES
122	blt	t1, sp, clear_stack_loop
123#endif
124
125call_board_init_f_0:
126	/* find top of reserve space */
127#if CONFIG_IS_ENABLED(SMP)
128	li	t1, CONFIG_NR_CPUS
129#else
130	li	t1, 1
131#endif
132	slli	t1, t1, CONFIG_STACK_SIZE_SHIFT
133	sub	a0, t0, t1		/* t1 -> size of all CPU stacks */
134	jal	board_init_f_alloc_reserve
135
136	/*
137	 * Save global data pointer for later. We don't set it here because it
138	 * is not initialized yet.
139	 */
140	mv	s0, a0
141
142
143	/* Configure proprietary settings and customized CSRs of harts */
144call_harts_early_init:
145	jal	harts_early_init
146
147#if !CONFIG_IS_ENABLED(XIP)
148	/*
149	 * Pick hart to initialize global data and run U-Boot. The other harts
150	 * wait for initialization to complete.
151	 */
152	la	t0, hart_lottery
153	li	t1, 1
154	amoswap.w s2, t1, 0(t0)
155	bnez	s2, wait_for_gd_init
156#else
157	/*
158	 * FIXME: gp is set before it is initialized. If an XIP U-Boot ever
159	 * encounters a pending IPI on boot it is liable to jump to whatever
160	 * memory happens to be in ipi_data.addr on boot. It may also run into
161	 * problems if it encounters an exception too early (because printf/puts
162	 * accesses gd).
163	 */
164	mv	gp, s0
165#if CONFIG_IS_ENABLED(RISCV_MMODE)
166	bnez	tp, secondary_hart_loop
167#endif
168#endif
169	
170	mv      a0, s0
171	jal	board_init_f_init_reserve
172
173	SREG	s1, GD_FIRMWARE_FDT_ADDR(gp)
174	/* save the boot hart id to global_data */
175	SREG	tp, GD_BOOT_HART(gp)
176
177#if !CONFIG_IS_ENABLED(XIP)
178#ifdef CONFIG_AVAILABLE_HARTS
179	la	t0, available_harts_lock
180	amoswap.w.rl zero, zero, 0(t0)
181#endif
182
183wait_for_gd_init:
184	/*
185	 * Set the global data pointer only when gd_t has been initialized.
186	 * This was already set by arch_setup_gd on the boot hart, but all other
187	 * harts' global data pointers gets set here.
188	 */
189	mv	gp, s0
190#ifdef CONFIG_AVAILABLE_HARTS
191	la	t0, available_harts_lock
192	li	t1, 1
1931:	amoswap.w.aq t1, t1, 0(t0)
194	bnez	t1, 1b
195
196	/* register available harts in the available_harts mask */
197	li	t1, 1
198	sll	t1, t1, tp
199	LREG	t2, GD_AVAILABLE_HARTS(gp)
200	or	t2, t2, t1
201	SREG	t2, GD_AVAILABLE_HARTS(gp)
202
203	amoswap.w.rl zero, zero, 0(t0)
204#endif
205
206	/*
207	 * Continue on hart lottery winner, others branch to
208	 * secondary_hart_loop.
209	 */
210	bnez	s2, secondary_hart_loop
211#endif
212
213#ifdef CONFIG_DEBUG_UART
214	jal	debug_uart_init
215#endif
216
217	mv	a0, zero		/* a0 <-- boot_flags = 0 */
218	la	t5, board_init_f
219	jalr	t5			/* jump to board_init_f() */
220
221#ifdef CONFIG_XPL_BUILD
222spl_clear_bss:
223	la	t0, __bss_start
224	la	t1, __bss_end
225	beq	t0, t1, spl_stack_gd_setup
226
227spl_clear_bss_loop:
228	SREG	zero, 0(t0)
229	addi	t0, t0, REGBYTES
230	blt	t0, t1, spl_clear_bss_loop
231
232spl_stack_gd_setup:
233	jal	spl_relocate_stack_gd
234
235	/* skip setup if we did not relocate */
236	beqz	a0, spl_call_board_init_r
237	mv	s0, a0
238
239	/* setup stack on main hart */
240#if CONFIG_IS_ENABLED(SMP)
241	/* tp: hart id */
242	slli	t0, tp, CONFIG_STACK_SIZE_SHIFT
243	sub	sp, s0, t0
244#else
245	mv	sp, s0
246#endif

33-246줄 해설

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

33#endif

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

34(빈 줄)

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

35.section .data

원본 35번 줄의 .section .data는 앞의 이전 block 경계 결과를 받아 다음 secondary_harts_relocation_error:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

36secondary_harts_relocation_error:

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

37.ascii "Relocation of secondary harts has failed, error %d\n"

원본 37번 줄의 .ascii "Relocation of secondary harts has failed, error %d\n"는 앞의 secondary_harts_relocation_error: 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

38(빈 줄)

.ascii "Relocation of secondary harts has failed, error %d\n"까지의 동작과 .section .text에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

39.section .text

원본 39번 줄의 .section .text는 앞의 이전 block 경계 결과를 받아 다음 .globl _start로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

40.globl _start

원본 40번 줄의 .globl _start는 앞의 .section .text 결과를 받아 다음 _start:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

41_start:

ROM 또는 previous stage가 넘긴 모든 hart가 도착할 수 있는 RISC-V U-Boot entry다.

42#if CONFIG_IS_ENABLED(RISCV_MMODE)

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

43csrr a0, CSR_MHARTID

M-mode build에서 현재 hardware hart ID를 읽어 boot argument a0를 만든다.

44#endif

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

45(빈 줄)

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

46/*

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

47* Save hart id and dtb pointer. The thread pointer register is not

원본 주석이 'Save hart id and dtb pointer. The thread pointer register is not'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

48* modified by C code. It is used by secondary_hart_loop.

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

49*/

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

50mv tp, a0

hart ID를 tp에 보존한다. 아직 Linux식 current pointer가 아니라 초기 per-hart 계산 값이다.

51mv s1, a1

firmware가 넘긴 FDT pointer를 relocation 전 호출 사이에 보존한다.

52(빈 줄)

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

53/*

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

54* Set the global data pointer to a known value in case we get a very

원본 주석이 'Set the global data pointer to a known value in case we get a very'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

55* early trap. The global data pointer will be set its actual value only

원본 주석이 'early trap. The global data pointer will be set its actual value only'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

56* after it has been initialized.

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

57*/

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

58mv gp, zero

global pointer를 일부러 무효화해 너무 이른 gp-relative global 접근을 막는다.

59(빈 줄)

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

60/*

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

61* Set the trap handler. This must happen after initializing gp because

원본 주석이 'Set the trap handler. This must happen after initializing gp because'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

62* the handler may use it.

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

63*/

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

64la t0, trap_entry

원본 64번 줄의 la t0, trap_entry는 앞의 */ 결과를 받아 다음 csrw MODE_PREFIX(tvec), t0로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

65csrw MODE_PREFIX(tvec), t0

현재 privilege mode의 trap vector를 early trap entry로 설정한다.

66(빈 줄)

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

67/*

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

68* Mask all interrupts. Interrupts are disabled globally (in m/sstatus)

원본 주석이 'Mask all interrupts. Interrupts are disabled globally (in m/sstatus)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

69* for U-Boot, but we will need to read m/sip to determine if we get an

원본 주석이 'for U-Boot, but we will need to read m/sip to determine if we get an'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

70* IPI

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

71*/

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

72csrw MODE_PREFIX(ie), zero

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

73(빈 줄)

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

74#if CONFIG_IS_ENABLED(SMP)

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

75/* check if hart is within range */

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

76/* tp: hart id */

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

77li t0, CONFIG_NR_CPUS

원본 77번 줄의 li t0, CONFIG_NR_CPUS는 앞의 /* tp: hart id */ 결과를 받아 다음 bge tp, t0, hart_out_of_bounds_loop로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

78bge tp, t0, hart_out_of_bounds_loop

원본 78번 줄의 bge tp, t0, hart_out_of_bounds_loop는 앞의 li t0, CONFIG_NR_CPUS 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

79(빈 줄)

bge tp, t0, hart_out_of_bounds_loop까지의 동작과 /* set xSIE bit to receive IPIs */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

80/* set xSIE bit to receive IPIs */

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

81#if CONFIG_IS_ENABLED(RISCV_MMODE)

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

82li t0, MIE_MSIE

원본 82번 줄의 li t0, MIE_MSIE는 앞의 #if CONFIG_IS_ENABLED(RISCV_MMODE) 결과를 받아 다음 #else로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

83#else

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

84li t0, SIE_SSIE

원본 84번 줄의 li t0, SIE_SSIE는 앞의 #else 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

85#endif

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

86csrs MODE_PREFIX(ie), t0

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

87#endif

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

88(빈 줄)

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

89/*

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

90* Set stackpointer in internal/ex RAM to call board_init_f

원본 주석이 'Set stackpointer in internal/ex RAM to call board_init_f'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

91*/

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

92call_board_init_f:

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

93#if CONFIG_IS_ENABLED(HAVE_INIT_STACK)

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

94li t0, CONFIG_VAL(STACK)

CONFIG_VAL(STACK)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.

95#else

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

96li t0, SYS_INIT_SP_ADDR

원본 96번 줄의 li t0, SYS_INIT_SP_ADDR는 앞의 #else 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

97#endif

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

98and t0, t0, -16 /* force 16 byte alignment */

원본 98번 줄의 and t0, t0, -16 /* force 16 byte alignment */는 앞의 #endif 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

99(빈 줄)

and t0, t0, -16 /* force 16 byte alignment */까지의 동작과 /* setup stack */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

100/* setup stack */

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

101#if CONFIG_IS_ENABLED(SMP)

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

102/* tp: hart id */

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

103slli t1, tp, CONFIG_STACK_SIZE_SHIFT

raw hart ID에 2^CONFIG_STACK_SIZE_SHIFT를 곱해 hart별 stack slot offset을 만든다. sparse hart ID에서는 reservation 상한을 반드시 확인한다.

104sub sp, t0, t1

sub sp, t0, t1 명령이 register, memory 또는 PC를 직접 바꾼다. MMU와 일반 scheduler가 없는 multi-hart 초기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

105#else

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

106mv sp, t0

원본 106번 줄의 mv sp, t0는 앞의 #else 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

107#endif

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

108/*

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

109* Now sp points to the right stack belonging to current CPU.

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

110* It's essential before any function call, otherwise, we get data-race.

원본 주석이 'It's essential before any function call, otherwise, we get data-race.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

111*/

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

112(빈 줄)

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

113/* clear stack if necessary */

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

114#if CONFIG_IS_ENABLED(ZERO_MEM_BEFORE_USE)

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

115clear_stack:

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

116li t1, 1

원본 116번 줄의 li t1, 1는 앞의 clear_stack: 결과를 받아 다음 slli t1, t1, CONFIG_STACK_SIZE_SHIFT로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

117slli t1, t1, CONFIG_STACK_SIZE_SHIFT

원본 117번 줄의 slli t1, t1, CONFIG_STACK_SIZE_SHIFT는 앞의 li t1, 1 결과를 받아 다음 sub t1, sp, t1로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

118sub t1, sp, t1

sub t1, sp, t1 명령이 register, memory 또는 PC를 직접 바꾼다. MMU와 일반 scheduler가 없는 multi-hart 초기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

119clear_stack_loop:

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

120SREG zero, 0(t1) /* t1 is always 16 byte aligned */

원본 120번 줄의 SREG zero, 0(t1) /* t1 is always 16 byte aligned */는 앞의 clear_stack_loop: 결과를 받아 다음 addi t1, t1, REGBYTES로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

121addi t1, t1, REGBYTES

원본 121번 줄의 addi t1, t1, REGBYTES는 앞의 SREG zero, 0(t1) /* t1 is always 16 byte aligned */ 결과를 받아 다음 blt t1, sp, clear_stack_loop로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

122blt t1, sp, clear_stack_loop

원본 122번 줄의 blt t1, sp, clear_stack_loop는 앞의 addi t1, t1, REGBYTES 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

123#endif

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

124(빈 줄)

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

125call_board_init_f_0:

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

126/* find top of reserve space */

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

127#if CONFIG_IS_ENABLED(SMP)

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

128li t1, CONFIG_NR_CPUS

원본 128번 줄의 li t1, CONFIG_NR_CPUS는 앞의 #if CONFIG_IS_ENABLED(SMP) 결과를 받아 다음 #else로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

129#else

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

130li t1, 1

원본 130번 줄의 li t1, 1는 앞의 #else 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

131#endif

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

132slli t1, t1, CONFIG_STACK_SIZE_SHIFT

원본 132번 줄의 slli t1, t1, CONFIG_STACK_SIZE_SHIFT는 앞의 #endif 결과를 받아 다음 sub a0, t0, t1 /* t1 -> size of all CPU stacks */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

133sub a0, t0, t1 /* t1 -> size of all CPU stacks */

sub a0, t0, t1 /* t1 -> size of all CPU stacks */ 명령이 register, memory 또는 PC를 직접 바꾼다. MMU와 일반 scheduler가 없는 multi-hart 초기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

134jal board_init_f_alloc_reserve

원본 134번 줄의 jal board_init_f_alloc_reserve는 앞의 sub a0, t0, t1 /* t1 -> size of all CPU stacks */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

135(빈 줄)

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

136/*

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

137* Save global data pointer for later. We don't set it here because it

원본 주석이 'Save global data pointer for later. We don't set it here because it'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

138* is not initialized yet.

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

139*/

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

140mv s0, a0

원본 140번 줄의 mv s0, a0는 앞의 */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

141(빈 줄)

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

142(빈 줄)

이전 block 경계까지의 동작과 /* Configure proprietary settings and customized CSRs of harts */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

143/* Configure proprietary settings and customized CSRs of harts */

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

144call_harts_early_init:

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

145jal harts_early_init

원본 145번 줄의 jal harts_early_init는 앞의 call_harts_early_init: 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

146(빈 줄)

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

147#if !CONFIG_IS_ENABLED(XIP)

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

148/*

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

149* Pick hart to initialize global data and run U-Boot. The other harts

원본 주석이 'Pick hart to initialize global data and run U-Boot. The other harts'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

150* wait for initialization to complete.

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

151*/

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

152la t0, hart_lottery

원본 152번 줄의 la t0, hart_lottery는 앞의 */ 결과를 받아 다음 li t1, 1로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

153li t1, 1

원본 153번 줄의 li t1, 1는 앞의 la t0, hart_lottery 결과를 받아 다음 amoswap.w s2, t1, 0(t0)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

154amoswap.w s2, t1, 0(t0)

hart lottery 값을 원자적으로 교환해 global 초기화를 실행할 한 hart를 고른다.

155bnez s2, wait_for_gd_init

원본 155번 줄의 bnez s2, wait_for_gd_init는 앞의 amoswap.w s2, t1, 0(t0) 결과를 받아 다음 #else로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

156#else

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

157/*

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

158* FIXME: gp is set before it is initialized. If an XIP U-Boot ever

원본 주석이 'FIXME: gp is set before it is initialized. If an XIP U-Boot ever'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

159* encounters a pending IPI on boot it is liable to jump to whatever

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

160* memory happens to be in ipi_data.addr on boot. It may also run into

원본 주석이 'memory happens to be in ipi_data.addr on boot. It may also run into'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

161* problems if it encounters an exception too early (because printf/puts

원본 주석이 'problems if it encounters an exception too early (because printf/puts'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

162* accesses gd).

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

163*/

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

164mv gp, s0

원본 164번 줄의 mv gp, s0는 앞의 */ 결과를 받아 다음 #if CONFIG_IS_ENABLED(RISCV_MMODE)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

165#if CONFIG_IS_ENABLED(RISCV_MMODE)

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

166bnez tp, secondary_hart_loop

winner가 아닌 hart는 global board 초기화에 진입하지 않고 자신에게 새 실행 상태가 게시될 때까지 secondary loop로 간다.

167#endif

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

168#endif

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

169(빈 줄)

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

170mv a0, s0

원본 170번 줄의 mv a0, s0는 앞의 이전 block 경계 결과를 받아 다음 jal board_init_f_init_reserve로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

171jal board_init_f_init_reserve

원본 171번 줄의 jal board_init_f_init_reserve는 앞의 mv a0, s0 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

172(빈 줄)

jal board_init_f_init_reserve까지의 동작과 SREG s1, GD_FIRMWARE_FDT_ADDR(gp)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

173SREG s1, GD_FIRMWARE_FDT_ADDR(gp)

GD_FIRMWARE_FDT_ADDR(gp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.

174/* save the boot hart id to global_data */

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

175SREG tp, GD_BOOT_HART(gp)

GD_BOOT_HART(gp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.

176(빈 줄)

SREG tp, GD_BOOT_HART(gp)까지의 동작과 #if !CONFIG_IS_ENABLED(XIP)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

177#if !CONFIG_IS_ENABLED(XIP)

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

178#ifdef CONFIG_AVAILABLE_HARTS

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

179la t0, available_harts_lock

원본 179번 줄의 la t0, available_harts_lock는 앞의 #ifdef CONFIG_AVAILABLE_HARTS 결과를 받아 다음 amoswap.w.rl zero, zero, 0(t0)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

180amoswap.w.rl zero, zero, 0(t0)

boot hart가 shared slot에 release semantic으로 0을 게시해 앞선 gd/stack store가 secondary보다 먼저 보이게 한다.

181#endif

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

182(빈 줄)

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

183wait_for_gd_init:

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

184/*

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

185* Set the global data pointer only when gd_t has been initialized.

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

186* This was already set by arch_setup_gd on the boot hart, but all other

원본 주석이 'This was already set by arch_setup_gd on the boot hart, but all other'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

187* harts' global data pointers gets set here.

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

188*/

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

189mv gp, s0

원본 189번 줄의 mv gp, s0는 앞의 */ 결과를 받아 다음 #ifdef CONFIG_AVAILABLE_HARTS로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

190#ifdef CONFIG_AVAILABLE_HARTS

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

191la t0, available_harts_lock

원본 191번 줄의 la t0, available_harts_lock는 앞의 #ifdef CONFIG_AVAILABLE_HARTS 결과를 받아 다음 li t1, 1로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

192li t1, 1

원본 192번 줄의 li t1, 1는 앞의 la t0, available_harts_lock 결과를 받아 다음 1: amoswap.w.aq t1, t1, 0(t0)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

1931: amoswap.w.aq t1, t1, 0(t0)

원본 193번 줄의 1: amoswap.w.aq t1, t1, 0(t0)는 앞의 li t1, 1 결과를 받아 다음 bnez t1, 1b로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

194bnez t1, 1b

원본 194번 줄의 bnez t1, 1b는 앞의 1: amoswap.w.aq t1, t1, 0(t0) 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

195(빈 줄)

bnez t1, 1b까지의 동작과 /* register available harts in the available_harts mask */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

196/* register available harts in the available_harts mask */

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

197li t1, 1

원본 197번 줄의 li t1, 1는 앞의 /* register available harts in the available_harts mask */ 결과를 받아 다음 sll t1, t1, tp로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

198sll t1, t1, tp

원본 198번 줄의 sll t1, t1, tp는 앞의 li t1, 1 결과를 받아 다음 LREG t2, GD_AVAILABLE_HARTS(gp)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

199LREG t2, GD_AVAILABLE_HARTS(gp)

GD_AVAILABLE_HARTS(gp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.

200or t2, t2, t1

원본 200번 줄의 or t2, t2, t1는 앞의 LREG t2, GD_AVAILABLE_HARTS(gp) 결과를 받아 다음 SREG t2, GD_AVAILABLE_HARTS(gp)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

201SREG t2, GD_AVAILABLE_HARTS(gp)

GD_AVAILABLE_HARTS(gp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.

202(빈 줄)

SREG t2, GD_AVAILABLE_HARTS(gp)까지의 동작과 amoswap.w.rl zero, zero, 0(t0)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

203amoswap.w.rl zero, zero, 0(t0)

boot hart가 shared slot에 release semantic으로 0을 게시해 앞선 gd/stack store가 secondary보다 먼저 보이게 한다.

204#endif

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

205(빈 줄)

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

206/*

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

207* Continue on hart lottery winner, others branch to

원본 주석이 'Continue on hart lottery winner, others branch to'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

208* secondary_hart_loop.

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

209*/

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

210bnez s2, secondary_hart_loop

원본 210번 줄의 bnez s2, secondary_hart_loop는 앞의 */ 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

211#endif

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

212(빈 줄)

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

213#ifdef CONFIG_DEBUG_UART

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

214jal debug_uart_init

원본 214번 줄의 jal debug_uart_init는 앞의 #ifdef CONFIG_DEBUG_UART 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

215#endif

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

216(빈 줄)

#endif까지의 동작과 mv a0, zero /* a0 <-- boot_flags = 0 */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

217mv a0, zero /* a0 <-- boot_flags = 0 */

원본 217번 줄의 mv a0, zero /* a0 <-- boot_flags = 0 */는 앞의 이전 block 경계 결과를 받아 다음 la t5, board_init_f로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

218la t5, board_init_f

원본 218번 줄의 la t5, board_init_f는 앞의 mv a0, zero /* a0 <-- boot_flags = 0 */ 결과를 받아 다음 jalr t5 /* jump to board_init_f() */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

219jalr t5 /* jump to board_init_f() */

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

220(빈 줄)

jalr t5 /* jump to board_init_f() */까지의 동작과 #ifdef CONFIG_XPL_BUILD에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

221#ifdef CONFIG_XPL_BUILD

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

222spl_clear_bss:

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

223la t0, __bss_start

원본 223번 줄의 la t0, __bss_start는 앞의 spl_clear_bss: 결과를 받아 다음 la t1, __bss_end로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

224la t1, __bss_end

원본 224번 줄의 la t1, __bss_end는 앞의 la t0, __bss_start 결과를 받아 다음 beq t0, t1, spl_stack_gd_setup로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

225beq t0, t1, spl_stack_gd_setup

원본 225번 줄의 beq t0, t1, spl_stack_gd_setup는 앞의 la t1, __bss_end 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

226(빈 줄)

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

227spl_clear_bss_loop:

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

228SREG zero, 0(t0)

원본 228번 줄의 SREG zero, 0(t0)는 앞의 spl_clear_bss_loop: 결과를 받아 다음 addi t0, t0, REGBYTES로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

229addi t0, t0, REGBYTES

원본 229번 줄의 addi t0, t0, REGBYTES는 앞의 SREG zero, 0(t0) 결과를 받아 다음 blt t0, t1, spl_clear_bss_loop로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

230blt t0, t1, spl_clear_bss_loop

원본 230번 줄의 blt t0, t1, spl_clear_bss_loop는 앞의 addi t0, t0, REGBYTES 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

231(빈 줄)

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

232spl_stack_gd_setup:

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

233jal spl_relocate_stack_gd

원본 233번 줄의 jal spl_relocate_stack_gd는 앞의 spl_stack_gd_setup: 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

234(빈 줄)

jal spl_relocate_stack_gd까지의 동작과 /* skip setup if we did not relocate */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

235/* skip setup if we did not relocate */

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

236beqz a0, spl_call_board_init_r

원본 236번 줄의 beqz a0, spl_call_board_init_r는 앞의 /* skip setup if we did not relocate */ 결과를 받아 다음 mv s0, a0로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

237mv s0, a0

원본 237번 줄의 mv s0, a0는 앞의 beqz a0, spl_call_board_init_r 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

238(빈 줄)

mv s0, a0까지의 동작과 /* setup stack on main hart */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

239/* setup stack on main hart */

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

240#if CONFIG_IS_ENABLED(SMP)

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

241/* tp: hart id */

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

242slli t0, tp, CONFIG_STACK_SIZE_SHIFT

원본 242번 줄의 slli t0, tp, CONFIG_STACK_SIZE_SHIFT는 앞의 /* tp: hart id */ 결과를 받아 다음 sub sp, s0, t0로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

243sub sp, s0, t0

sub sp, s0, t0 명령이 register, memory 또는 PC를 직접 바꾼다. MMU와 일반 scheduler가 없는 multi-hart 초기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

244#else

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

245mv sp, s0

원본 245번 줄의 mv sp, s0는 앞의 #else 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

246#endif

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

함께 읽어야 하는 원본 코드

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

01. winner와 secondary를 가르는 hart lottery 구간

arch/riscv/cpu/start.S 128-234줄이다.

filearch/riscv/cpu/start.Sanchorla t0, hart_lottery
128	li	t1, CONFIG_NR_CPUS
129#else
130	li	t1, 1
131#endif
132	slli	t1, t1, CONFIG_STACK_SIZE_SHIFT
133	sub	a0, t0, t1		/* t1 -> size of all CPU stacks */
134	jal	board_init_f_alloc_reserve
135
136	/*
137	 * Save global data pointer for later. We don't set it here because it
138	 * is not initialized yet.
139	 */
140	mv	s0, a0
141
142
143	/* Configure proprietary settings and customized CSRs of harts */
144call_harts_early_init:
145	jal	harts_early_init
146
147#if !CONFIG_IS_ENABLED(XIP)
148	/*
149	 * Pick hart to initialize global data and run U-Boot. The other harts
150	 * wait for initialization to complete.
151	 */
152	la	t0, hart_lottery
153	li	t1, 1
154	amoswap.w s2, t1, 0(t0)
155	bnez	s2, wait_for_gd_init
156#else
157	/*
158	 * FIXME: gp is set before it is initialized. If an XIP U-Boot ever
159	 * encounters a pending IPI on boot it is liable to jump to whatever
160	 * memory happens to be in ipi_data.addr on boot. It may also run into
161	 * problems if it encounters an exception too early (because printf/puts
162	 * accesses gd).
163	 */
164	mv	gp, s0
165#if CONFIG_IS_ENABLED(RISCV_MMODE)
166	bnez	tp, secondary_hart_loop
167#endif
168#endif
169	
170	mv      a0, s0
171	jal	board_init_f_init_reserve
172
173	SREG	s1, GD_FIRMWARE_FDT_ADDR(gp)
174	/* save the boot hart id to global_data */
175	SREG	tp, GD_BOOT_HART(gp)
176
177#if !CONFIG_IS_ENABLED(XIP)
178#ifdef CONFIG_AVAILABLE_HARTS
179	la	t0, available_harts_lock
180	amoswap.w.rl zero, zero, 0(t0)
181#endif
182
183wait_for_gd_init:
184	/*
185	 * Set the global data pointer only when gd_t has been initialized.
186	 * This was already set by arch_setup_gd on the boot hart, but all other
187	 * harts' global data pointers gets set here.
188	 */
189	mv	gp, s0
190#ifdef CONFIG_AVAILABLE_HARTS
191	la	t0, available_harts_lock
192	li	t1, 1
1931:	amoswap.w.aq t1, t1, 0(t0)
194	bnez	t1, 1b
195
196	/* register available harts in the available_harts mask */
197	li	t1, 1
198	sll	t1, t1, tp
199	LREG	t2, GD_AVAILABLE_HARTS(gp)
200	or	t2, t2, t1
201	SREG	t2, GD_AVAILABLE_HARTS(gp)
202
203	amoswap.w.rl zero, zero, 0(t0)
204#endif
205
206	/*
207	 * Continue on hart lottery winner, others branch to
208	 * secondary_hart_loop.
209	 */
210	bnez	s2, secondary_hart_loop
211#endif
212
213#ifdef CONFIG_DEBUG_UART
214	jal	debug_uart_init
215#endif
216
217	mv	a0, zero		/* a0 <-- boot_flags = 0 */
218	la	t5, board_init_f
219	jalr	t5			/* jump to board_init_f() */
220
221#ifdef CONFIG_XPL_BUILD
222spl_clear_bss:
223	la	t0, __bss_start
224	la	t1, __bss_end
225	beq	t0, t1, spl_stack_gd_setup
226
227spl_clear_bss_loop:
228	SREG	zero, 0(t0)
229	addi	t0, t0, REGBYTES
230	blt	t0, t1, spl_clear_bss_loop
231
232spl_stack_gd_setup:
233	jal	spl_relocate_stack_gd
234

128-234줄 해설

128li t1, CONFIG_NR_CPUS

원본 128번 줄의 li t1, CONFIG_NR_CPUS는 앞의 이전 block 경계 결과를 받아 다음 #else로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

129#else

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

130li t1, 1

원본 130번 줄의 li t1, 1는 앞의 #else 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

131#endif

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

132slli t1, t1, CONFIG_STACK_SIZE_SHIFT

원본 132번 줄의 slli t1, t1, CONFIG_STACK_SIZE_SHIFT는 앞의 #endif 결과를 받아 다음 sub a0, t0, t1 /* t1 -> size of all CPU stacks */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

133sub a0, t0, t1 /* t1 -> size of all CPU stacks */

sub a0, t0, t1 /* t1 -> size of all CPU stacks */ 명령이 register, memory 또는 PC를 직접 바꾼다. MMU와 일반 scheduler가 없는 multi-hart 초기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

134jal board_init_f_alloc_reserve

원본 134번 줄의 jal board_init_f_alloc_reserve는 앞의 sub a0, t0, t1 /* t1 -> size of all CPU stacks */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

135(빈 줄)

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

136/*

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

137* Save global data pointer for later. We don't set it here because it

원본 주석이 'Save global data pointer for later. We don't set it here because it'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

138* is not initialized yet.

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

139*/

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

140mv s0, a0

원본 140번 줄의 mv s0, a0는 앞의 */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

141(빈 줄)

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

142(빈 줄)

이전 block 경계까지의 동작과 /* Configure proprietary settings and customized CSRs of harts */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

143/* Configure proprietary settings and customized CSRs of harts */

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

144call_harts_early_init:

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

145jal harts_early_init

원본 145번 줄의 jal harts_early_init는 앞의 call_harts_early_init: 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

146(빈 줄)

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

147#if !CONFIG_IS_ENABLED(XIP)

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

148/*

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

149* Pick hart to initialize global data and run U-Boot. The other harts

원본 주석이 'Pick hart to initialize global data and run U-Boot. The other harts'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

150* wait for initialization to complete.

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

151*/

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

152la t0, hart_lottery

원본 152번 줄의 la t0, hart_lottery는 앞의 */ 결과를 받아 다음 li t1, 1로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

153li t1, 1

원본 153번 줄의 li t1, 1는 앞의 la t0, hart_lottery 결과를 받아 다음 amoswap.w s2, t1, 0(t0)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

154amoswap.w s2, t1, 0(t0)

hart lottery 값을 원자적으로 교환해 global 초기화를 실행할 한 hart를 고른다.

155bnez s2, wait_for_gd_init

원본 155번 줄의 bnez s2, wait_for_gd_init는 앞의 amoswap.w s2, t1, 0(t0) 결과를 받아 다음 #else로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

156#else

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

157/*

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

158* FIXME: gp is set before it is initialized. If an XIP U-Boot ever

원본 주석이 'FIXME: gp is set before it is initialized. If an XIP U-Boot ever'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

159* encounters a pending IPI on boot it is liable to jump to whatever

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

160* memory happens to be in ipi_data.addr on boot. It may also run into

원본 주석이 'memory happens to be in ipi_data.addr on boot. It may also run into'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

161* problems if it encounters an exception too early (because printf/puts

원본 주석이 'problems if it encounters an exception too early (because printf/puts'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

162* accesses gd).

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

163*/

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

164mv gp, s0

원본 164번 줄의 mv gp, s0는 앞의 */ 결과를 받아 다음 #if CONFIG_IS_ENABLED(RISCV_MMODE)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

165#if CONFIG_IS_ENABLED(RISCV_MMODE)

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

166bnez tp, secondary_hart_loop

winner가 아닌 hart는 global board 초기화에 진입하지 않고 자신에게 새 실행 상태가 게시될 때까지 secondary loop로 간다.

167#endif

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

168#endif

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

169(빈 줄)

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

170mv a0, s0

원본 170번 줄의 mv a0, s0는 앞의 이전 block 경계 결과를 받아 다음 jal board_init_f_init_reserve로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

171jal board_init_f_init_reserve

원본 171번 줄의 jal board_init_f_init_reserve는 앞의 mv a0, s0 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

172(빈 줄)

jal board_init_f_init_reserve까지의 동작과 SREG s1, GD_FIRMWARE_FDT_ADDR(gp)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

173SREG s1, GD_FIRMWARE_FDT_ADDR(gp)

GD_FIRMWARE_FDT_ADDR(gp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.

174/* save the boot hart id to global_data */

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

175SREG tp, GD_BOOT_HART(gp)

GD_BOOT_HART(gp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.

176(빈 줄)

SREG tp, GD_BOOT_HART(gp)까지의 동작과 #if !CONFIG_IS_ENABLED(XIP)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

177#if !CONFIG_IS_ENABLED(XIP)

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

178#ifdef CONFIG_AVAILABLE_HARTS

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

179la t0, available_harts_lock

원본 179번 줄의 la t0, available_harts_lock는 앞의 #ifdef CONFIG_AVAILABLE_HARTS 결과를 받아 다음 amoswap.w.rl zero, zero, 0(t0)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

180amoswap.w.rl zero, zero, 0(t0)

boot hart가 shared slot에 release semantic으로 0을 게시해 앞선 gd/stack store가 secondary보다 먼저 보이게 한다.

181#endif

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

182(빈 줄)

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

183wait_for_gd_init:

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

184/*

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

185* Set the global data pointer only when gd_t has been initialized.

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

186* This was already set by arch_setup_gd on the boot hart, but all other

원본 주석이 'This was already set by arch_setup_gd on the boot hart, but all other'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

187* harts' global data pointers gets set here.

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

188*/

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

189mv gp, s0

원본 189번 줄의 mv gp, s0는 앞의 */ 결과를 받아 다음 #ifdef CONFIG_AVAILABLE_HARTS로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

190#ifdef CONFIG_AVAILABLE_HARTS

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

191la t0, available_harts_lock

원본 191번 줄의 la t0, available_harts_lock는 앞의 #ifdef CONFIG_AVAILABLE_HARTS 결과를 받아 다음 li t1, 1로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

192li t1, 1

원본 192번 줄의 li t1, 1는 앞의 la t0, available_harts_lock 결과를 받아 다음 1: amoswap.w.aq t1, t1, 0(t0)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

1931: amoswap.w.aq t1, t1, 0(t0)

원본 193번 줄의 1: amoswap.w.aq t1, t1, 0(t0)는 앞의 li t1, 1 결과를 받아 다음 bnez t1, 1b로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

194bnez t1, 1b

원본 194번 줄의 bnez t1, 1b는 앞의 1: amoswap.w.aq t1, t1, 0(t0) 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

195(빈 줄)

bnez t1, 1b까지의 동작과 /* register available harts in the available_harts mask */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

196/* register available harts in the available_harts mask */

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

197li t1, 1

원본 197번 줄의 li t1, 1는 앞의 /* register available harts in the available_harts mask */ 결과를 받아 다음 sll t1, t1, tp로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

198sll t1, t1, tp

원본 198번 줄의 sll t1, t1, tp는 앞의 li t1, 1 결과를 받아 다음 LREG t2, GD_AVAILABLE_HARTS(gp)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

199LREG t2, GD_AVAILABLE_HARTS(gp)

GD_AVAILABLE_HARTS(gp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.

200or t2, t2, t1

원본 200번 줄의 or t2, t2, t1는 앞의 LREG t2, GD_AVAILABLE_HARTS(gp) 결과를 받아 다음 SREG t2, GD_AVAILABLE_HARTS(gp)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

201SREG t2, GD_AVAILABLE_HARTS(gp)

GD_AVAILABLE_HARTS(gp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.

202(빈 줄)

SREG t2, GD_AVAILABLE_HARTS(gp)까지의 동작과 amoswap.w.rl zero, zero, 0(t0)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

203amoswap.w.rl zero, zero, 0(t0)

boot hart가 shared slot에 release semantic으로 0을 게시해 앞선 gd/stack store가 secondary보다 먼저 보이게 한다.

204#endif

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

205(빈 줄)

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

206/*

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

207* Continue on hart lottery winner, others branch to

원본 주석이 'Continue on hart lottery winner, others branch to'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.

208* secondary_hart_loop.

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

209*/

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

210bnez s2, secondary_hart_loop

원본 210번 줄의 bnez s2, secondary_hart_loop는 앞의 */ 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

211#endif

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

212(빈 줄)

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

213#ifdef CONFIG_DEBUG_UART

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

214jal debug_uart_init

원본 214번 줄의 jal debug_uart_init는 앞의 #ifdef CONFIG_DEBUG_UART 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

215#endif

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

216(빈 줄)

#endif까지의 동작과 mv a0, zero /* a0 <-- boot_flags = 0 */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

217mv a0, zero /* a0 <-- boot_flags = 0 */

원본 217번 줄의 mv a0, zero /* a0 <-- boot_flags = 0 */는 앞의 이전 block 경계 결과를 받아 다음 la t5, board_init_f로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

218la t5, board_init_f

원본 218번 줄의 la t5, board_init_f는 앞의 mv a0, zero /* a0 <-- boot_flags = 0 */ 결과를 받아 다음 jalr t5 /* jump to board_init_f() */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

219jalr t5 /* jump to board_init_f() */

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

220(빈 줄)

jalr t5 /* jump to board_init_f() */까지의 동작과 #ifdef CONFIG_XPL_BUILD에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

221#ifdef CONFIG_XPL_BUILD

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

222spl_clear_bss:

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

223la t0, __bss_start

원본 223번 줄의 la t0, __bss_start는 앞의 spl_clear_bss: 결과를 받아 다음 la t1, __bss_end로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

224la t1, __bss_end

원본 224번 줄의 la t1, __bss_end는 앞의 la t0, __bss_start 결과를 받아 다음 beq t0, t1, spl_stack_gd_setup로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

225beq t0, t1, spl_stack_gd_setup

원본 225번 줄의 beq t0, t1, spl_stack_gd_setup는 앞의 la t1, __bss_end 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

226(빈 줄)

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

227spl_clear_bss_loop:

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

228SREG zero, 0(t0)

원본 228번 줄의 SREG zero, 0(t0)는 앞의 spl_clear_bss_loop: 결과를 받아 다음 addi t0, t0, REGBYTES로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

229addi t0, t0, REGBYTES

원본 229번 줄의 addi t0, t0, REGBYTES는 앞의 SREG zero, 0(t0) 결과를 받아 다음 blt t0, t1, spl_clear_bss_loop로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

230blt t0, t1, spl_clear_bss_loop

원본 230번 줄의 blt t0, t1, spl_clear_bss_loop는 앞의 addi t0, t0, REGBYTES 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

231(빈 줄)

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

232spl_stack_gd_setup:

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

233jal spl_relocate_stack_gd

원본 233번 줄의 jal spl_relocate_stack_gd는 앞의 spl_stack_gd_setup: 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

234(빈 줄)

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

02. secondary hart가 software interrupt를 기다리는 loop

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를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

439sub sp, a1, t0

sub sp, a1, t0 명령이 register, memory 또는 PC를 직접 바꾼다. MMU와 일반 scheduler가 없는 multi-hart 초기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.

440(빈 줄)

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

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를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

443#endif

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

444(빈 줄)

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

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를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

451(빈 줄)

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

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 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.

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를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

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를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

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를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

460(빈 줄)

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

461mv a0, tp

원본 461번 줄의 mv a0, tp는 앞의 이전 block 경계 결과를 받아 다음 jal handle_ipi로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

462jal handle_ipi

원본 462번 줄의 jal handle_ipi는 앞의 mv a0, tp 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

463#endif

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

464(빈 줄)

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

465j secondary_hart_loop

원본 465번 줄의 j secondary_hart_loop는 앞의 이전 block 경계 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

466(빈 줄)

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

03. per-hart stack 크기와 CPU 개수를 제공하는 설정

arch/riscv/include/asm/global_data.h 15-65줄이다.

filearch/riscv/include/asm/global_data.hanchorstruct arch_global_data
15#include <asm/u-boot.h>
16#include <compiler.h>
17#include <config.h>
18
19/* Architecture-specific global data */
20struct arch_global_data {
21	long boot_hart;		/* boot hart id */
22	phys_addr_t firmware_fdt_addr;
23#if CONFIG_IS_ENABLED(RISCV_ACLINT)
24	void __iomem *aclint;	/* aclint base address */
25#endif
26#ifdef CONFIG_ANDES_PLICSW
27	void __iomem *plicsw;	/* andes plicsw base address */
28#endif
29#if CONFIG_IS_ENABLED(SMP)
30	struct ipi_data ipi[CONFIG_NR_CPUS];
31#endif
32#if !CONFIG_IS_ENABLED(XIP)
33#ifdef CONFIG_AVAILABLE_HARTS
34	ulong available_harts;
35#endif
36#endif
37#if CONFIG_IS_ENABLED(ACPI)
38	ulong table_start;		/* Start address of ACPI tables */
39	ulong table_end;		/* End address of ACPI tables */
40	ulong table_start_high;		/* Start address of high ACPI tables */
41	ulong table_end_high;		/* End address of high ACPI tables */
42#endif
43#ifdef CONFIG_SMBIOS
44	ulong smbios_start;		/* Start address of SMBIOS table */
45#endif
46	struct resume_data *resume;
47};
48
49#include <asm-generic/global_data.h>
50
51#if defined(__clang__) || CONFIG_IS_ENABLED(LTO)
52
53#define DECLARE_GLOBAL_DATA_PTR
54#define gd			get_gd()
55
56static inline gd_t *get_gd(void)
57{
58	gd_t *gd_ptr;
59
60	__asm__ volatile ("mv %0, gp\n" : "=r" (gd_ptr));
61
62	return gd_ptr;
63}
64
65#else

15-65줄 해설

15#include <asm/u-boot.h>

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

16#include <compiler.h>

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

17#include <config.h>

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

18(빈 줄)

#include <config.h>까지의 동작과 /* Architecture-specific global data */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 hart_lottery 상태가 아래 블록의 입력으로 사용되는 경계다.

19/* Architecture-specific global data */

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

20struct arch_global_data {

struct arch_global_data {를 선언한다. 함수 안 선언이면 현재 stack frame, file scope와 static이면 image의 data/BSS에 놓인다. 이 값이 per-hart stack / hart_lottery / gd_t를 직접 소유하는지 pointer만 빌리는지, MMU와 일반 scheduler가 없는 multi-hart 초기 문맥를 벗어난 뒤에도 참조되는지 다음 대입과 callback 등록까지 따라간다.

21long boot_hart; /* boot hart id */

원본 21번 줄의 long boot_hart; /* boot hart id */는 앞의 struct arch_global_data { 결과를 받아 다음 phys_addr_t firmware_fdt_addr;로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

22phys_addr_t firmware_fdt_addr;

원본 22번 줄의 phys_addr_t firmware_fdt_addr;는 앞의 long boot_hart; /* boot hart id */ 결과를 받아 다음 #if CONFIG_IS_ENABLED(RISCV_ACLINT)로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

23#if CONFIG_IS_ENABLED(RISCV_ACLINT)

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

24void __iomem *aclint; /* aclint base address */

원본 24번 줄의 void __iomem *aclint; /* aclint base address */는 앞의 #if CONFIG_IS_ENABLED(RISCV_ACLINT) 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

25#endif

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

26#ifdef CONFIG_ANDES_PLICSW

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

27void __iomem *plicsw; /* andes plicsw base address */

원본 27번 줄의 void __iomem *plicsw; /* andes plicsw base address */는 앞의 #ifdef CONFIG_ANDES_PLICSW 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

28#endif

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

29#if CONFIG_IS_ENABLED(SMP)

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

30struct ipi_data ipi[CONFIG_NR_CPUS];

struct ipi_data ipi[CONFIG_NR_CPUS]를 선언한다. 함수 안 선언이면 현재 stack frame, file scope와 static이면 image의 data/BSS에 놓인다. 이 값이 per-hart stack / hart_lottery / gd_t를 직접 소유하는지 pointer만 빌리는지, MMU와 일반 scheduler가 없는 multi-hart 초기 문맥를 벗어난 뒤에도 참조되는지 다음 대입과 callback 등록까지 따라간다.

31#endif

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

32#if !CONFIG_IS_ENABLED(XIP)

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

33#ifdef CONFIG_AVAILABLE_HARTS

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

34ulong available_harts;

ulong available_harts를 선언한다. 함수 안 선언이면 현재 stack frame, file scope와 static이면 image의 data/BSS에 놓인다. 이 값이 per-hart stack / hart_lottery / gd_t를 직접 소유하는지 pointer만 빌리는지, MMU와 일반 scheduler가 없는 multi-hart 초기 문맥를 벗어난 뒤에도 참조되는지 다음 대입과 callback 등록까지 따라간다.

35#endif

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

36#endif

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

37#if CONFIG_IS_ENABLED(ACPI)

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

38ulong table_start; /* Start address of ACPI tables */

원본 38번 줄의 ulong table_start; /* Start address of ACPI tables */는 앞의 #if CONFIG_IS_ENABLED(ACPI) 결과를 받아 다음 ulong table_end; /* End address of ACPI tables */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

39ulong table_end; /* End address of ACPI tables */

원본 39번 줄의 ulong table_end; /* End address of ACPI tables */는 앞의 ulong table_start; /* Start address of ACPI tables */ 결과를 받아 다음 ulong table_start_high; /* Start address of high ACPI tables */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

40ulong table_start_high; /* Start address of high ACPI tables */

원본 40번 줄의 ulong table_start_high; /* Start address of high ACPI tables */는 앞의 ulong table_end; /* End address of ACPI tables */ 결과를 받아 다음 ulong table_end_high; /* End address of high ACPI tables */로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

41ulong table_end_high; /* End address of high ACPI tables */

원본 41번 줄의 ulong table_end_high; /* End address of high ACPI tables */는 앞의 ulong table_start_high; /* Start address of high ACPI tables */ 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

42#endif

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

43#ifdef CONFIG_SMBIOS

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

44ulong smbios_start; /* Start address of SMBIOS table */

원본 44번 줄의 ulong smbios_start; /* Start address of SMBIOS table */는 앞의 #ifdef CONFIG_SMBIOS 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

45#endif

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

46struct resume_data *resume;

struct resume_data *resume를 선언한다. 함수 안 선언이면 현재 stack frame, file scope와 static이면 image의 data/BSS에 놓인다. 이 값이 per-hart stack / hart_lottery / gd_t를 직접 소유하는지 pointer만 빌리는지, MMU와 일반 scheduler가 없는 multi-hart 초기 문맥를 벗어난 뒤에도 참조되는지 다음 대입과 callback 등록까지 따라간다.

47};

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

48(빈 줄)

};까지의 동작과 #include <asm-generic/global_data.h>에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

49#include <asm-generic/global_data.h>

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

50(빈 줄)

#include <asm-generic/global_data.h>까지의 동작과 #if defined(__clang__) || CONFIG_IS_ENABLED(LTO)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

51#if defined(__clang__) || CONFIG_IS_ENABLED(LTO)

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

52(빈 줄)

#if defined(__clang__) || CONFIG_IS_ENABLED(LTO)까지의 동작과 #define DECLARE_GLOBAL_DATA_PTR에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

53#define DECLARE_GLOBAL_DATA_PTR

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

54#define gd get_gd()

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

55(빈 줄)

#define gd get_gd()까지의 동작과 static inline gd_t *get_gd(void)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

56static inline gd_t *get_gd(void)

get_gd(void)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.

57{

바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 per-hart stack / hart_lottery / gd_t의 수명에 맞춰 묶어 읽는다.

58gd_t *gd_ptr;

원본 58번 줄의 gd_t *gd_ptr;는 앞의 { 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.

59(빈 줄)

gd_t *gd_ptr;까지의 동작과 __asm__ volatile ("mv %0, gp\n" : "=r" (gd_ptr));에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

60__asm__ volatile ("mv %0, gp\n" : "=r" (gd_ptr));

__asm__ volatile ("mv %0, gp\n" : "r" (gd_ptr))를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 board_init_f 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.

61(빈 줄)

__asm__ volatile ("mv %0, gp\n" : "=r" (gd_ptr));까지의 동작과 return gd_ptr;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.

62return gd_ptr;

gd_ptr를 호출자에게 반환한다. caller가 이 값을 검사한 뒤 부분 초기화된 per-hart stack / hart_lottery / gd_t를 정리하거나 다음 단계로 진행하는지 확인한다.

63}

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

64(빈 줄)

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

65#else

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

04. boot hart와 secondary의 tp에 gd pointer를 설치

arch/riscv/cpu/cpu.c 744-755줄이다.

filearch/riscv/cpu/cpu.canchorvoid arch_setup_gd(gd_t *new_gd)
744	disable_interrupts();
745
746	cache_flush();
747
748	return 0;
749}
750
751void arch_setup_gd(gd_t *new_gd)
752{
753	set_gd(new_gd);
754}
755

744-755줄 해설

744disable_interrupts();

disable_interrupts(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 a0 hartid and a1 FDT 항목과 대조한다.

745(빈 줄)

disable_interrupts();까지의 동작과 cache_flush();에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 tp and s1 preserve 상태가 아래 블록의 입력으로 사용되는 경계다.

746cache_flush();

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

747(빈 줄)

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

748return 0;

0를 호출자에게 반환한다. caller가 이 값을 검사한 뒤 부분 초기화된 per-hart stack / hart_lottery / gd_t를 정리하거나 다음 단계로 진행하는지 확인한다.

749}

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

750(빈 줄)

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

751void arch_setup_gd(gd_t *new_gd)

RISC-V의 gd 전용 register인 tp를 새 global data pointer로 교체하는 architecture hook이다.

752{

바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 per-hart stack / hart_lottery / gd_t의 수명에 맞춰 묶어 읽는다.

753set_gd(new_gd);

compiler가 기대하는 tp/gd binding을 실제 new_gd 주소로 갱신한다. 이후 gd-> access의 주소 생산자가 바뀐다.

754}

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

755(빈 줄)

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

내부 동작을 더 깊게 읽기

01

진입 조건을 먼저 고정한다

firmware or reset에서 들어온 실행은 MMU와 일반 scheduler가 없는 multi-hart 초기 문맥에 놓여 있다. 이때 interrupt, MMU/cache, stack, heap 중 무엇이 이미 준비되었는지 소스의 호출자까지 올라가 확인한다. 같은 함수라도 SPL, relocation 전후, app thread처럼 호출 문맥이 달라지면 허용되는 API와 지연 시간이 달라진다.

RISC-V에서는 여러 hart가 동시에 같은 entry에 도착할 수 있다. stack을 hart별로 분리하고 atomic lottery의 winner만 global 초기화를 실행하며, loser는 winner가 게시한 상태를 기다린다.

02

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

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

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

03

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

부트 코드의 오류는 논리보다 주소 계산에서 먼저 드러나는 경우가 많다. source range, destination range, header가 말하는 payload size, block 또는 page 단위를 표로 적고 각 구간의 끝 주소를 직접 계산한다. 끝 주소는 start + size - 1인지 exclusive end인지 API 계약을 확인한다.

어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다.

04

성공 flag와 실제 완료 시점을 맞춘다

hartid and FDT preserve → trap vector and interrupt mask → per-hart stack → hart lottery → boot hart gd → secondary wait 구간에서는 부분 초기화 상태가 생길 수 있다. flag, list insertion, callback 등록, storage write 완료 중 무엇이 성공의 기준인지 찾는다. hardware write나 DMA가 포함되면 함수 반환과 장치 완료가 같은 시점인지도 확인한다.

다른 CPU, interrupt handler, USB completion 또는 shell command가 상태를 관찰할 수 있다면 memory ordering과 lock 범위도 함께 읽는다.

05

마지막 handoff의 계약을 적는다

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

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

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

01

RISC-V U-Boot entry의 a0와 a1은 Linux entry와 비슷하지만 같은 계약은 아니다

M-mode에서는 _startmhartid를 읽어 a0에 넣고, firmware가 들어오는 경로에서는 a0의 hart ID와 a1의 FDT pointer를 보존한다. Linux와 register 모양은 비슷해도 U-Boot는 먼저 자체 gd, stack, hart lottery와 relocation을 완성해야 한다.

초기 code가 a0/a1을 임시 계산에 사용하기 전에 tp와 s1 등에 복사하는 줄을 따라가야 한다. FDT pointer가 relocation source/destination이나 BSS clear와 겹치지 않는지도 memory map에서 확인한다.

02

gp를 0으로 만드는 것은 실수로 relocation 전 global을 쓰는 일을 조기에 드러낸다

초기 entry는 global pointer를 바로 유효하게 만들지 않고 0으로 둔다. small-data access가 너무 일찍 발생하면 조용히 잘못된 global을 읽는 대신 fault나 명확한 오동작으로 드러난다. 이후 relocation 단계와 secondary path에서 load_global_pointer가 책임 있게 gp를 설정한다.

compiler가 예상하지 못한 global access를 assembly helper나 instrumented C 함수에 삽입하지 않았는지 disassembly에서 gp-relative instruction을 검색한다.

03

trap vector와 interrupt mask는 stack보다 먼저 정리한다

tvectrap_entry를 가리키고 IE/IP CSR을 정리한다. 아직 per-hart stack 계산이 끝나지 않았으므로 비동기 interrupt는 막지만, synchronous exception이 발생하면 최소 trap code로 원인을 남길 준비는 한다.

M-mode와 S-mode build는 CSR prefix와 return instruction이 달라진다. 실제 U-Boot가 어느 privilege mode로 link됐는지 .config, objdump, firmware delegation state를 함께 본다.

04

per-hart stack은 하나의 top에서 hart ID만큼 일정 간격으로 내려간다

초기 stack top을 16-byte로 정렬하고 hartid * CONFIG_STACK_SIZE를 빼서 각 hart의 slot을 고른다. 다시 전체 CPU 수만큼 예약 cursor를 내리므로 boot hart의 gd/early malloc과 secondary stack range가 겹치지 않는다.

DT가 보고한 hart ID가 0부터 조밀하지 않을 수 있다는 점을 board code가 어떻게 처리하는지 확인한다. 단순 hart ID 곱셈이 reservation 범위를 넘는 platform은 별도 mapping이나 최대 hart ID 제약이 필요하다.

05

hart lottery는 boot CPU 번호 선택이 아니라 global 초기화 단일 실행을 보장한다

여러 hart가 동시에 _start에 들어올 수 있으므로 atomic swap/add의 이전 값을 보고 한 hart만 gd 생성과 board_init_f로 간다. 나머지는 winner가 게시할 stack, gd 또는 IPI를 기다린다. hart ID가 가장 작은 CPU가 반드시 이기는 것은 아니다.

lottery variable이 BSS clear 대상과 어떤 순서로 초기화되는지, cache coherence가 아직 완전히 설정되지 않은 platform에서 atomic이 어느 memory attribute를 사용하는지 확인한다.

06

gd pointer 게시와 secondary_hart_loop의 소비를 한 쌍으로 본다

winner는 board_init_f_init_reserve로 gd를 만들고 boot hart/FDT를 기록한다. secondary는 자신에게 배정된 pointer가 유효해질 때까지 대기하고, software interrupt 또는 shared slot을 통해 다음 경로를 받는다.

pointer 저장 뒤 fence와 IPI 순서가 잘못되면 secondary가 wakeup은 받았지만 0 또는 오래된 stack/gd를 읽는다. producer의 store와 consumer의 load를 source 양쪽에서 대조한다.

07

boot hart와 secondary hart의 로그를 같은 UART 순서로 해석하지 않는다

여러 hart가 동시에 POST/debug UART를 사용하면 byte가 섞이고 timestamp 순서도 실제 memory publication 순서와 다를 수 있다. 각 로그에 mhartid와 cycle counter를 붙여야 한다.

초기 latency는 winner의 DRAM init 시간과 secondary의 wait 시간을 분리한다. secondary wait upper bound는 boot hart가 relocation과 per-hart data 게시를 마치는 최악 시간에 의해 결정된다.

08

per-hart SP를 실제 hart ID와 reservation 크기로 계산한다

초기 stack top이 0x88000000, CONFIG_STACK_SIZE_SHIFT=14라면 slot 크기는 1 << 14 = 0x4000이다. 공식이 SP(h)=top-(hartid << 14)라면 hart 0은 0x88000000, hart 1은 0x87ffc000, hart 7은 0x87fe4000을 사용한다. 8 hart 전체 reservation은 최소 0x20000이고 각 SP는 ABI의 16-byte 정렬을 만족한다.

DT에 hart 0과 7만 존재해도 raw hart ID를 곱하면 중간 slot까지 포함한 범위를 예약해야 한다. logical CPU index로 압축하는 구현인지, maximum hart ID를 제한하는 구현인지 source를 확인하지 않으면 hart 7 stack이 gd/malloc_f 아래로 벗어날 수 있다.

09

amoswap의 반환값으로 winner 한 명을 증명한다

hart_lottery 초기값이 0이고 각 hart가 새 값 1을 amoswap.w로 쓴다면 정확히 한 hart만 old value 0을 받는다. 그 hart가 boot path로 가고 나머지는 old value 1을 받아 secondary path로 간다. winner의 hart ID가 가장 작다는 보장은 없다.

atomicity만으로 뒤의 gd/FDT publication ordering이 자동 보장되는 것은 아니다. instruction의 aq/rl bit와 별도 fence, IPI send 전 store ordering을 같이 봐야 secondary가 winner의 완성된 데이터를 읽는다.

10

secondary 대기 시간은 boot hart의 가장 긴 선행 경로로 제한된다

secondary wait upper bound는 Tlottery + Tdram + Treserve + Tcopy + Tfixup + Tpublish + Tipi다. 10MHz cycle counter에서 entry 2,000 cycle, DRAM 6,000,000, relocation 800,000, publish/IPI 20,000이면 약 682.2ms다.

각 hart의 cycle counter가 동기화됐다는 보장이 없으면 boot hart가 게시한 sequence number와 local delta를 함께 기록한다. UART byte 순서만으로 682ms의 어느 부분이 긴지 판단하지 않는다.

객체와 수명

대상만들어지는 시점유효 범위확인할 조건
per-hart stack / hart_lottery / gd_thartid and FDT preserveboard_init_f 또는 오류 정리 완료까지어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다.
입력 buffer / metadatafirmware or resetparse와 검증이 끝날 때까지길이, 정렬, 소유권, 변조 가능성
등록된 list / descriptortrap vector and interrupt masksubsystem 종료 또는 image handoff까지이중 등록, dangling pointer, 오류 unwind
hardware 또는 persistent state실제 write/probe가 완료된 뒤reset 또는 명시적 해제·갱신까지flush, timeout, 전원 차단, rollback
RISC-V U-Boot pre-relocation imagelink/image 생성 시점다음 stage가 새 image로 교체할 때까지load address, entry, section과 header 일치

실패 지점과 증상

#조건관찰되는 증상먼저 볼 단계
01hart ID 범위 오류stack reservation 밖 접근hartid and FDT preserve
02lottery/visibility 오류둘 이상의 hart가 global inittrap vector and interrupt mask
03FDT pointer 훼손board identification 실패per-hart stack
04early trap의 stack 부재원인 기록 중 재 faulthart lottery

로그가 끊긴 마지막 함수만 고치지 않는다. 그 함수가 받은 주소, 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 '_start'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
    모든 early log에 mhartid와 cycle counter 추가

    hartid and FDT preserve 진입 전후에 a0 hartid and a1 FDT의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다. 조건이 처음 깨지는 줄을 기록한다.

  2. 02
    hart별 SP range와 CONFIG_STACK_SIZE 대조

    trap vector and interrupt mask 진입 전후에 tp and s1 preserve의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다. 조건이 처음 깨지는 줄을 기록한다.

  3. 03
    lottery variable watchpoint로 winner 확인

    per-hart stack 진입 전후에 per-hart stack slots의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다. 조건이 처음 깨지는 줄을 기록한다.

  4. 04
    secondary wakeup 전후 shared pointer와 fence 기록

    hart lottery 진입 전후에 hart_lottery의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다. 조건이 처음 깨지는 줄을 기록한다.

원문과 다음 글