Linux Device Tree
LinuxEmbedded
Device Tree는 하드웨어 구성을 기술하는 데이터 구조다. 커널 소스를 수정하지 않고 보드별 하드웨어 차이를 기술할 수 있다. ARM Linux에서는 사실상 필수다.
파일 구조
| 확장자 | 역할 | 예시 |
|---|---|---|
.dtsi | SoC 공통 정의. 재사용 | bcm2711.dtsi |
.dts | 보드별 최종 설정. dtsi를 include | bcm2711-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.txt에 dtoverlay=sensor를 추가하면 부팅 시 자동 적용된다.
메모
- compatible 문자열 순서가 중요하다
- 여러 개를 나열하면 앞쪽이 우선.
compatible = "vendor,model-v2", "vendor,model"; - 커널이 v2 드라이버를 먼저 찾고, 없으면 v1 드라이버로 폴백
- 여러 개를 나열하면 앞쪽이 우선.
- status가 없으면 “okay”
- dtsi에서
status = "disabled";로 기본 비활성화하고, dts에서status = "okay";로 켜는 패턴이 일반적
- dtsi에서
- #address-cells / #size-cells 실수
- 부모 노드의 이 값이 자식 노드의
reg를 해석하는 방법을 결정한다 - SPI/I2C 컨트롤러는
#size-cells = <0>이어야 한다. 자식 노드의reg가 주소만 있고 크기가 없으므로
- 부모 노드의 이 값이 자식 노드의
- 오버레이 컴파일 시
-@필수-@플래그가 없으면 심볼 테이블이 생성되지 않아target = <&label>참조가 실패한다
- devm_ 접두어 함수
devm_ioremap_resource,devm_kzalloc등은 디바이스가 제거될 때 자동 해제된다- probe에서 할당하고 remove에서 해제하는 보일러플레이트를 없앤다