Rigo的胡言乱语录

Rigo's Stupid Blogs Archives

View on GitHub

01 Storport 简述

Storport负责什么?

Windows上,下层的驱动为磁盘产生一个PDO,然后disk.sys会产生磁盘FDO挂在上面。

block-beta
    columns 1

    block:disk_sys
        columns 2

        DiskFDO["磁盘FDO(逻辑层接口)"]
        disk.sys

    end

    block:bus_driver
        DiskPDO["PDO(“磁盘设备”本身)"]

        DiskFDO --> DiskPDO
        BusDrv["Whatever Bus Driver"]
    end


在普通PC主板上,电路连接一般是这样的:

block-beta
    columns 1

    block
        columns 4
        disk1["Disk 1"]
        disk2["Disk 2"]
        disk3["Disk 3"]
        disks["..."]
    end
    disks2hba<["&nbsp;"]>(down)

    hba["HBA(主机总线适配器)"]
    hba2bus<["PCI"]>(down)

    bus_ctrl["总线控制器"] 


如果你愿意绕过微软的条条框框,可以做一个这样架构的驱动来提供磁盘PDO:

block-beta
    columns 5

    block:DiskSys:5
        columns 5

        DiskFDO1["FDO"]
        DiskFDO2["FDO"]
        DiskFDO3["FDO"]
        DiskFDO4["..."]
        disk.sys

    end

    block:BusDriver:5
        columns 5
        DiskPDO1["PDO\n(Disk1)"]
        DiskPDO2["PDO\n(Disk2)"]
        DiskPDO3["PDO\n(Disk3)"]
        DiskPDO4["..."]
        space

        DiskFDO1 --> DiskPDO1
        DiskFDO2 --> DiskPDO2
        DiskFDO3 --> DiskPDO3
        DiskFDO4 --> DiskPDO4

        space:4
        BusDrv["你的HBA驱动 "]

        HbaFdo["HBA FDO"]:4

        DiskPDO1 --> HbaFdo
        DiskPDO2 --> HbaFdo
        DiskPDO3 --> HbaFdo
        DiskPDO4 --> HbaFdo
    end

    block:PciDriver:5
        columns 5
        space:5

        HbaPdo["HBA PDO\n(PCI驱动产生的)"]
        space:3
        PCI.sys
        HbaFdo --> HbaPdo
    end

在这种模式下,参考开机时枚举设备的过程,HAL先从ACPI信息里找到PCI总线,然后为各种PCI设备产生它们的PDO;然后遍历树初始化PCI设备的时候,加载这个PCI设备的驱动。

于是PCI总线上的HBA就被加载了你编写的这个HBA驱动,它的AddDevice会创建这个HBA的FDO挂在PCI的PDO上面,这样HBA就初始化完成了。随后PnP管理器会重新枚举这个设备(看ReactOS源码可知,devnode指向的是PDO,而PnP管理器会沿着PDO找到目前的DO栈顶端也就是HBA FDO,然后向它发送PNP/QUERY_DEVICE_RELATIONS的IRP枚举它上面的设备),于是你的HBA驱动查了HBA寄存器,找到了所有的硬盘,为它们产生了PDO。

那么Storport是什么?

Storport是为了减少让HBA厂家自己写垃圾代码导致Windows不稳定而产生的中间件。Storport以一个类似于动态库的形式存在(但其实它是磁盘IO逻辑的中心),用户编写的Storport Miniport需要调用Storport的API、向Storport注册一些必须实现的(和可以不实现的)回调函数,结合起来组成面向硬件的磁盘IO。

用上Storport之后:

取代了上图中“HBA驱动”部分的,是你的Miniport驱动。所以,PnP对HBA的PDO加载驱动的时候,你的Miniport驱动的DriverEntry将被执行:

(KD调试内容来自安装在VMWare Workstation 17上的Win7 SP1 7601,使用模拟的LSI SAS HBA。后文所有基于Windows的调试均出自于此。)

