SCSI 中间层 - 底层驱动程序接口

简介

本文档概述了 Linux SCSI 中间层和 SCSI 底层驱动程序之间的接口。底层驱动程序 (LLD) 有时被称为主机总线适配器 (HBA) 驱动程序和主机驱动程序 (HD)。在这种上下文中,“主机”是计算机 IO 总线(例如 PCI 或 ISA)与 SCSI 传输上的单个 SCSI 发起程序端口之间的桥梁。“发起程序”端口(SCSI 术语,参见 SAM-3,网址为 http://www.t10.org)将 SCSI 命令发送到“目标”SCSI 端口(例如磁盘)。在运行的系统中可以有许多 LLD,但每个硬件类型只有一个。大多数 LLD 可以控制一个或多个 SCSI HBA。一些 HBA 包含多个主机。

在某些情况下,SCSI 传输是已经具有其自己在 Linux 中的子系统的外部总线(例如 USB 和 ieee1394)。在这种情况下,SCSI 子系统 LLD 是到另一个驱动程序子系统的软件桥梁。示例包括 usb-storage 驱动程序(位于 drivers/usb/storage 目录中)和 ieee1394/sbp2 驱动程序(位于 drivers/ieee1394 目录中)。

例如,aic7xxx LLD 控制基于该公司 7xxx 芯片系列的 Adaptec SCSI 并行接口 (SPI) 控制器。aic7xxx LLD 可以构建到内核中或作为模块加载。在 Linux 系统中只能运行一个 aic7xxx LLD,但它可以控制许多 HBA。这些 HBA 可能位于 PCI 子板上或构建到主板中(或两者兼有)。一些基于 aic7xxx 的 HBA 是双控制器,因此代表两个主机。与大多数现代 HBA 一样,每个 aic7xxx 主机都有其自己的 PCI 设备地址。[SCSI 主机和 PCI 设备之间的一对一对应关系很常见,但不是必需的(例如,使用 ISA 适配器)。]

SCSI 中间层将 LLD 与其他层(例如 SCSI 上层驱动程序和块层)隔离开来。

本文档的版本大致匹配 Linux 内核版本 2.6.8 。

文档

内核源代码树中有一个 SCSI 文档目录,通常是 Documentation/scsi 。大多数文档采用 reStructuredText 格式。此文件名为 SCSI 中间层 - 底层驱动程序接口,可以在该目录中找到。本文档的更新副本可以在 https://docs.linuxkernel.org.cn/scsi/scsi_mid_low_api.html 找到。许多 LLD 都在 Documentation/scsi 中进行了文档化(例如,Adaptec Aic7xxx Fast -> Ultra160 Family Manager Set v7.0)。SCSI 中间层在 SCSI 子系统文档 中进行了简要描述,其中包含指向描述 Linux 内核 2.4 系列中的 SCSI 子系统的文档的 URL。两个上层驱动程序在该目录中有文档:SCSI 磁带驱动程序(SCSI 磁带驱动程序)和 SCSI 通用 (sg) 驱动程序(对于 sg 驱动程序)。

LLD 的一些文档(或 URL)可以在 C 源代码中或与 C 源代码相同的目录中找到。例如,要查找有关 USB 大容量存储驱动程序的 URL,请参阅 /usr/src/linux/drivers/usb/storage 目录。

驱动程序结构

传统上,SCSI 子系统的 LLD 至少是 drivers/scsi 目录中的两个文件。例如,名为“xyz”的驱动程序有一个头文件“xyz.h”和一个源文件“xyz.c”。[实际上,没有充分的理由说明这不能都在一个文件中;头文件是多余的。] 移植到多个操作系统的某些驱动程序具有两个以上的文件。例如,aic7xxx 驱动程序具有用于通用和 OS 特定代码(例如 FreeBSD 和 Linux)的单独文件。此类驱动程序往往在 drivers/scsi 目录下有其自己的目录。

将新的 LLD 添加到 Linux 时,需要注意以下文件(位于 drivers/scsi 目录中):Makefile 和 Kconfig 。最好研究现有 LLD 的组织方式。

随着 2.5 系列开发内核演变为 2.6 系列生产系列,此接口正在引入更改。一个例子是驱动程序初始化代码,现在有两种可用的模型。较旧的一种,类似于 Linux 2.4 系列中发现的,基于在 HBA 驱动程序加载时检测到的主机。这被称为“被动”初始化模型。较新的模型允许在 LLD 的生命周期内热插拔(和拔出)HBA,这被称为“热插拔”初始化模型。较新的模型是首选,因为它既可以处理永久连接的传统 SCSI 设备,也可以处理热插拔的现代“SCSI”设备(例如,USB 或 IEEE 1394 连接的数码相机)。以下各节将讨论这两种初始化模型。

LLD 通过以下几种方式与 SCSI 子系统接口

  1. 直接调用由中间层提供的函数

  2. 将一组函数指针传递给由中间层提供的注册函数。然后,中间层将在未来的某个时间点调用这些函数。LLD 将提供这些函数的实现。

  3. 直接访问由中间层维护的众所周知的数据结构的实例

a) 组中的那些函数在下面标题为“中间层提供的函数”的部分中列出。

b) 组中的那些函数在下面标题为“接口函数”的部分中列出。它们的函数指针放置在“struct scsi_host_template”的成员中,该实例传递给 scsi_host_alloc()。LLD 不希望提供的那些接口函数应在 struct scsi_host_template 的相应成员中放置 NULL。在文件范围内定义 struct scsi_host_template 的实例会导致将 NULL 放置在未显式初始化的函数指针成员中。

c) 组中的那些用法应谨慎处理,尤其是在“热插拔”环境中。LLD 应该了解与中间层和其他层共享的实例的生命周期。

