驱动开发代码研读

时间:2022-12-06 14:54:42

一、程序流程图

驱动开发代码研读

二、头文件程序详解

在WDF的PCIe驱动程序中,一般有四个.h文件:

  • Public.h
  • Driver.h
  • Device.h
  • Trace.h
    具体如下:

1、public.h

XDMA的代码中,命名为:xdma_public.h,顾名思义,public.h文件被驱动程序应用程序共同使用,其主要内容就是提供GUID接口
应用程序与驱动通信的设计过程中有两个重要的概念,即GUID值和CTL_CODE宏。

  • GUID(Globally Unique Identifier)是全局唯一标识符,通过特定算法(比如时间或地点等信息)生成一组128位二进制数,来唯一标识某个实体:
	DEFINE_GUID(GUID_DEVINTERFACE_XDMA, 0x74c7e4a9, 0x6d5d, 0x4a70, 0xbc, 0x0d, 0x20, 0x69, 0x1d, 0xff, 0x9e, 0x9d);
  • CTL_CODE是一个用于创建一个唯一的32位系统I/O控制代码的宏:
	#define XDMA_IOCTL(index) CTL_CODE(FILE_DEVICE_UNKNOWN, index, METHOD_BUFFERED, FILE_ANY_ACCESS)       

其中四个参数分别表示:

  1. DeviceType(设备类型,高16位(16-31位),FILE_DEVICE_UNKNOWN表示自定义板卡)
  2. Function(功能2-13 位,从0x800开始取值,之前的已经被微软占用)
  3. Method(I/O访问内存使用方式,有METHOD_BUFFERED、METHOD_IN_DIRECT、METHOD_OUT_DIRECT、METHOD_NEITHER四种:
    • buffered方式:I/O管理器会创建与应用程序数据缓冲区完全相同的系统缓冲区,驱动程序在这个缓冲区工作,由I/O管理器完成复制数据任务;
    • direct方式:XDMA使用,I/O管理器锁定应用程序缓冲区的物理内存页,并创建一个MDL(内存描述符表)来描述该页,驱动程序将使用MDL工作;
    • neither方式:一般不用,应用程序缓冲区的虚拟地址传递给驱动程序)
  4. Access(访问限制,14-15位,一般有:FILE_ANY_ACCESS、FILE_READ_ACCESS、FILE_WRITE_ACCESS)

其余都是一些宏定义,主要定义事件和文件,如:

	#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"

2、driver.h

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)

3、device.h

  • 设置最大传输大小:
#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;
  • 定义设备相关变量,体现WDF中的一种“对象封装”思想
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;

4、trace.h

用来调试和跟踪,部分代码为默认生成

#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
           

5.XDMA.h


#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);

三、C++程序详解

1、driver.c

1.头文件

#include "driver.h"
#include "file_io.h"
#include "trace.h"

2.声明

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;

其中声明的事件分别是:

  • DriverEntry 是驱动入口
  • DriverUnload 是驱动出口(卸载
  • EvtDeviceAdd 是添加驱动设备
  • EvtDeviceCleanup 是设备清理
  • EvtDevicePrepareHardware 是硬件准备
  • EvtDeviceReleaseHardware 是硬件发布

声明一个引擎队列的创建方法,用于WDF框架为DMA创建I/O队列,参数分别是:当前设备、引擎、队列:

static NTSTATUS EngineCreateQueue(WDFDEVICE device, XDMA_ENGINE* engine, WDFQUEUE* queue);

详见第9节。

3.标记分页函数

驱动程序开发中,需要为每个函数指定位于分页内存还是非分页内存:

  • INIT表示入口函数,驱动成功加载后可以从内存删除
  • PAGE表示可以在驱动运行时被交换到硬盘上;
  • 如果不指定,将被编译器默认为非分页内存。
#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

4.定义

日期:

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

5.主函数

1)参数&类型

