在高科技研究以及工业、医疗、交通、航空等众多领域中,对高帧率和高分辨率的图像数据需求逐渐加大,例如航天和军工中高速物体运动轨迹的捕捉、3D动漫、视频定位和测量、高速公路交通监控等。然而,连续的高帧率和高分辨率图像采集将产生巨大的数据量,例如以10 f/s帧率传输分辨率为4 928 V×3 280H的视频图像时,总线上的数据速率高达1.23 Gb/s;以500 f/s帧率传输分辨率为640 V×480 H的视频图像时,总线上的数据速率也高达1.18 Gb/s。为了实现1 Gb/s以上的传输速度且满足实时性和长距离传输的要求,选用光纤技术和PCI Express(简称PCI-E)总线技术设计了PCI-E总线光纤接口卡。
光纤具有频带宽、传输距离长、损耗低、抗干扰能力强等优点[1],适用于大量数据的远程传输。PCI-E总线采用了串行点对点通信技术,使传输速率大幅度提高,PCI-E 1.0标准的单通道单向速率高达2.5 Gb/s,且最大支持32通道[2]。PCI-E 3.0标准单通道单向速率可达8 Gb/s,是传输高速、大数据量总线的首选,具有研究和应用价值。
本文基于Spartan-6芯片设计的PCI Express光纤接口卡的驱动程序,完成的功能有:(1)驱动程序对光纤接口卡硬件资源的获取; (2)光纤接口卡能够将大量数据及时发送到计算机内存中; (3)合理的中断机制能够满足处理高速图像的需要; (4)计算机可以向光纤接口卡发送参数命令。
1 PCI Express接口卡的硬件结构
PCI Express接口卡基于Xilinx公司的Spartan-6 LTX系列FPGA设计,完成图像数据收发、数据缓存以及PCI-E协议等工作,并将采集到的图像数据通过PCI-E总线传输给计算机。Spartan-6 LTX系列FPGA内部包含一个PCI-E端点硬核,完整实现了PCI-E1.0标准协议中的数据链路层和物理层的功能,可以完成1链路的PCI-E总线传输[3]。光纤接口卡采用单片高性能FPGA设计,具有系统集成度高、硬件设计简洁且便于扩展的特点。末端利用计算机对采集的图像进行显示、存储以及回放等操作。PCI Express接口卡的硬件结构如图1所示。
利用ISE中的IP核生成工具CORE Generator对PCIE端点硬核进行定制:设备ID采用默认值,Vendor ID = 0x10EE,Device ID = 0x0007,这是驱动程序识别该硬件的厂商号和设备号; 基址寄存器的类型选择为Memory,开辟一个32 位寻址的1 KB大小的存储器空间;中断设置为采用INTA的传统中断类型。
基址寄存器设计是实现接口卡功能关键内容,按照偏移地址将1 KB的存储器空间划分成功能不同的寄存器,部分定义如表1所示。
2 PCI Express接口卡的驱动程序
2.1 开发工具Windows DDK简介
Windows DDK是微软提供的驱动程序开发工具包,包括了设备驱动开发的文档、头文件和库文件、调试工具和程序示例。先将Windows DDK导入VC++软件开发工具,然后用C语言编写驱动程序代码,编译后再用Windows DDK中的调试软件调试,生成可执行的驱动程序。使用Windows DDK能够开发出真正意义上的核心态驱动程序,它所开发的驱动程序效率也是最高的,但必须详细了解操作系统的内核工作方式,开发难度较大[4]。
常用开发工具还有WinDriver和DriverStudio,为了获得最大的驱动程序效率,选择采用Windows DDK来设计PCI Express接口卡的驱动程序。
2.2 WDM启动程序的结构
通常WDM驱动程序都是基于分层的结构设计,由功能驱动函数、总线驱动程序和过滤器驱动程序三部分组成。完成一个设备的操作,至少要有两个驱动设备共同完成[5],一个是总线驱动程序生成的物理设备对象(PDO),另一个是功能驱动程序生成的功能设备对象(FDO)。
Windows XP系统检测到PCI-E接口卡插入主板卡槽时,总线驱动会自动创建出一个PDO。但是PDO不能单独操作设备,还需要将实现功能的FDO挂载在总线驱动生成的PDO上,即相当于功能驱动程序挂载到了指定的硬件设备上[6],实现驱动程序对硬件设备的控制。
2.3 接口卡发送数据
为了提高接口卡的传输效率,采用DMA的传输方式将图像数据通过PCI-E总线发送到计算机。在DMA传输过程中,PCI-E接口卡作为DMA传输的主控器,数据的传输速度基本由PCI-E总线的传输速度决定[7]。
2.3.1 获取接口卡的资源信息
接口卡发送图像数据之前,驱动程序要将DMA传输的目标地址以及数据负载的大小发送给接口卡,驱动程序需要先获取接口卡上的硬件资源,如接口卡端点内存的物理地址等。
首先根据设备类型和设备扩展的大小,利用函数IoCreateDevice生成并初始化一个FDO;其次利用函数IoAttachDeviceToDeviceStack将生成的FDO附加在系统生成的PDO上;然后向PDO转发一个IRP_MN_START_DEVICE类型的I/O 请求包(IRP),PDO会根据设备的类型自动枚举该设备的所有资源。为了使FDO能够得到底层PDO处理IRP的结果,需要同时将一个同步事件发送给底层PDO。PDO处理完IRP后立刻激发同步事件,通知FDO查询此次IRP的结果; PDO完成了对IRP_
MN_START_DEVICE命令的响应后,将获取到的接口卡设备资源存储在IRP中名为IO_STACK_LOCATION结构的设备堆栈中[8],流程如图2所示。
利用函数IoGetCurrentIrpStackLocation可以获得该IO_STACK_LOCATION结构,主要包含接口卡的中断信息、端点内存寄存器资源以及接口卡的物理地址等设备硬件资源。
2.3.2 分配内存空间并实现乒乓操作
为了尽可能提高计算机内存资源利用率,驱动程序分配的内存空间大小与每次DMA传输的图像数据大小需保持一致。先利用函数IoGetDmaAdapter获取一个DMA适配器,包含了开辟内存空间的函数AllocateCommonBuffer,调用此函数获得一段指定大小的连续物理内存,保存物理内存的逻辑地址作为DMA传输的目标地址。
预先在计算机中分配了多块内存空间,使计算机可以在处理一块写满了图像数据的内存缓冲区的同时,用另一块闲置的内存缓冲区接收DMA传输的图像数据,从而实现乒乓操作,提高传输效率[9]。
接收到DMA传输开始的指令后,PCI-E接口卡会根据分配好的内存空间地址和设定好的DMA传输负载长度,不断地向计算机发送带有图像数据的TLPs数据包[10]。每次DMA传输结束后,驱动程序都会根据是否希望继续进行DMA传输选择轮转一块新的内存地址发送给接口卡或者释放相关资源。DMA传输操作流程图如图3所示。
2.4 驱动程序的中断处理
驱动程序接收到PCI-E接口卡产生的中断信号,表明PCI-E接口卡已经向计算机指定的内存空间完成了一次DMA传输,要通知应用层程序对内存中的数据进行访问,完成显示、存储等功能。先利用函数IoConnectInterrupt将中断例程与设备产生的中断信号绑定。根据计算机中断优先级的规则,硬件中断会得到操作系统的优先响应[11]。
计算机操作系统检测到PCI-E接口卡的中断信号来临时,立即进入中断服务例程(ISR)。由于接口卡采用的传统中断是一种电平中断信号,还需要驱动程序在ISR中进一步完成对中断来源的判断和清除中断的操作。
采用同步事件方式实现中断信号的同步处理,先在应用程序中定义一个与驱动程序共享的同步事件,并通过DeviceIoControl函数把该事件句柄传递给驱动程序,等待事件触发之后再进行读数据操作。驱动程序根据所传递的这个事件句柄创建一个相应的内核事件[12]。中断发生时驱动程序调用KeSetEvent函数将同步事件设置为激发态,通知应用程序中断已经发生。尽量减少ISR程序的运行时间,将激发同步事件的动作设计发生在拥有较低中断等级的延迟过程调用例程(DPC)中[13]。中断处理流程如图4所示。