内核模块开发 platform驱动与ioctl接口实战 - 第4节

4. Platform驱动模型概述

在早期的Linux内核中,设备信息(如内存地址、中断号、寄存器偏移)通常被直接硬编码在驱动代码中。这种做法在嵌入式系统面临两个致命问题:

  • 代码冗余:同一款芯片的多个板级变种,需要复制粘贴大量相似的驱动代码。
  • 维护灾难:硬件引脚变更或内存映射调整时,必须修改驱动源码并重新编译内核。

Platform驱动模型的核心思想是“设备与驱动分离”。它将硬件描述信息从驱动代码中剥离,交给一个独立的实体——Platform设备来承载。驱动只负责操作逻辑,不再关心具体的硬件资源数值。这种架构通过Platform总线作为匹配中介,实现了设备与驱动的松耦合。

关键收益: 同一份驱动代码可以服务于多个硬件实例;硬件变更时只需修改设备描述文件(如设备树或ACPI表),无需重新编译驱动。
Platform驱动模型:设备与驱动分离架构 Platform驱动模型:设备与驱动分离架构 Platform 设备 硬件资源描述 (内存基址、中断号、DMA通道) Platform 驱动 操作逻辑代码 (probe/remove/PM回调) Platform 总线 匹配引擎 设备与驱动通过 name 或 of_match_table 在总线上完成匹配 匹配成功后调用驱动的 probe() 函数,传入设备资源

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(最低) 名称直接匹配 设备名与驱动名相同 简单设备或遗留代码
开发建议: 在新项目中,优先使用设备树(OF)匹配方式。它最灵活,且与硬件描述解耦最彻底。ID表匹配和名称匹配主要用于兼容旧代码。
Platform总线匹配流程 Platform总线匹配流程 platform_match() 调用 1. OF (设备树) 匹配? 是 否 匹配成功 → probe() 2. ACPI 匹配? 是 否 3. ID表匹配? 是 否 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() 函数。这是驱动初始化的入口,典型流程如下:

  1. 获取资源:通过 platform_get_resource() 获取内存、中断等资源。
  2. 映射寄存器:使用 devm_ioremap_resource() 将物理地址映射到内核虚拟地址空间。
  3. 注册中断:调用 devm_request_irq() 注册中断处理函数。
  4. 初始化硬件:配置寄存器、复位外设等。
  5. 创建接口:注册字符设备、sysfs属性或其他内核接口。
常见错误: 在 probe() 中直接使用 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 注册顺序与匹配时机

场景 设备注册 驱动注册 匹配时机
设备先注册 早 晚 驱动注册时立即匹配
驱动先注册 晚 早 设备注册时立即匹配
同时注册 同时 同时 注册过程中匹配
工程实践: 在嵌入式系统中,设备通常由板级代码或设备树在系统启动早期注册,而驱动则作为模块在后续加载。这种“设备先于驱动”的模式是主流,确保驱动加载时能立即找到对应的硬件。
设备与驱动注册时序 设备与驱动注册时序(设备先注册场景) 时间 设备注册 设备已注册,等待驱动 驱动注册 匹配成功 → probe() 总线触发匹配 无论设备与驱动谁先注册,总线都会在第二个实体注册时完成匹配 匹配成功后立即调用驱动的 probe() 回调

4.6 分离思想的深层价值

Platform驱动模型的“分离”不仅仅是代码层面的解耦,它带来了以下架构层面的优势:

  • 硬件描述标准化:设备树(Device Tree)成为硬件描述的通用语言,驱动不再关心具体板级布局。
  • 驱动可移植性:同一份驱动可以运行在数百种不同的板卡上,只要设备树提供正确的资源描述。
  • 模块化开发:设备工程师和驱动工程师可以并行工作,前者负责设备树,后者负责驱动逻辑。
  • 热插拔支持:虽然Platform设备通常不是热插拔的,但分离模型为未来支持提供了统一的框架。
  • 电源管理集成:Platform驱动模型内置了系统级电源管理回调(suspend/resume),与设备生命周期无缝衔接。