PCI接口开发笔记

1. PCI 简介

PCI 总线标准是一种将系统外部设备连接起来的总线标准,是 PC 中最重要的总线,实际上是系统的各个部分如何交互的接口。传输速率可达到 133MB/s。在当前的 PC 体系结构中,几乎所有的外部设备采用的各种各样的接口总线,均是通过桥接电路挂接到 PCI 系统上。在这种 PCI 系统中, Host/PCI 桥称为北桥,连接主处理器总线到基础 PCI 局部总线。 PCI 与其他总线的接口称为南桥,其中南桥还通常含有中断控制器、IDE 控制器、USB 控制器和 DMA 控制器等。南桥和北桥组成主板的芯片组。

2. PCI配置空间
每个PCI设备都有自己的配置空间,用于支持即插即用,使之满足现行的系统配置结构。下面对PCI配置空间做一下简要介绍。

配置空间是一容量为256字节并具有特定结构的地址空间。这个空间又分为头标区和设备有关区两部分。头标区的长度是64字节,每个设备都必须配置该区的寄存 器。该区中的各个字段用来唯一地识别设备。其余的192字节因设备而异。

为了实现即插即用,系统可根据硬件资源的使用情况,为PCI设备分配新的资源。因此编写设备驱动程序重点是获得基址寄存器(Base Address)中断干线寄存器的内容。配置空间共有六个基址寄存器和一个中断干线寄存器,具体用法如下:

PCI Base Address 0 寄存器:系统利用此寄存器为PCI接口芯片的配置寄存器分配一段PCI地址空间,通过这段地址我们可以以内存映射的形式访问PCI接口芯片的配置寄存器。

PCI Base Address 1寄存器:系统利用此寄存器为 PCI 接口芯片的配置寄存器分配一段PCI地址空间,通过这段地址我们可以以I/O的形式访问PCI接口芯片的配置寄存器。

PCI Base Address 2、3、4、5寄存器:系统BIOS利用这些寄存器分配PCI地址空间以支持PCI接口芯片的局部配置寄存器0、1、2、3的访问。

在所有基址寄存器中,第0位均为只读位,表示这段地址映射到存储器空间还是I/O空间,如果是“1”表示映射到I/O空间,如果是“0”则表示映射到存储器空间。

中断干线寄存器(Interrupt Line):用于说明中断线的连接情况,这个寄存器的值与标准8259的IRQ编号(0~15)对应。


3. I/O端口访问
在 PC机上,I/O寻址方式与内存寻址方式不同,所以处理方法也不同。I/O空间是一个64K字节的寻址空间,I/O寻址没有实模式与保护模式之分,在各种 模式下寻址方式相同。在Windows NT下,系统不允许处于Ring3级的用户程序和用户模式驱动程序直接使用I/O指令,对I/O端口进行访问,任何对I/O的操作都需要借助内核模式驱动 来完成。在访问I/O端口时,使用READ_PORT_XXX与WRITE_PORT_XXX函数来进行读写。I/O端口基地址使用从配置空间基址寄存器 PCI Base Address 1中返回的I/O端口基地址。

示例代码如下:
RegValue = READ_PORT_ULONG(pBaseAddr+RegOffSet);
WRITE_PORT_ULONG(pBaseAddr+ RegOffset, RegValue);


