原文出處:http://www.cnblogs.com/jacklu/p/4687325.html
如果你覺得這篇博客對你的項目有用,請引用以下論文:
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)
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 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標識是指此函數可以在驅動運行時被交換到硬盤上,如果不指定,將被編譯器默認為非分頁內存。
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 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回調例程,設備進入工作狀態會在以下幾種情況下發生:
- 即插即用設備被系統發現;
- 操作系統和設備從睡眠狀態被喚醒;
- (如果設備支持低電壓閑置狀態)設備從低電壓閑置狀態被喚醒;
- 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 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框架提供三種數據傳輸方式:
- 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接口高速數據傳輸卡的驅動程序開發
廣告時間~本人博士賺外快,如需要完整的驅動程序源代碼請聯系合作email: 346457821@qq.com