VxWorks PCI驱动

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设备(或插槽)的ISDEL引脚区分不同设备的配置空间。

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

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

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

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

                                        图 4.1 PCI设备配置空间头表区结构

        下面以PCI网卡配置芯片AM79C97X为例对配置空间头标区各个控制状态字做个简单说明。表4.1为AM79C97X网卡控制芯片PCI配置空间头表区的寄存器设置。

        表格 4.1 AM79C97X网卡配置芯片中PCI配置空间头表区的寄存器设置

地址

寄存器名

长度(byte)

读写

am79c97x芯片

的相应设置

00h

Vendor ID

2

RO

1022h

02h

Device ID

2

RO

2000h

04h

Command 

2

RW

0000h

06h

Status 

2

RW

0280

08h 

Revision ID

1

RO

10h

09h

Programming interface 

1

RO

00h

0Ah

Sub-Class 

1

RO

00h

0Bh

Base-Class 

2

RO

02h

0Ch

Cache Line Size 

1

RW

0Dh

Latency Time 

1

RW

00h

0Eh

Header Type 

1

RO

00h

10h

Reserved

1

RO

00h

10h

Base 

4

RW

0000 0001h

14h

Base 

4

RW

0000 0000h

18h

Base 

4

RO

保留,00h

1Ch

Base 

4

RO

保留,00h

20h

Base 

4

RO

保留,00h

24h

Base 

4

RO

保留,00h

28h

Cardbus CIS Pointer

4

RO

保留,00h

2Ch

Subsystem vendor ID

2

RO

00h

2Eh

Subsystem ID

2

RO

00h

30h

Expansion ROM Base Address 

4

RW

0000 0000h

34h

Capabilities Pointer

1

RO

00h

35h

Reserved

3

RO

00h

38h

Reserved

4

RO

00h

3Ch

Interrupt Line 

1

RW

00h

3Dh

Interrupt Pin 

1

RO

01h

3Eh

MIN_GNT 

1

RO

06h

3Fh

MAX_LAT 

1

RO

FF

        在分析PCI设备配置空间之前,首先需要弄清楚几个概念:

        PCI存储空间不同于PCI配置空间。PCI存储空间是作为计算机系统和PCI设备通信的通道,该通道使得计算机可以直接像访问内存或者IO端口一样访问PCI设备,包括该设备的特殊功能寄存器,以及用做数据缓冲和存储区域。

        PCI存储空间分为内存空间和I/O空间两类,它们独立寻址,并使用不同的总线操作命令进行访问。MEMORY空间适用于设备功能寄存器较多或数据流量较大的场合,例如网口芯片、PCI-PCI 桥等。I/O空间适用于设备功能寄存器较少或数据流量较小的场合,例如串口芯片、LED控制寄存器等。但在地址空间比较充裕的条件下,也有把范围较小的存储区放入MEMORY空间的做法,因为与I/O空间的访问相比,内存空间的访问速度要快得多。

        配置寄存器组中会有一个或多个基址寄存器BAR(Base Address Register),用于保存存储空间的指针。BAR中可读不可写的位数决定了该存储空间的大小,BAR的最低位是只读位,它定义了BAR的空间属性。当BAR描述的是内存存储空间时,该位读回为0;当BAR描述的是I/O存储空间时,该位读回为1。由于BAR的这一属性,使得PCI设备占用的存储区域是放在内存空间,还是放在I/O空间,完全由设备制造商决定,用户无法修改。

        单个设备/功能的PCI内存空间的容量可选取16byte~2Gbyte,但为了减少参与地址译码的地址线数量,PCI规范建议MEMORY空间分配不小于4Kbyte;单个设备/功能的PCI I/O空间的容量可选取4byte~256byte ,不允许超过256byte。

        映射到I/O空间的基地址寄存器总是32位,其中位0恒为1(由硬件实现),位1为保留位,其余各位用来把设备映射到I/0空间。

        映射到存储空间的基地址寄存器可以是32位,也可以是64位。其中位0通过硬件设置使其恒为0。而位2和位1用来表示映射类型:00表示基地址寄存器宽32位,可以映射到32位空间的任何位置;01表示映射基地址寄存器宽32位,但只能映射到32位空间的低1MB范围内;10表示基地址寄存器宽64位,可以映射到64位表示的存储器空间的任何位置;11为保留编码。

        在配置空间中,从偏移地址10H开始,为基址寄存器分配了6个双字单元的位置。第一个基址寄存器总是在10H处,第二个基址寄存器要根据第一个的长度来决定它的偏移地址,可能是14H或18H。依此类推,后面的基址寄存器的偏移地址都要由前面寄存器的大小来决定。设备根据需要设置基址寄存器的类型及个数。

        设备通过设定可读写的高位数值来向操作系统指示所需资源空间的大小。比如,某设备需要64K字节的内存空间,可以将配置空间的某基地址寄存器的高16位设成可读写的,而将低16位置为0(只可读)。操作系统在建立时,先向所有位写1,实际上只有高16位被接受而被置成了1,低16位仍为0.这样操作系统读取该寄存器时,返回值为FFFF0000h,据此操作系统可以断定其需要的空间大小是64K字节,然后分配一段空闲的内存空间并向该寄存器的高16位填写其地址。