在 LLD 中定义的所有函数和在文件范围内定义的所有数据都应该是静态的。例如,在名为“xxx”的 LLD 中的 sdev_init() 函数可以定义为 static int xxx_sdev_init(struct scsi_device * sdev) { /* code */ }

热插拔初始化模型

在此模型中,LLD 控制何时将 SCSI 主机引入 SCSI 子系统以及何时从 SCSI 子系统中删除 SCSI 主机。主机可以最早在驱动程序初始化时引入,最迟在驱动程序关闭时删除。通常,驱动程序将响应 sysfs probe() 回调,该回调指示已检测到 HBA。在确认新设备是 LLD 想要控制的设备后,LLD 将初始化 HBA,然后向 SCSI 中间层注册新的主机。

在 LLD 初始化期间,驱动程序应向其期望找到 HBA(s) 的适当 IO 总线(例如 PCI 总线)注册自己。这可能可以通过 sysfs 完成。任何驱动程序参数(尤其是那些在驱动程序加载后可写的参数)也可以在此处向 sysfs 注册。SCSI 中间层首次意识到 LLD 是在该 LLD 注册其第一个 HBA 时。

在稍后的某个时间,LLD 意识到 HBA,接下来是 LLD 和中间层之间的一系列典型调用。此示例显示了中间层扫描新引入的 HBA 以查找 3 个 scsi 设备,其中只有前 2 个响应

    HBA PROBE: assume 2 SCSI devices found in scan
LLD                   mid level                    LLD
===-------------------=========--------------------===------
scsi_host_alloc()  -->
scsi_add_host()  ---->
scsi_scan_host()  -------+
                        |
                    sdev_init()
                    sdev_configure() -->  scsi_change_queue_depth()
                        |
                    sdev_init()
                    sdev_configure()
                        |
                    sdev_init()   ***
                    sdev_destroy() ***


*** For scsi devices that the mid level tries to scan but do not
    respond, a sdev_init(), sdev_destroy() pair is called.

如果 LLD 想要调整默认队列设置,它可以在其 sdev_configure() 例程中调用 scsi_change_queue_depth()

卸载 LLD 模块时(例如使用“rmmod”命令)或响应 sysfs() 的 remove() 回调被调用时指示的“热拔插”时,删除 HBA 可能是作为有序关闭的一部分。在任一情况下,序列都是相同的

        HBA REMOVE: assume 2 SCSI devices attached
LLD                      mid level                 LLD
===----------------------=========-----------------===------
scsi_remove_host() ---------+
                            |
                    sdev_destroy()
                    sdev_destroy()
scsi_host_put()

LLD 跟踪 struct Scsi_Host 实例(指针由 scsi_host_alloc() 返回)可能很有用。此类实例由中间层“拥有”。struct Scsi_Host 实例从 scsi_host_put() 释放,当引用计数达到零时。

热拔插控制磁盘的 HBA,该磁盘正在已挂载的文件系统上处理 SCSI 命令,这是一个有趣的情况。引用计数逻辑正在引入中间层,以处理涉及的许多问题。请参阅下面的关于引用计数的部分。

热插拔概念可以扩展到 SCSI 设备。当前,添加 HBA 时,scsi_scan_host() 函数会导致扫描连接到 HBA 的 SCSI 传输的 SCSI 设备。在较新的 SCSI 传输上,HBA 可能会在扫描完成后 _之后_ 意识到新的 SCSI 设备。LLD 可以使用此序列使中间层意识到 SCSI 设备

                SCSI DEVICE hotplug
LLD                   mid level                    LLD
===-------------------=========--------------------===------
scsi_add_device()  ------+
                        |
                    sdev_init()
                    sdev_configure()   [--> scsi_change_queue_depth()]

以类似的方式,LLD 可能会意识到 SCSI 设备已被删除(拔出)或与其的连接已中断。某些现有 SCSI 传输(例如 SPI)可能不会意识到 SCSI 设备已被删除,直到后续 SCSI 命令失败,这可能会导致该设备被中间层设置为脱机。检测到 SCSI 设备删除的 LLD 可以使用以下序列从上层启动其删除

                SCSI DEVICE hot unplug
LLD                      mid level                 LLD
===----------------------=========-----------------===------
scsi_remove_device() -------+
                            |
                    sdev_destroy()

LLD 跟踪 struct scsi_device 实例(指针作为参数传递给 sdev_init() 和 sdev_configure() 回调)可能很有用。此类实例由中间层“拥有”。struct scsi_device 实例在 sdev_destroy() 之后释放。

引用计数

Scsi_Host 结构已添加了引用计数基础结构。这有效地将 struct Scsi_Host 实例的所有权分布在使用它们的各个 SCSI 层中。以前,此类实例完全由中间层拥有。LLD 通常不需要直接操作这些引用计数,但在某些情况下他们可能需要这样做。

有 3 个与 struct Scsi_Host 关联的引用计数函数值得关注

  • scsi_host_alloc()

    返回指向 struct Scsi_Host 的新实例的指针,该实例的引用计数 ^^ 设置为 1

  • scsi_host_get()

    将 1 添加到给定实例的引用计数

  • scsi_host_put()

    从给定实例的引用计数中递减 1。如果引用计数达到 0,则释放给定实例

scsi_device 结构已添加了引用计数基础结构。这有效地将 struct scsi_device 实例的所有权分布在使用它们的各个 SCSI 层中。以前,此类实例完全由中间层拥有。请参阅 include/scsi/scsi_device.h 结尾处声明的访问函数。如果 LLD 想要保留指向 scsi_device 实例的指针副本,它应该使用 scsi_device_get() 来增加其引用计数。完成指针后,它可以使用 scsi_device_put() 来递减其引用计数(并可能删除它)。

