内核模块开发 platform驱动与ioctl接口实战 - 第4节
4. Platform驱动模型概述
在早期的Linux内核中,设备信息(如内存地址、中断号、寄存器偏移)通常被直接硬编码在驱动代码中。这种做法在嵌入式系统面临两个致命问题:
- 代码冗余:同一款芯片的多个板级变种,需要复制粘贴大量相似的驱动代码。
- 维护灾难:硬件引脚变更或内存映射调整时,必须修改驱动源码并重新编译内核。
Platform驱动模型的核心思想是“设备与驱动分离”。它将硬件描述信息从驱动代码中剥离,交给一个独立的实体——Platform设备来承载。驱动只负责操作逻辑,不再关心具体的硬件资源数值。这种架构通过Platform总线作为匹配中介,实现了设备与驱动的松耦合。
4.2 Platform总线:虚拟总线的核心职责
Platform总线是一种虚拟总线,它并不对应物理硬件上的总线(如PCIe、USB)。它的存在是为了给那些“自描述”能力较弱的片上设备(如UART、I2C控制器、GPIO控制器)提供一个统一的挂接和匹配框架。
4.2.1 总线核心数据结构
在Linux内核中,Platform总线由 platform_bus_type 结构体表示,定义在 drivers/base/platform.c 中:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
其中最重要的成员是 .match 函数指针,它决定了设备与驱动如何配对。
4.2.2 匹配机制:四种匹配方式
platform_match 函数按优先级依次尝试以下四种匹配策略:
| 优先级 | 匹配方式 | 匹配依据 | 典型场景 |
|---|---|---|---|
| 1(最高) | OF(设备树)匹配 | of_match_table 中的 compatible 字符串 |
ARM/ARM64/RISC-V 嵌入式平台 |
| 2 | ACPI 匹配 | ACPI 表中的 HID/CID | x86/IA64 平台 |
| 3 | ID 表匹配 | id_table 中的 name 字段 |
传统板级代码(非设备树) |
| 4(最低) | 名称直接匹配 | 设备名与驱动名相同 | 简单设备或遗留代码 |
4.3 Platform设备:硬件资源的载体
Platform设备是对片上外设的抽象。它通过 struct platform_device 结构体描述,核心成员包括:
struct platform_device {
const char *name; // 设备名称,用于匹配
int id; // 设备实例ID(-1表示单实例)
struct device dev; // 内嵌通用设备结构
u32 num_resources; // 资源数量
struct resource *resource; // 资源数组(内存、中断、DMA等)
const struct platform_device_id *id_entry; // 匹配到的ID表项
};
4.3.1 资源描述:struct resource
硬件资源通过 struct resource 描述,这是驱动获取硬件信息的标准途径:
struct resource {
resource_size_t start; // 起始地址(如寄存器基址)
resource_size_t end; // 结束地址
const char *name; // 资源名称
unsigned long flags; // 资源类型标志
struct resource *parent, *sibling, *child;
};
常用的资源类型标志(flags):
| 标志宏 | 值 | 说明 |
|---|---|---|
IORESOURCE_MEM |
0x00000200 | 内存映射寄存器区域 |
IORESOURCE_IO |
0x00000100 | 端口I/O区域 |
IORESOURCE_IRQ |
0x00000400 | 中断号 |
IORESOURCE_DMA |
0x00000800 | DMA通道 |
4.4 Platform驱动:操作逻辑的封装
Platform驱动通过 struct platform_driver 结构体定义,核心是 probe() 和 remove() 回调:
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);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
4.4.1 probe() 生命周期
当总线匹配成功后,内核调用驱动的 probe() 函数。这是驱动初始化的入口,典型流程如下:
- 获取资源:通过
platform_get_resource()获取内存、中断等资源。 - 映射寄存器:使用
devm_ioremap_resource()将物理地址映射到内核虚拟地址空间。 - 注册中断:调用
devm_request_irq()注册中断处理函数。 - 初始化硬件:配置寄存器、复位外设等。
- 创建接口:注册字符设备、sysfs属性或其他内核接口。
ioremap() 而不是 devm_ioremap_resource()。后者会自动处理资源冲突检查,并在驱动卸载时自动释放映射,避免内存泄漏。
4.5 设备与驱动注册:两条独立的路径
分离模型的核心在于:设备和驱动的注册是完全独立的。它们可以在不同时间、不同模块中注册,总线负责在两者都就绪时触发匹配。
4.5.1 设备注册
// 静态定义设备资源
static struct resource my_dev_resources[] = {
{
.start = 0x10000000,
.end = 0x10000FFF,
.flags = IORESOURCE_MEM,
},
{
.start = IRQ_LINE_42,
.end = IRQ_LINE_42,
.flags = IORESOURCE_IRQ,
},
};
// 定义并注册Platform设备
static struct platform_device my_device = {
.name = "my-char-device",
.id = -1,
.num_resources = ARRAY_SIZE(my_dev_resources),
.resource = my_dev_resources,
};
platform_device_register(&my_device);
4.5.2 驱动注册
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my-char-device",
.of_match_table = my_of_match, // 设备树匹配表
},
};
platform_driver_register(&my_driver);
4.5.3 注册顺序与匹配时机
| 场景 | 设备注册 | 驱动注册 | 匹配时机 |
|---|---|---|---|
| 设备先注册 | 早 | 晚 | 驱动注册时立即匹配 |
| 驱动先注册 | 晚 | 早 | 设备注册时立即匹配 |
| 同时注册 | 同时 | 同时 | 注册过程中匹配 |
4.6 分离思想的深层价值
Platform驱动模型的“分离”不仅仅是代码层面的解耦,它带来了以下架构层面的优势:
- 硬件描述标准化:设备树(Device Tree)成为硬件描述的通用语言,驱动不再关心具体板级布局。
- 驱动可移植性:同一份驱动可以运行在数百种不同的板卡上,只要设备树提供正确的资源描述。
- 模块化开发:设备工程师和驱动工程师可以并行工作,前者负责设备树,后者负责驱动逻辑。
- 热插拔支持:虽然Platform设备通常不是热插拔的,但分离模型为未来支持提供了统一的框架。
- 电源管理集成:Platform驱动模型内置了系统级电源管理回调(suspend/resume),与设备生命周期无缝衔接。