<返回更多

Linux Scsi子系统框架介绍

2020-09-21    
加入收藏

scsi是一套古老的协议,至今它还在一些硬件中存在和使用,例如基于sata协议的ssd硬盘,ufs器件等。因为scsi命令已经标准化,因此scsi子系统也成为了linux kernel众多子系统中的一份子。

Linux Scsi子系统框架介绍

 

这篇文章以抽象硬件模型,引申出linux scsi子系统的设计框架

一、硬件建模

以下描述:

linux内部的任何大的驱动子系统(例如mmc,scsi,pcie,usb等等)都是以硬件对象为基础设计的,包括

因此了解linux scsi子系统前,需要先了解scsi硬件拓扑模型

硬件模型:

Linux Scsi子系统框架介绍

 

上面这张图是一个抽象的scsi子系统的硬件拓扑图。图上:

下面分别详细介绍:

1. Host(0-k)

注意:需要说明的是,现实硬件里看不到任何纯scsi控制器;例如ufs的scsi命令是ufs控制器通过upiu传送和接收的,upiu是在mipi总线上传送的物理信息,而scsi则是cmd upiu中的字段。再例如usb U盘,也是类似情况。

因此这里的host(0),host(1)...host(k)是一个控制器抽象描述,真实的控制器可以是ufs、usb上接着的硬盘控制器(这个应该画在soc外面)或者pcie上挂着的硬盘控制器(这个也应该画在soc外面)。

2. device(0-k)

图示中,例子device(0)是连接到host(0)控制器上的外设;device(k)是连接到host(k)控制器上的外设。外设可以是硬盘,光驱,ufs等。

注意:

Host和device之间的连接方式用了一个双箭头表示,它是一个抽象描述,代表scsi命令通道。

Linux Scsi子系统框架介绍

 

Scsi只是一个协议,因此各种五花八门的控制器都可以使用scsi进行交互。因此这个“通道”是借助各种控制器的驱动来完成的。有点像协议分层,scsi类似于协议层,而物理层,链路层则交给了各种控制器去完成。

在软件上,linux scsi子系统发送和接收的任何scsi命令都是由底层物理设备对应的驱动程序完成的,例如可以通过usb某个子设备驱动或者通过ufs驱动去实现scsi命令传输

3. channel+id

 

Linux Scsi子系统框架介绍

 

Linux Scsi子系统框架介绍

 

其中channel(0)_id(0)到channel(m)_id(n)就是target(k:0:0)到target(k:m:n)。

4. lun

二、Linux scsi子系统软件模型

接下来会基于linux设备驱动模型描述scsi子系统的框图。看到这里的小伙伴接下来需要有linux设备驱动模型的基础知识背景了。

由于linux scsi子系统代码庞大,直接说代码,会把人绕进去,这里会通过linux设备驱动模型中的bus,class,device框图来描述linux scsi子系统框架,后面会结合框架,指出子系统中的关键代码和位置。

1.图示说明

 

Linux Scsi子系统框架介绍

 

后面的图中会使用到上面的各种颜色和图形,这里描述了这些信息的含义。这些信息都是linux软件层面的含义。

2.主要bus和class

图示中有三个主要的bus和class,分别是左边的”scsi”,右边的”scsi_host”,和下边的”scsi_device”

它们三个构成了scsi的主体范围。也就是下面三个很粗的双向箭头包裹的区域。

Linux Scsi子系统框架介绍

 

简单介绍:

”scsi” bus:所有host,target,lun都有对应的structdevice放在这上面;通用的scsi的磁盘驱动”sd”,光盘驱动”sr”,磁带驱动”osst”等驱动也在这个bus上面,这些驱动通过struct device被激活。

“scsi_host” class: host有对应的device寄存在这上面,通过host的structdevice的attr(group,type)获取到控制器的属性。例如可以通过这上面的scan触发系统做对整个host做scan动作。

“scsi_device” class:所有lun的对应的structdevice寄存在这上面。操作它们的驱动是sg.c。

 

下面详细说明

3. Host,target,lun设备建模

(1) host(0-k)

 

Linux Scsi子系统框架介绍

 

 

Linux Scsi子系统框架介绍

 

 

Linux Scsi子系统框架介绍

 

 

Linux Scsi子系统框架介绍

 


Linux Scsi子系统框架介绍

 


Linux Scsi子系统框架介绍

 

可以看到我本地电脑有

---->触发ahci驱动的probe(ahci_init_on)

---->ahci扫描port个数(这里有6个)之后为每个port在scsi内部申请对应的host的structdevice

ahci_init_one-->ahci_host_activate-->ata_host_register-->ata_scsi_add_hosts

-->scsi_host_allocscsi_add_host_with_dma

这里用到了两个scsi子系统重要的对外接入函数。

(2) target

 

Linux Scsi子系统框架介绍

 


Linux Scsi子系统框架介绍

 

 