注意

struct Scsi_Host 实际上有 2 个引用计数,这些函数并行操作这些引用计数。

约定

首先,可以在 Linux 内核编码风格 文件中找到 Linus Torvalds 关于 C 编码风格的想法。

此外,鼓励大多数 C99 增强功能,只要它们得到相关 gcc 编译器的支持。因此,在适当的情况下,鼓励使用 C99 样式结构和数组初始化程序。不要走得太远,VLA 尚未得到适当的支持。此规则的一个例外是使用 // 样式注释;在 Linux 中仍然首选 /*...*/ 注释。

写得好、经过测试和文档化的代码,无需重新格式化以符合上述约定。例如,aic7xxx 驱动程序来自 FreeBSD 和 Adaptec 自己的实验室。毫无疑问,FreeBSD 和 Adaptec 都有其自己的编码约定。

中间层提供的函数

这些函数由 SCSI 中间层提供,供 LLD 使用。这些函数的名称(即入口点)已导出,因此作为模块的 LLD 可以访问它们。内核将安排在初始化任何 LLD 之前加载和初始化 SCSI 中间层。以下函数按字母顺序列出,它们的名称都以 scsi_ 开头。

摘要

  • scsi_add_device - 创建新的 scsi 设备 (lu) 实例

  • scsi_add_host - 执行 sysfs 注册并设置传输类

  • scsi_change_queue_depth - 更改 SCSI 设备上的队列深度

  • scsi_bios_ptable - 返回块设备分区表的副本

  • scsi_block_requests - 阻止进一步的命令排队到给定主机

  • scsi_host_alloc - 返回新的 scsi_host 实例,其 refcount==1

  • scsi_host_get - 递增 Scsi_Host 实例的 refcount

  • scsi_host_put - 递减 Scsi_Host 实例的 refcount(如果为 0,则释放)

  • scsi_remove_device - 分离并删除 SCSI 设备

  • scsi_remove_host - 分离并删除主机拥有的所有 SCSI 设备

  • scsi_report_bus_reset - 报告观察到的 scsi _bus_ 复位

  • scsi_scan_host - 扫描 SCSI 总线

  • scsi_track_queue_full - 跟踪连续的 QUEUE_FULL 事件

  • scsi_unblock_requests - 允许将进一步的命令排队到给定主机

详细信息

/**
* scsi_add_device - creates new scsi device (lu) instance
* @shost:   pointer to scsi host instance
* @channel: channel number (rarely other than 0)
* @id:      target id number
* @lun:     logical unit number
*
*      Returns pointer to new struct scsi_device instance or
*      ERR_PTR(-ENODEV) (or some other bent pointer) if something is
*      wrong (e.g. no lu responds at given address)
*
*      Might block: yes
*
*      Notes: This call is usually performed internally during a scsi
*      bus scan when an HBA is added (i.e. scsi_scan_host()). So it
*      should only be called if the HBA becomes aware of a new scsi
*      device (lu) after scsi_scan_host() has completed. If successful
*      this call can lead to sdev_init() and sdev_configure() callbacks
*      into the LLD.
*
*      Defined in: drivers/scsi/scsi_scan.c
**/
struct scsi_device * scsi_add_device(struct Scsi_Host *shost,
                                    unsigned int channel,
                                    unsigned int id, unsigned int lun)


/**
* scsi_add_host - perform sysfs registration and set up transport class
* @shost:   pointer to scsi host instance
* @dev:     pointer to struct device of type scsi class
*
*      Returns 0 on success, negative errno of failure (e.g. -ENOMEM)
*
*      Might block: no
*
*      Notes: Only required in "hotplug initialization model" after a
*      successful call to scsi_host_alloc().  This function does not
*   scan the bus; this can be done by calling scsi_scan_host() or
*   in some other transport-specific way.  The LLD must set up
*   the transport template before calling this function and may only
*   access the transport class data after this function has been called.
*
*      Defined in: drivers/scsi/hosts.c
**/
int scsi_add_host(struct Scsi_Host *shost, struct device * dev)


/**
* scsi_change_queue_depth - allow LLD to change queue depth on a SCSI device
* @sdev:       pointer to SCSI device to change queue depth on
* @tags        Number of tags allowed if tagged queuing enabled,
*              or number of commands the LLD can queue up
*              in non-tagged mode (as per cmd_per_lun).
*
*      Returns nothing
*
*      Might block: no
*
*      Notes: Can be invoked any time on a SCSI device controlled by this
*      LLD. [Specifically during and after sdev_configure() and prior to
*      sdev_destroy().] Can safely be invoked from interrupt code.
*
*      Defined in: drivers/scsi/scsi.c [see source code for more notes]
*
**/
int scsi_change_queue_depth(struct scsi_device *sdev, int tags)


/**
* scsi_bios_ptable - return copy of block device's partition table
* @dev:        pointer to block device
*
*      Returns pointer to partition table, or NULL for failure
*
*      Might block: yes
*
*      Notes: Caller owns memory returned (free with kfree() )
*
*      Defined in: drivers/scsi/scsicam.c
**/
unsigned char *scsi_bios_ptable(struct block_device *dev)


/**
* scsi_block_requests - prevent further commands being queued to given host
*
* @shost: pointer to host to block commands on
*
*      Returns nothing
*
*      Might block: no
*
*      Notes: There is no timer nor any other means by which the requests
*      get unblocked other than the LLD calling scsi_unblock_requests().
*
*      Defined in: drivers/scsi/scsi_lib.c
**/
void scsi_block_requests(struct Scsi_Host * shost)


