U-Boot 드라이버 구조와 포팅 방법
임베디드 시스템 개발에서 U-Boot는 단순한 부트로더를 넘어, 다양한 하드웨어 초기화와 디바이스 제어 기능을 제공하는 중요한 소프트웨어입니다. 특히 최근의 U-Boot는 "Driver Model(DM)"이라는 구조를 도입하여 리눅스에 가까운 드라이버 아키텍처를 제공하고 있습니다. 본 글에서는 U-Boot의 드라이버 모델 개념부터 실제 포팅 단계, 그리고 Rockchip RK3399 기반 UART/SPI 예제까지 상세히 다루어 보겠습니다.
1. U-Boot Driver Model(DM) 개요
과거의 U-Boot 드라이버 구현 방식은 보드별로 분산되어 있었고, 공통화가 어렵고 유지보수가 힘들었습니다. 이를 개선하기 위해 Driver Model(DM)이 도입되었습니다.
● Driver Model의 핵심 개념
- Device: 실제 하드웨어 장치를 의미하며 노드 단위로 관리됨
- Driver: 장치를 제어하는 소프트웨어 코드
- uclass: 동일한 기능을 가진 드라이버들을 그룹화 (예: UART uclass, SPI uclass)
- Device Tree 기반 초기화: 대부분의 장치는 DT의 노드를 기준으로 생성 및 초기화됨
- Probe 함수 중심 구조: 장치 활성화는 probe 단계에서 이루어짐
● Driver Model의 장점
- 리눅스와 유사해 이해가 쉬움
- 코드 중복 제거 및 유지보수 용이
- 다수의 보드에서 공통 드라이버 재활용 가능
- Device Tree로 하드웨어 의존성을 최소화
2. U_BOOT_DRIVER 매크로 분석
U-Boot에서 드라이버는 U_BOOT_DRIVER 매크로를 통해 등록됩니다.
예시는 다음과 같습니다:
U_BOOT_DRIVER(rk3399_uart) = {
.name = "rk3399_uart",
.id = UCLASS_SERIAL,
.of_match = rk3399_uart_ids,
.probe = rk3399_uart_probe,
.ops = &rk3399_uart_ops,
.priv_auto = sizeof(struct rk_uart_priv),
};
각 항목의 의미는 다음과 같습니다:
| 필드 | 의미 |
|---|---|
| name | 드라이버 이름 |
| id | uclass ID (예: UCLASS_SERIAL, UCLASS_SPI) |
| of_match | Device Tree 호환 문자열 |
| probe | 장치 초기화 함수 |
| ops | 드라이버 기능 함수 테이블 |
| priv_auto | private 데이터 구조 자동 할당 |
probe는 드라이버의 핵심이며, 이 함수에서 Clock, Reset, Register Mapping 등의 동작을 수행합니다.
3. Device Tree 기반 장치 초기화 흐름
U-Boot의 DM은 대부분의 장치를 DT 기반으로 초기화합니다.
● 초기화 흐름
- DTB 로딩
- UCLASS 스캔 (serial, spi 등)
of_match를 기반으로 각 드라이버와 노드 매칭- 드라이버 probe 호출
- 장치 활성화 완료
● RK3399 UART Device Tree 예시
uart0: serial@ff180000 {
compatible = "rockchip,rk3399-uart";
reg = <0x0 0xff180000 0x0 0x100>;
clocks = <&cru SCLK_UART0>;
clock-names = "baudclk";
status = "okay";
};
U-Boot는 compatible 문자열을 기반으로 맞는 드라이버를 선택합니다.
4. 간단한 UART 드라이버 포팅 실습 (RK3399)
RK3399의 UART 드라이버는 이미 U-Boot에 존재하지만, 이해를 돕기 위해 간단한 형태의 UART 드라이버를 직접 만드는 과정을 예제로 설명합니다.
(1) 드라이버 소스 파일 생성
경로: drivers/serial/serial_rk3399_simple.c
#include <common.h>
#include <dm.h>
#include <serial.h>
#include <asm/io.h>
struct rk_uart_priv {
void __iomem *base;
};
static int rk_uart_probe(struct udevice *dev)
{
struct rk_uart_priv *priv = dev_get_priv(dev);
fdt_addr_t addr = dev_read_addr(dev);
if (addr == FDT_ADDR_T_NONE)
return -EINVAL;
priv->base = (void *)addr;
return 0;
}
static int rk_uart_putc(struct udevice *dev, const char ch)
{
struct rk_uart_priv *priv = dev_get_priv(dev);
writel(ch, priv->base + 0x00); // TX register offset (예시)
return 0;
}
static const struct dm_serial_ops rk_uart_ops = {
.putc = rk_uart_putc,
};
static const struct udevice_id rk_uart_ids[] = {
{ .compatible = "rockchip,rk3399-simple-uart" },
{}
};
U_BOOT_DRIVER(rk3399_simple_uart) = {
.name = "rk3399_simple_uart",
.id = UCLASS_SERIAL,
.of_match = rk_uart_ids,
.probe = rk_uart_probe,
.ops = &rk_uart_ops,
.priv_auto = sizeof(struct rk_uart_priv),
};
(2) Device Tree에 노드 추가
arch/arm/dts/rk3399-u-boot.dtsi 파일에 다음 추가:
serial_simple: serial@ff190000 {
compatible = "rockchip,rk3399-simple-uart";
reg = <0x0 0xff190000 0x0 0x100>;
status = "okay";
};
(3) Kconfig 등록
drivers/serial/Kconfig 수정:
config SERIAL_RK3399_SIMPLE
bool "RK3399 Simple UART support"
depends on ARCH_ROCKCHIP
default y
(4) Makefile 등록
drivers/serial/Makefile 수정:
obj-$(CONFIG_SERIAL_RK3399_SIMPLE) += serial_rk3399_simple.o
이렇게 하면 기본적인 UART 장치가 U-Boot 부팅 시 자동으로 probe되고 출력 함수가 사용 가능하게 됩니다.
5. drivers/ 하위 디렉토리 구성 설명
U-Boot의 drivers/ 디렉토리는 기능별 드라이버 모음입니다.
주요 디렉토리
- serial/: UART 드라이버
- spi/: SPI 컨트롤러 및 SPI flash 드라이버
- mmc/: eMMC/SD 카드 드라이버
- usb/: USB 호스트/디바이스
- net/: Ethernet 드라이버
- clk/: Clock framework
- gpio/: GPIO controller
- power/: PMIC, regulator
각 디렉토리는 uclass에 맞춰 구조화되어 있으며, 대부분 리눅스 드라이버 구조를 단순하게 축소한 형태입니다.
6. 결론 요약
마지막으로 오늘 내용을 한 문장으로 정리하면 다음과 같습니다.
“U-Boot 드라이버는 리눅스의 축소판이다.”
U-Boot는 미니멀한 환경임에도 불구하고, 리눅스와 흡사한 Driver Model 기반 구조를 갖추고 있습니다. 이는 복잡한 보드를 지원하거나 다양한 주변장치 초기화가 필요한 개발에서 매우 강력한 장점이 됩니다.
본 글에서 다룬 UART 예제는 아주 단순화된 형태이지만, 실제 SPI, MMC, USB 등 모든 드라이버가 동일한 구조에 기반하고 있으므로 이를 이해한다면 U-Boot 기반의 다양한 장치 포팅을 훨씬 쉽게 진행할 수 있을 것입니다.
'u-boot' 카테고리의 다른 글
| U-Boot 환경 변수와 스크립트 완전 정복(Rockchip RK3399 기반) (0) | 2025.12.08 |
|---|---|
| U‑Boot 명령어(Command) 추가 및 실행 구조 완전 정리 (0) | 2025.12.07 |
| U-Boot 부팅 시퀀스 완전 해부 (0) | 2025.12.04 |
| U-Boot 빌드 및 보드 포팅 준비 (0) | 2025.12.03 |
| U-Boot 소스코드 구조 분석 – RK3399 기반으로 이해하기 (0) | 2025.12.02 |