1、SCSI简介
SCSI是Small Computer System Interface(小型计算机系统接口)的缩写,使用50针接口,外观和普通硬盘接口有些相似。SCSI硬盘和普通IDE硬盘相比有很多优点:接口速度快,并且由于主要用于服务器,因此硬盘本身的性能也比较高,硬盘转速快,缓存容量大,CPU占用率低,扩展性远优于IDE硬盘,并且支持热插拔。
2、SCSI驱动设计
在EFI Driver Model中,UEFI驱动程序是重要的组成部分,它们与UEFI应用程序有所区别。对于UEFI驱动程序来说,EFI System Table、Memory、Handles、Images和Events等对象的管理至关重要。在初始化驱动程序时,有些驱动程序可能不会产生任何Handle,也不会向Handle Database增加任何Protocol,它们主要进行初始化操作,并在执行完毕后从系统内存中卸载。
具体到SCSI驱动设计,它在Linux系统中通常被划分为三个层次:top level、middle level和lower level。Top level为具体的scsi设备驱动,例如常用的磁盘设备驱动。这些驱动与具体的scsi设备相关,因此通常由设备开发者提供。对于标准类设备,驱动可以通用。Middle level实际上是scsi总线层驱动,它负责按照scsi协议进行设备枚举、数据传输和出错处理。而在lower level层,驱动可能并不直接对硬件进行操作,例如可以做虚拟的scsi host。
在SCSI驱动设计中,scsi_driver结构体和scsi磁盘驱动实例(如sd_template,定义在文件drivers/scsi/sd.c)是关键部分。PCI子系统和SCSI子系统的工作模式也是设计过程中需要考虑的重要因素。PCI子系统会注册PCI总线类型,扫描PCI总线以获取所有PCI设备。而SCSI子系统则注册SCSI总线类型,其较高层注册SCSI设备驱动,较低层(如SCSI HBA驱动)负责扫描SCSI总线以获取所有SCSI设备。
总的来说,EFI Driver Model下的SCSI驱动设计是一个复杂而精细的过程,需要考虑到各种设备、总线和协议的特性,以确保驱动的稳定性和高效性。对于开发者来说,深入理解EFI Driver Model以及SCSI的工作原理是设计高质量驱动的基础。
驱动类别 | 描述 |
---|---|
SCSI host controller | Consumes PCI I/O Protocol on the SCSI host controller handle and produces the Ext SCSI Pass Thru Protocol,如果需要一个驱动程序与EFI 1.10规范兼容,则必须生成SCSI通过协议。 |
SCSI bus driver | Consumes the Ext SCSI Pass Thru Protocol and produces a child handle for SCSI targets on the SCSI bus. Installs the Device Path Protocol and SCSI I/O Protocol onto each child handle. |
SCSI 设备驱动 | 使用SCSI I/O协议,并生成一个I/O抽象,它为启动符合EFI的操作系统所需的控制台设备和引导设备提供服务。 |
EFI 1.10规范定义了SCSIPass Thru Protocol.用于SCSI host controller的UEFI驱动程序被需要符合EFI 1.10规范的平台上正常工作,生成SCSI通过协议,并为SCSI host controller管理的物理驱动器和逻辑驱动器生成BLOCK I/O协议。这意味着需要一个用于EFI 1.10平台中的SCSI主机控制器的UEFI Driver来执行上表中描述的SCSI驱动程序堆栈的所有功能。UEFI 2.0及以上规范要求平台固件为大容量存储设备提供SCSI总线驱动程序和SCSI设备驱动程序。
2.1 SCSI Host Controller Driver
SCSI主机控制器驱动程序管理包含一个或多个SCSI通道的SCSI主机控制器,它为每个SCSI通道创建句柄,并安装扩展的SCSI Pass Thru Protocol和Device Path Protocol到每一个驱动产生的handle上。有关EFI_EXT_SCSI_PASS_THRU_PROTOCOL的详细信息,请参阅UEFI规范中的SCSI驱动程序模型和总线支持章节。
struct _EFI_EXT_SCSI_PASS_THRU_PROTOCOL {
///
/// A pointer to the EFI_EXT_SCSI_PASS_THRU_MODE data for this SCSI channel.
///
EFI_EXT_SCSI_PASS_THRU_MODE *Mode;
EFI_EXT_SCSI_PASS_THRU_PASSTHRU PassThru;
EFI_EXT_SCSI_PASS_THRU_GET_NEXT_TARGET_LUN GetNextTargetLun;
EFI_EXT_SCSI_PASS_THRU_BUILD_DEVICE_PATH BuildDevicePath;
EFI_EXT_SCSI_PASS_THRU_GET_TARGET_LUN GetTargetLun;
EFI_EXT_SCSI_PASS_THRU_RESET_CHANNEL ResetChannel;
EFI_EXT_SCSI_PASS_THRU_RESET_TARGET_LUN ResetTargetLun;
EFI_EXT_SCSI_PASS_THRU_GET_NEXT_TARGET GetNextTarget;
};
一个SCSI主机控制器驱动程序遵循UEFI驱动程序模型。根据其管理的适配器,SCSI主机控制器驱动程序可以被归类为设备驱动程序或混合驱动程序。它为每个SCSI通道创建子句柄(如果有多个1),它还可以在自己的句柄上安装协议。通常,SCSI主机控制器驱动器是特定于芯片的,因为需要初始化和管理当前绑定的SCSI主机控制器。
由于在一个平台中可能存在多个可能由一个SCSI主机控制器驱动程序管理的SCSI主机适配器,因此建议将SCSI主机控制器驱动程序设计为重新进入,并为每个SCSI主机控制器分配不同的私有上下文数据结构。
2.2 Single-Channel SCSI Adapters
如果SCSI适配器支持一个通道,则SCSI主机控制器驱动程序将执行以下操作:
- 将扩展的SCSI Pass Thru Protocol安装到控制器句柄上的SCSI host controller的controller handle上。
- 在模式结构中设置SCSI通道的逻辑属性。
- 在模式结构中设置SCSI通道的物理属性
下图显示了一个在单通道SCSI适配器上的实现示例。绿色的图层表示SCSI主机控制器驱动程序。
因为只有一个SCSI通道,所以SCSI驱动程序可以简单地实现扩展SCSI通过协议的一个实例。平台固件提供了SCSI总线驱动程序和SCSI磁盘驱动程序,它们通过执行以下操作来完成驱动程序堆栈:
- 扫描SCSI通道上的SCSI目标并创建子句柄。
- 向每个子句柄安装设备路径协议
- 向每个子句柄上安装SCSI I/O协议
- 向每个子句柄安装I/O抽象,例如Block I/O协议
2.2 Multi-Channel SCSI Adapters
如果SCSI适配器提供多个SCSI通道,则SCSI主机控制器驱动程序会更加复杂。下图显示了在双通道SCSI适配器上可能实现的SCSI驱动程序实现。
在这种情况下,SCSI适配器通过执行以下操作产生两个物理SCSI通道:
- 为每个物理SCSI通道创建一个子句柄。
- 向每个子句柄安装设备路径协议。
- 在每个子句柄上安装扩展的SCSI传递协议
- 在每个子句柄上的模式结构中设置SCSI通道的逻辑属性。
- 在每个子句柄上的模式结构中设置SCSI通道的物理属性。
平台固件提供了SCSI总线驱动程序和SCSI磁盘驱动程序,通过执行以下操作,可以完成上述每个扩展SCSI通过协议上的两个驱动程序堆栈:
- 扫描每个SCSI通道上的SCSI目标,并创建子句柄。
- 向每个子句柄安装设备路径协议。
- 向每个子句柄上安装SCSI I/O协议
- 向每个子句柄安装I/O抽象,如Block I/O协议。
2.3 SCSI Adapters with RAID
SCSI主机控制器驱动程序也可以支持具有RAID功能的SCSI适配器。下图显示了一个具有两个物理SCSI通道和一个逻辑通道的示例实现。这两个物理通道是在SCSI适配器上实现的,然后,RAID组件配置这两个通道,以生成一个逻辑SCSI通道。两个物理通道都安装了扩展SCSI通过通道,但除了用于诊断外,不使用这些通道。对于逻辑通道,SCSI主机控制器驱动程序根据RAID配置生成另一个扩展SCSI通过协议(关闭物理位)实例。发送到逻辑通道的扩展SCSI传递协议的请求由SCSI主机控制器驱动程序处理,并转换为物理SCSI通道上的请求,平台固件必须只枚举并从逻辑SCSI通道上存在的SCSI目标启动。
在实现RAID时,SCSI适配器硬件可能无法将物理SCSI通道)暴露给上层软件。如果物理SCSI通道不能暴露于上层软件,则SCSI主机控制器驱动程序只需要为RAID生成单个逻辑通道。
虽然基本理论与物理通道上的理论相同,但从制造和诊断的角度是不同的。如果物理SCSI通道暴露,任何SCSI命令,包括诊断命令,都可以发送到单个通道,这对生产线非常有帮助。此外,可以使用扩展SCSI传递协议支持的非阻塞模式同时将诊断命令发送到所有物理通道。诊断过程可能会从性能的增益中显著获益。总之,建议尽可能公开物理SCSI通道。
当然,有许多可能的设计来实现SCSI RAID功能。关键是,SCSI主机控制器驱动程序可以为各种SCSI适配器类型设计和实现,并且这些SCSI主机控制器驱动程序可以生成包含可作为UEFI引导设备使用的SCSI目标的扩展SCSI通过协议。
3、源码分析
ScsiDiskDriverBindingSupported 用于检测该SCSI磁盘驱动程序是否支持特定的控制器设备
EFI_STATUS
EFIAPI
ScsiDiskDriverBindingSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
);
ScsiDiskDriverBindingStart 这个函数是用来启动指定的驱动程序并将其绑定到指定的ControllerHandle上的。它是EFI_DRIVER_BINDING_PROTOCOL协议的一部分,是UEFI(统一可扩展固件接口)引导过程中重要的一环,用于初始化和启动设备驱动程序。
EFI_STATUS
EFIAPI
ScsiDiskDriverBindingStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
);
ScsiDiskDriverBindingStop 用于停止指定控制器上的驱动程序。这个函数属于EFI_DRIVER_BINDING_PROTOCOL协议,该协议定义了驱动程序如何绑定到控制器、启动和停止服务。
EFI_STATUS
EFIAPI
ScsiDiskDriverBindingStop (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE Controller,
IN UINTN NumberOfChildren,
IN EFI_HANDLE *ChildHandleBuffer OPTIONAL
);
ScsiIo使用
Status = gBS->OpenProtocol (
Controller,
&gEfiScsiIoProtocolGuid,
(VOID **)&ScsiIo,
This->DriverBindingHandle,
Controller,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ERROR (Status)) {
FreePool (ScsiDiskDevice);
return Status;
}
ScsiDiskDevice->Signature = SCSI_DISK_DEV_SIGNATURE;
ScsiDiskDevice->ScsiIo = ScsiIo;
ScsiDiskDevice->BlkIo.Revision = EFI_BLOCK_IO_PROTOCOL_REVISION3;
ScsiDiskDevice->BlkIo.Media = &ScsiDiskDevice->BlkIoMedia;
ScsiDiskDevice->BlkIo.Media->IoAlign = ScsiIo->IoAlign;
ScsiDiskDevice->BlkIo.Reset = ScsiDiskReset;
ScsiDiskDevice->BlkIo.ReadBlocks = ScsiDiskReadBlocks;
ScsiDiskDevice->BlkIo.WriteBlocks = ScsiDiskWriteBlocks;
ScsiDiskDevice->BlkIo.FlushBlocks = ScsiDiskFlushBlocks;
ScsiDiskDevice->BlkIo2.Media = &ScsiDiskDevice->BlkIoMedia;
ScsiDiskDevice->BlkIo2.Reset = ScsiDiskResetEx;
ScsiDiskDevice->BlkIo2.ReadBlocksEx = ScsiDiskReadBlocksEx;
ScsiDiskDevice->BlkIo2.WriteBlocksEx = ScsiDiskWriteBlocksEx;
ScsiDiskDevice->BlkIo2.FlushBlocksEx = ScsiDiskFlushBlocksEx;
ScsiDiskDevice->StorageSecurity.ReceiveData = ScsiDiskReceiveData;
ScsiDiskDevice->StorageSecurity.SendData = ScsiDiskSendData;
ScsiDiskDevice->EraseBlock.Revision = EFI_ERASE_BLOCK_PROTOCOL_REVISION;
ScsiDiskDevice->EraseBlock.EraseLengthGranularity = 1;
ScsiDiskDevice->EraseBlock.EraseBlocks = ScsiDiskEraseBlocks;
ScsiDiskDevice->UnmapInfo.MaxBlkDespCnt = 1;
ScsiDiskDevice->BlockLimitsVpdSupported = FALSE;
ScsiDiskDevice->Handle = Controller;
Status = gBS->InstallMultipleProtocolInterfaces (
&Controller,
&gEfiBlockIoProtocolGuid,
&ScsiDiskDevice->BlkIo,
&gEfiBlockIo2ProtocolGuid,
&ScsiDiskDevice->BlkIo2,
&gEfiDiskInfoProtocolGuid,
&ScsiDiskDevice->DiskInfo,
NULL
);