2 PCI配置驱动程序的结构

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

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

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

                                                    图4.2 PCI接口驱动程序的结构

        下面将针对图中的各个函数进行详细说明。

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号。

注意:

  •  ((deviceNo == 0x1f) && (funcNo == 0x07))是一个特殊周期,并没有function存在,无需判断。
  • lvendorID为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寄存器。

配置过程:

  • 首先通过清除配置头的命令寄存器使fcuntion无效;
  • 设置IO或内存空间基地址寄存器、latency timer寄存器和cache line size寄存器(这两个数值都是采用宏定义的数值,和设备无关)。
  • 最后通过向命令寄存器装载特定的命令(参数command)重新使设备有效。

注意: 

  • 该函数仅仅适用于type0设备,也就是普通PCI设备的头标区配置,type1设备(PCI-to-PCI桥)是强制自动配置的。
  • 根据PCI规范,可以通过下面的方法确定设备需要的地址空间。先向寄存器写入全1,然后读出该寄存器,根据读回的数值判断该PCI接口映射的是内存地址还是IO地址以及映射地址空间的大小,然后写入相应的映射地址。不过该函数并没有判断所需空间的大小,而是直接写入参数给定的数值devIoBaseAdrs或者devMemBaseAdrs。说明在调用该函数之前就已经确定好该function所需要的配置空间的大小了,这在使用该函数前需要注意。
  • 根据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字节处表明了列表头部在配置空间中的偏移量,系统可以按照固定的格式查找链表中的每一个元素。如图:

                                               图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函数。

2.3 PCI中断处理函数库pciIntLib

        在库文件src\dev\pci\pciIntLib.c中描述了PCI设备的中断处理方法。这个函数库并不是在PCI自动配置的时候调用,因为这时候各PCI设备的中断处理函数还不明确,只能是作为具体PCI设备初始化的时候调用该函数库从而完成该中断向量初始化功能。

        根据PCI规范,PCI设备function的中断可以连接到4个IRQ中断的一个,对系统的PCI总线来说,所有设备中断都将连接在这4根IRQ上,因此当一个PCI中断IRQ信号到来时,系统并不知道具体是那个PCI function发生了中断,它必须调用系统中所有中断号为IRQ的function,这也就是中断处理函数pciInt要做的工作。

        当CPU发现了一个PCI总线上的中断,它并不知道到底是那个设备中断的,为了处理的方便,系统构造了一个数组pciIntList,这个数组中一共有4个元素,分别对应于IRQ的4个中断。每个元素就是一个链表头,该链表中链接的是系统中所有中断IRQ号为function的中断处理函数。图4.9表示pciIntList[0]链表对应的数据结构。

                                                        图4.9 pciIntList[0]链表对应的数据结构

1. pciIntLibInit()函数

                                                        初始化pciIntList[PCI_INT_LINES]。如图4.10所示。

                                        图4.10 pciIntLibInit初始化的数据结构

