OpenSBI v1.9 · master 6ad246a · RISC-V M-mode

OpenSBI는 여러 hart 중 누구에게 초기화를 맡깁니까?

진입한 hart들은 같은 _start에서 출발합니다. fw_base.S는 한 hart만 relocation과 공용 초기화를 수행하게 하고, 나머지는 _boot_status를 보며 기다리게 합니다. 여기서는 v1.9와 헤더가 있는 고정 커밋 6ad246a를 비교합니다. 두 리비전의 lottery 분기와 대기 방식은 같습니다.

펌웨어 종류와 넘겨받는 레지스터

OpenSBI의 일반 펌웨어는 FW_DYNAMIC, FW_JUMP, FW_PAYLOAD 세 가지입니다. 모두 fw_base.S의 시작 코드를 공유하지만, 이전 단계의 인자 처리와 다음 단계 이미지를 준비하는 방법이 다릅니다. FW_JUMP는 빌드 때 정한 주소로 넘어가고, FW_PAYLOAD는 다음 단계 이미지를 포함합니다. FW_DYNAMIC은 다음 단계 주소·모드·옵션을 struct fw_dynamic_info에서 읽고, 다음 단계용 arg1은 a1에서 저장합니다.

레지스터이전 단계가 넣는 값
a0이 펌웨어로 들어오는 hart의 ID
a1device tree blob 주소. 8바이트 정렬이어야 합니다.
a2FW_DYNAMIC에서는 struct fw_dynamic_info의 주소. RV64는 8바이트, RV32는 4바이트 정렬입니다.

이 레지스터 규약은 v1.9와 고정 커밋 6ad246a에서 같습니다. 후자는 a1·a2 값을 이미지 헤더로 넘기고, a0를 각 hart의 mhartid에서 얻는 선택지를 추가합니다. 헤더의 flags가 켜진 인자만 바뀝니다. v1.9 펌웨어 문서 FW_DYNAMIC 문서

fw_boot_hart가 갈림길을 정합니다

v1.9의 _start(48번 줄)는 a0~a2를 s0~s2에 보관한 뒤 fw_boot_hart를 호출하고, 반환값을 a6에 둡니다. a6가 -1이면 _try_lottery로 갑니다. 특정 hart가 지정되면 나머지는 _wait_for_boot_hart로 분기하지만, 지정된 hart는 바로 다음의 _try_lottery로 이어집니다. 지정은 참여 hart를 제한할 뿐, lottery 코드를 생략하지 않습니다. fw_base.S _start

펌웨어fw_boot_hart 반환값결과
FW_JUMP, FW_PAYLOAD항상 -1모든 hart가 lottery에 참여합니다.
FW_DYNAMIC, info version 2fw_dynamic_info.boot_hart지정된 hart만 lottery로 이어지고 나머지는 기다립니다. -1UL이면 진입한 hart들이 lottery에 참여합니다.
FW_DYNAMIC, info version 1-1lottery를 씁니다.

FW_DYNAMIC의 fw_boot_hart는 magic 0x4942534f와 버전 상한 2를 검사합니다. 정상적인 version 1은 -1을 반환하고 version 2는 boot_hart를 읽으므로, 이 구현을 version 2 이상 모두 지원한다고 읽으면 안 됩니다. fw_dynamic.h 주석은 이전 단계와 FW_DYNAMIC의 링크 주소가 겹칠 때, 다른 hart가 이전 단계 코드를 실행하는 동안 relocation이 이를 덮어쓸 위험을 설명합니다. 이를 피하기 위해 이전 단계는 마지막으로 진입하는 hart를 boot_hart로 지정할 수 있습니다. fw_dynamic.S fw_boot_hart fw_dynamic.h boot_hart 주석 fw_jump.S fw_boot_hart

lottery를 이긴 hart가 하는 일

