U-Boot · source analysis

SPL의 image 선택과 적재

작은 SRAM 단계인 SPL이 boot device를 선택하고 U-Boot proper 또는 OS 이미지를 DRAM으로 적재하는 경로를 읽습니다.

표의 버전은 소스 분석 기준이며 최신 보안 버전 권고가 아닙니다. TF-A LTS 2.14.3 이후에는 보안 수정이 있습니다. 제품 적용 시 공식 변경 기록과 사용 플랫폼의 errata를 확인하세요.

Series
07 / 22
Baseline
v2026.07
Commit
ece349ade297
Source
common/spl/spl.c:648-736 (발췌 범위)

SPL의 image 선택과 적재 단계에서 실제로 바뀌는 상태는 무엇인가?

SPL은 U-Boot가 반드시 거치는 단계가 아니다. Boot ROM의 적재 주소·크기 제한, DDR 초기화 주체와 보드의 후속 이미지·검증 정책에 따라 ROM → U-Boot proper 또는 ROM → SPL → U-Boot proper 같은 경로를 사용한다. 공식 부트 단계 문서에서 TPL/VPL/SPL은 선택 단계다. 아래 loader 분석은 SPL을 사용하는 빌드에 관한 것이며, SPL이 Linux나 다른 펌웨어를 적재하는 구성도 있다.

NXP의 2020년 i.MX 포팅 가이드 §3.2.1에서는 i.MX 6Quad 구성 예의 ROM이 이미지 헤더에 있는 DCD 명령으로 DDR을 초기화한다. 같은 문서의 i.MX 8M Quad/Mini/Nano/Plus BSP 경로는 SPL에서 DDR controller/PHY 초기화와 training을 수행한다. 이는 해당 가이드의 구성 대비이며 모든 i.MX 6·8 보드에 적용되는 부팅 경로를 뜻하지 않는다.

SPL은 크기 제약 때문에 full U-Boot와 같은 기능을 가정할 수 없다. loader 등록, 우선순위, fallback과 spl_image_info 필드가 채워지는 순간을 본다.

선택된 loader는 검증된 크기와 주소를 제공하고 제어권 이전 전에 cache와 실행 환경을 정리해야 한다. 이 문장을 기준으로 코드를 위에서 아래로 읽으면, 함수 이름을 외우는 대신 어느 시점에 어떤 상태를 신뢰할 수 있는지 판단할 수 있다.

객체와 주소가 놓이는 구조

그림 1. SPL의 image 선택과 적재에서 입력, 내부 상태, 출력의 경계
입력과 전제내부 상태외부로 공개되는 결과
01SRAM SPL
02loader methods
03spl_image_info
04DRAM destination

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

실행 흐름

그림 2. SPL이 U-Boot proper를 적재하는 구성의 예
ROM
SPL 진입
boot device
image loader
U-Boot proper

앞선 TPL/VPL을 사용하는 경우 그 인계 단계는 이 그림에서 생략했다. TPL/VPL/SPL 자체는 보드 구성에 따라 생략될 수 있고, SPL의 다음 대상도 U-Boot proper로 고정되지 않는다. 화살표는 단계 간 제어권 인계와 SPL 내부 처리를 함께 요약한 것이며 모두 함수 호출·반환을 뜻하지 않는다.

선택된 loader가 실패하면 구성된 부트 순서에 따라 다음 후보를 시도할 수 있다. 모든 후보가 실패한 경우와 이미지를 적재한 뒤 진입점으로 제어권을 넘기는 경우를 구분한다.

단계별 입력과 출력

각 단계의 입력과 처리 후 바뀌는 상태를 정리한 표다. 사용 중인 소스 버전에서 다른 필드를 변경한다면 그에 맞춰 표도 수정한다.

#단계진입 시 신뢰할 상태성공 뒤 남아야 할 상태다음 소비자
01ROMSoC가 정한 부트 조건해당 구성의 첫 단계 이미지와 진입 주소SPL 또는 앞선 TPL/VPL 경로
02SPL 진입ROM 또는 앞선 단계의 인계 상태SPL 실행 환경과 보드가 정한 부트 후보 순서boot device 선택
03boot device부트 후보 순서와 빌드에 포함된 loader시도할 매체와 해당 loaderimage loader
04image loader매체·형식에 맞는 읽기 조건적재된 이미지와 spl_image_info의 주소·진입점·OS 정보이 예의 U-Boot proper 진입 경로
05U-Boot properSPL이 넘긴 실행 상태와 인수부팅 명령이 준비한 후속 이미지와 인수선택된 OS 또는 후속 펌웨어(부팅 명령 후)

