单个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
相关产品推荐
相关产品推荐

