PCI同步时钟卡的WDF驱动程序设计

时间:2022-05-05 01:53:14

PCI同步时钟卡的WDF驱动程序设计

摘要:本文介绍了一种基于WDF模型的PCI同步时钟卡的驱动程序设计。简要介绍了自行研发的PCI同步时钟卡的系统架构及工作原理,分析了WDF模型的框架结构及设备驱动程序设计流程,重点讨论了基于PCI同步时钟卡的WDF驱动程序开发,主要包括中断通知、硬件访问等内容。经测试,该驱动程序具有很好的稳定性和可靠性。

关键词:WDF PCI 中断 驱动程序 同步时钟卡

中图分类号:TP336 文献标识码:A 文章编号:1007-9416(2015)05-0000-00

Abstract:This paper introduces the design of Device Drivers of PCI synchronous clock card based on WDF model. Briefly introduces the system architecture and works on our own PCI synchronous clock card, and Analysis the framework of the WDF model and the design process. Focused on the research and development of the WDF Device Drivers based on the PCI synchronous clock card, including hardware access, Interrupt notification. The driver has passed the test for stability and reliability.

Key words: WDF; PCI; interrupt; driver; synchronous clock card.

时间是科学实验、科学研究和工程技术等领域中的一个基本物理参量。为了保证系统各部分时间的一致性和正确性,系统内各设备的同步时钟从卡从时钟源获取高精度的标准时间,提供给相应设备。这样系统内各设备的时间与时间源相同而保持一致。同步时钟卡一般采用PCI总线方式。PCI总线能够实现设备间的快速访问,它以突出的性能受到计算机和通信界工程师们的青睐。

因此如何开发出稳定、可靠、高效的PCI设备驱动程序成为驱动工程师们面临的一个棘手的问题[5]。过去对于PCI设备驱动程序的开发大多采用WDM(Windows Driver Model)框架,但是它编程比较复杂,快速掌握其开发要领对于初学者来说比较困难[5]。本文所述的PCI同步时钟卡的驱动程序的开发采用微软最新推出的WDF(Windows Driver Foundation)驱动模型。WDF驱动模型提供事件驱动和面向对象的驱动程序开发框架,大大降低了设备驱动程序的开发难度[5]。

1 同步时钟卡系统架构

本文所述的驱动程序是基于自行研发的PCI同步时钟卡,其原理框图如图1所示。本同步时钟卡选择PCI9052芯片做为PCI总线的接口芯片。该电路除了用到PCI9052外,还用到了单片机、EEPROM、双口RAM、CPLD。单片机是系统的控制单元;串行EEPROM存储了PCI9052芯片所需要的配置信息;双口RAM用于PC机与时钟卡之间交换数据;CPLD用于200us时标的产生和中断的控制。

同步时钟卡的工作流程如下:同步时钟从卡接收时钟源输出的时间信号,单片机将其解析成高精度的同步时间信息,控制逻辑(CPLD)通过1PPS脉冲信号产生200us的高精度时间刻度,于是产生高精度的同步绝对时标,连续存储于双口RAM中,最后计算机通过PCI总线接口获取高精度的绝对时间。本系统中计算机获取双口RAM中的时间数据的方式有两种:(1)PC机主动读取双口RAM中的数据。(2)外部事件通过中断通知PC机事件发生,PC机收到通知后读取双口RAM中的时间信息,可获得外部事件发生的精确时刻。两种方式分别涉及驱动程序的硬件访问和中断通知。于是涉及到本文介绍的重点:基于WDF模型的PCI总线驱动程序的开发。

2 WDF驱动程序设计