/**
* scsi_host_alloc - create a scsi host adapter instance and perform basic
*                   initialization.
* @sht:        pointer to scsi host template
* @privsize:   extra bytes to allocate in hostdata array (which is the
*              last member of the returned Scsi_Host instance)
*
*      Returns pointer to new Scsi_Host instance or NULL on failure
*
*      Might block: yes
*
*      Notes: When this call returns to the LLD, the SCSI bus scan on
*      this host has _not_ yet been done.
*      The hostdata array (by default zero length) is a per host scratch
*      area for the LLD's exclusive use.
*      Both associated refcounting objects have their refcount set to 1.
*      Full registration (in sysfs) and a bus scan are performed later when
*      scsi_add_host() and scsi_scan_host() are called.
*
*      Defined in: drivers/scsi/hosts.c .
**/
struct Scsi_Host * scsi_host_alloc(const struct scsi_host_template * sht,
                                int privsize)


/**
* scsi_host_get - increment Scsi_Host instance refcount
* @shost:   pointer to struct Scsi_Host instance
*
*      Returns nothing
*
*      Might block: currently may block but may be changed to not block
*
*      Notes: Actually increments the counts in two sub-objects
*
*      Defined in: drivers/scsi/hosts.c
**/
void scsi_host_get(struct Scsi_Host *shost)


/**
* scsi_host_put - decrement Scsi_Host instance refcount, free if 0
* @shost:   pointer to struct Scsi_Host instance
*
*      Returns nothing
*
*      Might block: currently may block but may be changed to not block
*
*      Notes: Actually decrements the counts in two sub-objects. If the
*      latter refcount reaches 0, the Scsi_Host instance is freed.
*      The LLD need not worry exactly when the Scsi_Host instance is
*      freed, it just shouldn't access the instance after it has balanced
*      out its refcount usage.
*
*      Defined in: drivers/scsi/hosts.c
**/
void scsi_host_put(struct Scsi_Host *shost)


/**
* scsi_remove_device - detach and remove a SCSI device
* @sdev:      a pointer to a scsi device instance
*
*      Returns value: 0 on success, -EINVAL if device not attached
*
*      Might block: yes
*
*      Notes: If an LLD becomes aware that a scsi device (lu) has
*      been removed but its host is still present then it can request
*      the removal of that scsi device. If successful this call will
*      lead to the sdev_destroy() callback being invoked. sdev is an
*      invalid pointer after this call.
*
*      Defined in: drivers/scsi/scsi_sysfs.c .
**/
int scsi_remove_device(struct scsi_device *sdev)


/**
* scsi_remove_host - detach and remove all SCSI devices owned by host
* @shost:      a pointer to a scsi host instance
*
*      Returns value: 0 on success, 1 on failure (e.g. LLD busy ??)
*
*      Might block: yes
*
*      Notes: Should only be invoked if the "hotplug initialization
*      model" is being used. It should be called _prior_ to
*      calling scsi_host_put().
*
*      Defined in: drivers/scsi/hosts.c .
**/
int scsi_remove_host(struct Scsi_Host *shost)


/**
* scsi_report_bus_reset - report scsi _bus_ reset observed
* @shost: a pointer to a scsi host involved
* @channel: channel (within) host on which scsi bus reset occurred
*
*      Returns nothing
*
*      Might block: no
*
*      Notes: This only needs to be called if the reset is one which
*      originates from an unknown location.  Resets originated by the
*      mid level itself don't need to call this, but there should be
*      no harm.  The main purpose of this is to make sure that a
*      CHECK_CONDITION is properly treated.
*
*      Defined in: drivers/scsi/scsi_error.c .
**/
void scsi_report_bus_reset(struct Scsi_Host * shost, int channel)


/**
* scsi_scan_host - scan SCSI bus
* @shost: a pointer to a scsi host instance
*
*   Might block: yes
*
*   Notes: Should be called after scsi_add_host()
*
*   Defined in: drivers/scsi/scsi_scan.c
**/
void scsi_scan_host(struct Scsi_Host *shost)


/**
* scsi_track_queue_full - track successive QUEUE_FULL events on given
*                      device to determine if and when there is a need
*                      to adjust the queue depth on the device.
* @sdev:  pointer to SCSI device instance
* @depth: Current number of outstanding SCSI commands on this device,
*         not counting the one returned as QUEUE_FULL.
*
*      Returns 0  - no change needed
*              >0 - adjust queue depth to this new depth
*              -1 - drop back to untagged operation using host->cmd_per_lun
*                   as the untagged command depth
*
*      Might block: no
*
*      Notes: LLDs may call this at any time and we will do "The Right
*              Thing"; interrupt context safe.
*
*      Defined in: drivers/scsi/scsi.c .
**/
int scsi_track_queue_full(struct scsi_device *sdev, int depth)


/**
* scsi_unblock_requests - allow further commands to be queued to given host
*
* @shost: pointer to host to unblock commands on
*
*      Returns nothing
*
*      Might block: no
*
*      Defined in: drivers/scsi/scsi_lib.c .
**/
void scsi_unblock_requests(struct Scsi_Host * shost)

接口函数

接口函数由 LLD 提供(定义),它们的函数指针放置在 struct scsi_host_template 的实例中,该实例传递给 scsi_host_alloc()。有些是强制性的。接口函数应声明为静态。接受的约定是驱动程序“xyz”将其 sdev_configure() 函数声明为

static int xyz_sdev_configure(struct scsi_device * sdev);

对于下面列出的所有接口函数都是如此。

指向此函数的指针应放置在“struct scsi_host_template”实例的“sdev_configure”成员中。指向此类实例的指针应传递给中间层的 scsi_host_alloc()。。

