原文出处: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

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

 #include "driver.h"
#include "driver.tmh" #ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, Spw_PCIeEvtDeviceAdd)
#pragma alloc_text (PAGE, Spw_PCIeEvtDriverContextCleanup)
#endif NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
WDF_DRIVER_CONFIG config;
//WDFDRIVER driver;//????
NTSTATUS status = STATUS_SUCCESS;
WDF_OBJECT_ATTRIBUTES attributes; //
// Initialize WPP Tracing
//
WPP_INIT_TRACING( DriverObject, RegistryPath ); TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry"); //
// Register a cleanup callback so that we can call WPP_CLEANUP when
// the framework driver object is deleted during driver unload.
// WDF_OBJECT_ATTRIBUTES_INIT(&attributes); attributes.EvtCleanupCallback = Spw_PCIeEvtDriverContextCleanup; WDF_DRIVER_CONFIG_INIT(&config,
Spw_PCIeEvtDeviceAdd
); status = WdfDriverCreate(DriverObject,
RegistryPath,
&attributes,
&config,
WDF_NO_HANDLE
); if (!NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfDriverCreate failed %!STATUS!", status);
WPP_CLEANUP(DriverObject);
return status;
} TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit"); return status;
} NTSTATUS
Spw_PCIeEvtDeviceAdd(
_In_ WDFDRIVER Driver,
_Inout_ PWDFDEVICE_INIT DeviceInit
)
{
NTSTATUS status = STATUS_SUCCESS;
WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
WDF_OBJECT_ATTRIBUTES deviceAttributes;
WDFDEVICE device;
PDEVICE_CONTEXT deviceContext; WDFQUEUE queue;
WDF_IO_QUEUE_CONFIG queueConfig; /*+++++Interrupt
WDF_INTERRUPT_CONFIG interruptConfig;
-----*/
// WDF_IO_QUEUE_CONFIG ioQueueConfig; UNREFERENCED_PARAMETER(Driver); PAGED_CODE(); //采用WdfDeviceIoDirect方式
WdfDeviceInitSetIoType(DeviceInit, WdfDeviceIoDirect);//WdfDeviceIoBuffered???重要吗?
//When the I/O manager sends a request for buffered I/O, the IRP contains an internal copy of the caller's buffer
//rather than the caller's buffer itself. The I/O manager copies data from the caller's buffer to the internal buffer
//during a write request or from the internal buffer to the caller's buffer when the driver completes a read
//request.
//The WDF driver receives a WDF request object, which in turn contains an embedded WDF memory object.
//The memory object contains the address of the buffer on which the driver should operate. // status = Spw_PCIeCreateDevice(DeviceInit); //初始化即插即用和电源管理例程配置结构
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); //设置即插即用基本例程
pnpPowerCallbacks.EvtDevicePrepareHardware = Spw_PCIeEvtDevicePrepareHardware;
pnpPowerCallbacks.EvtDeviceReleaseHardware = Spw_PCIeEvtDeviceReleaseHardware;
pnpPowerCallbacks.EvtDeviceD0Entry = Spw_PCIeEvtDeviceD0Entry;
pnpPowerCallbacks.EvtDeviceD0Exit = Spw_PCIeEvtDeviceD0Exit; //注册即插即用和电源管理例程
WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks); WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT); //deviceAttributes.EvtCleanupCallback = Spw_PCIeEvtDriverContextCleanup;
//
// Set WDFDEVICE synchronization scope. By opting for device level
// synchronization scope, all the queue and timer callbacks are
// synchronized with the device-level spinlock.
//
deviceAttributes.SynchronizationScope = WdfSynchronizationScopeDevice; status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
if (!NT_SUCCESS(status)) {
return status;
}
deviceContext = GetDeviceContext(device);///????
//deviceContext->Device = device;
//
// 初始化Context这个结构里的所有成员.
//
//deviceContext->PrivateDeviceData = 0;
/*++++++Interrupt & DMA
//设置中断服务例程和延迟过程调用
WDF_INTERRUPT_CONFIG_INIT(&interruptConfig,
PCISample_EvtInterruptIsr,
PCISample_EvtInterruptDpc); //创建中断对象
status = WdfInterruptCreate(device,
&interruptConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&pDeviceContext->Interrupt);
if (!NT_SUCCESS (status)) {
return status;
} status = InitializeDMA(device); if (!NT_SUCCESS(status)) {
return status;
}
-----*/
//WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchSequential);
//Initialize the Queue
// queueConfig.EvtIoDefault = Spw_PCIeEvtIoDefault;
// queueConfig.EvtIoWrite = Spw_PCIeEvtIoWrite;
//queueConfig.EvtIoRead = Spw_PCIeEvtIoRead;
// queueConfig.EvtIoStop = Spw_PCIeEvtIoStop;
//The driver must initialize the WDF_IO_QUEUE_CONFIG structure
//by calling WDF_IO_QUEUE_CONFIG_INIT or WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE.
//用default初始化default 队列,用另一个初始化非default队列
WDF_IO_QUEUE_CONFIG_INIT(
&queueConfig,
WdfIoQueueDispatchSequential
); queueConfig.EvtIoDeviceControl = Spw_PCIeEvtIoDeviceControl; status = WdfIoQueueCreate(device, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, &queue);
if (!NT_SUCCESS(status)) {
return status;
} //对于非默认队列,必须指定要分发的I/O请求类型
//The WdfDeviceConfigureRequestDispatching method causes the framework to queue a specified type of I/O requests to a specified I/O queue.
status = WdfDeviceConfigureRequestDispatching(
device,
queue,
WdfRequestTypeDeviceControl
);
if (!NT_SUCCESS(status)) {
return status;
}
//创建驱动程序接口与应用程序通信
status = WdfDeviceCreateDeviceInterface(
device,
(LPGUID)&GUID_DEVINTERFACE_Spw_PCIe,
NULL // ReferenceString
);
if (!NT_SUCCESS(status)) {
return status;
}
/*
if (NT_SUCCESS(status)) {
//
// Initialize the I/O Package and any Queues
//
status = Spw_PCIeQueueInitialize(device);
}
*/
//deviceContext->MemLength = MAXNLEN; //TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit"); return status;
} VOID
Spw_PCIeEvtDriverContextCleanup(
_In_ WDFOBJECT DriverObject
)
/*++
Routine Description: Free all the resources allocated in DriverEntry. Arguments: DriverObject - handle to a WDF Driver object. Return Value: VOID. --*/
{
UNREFERENCED_PARAMETER(DriverObject); PAGED_CODE (); TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry"); //没有必要清除WDFINTERRUPT对象,因为框架会自动清除
// Stop WPP Tracing
//
WPP_CLEANUP( WdfDriverWdmGetDriverObject(DriverObject) ); }

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

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

 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

 #include "driver.h"
