浅析Linux下PCI设备驱动的访问
本文主要是基于笔者最近在实现Linux系统中PCI设备驱动过程中的一些学习总结,总共分成三部分,下面依次对这三部分进行介绍。
一、PCI总线
众所周知,PCI总线体系结构是一种层次式的体系结构,而在这种结构中,PCI桥设备则占据着重要的位置,它将父总线与子总线连接在一起,从而使整个系统看起来像一颗倒置的树形结构。树的顶端是系统的CPU,它通过一个较为特殊的PCI桥设备—Host/PCI桥设备与根PCI总线连接起来,如下图1所示。可以看出CPU通过Host/PCI桥与一条PCI总线相连,处在这种位置上的PCI总线为根总线。PC机中通常只有一个Host/PCI桥,在一条PCI总线的基础上,可以再通过PCI桥连接到其他次一层的总线。
图1
而 在Linux系统中,当前存在的所有根总线都通过pci_bus结构体中的node成员链接成一条全局的根总线链表,pci_bus结构体如图2所示,其在linux\include\pci.h下定义。node表头由list类型的全局变量pci_root_buses来描述。而根总线下面的所有下级总线则都通过pci_bus结构体中的node成员链接到其父总线的children链表中,如图3所示。
图2
图3
二、PCI设备
对于所有种类的PCI设备来说,它们都可以用内核中pci_dev结构体来描述,图4给出了结构体中的部分内容,其在linux\include\pci.h下定义。由于一个PCI接口卡上可能包括多个功能模块,每个功能都被当作一个独立的逻辑设备,因此,每一个PCI逻辑设备都唯一地对应一个pci_dev结构体。同时,在pci_dev结构体中的bus_list成员将当前PCI总线上的PCI设备链接成一个链表,表头则有该PCI总线的pci_bus结构体中的devices成员所定义,如图5所示。
图4
图5
三、访问PCI设备驱动
关于访问系统中的任意PCI设备,就笔者目前所掌握的知识来说,大体可分为三种方法。
①
②
id_table表示驱动模块所针对的硬件设备。probe函数负责硬件的检测工作并保存配置信息。remove表示驱动模块移除时移除具体的设备的操作。suspend表示设备挂起,resume表示唤醒设备。
如何获取到PCI桥设备,一种可通过查询PCI手册了解PCI桥设备的Base Class,如图6是摘自PCI Local Bus Specification Revision 3.0手册。
图6
通过这段描述,结合Class Code的编码规则:bit[7:0]为编程接口,bit[15:8]为子类别代码,bit[23:16]为基类别代码,bit[31:24]无意义。组合得知PCI-PCI桥的Class Code为0x00060400。由于PCI-PCI桥是用于PCI主总线与次总线的。结合本文前两部分内容,PCI设备之间是以链表形式链接的,因此通过遍历系统所有PCI桥设备,实现对任意PCI设备的访问。
另一种则是利用内核中的提供PCI桥设备的宏定义,其在linux\include\linux\pci_ids.h中定义,如图7所示,事实上还是和手册相关的,只是内核将其宏常量化,便于程序的理解。
图7
③
有了这三点,我们知道在较低版本的内核中pci_dev结构体保留的global_list这个成员变量确实用处不大。当然,由于笔者水平所限,可能并不止这三种方法,望能与各位读者相互交流,共同进步。
至此,关于Linux下PCI设备的访问的内容介绍的差不多了,事实上,关于Linux系统下PCI设备驱动的开发还是有相当一部分内容值得研究,包括内核中实现的关于PCI设备的枚举,PCI设备的中断处理等内容,后续若有时间定要在深入研究。