2. PnP管理器基础

即插即用(PnP)管理器是Windows操作系统中负责硬件设备生命周期管理的核心组件。在UMDF驱动开发中,理解PnP管理器的工作机制是编写稳定、高效驱动程序的前提。本章将深入剖析PnP管理器的角色定位、设备节点(devnode)的层次结构、PnP状态机的状态转换以及IRP_MJ_PNP处理流程。

2.1 PnP管理器的角色与职责

PnP管理器在Windows I/O体系中扮演着“硬件设备协调者”的角色,其主要职责包括:

  • 设备检测与枚举:检测总线上的硬件变化(如USB插入/拔出),并枚举新设备。
  • 资源分配:为设备分配I/O端口、IRQ、DMA通道等硬件资源,解决资源冲突。
  • 设备状态管理:驱动设备在启动、停止、挂起、移除等状态之间有序转换。
  • 驱动程序加载与卸载:根据设备匹配结果加载相应的功能驱动和总线驱动。
  • IRP分发:将PnP相关的IRP(I/O请求包)发送到正确的驱动程序堆栈。
关键概念: PnP管理器不直接与硬件通信,而是通过总线驱动(Bus Driver)获取硬件信息,再协调功能驱动(Function Driver)完成设备操作。
PnP管理器在Windows I/O体系中的位置 用户模式应用程序 PnP管理器(内核态) 功能驱动(UMDF) 功能驱动(KMDF) 总线驱动 硬件抽象层(HAL) 物理硬件设备

2.2 设备节点(Devnode)概念

设备节点(Devnode)是PnP管理器在内存中维护的数据结构,用于描述一个逻辑设备及其在设备树中的位置。每个设备节点包含以下关键信息:

字段 描述 示例
设备实例ID 唯一标识设备的字符串 USB\VID_1234&PID_5678\123456
硬件ID 设备兼容的硬件标识列表 USB\Class_02
资源需求 设备所需的硬件资源列表 IRQ 11, I/O 0x300-0x31F
状态标志 当前设备状态(如DN_STARTED) 0x00000400
驱动栈指针 指向关联的设备对象栈 DEVICE_OBJECT 链表
开发提示: 在UMDF驱动中,每个设备对象(IWDFDevice)背后都对应一个内核态的devnode。驱动通过IWDFDevice::GetDeviceStack可以获取到设备栈的底层信息,但通常不需要直接操作devnode结构。
设备树与Devnode层次结构 根设备(Root) PCI总线 USB控制器 显卡 网卡 USB HID设备 键盘功能设备 每个矩形代表一个Devnode 父节点管理子节点的 枚举与资源分配 子节点继承父节点总线

2.3 PnP状态机

PnP状态机定义了设备从插入到移除的完整生命周期。每个设备节点在任意时刻都处于以下某个状态:

状态 状态码 描述 触发IRP
未启动 DN_STARTED = 0 设备已枚举但尚未启动 IRP_MN_START_DEVICE
已启动 DN_STARTED 设备正常运行,资源已分配 —
停止中 DN_STOPPED 设备正在停止,资源即将释放 IRP_MN_STOP_DEVICE
移除中 DN_REMOVED 设备正在被移除 IRP_MN_REMOVE_DEVICE
挂起 DN_SUSPENDED 设备进入低功耗状态 IRP_MN_QUERY_POWER
意外移除 DN_SURPRISE_REMOVED 设备被意外拔出 IRP_MN_SURPRISE_REMOVAL
重要: UMDF驱动必须正确处理状态转换。例如,在收到IRP_MN_SURPRISE_REMOVAL时,驱动应立即停止所有I/O操作并释放DMA缓冲区,否则可能导致系统崩溃。
PnP状态机转换图 未启动 已启动 停止中 移除中 挂起 意外移除 Start Stop Start Remove Suspend Resume SurpriseRemove 状态转换由PnP IRP触发 驱动需在回调中完成 资源分配/释放操作