#include "device.tmh" #pragma warning(disable:4013) // assuming extern returning int
#ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, Spw_PCIeEvtDevicePrepareHardware)
#pragma alloc_text(PAGE, Spw_PCIeEvtDeviceReleaseHardware)
#pragma alloc_text(PAGE, Spw_PCIeEvtDeviceD0Entry)
#pragma alloc_text(PAGE, Spw_PCIeEvtDeviceD0Exit) #endif NTSTATUS
Spw_PCIeEvtDevicePrepareHardware(
IN WDFDEVICE Device,
IN WDFCMRESLIST ResourceList,
IN WDFCMRESLIST ResourceListTranslated
)
{
ULONG i;
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_CONTEXT pDeviceContext; PCM_PARTIAL_RESOURCE_DESCRIPTOR descriptor;//record the Hareware resource that OS dispatched to PCIe
/*
在Windows驱动开发中,PCM_PARTIAL_RESOURCE_DESCRIPTOR记录了为PCI设备分配的硬件资源,
可能有CmResourceTypePort, CmResourceTypeMemory等,
后者表示一段memory地址空间,顾名思义,是通过memory space访问的,
前者表示一段I/O地址空间,但其flag有CM_RESOURCE_PORT_MEMORY和CM_RESOURCE_PORT_IO两种,
分别表示通过memory space访问以及通过I/O space访问,这就是PCI请求与实际分配的差异,
在x86下,CmResourceTypePort的flag都是CM_RESOURCE_PORT_IO,即表明PCI设备请求的是I/O地址空间,分配的也是I/O地址空间,
而在ARM或Alpha等下,flag是CM_RESOURCE_PORT_MEMORY,表明即使PCI请求的I/O地址空间,但分配在了memory space,
我们需要通过memory space访问I/O设备(通过MmMapIoSpace映射物理地址空间到虚拟地址空间,当然,是内核的虚拟地址空间,这样驱动就可以正常访问设备了)。
*/
PAGED_CODE(); // UNREFERENCED_PARAMETER(Resources);//告诉编译器不要发出Resources没有被引用的警告 pDeviceContext = GetDeviceContext(Device);
pDeviceContext->MemBaseAddress = NULL;
pDeviceContext->Counter_i = ;
//get resource
for (i = ; i < WdfCmResourceListGetCount(ResourceListTranslated); i++) { descriptor = WdfCmResourceListGetDescriptor(ResourceListTranslated, i);
//if failed:
if (!descriptor) {
return STATUS_DEVICE_CONFIGURATION_ERROR;
} switch (descriptor->Type) { case CmResourceTypeMemory:
//MmMapIoSpace将物理地址转换成系统内核模式地址
if (i == ){
pDeviceContext->PhysicalAddressRegister = descriptor->u.Memory.Start.LowPart;
pDeviceContext->BAR0_VirtualAddress = MmMapIoSpace(
descriptor->u.Memory.Start,
descriptor->u.Memory.Length,
MmNonCached);
} pDeviceContext->MemBaseAddress = MmMapIoSpace(
descriptor->u.Memory.Start,
descriptor->u.Memory.Length,
MmNonCached);
pDeviceContext->MemLength = descriptor->u.Memory.Length; break; default:
break;
}
if (!pDeviceContext->MemBaseAddress){
return STATUS_INSUFFICIENT_RESOURCES;
}
}
pDeviceContext->Counter_i = i;
DbgPrint("EvtDevicePrepareHardware - ends\n"); return STATUS_SUCCESS;
} NTSTATUS
Spw_PCIeEvtDeviceReleaseHardware(
IN WDFDEVICE Device,
IN WDFCMRESLIST ResourceListTranslated
)
{
PDEVICE_CONTEXT pDeviceContext = NULL; PAGED_CODE(); DbgPrint("EvtDeviceReleaseHardware - begins\n"); pDeviceContext = GetDeviceContext(Device); if (pDeviceContext->MemBaseAddress) {
//MmUnmapIoSpace解除物理地址与系统内核模式地址的关联
MmUnmapIoSpace(pDeviceContext->MemBaseAddress, pDeviceContext->MemLength);
pDeviceContext->MemBaseAddress = NULL;
} DbgPrint("EvtDeviceReleaseHardware - ends\n"); return STATUS_SUCCESS;
} NTSTATUS
Spw_PCIeEvtDeviceD0Entry(
IN WDFDEVICE Device,
IN WDF_POWER_DEVICE_STATE PreviousState
)
{
UNREFERENCED_PARAMETER(Device);
UNREFERENCED_PARAMETER(PreviousState); return STATUS_SUCCESS;
} NTSTATUS
Spw_PCIeEvtDeviceD0Exit(
IN WDFDEVICE Device,
IN WDF_POWER_DEVICE_STATE TargetState
)
{
UNREFERENCED_PARAMETER(Device);
UNREFERENCED_PARAMETER(TargetState); PAGED_CODE(); return STATUS_SUCCESS;
}

