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 总线模型架构 platform_bus (虚拟总线) device 链表 platform_device_1 platform_device_2 platform_device_3 driver 链表 platform_driver_1 platform_driver_2 platform_driver_3 匹配引擎 (match) probe() 回调执行
核心概念: 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总线会触发一次匹配扫描。匹配顺序遵循以下优先级:

  1. 设备树(OF)匹配: 比较device的of_node与driver的of_match_table中的compatible字符串。
  2. ACPI匹配: 在支持ACPI的平台上,使用ACPI表进行匹配。
  3. ID表(ID Table)匹配: 比较driver的id_table与device的name字段。
  4. 名称(Name)匹配: 直接比较driver的driver.name与device的name字段。

匹配流程如下图所示:

platform 匹配流程 匹配触发 步骤1: 设备树 (OF) 匹配 比较 compatible 字符串 匹配? 调用 probe() 是 否 步骤2: ACPI 匹配 比较 ACPI ID 匹配? 是 否 步骤3: ID Table 匹配 比较 id_table 与 device name 匹配? 是 否 步骤4: 名称 (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的完整生命周期如下图所示:

platform 设备生命周期 设备注册 匹配成功 probe() remove() 生命周期说明 设备注册 → 匹配成功 → probe() 初始化 → 运行 → remove() 释放 匹配可在设备注册或驱动注册时触发,probe失败则不会进入运行状态

2.6 常见问题与调试方法

常见问题1:probe没有被调用
原因: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内存映射的基础保障。