3. 设备树基础语法(二):数据类型与地址单元

好,咱们接着聊设备树的语法。上一节我们把骨架搭起来了,这一节要往里面填“肉”——也就是各种数据类型。

说实话,我刚接触设备树那会儿,最头疼的就是这些数据类型。尤其是 phandle#address-cells,看着就头大。但后来我发现,只要搞懂了它们背后的设计思路,其实一点都不难。

3.1 设备树支持的数据类型

设备树的数据类型,说白了就六种。我列个表给你看,一目了然:

数据类型 关键字/写法 说明 示例
32位整数 u32 最常用的类型,地址、大小、标志位都用它 reg = <0x10000000 0x1000>;
64位整数 u64 用两个 u32 拼起来表示 reg = <0x00000000 0x80000000>;
字符串 string 用双引号括起来 compatible = "ti,am335x-gpio";
引用句柄 phandle 指向另一个节点的指针 interrupt-parent = <&intc>;
布尔值 bool 存在即为 true,不存在为 false status = "disabled"; 或直接写属性名
字节串 bytestring 用方括号括起来的十六进制字节 local-mac-address = [00 11 22 33 44 55];

3.2 逐个拆解,讲点实在的

3.2.1 u32 —— 最常用的“小兵”

u32 就是 32 位无符号整数。设备树里几乎所有数值都用它。比如描述一个 GPIO 控制器的寄存器基地址:

gpio0: gpio@44e07000 {
    compatible = "ti,omap4-gpio";
    reg = <0x44e07000 0x1000>;
    interrupts = <96>;
};

这里 0x44e07000 是基地址,0x1000 是地址范围。两个都是 u32。

我的小经验: 写 u32 数值时,地址尽量用十六进制,大小也建议用十六进制。这样一眼就能看出对齐关系。我见过有人用十进制写地址,结果算偏移量算到崩溃。

3.2.2 u64 —— 两个 u32 拼出来的“大块头”

有些 64 位 SoC,地址空间很大,一个 u32 装不下。这时候就用两个 u32 拼成一个 u64。

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

上面这个例子,表示两段内存:第一段从 0x80000000 开始,大小 2GB;第二段从 0x100000000 开始,大小 4GB。注意看,每个地址和大小都是两个 u32 拼起来的。

注意: 用 u64 的时候,#address-cells#size-cells 必须设置为 2。否则编译器会按 u32 解析,结果全乱套。我曾经在调试一个 FPGA 加速卡时踩过这个坑,查了两天才发现是 cells 设错了。

3.2.3 string —— 最直观的类型

字符串用双引号括起来,多个字符串可以用逗号分隔。最常见的用途就是 compatible 属性:

compatible = "ti,am335x-gpio", "ti,omap4-gpio";

内核匹配驱动时,会从左到右依次尝试。第一个匹配上了就用,没匹配上就试下一个。所以一般把最具体的型号放前面,通用的放后面。

3.2.4 phandle —— 设备树的“指针”

phandle 是设备树里最巧妙的设计之一。它用来引用另一个节点。比如一个 UART 设备要连接某个中断控制器:

uart0: serial@44e09000 {
    compatible = "ti,am335x-uart";
    reg = <0x44e09000 0x2000>;
    interrupt-parent = <&intc>;
    interrupts = <72>;
};

intc: interrupt-controller@48200000 {
    compatible = "ti,am335x-intc";
    reg = <0x48200000 0x1000>;
    interrupt-controller;
    #interrupt-cells = <1>;
};

这里的 &intc 就是一个 phandle,指向了 intc 这个节点。编译时,设备树编译器会把 &intc 替换成对应的整数句柄值。

核心理解: phandle 就是设备树里的“指针”。它让节点之间可以互相引用,形成复杂的拓扑关系。没有 phandle,设备树就是一盘散沙。

3.2.5 bool —— 有就是 true

布尔值在设备树里很简单:属性存在就是 true,不存在就是 false。比如:

interrupt-controller;

这个属性没有值,只要写了,就表示这个节点是一个中断控制器。内核驱动里检查 of_property_read_bool() 返回 true 就说明有这个属性。

3.2.6 bytestring —— 原始字节数据

字节串用方括号括起来,每个字节用空格隔开。最典型的应用是 MAC 地址:

local-mac-address = [00 11 22 33 44 55];

也可以用来存放一些固件参数、校准数据等。我在一个项目里就用字节串存放过 WiFi 模块的校准参数,省去了单独烧录 EEPROM 的麻烦。

3.3 #address-cells 和 #size-cells —— 地址单元的“尺子”

这两个属性,可以说是设备树里最容易让人迷糊的地方。我尽量讲清楚。

#address-cells 表示用几个 u32 来表示一个地址。
#size-cells 表示用几个 u32 来表示一个地址范围的大小。

看个例子就明白了:

soc {
    compatible = "simple-bus";
    #address-cells = <1>;
    #size-cells = <1>;

    gpio0: gpio@44e07000 {
        reg = <0x44e07000 0x1000>;
    };

    uart0: serial@44e09000 {
        reg = <0x44e09000 0x2000>;
    };
};

这里 #address-cells = <1>,所以每个地址用一个 u32 表示。
#size-cells = <1>,所以每个大小也用一个 u32 表示。

那如果换成 64 位地址空间呢?

soc {
    compatible = "simple-bus";
    #address-cells = <2>;
    #size-cells = <1>;

    dma@100000000 {
        reg = <0x00000001 0x00000000 0x1000>;
    };
};

看到没?地址用了两个 u32:0x00000001 0x00000000,拼起来就是 0x100000000。大小还是用一个 u32:0x1000

避坑指南: 我曾经在一个项目里,把 #address-cells 设成了 2,但子节点的 reg 里只写了一个 u32 的地址。结果设备树编译不报错,但内核解析出来的地址完全不对。调试了整整一天才发现是 cells 和 reg 不匹配。所以记住:父节点的 cells 决定了子节点 reg 的格式,必须严格对应。

3.4 知识体系总览

说了这么多,我画个图帮你理一理思路:

设备树数据类型与地址单元知识体系 设备树数据类型 u32 32位整数 u64 64位整数 string 字符串 phandle 引用句柄 bool 布尔值 bytestring 字节串 #address-cells 和 #size-cells #address-cells = <1> 或 <2> #size-cells = <1> 或 <2> 决定 reg 属性的格式 父节点定义规则,子节点遵守规则

3.5 组合使用,才是真功夫

实际项目中,这些数据类型很少单独出现。它们会组合在一起,描述一个完整的硬件设备。比如一个带 DMA 的以太网控制器:

ethernet@4a100000 {
    compatible = "ti,cpsw";
    reg = <0x4a100000 0x1000>;
    interrupts = <40 41 42>;
    interrupt-parent = <&intc>;
    status = "okay";
    local-mac-address = [00 00 00 00 00 00];

    phy-mode = "rgmii";
    #address-cells = <1>;
    #size-cells = <0>;

    phy0: ethernet-phy@0 {
        reg = <0>;
    };
};

你看,这里 u32、string、phandle、bool、bytestring 全用上了。#address-cells#size-cells 也配合得恰到好处。

我的建议: 刚开始写设备树时,先照着参考手册抄,抄多了自然就熟了。遇到不懂的数据类型,就想想它对应硬件上的什么信息。地址?大小?字符串?引用?想清楚这个,就不会用错。

嗯,数据类型这部分就讲到这里。记住一句话:设备树的数据类型,本质上是为描述硬件服务的。搞懂了硬件需要什么信息,数据类型自然就选对了。


公众号:蓝海资料掘金营,微信deep3321