Linux | SCSI子系统概述和UFS在其中的位置

SCSI分层

Linux SCSI子系统和其他子系统一样,也是一种分层的架构。共分为三层,最底下是低层,代表适用于SCSI的物理接口的实际驱动器,例如各个厂商为设备特定的主机适配器(也被称为主机总线适配器,Host Bus Adapter HBA)开发的驱动。低层驱动主要作用是发现连接到主线适配器上的SCSI设备,在内存中为它们建立好数据结构,并提供消息传递接口,将SCSI命令的接收与发送解释为主机适配器操作。

2ee63f1de2430224195c139711d9a035.jpeg

最上面的一层是高层,代表各种SCSI设备类型的驱动(磁盘类型、磁带类型、CD类型等)。高层驱动会“认领”低层驱动发现的SCSI设备,为他们分配设备名,将对设备的request转换为SCSI命令,交由低层驱动处理。

中间层也称为公共层或统一层,包含了SCSI堆栈的高层和低层的一些公共服务函数。高层和低层通过调用中间层的函数来实现其功能,而中间层在执行过程中,也需要调用高层和低层注册的回调函数来做个性化处理。

Linux SCSI模型

9265dabaa3f1a009666d4d23388714f2.jpeg

如上图,Linux SCSI模型基于传统的并行SCSI,主机适配器连接主机IO总线(通常是PCI总线)和存储IO总线(这里是SCSI总线)。一台计算机可以有多个主机适配器,而主机适配器根据其通道数,可以控制一个或多个SCSI总线,一条总线可以有多个目标节点与之相连,并且一个目标节点可以有多个逻辑单元(logic unit, lu),每个逻辑单元有其编号lun(logic unit number)。在Linux语义中,用SCSI设备描述符表示具体的逻辑单元,但和我们通常谈到的SCSI设备(例如SCSI磁盘)是不一样的。一个SCSI磁盘应该被视作一个目标节点,在这样一个目标节点内,可以有多个逻辑单元,统一由磁盘控制器控制,这些逻辑单元才是真正作为IO终点的存储设备。但是,为了简单起见,大多数厂商生产的磁盘只做了基本实现,只实现了LUN0。

因此,确定了Host、channel、目标节点id、lun,即可唯一定位一个逻辑单元,在Linux中,可以表示为<host:channel:id:lun>这样的四元组。

举个栗子:

6af1122eb7d7ac647562873f58585d75.png

5:0:0:0 即表示,插在host5适配器上,0通道总线下第0个目标设备的第0个逻辑单元。

对于并行SCSI总线,其所能挂接的设备数目是受物理层,总线宽度限制的,依据在SCSI总线上可设置的地址数量,并行SCSI又细分为:8个地址的窄SCSI(3位寻址)和16个地址(4位寻址)的宽SCSI,SCSI主机适配器是不可或缺的,它占用一个总线地址(7),对于窄SCSI,能挂载的目标节点数为7,对于宽SCSI能挂载的目标节点数为15,因此SCSI目标节点ID范围为0~6或8~15(7为host)。

而对于使用交换式拓扑结构的SAS或者“虚拟”iSCSI来说,channel和id是纯粹逻辑概念,没有物理意义,它的channel和id的范围也没有限制。

SCSI子系统的主要功能是:

  • 探测SCSI设备,在内存建立可供设备驱动使用的核心结构;

  • 在sysfs文件系统中构造SCSI子系统目录树;

  • SCSI高层驱动绑定SCSI设备,在内存中构建对应的核心结构;

  • 提供错误恢复API,在SCSI命令错误和超时后被调用。

SCSI数据结构

根据上面的介绍,要将物理设备抽象到SCSI子系统中,至少需要定义下Host、Target(目标设备)、Device(逻辑单元)的结构。

于是在Linux中,这些数据结构具有以下的关系:

374c0c73050da56c1d62fbfb353874bb.jpeg

scsi_host:描述主机适配器,在linux中可以安装多个型号相同的主机适配器,由同一个低层驱动管理。当SCSI主机适配器被发现时,SCSI低层驱动为它分配一个scsi_host描述符,该描述符中一部分可供scsi中间层使用,另一部分为scsi低层驱动专用。在分配好scsi_host后,将其添加到系统并启动探测过程,探明scsi_target和scsi_device,并完成相关域、链表的配置。

scsi_target:描述目标节点,scsi_device:描述逻辑单元,他们的hostdata域指向SCSI低层驱动专用信息。

更具体的暂时不在本文说。

UFS概述

UFS全名为Universal Flash Storage(通用闪存存储),是一种具有串行接口的简单、高性能的大容量存储设备,主要被用在移动系统中,介于主机处理和大容量存储设备之间。

UFS采用一种多层次的架构设计,如下图:

386e77e816b8953a7af9388074e5b00b.png

