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

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

本节剖析磁盘 Miniport 驱动模型(Scsiport/Storport)。 Miniport 模型是 类驱动/端口驱动分离 设计:端口驱动(scsiport.sysstorport.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 ScsiPortInitializeHW_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 ScsiPortInitializeHW_INITIALIZATION_DATA

Miniport 驱动的 DriverEntry 调用 ScsiPortInitialize

c 复制代码
// 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) 中:

  1. 分配 FDO 和 SCSI_PORT_DEVICE_EXTENSION
  2. 调用 HwFindAdapter 让 Miniport 探测 HBA
  3. 注册 PCI 资源(IoAssignResources
  4. 注册中断(IoConnectInterrupt
  5. 调用 HwInitialize 初始化 HBA 寄存器

9.10.2 Miniport 回调函数族

HwFindAdapter

c 复制代码
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_FOUNDSP_RETURN_NOT_FOUNDSP_RETURN_ERROR

Miniport 应:

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

HwInitialize

c 复制代码
BOOLEAN NTAPI
AtapiInitialize(IN PVOID DeviceExtension);
  • 初始化 HBA 寄存器
  • 启用中断
  • 返回 TRUE 表示成功

HwStartIo

c 复制代码
BOOLEAN NTAPI
AtapiStartIo(IN PVOID DeviceExtension, IN PSCSI_REQUEST_BLOCK Srb);
  • 接收到新 SRB 时调用(DISPATCH_LEVEL)
  • Miniport 必须启动 IO 操作(写寄存器、构造 PRDT)
  • 之后 HwInterrupt 会被调用
  • 返回 TRUE 成功

HwInterrupt

c 复制代码
BOOLEAN NTAPI
AtapiInterrupt(IN PVOID DeviceExtension, IN PBOOLEAN CallMiniport);
  • 在 DIRQL 中断级别
  • 检查中断原因(AtapiInterruptIrq
  • 处理完成的 SRB,调用 ScsiPortNotification(NextRequest, ...) 通知端口驱动
  • 返回 TRUE 表示 我们处理了中断

HwResetBus

c 复制代码
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_EXTENSIONscsiport.h(file:///d:/reactos/drivers/storage/port/scsiport/scsiport.h)):

c 复制代码
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 后引入的替代品,主要优势:

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

StorPort 入口

c 复制代码
NTSTATUS NTAPI
StorPortInitialize(IN PVOID Argument1,  // DriverObject
                   IN PVOID Argument2,  // RegistryPath
                   IN PHW_INITIALIZATION_DATA HwInitializationData,
                   IN PVOID HwContext);

数据结构差异

c 复制代码
// 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 实现:

c 复制代码
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 设备扩展

c 复制代码
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.cstorport.c 可以看到,ScsiPortInitializeStorPortInitialize 函数在处理 HW_INITIALIZATION_DATA 时执行以下关键步骤:

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

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

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

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

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

HwFindAdapter 与硬件探测

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

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

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

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

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

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

HwStartIo 与命令构造

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

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

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

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

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

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

HwInterrupt 与完成处理

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

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

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

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

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

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

SRB 队列管理与超时处理

端口驱动负责管理 SRB 队列,处理超时和重试。从 ReactOS 的 scsiport.h 可以看到,SCSI_PORT_DEVICE_EXTENSION 包含 KDEVICE_QUEUE DeviceQueue 用于排队等待的 SRB,以及 KTIMER IoRequestTimerKDpc 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 存在以下差异:

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

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

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

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

调试技巧与常见问题

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

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

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

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

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

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


9.10.7 总结

Miniport 模型的 关键要点

  1. 三层架构:类驱动 → 端口驱动 → Miniport → HBA
  2. HW_INITIALIZATION_DATA:Miniport 入口的注册表
  3. Miniport 回调HwFindAdapterHwInitializeHwStartIoHwInterruptHwResetBus
  4. Scsiport:传统端口驱动,串行 SRB
  5. Storport:新式端口驱动,并行 SRB + MSI
  6. SRB 生命周期:构造 → 启动 → 中断 → 完成
  7. AHCI Miniport:使用 FIS 和 PRDT

下一节 9.11 将剖析命名管道与 Mailslot 文件系统驱动。


9.10.8 设计哲学问答

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

A减少冗余 + 统一的框架

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

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

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

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

A历史瓶颈与性能演进

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

  • NCQ 利用:现代硬盘支持 Native Command Queuing,最多同时处理 32 条命令,串行模型无法利用这一特性;
  • 多核扩展:并行模型允许在不同 CPU 核心上同时处理不同 SRB 的完成例程;
  • 队列深度ConfigInfo->TaggedQueuingMultipleRequestPerLu 标志在 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 在设备扩展中统一分配所有状态?

ASRB 粒度的上下文隔离

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

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

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

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

A协议标准化与 DMA 效率

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

  • 命令队列支持 :FIS 支持 Command FISDMA Setup FISPIO 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 在读取中断状态后只需调用:

c 复制代码
ScsiPortNotification(RequestComplete, DeviceExtension, Srb);
ScsiPortNotification(NextRequest, DeviceExtension, 0);  // 启动队列下一个

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

Q7:为什么 HwFindAdapterHwInitialize 要分开成两个回调?

A分阶段初始化与 PnP 回滚

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

  1. HwFindAdapter (PnP 探测阶段):在硬件资源尚未分配时调用。Miniport 读取 PCI 配置空间、验证厂商/设备 ID、检查设备是否支持。如果返回 SP_RETURN_NOT_FOUND,PnP 管理器继续尝试其他驱动,无需回滚任何硬件状态。
  2. HwInitialize (资源分配后):端口驱动已调用 IoConnectInterrupt 注册中断和 IoAssignResources 分配 I/O 范围后调用。此时 Miniport 可以安全地初始化寄存器、启用中断。

这种分离允许 PnP 管理器在资源分配失败时优雅回滚------如果 IoAssignResources 失败(例如 IRQ 冲突),PnP 管理器发出 IRP_MN_STOP_DEVICE 而无需撤销已初始化的硬件寄存器:

c 复制代码
// 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 后:

  1. 端口驱动在调用 HwStartIo 前调用 MmGetSystemAddressForMdlSafe 映射缓冲区;
  2. Srb->DataBuffer 指向映射好的系统空间虚拟地址;
  3. Miniport 直接通过 DataBuffer 访问数据,无需任何映射操作;
  4. 端口驱动在 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 = LevelSensitiveProcessorEnableMask,而 Scsiport 仅使用 KeConnectInterrupt 的单核模式。

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

A平台无关性与资源跟踪

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

  1. 总线地址翻译 :调用 HalTranslateBusAddress 将 PCI 总线地址转换为系统物理地址。在不同体系结构上(x86 IO 空间 vs. x64 内存映射 IO),翻译逻辑不同;
  2. 缓存属性设置 :通过 HalGetInterruptVector 获取的 CacheType 设置映射的缓存属性。设备内存必须设置为非缓存(MmNonCached)或写合并(MmWriteCombined),错误的缓存属性会导致 DMA 数据不一致;
  3. 资源跟踪:端口驱动内部维护映射表,记录每个 Miniport 映射了多少地址范围,在设备移除时自动释放。
c 复制代码
// 正确做法(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 适配器
相关推荐
xiangw@GZ1 小时前
WiFi系统BCC与LDPC纠错编码技术性能对比
单片机·嵌入式硬件
caimouse1 小时前
Reactos 第 9 章 设备驱动 — 9.11 命名管道与Mailslot
windows
x***r1511 小时前
Krita 5.2.13 安装教程 Windows版:自定义路径+开源绘画软件配置指南
windows
AoDeLuo2 小时前
EthercCAT软件主站方案对比
stm32·单片机·嵌入式硬件
平凡灵感码头2 小时前
半导体三大主流制程详解:Bipolar、CMOS 与 BCD
单片机·嵌入式硬件
就改了2 小时前
Windows Elasticsearch 完整上手教程
大数据·windows·elasticsearch
fastjson_2 小时前
备份与恢复驱动
windows
caimouse2 小时前
Reactos 第 9 章 设备驱动 — 9.12 MDL
windows
daly5202 小时前
Notepad++怎么下载?2026最新版Notepad++安装教程(Windows免费文本编辑器)
windows·notepad++·notepad