4、设备启动流程:设备枚举、资源分配、启动顺序、OnD0Entry与OnD0Exit回调详解
UMDF 驱动框架中,设备启动流程是 PnP 管理器与驱动交互的核心环节。本章深入剖析从设备枚举到驱动进入工作状态(D0)的完整路径,重点解析资源分配策略、启动顺序约束以及 OnD0Entry / OnD0Exit 回调的实现细节。
核心要点: 设备启动流程本质上是 PnP 状态机从
Stopped → Started 的迁移过程,驱动开发者必须理解每个阶段的责任边界,避免在错误阶段访问未就绪的硬件资源。
4.1 设备枚举与资源分配
PnP 管理器通过总线驱动(如 PCI、USB)枚举设备,收集硬件资源需求(I/O 端口、内存范围、中断、DMA 通道)。UMDF 驱动不直接参与枚举,但必须通过 IPnpCallbackHardware 接口接收分配结果。
| 资源类型 | 数据结构 | UMDF 回调接口 | 典型用途 |
|---|---|---|---|
| I/O 端口范围 | CM_PARTIAL_RESOURCE_DESCRIPTOR | OnPrepareHardware | 配置寄存器访问 |
| 内存映射范围 | CM_PARTIAL_RESOURCE_DESCRIPTOR | OnPrepareHardware | MMIO 寄存器、FIFO 缓冲区 |
| 中断 | CM_PARTIAL_RESOURCE_DESCRIPTOR | OnPrepareHardware | 设备中断处理 |
| DMA 通道 | DMA_ADAPTER_INFO | OnD0Entry (通过 DMA 接口) | DMA 传输控制 |
4.2 启动顺序与状态机
设备启动遵循严格的顺序约束。PnP 管理器按以下阶段推进,每个阶段对应一个 IRP 或回调:
- 资源分配阶段: 发送
IRP_MN_START_DEVICE,触发OnPrepareHardware。 - DMA 适配器初始化: 驱动创建 DMA 适配器对象(如果设备支持 DMA)。
- 电源状态转换: 从 D3(关闭)→ D0(工作),触发
OnD0Entry。 - 中断连接: 注册中断服务例程(如果设备使用中断)。
- DMA 通道准备: 分配 DMA 通道并配置传输参数。
4.3 OnD0Entry 回调详解
OnD0Entry 是驱动从低功耗状态(D1/D2/D3)进入全功能状态(D0)时被调用的关键回调。它属于 IPnpCallbackPower 接口,在电源 IRP(IRP_MN_SET_POWER)处理路径中被触发。
最佳实践: OnD0Entry 中只做与电源状态恢复相关的操作,如恢复寄存器配置、重新初始化 DMA 通道、启动定时器等。不要在此处分配资源或枚举设备——这些应在 OnPrepareHardware 中完成。
4.3.1 函数签名与参数
HRESULT OnD0Entry(
_In_ IWDFDevice* pDevice,
_In_ WDF_POWER_DEVICE_STATE previousState
);
| 参数 | 类型 | 说明 |
|---|---|---|
| pDevice | IWDFDevice* | 指向设备对象的指针,用于访问设备上下文和 I/O 目标 |
| previousState | WDF_POWER_DEVICE_STATE | 进入 D0 之前的电源状态(D1/D2/D3),帮助驱动判断恢复深度 |
4.3.2 典型实现模板
HRESULT CMyDevice::OnD0Entry(
IWDFDevice* pDevice,
WDF_POWER_DEVICE_STATE previousState
)
{
HRESULT hr = S_OK;
CMyDeviceContext* pContext = nullptr;
// 1. 获取设备上下文
pContext = GetDeviceContext(pDevice);
if (nullptr == pContext) {
return E_UNEXPECTED;
}
// 2. 根据 previousState 决定恢复策略
if (previousState == WdfPowerDeviceD3) {
// 完全断电恢复:需要完整重新初始化
hr = RestoreHardwareState(pContext);
if (FAILED(hr)) {
return hr;
}
} else if (previousState == WdfPowerDeviceD1 ||
previousState == WdfPowerDeviceD2) {
// 浅睡眠恢复:只需恢复部分寄存器
hr = RestorePartialState(pContext);
if (FAILED(hr)) {
return hr;
}
}
// 3. 重新初始化 DMA 通道(如果设备使用 DMA)
hr = ReinitializeDmaChannel(pContext);
if (FAILED(hr)) {
return hr;
}
// 4. 启动 I/O 处理
pContext->m_bDeviceActive = TRUE;
StartIoProcessing(pContext);
return S_OK;
}
4.4 OnD0Exit 回调详解
OnD0Exit 是设备从 D0 状态退出时的清理回调。它负责在设备进入低功耗状态前,安全地停止 I/O、保存硬件状态并释放 DMA 资源。
常见错误: 在 OnD0Exit 中直接调用
IWDFDevice::Release 或删除设备对象。OnD0Exit 只应暂停设备活动,不应销毁设备实例。设备对象的生命周期由 PnP 管理器控制。
4.4.1 函数签名与参数
HRESULT OnD0Exit(
_In_ IWDFDevice* pDevice,
_In_ WDF_POWER_DEVICE_STATE newState
);
| 参数 | 类型 | 说明 |
|---|---|---|
| pDevice | IWDFDevice* | 指向设备对象的指针 |
| newState | WDF_POWER_DEVICE_STATE | 设备即将进入的目标电源状态(D1/D2/D3) |
4.4.2 典型实现模板
HRESULT CMyDevice::OnD0Exit(
IWDFDevice* pDevice,
WDF_POWER_DEVICE_STATE newState
)
{
HRESULT hr = S_OK;
CMyDeviceContext* pContext = GetDeviceContext(pDevice);
if (nullptr == pContext) {
return E_UNEXPECTED;
}
// 1. 停止 I/O 处理
pContext->m_bDeviceActive = FALSE;
StopIoProcessing(pContext);
// 2. 等待所有未完成的 DMA 传输完成
hr = WaitForDmaCompletion(pContext, 5000); // 5 秒超时
if (FAILED(hr)) {
// 超时处理:强制中止 DMA
AbortDmaTransfer(pContext);
}
// 3. 保存硬件状态(如果进入 D3,必须保存)
if (newState == WdfPowerDeviceD3) {
hr = SaveHardwareState(pContext);
if (FAILED(hr)) {
return hr;
}
}
// 4. 释放 DMA 通道资源
ReleaseDmaChannel(pContext);
return S_OK;
}
4.5 OnD0Entry 与 OnD0Exit 的对称性
这两个回调必须成对实现,形成完整的电源管理生命周期。下表总结了它们的对称关系:
| 操作 | OnD0Entry | OnD0Exit |
|---|---|---|
| I/O 处理 | 启动 I/O 队列处理 | 停止 I/O 队列处理 |
| DMA 通道 | 重新初始化 DMA 通道 | 释放 DMA 通道资源 |
| 硬件寄存器 | 恢复寄存器配置 | 保存寄存器状态 |
| 中断 | 连接中断(如果支持运行时 Dx) | 断开中断 |
| 设备上下文标志 | 设置 m_bDeviceActive = TRUE | 设置 m_bDeviceActive = FALSE |
4.6 常见问题与调试建议
- 资源未就绪: 在
OnD0Entry中访问硬件资源前,必须确认OnPrepareHardware已成功完成。使用设备上下文中的标志位跟踪资源状态。 - DMA 死锁: 如果
OnD0Exit中等待 DMA 完成超时,检查 DMA 传输是否被 I/O 取消路径阻塞。建议使用单独的取消回调处理。 - 状态丢失: 进入 D3 后硬件状态会丢失,务必在
OnD0Exit中保存所有易失寄存器,并在OnD0Entry中完整恢复。 - 调试工具: 使用
!wdfkd.wdfdevice和!wdfkd.wdfpower调试器扩展查看设备电源状态和回调调用历史。
调试检查清单:
- 确认 OnPrepareHardware 返回 S_OK。
- 检查 OnD0Entry 中 previousState 参数是否符合预期。
- 验证 DMA 适配器句柄在 OnD0Entry 中非 NULL。
- 使用 WPP 软件跟踪记录每个回调的进入和退出时间戳。