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

SCSI分层

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

Linux | SCSI子系统概述和UFS在其中的位置_第1张图片

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

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

Linux SCSI模型

Linux | SCSI子系统概述和UFS在其中的位置_第2张图片

如上图,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中,可以表示为这样的四元组。

举个栗子:

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中,这些数据结构具有以下的关系:

Linux | SCSI子系统概述和UFS在其中的位置_第3张图片

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采用一种多层次的架构设计,如下图:

Linux | SCSI子系统概述和UFS在其中的位置_第4张图片

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从而进行设备级的操作和配置。

Linux | SCSI子系统概述和UFS在其中的位置_第5张图片

例如:

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 物理层,进行实际的数据传输。

Linux | SCSI子系统概述和UFS在其中的位置_第6张图片

UFS系统模型如上图。

UFS在SCSI中

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

Linux | SCSI子系统概述和UFS在其中的位置_第7张图片

图源/参考:

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

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

Linux | SCSI子系统概述和UFS在其中的位置_第8张图片

可以发现,当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;
}

你可能感兴趣的:(linux,运维,服务器)