kd> bp lsi_sas!DriverEntry
kd> bp storport!RaDriverAddDevice
kd> g
Breakpoint 3 hit
lsi_sas!DriverEntry:
fffff880`00fa3008 48895c2408      mov     qword ptr [rsp+8],rbx
kd> k
Child-SP          RetAddr           Call Site
fffff880`009b06d8 fffff800`045e3c78 lsi_sas!DriverEntry [e:\win7\drivers\oem\src\storage\lsi_sas\ca_init.c @ 103]
fffff880`009b06e0 fffff800`045e3dce nt!IopInitializeBuiltinDriver+0x368
fffff880`009b07b0 fffff800`045e476a nt!PnpInitializeBootStartDriver+0xbe
fffff880`009b0830 fffff800`045e523f nt!IopInitializeBootDrivers+0x44a
fffff880`009b0900 fffff800`045e8443 nt!IoInitSystem+0x80f
fffff880`009b0a00 fffff800`0454be89 nt!Phase1InitializationDiscard+0x1293
fffff880`009b0bd0 fffff800`0439e1a0 nt!Phase1Initialization+0x9
fffff880`009b0c00 fffff800`040f6ba6 nt!PspSystemThreadStartup+0x194
fffff880`009b0c40 00000000`00000000 nt!KxStartSystemThread+0x16

但是,Storport miniport会调用storport!StorPortInitialize初始化Miniport驱动对象。DriverObject将被作为参数传入(一同还有HW_INITIALIZATION_INFO结构体里的回调、配置参数等);此函数会将DRIVER_OBJECT::MajorFunction的若干函数填写为Storport自己的实现。

所以,后面PnP管理器为这个还没有驱动认领的HBA PDO调用驱动AddDevice时,接手的其实是Storport的函数:

kd> g
Breakpoint 4 hit
storport!RaDriverAddDevice:
fffff880`01059dc0 48895c2408      mov     qword ptr [rsp+8],rbx
kd> k
Child-SP          RetAddr           Call Site
fffff880`009b0488 fffff800`041c3176 storport!RaDriverAddDevice
fffff880`009b0490 fffff800`0447a375 nt!PpvUtilCallAddDevice+0x36
fffff880`009b04d0 fffff800`0447dfa1 nt!PnpCallAddDevice+0xd5
fffff880`009b0550 fffff800`0447e1ef nt!PipCallDriverAddDevice+0x661
fffff880`009b0700 fffff800`045e298e nt!PiProcessAddBootDevices+0x4f
fffff880`009b0730 fffff800`044c07a1 nt!PipAddDevicesToBootDriverWorker+0x1e
fffff880`009b0760 fffff800`045d50f3 nt!PipApplyFunctionToServiceInstances+0x1e1
fffff880`009b07e0 fffff800`045e47ec nt!PipAddDevicesToBootDriver+0x33
fffff880`009b0830 fffff800`045e523f nt!IopInitializeBootDrivers+0x4cc
fffff880`009b0900 fffff800`045e8443 nt!IoInitSystem+0x80f
fffff880`009b0a00 fffff800`0454be89 nt!Phase1InitializationDiscard+0x1293
fffff880`009b0bd0 fffff800`0439e1a0 nt!Phase1Initialization+0x9
fffff880`009b0c00 fffff800`040f6ba6 nt!PspSystemThreadStartup+0x194
fffff880`009b0c40 00000000`00000000 nt!KxStartSystemThread+0x16
kd> !drvobj rcx
Driver object (fffffa8031ec87c0) is for:
\Driver\LSI_SAS
Driver Extension List: (id , addr)
(fffff8800104b090 fffffa8031001630)  
Device Object list:

kd> !devobj rdx
Device object (fffffa8031eb7060) is for:
NTPNP_PCI0045 \Driver\pci DriverObject fffffa8032638da0
Current Irp 00000000 RefCount 0 Type 00000022 Flags 00001040
Dacl fffff9a10008b861 DevExt fffffa8031eb71b0 DevObjExt fffffa8031eb75d8 DevNode fffffa80323be6e0 
ExtensionFlags (0x00000810)  DOE_START_PENDING
                            Unknown flags 0x00000800
AttachedDevice (Upper) fffffa8030e90190 \Driver\ACPI
Device queue is not busy.

可以看到对PCI驱动产生的PDO(RDX的Device Object,第二参数。此处调用约定为Fastcall)调用AddDevice,却调到了Storport的地盘上。Storport拿到的Driver Object(在RCX中,第一参数)其实是Miniport的,这也是Storport其实是一个函数库的侧面印证(但不妨碍是它处理了大部分的逻辑的事实)。

于是Storport“替”你创建了HBA的FDO,IoCreateDevice里填写的也还是Miniport的Driver Object,但是因为Major Function回调全都写成了Storport的,所以后续所有对FDO的IRP还是会先抵达Storport自己的IRP处理函数。

kd> x storport!RaDriver*
fffff880`0105d9d0 storport!RaDriverPowerIrp = <no type information>
fffff880`010446c0 storport!RaDriverScsiIrp = <no type information>
fffff880`01096010 storport!RaDriverDeviceControlIrp = <no type information>
fffff880`0104fdc0 storport!RaDriverDeleteDevice = <no type information>
fffff880`0109b1c0 storport!RaDriverCloseIrp = <no type information>
fffff880`0109b2a0 storport!RaDriverCreateIrp = <no type information>
fffff880`01059dc0 storport!RaDriverAddDevice = <no type information>
fffff880`0109bf40 storport!RaDriverSystemControlIrp = <no type information>
fffff880`0109cb00 storport!RaDriverPnpIrp = <no type information>
fffff880`01058020 storport!RaDriverUnload = <no type information>

眼熟不?这些就是Storport的Major Function。所以后续IRP_MJ_PNP抵达HBA FDO的时候,Storport会接住这个IRP(在storport!RaDriverPnpIrp),判断一下发现这个是HBA FDO(在Device Extension的第一个DWORD中有记录,因为Storport也处理逻辑磁盘PDO的IRP(以后会写);第一个DWORD是0就是HBA FDO,是1就是逻辑磁盘PDO),于是开始想办法找HBA Miniport查询HBA上接的所有硬盘。

PNP/START_DEVICE的处理:

PNP/QUERY_DEVICE_RELATIONS的处理:

等等如此。可见Miniport通过把所有的Major Function设为Storport的实现,把对设备对象的实质控制权“拱手让给”了Storport,而自己只在Storport需要时被调用(虽然加载驱动的时候还是写的他的名字)。

一些可能有用的资料

SCSI CDB命令定义:https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf

ATA命令定义:https://people.freebsd.org/~imp/asiabsdcon2015/works/d2161r5-ATAATAPI_Command_Set_-_3.pdf

* 本节结束