Reactos 第 9 章 设备驱动 — 9.5 一组PnP设备驱动模块的实例

第 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 驱动栈的典型组成:

  1. 总线驱动(PCI bus driver):枚举总线上的设备,创建 PDO
  2. 根总线驱动(ACPI):提供电源管理、配置信息
  3. 功能驱动:实现设备的核心功能
  4. 过滤驱动:在 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/) IopProcessStartDeviceIopProcessAssignResources
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 设备栈的构造是:

  1. ACPI 总线驱动 在启动时调用 IoInvalidateDeviceRelations 触发 PCI 设备枚举
  2. PCI 总线驱动IRP_MN_QUERY_DEVICE_RELATIONS 处理函数枚举所有 PCI 设备
  3. PnP 管理器为每个 PCI 设备创建 PDO
  4. PnP 管理器加载对应的 功能驱动 (如 NIC 驱动),调用其 AddDevice
  5. 功能驱动创建 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;
}

关键点

  1. PDO 来自 PnP 管理器PhysicalDeviceObject 参数是 PnP 管理器新创建的 PDO
  2. 附加 :调用 IoAttachDeviceToDeviceStack 把 FDO 放在 PDO 之上
  3. 保存下层指针Ext->LowerDevice 用于后续 IoCallDriver
  4. 设置标志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 完成资源分配:

  1. IRP_MN_QUERY_RESOURCE_REQUIREMENTS:设备驱动报告所需资源
  2. PnP 管理器仲裁:arbiter.c 决定资源分配
  3. IRP_MN_FILTER_RESOURCE_REQUIREMENTS:可选调整
  4. 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枚举表示,包括DeviceNodeUninitializedDeviceNodeInitializedDeviceNodeStartedDeviceNodeQueryStopped等状态
  • PhysicalDeviceObject: 指向物理设备对象(PDO)的指针
  • Parent/Child/Sibling: 设备树的父子兄弟关系指针,构成完整的设备树拓扑
  • ResourceList: 设备已分配的资源列表
  • InstancePath: 设备实例路径,用于注册表匹配

devnode.cPipAllocateDeviceNode函数中,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问题非常有用,可以追踪设备经历了哪些状态转换。典型的设备状态转换序列是:

  1. DeviceNodeUninitializedDeviceNodeInitialized(驱动加载完成)
  2. DeviceNodeInitializedDeviceNodeResourcesAssigned(资源分配完成)
  3. DeviceNodeResourcesAssignedDeviceNodeStarted(设备启动成功)
  4. DeviceNodeStartedDeviceNodeQueryStopped(查询停止)
  5. DeviceNodeQueryStoppedDeviceNodeStopped(设备停止)

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\CriticalDeviceDatabaseIopInstallCriticalDevice函数负责将设备信息写入该数据库,用于早期启动阶段的设备匹配。

该函数从设备实例键中读取HardwareIDCompatibleIDs,然后将服务名称和类GUID写入关键设备数据库。这样在系统启动早期,即使PnP管理器尚未完全初始化,内核也能根据硬件ID快速找到并加载相应的驱动程序。

与Windows PnP管理器的对比

ReactOS的PnP管理器实现与Windows基本兼容,但存在一些差异:

  1. 设备枚举策略: Windows使用更复杂的枚举缓存机制,ReactOS采用简单的树形结构
  2. 资源仲裁算法: Windows的仲裁器支持更复杂的优先级和冲突解决策略
  3. 状态机复杂度: Windows包含更多的中间状态和转换路径
  4. 热插拔支持: 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_DEVICEPnP 管理器通知驱动"资源已分配完毕,可以初始化硬件了" 的信号。在此 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 状态机精确追踪每个设备当前处于哪个阶段:UninitializedInitializedResourcesAssignedStartedQueryStoppedStopped。这是必要的,因为:

  • 并发安全:多个 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 设备驱动的协作要点:

  1. 三层模型:ACPI(根总线)→ PCI(PCI 总线)→ 功能驱动(设备)
  2. PnP 子功能码IRP_MN_START_DEVICEIRP_MN_QUERY_DEVICE_RELATIONSIRP_MN_FILTER_RESOURCE_REQUIREMENTS
  3. PCI 枚举:扫描所有 PCI 设备,创建 PDO,构造 HardwareID
  4. AddDevice:功能驱动接收 PDO,创建 FDO 并附加
  5. PnP IRP 下传 :必须 IoCallDriver 到 PDO 才能完成
  6. 完成例程 :设置 IoSetCompletionRoutine 在 PDO 处理完后回到 FDO
  7. 资源分配 :通过 IRP_MN_START_DEVICE 接收 CM_RESOURCE_LIST
  8. 协作增强: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_PNPIRP_MN_*
相关推荐
神成12 小时前
vmware 上 win7 系统按照 vmware tool
windows
袁小皮皮不皮2 小时前
3.HCIP OSPF补充知识(优化版)
服务器·网络·数据库·网络协议·智能路由器
虾壳云官方2 小时前
OpenClaw 2.7.9 Windows 一键部署教程:零基础也能搭建 AI 自动化助手
运维·人工智能·windows·自动化·openclaw·openclaw一键部署
志栋智能2 小时前
超自动化巡检:知识沉淀与团队协作的新载体
大数据·运维·网络·数据库·人工智能·自动化
酣大智3 小时前
策略路由PBR--企业双出口实验
网络·智能路由器·策略路由·pbr
袁小皮皮不皮3 小时前
1.HCIP BFD 学习笔记(优化版)
服务器·网络·笔记·网络协议·学习·智能路由器·ip
梁辰兴4 小时前
计算机网络基础:数据加密模型
网络·计算机网络·计算机·数据加密·计算机网络基础·梁辰兴·数据加密模型
fofantasy4 小时前
NSK LH12AN 微型导轨技术手册
运维·网络·数据库·经验分享·规格说明书
网络系统管理4 小时前
第八届江苏技能状元大赛“信息通信网络运行管理”项目技术文件
网络