Linux内核与驱动:13.从设备树到Platform平台总线

上两篇博客描述了一些设备树的基础知识和一些简单常见的设备树撰写,然而撰写设备树的很大一部分目的是用于代替平台总线Platform的 device 部分。

所以,这篇文章我们讲述这些设备树节点最终是怎么进入 Linux 驱动模型的,设备树是怎样代替 device.c 的。

1.从设备树源码的Platform的总体流程

整体流程可以描述为如下内容:

bash 复制代码
.dts / .dtsi
   ↓ 通过 dtc 编译
.dtb
   ↓ 由 bootloader 传给内核
内核早期解析 dtb
   ↓
展开成 device_node 树
   ↓
of_platform_populate()
   ↓
生成 platform_device
   ↓
挂到 platform bus
   ↓
platform_driver 根据 of_match_table 匹配 compatible
   ↓
调用 probe()
   ↓
驱动在 probe 中读取 reg / irq / gpio / clock 等资源

1.1 第一阶段

首先,我们通过DTC编译把 dts 设备树源文件编译为 dtb文件,如:

bash 复制代码
dtc -I dts -O dtb -o xxx.dtb xxx.dts

然后通过bootloader 把 DTB 传给内核:

.dtb 生成之后,接下来并不是驱动立刻去解析它,而是由 bootloader(例如 U-Boot)在启动内核时把这份 DTB 一起传进去。Linux 官方文档把这个过程称为 Linux 的 device tree usage model:bootloader 负责提供硬件描述,内核在启动初期接收并展开它。

1.2 第二阶段

内核收到 DTB 后,首先做的不是马上创建设备,而是先把二进制设备树展开成一棵内核内部可访问的树,也就是 device_node 树。

这棵树里的每个节点,都会对应一个 struct device_node。

也就是说,内核眼里的设备树,不再是文本,而是:一组互相连接的 device_node 节点对象。

所以你后面看到的很多 of_* 函数,本质上都是在对这棵 device_node 树做操作,比如:

  • 找节点
  • 读属性
  • 解析地址
  • 解析中断
  • 找父子关系

这也是为什么设备树相关接口都叫 of_*------它们是 OF(Open Firmware / Device Tree)框架下的一套 API。

1.3 第三阶段:platform_device的诞生

我们刚刚讲过设备树的很大一部分目的是替换平台总线中的device,所以设备树节点device_node是要转换成platform_device的。

而 platform 设备的创建,关键入口之一就是:

cpp 复制代码
of_platform_populate()

函数的作用可以理解为:

把设备树中的合适节点,转换成 platform_device,并挂到 platform 总线。

**注意:**然而内核并不会把设备树里的所有节点都转换成 platform_device!

of_platform_populate有着严格的准入规则:

  1. **根节点下的子节点:**只要含有 compatible 属性,通常会被转换为 platform_device。
  2. **特定的"总线"子节点:**带有 simple-bus、simple-mfd、isa、arm,amba-bus 等属性的节点,其子节点也会被递归地转化为 platform_device。(例如 RK3588 的 DTS 中,大量外设节点都在一个 compatible = "simple-bus" 的根节点之下)。

注意: 挂载在 I2C、SPI 等具体物理总线控制器下的子节点(如摄像头 Sensor),在这里不会被转换为 platform_device。它们会在 I2C/SPI 主机控制器驱动初始化时,由总线特定的代码(如 of_i2c_register_devices)解析,并转化为 i2c_client 或 spi_device。

1.4 第四阶段

当 platform_device 被注册到 platform bus 后,Linux 驱动模型就开始进行设备-驱动匹配。

在设备树场景下,匹配最关键的是:

1. 设备树节点里的 compatible

例如:

cpp 复制代码
compatible = "myvendor,myled";

2. 驱动里的 of_match_table

例如:

cpp 复制代码
static const struct of_device_id myled_of_match[] = {
    { .compatible = "myvendor,myled" },
    { }
};
MODULE_DEVICE_TABLE(of, myled_of_match);

然后平台驱动会这样注册:

cpp 复制代码
static struct platform_driver myled_driver = {
    .probe = myled_probe,
    .driver = {
        .name = "myled",
        .of_match_table = myled_of_match,
    },
};

这时 platform bus 会拿:

  • 节点的 compatible
  • 驱动的 of_match_table

进行匹配。匹配成功后,就会进入驱动的 probe()。

2.核心 of_ API 解析

当流程进入你编写的 probe 函数时,你需要从硬件设备中提取配置。此时,你需要大量使用 of_ 开头的 API(定义在 include/linux/of.h 等头文件中)。

所有获取属性的操作,都围绕着 platform_device->dev.of_node 展开。

1. 节点查找函数 (通常用于非标准获取,如查找引用节点)

  • of_find_node_by_path(const char *path): 通过绝对路径寻找节点(如 "/soc/gpio@fe740000")。
  • of_find_compatible_node(struct device_node *from, const char *type, const char *compatible): 通过 compatible 属性全局查找目标节点。
  • of_get_parent(const struct device_node *node): 获取当前节点的父节点。
相关推荐
小周学学学10 分钟前
vmware-horizon第一章:horizon服务器安装
运维·服务器·vmware
小雪崩29 分钟前
嵌入式学习 day25:哈希表及排序与查找
linux·c语言·数据结构·学习·排序算法
单片机杂货铺39 分钟前
【单片机毕业设计选题】基于stm32单片机儿童公路车速度检测报警
stm32·单片机·嵌入式硬件·51单片机·硬件工程·课程设计
gsls2008081 小时前
Penpot Docker 部署与 MCP 服务配置文档
运维·docker·容器·原型·penpot
九硕智慧建筑一体化厂家1 小时前
直流智能照明|全场景节能升级!打造安全低碳的智慧建筑光环境
运维·笔记·安全·智慧城市
蓝速科技1 小时前
蓝速科技丨数智人终端选型指南:后台运维如何决定长期运营成本
运维·科技
奇树谦1 小时前
HDD 为什么适合顺序大文件读写:从 RAID 5 到 RAID 50 的原理与性能分析
linux·运维·网络
振南的单片机世界2 小时前
看门狗硬核拆解:1.6秒不喂狗,硬件强制“掀桌子”
arm开发·stm32·单片机·嵌入式硬件
hi_ro_a2 小时前
Linux基础开发工具详解
linux·运维·服务器
嵌入式阿蔡2 小时前
Linux_01:交叉编译与开发环境实战——从单片机跨到 Linux 的第一道坎
linux·运维·网络·单片机·嵌入式硬件·嵌入式实时数据库