接口函数也在 include/scsi/scsi_host.h 文件中,紧靠它们在“struct scsi_host_template”中的定义点之上进行描述。在某些情况下,scsi_host.h 中提供的细节比下面的细节更多。

接口函数在下面按字母顺序列出。

摘要

  • bios_param - 获取磁盘的磁头、扇区、柱面信息

  • eh_timed_out - 通知主机命令计时器已过期

  • eh_abort_handler - 中止给定的命令

  • eh_bus_reset_handler - 发出 SCSI 总线复位

  • eh_device_reset_handler - 发出 SCSI 设备复位

  • eh_host_reset_handler - 复位主机(主机总线适配器)

  • info - 提供有关给定主机的信息

  • ioctl - 驱动程序可以响应 ioctl

  • proc_info - 支持 /proc/scsi/{driver_name}/{host_no}

  • queuecommand - 队列 scsi 命令,在完成时调用“done”

  • sdev_init - 在将任何命令发送到新设备之前

  • sdev_configure - 驱动程序在连接后微调给定的设备

  • sdev_destroy - 给定的设备即将关闭

详细信息

/**
*      bios_param - fetch head, sector, cylinder info for a disk
*      @sdev: pointer to scsi device context (defined in
*             include/scsi/scsi_device.h)
*      @bdev: pointer to block device context (defined in fs.h)
*      @capacity:  device size (in 512 byte sectors)
*      @params: three element array to place output:
*              params[0] number of heads (max 255)
*              params[1] number of sectors (max 63)
*              params[2] number of cylinders
*
*      Return value is ignored
*
*      Locks: none
*
*      Calling context: process (sd)
*
*      Notes: an arbitrary geometry (based on READ CAPACITY) is used
*      if this function is not provided. The params array is
*      pre-initialized with made up values just in case this function
*      doesn't output anything.
*
*      Optionally defined in: LLD
**/
    int bios_param(struct scsi_device * sdev, struct block_device *bdev,
                sector_t capacity, int params[3])


/**
*      eh_timed_out - The timer for the command has just fired
*      @scp: identifies command timing out
*
*      Returns:
*
*      EH_HANDLED:             I fixed the error, please complete the command
*      EH_RESET_TIMER:         I need more time, reset the timer and
*                              begin counting again
*      EH_NOT_HANDLED          Begin normal error recovery
*
*
*      Locks: None held
*
*      Calling context: interrupt
*
*      Notes: This is to give the LLD an opportunity to do local recovery.
*      This recovery is limited to determining if the outstanding command
*      will ever complete.  You may not abort and restart the command from
*      this callback.
*
*      Optionally defined in: LLD
**/
    int eh_timed_out(struct scsi_cmnd * scp)


/**
*      eh_abort_handler - abort command associated with scp
*      @scp: identifies command to be aborted
*
*      Returns SUCCESS if command aborted else FAILED
*
*      Locks: None held
*
*      Calling context: kernel thread
*
*      Notes: This is called only for a command that has timed out.
*
*      Optionally defined in: LLD
**/
    int eh_abort_handler(struct scsi_cmnd * scp)


/**
*      eh_bus_reset_handler - issue SCSI bus reset
*      @scp: SCSI bus that contains this device should be reset
*
*      Returns SUCCESS if command aborted else FAILED
*
*      Locks: None held
*
*      Calling context: kernel thread
*
*      Notes: Invoked from scsi_eh thread. No other commands will be
*      queued on current host during eh.
*
*      Optionally defined in: LLD
**/
    int eh_bus_reset_handler(struct scsi_cmnd * scp)


/**
*      eh_device_reset_handler - issue SCSI device reset
*      @scp: identifies SCSI device to be reset
*
*      Returns SUCCESS if command aborted else FAILED
*
*      Locks: None held
*
*      Calling context: kernel thread
*
*      Notes: Invoked from scsi_eh thread. No other commands will be
*      queued on current host during eh.
*
*      Optionally defined in: LLD
**/
    int eh_device_reset_handler(struct scsi_cmnd * scp)


/**
*      eh_host_reset_handler - reset host (host bus adapter)
*      @scp: SCSI host that contains this device should be reset
*
*      Returns SUCCESS if command aborted else FAILED
*
*      Locks: None held
*
*      Calling context: kernel thread
*
*      Notes: Invoked from scsi_eh thread. No other commands will be
*      queued on current host during eh.
*      With the default eh_strategy in place, if none of the _abort_,
*      _device_reset_, _bus_reset_ or this eh handler function are
*      defined (or they all return FAILED) then the device in question
*      will be set offline whenever eh is invoked.
*
*      Optionally defined in: LLD
**/
    int eh_host_reset_handler(struct scsi_cmnd * scp)


/**
*      info - supply information about given host: driver name plus data
*             to distinguish given host
*      @shp: host to supply information about
*
*      Return ASCII null terminated string. [This driver is assumed to
*      manage the memory pointed to and maintain it, typically for the
*      lifetime of this host.]
*
*      Locks: none
*
*      Calling context: process
*
*      Notes: Often supplies PCI or ISA information such as IO addresses
*      and interrupt numbers. If not supplied struct Scsi_Host::name used
*      instead. It is assumed the returned information fits on one line
*      (i.e. does not included embedded newlines).
*      The SCSI_IOCTL_PROBE_HOST ioctl yields the string returned by this
*      function (or struct Scsi_Host::name if this function is not
*      available).
*
*      Optionally defined in: LLD
**/
    const char * info(struct Scsi_Host * shp)


