4.2 PCI配置驱动程序的结构

前面已经说过,PCI设备是一种接口标准,所有PCI设备都要服从这个标准,因此对于操作系统来说,无需为每个设备都单独编写一套驱动程序,而只需要写出一套符合PCI规范的驱动程序就能够满足操作系统配置PCI接口的要求了。从这个意义上来说,PCI接口驱动程序的结构相对要简单一些。

PCI总线驱动主要为上层软件提供两方面的接口,一方面用于系统内PCI设备接口的自动配置,另一方面则是用于系统内PCI设备接口的手工配置。对自动配置来说,它提供了一个自动配置函数,通过调用该函数完成了系统内所有需要自动配置的PCI接口的自动配置,并建立一个专门的数据结构来保存系统中所有自动配置设备的必要的信息;而对于手工配置来说,它也提供了手工配置的一系列操作函数。鉴于目前大多数的设备都采用自动配置的情况,本节只分析自动配置驱动程序结构。

图4.2描述了PCI接口驱动程序的结构。

VxWorks PCI Interface Device Driver Structure

图4.2 PCI接口驱动程序的结构下面将针对图中的各个函数进行详细说明。

4.2.1 基本配置函数库pciConfigLib

该函数库主要提供了一些PCI配置空间访问的一些基本的方法,通过这些底层的访问方法,函数库pciAutoConfigLib就可以完成系统内PCI接口的自动配置。本节还是通过最基本的函数分析研究函数库peiConfigLib提供给上层的基本的访问控制方法。


1. STATUS pciConfigLibInit(mechanism,addr0,addr1,addr2)

该函数主要是配置了PCI配置空间寄存器的访问机制。访问方法主要有三种,比较常用的是访问机制1,这里仅仅对访问机制1进行分析。对PC-AT 兼容的计算机来说,对function 配置寄存器的访问是通过两个标准的IO寄存器进行的。这两个默认的配置寄存器为PCI_CONFIG_ADDR (0xCF8)及PCI_CONFIG_DATA (0xCFC)。访问的过程是这样的,首先向地址PCI_CONFIG_ADDR(IO地址为0xCF8)寄存器中写入要访问的function的配置空间的地址,并在第31位置1,然后对寄存器PCI_CONFIG_DATA (IO地址为0xCFC)进行访问即可。

该函数需要在该库的其他函数被调用前执行,它的主要作用是定义了访问PCI配置空间的访问方法。该函数是通过函数sysHwInit()来调用执行的。该函数执行完毕后就可以调用该库的其他函数进行设备查找、PCI配置空间访问等操作了。


2. pciFindDevice(vendorId,deviceId,index,pBusNo,pDeviceNo,pFuncNo)

查找系统中出现的满足设备号为device ID、制造商号为vendor ID的第index个function,返回其所在的总线好、设备号和function号。

该函数正是通过遍历的方法访问每个function的vendorId寄存器和devicdId寄存器,找到与参数匹配的一个返回其相应的总线号、设备号及function号。

注意:

l ((deviceNo == 0x1f) && (funcNo == 0x07))是一个特殊周期,并没有function存在,无需判断。

l vendorID为0x0000ffff说明设备不存在。


3. pciFindClass(classCode,index,pBusNo,pDeviceNo,pFuncNo)

类似于函数pciFindDevice,查找系统中满足类代码为classCode的第index个function所在的总线号、设备号和函数号。


4. STATUS pciDevConfig

    (

    int pciBusNo,

    int pciDevNo,

    int pciFuncNo,

    UINT32 devIoBaseAdrs,

    UINT32 devMemBaseAdrs,

    UINT32 command

)

该函数通过向选中的设备的配置头写入数据来实现对PCI总线上的function的配置。配置的主要寄存器包括BAR、cache line size寄存器、max latency寄存器。

配置过程:

l 首先通过清除配置头的命令寄存器使fcuntion无效;

l 设置IO或内存空间基地址寄存器、latency timer寄存器和cache line size寄存器(这两个数值都是采用宏定义的数值,和设备无关)。

l 最后通过向命令寄存器装载特定的命令(参数command)重新使设备有效。

注意:

l 该函数仅仅适用于type0设备,也就是普通PCI设备的头标区配置,type1设备(PCI-to-PCI桥)是强制自动配置的。

l 根据PCI规范,可以通过下面的方法确定设备需要的地址空间。先向寄存器写入全1,然后读出该寄存器,根据读回的数值判断该PCI接口映射的是内存地址还是IO地址以及映射地址空间的大小,然后写入相应的映射地址。不过该函数并没有判断所需空间的大小,而是直接写入参数给定的数值devIoBaseAdrs或者devMemBaseAdrs。说明在调用该函数之前就已经确定好该function所需要的配置空间的大小了,这在使用该函数前需要注意。

l 根据PCI规范,有效的BAR都是从0开始配置,如果遇到一个无效BAR,则后面的BAR也是无效的。此外这个函数假定一个function只需要最多一个IO空间和一个mem空间,如果function需要两个IO空间或者两个mem空间,那么该函数将会出现配置错误(这一点在规范里面并没有要求说PCI接口不能映射两个独立的IO空间或者两个内存空间)。


