You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单个platform_driver如何绑定多个设备树平台设备?

BeagleBone UART+GPIO双平台设备驱动适配指南

Linux内核平台驱动框架下,单个platform_driver支持匹配多个compatible对应的设备,不需要拆分多个驱动模块,按以下步骤实现即可。

核心注意点

不要直接在业务逻辑里假设两个设备的probe顺序,内核加载设备的顺序不固定,必须加状态标记确认两个设备都初始化完成后,再启动GPIO采集、UART转发、用户空间交互的业务逻辑。


步骤1:补全设备树匹配表

在原有hw_match_table里新增gpio_device的匹配项,给每个匹配项绑定类型标记,方便probe函数区分当前初始化的是哪个设备。注意匹配表末尾必须留空的哨兵项,否则内核遍历匹配表时会出现内存越界。

// 定义设备类型枚举,用于probe阶段区分设备
enum hw_dev_type {
    DEV_UART,
    DEV_GPIO
};

static const struct of_device_id hw_match_table[] = {
    {
        .compatible = "serial",
        .data = (void*)DEV_UART
    },
    {
        .compatible = "gpio_device",
        .data = (void*)DEV_GPIO
    },
    { /* 哨兵项,不可删除 */ }
};
// 导出设备树匹配表,确保内核可以自动匹配设备
MODULE_DEVICE_TABLE(of, hw_match_table);

步骤2:定义全局驱动上下文

因为UART和GPIO是两个独立的平台设备,会分别触发probe回调,需要定义全局上下文结构,存储两个设备初始化后拿到的寄存器地址、中断号、GPIO描述符等资源,同时标记两个设备的初始化状态。

struct hw_drv_ctx {
    void __iomem *uart_reg_base;  // UART寄存器映射基地址
    int uart_irq;                 // UART中断号
    struct gpio_desc *data_gpio;  // 采集数据用的GPIO描述符
    bool uart_ready;              // UART初始化完成标记
    bool gpio_ready;              // GPIO初始化完成标记
    struct cdev user_cdev;        // 与用户空间交互的字符设备
    struct task_struct *gpio_poll_task; // GPIO数据采集任务
};
static struct hw_drv_ctx g_drv_ctx;

步骤3:改造probe回调函数

probe触发时先读取匹配项里的设备类型标记,分别执行UART、GPIO的初始化逻辑,两个设备都初始化完成后,再启动数据采集、字符设备注册等业务逻辑。

static int Serial_probe(struct platform_device *pdev)
{
    const struct of_device_id *match = of_match_device(hw_match_table, &pdev->dev);
    enum hw_dev_type dev_type = (enum hw_dev_type)match->data;

    if (dev_type == DEV_UART) {
        // 原有UART初始化逻辑:映射寄存器、申请中断、配置串口参数
        g_drv_ctx.uart_reg_base = devm_platform_ioremap_resource(pdev, 0);
        g_drv_ctx.uart_irq = platform_get_irq(pdev, 0);
        // 补充你原来的8250串口初始化代码
        g_drv_ctx.uart_ready = true;
        dev_info(&pdev->dev, "UART device initialized\n");
    } else if (dev_type == DEV_GPIO) {
        // GPIO初始化逻辑:申请GPIO、配置输入模式、如需中断则申请GPIO中断
        g_drv_ctx.data_gpio = devm_gpiod_get(&pdev->dev, NULL, GPIOD_IN);
        // 补充GPIO上下拉、中断触发方式配置代码
        g_drv_ctx.gpio_ready = true;
        dev_info(&pdev->dev, "GPIO device initialized\n");
    }

    // 双设备都初始化完成后启动业务
    if (g_drv_ctx.uart_ready && g_drv_ctx.gpio_ready) {
        // 1. 注册字符设备,提供read接口给用户空间读取数据
        // 2. 启动内核线程/中断处理流程,读取GPIO电平数据
        // 3. 配置UART发送逻辑,将采集到的数据转发同时上报用户空间
        dev_info(&pdev->dev, "All hardware ready, start data transfer\n");
    }

    return 0;
}

原有platform_driver的定义不需要修改,保持原有结构即可,of_match_table已经关联了补全后的匹配表,会自动匹配两个设备。

步骤4:改造remove回调函数

设备卸载时同样根据设备类型分别清理对应资源,两个设备都卸载完成后,再释放字符设备、内核线程等公共资源。

static int Serial_remove(struct platform_device *pdev)
{
    const struct of_device_id *match = of_match_device(hw_match_table, &pdev->dev);
    enum hw_dev_type dev_type = (enum hw_dev_type)match->data;

    if (dev_type == DEV_UART) {
        g_drv_ctx.uart_ready = false;
        // 关闭UART时钟、释放中断(如果未使用devm接口申请需要手动释放)
    } else if (dev_type == DEV_GPIO) {
        g_drv_ctx.gpio_ready = false;
        // 释放GPIO中断、停掉GPIO采集任务
    }

    if (!g_drv_ctx.uart_ready && !g_drv_ctx.gpio_ready) {
        // 注销字符设备、清理全局上下文
        cdev_del(&g_drv_ctx.user_cdev);
        memset(&g_drv_ctx, 0, sizeof(g_drv_ctx));
    }
    return 0;
}

开发注意事项

  • 资源申请优先使用devm_*系列内核接口,驱动卸载时内核会自动释放相关资源,减少资源残留、内存泄漏问题
  • 不要在probe回调里写死循环轮询GPIO电平,会阻塞内核设备注册流程导致系统挂死,要用内核线程、工作队列或者GPIO中断触发的方式采集数据
  • 内核空间向用户空间传递数据时,不要直接访问用户态指针,要通过字符设备的文件操作接口,使用copy_to_user完成数据拷贝
  • 设备树里的compatible字符串要和驱动里的定义完全一致,大小写、标点不能有差异,否则会出现设备匹配失败的问题

内容的提问来源于stack exchange,提问作者Yael

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 15:18:15