原文出处:http://www.cnblogs.com/jacklu/p/4687325.htmlhtml
若是你以为这篇博客对你的项目有用,请引用如下论文:并发
Meng Shengwei, Lu Jianjie. Design of a PCIe Interface Card Control Software Based on WDF. Fifth International Conference on Instrumentation and Measurement, Computer, Communication and Control. IEEE, 2016:767-770.框架
本篇文章将对PCIe驱动程序的部分源文件代码做详细解释与说明。完整代码,有偿提供~整个WDF驱动程序工程共包含4个头文件(已经在上篇文章中讲解)和3个.c文件(Driver.c Device.c Queue.c)less
Driver.c函数
在看复杂的代码前,先给出程序流程图ui
1 #include "driver.h" 2 #include "driver.tmh" 3 4 #ifdef ALLOC_PRAGMA 5 #pragma alloc_text (INIT, DriverEntry) 6 #pragma alloc_text (PAGE, Spw_PCIeEvtDeviceAdd) 7 #pragma alloc_text (PAGE, Spw_PCIeEvtDriverContextCleanup) 8 #endif 9 10 11 NTSTATUS 12 DriverEntry( 13 IN PDRIVER_OBJECT DriverObject, 14 IN PUNICODE_STRING RegistryPath 15 ) 16 { 17 WDF_DRIVER_CONFIG config; 18 //WDFDRIVER driver;//???? 19 NTSTATUS status = STATUS_SUCCESS; 20 WDF_OBJECT_ATTRIBUTES attributes; 21 22 // 23 // Initialize WPP Tracing 24 // 25 WPP_INIT_TRACING( DriverObject, RegistryPath ); 26 27 TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry"); 28 29 // 30 // Register a cleanup callback so that we can call WPP_CLEANUP when 31 // the framework driver object is deleted during driver unload. 32 // 33 34 WDF_OBJECT_ATTRIBUTES_INIT(&attributes); 35 36 attributes.EvtCleanupCallback = Spw_PCIeEvtDriverContextCleanup; 37 38 WDF_DRIVER_CONFIG_INIT(&config, 39 Spw_PCIeEvtDeviceAdd 40 ); 41 42 status = WdfDriverCreate(DriverObject, 43 RegistryPath, 44 &attributes, 45 &config, 46 WDF_NO_HANDLE 47 ); 48 49 if (!NT_SUCCESS(status)) { 50 TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfDriverCreate failed %!STATUS!", status); 51 WPP_CLEANUP(DriverObject); 52 return status; 53 } 54 55 TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit"); 56 57 return status; 58 } 59 60 61 NTSTATUS 62 Spw_PCIeEvtDeviceAdd( 63 _In_ WDFDRIVER Driver, 64 _Inout_ PWDFDEVICE_INIT DeviceInit 65 ) 66 { 67 NTSTATUS status = STATUS_SUCCESS; 68 WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; 69 WDF_OBJECT_ATTRIBUTES deviceAttributes; 70 WDFDEVICE device; 71 PDEVICE_CONTEXT deviceContext; 72 73 WDFQUEUE queue; 74 WDF_IO_QUEUE_CONFIG queueConfig; 75 76 /*+++++Interrupt 77 WDF_INTERRUPT_CONFIG interruptConfig; 78 -----*/ 79 // WDF_IO_QUEUE_CONFIG ioQueueConfig; 80 81 UNREFERENCED_PARAMETER(Driver); 82 83 PAGED_CODE(); 84 85 //采用WdfDeviceIoDirect方式 86 WdfDeviceInitSetIoType(DeviceInit, WdfDeviceIoDirect);//WdfDeviceIoBuffered???重要吗? 87 //When the I/O manager sends a request for buffered I/O, the IRP contains an internal copy of the caller's buffer 88 //rather than the caller's buffer itself. The I/O manager copies data from the caller's buffer to the internal buffer 89 //during a write request or from the internal buffer to the caller's buffer when the driver completes a read 90 //request. 91 //The WDF driver receives a WDF request object, which in turn contains an embedded WDF memory object. 92 //The memory object contains the address of the buffer on which the driver should operate. 93 94 95 96 // status = Spw_PCIeCreateDevice(DeviceInit); 97 98 //初始化即插即用和电源管理例程配置结构 99 WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); 100 101 //设置即插即用基本例程 102 pnpPowerCallbacks.EvtDevicePrepareHardware = Spw_PCIeEvtDevicePrepareHardware; 103 pnpPowerCallbacks.EvtDeviceReleaseHardware = Spw_PCIeEvtDeviceReleaseHardware; 104 pnpPowerCallbacks.EvtDeviceD0Entry = Spw_PCIeEvtDeviceD0Entry; 105 pnpPowerCallbacks.EvtDeviceD0Exit = Spw_PCIeEvtDeviceD0Exit; 106 107 //注册即插即用和电源管理例程 108 WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks); 109 110 111 WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT); 112 113 114 //deviceAttributes.EvtCleanupCallback = Spw_PCIeEvtDriverContextCleanup; 115 // 116 // Set WDFDEVICE synchronization scope. By opting for device level 117 // synchronization scope, all the queue and timer callbacks are 118 // synchronized with the device-level spinlock. 119 // 120 deviceAttributes.SynchronizationScope = WdfSynchronizationScopeDevice; 121 122 status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device); 123 if (!NT_SUCCESS(status)) { 124 return status; 125 } 126 deviceContext = GetDeviceContext(device);///???? 127 //deviceContext->Device = device; 128 // 129 // 初始化Context这个结构里的全部成员. 130 // 131 //deviceContext->PrivateDeviceData = 0; 132 /*++++++Interrupt & DMA 133 //设置中断服务例程和延迟过程调用 134 WDF_INTERRUPT_CONFIG_INIT(&interruptConfig, 135 PCISample_EvtInterruptIsr, 136 PCISample_EvtInterruptDpc); 137 138 //建立中断对象 139 status = WdfInterruptCreate(device, 140 &interruptConfig, 141 WDF_NO_OBJECT_ATTRIBUTES, 142 &pDeviceContext->Interrupt); 143 if (!NT_SUCCESS (status)) { 144 return status; 145 } 146 147 status = InitializeDMA(device); 148 149 if (!NT_SUCCESS(status)) { 150 return status; 151 } 152 -----*/ 153 //WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchSequential); 154 //Initialize the Queue 155 // queueConfig.EvtIoDefault = Spw_PCIeEvtIoDefault; 156 // queueConfig.EvtIoWrite = Spw_PCIeEvtIoWrite; 157 //queueConfig.EvtIoRead = Spw_PCIeEvtIoRead; 158 // queueConfig.EvtIoStop = Spw_PCIeEvtIoStop; 159 //The driver must initialize the WDF_IO_QUEUE_CONFIG structure 160 //by calling WDF_IO_QUEUE_CONFIG_INIT or WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE. 161 //用default初始化default 队列,用另外一个初始化非default队列 162 WDF_IO_QUEUE_CONFIG_INIT( 163 &queueConfig, 164 WdfIoQueueDispatchSequential 165 ); 166 167 queueConfig.EvtIoDeviceControl = Spw_PCIeEvtIoDeviceControl; 168 169 170 status = WdfIoQueueCreate(device, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, &queue); 171 if (!NT_SUCCESS(status)) { 172 return status; 173 } 174 175 //对于非默认队列,必须指定要分发的I/O请求类型 176 //The WdfDeviceConfigureRequestDispatching method causes the framework to queue a specified type of I/O requests to a specified I/O queue. 177 status = WdfDeviceConfigureRequestDispatching( 178 device, 179 queue, 180 WdfRequestTypeDeviceControl 181 ); 182 if (!NT_SUCCESS(status)) { 183 return status; 184 } 185 //建立驱动程序接口与应用程序通讯 186 status = WdfDeviceCreateDeviceInterface( 187 device, 188 (LPGUID)&GUID_DEVINTERFACE_Spw_PCIe, 189 NULL // ReferenceString 190 ); 191 if (!NT_SUCCESS(status)) { 192 return status; 193 } 194 /* 195 if (NT_SUCCESS(status)) { 196 // 197 // Initialize the I/O Package and any Queues 198 // 199 status = Spw_PCIeQueueInitialize(device); 200 } 201 */ 202 //deviceContext->MemLength = MAXNLEN; 203 204 //TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit"); 205 206 return status; 207 } 208 209 VOID 210 Spw_PCIeEvtDriverContextCleanup( 211 _In_ WDFOBJECT DriverObject 212 ) 213 /*++ 214 Routine Description: 215 216 Free all the resources allocated in DriverEntry. 217 218 Arguments: 219 220 DriverObject - handle to a WDF Driver object. 221 222 Return Value: 223 224 VOID. 225 226 --*/ 227 { 228 UNREFERENCED_PARAMETER(DriverObject); 229 230 PAGED_CODE (); 231 232 TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry"); 233 234 //没有必要清除WDFINTERRUPT对象,由于框架会自动清除 235 // Stop WPP Tracing 236 // 237 WPP_CLEANUP( WdfDriverWdmGetDriverObject(DriverObject) ); 238 239 }
4-8行是作一些预处理,驱动程序开发中,须要为每一个函数指定位于分页内存仍是非分页内存。INIT标识是指此函数为入口函数,驱动成功加载后能够从内存删除。PAGE标识是指此函数能够在驱动运行时被交换到硬盘上,若是不指定,将被编译器默认为非分页内存。this
11-58行定义了DriverEntry函数,每一个 KMDF 驱动程序必须有一个 DriverEntry 例程,当操做系统检测到有新硬 件设备插入后,会查找它对应的驱动程序,找到这个驱动程序中的 DriverEntry 例程。DriverEntry 是驱动程序的入口,它至关于 C 语言程序里的 main 函数。 DriverEntry 例程的原型声明以下:spa
1 NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) ;
函数返回类型 NTSTATUS 是 WDF 中的一个宏,它其实是一个 32 位的二进制数,不一样的数值表示不一样的状态,在 PCIe 设备驱动程序开发中,须要用到的状态有: STATUS_SUCCESS、 STATUS_PENDING、 STATUS_UNSUCCESSFUL, 分 别表示例程回调成功、 例程回调未完成、 例程回调失败。在传入参数里, IN 是一 个宏, 表明这个参数为入口参数,这与例程编写无关,只是为了让开发者可以更 容易的知道参数特性,其中 OUT 表示出口参数。关于参数标识, 还有另外一种写法, 即_In_和_Out_, 两种写法对回调例程的编写都没影响。操作系统
DriverEntry 的第一个参数是一个指向驱动程序对象的指针, 该对象就表明驱 动程序。 在 DriverEntry 例程中, 应该完成对这个对象的初始化并返回。 DriverEntry 的第二个参数是设备驱动对应服务键在注册表中的路径。DriverEntry 例程须要完成的任务主要包括:设计
61-206行定义了EvtDriverDeviceAdd函数。每一个支持即插即用的 KMDF 驱动程序必须有 EvtDriverDeviceAdd 回调例程, 每次操做系统枚举设备时, PnP 管理器就调用这个回调例程。 EvtDriverDeviceAdd 例程的主要任务包括:
EvtDriverDeviceAdd 例程的原型声明以下:
EvtDriverDeviceAdd( IN WDFDRIVER Driver, IN PWDFDEVICE_INIT DeviceInit ) ;
DeviceInit 指向 KMDF 自定义的一个结构体, 它在设置传输方式、 注册即插即 用和电源管理例程、 建立设备对象这些任务中起着传递重要数据的做用。
209-239行定义了EvtDriverContextCleanup函数。EvtDriverContextCleanup 回调例程用来删除设备和回收操做系统分配给设备 的资源。对于即插即用设备,当手动拔出设备后, PnP 管理器会自动识别并删除设 备 , 之 后 Windows 操做系统会自动回收资源 , 所 以 设 计 者 无 需 编 写 EvtDriverContextCleanup 例程。
Device.c
1 #include "driver.h" 2 #include "device.tmh" 3 4 #pragma warning(disable:4013) // assuming extern returning int 5 #ifdef ALLOC_PRAGMA 6 7 #pragma alloc_text(PAGE, Spw_PCIeEvtDevicePrepareHardware) 8 #pragma alloc_text(PAGE, Spw_PCIeEvtDeviceReleaseHardware) 9 #pragma alloc_text(PAGE, Spw_PCIeEvtDeviceD0Entry) 10 #pragma alloc_text(PAGE, Spw_PCIeEvtDeviceD0Exit) 11 12 #endif 13 14 NTSTATUS 15 Spw_PCIeEvtDevicePrepareHardware( 16 IN WDFDEVICE Device, 17 IN WDFCMRESLIST ResourceList, 18 IN WDFCMRESLIST ResourceListTranslated 19 ) 20 { 21 ULONG i; 22 NTSTATUS status = STATUS_SUCCESS; 23 PDEVICE_CONTEXT pDeviceContext; 24 25 PCM_PARTIAL_RESOURCE_DESCRIPTOR descriptor;//record the Hareware resource that OS dispatched to PCIe 26 /* 27 在Windows驱动开发中,PCM_PARTIAL_RESOURCE_DESCRIPTOR记录了为PCI设备分配的硬件资源, 28 可能有CmResourceTypePort, CmResourceTypeMemory等, 29 后者表示一段memory地址空间,顾名思义,是经过memory space访问的, 30 前者表示一段I/O地址空间,但其flag有CM_RESOURCE_PORT_MEMORY和CM_RESOURCE_PORT_IO两种, 31 分别表示经过memory space访问以及经过I/O space访问,这就是PCI请求与实际分配的差别, 32 在x86下,CmResourceTypePort的flag都是CM_RESOURCE_PORT_IO,即代表PCI设备请求的是I/O地址空间,分配的也是I/O地址空间, 33 而在ARM或Alpha等下,flag是CM_RESOURCE_PORT_MEMORY,代表即便PCI请求的I/O地址空间,但分配在了memory space, 34 咱们须要经过memory space访问I/O设备(经过MmMapIoSpace映射物理地址空间到虚拟地址空间,固然,是内核的虚拟地址空间,这样驱动就能够正常访问设备了)。 35 */ 36 PAGED_CODE(); 37 38 // UNREFERENCED_PARAMETER(Resources);//告诉编译器不要发出Resources没有被引用的警告 39 40 pDeviceContext = GetDeviceContext(Device); 41 pDeviceContext->MemBaseAddress = NULL; 42 pDeviceContext->Counter_i = 0; 43 //get resource 44 for (i = 0; i < WdfCmResourceListGetCount(ResourceListTranslated); i++) { 45 46 descriptor = WdfCmResourceListGetDescriptor(ResourceListTranslated, i); 47 //if failed: 48 if (!descriptor) { 49 return STATUS_DEVICE_CONFIGURATION_ERROR; 50 } 51 52 switch (descriptor->Type) { 53 54 case CmResourceTypeMemory: 55 //MmMapIoSpace将物理地址转换成系统内核模式地址 56 if (i == 0){ 57 pDeviceContext->PhysicalAddressRegister = descriptor->u.Memory.Start.LowPart; 58 pDeviceContext->BAR0_VirtualAddress = MmMapIoSpace( 59 descriptor->u.Memory.Start, 60 descriptor->u.Memory.Length, 61 MmNonCached); 62 } 63 64 pDeviceContext->MemBaseAddress = MmMapIoSpace( 65 descriptor->u.Memory.Start, 66 descriptor->u.Memory.Length, 67 MmNonCached); 68 pDeviceContext->MemLength = descriptor->u.Memory.Length; 69 70 break; 71 72 default: 73 break; 74 } 75 if (!pDeviceContext->MemBaseAddress){ 76 return STATUS_INSUFFICIENT_RESOURCES; 77 } 78 } 79 pDeviceContext->Counter_i = i; 80 DbgPrint("EvtDevicePrepareHardware - ends\n"); 81 82 return STATUS_SUCCESS; 83 } 84 85 NTSTATUS 86 Spw_PCIeEvtDeviceReleaseHardware( 87 IN WDFDEVICE Device, 88 IN WDFCMRESLIST ResourceListTranslated 89 ) 90 { 91 PDEVICE_CONTEXT pDeviceContext = NULL; 92 93 PAGED_CODE(); 94 95 DbgPrint("EvtDeviceReleaseHardware - begins\n"); 96 97 pDeviceContext = GetDeviceContext(Device); 98 99 if (pDeviceContext->MemBaseAddress) { 100 //MmUnmapIoSpace解除物理地址与系统内核模式地址的关联 101 MmUnmapIoSpace(pDeviceContext->MemBaseAddress, pDeviceContext->MemLength); 102 pDeviceContext->MemBaseAddress = NULL; 103 } 104 105 DbgPrint("EvtDeviceReleaseHardware - ends\n"); 106 107 return STATUS_SUCCESS; 108 } 109 110 NTSTATUS 111 Spw_PCIeEvtDeviceD0Entry( 112 IN WDFDEVICE Device, 113 IN WDF_POWER_DEVICE_STATE PreviousState 114 ) 115 { 116 UNREFERENCED_PARAMETER(Device); 117 UNREFERENCED_PARAMETER(PreviousState); 118 119 return STATUS_SUCCESS; 120 } 121 122 123 NTSTATUS 124 Spw_PCIeEvtDeviceD0Exit( 125 IN WDFDEVICE Device, 126 IN WDF_POWER_DEVICE_STATE TargetState 127 ) 128 { 129 UNREFERENCED_PARAMETER(Device); 130 UNREFERENCED_PARAMETER(TargetState); 131 132 PAGED_CODE(); 133 134 return STATUS_SUCCESS; 135 }
13-83行定义了EvtDevicePrepareHardware例程。EvtDevicePrepareHardware和EvtDeviceReleaseHardware两个例程对硬件设备可否得到Windows操做系统分配的资源起着相当重要的做用。EvtDevicePrepareHardware的任务主要包括得到内存资源、内存物理地址与虚拟地址的映射、I/O端口映射和中断资源分配。
EvtDevicePrepareHardware例程的原型声明以下:
1 NTSTATUS EvtDevicePrepareHardware( 2 IN WDFDEVICE Device, 3 IN WDFCMRESLIST ResourceList, 4 IN WDFCMRESLIST ResourceListTranslated 5 ) ;
传入函数的三个参数,Device是在EvtDriverDeviceAdd建立的设备对象,另外两个参数是两个硬件资源列表,这两个硬件资源列表实际上表明了不一样版本的同一份硬件资源集。ResourceList表明的硬件资源是经过总线地址描述的;ResourceListTranslated表明的硬件资源是经过内存物理地址描述的。
WDF框架分配给硬件资源的具体过程以下:
(1)用户插入PnP设备,总线驱动识别设备并枚举;
(2)WDF框架调用总线驱动的EvtDeviceResourcesQuery,建立资源列表;
(3)WDF框架调用总线驱动的EvtDeviceResourcesRequirementQuery,建立资源需求列表;
(4)PnP管理器决定设备须要什么驱动程序;
(5)PnP管理器建立设备资源列表并发送给驱动程序;
(6)若是驱动程序调用WdfInterruptCreate例程,WDF框架就会在资源列表中分配给中断资源给驱动程序;
(7)设备进入工做状态后,KMDF调用EvtDevicePrepareHardware例程传递两个资源列表,驱动程序保存这两个资源列表,直到WDF框架调用了EvtDeviceReleaseHardware例程。
驱动程序经过EvtDevicePrepareHardware得到内存资源后,须要用MmMapIoSpace函数将物理地址映射成虚拟地址。
85-108行定义了EvtDeviceReleaseHardware回调例程,其调用过程是EvtDevicePrepareHardware的逆过程,即得到虚拟地址后,利用MmUnMapIoSpace 函数将虚拟地址解映射成物理地址,而后再交给WDF框架释放,这里再也不赘述。
当 PCIe-SpaceWire接口卡设备被移除时,WDF框架会自动调用Spw_PCIeEvtDeviceReleaseHardware 函数释放设备和驱动程序的内存空间。因为系统每次检测到PCIe接口卡,会自动调用Spw_PCIeEvtDevicePrepareHardware函数提供内存资源,所以,断电或移除设备时,必须调用Spw_PCIeEvtDeviceReleaseHardware函数必须释放所分配的内存空间,不然,有可能致使内存溢出甚至操做系统崩溃。
110-135定义了EvtDeviceD0Entry和EvtDeviceD0Exit例程,WDF框架会在设备进入工做状态后调用EvtDeviceD0Entry回调例程,设备进入工做状态会在如下几种状况下发生:
因为设备进入工做状态后,WDF框架就会根据事件调用各类回调例程,因此EvtDeviceD0Entry例程里通常不须要处理任何任务。设备离开工做状态后,WDF调EvtDeviceD0Exit回调例程,一般EvtDeviceD0Exit例程也不须要处理任何任务。须要注意的是,在注册这两个例程的时候,必须调用WdfDeviceInitSetPnpPowerEventCallbacks来注册设备即插即用和电源管理回调例程。
Queue.c
1 #include "driver.h" 2 #include "queue.tmh" 3 4 #pragma warning(disable:4013) // assuming extern returning int 5 6 #ifdef ALLOC_PRAGMA 7 #pragma alloc_text (PAGE, Spw_PCIeEvtIoDeviceControl) 8 9 #endif 10 /* 11 单一的默认I/O队列和单一的请求处理函数,EvtIoDefault。KMDF将会将设备全部的请求发送到默认I/O队列, 12 而后它会调用驱动程序的EvtIoDefault来将每个请求递交给驱动程序。 13 14 *单一的默认I/O队列和多个请求处理函数,例如EvtIoRead、EvtIoWrite和EvtIoDeviceControl。KMDF会将设备全部的请求发送到默认I/O队列。 15 而后会调用驱动程序的EvtIoRead处理函数来递交读请求、调用EvtIoWrite处理函数来递交写请求、调用EvtIoDeviceControl处理函数来递交设备I/O控制请求。 16 */ 17 18 19 VOID 20 Spw_PCIeEvtIoDeviceControl( 21 IN WDFQUEUE Queue, 22 IN WDFREQUEST Request, 23 IN size_t OutputBufferLength, 24 IN size_t InputBufferLength, 25 IN ULONG IoControlCode 26 ) 27 { 28 WDFDEVICE device; 29 PDEVICE_CONTEXT pDevContext; 30 31 NTSTATUS status; 32 33 PVOID inBuffer; 34 PVOID outBuffer; 35 ULONG AddressOffset; 36 37 //PAGED_CODE(); do not uncomment this sentence 38 device = WdfIoQueueGetDevice(Queue); 39 pDevContext = GetDeviceContext(device); 40 41 switch (IoControlCode) { 42 //根据CTL_CODE请求码做相应的处理 43 case Spw_PCIe_IOCTL_WRITE_OFFSETADDRESS: 44 status = WdfRequestRetrieveInputBuffer( 45 Request, 46 sizeof(ULONG), 47 &inBuffer, 48 NULL 49 ); 50 pDevContext->OffsetAddressFromApp = *(ULONG*)inBuffer; 51 WdfRequestCompleteWithInformation(Request, status, sizeof(ULONG)); 52 if (!NT_SUCCESS(status)){ 53 goto Exit; 54 } 55 break; 56 57 case Spw_PCIe_IOCTL_IN_BUFFERED: 58 status = WdfRequestRetrieveInputBuffer( 59 Request, 60 sizeof(ULONG), 61 &inBuffer, 62 NULL 63 ); 64 AddressOffset = PCIE_WRITE_MEMORY_OFFSET + pDevContext->OffsetAddressFromApp; 65 *(ULONG*)WDF_PTR_ADD_OFFSET(pDevContext->BAR0_VirtualAddress, AddressOffset) = *(ULONG*)inBuffer; 66 WdfRequestCompleteWithInformation(Request, status, sizeof(ULONG)); 67 if (!NT_SUCCESS(status)){ 68 goto Exit; 69 } 70 break; 71 72 case Spw_PCIe_IOCTL_OUT_BUFFERED: 73 status = WdfRequestRetrieveOutputBuffer( 74 Request, 75 sizeof(ULONG), 76 &outBuffer, 77 NULL 78 ); 79 AddressOffset = PCIE_WRITE_MEMORY_OFFSET + pDevContext->OffsetAddressFromApp; 80 //-------------------------------------------------------------------------- 81 *(ULONG*)outBuffer = *(ULONG*)WDF_PTR_ADD_OFFSET(pDevContext->BAR0_VirtualAddress, AddressOffset); 82 //-------------------------------------------------------------------------- 83 //*(ULONG*)outBuffer = pDevContext->Counter_i; 84 //-------------------------------------------------------------------------- 85 WdfRequestCompleteWithInformation(Request, status, sizeof(ULONG)); 86 if (!NT_SUCCESS(status)){ 87 goto Exit; 88 } 89 break; 90 case Spw_PCIe_IOCTL_READ_PADDRESS: 91 //Just think about the size of the data when you are choosing the METHOD. 92 //METHOD_BUFFERED is typically the fastest for small (less the 16KB) buffers, 93 //and METHOD_IN_DIRECT and METHOD_OUT_DIRECT should be used for larger buffers than that. 94 //METHOD_BUFFERED,METHOD_OUT_DIRECT,METHOD_IN_DIRECT三种方式, 95 //输入缓冲区地址可经过调用WdfRequestRetrieveInputBuffer函数得到 96 //输出缓冲区地址可经过调用WdfRequestRetrieveOutputBuffer函数得到 97 98 status = WdfRequestRetrieveOutputBuffer( 99 Request, 100 sizeof(ULONG), 101 &outBuffer, 102 NULL 103 ); 104 105 *(ULONG*)outBuffer = pDevContext->PhysicalAddressRegister;//read BAR0 pysical address 106 107 WdfRequestCompleteWithInformation(Request, status, sizeof(ULONG)); 108 if (!NT_SUCCESS(status)){ 109 goto Exit; 110 } 111 break; 112 113 default: 114 status = STATUS_INVALID_DEVICE_REQUEST; 115 WdfRequestCompleteWithInformation(Request, status, 0); 116 break; 117 } 118 119 Exit: 120 if (!NT_SUCCESS(status)) { 121 WdfRequestCompleteWithInformation( 122 Request, 123 status, 124 0 125 ); 126 } 127 return; 128 }
整个源代码文件只定义了一个例程EvtIoDeviceControl,当WDF框架处理I/O请求时,根据I/O 请求的副功能码执行相应的操做,I/O 请求处理结束后,须要经过一个例程完成I/O请求,以通知应用程序处理结束。不然,会由于应用程序没法正常退出而致使系统挂起。接口卡驱动程序中处理I/O请求的例程为Spw_PCIeEvtIoDeviceControl,它根据应用程序传入控制字的不一样会执行不一样的任务,包括读BAR0物理起始地址、读寄存器、写寄存器、写入偏移地址。
Windows 2000及其之后的操做系统都是以I/O请求包的形式与驱动程序进行通讯的。在WDF驱动程序中,处理I/O请求的关键判断哪些类型的I/O请求由驱动程序处理,哪些类型的I/O请求由WDF框架自动处理。当Windows操做系统收到一个从应用程序传送过来的I/O请求后,I/O管理器将它封装成I/O请求包发送给设备驱动程序。常见的I/O请求包括:create, close, read, write, 和 device I/O control,分别表示建立设备、关闭设备、读操做、写操做和控制命令字传输。
应用程序执行I/O操做时,向I/O管理器提供了一个数据缓冲区。WDF框架提供三种数据传输方式:
在I/O请求处理中,WDF规定驱动程序必须包括如下一个或多个I/O回调例程,来处理从队列调度的I/O请求:
下面以完成一个读请求为例,描述WDF框架处理I/O请求的全过程
第1步,应用程序调用Win32 API函数ReadFile进行读操做;第2步,ReadFile函数调用NTDLL.dll中的原生函数NtReadFile,从而进入内核服务,I/O管理器将接管读操做。第3步,I/O管理器为读请求构造类型为IRP_MJ_READ的请求包;第4步,I/O管理器找到由WDF框架建立的设备对象,并将请求包发送到它的读派遣函数;第5步,WDF框架收到请求包后,查看WDF驱动是否注册了读回调例程,若是注册了,就将请求包封装成一个I/O请求对象把它放到WDF驱动的某个指定队列中;第6步,队列将I/O请求对象发送给WDF驱动处理,WDF驱动注册的读回调例程被执行。
现代操做系统好比Windows、Linux在内存管理上均采用分页机制。分页内存可被交换到硬盘,而非分页内存则不会交换到硬盘上。运行的程序代码中断请求优先级高于DISPATCH_LEVEL(包括DISPATCH_LEVEL)的,必须保证程序所在内存页为非分页内存,不然会形成系统挂起。在WDF驱动程序开发中,使用宏PAGE_CODE来标记某例程应在分页内存上。所以在驱动程序开发过程当中要特别注意PAGE_CODE的使用。
对于PCIe设备驱动开发,开发者还注意读写映射内存不能越界。好比在本次毕业设计中,BAR2为配置寄存器,编写程序时因为误写入BAR2映射的内存地址,形成操做系统一执行写操做就发生蓝屏。
在看完这几篇文章后,将源代码经过VS2013+WDK8.1编译就能生成相应PCI/PCIe硬件板卡的Windows驱动程序(.sys文件),为了实现对驱动程序的安装与验证,还须要编写INF文件和应用程序文件,这部分将在下一篇文章中讲述。
参考资料:
武安河. Windows设备驱动程序WDF开发
孔鹏. 基于WDF的光纤传输卡PCIe接口驱动的研究和实现
杨阿锋基于WDF的PCIe接口高速数据传输卡的驱动程序开发
广告时间~本人博士赚外快,驱动程序源代码及移植使用说明购买连接(很便宜啦~):https://item.taobao.com/item.htm?spm=2013.1.0.0.Ak5dSz&id=544655048960