驱动开发代码研读
函数返回类型 NTSTATUS 是 WDF 中的一个宏,它实际上是一个 32 位的二进制数,不同的数值表示不同的状态,在 PCIe 设备驱动程序开发中,需要用到的状态有:

  • STATUS_SUCCESS 例程回调成功
  • STATUS_PENDING 例程回调未完成
  • STATUS_UNSUCCESSFUL 例程回调失败

DriverEntry例程类似于C语言中的main函数,在WDF中,操作系统检测到有新硬件设备插入后,会查找对应的驱动程序,其中的两个参数分别是:

  • 驱动对象DriverObject,指向驱动程序对象的指针;
  • 注册表路径RegistryPath

在传入参数里, IN、OUT是宏,IN 代表入口参数,OUT 代表出口参数。还有一种写法, 即_In_、_Out_, 两种写法对回调例程的编写都没影响。

2)WPP(非必要)

WPP软件跟踪:

    WPP_INIT_TRACING(driverObject, registryPath);
    TraceInfo(DBG_INIT, "XDMA Driver - %s", dateTimeStr);//debug初始化

WPP主要用于开发过程中调试代码,跟踪程序可以是:

  • 内核模式驱动程序
  • 用户模式驱动程序、应用程序或动态链接库 (DLL)

如果使用 WDK 中提供的Visual Studio模板创建 WDF 驱动程序,则大部分工作都由程序员完成。
将 WPP 软件跟踪添加到驱动程序或应用程序的基本过程包括以下步骤(不关键):

  1. 定义一个控件 GUID,用于唯一地将驱动程序或应用程序标识为 跟踪提供程序。 提供程序在跟踪宏的定义中指定此 GUID,WPP_CONTROL_GUIDS Tracelog 或其他跟踪控制器使用的相关控制文件中指定此 GUID。
  2. 将所需的 WPP 相关 C 预处理器指令和 WPP 宏调用添加到提供程序的源文件,如将 WPP 软件跟踪添加到 Windows 驱动程序和 WPP 软件跟踪引用中所述。
  3. 修改 Visual Studio 项目以运行 WPP 预处理器并生成驱动程序,如将 WPP 软件跟踪添加到 Windows 驱动程序中所述。 有关更多生成时间选项,可以参考WPP预处理器。
  4. 安装驱动程序或组件,启动跟踪会话并记录跟踪消息。 使用 TraceView、 Tracelog、 Tracefmt 和 Tracepdb 等软件跟踪工具配置、启动和停止跟踪会话,以及显示和筛选跟踪消息。 这些工具包含在 WDK Windows驱动程序 (工具包) 。

3)清理回调函数

暂时忽略这部分

 WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
 attributes.EvtCleanupCallback = Spw_PCIeEvtDriverContextCleanup;

4)驱动初始化

注册驱动程序的 EvtDeviceAdd 回调函数

WDF_DRIVER_CONFIG_INIT(&DriverConfig, EvtDeviceAdd);

详见第6节

5)创建对象

创建一个驱动程序对象, 向框架“注册”驱动程序

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;

6.EvtDeviceAdd函数

EvtDeviceAdd 例程的原型声明如下:

EvtDeviceAdd( IN WDFDRIVER Driver, IN PWDFDEVICE_INIT DeviceInit ) ;

每次操作系统枚举设备时, PnP 管理器就调用这个回调例程,函数与例程原型一样,函数内部:

1)初始化:状态、分页、详细追踪

    NTSTATUS status = STATUS_SUCCESS;
    PAGED_CODE();
    TraceVerbose(DBG_INIT, "(Driver=0x%p)", Driver);  

2)设置传输: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用于传输的其他部分.

3)PNP(即插即用)事件

初始化PnpPowerCallbacks

	WDF_PNPPOWER_EVENT_CALLBACKS PnpPowerCallbacks;

初始化即插即用例程配置结构

	WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&PnpPowerCallbacks);

