3. Platform驱动模型初探:platform_driver与platform_device结构体,总线、设备、驱动模型

在Linux内核中,Platform驱动模型是设备驱动开发的核心基础之一。它并非针对某种具体的物理总线(如PCI、USB、I2C),而是为那些不挂载在可枚举总线上的设备提供了一套标准的驱动框架。本章将深入剖析Platform驱动模型的三个核心角色:总线(Bus)、设备(Device)和驱动(Driver),并重点解读platform_driver与platform_device这两个关键结构体。

核心概念: Platform驱动模型本质上是Linux设备驱动模型(Device Driver Model)在“虚拟平台总线”上的具体实现。它将硬件设备与软件驱动解耦,通过匹配机制实现自动绑定。

3.1 总线、设备、驱动模型概述

Linux内核的设备驱动模型采用面向对象的思想,抽象出三个核心实体:总线(bus_type)、设备(device)和驱动(device_driver)。它们之间的关系可以概括为:设备和驱动分别注册到总线上,总线负责维护一个匹配列表,当发现一个驱动与一个设备匹配时,总线会调用驱动的probe函数,完成驱动的初始化。

对于Platform设备,内核定义了一个虚拟的platform_bus_type。所有Platform设备和Platform驱动都挂载在这个虚拟总线上。这种模型带来了以下好处:

  • 解耦: 驱动代码不再直接操作硬件地址,而是通过设备描述(如设备树或板级文件)获取资源。
  • 可移植性: 同一份驱动代码可以适配不同硬件平台,只需修改设备描述即可。
  • 热插拔支持: 虽然Platform设备通常不可热插拔,但模型框架支持动态添加和移除设备。
Platform总线-设备-驱动模型关系图 platform_bus_type platform_device name, id, resource platform_driver probe, remove, id_table 匹配 & probe 说明:设备和驱动分别注册到总线上,总线负责匹配并调用驱动的 probe 函数完成绑定。

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; // 指向设备树节点的指针
};
platform_device 关键字段说明
字段 类型 说明
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;                // 是否阻止延迟探测
};
platform_driver 关键字段说明
字段/函数 说明
probe 驱动的入口函数。当总线匹配成功后调用,在此函数中完成硬件初始化、注册字符设备、申请中断等操作。
remove 设备移除或驱动卸载时调用,用于释放 probe 中申请的资源。
driver 内嵌的 struct device_driver,其中的 name 字段用于和设备名进行匹配(当不使用 id_table 时)。
id_table 指向 platform_device_id 数组的指针,用于支持多个设备名称的匹配。这是传统匹配方式,设备树匹配优先使用 of_match_table。

3.4 匹配机制:从设备树到驱动绑定

Platform驱动与设备的匹配过程是模型的核心。当内核启动或动态添加设备/驱动时,platform_bus_type 的匹配函数会被调用。匹配顺序如下:

  1. 设备树匹配(OF匹配): 如果驱动提供了 of_match_table,内核会比较设备树节点的 compatible 属性与驱动表中的 .compatible 字符串。这是当前最主流的匹配方式。
  2. ACPI匹配: 在支持ACPI的平台上,会尝试使用ACPI表进行匹配。
  3. ID表匹配: 如果驱动提供了 id_table,内核会比较设备的 name 字段与 id_table 中的 .name 字段。
  4. 名称匹配: 最后,如果以上都未匹配,内核会直接比较设备的 name 字段与驱动的 driver.name 字段。
Platform驱动匹配流程图 匹配开始 1. OF匹配 (compatible) 检查 of_match_table 成功? 2. ACPI匹配 检查 ACPI 表 成功? 3. ID表匹配 (id_table) 检查 platform_device_id 成功? 匹配失败,返回 -ENODEV 匹配成功,调用 probe

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驱动。掌握这些基础知识,是后续学习设备树解析、资源获取以及复杂驱动开发的前提。