4. KMDF 设备对象:WDFDEVICE 对象创建、设备扩展上下文、设备接口注册、PNP 与电源管理回调

在 KMDF 驱动模型中,WDFDEVICE 对象是驱动与硬件交互的核心抽象。它代表了 PnP 管理器枚举出的一个设备实例,驱动通过操作该对象来完成硬件初始化、I/O 处理、电源状态转换以及中断与 DMA 资源的绑定。本章将深入剖析 WDFDEVICE 的创建流程、设备扩展上下文的设计模式、设备接口的注册机制,以及 PnP 与电源管理回调的注册与实现要点。

核心要点:WDFDEVICE 的生命周期完全由 KMDF 框架管理,驱动只需在 EvtDriverDeviceAdd 回调中完成创建与配置,框架会自动处理 PnP 状态机与电源状态机的转换。

4.1 WDFDEVICE 对象创建流程

WDFDEVICE 对象的创建发生在驱动入口点 EvtDriverDeviceAdd 回调函数中。该回调由框架在 PnP 管理器检测到新设备时调用,驱动在此函数中完成设备对象的初始化、硬件资源的分配以及 I/O 队列的配置。

创建 WDFDEVICE 的标准步骤如下:

  1. 获取配置信息:从 WDFDRIVER_INIT 结构中提取设备标识与资源。
  2. 初始化 WDFDEVICE_INIT 结构:调用 WdfDeviceInitAllocate 获取初始化结构指针。
  3. 配置设备属性:设置设备类型、独占访问标志、电源策略等。
  4. 注册 PnP 与电源回调:通过 WdfDeviceInitSetPnpPowerEventCallbacks 注册回调函数。
  5. 创建 WDFDEVICE 对象:调用 WdfDeviceCreate 完成对象创建。
  6. 创建 I/O 队列:为设备创建默认或自定义的 I/O 队列。
  7. 注册设备接口:调用 WdfDeviceCreateDeviceInterface 暴露设备接口。
最佳实践:在 WdfDeviceCreate 之前完成所有配置,因为一旦设备对象创建成功,大部分属性将变为只读。
NTSTATUS
EvtDriverDeviceAdd(
    _In_ WDFDRIVER        Driver,
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
{
    WDFDEVICE device;
    NTSTATUS status;
    PDEVICE_CONTEXT devContext;
    WDF_OBJECT_ATTRIBUTES attributes;
    WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;

    // 1. 初始化 PnP/电源回调结构
    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
    pnpPowerCallbacks.EvtDevicePrepareHardware = EvtDevicePrepareHardware;
    pnpPowerCallbacks.EvtDeviceReleaseHardware = EvtDeviceReleaseHardware;
    pnpPowerCallbacks.EvtDeviceD0Entry = EvtDeviceD0Entry;
    pnpPowerCallbacks.EvtDeviceD0Exit = EvtDeviceD0Exit;
    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);

    // 2. 设置设备扩展上下文大小
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DEVICE_CONTEXT);
    attributes.SynchronizationScope = WdfSynchronizationScopeNone;

    // 3. 创建 WDFDEVICE 对象
    status = WdfDeviceCreate(&DeviceInit, &attributes, &device);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    // 4. 获取设备扩展上下文并初始化
    devContext = GetDeviceContext(device);
    devContext->Device = device;
    devContext->InterruptObject = NULL;

    // 5. 注册设备接口
    status = WdfDeviceCreateDeviceInterface(
        device,
        &GUID_DEVINTERFACE_MYDRIVER,
        NULL
    );
    if (!NT_SUCCESS(status)) {
        return status;
    }

    // 6. 创建 I/O 队列
    status = QueueCreate(device);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    return STATUS_SUCCESS;
}

4.2 设备扩展上下文(Device Extension Context)

设备扩展上下文是 KMDF 提供的一种高效机制,用于将驱动自定义数据与 WDFDEVICE 对象关联。与 WDM 中的设备扩展不同,KMDF 的上下文管理完全由框架负责,驱动只需通过 WDF_DECLARE_CONTEXT_TYPE 宏声明上下文类型,并在创建设备时指定大小。

设备扩展上下文的设计原则:

  • 类型安全:使用 WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE 宏,框架在运行时验证上下文类型。
  • 生命周期管理:上下文内存的分配与释放由框架自动管理,与 WDFDEVICE 对象同生命周期。
  • 访问效率:通过 GetDeviceContext 宏直接获取指针,无额外查找开销。
