3. UMDF PnP事件回调:IPnpCallbackHardware与IPnpCallbackSelfManagedIo
在UMDF驱动模型中,即插即用(PnP)与电源管理事件是驱动与内核交互的核心通道。本章聚焦于两个关键回调接口——IPnpCallbackHardware和IPnpCallbackSelfManagedIo,以及它们的核心方法OnPrepareHardware与OnReleaseHardware。理解这些回调的触发时机、职责边界和实现规范,是构建稳定DMA传输管道的前提。
核心认知: PnP回调不是可选的“钩子”,而是驱动必须实现的“契约”。硬件资源分配、DMA适配器初始化、I/O队列启停,都依赖这些回调的正确编排。
3.1 接口职责与生命周期定位
UMDF框架将硬件生命周期划分为多个阶段,每个阶段由特定的回调接口负责。下表对比了IPnpCallbackHardware与IPnpCallbackSelfManagedIo的职责范围:
| 接口 | 核心方法 | 触发阶段 | 主要职责 | 与DMA的关系 |
|---|---|---|---|---|
| IPnpCallbackHardware | OnPrepareHardwareOnReleaseHardware |
设备进入D0(工作状态) 设备离开D0 |
分配/释放硬件资源 映射内存空间 初始化寄存器 |
创建DMA适配器对象 分配公共缓冲区 |
| IPnpCallbackSelfManagedIo | OnSelfManagedIoStartOnSelfManagedIoStopOnSelfManagedIoRestart |
设备启动后 设备挂起/重启时 |
启停I/O处理 管理软件状态机 |
启动/暂停DMA传输 重置DMA描述符环 |
设计原则: 硬件资源分配(如PCI BAR空间、中断)必须在
OnPrepareHardware中完成;而I/O逻辑的启停(如DMA传输队列)则交给SelfManagedIo回调。两者不可混淆。
3.2 OnPrepareHardware:硬件资源就绪的入口
OnPrepareHardware是驱动接收硬件资源的第一个机会。当PnP管理器将设备分配到D0电源状态并完成资源仲裁后,框架调用此方法。驱动在此方法中必须完成以下工作:
- 解析资源列表:从
IResourceList中提取内存地址、中断向量、DMA通道。 - 映射物理内存:调用
IWDFDevice::CreateMemory或直接访问CM_PARTIAL_RESOURCE_DESCRIPTOR。 - 创建DMA适配器:通过
IWDFDmaAdapter::CreateDmaAdapter初始化DMA引擎。 - 初始化硬件寄存器:设置设备控制寄存器、FIFO阈值等。
// OnPrepareHardware 典型实现骨架
HRESULT CMyDevice::OnPrepareHardware(
_In_ IWDFDevice* pDevice,
_In_ IResourceList* pResourcesRaw,
_In_ IResourceList* pResourcesTranslated
)
{
HRESULT hr = S_OK;
// 1. 遍历资源列表,查找内存资源
ULONG count = pResourcesTranslated->GetCount();
for (ULONG i = 0; i < count; i++)
{
PCM_PARTIAL_RESOURCE_DESCRIPTOR desc;
pResourcesTranslated->GetDescriptor(i, &desc);
if (desc->Type == CmResourceTypeMemory)
{
// 2. 映射BAR空间
PHYSICAL_ADDRESS start = desc->u.Memory.Start;
ULONG length = desc->u.Memory.Length;
// 调用框架API映射到虚拟地址
hr = pDevice->CreateMemory(/* ... */);
BREAK_IF_FAILED(hr);
}
}
// 3. 创建DMA适配器(如果设备支持总线主控DMA)
if (SUCCEEDED(hr))
{
hr = CreateDmaAdapter(pDevice);
}
// 4. 初始化硬件寄存器
if (SUCCEEDED(hr))
{
hr = InitializeHardwareRegisters();
}
return hr;
}
常见陷阱: 不要在
OnPrepareHardware中启动I/O请求或DMA传输。此时设备虽然已分配资源,但框架尚未完成I/O路径的初始化。启动传输应放在OnSelfManagedIoStart中。
3.3 OnReleaseHardware:资源清理的保障
OnReleaseHardware是OnPrepareHardware的逆操作。当设备被移除、资源重新平衡或进入低功耗状态时,框架调用此方法。驱动必须在此释放所有在OnPrepareHardware中获取的资源:
- 取消DMA适配器:调用
IWDFDmaAdapter::Delete释放DMA通道。 - 释放内存映射:框架自动管理大部分内存,但驱动分配的额外缓冲区需手动释放。
- 重置硬件状态:将设备寄存器恢复为安全默认值,防止后续驱动误操作。
- 清理软件状态:置空指针、释放引用计数。
// OnReleaseHardware 典型实现
HRESULT CMyDevice::OnReleaseHardware(
_In_ IWDFDevice* pDevice,
_In_ IResourceList* pResourcesTranslated
)
{
// 1. 停止所有正在进行的DMA传输(确保硬件空闲)
AbortAllDmaTransfers();
// 2. 释放DMA适配器
if (m_pDmaAdapter)
{
m_pDmaAdapter->Delete();
m_pDmaAdapter = NULL;
}
// 3. 释放公共缓冲区
if (m_pCommonBuffer)
{
// 框架API释放
m_pCommonBuffer->Release();
m_pCommonBuffer = NULL;
}
// 4. 重置硬件寄存器(可选但推荐)
ResetHardwareToSafeState();
return S_OK;
}
幂等性要求:
OnReleaseHardware可能被多次调用(例如在意外移除后)。实现必须幂等——第二次调用不应崩溃或泄漏资源。检查指针是否为NULL再释放是基本防护。
3.4 IPnpCallbackSelfManagedIo:I/O生命周期的精细控制
与硬件资源回调不同,IPnpCallbackSelfManagedIo管理的是驱动自身的I/O处理状态。它提供了三个方法,对应设备I/O的三个状态转换:
| 方法 | 触发时机 | 典型操作 |
|---|---|---|
OnSelfManagedIoStart |
设备完成初始化后 (OnPrepareHardware之后) |
启动I/O队列、使能中断、开始DMA传输 |
OnSelfManagedIoStop |
设备即将进入低功耗或资源重平衡 | 暂停I/O队列、等待正在进行的DMA完成、屏蔽中断 |
OnSelfManagedIoRestart |
设备从低功耗恢复后 | 恢复I/O队列、重新使能中断、重启DMA传输 |
// SelfManagedIo 回调实现示例
HRESULT CMyDevice::OnSelfManagedIoStart(
_In_ IWDFDevice* pDevice
)
{
// 1. 启动I/O队列(框架开始分发请求)
HRESULT hr = m_pIoQueue->Start();
// 2. 使能硬件中断(如果设备使用中断)
if (SUCCEEDED(hr))
{
EnableInterrupts();
}
// 3. 启动DMA传输引擎(例如:开始轮询描述符环)
if (SUCCEEDED(hr))
{
hr = StartDmaEngine();
}
return hr;
}
HRESULT CMyDevice::OnSelfManagedIoStop(
_In_ IWDFDevice* pDevice
)
{
// 1. 停止I/O队列(框架停止分发新请求)
m_pIoQueue->Stop();
// 2. 等待正在进行的DMA传输完成(带超时)
WaitForPendingDmaComplete(5000); // 5秒超时
// 3. 屏蔽中断
DisableInterrupts();
return S_OK;
}
最佳实践:
OnSelfManagedIoStop必须同步等待所有正在进行的DMA传输完成。如果硬件无法立即停止,驱动应使用IWDFDmaTransaction::Cancel取消未完成的传输,并等待DMA引擎进入空闲状态。
3.5 回调协作流程与DMA集成
下图展示了PnP回调与DMA传输引擎的完整协作流程,从设备枚举到I/O运行:
3.6 回调实现避坑指南
基于实际项目经验,以下是PnP回调实现中最高频的五个错误及其解决方案:
| 错误场景 | 后果 | 解决方案 |
|---|---|---|
在OnPrepareHardware中启动DMA传输 |
I/O队列未就绪,请求丢失或崩溃 | 将传输启动移至OnSelfManagedIoStart |
OnReleaseHardware未释放DMA适配器 |
资源泄漏,后续设备枚举失败 | 始终调用IWDFDmaAdapter::Delete |
OnSelfManagedIoStop未等待DMA完成 |
硬件仍在传输时进入低功耗,数据损坏 | 使用事件同步等待所有传输完成 |
| 回调中抛出异常 | 框架无法处理,导致系统BSOD | 所有回调使用__try/__except或返回HRESULT错误码 |
多次调用OnPrepareHardware未重置状态 |
重复分配资源,内存泄漏 | 在分配前检查并释放已有资源(幂等设计) |
严重警告: 如果
OnSelfManagedIoStop无法在合理时间内(通常5秒)完成DMA传输停止,驱动必须强制取消传输并记录错误日志。无限等待会导致系统挂起或电源管理超时。
3.7 小结:回调契约与DMA传输的绑定关系
本章的核心结论可以归纳为以下三点:
- 资源与逻辑分离:
IPnpCallbackHardware负责硬件资源(内存、DMA适配器),IPnpCallbackSelfManagedIo负责I/O逻辑(传输启停)。两者通过OnPrepareHardware→OnSelfManagedIoStart的顺序耦合。 - DMA适配器的生命周期:创建于
OnPrepareHardware,销毁于OnReleaseHardware。在OnSelfManagedIoStart/Stop中只控制传输状态,不重建适配器。 - 幂等与同步:所有回调必须幂等;
Stop回调必须同步等待硬件完成,否则后续ReleaseHardware可能访问正在使用的资源。