분석 범위의 전제와 비교해, 다음 단계에 잘못된 입력을 넘긴 부분부터 조사한다.

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

아래 그림은 주소, 객체를 사용하는 기간, 제어권을 넘기는 시점을 중심으로 정리했다. 실제 주소와 크기는 사용 중인 보드의 bdinfo, 맵 파일과 링커 심벌로 확인한다.

그림 3. storage에서 다음 stage entry까지의 image 이동

medium read와 image 해석, 검증, 적재를 한 단계로 뭉치지 않는다.

01boot device 선택
02header read
03format·size 검증
04configuration 선택
05payload read
06hash/signature 검증
07destination 배치
08entry handoff
그림 4. source buffer와 destination이 겹치지 않는 경우와 겹치는 경우

압축 image라면 compressed source와 decompressed output을 모두 그린다.

안전

01SPL text/stack

02read buffer

03검증 범위

04빈 destination

05next-stage image

위험

01SPL text/stack

02read buffer

03destination overlap

04copy 중 source 훼손

05entry 이전 corruption

원본 코드

아래 코드는 U-Boot v2026.07의 common/spl/spl.c에서 648-736줄을 그대로 가져온 것이다. 설명을 위해 실제 코드를 가짜 의사 코드로 바꾸지 않았다.

commitece349ade2973e220f524ce59e59711cc919263fanchor · L672void board_init_r(gd_t *dummy1, ulong dummy2)
648			}
649
650			ret = spl_load_image(spl_image, loader);
651			if (!ret) {
652				spl_image->boot_device = bootdev;
653				return 0;
654			}
655			printf("Error: %d\n", ret);
656		}
657
658		if (!CONFIG_IS_ENABLED(SILENT_CONSOLE)) {
659			if (CONFIG_IS_ENABLED(SHOW_ERRORS))
660				printf(PHASE_PROMPT
661				       "Unsupported Boot Device %d\n",
662				       bootdev);
663			else
664				printf(PHASE_PROMPT
665				       "Unsupported Boot Device!\n");
666		}
667	}
668
669	return ret;
670}
671
672void board_init_r(gd_t *dummy1, ulong dummy2)
673{
674	u32 spl_boot_list[] = {
675		BOOT_DEVICE_NONE,
676		BOOT_DEVICE_NONE,
677		BOOT_DEVICE_NONE,
678		BOOT_DEVICE_NONE,
679		BOOT_DEVICE_NONE,
680	};
681	spl_jump_to_image_t jumper = &jump_to_image;
682	struct spl_image_info spl_image;
683	int ret, os;
684	void *fdt;
685
686	debug(">>" PHASE_PROMPT "board_init_r()\n");
687
688	spl_set_bd();
689
690	if (IS_ENABLED(CONFIG_SPL_SYS_MALLOC)) {
691		mem_malloc_init(SPL_SYS_MALLOC_START, SPL_SYS_MALLOC_SIZE);
692		gd->flags |= GD_FLG_FULL_MALLOC_INIT;
693	}
694	if (!(gd->flags & GD_FLG_SPL_INIT)) {
695		if (spl_init())
696			hang();
697	}
698	timer_init();
699	if (CONFIG_IS_ENABLED(BLOBLIST)) {
700		ret = bloblist_init();
701		if (ret) {
702			debug("%s: Failed to set up bloblist: ret=%d\n",
703			      __func__, ret);
704			puts(PHASE_PROMPT "Cannot set up bloblist\n");
705			hang();
706		}
707	}
708	if (CONFIG_IS_ENABLED(HANDOFF)) {
709		int ret;
710
711		ret = setup_spl_handoff();
712		if (ret) {
713			puts(PHASE_PROMPT "Cannot set up SPL handoff\n");
714			hang();
715		}
716	}
717
718	if (CONFIG_IS_ENABLED(SOC_INIT))
719		spl_soc_init();
720
721	if (IS_ENABLED(CONFIG_SPL_WATCHDOG) && CONFIG_IS_ENABLED(WDT))
722		initr_watchdog();
723
724	if (IS_ENABLED(CONFIG_SPL_OS_BOOT) || CONFIG_IS_ENABLED(HANDOFF) ||
725	    IS_ENABLED(CONFIG_SPL_ATF) || IS_ENABLED(CONFIG_SPL_NET))
726		dram_init_banksize();
727
728	if (IS_ENABLED(CONFIG_SPL_LMB))
729		lmb_init();
730
731	if (CONFIG_IS_ENABLED(PCI) && !(gd->flags & GD_FLG_DM_DEAD)) {
732		ret = pci_init();
733		if (ret)
734			puts(PHASE_PROMPT "Cannot initialize PCI\n");
735		/* Don't fail. We still can try other boot methods. */
736	}

발췌 범위와 읽기 기준

원문 648-736줄에는 앞뒤 문맥이 포함됩니다. 기준 구문 void board_init_r(gd_t *dummy1, ulong dummy2)은 L672에 있습니다. 발췌 첫 줄을 함수 정의의 시작으로 해석하지 않습니다.

SPL은 크기 제약 때문에 full U-Boot와 같은 기능을 가정할 수 없다. loader 등록, 우선순위, fallback과 spl_image_info 필드가 채워지는 순간을 본다.

내부 동작을 더 깊게 읽기

01

진입 조건을 먼저 고정한다

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

SPL은 크기 제약 때문에 full U-Boot와 같은 기능을 가정할 수 없다. loader 등록, 우선순위, fallback과 spl_image_info 필드가 채워지는 순간을 본다.

02

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

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

symbol과 section은 ELF인 spl/u-boot-spl과 spl/u-boot-spl.map에서 확인한다. spl/u-boot-spl.bin은 별도 바이너리 산출물이므로 ELF의 심볼 정보를 그대로 갖는다고 가정하지 않는다. 실제 부트 이미지는 보드에 따라 헤더나 다른 구성 요소를 더해 만들 수 있다.

03

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

부트 코드에서는 주소 계산 오류를 주의해야 한다. 원본과 목적지의 주소 범위, 헤더에 기록된 데이터 크기, 블록·페이지 단위를 표로 정리하고 각 구간의 끝 주소를 계산한다. 끝 주소가 start + size - 1처럼 마지막 바이트를 가리키는지, 구간 바로 다음 주소를 가리키는지는 API 규약에서 확인한다.

04

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

TPL or SPL → boot device → image loader 구간에서는 부분 초기화 상태가 생길 수 있다. flag, list insertion, callback 등록, storage write 완료 중 무엇이 성공의 기준인지 찾는다. hardware write나 DMA가 포함되면 함수 반환과 장치 완료가 같은 시점인지도 확인한다.

다른 CPU, 인터럽트 처리기, USB 완료 콜백, 셸 명령에서도 이 상태를 읽는다면 메모리 접근 순서와 잠금 범위를 함께 확인한다.

05

제어권을 넘기기 전에 확인할 조건을 정리한다

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

호출한 함수로 돌아오는 경로라면 오류와 일부 처리만 완료된 상태를 구분하는지 확인한다. 이어서 다음 후보를 시도할지, 복구 절차를 실행할지 살펴본다.

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

01

SPL loader는 boot device를 image reader로 추상화한다

ROM이나 board code가 고른 boot device는 MMC, SPI, NAND, USB 같은 loader로 연결된다. loader는 block 단위와 byte offset, header 위치를 spl_load_info 계약에 맞춰 제공해야 하며 medium별 alignment 차이를 공통 image parser에 노출하지 않아야 한다.

동일한 partition 이름이라도 hardware partition, raw offset, filesystem file 경로가 될 수 있다. 실제 loader가 선택한 source와 offset을 로그에 남겨야 다른 medium의 성공 결과와 섞이지 않는다.

02

spl_image_info는 검증 결과와 실행 계획을 함께 품는다

image type, load address, entry point, payload size, OS와 flags가 parser에서 채워진다. header가 선언한 값과 board가 강제한 값 중 어느 쪽이 우선하는지, FIT configuration을 쓸 때 선택된 subimage 값이 어디에 반영되는지 확인한다.

적재 목적지가 현재 SPL text, stack, BSS, malloc 또는 FDT와 겹치지 않아야 한다. 압축 image는 compressed source와 decompressed destination 두 구간을 모두 계산한다.

03

fallback은 오류 종류에 따라 달라져야 한다

매체가 없다는 오류, image가 없다는 오류, signature가 틀린 오류를 같은 fallback으로 처리하면 보안 정책을 우회할 수 있다. verified boot 실패 뒤 unsigned 다른 medium으로 넘어가는 것이 제품 요구인지 명시해야 한다.

각 loader에 짧은 read, CRC 오류, timeout을 주입하고 다음 후보로 이동하는 return code를 기록한다. watchdog service가 긴 storage retry 동안 유지되는지도 측정한다.

객체와 수명

대상만들어지는 시점유효 범위확인할 조건
struct spl_image_info / spl_boot_deviceTPL or SPLU-Boot proper 또는 오류 정리 완료까지진입 전제와 상태 계약
입력 buffer / metadataROMparse와 검증이 끝날 때까지길이, 정렬, 소유권, 변조 가능성
등록된 list / descriptorboot device하위 시스템을 종료하거나 다음 이미지에 제어권을 넘길 때까지중복 등록, 해제된 객체를 가리키는 포인터, 오류 후 자원 정리
hardware 또는 persistent state실제 write/probe가 완료된 뒤reset 또는 명시적 해제·갱신까지쓰기 완료, 시간 초과, 전원 차단, 이전 상태 복원
spl/u-boot-spl.binSPL 빌드의 바이너리 생성 시점배포용 파일의 수명과 적재된 SPL 메모리의 수명은 별개ELF/map과 바이너리 크기·적재 주소·보드별 이미지 헤더의 관계

실패 지점과 증상

#조건관찰되는 증상먼저 볼 단계
01boot device 오판잘못된 매체 접근TPL or SPL
02image size 신뢰DRAM overwriteboot device
03fallback 순서 오류복구 장치 무시image loader

로그가 끊긴 함수뿐 아니라 그 함수에 주소, 크기, 플래그를 전달한 앞 단계도 조사한다. 오류 처리 후 등록된 객체나 하드웨어 설정이 그대로 남아 있는지도 확인한다.

소스 밖에서 확인할 증거

소스에서 파악한 동작을 실제 빌드 결과와 대상 보드의 로그로 확인한다. 아래 명령의 보드 이름과 툴체인 접두사는 사용 중인 빌드 환경에 맞게 바꾸고, 별도 출력 디렉터리를 쓰면 그 디렉터리에서 ELF 경로를 지정한다. 이 글에서 특정 보드의 빌드·실행을 재현한 것은 아니다.

#목적명령 또는 계측판정 기준
01빌드 설정과 명령make <board>_defconfig && make V=1선택된 CONFIG와 실제 compile/link 명령을 보관한다.
02SPL symbol${CROSS_COMPILE}nm -n spl/u-boot-spl | grep ' board_init_r$'nm의 심볼명은 C 함수 선언문이 아니다. 해당 보드 구성에서 심볼의 존재와 주소를 확인한다.
03SPL 어셈블리 대조${CROSS_COMPILE}objdump -drS spl/u-boot-splELF의 명령·재배치를 대조한다. -S 소스 줄 병기는 디버그 정보와 소스 파일을 찾을 수 있는지에 달려 있다.
04proper의 주소 배치bdinfo해당 명령을 포함한 U-Boot proper에서 확인한다. 이 출력이 앞선 SPL의 당시 주소 배치를 그대로 뜻하지는 않는다.
05proper의 runtime 관찰dm tree; dm uclass; printenv해당 명령을 포함한 U-Boot proper에서 확인한다. SPL 내부 상태는 해당 단계의 로그·계측으로 별도 확인한다.

직접 확인할 실험

  1. 01
    SPL 로그에 boot device와 loader 이름 추가

    TPL or SPL 진입 전후에 SRAM SPL의 주소·크기·반환값과 timestamp를 함께 남긴다.

  2. 02
    이미지 헤더 길이 변조 후 거부 확인

    boot device 진입 전후에 loader methods의 주소·크기·반환값과 timestamp를 함께 남긴다.

  3. 03
    주 장치 read 실패를 주입해 fallback 추적

    image loader 진입 전후에 spl_image_info의 주소·크기·반환값과 timestamp를 함께 남긴다.

원문과 다음 글