欢迎光临
我们一直在努力

Reactos 第 9 章 设备驱动 — 9.10 磁盘的Miniport驱动模块

第 9 章 设备驱动 — 9.10 磁盘的Miniport驱动模块

本节剖析磁盘 Miniport 驱动模型(Scsiport/Storport)。 Miniport 模型是 类驱动/端口驱动分离 设计:端口驱动(scsiport.sys、storport.sys)提供 通用框架(中断处理、DMA 编程、SRB 队列、IRP 完成),Miniport 驱动提供 硬件特定代码(PCI 设备探测、命令构造、寄存器操作)。ReactOS 的 [drivers/storage/port/](file:///d:/reactos/drivers/storage/port/) 实现了两个端口驱动。


概述

Miniport 模型的关键设计是 可移植性:HBA 厂商(LSI、Intel、AMD)只需实现几十个回调,复杂的内核资源管理(中断、DMA、内存、IRP 排队)由端口驱动统一处理。

三层架构

+————————-+
| 类驱动 disk.sys | IRP_MJ_READ/WRITE
+————————-+
| IRP_MJ_SCSI
v
+————————-+
| 端口驱动 scsiport/storport | 通用框架
+————————-+
| HwInitialize / HwStartIo / HwInterrupt
v
+————————-+
| Miniport (e.g. storahci) | 硬件特定
+————————-+
| I/O 寄存器/DMA
v
+————————-+
| HBA 硬件 (SATA/SAS/IDE) |
+————————-+

本节内容概览

  • 9.10.0 框架图
  • 9.10.1 ScsiPortInitialize 与 HW_INITIALIZATION_DATA
  • 9.10.2 Miniport 回调函数族
  • 9.10.3 Scsiport 内部架构
  • 9.10.4 Storport(新式端口驱动)
  • 9.10.5 SRB 生命周期
  • 9.10.6 ATAPI Miniport 实例
  • 9.10.7 总结与代码索引

学习目标

  • 理解 Miniport 与端口驱动的接口
  • 掌握 HW_INITIALIZATION_DATA 关键字段
  • 知道 Scsiport vs Storport 区别
  • 跟踪 SRB 从提交到完成的路径

涉及的内核子系统

子系统头文件/源文件核心作用
Scsiport 框架 [drivers/storage/port/scsiport/scsiport.c](file:///d:/reactos/drivers/storage/port/scsiport/scsiport.c) ScsiPortInitialize
Scsiport FDO [fdo.c](file:///d:/reactos/drivers/storage/port/scsiport/fdo.c) FDO PnP 处理
Scsiport PDO [pdo.c](file:///d:/reactos/drivers/storage/port/scsiport/pdo.c) PDO PnP 处理
Scsiport 头 [scsiport.h](file:///d:/reactos/drivers/storage/port/scsiport/scsiport.h) 数据结构
Storport 框架 [drivers/storage/port/storport/storport.c](file:///d:/reactos/drivers/storage/port/storport/storport.c) StorPortInitialize
Storport FDO [fdo.c](file:///d:/reactos/drivers/storage/port/storport/fdo.c) FDO 处理
Storport Miniport [miniport.c](file:///d:/reactos/drivers/storage/port/storport/miniport.c) miniport 接口
StorAHCI [drivers/storage/port/storahci/storahci.c](file:///d:/reactos/drivers/storage/port/storahci/storahci.c) AHCI Miniport
ATAPI [drivers/storage/ide/atapi/atapi.c](file:///d:/reactos/drivers/storage/ide/atapi/atapi.c) IDE/ATAPI
ATAPI FDO [fdo.c](file:///d:/reactos/drivers/storage/ide/atapi/fdo.c) ATAPI FDO
BusLogic [BusLogic958.c](file:///d:/reactos/drivers/storage/port/buslogic/BusLogic958.c) 经典 SCSI 适配器

9.10.0 框架图

+—————————–+
| 应用 IRP_MJ_READ/WRITE |
+—————————–+
|
v
+—————————–+
| classpnp / disk |
+—————————–+
| IOCTL_SCSI_EXECUTE
v
+—————————–+
| 端口驱动 Scsiport / Storport |
| – ScsiPortStartIo |
| – ScsiPortBuildIo |
| – 队列管理 |
| – 中断分发 |
| – 超时监控 |
+—————————–+
| HwInitialize / HwFindAdapter
| HwStartIo / HwInterrupt
v
+—————————–+
| Miniport (storahci) |
| – PCI 配置空间 |
| – 寄存器 I/O |
| – 构建 PRDT/SG list |
| – 命令构造 |
+—————————–+
| MMIO
v
+—————————–+
| HBA (AHCI/SATA 控制器) |
+—————————–+


9.10.1 ScsiPortInitialize 与 HW_INITIALIZATION_DATA

Miniport 驱动的 DriverEntry 调用 ScsiPortInitialize:

// atapi 风格 Miniport
NTSTATUS NTAPI
DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
HW_INITIALIZATION_DATA HwInitData = { 0 };

HwInitData.HwInitializationDataSize = sizeof(HW_INITIALIZATION_DATA);
HwInitData.AdapterInterfaceType = PCIBus; // 或 Internal
HwInitData.HwFindAdapter = AtapiFindAdapter;
HwInitData.HwInitialize = AtapiInitialize;
HwInitData.HwStartIo = AtapiStartIo;
HwInitData.HwInterrupt = AtapiInterrupt;
HwInitData.HwResetBus = AtapiResetBus;
HwInitData.HwDmaStarted = NULL;
HwInitData.HwAdapterControl = AtapiAdapterControl;

HwInitData.DeviceExtensionSize = sizeof(IDE_CONTROLLER_EXTENSION);
HwInitData.SpecificLuExtensionSize = sizeof(IDE_DEVICE_EXTENSION);
HwInitData.SrbExtensionSize = sizeof(IDE_SRB_EXTENSION);
HwInitData.NumberOfAccessRanges = 5;
HwInitData.MapBuffers = TRUE;
HwInitData.TaggedQueuing = TRUE;
HwInitData.AutoRequestSense = TRUE;
HwInitData.MultipleRequestPerLu = TRUE;
HwInitData.ReceiveEvent = FALSE;
HwInitData.VendorIdLength = 0;

return ScsiPortInitialize(DriverObject, RegistryPath, &HwInitData, NULL);
}

HW_INITIALIZATION_DATA 关键字段

字段含义
AdapterInterfaceType 总线类型(PCIBus、PCIxBus、Internal 等)
HwFindAdapter 探测/确定适配器回调
HwInitialize 初始化回调
HwStartIo 启动 IO 回调(每次 SRB)
HwInterrupt 中断处理(DISPATCH_LEVEL)
HwResetBus 总线复位
HwAdapterControl 控制命令(暂停/恢复等)
DeviceExtensionSize Miniport 私有扩展字节数
SrbExtensionSize SRB 扩展字节数(Miniport 私有)
MapBuffers 是否映射 MDL 缓冲区
TaggedQueuing 是否支持命令队列
AutoRequestSense 端口自动 REQUEST_SENSE
MultipleRequestPerLu 每 LUN 多个请求
NumberOfAccessRanges I/O 访问范围数(用于 PCI BAR)

ScsiPortInitialize 流程

[scsiport.c](file:///d:/reactos/drivers/storage/port/scsiport/scsiport.c) 中:

  • 分配 FDO 和 SCSI_PORT_DEVICE_EXTENSION
  • 调用 HwFindAdapter 让 Miniport 探测 HBA
  • 注册 PCI 资源(IoAssignResources)
  • 注册中断(IoConnectInterrupt)
  • 调用 HwInitialize 初始化 HBA 寄存器

  • 9.10.2 Miniport 回调函数族

    HwFindAdapter

    ULONG NTAPI
    AtapiFindAdapter(IN PVOID DeviceExtension,
    IN PVOID HwContext,
    IN PVOID BusInformation,
    IN PCHAR ArgumentString,
    IN OUT PPORT_CONFIGURATION_INFORMATION ConfigInfo,
    OUT PBOOLEAN Again);

    • DeviceExtension:Miniport 私有设备扩展
    • ConfigInfo:端口驱动提供 PCI 资源(BAR、IRQ、DMA)
    • 返回值:SP_RETURN_FOUND、SP_RETURN_NOT_FOUND、SP_RETURN_ERROR

    Miniport 应:

  • 检查 ConfigInfo->AdapterInterfaceType
  • 读取 PCI 配置空间验证厂商/设备 ID
  • 设置最大 SRB 数 ConfigInfo->NumberOfBuses = 1
  • 设置 ConfigInfo->InitiatorBusId[0] = 0
  • HwInitialize

    BOOLEAN NTAPI
    AtapiInitialize(IN PVOID DeviceExtension);

    • 初始化 HBA 寄存器
    • 启用中断
    • 返回 TRUE 表示成功

    HwStartIo

    BOOLEAN NTAPI
    AtapiStartIo(IN PVOID DeviceExtension, IN PSCSI_REQUEST_BLOCK Srb);

    • 接收到新 SRB 时调用(DISPATCH_LEVEL)
    • Miniport 必须启动 IO 操作(写寄存器、构造 PRDT)
    • 之后 HwInterrupt 会被调用
    • 返回 TRUE 成功

    HwInterrupt

    BOOLEAN NTAPI
    AtapiInterrupt(IN PVOID DeviceExtension, IN PBOOLEAN CallMiniport);

    • 在 DIRQL 中断级别
    • 检查中断原因(AtapiInterruptIrq)
    • 处理完成的 SRB,调用 ScsiPortNotification(NextRequest, …) 通知端口驱动
    • 返回 TRUE 表示 我们处理了中断

    HwResetBus

    BOOLEAN NTAPI
    AtapiResetBus(IN PVOID DeviceExtension, IN ULONG PathId);

    • 复位 SCSI 总线
    • 标记所有 SRB 完成(SpStatus = SRB_STATUS_BUS_RESET)

    9.10.3 Scsiport 内部架构

    scsiport.sys 的核心数据结构是 SCSI_PORT_DEVICE_EXTENSION([scsiport.h](file:///d:/reactos/drivers/storage/port/scsiport/scsiport.h)):

    typedef struct _SCSI_PORT_DEVICE_EXTENSION {
    // 设备对象
    PDEVICE_OBJECT DeviceObject;

    // 中断
    PKINTERRUPT Interrupt;
    BOOLEAN IsrDisabled;

    // 队列管理
    KDEVICE_QUEUE DeviceQueue; // StartIO 队列

    // LUN 数组
    PSCSI_PORT_LU_EXTENSION LuExtensionArray[LUS_NUMBER];

    // 配置
    PPORT_CONFIGURATION_INFORMATION PortConfig;

    // 计时器
    KTIMER IoRequestTimer;
    KDPC IoTimerDpc;

    // 状态标志
    ULONG Flags; // SCSI_PORT_DEVICE_BUSY 等
    } SCSI_PORT_DEVICE_EXTENSION;

    关键例程

    函数源作用
    ScsiPortInitialize scsiport.c 注册 Miniport
    ScsiPortStartIo scsi.c 启动下一个 SRB
    ScsiPortBuildIo scsi.c 构造 IRP_MJ_SCSI
    ScsiPortFdoHandleInterrupts fdo.c 中断分发
    ScsiPortIoTimer fdo.c SRB 超时管理
    ScsiPortNotification scsiport.c 通知端口完成
    ScsiPortGetDeviceBase scsiport.c 映射 PCI BAR
    ScsiPortGetBusData scsiport.c 读 PCI 配置
    ScsiPortReadPortUchar scsiport.c 端口 I/O

    9.10.4 Storport(新式端口驱动)

    storport.sys 是 Windows XP 后引入的替代品,主要优势:

  • 并行 SCSI 命令(Storport 允许多 SRB 排队,Scsiport 强制串行)
  • MSI/MSI-X 中断支持
  • 更少 IRQL 限制(可在 IRQL <= DISPATCH_LEVEL 调用)
  • StorPort 入口

    NTSTATUS NTAPI
    StorPortInitialize(IN PVOID Argument1, // DriverObject
    IN PVOID Argument2, // RegistryPath
    IN PHW_INITIALIZATION_DATA HwInitializationData,
    IN PVOID HwContext);

    数据结构差异

    // Storport 扩展
    typedef struct _DRIVER_OBJECT_EXTENSION {
    LIST_ENTRY InitDataListHead; // 多个 HwInitData
    // …
    } DRIVER_OBJECT_EXTENSION;

    typedef struct _PORT_DEVICE_EXTENSION {
    PDRIVER_OBJECT_EXTENSION CommonExtension;
    // …
    } PORT_DEVICE_EXTENSION;

    StorAHCI Miniport

    [drivers/storage/port/storahci/storahci.c](file:///d:/reactos/drivers/storage/port/storahci/storahci.c) 是 AHCI(Advanced Host Controller Interface)Miniport 实现:

    NTSTATUS NTAPI
    DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
    {
    HW_INITIALIZATION_DATA HwInitData = { 0 };
    HwInitData.HwInitializationDataSize = sizeof(HW_INITIALIZATION_DATA);
    HwInitData.AdapterInterfaceType = PCIBus;
    HwInitData.HwFindAdapter = AhciFindAdapter;
    HwInitData.HwInitialize = AhciInitialize;
    HwInitData.HwStartIo = AhciStartIo;
    HwInitData.HwInterrupt = AhciInterrupt;
    HwInitData.HwResetBus = AhciResetBus;
    HwInitData.HwAdapterControl = AhciAdapterControl;
    // …
    return StorPortInitialize(DriverObject, RegistryPath, &HwInitData, NULL);
    }

    AHCI 使用 ATA 命令 FIS(Frame Information Structure) 和 PRDT(Physical Region Descriptor Table):

    +——————————–+
    | Command FIS (0x27 bytes) | ATA 命令
    +——————————–+
    | PRDT 数组 | 物理内存描述
    +——————————–+
    | Command Table |
    +——————————–+


    9.10.5 SRB 生命周期

    一个 SRB 的完整生命周期:

    1. 类驱动构造 SRB 并发起 IRP_MJ_SCSI
    |
    v
    2. 端口驱动 ScsiPortStartIo
    – 提取 SRB
    – 检查队列
    – 调 HwStartIo(DeviceExtension, Srb)
    |
    v
    3. Miniport HwStartIo
    – 构造命令
    – 启动 DMA
    – 写 HBA 寄存器
    |
    v
    4. HBA 完成 IO
    |
    v
    5. 硬件触发中断
    |
    v
    6. 端口驱动 ISR (KeConnectInterrupt 注册)
    – KeSynchronizeExecution 同步到 ISR spinlock
    – 调 HwInterrupt
    |
    v
    7. Miniport HwInterrupt
    – 读取中断状态
    – 识别完成的 SRB
    – 设置 Srb->SrbStatus
    – 调 ScsiPortNotification(RequestComplete, …)
    |
    v
    8. 端口驱动完成 SRB
    – ScsiPortCompleteRequest (内部)
    – 完成 IRP
    |
    v
    9. 类驱动收到完成
    – IoMarkIrpPending 等
    – 完成原始 I/O

    ScsiPortNotification 通知类型

    通知作用
    NextRequest 启动队列中下一个 SRB
    NextLuRequest 启动指定 LUN 下一个 SRB
    RequestComplete SRB 已完成
    ResetDetected 总线复位检测
    CallEnableInterrupts 启用中断

    9.10.6 ATAPI Miniport 实例

    ATAPI(AT Attachment with Packet Interface)由 [drivers/storage/ide/atapi/atapi.c](file:///d:/reactos/drivers/storage/ide/atapi/atapi.c) 实现。代码庞大,包含 IDE 控制器(PIIX、ICH 系列)处理。

    关键回调

    回调实现作用
    AtapiFindAdapter 读 PCI 配置空间 验证 PIIX/ICH
    AtapiInitialize 编程 ATA 控制器 启动 DMA
    AtapiStartIo 构造 ATA 命令 启动 IO
    AtapiInterrupt 中断服务 处理完成
    AtapiResetBus 总线复位 SRB 完成

    ATAPI 设备扩展

    typedef struct _IDE_CONTROLLER_EXTENSION {
    // PCI I/O 端口
    ULONG IoBase; // 命令端口基地址
    ULONG ControlPort; // 控制端口
    ULONG BusMasterPort; // 总线主控端口
    BOOLEAN IsNative; // 原生 PCI IDE

    // 通道信息
    struct {
    BOOLEAN IsActive;
    BOOLEAN InDMA;
    PSCSI_REQUEST_BLOCK CurrentSrb;
    // …
    } Channels[2];
    } IDE_CONTROLLER_EXTENSION;


    深入剖析:Miniport 驱动的内部机制

    Scsiport 与 Storport 的架构差异

    Scsiport 和 Storport 虽然都是端口驱动框架,但它们的内部架构存在显著差异。理解这些差异对于选择合适的 Miniport 开发模式至关重要。

    Scsiport 的串行模型:Scsiport 采用单命令串行模型,即同一时刻只有一个 SRB 在执行。从 ReactOS 的 scsiport.h 可以看到,SCSI_PORT_DEVICE_EXTENSION 中包含 SCSI_PORT_LUN_EXTENSION 数组,每个 LUN 扩展维护自己的设备队列(KDEVICE_QUEUE DeviceQueue)和活动请求计数器。当端口驱动调用 HwStartIo 时,它会将 SRB 标记为活动状态,并设置 SCSI_PORT_LU_ACTIVE 标志。在 SRB 完成之前,该 LUN 上的其他 SRB 会被排队等待。

    Storport 的并行模型:Storport 支持多命令并行执行,允许同一 LUN 上同时有多个 SRB 在处理。从 storport.c 的实现可以看到,Storport 使用 DRIVER_OBJECT_EXTENSION 管理多个 HW_INITIALIZATION_DATA,并通过更复杂的队列管理算法实现命令调度。Storport 还支持命令队列深度(Queue Depth)配置,Miniport 可以在 HwFindAdapter 中设置 ConfigInfo->TaggedQueuing = TRUE 并指定最大队列深度。

    中断处理差异:Scsiport 的中断处理相对简单,ISR(中断服务例程)直接调用 Miniport 的 HwInterrupt,后者在 DIRQL(设备中断请求级别)执行。Storport 则支持消息信号中断(MSI/MSI-X),允许将中断路由到不同的 CPU 核心,从而提高多核系统的性能。此外,Storport 的 ISR 可以在更低的 IRQL 级别执行某些操作,减少了中断处理的限制。

    HW_INITIALIZATION_DATA 的深层含义

    HW_INITIALIZATION_DATA 结构体是 Miniport 驱动与端口驱动框架之间的契约。它不仅定义了回调函数,还包含了大量配置参数,这些参数决定了端口驱动如何管理硬件资源。

    从 ReactOS 的 scsiport.c 和 storport.c 可以看到,ScsiPortInitialize 和 StorPortInitialize 函数在处理 HW_INITIALIZATION_DATA 时执行以下关键步骤:

  • 资源分配:端口驱动根据 AdapterInterfaceType(如 PCIBus)调用 HAL(硬件抽象层)函数获取 PCI 资源。这包括读取 PCI 配置空间获取 BAR(基地址寄存器)、分配 I/O 端口范围、映射内存地址、注册中断向量。

  • 设备扩展分配:端口驱动为 FDO(功能设备对象)分配设备扩展,大小由 DeviceExtensionSize 指定。这个扩展是 Miniport 的私有存储空间,用于保存硬件状态、寄存器基地址、DMA 缓冲区指针等。

  • SRB 扩展分配:每个 SRB 都有一个关联的 SrbExtension,大小由 SrbExtensionSize 指定。Miniport 可以使用这个扩展存储命令特定的数据,如 DMA 描述符表、命令缓冲区指针等。

  • 访问范围配置:NumberOfAccessRanges 指定了 Miniport 需要访问的 I/O 范围数量。端口驱动会根据这个值分配 ACCESS_RANGE 数组,并在调用 HwFindAdapter 时通过 ConfigInfo->AccessRanges 传递。

  • 特性标志设置:MapBuffers、TaggedQueuing、AutoRequestSense、MultipleRequestPerLu 等标志决定了端口驱动的行为。例如,MapBuffers = TRUE 表示端口驱动应该在调用 HwStartIo 之前将 MDL 描述的虚拟内存映射为物理地址;AutoRequestSense = TRUE 表示当 SRB 失败时,端口驱动应该自动发送 REQUEST_SENSE 命令获取错误详情。

  • HwFindAdapter 与硬件探测

    HwFindAdapter 是 Miniport 驱动中最复杂的回调之一。它负责探测硬件、验证设备 ID、配置资源、初始化硬件状态。从 ReactOS 的 atapi.c 可以看到,ATAPI Miniport 的 AtapiFindAdapter 执行以下步骤:

  • PCI 配置空间读取:Miniport 使用 ScsiPortGetBusData 读取 PCI 配置空间,验证厂商 ID 和设备 ID 是否匹配支持的硬件。例如,ATAPI 驱动会检查是否为 Intel PIIX、ICH 系列或其他兼容的 IDE 控制器。

  • 资源验证:Miniport 检查 ConfigInfo 中的资源是否足够。这包括 I/O 端口基地址、内存映射地址、中断向量、DMA 通道。如果资源不足,Miniport 返回 SP_RETURN_NOT_FOUND。

  • 硬件初始化:Miniport 写入硬件寄存器进行初始化。对于 IDE 控制器,这包括设置 PIO/DMA 模式、启用中断、配置总线主控(Bus Mastering)寄存器。

  • 设备枚举:Miniport 探测连接的设备的。对于 IDE,这意味着发送 IDENTIFY 命令获取磁盘或光驱的信息。Miniport 将设备类型、容量、支持的传输模式等信息保存到设备扩展中。

  • 配置信息填充:Miniport 更新 ConfigInfo 的多个字段,包括 NumberOfBuses(总线数量)、MaximumTransferLength(最大传输长度)、AlignmentMask(对齐要求)等。这些值会影响端口驱动如何构造 SRB。

  • HwStartIo 与命令构造

    HwStartIo 是 Miniport 驱动的核心函数,它在 DISPATCH_LEVEL 被调用,负责将 SRB 转换为硬件可识别的命令。这个过程涉及多个步骤:

  • SRB 解析:Miniport 从 SRB 中提取关键信息,包括 Function(读/写/其他)、PathId、TargetId、Lun(设备地址)、Cdb(命令描述块)、DataBuffer(数据缓冲区)、DataTransferLength(传输长度)。

  • 命令构造:根据 SRB 的 Function,Miniport 构造相应的硬件命令。对于 SCSI 命令(SRB_FUNCTION_EXECUTE_SCSI),Miniport 直接将 Cdb 复制到硬件命令缓冲区。对于 ATA 命令,Miniport 需要将 SCSI CDB 转换为 ATA 命令(这称为 SAT,SCSI-ATA Translation)。

  • DMA 设置:如果 SRB 包含数据缓冲区,Miniport 需要设置 DMA 传输。这包括构造 PRDT(物理区域描述符表)或 SG(散点-聚集)列表,将虚拟地址映射为物理地址,设置 DMA 方向和传输长度。

  • 寄存器编程:Miniport 将命令写入硬件寄存器。对于 IDE,这包括写入命令寄存器、特征寄存器、LBA 寄存器、设备寄存器等。对于 AHCI,Miniport 将命令写入内存中的命令列表,然后写入端口寄存器启动执行。

  • 中断启用:Miniport 启用硬件中断,以便在命令完成时收到通知。对于某些硬件,还需要设置超时定时器。

  • HwInterrupt 与完成处理

    HwInterrupt 在 DIRQL(设备中断请求级别)被调用,负责处理硬件中断、识别完成的命令、通知端口驱动。从 ReactOS 的 atapi.c 可以看到,ATAPI 的 AtapiInterrupt 执行以下步骤:

  • 中断状态读取:Miniport 读取硬件状态寄存器,确定中断原因。对于 IDE,这包括检查状态寄存器的 BSY(忙)、DRQ(数据请求)、ERR(错误)位。

  • 数据传输:如果是 PIO 传输(非 DMA),Miniport 需要在中断处理中读写数据寄存器。对于读操作,Miniport 从数据寄存器读取数据并复制到 SRB 的缓冲区;对于写操作,Miniport 将数据写入数据寄存器。

  • 命令完成检测:Miniport 检查命令是否已完成。对于 IDE,这通过检查状态寄存器的 BSY 位是否为 0、DRQ 位是否为 0 来判断。

  • 错误处理:如果命令失败,Miniport 读取错误寄存器获取错误代码,并设置 SRB 的 SrbStatus。如果启用了 AutoRequestSense,端口驱动会自动发送 REQUEST_SENSE 命令。

  • 通知端口驱动:Miniport 调用 ScsiPortNotification(RequestComplete, …) 通知端口驱动 SRB 已完成。端口驱动会完成 IRP 并启动队列中的下一个 SRB。

  • SRB 队列管理与超时处理

    端口驱动负责管理 SRB 队列,处理超时和重试。从 ReactOS 的 scsiport.h 可以看到,SCSI_PORT_DEVICE_EXTENSION 包含 KDEVICE_QUEUE DeviceQueue 用于排队等待的 SRB,以及 KTIMER IoRequestTimer 和 KDpc IoTimerDpc 用于超时监控。

    队列管理:当类驱动发送新的 SRB 时,端口驱动检查是否有活动的 SRB。如果没有,立即调用 HwStartIo 启动命令;如果有,将 SRB 排队到 DeviceQueue。当 SRB 完成时,端口驱动从队列中取出下一个 SRB 并启动。

    超时处理:端口驱动为每个 SRB 设置超时定时器。如果 SRB 在指定时间内未完成,定时器 DPC(延迟过程调用)会触发,端口驱动会尝试复位总线或标记 SRB 为超时完成。从 scsiport.h 可以看到 SCSI_PORT_TIMER_STATES 枚举定义了定时器的状态机,包括空闲、等待命令、复位等待等状态。

    错误恢复:当 SRB 失败时,端口驱动根据 SrbStatus 决定恢复策略。常见的策略包括:重试(对于瞬态错误)、降速(对于 DMA 错误,回退到 PIO 模式)、复位总线(对于严重错误)、标记设备离线(对于无法恢复的错误)。

    与 Windows 实现的对比分析

    ReactOS 的 Scsiport 和 Storport 实现与 Windows 存在以下差异:

  • Scsiport 的成熟度:ReactOS 的 Scsiport 是独立实现的,虽然功能上与 Windows 2003 的 Scsiport 相似,但在某些边缘情况(如多路径 I/O、热插拔)上可能不够完善。

  • Storport 的初级阶段:ReactOS 的 Storport 实现相对初级,主要支持基本的 AHCI 功能。Windows 的 Storport 已经发展到支持 NVMe、SCSI、SAS 等现代协议,并实现了高级特性如存储空间(Storage Spaces)、重复数据删除(Deduplication)等。

  • ATAPI 的实现:ReactOS 有两个 ATAPI 实现:旧的 atapi 目录和新的 uniata 目录。uniata 是一个第三方驱动,支持更多的硬件特性和更好的性能。ReactOS 正在逐步迁移到 uniata。

  • StorAHCI 的局限:ReactOS 的 storahci Miniport 虽然能够工作,但在多队列深度、NCQ(Native Command Queuing)、热插拔等高级特性上还有差距。Windows 的 stornvme 和 storahci 已经支持这些特性。

  • 调试技巧与常见问题

  • Miniport 加载失败:如果 Miniport 驱动无法加载,通常是 HwFindAdapter 返回了 SP_RETURN_NOT_FOUND。检查 PCI 配置空间读取是否正确、资源分配是否成功、硬件 ID 是否匹配。

  • SRB 超时:SRB 超时通常表示硬件未响应中断。使用 WinDbg 的 !devext 查看设备扩展中的定时器状态和 SRB 队列。检查 HwInterrupt 是否被调用、中断状态寄存器是否正确读取。

  • DMA 错误:DMA 传输失败通常表现为数据损坏或传输超时。检查 PRDT/SG 列表构造是否正确、物理地址映射是否正确、DMA 方向是否设置正确。

  • 命令队列问题:如果启用命令队列后出现性能下降或命令乱序,检查 Miniport 是否正确实现了队列管理。某些旧硬件不支持命令队列,需要在 HwFindAdapter 中设置 ConfigInfo->TaggedQueuing = FALSE。

  • 中断共享冲突:如果多个设备共享同一中断向量,可能导致中断处理冲突。检查 PCI 中断路由配置,确保每个设备有独立的中断向量(或使用 MSI/MSI-X)。

  • ReactOS 特有的调试输出:ReactOS 的存储栈启用了 DPRINT 和 DPRINT1 宏进行调试输出。设置 HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Debug Print Filter 中 SCSI 组件的级别为 0xFF,可以在调试串口看到详细的 SRB 流转日志。


  • 9.10.7 总结

    Miniport 模型的 关键要点:

  • 三层架构:类驱动 → 端口驱动 → Miniport → HBA
  • HW_INITIALIZATION_DATA:Miniport 入口的注册表
  • Miniport 回调:HwFindAdapter、HwInitialize、HwStartIo、HwInterrupt、HwResetBus
  • Scsiport:传统端口驱动,串行 SRB
  • Storport:新式端口驱动,并行 SRB + MSI
  • SRB 生命周期:构造 → 启动 → 中断 → 完成
  • AHCI Miniport:使用 FIS 和 PRDT
  • 下一节 9.11 将剖析命名管道与 Mailslot 文件系统驱动。


    9.10.8 设计哲学问答

    Q1:为什么需要端口驱动+Miniport 分离,而不是让类驱动直接操作硬件?

    A:减少冗余 + 统一的框架。

    如果每个 HBA 厂商都从零实现完整的存储驱动栈,会产生大量重复的内核代码——中断注册、DMA 编程、IRP 排队、MDL 映射等都是通用功能。端口驱动(scsiport/storport)将这些集中实现,Miniport 只需提供约 10 个回调(HwFindAdapter、HwStartIo、HwInterrupt 等):

    • 开发成本骤降:HBA 厂商的开发周期从数月降为几天;
    • 减少内核 bug:端口驱动的代码经过充分测试,Miniport 的代码量极小(通常仅几千行);
    • 热替换能力:端口驱动可以独立升级而不影响 Miniport(例如从 Scsiport 迁移到 Storport)。

    如果没有这种分离,disk.sys 就需要直接调用 atapi.c 或 storahci.c 的硬件操作函数,设备栈的每一层都直接耦合到具体硬件,分层设计的优势荡然无存。

    Q2:为什么 Scsiport 采用串行 SRB 模型而 Storport 采用并行模型?

    A:历史瓶颈与性能演进。

    Scsiport 设计于 1990 年代,当时磁盘是机械式 IDE/SCSI,IOPS 通常在几百级别。串行模型(单队列+单命令)实现简单,一个 SRB 完成后才启动下一个,足以满足当时需求。Storport 随着高速存储设备(SAS、SSD、NVMe)的出现而引入:

    • NCQ 利用:现代硬盘支持 Native Command Queuing,最多同时处理 32 条命令,串行模型无法利用这一特性;
    • 多核扩展:并行模型允许在不同 CPU 核心上同时处理不同 SRB 的完成例程;
    • 队列深度:ConfigInfo->TaggedQueuing 和 MultipleRequestPerLu 标志在 Storport 下才能真正发挥效果。

    串行模型实现简单、内存开销低,适合嵌入式或低端设备;并行模型性能高、延迟低,适合服务器和现代桌面环境。

    Q3:为什么 HwInterrupt 在 DIRQL 执行而 HwStartIo 在 DISPATCH_LEVEL 执行?

    A:原子性需求与内存访问能力的权衡。

    DIRQL(设备 IRQL)是最高软件 IRQL,在此级别执行确保:

    • 硬件寄存器原子访问:中断处理不会被其他中断或线程抢占,避免读写寄存器时的竞争条件;
    • 中断确认:Miniport 必须快速读取中断状态寄存器并确认中断,防止同一中断线路上其他设备饿死。

    HwStartIo 在 DISPATCH_LEVEL 执行是因为它需要访问分页内存(如 MDL 映射的缓冲区、PRDT 构造),而 DIRQL 下不允许访问分页内存(会导致页面错误):

    DIRQL: HwInterrupt → 读状态寄存器、确认中断(< 100 µs)
    DISPATCH_LEVEL: HwStartIo → 构造命令、映射 DMA、写寄存器(ms 级)

    这种 IRQL 分层策略确保了中断响应时间最短,同时给命令构造留出足够灵活的内存访问能力。

    Q4:为什么需要 SrbExtension 而不是让 Miniport 在设备扩展中统一分配所有状态?

    A:SRB 粒度的上下文隔离。

    每个 SRB 可能有不同的命令参数、DMA 描述符、超时状态。SrbExtension 实现了:

    • 命令级别隔离:每个 SRB 绑定自己的 DMA 描述符表(PRDT)、命令缓冲区指针、状态标志。多命令并行时不会相互覆盖;
    • 生命周期自动管理:端口驱动在 SRB 分配时自动创建 SrbExtension,在 SRB 完成时自动释放。Miniport 无需跟踪哪些扩展正在使用;
    • 线程安全:不同 SRB 的 SrbExtension 在内存中隔离,Miniport 访问时无需加锁。

    如果统一使用设备扩展保存命令状态,多命令并行时需要使用偏移量或索引来区分不同命令,容易因编程错误导致数据覆盖——特别是 IDE 控制器有两个通道(Channels[2]),每个通道的 CurrentSrb 必须独立管理。

    Q5:为什么 AHCI Miniport 使用 FIS 和 PRDT 而不是简单的寄存器 I/O?

    A:协议标准化与 DMA 效率。

    AHCI 是 SATA 的接口标准,FIS(Frame Information Structure)封装了 ATA 命令的标准格式:

    • 命令队列支持:FIS 支持 Command FIS、DMA Setup FIS、PIO Setup FIS 等多种帧类型。通过命令列表(Command List)和命令表(Command Table),AHCI 可以同时提交多达 32 个命令到设备;
    • 热插拔与端口倍增器:FIS 协议原生支持设备热插拔检测和端口倍增器(Port Multiplier),IDE 寄存器模式不支持这些特性。

    PRDT(Physical Region Descriptor Table)解决了物理内存的散点-聚集(Scatter-Gather)传输问题:

    • 一次 DMA 操作可以访问多个不连续的物理内存页,无需数据拷贝;
    • 每个 PRD 条目描述一段物理内存(基址+长度),多个 PRD 组成一个散点-聚集列表;
    • 相比 IDE 的单一 PRD(仅支持连续物理内存),AHCI 的 PRDT 消除了对大块连续物理内存的需求。

    IDE 寄存器: outb(ATA_SECTOR_CNT, 1); outb(ATA_LBA_LO, lba);
    AHCI FIS: cmd_fis->fis_type = FIS_TYPE_REG_H2D; cmd_fis->lba = lba;
    cmd_fis->count = 1; // 更标准、更灵活的协议封装

    Q6:为什么 ScsiPortNotification 需要 NextRequest、RequestComplete 等多种通知类型?

    A:端口驱动的状态机驱动。

    Scsiport 内部维护 SRB 队列的有限状态机,不同通知触发不同状态转换:

    通知触发时机端口驱动的响应
    RequestComplete SRB 完成时 完成 IRP、解除 MDL 映射、更新计数器
    NextRequest 准备好接收新 SRB 从 DeviceQueue 弹出并调用 HwStartIo
    NextLuRequest 指定 LUN 空闲 仅启动该 LUN 下个 SRB
    ResetDetected 总线复位 标记所有 SRB 为 SRB_STATUS_BUS_RESET
    CallEnableInterrupts 中断使能 调用 HalEnableInterrupt

    这种设计的精妙之处在于:Miniport 不需要了解队列管理和 IRP 完成细节,只需在恰当的时机发送通知。例如 ATAPI 的 AtapiInterrupt 在读取中断状态后只需调用:

    ScsiPortNotification(RequestComplete, DeviceExtension, Srb);
    ScsiPortNotification(NextRequest, DeviceExtension, 0); // 启动队列下一个

    端口驱动负责剩下的所有队列操作。

    Q7:为什么 HwFindAdapter 和 HwInitialize 要分开成两个回调?

    A:分阶段初始化与 PnP 回滚。

    这两个回调对应于 PnP 启动的两个不同阶段:

  • HwFindAdapter(PnP 探测阶段):在硬件资源尚未分配时调用。Miniport 读取 PCI 配置空间、验证厂商/设备 ID、检查设备是否支持。如果返回 SP_RETURN_NOT_FOUND,PnP 管理器继续尝试其他驱动,无需回滚任何硬件状态。
  • HwInitialize(资源分配后):端口驱动已调用 IoConnectInterrupt 注册中断和 IoAssignResources 分配 I/O 范围后调用。此时 Miniport 可以安全地初始化寄存器、启用中断。
  • 这种分离允许 PnP 管理器在资源分配失败时优雅回滚——如果 IoAssignResources 失败(例如 IRQ 冲突),PnP 管理器发出 IRP_MN_STOP_DEVICE 而无需撤销已初始化的硬件寄存器:

    // PnP 路径
    ScsiPortInitialize → HwFindAdapter → IoAssignResources → IoConnectInterrupt → HwInitialize
    ↓ 失败 ↓ 回滚
    IRP_MN_STOP_DEVICE 不需要撤销

    Q8:为什么使用 MapBuffers 标志让端口驱动处理 MDL 映射,而不是让 Miniport 直接调用 MmGetSystemAddressForMdlSafe?

    A:减少冗余 + 架构兼容性。

    MDL(Memory Descriptor List)映射涉及内核虚拟地址到物理地址的转换,在不同 CPU 架构上实现差异显著:

    • x86 非 PAE:物理地址 32 位,映射简单直接;
    • x86 PAE:物理地址 36 位,需要处理页目录指针表(PDPT);
    • x64:物理地址可达 48 位,使用四级页表;
    • ARM/ARM64:不同页大小(4K/16K/64K)和映射属性。

    如果每个 Miniport 都自己处理 MDL 映射,不仅会产生大量重复代码,还容易引入架构相关的 bug(如 PAE 模式下的地址截断)。设置 MapBuffers = TRUE 后:

  • 端口驱动在调用 HwStartIo 前调用 MmGetSystemAddressForMdlSafe 映射缓冲区;
  • Srb->DataBuffer 指向映射好的系统空间虚拟地址;
  • Miniport 直接通过 DataBuffer 访问数据,无需任何映射操作;
  • 端口驱动在 SRB 完成时自动解除映射。
  • Q9:为什么 Storport 需要支持 MSI/MSI-X 而 Scsiport 只需要传统 PIC 中断?

    A:多核扩展性与中断亲缘性。

    传统 PIC(Programmable Interrupt Controller)中断存在两个根本限制:

    • 中断线共享:多个设备可能共享同一条 IRQ 线,ISR 需要逐个设备查询中断状态(CallMiniport 逐一遍历),增加延迟;
    • 单核路由:传统中断只能路由到单个 CPU(通常是 CPU0),在多核系统中所有磁盘 I/O 中断集中在一个核上,造成 CPU0 的 IRQ 负载过高。

    MSI/MSI-X 将中断直接写入 PCIe 配置空间的 Message Data 寄存器,具有以下优势:

    • 每个队列独立中断:AHCI 的每个端口(Port)可以分配独立的 MSI 向量,不同端口的完成中断路由到不同 CPU;
    • 中断亲缘性:通过 KeSetTargetProcessorDpc 将 DPC 调度到特定 CPU,实现 Cache 亲和性;
    • 无中断线共享:MSI 是点对点的消息事务,不存在中断线共享带来的查询开销。

    从 ReactOS 的 storport/fdo.c 可以看到,Storport 在注册中断时调用 IoConnectInterrupt 时指定了 InterruptMode = LevelSensitive 和 ProcessorEnableMask,而 Scsiport 仅使用 KeConnectInterrupt 的单核模式。

    Q10:为什么 Miniport 使用 ScsiPortGetDeviceBase 映射 PCI BAR 而不是直接调用 MmMapIoSpace?

    A:平台无关性与资源跟踪。

    ScsiPortGetDeviceBase 是端口驱动提供的封装函数,它在内部处理了以下平台相关差异:

  • 总线地址翻译:调用 HalTranslateBusAddress 将 PCI 总线地址转换为系统物理地址。在不同体系结构上(x86 IO 空间 vs. x64 内存映射 IO),翻译逻辑不同;
  • 缓存属性设置:通过 HalGetInterruptVector 获取的 CacheType 设置映射的缓存属性。设备内存必须设置为非缓存(MmNonCached)或写合并(MmWriteCombined),错误的缓存属性会导致 DMA 数据不一致;
  • 资源跟踪:端口驱动内部维护映射表,记录每个 Miniport 映射了多少地址范围,在设备移除时自动释放。
  • // 正确做法(Miniport 使用端口驱动封装)
    PVOID IoBase = ScsiPortGetDeviceBase(Extension, SYSTEM_BUS, 0, 0,
    BAR0_PHYSICAL, BAR0_LENGTH, FALSE);

    // 错误做法(某些 Miniport 可能尝试直接映射)
    PVOID IoBase = MmMapIoSpace(BAR0_PHYSICAL, BAR0_LENGTH, MmNonCached);
    // 问题:无法处理 IO 空间 vs. 内存空间差异,资源泄漏风险高

    因此,使用 ScsiPortGetDeviceBase 不仅简化了 Miniport 的实现,还确保了跨架构的兼容性和资源管理的正确性。


    本章代码索引

    文件内容
    [scsiport.c](file:///d:/reactos/drivers/storage/port/scsiport/scsiport.c) SCSI 端口驱动框架
    [scsiport.h](file:///d:/reactos/drivers/storage/port/scsiport/scsiport.h) SCSI 端口驱动数据结构
    [scsiport/fdo.c](file:///d:/reactos/drivers/storage/port/scsiport/fdo.c) FDO 处理
    [scsiport/pdo.c](file:///d:/reactos/drivers/storage/port/scsiport/pdo.c) PDO 处理
    [scsiport/scsi.c](file:///d:/reactos/drivers/storage/port/scsiport/scsi.c) SRB 处理
    [storport.c](file:///d:/reactos/drivers/storage/port/storport/storport.c) 新式存储端口
    [storport/fdo.c](file:///d:/reactos/drivers/storage/port/storport/fdo.c) Storport FDO
    [storport/miniport.c](file:///d:/reactos/drivers/storage/port/storport/miniport.c) Storport miniport 接口
    [storahci.c](file:///d:/reactos/drivers/storage/port/storahci/storahci.c) AHCI Miniport
    [atapi.c](file:///d:/reactos/drivers/storage/ide/atapi/atapi.c) ATAPI 端口驱动
    [BusLogic958.c](file:///d:/reactos/drivers/storage/port/buslogic/BusLogic958.c) BusLogic SCSI 适配器
    赞(0)
    未经允许不得转载:171主机测评 » Reactos 第 9 章 设备驱动 — 9.10 磁盘的Miniport驱动模块
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址