设备树语法

设备树(Device Tree)本质上就是一份写给操作系统的硬件说明书。

1. Devicetree格式

1.1 DTS文件的格式

DTS文件布局(layout):

/dts-v1/;                // 表示版本
/* 设备树的内存保留区 告诉 Linux 内核:这块物理内存你不能用,留给硬件 / 固件 / 启动程序专用!*/
[memory reservations]    // 格式为: /memreserve/ <address> <length>; 从物理地址 0x80000000 开始,保留长度 0x00100000(1MB),内核永远不会使用这段内存
/ {
    [property definitions]
    [child nodes]
};

1.2 node的格式

设备树中的基本单元,被称为“node”,其格式为:

/* label是标号,可以省略。label的作用是为了方便地引用node */
[label:] node-name[@unit-address] { 
    [properties definitions]
    [child nodes]
};

比如:

/dts-v1/;
/ {
    uart0: uart@fe001000 {
        compatible="ns16550";
        reg=<0xfe001000 0x100>;
    };
};

可以使用下面2种方法来修改uart@fe001000这个node:

// 在根节点之外使用label引用node:
&uart0 {
    status = “disabled”;
};
或在根节点之外使用全路径:
&{/uart@fe001000}  {
    status = “disabled”;
};

1.3 properties的格式

简单地说,properties就是“name=value”,value有多种取值方式。
Property格式1:
[label:] property-name = value;
Property格式2(没有值):
[label:] property-name;
Property取值只有3种:

arrays of cells(1个或多个32位数据, 64位数据使用2个32位数据表示), 
string(字符串), 
bytestring(1个或多个字节)

如:

device@12340000 {
    compatible = "myvendor,mydevice";  // 字符串类型
    reg = <0x12340000 0x100>;         // cells数组类型
    mac-address = [00 1A 2B 3C 4D 5E];// bytestring类型
    interrupt-controller;              // 无值属性
};

2. dts文件包含dtsi文件

设备树文件不需要我们从零写出来,内核支持了某款芯片,在内核的arch/arm/boot/dts目录下就有了能用的设备树模板,一般命名为xxxx.dtsi。“i”表示“include”,被别的文件引用的。
dts中可以包含.h头文件,也可以包含dtsi文件,在.h头文件中可以定义一些宏。
示例:

/dts-v1/;
#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"
/ {
……
};

3. 常用的属性

3.1 #address-cells、#size-cells

  • cell指一个32位的数值,
  • address-cells:address要用多少个32位数(cell)来表示;
  • size-cells:size要用多少个32位数(cell)来表示。

它们是父节点写给子节点的 “说明书”,告诉子节点:你写地址和长度的时候,要按我定的规则来。
比如一段内存,怎么描述它的起始地址和大小?
下例中,address-cells为1,所以reg中用1个数来表示地址,即用0x80000000来表示地址;size-cells为1,所以reg中用1个数来表示大小,即用0x20000000表示大小:
合起来就是:内存从 0x80000000 开始,大小是 0x20000000(512MB)。

  • 格式: 赋值为单个 32 位数字。
/ {
    #address-cells = <1>;
    #size-cells = <1>;
    memory {
    reg = <0x80000000 0x20000000>;
    };
};

3.2 compatible

  • 作用:设备树中最重要的“接头暗号”,表示设备的兼容性。内核启动时,会严格根据这个字符串列表(从左到右的优先级)去寻找对应的 C 语言驱动程序。
  • 格式:字符串列表。官方强烈建议采用 "manufacturer,model"("厂家名,模块名")的命名规范。
led {
    compatible = “A”, “B”, “C”;// 内核中可能有A、B、C三个驱动都支持它
};

内核启动时,就会为这个LED按这样的优先顺序为它找到驱动程序:A、B、C。
compatible的值,建议取这样的形式:“manufacturer,model”,即“厂家名,模块名”。

3.3 model

  • 作用: 用来向内核准确、唯一地宣告“这块板子/这个设备确切是什么型号”。因为多个不同的单板可能拥有相同的 compatible 兼容列表,内核需要依靠 model 属性来做最终的具体区分
  • 格式: 一个精确的字符串。
{
    compatible = "samsung,smdk2440", "samsung,mini2440";
    model = "jz2440_v3";// model用来准确地定义这个硬件是什么
};
/* 
    这段代码定义了一个硬件平台的基本信息,告诉内核:
    这个硬件平台与三星的SMDK2440和Mini2440兼容(优先匹SMDK2440)。
    硬件平台的型号为“jz2440_v3”。
    从compatible属性中可以知道它兼容哪些板,但是它到底是什么板?用model属性来明确。
*/

3.4 status

  • 作用:dtsi文件中定义了很多设备,但是在你的板子上某些设备是没有的。这时你可以给这个设备节点添加一个status属性,设置为“disabled”:
  • 格式:字符串。常用值为 "okay"(设备正常运行)或 "disabled"(设备停用/不加载驱动)。
&uart1 {
      status = "disabled";
};
value description
okay 设备正常运行
disabled 设备不可操作,但是后面可以恢复工作
fail 发生了严重错误,需修复
fail-sss 发生了严重错误,需修复;sss 表示错误信息

3.5 reg

  • 作用:reg的本意是register,用来表示寄存器地址。但是在设备树里,它可以用来描述一段空间。反正对于ARM系统,寄存器和内存是统一编址的,即访寄存器时用某块地址,访问内存时用某块地址,在访问方法上没有区别。
  • 格式:<address size address size ...> 的组合。reg属性的值,是一系列的“address size”,用多少个32位的数来表示address和size,由其父节点的#address-cells、#size-cells决定。
