基于WDF的PCI/PCIe接口卡Windows驱动程序(3)- 驱动程序代码(头文件)
2015-07-28 21:35
393 查看
原文出处:http://www.cnblogs.com/jacklu/p/4679304.html
在WDF的PCIe驱动程序中,共有四个.h文件(Public.h Driver.h Device.h Trace.h)。本文将分别对四个文件源代码进行详细的解释。
Public.h
代码文件名为Public,是因为这个文件要被驱动程序和应用程序共同使用的。在第4行的注释里,写明了本文件的主要作用是为驱动程序和应用程序的通信提供GUID接口。在Windows平台下实现对硬件设备的控制,需要应用程序能够与底层驱动进行通信,应用程序与驱动通信的设计过程中有两个重要的概念,即GUID值和CTL_CODE宏。
GUID(Globally Unique Identifier)是微软推出的全局唯一标识符,通过使用某个特定的算法(比如根据时间或地点等信息)生成一组128位二进制数,来标识某一个实体,比如硬盘上的一张图片。GUID广泛应用于微软的产品中,用于识别接口、文件等对象。开发者可以使用VS2013下的工具GUIDGen.exe生成GUID值,该GUID标识驱动程序,应用程序根据这个GUID值来找到对应的驱动程序。注意使用DEFINE_GUID宏,要包含initguid.h文件,否则会报出无法识别的error。
I/O处理例程DeviceIoControl(这个例程将在下一篇文章中详细介绍)的第二个参数dwIoControlCode就是由CTL_CODE宏定义的。CTL_CODE是一个用于创建一个唯一的32位系统I/O控制代码的宏,这个控制代码包括4部分组成:DeviceType(设备类型,高16位(16-31位)),Access(访问限制,14-15位),Function(功能2-13 位),Method(I/O访问内存使用方式)。CTL_CODE定义中有一个Method域,该域定义了驱动程序中获取应用程序数据缓冲区地址的方式。
10-13行代码为用户自定义的4个I/O控制命令,分别为读数据、写数据、读映射的BAR0的物理起始地址、写偏移地址(用来读写数据)。
第一个参数为设备类型,通常为自定义的板卡选择FILE_DEVICE_UNKNOWN;
注意,第二个数字要从0x800开始取值,之前的已经被微软占用;
第三个参数有四种选择(METHOD_BUFFERED、METHOD_IN_DIRECT、METHOD_OUT_DIRECT、METHOD_NEITHER),buffered方式:I/O管理器会创建与应用程序数据缓冲区完全相同的系统缓冲区,驱动程序在这个缓冲区工作,由I/O管理器完成复制数据任务;direct方式:I/O管理器锁定应用程序缓冲区的物理内存页,并创建一个MDL(内存描述符表)来描述该页,驱动程序将使用MDL工作;neither方式:I/O管理器把应用程序缓冲区的虚拟地址传递给驱动程序,一般不采用这种方式。
第四个参数用来设置文件读写权限,有三种选择(FILE_ANY_ACCESS、FILE_READ_ACCESS、FILE_WRITE_ACCESS),微软官方的说法是“The FILE_ACCESS_ANY is generally the correct value.”,所以我们还是老实的选用FILE_ACCESS吧。
Driver.h
这个文件将基于WDF的PCIe驱动程序所需要的头文件都包含在了一起,并且disable了一些警告,由于驱动程序的开发非常注意warnning的处理,VS2013下是默认有warnning的时候不通过编译的,需要开发者手动设置一下。对于一些无关痛痒的warnning,我们也可以通过预处理的方式来disable它。
Device.h
这个文件定义了与设备和驱动程序密切相关的一些东西,非常重要。
3-4行定义了两个宏,即设置了两块内存在BAR0映射的偏移地址0x20000和0x22000,这两个值与PCIe硬件板卡有关。在读写操作时,BAR0的物理地址一定要加上偏移地址,否则会因为写到未知内存单元而造成蓝屏等后果。
6行定义了最大传输的大小,在以后DMA操作中会用到。
10-18行定义了与设备相关的变量,把这些变量参数封装在一个结构体中,体现WDF中的一种“对象封装”的思想。几个参数分别表示资源计数器(记录WDF框架分配给设备的资源个数)、记录BAR2的起始地址(当资源计数器 i == 0 时)、BAR0经过转换后的虚拟地址(可被应用程序使用)、BAR0映射的起始地址(物理地址,与设备管理器中所获得结果相同)、内存大小、偏移地址(由应用程序传递过来,供I/O读取使用)。在WDF中这个结构体被叫做设备上下文(DEVICE_CONTEXT),在WDM中叫做DEVICE_EXTENSION
26行的宏非常重要,具体作用将在下一篇介绍源文件中详细说明。
31-40行声明了一些WDF事件回调例程,声明后,我们可以直接使用自定义的回调函数名,这些声明的作用只起到开发者方便编写程序的作用。
DriverEntry为驱动程序入口函数;
Spw_PCIeEvtDeviceAdd为设备添加函数,非常重要,要自己说三遍;
Spw_PCIeEvtDriverContextCleanup为资源清理函数,由于操作系统越来越智能,当设备被拔出后,操作系统会自动回收一些资源,所以这个函数貌似在PCIe驱动里并没什么卵用,即使我在这做了声明,在下一篇介绍源代码中,我们也会发现,它的函数定义也只“打了一个酱油”;
Spw_PCIeEvtDeviceD0Entry和Spw_PCIeEvtDeviceD0Exit是与电源管理相关的两个函数,WDF已经将电源管理做了很好的封装,一般不需要驱动程序开发者在作处理,所以它们两个也是“打酱油”的;
Spw_PCIeEvtDevicePrepareHardware和Spw_PCIeEvtDeviceReleaseHardware非常重要!!!自己说三遍,分别为设备获取资源和释放资源;
Spw_PCIeEvtIoDeviceControl是实现应用程序与驱动程序通信的函数,里面规定了不同的控制码实现不同的操作。
在这一部分,先对这几个例程做简要概述,详细的解释将会在下一篇给出。
还有最后一个头文件,用来调试和跟踪,由于我也没用到调试和跟踪驱动程序,所以直接给出VS2013+WDK8.1自动生成的代码文件,不做解释了。
trace.h
在WDF的PCIe驱动程序中,共有四个.h文件(Public.h Driver.h Device.h Trace.h)。本文将分别对四个文件源代码进行详细的解释。
Public.h
#ifndef _USER_H #define _USER_H // // Define an Interface Guid so that app can find the device and talk to it. // #include <initguid.h> // {49FA63A7-C525-4409-8DD5-EFF37A7375F8} DEFINE_GUID(GUID_DEVINTERFACE_Spw_PCIe, 0x49fa63a7, 0xc525, 0x4409, 0x8d, 0xd5, 0xef, 0xf3, 0x7a, 0x73, 0x75, 0xf8); #define Spw_PCIe_IOCTL_IN_BUFFERED CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)//the least value is 0x800 #define Spw_PCIe_IOCTL_OUT_BUFFERED CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) #define Spw_PCIe_IOCTL_READ_PADDRESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS) #define Spw_PCIe_IOCTL_WRITE_OFFSETADDRESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS) #endif
代码文件名为Public,是因为这个文件要被驱动程序和应用程序共同使用的。在第4行的注释里,写明了本文件的主要作用是为驱动程序和应用程序的通信提供GUID接口。在Windows平台下实现对硬件设备的控制,需要应用程序能够与底层驱动进行通信,应用程序与驱动通信的设计过程中有两个重要的概念,即GUID值和CTL_CODE宏。
GUID(Globally Unique Identifier)是微软推出的全局唯一标识符,通过使用某个特定的算法(比如根据时间或地点等信息)生成一组128位二进制数,来标识某一个实体,比如硬盘上的一张图片。GUID广泛应用于微软的产品中,用于识别接口、文件等对象。开发者可以使用VS2013下的工具GUIDGen.exe生成GUID值,该GUID标识驱动程序,应用程序根据这个GUID值来找到对应的驱动程序。注意使用DEFINE_GUID宏,要包含initguid.h文件,否则会报出无法识别的error。
I/O处理例程DeviceIoControl(这个例程将在下一篇文章中详细介绍)的第二个参数dwIoControlCode就是由CTL_CODE宏定义的。CTL_CODE是一个用于创建一个唯一的32位系统I/O控制代码的宏,这个控制代码包括4部分组成:DeviceType(设备类型,高16位(16-31位)),Access(访问限制,14-15位),Function(功能2-13 位),Method(I/O访问内存使用方式)。CTL_CODE定义中有一个Method域,该域定义了驱动程序中获取应用程序数据缓冲区地址的方式。
10-13行代码为用户自定义的4个I/O控制命令,分别为读数据、写数据、读映射的BAR0的物理起始地址、写偏移地址(用来读写数据)。
第一个参数为设备类型,通常为自定义的板卡选择FILE_DEVICE_UNKNOWN;
注意,第二个数字要从0x800开始取值,之前的已经被微软占用;
第三个参数有四种选择(METHOD_BUFFERED、METHOD_IN_DIRECT、METHOD_OUT_DIRECT、METHOD_NEITHER),buffered方式:I/O管理器会创建与应用程序数据缓冲区完全相同的系统缓冲区,驱动程序在这个缓冲区工作,由I/O管理器完成复制数据任务;direct方式:I/O管理器锁定应用程序缓冲区的物理内存页,并创建一个MDL(内存描述符表)来描述该页,驱动程序将使用MDL工作;neither方式:I/O管理器把应用程序缓冲区的虚拟地址传递给驱动程序,一般不采用这种方式。
第四个参数用来设置文件读写权限,有三种选择(FILE_ANY_ACCESS、FILE_READ_ACCESS、FILE_WRITE_ACCESS),微软官方的说法是“The FILE_ACCESS_ANY is generally the correct value.”,所以我们还是老实的选用FILE_ACCESS吧。
Driver.h
#define INITGUID #pragma warning(disable:4200) // #pragma warning(disable:4201) // nameless struct/union #pragma warning(disable:4214) // bit field types other than int #include <ntddk.h> #include <wdf.h> #include "Public.h" #include "device.h" #include "trace.h"
这个文件将基于WDF的PCIe驱动程序所需要的头文件都包含在了一起,并且disable了一些警告,由于驱动程序的开发非常注意warnning的处理,VS2013下是默认有warnning的时候不通过编译的,需要开发者手动设置一下。对于一些无关痛痒的warnning,我们也可以通过预处理的方式来disable它。
Device.h
#include "public.h" #define PCIE_WRITE_MEMORY_OFFSET 0x20000//memory1's offset address to BAR0 in FPGA #define PCIE_READ_MEMORY_OFFSET 0x22000//memory2's offset address to BAR0 in FPGA #define MAXNLEN 1024 //define the largest length // //device context is same as device extension in WDM // typedef struct _DEVICE_CONTEXT { ULONG Counter_i;//counter for WdfCmResourceListGetCount(ResourceListTranslated) PVOID MemBaseAddress;//when i == 5,it gets BAR2 start virtual address PVOID BAR0_VirtualAddress;//BAR0 start virtual address ULONG PhysicalAddressRegister;//store the BAR0 start physical address ULONG MemLength;//it records the length of menmory on hardware ULONG OffsetAddressFromApp;//get offset address that is given by application } DEVICE_CONTEXT, *PDEVICE_CONTEXT; // // This macro will generate an inline function called DeviceGetContext // which will be used to get a pointer to the device context memory // in a type safe manner. // //WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DEVICE_CONTEXT, DeviceGetContext) WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DEVICE_CONTEXT, GetDeviceContext)//very important! // // WDFDRIVER Events including "EVT" // DRIVER_INITIALIZE DriverEntry; EVT_WDF_DRIVER_DEVICE_ADD Spw_PCIeEvtDeviceAdd; EVT_WDF_OBJECT_CONTEXT_CLEANUP Spw_PCIeEvtDriverContextCleanup; EVT_WDF_DEVICE_D0_ENTRY Spw_PCIeEvtDeviceD0Entry; EVT_WDF_DEVICE_D0_EXIT Spw_PCIeEvtDeviceD0Exit; EVT_WDF_DEVICE_PREPARE_HARDWARE Spw_PCIeEvtDevicePrepareHardware; EVT_WDF_DEVICE_RELEASE_HARDWARE Spw_PCIeEvtDeviceReleaseHardware; EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL Spw_PCIeEvtIoDeviceControl;
这个文件定义了与设备和驱动程序密切相关的一些东西,非常重要。
3-4行定义了两个宏,即设置了两块内存在BAR0映射的偏移地址0x20000和0x22000,这两个值与PCIe硬件板卡有关。在读写操作时,BAR0的物理地址一定要加上偏移地址,否则会因为写到未知内存单元而造成蓝屏等后果。
6行定义了最大传输的大小,在以后DMA操作中会用到。
10-18行定义了与设备相关的变量,把这些变量参数封装在一个结构体中,体现WDF中的一种“对象封装”的思想。几个参数分别表示资源计数器(记录WDF框架分配给设备的资源个数)、记录BAR2的起始地址(当资源计数器 i == 0 时)、BAR0经过转换后的虚拟地址(可被应用程序使用)、BAR0映射的起始地址(物理地址,与设备管理器中所获得结果相同)、内存大小、偏移地址(由应用程序传递过来,供I/O读取使用)。在WDF中这个结构体被叫做设备上下文(DEVICE_CONTEXT),在WDM中叫做DEVICE_EXTENSION
26行的宏非常重要,具体作用将在下一篇介绍源文件中详细说明。
31-40行声明了一些WDF事件回调例程,声明后,我们可以直接使用自定义的回调函数名,这些声明的作用只起到开发者方便编写程序的作用。
DriverEntry为驱动程序入口函数;
Spw_PCIeEvtDeviceAdd为设备添加函数,非常重要,要自己说三遍;
Spw_PCIeEvtDriverContextCleanup为资源清理函数,由于操作系统越来越智能,当设备被拔出后,操作系统会自动回收一些资源,所以这个函数貌似在PCIe驱动里并没什么卵用,即使我在这做了声明,在下一篇介绍源代码中,我们也会发现,它的函数定义也只“打了一个酱油”;
Spw_PCIeEvtDeviceD0Entry和Spw_PCIeEvtDeviceD0Exit是与电源管理相关的两个函数,WDF已经将电源管理做了很好的封装,一般不需要驱动程序开发者在作处理,所以它们两个也是“打酱油”的;
Spw_PCIeEvtDevicePrepareHardware和Spw_PCIeEvtDeviceReleaseHardware非常重要!!!自己说三遍,分别为设备获取资源和释放资源;
Spw_PCIeEvtIoDeviceControl是实现应用程序与驱动程序通信的函数,里面规定了不同的控制码实现不同的操作。
在这一部分,先对这几个例程做简要概述,详细的解释将会在下一篇给出。
还有最后一个头文件,用来调试和跟踪,由于我也没用到调试和跟踪驱动程序,所以直接给出VS2013+WDK8.1自动生成的代码文件,不做解释了。
trace.h
/*++ Module Name: Trace.h Abstract: Header file for the debug tracing related function defintions and macros. Environment: Kernel mode --*/ // // Define the tracing flags. // // Tracing GUID - ad7b826c-e901-457e-a559-a221404519c6 // #define WPP_CONTROL_GUIDS \ WPP_DEFINE_CONTROL_GUID( \ Spw_PCIeTraceGuid, (ad7b826c,e901,457e,a559,a221404519c6), \ \ WPP_DEFINE_BIT(MYDRIVER_ALL_INFO) \ WPP_DEFINE_BIT(TRACE_DRIVER) \ WPP_DEFINE_BIT(TRACE_DEVICE) \ WPP_DEFINE_BIT(TRACE_QUEUE) \ ) #define WPP_FLAG_LEVEL_LOGGER(flag, level) \ WPP_LEVEL_LOGGER(flag) #define WPP_FLAG_LEVEL_ENABLED(flag, level) \ (WPP_LEVEL_ENABLED(flag) && \ WPP_CONTROL(WPP_BIT_ ## flag).Level >= level) #define WPP_LEVEL_FLAGS_LOGGER(lvl,flags) \ WPP_LEVEL_LOGGER(flags) #define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) \ (WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl) // // This comment block is scanned by the trace preprocessor to define our // Trace function. // // begin_wpp config // FUNC Trace{FLAG=MYDRIVER_ALL_INFO}(LEVEL, MSG, ...); // FUNC TraceEvents(LEVEL, FLAGS, MSG, ...); // end_wpp //
相关文章推荐
- java 泛型详解(普通泛型、 通配符、 泛型接口,泛型数组,泛型方法,泛型嵌套)
- python 的常用时间操作,取得当前时间等
- 《机器学习实战》之二分K-均值聚类算法的python实现
- LeetCode138 Copy List with Random Pointer(深度复制带有随机指针的链表) Java题解
- Python缺乏调查的陷阱 动态实例属性、引用、逃生
- Struts2中result配置中常见的几种视图转发类型
- Qt窗口中的一些小技术总结
- C#委托
- php.ini配置文件
- Spring的Bean的生命周期
- 《Java程序性能优化》学习笔记之ArrayList和LinkedList
- C++中指针和引用的区别与联系
- 【Java】(1)Base64加密技术
- php读取目录下的文件
- 黑马程序员-----C语言学习之通讯录应用的代码实现
- struts2的验证
- Python笔记之不可不知
- 【UNIX环境高级编程】文件 IO 操作 一 ( open | close | creat | lseek | write | read )
- C语言陷阱与缺陷(4)
- Scala界面Panel、Layout初探