2.4 IRP_MJ_PNP处理流程

当PnP管理器需要改变设备状态时,会构造一个主功能码为IRP_MJ_PNP的IRP,并设置相应的子功能码(Minor Function Code)。UMDF驱动通过注册IPnpCallback接口来接收这些IRP。

2.4.1 子功能码清单

子功能码 值 触发时机 UMDF回调接口
IRP_MN_START_DEVICE 0x00 设备首次启动或重启 IPnpCallback::OnPrepareHardware
IRP_MN_STOP_DEVICE 0x01 设备停止(资源重分配前) IPnpCallback::OnReleaseHardware
IRP_MN_REMOVE_DEVICE 0x02 设备被移除 IPnpCallback::OnReleaseHardware
IRP_MN_SURPRISE_REMOVAL 0x03 设备意外拔出 IPnpCallback::OnReleaseHardware
IRP_MN_QUERY_STOP_DEVICE 0x04 查询设备是否可以停止 IPnpCallback::OnQueryStop
IRP_MN_QUERY_REMOVE_DEVICE 0x05 查询设备是否可以移除 IPnpCallback::OnQueryRemove

2.4.2 典型处理流程(以Start Device为例)

  1. PnP管理器构造IRP:设置MajorFunction = IRP_MJ_PNP,MinorFunction = IRP_MN_START_DEVICE,并在Parameters.StartDevice.AllocatedResources中传入已分配的资源列表。
  2. IRP遍历驱动栈:从最底层的总线驱动开始,依次向上传递到功能驱动。
  3. UMDF驱动接收回调:UMDF框架将IRP转换为对IPnpCallback::OnPrepareHardware的调用。
  4. 驱动处理资源:驱动解析资源列表,映射I/O端口、注册中断、配置DMA通道。
  5. 完成IRP:驱动返回成功状态,PnP管理器将设备状态更新为DN_STARTED。
代码示例: UMDF驱动中处理Start Device的典型实现
HRESULT CMyDevice::OnPrepareHardware(
    _In_ IWDFDevice* pDevice,
    _In_ IWDFCmResourceList* pResourcesRaw,
    _In_ IWDFCmResourceList* pResourcesTranslated
)
{
    HRESULT hr = S_OK;
    ULONG count = pResourcesTranslated->GetCount();

    for (ULONG i = 0; i < count; i++)
    {
        PCM_PARTIAL_RESOURCE_DESCRIPTOR desc;
        desc = pResourcesTranslated->GetDescriptor(i);

        switch (desc->Type)
        {
        case CmResourceTypePort:
            // 映射I/O端口
            m_IoBase = desc->u.Port.Start;
            m_IoLen  = desc->u.Port.Length;
            break;

        case CmResourceTypeInterrupt:
            // 注册中断
            hr = pDevice->CreateInterrupt(
                desc->u.Interrupt.Vector,
                desc->u.Interrupt.Level,
                desc->u.Interrupt.Affinity,
                TRUE,  // EdgeTriggered
                &m_pInterrupt
                );
            break;

        case CmResourceTypeDma:
            // 配置DMA通道
            m_DmaChannel = desc->u.Dma.Channel;
            m_DmaWidth   = desc->u.Dma.Width;
            break;
        }
    }
    return hr;
}
IRP_MJ_PNP处理流程(Start Device示例) ① PnP管理器 构造IRP_MJ_PNP ② 总线驱动 处理资源、传递IRP ③ UMDF框架 转换为回调调用 ④ UMDF驱动 OnPrepareHardware ⑤ 资源处理 I/O/中断/DMA配置 ⑥ 完成IRP 返回STATUS_SUCCESS 处理要点 • 资源列表由PnP管理器分配 • 驱动必须解析Translated资源 • 失败时需回滚已分配的资源

2.5 UMDF中的PnP回调实现要点

在UMDF驱动中,PnP处理通过实现IPnpCallback接口来完成。以下是关键实现要点: