2. Linux 设备驱动模型基础:platform总线、device与driver的匹配机制、probe流程
在嵌入式Linux系统中,许多设备(如UART、I2C、SPI控制器、DMA控制器)并不挂载在PCIe、USB等可枚举总线上,而是直接集成在SoC内部,通过内存映射寄存器访问。为了统一管理这类设备,Linux内核引入了platform总线(虚拟总线)及配套的设备驱动模型。本章将深入剖析platform总线的核心机制:device与driver的匹配规则,以及probe函数的完整调用流程。
学习提示: platform驱动是Linux驱动开发中最常用的框架之一。理解device/driver分离与匹配机制,是后续实现DMA传输与mmap内存映射的基础。
2.1 platform总线模型概述
Linux设备驱动模型的核心思想是总线(Bus)、设备(Device)、驱动(Driver)三者分离。platform总线是一种虚拟总线,用于管理那些不依附于物理总线(如PCI、USB)的SoC内部外设。
下图展示了platform总线在Linux驱动模型中的位置与关系:
核心概念: platform总线本身不传输数据,它的职责是维护device和driver的链表,并在两者之间执行匹配。匹配成功后,调用driver的probe函数完成设备初始化。
2.2 platform_device 与 platform_driver 数据结构
内核使用两个核心结构体来描述platform设备与驱动:
| 结构体 | 头文件 | 关键成员 | 说明 |
|---|---|---|---|
| platform_device | linux/platform_device.h |
name, id, num_resources, resource, dev |
描述一个硬件设备,包含名称、ID、IO资源(内存、中断等) |
| platform_driver | linux/platform_device.h |
driver.name, probe, remove, shutdown, suspend, resume |
描述一个驱动程序,包含匹配名称和操作函数指针 |
以下是一个典型的platform_device定义示例(通常在BSP板级文件或设备树中):
/* 静态定义 platform_device */
static struct resource dma_resources[] = {
{
.start = 0x11000000, /* 寄存器基地址 */
.end = 0x11000FFF, /* 寄存器结束地址 */
.flags = IORESOURCE_MEM, /* 内存类型资源 */
},
{
.start = 37, /* 中断号 */
.end = 37,
.flags = IORESOURCE_IRQ, /* 中断类型资源 */
},
};
static struct platform_device dma_device = {
.name = "my-dma-controller",
.id = 0,
.num_resources = ARRAY_SIZE(dma_resources),
.resource = dma_resources,
};
对应的platform_driver定义:
static struct platform_driver my_dma_driver = {
.probe = my_dma_probe,
.remove = my_dma_remove,
.driver = {
.name = "my-dma-controller",
.of_match_table = my_dma_of_match, /* 设备树匹配表 */
},
};
/* 模块入口注册 */
module_platform_driver(my_dma_driver);
2.3 device 与 driver 的匹配机制
当内核注册一个新的platform_device或platform_driver时,platform总线会触发一次匹配扫描。匹配顺序遵循以下优先级:
- 设备树(OF)匹配: 比较device的
of_node与driver的of_match_table中的compatible字符串。 - ACPI匹配: 在支持ACPI的平台上,使用ACPI表进行匹配。
- ID表(ID Table)匹配: 比较driver的
id_table与device的name字段。 - 名称(Name)匹配: 直接比较driver的
driver.name与device的name字段。
匹配流程如下图所示:
注意事项: 在设备树(DT)普及的现代内核中,OF匹配是首选方式。如果设备树中定义了compatible属性,驱动必须提供对应的
of_match_table,否则即使name匹配也可能无法正确绑定。
2.4 probe 流程详解
当匹配成功后,内核调用driver的probe函数。probe是驱动初始化的入口,其典型执行流程如下:
| 步骤 | 操作 | 关键函数/宏 | 说明 |
|---|---|---|---|
| 1 | 获取设备资源 | platform_get_resource() |
从platform_device中提取MEM、IRQ等资源 |
| 2 | IO内存映射 | devm_ioremap_resource() |
将物理地址映射到内核虚拟地址空间 |
| 3 | 获取中断号 | platform_get_irq() |
获取设备中断号并注册中断处理函数 |
| 4 | 初始化硬件 | 寄存器读写操作 | 复位设备、配置工作模式、启动时钟等 |
| 5 | 注册子系统接口 | dma_async_device_register()等 |
将设备注册到对应的内核框架(如DMA引擎、RTC、GPIO等) |
| 6 | 创建设备文件 | device_create() |
在/dev下创建设备节点,供用户空间访问 |
| 7 | 初始化完成 | 返回0 | 返回0表示成功,负值表示失败 |
以下是一个完整的probe函数示例(DMA控制器驱动片段):
static int my_dma_probe(struct platform_device *pdev)
{
struct my_dma_dev *dma_dev;
struct resource *res;
int irq, ret;
/* 1. 分配私有数据结构 */
dma_dev = devm_kzalloc(&pdev->dev, sizeof(*dma_dev), GFP_KERNEL);
if (!dma_dev)
return -ENOMEM;
/* 2. 获取MEM资源并映射 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
dma_dev->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(dma_dev->regs))
return PTR_ERR(dma_dev->regs);
/* 3. 获取中断资源 */
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
/* 4. 注册中断处理 */
ret = devm_request_irq(&pdev->dev, irq, my_dma_irq_handler,
0, pdev->name, dma_dev);
if (ret)
return ret;
/* 5. 初始化DMA硬件寄存器 */
writel(DMA_SOFT_RESET, dma_dev->regs + DMA_CTRL_REG);
writel(DMA_ENABLE, dma_dev->regs + DMA_CTRL_REG);
/* 6. 注册DMA引擎设备 */
dma_dev->dma_dev.dev = &pdev->dev;
ret = dma_async_device_register(&dma_dev->dma_dev);
if (ret)
return ret;
/* 7. 保存私有数据 */
platform_set_drvdata(pdev, dma_dev);
dev_info(&pdev->dev, "DMA controller initialized\n");
return 0;
}
最佳实践: 推荐使用
devm_系列资源管理函数(如devm_ioremap_resource、devm_request_irq),它们会在驱动卸载或probe失败时自动释放资源,避免内存泄漏和资源泄漏。
2.5 匹配与probe的触发时机
理解匹配触发的时机对调试驱动至关重要:
- 设备注册时: 当调用
platform_device_register()时,内核遍历已注册的driver链表进行匹配。 - 驱动注册时: 当调用
platform_driver_register()时,内核遍历已注册的device链表进行匹配。 - 设备树解析时: 内核启动时,DTB中的节点被解析为platform_device,随后触发匹配。
- 热插拔事件: 虽然platform设备通常不可热插拔,但某些虚拟设备支持动态创建和移除。
匹配与probe的完整生命周期如下图所示:
2.6 常见问题与调试方法
常见问题1:probe没有被调用
原因:device和driver的name不匹配,或设备树compatible属性与of_match_table不一致。
调试方法:
- 检查
- 检查
- 使用
- 启用内核调试:
原因:device和driver的name不匹配,或设备树compatible属性与of_match_table不一致。
调试方法:
- 检查
/sys/bus/platform/devices/ 下是否存在设备节点。- 检查
/sys/bus/platform/drivers/ 下是否存在驱动节点。- 使用
ls -l /sys/bus/platform/drivers/<driver_name>/ 查看驱动是否绑定到设备。- 启用内核调试:
echo 'file platform.c +p' > /sys/kernel/debug/dynamic_debug/control
调试技巧: 在probe函数中添加
dev_dbg()或dev_info()输出,通过dmesg观察调用顺序。使用platform_get_resource后检查返回值是否为NULL,这是资源获取失败的常见原因。
2.7 本章小结
本章深入剖析了Linux platform总线模型的核心机制:
- platform总线作为虚拟总线,实现了device与driver的分离管理。
- 匹配机制遵循OF → ACPI → ID Table → Name的优先级顺序,确保设备与驱动的正确绑定。
- probe流程是驱动初始化的核心,负责资源获取、硬件初始化、子系统注册等关键操作。
- 掌握匹配与probe机制,是后续实现DMA传输和mmap内存映射的基础保障。