13-83行定义了EvtDevicePrepareHardware例程。EvtDevicePrepareHardware和EvtDeviceReleaseHardware两个例程对硬件设备能否获得Windows操作系统分配的资源起着至关重要的作用。EvtDevicePrepareHardware的任务主要包括获得内存资源、内存物理地址与虚拟地址的映射、I/O端口映射和中断资源分配。

EvtDevicePrepareHardware例程的原型声明如下:

 NTSTATUS EvtDevicePrepareHardware(
IN WDFDEVICE Device,
IN WDFCMRESLIST ResourceList,
IN WDFCMRESLIST ResourceListTranslated
) ;

传入函数的三个参数,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

 #include "driver.h"
#include "queue.tmh" #pragma warning(disable:4013) // assuming extern returning int #ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, Spw_PCIeEvtIoDeviceControl) #endif
/*
单一的默认I/O队列和单一的请求处理函数,EvtIoDefault。KMDF将会将设备所有的请求发送到默认I/O队列,
然后它会调用驱动程序的EvtIoDefault来将每一个请求递交给驱动程序。 *单一的默认I/O队列和多个请求处理函数,例如EvtIoRead、EvtIoWrite和EvtIoDeviceControl。KMDF会将设备所有的请求发送到默认I/O队列。
然后会调用驱动程序的EvtIoRead处理函数来递交读请求、调用EvtIoWrite处理函数来递交写请求、调用EvtIoDeviceControl处理函数来递交设备I/O控制请求。
*/ VOID
Spw_PCIeEvtIoDeviceControl(
IN WDFQUEUE Queue,
IN WDFREQUEST Request,
IN size_t OutputBufferLength,
IN size_t InputBufferLength,
IN ULONG IoControlCode
)
{
WDFDEVICE device;
PDEVICE_CONTEXT pDevContext; NTSTATUS status; PVOID inBuffer;
PVOID outBuffer;
ULONG AddressOffset; //PAGED_CODE(); do not uncomment this sentence
device = WdfIoQueueGetDevice(Queue);
pDevContext = GetDeviceContext(device); switch (IoControlCode) {
//根据CTL_CODE请求码作相应的处理
case Spw_PCIe_IOCTL_WRITE_OFFSETADDRESS:
status = WdfRequestRetrieveInputBuffer(
Request,
sizeof(ULONG),
&inBuffer,
NULL
);
pDevContext->OffsetAddressFromApp = *(ULONG*)inBuffer;
WdfRequestCompleteWithInformation(Request, status, sizeof(ULONG));
if (!NT_SUCCESS(status)){
goto Exit;
}
break; case Spw_PCIe_IOCTL_IN_BUFFERED:
status = WdfRequestRetrieveInputBuffer(
Request,
sizeof(ULONG),
&inBuffer,
NULL
);
AddressOffset = PCIE_WRITE_MEMORY_OFFSET + pDevContext->OffsetAddressFromApp;
*(ULONG*)WDF_PTR_ADD_OFFSET(pDevContext->BAR0_VirtualAddress, AddressOffset) = *(ULONG*)inBuffer;
WdfRequestCompleteWithInformation(Request, status, sizeof(ULONG));
if (!NT_SUCCESS(status)){
goto Exit;
}
break; case Spw_PCIe_IOCTL_OUT_BUFFERED:
status = WdfRequestRetrieveOutputBuffer(
Request,
sizeof(ULONG),
&outBuffer,
NULL
);
AddressOffset = PCIE_WRITE_MEMORY_OFFSET + pDevContext->OffsetAddressFromApp;
//--------------------------------------------------------------------------
*(ULONG*)outBuffer = *(ULONG*)WDF_PTR_ADD_OFFSET(pDevContext->BAR0_VirtualAddress, AddressOffset);
//--------------------------------------------------------------------------
//*(ULONG*)outBuffer = pDevContext->Counter_i;
//--------------------------------------------------------------------------
WdfRequestCompleteWithInformation(Request, status, sizeof(ULONG));
if (!NT_SUCCESS(status)){
goto Exit;
}
break;
case Spw_PCIe_IOCTL_READ_PADDRESS:
//Just think about the size of the data when you are choosing the METHOD.
//METHOD_BUFFERED is typically the fastest for small (less the 16KB) buffers,
//and METHOD_IN_DIRECT and METHOD_OUT_DIRECT should be used for larger buffers than that.
//METHOD_BUFFERED,METHOD_OUT_DIRECT,METHOD_IN_DIRECT三种方式,
//输入缓冲区地址可通过调用WdfRequestRetrieveInputBuffer函数获得
//输出缓冲区地址可通过调用WdfRequestRetrieveOutputBuffer函数获得 status = WdfRequestRetrieveOutputBuffer(
Request,
sizeof(ULONG),
&outBuffer,
NULL
); *(ULONG*)outBuffer = pDevContext->PhysicalAddressRegister;//read BAR0 pysical address WdfRequestCompleteWithInformation(Request, status, sizeof(ULONG));
if (!NT_SUCCESS(status)){
goto Exit;
}
break; default:
status = STATUS_INVALID_DEVICE_REQUEST;
WdfRequestCompleteWithInformation(Request, status, );
break;
} Exit:
if (!NT_SUCCESS(status)) {
WdfRequestCompleteWithInformation(
Request,
status, );
}
return;
}

