Linux 设备树 DTS 与 DTSI 入门指南
📌 什么是 DTS / DTSI?
- DTS(Device Tree Source):主设备树源文件,定义某个具体设备或开发板的硬件结构,比如处理器、内存、外设等。
- DTSI(Device Tree Source Include):设备树的包含文件,可被多个 DTS 文件共享,主要用于公用的硬件部分(如同系列 SoC)。
类似于 C 语言的
.h
头文件和.c
源文件关系。
🧠 它们如何工作?
- 开发者编写
.dts
和.dtsi
文件; - 使用
dtc
编译成.dtb
(Device Tree Blob); - 内核启动时加载
.dtb
,据此初始化硬件; - Linux 内核用它来注册平台设备、加载驱动等。
📚 DTS / DTSI 基本语法
设备树是一种类似 C 结构体风格的描述语言。
✅ 示例结构
/ {
compatible = "vendor,board-model";
model = "My Device Board";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
cpus {
cpu@0 {
compatible = "arm,cortex-a53";
reg = <0>;
};
};
soc {
uart@40000000 {
compatible = "vendor,uart";
reg = <0x40000000 0x1000>;
interrupts = <5>;
};
};
};
🧾 常用关键字说明
属性名 | 含义 |
---|---|
compatible | 匹配驱动 |
reg | 寄存器地址和大小 |
interrupts | 中断号 |
status | 启用状态(okay /disabled ) |
#address-cells | 地址单元数 |
#size-cells | 大小单元数 |
include | 引用 dtsi 文件 |
🔧 DTS / DTSI 示例
dtsi 文件(通用 SoC)
// soc.dtsi
/soc {
#address-cells = <1>;
#size-cells = <1>;
uart0: serial@40000000 {
compatible = "vendor,uart";
reg = <0x40000000 0x1000>;
interrupts = <5>;
status = "disabled";
};
};
dts 文件(具体开发板)
// myboard.dts
/include/ "soc.dtsi"
/ {
model = "Vendor MyBoard";
compatible = "vendor,myboard";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
&uart0 {
status = "okay";
};
};
🛠️ 编译 DTS 到 DTB
dtc -I dts -O dtb -o myboard.dtb myboard.dts
参数说明:
-I dts
:输入是 DTS 格式;-O dtb
:输出为 DTB 格式;-o
:指定输出文件。
🚀 使用场景
- ARM SoC(Allwinner、NXP i.MX、Rockchip 等)平台;
- 添加 SPI/I2C 传感器;
- 配置 GPIO 中断、引脚复用;
- 适配裸机驱动或 Linux 驱动。
✅ 开发流程总结
- 阅读芯片手册,了解硬件外设;
- 编写通用的
.dtsi
文件; - 编写开发板专属的
.dts
文件; - 使用
dtc
编译生成.dtb
; - 与 Bootloader 或内核一起部署。
通常linux 的arch/arm/dts中带有一些对应芯片平台通用的 DTS 文件, 可以根据SoC 型号和硬件信息(如 UART、SPI、LED 等)进行修改。
myboard.dts
文件解析与说明
以下是一个典型的设备树源文件 myboard.dts
,用于描述某个开发板上的硬件信息,并引用了通用的 SoC 定义文件 soc.dtsi
。
📄 源码内容
// myboard.dts
/include/ "soc.dtsi"
/ {
model = "Vendor MyBoard";
compatible = "vendor,myboard";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
&uart0 {
status = "okay";
};
};
🧠 分段解析
#include "soc.dtsi"
- 引入通用的 SoC 定义文件,提供基础硬件(如 UART、I2C 等)配置。
.dtsi
文件中一般包含 CPU、外设控制器等节点。
根节点 / { ... }
model
和 compatible
-
model = "Vendor MyBoard";
- 表示当前开发板的名称,仅用于标识。
-
compatible = "vendor,myboard";
- Linux 内核用它来选择合适的驱动程序。
内存定义
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>;
};
- 描述系统内存的起始地址和大小:
- 起始地址:
0x80000000
- 大小:
0x10000000
(256MB)
- 起始地址:
device_type = "memory"
是必须的标准属性。
外设引用:&uart0
&uart0 {
status = "okay";
};
&uart0
表示引用在soc.dtsi
中定义的 UART 节点。- 设置其状态为
"okay"
,表示启用该设备(默认可能是"disabled"
)。
🛠️ 编译方式
使用 dtc
(Device Tree Compiler)将 dts
文件编译为内核可识别的 dtb
格式:
dtc -I dts -O dtb -o myboard.dtb myboard.dts
-I dts
:输入格式为 dts;-O dtb
:输出格式为 dtb;-o myboard.dtb
:指定输出文件名。
✅ 总结
这个 myboard.dts
文件:
- 定义了开发板的模型信息;
- 配置了内存;
- 启用了一个串口设备(通过引用
soc.dtsi
中的uart0
); - 是 Linux 启动过程中平台设备初始化的重要组成部分。
在 DTS 中自定义新的外设节点指南
在设备树(DTS)中,除了继承和修改 .dtsi
文件中已有的外设外,你也可以完全自定义新的设备节点,比如一个额外的 GPIO 接口、LED、I2C/SPI 传感器等。
✅ 示例:自定义一个 GPIO 控制的 LED
前提:SoC 的 GPIO 控制器定义在 soc.dtsi
中
gpio1: gpio@50000000 {
compatible = "vendor,gpio";
reg = <0x50000000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
};
自定义 LED 外设节点(在 DTS 中添加)
/ {
model = "Vendor MyBoard";
compatible = "vendor,myboard";
leds {
compatible = "gpio-leds";
status-led {
label = "status";
gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>; // GPIO1 第5号管脚
default-state = "off";
};
};
};
🧾 字段说明
字段 | 说明 |
---|---|
leds | 自定义的设备节点名,可任意命名 |
compatible | 使用内核中已有的 gpio-leds 驱动 |
status-led | LED 子节点名称 |
label | LED 的名字标签 |
gpios | 使用哪一个 GPIO 控制器和引脚,格式为 <控制器别名 管脚号 电平> |
default-state | 初始状态(on 、off 、keep ) |
🚀 其他常见的可自定义外设
SPI 设备示例
&spi0 {
my_sensor@0 {
compatible = "vendor,my-sensor";
reg = <0>; // SPI 片选号
spi-max-frequency = <1000000>;
};
};
I2C 设备示例
&i2c1 {
my_touch@14 {
compatible = "vendor,my-touch";
reg = <0x14>; // I2C 地址
};
};
⚠️ 注意事项
- 所有自定义节点必须挂载在正确的父节点下(如 SPI、I2C、GPIO 等控制器);
compatible
字符串必须与你的驱动支持的值一致;- 如果没有现成驱动,也可以基于
platform_driver
写你自己的 Linux 驱动。
如果 .dtsi
文件中没有定义父节点,但你想在 .dts
文件中添加新的外设节点,该怎么办呢?
⚠️ 设备树节点必须有父节点
设备树是树形结构,每个节点都必须有父节点。你不能直接在根节点之外放置孤立节点。
.dtsi
里通常定义了 SoC 的各种硬件总线(如&uart0
,&spi0
,&gpio1
)等父节点。- 如果
.dtsi
里没有这些父节点,你就得自己在.dts
里补上。
🚀 如果 .dtsi
没有父节点怎么办?
方案 A:自己在 .dts
中定义父节点
你可以在 .dts
文件的根节点下,手动添加完整的父节点定义,例如:
/ {
gpio1: gpio@50000000 {
compatible = "vendor,gpio";
reg = <0x50000000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
};
leds {
compatible = "gpio-leds";
status-led {
label = "status";
gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
default-state = "off";
};
};
};
这样你的设备树里就有了 gpio1
这个父节点,status-led
才能引用它。
方案 B:向 SoC DTSI 文件中添加父节点
如果你能修改 .dtsi
,建议把父节点放进 .dtsi
,然后在 .dts
里只写外设节点,结构更清晰。
方案 C:自定义新的父节点(如果是新硬件)
如果你的硬件平台全新设计,没有 .dtsi
,你就需要在 .dts
里写完整的设备树,包括所有父节点和外设。
✅ 小结
- 设备树节点必须在已有父节点下,不能孤立。
- 父节点可以在
.dtsi
里,也可以自己在.dts
中补上。 - 如果没有父节点,设备树就不完整,内核驱动无法识别设备。