3. Platform驱动模型初探:platform_driver与platform_device结构体,总线、设备、驱动模型
在Linux内核中,Platform驱动模型是设备驱动开发的核心基础之一。它并非针对某种具体的物理总线(如PCI、USB、I2C),而是为那些不挂载在可枚举总线上的设备提供了一套标准的驱动框架。本章将深入剖析Platform驱动模型的三个核心角色:总线(Bus)、设备(Device)和驱动(Driver),并重点解读platform_driver与platform_device这两个关键结构体。
3.1 总线、设备、驱动模型概述
Linux内核的设备驱动模型采用面向对象的思想,抽象出三个核心实体:总线(bus_type)、设备(device)和驱动(device_driver)。它们之间的关系可以概括为:设备和驱动分别注册到总线上,总线负责维护一个匹配列表,当发现一个驱动与一个设备匹配时,总线会调用驱动的probe函数,完成驱动的初始化。
对于Platform设备,内核定义了一个虚拟的platform_bus_type。所有Platform设备和Platform驱动都挂载在这个虚拟总线上。这种模型带来了以下好处:
- 解耦: 驱动代码不再直接操作硬件地址,而是通过设备描述(如设备树或板级文件)获取资源。
- 可移植性: 同一份驱动代码可以适配不同硬件平台,只需修改设备描述即可。
- 热插拔支持: 虽然Platform设备通常不可热插拔,但模型框架支持动态添加和移除设备。
3.2 platform_device 结构体深度解析
platform_device 是内核用来描述一个Platform设备的数据结构。它包含了设备的基本信息、资源列表以及与设备树相关的节点信息。其定义位于 include/linux/platform_device.h。
struct platform_device {
const char *name; // 设备名称,用于匹配驱动
int id; // 设备编号,-1表示只有一个设备
bool id_auto; // ID是否自动分配
struct device dev; // 内嵌的通用设备结构体
u32 num_resources; // 资源数量
struct resource *resource; // 指向资源数组的指针
const struct platform_device_id *id_entry; // 匹配到的ID表项
/* 设备树相关 */
struct device_node *dev.of_node; // 指向设备树节点的指针
};
| 字段 | 类型 | 说明 |
|---|---|---|
| name | const char * |
设备名称,是驱动匹配的核心依据之一。驱动通过 id_table 或 driver.name 与之匹配。 |
| id | int |
设备实例编号。当存在多个同类型设备时,用于区分。通常设置为 -1 表示只有一个设备。 |
| num_resources | u32 |
设备使用的资源数量,如I/O内存、中断号、DMA通道等。 |
| resource | struct resource * |
指向资源数组的指针,驱动通过 platform_get_resource() 系列函数获取。 |
| dev.of_node | struct device_node * |
指向设备树中对应节点的指针。当使用设备树时,内核通过此指针解析设备配置。 |
platform_device 通常由内核在解析设备树时动态创建。驱动开发者很少需要手动构造 platform_device,而是专注于从 dev.of_node 中读取设备树属性。
3.3 platform_driver 结构体深度解析
platform_driver 是驱动开发者需要重点实现的结构体。它定义了驱动的核心操作函数,如 probe、remove、shutdown 等,以及用于匹配设备的 id_table。
struct platform_driver {
int (*probe)(struct platform_device *); // 设备匹配成功时调用
int (*remove)(struct platform_device *); // 设备移除时调用
void (*shutdown)(struct platform_device *); // 系统关机时调用
int (*suspend)(struct platform_device *, pm_message_t state); // 挂起
int (*resume)(struct platform_device *); // 恢复
struct device_driver driver; // 内嵌的通用驱动结构体
const struct platform_device_id *id_table; // 传统匹配ID表
bool prevent_deferred_probe; // 是否阻止延迟探测
};
| 字段/函数 | 说明 |
|---|---|
| probe | 驱动的入口函数。当总线匹配成功后调用,在此函数中完成硬件初始化、注册字符设备、申请中断等操作。 |
| remove | 设备移除或驱动卸载时调用,用于释放 probe 中申请的资源。 |
| driver | 内嵌的 struct device_driver,其中的 name 字段用于和设备名进行匹配(当不使用 id_table 时)。 |
| id_table | 指向 platform_device_id 数组的指针,用于支持多个设备名称的匹配。这是传统匹配方式,设备树匹配优先使用 of_match_table。 |
3.4 匹配机制:从设备树到驱动绑定
Platform驱动与设备的匹配过程是模型的核心。当内核启动或动态添加设备/驱动时,platform_bus_type 的匹配函数会被调用。匹配顺序如下:
- 设备树匹配(OF匹配): 如果驱动提供了
of_match_table,内核会比较设备树节点的compatible属性与驱动表中的.compatible字符串。这是当前最主流的匹配方式。 - ACPI匹配: 在支持ACPI的平台上,会尝试使用ACPI表进行匹配。
- ID表匹配: 如果驱动提供了
id_table,内核会比较设备的name字段与id_table中的.name字段。 - 名称匹配: 最后,如果以上都未匹配,内核会直接比较设备的
name字段与驱动的driver.name字段。
3.5 驱动注册与注销流程
驱动开发者需要按照标准流程注册和注销Platform驱动。核心API是 platform_driver_register() 和 platform_driver_unregister()。为了方便,内核提供了 module_platform_driver() 宏,它封装了模块加载和卸载时的注册与注销操作。
/* 传统注册方式 */
static int __init my_driver_init(void)
{
return platform_driver_register(&my_platform_driver);
}
static void __exit my_driver_exit(void)
{
platform_driver_unregister(&my_platform_driver);
}
module_init(my_driver_init);
module_exit(my_driver_exit);
/* 推荐:使用宏简化 */
module_platform_driver(my_platform_driver);
module_platform_driver() 宏时,驱动必须定义 my_platform_driver 变量,并且该宏会隐式创建 module_init 和 module_exit 函数。不要在同一个模块中重复定义这两个入口函数。
3.6 完整示例:一个简单的Platform驱动
下面展示一个完整的、基于设备树匹配的Platform驱动框架。该驱动在 probe 中打印设备信息,在 remove 中清理。
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
/* 匹配表:设备树 compatible 属性 */
static const struct of_device_id my_of_match[] = {
{ .compatible = "vendor,my-device", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
/* probe 函数:驱动入口 */
static int my_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res;
dev_info(dev, "My device probed!\n");
/* 获取I/O内存资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res) {
dev_info(dev, "Memory start: 0x%llx, end: 0x%llx\n",
(unsigned long long)res->start,
(unsigned long long)res->end);
}
/* 获取中断资源 */
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res) {
dev_info(dev, "IRQ number: %d\n", (int)res->start);
}
return 0;
}
/* remove 函数:驱动退出 */
static int my_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "My device removed!\n");
return 0;
}
/* 定义 platform_driver 结构体 */
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_device_driver",
.of_match_table = my_of_match,
},
};
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple platform driver example");
of_match_table是设备树匹配的核心,必须与设备树节点的compatible属性一致。platform_get_resource()是驱动获取硬件资源的标准API,替代了传统的ioremap直接操作物理地址。module_platform_driver()宏简化了模块的注册和注销代码。
3.7 本章小结
本章深入剖析了Platform驱动模型的三个核心角色:总线、设备和驱动。我们详细解读了 platform_device 和 platform_driver 结构体的关键字段,并梳理了从设备树到驱动绑定的完整匹配流程。通过一个完整的示例,展示了如何编写一个基于设备树的Platform驱动。掌握这些基础知识,是后续学习设备树解析、资源获取以及复杂驱动开发的前提。