4. 设备内存访问
Winsows 工作在32位保护模式下,保护模式与实模式的根本区别在于CPU寻址方式上的不同,这也是Windows驱动程序设计中需要着重解决的问题。 Windows采用了分段、分页机制,使得一个程序可以很容易地在物理内存容量不一样的、配置范围差别很大的计算机上运行,编程人员使用虚拟存储器可以写 出比任何实际配置的物理存储器都大得多的程序。每个虚拟地址由16位的段选择字和32位段偏移量组成。通过分段机制,系统由虚拟地址产生线性地址。再通过 分页机制,由线性地址产生物理地址。线性地址被分割成页目录(Page Directory)、页表(Page Table)和页偏移(Offset)三个部分。当建立一个新的Win32进程时,操作系统会为它分配一块内存,并建立它自己的页目录、页表,页目录的地 址也同时放入进程的现场信息中。当计算一个地址时,系统首先从CPU控制器CR3中读出页目录所在的地址,然后根据页目录得到页表所在的地址,再根据页表 得到实际代码/数据页的页帧,最后再根据页偏移访问特定的单元。硬件设备读写的是物理内存,但应用程序读写的是虚拟地址,所以存在着将物理内存地址映射到 用户程序线性地址的问题。

从物理内存到线性地址的转换是驱动程序需要完成的工作,可以在初始化驱动程序的进行。在已经获得设备的存 储器基地址后,首先调用HalTranslateBusAddress()函数将总线相关的内存地址转换成系统的物理地址,然后调用 MmMapIoSpace()函数将系统的物理地址映射到线性地址空间。在需要访问设备内存时,调用READ_REGISTER_XXX()与 WRITE_REGISTER_XXX ()函数来进行,基地址使用前面映射后的线性地址。在设备卸载时,调用MmUnmapIoSpace()断开设备内存与线性地址空间的映射。

示例代码如下:
HalTranslateBusAddress(InterfaceType,
BusNumber,
BaseAddress->RangeStart,
&addressSpace, 
&cardAddress)

BaseAddress->MappedRangeStart = MmMapIoSpace(cardAddress,
BaseAddress->RangeLength,
MmCached );
……
RegValue = READ_REGISTER_ULONG(pRegister);
WRITE_REGISTER_ULONG(pRegister, pInBuf->RegValue);
……
MmUnmapIoSpace(pBaseAddress->MappedRangeStart, pBaseAddress->RangeLength );


5. 中断处理
中 断的设置、响应与调用在驱动程序中完成。设置中断应该在设备创建时完成,使用从CmResourceTypeInterrupt描述符中提取的参数,先调 用HalGetInterruptVector()将与总线有关的中断向量参数转换为系统的中断向量,然后调用IoConnectInterrupt() 指定中断服务,注册中断服务函数ISR(Interrupt Service Routine)的函数指针。

当硬件设备产生中断时,系统 会自动调用ISR函数来响应中断。ISR函数运行的中断请求级较高,主要完成对硬件设备中断的清除,不适合执行过多的代码。在传输大块数据时,需要使用延 迟过程调用(Delay Process Call,DPC)机制。例如,使用PCI设备进行DMA通信时,在ISR函数中完成对指定设备中断的判断以及清除中断,在退出ISR前,调用DPC函 数;在DPC函数中,完成DMA通信的过程,并将数据返回给用户程序。


6.DMA通信过程
DMA通信在驱动程序中实现,需要多个例程才能完成一次DMA通信。

1) DriverEntry例程
构造DEVICE_DESCRIPTION结构,并调用HalGetAdapter,找到与设备关联的Adapter对象,并将返回的Adapter对象的地址和映射寄存器的数目保存在设备扩展的数据结构中。

示例代码:
// 申请DMA的适配器对象
deviceDescription.Version = DEVICE_DESCRIPTION_VERSION;
deviceDescription.Master = TRUE;
deviceDescription.ScatterGather = pDevExt->ScatterGather;
deviceDescription.DemandMode = FALSE;
deviceDescription.AutoInitialize = FALSE;
deviceDescription.Dma32BitAddresses = TRUE;
deviceDescription.BusNumber = pDevExt->BusNumber;
deviceDescription.InterfaceType = pDevExt->InterfaceType;
deviceDescription.MaximumLength = pDevExt->MaxTransferLength;
pDevExt->AdapterObject = HalGetAdapter(&deviceDescription,
&numberOfMapRegisters
);
……

2)Start I/O例程

