라즈베리파이 u-boot 올리기

라즈베리파이 펌웨어는 config.txt에 적힌 커널 이미지를 SD 카드에서 읽어 바로 실행한다. 중간에 개입할 지점이 없어서 커널을 여러 개 두고 골라 부팅하거나, TFTP·USB에서 이미지를 받아오거나, 부트 인자를 바꿔가며 시험하려면 그때마다 SD 카드를 뽑아 파일을 갈아끼워야 한다. 펌웨어와 커널 사이에 U-Boot를 끼워 넣으면 이 작업이 전부 시리얼 콘솔에서 끝난다. 이 글에서는 U-Boot를 크로스 빌드해 라즈베리파이 부트 파티션에 올리는 과정을, 실제 보드에 꽂기 전에 QEMU로 검증하는 방법과 함께 정리한다.

부팅 체인에서 U-Boot의 자리

단계기본 구성U-Boot 적용 후
1. BootROMbootcode.bin(Pi 3) 또는 EEPROM(Pi 4 이상) 로드동일
2. 2차 부트로더start*.elfconfig.txt 해석동일
3. 커널 로드kernel8.img를 로드해 실행u-boot.bin을 로드해 실행
4. 이후U-Boot가 커널·DTB·initrd를 로드

바꾸는 것은 3단계에서 펌웨어가 읽어갈 파일 이름 하나뿐이다. 다만 그 앞단인 2단계 구성은 모델마다 다르다.

모델부트 파티션의 펌웨어 파일기본 커널 파일arm_64bit
Pi 3 / 3+ / Zero 2 Wbootcode.bin + start.elf + fixup.datkernel8.img직접 1로 지정
Pi 4 / 400 / CM4start4.elf + fixup4.datkernel8.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-dev

libgnutls28-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_defconfigPi 3 / 3+ / 4 / 400 / CM3 / CM4 / Zero 2 W펌웨어가 넘겨준 것
rpi_4_defconfigPi 4 전용 (64비트)빌드에 포함된 bcm2711-rpi-4-b.dtb
rpi_3_b_plus_defconfigPi 3+ 전용 (64비트)빌드에 포함된 bcm2837-rpi-3-b-plus.dtb
rpi_4_32b_defconfigPi 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:stdio
U-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 항목만 덧붙인다. dtoverlaydtparam 설정이 이미 들어 있는 경우가 많다.

Pi 3 계열이라면 arm_64bit=1도 같이 넣어야 64비트 U-Boot가 올라간다. 이후 GPIO 14/15에 USB-시리얼 어댑터를 물리고 115200 8N1로 붙으면 U-Boot> 프롬프트가 나온다.

주의사항

  • QEMU의 raspi3b는 라즈베리파이 펌웨어를 실행하지 않는다. -kernelu-boot.bin을 직접 올리는 구조라, config.txtkernel= 항목이 실제로 먹히는지는 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> 프롬프트에서 커널을 골라 부팅하는 다음 단계로 넘어갈 준비가 끝난다.

참고

답글 남기기