본문 바로가기
임베디드 개발/리눅스 디바이스 드라이버

LDD ] Device Tree (DT)

by eteo 2025. 3. 27.

 

 

 

디바이스 트리

 

디바이스 트리(Device Tree, DT)란 하드웨어의 구조와 특성을 운영체제나 소프트웨어가 이해할 수 있도록 기술한 데이터 구조이다.

이전에는 커널 소스에 하드웨어 정보를 하드코딩하는 방식이었다면 디바이스 트리 파일의 등장으로 인해 다양한 하드웨어를 단일 커널 이미지로 지원할 수 있게 되었다. 이로 인해 코드 중복이 줄어들고 유지보수성이 향상되는 이점이 있다.

 

디바이스 트리의 주요 목적은 다음과 같다.

  • 플랫폼 식별 : 커널이 실행 중인 플랫폼(보드)의 하드웨어 구조와 자원을 설명하여 플랫폼에 맞는 초기화 과정을 수행할 수 있도록 함
  • 디바이스 등록 : 디바이스 트리 데이터를 파싱하여 커널 내부 데이터 구조를 생성하고 디바이스 드라이버로 전달함

 

 

 

 

 

 

 

 


디바이스 트리의 계층적 작성 및 재사용

 

디바이스 트리는 보통 계층적 작성 방식을 통해 공통 정보와 board specific 정보를 구분하여 효율적으로 관리한다.

