1. 设备树基础

1.1 设备树起源:为什么要有设备树?

做嵌入式Linux开发的朋友,一定遇到过这样的场景:换一颗Flash芯片,或者改一下GPIO的配置,就得重新编译整个内核。这太痛苦了。

在设备树出现之前,Linux内核里充斥着大量的板级代码。每个开发板、每个设备,都要在C代码里硬编码硬件信息。我记得刚入行那会儿,光是维护一个平台的board-xxx.c文件,就够我喝一壶的。每次换硬件,都得改代码、重新编译,效率极低。

设备树(Device Tree)的出现,就是为了解决这个问题。它把硬件描述从内核代码中剥离出来,变成一种独立的数据结构。说白了,就是一份硬件清单。内核启动时,通过这份清单来了解板子上有什么设备、怎么访问它们。

这个思路其实很朴素:硬件信息是数据,不是代码。数据就应该用数据的方式去管理。设备树用一套统一的语法来描述硬件,内核则负责解析这套语法。这样一来,换硬件只需要改设备树文件,内核本身不用动。

核心思想:设备树 = 硬件描述语言,用于向操作系统传递硬件配置信息。

1.2 设备树语法:怎么描述硬件?

设备树的语法,其实不复杂。它由节点(node)和属性(property)组成。每个节点代表一个硬件设备或总线,属性则描述这个设备的具体参数。

来看一个最简单的例子:

/dts-v1/;

/ {
    model = "MyBoard";
    compatible = "vendor,myboard";

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

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;
    };

    uart0: serial@10000000 {
        compatible = "ns16550";
        reg = <0x10000000 0x1000>;
        interrupts = <0 33 4>;
        clock-frequency = <24000000>;
    };
};

嗯,这里要注意几个关键点:

  • 根节点:用 / 表示,是整个设备树的起点
  • 节点名称:格式是 名称@地址,比如 cpu@0、serial@10000000
  • 属性:键值对的形式,值可以是字符串、数字、数组等
  • 标签:比如 uart0:,方便在其他地方引用这个节点

我在项目中遇到过一个问题:两个外设的中断号冲突了。排查了半天,才发现是设备树里 interrupts 属性写错了。所以,写设备树时一定要仔细核对芯片手册里的中断号。

1.3 常用属性详解

有些属性是设备树里的常客,我列出来给大家看看:

属性名 含义 示例
compatible 设备兼容性标识,驱动匹配的关键 "vendor,device"
reg 寄存器地址和长度 <0x10000000 0x1000>
interrupts 中断号、类型 <0 33 4>
clocks 时钟源引用 <&clkc 15>
status 设备状态(okay/disabled) "okay"

你想想看,compatible 属性就像设备的身份证。驱动通过它来找到对应的硬件。我习惯把厂商名和设备名用逗号隔开,比如 "ti,am335x-uart",这样既清晰又规范。

1.4 设备树编译与反编译

设备树源文件(.dts)不能直接被内核使用,需要编译成二进制格式(.dtb)。反过来,有时候我们拿到一个 .dtb 文件,想看看里面是什么内容,就需要反编译。

编译和反编译的工具是 dtc(Device Tree Compiler)。用法很简单:

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

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

我个人习惯在编译时加上 -@ 选项,这样生成的 .dtb 支持插件(overlay),方便后期动态修改。

小技巧:用 dtc -I dtb -O dts myboard.dtb 可以直接把 .dtb 内容打印到终端,适合快速查看。

我曾经有一次,从客户那里拿到一个 .dtb 文件,但不知道它对应什么硬件。反编译之后,一看 compatible 属性,就明白了。所以,反编译是调试设备树问题的利器。

1.5 设备树知识体系

下面这张图,是我梳理的设备树核心知识结构。你可以把它当作学习路线图:

设备树知识体系 设备树 (Device Tree) 起源:为什么需要设备树? 语法:节点与属性 编译与反编译 解耦硬件与内核代码 compatible / reg / interrupts dtc 工具的使用 核心:数据驱动硬件描述

1.6 避坑指南

做设备树开发,有几个坑我踩过,分享给大家:

  • 地址对齐问题:reg 属性里的地址和长度,必须和硬件手册一致。我曾经因为少写了一个0,导致内存映射失败。
  • 中断号搞混:设备树里的中断号,通常是硬件中断号,不是Linux的IRQ号。这个区别很重要。
  • 忘记加 status = "okay":有些节点默认是 disabled 的,不显式启用的话,驱动根本不会加载。

注意:设备树文件里不要出现中文注释!dtc 编译器对中文支持不好,容易报错。我习惯用英文写注释,或者干脆不写。

好了,设备树的基础就讲到这里。说白了,它就是一份硬件描述文件,用节点和属性来告诉内核:我有什么设备、在哪里、怎么用。掌握了这些,后面写驱动就轻松多了。