一、顶级架构一句话总结
设备树(DTS) → 编译(DTB) → 内核解析 → 驱动匹配 → 硬件初始化
设备树实现了硬件描述与驱动代码分离,同一份驱动代码可适配不同硬件平台,这是嵌入式Linux开发的工业标准方案。
二、为什么需要设备树?
传统方式的痛点
在传统嵌入式Linux开发中,硬件信息(如寄存器地址、中断号、外设连接关系)直接硬编码在驱动程序中,导致:
| 问题 | 影响 |
|---|---|
| 驱动与硬件强耦合 | 更换硬件平台需修改驱动源码 |
| 代码可移植性差 | 同一驱动无法适配不同板卡 |
| 维护成本高 | 硬件变更需要重新编译驱动 |
| 代码冗余 | 大量平台相关的宏定义 |
设备树的解决方案
传统方式:
驱动代码 + 硬件信息(硬编码)= 不可移植
设备树方式:
驱动代码(通用)+ 设备树(硬件描述)= 高度可移植
核心思想:硬件信息从驱动代码中剥离,通过设备树文件独立描述。
三、设备树核心概念(一句话秒懂)
- DTS(Device Tree Source):设备树源文件,人类可读的文本格式
- DTC(Device Tree Compiler):设备树编译器,将DTS编译为DTB
- DTB(Device Tree Blob):编译后的二进制设备树,内核可解析
- DTSI:设备树头文件,包含公共定义,可被多个DTS包含
- 节点(Node):设备树中的设备描述单元
- 属性(Property):节点的具体配置信息
四、设备树文件层次结构
架构层次:
soc.dtsi // SoC级公共定义
├── CPU信息
├── 内存控制器
├── 总线架构
└── 内置外设
board.dts // 板级定义
├── #include "soc.dtsi"
├── 板载设备
├── GPIO配置
└── 引脚复用
五、设备树语法详解
1. 基本节点结构
/ {
// 根节点
node_name@address {
// 节点名@设备地址
compatible = "vendor,device"; // 兼容性字符串
reg = <0x1000 0x100>; // 寄存器地址和大小
status = "okay"; // 设备状态
};
};
2. 常用属性说明
| 属性 | 说明 | 示例 |
|---|---|---|
| compatible | 兼容性字符串,用于驱动匹配 | “ti,omap4-i2c” |
| reg | 寄存器地址和大小 | <0x44e0b000 0x1000> |
| interrupts | 中断号和类型 | <0 24 4> |
| status | 设备状态 | “okay”/“disabled” |
| clocks | 时钟引用 | <&clk_periph> |
| pinctrl-* | 引脚复用配置 | pinctrl-names = “default” |
| gpio-* | GPIO配置 | gpios = <&gpio0 10 0> |
3. 实际案例:LED设备节点
/ {
leds {
compatible = "gpio-leds";
led0 {
label = "heartbeat";
gpios = <&gpio1 21 GPIO_ACTIVE_LOW>;
linux,default-trigger = "heartbeat";
default-state = "off";
};
led1 {
label = "status";
gpios = <&gpio1 22 GPIO_ACTIVE_LOW>;
default-state = "on";
};
};
};
六、设备树编译流程
1. 编译命令
# 编译单个DTS文件
dtc -I dts -O dtb -o board.dtb board.dts
# 使用内核构建系统
make dtbs ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
2. 反编译DTB
# 将DTB反编译为DTS(调试用)
dtc -I dtb -O dts -o board.dts board.dtb
3. 编译流程图
.dts源文件
↓ (dtc编译器)
.dtb二进制文件
↓ (Bootloader传递)
内核解析
↓
device_node结构体
↓
驱动匹配
七、驱动与设备树匹配机制
1. of_match_table匹配表
static const struct of_device_id my_driver_match[] = {
{ .compatible = "vendor,mydevice", },
{ .compatible = "vendor,mydevice-v2", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_driver_match);
2. platform_driver注册
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_device",
.of_match_table = my_driver_match,
},
};
module_platform_driver(my_driver);
3. 匹配流程
设备树节点 compatible = "vendor,mydevice"
↓
驱动 of_match_table 包含 "vendor,mydevice"
↓
匹配成功 → 调用 probe() 函数
↓
驱动初始化完成
八、驱动中获取设备树信息
1. 获取设备节点
// 方法1:通过compatible查找
struct device_node *np = of_find_compatible_node(NULL, NULL, "vendor,mydevice");
// 方法2:通过路径查找
struct device_node *np = of_find_node_by_path("/soc/mydevice@1000");
// 方法3:在probe中直接获取(推荐)
static int my_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
// ...
}
2. 读取属性值
// 读取reg属性
u32 reg[2];
of_property_read_u32_array(np, "reg", reg, 2);
// 读取字符串属性
const char *name;
of_property_read_string(np, "label", &name);
// 读取GPIO
int gpio = of_get_named_gpio(np, "gpios", 0);
// 读取中断号
int irq = irq_of_parse_and_map(np, 0);
// 判断属性是否存在
if (of_property_read_bool(np, "my-custom-property")) {
// 属性存在
}
3. 完整驱动示例
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio.h>
struct my_device_data {
int gpio;
const char *name;
};
static int my_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct my_device_data *data;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
// 从设备树获取GPIO
data->gpio = of_get_named_gpio(np, "led-gpio", 0);
if (!gpio_is_valid(data->gpio))
return -EINVAL;
// 获取名称
of_property_read_string(np, "label", &data->name);
// 申请GPIO
devm_gpio_request(&pdev->dev, data->gpio, data->name);
// 设置为输出
gpio_direction_output(data->gpio, 1);
platform_set_drvdata(pdev, data);
dev_info(&pdev->dev, "Device %s initialized\n", data->name);
return 0;
}
static int my_remove(struct platform_device *pdev)
{
struct my_device_data *data = platform_get_drvdata(pdev);
gpio_set_value(data->gpio, 0);
dev_info(&pdev->dev, "Device removed\n");
return 0;
}
static const struct of_device_id my_match[] = {
{ .compatible = "mycompany,myled", },
{ }
};
MODULE_DEVICE_TABLE(of, my_match);
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_led_driver",
.of_match_table = my_match,
},
};
module_platform_driver(my_driver);
MODULE_AUTHOR("Driver Developer");
MODULE_LICENSE("GPL");
九、设备树调试技巧
1. 查看内核识别的设备树
# 查看设备树节点
ls /proc/device-tree/
# 查看特定节点
cat /proc/device-tree/mydevice@1000/compatible
# 查看设备树完整信息
dtc -I fs /proc/device-tree
2. 内核日志调试
# 查看设备树解析日志
dmesg | grep -i "device tree"
dmesg | grep -i "of:"
# 查看驱动匹配日志
dmesg | grep -i "probe"
3. 常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 驱动不加载 | compatible不匹配 | 检查驱动和DTS的compatible字符串 |
| 获取GPIO失败 | GPIO未正确配置 | 检查pinctrl配置和GPIO编号 |
| 中断不工作 | 中断属性缺失 | 添加interrupts和interrupt-parent |
| 设备状态异常 | status属性错误 | 设置status = “okay” |
十、设备树最佳实践
1. 命名规范
// 推荐:使用厂商前缀
compatible = "ti,omap4-i2c";
compatible = "nxp,imx6-uart";
// 推荐:节点名使用通用名称
i2c@44e0b000 { ... }
serial@48020000 { ... }
// 不推荐:节点名包含厂商信息
ti-i2c@44e0b000 { ... }
2. 层次结构设计
/ {
// SoC级定义放在dtsi
soc {
i2c0: i2c@44e0b000 {
compatible = "ti,omap4-i2c";
// 公共配置
};
};
// 板级定义放在dts
&i2c0 {
status = "okay";
clock-frequency = <400000>;
// 板载设备
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
};
};
3. 使用引用简化代码
// 定义标签
gpio1: gpio@4804c000 { ... };
// 使用引用
gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;
十一、终极总结
设备树 = 硬件描述与驱动解耦的最优方案
- 核心价值:驱动代码可移植、硬件配置独立维护
- 关键机制:compatible字符串实现驱动与设备匹配
- 开发流程:DTS编写 → DTB编译 → 内核解析 → 驱动匹配
- 调试手段:/proc/device-tree、dmesg、dtc工具
掌握设备树,是嵌入式Linux驱动开发的必备技能!
欢迎关注微信公众号:LinuxRos

和大家一起聊聊Linux驱动、应用开发、嵌入式软件、AI与机器人相关干货,希望能给大家带来一点点帮助与启发

3110

被折叠的 条评论
为什么被折叠?