整个源代码文件只定义了一个例程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

基于WDF的PCI/PCIe接口卡Windows驱动程序(4)- 驱动程序代码(源文件)的更多相关文章

  1. 基于WDF的PCI/PCIe接口卡Windows驱动程序(2)-开发者需要了解的WDF中的一些重要的概念

    原文出处:http://www.cnblogs.com/jacklu/p/4646601.html 本科毕业设计是这方面的工作,所以想开几篇博客来介绍使用WDF开发PCI/PCIe接口卡的驱动程序方法 ...

  2. 基于WDF的PCI/PCIe接口卡Windows驱动程序(1)-WDF概述及开发环境搭建

    原文出处:http://www.cnblogs.com/jacklu/p/4619110.html 本科毕业设计是这方面的工作,所以想开几篇博客来介绍使用WDF开发PCI/PCIe接口卡的驱动程序方法 ...

  3. 基于WDF的PCI/PCIe接口卡Windows驱动程序(3)- 驱动程序代码(头文件)

    原文出处:http://www.cnblogs.com/jacklu/p/4679304.html 在WDF的PCIe驱动程序中,共有四个.h文件(Public.h  Driver.h  Device ...

  4. 基于WDF的PCI/PCIe接口卡Windows驱动程序(5)-如何为硬件移植驱动程序

    原文地址:http://www.cnblogs.com/jacklu/p/6139347.html 正如前几篇博客所说,使用WDF开发PCIe驱动程序是我本科毕业设计的主要工作.在读研的两年,我也分别 ...

  5. 转:基于 xilinx vivado 的PCIE ip核设置与例程代码详解

    连接:https://blog.csdn.net/u014586651/article/details/103826967#comments

  6. 基于Camera Link和PCIe DMA的多通道视频采集和显示系统

    基于Camera Link和PCIe DMA的多通道视频采集和显示系统 在主机端PCIe驱动的控制和调度下,视频采集与显示系统可以同时完成对多个Camera Link接口视频采集以及Camera Li ...

  7. PCIE_DMA实例五:基于XILINX XDMA的PCIE高速采集卡

    PCIE_DMA实例五:基于XILINX XDMA的PCIE高速采集卡 一:前言 这一年关于PCIE高速采集卡的业务量激增,究其原因,发现百度"xilinx pcie dma",出 ...

  8. KVM 介绍(4):I/O 设备直接分配和 SR-IOV [KVM PCI/PCIe Pass-Through SR-IOV]

    学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分 ...

  9. Linux PCI/PCI-E设备配置空间读取与修改

    Linux PCI/PCI-E设备配置空间读取与修改 1 前言 PCI和PCI Express,是计算机常使用的一种高速总线.操作系统中的PCI/PCI-E设备驱动以及操作系统内核,都需要访问PCI及 ...

