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 的完整链路:
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 函数中按以下优先级尝试匹配:
- 设备树匹配:比较设备节点的 compatible 属性与驱动的 of_match_table。
- ACPI 匹配:在支持 ACPI 的平台上尝试 ACPI 表匹配。
- ID 表匹配:比较 platform_device.name 与 id_table 中的 name 字段。
- 名称直接匹配:比较 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");