Chinaunix首页 | 论坛 | 博客
  • 博客访问: 15483051
  • 博文数量: 2005
  • 博客积分: 11986
  • 博客等级: 上将
  • 技术积分: 22535
  • 用 户 组: 普通用户
  • 注册时间: 2007-05-17 13:56
文章分类

全部博文(2005)

文章存档

2014年(2)

2013年(2)

2012年(16)

2011年(66)

2010年(368)

2009年(743)

2008年(491)

2007年(317)

分类: 嵌入式

2009-09-08 15:28:45

为一块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)之间指示是否就绪,如果有一方未就绪,数据周期中间会插入等待周期。

阅读(3062) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~