基於WDF的PCI/PCIe接口卡Windows驅動程序(4)- 驅動程序代碼(源文件)

原文出處: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 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例程。EvtDevicePrepareHardwareEvtDeviceReleaseHardware兩個例程對硬件設備能否獲得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接口高速數據傳輸卡的驅動程序開發

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章