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)完成设备操作。
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结构。
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缓冲区,否则可能导致系统崩溃。
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为例)
- PnP管理器构造IRP:设置MajorFunction =
IRP_MJ_PNP,MinorFunction =IRP_MN_START_DEVICE,并在Parameters.StartDevice.AllocatedResources中传入已分配的资源列表。 - IRP遍历驱动栈:从最底层的总线驱动开始,依次向上传递到功能驱动。
- UMDF驱动接收回调:UMDF框架将IRP转换为对
IPnpCallback::OnPrepareHardware的调用。 - 驱动处理资源:驱动解析资源列表,映射I/O端口、注册中断、配置DMA通道。
- 完成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;
}
2.5 UMDF中的PnP回调实现要点
在UMDF驱动中,PnP处理通过实现IPnpCallback接口来完成。以下是关键实现要点:
- 注册回调:在驱动入口点
IDriverEntry::OnInitialize中,通过IWDFDriver::CreateDevice创建设备对象时,传入IPnpCallback接口指针。 - 资源解析:
OnPrepareHardware中接收两个资源列表:pResourcesRaw(原始资源)和<