SCSI子系统基础学习笔记 (之UFS子系统) - 2.1UFS子系统初始化之ufs_qcom_probe

1. 前言

本专题我们开始学习SCSI子系统的相关内容。本专题主要参考了《存储技术原理分析》、ULA、ULK的相关内容。本专题主要以硬件UFS为例,记录SCSI子系统的框架流程。本文是UFS子系统初始化部分。
如何发现有哪些场景?主要可以从如下几个角度:

  1. 初始化和退出;
  2. 线程处理函数;
  3. 调用中间层导出函数的流程;
  4. 调用底层回调函数的;
  5. 顶层使用者的动作

ufs的初始化主要包含了如下几部分:

  1. ufshcd_pltfrm_probe的执行过程
    主要完成ufs host的初始化,分配scsi_host结构体,并完成host, channel, target, device的扫描,为发现的每一个LUN 创建scsi_device,同时为每一个scsi_devcie创建了request_queue

  2. sd_probe的执行过程
    在每一个scsi_device->dev注册时,会引发sd.c中的sd_probe的执行,它会在每一次执行时创建gendisk,并通过add_gendisk添加到系统

本文主要介绍ufs_qcom_probe的执行过程

kernel版本:5.10
平台:arm64

注:
为方便阅读,正文标题采用分级结构标识,每一级用一个"-“表示,如:两级为”|- -", 三级为”|- - -“

2.ufs_qcom_probe

static int ufs_qcom_probe(struct platform_device *pdev)
	|--struct ufs_hba *hba;
	|  void __iomem *mmio_base; 
	|  struct device *dev = &pdev->dev;
	|--ufshcd_pltfrm_init(pdev, &ufs_hba_qcom_vops);
			|--mmio_base = devm_platform_ioremap_resource(pdev, 0);
			|--irq = platform_get_irq(pdev, 0);
			|--ufshcd_alloc_host(dev, &hba); 
			|--hba->vops = vops;
			|--ufshcd_parse_clock_info(hba);
			|--ufshcd_parse_regulator_info(hba);
			|--ufshcd_init_lanes_per_dir(hba);
			|--ufshcd_init(hba, mmio_base, irq);
			|--platform_set_drvdata(pdev, hba);
			|--pm_runtime_set_active(&pdev->dev);
			\--pm_runtime_enable(&pdev->dev);

