임베디드 리눅스 커널을 다룰 때 디바이스 트리(Device Tree)를 정확하게 이해하는 것은 하드웨어 제어의 기본이자 핵심입니다. 과거에는 새로운 보드가 출시될 때마다 수많은 C 소스 코드를 직접 수정하고 커널을 매번 다시 빌드해야 하는 번거로움이 있었습니다. 하드웨어 구성 정보를 소프트웨어와 완전히 분리하여 유지보수 효율을 극대화하기 위해 도입된 디바이스 트리의 개념과 문법, 그리고 실제 적용 방법까지 상세히 정리했습니다.

핵심 요약 3줄
- 디바이스 트리는 하드웨어 구성 정보를 커널 소스와 분리하여 독립적인 텍스트 파일로 기술하는 데이터 구조입니다.
- 구조는 노드와 속성으로 이루어져 있으며 보드의 메모리 맵, 인터럽트, 클럭 정보 등을 트리 형태로 계층화하여 표현합니다.
- 텍스트 형태인 소스 파일(.dts)을 디바이스 트리 컴파일러(dtc)를 통해 바이너리 파일(.dtb)로 변환하여 부트로더와 커널에 전달합니다.
디바이스 트리(Device Tree)의 개념과 도입 배경
ARM 기반의 SoC(System on Chip) 시장이 급격히 성장하면서 수많은 제조사의 다양한 보드 변형이 등장했습니다. 기존 방식대로 C 코드 내에 하드웨어 레지스터 주소와 인터럽트 번호를 하드코딩하던 방식은 커널 소스를 비대해지게 만들고 유지보수를 불가능하게 만들었습니다. 디바이스 트리는 이러한 문제를 해결하기 위해 하드웨어 정보를 별도의 텍스트 파일로 정의하고, 커널은 부팅 시 이 파일을 읽어 유연하게 드라이버를 로드하도록 돕습니다.
| 비교 항목 | 기존 방식 (하드코딩 C 소스) | 디바이스 트리 방식 (DTS/DTB) |
| 관리 위치 | 커널 소스 코드 내 장치 정의 (arch/arm/mach-*) | 독립된 소스 파일 (.dts, .dtsi) |
| 하드웨어 변경 시 | 커널 전체 또는 해당 모듈 재빌드 필요 | 디바이스 트리 파일만 컴파일 후 대체 가능 |
| 커널 바이너리 | 각 보드마다 독립적인 커널 이미지 필요 | 하나의 커널 이미지로 다수의 보드 대응 가능 |
| 주요 장점 | 직관적인 C 코드 제어 | 하드웨어 추상화 및 코드 가독성 향상 |
디바이스 트리의 기본 파일 구조
디바이스 트리 파일은 주로 구조적 특성에 따라 세 가지 확장자로 구분되어 관리됩니다. 하드웨어 장치를 나타내는 노드(Node)와 해당 장치의 세부 설정을 정의하는 속성(Property)이 계층적으로 중첩된 구조를 이룹니다.
| 확장자 | 파일 종류 | 설명 |
| .dtsi | 포함 파일 (Include) | SoC의 공통 레지스터, 클럭, 주변 장치 등 범용적인 하드웨어 정보를 기술 |
| .dts | 소스 파일 (Source) | 개발자가 최종 타깃 보드에 맞추어 작성하는 파일로, .dtsi를 포함하여 특정 핀이나 장치 활성화 상태를 명시 |
| .dtb | 바이너리 파일 (Blob) | 텍스트 파일인 .dts를 컴파일러를 통해 변환한 형태로, 부트로더가 커널에 전달하는 실제 파일 |
디바이스 트리 소스 분석 및 작성법
가장 널리 쓰이는 표준적인 SoC의 UART(범용 비동기 송수신기) 장치 설정을 기준으로 디바이스 트리 파일의 작성 형태를 파악할 수 있습니다.
디바이스 트리 기본 문법 예시
/dts-v1/;
/ {
model = "Generic ARM Cortex Board";
compatible = "manufacturer,generic-soc";
chosen {
bootargs = "console=ttyS2,115200n8 root=/dev/mmcblk0p1 rw rootwait";
};
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges;
uart2: serial@ff1a0000 {
compatible = "ns16550a";
reg = <0xff1a0000 0x1000>;
interrupts = <0 54 4>;
clocks = <&cru_clocks 1>, <&cru_clocks 2>;
clock-names = "pclk", "sclk";
status = "okay";
};
};
};
필수 속성 기술 규격
| 속성명 (Property) | 역할 및 설명 | 작성 예시 |
| compatible | 커널 드라이버와 매칭하기 위한 고유 식별 문자열 | "ns16550a", "vendor,device" |
| reg | 장치가 사용하는 베이스 레지스터의 시작 주소와 크기 영역 | <0xff1a0000 0x1000> |
| interrupts | 장치가 사용하는 인터럽트 타입, 번호, 트리거 플래그 지정 | <0 54 4> (SPI, IRQ 번호, 하이 레벨 트리거) |
| clocks | 해당 장치 제어 및 동작에 필요한 시스템 클럭 소스 지정 | <&cru_clocks 1> |
| status | 커널 부팅 시 해당 드라이버를 활성화할지 여부 결정 | "okay" 또는 "disabled" |
디바이스 트리 빌드 및 시스템 적용 절차
텍스트로 작성된 소스 파일은 커널이 직접 읽을 수 없으므로 바이너리 형태로 변환하여 호스트 시스템에서 타깃 보드로 배포해야 합니다.
1. 소스 컴파일 (DTC 활용)
리눅스 커널 소스 트리 내에 포함되어 있거나 패키지로 제공되는 디바이스 트리 컴파일러(dtc)를 사용하여 빌드를 진행합니다.
dtc -I dts -O dtb -o custom-board.dtb custom-board.dts
2. 타깃 시스템 배치 및 로드
생성된 바이너리 파일(.dtb)을 타깃 시스템의 부트 디렉토리로 복사한 뒤, 부트로더 환경 변수를 수정하여 부팅 시 커널과 함께 메모리에 로드되도록 설정합니다.
# 타깃 시스템의 부트 영역으로 복사
cp custom-board.dtb /boot/
# U-Boot 환경 변수 설정 및 저장 예시
=> setenv fdtfile custom-board.dtb
=> saveenv
=> boot
개발을 위한 팁
- 상태 값 활용을 통한 디버깅: 하드웨어 보드 검증 과정에서 특정 주변 장치 동작에 문제가 의심된다면, 소스 코드를 건드리지 않고 디바이스 트리에서 status = "disabled"; 처리를 하여 해당 장치를 안전하게 격리한 뒤 테스트할 수 있습니다.
- 런타임 디바이스 트리 확인: 리눅스가 이미 부팅된 상태라면 타깃 시스템 내부의 /sys/firmware/devicetree/base 경로에서 현재 커널이 로드하여 사용 중인 트리 구조를 실시간으로 탐색하고 레지스터 설정값을 교차 검증할 수 있습니다.
- 라벨(Label) 지정을 통한 오버라이드: 부모 칩 셋 파일(.dtsi)에 정의된 장치 주소를 자식 파일(.dts)에서 재정의할 때는 장치 주소를 모두 적지 않고 &uart2 { ... } 형태와 같이 라벨을 참조하여 필요한 속성만 깔끔하게 덮어쓰는 것이 좋습니다.
흔히 하는 실수
- 주소 셀 개수 설정 오류 (#address-cells): 상위 soc 노드에 선언된 #address-cells와 #size-cells의 값과 하위 장치 노드의 reg 속성에 기재된 인자 개수가 일치하지 않으면 컴파일 에러가 발생하거나 커널이 메모리 매핑 주소를 잘못 해석하여 다운됩니다.
- 상태 값 문자열 오타: status 속성에 부여하는 값은 예약어 문자열입니다. 간혹 무의식적으로 status = okay;와 같이 따옴표를 누락하거나 대문자를 섞어 쓰면 드라이버가 로드되지 않는 현상이 발생하므로 반드시 큰따옴표 안에 소문자로 "okay"라고 작성해야 합니다.
- 제조사 접두사(Vendor Prefix) 누락: compatible 속성을 임의로 지정할 때 표준 등록된 제조사 텍스트 식별자를 누락하면 커널 드라이버 매칭 테이블이 정상적으로 매핑 동작을 수행하지 못해 장치 인식을 실패하곤 합니다.
결론
디바이스 트리는 임베디드 리눅스 생태계에서 커널 소스의 청결함을 유지하고, 다양한 하드웨어 변형에 유연하게 대응할 수 있도록 설계된 강력한 도구입니다. 노드 구조와 주소 셀 정의 방식 등 다소 생소한 문법적 규칙만 명확하게 숙지해 두면 하드웨어 사양이 바뀔 때마다 복잡한 드라이버 C 코드를 헤집지 않고도 텍스트 파일 수정 몇 줄과 가벼운 컴파일 과정을 거쳐 시스템을 효율적으로 제어할 수 있습니다.
'Embedded System > Linux Kernel & Device Drivers' 카테고리의 다른 글
| 임베디드 리눅스 BSP 개념과 개발 공정: 수정부터 신규 작성까지 (0) | 2025.03.12 |
|---|---|
| 임베디드 리눅스 커널 포팅 절차 총정리: 환경 구축부터 빌드까지 (0) | 2025.03.11 |
| 임베디드 리눅스 부팅 과정 완벽 가이드: BootROM부터 Rootfs까지 (0) | 2025.03.09 |
| ARM64 임베디드 리눅스 커널 빌드 환경 구축 및 컴파일 실무 (0) | 2025.03.08 |
| 임베디드 리눅스 커널 구조 이해와 ARM64 빌드 실무 가이드 (0) | 2025.03.07 |