3. 设备树基础概念:DTS语法、节点与属性
设备树(Device Tree)是嵌入式Linux中描述硬件拓扑结构的核心机制。对于Camera Sensor驱动开发而言,设备树是驱动与硬件之间的“桥梁”——它告诉内核:Sensor挂接在哪个I2C总线上、使用哪个地址、需要哪些电源、中断引脚是什么。本章将系统讲解设备树的语法、节点与属性,重点剖析reg和compatible这两个驱动匹配的关键属性。
.dts(Device Tree Source)为后缀,编译后生成.dtb(Device Tree Blob)供内核解析。驱动开发者主要阅读和编写.dts文件,无需手动处理二进制格式。
3.1 设备树语法基础
设备树采用树形结构描述硬件,每个硬件设备对应一个节点(node),节点内包含若干属性(property)。语法风格类似C语言的大括号块结构,以分号结尾。
3.1.1 节点定义
节点的基本格式如下:
[label:] node-name[@unit-address] {
[property-list];
[child-node-list];
};
- label:可选标签,用于在其他位置引用该节点(如
&i2c0)。 - node-name:节点名称,应体现设备类型(如
camera-sensor)。 - unit-address:设备地址,通常与
reg属性中的第一个地址一致(如I2C从设备地址)。 - property-list:属性列表,描述设备特性。
- child-node-list:子节点列表,形成层级结构。
/* I2C控制器节点 */
&i2c0 {
status = "okay";
clock-frequency = <400000>;
/* Camera Sensor子节点 */
ov5640: camera-sensor@3c {
compatible = "ovti,ov5640";
reg = <0x3c>;
clocks = <&clks IMX8MM_CLK_CLK_OUT1>;
reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>;
powerdown-gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>;
port {
ov5640_ep: endpoint {
remote-endpoint = <&mipi_csi_ep>;
data-lanes = <1 2>;
};
};
};
};
3.1.2 属性类型与写法
| 属性类型 | 语法示例 | 说明 |
|---|---|---|
| 字符串 | compatible = "ovti,ov5640"; |
双引号包裹,用于驱动匹配 |
| 32位整数 | reg = <0x3c>; |
尖括号包裹,可包含多个值 |
| 64位整数 | reg = <0x00000000 0x10000000>; |
两个32位整数拼接表示64位 |
| 布尔值 | status = "okay"; |
通过字符串"okay"/"disabled"表示 |
| 字节数组 | local-mac-address = [00 11 22 33 44 55]; |
方括号包裹,十六进制字节 |
| 字符串列表 | clock-names = "xvclk", "mclk"; |
逗号分隔多个字符串 |
| 整数列表 | data-lanes = <1 2>; |
尖括号内空格分隔 |
| 混合结构 | gpios = <&gpio1 7 GPIO_ACTIVE_LOW>; |
包含phandle+整数+宏 |
3.2 核心属性详解:compatible 与 reg
在设备树中,compatible和reg是驱动与设备匹配的关键属性。内核通过compatible找到对应的驱动,通过reg获取设备的硬件地址。
3.2.1 compatible 属性——驱动匹配的“身份证”
compatible属性是一个字符串列表,用于指定设备所兼容的驱动。内核在启动时,会遍历设备树中的所有节点,将节点的compatible值与驱动中of_match_table里的条目进行匹配。
命名规范:
- 格式:
"厂商,设备型号" - 厂商名使用小写字母,设备型号使用小写字母和数字
- 例如:
"ovti,ov5640"、"sony,imx219"、"omnivision,ov8865"
匹配优先级:内核按照compatible列表中字符串的从左到右顺序进行匹配,越靠前的字符串优先级越高。
of_match_table与设备树中的compatible字符串必须完全一致(包括大小写和标点)。例如驱动中写"ovti,ov5640",设备树中写"ovti,OV5640"会导致匹配失败。
3.2.2 reg 属性——设备地址的“坐标”
reg属性用于描述设备在总线上的地址范围。对于I2C设备,reg通常只包含一个从设备地址(7位或10位)。对于内存映射设备,reg包含起始地址和长度。
reg属性的通用格式:
reg = <address1 length1 [address2 length2] ...>;
I2C设备的reg使用:
- 对于7位I2C地址,直接写入地址值(如
0x3c) - 对于10位I2C地址,需要设置
reg的高位标志(通常由I2C控制器驱动处理) - Camera Sensor常用的I2C地址范围:
0x20~0x7f
| 设备类型 | reg示例 | 含义 |
|---|---|---|
| I2C Sensor | reg = <0x3c>; |
I2C从设备地址为0x3c |
| SPI设备 | reg = <0>; |
SPI片选号为0 |
| 内存映射设备 | reg = <0x30000000 0x1000>; |
基地址0x30000000,长度0x1000 |
| 多地址设备 | reg = <0x40000000 0x1000 0x50000000 0x2000>; |
两个地址区间 |
3.3 设备树层级结构与节点关系
设备树的层级结构反映了硬件的物理连接关系。对于Camera Sensor,典型的层级路径为:
/ {
soc {
i2c@30a20000 { /* I2C控制器 */
camera-sensor@3c { /* Camera Sensor */
port { /* 端口子节点 */
endpoint { /* 端点描述 */
remote-endpoint = <&csi_ep>;
};
};
};
};
mipi-csi@32e00000 { /* MIPI CSI接收器 */
port {
csi_ep: endpoint {
remote-endpoint = <&sensor_ep>;
};
};
};
};
};
这种层级结构通过remote-endpoint属性建立跨节点连接,形成完整的数据通路。
3.4 设备树中的地址编码与#address-cells/#size-cells
设备树使用#address-cells和#size-cells属性定义reg属性中地址和长度的字长(以32位为单位)。这两个属性在父节点中定义,影响所有子节点的reg解析。
| 属性 | 含义 | 常见值 |
|---|---|---|
#address-cells |
地址字段占用的32位字数量 | 1(32位地址)、2(64位地址) |
#size-cells |
长度字段占用的32位字数量 | 0(无长度)、1(32位长度) |
I2C总线场景:
/* I2C控制器节点 */
i2c@30a20000 {
#address-cells = <1>; /* 地址占1个32位字 */
#size-cells = <0>; /* 无长度字段(I2C设备只有地址) */
camera-sensor@3c {
reg = <0x3c>; /* 解析为:地址=0x3c,无长度 */
};
};
内存映射设备场景:
/* 内存映射设备 */
soc {
#address-cells = <2>; /* 64位地址 */
#size-cells = <2>; /* 64位长度 */
display-controller@32e00000 {
reg = <0x0 0x32e00000 0x0 0x1000>;
/* 解析为:地址=0x32e00000,长度=0x1000 */
};
};
3.5 设备树中的phandle与引用
设备树使用phandle(指针句柄)实现节点间的引用。常见的引用方式有两种:
- 显式phandle:使用
phandle = <编号>属性定义句柄 - 隐式phandle:使用
标签名: 节点名语法,编译器自动生成phandle
Camera Sensor中的典型引用:
/* 定义带标签的节点 */
ov5640: camera-sensor@3c {
compatible = "ovti,ov5640";
reg = <0x3c>;
/* ... */
port {
ov5640_ep: endpoint {
remote-endpoint = <&csi_ep>; /* 引用CSI端点 */
};
};
};
/* 在另一个节点中引用Sensor */
csi@32e00000 {
/* ... */
port {
csi_ep: endpoint {
remote-endpoint = <&ov5640_ep>; /* 引用Sensor端点 */
};
};
};
&ov5640_ep中的&符号表示引用标签ov5640_ep对应的节点。编译器会将这种引用转换为实际的phandle值(一个32位整数),内核通过这个整数找到对应的设备树节点。
3.6 设备树编译与反编译
设备树源文件(.dts)需要编译为二进制格式(.dtb)才能被内核使用。常用的工具和命令如下:
| 操作 | 命令 | 说明 |
|---|---|---|
| 编译DTS→DTB | dtc -I dts -O dtb -o output.dtb input.dts |
将文本格式编译为二进制 |
| 反编译DTB→DTS | dtc -I dtb -O dts -o output.dts input.dtb |
将二进制反编译为文本(调试用) |
| 查看DTB内容 | dtc -I dtb -O dts input.dtb | less |
管道输出到分页器查看 |
| 检查DTB语法 | dtc -I dts -O dtb input.dts >/dev/null |
只检查语法不输出文件 |
.dtb文件,查看I2C节点和Sensor节点的实际配置。使用dtc -I dtb -O dts命令可以快速获取可读的设备树内容。
3.7 本章小结
设备树是嵌入式Linux驱动开发的基石。对于Camera Sensor驱动,需要重点掌握:
- 节点结构:Sensor作为I2C控制器的子节点,通过
compatible属性与驱动匹配 - reg属性:指定I2C从设备地址,驱动通过该地址与Sensor通信
- 层级关系:通过
port和endpoint子节点描述数据通路 - 跨节点引用:使用
remote-endpoint和phandle建立Sensor与CSI接收器的连接