2. VOID pciInt(int irq)

        运行pciIntList[irq]链上的各个元素中的函数。参见图4.9。

3. STATUS pciIntConnect(VOIDFUNCPTR *vector,VOIDFUNCPTR routine,int parameter)

        如图4.9的结构,给定了vector,找到相应的pciIntList[irq]链表,将参数指定的中断处理函数插入到pciIntList[irq]链表中去,如果之前pciIntList[irq]链表为空则需要调用函数intConnect将pciInt安装在相应的中断向量位置,以后每次需要在链表中增加相应的中断处理函数即可。

4. STATUS pciIntDisconnect(

            VOIDFUNCPTR *vector,        /* interrupt vector to attach to     */

            VOIDFUNCPTR routine         /* routine to be called              */)

        pciIntConnect函数的逆过程。从pciIntList[irq]链表中删除routine函数,此时如果pciIntList[irq]为空,则unbind该pciInt(irq)函数。

5. pciIntDisconnect2()函数

        和函数pciIntConnect相同,只是不光是查找函数,还要查找参数,必须是函数和参数都相同才能够删除。

2.4 PCI总线配置函数库

        在vxWorks系统中的函数库config/templatex86/sysBusPci.c是提供给用户进行pci总线及设备配置参数的一个入口,它负责设置PCI的配置参数设置并调用自动配置函数。

1. LOCAL STATUS sysPciAutoConfigInclude(PCI_SYSTEM *pSys,PCI_LOC *pciLoc,                                                                                            UINT     devVend)

        该函数列举出不支持自动配置的设备,返回ERROR。对于自动支持的设备则返回OK。

        用户可以通过对这个函数进行定制以确定那些设备需要自动配置,那些不需要。默认都需要自动配置。

2. LOCAL UCHAR sysPciAutoConfigIntAsgn(PCI_SYSTEM * pSys,    PCI_LOC * pFunc,                                                                                      UCHAR intPin)

        这个函数的作用是由用户定义指定PCI设备IRQ号的函数。

        它主要是来自一个数组intLine,intLine是一个16×4的阵列,其中每一行对应的是一个设备号(0~15),每一列则是对应的是function对应的4个中断输出线INTA、INTB、INTC、INTD,这个是需要用户定义的。

注意:PCI设备的Int pin配置寄存器是只读的,也就是说是PCI设备在出厂的时候就已经确定的。不过它们并不是直接连接到计算机系统的IRQn上,而是通过PCI总线连接到IRQn上的,通过硬件连接及int pin配置寄存器的就可以控制function中断连接到一个指定的IRQ上。

3. void sysPciAutoConfig (void)

        初始化配置参数PCI_SYSTEM sysParams,并调用函数pciAutoConfig()进行自动配置。该函数被函数中sysHwInit所调用,从而完成了PCI总线的自动配置。

3 PCI总线配置过程

        在文件config/pcpentium/sysLib.c中,函数sysHwInit()揭示了PCI的初始化配置过程:首先,函数sysHwInit()调用了pciConfigLibInit (PCI_MECHANISM_1, PCI_CONFIG_ADDR, PCI_CONFIG_DATA, NONE),它定义了PCI配置空间的访问方法。

        然后调用函数sysPciIntInit (),它对PCI中断的数据结构进行了初始化。

        然后是调用函数sysPciAutoConfig(),用于初始化配置参数PCI_SYSTEM sysParams,并调用数pciAutoConfig()进行自动配置。图4.11为PCI自动配置过程。

        从本章的分析可以看出,PCI接口驱动的核心就是根据每个PCI设备的去求为其分配特定的IO或内存资源,利用DMA来实现CPU和外部设备之间的快速通信。关于PCI接口设备驱动的例子,将在后面的章节中结合网卡驱动和USB控制器驱动进行详细分析,其中也包括PCI设备的中断的设置。

                                      图4.11 PCI总线的初始化配置过程

引用:第4章 VxWorks PCI驱动

  • 14
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值