QUESTION
동시에 깨어난 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를 관찰해야 한다. 이 문장을 기준으로 코드를 위에서 아래로 읽으면, 함수 이름을 외우는 대신 어느 시점에 어떤 상태를 신뢰할 수 있는지 판단할 수 있다.
STRUCTURE
객체와 주소가 놓이는 구조
어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다.
per-hart stack / hart_lottery / gd_t를 중심에 놓고 왼쪽의 입력이 어떤 검사를 거쳐 오른쪽 결과로 공개되는지 표시했다. 실제 디버깅에서는 각 블록의 주소와 크기를 로그에 대입한다.
CALL PATH
실행 흐름
화살표는 단순 호출 순서만 뜻하지 않는다. 각 단계가 성공을 반환할 때 다음 단계가 읽을 수 있는 상태가 무엇인지 함께 확인한다. 오류 반환이 발생하면 바로 다음 화살표로 진행하지 않고 해당 단계의 정리 경로를 따라간다.
STATE LEDGER
단계별 입력과 출력
호출 순서를 함수 이름으로만 외우지 않고, 각 단계가 무엇을 받아 무엇을 공개하는지 적은 표다. 실제 소스에서 생산 필드가 다르면 표를 고치는 방식으로 사용한다.
| # | 단계 | 진입 시 신뢰할 상태 | 성공 뒤 남아야 할 상태 | 다음 소비자 |
|---|---|---|---|---|
| 01 | firmware or reset | MMU와 일반 scheduler가 없는 multi-hart 초기 문맥 | a0 hartid and a1 FDT | hartid and FDT preserve |
| 02 | hartid and FDT preserve | firmware or reset 완료 상태 | tp and s1 preserve | trap vector and interrupt mask |
| 03 | trap vector and interrupt mask | hartid and FDT preserve 완료 상태 | per-hart stack slots | per-hart stack |
| 04 | per-hart stack | trap vector and interrupt mask 완료 상태 | hart_lottery | hart lottery |
| 05 | hart lottery | per-hart stack 완료 상태 | gd publication | boot hart gd |
| 06 | boot hart gd | hart lottery 완료 상태 | secondary_hart_loop | secondary wait |
| 07 | secondary wait | boot hart gd 완료 상태 | secondary_hart_loop | board_init_f |
| 08 | board_init_f | secondary wait 완료 상태 | secondary_hart_loop | 최종 RISC-V U-Boot pre-relocation image 또는 다음 stage |
공통 불변 조건: 어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다. 한 단계의 출력이 다음 단계의 입력 조건을 만족하지 않으면 오류가 실제로 드러난 위치보다 앞의 생산 단계부터 조사한다.
VISUAL WALKTHROUGH
주소와 객체의 이동을 그림으로 읽기
아래 그림은 호출 이름보다 주소, 객체 수명과 handoff 경계를 먼저 볼 수 있도록 구성했다. 실제 주소와 크기는 사용 중인 보드의 bdinfo, map과 linker symbol을 대입한다.
크기는 개념 비율이다. 실제 분석에서는 map, symbol과 runtime 출력의 시작·끝 주소를 대입한다.
두 열의 객체는 이름이 비슷해도 생성 시점, 사용 가능한 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 뒤 합류
호출 이름만 나열하지 않고, 각 단계가 다음 단계에 넘기는 상태를 기준으로 실행 순서를 표시한다.
UPSTREAM SOURCE
원본 코드
아래 코드는 U-Boot v2026.07의 arch/riscv/cpu/start.S에서 33-246줄을 그대로 가져온 것이다. 설명을 위해 실제 코드를 가짜 의사 코드로 바꾸지 않았다.
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
LINE BY LINE
33-246줄 해설
원본에 보이는 모든 줄을 순서대로 설명한다. 빈 줄도 block 경계로 남겨, 코드와 설명의 위치가 어긋나지 않게 했다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 .section .data에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 tp and s1 preserve 상태가 아래 블록의 입력으로 사용되는 경계다.
.section .data원본 35번 줄의 .section .data는 앞의 이전 block 경계 결과를 받아 다음 secondary_harts_relocation_error:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
secondary_harts_relocation_error:'secondary_harts_relocation_error' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
.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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄).ascii "Relocation of secondary harts has failed, error %d\n"까지의 동작과 .section .text에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
.section .text원본 39번 줄의 .section .text는 앞의 이전 block 경계 결과를 받아 다음 .globl _start로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
.globl _start원본 40번 줄의 .globl _start는 앞의 .section .text 결과를 받아 다음 _start:로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
_start:ROM 또는 previous stage가 넘긴 모든 hart가 도착할 수 있는 RISC-V U-Boot entry다.
#if CONFIG_IS_ENABLED(RISCV_MMODE)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
csrr a0, CSR_MHARTIDM-mode build에서 현재 hardware hart ID를 읽어 boot argument a0를 만든다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Save hart id and dtb pointer. The thread pointer register is not원본 주석이 'Save hart id and dtb pointer. The thread pointer register is not'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* modified by C code. It is used by secondary_hart_loop.원본 주석이 'modified by C code. It is used by secondary_hart_loop.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
mv tp, a0hart ID를 tp에 보존한다. 아직 Linux식 current pointer가 아니라 초기 per-hart 계산 값이다.
mv s1, a1firmware가 넘긴 FDT pointer를 relocation 전 호출 사이에 보존한다.
(빈 줄)mv s1, a1까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* after it has been initialized.원본 주석이 'after it has been initialized.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
mv gp, zeroglobal pointer를 일부러 무효화해 너무 이른 gp-relative global 접근을 막는다.
(빈 줄)mv gp, zero까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Set the trap handler. This must happen after initializing gp because원본 주석이 'Set the trap handler. This must happen after initializing gp because'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* the handler may use it.원본 주석이 'the handler may use it.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
la 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
csrw MODE_PREFIX(tvec), t0현재 privilege mode의 trap vector를 early trap entry로 설정한다.
(빈 줄)csrw MODE_PREFIX(tvec), t0까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Mask all interrupts. Interrupts are disabled globally (in m/sstatus)원본 주석이 'Mask all interrupts. Interrupts are disabled globally (in m/sstatus)'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* IPI원본 주석이 'IPI'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
csrw MODE_PREFIX(ie), zeroMODE_PREFIX(ie)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.
(빈 줄)csrw MODE_PREFIX(ie), zero까지의 동작과 #if CONFIG_IS_ENABLED(SMP)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
#if CONFIG_IS_ENABLED(SMP)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
/* check if hart is within range */원본 주석이 'check if hart is within range'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
/* tp: hart id */원본 주석이 'tp: hart id'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
li 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
bge 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)bge tp, t0, hart_out_of_bounds_loop까지의 동작과 /* set xSIE bit to receive IPIs */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/* set xSIE bit to receive IPIs */원본 주석이 'set xSIE bit to receive IPIs'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
#if CONFIG_IS_ENABLED(RISCV_MMODE)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
li 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#else#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
li t0, SIE_SSIE원본 84번 줄의 li t0, SIE_SSIE는 앞의 #else 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
csrs MODE_PREFIX(ie), t0MODE_PREFIX(ie)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Set stackpointer in internal/ex RAM to call board_init_f원본 주석이 'Set stackpointer in internal/ex RAM to call board_init_f'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
call_board_init_f:'call_board_init_f' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
#if CONFIG_IS_ENABLED(HAVE_INIT_STACK)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
li t0, CONFIG_VAL(STACK)CONFIG_VAL(STACK)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.
#else#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
li 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
and 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)and t0, t0, -16 /* force 16 byte alignment */까지의 동작과 /* setup stack */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/* setup stack */원본 주석이 'setup stack'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
#if CONFIG_IS_ENABLED(SMP)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
/* tp: hart id */원본 주석이 'tp: hart id'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
slli t1, tp, CONFIG_STACK_SIZE_SHIFTraw hart ID에 2^CONFIG_STACK_SIZE_SHIFT를 곱해 hart별 stack slot offset을 만든다. sparse hart ID에서는 reservation 상한을 반드시 확인한다.
sub sp, t0, t1sub sp, t0, t1 명령이 register, memory 또는 PC를 직접 바꾼다. MMU와 일반 scheduler가 없는 multi-hart 초기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
#else#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
mv sp, t0원본 106번 줄의 mv sp, t0는 앞의 #else 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Now sp points to the right stack belonging to current CPU.원본 주석이 'Now sp points to the right stack belonging to current CPU.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* It's essential before any function call, otherwise, we get data-race.원본 주석이 'It's essential before any function call, otherwise, we get data-race.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
(빈 줄)*/까지의 동작과 /* clear stack if necessary */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/* clear stack if necessary */원본 주석이 'clear stack if necessary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
#if CONFIG_IS_ENABLED(ZERO_MEM_BEFORE_USE)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
clear_stack:'clear_stack' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
li 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
slli 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
sub t1, sp, t1sub t1, sp, t1 명령이 register, memory 또는 PC를 직접 바꾼다. MMU와 일반 scheduler가 없는 multi-hart 초기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
clear_stack_loop:'clear_stack_loop' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
SREG 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
addi 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
blt 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 call_board_init_f_0:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
call_board_init_f_0:'call_board_init_f_0' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
/* find top of reserve space */원본 주석이 'find top of reserve space'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
#if CONFIG_IS_ENABLED(SMP)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
li 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#else#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
li t1, 1원본 130번 줄의 li t1, 1는 앞의 #else 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
slli 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
sub 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로 확인한다.
jal 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)jal board_init_f_alloc_reserve까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* is not initialized yet.원본 주석이 'is not initialized yet.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
mv s0, a0원본 140번 줄의 mv s0, a0는 앞의 */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)mv s0, a0까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
(빈 줄)이전 block 경계까지의 동작과 /* Configure proprietary settings and customized CSRs of harts */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Configure proprietary settings and customized CSRs of harts */원본 주석이 'Configure proprietary settings and customized CSRs of harts'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
call_harts_early_init:'call_harts_early_init' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
jal 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)jal harts_early_init까지의 동작과 #if !CONFIG_IS_ENABLED(XIP)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
#if !CONFIG_IS_ENABLED(XIP)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* wait for initialization to complete.원본 주석이 'wait for initialization to complete.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
la 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
li 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
amoswap.w s2, t1, 0(t0)hart lottery 값을 원자적으로 교환해 global 초기화를 실행할 한 hart를 고른다.
bnez 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#else#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* problems if it encounters an exception too early (because printf/puts원본 주석이 'problems if it encounters an exception too early (because printf/puts'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* accesses gd).원본 주석이 'accesses gd).'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
mv 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#if CONFIG_IS_ENABLED(RISCV_MMODE)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
bnez tp, secondary_hart_loopwinner가 아닌 hart는 global board 초기화에 진입하지 않고 자신에게 새 실행 상태가 게시될 때까지 secondary loop로 간다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 mv a0, s0에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
mv 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
jal 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)jal board_init_f_init_reserve까지의 동작과 SREG s1, GD_FIRMWARE_FDT_ADDR(gp)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
SREG s1, GD_FIRMWARE_FDT_ADDR(gp)GD_FIRMWARE_FDT_ADDR(gp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.
/* save the boot hart id to global_data */원본 주석이 'save the boot hart id to global_data'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
SREG tp, GD_BOOT_HART(gp)GD_BOOT_HART(gp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.
(빈 줄)SREG tp, GD_BOOT_HART(gp)까지의 동작과 #if !CONFIG_IS_ENABLED(XIP)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
#if !CONFIG_IS_ENABLED(XIP)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#ifdef CONFIG_AVAILABLE_HARTS#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_AVAILABLE_HARTS이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
la 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
amoswap.w.rl zero, zero, 0(t0)boot hart가 shared slot에 release semantic으로 0을 게시해 앞선 gd/stack store가 secondary보다 먼저 보이게 한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 wait_for_gd_init:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
wait_for_gd_init:'wait_for_gd_init' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Set the global data pointer only when gd_t has been initialized.원본 주석이 'Set the global data pointer only when gd_t has been initialized.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* harts' global data pointers gets set here.원본 주석이 'harts' global data pointers gets set here.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
mv gp, s0원본 189번 줄의 mv gp, s0는 앞의 */ 결과를 받아 다음 #ifdef CONFIG_AVAILABLE_HARTS로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
#ifdef CONFIG_AVAILABLE_HARTS#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_AVAILABLE_HARTS이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
la 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
li 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
1: 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
bnez 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)bnez t1, 1b까지의 동작과 /* register available harts in the available_harts mask */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/* register available harts in the available_harts mask */원본 주석이 'register available harts in the available_harts mask'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
li 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
sll 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
LREG t2, GD_AVAILABLE_HARTS(gp)GD_AVAILABLE_HARTS(gp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.
or 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
SREG t2, GD_AVAILABLE_HARTS(gp)GD_AVAILABLE_HARTS(gp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.
(빈 줄)SREG t2, GD_AVAILABLE_HARTS(gp)까지의 동작과 amoswap.w.rl zero, zero, 0(t0)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
amoswap.w.rl zero, zero, 0(t0)boot hart가 shared slot에 release semantic으로 0을 게시해 앞선 gd/stack store가 secondary보다 먼저 보이게 한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Continue on hart lottery winner, others branch to원본 주석이 'Continue on hart lottery winner, others branch to'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* secondary_hart_loop.원본 주석이 'secondary_hart_loop.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
bnez 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 #ifdef CONFIG_DEBUG_UART에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
#ifdef CONFIG_DEBUG_UART#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_DEBUG_UART이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
jal 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 mv a0, zero /* a0 <-- boot_flags = 0 */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
mv 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
la 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
jalr t5 /* jump to board_init_f() */board_init_f(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.
(빈 줄)jalr t5 /* jump to board_init_f() */까지의 동작과 #ifdef CONFIG_XPL_BUILD에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
#ifdef CONFIG_XPL_BUILD#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_XPL_BUILD이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
spl_clear_bss:'spl_clear_bss' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
la 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
la 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
beq 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)beq t0, t1, spl_stack_gd_setup까지의 동작과 spl_clear_bss_loop:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
spl_clear_bss_loop:'spl_clear_bss_loop' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
SREG 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
addi 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
blt 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)blt t0, t1, spl_clear_bss_loop까지의 동작과 spl_stack_gd_setup:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
spl_stack_gd_setup:'spl_stack_gd_setup' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
jal 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)jal spl_relocate_stack_gd까지의 동작과 /* skip setup if we did not relocate */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/* skip setup if we did not relocate */원본 주석이 'skip setup if we did not relocate'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
beqz 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
mv 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)mv s0, a0까지의 동작과 /* setup stack on main hart */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/* setup stack on main hart */원본 주석이 'setup stack on main hart'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
#if CONFIG_IS_ENABLED(SMP)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
/* tp: hart id */원본 주석이 'tp: hart id'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
slli t0, tp, CONFIG_STACK_SIZE_SHIFT원본 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
sub sp, s0, t0sub sp, s0, t0 명령이 register, memory 또는 PC를 직접 바꾼다. MMU와 일반 scheduler가 없는 multi-hart 초기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
#else#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
mv sp, s0원본 245번 줄의 mv sp, s0는 앞의 #else 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
RELATED SOURCE
함께 읽어야 하는 원본 코드
첫 코드 조각만으로 동작이 완성되지 않는 경우 호출 매크로, 객체 정의와 실제 실행 목록을 같은 페이지에 묶었다. 각 조각은 같은 기준 commit에서 가져왔다.
01. winner와 secondary를 가르는 hart lottery 구간
arch/riscv/cpu/start.S 128-234줄이다.
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줄 해설
li 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#else#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
li t1, 1원본 130번 줄의 li t1, 1는 앞의 #else 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
slli 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
sub 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로 확인한다.
jal 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)jal board_init_f_alloc_reserve까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* is not initialized yet.원본 주석이 'is not initialized yet.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
mv s0, a0원본 140번 줄의 mv s0, a0는 앞의 */ 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)mv s0, a0까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
(빈 줄)이전 block 경계까지의 동작과 /* Configure proprietary settings and customized CSRs of harts */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Configure proprietary settings and customized CSRs of harts */원본 주석이 'Configure proprietary settings and customized CSRs of harts'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
call_harts_early_init:'call_harts_early_init' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
jal 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)jal harts_early_init까지의 동작과 #if !CONFIG_IS_ENABLED(XIP)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
#if !CONFIG_IS_ENABLED(XIP)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* wait for initialization to complete.원본 주석이 'wait for initialization to complete.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
la 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
li 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
amoswap.w s2, t1, 0(t0)hart lottery 값을 원자적으로 교환해 global 초기화를 실행할 한 hart를 고른다.
bnez 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#else#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* problems if it encounters an exception too early (because printf/puts원본 주석이 'problems if it encounters an exception too early (because printf/puts'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* accesses gd).원본 주석이 'accesses gd).'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
mv 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#if CONFIG_IS_ENABLED(RISCV_MMODE)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
bnez tp, secondary_hart_loopwinner가 아닌 hart는 global board 초기화에 진입하지 않고 자신에게 새 실행 상태가 게시될 때까지 secondary loop로 간다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 mv a0, s0에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
mv 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
jal 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)jal board_init_f_init_reserve까지의 동작과 SREG s1, GD_FIRMWARE_FDT_ADDR(gp)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
SREG s1, GD_FIRMWARE_FDT_ADDR(gp)GD_FIRMWARE_FDT_ADDR(gp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.
/* save the boot hart id to global_data */원본 주석이 'save the boot hart id to global_data'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
SREG tp, GD_BOOT_HART(gp)GD_BOOT_HART(gp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.
(빈 줄)SREG tp, GD_BOOT_HART(gp)까지의 동작과 #if !CONFIG_IS_ENABLED(XIP)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
#if !CONFIG_IS_ENABLED(XIP)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#ifdef CONFIG_AVAILABLE_HARTS#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_AVAILABLE_HARTS이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
la 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
amoswap.w.rl zero, zero, 0(t0)boot hart가 shared slot에 release semantic으로 0을 게시해 앞선 gd/stack store가 secondary보다 먼저 보이게 한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 wait_for_gd_init:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
wait_for_gd_init:'wait_for_gd_init' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Set the global data pointer only when gd_t has been initialized.원본 주석이 'Set the global data pointer only when gd_t has been initialized.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* 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'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* harts' global data pointers gets set here.원본 주석이 'harts' global data pointers gets set here.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
mv gp, s0원본 189번 줄의 mv gp, s0는 앞의 */ 결과를 받아 다음 #ifdef CONFIG_AVAILABLE_HARTS로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
#ifdef CONFIG_AVAILABLE_HARTS#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_AVAILABLE_HARTS이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
la 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
li 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
1: 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
bnez 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)bnez t1, 1b까지의 동작과 /* register available harts in the available_harts mask */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/* register available harts in the available_harts mask */원본 주석이 'register available harts in the available_harts mask'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
li 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
sll 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
LREG t2, GD_AVAILABLE_HARTS(gp)GD_AVAILABLE_HARTS(gp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.
or 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
SREG t2, GD_AVAILABLE_HARTS(gp)GD_AVAILABLE_HARTS(gp)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.
(빈 줄)SREG t2, GD_AVAILABLE_HARTS(gp)까지의 동작과 amoswap.w.rl zero, zero, 0(t0)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
amoswap.w.rl zero, zero, 0(t0)boot hart가 shared slot에 release semantic으로 0을 게시해 앞선 gd/stack store가 secondary보다 먼저 보이게 한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Continue on hart lottery winner, others branch to원본 주석이 'Continue on hart lottery winner, others branch to'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* secondary_hart_loop.원본 주석이 'secondary_hart_loop.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
bnez 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 #ifdef CONFIG_DEBUG_UART에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
#ifdef CONFIG_DEBUG_UART#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_DEBUG_UART이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
jal 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 mv a0, zero /* a0 <-- boot_flags = 0 */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
mv 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
la 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
jalr t5 /* jump to board_init_f() */board_init_f(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.
(빈 줄)jalr t5 /* jump to board_init_f() */까지의 동작과 #ifdef CONFIG_XPL_BUILD에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
#ifdef CONFIG_XPL_BUILD#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_XPL_BUILD이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
spl_clear_bss:'spl_clear_bss' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
la 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
la 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
beq 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)beq t0, t1, spl_stack_gd_setup까지의 동작과 spl_clear_bss_loop:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
spl_clear_bss_loop:'spl_clear_bss_loop' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
SREG 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
addi 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
blt 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)blt t0, t1, spl_clear_bss_loop까지의 동작과 spl_stack_gd_setup:에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
spl_stack_gd_setup:'spl_stack_gd_setup' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
jal 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)jal spl_relocate_stack_gd까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
02. secondary hart가 software interrupt를 기다리는 loop
arch/riscv/cpu/start.S 437-466줄이다.
437 /* setup stack */
438 slli t0, tp, CONFIG_STACK_SIZE_SHIFT
439 sub sp, a1, t0
440
441 /* update global data pointer */
442 mv gp, a2
443#endif
444
445/*
446 * Interrupts are disabled globally, but they can still be read from m/sip. The
447 * wfi function will wake us up if we get an IPI, even if we do not trap.
448 */
449secondary_hart_loop:
450 wfi
451
452#if CONFIG_IS_ENABLED(SMP)
453 csrr t0, MODE_PREFIX(ip)
454#if CONFIG_IS_ENABLED(RISCV_MMODE)
455 andi t0, t0, MIE_MSIE
456#else
457 andi t0, t0, SIE_SSIE
458#endif
459 beqz t0, secondary_hart_loop
460
461 mv a0, tp
462 jal handle_ipi
463#endif
464
465 j secondary_hart_loop
466
437-466줄 해설
/* setup stack */원본 주석이 'setup stack'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
slli t0, tp, CONFIG_STACK_SIZE_SHIFT원본 438번 줄의 slli t0, tp, CONFIG_STACK_SIZE_SHIFT는 앞의 /* setup stack */ 결과를 받아 다음 sub sp, a1, t0로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
sub sp, a1, t0sub sp, a1, t0 명령이 register, memory 또는 PC를 직접 바꾼다. MMU와 일반 scheduler가 없는 multi-hart 초기 문맥에서 source operand의 이전 값과 destination operand의 새 값을 debugger로 확인한다.
(빈 줄)sub sp, a1, t0까지의 동작과 /* update global data pointer */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 hart_lottery 상태가 아래 블록의 입력으로 사용되는 경계다.
/* update global data pointer */원본 주석이 'update global data pointer'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
mv 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 /*에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
/*원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* Interrupts are disabled globally, but they can still be read from m/sip. The원본 주석이 'Interrupts are disabled globally, but they can still be read from m/sip. The'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
* wfi function will wake us up if we get an IPI, even if we do not trap.원본 주석이 'wfi function will wake us up if we get an IPI, even if we do not trap.'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
*/원본 주석이 'block boundary'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
secondary_hart_loop:'secondary_hart_loop' label이다. 이곳을 참조하는 branch를 역검색하고 각 진입 경로의 register, stack, lock 조건이 같은지 확인한다.
wfi원본 450번 줄의 wfi는 앞의 secondary_hart_loop: 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)wfi까지의 동작과 #if CONFIG_IS_ENABLED(SMP)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
#if CONFIG_IS_ENABLED(SMP)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
csrr t0, MODE_PREFIX(ip)MODE_PREFIX(ip)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.
#if CONFIG_IS_ENABLED(RISCV_MMODE)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
andi t0, t0, MIE_MSIE원본 455번 줄의 andi t0, t0, MIE_MSIE는 앞의 #if CONFIG_IS_ENABLED(RISCV_MMODE) 결과를 받아 다음 #else로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
#else#else 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
andi t0, t0, SIE_SSIE원본 457번 줄의 andi t0, t0, SIE_SSIE는 앞의 #else 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
beqz t0, secondary_hart_loop원본 459번 줄의 beqz t0, secondary_hart_loop는 앞의 #endif 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)beqz t0, secondary_hart_loop까지의 동작과 mv a0, tp에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
mv a0, tp원본 461번 줄의 mv a0, tp는 앞의 이전 block 경계 결과를 받아 다음 jal handle_ipi로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
jal handle_ipi원본 462번 줄의 jal handle_ipi는 앞의 mv a0, tp 결과를 받아 다음 #endif로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#endif까지의 동작과 j secondary_hart_loop에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
j secondary_hart_loop원본 465번 줄의 j secondary_hart_loop는 앞의 이전 block 경계 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)j secondary_hart_loop까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
03. per-hart stack 크기와 CPU 개수를 제공하는 설정
arch/riscv/include/asm/global_data.h 15-65줄이다.
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줄 해설
#include <asm/u-boot.h>#include 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#include <compiler.h>#include 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#include <config.h>#include 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#include <config.h>까지의 동작과 /* Architecture-specific global data */에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 hart_lottery 상태가 아래 블록의 입력으로 사용되는 경계다.
/* Architecture-specific global data */원본 주석이 'Architecture-specific global data'라고 기록한 줄이다. 바로 아래 구현이 이 전제와 같은 순서·단위를 사용하는지 대조한다.
struct 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 등록까지 따라간다.
long 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
phys_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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#if CONFIG_IS_ENABLED(RISCV_ACLINT)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
void __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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#ifdef CONFIG_ANDES_PLICSW#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_ANDES_PLICSW이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
void __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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#if CONFIG_IS_ENABLED(SMP)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
struct 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 등록까지 따라간다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#if !CONFIG_IS_ENABLED(XIP)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#ifdef CONFIG_AVAILABLE_HARTS#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_AVAILABLE_HARTS이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
ulong 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 등록까지 따라간다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#if CONFIG_IS_ENABLED(ACPI)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
ulong 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
ulong 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
ulong 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
ulong 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#ifdef CONFIG_SMBIOS#ifdef 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_SMBIOS이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
ulong 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를 관찰해야 한다' 조건이 유지되는지 확인한다.
#endif#endif 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
struct 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 등록까지 따라간다.
};현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)};까지의 동작과 #include <asm-generic/global_data.h>에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
#include <asm-generic/global_data.h>#include 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#include <asm-generic/global_data.h>까지의 동작과 #if defined(__clang__) || CONFIG_IS_ENABLED(LTO)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
#if defined(__clang__) || CONFIG_IS_ENABLED(LTO)#if 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. 관련 설정은 CONFIG_IS_ENABLED이다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#if defined(__clang__) || CONFIG_IS_ENABLED(LTO)까지의 동작과 #define DECLARE_GLOBAL_DATA_PTR에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
#define DECLARE_GLOBAL_DATA_PTR#define 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
#define gd get_gd()#define 전처리 지시문으로 이 줄 아래의 code가 binary에 존재할지를 결정한다. architecture 또는 build stage 조건을 여닫는다. 현재 .config와 compiler의 -dD -E 출력에서 실제로 남은 branch를 확인한 뒤 line-by-line 흐름에 포함한다.
(빈 줄)#define gd get_gd()까지의 동작과 static inline gd_t *get_gd(void)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
static inline gd_t *get_gd(void)get_gd(void)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 secondary_hart_loop 항목과 대조한다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 per-hart stack / hart_lottery / gd_t의 수명에 맞춰 묶어 읽는다.
gd_t *gd_ptr;원본 58번 줄의 gd_t *gd_ptr;는 앞의 { 결과를 받아 다음 다음 block 경계로 넘기는 중간 연산이다. 이 줄이 바꾸는 register·field·list link를 찾고, 변경 뒤에도 '어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다' 조건이 유지되는지 확인한다.
(빈 줄)gd_t *gd_ptr;까지의 동작과 __asm__ volatile ("mv %0, gp\n" : "=r" (gd_ptr));에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
__asm__ volatile ("mv %0, gp\n" : "=r" (gd_ptr));__asm__ volatile ("mv %0, gp\n" : "에 r" (gd_ptr))를 = 연산으로 반영해 현재 scope의 계산 결과를 저장한다. 이후 board_init_f 단계가 이 값을 처음 소비하는 지점을 찾는다. 실패 경로가 이 field를 이전 값으로 되돌리거나 객체 전체를 폐기하는지도 확인한다.
(빈 줄)__asm__ volatile ("mv %0, gp\n" : "=r" (gd_ptr));까지의 동작과 return gd_ptr;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
return gd_ptr;gd_ptr를 호출자에게 반환한다. caller가 이 값을 검사한 뒤 부분 초기화된 per-hart stack / hart_lottery / gd_t를 정리하거나 다음 단계로 진행하는지 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 #else에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
#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줄이다.
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줄 해설
disable_interrupts();disable_interrupts(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 a0 hartid and a1 FDT 항목과 대조한다.
(빈 줄)disable_interrupts();까지의 동작과 cache_flush();에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 tp and s1 preserve 상태가 아래 블록의 입력으로 사용되는 경계다.
cache_flush();cache_flush(인자 없음)를 호출한다. 반환값을 직접 사용하지 않으므로 이 함수가 실패를 내부 처리하는지 확인해야 한다. pointer 인자는 per-hart stack / hart_lottery / gd_t의 소유권을 넘기는지 호출 동안만 빌리는지 구분하고, 호출 뒤 공개되는 상태를 per-hart stack slots 항목과 대조한다.
(빈 줄)cache_flush();까지의 동작과 return 0;에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 hart_lottery 상태가 아래 블록의 입력으로 사용되는 경계다.
return 0;0를 호출자에게 반환한다. caller가 이 값을 검사한 뒤 부분 초기화된 per-hart stack / hart_lottery / gd_t를 정리하거나 다음 단계로 진행하는지 확인한다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 void arch_setup_gd(gd_t *new_gd)에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
void arch_setup_gd(gd_t *new_gd)RISC-V의 gd 전용 register인 tp를 새 global data pointer로 교체하는 architecture hook이다.
{바로 위 함수·조건·초기화의 block이 열린다. 이 scope 안에서 만들어지는 지역 객체와 오류 이동 지점을 per-hart stack / hart_lottery / gd_t의 수명에 맞춰 묶어 읽는다.
set_gd(new_gd);compiler가 기대하는 tp/gd binding을 실제 new_gd 주소로 갱신한다. 이후 gd-> access의 주소 생산자가 바뀐다.
}현재 block, initializer 또는 호출의 경계를 닫는다. 이 지점까지 획득한 resource가 성공 경로와 실패 경로에서 대칭인지 점검한다.
(빈 줄)}까지의 동작과 다음 block 경계에서 시작하는 동작을 나누는 빈 줄이다. 앞 블록이 만든 secondary_hart_loop 상태가 아래 블록의 입력으로 사용되는 경계다.
DETAILS
내부 동작을 더 깊게 읽기
진입 조건을 먼저 고정한다
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가 게시한 상태를 기다린다.
중심 객체의 생성과 공개를 나눈다
이 글의 중심 객체는 per-hart stack / hart_lottery / gd_t다. 메모리를 확보한 시점, 필드를 채운 시점, 전역 list나 다른 subsystem에 공개한 시점을 구분한다. 공개 뒤 오류가 발생한다면 목록에서 제거하고 child, buffer, reference를 역순으로 정리하는지 확인한다.
빌드 산출물 관점에서는 RISC-V U-Boot pre-relocation image 안에 해당 symbol과 section이 실제로 포함되었는지도 map과 objdump로 검증한다.
주소, 크기와 정렬을 계산한다
부트 코드의 오류는 논리보다 주소 계산에서 먼저 드러나는 경우가 많다. 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를 관찰해야 한다.
성공 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 범위도 함께 읽는다.
마지막 handoff의 계약을 적는다
정상 경로는 board_init_f에서 끝난다. 이 단계가 함수 반환인지, scheduler 전환인지, 다른 image로의 비복귀 분기인지 구분한다. 비복귀 handoff라면 cache clean/invalidate, interrupt disable, 장치 quiesce, argument register와 FDT 또는 image address가 최종 점검 항목이다.
반환하는 경로라면 caller가 오류와 부분 성공을 구분하고 다음 후보 또는 복구 경로를 선택하는지 확인한다.
IMPLEMENTATION NOTES
구현을 읽을 때 놓치기 쉬운 부분
RISC-V U-Boot entry의 a0와 a1은 Linux entry와 비슷하지만 같은 계약은 아니다
M-mode에서는 _start가 mhartid를 읽어 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에서 확인한다.
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을 검색한다.
trap vector와 interrupt mask는 stack보다 먼저 정리한다
tvec는 trap_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를 함께 본다.
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 제약이 필요하다.
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를 사용하는지 확인한다.
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 양쪽에서 대조한다.
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 게시를 마치는 최악 시간에 의해 결정된다.
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 아래로 벗어날 수 있다.
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의 완성된 데이터를 읽는다.
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의 어느 부분이 긴지 판단하지 않는다.
OBJECT LIFETIME
객체와 수명
| 대상 | 만들어지는 시점 | 유효 범위 | 확인할 조건 |
|---|---|---|---|
per-hart stack / hart_lottery / gd_t | hartid and FDT preserve | board_init_f 또는 오류 정리 완료까지 | 어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다. |
| 입력 buffer / metadata | firmware or reset | parse와 검증이 끝날 때까지 | 길이, 정렬, 소유권, 변조 가능성 |
| 등록된 list / descriptor | trap vector and interrupt mask | subsystem 종료 또는 image handoff까지 | 이중 등록, dangling pointer, 오류 unwind |
| hardware 또는 persistent state | 실제 write/probe가 완료된 뒤 | reset 또는 명시적 해제·갱신까지 | flush, timeout, 전원 차단, rollback |
RISC-V U-Boot pre-relocation image | link/image 생성 시점 | 다음 stage가 새 image로 교체할 때까지 | load address, entry, section과 header 일치 |
FAILURE PATH
실패 지점과 증상
| # | 조건 | 관찰되는 증상 | 먼저 볼 단계 |
|---|---|---|---|
| 01 | hart ID 범위 오류 | stack reservation 밖 접근 | hartid and FDT preserve |
| 02 | lottery/visibility 오류 | 둘 이상의 hart가 global init | trap vector and interrupt mask |
| 03 | FDT pointer 훼손 | board identification 실패 | per-hart stack |
| 04 | early trap의 stack 부재 | 원인 기록 중 재 fault | hart lottery |
로그가 끊긴 마지막 함수만 고치지 않는다. 그 함수가 받은 주소, size, flag가 만들어진 앞 단계까지 올라가고, 오류 뒤 등록 객체와 hardware 상태가 남았는지도 확인한다.
EVIDENCE
소스 밖에서 확인할 증거
소스 해석은 실제 빌드 산출물과 target 로그로 닫아야 한다. 아래 명령의 보드 이름과 toolchain prefix는 사용 중인 빌드 환경에 맞게 바꾼다.
| # | 목적 | 명령 또는 계측 | 판정 기준 |
|---|---|---|---|
| 01 | 전처리 결과 | make <board>_defconfig && make V=1 | 선택된 CONFIG와 실제 compile/link 명령을 보관한다. |
| 02 | symbol과 section | ${CROSS_COMPILE}nm -n u-boot | grep '_start' | symbol 주소가 기대한 stage와 image에 들어갔는지 확인한다. |
| 03 | 어셈블리 대조 | ${CROSS_COMPILE}objdump -drS u-boot | C 한 줄이 실제 branch, relocation, memory access로 어떻게 바뀌었는지 본다. |
| 04 | 주소 배치 | bdinfo | relocaddr, malloc, FDT, stack과 DRAM bank 주소를 한 번에 기록한다. |
| 05 | runtime 관찰 | dm tree; dm uclass; printenv | driver model과 환경이 이 단계에서 실제로 공개되었는지 확인한다. |
LAB
직접 확인할 실험
- 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를 관찰해야 한다. 조건이 처음 깨지는 줄을 기록한다. - 02hart별 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를 관찰해야 한다. 조건이 처음 깨지는 줄을 기록한다. - 03lottery variable watchpoint로 winner 확인
per-hart stack진입 전후에 per-hart stack slots의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다. 조건이 처음 깨지는 줄을 기록한다. - 04secondary wakeup 전후 shared pointer와 fence 기록
hart lottery진입 전후에 hart_lottery의 주소·크기·반환값과 timestamp를 함께 남긴다. 결과는 정상 부팅 여부로 끝내지 말고 어느 hart가 winner가 되더라도 각 hart stack이 겹치지 않고, boot hart가 gd와 FDT를 완성한 뒤에만 secondary가 그 pointer를 관찰해야 한다. 조건이 처음 깨지는 줄을 기록한다.
PRIMARY REFERENCES