该例程请求Adapter对象的拥有权,然后把其余的工作留给AdapterControl回调例程。

a) 调用KeFlushIoBuffers从CPU的Cache把数据清到物理内存,然后计算映射寄存器的数目和用户缓冲区的大小,及在第一次设备操作中传输的字节数。
b) 调用MmGetMdlVirtualAddress,从MDL中恢复用户缓冲区的虚地址,并存入设备扩展数据结构中。
c) 调用IoAllocateAdapterChannel请求Adapter对象的拥有权。如果调用成功,其余的设置工作由AdapterControl例程去做;如果失败了,则完成本次IRP包处理,开始处理下一个IRP。

3) AdapterControl例程
该例程完成初始化DMA控制器,并启动设备的工作。
a) 调用IoMapTransfer,装入Adapter对象的映射寄存器。
b) 向设备发送合适的命令开始传输操作。
c) 返回值KeepObject保留Adapter对象的拥有权。

4)中断服务(ISR)例程
在设备中断时,由系统调用。
a) 向硬件设备发出中断响应的指令。
b) 调用IoRequestDpc在驱动程序的DpcForIsr中继续处理该请求。
c) 返回TRUE,表示已经服务了本次中断。

5)DpcForIsr例程
由ISR在每个部分数据传输操作的结束时触发,完成当前IRP请求。
a) 调用IoFlushAdapterBuffers,清除Adapter对象的Cache中的任何剩余数据。
b) 调用IoFreeMapRegisters,释放所使用的映射寄存器。
c) 检查有未传完的剩余数据,如果有,则计算下次设备操作中需要传输的字节数,调用IoMapTransfer重设映射寄存器,并启动设备;如果没有剩余数据,则完成当前IRP请求,并开始下一个请求。


以上转自:http://blog.csdn.net/augusdi/article/details/6120014

7. PCI端地址和Local地址的转换重映射

例如Local端地址空间大小为1MB,即0xFFFFFh。在写入31-4位时应该是它的反码即0x0000h,其中的低4比特位由0:3来表示,即0x00000h。设置完了地址空间范围之后,就需要指定Local地址与PCI地址转换时涉及到的比特位,PCI9054手册上设高12位为两者的转换位。即高12位为0xFFF,那么整个Local Space0的范围寄存器设置为0xFFF00000h。

在设置Local Space0 基址寄存器,手册上设置为0x12300000h,如此,在PCI地址与Local端地址转换的时候,用Local Space0基址寄存器的高12位即0x123h替换PCI地址的高12位。

在PCI端的寄存器,由PCI上的主机来设置,在系统上电之后,PC机会给PCI端分配一个基地址0x78900000h,而PCI基址寄存器中存放的是空间偏移地址的。或者是这样理解,0x78900000h是存放在PCI基址寄存器2中,在系统进行写入1到PCI基址寄存器中时,PCI9054通过这个写入信息,结合Local Space0 Range寄存器的值来返回一个值,而实际上PCI基址寄存器的0x78900000h值没有变,所以在后面的寻址时这个PCI基址0x78900000h依然存在:)再或者,在系统写入1到PCI基址寄存器的时候,已经把PCI基址寄存器的值读取出来并保存,在查询完Local端的地址空间大小的时候,重新写入0x78900000h到PCI基址寄存器,这后面一种假设情况似乎更符合PCI9054手册中的:PCI software writes all ones to PCI Base Address,then return the value of 0xFFF00000h,and then PCI software writes to the PCI Base Address Registers。Please Note:the PCI Base Address were written twice!

在应用程序开发操作硬件的时候,例如:PCI基址寄存器+offset ,通过PCI驱动底层的地址转换得到PCI基址寄存器(0x78900000h)+offset,这个地址可以找到PCI9054芯片的内部,然后,PCI9054芯片再把这个PCI地址进行重映射到Local端的内存空间地址,这一步根据Local Space0 Range 寄存器的设置,其高12位由Local端基址寄存器的高12位替换即所谓的重映射,而Local端基地址的高12位是0x123h,所以,PCI端的地址” PCI基址寄存器(0x78900000h)+offset”----àLocal端地址“0x12300000h +offset”,如此完成地址的重映射。

在驱动程序的开发中,我们所操作的Local端内存的偏移地址就是offset。而在操作PCI9054芯片的内部寄存器时,其基地址由主机进行分配进PCI Base0(Mem映射)和PCI Base1(I/O映射)中。实际操作的地址就是“PCI Base0 基址寄存器 + offset”,通过offset来寻找芯片内部寄存器。

以上转自:http://blog.csdn.net/augusdi/article/details/6119782


8. PCI配置空间和PCI存储空间

CPU对PCI 设备的配置,是为了完成PCI 设备存储空间的地址分配、PCI 设备功能设置等工作。配置操作时,CPU通过北桥芯片实现设备片选。
    配置寄存器组内保留了对PCI 设备的基本特性进行详尽说明的可读信息,CPU读取这些信息后,就可以为PCI 设备设定符合需要的配置内容,从而实现自动配置。这些可读信息包括:
    Vendor ID :设备供应商编号,由PCI   SIG 国际组织分配。
    Device ID :特定设备编号,由设备供应商分配。
    Revision ID :设备的特定版本号,由设备供应商分配。
    Class Code :设备的功能类别编号。
    Header Type :指示Header 中从地址10H到3FH区域的内容格式,同时指示该设备是否为多功能设备。
    PCI 存储空间不同于PCI 配置空间。PCI 存储空间可以存放PCI 设备的特殊功能寄存器,也可以用做数据缓冲和存储区域。
    PCI 存储空间分为MEMORY空间和I/O空间两类,它们独立寻址,并使用不同的总线操作命令进行访问。MEMORY空间适用于设备功能寄存器较多或数据流量较大的场合,例如网口芯片、PCI-PCI 桥等。I/O 空间适用于设备功能寄存器较少或数据流量较小的场合,例如串口芯片、LED控制寄存器等。但在地址空间比较充裕的条件下,也有把范围较小的存储区放入MEMORY空间的做法。
    操作响应者根据操作发起者发出的总线操作命令类型,来识别当前操作是指向MEMORY空间还是I/O空间
    配置寄存器组中会有一个或多个基址寄存器BAR(Base Address Register),用于保存指向PCI 存储空间的指针,同时BAR中可读不可写的位数决定了该存储空间的大小。BAR的最低位是只读位,它定义了BAR的空间属性。当BAR指向PCI   MEMORY存储空间时,该位读回为0;当BAR指向PCI   I/O存储空间时,该位读回为1。 由于BAR的这一属性,使得PCI 设备占用的存储区域是放在MEMORY空间,还是放在I/O空间,完全由设备制造商决定,用户无法修改。
    单个设备/功能的PCI   MEMROY空间的容量可选取16byte~2Gbyte。但为了减少参与地址译码的地址线数量,PCI 规范建议MEMORY空间分配不小于4Kbyte。
    单个设备/功能的 PCI   I/O空间的容量可选取4byte~256byte ,不允许超过256byte。

     访问PCI 存储空间时,都使用“BAR指针+地址偏移量”作为存储空间的访问地址。因此,为每个PCI 设备/功能分配合理的地址空间,保证设备/功能间的存储空间不发生重叠,是PCI 设备配置操作的一项重要内容。

以上转自:http://blog.csdn.net/augusdi/article/details/6201493


9. echo PCI EEPROM VALUES

