怎么理解 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_devicebus_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 中。

  1. 核心代码实现

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 由驱动自身提供。

  1. 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

  1. 初始化完成后的 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 函数完成设备初始化。

  1. 内核版本差异说明

不同内核版本的 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 失败问题。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