U-Boot 새 보드 포팅 — defconfig·board.c·DTS 작성 흐름
#한 줄 요약
“BSP 관점의 U-Boot 포팅은 시리얼 첫 한 줄에 도달하기까지의 디버깅 사이클입니다.” — 이후 부분은 환경 설정과 부트 명령 튜닝입니다.
U-Boot은 거의 모든 ARM SoC에서 사실상 표준 부트로더입니다. NXP, TI, ST, Rockchip, Broadcom이 모두 vendor BSP에서 U-Boot을 씁니다. Zephyr나 Xen 환경이 아니라면 BSP의 부트로더는 U-Boot입니다.
#U-Boot의 두 단계
요즘 SoC의 U-Boot은 항상 두 단계입니다.
[BootROM (SoC 내부)] │ ▼[SPL — Secondary Program Loader] - SoC 내부 SRAM에서 동작 (DDR 없음) - 크기 제한 (보통 128KB 이내) - DDR 초기화, clock·pinmux 설정 - 다음 단계를 DDR로 적재 │ ▼[U-Boot proper (또는 BL31 → BL33 U-Boot)] - DDR에서 동작 - 시리얼 console + 명령 인터프리터 - 부트 환경 (uEnv.txt, fw_env) - 커널 적재 + 부팅SPL과 U-Boot proper는 같은 소스 트리에서 다른 .config로 빌드됩니다. 동일한 driver를 다르게 컴파일해 양쪽에 link합니다.
ARMv8 보드는 SPL 자리에 TF-A BL2가 오는 경우가 늘고 있습니다. Ch 7에서 다룹니다.
#U-Boot 디렉터리 구조
새 보드를 추가하기 위해 알아야 할 디렉터리입니다.
u-boot/├── arch/│ ├── arm/│ │ ├── cpu/│ │ ├── mach-imx/ ← NXP i.MX SoC 공통│ │ │ ├── imx8m/│ │ │ └── ...│ │ ├── mach-rockchip/│ │ ├── mach-omap2/ ← TI OMAP/AM335x│ │ └── dts/ ← SoC dtsi│ └── ...├── board/│ ├── freescale/│ │ ├── imx8mp_evk/ ← NXP reference 보드│ │ │ ├── Kconfig│ │ │ ├── MAINTAINERS│ │ │ ├── Makefile│ │ │ ├── imx8mp_evk.c│ │ │ ├── spl.c│ │ │ ├── lpddr4_timing.c│ │ │ └── README│ │ └── ...│ ├── ti/│ ├── beagle/│ └── <vendor>/│ └── <board>/ ← 우리 보드가 여기 들어감├── configs/│ ├── imx8mp_evk_defconfig│ ├── am335x_evm_defconfig│ └── <board>_defconfig ← 우리 defconfig├── include/│ └── configs/│ └── <board>.h ← 보드별 헤더 (점점 비어 가는 추세)├── drivers/└── common/새 보드 추가는 다음 6개 파일을 만들면 됩니다.
board/<vendor>/<board>/Kconfig— Kconfig 통합board/<vendor>/<board>/Makefile— build rulesboard/<vendor>/<board>/<board>.c— board init 코드board/<vendor>/<board>/spl.c— SPL 단계 코드configs/<board>_defconfig— defconfigarch/arm/dts/<board>.dts— device tree
DDR 코드는 NXP의 경우 board/<vendor>/<board>/lpddr4_timing.c로 별도, TI는 board/ti/<board>/에 통합되는 식입니다.
#가장 빠른 출발 — 비슷한 reference 보드 복사
vendor reference 보드와 가장 비슷한 것을 골라 통째로 복사한 뒤 수정합니다.
NXP i.MX 8M Plus 기반 ACME 카메라 보드를 예로 들면 imx8mp_evk가 베이스입니다.
cd u-boot
# 1. board 디렉터리 복사cp -r board/freescale/imx8mp_evk board/acme/cam
# 2. 파일 이름 바꾸기cd board/acme/cammv imx8mp_evk.c acme_cam.csed -i 's/imx8mp_evk/acme_cam/g' Kconfig Makefile MAINTAINERS
# 3. defconfig 복사cd ../../../cp configs/imx8mp_evk_defconfig configs/acme_cam_defconfigsed -i 's/imx8mp-evk/acme-cam/g' configs/acme_cam_defconfigsed -i 's/imx8mp_evk/acme_cam/g' configs/acme_cam_defconfig
# 4. DT 복사cp arch/arm/dts/imx8mp-evk.dts arch/arm/dts/acme-cam.dtssed -i 's/imx8mp-evk/acme-cam/g' arch/arm/dts/acme-cam.dts복사 후 컴파일 통과만 확인합니다. 동작은 그 다음 단계에서 잡습니다.
make ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- acme_cam_defconfigmake ARCH=arm CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)#defconfig 핵심 옵션
i.MX 8M Plus용 defconfig에서 보드별로 반드시 확인할 항목입니다.
CONFIG_ARM=yCONFIG_ARCH_IMX8M=yCONFIG_SYS_TEXT_BASE=0x40200000 ← U-Boot 적재 주소 (DDR 안)CONFIG_TARGET_ACME_CAM=y ← 우리 보드 선택
# SPLCONFIG_SPL=yCONFIG_SPL_TEXT_BASE=0x920000 ← SoC SRAM 안CONFIG_SPL_MAX_SIZE=0x25000CONFIG_SPL_STACK=0x96fff0CONFIG_SPL_BSS_START_ADDR=0x95e000
# DTCONFIG_DEFAULT_DEVICE_TREE="acme-cam"CONFIG_OF_CONTROL=yCONFIG_SPL_OF_CONTROL=y
# Console / Debug UARTCONFIG_DEBUG_UART=yCONFIG_DEBUG_UART_BASE=0x30890000 ← UART2 base (보드별 확인)CONFIG_DEBUG_UART_CLOCK=24000000CONFIG_DEBUG_UART_IMX=yCONFIG_BAUDRATE=115200CONFIG_SYS_CONSOLE_INFO_QUIET=y
# DriversCONFIG_DM=yCONFIG_DM_GPIO=yCONFIG_DM_I2C=yCONFIG_DM_MMC=yCONFIG_DM_PMIC=yCONFIG_DM_REGULATOR=yCONFIG_PHY_IMX8MQ_USB=yCONFIG_USB_DWC3=y
# Boot 미디어CONFIG_SUPPORT_EMMC_BOOT=yCONFIG_MMC=yCONFIG_FSL_USDHC=y
# NetworkCONFIG_NET=yCONFIG_DM_ETH=yCONFIG_FEC_MXC=yCONFIG_PHY_REALTEK=y
# EnvironmentCONFIG_ENV_IS_IN_MMC=yCONFIG_SYS_MMC_ENV_DEV=0CONFIG_SYS_MMC_ENV_PART=1CONFIG_ENV_OFFSET=0x400000CONFIG_ENV_SIZE=0x2000CONFIG_DEBUG_UART_* 네 줄이 첫 시리얼 출력을 결정합니다. base address, clock 주파수, baud rate가 정확해야 합니다.
CONFIG_SYS_TEXT_BASE는 U-Boot proper가 DDR의 어느 주소에서 동작할지입니다. DDR 영역 안이어야 하고, 충분한 공간이 위·아래로 있어야 합니다.
#SPL 단계 — spl.c
가장 어려운 코드입니다. DDR이 아직 없는 SRAM 안에서, console도 처음 띄우면서, DDR을 깨우고, 다음 단계로 점프합니다.
i.MX 8M Plus SPL의 핵심 함수만 정리하면 다음과 같습니다.
#include <common.h>#include <asm/arch/clock.h>#include <asm/arch/sys_proto.h>#include <asm/mach-imx/iomux-v3.h>#include <asm/arch/imx8mp_pins.h>
extern struct dram_timing_info dram_timing;
void spl_dram_init(void){ /* NXP DDR Tool 출력을 사용해 DDRC + PHY 설정 */ ddr_init(&dram_timing);}
static iomux_v3_cfg_t const uart_pads[] = { MX8MP_PAD_UART2_RXD__UART2_DCE_RX | MUX_PAD_CTRL(0x140), MX8MP_PAD_UART2_TXD__UART2_DCE_TX | MUX_PAD_CTRL(0x140),};
int board_early_init_f(void){ init_uart_clk(1); /* UART2 clock enable */ imx_iomux_v3_setup_multiple_pads(uart_pads, ARRAY_SIZE(uart_pads)); return 0;}
void board_init_f(ulong dummy){ int ret;
arch_cpu_init(); init_uart_clk(1); board_early_init_f();
timer_init();
preloader_console_init(); /* 첫 printf가 동작하는 시점 */
ret = spl_init(); if (ret) { debug("spl_init() failed: %d\n", ret); hang(); }
enable_tzc380(); /* TrustZone 메모리 컨트롤러 */ power_init_board(); /* PMIC 초기화 */
spl_dram_init(); /* DDR 초기화 */
board_init_r(NULL, 0);}이 흐름의 각 단계가 살아 있는지를 확인하면서 디버깅합니다. 어디서 멈췄는지 알면 절반은 해결입니다.
#첫 부팅의 디버깅 사이클 — 가장 길고 외로운 단계
새 보드의 BSP에서 가장 시간이 많이 드는 부분이 SPL부터 첫 시리얼까지입니다. 다음 순서로 점검합니다.
#단계 1: SPL이 실행 자체 안 되는가
시리얼이 아무것도 안 나오면 SPL이 실행 시작도 안 한 것일 수 있습니다. BootROM이 SPL 이미지를 못 찾았거나 못 적재한 것입니다.
확인할 것:
- SD 카드의 오프셋 1KB에 imx8mp의 boot image가 있는가 (
dd ... seek=2 bs=512). - BOOT_MODE 핀의 strap이 SD를 가리키는가.
- 보드에 전원이 정상인가.
- 보드의 reset 핀이 안정적인가.
JTAG이 있으면 *SPL의 첫 명령어 주소(0x920000)*에 break하고 PC가 거기 닿는지 확인합니다.
#단계 2: SPL이 실행은 됐는데 시리얼 출력이 없는가
preloader_console_init() 호출 전에 죽었거나, console 설정이 잘못된 것입니다.
확인할 것:
CONFIG_DEBUG_UART_BASE가 회로도와 일치- UART pad가 *board_early_init_f()*에서 설정됨
- UART parent clock이 enable됨
- USB-TTL 어댑터의 baud rate 일치
가장 빠른 진단은 DEBUG_UART를 manual로 토글하는 것입니다.
/* SPL 초기에 강제로 'A'를 보내기 */void board_init_f(ulong dummy) { /* IOMUXC 직접 set */ writel(0x0, 0x303404f0); /* UART2 RXD MUX_MODE = ALT0 */ writel(0x140, 0x30340754); /* PAD_CTL */ writel(0x0, 0x303404f4); /* UART2 TXD MUX_MODE = ALT0 */ writel(0x140, 0x30340758);
/* UART 직접 send */ writel('A', 0x30890040); /* UART2_UTXD */ while(1);}‘A’가 시리얼에 한 글자라도 나오면 시리얼 path는 살아 있는 것입니다. 안 나오면 pad mux 또는 baud rate가 틀린 것입니다.
#단계 3: 시리얼은 뜨는데 DDR training 실패
U-Boot SPL 2024.04 (...)DDRINFO: start DRAM initDDR PHY training FAILED at frequency 0Ch 5에서 다룬 DDR 디버깅 사이클로 들어갑니다. DDR Tool 재실행, training firmware 버전 확인.
#단계 4: DDR은 통과, U-Boot proper가 적재 안 됨
DDRINFO: end DRAM initTrying to boot from MMC1SPL: failed to boot from all boot devicesSPL이 SD/eMMC에서 u-boot.itb(또는 u-boot.img)를 못 찾는 것입니다. 확인:
- u-boot.itb가 SD의 기대 오프셋에 있는가
- MMC 컨트롤러 핀이 설정됐는가 (SPL의 board_init_f에서)
- USDHC clock이 enable됐는가
- vmmc 전원이 PMIC에서 켜졌는가
#단계 5: U-Boot proper가 부팅, commands 안 되는
U-Boot 2024.04 (...)DRAM: 2 GiB=>=> mmc list(아무 응답 없음)MMC driver가 probe 실패한 것입니다. dm tree로 driver model 트리 확인.
=> dm tree Class Index Probed Driver Name----------------------------------------------------------- root 0 [ + ] root_driver root_driver cpu 0 [ ] imx8_cpu cpu@0 ... mmc 0 [ ] fsl_esdhc_imx mmc@30b40000 <-- 미probedprobe 실패 원인은 clock 부재, regulator 부재, pinctrl 미설정입니다. dm tree -v와 dts를 비교.
#U-Boot 환경 설정
부팅 흐름은 환경 변수로 정합니다. 보드 헤더에 기본값을 정의합니다.
#define CONFIG_EXTRA_ENV_SETTINGS \ "image=Image\0" \ "fdt_file=acme-cam.dtb\0" \ "loadaddr=0x40480000\0" \ "fdt_addr=0x43000000\0" \ "mmcdev=0\0" \ "mmcpart=1\0" \ "mmcroot=/dev/mmcblk0p2 rootwait rw\0" \ "mmcargs=setenv bootargs console=ttymxc1,115200 root=${mmcroot}\0" \ "loadimage=load mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}\0" \ "loadfdt=load mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}\0" \ "mmcboot=" \ "run loadimage; run loadfdt; run mmcargs; " \ "booti ${loadaddr} - ${fdt_addr}\0"
#define CONFIG_BOOTCOMMAND \ "mmc dev ${mmcdev}; " \ "if mmc rescan; then " \ "run mmcboot; " \ "fi"부팅 시 CONFIG_BOOTCOMMAND가 실행됩니다. run mmcboot가 Image, dtb, bootargs를 차례로 적재해 booti로 점프합니다.
런타임 환경 변수 변경은 다음과 같습니다.
=> setenv bootdelay 3=> setenv ipaddr 192.168.1.100=> saveenvSaving Environment to MMC...Writing to MMC(0)... OKsaveenv가 변경을 eMMC의 지정 오프셋에 영구 기록합니다. CONFIG_ENV_IS_IN_MMC=y + CONFIG_ENV_OFFSET=0x400000 설정에 따른 위치입니다.
#TFTP로 빠른 dev cycle
개발 중에는 SD에 매번 굽는 것이 비효율입니다. TFTP로 호스트에서 직접 커널 적재합니다.
=> setenv serverip 192.168.1.10=> setenv ipaddr 192.168.1.100=> tftp ${loadaddr} ImageUsing FEC ethernet deviceTFTP from server 192.168.1.10; our IP address is 192.168.1.100Loading: ################################################# 41.6 MiB=> tftp ${fdt_addr} acme-cam.dtb=> run mmcargs=> booti ${loadaddr} - ${fdt_addr}호스트에 dnsmasq나 tftpd-hpa를 띄우면 build 결과를 바로 부팅할 수 있습니다. 1초 컴파일 후 1초 부팅이 BSP 개발 속도의 핵심입니다.
#U-Boot의 첫 출력 — 표준 형태
성공적으로 동작하는 i.MX 8M Plus 보드의 첫 출력입니다.
U-Boot SPL 2024.04 (May 19 2026 - 14:23:01 +0900)DDRINFO: start DRAM initDDRINFO: DRAM rate 4000MTSDDRINFO:ddrphy calibration doneDDRINFO: ddrmix config doneNormal BootTrying to boot from MMC2
U-Boot 2024.04 (May 19 2026 - 14:23:01 +0900)
CPU: Freescale i.MX8MP[8] rev1.1 1600 MHz (running at 1200 MHz)CPU: Industrial temperature grade (-40C to 105C) at 47CReset cause: PORModel: ACME i.MX 8M Plus Camera Board v1DRAM: 2 GiBCore: 92 devices, 22 uclasses, devicetree: separateWDT: Started watchdog@30280000 with servicing every 1000ms (60s timeout)MMC: FSL_SDHC: 0, FSL_SDHC: 2Loading Environment from MMC... OKIn: serial@30890000Out: serial@30890000Err: serial@30890000Net: eth0: ethernet@30be0000Hit any key to stop autoboot: 0=>이 메시지의 각 줄이 살아 있다는 것은 BSP의 큰 부분이 완성됐다는 뜻입니다.
DDRINFO— DDR이 깨졌습니다.CPU:— clock tree가 정상입니다.Model:— DT가 맞게 적재됐습니다.MMC:— eMMC controller가 probe됐습니다.Net:— PHY가 잡혔습니다.
이제 커널 부팅 단계로 갑니다.
#자주 하는 실수
#CONFIG_SYS_TEXT_BASE를 SPL 자리와 헷갈림
CONFIG_SYS_TEXT_BASE는 U-Boot proper의 적재 주소(DDR 안). CONFIG_SPL_TEXT_BASE가 SPL의 적재 주소(SRAM 안). 둘이 바뀌면 어디서도 동작 안 함.
#SD 이미지의 오프셋을 놓침
i.MX는 boot image를 오프셋 1KB(SD) 또는 *오프셋 0(eMMC boot partition)*에서 찾습니다. dd seek=2 bs=512처럼 정확히 굽지 않으면 BootROM이 못 찾습니다.
#시리얼 둘 다 (UART1/UART2) 다른 것 사용
DT가 UART2를 console로 쓰는데 CONFIG_DEBUG_UART_BASE는 UART1을 가리킬 수 있습니다. 한쪽만 동작하는 부분 시리얼이 됩니다. 반드시 같은 UART.
#bootargs에 DT를 통한 console과 다른 console 명시
console=ttymxc0이지만 실제 UART2가 console이면, 커널이 다른 UART에 메시지를 보냅니다. 커널 메시지가 안 보이는 이유 1순위.
#saveenv를 안 함
환경 변수를 setenv로 바꾼 후 saveenv 안 하면 재부팅 시 사라집니다. 개발 중에 환경을 자주 잃으면 saveenv 누락입니다.
#Ethernet PHY MDIO 주소 잘못
DT에서 phy-handle = <ðphy0>로 가리키는 PHY의 MDIO 주소(reg)가 실제 보드의 strap과 다르면 link가 안 잡힙니다. mii info 명령으로 확인.
=> mii infoPHY 0x01: OUI = 0x001CC8, Model = 0x05, Rev = 0x01, 100baseT, FDX#정리
- BSP에서의 U-Boot 포팅은 시리얼 첫 한 줄에 도달하기까지가 핵심입니다.
- 가장 비슷한 vendor reference 보드를 통째로 복사해서 시작합니다. board 디렉터리·defconfig·dts 6개 파일을 만듭니다.
- defconfig의
CONFIG_DEBUG_UART_*네 줄이 첫 시리얼 출력을 결정합니다. base, clock, baud rate가 정확해야 합니다. - SPL은 DDR 없는 SRAM 안에서 동작합니다. console init → DDR init → 다음 단계 적재의 흐름입니다.
- 첫 부팅 디버깅 사이클은 5단계입니다. SPL 실행 → 시리얼 출력 → DDR training → U-Boot proper 적재 → driver probe.
- 환경 변수는
CONFIG_EXTRA_ENV_SETTINGS에 기본을 정의하고,setenv+saveenv로 영구 변경합니다. - 개발 중에는 TFTP로 SD 굽는 시간을 절약합니다. dnsmasq 또는 tftpd-hpa로 호스트에 서버 설치.
- 첫 출력의
DDRINFO,CPU:,Model:,MMC:,Net:이 모두 나오면 BSP의 큰 부분이 완성된 것입니다.
#다음 편
Ch 7 — TF-A와 TrustZone 통합에서는 ARMv8 보드에 반드시 들어가는 ARM Trusted Firmware-A를 BSP에 통합하고, U-Boot이 BL33으로 동작하는 구조를 다룹니다.
#관련 항목
BSP Development · 6 of 21
- 1BSP의 본질 분해 — 새 보드 부팅을 위한 코드의 자리
- 2SoC 데이터시트 읽기 — Pin Mux·Clock·Memory Map 파악법
- 3새 보드 Device Tree 설계 — node·property·phandle 작성 흐름
- 4Pin Mux와 Clock Tree 분석 — 보드 부팅의 첫 두 관문
- 5DDR 매개변수 결정 — 보드별 Timing·Training 추출
- 6U-Boot 새 보드 포팅 — defconfig·board.c·DTS 작성 흐름
- 7TF-A·TrustZone 통합 — BL31·secure world·SMC 흐름 적용
- 8Linux 커널 BSP 설정 — defconfig·Kconfig·DT 통합
- 9Multi-core SMP Bring-up — PSCI·Secondary CPU 깨우기
- 10첫 부팅 추적 — 0%부터 login prompt까지의 단계 분석
- 11부트로그 디버깅 — earlyprintk·loglevel·serial 추적
- 12BSP 드라이버 추가 — 보드별 Peripheral 통합 흐름
- 13BSP Power Management — Suspend/Resume·Runtime PM·Regulator
- 14BSP Thermal과 Watchdog — Trip Point·Cooling Device·Hardware Reset
- 15BSP 부트 시간 최적화 — Bootchart·initcall_debug·Parallel Init
- 16BSP RootFS 통합 — Buildroot·Yocto와 보드별 패키지 묶기
- 17BSP 이미지 패키징 — Flash Layout·Partition·GPT 설계
- 18BSP OTA와 Field Recovery — A/B 슬롯·롤백·BootCount
- 19BSP Stability Testing — Stress·Soak·Power Cycle 시나리오
- 20BSP 양산 환경 구축 — CI/CD·재현 가능 빌드·서명
- 21BSP 유지보수 — 업스트림 기여·커널 버전업·LTS 전략
관련 글
이 글을 참조하는 글 (13)
- TF-A·TrustZone 통합 — BL31·secure world·SMC 흐름 적용— BSP Development
- DDR 매개변수 결정 — 보드별 Timing·Training 추출— BSP Development
- Pin Mux와 Clock Tree 분석 — 보드 부팅의 첫 두 관문— BSP Development
- 새 보드 Device Tree 설계 — node·property·phandle 작성 흐름— BSP Development
- Modern U-Boot bootflow / bootmeth — 새 추상화 레이어 분석— Bootloader Internals
- U-Boot 환경 변수와 bootcmd — 부팅 시나리오 정의하기— Bootloader Internals
- U-Boot 보드 초기화 시퀀스 — board_init_f와 board_init_r 분리 이유— Bootloader Internals
- U-Boot Driver Model 내부 — uclass·driver·device 추상화 구조— Bootloader Internals
- Device Tree DTB 부트로더 처리 — 로딩 시점과 fixup 메커니즘 추적— Bootloader Internals
- U-Boot Falcon Mode — SPL이 U-Boot Proper 없이 커널 직접 부팅— Bootloader Internals
- ARM 임베디드 부트 4단계 분해 — BL1·SPL·TPL·U-Boot Proper의 역할— Bootloader Internals
- U-Boot 빌드 시스템 분석 — Kconfig·Makefile·defconfig 동작 추적— Bootloader Internals
- ROM부터 init까지 — 임베디드 부팅 단계의 빈자리 분석— Bootloader Internals