/**
*      ioctl - driver can respond to ioctls
*      @sdp: device that ioctl was issued for
*      @cmd: ioctl number
*      @arg: pointer to read or write data from. Since it points to
*            user space, should use appropriate kernel functions
*            (e.g. copy_from_user() ). In the Unix style this argument
*            can also be viewed as an unsigned long.
*
*      Returns negative "errno" value when there is a problem. 0 or a
*      positive value indicates success and is returned to the user space.
*
*      Locks: none
*
*      Calling context: process
*
*      Notes: The SCSI subsystem uses a "trickle down" ioctl model.
*      The user issues an ioctl() against an upper level driver
*      (e.g. /dev/sdc) and if the upper level driver doesn't recognize
*      the 'cmd' then it is passed to the SCSI mid level. If the SCSI
*      mid level does not recognize it, then the LLD that controls
*      the device receives the ioctl. According to recent Unix standards
*      unsupported ioctl() 'cmd' numbers should return -ENOTTY.
*
*      Optionally defined in: LLD
**/
    int ioctl(struct scsi_device *sdp, int cmd, void *arg)


/**
*      proc_info - supports /proc/scsi/{driver_name}/{host_no}
*      @buffer: anchor point to output to (0==writeto1_read0) or fetch from
*               (1==writeto1_read0).
*      @start: where "interesting" data is written to. Ignored when
*              1==writeto1_read0.
*      @offset: offset within buffer 0==writeto1_read0 is actually
*               interested in. Ignored when 1==writeto1_read0 .
*      @length: maximum (or actual) extent of buffer
*      @host_no: host number of interest (struct Scsi_Host::host_no)
*      @writeto1_read0: 1 -> data coming from user space towards driver
*                            (e.g. "echo some_string > /proc/scsi/xyz/2")
*                       0 -> user what data from this driver
*                            (e.g. "cat /proc/scsi/xyz/2")
*
*      Returns length when 1==writeto1_read0. Otherwise number of chars
*      output to buffer past offset.
*
*      Locks: none held
*
*      Calling context: process
*
*      Notes: Driven from scsi_proc.c which interfaces to proc_fs. proc_fs
*      support can now be configured out of the scsi subsystem.
*
*      Optionally defined in: LLD
**/
    int proc_info(char * buffer, char ** start, off_t offset,
                int length, int host_no, int writeto1_read0)


/**
*      queuecommand - queue scsi command, invoke scp->scsi_done on completion
*      @shost: pointer to the scsi host object
*      @scp: pointer to scsi command object
*
*      Returns 0 on success.
*
*      If there's a failure, return either:
*
*      SCSI_MLQUEUE_DEVICE_BUSY if the device queue is full, or
*      SCSI_MLQUEUE_HOST_BUSY if the entire host queue is full
*
*      On both of these returns, the mid-layer will requeue the I/O
*
*      - if the return is SCSI_MLQUEUE_DEVICE_BUSY, only that particular
*      device will be paused, and it will be unpaused when a command to
*      the device returns (or after a brief delay if there are no more
*      outstanding commands to it).  Commands to other devices continue
*      to be processed normally.
*
*      - if the return is SCSI_MLQUEUE_HOST_BUSY, all I/O to the host
*      is paused and will be unpaused when any command returns from
*      the host (or after a brief delay if there are no outstanding
*      commands to the host).
*
*      For compatibility with earlier versions of queuecommand, any
*      other return value is treated the same as
*      SCSI_MLQUEUE_HOST_BUSY.
*
*      Other types of errors that are detected immediately may be
*      flagged by setting scp->result to an appropriate value,
*      invoking the scp->scsi_done callback, and then returning 0
*      from this function. If the command is not performed
*      immediately (and the LLD is starting (or will start) the given
*      command) then this function should place 0 in scp->result and
*      return 0.
*
*      Command ownership.  If the driver returns zero, it owns the
*      command and must take responsibility for ensuring the
*      scp->scsi_done callback is executed.  Note: the driver may
*      call scp->scsi_done before returning zero, but after it has
*      called scp->scsi_done, it may not return any value other than
*      zero.  If the driver makes a non-zero return, it must not
*      execute the command's scsi_done callback at any time.
*
*      Locks: up to and including 2.6.36, struct Scsi_Host::host_lock
*             held on entry (with "irqsave") and is expected to be
*             held on return. From 2.6.37 onwards, queuecommand is
*             called without any locks held.
*
*      Calling context: in interrupt (soft irq) or process context
*
*      Notes: This function should be relatively fast. Normally it
*      will not wait for IO to complete. Hence the scp->scsi_done
*      callback is invoked (often directly from an interrupt service
*      routine) some time after this function has returned. In some
*      cases (e.g. pseudo adapter drivers that manufacture the
*      response to a SCSI INQUIRY) the scp->scsi_done callback may be
*      invoked before this function returns.  If the scp->scsi_done
*      callback is not invoked within a certain period the SCSI mid
*      level will commence error processing.  If a status of CHECK
*      CONDITION is placed in "result" when the scp->scsi_done
*      callback is invoked, then the LLD driver should perform
*      autosense and fill in the struct scsi_cmnd::sense_buffer
*      array. The scsi_cmnd::sense_buffer array is zeroed prior to
*      the mid level queuing a command to an LLD.
*
*      Defined in: LLD
**/
    int queuecommand(struct Scsi_Host *shost, struct scsi_cmnd * scp)


/**
*      sdev_init -   prior to any commands being sent to a new device
*                      (i.e. just prior to scan) this call is made
*      @sdp: pointer to new device (about to be scanned)
*
*      Returns 0 if ok. Any other return is assumed to be an error and
*      the device is ignored.
*
*      Locks: none
*
*      Calling context: process
*
*      Notes: Allows the driver to allocate any resources for a device
*      prior to its initial scan. The corresponding scsi device may not
*      exist but the mid level is just about to scan for it (i.e. send
*      and INQUIRY command plus ...). If a device is found then
*      sdev_configure() will be called while if a device is not found
*      sdev_destroy() is called.
*      For more details see the include/scsi/scsi_host.h file.
*
*      Optionally defined in: LLD
**/
    int sdev_init(struct scsi_device *sdp)