由于在dts中设置了UFS的设备节点,那么在kenrel初始化时会扫描设备节点为每个设备节点注册platform_device,在ufs-qcom.c中通过宏module_platform_driver(ufs_qcom_pltform);定义了init函数和exit函数,实际会执行platform_driver_register(ufs_qcom_pltform),由于与platform_device的compatible匹配(均为qcom,ufshc),因此会触发ufs_qcom_probe执行。最主要的会执行ufshcd_pltfrm_init(pdev, &ufs_hba_qcom_variant)函数,它负责获取IO资源,中断号,通过scsi_host_alloc为Scsi_host及私有数据空间hba分配空间,同时通过解析DTS中的属性值初始化hba相关成员,最后通过ufshcd_init对ufs controller进行初始化,并对链接的ufs device进行扫描

  1. devm_platform_ioremap_resource:主要对io资源进行地址映射

  2. platform_get_irq:获取中断号,此处返回的是虚拟中断号

  3. ufshcd_alloc_host:分配ufs控制器,此处主要调用了scsi_host_alloc(&ufshcd_driver_template,sizeof(struct ufs_hba))来分配Scsi_host空间,同时也一起为ufs host的私有数据struct ufs_hba分配空间(可参考添加磁盘章节),并对scsi_host执行基本的初始化。ufshcd_driver_template为传入的参数,它在ufshcd.c中定义,它定义了scsi host所实现的基本模板函数。

  4. hba->var = var:获取ufs的操作函数集,此处的var为全局的struct ufs_hba_variant ufs_hba_qcom_variant变量,其中主要的成员为ufs_hba_variant_ops函数集,这些函数集定义在ufs-qcom.c中,可以看出这些ops主要是关于时钟和电源相关的一些callback
    在这里插入图片描述
    如下几个函数主要从DTS解析,可以看出解析后的信息主要通过hba进行链接:

  5. ufshcd_parse_clock_info(hba)
    从DTS中解析时钟相关信息,解析出来的每组clk会存放到ufs_clk_info,并通过list链入到hba->clk_list_head链表中
    实际就是将上图DTS中"clock-names", "freq-table-hz"的内容进行解析

  6. ufshcd_parse_regulator_info(hba)
    从dts解析regulator相关的信息,获取的信息保存在hba->vreg_info中,vreg_info是struct ufs_vreg_info类型,它记录了UFS所使用的几路电源,包括vcc, vccq, vccq2,还有host controller的电源vdd-hba。
    此函数会针对每一路电源调用ufshcd_populate_vreg到dts中对应的regulaor节点解析,解析完毕会将每一路电源的信息存放到hba->vreg_info中

  7. ufshcd_parse_reset_info(hba)
    解析reset信息保存到hba->core_reset中

  8. ufshcd_parse_pinctrl_info(hba)
    从dts中解析出pinctl信息保存在hba->pctrl

  9. ufshcd_parse_dev_ref_clk_freq(hba)
    从dts中解析"dev-ref-clk-freq"参考时钟频率属性信息,保存在hba->dev_ref_clk_freq

  10. ufshcd_parse_pm_levels(hba)
    从dts解析"rpm-level" "spm-level"属性,分别保存在hba->rpm_lvl,hba->spm_lvl中
    此处rpm-level = <0>
    0 - Both UFS device and Link in active state (Highest power consumption)
    1 - UFS device in active state but Link in Hibern8 state
    2 - UFS device in Sleep state but Link in active state
    3 - UFS device in Sleep state and Link in hibern8 state (default PM level)
    4 - UFS device in Power-down state and Link in Hibern8 state
    5 - UFS device in Power-down state and Link in OFF state (Lowest power consumption)
    此处spm-level = <0>
    UFS System power management level. Allowed PM levels are same as rpm-level

  11. ufshcd_parse_gear_limits(hba)
    从dts中主要解析如下四个属性(位于lito-rumi.dtsi):
    “limit-tx-hs-gear” “limit-rx-hs-gear” “limit-tx-pwm-gear” “limit-rx-pwm-gear”
    分别存放在ufs_hba的:
    hba->limit_tx_hs_gear (dts取值为1 HS-G1)
    hba->u32 limit_rx_hs_gear; (dts取值为1 HS-G1)
    hba->u32 limit_tx_pwm_gear; (dts未定义)
    hba->u32 limit_rx_pwm_gear; (dts未定义)
    (1) limit-tx-hs-gear: Specify the max. limit on the TX HS gear.Valid range: 1-3.
    1 => HS-G1, 2 => HS-G2, 3 => HS-G3
    (2) limit-rx-hs-gear: Specify the max. limit on the RX HS gear. Refer “limit-tx-hs- gear” for expected values.
    (3) limit-tx-pwm-gear: Specify the max. limit on the TX PWM gear Valid range: 1-4.
    1 => PWM-G1, 2 => PWM-G2, 3 => PWM-G3, 4 => PWM-G4
    (4) limit-rx-pwm-gear: Specify the max. limit on the RX PWM gear. Refer “limit-tx-pwm-gear” for expected values.

  12. ufshcd_parse_cmd_timeout(hba)
    从DTS中获取"scsi-cmd-timeout"值,保存在hba->scsi_cmd_timeout中,scsi_cmd_timeout为scsi命令执行的超时时间,此项目中没有定义此属性,因此默认设置为hba->scsi_cmd_timeout为0. 执行时间如何计算需要确定(TODO)

  13. ufshcd_parse_force_g4_flag(hba)
    从DTS中获取”force_g4“属性信息,初始化hba->force_g4,如果没有定义为设为false,此属性含义TODO

  14. ufshcd_parse_extcon_info(hba)
    获取外部连接器设备信息(TODO)

  15. ufshcd_init_lanes_per_dir(hba)
    从DTS中获取lanes-per-direction每个方向的lane的数目,本项目中每个方向的ane数目为2

  16. ufshcd_init(hba, mmio_base, irq)
    对ufs控制器做初始化

  17. platform_set_drvdata(pdev, hba)
    将hba作为platform_device->dev的私有数据

  18. pm_runtime_set_active(&pdev->dev)
    更新dev->power.runtime_status为RPM_ACTIVE

  19. pm_runtime_enable(&pdev->dev)
    Enable runtime PM of a device

