八、九 月份的时候为一块PCI卡驱动程序增加了bus master DMA和中断服务功能,到现在有一段时间没弄,一些细节有些记不大起来了,赶紧把一些要点记录一下,以免日子久了都忘记了。 硬件是一块PCI数据采集卡,50MHz,8bit的采集速率,板上使用FPGA(Cyclone II)先对数据进行压缩等处理,然后给上位机。PCI总线用的是PLX公司的PCI9054芯片。原来的驱动不支持DMA,也不支持中断,上位机程序只能不断轮询某个标志位,如果有数据再读取。这作为一块高速的数据采集卡,实在是有点说不过去(原驱动还是出自国内某一流高校之手笔,汗)。 驱动程序这东西感觉这一点那一点,知识点比较杂,也不知道怎么理出个条理,反正就想到什么记什么了。
开发环境:VC++6.0, Win XP DDK, DriverStudio 3.1.0。
1。PCI9054芯片其实就是一个“桥”,一边是PCI总线,另一边是本地总线(local bus)。9054的作用就是把PCI的地址翻译到本地总线的地址。9054有M,C,J三种模式,我的理解就是本地总线的不同,一般用的是C模式。
2。PCI的4个空间要映射到哪个物理地址,是由宿主机器的操作系统或bios动态配置的。宿主系统怎么知道空间的大小呢?方法是系统启动时向PCI配置空间的基地址(PCI base addresss)寄存器写一个全1的32位数,然后立刻读回来,比如说读到0xFFFF0000,后16位是0,说明这个空间有64K。就是说前16位是可写的,被写入1,而后16位是不可写的,值永远是0。然后系统就分配一个基地址,把这个地址写入前16位中,于是基地址寄存器中就保存了分配到的物理基地址。反过来说,如果一个设备要求分配64K内存,那么它就应该实现一个基地址寄存器,其高16位是可读写的,后16位是只读并且为0的(其实最后4位是选项位,可能不为0)。对于中断号的分配也简单,系统把分配的中断号写入到PCI配置空间的Interrup Line寄存器中就ok了,意思就是把PCI卡的中断请求线连接(route)到了中断管理器的几号中断脚上。
3。PCI空间配置好以后,软件通过访问配置空间的基地址寄存器,可以得知空间被映射到了哪个地址。但最先怎么访问配置空间呢?对于PC架构的机器来说,是通过PCI桥来访问的。PCI桥作为PCI总线的管理芯片,可以区别访问不同的PCI插槽。PCI桥的寄存器被固定映射到IO空间的0XCF8,0XCFC,通过操作这两个寄存器,可以访问到各个总线号、设备号、功能号的寄存器了。但开发WDM驱动的时候,这些都不需要我们自己做了,一般我们只要提供Vendor ID和Devcie ID,就可以得到基地址指针了。
4。对于驱动程序,要完成一个事务一般得分成几步,在不同的函数里完成,并且同一个函数里还得根据状态完成不同事务的某个部分。所以采用状态机是个比较适合的选择。例如对于中断的响应,在Isr_irq()里主要是查明中断原因、清除中断源,然后排队一个DpcFor_Irq,尽快返回。因为Isr_irq()是中断级高的环境,运行期间其他中断会被屏蔽,从而降低系统性能。而余下的费时工作,如读数据等操作,则由DpcFor_Irq()来完成。DpcFor_Irq()中断级低,这样在DpcFor_Irq()运行期间Isr_irq()又可以响应其他中断了。
5。DMA传输涉及的步骤比较多,大概的流程是:
a)SerialRead()收到一个读的IRP,初始化一个KDmaTransfer对象;
b) KDmaTransfer会调用回调函数OnDmaReady(),在OnDmaReady()里设置硬件寄存器,开始DMA;
c)硬件DMA完成,触发中断,经Isr_Irq(),到DpcFor_Irq(),在DpcFor_Irq()里调用KDmaTransfer的Continue()函数。这是因为一个IRP请求的数据量可能一次DMA无法完成,需要分成多次。
d)KDmaTransfer再调用一次OnDmaReady(),OnDmaReady()判断BytesRemaining,看是否需要再启动一次硬件DMA。如果不需要,则调用KDmaTransfer的Terminate(),结束这个Transfer。然后调用PnpNextIrp(),完成这个IRP。 对于我们的卡,情况比较特殊,在应用程序发出IRP后,还必须等采集卡发出数据就绪中断后,才可以开始DMA。因此我在OnDmaReady()里并不立刻启动硬件DMA,而是在数据就绪中断的DpcFor_Irq()里,先判断是否有Read的IRP在等待处理,再启动硬件DMA。
6。PCI总线时序的简单描述: PCI传送以burst为单位,FRAME#信号指示burst的开始和结束。FRAME#信号使能后的第一个时钟周期是命令周期,以后是数据周期。FRAME#拉高后一个周期传送结束。 PCI master在拉低FRAME#后的第一个时钟上升沿(即命令周期)在AD[31:0}给出地址,在C/BE[3:0]给出命令。命令有I/O r/w, Memory r/w, Configuration r/w等 PCI master第二个上升沿在AD[31:0}给出数据,在C/BE[3:0]给出字节使能。 PCI master拉高FRAME#后的一个上升沿给出最后一个数据和字节使能,一个frame到此结束。 IRDY#和TRDY#用于master(initiator)和slave(target)之间指示是否就绪,如果有一方未就绪,数据周期中间会插入等待周期。