ESC
输入关键词搜索文章标题和内容

嵌入式Linux设备树开发实战:从DTS编写到驱动匹配全链路

本文由 linuxROS 整理发布,首发于 linuxros.cn,转载请注明出处。

嵌入式Linux设备树开发实战:从DTS编写到驱动匹配全链路

导读:设备树是嵌入式Linux驱动开发的基础设施——硬件信息写在DTS里,驱动通过compatible字符串找到对应设备。本文覆盖DTS编写、DTB编译、内核解析、驱动匹配全流程,附代码示例和踩坑方案。


一、原理简析

设备树(Device Tree)是一种描述硬件的数据结构,最早源于Open Firmware,2005年被引入PowerPC Linux,后来推广到ARM、MIPS、RISC-V等架构,现在已经是嵌入式Linux的标配。

没有设备树的时候,硬件信息(寄存器地址、中断号、GPIO编号)直接写死在驱动代码里,换一块板子就得改驱动、重新编译内核。设备树的做法很简单:把硬件描述从驱动代码里拿出来,放到独立的DTS文本文件中,内核启动时解析这个文件,根据内容加载对应的驱动。

内核拿到设备树后干三件事:平台识别(根节点的compatible匹配machine_desc)、运行时配置(/chosen节点传内核参数)、设备枚举(有compatible属性的节点注册为platform_device)。

整条链路:DTS编写 → DTC编译为DTB → Bootloader传递DTB → 内核解析为device_node → 转换为platform_device → compatible匹配驱动 → 调用probe函数。


二、实操步骤

2.1 编写DTS设备树源文件

设备树用树形结构描述硬件,每个节点靠compatible属性告诉内核"我是什么设备"。下面是一个LED设备的DTS示例:

来自 linuxros.cn · linuxROS
/dts-v1/;

/ {
    model = "My Embedded Board";
    compatible = "myboard";

    leds {
        compatible = "gpio-leds";

        led0: led-heartbeat {
            label = "heartbeat";
            gpios = <&gpio1 21 0>;          /* GPIO1_21, 低电平有效 */
            linux,default-trigger = "heartbeat";
            default-state = "off";
        };

        led1: led-status {
            label = "status";
            gpios = <&gpio1 22 0>;
            default-state = "on";
        };
    };
};

关键属性说明:

属性 含义 格式说明
compatible 驱动匹配字符串 推荐格式 "manufacturer,model"
reg 寄存器基地址和长度 <地址 长度>,父节点#address-cells和#size-cells决定cell数量
status 设备状态 "okay"启用,"disabled"禁用
gpios GPIO引用 <&gpio控制器 引脚号 标志>

2.2 编译DTS为DTB

# 使用dtc编译器
dtc -I dts -O dtb -o board.dtb board.dts

# 或使用内核构建系统(推荐)
make dtbs ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

# 反编译DTB用于调试
dtc -I dtb -O dts -o board.dts board.dtb

编译出来的DTB由Bootloader(U-Boot等)加载到内存,内核调用unflatten_device_tree()把扁平化的DTB解析成运行时的device_node树。

2.3 编写驱动匹配代码

驱动用of_match_table声明自己支持哪些设备:

static const struct of_device_id my_led_match[] = {
    { .compatible = "gpio-leds", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_led_match);

static struct platform_driver my_led_driver = {
    .probe  = my_led_probe,
    .remove = my_led_remove,
    .driver = {
        .name           = "my_led_driver",
        .of_match_table = my_led_match,
    },
};
module_platform_driver(my_led_driver);

内核遍历设备树节点时,拿节点的compatible值和所有已注册驱动的of_match_table逐个比对,匹配上了就调驱动的probe函数,把对应的platform_device传进去。


三、设备树 vs 传统硬编码

维度 传统硬编码方式 设备树方式
硬件信息存放 散落在驱动C文件中 集中在DTS文本文件
换板适配 修改驱动源码,重新编译内核 修改DTS,重新编译DTB即可
同一驱动多板卡 需要#ifdef条件编译,代码膨胀 一份驱动代码,多份DTS覆盖
硬件变更影响 驱动代码 + 内核均需重编 仅需重编DTB
可读性 寄存器地址散落各处,难以维护 树形结构,硬件拓扑一目了然
内核镜像通用性 每块板子一个内核镜像 同一SoC系列可共用内核镜像

四、核心流程图

flowchart TB subgraph 编写层["① DTS编写"] A(["开发者编写DTS"]) --> B["定义节点与属性<br/>compatible/reg/gpios"] end subgraph 编译层["② 编译阶段"] C["DTC编译器"] --> D(["生成DTB二进制"]) end subgraph 启动层["③ 启动阶段"] E["Bootloader<br/>加载DTB到内存"] --> F["内核解析DTB<br/>unflatten_device_tree()"] end subgraph 匹配层["④ 驱动匹配"] G["遍历device_node树"] --> H{"节点含compatible<br/>且在根节点或<br/>simple-bus子节点下?"} H -->|"是"| I["转换为platform_device<br/>注册到platform总线"] H -->|"否(I2C/SPI子节点)"| J["交由总线驱动处理<br/>创建i2c_client等"] I --> K{"of_match_table<br/>匹配成功?"} K -->|"是"| L["调用probe()<br/>设备初始化完成"] K -->|"否"| M["设备无驱动绑定<br/>打印警告日志"] J --> N["总线匹配"] end B --> C D --> E F --> G style A fill:#E3F2FD,stroke:#1976D2 style D fill:#E3F2FD,stroke:#1976D2 style H fill:#FFF8E1,stroke:#F57C00 style K fill:#FFF8E1,stroke:#F57C00 style L fill:#E8F5E9,stroke:#388E3C style M fill:#FFEBEE,stroke:#D32F2F style J fill:#F3E5F5,stroke:#7B1FA2 style N fill:#F3E5F5,stroke:#7B1FA2

五、常见问题解决

问题 原因 解决方案
驱动probe不触发 compatible字符串与驱动of_match_table不一致 检查DTS和驱动中的字符串是否完全一致(含大小写);用dmesg \| grep "probe"确认
设备不工作 节点status = "disabled" 改为status = "okay",或删除该属性(默认视为okay)
GPIO申请失败 GPIO编号计算错误,或pinctrl未配置 确认&gpio1 21在目标SoC上对应正确的bank和pin;检查pinctrl节点是否声明
DTB未更新 烧录了旧的DTB文件 重新make dtbs后替换目标板/boot/下的DTB,重启验证

运行时调试命令:

# 查看内核识别的设备树
ls /proc/device-tree/

# 查看某节点的compatible值
cat /proc/device-tree/leds/compatible

# 查看设备树解析日志
dmesg | grep -i "of:"

六、总结

设备树干的事就一件:把硬件描述从驱动代码里拆出来。DTS写硬件拓扑,驱动靠compatible字符串匹配设备,中间靠内核的OF子系统衔接。搞清楚"DTS编写 → DTB编译 → 内核解析 → 驱动匹配"这条线,大部分嵌入式Linux驱动适配问题都能定位。

版权声明

作者linuxROS
协议本作品采用 CC BY-NC-SA 4.0 许可协议:署名-非商业性使用-相同方式共享
关注欢迎关注微信公众号 linuxROS,获取更多机器人 / 嵌入式 / Linux 干货
返回首页