|- -ufshcd_alloc_host

int ufshcd_alloc_host(struct device *dev, struct ufs_hba **hba_handle)
	|--struct Scsi_Host *host;
	|  struct ufs_hba *hba;
	|--host = scsi_host_alloc(&ufshcd_driver_template,sizeof(struct ufs_hba));
	|--hba = shost_priv(host);
	|--hba->host = host;
	|--hba->dev = dev;
	|--*hba_handle = hba;
	|--hba->dev_ref_clk_freq = REF_CLK_FREQ_INVAL;
	|--INIT_LIST_HEAD(&hba->clk_list_head);

ufshcd_alloc_host:分配ufs控制器,此处主要调用了scsi_host_alloc来分配Scsi_host空间,同时也一起为ufs host的私有数据struct ufs_hba分配空间(SCSI子系统基础学习笔记 - 2. 添加SCSI适配器到系统),并对scsi_host执行基本的初始化。ufshcd_driver_template为传入的参数,它在ufshcd.c中定义,它定义了scsi host所实现的基本模板函数。执行完此函数会创建/proc/scsi/scsi文件,并在/sys/devices目录下创建scsi host相关文件

|- -ufshcd_init

int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq)
	|--struct Scsi_Host *host = hba->host;
	|  struct device *dev = hba->dev;
	|--hba->mmio_base = mmio_base;
	|  hba->irq = irq;
	|  hba->vps = &ufs_hba_vps;
	|
	|--ufshcd_hba_init(hba)
	|--ufshcd_hba_capabilities(hba)
	|--hba->ufs_version = ufshcd_get_ufs_version(hba)
	|--hba->intr_mask = ufshcd_get_intr_mask(hba)
	|--ufshcd_set_dma_mask(hba)
	|--ufshcd_memory_alloc(hba)//Allocate memory for host memory space
	|--ufshcd_host_memory_configure(hba)//Configure LRB
	|--host->can_queue = hba->nutrs;
	|  host->cmd_per_lun = hba->nutrs;
	|  host->max_id = UFSHCD_MAX_ID;
	|  host->max_lun = UFS_MAX_LUNS;
	|  host->max_channel = UFSHCD_MAX_CHANNEL;
	|  host->unique_id = host->host_no;
	|  host->max_cmd_len = UFS_CDB_SIZE;
	|--hba->eh_wq = create_singlethread_workqueue(eh_wq_name);
	|--INIT_WORK(&hba->eh_work, ufshcd_err_handler); 
	|  INIT_WORK(&hba->eeh_work, ufshcd_exception_event_handler);
	|--ufshcd_init_clk_gating(hba);
	|  ufshcd_init_clk_scaling(hba);
	|--devm_request_irq(dev, irq, ufshcd_intr, IRQF_SHARED, UFSHCD, hba);
	|--scsi_add_host(host, hba->dev)
	|--hba->cmd_queue = blk_mq_init_queue(&hba->host->tag_set);//多队列初始化
	|--blk_mq_alloc_tag_set(&hba->tmf_tag_set)
	|--hba->tmf_queue = blk_mq_init_queue(&hba->tmf_tag_set)
	|--ufshcd_vops_device_reset(hba)
	|--ufshcd_init_crypto(hba)
	|--INIT_DELAYED_WORK(&hba->rpm_dev_flush_recheck_work,
	|					ufshcd_rpm_dev_flush_recheck_work);
	|--ufshcd_set_ufs_dev_active(hba);
	|--async_schedule(ufshcd_async_scan, hba);\
	|--ufs_sysfs_add_nodes(hba->dev);
	

