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

简介

本文档概述了 Linux SCSI 中层和 SCSI 底层驱动程序之间的接口。底层驱动程序(LLD)也称为主机总线适配器(HBA)驱动程序和主机驱动程序(HD)。此处的“主机”是指计算机 IO 总线(例如 PCI 或 ISA)与 SCSI 传输上的单个 SCSI 发起者端口之间的桥梁。“发起者”端口(SCSI 术语,请参阅 http://www.t10.org 上的 SAM-3)将 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 系列管理器集 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 驱动程序为通用代码和操作系统特定代码(例如 FreeBSD 和 Linux)分别设置了文件。此类驱动程序往往在 drivers/scsi 目录下有自己的目录。

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

随着 2.5 系列开发内核演变为 2.6 系列生产系列,此接口中引入了更改。这方面的一个例子是驱动程序初始化代码,现在有两种模型可用。较旧的模型与 lk 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() [1]。LLD 不希望提供的那些接口函数应在 struct scsi_host_template 的相应成员中放置 NULL。在文件作用域中定义 struct scsi_host_template 的实例将导致 NULL 放置在未显式初始化的函数指针成员中。

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

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

热插拔初始化模型

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

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

稍后,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()  -------+
                        |
                    slave_alloc()
                    slave_configure() -->  scsi_change_queue_depth()
                        |
                    slave_alloc()
                    slave_configure()
                        |
                    slave_alloc()   ***
                    slave_destroy() ***


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

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

当删除 HBA 时,它可能是与正在卸载的 LLD 模块关联的有序关闭的一部分(例如,使用“rmmod”命令),也可能是响应于 sysfs() 的 remove() 回调被调用指示的“热插拔”。在这两种情况下,顺序都是相同的

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

对于 LLD(低级驱动程序)来说,跟踪 `struct Scsi_Host` 实例(通过 scsi_host_alloc() 返回一个指针)可能很有用。这些实例由中间层“拥有”。当引用计数归零时,`struct Scsi_Host` 实例会从 scsi_host_put() 中释放。

热插拔一个正在处理已挂载文件系统上 SCSI 命令的磁盘的 HBA(主机总线适配器)是一个有趣的情况。引用计数逻辑正在被引入到中间层,以应对其中涉及的许多问题。请参阅下面关于引用计数的部分。

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

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

类似地,LLD 可能会意识到 SCSI 设备已被移除(拔出)或与其的连接已中断。一些现有的 SCSI 传输(例如 SPI)可能直到后续的 SCSI 命令失败才会意识到 SCSI 设备已被移除,这可能会导致该设备被中间层设置为离线。检测到 SCSI 设备移除的 LLD 可以通过此序列发起将其从上层移除。

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

对于 LLD 来说,跟踪 `struct scsi_device` 实例(一个指针作为参数传递给 `slave_alloc()` 和 `slave_configure()` 回调)可能很有用。这些实例由中间层“拥有”。`struct scsi_device` 实例在 `slave_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 个引用计数,这些引用计数由这些函数并行操作。

约定

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

此外,鼓励使用大多数 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_register - 创建并注册 scsi 主机适配器实例。

  • scsi_remove_device - 分离并移除 SCSI 设备

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

  • scsi_report_bus_reset - 报告观察到的 scsi _总线_ 重置

  • scsi_scan_host - 扫描 SCSI 总线

  • scsi_track_queue_full - 跟踪连续的 QUEUE_FULL 事件

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

  • scsi_unregister - [调用 scsi_host_put()]

详细信息

/**
* 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 slave_alloc() and slave_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 slave_configure() and prior to
*      slave_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_register - create and register a scsi host adapter instance.
* @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.
*
*      Defined in: drivers/scsi/hosts.c .
**/
struct Scsi_Host * scsi_register(struct scsi_host_template * sht,
                                int privsize)


/**
* 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 slave_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
*      scsi_unregister().
*
*      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)


/**
* scsi_unregister - unregister and free memory used by host instance
* @shp:        pointer to scsi host instance to unregister.
*
*      Returns nothing
*
*      Might block: no
*
*      Notes: Should not be invoked if the "hotplug initialization
*      model" is being used. Called internally by exit_this_scsi_driver()
*      in the "passive initialization model". Hence a LLD has no need to
*      call this function directly.
*
*      Defined in: drivers/scsi/hosts.c .
**/
void scsi_unregister(struct Scsi_Host * shp)

接口函数

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

static int xyz_slave_configure(struct scsi_device * sdev);

依此类推,适用于下面列出的所有接口函数。

指向此函数的指针应放置在“struct scsi_host_template”实例的“slave_configure”成员中。指向此类实例的指针应传递给中间层的 scsi_host_alloc() [或 scsi_register() / init_this_scsi_driver()]。

接口函数也在 `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’

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

  • slave_configure - 驱动程序在连接后对给定设备进行微调

  • slave_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: If 'no_async_abort' is defined this callback
*   will be invoked from scsi_eh thread. No other commands
*   will then be queued on current host during eh.
*   Otherwise it will be called whenever scsi_timeout()
*      is called due to a command timeout.
*
*      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).
*      In a similar manner, init_this_scsi_driver() outputs to the console
*      each host's "info" (or name) for the driver it is registering.
*      Also if proc_info() is not supplied, the output of this function
*      is used instead.
*
*      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)


