4. KMDF 设备对象:WDFDEVICE 对象创建、设备扩展上下文、设备接口注册、PNP 与电源管理回调
在 KMDF 驱动模型中,WDFDEVICE 对象是驱动与硬件交互的核心抽象。它代表了 PnP 管理器枚举出的一个设备实例,驱动通过操作该对象来完成硬件初始化、I/O 处理、电源状态转换以及中断与 DMA 资源的绑定。本章将深入剖析 WDFDEVICE 的创建流程、设备扩展上下文的设计模式、设备接口的注册机制,以及 PnP 与电源管理回调的注册与实现要点。
4.1 WDFDEVICE 对象创建流程
WDFDEVICE 对象的创建发生在驱动入口点 EvtDriverDeviceAdd 回调函数中。该回调由框架在 PnP 管理器检测到新设备时调用,驱动在此函数中完成设备对象的初始化、硬件资源的分配以及 I/O 队列的配置。
创建 WDFDEVICE 的标准步骤如下:
- 获取配置信息:从
WDFDRIVER_INIT结构中提取设备标识与资源。 - 初始化 WDFDEVICE_INIT 结构:调用
WdfDeviceInitAllocate获取初始化结构指针。 - 配置设备属性:设置设备类型、独占访问标志、电源策略等。
- 注册 PnP 与电源回调:通过
WdfDeviceInitSetPnpPowerEventCallbacks注册回调函数。 - 创建 WDFDEVICE 对象:调用
WdfDeviceCreate完成对象创建。 - 创建 I/O 队列:为设备创建默认或自定义的 I/O 队列。
- 注册设备接口:调用
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)
下表列出了设备扩展上下文中常见的字段及其用途:
| 字段名称 | 数据类型 | 用途说明 |
|---|---|---|
| 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/电源回调之间的协作关系:
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 传输已完成或已取消。