多个PCI设备在同一系统中共存时,驱动如何区分它们,是不少开发者刚入门时踩坑最多的地方。我自己也曾经花了好几天才弄清楚PCI配置空间和设备ID到底是怎么匹配的。写这篇文章,就是想把这些经验整理出来,帮大家少走弯路。

系统如何识别多个PCI设备

Linux内核在启动时会扫描PCI总线,把所有挂在上面的设备都找出来。每个PCI设备都有一个唯一的组合:Vendor ID、Device ID、Class Code,再加上总线号和设备号,内核靠这些信息把它们区分开。

多个pci设备 linux驱动_苏州通润驱动设备_驱动设备无法启动代码10

当多个相同的PCI设备插在系统里时,它们可能拥有相同的Vendor ID和Device ID。这时候就要靠总线位置来区分了。比如两个同样的网卡,一个在PCI总线0上,一个在总线1上,驱动可以通过读取pci_dev->devfn拿到各自的位置信息。

在实际项目中,我遇到过一块板子上焊了两颗一样的FPGA,都是通过PCIe接进来的。一开始驱动识别时两个设备都匹配同一个驱动入口,但操作时总是互相干扰。后来发现是因为没有正确区分设备实例,导致寄存器地址映射混乱。这个问题只有通过记录每个设备的struct pci_dev指针来隔离操作才能解决。

驱动如何管理多个设备实例

probe函数里arm linux,每匹配到一个PCI设备linux服务器维护,内核就会分配一个新的struct pci_dev结构体,然后调用一次驱动中的probe。所以如果你的驱动支持多个同型号设备,每个设备都会触发一次独立的probe调用。

苏州通润驱动设备_多个pci设备 linux驱动_驱动设备无法启动代码10

这就需要在驱动里设计好数据结构来管理多个实例。常见的方式是定义一个设备私有结构体,里面存放每个设备的基地址、中断号、状态等信息。然后在probe里用kzalloc分配这个结构体,用pci_set_drvdata()把它挂到pci_dev上,这样在后续的openreadwrite操作中能通过pci_get_drvdata()拿回来。

我做过一个项目,驱动要管理4个DMA控制器,每个控制器都是一个独立的PCI设备。我定义了一个数组,在probe里按顺序把每个设备的私有数据填入数组,并在结构体里标明该设备是第几个。这样上层应用通过文件节点操作时,驱动就能根据传入的文件对象找到对应的设备实例。

设备中断共享时如何区分

多个PCI设备共享同一个中断号是常见的事情,特别是在老式PCI或者嵌入式系统里。共享中断意味着多个设备的中断引脚连到了同一个IRQ线上,当中断到来时,内核会依次调用所有注册在该IRQ上的中断处理函数。

驱动设备无法启动代码10_多个pci设备 linux驱动_苏州通润驱动设备

这种情况下,中断处理函数里必须能判断中断是否来自自己的设备。做法是在中断服务程序里读取设备的中断状态寄存器,如果状态寄存器显示不是本设备产生的中断,就返回IRQ_NONE;如果是,则处理完中断后返回IRQ_HANDLED

千万不能在中断处理函数里假设“只要调用了我,就一定是我的设备”。这种假设在共享中断场景下会导致误操作,比如错误地清除了其他设备的中断标志位,造成那个设备永远收不到中断。我在调试一个四路视频采集卡时就踩过这个坑,后来加上状态判断才稳定下来。

设备热拔插场景下的处理

PCIe设备支持热拔插,这在服务器和工业设备中越来越普遍。热拔插意味着设备可能在系统运行时随时插入或拔出,驱动必须能处理设备突然消失的情况。

苏州通润驱动设备_驱动设备无法启动代码10_多个pci设备 linux驱动

remove函数里,要释放所有申请的资源pci_iounmap释放映射的寄存器地址,pci_release_regions释放I/O或内存资源,kfree释放私有数据结构体,最后pci_disable_device关闭设备。如果设备在操作过程中被拔掉,驱动在读寄存器时可能会返回全F,这时候需要做超时或错误检测,避免系统挂死。

有个实际案例是热插拔固态硬盘的PCIe驱动。当硬盘被拔出时,如果上层还有未完成的读写请求,驱动要在remove中把这些请求全部标记为失败并唤醒等待的进程,否则进程会永远阻塞下去。这一块我见过很多初学者忽略,导致系统在拔设备后出现僵尸进程。

性能调优和多设备协同

当系统中存在多个PCI设备同时工作时,性能瓶颈往往出现在PCI总线的带宽上。每个PCI设备都会占用一定的总线带宽,如果多个设备同时进行大数据量传输多个pci设备 linux驱动,可能互相抢占带宽,导致整体吞吐量下降。

多个pci设备 linux驱动_苏州通润驱动设备_驱动设备无法启动代码10

解决办法之一是合理设置设备的DMA传输粒度,避免小包频繁传输。另一个方法是在设备允许的情况下设置不同的优先级,或者把不同类型的数据分配到不同的PCI总线上。比如在一个数据采集系统里,我把高速的数据采集卡放在独立的总线上,控制类的低速设备放到另一条总线,这样互相不干扰。

驱动层面也可以做一些调度多个pci设备 linux驱动,比如用一个工作队列来排队多个设备的DMA请求,避免同时发起大量传输导致总线拥塞。我优化过一个图像处理系统,四路摄像头的数据同时通过PCIe传输,最初帧率只有15fps,加入传输队列和优先级控制后提升到了30fps。

写驱动的时候多用dmseg查看内核打印,配合lspci -vvv看设备的配置空间,能帮你快速定位问题。多设备场景下,重点就是把每个设备的实例管理好,资源释放干净,中断处理严谨,性能调优做到心中有数。

Tagged:
Author

这篇优质的内容由TA贡献而来

刘遄

《Linux就该这么学》书籍作者,RHCA认证架构师,教育学(计算机专业硕士)。

发表回复