**设置即插即用基本例程**详见第7节

	PnpPowerCallbacks.EvtDevicePrepareHardware = EvtDevicePrepareHardware;
	PnpPowerCallbacks.EvtDeviceReleaseHardware = EvtDeviceReleaseHardware;

注册即插即用例程

	WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &PnpPowerCallbacks);

4) 电源管理事件

初始化powerPolicyCallbacks

	WDF_POWER_POLICY_EVENT_CALLBACKS powerPolicyCallbacks;

初始化电源管理例程配置结构

	WDF_POWER_POLICY_EVENT_CALLBACKS_INIT(&powerPolicyCallbacks);

注册电源管理例程

	WdfDeviceInitSetPowerPolicyEventCallbacks(DeviceInit, &powerPolicyCallbacks);

5)寄存器文件

寄存器文件回调

	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);

6)设备初始化

创建并初始化设备对象和相应的上下文区

	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;
    }

7)用户设备空间接口

	status = WdfDeviceCreateDeviceInterface(device, (LPGUID)&GUID_DEVINTERFACE_XDMA, NULL);
    if (!NT_SUCCESS(status)) 
    {
        TraceError(DBG_INIT, "WdfDeviceCreateDeviceInterface failed %!STATUS!", status);
        return status;
    }

8)队列例程

初始化队列配置结构

    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 接口?

7.EvtDevicePrepareHardware函数

设备进入工作状态后,KMDF调用EvtDevicePrepareHardware例程传递两个资源列表,驱动程序保存这两个资源列表,直到WDF框架调用了EvtDeviceReleaseHardware例程。

1)初始化

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);

其中ctx存放了xdma,队列和事件信号,结构体定义如下:
驱动开发代码研读

2)XDMA_DeviceOpen函数

1.driver.c中应用
 	NTSTATUS status = XDMA_DeviceOpen(device, xdma, Resources, ResourcesTranslated);
 	if (!NT_SUCCESS(status)) 
 	{
        TraceError(DBG_INIT, "XDMA_DeviceOpen failed: %!STATUS!", status);
        return status;
    }
2.入参
NTSTATUS XDMA_DeviceOpen(WDFDEVICE wdfDevice,
                         PXDMA_DEVICE xdma,
                         WDFCMRESLIST ResourcesRaw,
                         WDFCMRESLIST ResourcesTranslated)
3.用默认值初始化XDMA设备结构
 	NTSTATUS status = STATUS_INTERNAL_ERROR;
    DeviceDefaultInitialize(xdma);
    xdma->wdfDevice = wdfDevice;
4.将PCIe基地址寄存器映射到主机内存
	status = MapBARs(xdma, ResourcesTranslated);
    if (!NT_SUCCESS(status)) 
    {
        TraceError(DBG_INIT, "MapBARs() failed! %!STATUS!", status);
        return status;
    }
5.BAR配置

确定BAR配置,用户(可选)、配置、旁路(可选)

	status = IdentifyBars(xdma);
    if (!NT_SUCCESS(status))
     {
        TraceError(DBG_INIT, "IdentifyBars() failed! %!STATUS!", status);
        return status;
    }

获取BAR配置中的模块偏移量

	GetRegisterModules(xdma);
6.确认XDMA IP核心版本与此驱动程序匹配
	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;
    }
7.DMA启动程序

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;

3)轮询模式

获取轮询模式参数,并根据需要将引擎配置为轮询模式

	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);
        }
    }

4)创建队列

为每个引擎创建一个队列

    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;
                }
            }
        }
    }

5)注册

部分事件初始化,用户注册

	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;

}

8.EvtDeviceReleaseHardware函数

本质是回调例程,其调用过程是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 

9.EngineCreateQueue函数

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;
}

10.EvtDeviceCleanup函数

VOID EvtDeviceCleanup(IN WDFOBJECT device)
{
    UNREFERENCED_PARAMETER(device);
    TraceInfo(DBG_INIT, "%!FUNC!");
}

2、driver.c