_try_lottery(58번 줄)는 A 또는 Zaamo가 정의된 빌드에서 amoswap.w로 _boot_lottery에 1을 쓰고 이전 값을 봅니다. 이전 값이 0인 hart만 진행하고 나머지는 기다립니다. 이 조건이 아니고 Zalrsc가 정의되어 있으면 lr.w·sc.w 루프를 사용합니다. sc.w 실패는 재시도하며, 성공 후 읽었던 이전 값이 0이 아니면 기다립니다. 아래는 두 구현을 원자적 획득으로 묶은 흐름 요약입니다. _try_lottery

_start:              save a0..a2; preferred = fw_boot_hart()
  preferred == -1  -> _try_lottery
  a0 != preferred  -> _wait_for_boot_hart
  a0 == preferred  -> fall through to _try_lottery
_try_lottery:        atomic acquire (_boot_lottery, 1)
  old != 0         -> _wait_for_boot_hart
  R_RISCV_RELATIVE relocation; zero .bss; temporary trap/stack
  fw_save_info; fw_platform_init; scratch setup; optional FDT copy
  fence rw, rw; _boot_status = BOOT_HART_DONE; j _start_warm
_wait_for_boot_hart: spin until _boot_status == BOOT_HART_DONE
_start_warm:         validate hart index; scratch/stack/trap -> sbi_init
단계줄의미
relocation75–95링크 시작 주소 FW_TEXT_START와 실제 로드 주소의 차이를 구해 __rela_dyn의 R_RISCV_RELATIVE 항목을 고칩니다.
초기 준비96–164레지스터 초기화, .bss를 0으로 채움, 임시 trap 핸들러(_start_hang)와 임시 stack 설정, fw_save_info와 fw_platform_init 호출입니다.
_scratch_init165–254모든 hart의 scratch에 펌웨어 범위, 다음 단계 인자·주소·모드, warm boot 주소(_start_warm), platform 포인터, 옵션을 씁니다.
FDT 복사256–297필요하면 이전 단계가 준 FDT를 다음 단계용 주소로 복사합니다.
완료 표시299–304fence rw, rw 뒤에 _boot_status에 BOOT_STATUS_BOOT_HART_DONE을 저장하고 _start_warm으로 갑니다.

기다리는 hart는 _boot_status를 반복해서 읽습니다. div t2, t2, zero 세 줄은 주석상 버스 트래픽을 줄이기 위한 지연입니다. 완료 값이 보이면 _start_warm(317번 줄)으로 이어집니다. 이 경로는 hart index를 확인하고, 유효하지 않으면 _start_hang으로 보냅니다. 유효한 hart는 자신의 scratch와 stack, trap 핸들러를 설정해 385번 줄에서 sbi_init을 호출합니다. 초기화를 담당한 hart도 이 공통 진입 경로를 사용합니다. sbi_init 내부의 이후 동작까지 모든 hart가 같다는 뜻은 아닙니다. 완료 표시와 대기 루프 _start_warm에서 sbi_init까지

v1.9 분석을 master에 옮길 때

항목v1.9master 6ad246a
이미지 첫 명령_start의 레지스터 보관(MOV_3R)헤더를 건너뛰는 j, 실제 시작은 _start_real(111번 줄)
a0~a2의 출처이전 단계의 레지스터기본은 레지스터, flags가 켜진 항목만 헤더나 mhartid
lottery와 대기58번, 307번 줄같은 코드가 152번, 401번 줄로 이동
sbi_init 호출385번 줄479번 줄

이 글의 기준은 v1.9가 가리키는 cbf9f673 커밋과 헤더가 있는 6ad246a 커밋입니다. 현재 master나 보드 공급자의 펌웨어가 이 코드와 같다고 가정하지 않습니다. 첫 j와 OSBI magic은 헤더 식별 단서이며, 값을 패치하기 전에는 고정 레이아웃의 version·XLEN·크기와 메모리 배치를 함께 확인해야 합니다. 줄 번호는 직접 받은 원본 파일 기준입니다. 여기서는 코드와 문서를 대조했으며 보드 실행·펌웨어 빌드 검증은 하지 않았습니다. OpenSBI v1.9 릴리스 master lottery에서 sbi_init까지

공식 근거