UAP:应用层,应用层由UFS命令集UCS、设备管理device manager和任务管理Task manager组成。命令集UCS将会处理从SCSI子系统传入的常规的命令,如read、write等,UFS可能支持多命令集。UFS被设计为协议无关的,当前标准下的UCS是基于SCSI命令集的,UFS选择了简化的SCSI命令集,当需要扩展UFS功能时,可以支持UFS本机命令集。Task Manager处理用于命令队列控制命令,Device Manager提供设备级的控制,如Query Request和更低级连接层UIC的控制。

UFS Device Manager:设备管理器,设备管理器主要承担两个职责,处理设备级的操作和管理设备级的配置。设备级操作包含设备电源管理、与数据相关的设置、启用后台操作以及其他特定设备操作。设备管理器通过维护和存储一组描述符来管理设备级的配置。设备管理器处理类似于query request的命令允许修改或检索设备的配置信息。

Service Access Points:服务接入点,上图中椭圆形的部分称为服务接入点,例如,设备管理器通过访问UTP层暴露的服务接入点UDM_SAP从而进行设备级的操作和配置。

e76a08cf254ce622cf6a65721ddb62b0.png

例如:

UDM_SAP:响应UTP层定义的Query Request和Query Response功能。

UIO_SAP:UIC暴露的接入点,响应一些设备重置的功能。

UTP:传输协议层,向更高层提供服务,UPIU为“UFS Protocol Information Unit” ,UFS协议信息单元,用于在UTP层在UFShost和UFSdevice间交换。例如,如果host侧的UTP收到了来自应用层的请求,UTP会为此请求生成一个UPIU,并传输到设备侧UTP。

UIC:互连层,UFS的最低层级,用于处理UFS host和UFS device之间的连接,主要包括MIPI UniPro 数据链路层和MIPI M-PHY 物理层,进行实际的数据传输。

2b5418ced2194e4a1f2b505629646ff5.png

UFS系统模型如上图。

UFS在SCSI中

不难发现,UFS管理了具体的host和device,它应当属于SCSI的低层驱动。

74997258345589649b16344ee3e49c4f.png

图源/参考:

https://blog.csdn.net/jasonactions/article/details/116932302

之前对scsi请求处理的分析中,我们梳理了scsi请求处理的流程:

68660760ae4abe9d70037bf82203316f.png

可以发现,当scsi上层向下层派遣命令时,是通过queuecommand这个回调函数进行的,当处理完成时,调用scsi_done通知上层。

对应函数ufshcd_queuecommand:

static int ufshcd_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd)
{
  struct ufs_hba *hba = shost_priv(host);
  int tag = scsi_cmd_to_rq(cmd)->tag;
  struct ufshcd_lrb *lrbp;
  int err = 0;


  trace_android_vh_ufs_mcq_map_tag(hba,
        (scsi_cmd_to_rq(cmd)->mq_hctx->queue_num), &tag);


  WARN_ONCE(tag < 0, "Invalid tag %d\n", tag);


  if (!down_read_trylock(&hba->clk_scaling_lock))
    return SCSI_MLQUEUE_HOST_BUSY;


  switch (hba->ufshcd_state) {
  case UFSHCD_STATE_OPERATIONAL:
  case UFSHCD_STATE_EH_SCHEDULED_NON_FATAL:
    break;
  case UFSHCD_STATE_EH_SCHEDULED_FATAL:
    /*
     * pm_runtime_get_sync() is used at error handling preparation
     * stage. If a scsi cmd, e.g. the SSU cmd, is sent from hba's
     * PM ops, it can never be finished if we let SCSI layer keep
     * retrying it, which gets err handler stuck forever. Neither
     * can we let the scsi cmd pass through, because UFS is in bad
     * state, the scsi cmd may eventually time out, which will get
     * err handler blocked for too long. So, just fail the scsi cmd
     * sent from PM ops, err handler can recover PM error anyways.
     */
    if (hba->pm_op_in_progress) {
      hba->force_reset = true;
      set_host_byte(cmd, DID_BAD_TARGET);
      cmd->scsi_done(cmd);
      goto out;
    }
    fallthrough;
  case UFSHCD_STATE_RESET:
    err = SCSI_MLQUEUE_HOST_BUSY;
    goto out;
  case UFSHCD_STATE_ERROR:
    set_host_byte(cmd, DID_ERROR);
    cmd->scsi_done(cmd);
    goto out;
  }


  hba->req_abort_count = 0;


  err = ufshcd_hold(hba, true);
  if (err) {
    err = SCSI_MLQUEUE_HOST_BUSY;
    goto out;
  }
  WARN_ON(ufshcd_is_clkgating_allowed(hba) &&
    (hba->clk_gating.state != CLKS_ON));


  lrbp = &hba->lrb[tag];
  WARN_ON(lrbp->cmd);
  lrbp->cmd = cmd;
  lrbp->sense_bufflen = UFS_SENSE_SIZE;
  lrbp->sense_buffer = cmd->sense_buffer;
  lrbp->task_tag = tag;
  lrbp->lun = ufshcd_scsi_to_upiu_lun(cmd->device->lun);
  lrbp->intr_cmd = !ufshcd_is_intr_aggr_allowed(hba) ? true : false;


  trace_android_vh_ufs_mcq_set_sqid(hba, scsi_cmd_to_rq(cmd)->mq_hctx->queue_num, lrbp);


  ufshcd_prepare_lrbp_crypto(scsi_cmd_to_rq(cmd), lrbp);


  trace_android_vh_ufs_prepare_command(hba, scsi_cmd_to_rq(cmd), lrbp,
               &err);
  if (err) {
    lrbp->cmd = NULL;
    ufshcd_release(hba);
    goto out;
  }


  lrbp->req_abort_skip = false;


  ufshpb_prep(hba, lrbp);


  ufshcd_comp_scsi_upiu(hba, lrbp);


  err = ufshcd_map_sg(hba, lrbp);
  if (err) {
    lrbp->cmd = NULL;
    ufshcd_release(hba);
    goto out;
  }


  ufshcd_send_command(hba, tag);
out:
  up_read(&hba->clk_scaling_lock);


  if (ufs_trigger_eh()) {
    unsigned long flags;


    spin_lock_irqsave(hba->host->host_lock, flags);
    ufshcd_schedule_eh_work(hba);
    spin_unlock_irqrestore(hba->host->host_lock, flags);
  }


  return err;
}
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Linux内核中,SD卡、eMMC和UFS的驱动代码位于不同的目录中。 1. SD卡驱动代码位于`drivers/mmc`目录下。在该目录中,可以找到与SD卡相关的驱动文件和子目录,如`mmc_core.c`、`mmc_block.c`等。此外,还有一些特定厂商的SD卡驱动,例如`mmc/sdhci-s3c.c`。 2. eMMC驱动代码同样位于`drivers/mmc`目录下。在该目录中,也可以找到与eMMC相关的驱动文件和子目录,如`mmc_core.c`、`mmc_block.c`等。与SD卡相比,eMMC的驱动代码可能会有一些特定的文件,但大部分代码都是共用的。 3. UFS驱动代码位于`drivers/scsi/ufs`目录下。在该目录中,可以找到与UFS相关的驱动文件和子目录,如`ufs.c`、`ufs-sysfs.c`和`ufs-exynos.c`等。这些文件包含了UFS设备的驱动代码。 此外,还需要注意,以上目录可能会随着不同内核版本的变化而有所不同,因此在具体的内核版本中可能会有一些细微的差异。 ### 回答2: 在Linux内核中,SD卡、eMMC和UFS存储设备的驱动程序代码位于不同的目录下面。 1. SD卡驱动程序的代码位于`drivers/mmc`目录下。具体而言,SD卡的核心驱动程序是`mmc_core.c`,而与SD卡相关的SDIO(Secure Digital Input Output)功能驱动程序位于`sdio`目录下。 2. eMMC驱动程序的代码位于`drivers/mmc`目录下的`mmc_core.c`中。eMMC是一种闪存存储设备,因此其驱动程序与SD卡驱动程序共享。 3. UFS驱动程序的代码位于`drivers/scsi/ufs`目录下。UFS(Universal Flash Storage)是一种新的高性能存储标准,可以代替eMMC。UFS驱动程序的核心文件是`ufs.c`。此外,与UFS相关的Host控制器驱动程序代码位于`drivers/scsi/ufs/ufshcd.c`中。 需要注意的是,以上目录是指Linux内核的主线驱动程序目录结构。对于特定的内核版本或特定的Linux发行版,可能会存在一些小的差异。因此,在查找驱动程序代码时,最好根据具体的内核版本或系统来进行查找。 ### 回答3: 在Linux内核中,SD卡、eMMC和UFS的驱动代码可以在以下几个目录下找到: 1. SD卡驱动代码:可以在目录/drivers/mmc/host/下找到SD卡的驱动代码。主要文件包括: - sdhci.c: SD卡主机控制器(SD Host Controller)的驱动代码。 - mmc_block.c: SD卡块设备驱动代码,用于提供SD卡的块设备访问接口。 2. eMMC驱动代码:可以在目录/drivers/mmc/host/下找到eMMC驱动代码。主要文件包括: - sdhci.c: eMMC主机控制器(eMMC Host Controller)的驱动代码。 - mmc_block.c: eMMC块设备驱动代码,用于提供eMMC的块设备访问接口。 3. UFS驱动代码:可以在目录/drivers/scsi/ufs/下找到UFS驱动代码。主要文件包括: - ufs.h: UFS驱动的头文件。 - ufs-scsi.c: UFS SCSI驱动代码。 - ufs-qp.c: UFS队列处理驱动代码。 这些驱动代码文件包含了与SD卡、eMMC和UFS硬件交互的相关函数和数据结构,用于在Linux内核中实现对这些存储设备的访问和控制。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值