随机推荐

  1. c/c++ 函数指针 指针函数 数组的引用 指针数组 数组指针

    1.指针数组数组指针 引用数组 数组的引用 int *a[10] 指针数组 每一个元素都是一个指针 Int (*a)[10] 数组指针 P指向一个含有10个元素的数组 Int (&a)[10] ...

  2. Term_Application

    1 CRM(Customer Relationship Management)客户关系管理: (对ERP下游管理不足的补充) 是一个获取.保持和增加可获利客户的方法和过程. 市场营销.销售管理.客户关 ...

  3. 在线生成CSS样式和兼容的字体格式

    http://www.fontsquirrel.com/tools/webfont-generator 在线生成CSS样式和兼容的字体格式.

  4. 细说Javascript之null、undefined和NaN

    首先简单介绍一下Javascript中的数据类型,Javascript中的数据类型有undefined,boolen,number,string和object等5种,前4种是原始类型,第5种是引用类型 ...

  5. Validation failed for one or more entities. See ‘EntityValidationErrors’解决方法【转载】

    摘自:http://www.cnblogs.com/douqiumiao/default.aspx?opt=msg Validation failed for one or more entities ...

  6. TIOBE Index for December 2015(转载)

    There is hardly any doubt about it, Java will become TIOBE's programming language of the year 2015. ...

  7. oracle11g安装和基本的使用-转载

    一.测试操作系统和硬件环境是否符合,我使用的是win2008企业版.下面的都是step by step看图就ok了,不再详细解释. 请留意下面的总的设置步骤:--------------------- ...

  8. CPS冥想 - 2 手撸控制流

    原博客链接:http://blogs.msdn.com/b/ericlippert/archive/2010/10/22/continuation-passing-style-revisited-pa ...

  9. 闲谈Tomcat性能优化

    Tomcat在各位JavaWeb从业者常常就是默认的开发环境,但是Tomcat的默认配置作为生产环境,尤其是内存和线程的配置,默认都很低,容易成为性能瓶颈. 幸好Tomcat还有很多的提升空间.下文介 ...

  10. Sonar升级遇到的那些事儿

    目录 背景 如何升级 如何回滚 问题解决 参考 背景 目前我们用SonarQube版本是4.0,这次准备升级到最新版本5.1, 以便支持以后的JavaScript的项目. 如何升级 我们可以直接跨越版 ...