/**
*      slave_alloc -   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
*      slave_configure() will be called while if a device is not found
*      slave_destroy() is called.
*      For more details see the include/scsi/scsi_host.h file.
*
*      Optionally defined in: LLD
**/
    int slave_alloc(struct scsi_device *sdp)


/**
*      slave_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
*      slave_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 slave_configure(struct scsi_device *sdp)


/**
*      slave_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 slave_alloc()
*      and slave_configure() calls.]
*
*      Optionally defined in: LLD
**/
    void slave_destroy(struct scsi_device *sdp)

数据结构

struct scsi_host_template

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

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

proc_name
  • 在 “/proc/scsi/<proc_name>/<host_no>” 中以及 sysfs 在其“drivers”目录之一中使用的名称。因此,“proc_name”应该只包含 Unix 文件名可接受的字符。

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

该结构在 `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 发起者)的 SCSI ID,如果未知则为 -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
no_async_abort
  • 1=>不支持异步中止

  • 0=>超时命令将被异步中止

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

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

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

sh_list
  • 指向所有 struct Scsi_Host 实例的指针的双向链表(当前按 host_no 升序排列)

my_devices
  • 指向属于此主机的 struct scsi_device 实例的指针的双向链表。

hostdata[0]
  • 为 struct Scsi_Host 末尾的 LLD 保留的区域。大小由 scsi_host_alloc() 或 scsi_register() 的第二个参数(名为 ‘xtr_bytes’)设置。

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

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 命令的数组

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

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

request_bufflen
  • 要传输的数据字节数(如果无数据阶段则为 0)

use_sg
  • ==0 -> 无散射/聚集列表,因此传输数据

    来/自 request_buffer

  • >0 -> 散射/聚集列表(实际上是一个数组)在

    request_buffer 中,具有 use_sg 个元素

request_buffer
  • 根据 use_sg 的设置,包含数据缓冲区或散射/聚集列表。散射/聚集元素由 include/linux/scatterlist.h 中找到的 ‘struct scatterlist’ 定义。

done
  • LLD 完成 SCSI 命令(成功或失败)时应调用的函数指针。仅当 LLD 接受命令时(即 queuecommand() 返回或将返回 0)才应由 LLD 调用。LLD 可能会在 queuecommand() 完成之前调用 ‘done’。

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

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

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

resid
  • LLD 应将此无符号整数设置为请求的传输长度(即 ‘request_bufflen’)减去实际传输的字节数。 ‘resid’ 预设为 0,因此如果 LLD 无法检测到欠载,则可以忽略它(不应报告过载)。LLD 应在调用 ‘done’ 之前设置 ‘resid’。最有趣的情况是从 SCSI 目标设备(例如 READ)传输数据时发生欠载。

underflow
  • 如果实际传输的字节数小于此数字,LLD 应在 ‘result’ 中放置 (DID_ERROR << 16)。没有多少 LLD 实现此检查,有些 LLD 只是在日志中输出错误消息,而不是报告 DID_ERROR。LLD 最好实现 ‘resid’。

建议 LLD 在从 SCSI 目标设备(例如 READ)传输数据时设置 ‘resid’。当此类数据传输具有 MEDIUM ERROR 和 HARDWARE ERROR(以及可能 RECOVERED ERROR)的感知键时,设置 ‘resid’ 尤其重要。在这些情况下,如果 LLD 对已接收到的数据量有疑问,那么最安全的方法是表明没有收到任何字节。例如:要指示没有收到有效数据,LLD 可以使用以下帮助程序

scsi_set_resid(SCpnt, scsi_bufflen(SCpnt));

其中 ‘SCpnt’ 是指向 scsi_cmnd 对象的指针。要指示仅收到三个 512 字节的块,可以将 ‘resid’ 设置为如下所示

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

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

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

自动感知

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

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

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

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

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

自 lk 2.4 系列以来的更改

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

较旧的错误处理机制已被删除。这意味着 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 日