目录
SCSI子系统对象
1 scsi_host_template:SCSI主机适配器模板
2 Scsi_Host:SCSI主机适配器
3 scsi_target:SCSI目标节点
4 scsi_device:SCSI逻辑设备
5 scsi_cmnd:SCSI命令
SCSI子系统对象
下图SCSI子系统对象之间的关系。Scsi_Host、scsi_target和scsi_device分别描述的是Linux SCSI模型中的主机适配器、目标节点和逻辑单元,而scsi_host_template表示的是SCSI主机适配器模板。
在Linux系统中,可以安装多个相同型号的主机适配器,这些主机适配器被同一个低层驱动来管理。当PCI子系统通过ID匹配,或者手工方式,添加每一个SCSI主机适配器时,SCSI低层驱动为它分配一个Scsi_Host描述符。因此,如果系统中有多个SCSI主机适配器,系统中就存在同样多个Scsi_Host描述符。SCSI主机适配器描述符包含两部分,一部分可供SCSI中间层使用,另一部分则为SCSI低层驱动专用。两部分的空间一次性分配。
在分配好SCSI主机适配器描述符后,将它添加到系统中,然后启动探测过程。探测到的scsi_target通过siblings链入主机适配器以_targets为表头的链表。而探测到的scsi_device将host域指向该主机适配器,而且一方面通过siblings链入主机适配器以_devices为表头的链表,另一方面通过same_target_siblings链入SCSI目标节点以devices为表头的链表。SCSI目标节点描述符和SCSI设备描述符各自有为SCSI低层驱动专用的信息,都通过hostdata域指向它们。
SCSI各个核心结构的关系。一般来说一种类型的SCSI低层驱动可以驱动多个SCSI主机适配器。每个主机适配器可以挂接多个SCSI目标节点,每个目标节点中至多可以有多个逻辑设备。对于SCSI并行接口,目标节点数最多为7或15,这取决于SCSI总线的宽度,对于SCSI磁盘,逻辑设备数最多为8。
1 scsi_host_template:SCSI主机适配器模板
scsi_host_template,从字面上理解是“SCSI主机适配器模板”,它给出了相同型号主机适配器的公用内容,例如,队列深度、SCSI命令处理回调函数、错误恢复回调函数等。主机适配器的分配要依照“主机适配器模板”。也就是说,SCSI低层驱动先定义scsi_host_template,然后在发现每一个可以管理的主机适配器后,“刻印”出一个Scsi_Host。(scsi_host_template给中间层提供函数调用接口)
最新的驱动由PCI子系统负责检测它所能支持的主机适配器。每个SCSI设备驱动需要定义一个scsi_host_template描述符。在驱动加载过程中,以它为模板,为每个支持的主机适配器创建一个Scsi_Host结构。
在定义模板时,最核心的是实例化其中的回调函数,正是通过这些回调函数规范了由它“刻印”出来的主机适配器的行为。
1.int (* queuecommand)(struct scsi_cmnd *, void (*done)(struct scsi_cmnd*));
queuecommand函数用于将SCSI命令块排入低层设备驱动的队列。第一个参数为指向SCSI命令描述符的指针;第二个参数为指向完成回调函数的指针。在驱动程序完成处理命令之后,done回调函数被调用。如果函数返回0,则主机适配器已经接受了该命令。done函数必须在驱动处理完命令之后被调用,也可以在queuecommand返回之前对这条命令调用done,但这时必须返回0。主机适配器也可以拒绝这条命令,这种情况下,不应该改动这条命令,同时也不能调用done。
2.int (* transfer_response)(struct scsi_cmnd *, void (*done)(struct scsi_cmnd*));
一般情况下,主机适配器作为SCSI启动器使用,但是如果有目标器驱动支持,主机适配器也可以作为SCSI目标器使用。基于STGT的目标器驱动必须实现这个回调函数。STGT核心在处理完SCSI命令时,调用它让LLD(向启动器端)传送响应。第一个参数为指向SCSI命令描述符的指针,第二个参数为指向完成回调函数的指针。
4.int (* eh_abort_handler)(struct scsi_cmnd *);
int (* eh_device_reset_handler)(struct scsi_cmnd *);•
int (* eh_target_reset_handler)(struct scsi_cmnd *);•
int (* eh_bus_reset_handler)(struct scsi_cmnd *);•
int (* eh_host_reset_handler)(struct scsi_cmnd *);
eh_×××系列函数用于错误恢复处理,它们都有唯一的参数,为指向需要执行错误恢复的SCSI命令描述符的指针,如果错误恢复动作成功返回SUCCESS,否则返回FAILED。其中eh_abort_handler放弃给定的命令,eh_device_reset_handler使SCSI设备复位,eh_target_reset_handler使目标节点复位,eh_bus_reset_handler使SCSI总线复位,eh_host_reset_handler使主机适配器复位。关于它们的更详细介绍,参看SCSI错误恢复一节。
5.int (* slave_alloc)(struct scsi_device *);
int (* slave_configure)(struct scsi_device *);
void (* slave_destroy)(struct scsi_device *);
大多数SCSI设备都有专门的私有数据,slave_×××系列函数用于对这些数据进行处理。它们都有唯一的参数,即指向SCSI设备描述符的指针。slave_alloc在扫描到一个新的SCSI设备后调用,用户可以在这个函数中为设备分配结构或者进行初始化。slave_configure在接收到SCSI设备的INQUIRY响应之后调用。用户可以在这个函数中设置队列深度、修改设备标志位等工作。slave_destroy在销毁这个设备之前被调用,可以在这个函数中释放前面两个函数分配的内存等。
6.int (* target_alloc)(struct scsi_target *);
void (* target_destroy)(struct scsi_target *);
target_×××系列函数和slave_×××类似,只不过它们是用于对目标节点的专有数据进行配置的。它们都有唯一的参数,即指向目标节点描述符的指针。target_alloc在发现一个新的目标器后调用,通常在这个函数中分配特定的数据结构,并进行初始化。target_destroy在目标节点被销毁之前被调用,可以在这个函数中释放前一个函数分配的内存等。
7.int (* scan_finished)(struct Scsi_Host *, unsigned long);
void (* scan_start)(struct Scsi_Host *);
如果主机适配器有能力自己发现挂接到它的目标器,而不需要扫描整条总线,它可以填入scan_×××系列函数,并调用scsi_scan_host。(自己定义扫描逻辑)scan_start函数在SCSI中间层准备好,开始扫描之前被调用,而scan_finished被定期调用,直到扫描已经结束,或超时。
2 Scsi_Host:SCSI主机适配器
在很多实际的系统中,SCSI主机适配器为一块基于PCI总线的扩展卡或者为一个SCSI控制器芯片。每个SCSI主机适配器可以存在多个通道,一个通道实际扩展了一条SCSI总线。每个通道可以连接多个SCSI目标节点,具体连接的数量与SCSI总线带载能力有关,或者受具体SCSI协议的限制。
struct Scsi_Host {
struct scsi_host_template *hostt; //指向用于创建此主机适配器的模板
}
系统为扫描发现的每个主机适配器创建一个Scsi_Host描述符。hostt域指向创建它所依据的模板(也代表驱动)的描述符的指针。
struct Scsi_Host {
struct list_head __devices; //这个scsi适配器的scsi设备链表头
struct list_head __targets; //这个scsi适配器的scsi目标节点链表头
}
这个主机适配器后面的目标节点被链入到以__targets为首的链表,逻辑设备被链入到以__devices为首的链表。
struct Scsi_Host {
unsigned int max_id; //目标节点最大编号
unsigned int max_lun; //逻辑设备lun最大编号
unsigned int max_channel; //最大通道编号
}
max_channel、max_id和max_lun分别表示这个主机适配器的最大通道编号、连接到这个主机适配器的目标节点最大编号和连接到这个主机适配器的逻辑单元最大编号。
struct Scsi_Host {
unsigned short max_cmd_len; //可以接受的SCSI命令的最大长度
}
max_cmd_len为主机适配器可以接受的SCSI命令的最大长度。对大多数主机适配器来说,这个值是12,但是有些可以是16,或者为260,如果驱动支持可变长度的命令描述块。如果驱动程序没有专门设置,则这个域的值设定为12。
struct Scsi_Host {
struct task_struct * ehandler; //错误恢复线程
}
每个主机适配器有一个内核线程用于错误恢复处理,ehandler域为指向它的指针。这个错误恢复线程在分配本结构的同时创建。一旦SCSI命令执行出现错误或超时,这个内核线程会被启动,与此同时,会阻塞这个主机适配器,不会有新的SCSI命令被派发,这样当所有SCSI命令都已结束后,就可以开始错误恢复的动作。