// 声明设备上下文结构体
typedef struct _DEVICE_CONTEXT {
    WDFDEVICE           Device;
    WDFINTERRUPT        InterruptObject;
    WDFDMAENABLER       DmaEnabler;
    WDFCOMMONBUFFER     CommonBuffer;
    PHYSICAL_ADDRESS    CommonBufferPhysicalAddress;
    PVOID               CommonBufferVirtualAddress;
    ULONG               DeviceId;
    BOOLEAN             DevicePowerOn;
} DEVICE_CONTEXT, *PDEVICE_CONTEXT;

// 注册上下文类型
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DEVICE_CONTEXT, GetDeviceContext)
注意事项:设备扩展上下文的大小应尽量精简,避免在非分页内存中分配过大的结构体。对于需要大量缓冲区的场景,应使用 WDFCOMMONBUFFER 或 WDFMEMORY 对象。

下表列出了设备扩展上下文中常见的字段及其用途:

字段名称 数据类型 用途说明
Device WDFDEVICE 保存设备对象句柄,供其他回调使用
InterruptObject WDFINTERRUPT 中断对象句柄,用于中断同步与配置
DmaEnabler WDFDMAENABLER DMA 启用器句柄,管理 DMA 通道与适配器
CommonBuffer WDFCOMMONBUFFER DMA 共享缓冲区句柄
DevicePowerOn BOOLEAN 设备电源状态标志,用于电源管理决策

4.3 设备接口注册

设备接口是 KMDF 驱动向操作系统暴露设备功能的标准机制。通过注册设备接口,应用程序或其他内核组件可以通过符号链接或 GUID 找到并访问设备。KMDF 提供了 WdfDeviceCreateDeviceInterface 函数来完成接口注册。

设备接口注册的关键步骤:

  • 定义接口 GUID:在驱动头文件中使用 DEFINE_GUID 宏定义唯一的接口标识符。
  • 调用注册函数:在 EvtDriverDeviceAdd 中创建设备对象后立即注册接口。
  • 引用字符串(可选):为接口提供引用字符串,支持同一设备暴露多个接口实例。
// 定义设备接口 GUID
DEFINE_GUID(GUID_DEVINTERFACE_MYDRIVER,
    0x12345678, 0x1234, 0x1234,
    0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0);

// 注册设备接口
status = WdfDeviceCreateDeviceInterface(
    device,
    &GUID_DEVINTERFACE_MYDRIVER,
    NULL  // 无引用字符串
);
if (!NT_SUCCESS(status)) {
    // 错误处理:接口注册失败
    return status;
}
调试提示:使用 !devobj 或 !devnode WinDbg 命令可以验证设备接口是否成功注册。应用程序可通过 SetupDiGetClassDevs 枚举设备接口。

4.4 PnP 与电源管理回调

KMDF 框架通过一组标准化的回调函数管理设备的 PnP 状态与电源状态。驱动只需注册感兴趣的回调,框架会在适当的时机自动调用。这些回调是驱动与硬件交互的关键入口点,也是中断与 DMA 初始化的最佳位置。

核心 PnP 与电源回调包括:

回调函数 调用时机 典型操作
EvtDevicePrepareHardware 设备启动、资源重新平衡后 解析硬件资源、初始化中断、配置 DMA
EvtDeviceReleaseHardware 设备停止、资源重新平衡前 释放硬件资源、禁用中断、清理 DMA
EvtDeviceD0Entry 设备进入 D0 工作状态 恢复硬件上下文、启用中断
EvtDeviceD0Exit 设备离开 D0 工作状态 保存硬件上下文、禁用中断
EvtDeviceSelfManagedIoInit 设备启动后,I/O 初始化 启动自管理 I/O 逻辑
EvtDeviceSelfManagedIoSuspend 设备挂起时 暂停自管理 I/O 逻辑

下图展示了 WDFDEVICE 对象创建、设备接口注册与 PnP/电源回调之间的协作关系:

WDFDEVICE 对象创建与回调协作流程 EvtDriverDeviceAdd 配置 WDFDEVICE_INIT 注册 PnP/电源回调 WdfDeviceCreate 注册设备接口 创建 I/O 队列 EvtDevicePrepareHW EvtDeviceReleaseHW EvtDeviceD0Entry EvtDeviceD0Exit EvtDeviceSelfManagedIo 说明 左侧:驱动初始化流程 右侧:框架回调触发 虚线:回调注册关系

4.5 回调注册与实现要点