eep 00 905410b5 echo PCIIDR; Device ID, Vendor ID
eep 04 06800001 echo PCICCR; Class Code
eep 08 00000100 echo Maximum Latency, Minimum Grant, Int Pin, Int
Routing
eep 0c 00000000 echo MBOX0; Mail Box 0
eep 10 00000000 echo MBOX1; Mail Box 1
eep 14 ffc00000 echo LAS0RR; Local Address Space 0 Range
eep 18 02000001 echo LAS0BA; Local Address Space 0 Base Address
(Re-Map)
eep 1c 00000000 echo MARBR; Mode/DMA Arbitration Register
eep 20 00000400 echo PROT_AREA; Serial EEPROM Write-Protected
eep 24 00000000 echo EROMRR; Expansion ROM Range
eep 28 00000000 echo EROMBA; Expansion ROM Base Address (Re-Map)
eep 2c 40400022 echo LBRD0; Bus Region Descriptors for PCI-to-Local
Accesses
eep 30 00000000 echo DMRR; Range for PCI Initiator-to-PCI
eep 34 00000000 echo DMLBAM; Local Base Address for PCI
Initiator-to-PCI Memory
eep 38 00000000 echo DMLBAI; Local Bus Address for PCI Initiator-to-PCI
I/O Configuration
eep 3c 00000000 echo DMPBAM; PCI Base Address (Re-map) for PCI
Initiator-to-PCI
eep 40 00000000 echo DMCFGA; PCI Configuration Address Register for PCI
Initiator-to-PCI I/O Configuration
eep 44 905410b5 echo Subsystem ID, Subsystem Vendor ID
eep 48 ffffff01 echo LAS1RR; Local Address Space 1 Range
eep 4c 03000001 echo LAS1BA; Local Address Space 1 Base Address
(Re-Map)
eep 50 40000022 echo LBRD1; Bus Region Descriptors (Space 1) for
PCI-to-Local Accesses
eep 54 00000000 echo Hot Swap control


10. 硬件设计的注意事项

在C模式下,PCI9054的143脚LHOLD和144脚LHOLDA是总线仲裁输入输出信号控制引脚,从公开发表的论文来看,有人将这两个引脚直接与设计电路相连进行总线仲裁逻辑设计。笔者在设计时也曾采用过这种方式,结果计算机运行很不稳定,经常莫名其妙地死机。仔细分析这种方式设计的电路会发现,该设计电路其实是利用器件的引脚传输延时配合总线仲裁电路的时序关系的。一般情况下,当环境温度、工作电压等外界因素变化时,大多数据逻辑器件的引脚延时会有相应的变化,因此用器件引脚延时设计出的总线仲裁逻辑电路运行不稳定应该是预料之中的事。
利用D触发器只在触发时钟有效边沿对信号敏感的特性进行设计,提高电路运行的稳定性,是可编程逻辑器件设计中的一种常用技巧。

ADS#、BLAST#和READY#信号的时序配合很重要,如果配合不好,计算机同样会死机。要提高电路稳定性,其设计思想与图2中的总线仲裁逻辑电路的设计思想相似。从多次实验情况来看,无论使用哪种工作模式,在该设计思想下设计出的本地总线控制逻辑电路运行非常稳定,没有死机的情况发生。


11. 存储器和I/O操作
通常PCI扩展板应该有配置空间、存储器空间和I/O空间三类资源。空间配置是必须的,根据设计需要,后两种资源可以只包含其中之一。PCI9054有S0和S1两个配置空间,两个配置空间都可以配置成存储器空间和I/O空间,存储器空间和I/O空间的配置既有相同之处,也有很大的区别。其相同点为两者都以字节为单位进行空间配置。不同之处有以下几点:
(1)存储器空间支持0~4GB的空间配置和映射,而I/O空间只支持0~256字节的空间配置和映射;
(2)存储器空间支持8位、16位和32位总线位宽配置和操作,而I/O空间只支持32位总线位宽配置和操作;
(3)存储器空间支持单一周期和突发模式读写操作,而I/O空间只支持单一周期读写操作;
(4)在编写设备驱动程序时,存储器空间用程序指针进行访问,I/O空间则用专用的函数进行访问。
如果不清楚存储器空间和I/O空间的异同步,有可能配错相应的寄存器。当计算机重新启动时,很有可能会启动不起来。除此之外,值得注意的是:在配置存储器空间和I/O空间时,不要将两种硬件资源配置到相同的空间,导致空间重叠,使驱动程序产生误操作