/**
*      sdev_configure - driver fine tuning for given device just after it
*                     has been first scanned (i.e. it responded to an
*                     INQUIRY)
*      @sdp: device that has just been attached
*
*      Returns 0 if ok. Any other return is assumed to be an error and
*      the device is taken offline. [offline devices will _not_ have
*      sdev_destroy() called on them so clean up resources.]
*
*      Locks: none
*
*      Calling context: process
*
*      Notes: Allows the driver to inspect the response to the initial
*      INQUIRY done by the scanning code and take appropriate action.
*      For more details see the include/scsi/scsi_host.h file.
*
*      Optionally defined in: LLD
**/
    int sdev_configure(struct scsi_device *sdp)


/**
*      sdev_destroy - given device is about to be shut down. All
*                      activity has ceased on this device.
*      @sdp: device that is about to be shut down
*
*      Returns nothing
*
*      Locks: none
*
*      Calling context: process
*
*      Notes: Mid level structures for given device are still in place
*      but are about to be torn down. Any per device resources allocated
*      by this driver for given device should be freed now. No further
*      commands will be sent for this sdp instance. [However the device
*      could be re-attached in the future in which case a new instance
*      of struct scsi_device would be supplied by future sdev_init()
*      and sdev_configure() calls.]
*
*      Optionally defined in: LLD
**/
    void sdev_destroy(struct scsi_device *sdp)

数据结构

struct scsi_host_template

每个 LLD 只有一个“struct scsi_host_template”实例 [1]。它通常在驱动程序的头文件中初始化为文件范围静态。这样,未显式初始化的成员将被设置为 0 或 NULL。感兴趣的成员

name
  • 驱动程序的名称(可能包含空格,请限制为少于 80 个字符)

proc_name
  • 在“/proc/scsi/<proc_name>/<host_no>”中使用,并由 sysfs 在其“驱动程序”目录之一中使用的名称。因此,“proc_name”应仅包含 Unix 文件名可以接受的字符。

(*queuecommand)()
  • 中间层用于将 SCSI 命令注入到 LLD 中的主要回调。

vendor_id
  • 标识为 Scsi_Host 提供 LLD 的供应商的唯一值。最常用于验证供应商特定的消息请求。值由标识符类型和供应商特定的值组成。有关有效格式的描述,请参阅 scsi_netlink.h。

该结构在 include/scsi/scsi_host.h 中定义和注释

struct Scsi_Host

每个 LLD 控制的主机 (HBA) 都有一个 struct Scsi_Host 实例。struct Scsi_Host 结构与“struct scsi_host_template”有许多共同的成员。创建新的 struct Scsi_Host 实例时(在 hosts.c 中的 scsi_host_alloc() 中),这些通用成员从驱动程序的 struct scsi_host_template 实例初始化。感兴趣的成员

host_no
  • 用于标识此主机的系统范围的唯一编号。从 0 开始按升序发出。

can_queue
  • 必须大于 0;不要向适配器发送超过 can_queue 个命令。

this_id
  • 主机的 scsi id(scsi 发起程序),如果未知,则为 -1

sg_tablesize
  • 主机允许的最大散布收集元素。将此设置为 SG_ALL 或更小,以避免链接的 SG 列表。必须至少为 1。

max_sectors
  • 单个 SCSI 命令中允许的最大扇区数(通常为 512 字节)。默认值 0 导致设置为 SCSI_DEFAULT_MAX_SECTORS(在 scsi_host.h 中定义),当前设置为 1024。因此,对于磁盘,当未定义 max_sectors 时,最大传输大小为 512 KB。请注意,此大小可能不足以用于磁盘固件上传。

cmd_per_lun
hostt
  • 指向驱动程序的 struct scsi_host_template 的指针,该 struct Scsi_Host 实例由此生成

hostt->proc_name
  • LLD 的名称。这是 sysfs 使用的驱动程序名称。

transportt
  • 指向驱动程序的 struct scsi_transport_template 实例的指针(如果有)。当前支持 FC 和 SPI 传输。

hostdata[0]
  • struct Scsi_Host 结尾处为 LLD 保留的区域。大小由传递给 scsi_host_alloc() 的第二个参数(名为“privsize”)设置。

scsi_host 结构在 include/scsi/scsi_host.h 中定义

struct scsi_device

通常,每个主机上的每个 SCSI 逻辑单元都有此结构的一个实例。连接到主机的 SCSI 设备由通道号、目标 id 和逻辑单元号 (lun) 唯一标识。该结构在 include/scsi/scsi_device.h 中定义

struct scsi_cmnd

此结构的实例将 SCSI 命令传递给 LLD,并将响应传递回中间层。SCSI 中间层将确保针对 LLD 排队的 SCSI 命令不超过 scsi_change_queue_depth()(或 struct Scsi_Host::cmd_per_lun)指示的数量。每个 SCSI 设备至少有一个 struct scsi_cmnd 实例可用。感兴趣的成员

cmnd
  • 包含 SCSI 命令的数组

cmd_len
  • SCSI 命令的长度(以字节为单位)

sc_data_direction
  • 数据阶段中数据传输的方向。请参阅 include/linux/dma-mapping.h 中的“enum dma_data_direction”

result
  • 应由 LLD 在调用“done”之前设置。值为 0 表示命令已成功完成(并且所有数据(如果有)已传输到或从 SCSI 目标设备传输)。“result”是一个 32 位无符号整数,可以视为 2 个相关的字节。SCSI 状态值位于 LSB 中。请参阅 include/scsi/scsi.h status_byte() 和 host_byte() 宏以及相关的常量。