Linux Scsi子系统框架介绍

 

(3) lun

 

Linux Scsi子系统框架介绍

 

Linux Scsi子系统框架介绍

 

Linux Scsi子系统框架介绍

 


Linux Scsi子系统框架介绍

 

 

Linux Scsi子系统框架介绍

 

(4) 公版驱动

在”scsi”bus上挂着很多驱动:

Linux Scsi子系统框架介绍

 

Linux Scsi子系统框架介绍

 


Linux Scsi子系统框架介绍

 


Linux Scsi子系统框架介绍

 


Linux Scsi子系统框架介绍

 

只有符合指定类型的设备,才会触发对应的驱动程序。

三、主体代码描述

linux驱动子系统,一般包含下面几个内容:

1.子系统初始化

代码位置:kernel/drivers/scsi/scsi.c

Linux Scsi子系统框架介绍

 

(1) Scsi_init_queue:

这个函数主要是创建scsi cmd和sense cache用到的slab内存,这样后续scsi cmd和sense都可以在slab中申请内存,加快内存申请速度。

(2)Scsi_init_procfs:

这个是创建一个/proc/scsi/scsi的文件节点

这个节点会显示当前系统注册了哪些scsi设备,包括这些设备的channel编号,id编号 lun编号等信息。

这些信息都是实时变化的;如果有写入动作,也会触发子系统的scan动作。

Linux Scsi子系统框架介绍

 

(3) Scsi_init_devinfo

这个函数创建了/proc/scsi/device_info节点。

这个节点有点像kernel 里面常用的quirk等fix机制,内容如下

Linux Scsi子系统框架介绍

 


Linux Scsi子系统框架介绍

 

结构体的前面三类分别是vendor,model和revision,其实就是scsi inquiry命令返回的数据,最后一个是个整形flag值,这个flag值影响着设备的初始话过程和操作过程。例如BLIST_NOLUN会让scsi扫描外设时,只扫描lun0。

(4) Scsi_init_sysctl

这个函数创建了一个/proc/sys/dev/scsi/logging_level节点,这个节点控制着scsi子系统debug打印的log等级,值越小,打印越少。

(5) Scsi_init_hosts和scsi_sysfs_register

这两个函数创建了scsi子系统最关键的bus和class(“scsi”, “scsi_host”和“scsi_device”):

2.子设备驱动加载

这类驱动加载一般比较简单,而且单独以module形式,耦合性很小。它们一般在module初始化时注册到”scsi” bus总线上,然后一直等待有对应的子设备sdev_devgen挂到”scsi” bus上来。例如:

Linux Scsi子系统框架介绍

 

3.外设扫描

Scsi扫描过程定义: 是识别每个host,每个targe和每个lun,给其创建对应的device结构,并将device挂载到相应的bus或class上。

设备扫描的方式很多:

(1) Host扫描

由于host控制器各个芯片平台不一样,它的扫描过程是host device的父设备所在驱动完成的,它的父设备驱动可以是platform总线,也可以是pcie设备对应的pci_driver,也可以是ufs子系统(ufshcd.c)等等。

例如我电脑上,host设备是名为”ahci”的pci_driver扫描创建的控制器,一共有6个控制器,其中只有host4这个控制器上接了一块硬盘。

Linux Scsi子系统框架介绍

 

无论哪种当上一级驱动找到host后,会通过下面的

scsi_host_alloc:创建shost_gendev和shost_dev。

scsi_add_host: 把shost_gendev和shost_dev挂靠到各自的bus或class上。

(2) Target和lun扫描

Linux Scsi子系统框架介绍

 

从前面的硬件建模上来看,它的扫描过程是

Linux Scsi子系统框架介绍

 

以scsi_scan_host为例,这个函数是以host为单位进行全扫描

Linux Scsi子系统框架介绍

 

(3) 各种scan入口

4.通路建立:借助block层

Scsi注册block层有两个方式,一种是single q,另一种是multi q方式,这里介绍multi q的方式。

注册multi q,需要做两件事情

通过上述操作后,所有发送到request_queue中的request都会汇集到tag_set中做处理。

前面讲了host(k)和device(k)中间的双箭头是scsi命令的传输通道,lun是接收和处理scsi命令的实体。因此和block层关联的

 

Linux Scsi子系统框架介绍

 

Linux Scsi子系统框架介绍

 

代码截图:

第1步

Linux Scsi子系统框架介绍

 


Linux Scsi子系统框架介绍

 


Linux Scsi子系统框架介绍

 

第2步

Linux Scsi子系统框架介绍

 

