1. 概述

在Linux中,设备树(Device Tree)是一种描述硬件配置的数据结构,它被用来将硬件的结构信息(包括CPU 架构和型号, 内存基址和大小, 总线控制器,外设设备, 中断控制器和中断线, GPIO 控制器和引脚, 时钟源 )传递给操作系统内核。这样,内核就可以在不依赖于硬编码的情况下,支持不同的硬件平台。

设备树最初是在Open Firmware(定义计算器固件接口的标准,以前由IEEE认可。起源于Sun Microsystems,最初被称为OpenBoot)中使用的,后来被广泛用于嵌入式Linux系统,特别是那些使用ARM、PowerPC等架构的系统。设备树的主要目的是提供一种不依赖于架构的方式来描述硬件,使得同一个内核镜像能够支持多个不同的硬件平台。

设备树由以下几个部分组成:

  1. 设备树源文件(.dts):这些是文本文件,用于描述硬件设备。它们通常由硬件厂商或板级支持包(BSP)提供。

  2. 设备树编译器(dtc):这是一个工具,用于将设备树源文件(.dts)编译成设备树二进制文件(.dtb)。

  3. 设备树二进制文件(.dtb):这是由设备树编译器生成的二进制文件,它包含了硬件设备的描述信息。这个文件通常在启动时由引导程序(如U-Boot)加载到内存中,并传递给Linux内核。

  4. 设备树绑定(Bindings):这些是文档,描述了如何为特定设备编写设备树节点。它们通常位于Linux内核源码的Documentation/devicetree/bindings目录下。

设备树的基本语法类似于树形结构,由节点(node)和属性(property)组成。每个节点可以包含子节点,属性则是键值对,用于描述节点的特性。

一个简单的设备树示例:

/dts-v1/;

/ {
    model = "My Board";
    compatible = "my,board";

    cpus {
        cpu@0 {
            compatible = "arm,cortex-a9";
            device_type = "cpu";
            reg = <0>;
        };
    };

    memory@0 {
        device_type = "memory";
        reg = <0x00000000 0x40000000>;
    };

    uart0: serial@101f0000 {
        compatible = "ns16550a";
        reg = <0x101f0000 0x1000>;
        interrupts = <0x0 0x2 0x0>;
    };
};

在这个例子中:

  • 根节点(/)有两个属性:modelcompatible

  • cpus节点有一个子节点cpu@0,描述了CPU的信息。

  • memory@0节点描述了内存的起始地址和大小。

  • uart0节点描述了一个串口设备,包括其寄存器地址和中断信息。

设备树在Linux内核启动时被解析,内核会根据设备树中的信息来初始化和配置硬件设备。这样,内核就可以在不重新编译的情况下,适应不同的硬件平台。

在嵌入式开发中,经常需要修改设备树来添加或修改硬件设备。修改后,需要重新编译设备树生成新的.dtb文件,并将该文件用于启动内核。

总结一下,设备树在Linux中的作用是:

  • 描述硬件配置,使得内核能够动态地识别和初始化硬件。

  • 减少内核为支持不同硬件平台所需的代码量。

  • 提供一种标准化的方式来表示硬件,便于维护和移植。

在 Linux 中,设备树(Device Tree)是一种描述硬件配置的数据结构,它已经成为嵌入式 Linux 系统中描述硬件的标准方式。

2. 设备树编译和使用

# 编译 .dts 为 .dtb
dtc -I dts -O dtb -o myboard.dtb myboard.dts

# 反编译 .dtb 为 .dts
dtc -I dtb -O dts -o myboard.dts myboard.dtb

# 查看当前设备树
ls /proc/device-tree/

# 查看特定属性
cat /proc/device-tree/model

# 在启动时加载设备树
# 在 bootloader 中指定:
bootz kernel_addr initrd_addr dtb_addr

3. 示例

GPIO 控制器

gpio0: gpio@1000000 {

gpio0节点标签,其他节点可以通过 &gpio0 引用这个 GPIO 控制器;
gpio@1000000节点名称,表示这是一个 GPIO 控制器,位于地址 0x1000000
    compatible = "vendor,gpio-controller";//用于绑定设备驱动 

"vendor,gpio-controller" 表示这个设备与名为 "vendor,gpio-controller" 的驱动程序兼容,实际中 vendor 会被替换为具体的厂商名,如 "ti,gpio-controller"
    reg = <0x1000000 0x1000>;

  • 定义 GPIO 控制器的内存映射区域

  • 0x1000000:起始物理地址

  • 0x1000:地址空间大小(4KB)

    #gpio-cells = <2>;
    gpio-controller;

  • #gpio-cells = <2>:定义引用 GPIO 时需要多少个参数

    • 通常第一个参数是 GPIO 编号

    • 第二个参数是标志(如 GPIO_ACTIVE_HIGH/LOW)

  • gpio-controller声明这是一个 GPIO 控制器


    interrupt-controller;
    #interrupt-cells = <2>;

  • interrupt-controller声明这是一个中断控制器

  • #interrupt-cells = <2>:定义引用中断时需要多少个参数

    • 通常第一个参数是中断号

    • 第二个参数是中断类型(如边沿触发、电平触发)


}

I2C 设备

i2c0: i2c@2000000 {
    compatible = "vendor,i2c-controller";
    reg = <0x2000000 0x1000>;
    #address-cells = <1>;
    #size-cells = <0>;
    clock-frequency = <100000>;
    
    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
        pagesize = <16>;
    };
};

中断控制器

intc: interrupt-controller@3000000 {
    compatible = "arm,gic-400";
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0x3000000 0x1000>,
          <0x3002000 0x2000>;
};

设备树绑定(Bindings)

设备树绑定定义了特定设备所需的属性和格式:

// LED 设备绑定示例
leds {
    compatible = "gpio-leds";
    
    led0 {
        label = "heartbeat";
        gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>;
        linux,default-trigger = "heartbeat";
    };
    
    led1 {
        label = "mmc0";
        gpios = <&gpio0 16 GPIO_ACTIVE_HIGH>;
        linux,default-trigger = "mmc0";
    };
};

4. 调试技巧

查看设备树

# 查看完整设备树
dtc -I fs /sys/firmware/devicetree/base

# 查看特定节点
find /sys/firmware/devicetree/base -name "*uart*"

# 内核启动参数添加设备树调试
dracut --add "dts" ...

常用调试工具

# 设备树编译器
dtc --version

# 查看设备树 overlay
fdtoverlay --help

# 设备树实用工具
apt-get install device-tree-compiler

注:设备树使得 Linux 内核可以支持多种硬件平台而无需重新编译,大大提高了嵌入式系统的灵活性和可维护性。

Logo

纵情码海钱塘涌,杭州开发者创新动! 属于杭州的开发者社区!致力于为杭州地区的开发者提供学习、合作和成长的机会;同时也为企业交流招聘提供舞台!

更多推荐