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 子系统接口
直接调用由中间层提供的函数
将一组函数指针传递给由中间层提供的注册函数。然后,中间层将在未来的某个时间点调用这些函数。LLD 将提供这些函数的实现。
直接访问由中间层维护的众所周知的数据结构的实例
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
可以在主机控制的设备上排队的最大命令数。被 LLD 对
scsi_change_queue_depth()
的调用覆盖。- 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 状态时,应执行此操作:
指示 SCSI 协议(例如,SCSI 并行接口 (SPI))在此类响应上执行额外的数据输入阶段
或者,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 日