1. 前言
本专题我们开始学习SCSI子系统的相关内容。本专题主要参考了《存储技术原理分析》、ULA、ULK的相关内容。本专题主要以硬件UFS为例,记录SCSI子系统的框架流程。
在ufs_qcom_probe->ufshcd_pltfrm_init->ufshcd_init的最后执行了async_schedule(ufshcd_async_scan, hba),此处的async_schedule之所以称为异步调度,是因为通过创建了一个worker,并将worker加入到了system_unbound_wq,由系统在某个合适时机调度执行,执行函数为ufshcd_async_scan,本文是UFS子系统初始化第二部分,主要介绍ufshcd_pltfrm_probe的ufshcd_async_scan执行过程。
kernel版本:5.10
平台:arm64
注:
为方便阅读,正文标题采用分级结构标识,每一级用一个"-“表示,如:两级为”|- -", 三级为”|- - -“
2. ufshcd_async_scan
static void ufshcd_async_scan(void *data, async_cookie_t cookie)
|--struct ufs_hba *hba = (struct ufs_hba *)data;
|--ufshcd_probe_hba(hba, true);
|--ufshcd_add_lus(hba);
ufshcd_probe_hba(hba):主要通过探测ufs device是否链接,如果探测到ufs device则检测是否ufs device已经ready,获取host和device都能支持的传输速率,最后扫描well known lun和normal lun.
|- -ufshcd_probe_hba
static int ufshcd_probe_hba(struct ufs_hba *hba, bool async)
|--ufshcd_link_startup(hba)
| //Debug counters initialization
|--ufshcd_clear_dbg_ufs_stats(hba)
| //UniPro link is active now
|--ufshcd_set_link_active(hba)
| //Verify device initialization by sending NOP OUT UPIU
|--ufshcd_verify_dev_init(hba)
| //Initiate UFS initialization, and waiting until completion
|--ufshcd_complete_dev_init(hba);
|--ufshcd_tune_unipro_params(hba);
|--ufshcd_set_ufs_dev_active(hba);
|--ufshcd_force_reset_auto_bkops(hba)
| //Gear up to HS gear if supported
|--if (hba->max_pwr_info.is_valid)
| if (hba->dev_ref_clk_freq != REF_CLK_FREQ_INVAL)
| ufshcd_set_dev_ref_clk(hba);
| ufshcd_config_pwr_mode(hba, &hba->max_pwr_info.info);
|--ufshcd_set_active_icc_lvl(hba);
|--ufshcd_wb_config(hba);
| //Enable Auto-Hibernate if configured
|--ufshcd_auto_hibern8_enable(hba);
主要通过探测ufs device是否链接,如果探测到ufs device则检测是否ufs device已经ready,获取host和device都能支持的传输速率,最后扫描well known lun和normal lun.
- ufshcd_link_startup(hba)
Initialize unipro link startup
(1)ufshcd_vops_link_startup_notify(hba, PRE_CHANGE)
主要调用hba->var->vops->link_startup_notify回调,对于高通即为ufs_qcom_link_startup_pre_change,主要是在ufs device的unipro layer执行startup之前,执行host controller端的unipro layer初始化,即在ufs device的unipro layer执行startup操作之前,qcom需要执行一些预备操作,如确认qcom unipro layer RX已经使能等
(2)ufshcd_dme_link_startup(hba)
Notify Unipro to perform link startup,通过向Unipro layer发送UIC_CMD_DME_LINK_STARTUP命令来初始化Unipro layer启动流程,这样host controller才能检测到。
(3)ufshcd_init_pwr_info(hba);
Mark that link is up in PWM-G1, 1-lane, SLOW-AUTO mode
(4)ufshcd_make_hba_operational(hba)
To bring UFS host controller to operational state,
- Enable required interrupts
- Configure interrupt aggregation
- Program UTRL and UTMRL base address
- Configure run-stop-registers
-
ufshcd_clear_dbg_ufs_stats(hba)
Debug counters initialization -
ufshcd_set_link_active(hba)
设置link为激活状态UIC_LINK_ACTIVE_STATE,UniPro link is active now -
ufshcd_verify_dev_init(hba)
Send NOP OUT UPIU and wait for NOP IN response to check whether the
device Transport Protocol (UTP) layer is ready after a reset. If the
UTP layer at the device side is not initialized, it may not respond
with NOP IN UPIU within timeout of %NOP_OUT_TIMEOUT and we retry
sending NOP OUT for %NOP_OUT_RETRIES iterations.
通过发送NOP OUT UPIU,并等待NOT IN UPIU回应信息来判断UTP是否已经ready,如果ufs设备没有ready则需要重新发送NOP OUT UPIU,直到超出指定次数
-
ufshcd_complete_dev_init(hba)
检查ufs设备是否ready,主要通过发送UPIU_QUERY_OPCODE_SET_FLAG(UPIU:QUERY REQUEST UPIU.Set Flag), ufs device收到此upiu会将FLAG IDN清零,通过发送UPIU_QUERY_OPCODE_READ_FLAG(UPIU:QUERY REQUEST UPIU.Read Flag),来读取FLAG IDN,如果返回的UPIU RESPONSE值显示FLAG IDN 被清零,则说明ufs device已经ready -
ufs_read_device_desc_data(hba)
主要通过调用ufshcd_query_descriptor_retry通过发送QUERY REQUEST UPIU.Read Descriptor Opcode来获取UFS设备描述信息,相关信息将被保存到hba->dev_info中 -
ufshcd_init_desc_sizes(hba)
Init check for device descriptor sizes(TODO) -
ufs_get_device_desc(hba, &card)
通过QUERY REQUEST command code,UPIU_QUERY_OPCODE_READ_DESC,QUERY_DESC_IDN_STRING,读取UFS的设备细节信息,并保存在card中,此处的card为struct ufs_dev_desc类型 -
ufshcd_needs_reinit(hba)
判断ufs是否需要重新初始化,如果需要重新初始化则执行如下操作:
ufshcd_vops_full_reset(hba)
ufshcd_reset_device(hba)//reset ufs device
ufshcd_hba_stop(hba, false);//reset controller
ufshcd_hba_enable(hba) -
ufs_fixup_device_setup(hba, &card)
此处的card为struct ufs_dev_desc类型,它是上一步通过QUERY REQUEST 操作从UFS读取出的描述符信息,因为有一些UFS有一些怪癖,这些怪癖的UFS主要保存在ufs_fixups里,通过检查链接的UFS是否在这个列表,如果在这个列表,则需要标识出此UFS具有这些怪癖,这样在使用时就可以做特殊处理 -
ufshcd_tune_unipro_params(hba)
调整unipro的参数 -
ufshcd_set_ufs_dev_active(hba);
hba->ufshcd_state = UFSHCD_STATE_OPERATIONAL;
UFS device is also active now -
ufshcd_force_reset_auto_bkops(hba);
TODO -
ufshcd_get_max_pwr_mode(hba)
通过mipi命令获取UFS支持的最大的lane以及速率,通过与ufs host controller比较采用两者支持的最大值, -
ufshcd_set_active_icc_lvl(hba)
TODO -
hba->ufshcd_state = UFSHCD_STATE_OPERATIONAL;
ufshcd_wb_config(hba)
set the state as operational after switching to desired gear -
ufshcd_set_auto_hibern8_timer(hba)
设置进入hibern的定时器
|- -ufshcd_add_lus
static int ufshcd_add_lus(struct ufs_hba *hba)
|--ufshcd_scsi_add_wlus(hba);
|--if (ufshcd_is_clkscaling_supported(hba))
| if (!hba->devfreq)
| ufshcd_devfreq_init(hba);
|--ufs_bsg_probe(hba);
|--scsi_scan_host(hba->host);
|--pm_runtime_put_sync(hba->dev);
-
ufshcd_scsi_add_wlus(hba)
This function adds scsi device instances for each of all well known LUs (except “REPORT LUNS” LU). -
ufshcd_set_low_vcc_level(hba, &card)
lower VCC voltage level -
ufshcd_devfreq_init(hba)
通过调用devfreq_add_device将ufs加入到调频子系统 -
scsi_scan_host(hba->host)
扫描常规的LUN,scsi_scan_host对指定的scsi_host进行扫描,通过异步扫描host, channel, target, device, 最终将扫描到的lun加入到系统中
参考:SCSI子系统基础学习笔记 - 3. SCSI设备探测
3. 总结
现在,我们以UFS为例,来总结一下作为一个SCSI块设备,所必须经历哪些标准化的流程:
-
scsi_host_alloc:为Scsi host控制器Scsi_Host及私有属性分配空间;
-
scsi_add_host:将代表scsi host的devivce添加进设备模型,同时加入到sysfs,并创建相关属性文件。对于多队列,期间会执行blk_mq_alloc_tag_set,为每个硬队列分配blk_mq_tags指针数组,blk_mq_tag_set 一般内嵌在块设备相关结构体中,此处为hba中;分配数组mq_map用于维护软硬队列的映射,并建立软硬队列的映射关系,同时对每个硬件队列,根据队列深度为每个硬件队列分配bitmap,并根据队列深度创建相应数目的request。
-
blk_mq_init_queue:分配request_queue,并对其进行初始化,期间会分配软队列和硬队列并初始化,并进一步建立软队列和硬队列的映射关系,如果支持调度器,则对调度器初始化
-
scsi_scan_host:扫描常规的LUN,scsi_scan_host对指定的scsi_host进行扫描,通过异步扫描host, channel, target, device, 最终将扫描到的lun加入到系统中
-
sd_probe:通过alloc_disk分配gendisk;device_add_disk将通用磁盘添加到系统
参考文档
存储技术原理分析