sense_buffer
  • 一个数组(最大大小:SCSI_SENSE_BUFFERSIZE 字节),当 SCSI 状态(“result”的 LSB)设置为 CHECK_CONDITION (2) 时应写入。设置 CHECK_CONDITION 时,如果 sense_buffer[0] 的顶部半字节的值为 7,则中间层将假定 sense_buffer 数组包含有效的 SCSI 感知缓冲区;否则,中间层将发出 REQUEST_SENSE SCSI 命令以检索感知缓冲区。后一种策略在命令排队的情况下容易出错,因此 LLD 应始终“自动感知”。

device
  • 指向与此命令关联的 scsi_device 对象的指针。

resid_len(通过调用 scsi_set_resid() / scsi_get_resid() 访问)
  • LLD 应将此无符号整数设置为请求的传输长度(即“request_bufflen”),减去实际传输的字节数。“resid_len”预设为 0,因此如果 LLD 无法检测到欠载,则可以忽略它(不应报告溢出)。LLD 应在调用“done”之前设置“resid_len”。最有趣的情况是来自 SCSI 目标设备(例如 READ)的数据传输,这些数据传输欠载。

underflow
  • 如果实际传输的字节数小于此值,LLD 应将 (DID_ERROR << 16) 放入“result”中。 许多 LLD 没有实现此检查,一些实现了的 LLD 只是将错误消息输出到日志,而不是报告 DID_ERROR。 最好让 LLD 实现 'resid_len'。

建议 LLD 在从 SCSI 目标设备传输数据(例如,READ)时设置 'resid_len'。 当此类数据传输的 sense key 为 MEDIUM ERROR 和 HARDWARE ERROR(以及可能的 RECOVERED ERROR)时,设置 'resid_len' 尤为重要。 在这些情况下,如果 LLD 不确定已接收到多少数据,最安全的方法是指示未接收到任何字节。 例如:要指示未接收到任何有效数据,LLD 可以使用以下助手

scsi_set_resid(SCpnt, scsi_bufflen(SCpnt));

其中 'SCpnt' 是指向 scsi_cmnd 对象的指针。 要指示仅接收到三个 512 字节的块,可以这样设置 'resid_len'

scsi_set_resid(SCpnt, scsi_bufflen(SCpnt) - (3 * 512));

scsi_cmnd 结构定义在 include/scsi/scsi_cmnd.h 中

每个 struct Scsi_Host 实例都有一个 spin_lock,名为 struct Scsi_Host::default_lock,它在 scsi_host_alloc() [位于 hosts.c 中] 中初始化。 在同一函数中,struct Scsi_Host::host_lock 指针被初始化为指向 default_lock。 此后,中间层执行的锁定和解锁操作使用 struct Scsi_Host::host_lock 指针。 以前,驱动程序可以覆盖 host_lock 指针,但现在不允许这样做。

自动请求感应数据 (Autosense)

Autosense(或 auto-sense)在 SAM-2 文档中定义为“当状态为 CHECK CONDITION 时,与 SCSI 命令完成同时自动将感应数据返回给应用程序客户端”。 LLD 应该执行 autosense。 当 LLD 通过以下任一方式检测到 CHECK CONDITION 状态时,应执行此操作:

  1. 指示 SCSI 协议(例如,SCSI 并行接口 (SPI))在此类响应上执行额外的数据输入阶段

  2. 或者,LLD 本身发出 REQUEST SENSE 命令

无论哪种方式,当检测到 CHECK CONDITION 状态时,中间层通过检查 struct scsi_cmnd::sense_buffer[0] 来确定 LLD 是否已执行 autosense。 如果此字节的高位 nibble 为 7(或 0xf),则假定已执行 autosense。 如果它有另一个值(并且此字节在每个命令之前初始化为 0),则中间层将发出 REQUEST SENSE 命令。

在存在排队命令的情况下,维护从失败命令到后续 REQUEST SENSE 的感应缓冲区数据的“nexus”可能会失去同步。 这就是为什么最好让 LLD 执行 autosense。

自 Linux 内核 2.4 系列以来的变化

io_request_lock 已被几个更细粒度的锁取代。 与 LLD 相关的锁是 struct Scsi_Host::host_lock,每个 SCSI host 有一个。

旧的错误处理机制已被删除。 这意味着 LLD 接口函数 abort() 和 reset() 已被删除。 struct scsi_host_template::use_new_eh_code 标志已被删除。

在 2.4 系列中,SCSI 子系统的配置描述与 Documentation/Configure.help 文件中所有其他 Linux 子系统的配置描述聚合在一起。 在 2.6 系列中,SCSI 子系统现在有自己的(小得多的)drivers/scsi/Kconfig 文件,其中包含配置和帮助信息。

struct SHT 已重命名为 struct scsi_host_template。

增加了“热插拔初始化模型”和许多额外的函数来支持它。

鸣谢

以下人员为本文档做出了贡献

  • Mike Anderson <andmike at us dot ibm dot com>

  • James Bottomley <James dot Bottomley at hansenpartnership dot com>

  • Patrick Mansfield <patmans at us dot ibm dot com>

  • Christoph Hellwig <hch at infradead dot org>

  • Doug Ledford <dledford at redhat dot com>

  • Andries Brouwer <Andries dot Brouwer at cwi dot nl>

  • Randy Dunlap <rdunlap at xenotime dot net>

  • Alan Stern <stern at rowland dot harvard dot edu>

Douglas Gilbert dgilbert at interlog dot com

2004 年 9 月 21 日