
先说个最近的实际场景。我在基于瑞芯微RK3568的板子上调一个双路CAN驱动板子上两路CAN控制器用的是同一个IP设备树里也确实是两个独立节点看起来工程量不大。可真动手写驱动的时候才发现一个Linux驱动要同时管好两个硬件实例坑比想象中多。最初图省事在驱动里用了一个全局变量保存寄存器基地址结果第二路设备probe进来之后第一路的地址被覆盖中断回调里读到的寄存器全是第二路的现场用惨不忍睹来形容一点不过分。这篇文章就是围绕这个主题展开的。Linux驱动支持多设备说到底是两件事第一驱动怎么“认识”多个硬件节点第二驱动怎么把每个硬件节点各自的运行状态分开记不互相串扰。文章会给出两个亲测好用的技巧都基于瑞芯微平台的真实开发环境适合正在做嵌入式Linux驱动、或者准备在RK系列芯片上做二次开发的工程师参考。文章里涉及的代码、排查思路我尽量按实际调试时的真实过程来写。1. 先摸清底细驱动与设备原本就是“一对多”不是“一对一”1.1 多设备场景在瑞芯微平台上有多常见有人可能会觉得一个驱动管一个设备不是挺正常的吗为什么非要追求“一驱多设备”如果你只看过那种教学例程确实容易产生这种错觉。但瑞芯微平台的实际情况是一颗芯片上集成了大量同类型的外设控制器而且同一个IP会被反复例化多次。以RK3568为例CAN控制器有俩UART可能有八九路I2C、SPI的控制器数量也不少。你在设备树里看到的节点往往是这样的can0节点、can1节点或者uart0到uart7各一个节点。这些节点的compatible字符串经常是同一个背后对应同一个平台驱动但每个节点拥有独立的寄存器地址、独立的中断号、独立的时钟和引脚配置。换句话说一个驱动被probe多次是Linux驱动模型下的默认行为不是需要什么特殊魔法才能做到的事情。问题只在于很多人在写驱动的时候并没有意识到这一点用的是“一个驱动、一份全局数据”的老思路于是多设备一接入就乱了套。1.2 全局变量管理多设备状态为什么必挂前面我说的全局变量覆盖问题不是偶发是必然会遇到的问题。驱动被probe几次不代表顺序是串行安全的。设备模型在枚举的时候可能连续触发两次probe第二次probe执行到priv_base ioremap(...)时第一次probe里辛辛苦苦保存的地址就被覆盖掉了。更麻烦的是中断上下文。中断是异步发生的你没法保证它触发的时候使用的正是对应设备的全局变量。一旦两个设备共用一个中断处理函数而处理函数里读的是同一个全局变量那中断来了之后你到底该操作哪个设备完全取决于这个变量最后被哪次probe写过不确定性极大。这还只是地址覆盖的问题。如果全局变量是一个数组比如struct mydev *devs[2]你确实可以通过索引区分设备了但接下来要面对的是并发保护、数组越界、设备热插拔带来的空指针等一系列麻烦。我在实际项目中见过不少因为这