博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于WDF的PCI/PCIe接口卡Windows驱动程序(4)- 驱动程序代码(源文件)
阅读量:5080 次
发布时间:2019-06-13

本文共 21637 字,大约阅读时间需要 72 分钟。

原文出处:http://www.cnblogs.com/jacklu/p/4687325.html

本篇文章将对PCIe驱动程序的部分源文件代码作详细解释与说明。完整代码,有偿提供~整个WDF驱动程序工程共包含4个头文件(已经在上篇文章中讲解)和3个.c文件(Driver.c  Device.c   Queue.c)

Driver.c

在看复杂的代码前,先给出程序流程图

 

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 level117     // synchronization scope, all the queue and timer callbacks are118     // 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 & DMA133     //设置中断服务例程和延迟过程调用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 Queue155     //        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         WdfIoQueueDispatchSequential165         );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         WdfRequestTypeDeviceControl181         );182     if (!NT_SUCCESS(status)) {183         return status;184     }185     //创建驱动程序接口与应用程序通信186     status = WdfDeviceCreateDeviceInterface(187         device,188         (LPGUID)&GUID_DEVINTERFACE_Spw_PCIe,189         NULL // ReferenceString190         );191     if (!NT_SUCCESS(status)) {192         return status;193     }194     /*195     if (NT_SUCCESS(status)) {196     //197     // Initialize the I/O Package and any Queues198     //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 VOID210 Spw_PCIeEvtDriverContextCleanup(211     _In_ WDFOBJECT DriverObject212     )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 Tracing236     //237     WPP_CLEANUP( WdfDriverWdmGetDriverObject(DriverObject) );238 239 }

4-8行是做一些预处理,驱动程序开发中,需要为每个函数指定位于分页内存还是非分页内存。INIT标识是指此函数为入口函数,驱动成功加载后可以从内存删除。PAGE标识是指此函数可以在驱动运行时被交换到硬盘上,如果不指定,将被编译器默认为非分页内存。

11-58行定义了DriverEntry函数,每个 KMDF 驱动程序必须有一个 DriverEntry 例程,当操作系统检测到有新硬 件设备插入后,会查找它对应的驱动程序,找到这个驱动程序中的DriverEntry 例程。DriverEntry 是驱动程序的入口,它相当于 C 语言程序里的 main 函数。 DriverEntry 例程的原型声明如下:

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 例程需要完成的任务主要包括:

  • 激活 WPP( Windows software trace preprocessor)软件调试,为可选任务;(对应代码25-27行)
  • 注册驱动程序的 EvtDriverDeviceAdd 回调函数;(对应代码38-40行)
  • 创建一个驱动程序对象, 向框架“注册”驱动程序;(对应代码42-53行)

61-206行定义了EvtDriverDeviceAdd函数。每个支持即插即用的 KMDF 驱动程序必须有 EvtDriverDeviceAdd 回调例程, 每次操作系统枚举设备时, PnP 管理器就调用这个回调例程。 EvtDriverDeviceAdd 例程的主要任务包括:

  • 创建并初始化设备对象和相应的上下文区(122-126行);
  • 设置传输方式(86行)、 初始化即插即用和电源管理配置结构(99行), 注册即插即用和电源管理例程(101-108行);
  • 初始化队列配置结构(162-165行), 注册 I/O 处理例程(167行), 创建 I/O 队列(170行), 指定要分发的 I/O 请求类型(177-184行), 创建 GUID 接口(185-194行)。

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 NTSTATUS111 Spw_PCIeEvtDeviceD0Entry(112 IN  WDFDEVICE Device,113 IN  WDF_POWER_DEVICE_STATE PreviousState114 )115 {116     UNREFERENCED_PARAMETER(Device);117     UNREFERENCED_PARAMETER(PreviousState);118 119     return STATUS_SUCCESS;120 }121 122 123 NTSTATUS124 Spw_PCIeEvtDeviceD0Exit(125 IN  WDFDEVICE Device,126 IN  WDF_POWER_DEVICE_STATE TargetState127 )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 ResourceListTranslated5 ) ;

传入函数的三个参数,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回调例程,设备进入工作状态会在以下几种情况下发生:

  • 即插即用设备被系统发现;
  • 操作系统和设备从睡眠状态被唤醒;
  • (如果设备支持低电压闲置状态)设备从低电压闲置状态被唤醒;
  • PnP管理器重新为设备分配资源。

由于设备进入工作状态后,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             NULL103             );104 105             *(ULONG*)outBuffer = pDevContext->PhysicalAddressRegister;//read BAR0 pysical address106 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             0125             );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框架提供三种数据传输方式:

  •  buffered方式:I/O管理器会创建与应用程序数据缓冲区完全相同的系统缓冲区,驱动程序在这个缓冲区工作,由I/O管理器完成复制数据任务;
  •  direct方式:I/O管理器锁定应用程序缓冲区的物理内存页,并创建一个MDL(内存描述符表)来描述该页,驱动程序将使用MDL工作;
  •  neither方式:I/O管理器把应用程序缓冲区的虚拟地址传递给驱动程序,一般不采用这种方式。

在I/O请求处理中,WDF规定驱动程序必须包括以下一个或多个I/O回调例程,来处理从队列调度的I/O请求:

  •  EvtIoRead
  •  EvtIoWrite
  •  EvtIoDeviceIoControl
  •  EvtIoInternalDeeviceControl
  •  EvtIoDefault

 下面以完成一个读请求为例,描述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://www.cnblogs.com/yuanming/p/6722918.html

你可能感兴趣的文章
读构建之法第四章第十七章有感
查看>>
Windows Phone开发(4):框架和页 转:http://blog.csdn.net/tcjiaan/article/details/7263146
查看>>
Unity3D研究院之打开Activity与调用JAVA代码传递参数(十八)【转】
查看>>
python asyncio 异步实现mongodb数据转xls文件
查看>>
TestNG入门
查看>>
【ul开发攻略】HTML5/CSS3菜单代码 阴影+发光+圆角
查看>>
IOS-图片操作集合
查看>>
IO—》Properties类&序列化流与反序列化流
查看>>
测试计划
查看>>
Mysql与Oracle 的对比
查看>>
jquery实现限制textarea输入字数
查看>>
Codeforces 719B Anatoly and Cockroaches
查看>>
jenkins常用插件汇总
查看>>
c# 泛型+反射
查看>>
第九章 前后查找
查看>>
Python学习资料
查看>>
jQuery 自定义函数
查看>>
jquery datagrid 后台获取datatable处理成正确的json字符串
查看>>
ActiveMQ与spring整合
查看>>
web服务器
查看>>