原文出处: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 例程需要完成的任务主要包括:
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接口高速数据传输卡的驱动程序开发