在注册 PnP 与电源回调时,驱动需要遵循以下关键规则:

  • 回调注册时机:必须在 WdfDeviceCreate 之前完成回调注册,否则框架将忽略后续注册。
  • 回调执行级别:所有 PnP/电源回调在 PASSIVE_LEVEL 执行,但 EvtDeviceD0Entry 和 EvtDeviceD0Exit 可能在 DISPATCH_LEVEL 执行,需注意同步。
  • 错误处理:回调返回失败状态码将导致设备启动失败或电源状态转换失败,驱动应确保资源清理的完整性。
  • 上下文访问:在回调中通过 GetDeviceContext 宏获取设备扩展上下文,避免使用全局变量。
NTSTATUS
EvtDevicePrepareHardware(
    _In_ WDFDEVICE Device,
    _In_ WDFCMRESLIST ResourcesRaw,
    _In_ WDFCMRESLIST ResourcesTranslated
    )
{
    PDEVICE_CONTEXT devContext = GetDeviceContext(Device);
    NTSTATUS status;
    PCM_PARTIAL_RESOURCE_DESCRIPTOR descriptor;
    ULONG resourceCount;

    UNREFERENCED_PARAMETER(ResourcesRaw);

    // 解析翻译后的硬件资源
    resourceCount = WdfCmResourceListGetCount(ResourcesTranslated);
    for (ULONG i = 0; i < resourceCount; i++) {
        descriptor = WdfCmResourceListGetDescriptor(ResourcesTranslated, i);
        switch (descriptor->Type) {
        case CmResourceTypeInterrupt:
            // 保存中断信息,后续创建中断对象
            devContext->InterruptVector = descriptor->u.Interrupt.Vector;
            devContext->InterruptLevel = descriptor->u.Interrupt.Level;
            break;
        case CmResourceTypeMemory:
            // 保存内存映射地址
            devContext->MmioBase = descriptor->u.Memory.Start;
            devContext->MmioLength = descriptor->u.Memory.Length;
            break;
        case CmResourceTypeDma:
            // 保存 DMA 通道信息
            devContext->DmaChannel = descriptor->u.Dma.Channel;
            break;
        default:
            break;
        }
    }

    // 创建中断对象
    status = InterruptCreate(Device);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    // 创建 DMA 启用器
    status = DmaEnablerCreate(Device);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    return STATUS_SUCCESS;
}
性能优化提示:在 EvtDevicePrepareHardware 中完成中断对象和 DMA 启用器的创建,可以确保设备在进入 D0 状态时立即具备中断处理和 DMA 传输能力,减少 D0Entry 回调中的延迟。

4.6 电源管理回调的典型实现模式

电源管理回调 EvtDeviceD0Entry 和 EvtDeviceD0Exit 是驱动响应设备电源状态变化的关键入口。以下是一个典型的实现模式:

NTSTATUS
EvtDeviceD0Entry(
    _In_ WDFDEVICE Device,
    _In_ WDF_POWER_DEVICE_STATE PreviousState
    )
{
    PDEVICE_CONTEXT devContext = GetDeviceContext(Device);
    NTSTATUS status = STATUS_SUCCESS;

    // 恢复硬件上下文
    if (PreviousState == WdfPowerDeviceD3) {
        // 从 D3 恢复,需要重新初始化硬件
        status = HardwareInitialize(devContext);
        if (!NT_SUCCESS(status)) {
            return status;
        }
    }

    // 启用中断
    if (devContext->InterruptObject != NULL) {
        WdfInterruptEnable(devContext->InterruptObject);
    }

    devContext->DevicePowerOn = TRUE;
    return STATUS_SUCCESS;
}

NTSTATUS
EvtDeviceD0Exit(
    _In_ WDFDEVICE Device,
    _In_ WDF_POWER_DEVICE_STATE TargetState
    )
{
    PDEVICE_CONTEXT devContext = GetDeviceContext(Device);

    // 禁用中断
    if (devContext->InterruptObject != NULL) {
        WdfInterruptDisable(devContext->InterruptObject);
    }

    // 保存硬件上下文
    if (TargetState == WdfPowerDeviceD3) {
        HardwareSaveContext(devContext);
    }

    devContext->DevicePowerOn = FALSE;
    return STATUS_SUCCESS;
}
关键注意事项:
  • 在 EvtDeviceD0Exit 中必须禁用中断,否则设备进入低功耗状态后可能产生虚假中断。
  • 中断禁用操作必须在同步锁保护下执行,避免与中断服务例程产生竞态。
  • 对于支持 DMA 的设备,在 D0Exit 中还需要确保所有 DMA 传输已完成或已取消。