2. Platform驱动基础:platform_driver结构体、platform_device注册、设备树匹配机制

在Linux音频驱动开发中,Platform驱动是连接硬件与内核驱动的核心桥梁。本章将深入剖析Platform驱动的三大基石:platform_driver结构体、platform_device注册以及设备树匹配机制。理解这些基础是编写高质量音频驱动的第一步。

核心概念:Platform驱动并非针对PCI、USB等传统总线设备,而是用于那些直接挂在CPU总线上的“非枚举型”设备,例如大多数嵌入式SoC中的I2S、DMA控制器、音频编解码器接口。设备树(Device Tree)则是描述这些硬件资源的标准化方式。

2.1 platform_driver结构体深度解析

platform_driver是驱动开发者需要填充的核心数据结构,它定义了驱动的生命周期函数和匹配规则。其定义位于 include/linux/platform_device.h。

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;
};
成员 作用 音频驱动中的典型用法
probe 设备与驱动匹配成功时调用,完成硬件初始化、DMA缓冲区分配、注册ALSA声卡 初始化I2S控制器、申请DMA通道、创建snd_soc_card
remove 设备移除或驱动卸载时调用,释放资源 释放DMA缓冲区、注销声卡、关闭时钟
shutdown 系统关机或重启时调用 静音音频输出、保存硬件状态
driver 内嵌的通用设备驱动结构体,包含名称、所有者、设备树匹配表(of_match_table) 设置.name = "my-audio-driver",绑定.of_match_table
id_table 非设备树平台下的传统ID匹配表 用于旧版板级代码,现代音频驱动已较少使用
关键点:probe函数是音频驱动的“入口主战场”。几乎所有音频硬件的初始化、DMA请求、中断注册、ALSA组件绑定都在此完成。一个健壮的probe函数需要处理资源获取失败、DMA通道不可用等异常情况。
platform_driver 生命周期与 probe 流程 module_init() platform_driver_register() 设备树匹配 probe() 调用 probe 典型操作 获取资源 → 初始化硬件 注册ALSA → 申请DMA remove() 调用

2.2 platform_device注册:从板级代码到设备树

Platform设备有两种注册方式:传统板级代码和设备树。现代内核(3.x+)已全面转向设备树,但理解传统方式有助于阅读旧代码。

2.2.1 传统板级代码注册(Legacy方式)

在 arch/arm/mach-xxx/ 下的板级文件中,通过 platform_device_register() 静态注册设备。

static struct resource audio_resources[] = {
    {
        .start = 0x10004000,  // I2S寄存器基地址
        .end   = 0x10004FFF,
        .flags = IORESOURCE_MEM,
    },
    {
        .start = IRQ_I2S,     // I2S中断号
        .end   = IRQ_I2S,
        .flags = IORESOURCE_IRQ,
    },
};

static struct platform_device audio_device = {
    .name = "my-audio",
    .id   = -1,
    .num_resources = ARRAY_SIZE(audio_resources),
    .resource = audio_resources,
};

// 在板级初始化函数中调用
platform_device_register(&audio_device);

2.2.2 设备树方式注册(现代标准)

设备树(.dts/.dtsi)将硬件描述与内核代码分离。音频设备节点通常包含寄存器地址、中断、DMA通道、时钟等信息。

/* 设备树片段:I2S控制器节点 */
i2s0: i2s@10004000 {
    compatible = "vendor,my-audio-i2s";
    reg = <0x10004000 0x1000>;
    interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
    dmas = <&dma0 2>, <&dma0 3>;  // TX和RX DMA通道
    dma-names = "tx", "rx";
    clocks = <&clk_audio>;
    clock-names = "audio";
    status = "disabled";  // 默认关闭,由板级文件或bootloader启用
};
常见陷阱:设备树中的 compatible 属性必须与驱动中 of_match_table 定义的字符串完全一致(包括大小写和标点)。status = "okay" 是启用节点的标准写法,不要遗漏。

2.3 设备树匹配机制:驱动与设备的“握手”

设备树匹配是内核在启动或模块加载时,将设备节点与对应驱动关联起来的过程。匹配的核心是 of_match_table。

2.3.1 of_match_table 定义

在驱动中,通过 of_match_table 声明支持的设备兼容性字符串。

static const struct of_device_id audio_of_match[] = {
    { .compatible = "vendor,my-audio-i2s", },
    { .compatible = "vendor,my-audio-i2s-v2", },
    { /* sentinel */ }  // 必须以空项结尾
};
MODULE_DEVICE_TABLE(of, audio_of_match);

static struct platform_driver audio_driver = {
    .probe  = audio_probe,
    .remove = audio_remove,
    .driver = {
        .name = "my-audio",
        .of_match_table = audio_of_match,
    },
};

2.3.2 匹配流程

  1. 设备节点创建:内核解析DTB文件,为每个 compatible 属性不为空的节点创建 platform_device。
  2. 驱动注册:调用 platform_driver_register() 时,内核遍历已注册的设备列表。
  3. 匹配检查:将驱动的 of_match_table 中的每个 compatible 字符串与设备节点的 compatible 属性进行比对。
  4. probe触发:一旦匹配成功,内核调用驱动的 probe 函数,并传入对应的 platform_device 指针。
设备树匹配机制流程图 DTB 解析 创建 platform_device compatible = "vendor,my-audio" 驱动注册 platform_driver_register() of_match_table 列表 内核匹配引擎 遍历 of_match_table 匹配成功? probe() 不被调用 probe() 被调用

2.4 在probe中获取设备树资源

一旦 probe 被调用,驱动需要从 platform_device 中提取设备树描述的硬件资源。这是音频驱动初始化的关键步骤。

static int audio_probe(struct platform_device *pdev)
{
    struct resource *res;
    void __iomem *regs;
    int irq;
    struct device *dev = &pdev->dev;

    // 1. 获取寄存器内存区域
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    regs = devm_ioremap_resource(dev, res);
    if (IS_ERR(regs))
        return PTR_ERR(regs);

    // 2. 获取中断号
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;

    // 3. 获取DMA通道(通过设备树dmas属性)
    struct dma_chan *dma_tx = dma_request_slave_channel(dev, "tx");
    struct dma_chan *dma_rx = dma_request_slave_channel(dev, "rx");
    if (!dma_tx || !dma_rx) {
        dev_err(dev, "Failed to request DMA channels\n");
        return -ENODEV;
    }

    // 4. 获取时钟
    struct clk *audio_clk = devm_clk_get(dev, "audio");
    if (IS_ERR(audio_clk))
        return PTR_ERR(audio_clk);

    // ... 后续初始化ALSA声卡、DMA缓冲区等
    return 0;
}
最佳实践:优先使用 devm_ 系列API(如 devm_ioremap_resource、devm_clk_get),这些资源会在驱动卸载或 probe 失败时自动释放,避免内存泄漏。DMA通道的获取建议在 probe 中完成,而不是在音频流开启时,以减少运行时延迟。

2.5 本章小结

知识点 核心要点 音频开发中的注意事项
platform_driver probe/remove 生命周期、driver 内嵌结构体 probe 中完成所有硬件初始化,避免延迟初始化
platform_device 设备树节点自动生成,传统板级代码手动注册 设备树节点必须包含 reg、interrupts、dmas 等必要属性
设备树匹配 of_match_table 与 compatible 属性精确匹配 compatible 字符串必须完全一致,注意版本号管理
资源获取 platform_get_resource、dma_request_slave_channel 使用 devm_ 系列API自动管理资源生命周期