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提供给上层的基本的访问控制方法。

4.1 概述

PCI接口是一种DMA接口,通过DMA接口,CPU和外设能够进行内存的共享,这样CPU访问外设时只需要访问计算机系统的内存就可以了,这样做的一个重要的目的就是增加外部设备的自主性,在外部设备工作时可以不需要CPU的参与,从而省下了宝贵的CPU资源,而当CPU需要访问外部数据时,直接对相应内存的访问就可以完成,这样也增加了CPU与外部设备的数据交换的速度。因为以上的原因,PCI接口得以大量应用。

另一方面,PCI接口只是一种接口标准,通过这个接口标准可以完成数据的快速访问,但是PCI接口并不是一个完整的外部设备,而只是外部设备和CPU通信的一个接口标准而已,以后我们分析的PCI网卡,USB总线控制器等等这些设备除了PCI总线的驱动之外,还需要自己特殊的驱动。

本章不准备过多描述PCI规范的内容,只针对与PCI总线软件紧密相关的的接口配置部分做个简单描述,如果有兴趣的话可以参考相关的资料,如《PCI Local Bus Specification》。

PCI总线采用了即插即用协议,即在系统建立时由操作系统按照各设备的要求统一分配资源,资源分配的信息由系统写入各PCI设备的配置空间寄存器,并在操作系统内部备份。各PCI设备有其独自的配置空间,硬件设计者通过设置PCI设备(或插槽)的IDSEL引脚区分不同设备的配置空间。

PCI总线配置空间是容量为256字节并具有特定记录结构的一个地址空间。该空间分为头标区和设备相关区两部分,在每个区中,设备只设置必须的和与之相关的寄存器,一个设备的配置空间不仅在系统自举时可以访问,在其他时间也可以访问。

头标区的长度为64字节,偏移地址从00H到3FH,该区中的各个域用来识别各自不同的设备,并使设备能以一般的方法控制。每个设备都必须按照该区的格式和规定进行设置,而配置空间的其余192字节可以因设备而异。

通过系统配置软件,可以进行PCI总线的系统配置。进行配置时,是通过配置空间头标区中的寄存器来实现的,这些寄存器的具体格式与具体的设备有关,但必须遵守一些共同的规则。

图4.1为PCI配置空间头标区的结构图:

3.3.2 函数库intALib

函数库intALib是个汇编函数库,里面保存了与CPU结构相关的中断处理函数。这里进行简单的分析。

1.int intLevelSet (level)
x86系列的CPU并不支持中断级,因此该函数是个空函数。
2. int intLock ()

该函数返回刚intLock函数时CPU的EFLAGS寄存器中的中断允许标志位(IF),然后将中断允许位置0,表示禁止可屏蔽中断。如果需要恢复,则调用函数intUnlock(int)将中断允许标志恢复到intLock调用之前的状态。

intLock()函数既可以在中断级又可以在任务级调用,当在任务级调用时,并不能组织任务的调度,如果需要组织任务调用则需要使用函数taskLock和taskUnlock函数。

3. int intUnlock()
恢复intLock函数调用前CPU中EFLAGS寄存器的中断允许标志。
4. void intVBRSet(baseAddr)
将参数baseAddr指定的中断向量基地址保存到CPU的IDT寄存器中。
5. void intBoiExit()
直接从routineBoi函数中跳转到intExit函数且不再执行C中断函数和routineEoi函数(参见函数库i8259Intr中函数i8259IntBoi的分析)。

3.3.3 函数库i8259Intr

函数库i8259Intr中提供了对8259中断控制器的基本操作函数。分析这个函数之前,有几个问题需要注意:

中断号(IntNum)和IRQ号的关系。同一PIC上的IRQ号和中断号是线性关系,如主片上IRQ0对应中断号0x20,主片上的IRQ1对应中断号0x21等;从片上的IRQ0对应与中断号0x28,从片上的IRQ1对应中断号0x29等。

中断号(IntNum)和中断优先级没有必然的联系。这主要体现在有级联的中断上,加入从片的中断输出连接到中断的IRQ2上,那么主片上IRQ0和IRQ1的中断优先级总高于从片上的中断;而主片上IRQ3~IRQ7的中断优先级总低于从片上的中断。

表3.1给出了中断号、IRQ号与优先级的关系。主从片连接图参见图3.1。

表格 3.1 PC机对应的中断号、IRQ号与优先级的关系

3.4 中断驱动程序的动态分析

3.4.1 中断驱动程序的初始化

中断驱动程序结构比较简单,因此该驱动的构建过程也比较简单,几个函数库都是静态构建的,唯一需要动态创建的就是中断控制芯片的初始化。

在函数sysHwInit()中调用了函数sysIntInitPIC ()完成了中断控制芯片的初始化,并指定intEoiGet = sysIntEoiGet。此后就可以正常使用中断驱动了。如图3.7。

3.3.1 函数库intArchLib

函数库主要为上层应用程序提供了一组与外部硬件无关的中断控制函数。其中比较常用的就是函数intConnect、intEnable、intDisable。


1. STATUS intConnect

    (

    VOIDFUNCPTR *vector,

    VOIDFUNCPTR routine,

    int parameter

    )

该函数的作用是将一个C函数与一个硬件中断连接起来,当发生了硬件中断时,自动执行该C函数。

它主要完成了三件事情,一是调用函数(* intEoiGet)(......)获取该中断的ISR处理前后需要执行的函数routineBoi、routineEoi及其参数;

二是调用函数intHandlerCreateI86填充数组intConnectCode,主要填充routineBoi、routine、routineEoi,组成完整的中断执行代码;

三是调用函数intVecSet将数组intConnectCode存放到参数vector指定的位置。

注意:代码中调用intHandlerCreate的情况属较老的vxWorks版本,对于新版本已经被intHandlerCreateI86函数所代替,因此不再过多分析。