라즈베리파이 펌웨어는 config.txt에 적힌 커널 이미지를 SD 카드에서 읽어 바로 실행한다. 중간에 개입할 지점이 없어서 커널을 여러 개 두고 골라 부팅하거나, TFTP·USB에서 이미지를 받아오거나, 부트 인자를 바꿔가며 시험하려면 그때마다 SD 카드를 뽑아 파일을 갈아끼워야 한다. 펌웨어와 커널 사이에 U-Boot를 끼워 넣으면 이 작업이 전부 시리얼 콘솔에서 끝난다. 이 글에서는 U-Boot를 크로스 빌드해 라즈베리파이 부트 파티션에 올리는 과정을, 실제 보드에 꽂기 전에 QEMU로 검증하는 방법과 함께 정리한다.
부팅 체인에서 U-Boot의 자리
| 단계 | 기본 구성 | U-Boot 적용 후 |
|---|---|---|
| 1. BootROM | bootcode.bin(Pi 3) 또는 EEPROM(Pi 4 이상) 로드 | 동일 |
| 2. 2차 부트로더 | start*.elf가 config.txt 해석 | 동일 |
| 3. 커널 로드 | kernel8.img를 로드해 실행 | u-boot.bin을 로드해 실행 |
| 4. 이후 | — | U-Boot가 커널·DTB·initrd를 로드 |
바꾸는 것은 3단계에서 펌웨어가 읽어갈 파일 이름 하나뿐이다. 다만 그 앞단인 2단계 구성은 모델마다 다르다.
| 모델 | 부트 파티션의 펌웨어 파일 | 기본 커널 파일 | arm_64bit |
|---|---|---|---|
| Pi 3 / 3+ / Zero 2 W | bootcode.bin + start.elf + fixup.dat | kernel8.img | 직접 1로 지정 |
| Pi 4 / 400 / CM4 | start4.elf + fixup4.dat | kernel8.img | 기본값이 1 |
| Pi 5 / 500 / CM5 | 없음 (EEPROM 부트로더가 직접 로드) | kernel_2712.img | 무시됨 |
빌드 환경 준비
sudo apt install -y gcc-aarch64-linux-gnu bison flex libssl-dev \
libgnutls28-dev device-tree-compiler bc python3-dev \
python3-setuptools swig uuid-devlibgnutls28-dev를 빠뜨리면 U-Boot 본체가 아니라 호스트 툴을 만드는 단계에서 먼저 멈춘다. 크로스 컴파일러만 깔고 바로 make를 돌리면 나오는 에러다.
HOSTCC tools/mkeficapsule.o
tools/mkeficapsule.c:20:10: fatal error: gnutls/gnutls.h: No such file or directory
20 | #include <gnutls/gnutls.h>
| ^~~~~~~~~~~~~~~~~
compilation terminated.
make[1]: *** [scripts/Makefile.host:113: tools/mkeficapsule.o] Error 1
make: *** [Makefile:2209: tools] Error 2
defconfig 고르기
| defconfig | 대상 | DTB 출처 |
|---|---|---|
rpi_arm64_defconfig | Pi 3 / 3+ / 4 / 400 / CM3 / CM4 / Zero 2 W | 펌웨어가 넘겨준 것 |
rpi_4_defconfig | Pi 4 전용 (64비트) | 빌드에 포함된 bcm2711-rpi-4-b.dtb |
rpi_3_b_plus_defconfig | Pi 3+ 전용 (64비트) | 빌드에 포함된 bcm2837-rpi-3-b-plus.dtb |
rpi_4_32b_defconfig | Pi 4, AArch32 | 빌드에 포함된 것 |
여러 모델을 같이 굴린다면 rpi_arm64_defconfig가 편하다. 설정을 뜯어보면 그 근거가 그대로 드러난다.
$ grep -E "^CONFIG_(OF_BOARD|OF_SEPARATE|POSITION_INDEPENDENT|ENV_FAT)" .config
CONFIG_POSITION_INDEPENDENT=y
CONFIG_OF_SEPARATE=y
CONFIG_OF_BOARD=y
CONFIG_ENV_FAT_INTERFACE="mmc"
CONFIG_ENV_FAT_DEVICE_AND_PART="0:1"
CONFIG_ENV_FAT_FILE="uboot.env"
OF_BOARD=y라 보드 판별을 빌드 타임 DTB가 아니라 펌웨어가 넘긴 DTB로 한다. 바이너리 하나로 여러 모델을 커버할 수 있는 이유가 여기에 있고, 동시에 뒤에 나올 QEMU 실행에서 -dtb가 필수가 되는 이유이기도 하다.
크로스 빌드
git clone --depth 1 -b v2026.07 https://github.com/u-boot/u-boot.git
cd u-boot
export CROSS_COMPILE=aarch64-linux-gnu-
make rpi_arm64_defconfig
make -j$(nproc) LD u-boot
OBJCOPY u-boot.srec
OBJCOPY u-boot-nodtb.bin
RELOC u-boot-nodtb.bin
COPY u-boot.bin
COPY dts/dt.dtb
COPY u-boot.dtb
real 0m28.477s
user 1m25.517s
sys 0m37.839s
$ ls -l u-boot.bin
-rw-rw-r-- 1 noble noble 939472 Aug 16 12:12 u-boot.bin
6코어 x86_64 호스트에서 클린 빌드가 28초, 결과물은 1 MB가 채 안 된다. 커널과 달리 반복 빌드 부담이 없어서 설정을 바꿔가며 시험하기 좋다.
QEMU로 먼저 확인하기
SD 카드를 뽑았다 꽂았다 하기 전에, 부트 파티션과 똑같은 구조의 디스크 이미지를 만들어 QEMU에 물려본다.
dd if=/dev/zero of=sd.img bs=1M count=1024 status=none
sfdisk sd.img <<'EOF'
label: dos
start=2048, size=524288, type=c, bootable
start=526336, type=83
EOF
LOOP=$(sudo losetup -f -P --show sd.img)
sudo mkfs.vfat -F 32 -n BOOT ${LOOP}p1
sudo mount ${LOOP}p1 /mnt
sudo cp u-boot/u-boot.bin /mnt/
printf 'arm_64bit=1\nenable_uart=1\nkernel=u-boot.bin\n' | sudo tee /mnt/config.txt
sudo umount /mnt && sudo losetup -d $LOOP이미지 크기는 반드시 2의 거듭제곱이어야 한다. QEMU의 raspi 머신이 SD 카드 크기를 그렇게 제한한다.
$ qemu-system-aarch64 -M raspi3b ... -drive file=odd.img,if=sd,format=raw
qemu-system-aarch64: Invalid SD card size: 700 MiB
SD card size has to be a power of 2, e.g. 1 GiB.
qemu-system-aarch64 -M raspi3b -nographic \
-kernel u-boot/u-boot.bin \
-dtb u-boot/arch/arm/dts/bcm2837-rpi-3-b.dtb \
-drive file=sd.img,if=sd,format=raw \
-serial null -serial mon:stdioU-Boot 2026.07 (Aug 16 2026 - 12:12:13 +0000)
DRAM: 960 MiB
RPI 3 Model B (0xa02082)
Core: 68 devices, 14 uclasses, devicetree: board
MMC: mmc@7e202000: 0, mmc@7e300000: 1
Loading Environment from FAT... Unable to read "uboot.env" from mmc0:1...
In: serial,usbkbd
Out: serial,vidconsole
Err: serial,vidconsole
Net: No ethernet found.
starting USB...
USB DWC2
Bus usb@7e980000: 1 USB Device(s) found
scanning usb for storage devices... 0 Storage Device(s) found
Hit any key to stop autoboot: 0
Card did not respond to voltage select! : -110
Cannot persist EFI variables without system partition
Missing RNG device for EFI_RNG_PROTOCOL
** Booting bootflow '<NULL>' with efi_mgr
Loading Boot0000 'mmc 0' failed
EFI boot manager: Cannot load any image
Boot failed (err=-14)
U-Boot> mmc list
mmc@7e202000: 0 (SD)
mmc@7e300000: 1
U-Boot> ls mmc 0:1
939472 u-boot.bin
44 config.txt
2 file(s), 0 dir(s)
보드 모델을 RPI 3 Model B (0xa02082)로 인식하고 mmc 0:1에서 방금 넣은 두 파일을 그대로 읽어낸다. 부팅에 실패하는 것은 아직 커널을 안 넣었기 때문이고, Card did not respond는 카드가 꽂히지 않은 두 번째 SD 컨트롤러(mmc 1) 쪽 메시지다.
환경 변수가 SD 카드에 남는지 확인
부팅 로그의 Unable to read "uboot.env"는 아직 저장된 환경이 없다는 뜻이다. saveenv가 되는지까지 봐야 부트 파티션 구성이 끝났다고 할 수 있다.
U-Boot> setenv bootdelay 5
U-Boot> saveenv
Saving Environment to FAT... OK
# QEMU를 종료하고 같은 sd.img로 다시 부팅
Loading Environment from FAT... OK
U-Boot> printenv bootdelay
bootdelay=5
$ sudo mount ${LOOP}p1 /mnt && ls -l /mnt
total 934
-rwxr-xr-x 1 root root 44 Aug 16 12:05 config.txt
-rwxr-xr-x 1 root root 939472 Aug 16 12:05 u-boot.bin
-rwxr-xr-x 1 root root 16384 Jan 1 2000 uboot.env
호스트에서 마운트해보면 16 KB짜리 uboot.env가 부트 파티션에 생겨 있다. 위치와 파일 이름은 앞서 본 CONFIG_ENV_FAT_* 값 그대로다.
실제 SD 카드에 올리기
# 라즈베리파이 OS가 들어있는 SD 카드의 첫 번째(FAT32) 파티션
sudo mount /dev/sdX1 /mnt/boot
sudo cp /mnt/boot/config.txt /mnt/boot/config.txt.bak
sudo cp u-boot/u-boot.bin /mnt/boot/
sudo tee -a /mnt/boot/config.txt <<'EOF'
[all]
kernel=u-boot.bin
enable_uart=1
EOF
sudo umount /mnt/boot기존 config.txt를 통째로 갈아치우지 말고 kernel 항목만 덧붙인다. dtoverlay나 dtparam 설정이 이미 들어 있는 경우가 많다.
Pi 3 계열이라면 arm_64bit=1도 같이 넣어야 64비트 U-Boot가 올라간다. 이후 GPIO 14/15에 USB-시리얼 어댑터를 물리고 115200 8N1로 붙으면 U-Boot> 프롬프트가 나온다.
주의사항
- QEMU의
raspi3b는 라즈베리파이 펌웨어를 실행하지 않는다.-kernel로u-boot.bin을 직접 올리는 구조라,config.txt의kernel=항목이 실제로 먹히는지는 QEMU에서 확인할 수 없다. 검증 범위는 U-Boot 바이너리 자체와 부트 파티션 레이아웃까지다. rpi_arm64_defconfig는 DTB를 외부에서 받으므로 QEMU 실행에서-dtb를 빼면 시리얼에 한 글자도 안 나오고 그대로 멈춘다. 부팅이 안 되는 것처럼 보이지만 원인은 DTB 누락이다.- 라즈베리파이는 UART가 둘이라
-serial null -serial mon:stdio로 두 개를 순서대로 줘야 한다.-serial mon:stdio하나만 주면 U-Boot 콘솔이 붙은 PL011이 아니라 mini UART가 stdio에 연결돼 출력이 보이지 않는다. - Pi 5(BCM2712)는 U-Boot 문서의 지원 보드 목록에는 없지만 v2026.07 소스에는 모델 항목이 들어 있다 (
board/raspberrypi/rpi/rpi.c의[0x17] = { "5 Model B", ... }). 실기가 없어 부팅 검증은 하지 못했다. - 부트 파티션은 FAT32여야 하고 쓰기가 가능해야 한다. read-only로 마운트된 상태면
saveenv가 실패해 설정이 재부팅 때마다 초기화된다. - 되돌릴 때는
config.txt에서 추가한kernel=u-boot.bin줄만 지우면 펌웨어가 다시kernel8.img를 찾는다. 위 명령에서 원본을config.txt.bak으로 백업해두는 이유다.
마무리
U-Boot를 올리는 작업 자체는 크로스 빌드 한 번과 config.txt 한 줄이 전부다. 실수하기 쉬운 지점은 빌드 의존성과 DTB 조달 방식 쪽이고, 둘 다 SD 카드를 만들기 전에 QEMU에서 걸러낼 수 있다. 여기까지 하면 U-Boot> 프롬프트에서 커널을 골라 부팅하는 다음 단계로 넘어갈 준비가 끝난다.
참고
- U-Boot 공식 저장소 —
doc/board/broadcom/raspberrypi.rst에 defconfig별 지원 보드 목록이 있다 - Raspberry Pi Documentation — config.txt
- raspberrypi/firmware — boot 디렉터리
- QEMU — Raspberry Pi boards