12. Target时序图   DMA时序图 (暂时不使用burst模式进行发送)


13. 编写FPGA时需要用的参考,该参考使用VC编写的上位机发送程序

http://www.cnblogs.com/bude/p/6304573.html


14. 驱动开发相关

http://www.voidcn.com/blog/shejiannan/article/p-1475184.html

http://blog.csdn.net/augusdi/article/details/6202531

15. 在线烧录的注意事项

烧录EEPROM有两种方式,一是比较传统的方法,即采用烧录机进行烧录。采用这种方式时,在调试过程中EEPROM需采用插件式封装的芯片(DIP),因为每烧录一次,就需要拔出芯片到烧录机上进行烧写。这样频繁插拔,很明显会对芯片造成一定损害。当然对于最终的产品来讲,也可以采用贴片封装的EEPROM,因为在调试过程中已经把EEPROM的值调好了,把贴片的EEPROM在烧录机上烧好值再焊接在板子上。比较常见的烧写头是针对DIP封装的芯片,如果你采用贴片的封装,则可以买一个SOP封装的烧写头(最常用的贴片EEPROM的封装为SOP-8)。

       二就是进行在线烧录,这也是我极力推荐大家使用的方式。采用这种方式时,直接采用贴片封装的EEPROM即可。可以预先把空白的EEPROM芯片焊接在板子上,然后把PCI卡插在计算机的PCI插槽上,开机进入windows系统,运行PLXMON进行在线烧写,这种方式既省时又省力,而且方便快捷。

      但是采用在线烧录时,有几点务必要注意,否则将会费很多周折。在设计时,你就应该把PCI芯片(比如9054)的TEST引脚以及EEDI/O引脚同时设计成上下拉。这里有几种情况:

1 板上没有焊接EEPROM。 这时如果你还想调试PCI卡的话,就需要把TEST引脚下拉,EEDI/O引脚下拉。但在开机的时候要把TEST引脚上拉一下才能进入系统,这时PCI卡采用默认的配置数据。

2 板上焊接空白的EEPROM,这也是我们进行在线烧录时的情况。这时需要把TEST引脚下拉,EEDI/O引脚上拉。但在开机时需要把TEST引脚上拉一下才能进入系统,如果卡硬件没问题,系统会提示找到新硬件,并提示安装驱动程序。安装好驱动程序之后,打开PLXMON,这时EEPROM的标志会呈现黑色,即可访问状态,点击该标志,会打开EEPROM的内容。当然EEPROM空白的话,打开的内容是全‘F’的值,这时点击“浏览”打开配置数据文件(格式为xxx.eep),再点击write即可成功烧录EEPROM。如果你打开PLXMON时,EEPROM的标志呈现灰白色,即无法访问的状态,则有两种可能,一是驱动程序没有安装好,二是因为你硬件有问题,比如晶振或者是EEPROM或者是9054芯片没有焊好。

另外,如果焊接空白的EEPROM,你把TEST引脚下拉,EEDI/O也下拉时,这时不需要上拉一下TEST便可以直接进入操作系统。但此时没有办法进行在线烧录,你打开PLXMON的EEPROM的页面时,会发现内容是全‘0’的值,这时对EEPROM进行write时,会提示出现错误。所以这种配置也只能是为了调试卡之用,并不能进行在线烧录。

3 按照第二步的步骤烧录好EEPROM之后,你下次再进入系统便可直接进入了,这也是正常操作的模式。这时的配置也是TEST下拉,EEDI/O上拉。

以上转自:http://blog.csdn.net/augusdi/article/details/6118167











你可能感兴趣的:(PCI接口开发笔记)