SCSI子系统基础学习笔记 - 2. 添加SCSI适配器到系统

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依次形成父子关系

  1. kzalloc(sizeof(struct Scsi_Host) + privsize, gfp_mask)
    分配scsi_host空间和host私有空间
    在这里插入图片描述

  2. Scsi_Host初始化

  3. 通过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

  4. kthread_run(scsi_error_handler, shost,“scsi_eh_%d”, shost->host_no)
    通过kthread_run启动错误处理线程shost->ehandler,错误处理函数为scsi_error_handler

  5. alloc_workqueue:初始化工作队列shost->tmf_work_q,名称为scsi_tmf_host编号;

  6. 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总线设备模型中

  1. 判断shost->can_queue,即主机适配器可以同时接受的命令数,此处不能为0

  2. scsi_init_sense_cache(shost)
    主机适配器用于分配sense缓冲区的存储池结构

  3. 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进行关联,

  4. shost->shost_gendev.parent = dev ? dev : &platform_bus
    确定主机适配器在sysfs中的位置,对于ufs来讲,此处的dev为mas-ufs
    注:至此内嵌通用设备和内嵌类设备初始化完毕
    在这里插入图片描述
    注:scsi_device和scsi_target后面才会添加,如上device->class指向哪里?(TODO)

  5. device_add(&shost->shost_gendev)
    将主机适配器的内嵌通用设备添加到系统中

  6. error = device_add(&shost->shost_dev)
    将主机适配器的内嵌类设备添加到系统中

  7. scsi_sysfs_add_host(shost)
    通过调用transport_register_device(&shost->shost_gendev);
    transport_configure_device(&shost->shost_gendev)
    将主机适配器添加到子系统, 在/sys/class/scsi_host/host0下面创建属性文件

  8. scsi_proc_host_add(shost)
    为兼容早期版本允许在proc中显示或配置主机适配器

参考文献

《存储技术原理分析》

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值