1. 前言
本专题我们开始学习SCSI子系统的相关内容。本专题主要参考了《存储技术原理分析》、ULA、ULK的相关内容。本专题主要以硬件UFS为例,记录SCSI子系统的框架流程。
SCSI低层驱动是面向主机适配器的,低层驱动被加载时,首先要添加主机适配器。添加主机适配器包括两部分内容,为主机适配器分配数据结构,将主机适配器添加到系统。SCSI中间层为此提供了两个公共函数:scsi_host_alloc和scsi_add_host。
以UFS设备为例,这部分的内容是在ufs_qcom_probe->ufshcd_pltfrm_init中完成
kernel版本:5.10
平台:arm64
注:
为方便阅读,正文标题采用分级结构标识,每一级用一个"-“表示,如:两级为”|- -", 三级为”|- - -“
2. scsi_host_alloc
struct Scsi_Host *scsi_host_alloc(struct scsi_host_template *sht, int privsize)
|--struct Scsi_Host *shost;
|--shost = kzalloc(sizeof(struct Scsi_Host) + privsize, gfp_mask);
| //Scsi_Host初始化
|--shost->host_lock = &shost->default_lock;
| //初始化shost->host_lock锁
| spin_lock_init(shost->host_lock);
| shost->shost_state = SHOST_CREATED; //初始化shost->shost_state为SHOST_CREATED状态。
| INIT_LIST_HEAD(&shost->__devices);//初始化&shost->__devices链表用于链接host下所有的scsi_device
| INIT_LIST_HEAD(&shost->__targets); //初始化&shost->__targets链表用于链接host下所有的scsi_target
| INIT_LIST_HEAD(&shost->eh_cmd_q);//初始化&shost->eh_cmd_q链表用于链接host下所有的error command
| INIT_LIST_HEAD(&shost->starved_list); //初始化&shost->starved_list链表(TODO)
| init_waitqueue_head(&shost->host_wait);
| mutex_init(&shost->scan_mutex)
| index = ida_simple_get(&host_index_ida, 0, 0, GFP_KERNEL);//shost->host_no,通过idx子系统为host分配一个ID
| shost->host_no = index;//初始化host no
| shost->dma_channel = 0xff;
| /* These three are default values which can be overridden */
| shost->max_channel = 0;//初始化max_channel,max_id,max_lun,其中后两者要设置为比id号大1
| shost->max_id = 8;
| shost->max_lun = 8;
| shost->transportt = &blank_transport_template;//初始化shost->transportt为默认,是一个空的函数模板
| shost->max_cmd_len = 12;//初始化最大命令长度为12(命令最大长度or命令个数 TODO)
| shost->hostt = sht;
| shost->this_id = sht->this_id;//通过sht模板来初始化新创建的Scsi_host
| shost->can_queue = sht->can_queue;
| shost->sg_tablesize = sht->sg_tablesize;//初始化shost->sg_tablesize,值为UCD中PRDT数组个数
| shost->sg_prot_tablesize = sht->sg_prot_tablesize;
| shost->cmd_per_lun = sht->cmd_per_lun;//初始化shost->cmd_per_lun(每个lun命令队列支持的最大命令个数)
| shost->unchecked_isa_dma = sht->unchecked_isa_dma;
| shost->no_write_same = sht->no_write_same;
| shost->host_tagset = sht->host_tagset;
| shost->eh_deadline = ...;
| shost->active_mode = ...;
| shost->max_host_blocked = ...;
| shost->max_sectors = ...;//设置host能处理的最大扇区数量max_sectors
| shost->max_segment_size = ...;
| shost->dma_boundary = ...;//设置dma的界线shost->dma_boundary,默认是4G,也即是大于4G会被划分为两部分
| shost->virt_boundary_mask = sht->virt_boundary_mask;
| //初始化Scsi_Host内嵌通用设备描述符和设备描述符
|--device_initialize(&shost->shost_gendev);
| dev_set_name(&shost->shost_gendev, "host%d", shost->host_no);
| shost->shost_gendev.bus = &scsi_bus_type;//在scsi_bus下面创建节点, 如:/sys/bus/scsi/devices/host0
| shost->shost_gendev.type = &scsi_host_type;
|--device_initialize(&shost->shost_dev);
| shost->shost_dev.parent = &shost->shost_gendev;//shost_gendev为shost_dev的父设备
| shost->shost_dev.class = &shost_class;///sys/class/scsi_host/host0
| dev_set_name(&shost->shost_dev, "host%d", shost->host_no);
| shost->shost_dev.groups = scsi_sysfs_shost_attr_groups;
|
|--shost->ehandler = kthread_run(scsi_error_handler, shost,"scsi_eh_%d", shost->host_no);
|--shost->tmf_work_q = alloc_workqueue("scsi_tmf_%d", WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_SYSFS,1, shost->host_no);
|--scsi_proc_hostdir_add(shost->hostt);
scsi_host_alloc为Scsi_host和其私有数据分配了空间并对其进行初始化。
以高通ufs为例,则执行路径为ufs_qcom_probe->scsi_host_alloc。sht指向scsi_host_template,privsize为私有数据的大小,对于UFS就是ufs_hba结构体大小。scsi_host_alloc为Scsi_host和其私有数据分配了空间。注册一个scsi host实例,并对scsi_host成员变量进行初始化,其中shost->ehandler为错误处理回调线程,shost->tmf_work_q为工作队列,并在/proc/scsi下创建scsi以提供相关信息。/proc/scsi/scsi属性显示了各个LUN的详细信息。
qcom platform_device =》shost->shost_gendev =》 shost->shost_dev依次形成父子关系
-
kzalloc(sizeof(struct Scsi_Host) + privsize, gfp_mask)
分配scsi_host空间和host私有空间
-
Scsi_Host初始化
-
通过device_initialize对shost->shost_gendev和shost->shost_dev初始化
每个scsi主机适配器都由两个内嵌设备描述符:
(1)内嵌通用设备描述符shost->shost_gendev
(2)内嵌设备描述符shost->shost_dev
它的parent为内嵌通用设备描述符shost->shost_gendev
以UFS为例,在sysfs下将会创建如下两个文件:
/sys/devices/platform/soc/1d84000.ufshc/host0
/sys/devices/platform/soc/1d84000.ufshc/host0/scsi_host/host0
在执行device_add后将形成如下图的拓扑关系:
如上图引用部分的host#分别代表了shost->shost_gendev和shost->shost_dev -
kthread_run(scsi_error_handler, shost,“scsi_eh_%d”, shost->host_no)
通过kthread_run启动错误处理线程shost->ehandler,错误处理函数为scsi_error_handler -
alloc_workqueue:初始化工作队列shost->tmf_work_q,名称为scsi_tmf_host编号;
-
scsi_proc_hostdir_add(shost->hostt)
在/proc/下创建scsi目录,可以通过/proc/scsi/scsi查看lun的信息
3. scsi_add_host
static inline int __must_check scsi_add_host(struct Scsi_Host *host,
|--scsi_add_host_with_dma(host, dev, dev);
|--struct scsi_host_template *sht = shost->hostt;
|--if (!shost->can_queue)
| shost_printk(KERN_ERR, shost, "can_queue = 0 no longer supported\n");
| goto fail;
|--scsi_init_sense_cache(shost);
|--scsi_mq_setup_tags(shost);
| |--struct blk_mq_tag_set *tag_set = &shost->tag_set;
| |--tag_set->ops = &scsi_mq_ops;
| | tag_set->nr_hw_queues = shost->nr_hw_queues ? : 1;
| | tag_set->queue_depth = shost->can_queue;
| | tag_set->cmd_size = cmd_size;
| | tag_set->numa_node = NUMA_NO_NODE;
| | tag_set->flags = BLK_MQ_F_SHOULD_MERGE;
| |--blk_mq_alloc_tag_set(tag_set)
|--if (!shost->shost_gendev.parent)
| shost->shost_gendev.parent = dev ? dev : &platform_bus;
|--if (!dma_dev)
| dma_dev = shost->shost_gendev.parent;
| shost->dma_dev = dma_dev;
|--pm_runtime_get_noresume(&shost->shost_gendev);
| pm_runtime_set_active(&shost->shost_gendev);
| pm_runtime_enable(&shost->shost_gendev);
|--device_enable_async_suspend(&shost->shost_gendev);
|--device_add(&shost->shost_gendev);
|--scsi_host_set_state(shost, SHOST_RUNNING);
|--get_device(shost->shost_gendev.parent);
|--device_enable_async_suspend(&shost->shost_dev);
|--device_add(&shost->shost_dev);
|--get_device(&shost->shost_gendev);
|--if (shost->transportt->host_size)
| shost->shost_data = kzalloc(shost->transportt->host_size,GFP_KERNEL);
|--if (shost->transportt->create_work_queue)
| snprintf(shost->work_q_name, sizeof(shost->work_q_name),
| "scsi_wq_%d", shost->host_no);
| shost->work_q = alloc_workqueue("%s",
| WQ_SYSFS | __WQ_LEGACY | WQ_MEM_RECLAIM | WQ_UNBOUND,
| 1, shost->work_q_name);
|
|--scsi_sysfs_add_host(shost);
|--scsi_proc_host_add(shost);
|--scsi_autopm_put_host(shost);
ufs_qcom_probe->ufshcd_init->scsi_add_host时会调用scsi_add_host
scsi_add_host会直接调用scsi_add_host_with_dma(host, dev, dev),它将分配的Scsi_Host添加到scsi总线设备模型中
-
判断shost->can_queue,即主机适配器可以同时接受的命令数,此处不能为0
-
scsi_init_sense_cache(shost)
主机适配器用于分配sense缓冲区的存储池结构 -
scsi_mq_setup_tags
对&shost->tag_set进行初始化,其中tag_set->ops = &scsi_mq_ops中,则设定了tag_set的操作集,它最终在blk_mq_init_allocated_queue会用于初始化request_queue->mq_ops,其中的request_queue->mq_ops->queue_rq将在请求分发的过程中被调用。
blk_mq_alloc_tag_set 为每个硬队列分配blk_mq_tags指针数组,blk_mq_tag_set 一般内嵌在块设备相关结构体中,此处为Scsi_Host ;分配数组mq_map用于维护软硬队列的映射,并建立软硬队列的映射关系,同时对每个硬件队列,根据队列深度为每个硬件队列分配bitmap,并根据队列深度创建相应数目的request。此tag set将与一个或多个request_queue进行关联, -
shost->shost_gendev.parent = dev ? dev : &platform_bus
确定主机适配器在sysfs中的位置,对于ufs来讲,此处的dev为mas-ufs
注:至此内嵌通用设备和内嵌类设备初始化完毕
注:scsi_device和scsi_target后面才会添加,如上device->class指向哪里?(TODO) -
device_add(&shost->shost_gendev)
将主机适配器的内嵌通用设备添加到系统中 -
error = device_add(&shost->shost_dev)
将主机适配器的内嵌类设备添加到系统中 -
scsi_sysfs_add_host(shost)
通过调用transport_register_device(&shost->shost_gendev);
transport_configure_device(&shost->shost_gendev)
将主机适配器添加到子系统, 在/sys/class/scsi_host/host0下面创建属性文件 -
scsi_proc_host_add(shost)
为兼容早期版本允许在proc中显示或配置主机适配器
参考文献
《存储技术原理分析》