• 驱动开发代码研读


    一、程序流程图

    请添加图片描述

    二、头文件程序详解

    在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);
    
    • 1
    • CTL_CODE是一个用于创建一个唯一的32位系统I/O控制代码的宏:
    	#define XDMA_IOCTL(index) CTL_CODE(FILE_DEVICE_UNKNOWN, index, METHOD_BUFFERED, FILE_ANY_ACCESS)       
    
    • 1

    其中四个参数分别表示:

    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"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3、device.h

    • 设置最大传输大小:
    #define XDMA_MAX_NUM_BARS (3)
    
    • 1
    typedef void(*PFN_XDMA_USER_WORK)(ULONG eventId, void* userData);
    
    • 1
    • 定义相关事件:
    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 定义设备相关变量,体现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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    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
               
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    三、C++程序详解

    1、driver.c

    1.头文件

    #include "driver.h"
    #include "file_io.h"
    #include "trace.h"
    
    • 1
    • 2
    • 3

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    其中声明的事件分别是:

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

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

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

    详见第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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4.定义

    日期:

    const char * const dateTimeStr = "Built " __DATE__ ", " __TIME__ ".";
    
    • 1

    从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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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初始化
    
    • 1
    • 2

    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;
    
    • 1
    • 2
    4)驱动初始化

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

    WDF_DRIVER_CONFIG_INIT(&DriverConfig, EvtDeviceAdd);
    
    • 1

    详见第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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    6.EvtDeviceAdd函数

    EvtDeviceAdd 例程的原型声明如下:

    EvtDeviceAdd( IN WDFDRIVER Driver, IN PWDFDEVICE_INIT DeviceInit ) ;
    
    • 1

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

    1)初始化:状态、分页、详细追踪
        NTSTATUS status = STATUS_SUCCESS;
        PAGED_CODE();
        TraceVerbose(DBG_INIT, "(Driver=0x%p)", Driver);  
    
    • 1
    • 2
    • 3
    2)设置传输:I/O方式
     	WdfDeviceInitSetIoType(DeviceInit, WdfDeviceIoDirect);
    
    • 1

    其中第二个参数为枚举类,定义如下:

    typedef enum _WDF_DEVICE_IO_TYPE	 		//传输类型枚举 
    {									 
        WdfDeviceIoUndefined = 0,        		//默认未定义
        WdfDeviceIoNeither,				 		//两者都不					
        WdfDeviceIoBuffered, 			 		//缓冲	
        WdfDeviceIoDirect,				 		//直接
        WdfDeviceIoBufferedOrDirect = 4, 		//缓冲或直接
        WdfDeviceIoMaximum,				 		//最大值
    } WDF_DEVICE_IO_TYPE, *PWDF_DEVICE_IO_TYPE;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    直接I/O仅适用于延迟的缓冲区检索,不保证直接I/O实际上被使用,直接I/O仅用于全页缓冲区,缓冲I/O用于传输的其他部分.

    3)PNP(即插即用)事件

    初始化PnpPowerCallbacks

    	WDF_PNPPOWER_EVENT_CALLBACKS PnpPowerCallbacks;
    
    • 1

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

    	WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&PnpPowerCallbacks);
    
    • 1

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

    	PnpPowerCallbacks.EvtDevicePrepareHardware = EvtDevicePrepareHardware;
    	PnpPowerCallbacks.EvtDeviceReleaseHardware = EvtDeviceReleaseHardware;
    
    • 1
    • 2

    注册即插即用例程

    	WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &PnpPowerCallbacks);
    
    • 1
    4) 电源管理事件

    初始化powerPolicyCallbacks

    	WDF_POWER_POLICY_EVENT_CALLBACKS powerPolicyCallbacks;
    
    • 1

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

    	WDF_POWER_POLICY_EVENT_CALLBACKS_INIT(&powerPolicyCallbacks);
    
    • 1

    注册电源管理例程

    	WdfDeviceInitSetPowerPolicyEventCallbacks(DeviceInit, &powerPolicyCallbacks);
    
    • 1
    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    7)用户设备空间接口
    	status = WdfDeviceCreateDeviceInterface(device, (LPGUID)&GUID_DEVINTERFACE_XDMA, NULL);
        if (!NT_SUCCESS(status)) 
        {
            TraceError(DBG_INIT, "WdfDeviceCreateDeviceInterface failed %!STATUS!", status);
            return status;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    8)队列例程

    初始化队列配置结构

        WDF_IO_QUEUE_CONFIG queueConfig;
        WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchParallel);
    
    • 1
    • 2

    注册 I/O 处理例程

        queueConfig.EvtIoDeviceControl = EvtIoDeviceControl; //设备控制回调 
        queueConfig.EvtIoRead = EvtIoRead; // 读回调
        queueConfig.EvtIoWrite = EvtIoWrite; // 写回调
    
    • 1
    • 2
    • 3

    创建 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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    指定要分发的 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");
    
    • 1
    • 2
    • 3
    • 4
    • 5

    事件上下文信息ctx

    	DeviceContext* ctx = GetDeviceContext(device);
    	PXDMA_DEVICE xdma = &(ctx->xdma);
    
    • 1
    • 2

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

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

    	status = IdentifyBars(xdma);
        if (!NT_SUCCESS(status))
         {
            TraceError(DBG_INIT, "IdentifyBars() failed! %!STATUS!", status);
            return status;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    	GetRegisterModules(xdma);
    
    • 1
    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    检测并初始化硬件IP中配置的引擎

    	status = ProbeEngines(xdma);
        if (!NT_SUCCESS(status)) {
            TraceError(DBG_INIT, "ProbeEngines failed: %!STATUS!", status);
            return status;
        }
    
        return status;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    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);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    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;
                    }
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    }

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    其中,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 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    10.EvtDeviceCleanup函数

    VOID EvtDeviceCleanup(IN WDFOBJECT device)
    {
        UNREFERENCED_PARAMETER(device);
        TraceInfo(DBG_INIT, "%!FUNC!");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2、driver.c

  • 相关阅读:
    C++ Primer Plus第五版笔记(p1-50)
    一套基于 Ant Design 和 Blazor 的开源企业级组件库
    Spring MVC框架学习(五) ---- 传递参数
    【思考总结】正项级数可以条件收敛吗?【负项级数?任意项级数?】
    基于AM335X开发板 ARM Cortex-A8——Acontis EtherCAT主站开发案例
    pycharm联合Anaconda
    SpringCloud基础知识
    相机存储卡被格式化了怎么恢复?数据恢复办法分享!
    【描述一下 V8 执行一段JS代码的过程?】
    c++日期类的实现
  • 原文地址:https://blog.csdn.net/kris_paul/article/details/128019857