设备树详解
概述
设备树(Device Tree)是一种描述硬件设备的数据结构,用于将硬件信息从内核代码中分离出来,实现内核与硬件描述的解耦。
设备树发展历史
设备树最初由Open Firmware项目开发,后来被Linux内核采用。在ARM架构中,设备树取代了传统的平台设备注册方式。
设备树文件类型
| 文件类型 |
扩展名 |
说明 |
| 源文件 |
.dts |
设备树源文件 |
| 包含文件 |
.dtsi |
可被包含的设备树片段 |
| 二进制文件 |
.dtb |
编译后的设备树二进制 |
| 覆盖文件 |
.dtbo |
设备树覆盖二进制 |
基本语法结构
设备树基本框架
| Devicetree |
|---|
| /dts-v1/;
/ {
model = "My Development Board";
compatible = "vendor,myboard", "vendor,myfamily";
#address-cells = <1>;
#size-cells = <1>;
chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2";
stdout-path = &uart0;
};
aliases {
serial0 = &uart0;
i2c0 = &i2c0;
};
memory@40000000 {
device_type = "memory";
reg = <0x40000000 0x20000000>;
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <0>;
clock-frequency = <800000000>;
};
};
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges;
uart0: serial@40000000 {
compatible = "ns16550a";
reg = <0x40000000 0x100>;
interrupts = <0 10 4>;
clock-frequency = <100000000>;
status = "okay";
};
};
};
|
常用节点类型
chosen节点
| Devicetree |
|---|
| chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rw";
stdout-path = "serial0:115200n8";
stdin-path = "serial0";
linux,initrd-start = <0x45000000>;
linux,initrd-end = <0x45800000>;
};
|
memory节点
| Devicetree |
|---|
| memory@40000000 {
device_type = "memory";
reg = <0x40000000 0x20000000>; // 起始地址和大小
};
// 多段内存
memory@40000000 {
device_type = "memory";
reg = <0x40000000 0x10000000>,
<0x60000000 0x10000000>;
};
|
reserved-memory节点
| Devicetree |
|---|
| reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
framebuffer@40000000 {
compatible = "shared-dma-pool";
reg = <0x40000000 0x00800000>;
no-map;
};
gpu_reserved: gpu@40800000 {
compatible = "shared-dma-pool";
reg = <0x40800000 0x00400000>;
no-map;
};
};
|
中断描述
中断控制器
| Devicetree |
|---|
| intc: interrupt-controller@40030000 {
compatible = "arm,gic-400";
reg = <0x40031000 0x1000>,
<0x40032000 0x2000>,
<0x40034000 0x2000>,
<0x40036000 0x2000>;
interrupt-controller;
#interrupt-cells = <3>;
};
|
中断属性格式
| Devicetree |
|---|
| // GIC中断格式:<类型 中断号 触发方式>
// 类型:0=SPI, 1=PPI
// 触发方式:1=上升沿, 2=下降沿, 4=高电平, 8=低电平
uart0: serial@40000000 {
interrupts = <0 10 4>; // SPI中断,中断号10,高电平触发
interrupt-parent = <&intc>;
};
gpio: gpio@40020000 {
compatible = "vendor,gpio";
reg = <0x40020000 0x1000>;
interrupts = <0 20 4>, <0 21 4>, <0 22 4>;
interrupt-controller;
#interrupt-cells = <2>;
};
|
时钟描述
时钟提供者
| Devicetree |
|---|
| clk: clock-controller@40050000 {
compatible = "vendor,clock";
reg = <0x40050000 0x1000>;
#clock-cells = <1>;
clock-output-names = "cpu", "ahb", "apb";
};
|
时钟消费者
| Devicetree |
|---|
| uart0: serial@40000000 {
compatible = "ns16550a";
reg = <0x40000000 0x100>;
clocks = <&clk 2>; // 使用第2个时钟
clock-names = "apb";
};
|
GPIO描述
GPIO控制器
| Devicetree |
|---|
| gpio0: gpio@40020000 {
compatible = "vendor,gpio";
reg = <0x40020000 0x1000>;
#gpio-cells = <2>;
gpio-controller;
ngpios = <32>;
// GPIO中断
interrupt-controller;
#interrupt-cells = <2>;
};
|
GPIO消费者
| Devicetree |
|---|
| led {
compatible = "gpio-leds";
led0 {
gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>;
label = "status";
default-state = "on";
};
led1 {
gpios = <&gpio0 11 GPIO_ACTIVE_LOW>;
label = "heartbeat";
linux,default-trigger = "heartbeat";
};
};
|
I2C设备描述
| Devicetree |
|---|
| i2c0: i2c@40070000 {
compatible = "vendor,i2c";
reg = <0x40070000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
clock-frequency = <400000>;
eeprom@50 {
compatible = "atmel,24c08";
reg = <0x50>;
pagesize = <16>;
};
sensor@68 {
compatible = "invensense,mpu6050";
reg = <0x68>;
interrupt-parent = <&gpio0>;
interrupts = <17 IRQ_TYPE_EDGE_RISING>;
mount-matrix = "1", "0", "0",
"0", "1", "0",
"0", "0", "1";
};
};
|
SPI设备描述
| Devicetree |
|---|
| spi0: spi@40080000 {
compatible = "vendor,spi";
reg = <0x40080000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <40000000>;
spi-cpol;
spi-cpha;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "boot";
reg = <0x000000 0x040000>;
};
partition@40000 {
label = "kernel";
reg = <0x040000 0x400000>;
};
};
};
};
|
设备树覆盖(Overlay)
overlay示例
| Devicetree |
|---|
| /dts-v1/;
/plugin/;
/ {
compatible = "vendor,myboard";
fragment@0 {
target = <&i2c0>;
__overlay__ {
#address-cells = <1>;
#size-cells = <0>;
temp_sensor: tmp102@48 {
compatible = "ti,tmp102";
reg = <0x48>;
};
};
};
};
|
应用overlay
| Bash |
|---|
| # 编译dtbo
dtc -@ -I dts -O dtb -o my-overlay.dtbo my-overlay.dts
# 应用overlay
mkdir /sys/kernel/config/device-tree/overlays/my-overlay
cat my-overlay.dtbo > /sys/kernel/config/device-tree/overlays/my-overlay/dtbo
# 移除overlay
rmdir /sys/kernel/config/device-tree/overlays/my-overlay
|
常用属性说明
reg属性
| Devicetree |
|---|
| // #address-cells = <1>; #size-cells = <1>;
reg = <地址 大小>;
// #address-cells = <2>; #size-cells = <1>;
reg = <地址高位 地址低位 大小>;
// #address-cells = <1>; #size-cells = <0>;
reg = <地址>;
|
compatible属性
| Devicetree |
|---|
| // 单个compatible
compatible = "vendor,device";
// 多个compatible(按优先级排序)
compatible = "vendor,myboard", "vendor,myfamily", "vendor,soc";
|
status属性
| Devicetree |
|---|
| status = "okay"; // 设备启用
status = "disabled"; // 设备禁用
status = "fail"; // 设备故障
|
编译与反编译
编译设备树
| Bash |
|---|
| # 使用dtc编译
dtc -I dts -O dtb -o board.dtb board.dts
# 使用内核构建系统
make dtbs
# 编译特定dtb
make arch/arm/boot/dts/board.dtb
|
反编译设备树
| Bash |
|---|
| # dtb转dts
dtc -I dtb -O dts -o board.dts board.dtb
# 查看dtb信息
fdtdump board.dtb
|
在内核中使用设备树
获取节点
| C |
|---|
| #include <linux/of.h>
// 通过路径获取
struct device_node *np = of_find_node_by_path("/soc/uart@40000000");
// 通过compatible获取
struct device_node *np = of_find_compatible_node(NULL, NULL, "vendor,device");
// 通过phandle获取
struct device_node *np = of_find_node_by_phandle(phandle);
// 通过别名获取
struct device_node *np = of_find_node_by_name(NULL, "uart0");
|
读取属性
| C |
|---|
| // 读取字符串
const char *str;
of_property_read_string(np, "compatible", &str);
// 读取整数
u32 value;
of_property_read_u32(np, "clock-frequency", &value);
// 读取数组
u32 regs[2];
of_property_read_u32_array(np, "reg", regs, 2);
// 检查属性是否存在
if (of_property_read_bool(np, "my-property")) {
// 属性存在
}
|
获取GPIO
| C |
|---|
| #include <linux/of_gpio.h>
// 获取GPIO号
int gpio = of_get_named_gpio(np, "led-gpios", 0);
// 获取GPIO描述符
struct gpio_desc *gpiod = gpiod_get_from_of_node(np, "led", 0,
GPIOD_OUT_LOW, NULL);
|
获取中断
| C |
|---|
| #include <linux/of_irq.h>
int irq = irq_of_parse_and_map(np, 0);
|
调试设备树
在/proc中查看
| Bash |
|---|
| # 查看设备树信息
ls /proc/device-tree/
# 查看compatible
cat /proc/device-tree/compatible
# 查看模型
cat /proc/device-tree/model
|
使用dtc工具
| Bash |
|---|
| # 从运行的系统导出设备树
dtc -I fs -O dts -o current.dts /proc/device-tree/
|
参考资料