5. pciConfigBdfPack 

    (

    int busNo,    /* bus number */

    int deviceNo, /* device number */

    int funcNo    /* function number */

)

这个函数把三个参数打包以供访问配置地址寄存器使用。

返回值是32bit,其中23~16位是总线号,15~11位是设备号,10~8位是函数号。注意此时配置空间偏移地址为0。


6. STATUS pciConfigOutByte(

int busNo,

int deviceNo,

int funcNo,

int offset,

UINT8 data

)

向配置空间写入配置一字节数据。

采用访问机制1时,PCI配置空间访问是由两个32bit寄存器完成的,这两个寄存器是配置地址寄存器(Configuration AddressRegister,CAR)、配置数据寄存器(Configuration AddressRegister,CDR)。首先向CAR写入32位目标设备PCI总线号和要访问设备的配置寄存器的偏移地址;然后进行CDR的读写。CDR访问可以是8位、16位、32位。CAR和CDR都会被映射到标准的PCI IO空间:CAR=0XCF8,CDR=0xCFC。

注意:在正常情况下,配置空间是按照双字来访问的,因此需要4字节对齐访问,这里需要进行转化。


7. STATUS pciConfigOutWord(

int busNo,

int deviceNo,

int funcNo,

int offset,

UINT8 data

)

向配置空间写入一个16位数据。参见pciConfigOutByte函数。


8. STATUS pciConfigOutLong(

int busNo,

int deviceNo,

int funcNo,

int offset,

UINT8 data

)

向配置空间写入一个32位数据。参见pciConfigOutByte函数。


9. STATUS pciConfigInByte(

int busNo,

int deviceNo,

int funcNo,

int offset,

UINT8 data

)

从配置空间读取一字节数据。参见pciConfigOutByte函数。


10. STATUS pciConfigInWord(

int busNo,

int deviceNo,

int funcNo,

int offset,

UINT8 data

)

从配置空间读取16位数据。参见pciConfigOutByte函数。


11. STATUS pciConfigInByte(

int busNo,

int deviceNo,

int funcNo,

int offset,

UINT8 data

)

从配置空间读取32位数据。参见pciConfigOutByte函数。


12. STATUS pciConfigModifyByte(

int busNo,

int deviceNo, 

int funcNo,

int offset,

UINT8 bitMask,

UINT8 data

)

该函数向PCI配置空间中写入数据data,注意该函数只会修改bitMask为1的位,其他位不受影响。


13. STATUS pciConfigModifyWord(

int busNo,

int deviceNo, 

int funcNo,

int offset,

UINT8 bitMask,

UINT8 data 

)

该函数向PCI配置空间中写入数据data,注意该函数只会修改bitMask为1的位,其他位不受影响。


14. STATUS pciConfigModifyLong(

int busNo,

int deviceNo, 

int funcNo,

int offset,

UINT8 bitMask,

UINT8 data 

)

该函数向PCI配置空间中写入数据data,注意该函数只会修改bitMask为1的位,其他位不受影响。


15. STATUS pciSpecialCycle

(

int busNo,

UINT32 message

)

PCI特殊周期是用于向总线busNo上的一个或多个设备广播数据。操作方法是向总线号busNo、设备号deviceNo = 0x0000001f、funcNo= 0x00000007、配置空间偏移地址0的位置写入message即可。


16. pciConfigExtCapFind

    (

    UINT8 extCapFindId,

    int bus,

    int device,

    int function,

    UINT8 * pOffset

    )

2.1版本以后的PCI规范规定了可以在配置空间中列出设备支持的功能链表。在PCI设备配置空间的状态寄存器的bit4标识了该设备是否具有功能列表,如果有则在配置空间的34H字节处表明了列表头部在配置空间中的偏移量,系统可以按照固定的格式查找链表中的每一个元素。如图:

VxWorks PCI Configuration Space Function Linker Structure

图4.3 PCI配置空间功能链表结构图

链表中的每个功能项包含一个由PCI SIG分配的8位的功能ID、一个8位指针指向配置空间中的下一个功能以及一些附加寄存器(参考PCI规范6.7节)。这些指针都是双字对齐的,因此其指针的低2bit都必须为0。链表尾部的指针为0。

该函数用于查找配置空间的功能链,检查是否具备扩展功能,如果具备则返回配置空间中感兴趣的功能的首字节偏移地址(注意不是ID地址)。


17. STATUS pciConfigForeachFunc

(

UINT8 bus,

BOOL recurse,

PCI_FOREACH_FUNC funcCheckRtn,

void *pArg

)

遍历当前总线上的所有function对其执行函数(*funcCheckRtn)(pArg)。如果函数返回ERROR,将终止进一步的操作。


18. LOCAL STATUS pciFuncDisable

(

UINT bus,

UINT device,

UINT function

void * pArg

)

该函数比较简单,直接将配置空间命令寄存器中的PCI_CMD_IO_ENABLE、PCI_CMD_MEM_ENABLE、PCI_CMD_MASTER_ENABLE位置0,其他命令位不变,状态位清0。此时PCI功能关闭。


19. STATUS pciConfigReset

(

int startType /* for reboot hook, ignored */

)

对各个系统中的所有PCI function运行pciFuncDisable函数。