ufshcd初始化,主要包含hba初始化,获取hba的能力及版本,为UTRD/UCD/UTMRD等分配空间,创建了专用的工作队列以及专用work,用于处理如错误恢复,异常事件等。通过scsi_add_host将ufs host加入到系统中,最后通过ufshcd_async_scan扫描ufs device

  1. ufshcd_def_desc_sizes(hba)
    初始化hba->desc_size
    hba->desc_size.dev_desc = QUERY_DESC_DEVICE_DEF_SIZE;
    hba->desc_size.pwr_desc = QUERY_DESC_POWER_DEF_SIZE;
    hba->desc_size.interc_desc = QUE RY_DESC_INTERCONNECT_DEF_SIZE;
    hba->desc_size.conf_desc = QUERY_DESC_CONFIGURATION_DEF_SIZE;
    hba->desc_size.unit_desc = QUERY_DESC_UNIT_DEF_SIZE;
    hba->desc_size.geom_desc = QUERY_DESC_GEOMETRY_DEF_SIZE;
    hba->desc_size.hlth_desc = QUERY_DESC_HEALTH_DEF_SIZE;

  2. ufshcd_hba_init
    对host bus adapter进行初始化。主要包含了对host controller供电, 对dts中描述的时钟进行设置并使能,使能ufs device端供电,最后是高通host controller的初始化。创建ufs_qcom_host并对其进行初始化,初始化内容主要包括:phy初始化,qos初始化,注册bus client data, 设置host能力,开启参考时钟。
    ufshcd_init_hba_vreg
    ufshcd_setup_hba_vreg
    ufshcd_init_clocks
    ufshcd_enable_clocks
    ufshcd_init_vreg
    ufshcd_setup_vreg
    ufshcd_variant_hba_init

  3. ufshcd_hba_capabilities
    通过ufshcd_readl(hba, REG_CONTROLLER_CAPABILITIES)读取UFS的REG_CONTROLLER_CAPABILITIES控制器并初始化给hba->capabilities

  4. ufshcd_get_ufs_version
    获取HBA支持的ufs版本

  5. ufshcd_get_intr_mask
    获取中断mask??

  6. ufshcd_set_dma_mask
    (参考 https://chrtech.com/2017/08/03/17-DMA-summary/)
    通过dma_set_mask_and_coherent设置hba->dev是否可以访问64位地址来设置dma mask

  7. ufshcd_memory_alloc
    在这里插入图片描述
    (1)为UCD(struct utp_transfer_cmd_desc)数组分配DMA空间
    通过dmam_alloc_coherent分配hba->nutrs个struct utp_transfer_cmd_desc空间,它包含command upiu、response upiu、PRDT(数据缓冲区列表, 表中每一项管理一个sg segment,每个sg地址由上层传递)
    hba->nutrs来源于host controller capability register.
    返回的虚拟地址保存在 hba->ucdl_base_addr,物理地址保存在hba->ucdl_dma_addr
    由于分配的空间是需要供ufs控制器通过dma来访问的,因此通过dmam_alloc_coherent申请,如下类同
    (2)为UTRD分配DMA空间
    通过dmam_alloc_coherent分配个数为hba->nutrs个struct utp_transfer_req_desc空间,返回的虚拟地址保存在hba->utrdl_base_addr,物理地址保存在&hba->utrdl_dma_addr
    (3)为UTMRD分配DMA空间
    分配hba->nutmrs个struct utp_task_req_desc空间
    返回的虚拟地址保存在hba->utmrdl_base_addr,物理地址保存在hba->utmrdl_dma_addr
    hba->nutmrs来源于host controller capability register.
    (4)为local reference block(lrb)分配空间
    通过devm_kcalloc分配lrb空间,它的数目为hba->nutmrs,与utp_transfer_req_desc与utp_transfer_cmd_desc数目一致。返回的虚拟地址保存在hba->lrb,lrb保存了URTD以及UCD 的地址信息
    通过在函数中加打印,可知ucd和utrd的地址:
    在这里插入图片描述

  8. ufshcd_host_memory_configure(hba):
    遍历UTRDL列表中的每一项,获取每一个UCD的信息(command upiu地址、response地址、prd_table地址)赋值给UTRDL列表中的每一个UTRD,同时也赋值给每一个hba->lrb。如此可以看到:UCD , UTRD以及hba->lrb数组的每一项是一一对应的。local reference block,数目与utp_transfer_req_desc(UTRD)和utp_transfer_cmd_desc(UCD)的一致,它为执牛耳者,将命令描述符结构体UCD的command_upiu,response_upiu,prd_table的地址保存到自己的结构体中。ufshcd_lrb,utp_transfer_req_desc与utp_transfer_cmd_desc是一一对应的,ufshcd_lrb的ucd_req_ptr/ucd_req_dma_addr, ucd_rsp_ptr/ucd_rsp_dma_addr,, ucd_prdt_ptr/ucd_prdt_dma_addr分别保存了utp_transfer_cmd_desc的command_upiu地址,response_upiu地址,prd_table地址

  9. 初始化scsi_host相关变量
    host->can_queue = hba->nutrs;
    host->cmd_per_lun = hba->nutrs;
    host->max_id = UFSHCD_MAX_ID;
    host->max_lun = UFS_MAX_LUNS;
    host->max_channel = UFSHCD_MAX_CHANNEL;
    host->unique_id = host->host_no;
    host->max_cmd_len = MAX_CDB_SIZE;
    host->set_dbd_for_caching = 1;
    hba->max_pwr_info.is_valid = false;

  10. 为任务管理初始化等待队列:
    init_waitqueue_head(&hba->tm_wq);
    init_waitqueue_head(&hba->tm_tag_wq);

  11. 为任务恢复创建工作队列
    返回值保存在hba->recovery_wq

  12. 初始化work
    INIT_WORK(&hba->eh_work, ufshcd_err_handler);//用于错误处理
    INIT_WORK(&hba->eeh_work, ufshcd_exception_event_handler);//异常处理
    INIT_DELAYED_WORK(&hba->card_detect_work, ufshcd_card_detect_handler);//卡检测
    INIT_WORK(&hba->rls_work, ufshcd_rls_handler);

  13. 初始化相关信号量
    sema_init(&hba->sdev_sema, 1);
    mutex_init(&hba->card_mutex);
    /* Initialize UIC command mutex /
    mutex_init(&hba->uic_cmd_mutex);
    /
    Initialize mutex for device management commands */
    mutex_init(&hba->dev_cmd.lock);
    init_rwsem(&hba->lock);

  14. Initialize device management tag acquire wait queue
    init_waitqueue_head(&hba->dev_cmd.tag_wq);

  15. ufshcd_init_clk_gating(hba)
    主要初始化clk gating相关配置,为gate/ungate clk分别创建work, 初始化高精度定时器用于休眠后多长时间执行gating,执行gating的延时时间,以及创建gating延时相关属性文件,创建gating/ungating属性文件

  16. ufshcd_init_hibern8(hba)
    hiber相关初始化,对于支持auto hiber则设置了默认的auto-hiberate定时器为1ms,对于不支持auto hiber则通过work来进入和退出hiber

  17. ufshcd_writel(hba, ufshcd_readl(hba, REG_INTERRUPT_STATUS), REG_INTERRUPT_STATUS)
    ufshcd_writel(hba, 0, REG_INTERRUPT_ENABLE);
    清中断pending,中断禁用

  18. devm_request_irq(dev, irq, ufshcd_intr, IRQF_SHARED, UFSHCD, hba)
    注册中断

  19. scsi_add_host(host, hba->dev)
    将代表scsi host的devivce添加进设备模型,同时加入到sysfs,并创建相关属性文件
    /sys/devices/platform/soc/1d84000.ufshc/host0/scsi_host/host0
    参考:SCSI子系统基础学习笔记 - 2. 添加SCSI适配器到系统

  20. blk_mq_init_queue(&hba->host->tag_set)
    对应的blk_mq_alloc_tag_set发生在scsi_add_host->scsi_mq_setup_tags。
    分配request_queue,并对其进行初始化,期间会分配软队列和硬队列并初始化,并进一步建立软队列和硬队列的映射关系,如果支持调度器,则对调度器初始化
    参考:block多队列分析 - 2. block多队列的初始化

  21. blk_mq_alloc_tag_set(&hba->tmf_tag_set)
    blk_mq_alloc_tag_set分配的不是blk_mq_tag_set ,而是为每个硬队列分配blk_mq_tags指针数组,blk_mq_tag_set 一般内嵌在块设备相关结构体中,此处为hba;分配数组mq_map用于维护软硬队列的映射,并建立软硬队列的映射关系,同时对每个硬件队列,根据队列深度为每个硬件队列分配bitmap,并根据队列深度创建相应数目的request。此tag set将与一个或多个request_queue进行关联,在分配过程过程中可能由于内存不足而失败,这样就要通过调整queue depth来进行调整后,重新分配,并更新queue depth
    参考:block多队列分析 - 2. block多队列的初始化

  22. blk_mq_init_queue(&hba->tmf_tag_set)
    分配request_queue,并对其进行初始化,期间会分配软队列和硬队列并初始化,并进一步建立软队列和硬队列的映射关系,如果支持调度器,则对调度器初始化
    参考:block多队列分析 - 2. block多队列的初始化

  23. ufshcd_vops_full_reset(hba)
    Reset controller to power on reset (POR) state
    复位UFS控制器

  24. ufshcd_reset_device(hba)
    reset ufs device
    复位UFS设备

  25. ufshcd_hba_enable(hba)
    使能host controller,通过调用ufshcd_hba_execute_hce。
    主要通过写入HCI的REG_CONTROLLER_ENABLE(0X34)寄存器实现,同时也会通过写入REG_INTERRUPT_ENABLE(0x24)寄存器使能UIC相关中断:包括:UIC_COMMAND_COMPL,UIC_HIBERNATE_ENTER,UIC_HIBERNATE_EXIT,UIC_POWER_MODE。
    这部分将多处调用dme get和dme set命令,通过trace event抓取到的信息如下:
    ufshcd_hba_execute_hce

  26. ufshcd_is_clkscaling_supported(hba)
    如上,如果为true, 初始化hba->clk_scaling.suspend_work和hba->clk_scaling.resume_work,通过
    hba->clk_scaling.workq = create_singlethread_workqueue(wq_name)创建专有work queue; 通过ufshcd_clkscaling_init_sysfs(hba)在sysfs中创建clkscale_enable的属性

  27. ufs_get_desired_pm_lvl_for_dev_link_state
    通过ufshcd_is_valid_pm_lvl判断hba->rpm_lvl和hba->spm_lvl是否有效,如果无效则通过ufs_get_desired_pm_lvl_for_dev_link_state设置,默认选择省电模式

If rpm_lvl and and spm_lvl are not already set to valid levels, set the default power management level for UFS runtime and system suspend. Default power saving mode selected is keeping UFS link in Hibern8 state and UFS device in sleep state.

  1. ufshcd_set_ufs_dev_active(hba)
    假定boot阶段不会发生休眠,这样可以避免在ufshcd_probe_hba时UIC被启动两次

  2. ufshcd_cmd_log_init(hba)
    用于分配记录scsi cmd的缓冲区

  3. async_schedule(ufshcd_async_scan, hba)
    异步扫描ufs设备,后面将单独一节进行介绍扫描过程

  4. ufsdbg_add_debugfs(hba)
    在debugfs中增加ufs相关节点
    /sys/kernel/debug/1d84000.ufshc
    其中:
    dump_device_desc属性可以看到ufs device的信息
    power_mode属性可以查看速率及功耗相关的信息
    host_regs属性可以查看host controller的寄存器配置
    qcom/debug-regs可以查看debug相关寄存器信息,需要结合SOC spec
    show_hba属性可以查看hba的相干信息
    stas/目录下可以看到一些查询统计的信息

  5. ufs_sysfs_add_nodes(hba->dev)
    sysfs中增加ufs节点,此处hba->dev就时qcom ufs platform_device->dev
    sys路径:/sys/devices/platform/soc/1d84000.ufshc
    这里面包含了大量的ufs相关的信息,后面会由专门的章节进行总结

  6. ufshcd_register_pm_notifier(hba)
    pm注册通知连

注:从上面的分析可以看出,可以从四个路径查看ufs host和device的相关信息
host:
/proc/scsi/scsi
/sys/devices/platform/soc/1d84000.ufshc/host0/scsi_host/host0
device:
/sys/kernel/debug/1d84000.ufshc
/sys/devices/platform/soc/1d84000.ufshc

参考文档

存储技术原理分析


版权声明:本文为jasonactions原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。