Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,92 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/scsi/index.rst
:翻译:
郝栋栋 doubled <doubled@leap-io-kernel.com>
:校译:
==========
SCSI子系统
==========
.. toctree::
:maxdepth: 1
简介
====
.. toctree::
:maxdepth: 1
scsi
SCSI驱动接口
============
.. toctree::
:maxdepth: 1
scsi_mid_low_api
scsi_eh
SCSI驱动参数
============
.. toctree::
:maxdepth: 1
scsi-parameters
link_power_management_policy
SCSI主机适配器驱动
==================
.. toctree::
:maxdepth: 1
libsas
sd-parameters
wd719x
Todolist:
* 53c700
* aacraid
* advansys
* aha152x
* aic79xx
* aic7xxx
* arcmsr_spec
* bfa
* bnx2fc
* BusLogic
* cxgb3i
* dc395x
* dpti
* FlashPoint
* g_NCR5380
* hpsa
* hptiop
* lpfc
* megaraid
* ncr53c8xx
* NinjaSCSI
* ppa
* qlogicfas
* scsi-changer
* scsi_fc_transport
* scsi-generic
* smartpqi
* st
* sym53c500_cs
* sym53c8xx_2
* tcm_qla2xxx
* ufs
* scsi_transport_srp/figures
@@ -0,0 +1,425 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/scsi/libsas.rst
:翻译:
张钰杰 Yujie Zhang <yjzhang@leap-io-kernel.com>
:校译:
======
SAS 层
======
SAS 层是一个管理基础架构,用于管理 SAS LLDD。它位于 SCSI Core
与 SAS LLDD 之间。 体系结构如下: SCSI Core 关注的是 SAM/SPC 相
关的问题;SAS LLDD 及其序列控制器负责 PHY 层、OOB 信号以及链路
管理;而 SAS 层则负责以下任务::
* SAS Phy、Port 和主机适配器(HA)事件管理(事件由 LLDD
生成,由 SAS 层处理);
* SAS 端口的管理(创建与销毁);
* SAS 域的发现与重新验证;
* SAS 域内设备的管理;
* SCSI 主机的注册与注销;
* 将设备注册到 SCSI Core(SAS 设备)或 libata(SATA 设备);
* 扩展器的管理,并向用户空间导出扩展器控制接口。
SAS LLDD 是一种 PCI 设备驱动程序。它负责 PHY 层和 OOB(带外)
信号的管理、厂商特定的任务,并向 SAS 层上报事件。
SAS 层实现了 SAS 1.1 规范中定义的大部分 SAS 功能。
sas_ha_struct 结构体用于向 SAS 层描述一个 SAS LLDD。该结构的
大部分字段由 SAS 层使用,但其中少数字段需要由 LLDD 进行初始化。
在完成硬件初始化之后,应当在驱动的 probe() 函数中调用
sas_register_ha()。该函数会将 LLDD 注册到 SCSI 子系统中,创
建一个对应的 SCSI 主机,并将你的 SAS 驱动程序注册到其在 sysfs
下创建的 SAS 设备树中。随后该函数将返回。接着,你需要使能 PHY,
以启动实际的 OOB(带外)过程;此时驱动将开始调用 notify_* 系
列事件回调函数。
结构体说明
==========
``struct sas_phy``
------------------
通常情况下,该结构体会被静态地嵌入到驱动自身定义的 PHY 结构体中,
例如::
struct my_phy {
blah;
struct sas_phy sas_phy;
bleh;
}
随后,在主机适配器(HA)的结构体中,所有的 PHY 通常以 my_phy
数组的形式存在(如下文所示)。
在初始化各个 PHY 时,除了初始化驱动自定义的 PHY 结构体外,还
需要同时初始化其中的 sas_phy 结构体。
一般来说,PHY 的管理由 LLDD 负责,而端口(port)的管理由 SAS
层负责。因此,PHY 的初始化与更新由 LLDD 完成,而端口的初始化与
更新则由 SAS 层完成。系统设计中规定,某些字段可由 LLDD 进行读
写,而 SAS 层只能读取这些字段;反之亦然。其设计目的是为了避免不
必要的锁操作。
在该设计中,某些字段可由 LLDD 进行读写(RW),而 SAS 层仅可读
取这些字段;反之亦然。这样设计的目的在于避免不必要的锁操作。
enabled
- 必须设置(0/1)
id
- 必须设置[0,MAX_PHYS)]
class, proto, type, role, oob_mode, linkrate
- 必须设置。
oob_mode
- 当 OOB(带外信号)完成后,设置此字段,然后通知 SAS 层。
sas_addr
- 通常指向一个保存该 PHY 的 SAS 地址的数组,该数组可能位于
驱动自定义的 my_phy 结构体中。
attached_sas_addr
- 当 LLDD 接收到 IDENTIFY 帧或 FIS 帧时,应在通知 SAS 层
之前设置该字段。其设计意图在于:有时 LLDD 可能需要伪造或
提供一个与实际不同的 SAS 地址用于该 PHY/端口,而该机制允许
LLDD 这样做。理想情况下,应将 SAS 地址从 IDENTIFY 帧中
复制过来;对于直接连接的 SATA 设备,也可以由 LLDD 生成一
个 SAS 地址。后续的发现过程可能会修改此字段。
frame_rcvd
- 当接收到 IDENTIFY 或 FIS 帧时,将该帧复制到此处。正确的
操作流程是获取锁 → 复制数据 → 设置 frame_rcvd_size → 释
放锁 → 调用事件通知。该字段是一个指针,因为驱动无法精确确
定硬件帧的大小;因此,实际的帧数据数组应定义在驱动自定义的
PHY 结构体中,然后让此指针指向该数组。在持锁状态下,将帧从
DMA 可访问内存区域复制到该数组中。
sas_prim
- 用于存放接收到的原语(primitive)。参见 sas.h。操作流程同
样是:获取锁 → 设置 primitive → 释放锁 → 通知事件。
port
- 如果该 PHY 属于某个端口(port),此字段指向对应的 sas_port
结构体。LLDD 仅可读取此字段。它由 SAS 层设置,用于指向当前
PHY 所属的 sas_port。
ha
- 可以由 LLDD 设置;但无论是否设置,SAS 层都会再次对其进行赋值。
lldd_phy
- LLDD 应将此字段设置为指向自身定义的 PHY 结构体,这样当 SAS
层调用某个回调并传入 sas_phy 时,驱动可以快速定位自身的 PHY
结构体。如果 sas_phy 是嵌入式成员,也可以使用 container_of()
宏进行访问——两种方式均可。
``struct sas_port``
-------------------
LLDD 不应修改该结构体中的任何字段——它只能读取这些字段。这些字段的
含义应当是不言自明的。
phy_mask 为 32 位,目前这一长度已足够使用,因为尚未听说有主机适配
器拥有超过8 个 PHY。
lldd_port
- 目前尚无明确用途。不过,对于那些希望在 LLDD 内部维护自身端
口表示的驱动,实现时可以利用该字段。
``struct sas_ha_struct``
------------------------
它通常静态声明在你自己的 LLDD 结构中,用于描述您的适配器::
struct my_sas_ha {
blah;
struct sas_ha_struct sas_ha;
struct my_phy phys[MAX_PHYS];
struct sas_port sas_ports[MAX_PHYS]; /* (1) */
bleh;
};
(1) 如果你的 LLDD 没有自己的端口表示
需要初始化(示例函数如下所示)。
pcidev
^^^^^^
sas_addr
- 由于 SAS 层不想弄乱内存分配等, 因此这指向静态分配的数
组中的某个位置(例如,在您的主机适配器结构中),并保存您或
制造商等给出的主机适配器的 SAS 地址。
sas_port
^^^^^^^^
sas_phy
- 指向结构体的指针数组(参见上文关于 sas_addr 的说明)。
这些指针必须设置。更多细节见下文说明。
num_phys
- 表示 sas_phy 数组中 PHY 的数量,同时也表示 sas_port
数组中的端口数量。一个端口最多对应一个 PHY,因此最大端口数
等于 num_phys。因此,结构中不再单独使用 num_ports 字段,
而仅使用 num_phys。
事件接口::
/* LLDD 调用以下函数来通知 SAS 类层发生事件 */
void sas_notify_port_event(struct sas_phy *, enum port_event, gfp_t);
void sas_notify_phy_event(struct sas_phy *, enum phy_event, gfp_t);
端口事件通知::
/* SAS 类层调用以下回调来通知 LLDD 端口事件 */
void (*lldd_port_formed)(struct sas_phy *);
void (*lldd_port_deformed)(struct sas_phy *);
如果 LLDD 希望在端口形成或解散时接收通知,则应将上述回调指针设
置为符合函数类型定义的处理函数。
SAS LLDD 还应至少实现 SCSI 协议中定义的一种任务管理函数(TMFs)::
/* 任务管理函数. 必须在进程上下文中调用 */
int (*lldd_abort_task)(struct sas_task *);
int (*lldd_abort_task_set)(struct domain_device *, u8 *lun);
int (*lldd_clear_task_set)(struct domain_device *, u8 *lun);
int (*lldd_I_T_nexus_reset)(struct domain_device *);
int (*lldd_lu_reset)(struct domain_device *, u8 *lun);
int (*lldd_query_task)(struct sas_task *);
如需更多信息,请参考 T10.org。
端口与适配器管理::
/* 端口与适配器管理 */
int (*lldd_clear_nexus_port)(struct sas_port *);
int (*lldd_clear_nexus_ha)(struct sas_ha_struct *);
SAS LLDD 至少应实现上述函数中的一个。
PHY 管理::
/* PHY 管理 */
int (*lldd_control_phy)(struct sas_phy *, enum phy_func);
lldd_ha
- 应设置为指向驱动的主机适配器(HA)结构体的指针。如果 sas_ha_struct
被嵌入到更大的结构体中,也可以通过 container_of() 宏来获取。
一个示例的初始化与注册函数可以如下所示:(该函数应在 probe()
函数的最后调用)但必须在使能 PHY 执行 OOB 之前调用::
static int register_sas_ha(struct my_sas_ha *my_ha)
{
int i;
static struct sas_phy *sas_phys[MAX_PHYS];
static struct sas_port *sas_ports[MAX_PHYS];
my_ha->sas_ha.sas_addr = &my_ha->sas_addr[0];
for (i = 0; i < MAX_PHYS; i++) {
sas_phys[i] = &my_ha->phys[i].sas_phy;
sas_ports[i] = &my_ha->sas_ports[i];
}
my_ha->sas_ha.sas_phy = sas_phys;
my_ha->sas_ha.sas_port = sas_ports;
my_ha->sas_ha.num_phys = MAX_PHYS;
my_ha->sas_ha.lldd_port_formed = my_port_formed;
my_ha->sas_ha.lldd_dev_found = my_dev_found;
my_ha->sas_ha.lldd_dev_gone = my_dev_gone;
my_ha->sas_ha.lldd_execute_task = my_execute_task;
my_ha->sas_ha.lldd_abort_task = my_abort_task;
my_ha->sas_ha.lldd_abort_task_set = my_abort_task_set;
my_ha->sas_ha.lldd_clear_task_set = my_clear_task_set;
my_ha->sas_ha.lldd_I_T_nexus_reset= NULL; (2)
my_ha->sas_ha.lldd_lu_reset = my_lu_reset;
my_ha->sas_ha.lldd_query_task = my_query_task;
my_ha->sas_ha.lldd_clear_nexus_port = my_clear_nexus_port;
my_ha->sas_ha.lldd_clear_nexus_ha = my_clear_nexus_ha;
my_ha->sas_ha.lldd_control_phy = my_control_phy;
return sas_register_ha(&my_ha->sas_ha);
}
(2) SAS 1.1 未定义 I_T Nexus Reset TMF(任务管理功能)。
事件
====
事件是 SAS LLDD 唯一的通知 SAS 层发生任何情况的方式。
LLDD 没有其他方法可以告知 SAS 层其内部或 SAS 域中发生的事件。
Phy 事件::
PHYE_LOSS_OF_SIGNAL, (C)
PHYE_OOB_DONE,
PHYE_OOB_ERROR, (C)
PHYE_SPINUP_HOLD.
端口事件,通过 _phy_ 传递::
PORTE_BYTES_DMAED, (M)
PORTE_BROADCAST_RCVD, (E)
PORTE_LINK_RESET_ERR, (C)
PORTE_TIMER_EVENT, (C)
PORTE_HARD_RESET.
主机适配器事件:
HAE_RESET
SAS LLDD 应能够生成以下事件::
- 来自 C 组的至少一个事件(可选),
- 标记为 M(必需)的事件为必需事件(至少一种);
- 若希望 SAS 层处理域重新验证(domain revalidation),则
应生成标记为 E(扩展器)的事件(仅需一种);
- 未标记的事件为可选事件。
含义
HAE_RESET
- 当 HA 发生内部错误并被复位时。
PORTE_BYTES_DMAED
- 在接收到 IDENTIFY/FIS 帧时。
PORTE_BROADCAST_RCVD
- 在接收到一个原语时。
PORTE_LINK_RESET_ERR
- 定时器超时、信号丢失、丢失 DWS 等情况。 [1]_
PORTE_TIMER_EVENT
- DWS 复位超时定时器到期时。[1]_
PORTE_HARD_RESET
- 收到 Hard Reset 原语。
PHYE_LOSS_OF_SIGNAL
- 设备已断开连接。 [1]_
PHYE_OOB_DONE
- OOB 过程成功完成,oob_mode 有效。
PHYE_OOB_ERROR
- 执行 OOB 过程中出现错误,设备可能已断开。 [1]_
PHYE_SPINUP_HOLD
- 检测到 SATA 设备,但未发送 COMWAKE 信号。
.. [1] 应设置或清除 phy 中相应的字段,或者从 tasklet 中调用
内联函数 sas_phy_disconnected(),该函数只是一个辅助函数。
执行命令 SCSI RPC::
int (*lldd_execute_task)(struct sas_task *, gfp_t gfp_flags);
用于将任务排队提交给 SAS LLDD,@task 为要执行的任务,@gfp_mask
为定义调用者上下文的 gfp 掩码。
此函数应实现 执行 SCSI RPC 命令。
也就是说,当调用 lldd_execute_task() 时,命令应当立即在传输
层发出。SAS LLDD 中在任何层级上都不应再进行队列排放。
返回值::
* 返回 -SAS_QUEUE_FULL 或 -ENOMEM 表示未排入队列;
* 返回 0 表示任务已成功排入队列。
::
struct sas_task {
dev —— 此任务目标设备;
task_proto —— 协议类型,为 enum sas_proto 中的一种;
scatter —— 指向散布/聚集(SG)列表数组的指针;
num_scatter —— SG 列表元素数量;
total_xfer_len —— 预计传输的总字节数;
data_dir —— 数据传输方向(PCI_DMA_*);
task_done —— 任务执行完成时的回调函数。
};
发现
====
sysfs 树有以下用途::
a) 它显示当前时刻 SAS 域的物理布局,即展示当前物理世界中
域的实际结构。
b) 显示某些设备的参数。 _at_discovery_time_.
下面是一个指向 tree(1) 程序的链接,该工具在查看 SAS 域时非常
有用:
ftp://mama.indstate.edu/linux/tree/
我期望用户空间的应用程序最终能够为此创建一个图形界面。
也就是说,sysfs 域树不会显示或保存某些状态变化,例如,如果你更
改了 READY LED 含义的设置,sysfs 树不会反映这种状态变化;但它
确实会显示域设备的当前连接状态。
维护内部设备状态变化的职责由上层(命令集驱动)和用户空间负责。
当某个设备或多个设备从域中拔出时,这一变化会立即反映在 sysfs
树中,并且这些设备会从系统中移除。
结构体 domain_device 描述了 SAS 域中的任意设备。它完全由 SAS
层管理。一个任务会指向某个域设备,SAS LLDD 就是通过这种方式知
道任务应发送到何处。SAS LLDD 只读取 domain_device 结构的内容,
但不会创建或销毁它。
用户空间中的扩展器管理
======================
在 sysfs 中的每个扩展器目录下,都有一个名为 "smp_portal" 的
文件。这是一个二进制的 sysfs 属性文件,它实现了一个 SMP 入口
(注意:这并不是一个 SMP 端口),用户空间程序可以通过它发送
SMP 请求并接收 SMP 响应。
该功能的实现方式看起来非常简单:
1. 构建要发送的 SMP 帧。其格式和布局在 SAS 规范中有说明。保持
CRC 字段为 0。
open(2)
2. 以读写模式打开该扩展器的 SMP portal sysfs 文件。
write(2)
3. 将第 1 步中构建的帧写入文件。
read(2)
4. 读取与所构建帧预期返回长度相同的数据量。如果读取的数据量与
预期不符,则表示发生了某种错误。
close(2)
整个过程在 "expander_conf.c" 文件中的函数 do_smp_func()
及其调用者中有详细展示。
对应的内核实现位于 "sas_expander.c" 文件中。
程序 "expander_conf.c" 实现了上述逻辑。它接收一个参数——扩展器
SMP portal 的 sysfs 文件名,并输出扩展器的信息,包括路由表内容。
SMP portal 赋予了你对扩展器的完全控制权,因此请谨慎操作。
@@ -0,0 +1,32 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/scsi/link_power_management_policy.rst
:翻译:
郝栋栋 doubled <doubled@leap-io-kernel.com>
:校译:
================
链路电源管理策略
================
该参数允许用户设置链路(接口)的电源管理模式。
共计三类可选项:
===================== =====================================================
选项 作用
===================== =====================================================
min_power 指示控制器在可能的情况下尽量使链路处于最低功耗。
这可能会牺牲一定的性能,因为从低功耗状态恢复时会增加延迟。
max_performance 通常,这意味着不进行电源管理。指示
控制器优先考虑性能而非电源管理。
medium_power 指示控制器在可能的情况下进入较低功耗状态,
而非最低功耗状态,从而改善min_power模式下的延迟。
===================== =====================================================
@@ -0,0 +1,118 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/scsi/scsi-parameters.rst
:翻译:
郝栋栋 doubled <doubled@leap-io-kernel.com>
:校译:
============
SCSI内核参数
============
请查阅Documentation/admin-guide/kernel-parameters.rst以获取
指定模块参数相关的通用信息。
当前文档可能不完全是最新和全面的。命令 ``modinfo -p ${modulename}``
显示了可加载模块的参数列表。可加载模块被加载到内核中后,也会在
/sys/module/${modulename}/parameters/ 目录下显示其参数。其
中某些参数可以通过命令
``echo -n ${value} > /sys/module/${modulename}/parameters/${parm}``
在运行时修改。
::
advansys= [HW,SCSI]
请查阅 drivers/scsi/advansys.c 文件头部。
aha152x= [HW,SCSI]
请查阅 Documentation/scsi/aha152x.rst。
aha1542= [HW,SCSI]
格式:<portbase>[,<buson>,<busoff>[,<dmaspeed>]]
aic7xxx= [HW,SCSI]
请查阅 Documentation/scsi/aic7xxx.rst。
aic79xx= [HW,SCSI]
请查阅 Documentation/scsi/aic79xx.rst。
atascsi= [HW,SCSI]
请查阅 drivers/scsi/atari_scsi.c。
BusLogic= [HW,SCSI]
请查阅 drivers/scsi/BusLogic.c 文件中
BusLogic_ParseDriverOptions()函数前的注释。
gvp11= [HW,SCSI]
ips= [HW,SCSI] Adaptec / IBM ServeRAID 控制器
请查阅 drivers/scsi/ips.c 文件头部。
mac5380= [HW,SCSI]
请查阅 drivers/scsi/mac_scsi.c。
scsi_mod.max_luns=
[SCSI] 最大可探测LUN数。
取值范围为 1 到 2^32-1。
scsi_mod.max_report_luns=
[SCSI] 接收到的最大LUN数。
取值范围为 1 到 16384。
NCR_D700= [HW,SCSI]
请查阅 drivers/scsi/NCR_D700.c 文件头部。
ncr5380= [HW,SCSI]
请查阅 Documentation/scsi/g_NCR5380.rst。
ncr53c400= [HW,SCSI]
请查阅 Documentation/scsi/g_NCR5380.rst。
ncr53c400a= [HW,SCSI]
请查阅 Documentation/scsi/g_NCR5380.rst。
ncr53c8xx= [HW,SCSI]
osst= [HW,SCSI] SCSI磁带驱动
格式:<buffer_size>,<write_threshold>
另请查阅 Documentation/scsi/st.rst。
scsi_debug_*= [SCSI]
请查阅 drivers/scsi/scsi_debug.c。
scsi_mod.default_dev_flags=
[SCSI] SCSI默认设备标志
格式:<integer>
scsi_mod.dev_flags=
[SCSI] 厂商和型号的黑/白名单条目
格式:<vendor>:<model>:<flags>
(flags 为整数值)
scsi_mod.scsi_logging_level=
[SCSI] 日志级别的位掩码
位的定义请查阅 drivers/scsi/scsi_logging.h。
此参数也可以通过sysctl对dev.scsi.logging_level
进行设置(/proc/sys/dev/scsi/logging_level)。
此外,S390-tools软件包提供了一个便捷的
‘scsi_logging_level’ 脚本,可以从以下地址下载:
https://github.com/ibm-s390-linux/s390-tools/blob/master/scripts/scsi_logging_level
scsi_mod.scan= [SCSI] sync(默认)在发现SCSI总线过程中
同步扫描。async在内核线程中异步扫描,允许系统继续
启动流程。none忽略扫描,预期由用户空间完成扫描。
sim710= [SCSI,HW]
请查阅 drivers/scsi/sim710.c 文件头部。
st= [HW,SCSI] SCSI磁带参数(缓冲区大小等)
请查阅 Documentation/scsi/st.rst。
wd33c93= [HW,SCSI]
请查阅 drivers/scsi/wd33c93.c 文件头部。
@@ -0,0 +1,48 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/scsi/scsi.rst
:翻译:
郝栋栋 doubled <doubled@leap-io-kernel.com>
:校译:
==============
SCSI子系统文档
==============
Linux文档项目(LDP)维护了一份描述Linux内核(lk) 2.4中SCSI
子系统的文档。请参考:
https://www.tldp.org/HOWTO/SCSI-2.4-HOWTO 。LDP提供单页和
多页的HTML版本,以及PostScript与PDF格式的文档。
在SCSI子系统中使用模块的注意事项
================================
Linux内核中的SCSI支持可以根据终端用户的需求以不同的方式模块
化。为了理解你的选择,我们首先需要定义一些术语。
scsi-core(也被称为“中间层”)包含SCSI支持的核心。没有他你将
无法使用任何其他SCSI驱动程序。SCSI核心支持可以是一个模块(
scsi_mod.o),也可以编译进内核。如果SCSI核心是一个模块,那么
他必须是第一个被加载的SCSI模块,如果你将卸载该模块,那么他必
须是最后一个被卸载的模块。实际上,modprobe和rmmod命令将确保
SCSI子系统中模块加载与卸载的正确顺序。
一旦SCSI核心存在于内核中(无论是编译进内核还是作为模块加载),
独立的上层驱动和底层驱动可以按照任意顺序加载。磁盘驱动程序
(sd_mod.o)、光盘驱动程序(sr_mod.o)、磁带驱动程序 [1]_
(st.o)以及SCSI通用驱动程序(sg.o)代表了上层驱动,用于控制
相应的各种设备。例如,你可以加载磁带驱动程序来使用磁带驱动器,
然后在不需要该驱动程序时卸载他(并释放相关内存)。
底层驱动程序用于支持您所运行硬件平台支持的不同主机卡。这些不同
的主机卡通常被称为主机总线适配器(HBAs)。例如,aic7xxx.o驱动
程序被用于控制Adaptec所属的所有最新的SCSI控制器。几乎所有的底
层驱动都可以被编译为模块或直接编译进内核。
.. [1] 磁带驱动程序有一个变种用于控制OnStream磁带设备。其模块
名称为osst.o 。
@@ -0,0 +1,482 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/scsi/scsi_eh.rst
:翻译:
郝栋栋 doubled <doubled@leap-io-kernel.com>
:校译:
===================
SCSI 中间层错误处理
===================
本文档描述了SCSI中间层(mid layer)的错误处理基础架构。
关于SCSI中间层的更多信息,请参阅:
Documentation/scsi/scsi_mid_low_api.rst。
.. 目录
[1] SCSI 命令如何通过中间层传递并进入错误处理(EH)
[1-1] scsi_cmnd(SCSI命令)结构体
[1-2] scmd(SCSI 命令)是如何完成的?
[1-2-1] 通过scsi_done完成scmd
[1-2-2] 通过超时机制完成scmd
[1-3] 错误处理模块如何接管流程
[2] SCSI错误处理机制工作原理
[2-1] 基于细粒度回调的错误处理
[2-1-1] 概览
[2-1-2] scmd在错误处理流程中的传递路径
[2-1-3] 控制流分析
[2-2] 通过transportt->eh_strategy_handler()实现的错误处理
[2-2-1] transportt->eh_strategy_handler()调用前的中间层状态
[2-2-2] transportt->eh_strategy_handler()调用后的中间层状态
[2-2-3] 注意事项
1. SCSI命令在中间层及错误处理中的传递流程
=========================================
1.1 scsi_cmnd结构体
-------------------
每个SCSI命令都由struct scsi_cmnd(简称scmd)结构体
表示。scmd包含两个list_head类型的链表节点:scmd->list
与scmd->eh_entry。其中scmd->list是用于空闲链表或设备
专属的scmd分配链表,与错误处理讨论关联不大。而
scmd->eh_entry则是专用于命令完成和错误处理链表,除非
特别说明,本文讨论中所有scmd的链表操作均通过
scmd->eh_entry实现。
1.2 scmd是如何完成的?
----------------------
底层设备驱动(LLDD)在获取SCSI命令(scmd)后,存在两种
完成路径:底层驱动可通过调用hostt->queuecommand()时从
中间层传递的scsi_done回调函数主动完成命令,或者当命令未
及时完成时由块层(block layer)触发超时处理机制。
1.2.1 通过scsi_done回调完成SCSI命令
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
对于所有非错误处理(EH)命令,scsi_done()是其完成回调
函数。它只调用blk_mq_complete_request()来删除块层的
定时器并触发块设备软中断(BLOCK_SOFTIRQ)。
BLOCK_SOFTIRQ会间接调用scsi_complete(),进而调用
scsi_decide_disposition()来决定如何处理该命令。
scsi_decide_disposition()会查看scmd->result值和感
应码数据来决定如何处理命令。
- SUCCESS
调用scsi_finish_command()来处理该命令。该函数会
执行一些维护操作,然后调用scsi_io_completion()来
完成I/O操作。scsi_io_completion()会通过调用
blk_end_request及其相关函数来通知块层该请求已完成,
如果发生错误,还会判断如何处理剩余的数据。
- NEEDS_RETRY
- ADD_TO_MLQUEUE
scmd被重新加入到块设备队列中。
- otherwise
调用scsi_eh_scmd_add(scmd)来处理该命令。
关于此函数的详细信息,请参见 [1-3]。
1.2.2 scmd超时完成机制
^^^^^^^^^^^^^^^^^^^^^^
SCSI命令超时处理机制由scsi_timeout()函数实现。
当发生超时事件时,该函数
1. 首先调用可选的hostt->eh_timed_out()回调函数。
返回值可能是以下3种情况之一:
- ``SCSI_EH_RESET_TIMER``
表示需要延长命令执行时间并重启计时器。
- ``SCSI_EH_NOT_HANDLED``
表示eh_timed_out()未处理该命令。
此时将执行第2步的处理流程。
- ``SCSI_EH_DONE``
表示eh_timed_out()已完成该命令。
2. 若未通过回调函数解决,系统将调用
scsi_abort_command()发起异步中止操作,该操作最多
可执行scmd->allowed + 1次。但存在三种例外情况会跳
过异步中止而直接进入第3步处理:当检测到
SCSI_EH_ABORT_SCHEDULED标志位已置位(表明该命令先
前已被中止过一次且当前重试仍失败)、当重试次数已达上
限、或当错误处理时限已到期时。在这些情况下,系统将跳
过异步中止流程而直接执行第3步处理方案。
3. 最终未解决的命令会通过scsi_eh_scmd_add(scmd)移交给
错误处理子系统,具体流程详见[1-4]章节说明。
1.3 异步命令中止机制
--------------------
当命令超时触发后,系统会通过scsi_abort_command()调度异
步中止操作。若中止操作执行成功,则根据重试次数决定后续处
理:若未达最大重试限制,命令将重新下发执行;若重试次数已
耗尽,则命令最终以DID_TIME_OUT状态终止。当中止操作失败
时,系统会调用scsi_eh_scmd_add()将该命令移交错误处理子
系统,具体处理流程详见[1-4]。
1.4 错误处理(EH)接管机制
------------------------
SCSI命令通过scsi_eh_scmd_add()函数进入错误处理流程,该函
数执行以下操作:
1. 将scmd->eh_entry链接到shost->eh_cmd_q
2. 在shost->shost_state中设置SHOST_RECOVERY状态位
3. 递增shost->host_failed失败计数器
4. 当检测到shost->host_busy == shost->host_failed
时(即所有进行中命令均已失败)立即唤醒SCSI错误处理
线程。
如上所述,当任一scmd被加入到shost->eh_cmd_q队列时,系统
会立即置位shost_state中的SHOST_RECOVERY状态标志位,该操
作将阻止块层向对应主机控制器下发任何新的SCSI命令。在此状
态下,主机控制器上所有正在处理的scmd最终会进入以下三种状
态之一:正常完成、失败后被移入到eh_cmd_q队列、或因超时被
添加到shost->eh_cmd_q队列。
如果所有的SCSI命令都已经完成或失败,系统中正在执行的命令
数量与失败命令数量相等(
即shost->host_busy == shost->host_failed),此时将唤
醒SCSI错误处理线程。SCSI错误处理线程一旦被唤醒,就可以确
保所有未完成命令均已标记为失败状态,并且已经被链接到
shost->eh_cmd_q队列中。
需要特别说明的是,这并不意味着底层处理流程完全静止。当底层
驱动以错误状态完成某个scmd时,底层驱动及其下层组件会立刻遗
忘该命令的所有关联状态。但对于超时命令,除非
hostt->eh_timed_out()回调函数已经明确通知底层驱动丢弃该
命令(当前所有底层驱动均未实现此功能),否则从底层驱动视角
看该命令仍处于活跃状态,理论上仍可能在某时刻完成。当然,由
于超时计时器早已触发,所有此类延迟完成都将被系统直接忽略。
我们将在后续章节详细讨论关于SCSI错误处理如何执行中止操作(
即强制底层驱动丢弃已超时SCSI命令)。
2. SCSI错误处理机制详解
=======================
SCSI底层驱动可以通过以下两种方式之一来实现SCSI错误处理。
- 细粒度的错误处理回调机制
底层驱动可选择实现细粒度的错误处理回调函数,由SCSI中间层
主导错误恢复流程并自动调用对应的回调函数。此实现模式的详
细设计规范在[2-1]节中展开讨论。
- eh_strategy_handler()回调函数
该回调函数作为统一的错误处理入口,需要完整实现所有的恢复
操作。具体而言,它必须涵盖SCSI中间层在常规恢复过程中执行
的全部处理流程,相关实现将在[2-2]节中详细描述。
当错误恢复流程完成后,SCSI错误处理系统通过调用
scsi_restart_operations()函数恢复正常运行,该函数按顺序执行
以下操作:
1. 验证是否需要执行驱动器安全门锁定机制
2. 清除shost_state中的SHOST_RECOVERY状态标志位
3. 唤醒所有在shost->host_wait上等待的任务。如果有人调用了
scsi_block_when_processing_errors()则会发生这种情况。
(疑问:由于错误处理期间块层队列已被阻塞,为何仍需显式
唤醒?)
4. 强制激活该主机控制器下所有设备的I/O队列
2.1 基于细粒度回调的错误处理机制
--------------------------------
2.1.1 概述
^^^^^^^^^^^
如果不存在eh_strategy_handler(),SCSI中间层将负责驱动的
错误处理。错误处理(EH)的目标有两个:一是让底层驱动程序、
主机和设备不再维护已超时的SCSI命令(scmd);二是使他们准备
好接收新命令。当一个SCSI命令(scmd)被底层遗忘且底层已准备
好再次处理或拒绝该命令时,即可认为该scmd已恢复。
为实现这些目标,错误处理(EH)会逐步执行严重性递增的恢复
操作。部分操作通过下发SCSI命令完成,而其他操作则通过调用
以下细粒度的错误处理回调函数实现。这些回调函数可以省略,
若被省略则默认始终视为执行失败。
::
int (* eh_abort_handler)(struct scsi_cmnd *);
int (* eh_device_reset_handler)(struct scsi_cmnd *);
int (* eh_bus_reset_handler)(struct scsi_cmnd *);
int (* eh_host_reset_handler)(struct scsi_cmnd *);
只有在低级别的错误恢复操作无法恢复部分失败的SCSI命令
(scmd)时,才会采取更高级别的恢复操作。如果最高级别的错误
处理失败,就意味着整个错误恢复(EH)过程失败,所有未能恢复
的设备被强制下线。
在恢复过程中,需遵循以下规则:
- 错误恢复操作针对待处理列表eh_work_q中的失败的scmds执
行。如果某个恢复操作成功恢复了一个scmd,那么该scmd会
从eh_work_q链表中移除。
需要注意的是,对某个scmd执行的单个恢复操作可能会恢复
多个scmd。例如,对某个设备执行复位操作可能会恢复该设
备上所有失败的scmd。
- 仅当低级别的恢复操作完成且eh_work_q仍然非空时,才会
触发更高级别的操作
- SCSI错误恢复机制会重用失败的scmd来发送恢复命令。对于
超时的scmd,SCSI错误处理机制会确保底层驱动在重用scmd
前已不再维护该命令。
当一个SCSI命令(scmd)被成功恢复后,错误处理逻辑会通过
scsi_eh_finish_cmd()将其从待处理队列(eh_work_q)移
至错误处理的本地完成队列(eh_done_q)。当所有scmd均恢
复完成(即eh_work_q为空时),错误处理逻辑会调用
scsi_eh_flush_done_q()对这些已恢复的scmd进行处理,即
重新尝试或错误总终止(向上层通知失败)。
SCSI命令仅在满足以下全部条件时才会被重试:对应的SCSI设
备仍处于在线状态,未设置REQ_FAILFAST标志或递增后的
scmd->retries值仍小于scmd->allowed。
2.1.2 SCSI命令在错误处理过程中的流转路径
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1. 错误完成/超时
:处理: 调用scsi_eh_scmd_add()处理scmd
- 将scmd添加到shost->eh_cmd_q
- 设置SHOST_RECOVERY标记位
- shost->host_failed++
:锁要求: shost->host_lock
2. 启动错误处理(EH)
:操作: 将所有scmd移动到EH本地eh_work_q队列,并
清空 shost->eh_cmd_q。
:锁要求: shost->host_lock(非严格必需,仅为保持一致性)
3. scmd恢复
:操作: 调用scsi_eh_finish_cmd()完成scmd的EH
- 将scmd从本地eh_work_q队列移至本地eh_done_q队列
:锁要求: 无
:并发控制: 每个独立的eh_work_q至多一个线程,确保无锁
队列的访问
4. EH完成
:操作: 调用scsi_eh_flush_done_q()重试scmd或通知上层处理
失败。此函数可以被并发调用,但每个独立的eh_work_q队
列至多一个线程,以确保无锁队列的访问。
- 从eh_done_q队列中移除scmd,清除scmd->eh_entry
- 如果需要重试,调用scsi_queue_insert()重新入队scmd
- 否则,调用scsi_finish_command()完成scmd
- 将shost->host_failed置为零
:锁要求: 队列或完成函数会执行适当的加锁操作
2.1.3 控制流
^^^^^^^^^^^^
通过细粒度回调机制执行的SCSI错误处理(EH)是从
scsi_unjam_host()函数开始的
``scsi_unjam_host``
1. 持有shost->host_lock锁,将shost->eh_cmd_q中的命令移动
到本地的eh_work_q队里中,并释放host_lock锁。注意,这一步
会清空shost->eh_cmd_q。
2. 调用scsi_eh_get_sense函数。
``scsi_eh_get_sense``
该操作针对没有有效感知数据的错误完成命令。大部分SCSI传输协议
或底层驱动在命令失败时会自动获取感知数据(自动感知)。出于性
能原因,建议使用自动感知,推荐使用自动感知机制,因为它不仅有
助于提升性能,还能避免从发生CHECK CONDITION到执行本操作之间,
感知信息出现不同步的问题。
注意,如果不支持自动感知,那么在使用scsi_done()以错误状态完成
scmd 时,scmd->sense_buffer将包含无效感知数据。在这种情况下,
scsi_decide_disposition()总是返回FAILED从而触发SCSI错误处理
(EH)。当该scmd执行到这里时,会重新获取感知数据,并再次调用
scsi_decide_disposition()进行处理。
1. 调用scsi_request_sense()发送REQUEST_SENSE命令。如果失败,
则不采取任何操作。请注意,不采取任何操作会导致对该scmd执行
更高级别的恢复操作。
2. 调用scsi_decide_disposition()处理scmd
- SUCCESS
scmd->retries被设置为scmd->allowed以防止
scsi_eh_flush_done_q()重试该scmd,并调用
scsi_eh_finish_cmd()。
- NEEDS_RETRY
调用scsi_eh_finish_cmd()
- 其他情况
无操作。
4. 如果!list_empty(&eh_work_q),则调用scsi_eh_ready_devs()。
``scsi_eh_ready_devs``
该函数采取四种逐步增强的措施,使失败的设备准备好处理新的命令。
1. 调用scsi_eh_stu()
``scsi_eh_stu``
对于每个具有有效感知数据且scsi_check_sense()判断为失败的
scmd发送START STOP UNIT(STU)命令且将start置1。注意,由
于我们明确选择错误完成的scmd,可以确定底层驱动已不再维护该
scmd,我们可以重用它进行STU。
如果STU操作成功且sdev处于离线或就绪状态,所有在sdev上失败的
scmd都会通过scsi_eh_finish_cmd()完成。
*注意* 如果hostt->eh_abort_handler()未实现或返回失败,可能
此时仍有超时的scmd,此时STU不会导致底层驱动不再维护scmd。但
是,如果STU执行成功,该函数会通过scsi_eh_finish_cmd()来完成
sdev上的所有scmd,这会导致底层驱动处于不一致的状态。看来STU
操作应仅在sdev不包含超时scmd时进行。
2. 如果!list_empty(&eh_work_q),调用scsi_eh_bus_device_reset()。
``scsi_eh_bus_device_reset``
此操作与scsi_eh_stu()非常相似,区别在于使用
hostt->eh_device_reset_handler()替代STU命令。此外,由于我们
没有发送SCSI命令且重置会清空该sdev上所有的scmd,所以无需筛选错
误完成的scmd。
3. 如果!list_empty(&eh_work_q),调用scsi_eh_bus_reset()。
``scsi_eh_bus_reset``
对于每个包含失败scmd的SCSI通道调用
hostt->eh_bus_reset_handler()。如果总线重置成功,那么该通道上
所有准备就绪或离线状态sdev上的失败scmd都会被处理处理完成。
4. 如果!list_empty(&eh_work_q),调用scsi_eh_host_reset()。
``scsi_eh_host_reset``
调用hostt->eh_host_reset_handler()是最终的手段。如果SCSI主机
重置成功,主机上所有就绪或离线sdev上的失败scmd都会通过错误处理
完成。
5. 如果!list_empty(&eh_work_q),调用scsi_eh_offline_sdevs()。
``scsi_eh_offline_sdevs``
离线所有包含未恢复scmd的所有sdev,并通过
scsi_eh_finish_cmd()完成这些scmd。
5. 调用scsi_eh_flush_done_q()。
``scsi_eh_flush_done_q``
此时所有的scmd都已经恢复(或放弃),并通过
scsi_eh_finish_cmd()函数加入eh_done_q队列。该函数通过
重试或显示通知上层scmd的失败来刷新eh_done_q。
2.2 基于transportt->eh_strategy_handler()的错误处理机制
-------------------------------------------------------------
在该机制中,transportt->eh_strategy_handler()替代
scsi_unjam_host()的被调用,并负责整个错误恢复过程。该处理
函数完成后应该确保底层驱动不再维护任何失败的scmd并且将设备
设置为就绪(准备接收新命令)或离线状态。此外,该函数还应该
执行SCSI错误处理的维护任务,以维护SCSI中间层的数据完整性。
换句话说,eh_strategy_handler()必须实现[2-1-2]中除第1步
外的所有步骤。
2.2.1 transportt->eh_strategy_handler()调用前的SCSI中间层状态
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
进入该处理函数时,以下条件成立。
- 每个失败的scmd的eh_flags字段已正确设置。
- 每个失败的scmd通过scmd->eh_entry链接到scmd->eh_cmd_q队列。
- 已设置SHOST_RECOVERY标志。
- `shost->host_failed == shost->host_busy`。
2.2.2 transportt->eh_strategy_handler()调用后的SCSI中间层状态
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
从该处理函数退出时,以下条件成立。
- shost->host_failed为零。
- shost->eh_cmd_q被清空。
- 每个scmd->eh_entry被清空。
- 对每个scmd必须调用scsi_queue_insert()或scsi_finish_command()。
注意,该处理程序可以使用scmd->retries(剩余重试次数)和
scmd->allowed(允许重试次数)限制重试次数。
2.2.3 注意事项
^^^^^^^^^^^^^^
- 需明确已超时的scmd在底层仍处于活跃状态,因此在操作这些
scmd前,必须确保底层已彻底不再维护。
- 访问或修改shost数据结构时,必须持有shost->host_lock锁
以维持数据一致性。
- 错误处理完成后,每个故障设备必须彻底清除所有活跃SCSI命
令(scmd)的关联状态。
- 错误处理完成后,每个故障设备必须被设置为就绪(准备接收
新命令)或离线状态。
Tejun Heo
htejun@gmail.com
11th September 2005
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,38 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/scsi/sd-parameters.rst
:翻译:
郝栋栋 doubled <doubled@leap-io-kernel.com>
:校译:
============================
Linux SCSI磁盘驱动(sd)参数
============================
缓存类型(读/写)
------------------
启用/禁用驱动器读写缓存。
=========================== ===== ===== ======= =======
缓存类型字符串 WCE RCD 写缓存 读缓存
=========================== ===== ===== ======= =======
write through 0 0 关闭 开启
none 0 1 关闭 关闭
write back 1 0 开启 开启
write back, no read (daft) 1 1 开启 关闭
=========================== ===== ===== ======= =======
将缓存类型设置为“write back”并将该设置保存到驱动器::
# echo "write back" > cache_type
如果要修改缓存模式但不使更改持久化,可在缓存类型字符串前
添加“temporary ”。例如::
# echo "temporary write back" > cache_type
@@ -0,0 +1,35 @@
.. SPDX-License-Identifier: GPL-2.0
.. include:: ../disclaimer-zh_CN.rst
:Original: Documentation/scsi/libsas.rst
:翻译:
张钰杰 Yujie Zhang <yjzhang@leap-io-kernel.com>
:校译:
====================================================
Western Digital WD7193, WD7197 和 WD7296 SCSI 卡驱动
====================================================
这些卡需要加载固件。固件可从 WD 提供下载的 Windows NT 驱动程
序中提取。地址如下:
http://support.wdc.com/product/download.asp?groupid=801&sid=27&lang=en
该文件或网页上都未包含任何许可声明,因此该固件可能无法被收录到
linux-firmware 项目中。
提供的脚本可用于下载并提取固件,生成 wd719x-risc.bin 和
wd719x-wcs.bin 文件。请将它们放置在 /lib/firmware/ 目录下。
脚本内容如下:
#!/bin/sh
wget http://support.wdc.com/download/archive/pciscsi.exe
lha xi pciscsi.exe pci-scsi.exe
lha xi pci-scsi.exe nt/wd7296a.sys
rm pci-scsi.exe
dd if=wd7296a.sys of=wd719x-risc.bin bs=1 skip=5760 count=14336
dd if=wd7296a.sys of=wd719x-wcs.bin bs=1 skip=20096 count=514
rm wd7296a.sys