3. UMDF PnP事件回调:IPnpCallbackHardware与IPnpCallbackSelfManagedIo

在UMDF驱动模型中,即插即用(PnP)与电源管理事件是驱动与内核交互的核心通道。本章聚焦于两个关键回调接口——IPnpCallbackHardware和IPnpCallbackSelfManagedIo,以及它们的核心方法OnPrepareHardware与OnReleaseHardware。理解这些回调的触发时机、职责边界和实现规范,是构建稳定DMA传输管道的前提。

核心认知: PnP回调不是可选的“钩子”,而是驱动必须实现的“契约”。硬件资源分配、DMA适配器初始化、I/O队列启停,都依赖这些回调的正确编排。

3.1 接口职责与生命周期定位

UMDF框架将硬件生命周期划分为多个阶段,每个阶段由特定的回调接口负责。下表对比了IPnpCallbackHardware与IPnpCallbackSelfManagedIo的职责范围:

接口 核心方法 触发阶段 主要职责 与DMA的关系
IPnpCallbackHardware OnPrepareHardware
OnReleaseHardware
设备进入D0(工作状态)
设备离开D0
分配/释放硬件资源
映射内存空间
初始化寄存器
创建DMA适配器对象
分配公共缓冲区
IPnpCallbackSelfManagedIo OnSelfManagedIoStart
OnSelfManagedIoStop
OnSelfManagedIoRestart
设备启动后
设备挂起/重启时
启停I/O处理
管理软件状态机
启动/暂停DMA传输
重置DMA描述符环
设计原则: 硬件资源分配(如PCI BAR空间、中断)必须在OnPrepareHardware中完成;而I/O逻辑的启停(如DMA传输队列)则交给SelfManagedIo回调。两者不可混淆。

3.2 OnPrepareHardware:硬件资源就绪的入口

OnPrepareHardware是驱动接收硬件资源的第一个机会。当PnP管理器将设备分配到D0电源状态并完成资源仲裁后,框架调用此方法。驱动在此方法中必须完成以下工作:

  1. 解析资源列表:从IResourceList中提取内存地址、中断向量、DMA通道。
  2. 映射物理内存:调用IWDFDevice::CreateMemory或直接访问CM_PARTIAL_RESOURCE_DESCRIPTOR。
  3. 创建DMA适配器:通过IWDFDmaAdapter::CreateDmaAdapter初始化DMA引擎。
  4. 初始化硬件寄存器:设置设备控制寄存器、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运行:

PnP回调与DMA传输引擎协作流程 PnP管理器分配资源 OnPrepareHardware 解析资源列表 创建DMA适配器 映射BAR空间 OnSelfManagedIoStart 启动I/O队列 使能中断 启动DMA传输引擎 DMA传输运行中 设备移除/低功耗 OnSelfManagedIoStop 暂停I/O队列 等待DMA完成 屏蔽中断 OnReleaseHardware 释放DMA适配器 释放公共缓冲区 重置硬件寄存器 资源分配完成 I/O运行中 正常/异常停止 关键约束:OnPrepareHardware → OnSelfManagedIoStart 顺序固定;OnSelfManagedIoStop → OnReleaseHardware 顺序固定

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可能访问正在使用的资源。