跳转至

设备树详解

概述

设备树(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
1
2
3
4
5
6
7
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
1
2
3
4
5
6
7
8
9
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
1
2
3
4
5
6
clk: clock-controller@40050000 {
    compatible = "vendor,clock";
    reg = <0x40050000 0x1000>;
    #clock-cells = <1>;
    clock-output-names = "cpu", "ahb", "apb";
};

时钟消费者

Devicetree
1
2
3
4
5
6
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
1
2
3
4
5
6
7
8
9
# 编译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
1
2
3
4
5
6
7
8
// #address-cells = <1>; #size-cells = <1>;
reg = <地址 大小>;

// #address-cells = <2>; #size-cells = <1>;
reg = <地址高位 地址低位 大小>;

// #address-cells = <1>; #size-cells = <0>;
reg = <地址>;

compatible属性

Devicetree
1
2
3
4
5
// 单个compatible
compatible = "vendor,device";

// 多个compatible(按优先级排序)
compatible = "vendor,myboard", "vendor,myfamily", "vendor,soc";

status属性

Devicetree
1
2
3
status = "okay";      // 设备启用
status = "disabled";  // 设备禁用
status = "fail";      // 设备故障

编译与反编译

编译设备树

Bash
1
2
3
4
5
6
7
8
# 使用dtc编译
dtc -I dts -O dtb -o board.dtb board.dts

# 使用内核构建系统
make dtbs

# 编译特定dtb
make arch/arm/boot/dts/board.dtb

反编译设备树

Bash
1
2
3
4
5
# 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
1
2
3
4
5
6
7
8
#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
1
2
3
#include <linux/of_irq.h>

int irq = irq_of_parse_and_map(np, 0);

调试设备树

在/proc中查看

Bash
1
2
3
4
5
6
7
8
# 查看设备树信息
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/

参考资料