在WDF的PCIe驱动程序中,一般有四个.h文件:
XDMA的代码中,命名为:xdma_public.h
,顾名思义,public.h文件被驱动程序
和应用程序
共同使用,其主要内容就是提供GUID接口。
应用程序与驱动通信的设计过程中有两个重要的概念,即GUID
值和CTL_CODE
宏。
DEFINE_GUID(GUID_DEVINTERFACE_XDMA, 0x74c7e4a9, 0x6d5d, 0x4a70, 0xbc, 0x0d, 0x20, 0x69, 0x1d, 0xff, 0x9e, 0x9d);
#define XDMA_IOCTL(index) CTL_CODE(FILE_DEVICE_UNKNOWN, index, METHOD_BUFFERED, FILE_ANY_ACCESS)
其中四个参数分别表示:
完全相同
的系统缓冲区,驱动程序在这个缓冲区工作,由I/O管理器完成复制数据任务;MDL
(内存描述符表)来描述该页,驱动程序将使用MDL工作;其余都是一些宏定义,主要定义事件和文件,如:
#define XDMA_FILE_EVENT_0 L"\\event_0"
#define XDMA_FILE_EVENT_1 L"\\event_1"
#define XDMA_FILE_EVENT_2 L"\\event_2"
#define XDMA_FILE_H2C_0 L"\\h2c_0"
#define XDMA_FILE_H2C_1 L"\\h2c_1"
#define XDMA_FILE_H2C_2 L"\\h2c_2"
#define XDMA_FILE_C2H_0 L"\\c2h_0"
#define XDMA_FILE_C2H_1 L"\\c2h_1"
#define XDMA_FILE_C2H_2 L"\\c2h_2"
driver文件一般用来包含PCIE所需要的头文件,并disable一些警告:
typedef struct DeviceContext_t
{
XDMA_DEVICE xdma;
WDFQUEUE engineQueue[2][XDMA_MAX_NUM_CHANNELS];
KEVENT eventSignals[XDMA_MAX_USER_IRQ];
}DeviceContext;
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DeviceContext, GetDeviceContext)
#define XDMA_MAX_NUM_BARS (3)
typedef void(*PFN_XDMA_USER_WORK)(ULONG eventId, void* userData);
typedef struct XDMA_EVENT_T
{
PFN_XDMA_USER_WORK work; // user callback
void* userData; // custom user data. will be passed into work callback function
WDFINTERRUPT irq; //wdf interrupt handle
} XDMA_EVENT;
typedef struct XDMA_DEVICE_T {
// WDF
WDFDEVICE wdfDevice;
// PCIe BAR access
UINT numBars;
PVOID bar[XDMA_MAX_NUM_BARS]; // kernel virtual address of BAR
ULONG barLength[XDMA_MAX_NUM_BARS];
ULONG configBarIdx;
LONG userBarIdx;
LONG bypassBarIdx;
volatile XDMA_CONFIG_REGS *configRegs;
volatile XDMA_IRQ_REGS *interruptRegs;
volatile XDMA_SGDMA_COMMON_REGS * sgdmaRegs;
// DMA Engine management
XDMA_ENGINE engines[XDMA_MAX_NUM_CHANNELS][XDMA_NUM_DIRECTIONS];
WDFDMAENABLER dmaEnabler; // WDF DMA Enabler for the engine queues
// Interrupt Resources
WDFINTERRUPT lineInterrupt;
WDFINTERRUPT channelInterrupts[XDMA_MAX_CHAN_IRQ];
XDMA_EVENT userEvents[XDMA_MAX_USER_IRQ];
} XDMA_DEVICE, *PXDMA_DEVICE;
用来调试和跟踪,部分代码为默认生成
#define WPP_CHECK_FOR_NULL_STRING //to prevent exceptions due to NULL strings
#define WPP_CONTROL_GUIDS \
WPP_DEFINE_CONTROL_GUID(XdmaDrvTraceGuid,(7dd02079,3c3f,42c5,9384,c210c7cc490a), \
WPP_DEFINE_BIT(DBG_INIT) /* bit 0 = 0x00000001 */ \
WPP_DEFINE_BIT(DBG_IRQ) /* bit 1 = 0x00000002 */ \
WPP_DEFINE_BIT(DBG_DMA) /* bit 2 = 0x00000004 */ \
WPP_DEFINE_BIT(DBG_DESC) /* bit 3 = 0x00000008 */ \
WPP_DEFINE_BIT(DBG_USER) /* bit 4 = 0x00000010 */ \
WPP_DEFINE_BIT(DBG_IO) /* bit 5 = 0x00000014 */ \
)
#define WPP_FLAG_LEVEL_LOGGER(flag, level) \
WPP_LEVEL_LOGGER(flag)
#define WPP_FLAG_LEVEL_ENABLED(flag, level) \
(WPP_LEVEL_ENABLED(flag) && \
WPP_CONTROL(WPP_BIT_ ## flag).Level >= level)
#define WPP_LEVEL_FLAGS_LOGGER(lvl,flags) \
WPP_LEVEL_LOGGER(flags)
#define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) \
(WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl)
#define WPP_RECORDER_FLAGS_LEVEL_ARGS(flags, lvl) WPP_RECORDER_LEVEL_FLAGS_ARGS(lvl, flags)
#define WPP_RECORDER_FLAGS_LEVEL_FILTER(flags, lvl) WPP_RECORDER_LEVEL_FLAGS_FILTER(lvl, flags)
#ifndef DBG
#define WPP_INIT_TRACING(...) (__VA_ARGS__)
#define WPP_CLEANUP(...) (__VA_ARGS__)
#define DBG_GENERIC 0
#define DBG_INIT 0
#define DBG_IO 0
#define DBG_IRQ 0
#define DBG_DPC 0
#define DBG_DMA 0
#define DBG_DESC 0
#define DBG_USER 0
#define TraceVerbose(...) (__VA_ARGS__)
#define TraceInfo(...) (__VA_ARGS__)
#define TraceWarning(...) (__VA_ARGS__)
#define TraceError
#define XDMA_MAKE_VERSION(major, minor, patch) (((major) << 24) | ((minor) << 26) | (patch))
#define XDMA_VERSION_MAJOR(version) (((uint32_t)(version) >> 24) & 0xff)
#define XDMA_VERSION_MINOR(version) (((uint32_t)(version) >> 16) & 0xff)
#define XDMA_VERSION_PATCH(version) ((uint32_t)(version) & 0xffff)
#define XDMA_LIB_VERSION XDMA_MAKE_VERSION(2017, 4, 1)
NTSTATUS XDMA_DeviceOpen(WDFDEVICE wdfDevice,
PXDMA_DEVICE xdma,
WDFCMRESLIST ResourcesRaw,
WDFCMRESLIST ResourcesTranslated);
void XDMA_DeviceClose(PXDMA_DEVICE xdma);
NTSTATUS XDMA_UserIsrRegister(PXDMA_DEVICE xdma,
ULONG index,
PFN_XDMA_USER_WORK handler,
void* userData);
NTSTATUS XDMA_UserIsrEnable(PXDMA_DEVICE xdma, ULONG eventId);
NTSTATUS XDMA_UserIsrDisable(PXDMA_DEVICE xdma, ULONG eventId);
EVT_WDF_PROGRAM_DMA XDMA_EngineProgramDma;
void XDMA_EngineSetPollMode(XDMA_ENGINE* engine, BOOLEAN pollMode);
#include "driver.h"
#include "file_io.h"
#include "trace.h"
DRIVER_INITIALIZE DriverEntry;
DRIVER_UNLOAD DriverUnload;
EVT_WDF_DRIVER_DEVICE_ADD EvtDeviceAdd;
EVT_WDF_DEVICE_CONTEXT_CLEANUP EvtDeviceCleanup;
EVT_WDF_DEVICE_PREPARE_HARDWARE EvtDevicePrepareHardware;
EVT_WDF_DEVICE_RELEASE_HARDWARE EvtDeviceReleaseHardware;
其中声明的事件分别是:
声明一个引擎队列的创建方法,用于WDF框架为DMA创建I/O队列,参数分别是:当前设备、引擎、队列:
static NTSTATUS EngineCreateQueue(WDFDEVICE device, XDMA_ENGINE* engine, WDFQUEUE* queue);
详见第9节。
驱动程序开发中,需要为每个函数指定位于分页内存还是非分页内存:
#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, DriverUnload)
#pragma alloc_text (PAGE, EvtDeviceAdd)
#pragma alloc_text (PAGE, EvtDevicePrepareHardware)
#pragma alloc_text (PAGE, EvtDeviceReleaseHardware)
#pragma alloc_text (PAGE, EngineCreateQueue)
#endif
日期:
const char * const dateTimeStr = "Built " __DATE__ ", " __TIME__ ".";
从Windows注册表中获取轮询模式的驱动程序参数:
static NTSTATUS GetPollModeParameter(IN PULONG pollMode) {
WDFDRIVER driver = WdfGetDriver();
WDFKEY key;
NTSTATUS status = WdfDriverOpenParametersRegistryKey(driver, STANDARD_RIGHTS_ALL,
WDF_NO_OBJECT_ATTRIBUTES, &key);
ULONG tracepollmode;
if (!NT_SUCCESS(status)) {
TraceError(DBG_INIT, "WdfDriverOpenParametersRegistryKey failed: %!STATUS!", status);
WdfRegistryClose(key);
return
函数返回类型 NTSTATUS 是 WDF 中的一个宏,它实际上是一个 32 位的二进制数,不同的数值表示不同的状态,在 PCIe 设备驱动程序开发中,需要用到的状态有:
DriverEntry例程类似于C语言中的main函数,在WDF中,操作系统检测到有新硬件设备插入后,会查找对应的驱动程序,其中的两个参数分别是:
DriverObject
,指向驱动程序对象的指针;RegistryPath
,在传入参数里, IN、OUT
是宏,IN 代表入口参数,OUT 代表出口参数。还有一种写法, 即_In_、_Out_
, 两种写法对回调例程的编写都没影响。
WPP软件跟踪:
WPP_INIT_TRACING(driverObject, registryPath);
TraceInfo(DBG_INIT, "XDMA Driver - %s", dateTimeStr);//debug初始化
WPP主要用于开发过程中调试代码,跟踪程序可以是:
如果使用 WDK 中提供的Visual Studio模板创建 WDF 驱动程序,则大部分工作都由程序员完成。
将 WPP 软件跟踪添加到驱动程序或应用程序的基本过程包括以下步骤(不关键):
定义一个控件 GUID
,用于唯一地将驱动程序或应用程序标识为 跟踪提供程序。 提供程序在跟踪宏的定义中指定此 GUID,WPP_CONTROL_GUIDS Tracelog 或其他跟踪控制器使用的相关控制文件中指定此 GUID。添加到
提供程序的源文件
,如将 WPP 软件跟踪添加到 Windows 驱动程序和 WPP 软件跟踪引用中所述。安装驱动程序或组件
,启动跟踪会话并记录跟踪消息。 使用 TraceView、 Tracelog、 Tracefmt 和 Tracepdb 等软件跟踪工具配置、启动和停止跟踪会话,以及显示和筛选跟踪消息。 这些工具包含在 WDK Windows驱动程序 (工具包) 。暂时忽略这部分
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.EvtCleanupCallback = Spw_PCIeEvtDriverContextCleanup;
注册驱动程序的 EvtDeviceAdd 回调函数
WDF_DRIVER_CONFIG_INIT(&DriverConfig, EvtDeviceAdd);
详见第6节
创建一个驱动程序对象, 向框架“注册”驱动程序
status = WdfDriverCreate(driverObject, registryPath, WDF_NO_OBJECT_ATTRIBUTES, &DriverConfig,
&Driver);
if (!NT_SUCCESS(status)) {
TraceError(DBG_INIT, "WdfDriverCreate failed: %!STATUS!", status);
WPP_CLEANUP(driverObject);
return status;
}
driverObject->DriverUnload = DriverUnload;
return status;
EvtDeviceAdd 例程的原型声明如下:
EvtDeviceAdd( IN WDFDRIVER Driver, IN PWDFDEVICE_INIT DeviceInit ) ;
每次操作系统枚举设备时, PnP 管理器就调用这个回调例程,函数与例程原型一样,函数内部:
NTSTATUS status = STATUS_SUCCESS;
PAGED_CODE();
TraceVerbose(DBG_INIT, "(Driver=0x%p)", Driver);
I/O方式
WdfDeviceInitSetIoType(DeviceInit, WdfDeviceIoDirect);
其中第二个参数为枚举类,定义如下:
typedef enum _WDF_DEVICE_IO_TYPE //传输类型枚举
{
WdfDeviceIoUndefined = 0, //默认未定义
WdfDeviceIoNeither, //两者都不
WdfDeviceIoBuffered, //缓冲
WdfDeviceIoDirect, //直接
WdfDeviceIoBufferedOrDirect = 4, //缓冲或直接
WdfDeviceIoMaximum, //最大值
} WDF_DEVICE_IO_TYPE, *PWDF_DEVICE_IO_TYPE;
直接I/O仅适用于延迟的缓冲区检索,不保证直接I/O实际上被使用,直接I/O仅用于全页缓冲区,缓冲I/O用于传输的其他部分.
初始化PnpPowerCallbacks
WDF_PNPPOWER_EVENT_CALLBACKS PnpPowerCallbacks;
初始化即插即用例程配置结构
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&PnpPowerCallbacks);
**设置即插即用基本例程
**详见第7节
PnpPowerCallbacks.EvtDevicePrepareHardware = EvtDevicePrepareHardware;
PnpPowerCallbacks.EvtDeviceReleaseHardware = EvtDeviceReleaseHardware;
注册即插即用例程
WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &PnpPowerCallbacks);
初始化powerPolicyCallbacks
WDF_POWER_POLICY_EVENT_CALLBACKS powerPolicyCallbacks;
初始化电源管理例程配置结构
WDF_POWER_POLICY_EVENT_CALLBACKS_INIT(&powerPolicyCallbacks);
注册电源管理例程
WdfDeviceInitSetPowerPolicyEventCallbacks(DeviceInit, &powerPolicyCallbacks);
寄存器文件回调
WDF_OBJECT_ATTRIBUTES fileAttributes;
WDF_FILEOBJECT_CONFIG fileConfig;
WDF_FILEOBJECT_CONFIG_INIT(&fileConfig, EvtDeviceFileCreate, EvtFileClose, EvtFileCleanup);
WDF_OBJECT_ATTRIBUTES_INIT(&fileAttributes);
fileAttributes.SynchronizationScope = WdfSynchronizationScopeNone;
WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE(&fileAttributes, FILE_CONTEXT);
WdfDeviceInitSetFileObjectConfig(DeviceInit, &fileConfig, &fileAttributes);
创建并初始化设备对象和相应的上下文区
WDF_OBJECT_ATTRIBUTES deviceAttributes;
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DeviceContext);
deviceAttributes.EvtCleanupCallback = EvtDeviceCleanup;//有代码注释掉这部分
WDFDEVICE device;
status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
if (!NT_SUCCESS(status)) {
TraceError(DBG_INIT, "WdfDeviceCreate failed: %!STATUS!", status);
return status;
}
status = WdfDeviceCreateDeviceInterface(device, (LPGUID)&GUID_DEVINTERFACE_XDMA, NULL);
if (!NT_SUCCESS(status))
{
TraceError(DBG_INIT, "WdfDeviceCreateDeviceInterface failed %!STATUS!", status);
return status;
}
初始化队列配置结构
WDF_IO_QUEUE_CONFIG queueConfig;
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchParallel);
注册 I/O 处理例程
queueConfig.EvtIoDeviceControl = EvtIoDeviceControl; //设备控制回调
queueConfig.EvtIoRead = EvtIoRead; // 读回调
queueConfig.EvtIoWrite = EvtIoWrite; // 写回调
创建 I/O 队列
WDFQUEUE entryQueue;
status = WdfIoQueueCreate(device, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, &entryQueue);
if (!NT_SUCCESS(status))
{
TraceError(DBG_INIT, "WdfIoQueueCreate failed: %!STATUS!", status);
return status;
}
指定要分发的 I/O 请求类型?
创建 GUID 接口?
设备进入工作状态后,KMDF调用EvtDevicePrepareHardware例程传递两个资源列表,驱动程序保存这两个资源列表,直到WDF框架调用了EvtDeviceReleaseHardware例程。
NTSTATUS EvtDevicePrepareHardware(IN WDFDEVICE device, IN WDFCMRESLIST Resources, IN WDFCMRESLIST ResourcesTranslated)
{
PAGED_CODE();
UNREFERENCED_PARAMETER(Resources);
TraceVerbose(DBG_INIT, "-->Entry");
事件上下文信息ctx
DeviceContext* ctx = GetDeviceContext(device);
PXDMA_DEVICE xdma = &(ctx->xdma);
NTSTATUS status = XDMA_DeviceOpen(device, xdma, Resources, ResourcesTranslated);
if (!NT_SUCCESS(status))
{
TraceError(DBG_INIT, "XDMA_DeviceOpen failed: %!STATUS!", status);
return status;
}
NTSTATUS XDMA_DeviceOpen(WDFDEVICE wdfDevice,
PXDMA_DEVICE xdma,
WDFCMRESLIST ResourcesRaw,
WDFCMRESLIST ResourcesTranslated)
NTSTATUS status = STATUS_INTERNAL_ERROR;
DeviceDefaultInitialize(xdma);
xdma->wdfDevice = wdfDevice;
status = MapBARs(xdma, ResourcesTranslated);
if (!NT_SUCCESS(status))
{
TraceError(DBG_INIT, "MapBARs() failed! %!STATUS!", status);
return status;
}
确定BAR配置,用户(可选)、配置、旁路(可选)
status = IdentifyBars(xdma);
if (!NT_SUCCESS(status))
{
TraceError(DBG_INIT, "IdentifyBars() failed! %!STATUS!", status);
return status;
}
获取BAR配置中的模块偏移量
GetRegisterModules(xdma);
UINT version = GetVersion(xdma);
if (version != v2017_1)
{
TraceWarning(DBG_INIT, "Version mismatch! Expected 2017.1 (0x%x) but got (0x%x)",
v2017_1, version);
}
status = SetupInterrupts(xdma, ResourcesRaw, ResourcesTranslated);
if (!NT_SUCCESS(status))
{
TraceError(DBG_INIT, "SetupInterrupts failed: %!STATUS!", status);
return status;
}
WDF DMA启用程序-至少8字节对齐
WdfDeviceSetAlignmentRequirement(xdma->wdfDevice, 8 - 1); // TODO - choose correct value
WDF_DMA_ENABLER_CONFIG dmaConfig;
WDF_DMA_ENABLER_CONFIG_INIT(&dmaConfig, WdfDmaProfileScatterGather64Duplex, XDMA_MAX_TRANSFER_SIZE);
status = WdfDmaEnablerCreate(xdma->wdfDevice, &dmaConfig, WDF_NO_OBJECT_ATTRIBUTES, &xdma->dmaEnabler);
if (!NT_SUCCESS(status)) {
TraceError(DBG_INIT, " WdfDmaEnablerCreate() failed: %!STATUS!", status);
return status;
}
检测并初始化硬件IP中配置的引擎
status = ProbeEngines(xdma);
if (!NT_SUCCESS(status)) {
TraceError(DBG_INIT, "ProbeEngines failed: %!STATUS!", status);
return status;
}
return status;
获取轮询模式参数,并根据需要将引擎配置为轮询模式
ULONG pollMode = 0;
status = GetPollModeParameter(&pollMode);
if (!NT_SUCCESS(status))
{
TraceError(DBG_INIT, "GetPollModeParameter failed: %!STATUS!", status);
return status;
}
for (UINT dir = H2C; dir < 2; dir++)
{ // 0=H2C, 1=C2H
for (ULONG ch = 0; ch < XDMA_MAX_NUM_CHANNELS; ch++)
{
XDMA_ENGINE* engine = &(xdma->engines[ch][dir]);
XDMA_EngineSetPollMode(engine, (BOOLEAN)pollMode);
}
}
为每个引擎创建一个队列
for (UINT dir = H2C; dir < 2; dir++)
{ // 0=H2C, 1=C2H
for (ULONG ch = 0; ch < XDMA_MAX_NUM_CHANNELS; ch++)
{
XDMA_ENGINE* engine = &(xdma->engines[ch][dir]);
if (engine->enabled == TRUE)
{
status = EngineCreateQueue(device, engine, &(ctx->engineQueue[dir][ch]));
if (!NT_SUCCESS(status))
{
TraceError(DBG_INIT, "EngineCreateQueue() failed: %!STATUS!", status);
return status;
}
}
}
}
部分事件初始化,用户注册
for (UINT i = 0; i < XDMA_MAX_USER_IRQ; ++i)
{
KeInitializeEvent(&ctx->eventSignals[i], NotificationEvent, FALSE);
XDMA_UserIsrRegister(xdma, i, HandleUserEvent, &ctx->eventSignals[i]);
}
TraceVerbose(DBG_INIT, "<--Exit returning %!STATUS!", status);
return status;
}
本质是回调例程,其调用过程是EvtDevicePrepareHardware的逆过程,即获得虚拟地址后,利用MmUnMapIoSpace 函数将虚拟地址解映射成物理地址,然后再交给WDF框架释放。
NTSTATUS EvtDeviceReleaseHardware(IN WDFDEVICE Device, IN WDFCMRESLIST ResourcesTranslated) {
PAGED_CODE();
UNREFERENCED_PARAMETER(ResourcesTranslated);
TraceVerbose(DBG_INIT, "entry");
DeviceContext* ctx = GetDeviceContext(Device);
if (ctx != NULL) {
XDMA_DeviceClose(&ctx->xdma);
}
TraceVerbose(DBG_INIT, "exit");
return STATUS_SUCCESS;
}
其中,XDMA_DeviceClose用于停止所有引擎。
void XDMA_DeviceClose(PXDMA_DEVICE xdma) {
// todo - stop every engine?
// reset irq vectors?
if (xdma && xdma->interruptRegs) {
xdma->interruptRegs->userVector[0] = 0;
xdma->interruptRegs->userVector[1] = 0;
xdma->interruptRegs->userVector[2] = 0;
xdma->interruptRegs->userVector[3] = 0;
xdma->interruptRegs->channelVector[0] = 0;
xdma->interruptRegs->channelVector[1] = 0;
}
// Unmap any I/O ports. Disconnecting
NTSTATUS EngineCreateQueue(WDFDEVICE device, XDMA_ENGINE* engine, WDFQUEUE* queue)
// Create a WDF IO queue for a DMA engine
{
NTSTATUS status = STATUS_SUCCESS;
WDF_IO_QUEUE_CONFIG config;
WDF_OBJECT_ATTRIBUTES attribs;
PQUEUE_CONTEXT context;
PAGED_CODE();
// engine queue is sequential
WDF_IO_QUEUE_CONFIG_INIT(&config, WdfIoQueueDispatchSequential);
ASSERTMSG("direction is neither H2C nor C2H!", (engine->dir == C2H) || (engine->dir == H2C));
if (engine->dir == H2C)
{ // callback handler for write requests
config.EvtIoWrite = EvtIoWriteDma;
TraceInfo(DBG_INIT, "EvtIoWrite=EvtIoWriteDma");
}
else if (engine->dir == C2H)
{ // callback handler for read requests
if (engine->type == EngineType_ST)
{
config.EvtIoRead = EvtIoReadEngineRing;
TraceInfo(DBG_INIT, "EvtIoRead=EvtIoReadEngineRing");
}
else
{
config.EvtIoRead = EvtIoReadDma;
TraceInfo(DBG_INIT, "EvtIoRead=EvtIoReadDma");
}
}
// serialize all callbacks related to this queue. see ref [2]
WDF_OBJECT_ATTRIBUTES_INIT(&attribs);
attribs.SynchronizationScope = WdfSynchronizationScopeQueue;
WDF_OBJECT_ATTRIBUTES_SET_CONTEXT_TYPE(&attribs, QUEUE_CONTEXT);
status = WdfIoQueueCreate(device, &config, &attribs, queue);
if (!NT_SUCCESS(status))
{
TraceError(DBG_INIT, "WdfIoQueueCreate failed %d", status);
return status;
}
// store arguments into queue context
context = GetQueueContext(*queue);
context->engine = engine;
return status;
}
VOID EvtDeviceCleanup(IN WDFOBJECT device)
{
UNREFERENCED_PARAMETER(device);
TraceInfo(DBG_INIT, "%!FUNC!");
}