微软对过去的WDM(Windows Driver Model)驱动程序的架构做了改进,形成了全新的WDF(Windows Driver Foundation)框架结构。它将原来普通软件开发中面向对象的技术应用到了驱动程序的开发中。WDF改变了驱动程序与操作系统内核之间的关系,在传统的WDM驱动程序中,不仅要处理硬件,还要处理驱动程序与操作系统内核之间的交互[4]。现在WDF则使驱动程序与操作系统内核独立开来,驱动程序与操作系统交互工作将由框架内封装的方法(函数)去完成,这样驱动开发工程师只需专注处理目标硬件的行为即可,避免了两面不周顾此失彼的弊端。不仅大大降低了驱动程序的代码量,还使整个系统更加稳定、可靠。

WDF驱动程序包括两个类型,一个是内核级的,称为KMDF(Kernel-Mode Driver Framework);另一个是用户级的,称为UMDF(User-Mode Driver Framework)。本文所述的驱动程序采用KMDF模式。

2.1 WDF驱动程序开发流程

本文所用开发环境为Microsoft Windows Driver Kit(WDK) 8.1和Microsoft Visual Studio 2013,操作系统为Windows7。先安装VS2013,再安装WDK8.1,便可在VS2013中直接创建KMDF工程。根据同步时钟卡所需功能编写好驱动程序即可进行编译。

WDF驱动程序框图如图2所示。

2.2 基于WDF模型的PCI设备驱动程序的实现

WDF模型的设备驱动程序从功能上可分为三个部分:初始化设备、控制设置与交换数据[3]。初始化设备主要实现设备的识别、驱动对象与设备对象的建立与硬件资源的分配;控制设置负责应用程序与驱动程序的连接和设备的打开;交换数据处理的是设备功能的具体应用,即PCI总线与同步时钟卡之间的数据传输。

从本质上来说,WDF模型的设备驱动程序是由入口函数DriverEntry和事件例程及其子函数组成的[3]。操作系统在第一次加载驱动程序时会通过调用DriverEntry例程来完成设备驱动程序和框架的初始化[3]。所有的驱动程序都必须包含一个DriverEntry例程。对于不同类型的驱动程序其入口函数DriverEntry也不同,可分为:设备驱动、纯软件驱动与过滤驱动。本文所述的PCI总线驱动程序属于设备驱动,在入口函数DriverEntry中,主要完成两件事:注册EvtDriverDeviceAdd回调例程、创建和初始化WDFDRIVER对象。

WDF_DRIVER_CONFIG_INIT(&config,PCIdriverEvtDeviceAdd);

//注册EvtDriverDeviceAdd回调例程

status = WdfDriverCreate(DriverObject, RegistryPath,...);

//创建驱动对象

2.2.1初始化设备

在驱动程序被成功初始化完成之后,操作系统会顺序调用EvtDriverDeviceAdd、EvtDevicePrepareHardware等回调例程以实现所控制的设备的初始化。

当首次枚举设备时,EvtDriverDeviceAdd例程在系统初始化时被PnP管理器调用。在系统运行过程中,任何时候一个新的相同设备被枚举,系统都将调用此例程。EvtDriverDeviceAdd例程是设备初始化过程中最新被调用的回调例程,它需要完成:设备对象的创建,创建符号链接或设备对象GUID接口,创建一个或多个I/O队列,各种事件的回调函数的注册,如即插即用、电源管理、I/O处理例程等[1]。

EvtDriverDeviceAdd例程的主要代码如下所示:

注册即插即用基本例程:

WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);

pnpPowerCallbacks.EvtDevicePrepareHardware = PCIDriverEvtDevicePrepareHardware;

pnpPowerCallbacks.EvtDeviceReleaseHardware = PCIDriverEvtDeviceReleaseHardware;

..........

WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);

创建设备对象:

WDF_FILEOBJECT_CONFIG_INIT(&f_config,...);

WdfDeviceInitSetFileObjectConfig(DeviceInit, &f_config,WDF_NO_OBJECT_ATTRIBUTES);

WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, ..);

status = WdfDeviceCreate(&DeviceInit, &attributes, &control_device);

创建队列对象并注册回调例程:

WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioQueueConfig,....);

ioQueueConfig.EvtIoDeviceControl = PCIdriverEvtIoDeviceControl;

ioQueueConfig.EvtIoStop = PCIdriverEvtIoStop;

status = WdfIoQueueCreate(control_device,&ioQueueConfig,...);

创建符号链接:

status = WdfDeviceCreateSymbolicLink(control_device, &ustring);

创建中断对象:

deviceContext = GetDeviceContext(control_device);

WDF_INTERRUPT_CONFIG_INIT(&interruptConfig,PCIDriverEvtInterruptIsr,

PCIDriverEvtInterruptDpc);//设置中断服务例程和延迟过程调用WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&interruptAttributes,..);

status = WdfInterruptCreate(control_device,&interruptConfig,

&interruptAttributes,&deviceContext->Interrupt);

EvtDriverDeviceAdd例程调用完成之后,系统将调用EvtDevicePrepareHardware例程初始化地址指针,将设备所占用的I/O地址和内存地址映射为虚拟地址,驱动程序将通过这些虚拟地址完成与设备的数据传输。由于串行EEPROM存储了PCI9052芯片所需要的配置信息,系统将自动为本文所述的PCI同步时钟卡分配资源,它们包括双口RAM的内存地址、PCI9052的I/O地址空间,所以EvtDevicePrepareHardware例程必须将这些资源映射为虚拟地址。对于I/O端口,只需将首地址与地址数目值保存在设备上下文;对于存储器芯片,调用MmMapIoSpace函数将物理地址映射为系统内核虚拟地址,然后保存于设备上下文。相对应的,当设备被卸载时,系统会自动调用EvtDeviceReleaseHardware回调例程释放之前申请的硬件资源。

for (i = 0; i < WdfCmResourceListGetCount(ResourceListTranslated); i++) {//WdfCmResourceListGetDescriptor函数获取该资源的描述符

descri = WdfCmResourceListGetDescriptor(ResourceListTranslated, i);

switch (descri->Type)

{case CmResourceTypeMemory:

Mem_Count++;

if (Mem_Count == 2)//将双口RAM地址映射为虚拟地址

{pDevice_context->MemBaseAddress = MmMapIoSpace(

descri->u.Memory.Start,

descri->u.Memory.Length,

MmNonCached);

pDevice_context->MemLength = descri->u.Memory.Length;}

break;

case CmResourceTypePort://将PCI9052的I/O地址映射为虚拟地址

pDevice_context->Io_baseAddress = descri->u.Port.Start.LowPart;

pDevice_context->Io_length = descri->u.Port.Length;

default:

break;}}

2.2.2控制设置与数据交换

应用程序实现和驱动程序通信的过程是:应用程序首先调用CreateFile函数打开设备,然后可以使用DeviceIoControl和驱动程序通信,包括写数据给驱动程序和从驱动程序读数据两种情况,也可以用WriteFile写数据给驱动程序或用ReadFile从驱动程序读数据,当应用程序退出时,调用用CloseHandle关闭设备。本文所述的系统是用DeviceIoControl和驱动程序通信。CreateFile打开设备的方式有两种:符号链接名与GUID接口,本文所述驱动程序采用的是符号链接名的方式。

m_hDevice=CreateFile(sLinkName,...);//以符号链接名的方式打开设备

上述代码中sLinkName为符号链接名,它与驱动程序中设置的符号链接名相同。m_hDevice为返回的设备的有效句柄,应用程序就可以应用它调用DeviceIoControl函数与驱动程序交换数据。应用程序的请求会被放入请求队列中,并在EvtIoDeviceControl函数之中被处理。

本文中应用程序获取时钟卡上的时间信息的方式有两种:(1)直接读取。(2)中断方式。