/dts-v1/;
/ {
    #address-cells = <1>;
    #size-cells = <1>; 
    memory {
        reg = <0x80000000 0x20000000>;
    };
};

3.6 clocks 与 clock-names

  • 作用:任何数字硬件都需要时钟信号才能工作。设备树负责分配时钟源。
  • 格式:clocks 指向时钟提供者(通常是 CRU 节点),clock-names 给这个时钟起个别名,供 C 语言驱动中调用。
clocks = <&cru CLK_MIPI_CAMARAOUT_M2>;
clock-names = "xvclk";

3.7 xxx-gpios (GPIO 控制)

  • 作用:用于控制硬件的复位(Reset)、休眠(Power down)或电源使能等状态。
  • 格式:属性名通常由驱动规定(如 reset-gpios),值包含:引脚控制器、具体引脚号、有效电平(如高电平有效)。

reset-gpios = <&gpio3 RK_PB1 GPIO_ACTIVE_HIGH>;

3.8 interrupts (中断)

  • 作用:当硬件有突发事件(如 USB 插入、数据接收完毕)时,主动通知 CPU 的“门铃”。
  • 格式: 包含中断类型(如 GIC_SPI 共享中断)、中断号、触发方式(如电平触发/边缘触发)。

interrupts = <GIC_SPI 221 IRQ_TYPE_LEVEL_HIGH>;

4. 常用的节点(node)

4.1 根节点

dts文件中必须有一个根节点:

/dts-v1/;
/ {
    model = "SMDK24440";
    compatible = "samsung,smdk2440";

    #address-cells = <1>;
    #size-cells = <1>; 
};

根节点中必须有这些属性:

#address-cells // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
#size-cells    // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size)
compatible     // 定义一系列的字符串, 用来指定内核中哪个machine_desc可以支持本设备
               // 即这个板子兼容哪些平台
               // uImage : smdk2410 smdk2440 mini2440     ==> machine_desc
model          // 咱这个板子是什么
               // 比如有2款板子配置基本一致, 它们的compatible是一样的
               // 那么就通过model来分辨这2款板子

4.2 CPU节点

一般不需要我们设置,在dtsi文件中都定义好了:

cpus {
            #address-cells = <1>;
            #size-cells = <0>;

            cpu0: cpu@0 {
                .......
        }
};

4.3 memory节点

芯片厂家不可能事先确定你的板子使用多大的内存,所以memory节点需要板厂设置,比如:

memory {
    reg = <0x80000000 0x20000000>;
};

4.4 chosen节点

我们可以通过设备树文件给内核传入一些参数,这要在chosen节点中设置bootargs属性:

chosen {
    bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
};

5. Pinctrl (引脚复用系统)

核心属性:pinctrl-names 定义状态名(通常是 "default"),pinctrl-0 指向具体的引脚配置节点。

pinctrl-names = "default";
pinctrl-0 = <&mipim1_camera2_clk>; // 把物理针脚复用为 camera 时钟输出

6. 厂商相关内容

标准的设备树语法全世界通用,但不同的芯片原厂(如 Rockchip, Allwinner, Qualcomm)会定义自己独有的属性,用于特殊的业务逻辑匹配。

  • 规则: 必须带有厂商前缀,如 rockchip,xxx
  • 出处:这些属性名和值的含义,并非凭空捏造,必须查阅原厂提供的 Binding 文档 或直接阅读 C 语言驱动源码

devicetree rockchip,camera-module-name = "CMK-OT2016-FV1"; // 给 ISP 找对应调优文件的暗号

7. 静态枚举和动态发现

7.1 静态枚举

  • 适用总线:I2C, SPI, MIPI, UART 等不可发现总线。
  • 原理:CPU 无法感知引脚上挂了什么设备。
  • 做法:必须在设备树中写清楚挂载了什么设备(如 ov13855),内核才能根据设备树去加载驱动。

7.2 动态发现

  • 适用总线:USB, PCIe等。
  • 原理:协议自带热插拔和电子名片(设备描述符)机制。设备插入时,硬件产生中断,总线读取名片上的 Class ID(如 0x0E 代表 Video),内核自动匹配对应驱动(如 uvcvideo)。
  • 做法:设备树中只写控制器节点(火车站,如 dwc3 控制器和 PHY),绝对不写具体的外设节点(火车,如 U盘、键盘、摄像头)。

8. 一般模板

&csi2_dphy3 {
        port@0 {
            mipidphy1_in_ucam0: endpoint@0 {
            };
            mipidphy1_in_ucam1: endpoint@1 {
            };
        };
        port@1 {
                csidphy1_out: endpoint@0 {
                };
        };
    };
};

8.1 第一层:大模块设备(大楼)—— &csi2_dphy3

代表了真实的、物理存在的硬件控制器实体

8.2 第二层:物理接口(大门)—— port@0 和 port@1

一个复杂的硬件往往有输入口和输出口。port 就是用来区分不同数据方向或不同物理接口的。通常行规是:port@0 作为输入(Input),port@1 作为输出(Output)。

8.3 第三层:连接端点(卸货通道/网线)—— endpoint@x

一个物理接口(大门)可能同时连接了多个外部设备。endpoint 就是大门里面具体划分的连接通道。冒号前面的 mipidphy1_in_ucam0: 只是个方便引用的标签(Label),冒号后面的 endpoint@0 才是它真正的节点名。

posted @ 2026-05-04 22:36  r5ett  阅读(55)  评论(0)    收藏  举报