Linux platform driver疑问
怎么理解 platform_bus?
核心结论
platform_bus 不是一个真实的总线,它是内核中的一个"配对服务"。 它与 PCIe/USB 等真实总线的本质区别在于:
- 真实总线负责:传输数据 + 发现设备
- platform_bus 只负责:匹配名字,完全不参与数据传输
一句话对比
| 对比维度 | PCIe/USB 真实总线 | platform_bus |
|---|---|---|
| 本质 | 物理线路 + 硬件协议控制器 | 内存中的一个 struct bus_type 结构体 |
| 核心功能 | ① 传输数据 ② 硬件自动发现设备 | 唯一功能:字符串匹配(驱动名 vs 设备名) |
| 设备如何被发现 | 总线协议主动扫描,硬件读取设备 ID | 不被发现,由代码或设备树人工告知内核 |
| 设备有硬件 ID 吗 | 有(Vendor ID, Device ID 等) | 没有,只有一个软件赋予的名字字符串 |
| 能传输数据吗 | 能(内存访问、DMA、中断等) | 完全不能,它只是个匹配机制 |
| 驱动程序如何工作 | 匹配后,驱动直接操作硬件 | 匹配后,驱动从 platform_device 里取出预先告知的地址/中断,再操作硬件 |
最直白的理解
-
PCIe 总线:就像 USB 集线器,插上设备,总线自己就能"看到"它,知道它是什么、需要什么资源。
-
platform_bus:就像一个公告栏。你在上面贴一张纸:“我的名字叫 UART,我的地址是 0x12345000”。驱动也贴一张纸:“我的名字叫 UART,我能操作地址 0xXXXXX”。内核看到两个名字相同,就说"配对成功",然后把驱动叫过来,告诉它"地址在 0x12345000,你去干活吧"。
关键区别:公告栏本身不传输任何数据,数据是通过 CPU 的地址总线直接访问硬件的。而 PCIe 总线本身参与数据传输。
为什么要有 platform_bus?
因为 SoC 上的 UART、I2C、SPI 等控制器没有自动发现能力。
PCIe 设备可以回答"我是谁",但 UART 寄存器只会被动地被读写,不会主动告诉系统它的存在。
所以内核必须有一个纯软件的兜底机制,让开发者手动把硬件信息(地址、中断)告诉系统。这个机制就是 platform_bus。
总结一句话
真实总线是"硬件通道",platform_bus 是"软件配对器"。
bus_add_device 和 bus_add_driver 对比分析
bus_add_device 和 bus_add_driver 是 Linux 设备模型中两个对称的核心函数,负责将设备和驱动注册到总线上,并触发匹配与绑定过程。
简单来说,bus_add_device 是在添加设备时被调用,而 bus_add_driver 是在注册驱动时被调用。它们都会将各自的实体加入总线的管理链表,并主动发起一次“配对”尝试,最终都会调用到 really_probe 去执行真正的探测。
对称执行流程
| 方面 | bus_add_device (添加设备时) |
bus_add_driver (注册驱动时) |
|---|---|---|
| 调用入口 | device_add() -> bus_add_device() |
driver_register() -> bus_add_driver() |
| 主要工作 | 1. 为设备创建 sysfs 目录 (kobject) 2. 将该设备添加到总线的设备链表中 |
1. 为驱动创建 sysfs 目录 (kobject) 2. 将该驱动添加到总线的驱动链表中 3. 在 sysfs 中创建 bind/unbind 等属性文件 |
| 触发探测 | 调用 bus_probe_device(),尝试为这个新设备寻找已注册的驱动。 |
如果总线允许自动探测 (drivers_autoprobe),则调用 driver_attach(),尝试为这个新驱动寻找已注册的设备。 |
| 匹配函数 | bus_for_each_drv() (遍历驱动链表,寻找匹配项) |
bus_for_each_dev() (遍历设备链表,寻找匹配项) |
| 最终调用链 | … -> __device_attach_driver() -> driver_probe_device() -> really_probe() |
… -> __driver_attach() -> driver_probe_device() -> really_probe() |
并发竞态与修复
由于设备和驱动的注册是异步的,可能存在竞态条件。例如,当 CPU0 正在执行 bus_add_device() 但设备尚未完全初始化时,CPU1 上的 bus_add_driver() 可能已经匹配并尝试 probe 这个设备,导致错误。
为了解决这个问题,内核引入了更精细的状态同步机制:
probe_pending:设备添加到总线链表时设置。probe_ready:设备完全初始化后设置。
bus_add_driver 在匹配前会检查这些标志,确保只有在设备完全就绪后才发起 probe,从而保证了整个过程的稳健性。
总结
这两个函数是设备模型“热插拔”和“自动配置”特性的基石,确保了无论是设备先来还是驱动先到,最终都能正确、安全地建立绑定关系。
platform bus如何init?
# platform_bus 初始化与匹配机制详解
## 1. 初始化调用链总览
系统启动时,`platform_bus` 的初始化遵循以下调用路径:
```text
start_kernel()
└── rest_init()
└── kernel_init()
└── do_basic_setup()
└── driver_init() // 驱动模型总入口
└── platform_bus_init() ★ platform总线初始化入口
├── early_platform_cleanup() // 清理早期平台设备残留
├── device_register(&platform_bus) // 注册总线设备
├── bus_register(&platform_bus_type) // 注册总线类型
└── of_platform_register_reconfig_notifier() // 设备树通知器
关键说明:
· driver_init() 是 Linux 设备驱动模型的总初始化入口。
· platform_bus_init() 定义在 drivers/base/platform.c 中。
- 核心代码实现
2.1 platform_bus_init()
这是 platform 总线初始化的核心函数,负责创建总线设备和总线类型。
int __init platform_bus_init(void)
{
int error;
/* 清理早期平台设备残留数据(来自启动阶段的临时设备) */
early_platform_cleanup();
/* 1. 注册 platform 总线设备(在 /sys/devices/platform 下可见) */
error = device_register(&platform_bus);
if (error)
return error;
/* 2. 注册 platform 总线类型(定义匹配规则和行为) */
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
/* 3. 注册设备树重配置通知器(用于 OF 设备树系统的动态更新) */
of_platform_register_reconfig_notifier();
return error;
}
代码位置:drivers/base/platform.c
2.2 platform_bus 设备定义
platform 总线本身作为一个设备注册到系统中,其他所有 platform 设备都以它为父设备。
struct device platform_bus = {
.init_name = "platform",
};
EXPORT_SYMBOL_GPL(platform_bus);
注册后,在 sysfs 中表现为 /sys/devices/platform/ 目录,所有 platform 设备都存放在此目录下。
2.3 platform_bus_type 总线类型定义
总线类型定义了 platform 总线的匹配规则、电源管理、热插拔事件处理等行为。
struct bus_type platform_bus_type = {
.name = "platform", // 总线名称 → /sys/bus/platform/
.dev_groups = platform_dev_groups, // 设备属性组
.match = platform_match, // ★ 匹配函数:核心!
.uevent = platform_uevent, // 热插拔事件处理
.pm = &platform_dev_pm_ops, // 电源管理操作
.dma_configure = platform_dma_configure, // DMA 配置
};
代码位置:drivers/base/platform.c
注意:platform_bus_type 中没有定义 probe 函数,probe 由驱动自身提供。
- platform_match 匹配函数详解
platform_match() 是 platform 总线的核心,决定了设备(platform_device)和驱动(platform_driver)如何配对。当向总线注册新设备或新驱动时,总线会调用此函数遍历已有的设备/驱动列表进行匹配。
3.1 platform_match 完整调用链
设备/驱动注册触发匹配
│
├── 注册新设备时:device_add() → bus_probe_device() → device_initial_probe() → __device_attach()
│
└── 注册新驱动时:driver_register() → bus_add_driver() → driver_attach() → __driver_attach()
│
└── driver_match_device() → platform_match() ★
详细展开:
【场景一:注册新设备】
device_register(platform_device)
└── device_add()
└── bus_probe_device() // 总线尝试为设备找驱动
└── device_initial_probe()
└── __device_attach()
└── bus_for_each_drv() // 遍历总线上的所有驱动
└── __device_attach_driver()
└── driver_match_device()
└── platform_match() ★
【场景二:注册新驱动】
driver_register(platform_driver)
└── bus_add_driver()
└── driver_attach()
└── bus_for_each_dev() // 遍历总线上的所有设备
└── __driver_attach()
└── driver_match_device()
└── platform_match() ★
注:driver_match_device() 是设备驱动模型的核心包装函数,内部调用总线特定的 match 回调(即 platform_match)。
3.2 platform_match 源码实现
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* 方式1:驱动覆盖(用于强制绑定,优先级最高) */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* 方式2:设备树(DT)匹配(现代嵌入式主流,如 ARM 架构) */
if (of_driver_match_device(dev, drv))
return 1;
/* 方式3:ACPI 匹配(常见于 x86 平台) */
if (acpi_driver_match_device(dev, drv))
return 1;
/* 方式4:ID 表匹配(驱动支持多个设备名时使用) */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* 方式5:名字直接匹配(传统方式,最直观) */
return (strcmp(pdev->name, drv->name) == 0);
}
代码位置:drivers/base/platform.c
3.3 匹配方式详解
优先级 匹配方式 触发条件 匹配逻辑 典型场景
1 driver_override pdev->driver_override 非空 强制匹配指定驱动名 用户手动指定绑定关系
2 设备树 (DT) 系统支持设备树 比较 compatible 属性 ARM64、RISC-V 等现代嵌入式平台
3 ACPI 系统支持 ACPI 比较 ACPI ID x86 平台
4 ID 表 pdrv->id_table 非空 遍历 ID 表匹配设备名 驱动支持多个设备型号
5 直接名称 以上都不满足 比较 pdev->name 与 drv->name 传统/简单 platform 设备
ID 表匹配辅助函数:
static const struct platform_device_id *platform_match_id(
const struct platform_device_id *id,
struct platform_device *pdev)
{
while (id->name[0]) {
if (strcmp(pdev->name, id->name) == 0) {
pdev->id_entry = id;
return id;
}
id++;
}
return NULL;
}
3.4 匹配成功后的流程
匹配成功后,总线会调用驱动的 probe 函数进行初始化:
platform_match() 返回 1
└── driver_match_device() 返回真
└── 设备/驱动绑定流程
├── 驱动侧:__device_attach_driver() → driver_probe_device()
└── 设备侧:__driver_attach() → driver_probe_device()
└── really_probe()
├── pdrv->probe() // 执行驱动的 probe 函数
└── device_bind_driver() // 建立设备-驱动绑定关系
代码位置:drivers/base/dd.c
- 初始化完成后的 sysfs 结构
platform_bus 初始化完成后,可以在 sysfs 中看到以下目录结构:
/sys/
├── bus/
│ └── platform/ ← platform_bus_type 注册产生
│ ├── devices/ ← 该总线上的设备(符号链接)
│ └── drivers/ ← 该总线上的驱动
└── devices/
└── platform/ ← platform_bus 设备注册产生
├── (所有 platform 设备子目录) ← 如 serial、dmac 等
└── ...
两步注册总结:
步骤 函数 作用 可见效果
1 device_register(&platform_bus) 注册 platform 总线设备 /sys/devices/platform/
2 bus_register(&platform_bus_type) 注册 platform 总线类型 /sys/bus/platform/
完成这两步后,platform 总线就准备就绪,可以接收 platform_device 和 platform_driver 的注册了。每次注册新设备或驱动时,都会触发 platform_match 进行配对,配对成功则执行驱动的 probe 函数完成设备初始化。
- 内核版本差异说明
不同内核版本的 platform_match 实现存在细节差异,主要体现在匹配顺序和功能支持上:
内核版本 匹配顺序 主要特点
v2.6.32 ID表 → 名字匹配 无设备树和ACPI支持
v3.x+ 设备树 → ACPI → ID表 → 名字匹配 增加设备树和ACPI支持
v4.2+ driver_override → 设备树 → ACPI → ID表 → 名字匹配 增加驱动覆盖机制
v5.x+ 同 v4.2+ 增加匹配延迟机制
匹配延迟机制(v4.2+):部分内核版本将 platform 设备的匹配延迟到 late_initcall 阶段,确保所有内建驱动都已注册完成,避免因驱动注册顺序导致的 probe 失败问题。
更多推荐




所有评论(0)