对于第一种方式,应用程序直接调用DeviceIoControl函数与驱动程序交换数据。由于系统的双口RAM被映射到虚拟内存,驱动程序可以使用下面两条指令对双口RAM进行读写: READ_REGISTER_XXX;//读双口RAM,WRITE_REGISTER_XXX;//写双口RAM。

对于中断方式,当被捕获的外部事件发生时,驱动程序会进入中断服务例程EvtInterruptIsr,然后进入延时过程调用EvtInterruptDpc,首先清中断源,然后将双口RAM中的时间数据读取到设备上下文中缓存,该数据即为外部事件发生的时间,最后通知应用程序读取该数据。应用程序将调用DeviceIoControl函数获取设备上下文中的时间信息。驱动程序与应用程序通信的方法有两种:DeviceIoControl异步完成和WIN32事件通知。本文所述系统采用WIN32事件通知的方法。对于此种方法,应用程序初始化时首先生成一个通知事件,并通过DeviceIoControl函数的输入缓冲区发送给驱动程序,驱动程序创建相应的内核事件,同时使能PCI9052的LINT1中断,当该事件发生时,驱动程序会通知应用程序,应用程序的一个子线程不停的循环等待驱动程序发来的事件发生通知。当设备被卸载时需要撤销该内核事件。具体主要代码如下:

应用程序生成通知事件:

mhEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

应用程序子线程中等待事件发生:

while (WaitForSingleObject(mhEvent, 0) != WAIT_OBJECT_0)

{...}

驱动程序创建相应的内核事件:

ObReferenceObjectByHandle(....);

允许PCI中断,使能PCI9052的本地LINT1中断,pREG为PCI9052映射的I/O空间的基 地址:

inter = READ_PORT_USHORT(pREG + 0x4c);

inter |=0x43;

WRITE_PORT_USHORT(pREG + 0x4c, inter);

清中断源,设置PCI9052的CS3引脚有效,通知CPLD清掉LINT1信号:

inter = READ_PORT_USHORT(pREG + 0x50);

inter |= 0x800;

WRITE_PORT_USHORT(pREG + 0x50, inter);

驱动程序给应用程序发送事件,通知应用程序读取数据:

KeSetEvent(pDevice_context->Event, 0, FALSE);

驱动程序内撤销内核事件:

ObDereferenceObject(...);

3 结语

驱动程序是硬件与应用程序通信的桥梁,它对系统性能提升的作用举足轻重。高效、稳定、可靠的驱动程序可以使系统性能得到很好的提升。

本文简要介绍了PCI同步时钟从卡的工作原理,并重点讨论了基于WDF模型的PCI设备驱动程序设计方法。本文所述的PCI同步时钟卡驱动程序,在WDK8.1中成功编译,自动生成SYS文件(驱动程序代码)和INF文件(设备安装信息),成功安装并且能够稳定可靠地运行。经测试,捕获的时间精度达到误差小于200us,满足系统设计要求。涉及本驱动程序的系统已应用于三峡大坝左岸发电厂发变机组的故障录波系统中,运行稳定可靠。总而言之,WDF驱动模型优化并简化了设备驱动程序的开发,比传统的WDM驱动模型更加稳定。

参考文献

[1]武安河.Windows设备驱动程序WDF开发[M].北京:电子工业出版社,2009.

[2][美]Ronald D. Reeves 著,张猛等 译.Windows设备驱动程序开发[M].北京:人民邮电出版社,2012.

[3]黎顺杰,张艳荣.基于WDF的PCI-CAN设备驱动程序设计[J].电子测试,2013;5:20-21.

[4]宋爱美,徐建建.基于WDF的USB驱动程序设计[J].实验科学与技术,2012;10(1):58-63.

[5]黎绍秀,卫红,兰春嘉.PCI-E图像采集系统的WDF驱动程序设计[J].科学技术与工程,2011;11(16):2834-2837.

上一篇:多媒体计算机技术软件系统的维护难点及管理 下一篇:论数字广播发射天线技术及应用