第 9 章 设备驱动 --- 9.5 一组PnP设备驱动模块的实例
本节以 PCI/PCI-X/ACPI 三层驱动为例剖析一组 PnP 设备驱动模块的协作。 现代计算机的设备栈通常由 总线驱动(如 PCI)→ 父总线驱动(如 ACPI)→ 功能驱动(如 NIC、磁盘)→ 过滤驱动 构成。理解这组协作的关键是把握 IRP_MJ_PNP 的子功能码(IRP_MN_*) 、设备栈的构造 、以及 总线枚举 机制。ReactOS 的 drivers/bus/(file:///d:/reactos/drivers/bus/) 目录下实现了完整的 PnP 驱动栈。
概述
PnP 设备驱动的核心是 IRP_MJ_PNP 主功能码下的多个 Minor Function(子功能码),PnP 管理器通过这些子功能码通知设备状态变化。PnP 驱动栈的典型组成:
- 总线驱动(PCI bus driver):枚举总线上的设备,创建 PDO
- 根总线驱动(ACPI):提供电源管理、配置信息
- 功能驱动:实现设备的核心功能
- 过滤驱动:在 FDO 之上添加额外行为
本节内容概览
- 9.5.0 框架图
- 9.5.1 PnP 子功能码总览(IRP_MN_*)
- 9.5.2 PCI 总线驱动剖析
- 9.5.3 PCI-X 增强驱动剖析
- 9.5.4 ACPI 驱动剖析
- 9.5.5 AddDevice 与 PDO/FDO 附加
- 9.5.6 PnP IRP 流向与下传
- 9.5.7 设备资源分配
- 9.5.8 总结与代码索引
学习目标
- 能够列出 8-10 个常用
IRP_MN_PNP_*子功能码 - 理解 PCI 总线如何枚举子设备
- 理解 AddDevice 在 PnP 设备栈中的角色
- 知道 PnP IRP 必须 下传到 PDO 才能完成
涉及的内核子系统
| 子系统 | 头文件/源文件 | 核心作用 |
|---|---|---|
| PCI 驱动 | drivers/bus/pci/(file:///d:/reactos/drivers/bus/pci/) | 总线枚举、配置空间读取 |
| PCI-X 驱动 | drivers/bus/pcix/(file:///d:/reactos/drivers/bus/pcix/) | 增强的 PCI(PCI Express 兼容) |
| ACPI 驱动 | drivers/bus/acpi/(file:///d:/reactos/drivers/bus/acpi/) | ACPI 电源管理、配置 |
| PnP 管理器 | ntoskrnl/io/pnpmgr/(file:///d:/reactos/ntoskrnl/io/pnpmgr/) | IopProcessStartDevice、IopProcessAssignResources |
| PCI 头文件 | drivers/bus/pci/pci.h(file:///d:/reactos/drivers/bus/pci/pci.h) | PCI 设备标识 |
| HAL PCI | hal/halx86/generic/pcibus.c(file:///d:/reactos/hal/halx86/generic/pcibus.c) | HAL PCI 抽象 |
9.5.0 框架图
+-----------------------------------+
| 应用程序 |
+-----------------------------------+
|
v
+-----------------------------------+
| I/O 管理器 (ntoskrnl/io/iomgr) |
+-----------------------------------+
| (IRP_MJ_PNP)
v
+-----------------------------------+
| 应用层过滤驱动 (如 fltmgr) |
+-----------------------------------+
|
v
+-----------------------------------+
| 功能驱动 (FDO) - 如 NIC 类驱动 |
+-----------------------------------+
|
v
+-----------------------------------+
| PCI-X 桥驱动 (FDO + PDO 子节点) |
+-----------------------------------+
|
v
+-----------------------------------+
| PCI 总线驱动 (FDO) |
+-----------------------------------+
|
v
+-----------------------------------+
| ACPI 根总线驱动 (FDO) |
+-----------------------------------+
|
v
+-----------------------------------+
| HAL + 硬件 |
+-----------------------------------+
设备栈中,每个驱动同时充当:
- 上级设备的功能驱动 (FDO)
- 下级设备的总线驱动 (PDO)
9.5.1 PnP 子功能码总览(IRP_MN_*)
IRP_MJ_PNP 主功能码下有 30 多个子功能码:
| 子功能码 | 含义 | 处理动作 |
|---|---|---|
IRP_MN_START_DEVICE |
启动设备 | 分配资源、初始化硬件 |
IRP_MN_QUERY_REMOVE_DEVICE |
查询可移除 | 检查是否有打开的 handle |
IRP_MN_REMOVE_DEVICE |
移除设备 | 停止硬件、解绑中断 |
IRP_MN_CANCEL_REMOVE_DEVICE |
取消移除 | 恢复状态 |
IRP_MN_STOP_DEVICE |
停止 | 释放资源(保留句柄) |
IRP_MN_QUERY_STOP_DEVICE |
查询可停止 | 同查询可移除 |
IRP_MN_CANCEL_STOP_DEVICE |
取消停止 | 恢复 |
IRP_MN_QUERY_DEVICE_RELATIONS |
查询设备关系 | 返回 BusRelations / Children |
IRP_MN_QUERY_INTERFACE |
查询接口 | 返回 INTERFACE 结构 |
IRP_MN_QUERY_CAPABILITIES |
查询能力 | 返回 DEVICE_CAPABILITIES |
IRP_MN_QUERY_RESOURCES |
查询资源(已分配) | 返回 IO_RESOURCE_LIST |
IRP_MN_QUERY_RESOURCE_REQUIREMENTS |
查询资源需求 | 返回 IO_RESOURCE_REQUIREMENTS_LIST |
IRP_MN_QUERY_DEVICE_TEXT |
查询设备描述 | 返回本地化文本 |
IRP_MN_FILTER_RESOURCE_REQUIREMENTS |
修改资源需求 | 调整需求列表 |
IRP_MN_QUERY_ID |
查询 ID | 返回 HardwareID/InstanceID |
IRP_MN_QUERY_PNP_DEVICE_STATE |
查询 PnP 状态 | 返回 PNP_DEVICE_STATE |
IRP_MN_QUERY_BUS_INFORMATION |
查询总线信息 | 返回 BUS_INFORMATION |
IRP_MN_DEVICE_USAGE_NOTIFICATION |
设备使用通知 | 通知 Hibernation/Paging |
IRP_MN_SURPRISE_REMOVAL |
意外移除 | 同 REMOVE 但更快 |
IRP_MN_QUERY_LEGACY_BUS_INFORMATION |
查询遗留总线 | 返回 LEGACY_BUS_INFORMATION |
设备栈的 PnP 流向
PnP 管理器
|
| 1. 发送 IRP_MN_QUERY_DEVICE_RELATIONS(BusRelations) → PCI 总线驱动
| PCI 总线驱动枚举 PCI 设备并创建 PDO
|
| 2. 通知上层驱动 AddDevice(每个新 PDO)
| FDO 创建并附加到 PDO
|
| 3. 发送 IRP_MN_START_DEVICE → FDO
| FDO 必须:a) 资源分配 b) 初始化硬件 c) 完成后下传到 PDO
|
| 4. 后续 IRP_MJ_PNP 沿设备栈流动
9.5.2 PCI 总线驱动剖析
drivers/bus/pci/pci.c 是 PCI 总线驱动的核心。DriverEntry 入口:
c
NTSTATUS NTAPI
DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
PPCI_DRIVER_EXTENSION DriverExtension;
NTSTATUS Status;
/* 1. 初始化驱动扩展 */
DriverExtension = ExAllocatePoolWithTag(NonPagedPool, sizeof(PCI_DRIVER_EXTENSION), 'IcpP');
if (!DriverExtension) return STATUS_INSUFFICIENT_RESOURCES;
RtlZeroMemory(DriverExtension, sizeof(PCI_DRIVER_EXTENSION));
/* 2. 注册 PnP 派发 */
DriverObject->MajorFunction[IRP_MJ_PNP] = PciPnpDispatch;
DriverObject->MajorFunction[IRP_MJ_POWER] = PciPowerDispatch;
DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = PciSystemControl;
DriverObject->MajorFunction[IRP_MJ_ADD_DEVICE] = PciAddDevice;
DriverObject->DriverExtension->AddDevice = PciAddDevice;
/* 3. 调用 HAL 初始化 */
HalInitializeProcessor(CPuNumber(), 0); // 触发 HAL PCI 初始化
DriverObject->DriverExtension->AddDevice = PciAddDevice;
return STATUS_SUCCESS;
}
PCI 添加设备
PciAddDevice 是 PCI 总线驱动的 AddDevice(同时作为 PCI 主机桥的 FDO):
c
NTSTATUS NTAPI
PciAddDevice(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT Pdo OPTIONAL)
{
/* PCI 总线驱动通常是被 ACPI 调用的,所以 Pdo 是 NULL */
/* PCI 自身需要创建自己的根设备(root enumerated) */
...
}
实际上 PCI 设备栈的构造是:
- ACPI 总线驱动 在启动时调用
IoInvalidateDeviceRelations触发 PCI 设备枚举 - PCI 总线驱动 的
IRP_MN_QUERY_DEVICE_RELATIONS处理函数枚举所有 PCI 设备 - PnP 管理器为每个 PCI 设备创建 PDO
- PnP 管理器加载对应的 功能驱动 (如 NIC 驱动),调用其
AddDevice - 功能驱动创建 FDO 并附加到 PDO
PCI 设备枚举
c
NTSTATUS NTAPI
PciQueryDeviceRelations(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
PCI_SLOT_NUMBER SlotNumber;
PCI_COMMON_CONFIG PciConfig;
PPNP_DEVICE_RELATIONS DeviceRelations = NULL;
ULONG DeviceCount = 0;
PDEVICE_OBJECT ChildPdo;
PPCI_DEVICE_EXTENSION DeviceExtension = (PPCI_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
ULONG BufferSize;
/* 1. 扫描所有 PCI 总线、所有设备 */
for (Bus = 0; Bus < PCI_MAX_BUS; Bus++)
{
for (Device = 0; Device < PCI_MAX_DEVICES; Device++)
{
for (Function = 0; Function < PCI_MAX_FUNCTION; Function++)
{
SlotNumber.u.bits.Reserved = 0;
SlotNumber.u.bits.DeviceNumber = Device;
SlotNumber.u.bits.FunctionNumber = Function;
/* 2. 读 PCI 配置空间 */
PciReadDeviceConfig(DeviceExtension->BusNumber, SlotNumber, &PciConfig, 0, sizeof(PCI_COMMON_CONFIG));
/* 3. 检查 VendorID */
if (PciConfig.VendorID == PCI_INVALID_VENDORID) continue;
/* 4. 检查是否多功能设备 */
if (Function == 0 && PciConfig.HeaderType & PCI_MULTIFUNCTION)
{
/* 扫描所有功能 */
}
/* 5. 创建 PDO */
Status = PciCreatePdo(Bus, SlotNumber, &PciConfig, &ChildPdo);
if (NT_SUCCESS(Status)) DeviceCount++;
}
}
}
/* 6. 构造 DEVICE_RELATIONS */
BufferSize = sizeof(PNP_DEVICE_RELATIONS) + (DeviceCount - 1) * sizeof(PDEVICE_OBJECT);
DeviceRelations = ExAllocatePoolWithTag(PagedPool, BufferSize, 'IcpP');
DeviceRelations->Count = DeviceCount;
/* 填充 PDO 指针数组 */
/* 7. 设置 IRP 状态并完成 */
Irp->IoStatus.Information = (ULONG_PTR)DeviceRelations;
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
PCI PDO 创建
c
NTSTATUS NTAPI
PciCreatePdo(
IN UCHAR BusNumber,
IN PCI_SLOT_NUMBER Slot,
IN PPCI_COMMON_CONFIG Config,
OUT PDEVICE_OBJECT *Pdo)
{
NTSTATUS Status;
PDEVICE_OBJECT PciPdo;
PPCI_PDO_EXTENSION PdoExtension;
/* 1. 创建设备对象(无 DriverObject->DeviceExtension 共享,使用 PciPdoExtension) */
Status = IoCreateDevice(NULL, sizeof(PCI_PDO_EXTENSION), NULL, FILE_DEVICE_BUS_EXTENDER,
FILE_DEVICE_SECURE_OPEN, FALSE, &PciPdo);
if (!NT_SUCCESS(Status)) return Status;
/* 2. 初始化 PDO 扩展 */
PdoExtension = (PPCI_PDO_EXTENSION)PciPdo->DeviceExtension;
PdoExtension->BusNumber = BusNumber;
PdoExtension->Slot = Slot;
PdoExtension->VendorId = Config->VendorID;
PdoExtension->DeviceId = Config->DeviceID;
PdoExtension->SubsystemVendorId = Config->u.type0.SubVendorID;
PdoExtension->SubsystemId = Config->u.type0.SubSystemID;
/* 3. 构造 HardwareID 字符串 */
swprintf(PdoExtension->HardwareIDs, L"PCI\\VEN_%04X&DEV_%04X&SUBSYS_%04X%04X&REV_%02X",
Config->VendorID, Config->DeviceID,
Config->u.type0.SubVendorID, Config->u.type0.SubSystemID,
Config->RevisionID);
/* 4. 设置标志 */
PciPdo->Flags |= DO_BUS_ENUMERATED_DEVICE;
PciPdo->Flags &= ~DO_DEVICE_INITIALIZING;
*Pdo = PciPdo;
return STATUS_SUCCESS;
}
9.5.3 PCI-X 增强驱动剖析
drivers/bus/pcix/pci.c 实现 PCI-X 增强:
- 兼容传统 PCI
- 支持 PCI Express 配置空间(4KB)
- 提供资源调整
PCI-X 驱动的 AddDevice 与 PCI 类似,但功能上 增强:
c
NTSTATUS NTAPI
PciXAddDevice(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT Pdo OPTIONAL)
{
/* Pci-X 桥驱动作为 Pci Express Root Complex 的一部分 */
/* 它创建 FDO 并附加到 PCI PDO 上方 */
...
}
PCI-X 的关键增强是 IRP_MN_FILTER_RESOURCE_REQUIREMENTS:
c
NTSTATUS NTAPI
PciXFilterResourceRequirements(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
PIO_RESOURCE_REQUIREMENTS_LIST RequirementsList;
PIO_RESOURCE_DESCRIPTOR ResourceDescriptor;
ULONG i;
RequirementsList = (PIO_RESOURCE_REQUIREMENTS_LIST)Irp->IoStatus.Information;
/* 遍历所有资源描述符 */
for (i = 0; i < RequirementsList->ListSize; i++)
{
ResourceDescriptor = &RequirementsList->List[i];
if (ResourceDescriptor->Type == CmResourceTypeMemory)
{
/* 调整内存资源对齐(PCI-X 4KB 对齐) */
ResourceDescriptor->u.Memory.Alignment = 0xFFF;
}
}
/* 下传到 PCI 驱动 */
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(DeviceExtension->LowerDevice, Irp);
}
9.5.4 ACPI 驱动剖析
drivers/bus/acpi/ 实现 ACPI 驱动。它是 最根的总线驱动:
- 提供 ACPI 表(SDT、FADT、MCFG 等)
- 处理电源管理
- 报告 PCI 配置空间(通过 MCFG 表)
- 报告 IO APIC 信息
ACPI DriverEntry
c
NTSTATUS NTAPI
AcpiDriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
DriverObject->MajorFunction[IRP_MJ_PNP] = AcpiPnpDispatch;
DriverObject->MajorFunction[IRP_MJ_POWER] = AcpiPowerDispatch;
DriverObject->DriverExtension->AddDevice = AcpiAddDevice;
return STATUS_SUCCESS;
}
ACPI 总线枚举
ACPI 在系统启动时通过 IRP_MN_QUERY_BUS_INFORMATION 报告自己是 根总线:
c
NTSTATUS NTAPI
AcpiQueryBusInformation(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
PPNP_BUS_INFORMATION BusInfo;
BusInfo = ExAllocatePoolWithTag(PagedPool, sizeof(PNP_BUS_INFORMATION), 'IcpA');
BusInfo->BusTypeGuid = GUID_ACPI_BUS; /* ACPI 总线 GUID */
BusInfo->LegacyBusType = PNPBus;
BusInfo->BusNumber = 0;
Irp->IoStatus.Information = (ULONG_PTR)BusInfo;
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
ACPI 与 PCI 的协作
ACPI 通过 MCFG 表提供 PCI Express 增强配置空间:
c
typedef struct _MCFG_HEADER {
ACPI_TABLE_HEADER Header;
UINT64 Reserved;
UINT64 BaseAddress; /* 4KB PCI Express 配置空间基址 */
UINT16 PciSegmentGroup;
UINT8 StartBusNumber;
UINT8 EndBusNumber;
} MCFG_HEADER, *PMCFG_HEADER;
ACPI 报告 MCFG 后,PCI 驱动在 IRP_MN_QUERY_INTERFACE 中获取 PCI_EXPRESS_ROOT_COMPLEX_INTERFACE,用于访问 4KB 扩展配置空间。
9.5.5 AddDevice 与 PDO/FDO 附加
AddDevice 的角色
AddDevice 由 PnP 管理器为 每个新创建的 PDO 调用,功能驱动在此创建自己的 FDO 并附加到 PDO。
c
NTSTATUS NTAPI
MyAddDevice(
IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject)
{
PDEVICE_OBJECT Fdo;
PDEVICE_EXTENSION Ext;
/* 1. 创建 FDO */
Status = IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION), NULL,
FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &Fdo);
if (!NT_SUCCESS(Status)) return Status;
Ext = (PDEVICE_EXTENSION)Fdo->DeviceExtension;
RtlZeroMemory(Ext, sizeof(DEVICE_EXTENSION));
/* 2. 保存 PDO 指针 */
Ext->Pdo = PhysicalDeviceObject;
/* 3. 附加到 PDO 上方 */
Ext->LowerDevice = IoAttachDeviceToDeviceStack(Fdo, PhysicalDeviceObject);
if (!Ext->LowerDevice)
{
IoDeleteDevice(Fdo);
return STATUS_DEVICE_NOT_CONNECTED;
}
/* 4. 设置设备标志 */
Fdo->Flags |= DO_POWER_PAGABLE;
Fdo->Flags &= ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
}
关键点
- PDO 来自 PnP 管理器 :
PhysicalDeviceObject参数是 PnP 管理器新创建的 PDO - 附加 :调用
IoAttachDeviceToDeviceStack把 FDO 放在 PDO 之上 - 保存下层指针 :
Ext->LowerDevice用于后续IoCallDriver - 设置标志 :
DO_POWER_PAGABLE表示电源 IRP 可在 PASSIVE_LEVEL 处理
9.5.6 PnP IRP 流向与下传
PnP IRP 的标准处理
PnP IRP 必须 从顶层向下层传递:
c
NTSTATUS NTAPI
MyPnpDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PIO_STACK_LOCATION IoStack = IoGetCurrentIrpStackLocation(Irp);
PDEVICE_EXTENSION Ext = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;
switch (IoStack->MinorFunction)
{
case IRP_MN_START_DEVICE:
/* 自己处理(分配资源、初始化硬件) */
Status = MyStartDevice(DeviceObject, Irp);
/* 必须下传到 PDO */
return IoCallDriver(Ext->LowerDevice, Irp);
case IRP_MN_QUERY_DEVICE_RELATIONS:
/* 通常 PDO 处理 */
return ForwardIrp(DeviceObject, Irp);
default:
/* 转发即可 */
return ForwardIrp(DeviceObject, Irp);
}
}
通用下传模式
c
NTSTATUS ForwardIrp(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PDEVICE_EXTENSION Ext = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(Ext->LowerDevice, Irp);
}
START_DEVICE 的特殊处理
IRP_MN_START_DEVICE 是唯一需要 FDO 在调用 IoCallDriver 之前 进行处理的 PnP IRP:
c
NTSTATUS NTAPI
MyStartDevice(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PIO_STACK_LOCATION IoStack = IoGetCurrentIrpStackLocation(Irp);
PCM_RESOURCE_LIST ResourceList = IoStack->Parameters.StartDevice.AllocatedResources;
PDEVICE_EXTENSION Ext = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;
/* 1. 保存资源(IRP 中分配的资源在完成后失效) */
Ext->ResourceList = ExAllocatePoolWithTag(PagedPool,
ResourceList->List[0].PartialResourceList.Count *
sizeof(CM_PARTIAL_RESOURCE_DESCRIPTOR),
'CprR');
RtlCopyMemory(Ext->ResourceList, &ResourceList->List[0].PartialResourceList.PartialDescriptors[0],
ResourceList->List[0].PartialResourceList.Count * sizeof(CM_PARTIAL_RESOURCE_DESCRIPTOR));
/* 2. 不在这里初始化硬件------硬件初始化在 PnP 完成后 */
/* 3. 设置状态 */
Irp->IoStatus.Status = STATUS_SUCCESS;
/* 4. 完成后下传到 PDO(让 PDO 也启动) */
return IoCallDriver(Ext->LowerDevice, Irp);
}
完成例程的必要性
为了在 PDO 处理完后回到 FDO,需要 IoSetCompletionRoutine:
c
/* 替代下传方式:设置完成例程 */
NTSTATUS ForwardWithCompletion(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PDEVICE_EXTENSION Ext = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;
/* 设置完成例程 */
IoSetCompletionRoutine(Irp, MyIrpCompletion, Ext, TRUE, TRUE, TRUE);
/* 下传 */
return IoCallDriver(Ext->LowerDevice, Irp);
}
NTSTATUS NTAPI
MyIrpCompletion(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PVOID Context)
{
PDEVICE_EXTENSION Ext = (PDEVICE_EXTENSION)Context;
PIO_STACK_LOCATION IoStack = IoGetCurrentIrpStackLocation(Irp);
/* PDO 处理完成,回到 FDO */
if (Irp->IoStatus.Status != STATUS_SUCCESS)
{
/* 失败处理 */
}
else
{
/* 成功处理:例如启动硬件 */
}
/* 必须返回 STATUS_SUCCESS 或 STATUS_MORE_PROCESSING_REQUIRED */
return STATUS_SUCCESS;
}
9.5.7 设备资源分配
PnP 管理器通过多次 PnP IRP 完成资源分配:
- IRP_MN_QUERY_RESOURCE_REQUIREMENTS:设备驱动报告所需资源
- PnP 管理器仲裁:arbiter.c 决定资源分配
- IRP_MN_FILTER_RESOURCE_REQUIREMENTS:可选调整
- IRP_MN_START_DEVICE:传递最终分配的资源
资源类型
c
typedef enum _CM_RESOURCE_TYPE {
CmResourceTypeNull = 0,
CmResourceTypePort, // I/O 端口
CmResourceTypeInterrupt, // 中断
CmResourceTypeMemory, // 内存
CmResourceTypeDma, // DMA
CmResourceTypeDeviceSpecific, // 设备特定
CmResourceTypeBusNumber, // 总线号
CmResourceTypeMemoryLarge, // 大内存(64 位)
} CM_RESOURCE_TYPE;
资源结构
c
typedef struct _CM_PARTIAL_RESOURCE_DESCRIPTOR {
UCHAR Type;
UCHAR ShareDisposition;
USHORT Flags;
union {
struct { PHYSICAL_ADDRESS Start; ULONG Length; } Port;
struct { ULONG Level; ULONG Vector; KAFFINITY Affinity; } Interrupt;
struct { PHYSICAL_ADDRESS Start; ULONG Length; } Memory;
struct { ULONG Channel; ULONG Port; ULONG Reserved1; } Dma;
} u;
} CM_PARTIAL_RESOURCE_DESCRIPTOR;
IRQ 资源特殊处理
c
case CmResourceTypeInterrupt:
Ext->Irq = ResourceDescriptor->u.Interrupt.Vector;
Ext->IrqLevel = ResourceDescriptor->u.Interrupt.Level;
Ext->IrqAffinity = ResourceDescriptor->u.Interrupt.Affinity;
/* 保存后用于 IoConnectInterrupt */
break;
9.5.8 PnP管理器内部实现机制
ReactOS的PnP管理器实现在ntoskrnl/io/pnpmgr/目录下,其核心职责是维护设备树(Device Tree)并协调设备枚举、资源分配和驱动加载。PnP管理器的关键数据结构是DEVICE_NODE,定义在ntoskrnl/io/pnpmgr/devnode.c中。
DEVICE_NODE数据结构
DEVICE_NODE是PnP管理器用来表示设备树中每个节点的核心结构。每个设备对象(PDO、FDO)都关联一个DEVICE_NODE实例。该结构包含以下关键字段:
- State : 设备当前状态,使用
PNP_DEVNODE_STATE枚举表示,包括DeviceNodeUninitialized、DeviceNodeInitialized、DeviceNodeStarted、DeviceNodeQueryStopped等状态 - PhysicalDeviceObject: 指向物理设备对象(PDO)的指针
- Parent/Child/Sibling: 设备树的父子兄弟关系指针,构成完整的设备树拓扑
- ResourceList: 设备已分配的资源列表
- InstancePath: 设备实例路径,用于注册表匹配
在devnode.c的PipAllocateDeviceNode函数中,PnP管理器分配并初始化DEVICE_NODE:
c
PDEVICE_NODE PipAllocateDeviceNode(IN PDEVICE_OBJECT PhysicalDeviceObject)
{
PDEVICE_NODE DeviceNode;
DeviceNode = ExAllocatePoolZero(NonPagedPool, sizeof(DEVICE_NODE), TAG_IO_DEVNODE);
if (!DeviceNode) return NULL;
InterlockedIncrement(&IopNumberDeviceNodes);
DeviceNode->InterfaceType = InterfaceTypeUndefined;
DeviceNode->BusNumber = -1;
DeviceNode->State = DeviceNodeUninitialized;
InitializeListHead(&DeviceNode->DeviceArbiterList);
InitializeListHead(&DeviceNode->DeviceTranslatorList);
if (PhysicalDeviceObject)
{
DeviceNode->PhysicalDeviceObject = PhysicalDeviceObject;
IoGetDevObjExtension(PhysicalDeviceObject)->DeviceNode = DeviceNode;
PhysicalDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
}
return DeviceNode;
}
设备树构建过程
设备树的构建是一个自顶向下的过程。在系统启动时,PnP管理器首先创建根设备节点(IopRootDeviceNode),然后通过总线枚举逐步扩展设备树。PiInsertDevNode函数负责将新设备节点插入到设备树中:
c
VOID PiInsertDevNode(IN PDEVICE_NODE DeviceNode, IN PDEVICE_NODE ParentNode)
{
KIRQL oldIrql;
KeAcquireSpinLock(&IopDeviceTreeLock, &oldIrql);
DeviceNode->Parent = ParentNode;
DeviceNode->Sibling = NULL;
if (ParentNode->LastChild == NULL)
{
ParentNode->Child = DeviceNode;
ParentNode->LastChild = DeviceNode;
}
else
{
ParentNode->LastChild->Sibling = DeviceNode;
ParentNode->LastChild = DeviceNode;
}
KeReleaseSpinLock(&IopDeviceTreeLock, oldIrql);
DeviceNode->Level = ParentNode->Level + 1;
}
设备树使用自旋锁(IopDeviceTreeLock)保护并发访问,确保在多处理器环境下的线程安全。每个设备节点维护其层级(Level)信息,便于后续的遍历和查询操作。
设备状态机
PnP管理器通过状态机管理设备的生命周期。PiSetDevNodeState函数负责状态转换,并维护状态历史记录:
c
PNP_DEVNODE_STATE PiSetDevNodeState(
IN PDEVICE_NODE DeviceNode,
IN PNP_DEVNODE_STATE NewState)
{
KIRQL oldIrql;
KeAcquireSpinLock(&IopDeviceTreeLock, &oldIrql);
PNP_DEVNODE_STATE prevState = DeviceNode->State;
if (prevState != NewState)
{
DeviceNode->State = NewState;
DeviceNode->PreviousState = prevState;
DeviceNode->StateHistory[DeviceNode->StateHistoryEntry++] = prevState;
DeviceNode->StateHistoryEntry %= DEVNODE_HISTORY_SIZE;
}
KeReleaseSpinLock(&IopDeviceTreeLock, oldIrql);
return prevState;
}
状态历史记录功能对于调试PnP问题非常有用,可以追踪设备经历了哪些状态转换。典型的设备状态转换序列是:
DeviceNodeUninitialized→DeviceNodeInitialized(驱动加载完成)DeviceNodeInitialized→DeviceNodeResourcesAssigned(资源分配完成)DeviceNodeResourcesAssigned→DeviceNodeStarted(设备启动成功)DeviceNodeStarted→DeviceNodeQueryStopped(查询停止)DeviceNodeQueryStopped→DeviceNodeStopped(设备停止)
PnP IRP派发机制
ntoskrnl/io/pnpmgr/pnpirp.c文件实现了PnP IRP的派发逻辑。以IRP_MN_START_DEVICE为例,PiIrpStartDevice函数负责向设备栈发送启动请求:
c
NTSTATUS PiIrpStartDevice(IN PDEVICE_NODE DeviceNode)
{
PAGED_CODE();
ASSERT(DeviceNode);
ASSERT(DeviceNode->State == DeviceNodeResourcesAssigned);
PVOID info;
IO_STACK_LOCATION stack = {
.MajorFunction = IRP_MJ_PNP,
.MinorFunction = IRP_MN_START_DEVICE,
.Parameters.StartDevice.AllocatedResources = DeviceNode->ResourceList,
.Parameters.StartDevice.AllocatedResourcesTranslated = DeviceNode->ResourceListTranslated
};
NTSTATUS status = IopSynchronousCall(DeviceNode->PhysicalDeviceObject, &stack, &info);
DeviceNode->CompletionStatus = status;
return status;
}
IopSynchronousCall是一个关键的辅助函数,它负责构造IRP并同步发送到设备栈顶端。该函数会等待IRP完成,确保PnP操作的顺序性。
资源仲裁器(Arbiter)
PnP管理器使用资源仲裁器来分配系统资源(I/O端口、内存、中断、DMA)。ReactOS实现了五个根仲裁器,定义在ntoskrnl/io/pnpmgr/pnpinit.c中:
- IopRootPortArbiter: I/O端口资源仲裁
- IopRootMemArbiter: 内存资源仲裁
- IopRootDmaArbiter: DMA通道仲裁
- IopRootIrqArbiter: 中断资源仲裁
- IopRootBusNumberArbiter: 总线号仲裁
以内存仲裁器为例,ntoskrnl/io/pnpmgr/arb/arbmem.c实现了内存资源的解包和打包逻辑:
c
NTSTATUS IopArbMemUnpackRequirements(
IN PIO_RESOURCE_DESCRIPTOR IoDescriptor,
OUT PUINT64 OutMinimumAddress,
OUT PUINT64 OutMaximumAddress,
OUT PUINT32 OutLength,
OUT PUINT32 OutAlignment)
{
if (IoDescriptor->Type == CmResourceTypeMemory ||
IoDescriptor->Type == CmResourceTypeMemoryLarge)
{
*OutMinimumAddress = IoDescriptor->u.Memory.MinimumAddress.QuadPart;
*OutMaximumAddress = IoDescriptor->u.Memory.MaximumAddress.QuadPart;
*OutLength = IoDescriptor->u.Memory.Length;
*OutAlignment = IoDescriptor->u.Memory.Alignment;
}
else
{
*OutMinimumAddress = 0x1000;
*OutMaximumAddress = 0xFFFFFFFF;
*OutLength = 0x10000;
*OutAlignment = 0x1000;
}
return STATUS_SUCCESS;
}
仲裁器的工作流程是:首先从设备的资源需求描述符中解包出最小地址、最大地址、长度和对齐要求,然后在可用资源范围内寻找满足条件的分配方案,最后将分配结果打包回资源描述符。
关键设备数据库(CriticalDeviceDatabase)
ReactOS维护一个关键设备数据库,位于注册表HKLM\SYSTEM\CurrentControlSet\Control\CriticalDeviceDatabase。IopInstallCriticalDevice函数负责将设备信息写入该数据库,用于早期启动阶段的设备匹配。
该函数从设备实例键中读取HardwareID和CompatibleIDs,然后将服务名称和类GUID写入关键设备数据库。这样在系统启动早期,即使PnP管理器尚未完全初始化,内核也能根据硬件ID快速找到并加载相应的驱动程序。
与Windows PnP管理器的对比
ReactOS的PnP管理器实现与Windows基本兼容,但存在一些差异:
- 设备枚举策略: Windows使用更复杂的枚举缓存机制,ReactOS采用简单的树形结构
- 资源仲裁算法: Windows的仲裁器支持更复杂的优先级和冲突解决策略
- 状态机复杂度: Windows包含更多的中间状态和转换路径
- 热插拔支持: Windows对USB、PCIe热插拔的支持更加完善
尽管如此,ReactOS的PnP管理器已经能够支持大多数标准PnP设备的枚举和配置,为驱动程序开发提供了完整的WDM兼容接口。
9.5.9 十问为什么
1. 为什么 PnP IRP 必须下传到 PDO 才能完成?
因为 PDO 是 设备栈的根基,代表实际的物理硬件(如 PCI 设备本身)。功能驱动(FDO)处理的是逻辑层面的操作,而物理层面(如启用设备、配置总线)需要由总线驱动的 PDO 来完成。如果 FDO 截断 PnP IRP 不往下传,PDO 就永远收不到启动/停止/移除等通知,硬件不会真正被初始化或释放,导致资源泄漏或设备无法工作。
2. 为什么 PCI 总线驱动要扫描所有总线、所有设备、所有功能?
PCI 总线架构是 枚举型总线 ,不像 ISA 总线有固定的设备位置。PCI 规范规定每个总线(0-255)上最多 32 个设备,每个设备最多 8 个功能。PCI 总线驱动必须通过读取配置空间(PciReadDeviceConfig)的 VendorID 来判断该位置是否存在有效设备------如果 VendorID == PCI_INVALID_VENDORID,说明该位置没有设备。这种全扫描是唯一可靠的方式发现所有 PCI 设备。
3. 为什么 IRP_MN_START_DEVICE 是 PnP 流程中最关键的节点?
IRP_MN_START_DEVICE 是 PnP 管理器通知驱动"资源已分配完毕,可以初始化硬件了" 的信号。在此 IRP 之前,驱动只能做软件层面的准备(如创建设备对象、注册派发函数);只有收到这个 IRP 后,驱动才能访问 I/O 端口、映射内存、连接中断------因为这些资源在此之前还没有被仲裁器分配给该设备。跳过这个节点直接操作硬件会导致资源冲突或蓝屏。
4. 为什么 PCI-X 要单独实现 IRP_MN_FILTER_RESOURCE_REQUIREMENTS?
PCI-X 和 PCI Express 设备的资源需求比传统 PCI 更复杂 。例如,PCI Express 配置空间是 4KB 而不是 PCI 的 256 字节,需要更大的内存对齐(4KB 边界)。PCI-X 驱动通过 IRP_MN_FILTER_RESOURCE_REQUIREMENTS 拦截资源需求列表,在 PnP 管理器最终分配前修改对齐要求和长度,确保分配的资源满足 PCI-X 硬件的实际需求。如果没有这个机制,PnP 管理器按传统 PCI 参数分配的资源可能无法正确使用。
5. 为什么 ACPI 要作为根总线驱动?
因为 ACPI(Advanced Configuration and Power Interface)是 固件与操作系统之间的标准接口。在 x86 架构中,ACPI 提供系统启动时最关键的信息:PCI Express 配置空间的基地址(通过 MCFG 表)、IO APIC 的映射、电源管理策略。ACPI 作为根总线驱动,意味着它是整个设备树的最顶层,所有其他总线(PCI、ISA、USB 控制器)都从 ACPI 的设备节点派生出来。这样操作系统可以通过统一的 ACPI 接口获取硬件配置,而不必直接解析不同主板厂商各异的固件。
6. 为什么 PDO 要设置 DO_BUS_ENUMERATED_DEVICE 标志?
DO_BUS_ENUMERATED_DEVICE 标志告诉 PnP 管理器 这个设备是由总线驱动动态枚举出来的 ,不是由驱动硬编码创建的(老式驱动的 IoCreateDevice)。这个标志影响 PnP 管理器的处理逻辑:总线枚举的设备会参与完整的 PnP 生命周期(资源分配、启动、停止、移除),而老式设备则跳过这些步骤。没有这个标志,PnP 管理器可能错误地尝试对设备执行 PnP 操作,或者反过来忽略它。
7. 为什么 AddDevice 中 FDO 必须保存 LowerDevice 指针?
因为 IoAttachDeviceToDeviceStack 只是将 FDO 插入设备栈,FDO 本身并不知道自己在栈中的具体位置。保存 LowerDevice 指针 是为了后续所有 IRP 下传时使用------无论是 IoCallDriver(Ext->LowerDevice, Irp) 转发 IRP,还是发送同步请求,都需要知道下层设备对象是谁。如果丢失了这个指针,FDO 就无法将 IRP 传递给 PDO,设备栈断裂,后续所有 I/O 操作都无法到达硬件。
8. 为什么 PnP 管理器需要 DEVICE_NODE 状态机?
设备的状态转换是 异步且不可逆的 (例如启动后可能查询停止,查询失败后回到启动状态)。DEVICE_NODE 状态机精确追踪每个设备当前处于哪个阶段:Uninitialized → Initialized → ResourcesAssigned → Started → QueryStopped → Stopped。这是必要的,因为:
- 并发安全:多个 PnP 操作可能同时针对同一个设备(例如用户请求移除的同时系统请求休眠)
- 调试支持:状态历史记录可以追踪设备经历了哪些转换
- 合法性检查 :
PiIrpStartDevice断言设备必须在ResourcesAssigned状态才能启动,防止非法的状态转换
9. 为什么资源分配要经过仲裁器(Arbiter)而不是直接分配?
因为 多个设备可能请求相同的资源 。例如两个 PCI 设备都可能请求 I/O 端口 0x3F0-0x3F7,如果直接分配就会导致冲突。仲裁器的作用是:
- 收集所有设备的资源需求(
IRP_MN_QUERY_RESOURCE_REQUIREMENTS) - 在可用资源范围内寻找不冲突的分配方案
- 处理共享资源(如中断线可以共享)和独占资源(如 I/O 端口不能共享)
- 当资源不足时返回失败,避免硬件冲突导致系统不稳定
10. 为什么 IRP_MN_QUERY_DEVICE_RELATIONS(BusRelations) 由 PDO 处理而不是 FDO?
因为 总线驱动(创建 PDO 的驱动)才知道总线上有哪些子设备 。PCI 总线驱动的 PDO 对应的是"PCI 总线"这个物理实体,它通过扫描配置空间知道总线上插了哪些设备。FDO 是功能驱动,只关心自己的设备如何工作,不关心总线上还有什么其他设备。如果让 FDO 处理 BusRelations,它就不得不越俎代庖去扫描总线,违背了分层驱动的设计原则------谁拥有物理资源,谁负责枚举。
9.5.9 总结
PCI/PCI-X/ACPI 一组 PnP 设备驱动的协作要点:
- 三层模型:ACPI(根总线)→ PCI(PCI 总线)→ 功能驱动(设备)
- PnP 子功能码 :
IRP_MN_START_DEVICE、IRP_MN_QUERY_DEVICE_RELATIONS、IRP_MN_FILTER_RESOURCE_REQUIREMENTS等 - PCI 枚举:扫描所有 PCI 设备,创建 PDO,构造 HardwareID
- AddDevice:功能驱动接收 PDO,创建 FDO 并附加
- PnP IRP 下传 :必须
IoCallDriver到 PDO 才能完成 - 完成例程 :设置
IoSetCompletionRoutine在 PDO 处理完后回到 FDO - 资源分配 :通过
IRP_MN_START_DEVICE接收CM_RESOURCE_LIST - 协作增强:PCI-X 过滤资源需求,ACPI 提供 MCFG 等根总线信息
下一节 9.6 深入剖析 中断处理(KeConnectInterrupt / IoConnectInterrupt)。
本章代码索引
| 文件 | 内容 |
|---|---|
| pci/pci.c(file:///d:/reactos/drivers/bus/pci/pci.c) | PCI 总线驱动主程序 |
| pci/fdo.c(file:///d:/reactos/drivers/bus/pci/fdo.c) | PCI FDO 处理 |
| pci/pdo.c(file:///d:/reactos/drivers/bus/pci/pdo.c) | PCI PDO 处理(枚举的设备) |
| pci/pci.h(file:///d:/reactos/drivers/bus/pci/pci.h) | PCI 数据结构 |
| pcix/(file:///d:/reactos/drivers/bus/pcix/) | PCI-X 增强驱动 |
| acpi/main.c(file:///d:/reactos/drivers/bus/acpi/main.c) | ACPI 主程序 |
| acpi/busmgr/(file:///d:/reactos/drivers/bus/acpi/busmgr/) | ACPI 总线管理 |
| acpi_new/(file:///d:/reactos/drivers/bus/acpi_new/) | 新版 ACPI(uACPI 实现) |
| isapnp/(file:///d:/reactos/drivers/bus/isapnp/) | ISA PnP 总线驱动 |
| pnp/pnpmgr.c(file:///d:/reactos/ntoskrnl/io/pnpmgr/pnpmgr.c) | PnP 管理器核心 |
| io/pnpmgr/pnpdispatch.c(file:///d:/reactos/ntoskrnl/io/pnpmgr/pnpdispatch.c) | PnP IRP 派发 |
| arbiter.c(file:///d:/reactos/ntoskrnl/io/pnpmgr/arbiter.c) | 资源仲裁器 |
| iotypes.h(file:///d:/reactos/sdk/include/xdk/iotypes.h) | IRP_MJ_PNP、IRP_MN_* |