1. Platform驱动概述

Platform驱动(平台驱动)是Linux内核中用于管理非可枚举、非热插拔设备的核心机制。在嵌入式系统中,大量设备(如UART、I2C控制器、SPI控制器、GPIO控制器)直接集成在SoC内部,无法通过PCIe、USB等总线自动发现。Platform驱动模型为这类设备提供了统一的总线抽象、资源管理和驱动绑定框架。

核心认知: Platform驱动并非操作物理总线,而是内核软件层的一种“虚拟总线”模型,用于将设备与驱动进行匹配,并传递硬件资源(内存地址、中断号、DMA通道等)。

1.1 Platform总线模型

Platform总线是Linux设备驱动模型(LDDM)中的一条虚拟总线,注册为 platform_bus_type。其核心数据结构与匹配流程如下:

组件 数据结构 职责
总线 struct bus_type platform_bus_type 维护设备链表、驱动链表,执行匹配与探测
设备 struct platform_device 描述硬件设备实例,包含资源数组(IORESOURCE_MEM、IORESOURCE_IRQ等)
驱动 struct platform_driver 实现 probe()、remove() 回调,绑定设备后驱动硬件
匹配方式 of_match_table / id_table 设备树匹配(主流)或传统ID表匹配
匹配优先级: 内核首先尝试设备树(DT)匹配,即比较驱动 of_match_table 中的 compatible 字符串与设备节点中的 compatible 属性;若失败,则回退到 id_table 名称匹配。

下图展示了Platform总线模型中设备、驱动与总线之间的三角关系及匹配流程:

Platform总线模型架构 platform_bus_type platform_device name / num_resources / resource[] platform_device dev.of_node (设备树节点指针) platform_driver probe() / remove() / of_match_table platform_driver id_table (传统匹配) 匹配流程 DT compatible → id_table → 名称回退

1.2 Platform驱动与设备树的关系

在现代化嵌入式Linux开发中,设备树(Device Tree,DT)已成为描述硬件信息的标准方式。Platform驱动通过设备树获取硬件资源,实现了“驱动代码与硬件配置解耦”。

设备树中的每个节点对应一个 platform_device,驱动通过 of_match_table 声明自己支持的设备。关键映射关系如下:

设备树属性 Platform驱动获取方式 说明
compatible = "vendor,device" of_match_table 匹配 驱动与设备绑定的核心标识
reg = <0x10000000 0x1000> platform_get_resource(pdev, IORESOURCE_MEM, 0) 获取内存基地址与范围
interrupts = <0 31 4> platform_get_resource(pdev, IORESOURCE_IRQ, 0) 获取中断号与触发类型
dmas = <&dma0 2> dma_request_slave_channel(dev, "rx") 获取DMA通道(通过DMA引擎API)
clocks = <&clk 5> clk_get(dev, NULL) 获取时钟句柄
常见陷阱: 设备树中的 reg 属性顺序必须与驱动中 platform_get_resource 的索引一一对应。如果设备树定义了多个 reg 区域,驱动必须按索引0、1、2…依次获取,否则将访问到错误的地址空间。

下图展示了设备树节点如何映射到Platform驱动中的资源结构:

设备树 → Platform驱动资源映射 设备树 (DTS) uart0: serial@10000000 { compatible = "vendor,uart"; reg = <0x10000000 0x1000>; interrupts = <0 31 4>; dmas = <&dma0 2>; dma-names = "rx"; clocks = <&clk 5>; }; Platform驱动 (C代码) static int uart_probe(struct platform_device *pdev) { res = platform_get_resource( pdev, IORESOURCE_MEM, 0); irq = platform_get_irq( pdev, 0); chan = dma_request_slave_channel( &pdev->dev, "rx"); clk = clk_get(&pdev->dev, NULL); }

1.3 驱动开发基本流程

开发一个完整的Platform驱动通常遵循以下步骤。以UART控制器驱动为例:

  1. 定义设备树节点:在dts文件中添加硬件描述,包括compatible、reg、interrupts、dmas等属性。
  2. 实现驱动入口与出口:使用 module_platform_driver() 宏简化注册。
  3. 实现probe函数:获取资源、映射寄存器、注册中断、初始化DMA、注册字符设备或框架接口。
  4. 实现remove函数:反向释放probe中申请的所有资源。
  5. 实现操作函数集:根据设备类型实现 file_operations、uart_ops 等回调。
  6. 编译与测试:将驱动编译为模块或内置,加载后验证设备节点创建与功能。
最佳实践: 始终在 probe() 中使用 devm_* 系列API(如 devm_ioremap_resource、devm_request_irq),这些API会在设备注销时自动释放资源,避免内存泄漏和中断残留。

下面是一个典型的Platform驱动框架代码:

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>

static int my_driver_probe(struct platform_device *pdev)
{
    struct resource *res;
    void __iomem *base;
    int irq;

    /* 获取内存资源 */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(base))
        return PTR_ERR(base);

    /* 获取中断资源 */
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;

    /* 注册中断 */
    devm_request_irq(&pdev->dev, irq, my_isr, 0,
                     dev_name(&pdev->dev), dev);

    dev_info(&pdev->dev, "driver probed successfully\n");
    return 0;
}

static int my_driver_remove(struct platform_device *pdev)
{
    dev_info(&pdev->dev, "driver removed\n");
    return 0;
}

static const struct of_device_id my_of_match[] = {
    { .compatible = "vendor,my-device" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);

static struct platform_driver my_driver = {
    .probe  = my_driver_probe,
    .remove = my_driver_remove,
    .driver = {
        .name = "my_device",
        .of_match_table = my_of_match,
    },
};

module_platform_driver(my_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Platform driver example");

下表总结了驱动开发各阶段的关键动作与注意事项:

阶段 关键动作 常见错误
设备树编写 确保compatible字符串与驱动一致;reg地址范围正确 地址重叠、大小写不匹配、缺少中断父节点
probe实现 使用devm_* API;检查所有资源获取返回值 忘记检查IS_ERR()、资源索引越界
remove实现 释放所有非devm资源;注销设备接口 遗漏释放、顺序错误导致内核崩溃
匹配表声明 使用MODULE_DEVICE_TABLE导出;末尾加sentinel 缺少终止项、未导出导致模块无法自动加载
调试技巧: 在 probe() 中打印 pdev->dev.of_node->full_name 可以确认设备树节点是否被正确匹配。使用 ls /sys/bus/platform/devices/ 查看所有已注册的platform设备。