Linux Device Tree

LinuxEmbedded

Device Tree는 하드웨어 구성을 기술하는 데이터 구조다. 커널 소스를 수정하지 않고 보드별 하드웨어 차이를 기술할 수 있다. ARM Linux에서는 사실상 필수다.


파일 구조

확장자역할예시
.dtsiSoC 공통 정의. 재사용bcm2711.dtsi
.dts보드별 최종 설정. dtsi를 includebcm2711-rpi-4-b.dts
.dtb컴파일된 바이너리. 부트로더가 커널에 전달bcm2711-rpi-4-b.dtb
.dtbo오버레이 바이너리. 런타임 적용 가능spi-sensor.dtbo
bcm2711.dtsi          ← SoC 레벨 (UART, SPI, I2C 컨트롤러 정의)
  ↑ #include
bcm2711-rpi-4-b.dts   ← 보드 레벨 (어떤 핀에 뭐 연결, status 설정)
  ↓ dtc 컴파일
bcm2711-rpi-4-b.dtb   ← 바이너리 (부트로더 → 커널)

노드와 속성

Device Tree는 트리 구조다. 각 노드가 하드웨어 하나를 나타낸다.

/ {                              /* 루트 노드 */
    compatible = "vendor,board";
    #address-cells = <1>;        /* 자식 노드의 reg 주소 크기 (32-bit) */
    #size-cells = <1>;           /* 자식 노드의 reg 길이 크기 */

    spi0: spi@fe204000 {         /* 라벨: 노드이름@유닛주소 */
        compatible = "brcm,bcm2835-spi";
        reg = <0xfe204000 0x200>;    /* base address, size */
        interrupts = <2 22>;
        clocks = <&clocks BCM2835_CLOCK_SPI>;
        #address-cells = <1>;
        #size-cells = <0>;           /* 자식(SPI 디바이스)은 size 없음 */
        status = "okay";

        sensor@0 {                   /* SPI CS0에 연결된 센서 */
            compatible = "invensense,mpu6050";
            reg = <0>;               /* chip select 번호 */
            spi-max-frequency = <1000000>;
        };
    };
};

핵심 속성

속성역할
compatible드라이버 매칭 키. "vendor,model" 형식
reg레지스터 주소와 크기. 버스 타입에 따라 의미가 다름
status"okay" = 활성, "disabled" = 비활성
interrupts인터럽트 번호와 타입
clocks클럭 소스 phandle
#address-cells / #size-cells자식 노드의 reg 해석 방법

reg의 의미는 부모 노드에 따라 달라진다:

  • 메모리 맵 디바이스: 물리 주소 + 크기
  • SPI: chip select 번호
  • I2C: 슬레이브 주소

커널이 Device Tree를 사용하는 과정

1. 부트로더(U-Boot)가 DTB를 메모리에 로드
2. 커널 부팅 시 DTB를 언팩 (unflatten_device_tree)
3. 노드마다 platform_device 생성
4. 드라이버의 of_match_table과 노드의 compatible 비교
5. 일치하면 probe() 호출

드라이버 매칭 코드

/* 드라이버 측 */
static const struct of_device_id my_of_match[] = {
    { .compatible = "vendor,my-sensor" },
    { .compatible = "vendor,my-sensor-v2" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);

static struct platform_driver my_driver = {
    .probe = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "my-sensor",
        .of_match_table = my_of_match,
    },
};
module_platform_driver(my_driver);
/* Device Tree 측 */
my_sensor: sensor@40000000 {
    compatible = "vendor,my-sensor";   /* ← 이 문자열로 매칭 */
    reg = <0x40000000 0x1000>;
    status = "okay";
};

커널이 "vendor,my-sensor"를 보고 my_of_match 테이블에서 찾아서 my_probe()를 호출한다.

probe에서 DT 속성 읽기

static int my_probe(struct platform_device *pdev)
{
    struct resource *res;
    void __iomem *base;
    u32 freq;

    /* reg 속성 → 메모리 리소스 */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(base))
        return PTR_ERR(base);

    /* 커스텀 속성 읽기 */
    if (of_property_read_u32(pdev->dev.of_node, "clock-frequency", &freq))
        freq = 100000;  /* default */

    /* interrupts → IRQ 번호 */
    int irq = platform_get_irq(pdev, 0);

    return 0;
}

오버레이

기본 DTB를 수정하지 않고 하드웨어를 추가/변경한다.

/dts-v1/;
/plugin/;

/ {
    fragment@0 {
        target = <&spi0>;           /* 기존 노드를 참조 */
        __overlay__ {
            status = "okay";

            new_sensor: sensor@1 {  /* SPI CS1에 센서 추가 */
                compatible = "vendor,pressure-sensor";
                reg = <1>;
                spi-max-frequency = <500000>;
            };
        };
    };
};
# 컴파일
dtc -@ -I dts -O dtb -o sensor.dtbo sensor.dts

# 런타임 적용 (configfs)
mkdir /sys/kernel/config/device-tree/overlays/sensor
cat sensor.dtbo > /sys/kernel/config/device-tree/overlays/sensor/dtbo

# 제거
rmdir /sys/kernel/config/device-tree/overlays/sensor

Raspberry Pi에서는 config.txtdtoverlay=sensor를 추가하면 부팅 시 자동 적용된다.


메모

  • compatible 문자열 순서가 중요하다
    • 여러 개를 나열하면 앞쪽이 우선. compatible = "vendor,model-v2", "vendor,model";
    • 커널이 v2 드라이버를 먼저 찾고, 없으면 v1 드라이버로 폴백
  • status가 없으면 “okay”
    • dtsi에서 status = "disabled";로 기본 비활성화하고, dts에서 status = "okay";로 켜는 패턴이 일반적
  • #address-cells / #size-cells 실수
    • 부모 노드의 이 값이 자식 노드의 reg를 해석하는 방법을 결정한다
    • SPI/I2C 컨트롤러는 #size-cells = <0>이어야 한다. 자식 노드의 reg가 주소만 있고 크기가 없으므로
  • 오버레이 컴파일 시 -@ 필수
    • -@ 플래그가 없으면 심볼 테이블이 생성되지 않아 target = <&label> 참조가 실패한다
  • devm_ 접두어 함수
    • devm_ioremap_resource, devm_kzalloc 등은 디바이스가 제거될 때 자동 해제된다
    • probe에서 할당하고 remove에서 해제하는 보일러플레이트를 없앤다