SoC 레벨 정보나 여러 보드에서 공통적으로 사용되는 하드웨어 정보는 별도의 .dtsi 파일로 분리하여 이를 재사용 가능하도록 설계한다. 반면, 특정 보드에만 적용되는 board specific 정보는 .dts 파일에서 관리하며 필요한 경우 공통 정보 파일인 .dtsi를 포함(#include)하여 재사용성을 높이는 구조를 취한다.

 

 

예를 들어, 비글본 블랙 보드의 디바이스 트리 파일(am335x-boneblack.dts)의 상속 구조를 보면 다음과 같다.

 

 

am335x-boneblack.dts : 비글본 블랙 보드에만 적용되는 추가적인 하드웨어 정보를 포함하는 파일

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com/
 */
/dts-v1/;

#include "am33xx.dtsi"
#include "am335x-bone-common.dtsi"
#include "am335x-boneblack-common.dtsi"

/ {
	model = "TI AM335x BeagleBone Black";
	compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
};

 

  • am33xx.dtsi : AM33xx 시리즈 SoC와 관련된 공통 하드웨어 정보가 정의된 파일로 SoC 자체의 GPIO, 인터럽트 컨트롤러, 타이머 등과 같은 기본 하드웨어 구성 정보가 포함된다.
  • am335x-bone-common.dtsi : AM335x SoC를 사용하는 보드들 간에 공통으로 적용되는 설정이 포함된 파일
  • am335x-boneblack-common.dtsi : 비글본 블랙 모델에만 적용되는 공통 정보가 정의된 파일로 just 비글본 블랙 보드 또는 wireless, Industrial 보드 등이 해당 파일을 포함할 수 있다.

 

위와 같은 구조를 통해 공통된 설정은 재사용하고, 개별 보드에 필요한 설정만 별도로 추가하여 효율적인 디바이스 트리를 작성할 수 있다.

 

한편, dtsi 파일에 이미 선언된 내용을 수정하려는 경우, dtsi 파일을 직접 수정하는 것은 권장되지 않으며, 대신 dts 파일에서 기존 노드의 정보를 override하는 방식으로 덮어쓰는 방법이 권장된다. 

 

 

 

 

 

 

 

 


디바이스 트리 구조

 

디바이스 트리는 하드웨어 정보를 트리 구조로 설명하며 각 디바이스를 노드(Node)로 표현한다. 각 노드는 부모-자식 관계를 기반으로 조직되고, 트리의 최상단에는 루트 노드(Root Node)가 존재한다.

 

  • 루트 노드
    • 트리 구조의 시작점이며 / 로 식별된다. (다른 노드와 달리 이름이 없다.)
    • 모든 디바이스 노드는 루트 노드를 기준으로 참조된다.
    • 루트 노드는 시스템에 하나만 존재하며, 디바이스 트리 최상단에 위치한다.
    • 루트 노드는 반드시 하나의 /cpus 노드와 최소 하나 이상의 /memory 노드를 포함해야 한다.
  • 노드
    • 각 노드는 하드웨어 디바이스를 나타낸다.
    • 노드는 속성(Property)을 통데 데이터를 정의한다.
      • Property는 Key-Value 쌍으로 정의되며 문자열, 정수, boolean 등 다양한 데이터 유형을 지원한다.
      • Property는 Device Tree specification에 따라 작성해야 하며, 표준 속성과 사용자 정의 속성으로 구분된다.
    • 노드는 부모-자식 관계를 형성한다. 예를 들어 I2C 컨트롤러는 부모 노드이고 여기에 I2C 버스를 통해 연결된 디바이스들은 자식노드가 된다.

 

 

 

 

 

 

 

 

 


디바이스 트리 작성 규칙

 

1. Node Names

 

노드 이름은 다음과 같은 형식을 따른다.

node-name@unit-address

 

  • node-name : 노드의 이름을 정의한다.
    • 가능한 경우 Devicetree Specification 2.2.2.에 정의된 다소 일반적인 이름을 사용하는 것이 권장된다. (ex. i2c, uart, gpio, memory, cpu, led 등)
    • 문자 수는 1~31자로 제한된다.
    • 이름에 lower case or uppercase letter, digit, comma, period, undercore, plus sign, dash를 포함할 수 있다.
    • 반드시 알파벳으로 시작해야하며 소문자를 사용하는 것이 권장된다.
    • dash"-" 대신 underscore"_"를 쓰는 것이 권장 된다.
  • unit-address : 노드가 사용하는 주소를 나타내며, reg 속성의 첫 번째 주소와 일치한다.
    • 만약 reg 속성이 없는 노드라면 unit-address를 생략할 수 있다.
    • Memory Mapped I/O 장치의 경우 프로세서와 통신하기 위한 레지스터의 base-address가 unit-address로 사용된다.
    • "0x" 접두사를 붙이지 않는다. 접두사가 없더라도 unit-address의 숫자는 hexadecimal로 간주된다.
  • label : 디바이스 노드를 더 쉽게 참조하고 식별하기 위해 종종 label이 사용된다.
    • label은 노드 이름 앞에 colon과 함께 붙는다. (ex. i2c0: i2c@44e0b000)
    • label을 통해 노드를 참조할 때는 ampersand"&"를 사용한다. (ex. &i2c0)
    • 반드시 알파벳으로 시작해야 하며 소문자만 사용하는 것이 권장된다.
    • underscore"_" 대신 dash"-"를 쓰는 것이 권장된다.
    • label은 device tree compiler에 의해 내부적으로 특정 노드에 대한 고유 정수 값인 phandle로 변환된다.

 

- label 사용 예시.

i2c0: i2c@44e0b000 {
    compatible = "example,i2c";
    reg = <0x44e0b000 0x1000>;
};

&i2c0 {
    status = "okay";
};

 

 

 

 

 

 

 

2. Property

 

Property는 각 노드에 대한 다양한 정보를 정의하며, name과 value의 쌍으로 구성된다. Property는 크게 표준 속성(Standard Properties)와 사용자 정의 속성(Custom Properties)로 나눌 수 있다.

 

  1. Standard Properties
    • Devicetree Specifiaction 및 드라이버 바인딩 문서에서 정의된 속성을 뜻한다.
    • 대표적으로 compatible, status, reg, phandle 등이 있으며 명확한 규칙과 표준에 따라 사용된다.
  2. Custom Properties
    • 특정 벤더나 조직에서 자체적으로 정의한 속성을 의미한다.
    • 벤더나 조직이름으로 된 고유 prefix를 comma와 함께 사용하여 이름 충돌을 방지해야 한다. (ex. fsl,channel-fifo-len)

 

 

Property는 다음과 같은 형식을 따른다.

property-name = property-value;

 

  • property-name : 속성의 이름을 정의한다.
    • 문자 수는 1~31자로 제한된다.
    • 이름에 lower case or uppercase letter, digit, comma, period, undercore, plus sign, dash, question mark, hash를 포함할 수 있다.
    • Custom property의 경우 벤더나 조직이름을 의미하는 고유 접두사를 붙여야 하며 일반적으로 comma","와 함께 붙인다.
    • 반드시 알파벳으로 시작해야하며 소문자만 쓰는 것이 권장된다.
    • dash"-" 대신 underscore"_"를 쓰는 것이 권장된다.
  • property-value : 해당 속성에 대한 노드의 데이터를 나타내며 다양한 데이터 타입을 지원한다. 

 

Value Description Example
<empty> 속성 이름만 존재하고 속성 값이 없는 형태로, 속성의 존재 여부 자체로 true 또는 false를 나타낼 때 사용한다. dma-coherent;
<u32> big-endian 형식의 32비트 정수이다. hex와 decimal 형식 둘 다 사용하며 hex인 경우 "0x" 접두사를 붙인다. clock-frequency = <48000000>;
<u64> big-endian 형식의 64비트 정수로 두 개의 <u32> 값으로 구성된다. 첫 번째 <u32> 값이 MSB를 포함하고 두 번째 <u32>값이 LSB를 포함한다. timer-counter = <0x00000001 0x00000000>;
<string> null-terminated 문자열로 ASCII 문자로 표현한다. compatible = "arm,cortex-a53"
<prop-encoded-array> 구조화된 데이터 배열로 배열의 형식은 각 속성의 정의에 따라 다르다. reg = <0x48060000 0x1000>;
interrupts = <1 17 4>;
<phandle> 문자열 리스트로 여러 문자열을 comma로 구분하여 나열한다. compatible = "vendor,dev1", "vendor,dev2";
<stringlist> <u32> 값으로 다른 노트를 참조하기 위한 고유한 값이다. phandle = <0x01>;

 

 

 

 

 

 

 

 

 


주요 Standard Property

 

 

1. compatible

  • 값 타입 : <stringlist>
  • 설명 : 이 속성은 리눅스 커널이 해당 디바이스를 처리하기 위해 적합한 드라이버를 선택하는 데 사용된다. 문자열은 가장 구체적인 것부터 가장 일반적인 것 순으로 나열된다. 이를 통해 단일 디바이스 드라이버가 여러 디바이스와 매칭될 수 있다. 
  • 권장되는 형식 : "manufacturer,model" (ex. "fsl,mpc8641", "ns16550")
  • 작성 규칙 : 속성 값은 가장 구체적인 것부터 가장 일반적인 것까지 나열한다. 위 예에서 운영체제는 먼저 "fsl,mpc8641"을 지원하는 디바이스 드라이버를 찾으려고 시도하고, 드라이버가 발견되지 않는 경우엔 더 일반적인 "ns16550" 장치 유형을 지원하는 드라이버를 찾는다.
  • 사용 사례 :
    • 루트 노드의 compatible 속성은 커널이 머신을 식별하고 초기화 루틴을 실행하는데 사용된다.
    • 디바이스 노드의 compatible 속성은 커널이 드라이버의 of_device_id 목록과 비교하여 매칭되는 드라이버를 로드하는데 사용된다. 매칭이 성공하면 드라이버의 probe 함수가 호출되어 디바이스를 초기화한다.

 

 

 

 

2. phandle

  • 값 타입 : <u32>
  • 설명 : Devicetree에서 노드를 참조하기 위한 고유한 식별자로 다른 노드에서 이 식별자를 사용해 해당 노드에 접근할 수 있다.
  • 예시
pic@10000000 {
   phandle = <1>;
   interrupt-controller;
   reg = <0x10000000 0x100>;
};

another-device-node {
  interrupt-parent = <1>;
};

 

 

 

 

 

3. status

  • 값 타입 : <string>
  • 설명 : 디바이스의 동작 상태를 나타낸다. 속성이 없을 경우 기본값은 "okay"이다.
  • 유효한 값 :
    • "okay" : 정상 작동 중
    • "disabled" : 비활성 상태로 활성화 가능함
    • "reserved" : 다른 소프트웨어에서 제어하는 장치로 사용해선 안됨
    • "fail" : 디바이스에 치명적인 오류가 발생하여 작동하지 않음

 

 

 

 

 

4. #address-cells, #size-cells

  • 값 타입 : <u32>
  • 설명 : 자식 노드의 reg 속성을 정의할 때 필요한 주소 필드의 셀 수(#address-cells) 및 크기 필드(#size-cells)의 셀 수를 지정한다.

예시.

soc {
   #address-cells = <1>;
   #size-cells = <1>;

   serial@4600 {
      compatible = "ns16550";
      reg = <0x4600 0x100>;
      clock-frequency = <0>;
      interrupts = <0xA 0x8>;
      interrupt-parent = <&ipic>;
   };
};

 

 

 

 

 

 

 

5. reg

  • 값 타입 : <proc-encoded-array>
  • 설명 : 디바이스 리소스의 주소 및 크기를 나타낸다. 값은 (address, length) 쌍으로 표현하며 상위 노드의 #address-cells, #size-cells 속성에 따라 결정된다.

예시.

(#address-cells, #size-cells의 값은 1이고, 디바이스가 두 개의 레지스터 블락을 가지고 있으며, 하나는 오프셋 0x3000에 위치한 32바이트이고, 다른 하나는 오프셋 0xFE00에 위치한 256바이트인 경우)

reg = <0x3000 0x20 0xFE00 0x100>;
 
 
 

 

 

 

 

 

 

 

 


Device tree binding

 

그렇다면 디바이스 트리에서 어떤 속성을 정의해야 하는지는 어떻게 알 수 있을까? 이를 위해 특정 디바이스를 DT에서 올바르게 정의하기 위해 필요한 속성과 값을 규정한 문서인 Devicetree Binding이 존재한다.

 

  • 작성 주체 : 드라이버 개발자 또는 플랫폼 벤더
  • 목적 : 드라이버와 DT 간의 연결을 명확히 규정하고 해당 디바이스를 사용하기 위해 DT를 작성하는 사용자에게 표준화된 가이드를 제공한다.
  • 문서 위치 : 리눅스 커널 소스 내 다음 경로에서 참조 가능하다.
  • Documentation/devicetree/bindings/
  • 문서 내용 : 디바이스를 정의하기 위한 필수/권장/선택 속성 등이 명시되어 있으며, 작성 예시도 포함하고 있다.

 

예시. (다음은 TI OMAP 계열 SoC의 I2C Controller 디바이스 노드에 대한 bindings 문서이다.)

I2C for OMAP platforms

Required properties :
- compatible : Must be "ti,omap2420-i2c", "ti,omap2430-i2c", "ti,omap3-i2c"
  or "ti,omap4-i2c"
- ti,hwmods : Must be "i2c<n>", n being the instance number (1-based)
- #address-cells = <1>;
- #size-cells = <0>;

Recommended properties :
- clock-frequency : Desired I2C bus clock frequency in Hz. Otherwise
  the default 100 kHz frequency will be used.

Optional properties:
- Child nodes conforming to i2c bus binding

Note: Current implementation will fetch base address, irq and dma
from omap hwmod data base during device registration.
Future plan is to migrate hwmod data base contents into device tree
blob so that, all the required data will be used from device tree dts
file.

Examples :

i2c1: i2c@0 {
    compatible = "ti,omap3-i2c";
    #address-cells = <1>;
    #size-cells = <0>;
    ti,hwmods = "i2c1";
    clock-frequency = <400000>;
};

 

 

 

 

 

 

 

 

 

 

 


Device tree overay

 

디바이스 트리 오버레이란? 기존의 메인 dtb(Device Tree Blob)파일을 수정하거나 확장하기 위해 사용되는 패치 파일이다.

 

왜 필요할까? 보드에 새로운 디바이스를 연결하고자 할 때 해당 장치를 지원하기 위한 설정(노드, 속성, 핀 먹싱 등)을 디바이스 트리에 추가해야 한다. 이 때, 메인 dtb 파일을 직접 수정하는 것보다 디바이스 트리 오버레이를 사용하는 것이 권장된다. 이는 특정 하드웨어 구성을 독립된 파일로 관리할 수 있어 유연하게 추가/제거가 가능하고 유지보수가 용이해지기 때문이다.

 

 

 

1. 디바이스 트리 오버레이 적용 방법

  1. DTS 오버레이 파일 작성 : 추가 또는 수정하려는 하드웨어 구성을 설명하는 .dts 파일을 생성한다.
  2. DTS 오버레이 파일 컴파일 : 작성된 DTS 파일을 컴파일하여 .dtbo 확장자의 바이너리 파일을 생성한다.
    $ dtc -I dts -O dtb -o my-overlay.dtbo my-overlay.dts
  3. 메인 dtb와 병합 : 병합 방식은 플랫폼 제조사나 하드웨어 환경에 따라 다르다. 일반적인으로 다음의 방법이 존재한다.
    • 부트로더의 환경 변수 설정 파일을 수정하여 디바이스 트리 오버레이를 적용하는 방식. 일반적으로 많이 사용하는 방식이다. 다만, 설정 파일의 경우 젯슨은 extlinux.conf, 비글본 블랙은 uEnv.txt, 라즈베리 파이는 config.txt으로 파일명과 파일 위치, 파일 수정 방법이 플랫폼에 따라 제각각이다.
    • 부트로더가 동작하는 중에 커맨드 라인을 통해서 동적으로 디바이스 트리 오버레이를 적용하는 방식. 매번 적용해 주어야하는 번거로움이 존재한다.

 

 

 

2. DTS 오버레이 포맷 (/Documentation/devicetree/overlay-notes.txt)

/dts-v1/;
/plugin/;

/ {
	/* ignored properties by the overlay */

	fragment@0 {	/* first child node */

		target=<phandle>;	/* phandle target of the overlay */
	or
		target-path="/path";	/* target path of the overlay */

		__overlay__ {
			property-a;	/* add property-a to the target */
			node-a {	/* add to an existing, or create a node-a */
				...
			};
		};
	}
	fragment@1 {	/* second child node */
		...
	};
	/* more fragments follow */
}

 

  • 오버레이 파일에는 메인 dtb에 대한 추가/변경 사항만 포함하며 fragment 단위로 작성한다.
  • /dts-v1/ : 이 디바이스 트리 소스 파일이 Devicetree Specifictaion Version 1을 따르고 있음을 선언한다.
  • /plugin/ : 이 파일이 디바이스 트리 오버레이 파일임을 컴파일러(dtc)에게 알려준다.
  • 보통 /* ignored properties by the overlay */ 이 부분에 overlay-name 속성과 루트 노드의 compatible 속성을 써주는 경우가 많다. 해당 속성은 오버레이 파일을 쉽게 구분하고 어떤 플랫폼에서 사용 가능한지 확인하기 위해 사용하며 필수는 아니다.
  • fragment :
    • fragment는 오버레이에서 수정하거나 추가할 Target을 정의하는 단위이다.
    • 각 fragment는 하나의 Target 노드에 대한 변경 사항을 포함하며, 오버레이 파일은 fragment@0, fragment@1과 같이 여러 개의 fragment를 나열할 수 있다.
    • target :
      • target은 수정할 대상 노드를 지정하는 속성이다.
      • target=<phandle> : 일반적으로 사용하는 방식으로 정수값 대신 <&label>을 통해 참조한다.
      • target-path="/path" : 절대 경로를 통해 노드를 지정하는 방식으로 경로가 변경될 가능성이 있어 잘 사용하지 않는 방법이다. (ex. target-path = "/soc/i2c@48070000";)
    • __overlay__ :
      • 지정된 타겟 노드에 적용할 변경 사항을 기술하는 영역이다.
      • 기존 노드의 속성을 추가하거나 변경할 수 있고 자식 노드를 추가하거나 수정하는 것이 가능하다.

 

 

 

3. DTS 오버레이 예시

메인 디바이스 트리 :

/soc {
    i2c0: i2c@48070000 {
        status = "disabled";
    };
};

 

DTS 오버레이 파일 :

/dts-v1/;
/plugin/;

{
    fragment@0 {
        target = <&i2c0>;  /* 레이블 i2c0을 참조 */

        __overlay__ {
            status = "okay";  /* 기존 속성을 덮어씌움 */
            node-a {
                compatible = "my-node";  /* 새로운 노드 추가 */
                reg = <0x50>;
            };
        };
    };
};

 

병합 결과 :

/soc {
    i2c0: i2c@48070000 {
        status = "okay";
        node-a {
            compatible = "my-node";
            reg = <0x50>;
        };
    };
};

 

 

 

 

 

참고 : https://devicetree-specification.readthedocs.io/en/latest/