第 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:
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) 中:
- 分配 FDO 和
SCSI_PORT_DEVICE_EXTENSION - 调用
HwFindAdapter让 Miniport 探测 HBA - 注册 PCI 资源(
IoAssignResources) - 注册中断(
IoConnectInterrupt) - 调用
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_FOUND、SP_RETURN_NOT_FOUND、SP_RETURN_ERROR
Miniport 应:
- 检查
ConfigInfo->AdapterInterfaceType - 读取 PCI 配置空间验证厂商/设备 ID
- 设置最大 SRB 数
ConfigInfo->NumberOfBuses = 1 - 设置
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_EXTENSION(scsiport.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 后引入的替代品,主要优势:
- 并行 SCSI 命令(Storport 允许多 SRB 排队,Scsiport 强制串行)
- MSI/MSI-X 中断支持
- 更少 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.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 的
storahciMiniport 虽然能够工作,但在多队列深度、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 控制器有两个通道(Channels2),每个通道的 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 在读取中断状态后只需调用:
c
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 而无需撤销已初始化的硬件寄存器:
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 后:
- 端口驱动在调用
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 映射了多少地址范围,在设备移除时自动释放。
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 适配器 |