3. platform_driver 结构体详解

在 Linux 平台驱动模型中,struct platform_driver 是驱动开发者最核心的操作对象。它封装了驱动的生命周期回调、设备匹配逻辑以及电源管理接口。本章将深入剖析该结构体的每个成员、自动注册宏 module_platform_driver 的实现原理,以及设备树匹配的完整流程。

3.1 struct platform_driver 成员全景

结构体定义位于 include/linux/platform_device.h,其成员可分为三类:核心回调、ID 表与电源管理。下表列出了所有关键成员及其用途:

成员名称 类型 用途说明
probe int (*)(struct platform_device *) 驱动与设备匹配成功时调用,完成硬件初始化、资源申请、设备注册
remove int (*)(struct platform_device *) 设备移除或驱动卸载时调用,释放 probe 中申请的资源
shutdown void (*)(struct platform_device *) 系统关机或重启时调用,执行硬件安全断电序列
suspend int (*)(struct platform_device *, pm_message_t) 系统进入低功耗模式时调用,保存硬件上下文并关闭时钟
resume int (*)(struct platform_device *) 系统从低功耗模式唤醒时调用,恢复硬件上下文并重新使能时钟
id_table const struct platform_device_id * 传统非设备树匹配方式,存放设备名称列表
driver struct device_driver 内嵌通用驱动结构体,包含 name、owner、of_match_table 等字段
dma_parms struct device_dma_parameters * DMA 参数指针,用于设置最大 segment 大小等限制(可选)
关键认知: probe 和 remove 是驱动必须实现的两个回调。即使驱动不需要电源管理,也建议将 suspend/resume 初始化为 NULL,内核会安全跳过。

3.2 module_platform_driver 宏:一行代码的魔法

传统驱动注册需要手动调用 platform_driver_register() 并在模块卸载时调用 platform_driver_unregister()。module_platform_driver 宏将这两个步骤合并为一行,极大简化了样板代码。

宏定义展开后的等效代码如下:

/* 宏原型:module_platform_driver(__platform_driver) */
static int __init __platform_driver##_init(void) {
    return platform_driver_register(&__platform_driver);
}
module_init(__platform_driver##_init);

static void __exit __platform_driver##_exit(void) {
    platform_driver_unregister(&__platform_driver);
}
module_exit(__platform_driver##_exit);

使用示例:

static struct platform_driver my_dma_driver = {
    .probe  = my_dma_probe,
    .remove = my_dma_remove,
    .driver = {
        .name = "my-dma-driver",
        .of_match_table = my_dma_of_match,
        .owner = THIS_MODULE,
    },
};

module_platform_driver(my_dma_driver);
避坑指南: 如果驱动需要同时支持设备树匹配和传统 ID 表匹配,必须在 driver.of_match_table 和 id_table 中都填入数据。内核优先匹配设备树 compatible 属性,若失败则回退到 id_table 名称匹配。

3.3 设备树匹配:从 compatible 到 probe

设备树匹配是现代 ARM/嵌入式 Linux 平台驱动的主流方式。匹配过程由内核驱动核心层自动完成,开发者只需提供 of_match_table。下图展示了从设备树节点到驱动 probe 的完整链路:

设备树匹配到 probe 调用完整流程 设备树节点 compatible = "vendor,my-dma" platform_device 由 of_platform_populate 创建 platform_driver module_platform_driver 注册 解析 注册到总线 platform_bus 匹配层 遍历 of_match_table → 比较 compatible 匹配成功 of_match_device 返回匹配项 驱动 probe 函数执行 获取资源 → 注册 DMA 通道 → mmap 映射 匹配失败处理 返回 -ENODEV,继续尝试 下一个驱动或设备

3.4 of_match_table 编写规范

of_match_table 是一个以空结构体结尾的数组,每个条目包含设备树 compatible 字符串和私有数据指针。编写时需遵循以下规则:

  • compatible 字符串必须与设备树完全一致,包括厂商前缀和逗号,例如 "ti,am335x-edma"。
  • 使用 MODULE_DEVICE_TABLE(of, ...) 导出表,以便内核模块加载时自动生成别名,实现热插拔自动加载。
  • data 字段可用于传递平台特定配置,如 DMA 通道数量、传输宽度等,避免在 probe 中硬编码。

典型定义示例:

static const struct of_device_id my_dma_of_match[] = {
    {
        .compatible = "vendor,my-dma-v1",
        .data       = &dma_v1_config,
    },
    {
        .compatible = "vendor,my-dma-v2",
        .data       = &dma_v2_config,
    },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_dma_of_match);
常见错误: 忘记在 driver 结构体中赋值 of_match_table,或者 compatible 字符串末尾缺少空终止符(虽然 C 字符串自动包含,但数组元素必须确保最后一个为全零哨兵)。这两种情况都会导致匹配永远失败,probe 不会被调用。

3.5 匹配优先级与回退机制

内核在 platform_match 函数中按以下优先级尝试匹配:

  1. 设备树匹配:比较设备节点的 compatible 属性与驱动的 of_match_table。
  2. ACPI 匹配:在支持 ACPI 的平台上尝试 ACPI 表匹配。
  3. ID 表匹配:比较 platform_device.name 与 id_table 中的 name 字段。
  4. 名称直接匹配:比较 platform_device.name 与 driver.name。

对于嵌入式 DMA 驱动场景,绝大多数情况使用设备树匹配(优先级 1)。只有在老旧平台或 x86 嵌入式环境中才可能用到 ID 表或名称匹配。

匹配方式 适用场景 匹配速度 推荐度
设备树 compatible ARM/ARM64/RISC-V 嵌入式 快(直接字符串比较) ★★★★★
ACPI 表 x86/ARM64 服务器 中(需解析 ACPI 表) ★★★☆☆
platform_device_id 传统非设备树平台 快 ★★☆☆☆
driver.name 直接匹配 简单测试驱动 最快 ★☆☆☆☆

3.6 实战:构建一个完整的 platform_driver 骨架

结合 DMA 传输与 mmap 的课程主题,下面给出一个包含设备树匹配的驱动骨架,后续章节将在此基础上填充 DMA 和 mmap 逻辑:

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>

static int my_dma_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    const struct of_device_id *match;

    /* 获取匹配到的 of_device_id,进而获取私有配置数据 */
    match = of_match_device(my_dma_of_match, dev);
    if (!match) {
        dev_err(dev, "no matching device id found\n");
        return -ENODEV;
    }

    dev_info(dev, "DMA device probed with config: %s\n", match->compatible);

    /* TODO: 1. 获取 DMA 通道 (dma_request_chan)
     *       2. 注册字符设备 (alloc_chrdev_region + cdev_init)
     *       3. 建立 mmap 映射 (remap_pfn_range)
     */
    return 0;
}

static int my_dma_remove(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    dev_info(dev, "DMA device removed\n");

    /* TODO: 释放 probe 中申请的所有资源 */
    return 0;
}

static const struct of_device_id my_dma_of_match[] = {
    { .compatible = "vendor,dma-controller-v1", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_dma_of_match);

static struct platform_driver my_dma_driver = {
    .probe  = my_dma_probe,
    .remove = my_dma_remove,
    .driver = {
        .name = "my-dma-driver",
        .of_match_table = my_dma_of_match,
        .owner = THIS_MODULE,
    },
};

module_platform_driver(my_dma_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linux DMA Expert");
MODULE_DESCRIPTION("Platform DMA driver with mmap support");