Linux驱动如何管理设备树
导读:Linux 驱动与设备树的管理机制 一 核心流程总览 构建与传递:板级描述使用 .dts/.dtsi 编写,经 dtc 编译为 .dtb;Bootloader(如 U‑Boot)将 DTB 物理地址 传递给内核入口(ARM64 通过 x0...
Linux 驱动与设备树的管理机制
一 核心流程总览
- 构建与传递:板级描述使用 .dts/.dtsi 编写,经 dtc 编译为 .dtb;Bootloader(如 U‑Boot)将 DTB 物理地址 传递给内核入口(ARM64 通过 x0,ARM32 传统用 r2,RISC‑V 常用 a1)。内核早期使用 libfdt 做最小解析(如 chosen/bootargs、memory、reserved-memory),随后调用 unflatten_device_tree() 将 FDT 展开为内核的 struct device_node 树,并扫描 /aliases 建立稳定别名映射。
- 设备实例化:内核通过 of_platform_populate() 遍历带 compatible 的节点,在 /sys/devices/platform/ 下生成 platform_device;各总线桥接(如 I2C/SPI/MDIO/PCIe)在自身适配器就绪后,解析子节点生成对应的 i2c_client/spi_device 等设备对象。
- 驱动匹配与绑定:驱动通过 of_match_table 声明支持的 compatible 列表,注册时由内核按“最具体 → 最通用”的规则匹配节点,匹配成功后执行驱动的 probe() 完成硬件初始化与 sysfs 属性创建。
二 驱动侧如何读取设备树信息
- 匹配表与兼容性:在驱动中声明 of_device_id 表并使用 MODULE_DEVICE_TABLE(of, …),驱动注册时与设备节点的 compatible 进行匹配,命中后进入 probe。
- 资源获取:用 of_address_to_resource()/platform_get_resource() 解析 reg 得到 struct resource,再用 devm_ioremap_resource() 映射寄存器;多区域可用 reg-names 区分并通过索引访问。
- 中断与时钟:用 of_irq_get() 将 interrupts 映射为 Linux irq 号;用 of_clk_get_by_name()/devm_clk_get() 获取并启用时钟;GPIO/引脚复用通过 gpios 与 pinctrl 子系统配合。
- 属性读取:常用 of_property_read_u32/u64/string() 系列读取整型/字符串属性;枚举子节点可用 of_get_child_count()/of_for_each_child_of_node()。
三 典型驱动绑定示例
- 平台驱动骨架(精简版)
#include <
linux/module.h>
#include <
linux/platform_device.h>
#include <
linux/of.h>
#include <
linux/of_address.h>
#include <
linux/of_irq.h>
#include <
linux/clk.h>
static const struct of_device_id mydev_dt_ids[] = {
{
.compatible = "vendor,mydev" }
,
{
/* sentinel */ }
}
;
MODULE_DEVICE_TABLE(of, mydev_dt_ids);
static int mydev_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->
dev.of_node;
struct resource res;
void __iomem *base;
int irq, ret;
struct clk *clk;
/* 1) 地址映射 */
ret = of_address_to_resource(np, 0, &
res);
if (ret) return ret;
base = devm_ioremap_resource(&
pdev->
dev, &
res);
if (IS_ERR(base)) return PTR_ERR(base);
/* 2) 中断 */
irq = of_irq_get(np, 0);
if (irq <
0) return irq;
/* 3) 时钟 */
clk = devm_clk_get(&
pdev->
dev, NULL);
if (IS_ERR(clk)) return PTR_ERR(clk);
ret = clk_prepare_enable(clk);
if (ret) return ret;
/* TODO: 硬件初始化,注册字符/网络/其他接口 */
dev_info(&
pdev->
dev, "mydev probed @ %pa, irq %d\n", &
res.start, irq);
return 0;
}
static int mydev_remove(struct platform_device *pdev)
{
/* TODO: 关闭时钟、资源释放 */
return 0;
}
static struct platform_driver mydev_driver = {
.driver = {
.name = "mydev",
.of_match_table = mydev_dt_ids,
}
,
.probe = mydev_probe,
.remove = mydev_remove,
}
;
module_platform_driver(mydev_driver);
MODULE_LICENSE("GPL");
- 总线设备举例
- I2C 设备:控制器驱动注册 i2c_adapter 后,调用 of_i2c_register_devices() 解析总线子节点生成 i2c_client,按 compatible 触发对应 I2C 驱动 probe。
- SPI 设备:控制器就绪后,调用 spi_of_register_spi_devices() 生成 spi_device 并绑定 SPI 驱动。
四 构建 传递 与 动态管理
- 构建与部署:在内核树中编写 .dts/.dtsi,使用 make dtbs 生成 .dtb;将 Image + DTB [+ initramfs] 放入存储并在 Bootloader 中加载与传递。
- 启动期修补:Bootloader 可更新 /chosen/bootargs、/chosen/stdout-path、内存/保留区、MAC/SN 等,必要时叠加 Overlay(.dtbo) 实现增量配置。
- 动态覆盖:系统运行时可通过 configfs/sysfs 接口加载 .dtbo,调用 of_overlay_apply() 合并到主设备树并触发受影响设备重新枚举,常用于扩展板、插件化外设场景。
五 调试与最佳实践
- 查看与核对:用 /proc/device-tree/ 浏览展开后的节点与属性;用 of_node_full_name()/of_find_node_by_path() 在代码中定位节点;确认 status = “okay” 才会被枚举。
- 资源与时钟:优先使用 devm_* 系列资源管理;对 reg/interrupts/clocks/reg-names 做完备性与边界检查;必要时在 probe 中验证关键资源可用性。
- 绑定策略:在 of_match_table 中按“最具体 → 最通用”排序;为可复用外设提供通用兼容字符串,便于多平台共享驱动。
- 可维护性:公共片段放入 .dtsi,通过 #include 复用;板级差异集中在 .dts;使用 aliases 提供稳定设备名;对可选节点使用 status 控制启用状态。
声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!
若转载请注明出处: Linux驱动如何管理设备树
本文地址: https://pptw.com/jishu/772860.html
