【Linux驱动开发】第12天:Linux设备树核心:树形结构+节点+属性 完整全解
目录
- 设备树树形结构概述
- 节点(Node)全解:命名规范+标准节点+常用设备节点
- 属性(Property)全解:类型+核心属性+总线专用属性
- 标签与节点引用:设备树复用的核心
- 常见错误与注意事项
- 总结:驱动开发必背节点与属性
1. 设备树树形结构概述
设备树采用分层树形结构描述硬件,和计算机的文件系统目录结构几乎完全一致。它以根节点为起点,向下逐层展开,真实反映了计算机系统中硬件的物理连接关系。
1.1 树形结构的三大核心元素
设备树的所有内容都由这三个基本元素组成:
根节点(/) ├── 子节点1(代表一个硬件设备) │ ├── 属性1 = 值1(描述设备特性) │ ├── 属性2 = 值2 │ └── 孙子节点1(代表子设备) │ └── 属性3 = 值3 └── 子节点2 └── 属性4 = 值4- 根节点:所有节点的父节点,用
/表示,每个设备树有且只有一个 - 节点:每个硬件设备对应一个节点,节点可以包含子节点,形成分层结构
- 属性:每个节点包含多个键值对形式的属性,用于描述设备的具体硬件信息
1.2 最简完整设备树示例
/dts-v1/; // 设备树版本号,必须是第一行 / { // 根节点 compatible = "myvendor,myboard"; model = "My Custom Development Board"; // 片上系统节点,包含所有内部外设 soc { compatible = "simple-bus"; #address-cells = <1>; #size-cells = <1>; ranges; // UART串口节点 uart@12340000 { compatible = "myvendor,my-uart"; reg = <0x12340000 0x1000>; interrupts = <5>; status = "okay"; }; // GPIO控制器节点 gpio@12341000 { compatible = "myvendor,my-gpio"; reg = <0x12341000 0x1000>; gpio-controller; #gpio-cells = <2>; status = "okay"; }; }; // 外接LED节点 leds { compatible = "gpio-leds"; blue-led { label = "blue:user"; gpios = <&gpio 13 GPIO_ACTIVE_HIGH>; }; }; };2. 节点(Node)全解:命名规范+标准节点+常用设备节点
节点是设备树的基本单元,每个硬件设备对应一个节点。
2.1 节点命名规范(必须严格遵守)
不符合规范的节点会导致内核解析失败,驱动无法匹配。
标准命名格式
节点名@单元地址各部分含义
节点名:描述设备的通用类型,如
cpu、memory、uart、i2c- 只能使用小写字母、数字和连字符
- - 不能使用下划线
_、空格或其他特殊字符 - 尽量使用内核通用的设备类型名,不要使用厂商自定义名称
- 只能使用小写字母、数字和连字符
@单元地址:设备的唯一地址标识
- 内存映射设备:寄存器基地址(十六进制)
- I2C/SPI设备:从机地址/片选号
- CPU:CPU编号
- 作用:区分同类型的不同设备
正确与错误命名对比
| 正确命名 | 错误命名 | 错误原因 |
|---|---|---|
uart@12340000 | UART@12340000 | 使用了大写字母 |
i2c@12341000 | i2c_controller@12341000 | 节点名过长,应使用通用类型名 |
led@1 | led_1 | 使用了下划线,缺少@和单元地址 |
cpu@0 | cpu0 | 缺少@和单元地址 |
2.2 标准节点(每个设备树都必须包含)
这些节点不对应具体的外设,而是提供系统级的全局信息。
1. 根节点/
- 作用:整个设备树的起点,包含系统全局信息
- 必须属性:
/ { compatible = "厂商,板级型号"; // 板级兼容属性,匹配内核板级支持包 model = "开发板全称"; // 人类可读的开发板名称 #address-cells = <1>; // 子节点reg属性中地址部分占几个32位整数 #size-cells = <1>; // 子节点reg属性中长度部分占几个32位整数 };
2./aliases别名节点
- 作用:给节点起简短别名,方便内核和驱动引用
- 示例:
aliases { serial0 = &uart4; // 给uart4节点起别名serial0 i2c1 = &i2c1; // 给i2c1节点起别名i2c1 led0 = &blue_led; // 给LED节点起别名led0 }; - 用途:内核通过别名快速找到特定设备,如
console=ttySTM0对应serial0别名
3./chosen选择节点
- 作用:传递内核启动参数和运行时配置
- 最重要属性:
bootargs(内核命令行参数) - 示例:
chosen { bootargs = "console=ttySTM0,115200 root=/dev/mmcblk0p2 rootwait rw"; stdout-path = &uart4; // 指定标准输出设备 };
4./memory内存节点
- 作用:描述系统物理内存信息
- 必须属性:
memory@c0000000 { device_type = "memory"; // 固定值,标识这是内存节点 reg = <0xc0000000 0x20000000>; // 基地址0xc0000000,长度0x20000000(512MB) };
5./cpusCPU节点
- 作用:描述系统中的CPU核心信息
- 结构:包含一个或多个
cpu@x子节点 - 示例:
cpus { #address-cells = <1>; #size-cells = <0>; cpu@0 { compatible = "arm,cortex-a7"; device_type = "cpu"; reg = <0>; // CPU编号 clock-frequency = <800000000>; // CPU主频800MHz }; cpu@1 { compatible = "arm,cortex-a7"; device_type = "cpu"; reg = <1>; clock-frequency = <800000000>; }; };
6./soc片上系统节点
- 作用:包含所有片上外设(UART、I2C、SPI、GPIO等)
- 必须属性:
soc { compatible = "simple-bus"; // 告诉内核这是一个简单总线 #address-cells = <1>; #size-cells = <1>; ranges; // 地址映射,空表示子节点地址与父节点地址相同 // 所有片上外设节点都放在这里 uart@40010000 { ... }; i2c@40012000 { ... }; gpio@50002000 { ... }; };
2.3 驱动开发最常用的通用设备节点
1. 总线类节点
(1)UART串口节点
uart4: serial@40010000 { compatible = "st,stm32mp157-uart"; reg = <0x40010000 0x400>; // 寄存器基地址和长度 interrupts = <52>; // 中断号 clocks = <&rcc UART4_CLK>; // 时钟 status = "okay"; // 启用设备 };(2)I2C总线节点
i2c1: i2c@40012000 { compatible = "st,stm32mp157-i2c"; reg = <0x40012000 0x400>; interrupts = <31>; #address-cells = <1>; // 子节点reg占1个cell(I2C从地址) #size-cells = <0>; // 子节点reg没有长度部分 clock-frequency = <100000>; // I2C总线频率100kHz status = "okay"; // I2C从设备节点 eeprom@50 { compatible = "atmel,24c02"; reg = <0x50>; // I2C从机地址0x50 }; };(3)SPI总线节点
spi1: spi@40013000 { compatible = "st,stm32mp157-spi"; reg = <0x40013000 0x400>; interrupts = <32>; #address-cells = <1>; // 子节点reg占1个cell(片选号) #size-cells = <0>; status = "okay"; // SPI从设备节点 flash@0 { compatible = "winbond,w25q64"; reg = <0>; // 片选号0 spi-max-frequency = <10000000>; // 最大SPI频率10MHz }; };(4)GPIO控制器节点
gpioa: gpio@50002000 { compatible = "st,stm32mp157-gpio"; reg = <0x50002000 0x400>; gpio-controller; // 标识这是一个GPIO控制器 #gpio-cells = <2>; // GPIO引用需要2个参数:引脚号+极性 status = "okay"; };2. 外设类节点
(1)LED节点
leds { compatible = "gpio-leds"; blue_led: led-blue { label = "blue:user"; // LED名称 gpios = <&gpioa 13 GPIO_ACTIVE_HIGH>; // 使用GPIOA13,高电平点亮 linux,default-trigger = "heartbeat"; // 默认心跳模式 }; };(2)按键节点
keys { compatible = "gpio-keys"; key-user { label = "user-key"; gpios = <&gpioc 13 GPIO_ACTIVE_LOW>; // GPIOC13,低电平有效 linux,code = <KEY_ENTER>; // 按键码 }; };3. 属性(Property)全解:类型+核心属性+总线专用属性
属性是节点的核心,用于描述设备的具体硬件特性,采用键值对形式。
3.1 属性的5种基本类型
| 类型 | 语法 | 示例 | 用途 |
|---|---|---|---|
| 字符串类型 | 双引号""包裹 | compatible = "st,stm32-uart"; | 描述名称、兼容属性、状态 |
| 32位整数类型 | 尖括号<>包裹 | reg = <0x12340000 0x1000>; | 描述地址、长度、中断号 |
| 字节数组类型 | 方括号[]包裹 | mac-address = [00 11 22 33 44 55]; | 描述MAC地址、二进制数据 |
| 布尔类型 | 只有属性名,没有值 | gpio-controller; | 标识设备具有某种特性 |
| 字符串数组类型 | 多个字符串用逗号分隔 | compatible = "st,stm32-uart", "arm,pl011-uart"; | 多个兼容属性 |
3.2 核心属性(驱动开发每天都会用到)
这些属性是所有设备节点通用的,也是最重要的属性。
1.compatible(最重要,驱动匹配唯一依据)
- 作用:设备和驱动之间的"配对暗号"
- 格式:
"厂商,设备型号" - 匹配规则:内核从左到右依次匹配驱动的
of_device_id表,匹配到第一个就停止 - 示例:
compatible = "st,stm32mp157-uart", "arm,pl011-uart"; - 注意:必须和驱动代码中的字符串完全一致,一个字符都不能错,否则驱动永远不会执行probe函数
2.reg(地址资源描述)
- 作用:描述设备的寄存器地址范围或总线地址
- 格式:由父节点的
#address-cells和#size-cells决定- 父节点
#address-cells = <1>,#size-cells = <1>:格式为<基地址 长度> - 父节点
#address-cells = <2>,#size-cells = <2>:格式为<addr_high addr_low len_high len_low>
- 父节点
- 示例:
// 内存映射设备 reg = <0x40010000 0x400>; // 基地址0x40010000,长度0x400 // I2C设备(父节点#size-cells=0) reg = <0x50>; // I2C从机地址0x50 // SPI设备(父节点#size-cells=0) reg = <0>; // SPI片选号0
3.status(设备状态控制)
- 作用:控制设备是否启用
- 有效值:
值 含义 okay设备正常启用 disabled设备禁用,内核会忽略该节点 fail设备存在但工作异常 - 注意:
- 父节点
status = "disabled"时,所有子节点都会被递归忽略 .dtsi文件中节点默认状态通常是disabled,在.dts文件中显式改为okay启用
- 父节点
4.interrupts(中断资源描述)
- 作用:描述设备使用的中断号和触发方式
- 格式:
<中断号 触发方式> - 常用触发方式:
IRQ_TYPE_EDGE_RISING:上升沿触发IRQ_TYPE_EDGE_FALLING:下降沿触发IRQ_TYPE_LEVEL_HIGH:高电平触发IRQ_TYPE_LEVEL_LOW:低电平触发
- 示例:
interrupts = <52 IRQ_TYPE_LEVEL_HIGH>; // 中断号52,高电平触发
5.#address-cells和#size-cells
- 作用:定义子节点
reg属性的格式 #address-cells:用来描述子节点reg属性的地址表中用来描述首地址的元素所用字(cell)的数量,而每个字(cell)是一个无符号32位整形。#size-cells:用来描述子节点reg属性的地址表中用来描述地址范围大小的元素所用字(cell)的数量,每个字(cell)是一个无符号32位整形。- 示例:
soc { #address-cells = <1>; // 子节点地址占1个cell #size-cells = <1>; // 子节点长度占1个cell // 子节点reg格式:<addr len> uart@40010000 { reg = <0x40010000 0x400>; }; };
6.ranges(地址映射)
- 作用:定义子总线地址到父总线地址的映射关系
- 格式:
<子地址 父地址 长度> - 空值
ranges;:表示子节点地址与父节点地址完全相同 - 示例:
soc { ranges = <0 0x40000000 0x10000000>; // 子地址0映射到父地址0x40000000,长度0x10000000 };
3.3 常用总线专用属性
1. I2C总线专用
clock-frequency:I2C总线频率,单位Hzreg:I2C从机地址
2. SPI总线专用
spi-max-frequency:SPI设备最大工作频率,单位Hzreg:SPI片选号spi-cpol:时钟极性,1表示空闲时时钟为高电平spi-cpha:时钟相位,1表示在第二个时钟沿采样
3. GPIO专用
gpio-controller:标识这是一个GPIO控制器#gpio-cells:GPIO引用需要的参数个数,通常是2(引脚号+极性)gpios:引用GPIO引脚,格式为<&gpio_controller 引脚号 极性>GPIO_ACTIVE_HIGH:高电平有效GPIO_ACTIVE_LOW:低电平有效
4. 中断控制器专用
interrupt-controller:标识这是一个中断控制器#interrupt-cells:中断引用需要的参数个数,通常是2(中断号+触发方式)interrupt-parent:指定该设备的中断控制器
4. 标签与节点引用:设备树复用的核心
设备树通过标签和引用实现代码复用,这是.dtsi头文件机制的基础。
4.1 标签(Label)
- 在节点名前加
标签名:,给节点起一个唯一的标识 - 作用:方便在其他地方引用该节点
- 示例:
uart4: serial@40010000 { ... };
4.2 节点引用
- 使用
&标签名引用已经定义的节点 - 主要用途:在
.dts文件中修改.dtsi文件中定义的节点属性 - 示例:
// 在stm32mp157.dtsi中定义了uart4节点,默认状态是disabled uart4: serial@40010000 { compatible = "st,stm32mp157-uart"; reg = <0x40010000 0x400>; interrupts = <52>; status = "disabled"; }; // 在myboard.dts中引用uart4节点,修改为启用状态 &uart4 { status = "okay"; };
5. 常见错误与注意事项
- 节点名不规范:使用大写字母、下划线或特殊字符,导致内核解析失败
- 缺少@和单元地址:同类型的多个设备无法区分,驱动匹配混乱
- 属性值类型错误:应该用尖括号的用了双引号,应该用双引号的用了尖括号
- compatible属性不匹配:设备树和驱动的compatible字符串不一致,导致probe不执行
- 忘记以空结尾:
of_device_id表和数组类型的属性必须以空结尾 - 父节点禁用导致子节点失效:父节点
status = "disabled"时,所有子节点都会被忽略
6. 总结:驱动开发必背节点与属性
必须掌握的节点
- 标准节点:
/、/aliases、/chosen、/memory、/cpus、/soc - 常用设备节点:
uart、i2c、spi、gpio、leds、keys
必须掌握的属性
- 核心属性:
compatible、reg、status、interrupts、#address-cells、#size-cells - 总线属性:
ranges、clock-frequency、spi-max-frequency - GPIO属性:
gpios、gpio-controller、#gpio-cells