至此外界任何发送给lun的请求都会进入到lun相关的request_queue,例如通过ioctl对sda或者sg设备的命令request都会进入到其对应lun的request_queue。最终都会走到tag_set的queue_rq钩子函数,也就是走到了scsi_queue_rq->scsi_dispatch_cmd->host->hostt->queuecommand函数,其中queuecommand是底层驱动注册上来的钩子函数,scsi子系统把request请求发送到这一步之后,剩下的工作就交给底层类似于ufs,sata驱动去处理了。例如,ufs会根据请求的类型把上层传下来的信息封装成upiu发给硬件控制器,从而完成一次传输。

5.休眠唤醒

休眠唤醒是驱动的一部分,包括PM(suspendresume),runtime PM,也有shutdown,remove等。以休眠为例:在”scsi” bus上那些公版driver实现了子设备的休眠唤醒操作。这个级别的驱动操作的都是lun设备,因此这个级别的驱动是基于scsi命令对设备进行操作。那些更底层的操作例如断开link,给外设断电等是更底层的父设备们去完成的。例如

Linux Scsi子系统框架介绍

 

 

Linux Scsi子系统框架介绍

 

Linux设备驱动模型会保证子设备suspend之后,才会是父设备的suspend,向底层一级一级父辈驱动的suspend调用。

Scsi里面的父设备target是有channel和id虚拟出来的,没有任何休眠唤醒动作。

爷爷辈设备host从属于上一级驱动,前面说的sata是其中一种。sata也有更上一级的pcie相关的父设备。在手机里host的上一级也可能是ufs驱动。拿ufs为例,在子设备驱动休眠后,爷爷辈驱动的功耗相关的函数会被linux设备驱动模型触发,也就是ufs的suspend函数会让device和host的link状态进入hibernate8低功耗状态。

至于resume,runtime PM各位自己可以去阅读研究。

6.底层驱动注册

前面说了,没有纯粹的scsi控制器,现实的控制器是sata,ufs这些把scsi封装在自定义的通讯结构中的控制器。因此linux scsi提供一套用于scsi和各种实际控制器驱动交互的钩子函数模板scsi_host_template。

例如ufs驱动中注册了这套模板

Linux Scsi子系统框架介绍

 

这些钩子函数由scsi主动调用,scsi并不关注这些钩子的实现,例如ufshcd_queuecommand,用于接收scsi发下来的请求,并把scsi命令封装到upiu中并发送给硬件host控制。scsi不关心ufs驱动如何封装scsi命令,如何触发硬件发送命令。

7.关于channel和id的使用

前面说了channel和id没有在scsi协议文档里面找到对应描述,scsi里面也没有对target(channel+id)特别的操作,而是直接给host驱动去处理。这样驱动可以自由定义channel和id。Host驱动在申请scsi_host时会定义该驱动支持多少个channel和每个channel支持多少个id,例如:

 

 

Linux Scsi子系统框架介绍

 

因此对ufs驱动而言,ufs只需要关注lun,忽略channel和id的存在,通篇ufshcd.c中看不到channel和id的处理。

 

 

Linux Scsi子系统框架介绍

 

在这个驱动里面有channel和id的相关操作

Linux Scsi子系统框架介绍

 

在driver/scsi目录下搜索max_channel和channel,可以看到各种各样的用法,这些在scsi这层没有规定,完全取决于host驱动根据自身的情况来选择合适的用法。

 

8.标准外设驱动

scsi定义了很多组命令,除了一些common的scsi命令外,也对具体类型的外设定义了一些命令标准。

针对不同的外设,scsi子系统里面也集成了一些公版驱动,如下

Linux Scsi子系统框架介绍

 

(1) Sd.c

由于sd.c比较常用,这里把sd.c单独拿出来描述下,其余的外设驱动都大同小异,不再复述。Sd.c它操作的是硬盘,ssd等以sect为单位进行读取写入的存储设备。

 

Linux Scsi子系统框架介绍

 

 

Linux Scsi子系统框架介绍

 

(2) Sg.c

Sg.c比较特殊,不是对某个类型的设备驱动。它不管三七二一,对所有挂到“scsi_device”class上的device,都创建一个char类型的设备节点到user空间。由于所有被扫描出来的lun会有一个sdev_dev在”scsi_device”上,因此sg实际上是给每个lun创建了char设备节点。

它也会创建一个同名的sg device挂在自定义的”scsi_generic” class上(没有什么特别作用)。

sg作用:

Linux Scsi子系统框架介绍

 

四、数据链表结构

硬件拓扑结构是一个树形。在linuxscsi子系统里面,host,target,lun对应的scsi_host, scsi_target, scsi_device也是一个树形链表结构,如下图:

Linux scsi子系统中的所有list链表操作都是按照这张图中的数据结构处理的。

 

Linux Scsi子系统框架介绍

 

scsi.c代码很多是围绕这个结构体进行操作的,例如:

shost_for_each_device

starget_for_each_device

scsi_device_lookup_by_target

scsi_device_lookup

__scsi_iterate_devices

具体实现,大家可以自己研究,对照这张图,代码看起来不会很难。

 

五、总结:

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>