SCSI FC 传输

日期:2008 年 11 月 18 日

功能相关的内核修订

rports : <<TBS>>
vports : 2.6.22
bsg support : 2.6.30 (?TBD?)

引言

本文件记录了 SCSI FC 传输的功能和组件。它还提供了传输与 FC LLDD 之间的 API 文档。

FC 传输可在以下位置找到:

drivers/scsi/scsi_transport_fc.c
include/scsi/scsi_transport_fc.h
include/scsi/scsi_netlink_fc.h
include/scsi/scsi_bsg_fc.h

此文件位于 SCSI FC 传输

FC 远程端口 (rports)

<< 待补充 >>

FC 虚拟端口 (vports)

概述

新的 FC 标准定义了允许单个物理端口显示为多个通信端口的机制。使用 N_Port ID 虚拟化 (NPIV) 机制,可以为到 Fabric 的点对点连接分配多个 N_Port_ID。每个 N_Port_ID 在 Fabric 上显示为独立的端口,尽管它共享一个物理链路与交换机进行通信。每个 N_Port_ID 可以根据 Fabric 分区和阵列 LUN 掩码拥有独特的 Fabric 视图(就像一个普通的非 NPIV 适配器一样)。使用虚拟 Fabric (VF) 机制,为每个帧添加 Fabric 头部允许端口与 Fabric 端口交互以加入多个 Fabric。端口将在其加入的每个 Fabric 上获取一个 N_Port_ID。每个 Fabric 将拥有其自身独特的端点和配置参数视图。NPIV 可以与 VF 一起使用,以便端口可以在每个虚拟 Fabric 上获取多个 N_Port_ID。

FC 传输现在识别一个新的对象——vport(虚拟端口)。vport 是一个具有全球唯一端口名称 (WWPN) 和全球节点名称 (WWNN) 的实体。传输还允许为 vport 指定 FC4 角色,其中 FCP_Initiator 是预期的主要角色。一旦通过上述方法之一实例化,它将拥有一个独特的 N_Port_ID 和 Fabric 端点及存储实体的视图。与物理适配器关联的 fc_host 将导出创建 vports 的能力。传输将在 Linux 设备树中创建 vport 对象,并指示 fc_host 的驱动程序实例化虚拟端口。通常,驱动程序将在 vport 上创建一个新的 scsi_host 实例,从而为 vport 形成一个唯一的 <H,C,T,L> 命名空间。因此,无论 FC 端口是基于物理端口还是基于虚拟端口,每个都将作为唯一的 scsi_host 出现,拥有自己的目标和 LUN 空间。

注意

目前,传输层仅支持创建基于 NPIV 的 vport。然而,我们已考虑了基于 VF 的 vport,如果需要,添加支持应仅需少量修改。接下来的讨论将集中在 NPIV 上。

注意

全球名称的分配(和唯一性保证)由控制 vport 的管理实体负责。例如,如果 vports 要与虚拟机关联,XEN 管理工具将负责为 vport 创建 WWPN/WWNN,使用其自身的命名权限和 OUI。(注意:它已经为虚拟 MAC 地址做了这些。)

设备树和 Vport 对象:

如今,设备树通常包含 scsi_host 对象,其下方有 rport 和 SCSI 目标对象。目前,FC 传输层会创建 vport 对象并将其放置在与物理适配器对应的 scsi_host 对象之下。LLDD 将为 vport 分配一个新的 scsi_host,并将其对象链接到 vport 之下。vport 的 scsi_host 之下树的其余部分与非 NPIV 情况相同。当前编写的传输层易于允许 vport 的父级是 scsi_host 以外的其他对象。这在未来可用于将对象链接到虚拟机特定的设备树。如果 vport 的父级不是物理端口的 scsi_host,则指向 vport 对象的符号链接将放置在物理端口的 scsi_host 中。

以下是设备树中预期的结构:

典型的物理端口 Scsi_Host

/sys/devices/.../host17/

并且它具有典型的子树结构

/sys/devices/.../host17/rport-17:0-0/target17:0:0/17:0:0:0:

然后 vport 在物理端口上创建

/sys/devices/.../host17/vport-17:0-0

然后 vport 的 Scsi_Host 被创建

/sys/devices/.../host17/vport-17:0-0/host18

然后树的其余部分继续,例如

/sys/devices/.../host17/vport-17:0-0/host18/rport-18:0-0/target18:0:0/18:0:0:0:

以下是 sysfs 树中预期的结构

scsi_hosts:
  /sys/class/scsi_host/host17                physical port's scsi_host
  /sys/class/scsi_host/host18                vport's scsi_host
fc_hosts:
  /sys/class/fc_host/host17                  physical port's fc_host
  /sys/class/fc_host/host18                  vport's fc_host
fc_vports:
  /sys/class/fc_vports/vport-17:0-0          the vport's fc_vport
fc_rports:
  /sys/class/fc_remote_ports/rport-17:0-0    rport on the physical port
  /sys/class/fc_remote_ports/rport-18:0-0    rport on the vport

Vport 属性

新的 fc_vport 类对象具有以下属性:

node_name: 只读

vport 的 WWNN

port_name: 只读

vport 的 WWPN

roles: 只读

指示 vport 上启用的 FC4 角色。

symbolic_name: 读写

一个字符串,附加到驱动程序的符号端口名称字符串后,并在交换机上注册以识别 vport。例如,一个管理程序可以将此字符串设置为“Xen Domain 2 VM 5 Vport 2”,并且这组标识符可以在交换机管理屏幕上看到,用于识别该端口。

vport_delete: 只写

当写入“1”时,将销毁 vport。

vport_disable: 只写

当写入“1”时,将把 vport 转换到禁用状态。vport 仍将在 Linux 内核中实例化,但不会在 FC 链路上处于活动状态。当写入“0”时,将启用 vport。

vport_last_state: 只读

指示 vport 的前一状态。请参阅下面关于“Vport 状态”的部分。

vport_state: 只读

指示 vport 的当前状态。请参阅下面关于“Vport 状态”的部分。

vport_type: 只读

反映用于创建虚拟端口的 FC 机制。目前仅支持 NPIV。

对于 fc_host 类对象,为 vports 添加了以下属性:

max_npiv_vports: 只读

指示驱动程序/适配器在 fc_host 上支持的最大 NPIV vport 数量。

npiv_vports_inuse: 只读

指示在 fc_host 上已实例化的 NPIV vport 数量。

vport_create: 只写

一个“简单”的创建接口,用于在 fc_host 上实例化一个 vport。将一个“<WWPN>:<WWNN>”字符串写入属性。传输层随后实例化 vport 对象并调用 LLDD 以 FCP_Initiator 角色创建 vport。每个 WWN 都指定为 16 个十六进制字符,并且 *不能* 包含任何前缀(例如 0x, x 等)。

vport_delete: 只写

一个“简单”的删除接口,用于销毁 vport。将一个“<WWPN>:<WWNN>”字符串写入属性。传输层将定位 fc_host 上具有相同 WWN 的 vport 并将其销毁。每个 WWN 都指定为 16 个十六进制字符,并且 *不能* 包含任何前缀(例如 0x, x 等)。

Vport 状态

Vport 实例化包括两部分:

  • 在内核和 LLDD 中创建。这意味着所有传输和驱动程序数据结构都已构建,并且设备对象已创建。这等同于驱动程序在适配器上的“附加”,这与适配器的链路状态无关。

  • 通过 ELS 流量等方式在 FC 链路上实例化 vport。这等同于“链路激活”和成功的链路初始化。

有关 Vport 创建的更多信息,请参阅下面的接口部分。

一旦 vport 在内核/LLDD 中实例化,其状态可以通过 sysfs 属性报告。存在以下状态:

FC_VPORT_UNKNOWN - 未知

一个临时状态,通常只在 vport 正在内核和 LLDD 中实例化时设置。

FC_VPORT_ACTIVE - 活动

vport 已在 FC 链路上成功创建。它功能齐全。

FC_VPORT_DISABLED - 禁用

vport 已实例化,但处于“禁用”状态。vport 未在 FC 链路上实例化。这等同于物理端口的链路“已断开”。

FC_VPORT_LINKDOWN - 链路断开

vport 无法运行,因为物理链路未运行。

FC_VPORT_INITIALIZING - 初始化中

vport 正在 FC 链路上实例化。LLDD 将在开始 ELS 流量以创建 vport 之前设置此状态。此状态将持续到 vport 成功创建(状态变为 FC_VPORT_ACTIVE)或失败(状态为以下值之一)。由于此状态是暂时的,它不会保留在“vport_last_state”中。

FC_VPORT_NO_FABRIC_SUPP - 无 Fabric 支持

vport 无法运行。遇到以下条件之一:

  • FC 拓扑不是点对点

  • FC 端口未连接到 F_Port

  • F_Port 已指示不支持 NPIV。

FC_VPORT_NO_FABRIC_RSCS - 无 Fabric 资源

vport 无法运行。Fabric 的 FDISC 操作失败,状态指示其没有足够的资源完成该操作。

FC_VPORT_FABRIC_LOGOUT - Fabric 注销

vport 无法运行。Fabric 已注销与 vport 关联的 N_Port_ID。

FC_VPORT_FABRIC_REJ_WWN - Fabric 拒绝 WWN

vport 无法运行。Fabric 的 FDISC 操作失败,状态指示 WWN 无效。

FC_VPORT_FAILED - VPort 失败

vport 无法运行。这是所有其他错误条件的通用捕获。

下表显示了不同的状态转换:

状态

事件

新状态

不适用

初始化

未知

未知

链路断开

链路断开

链路激活 & 环路

无 Fabric 支持

链路激活 & 无 Fabric

无 Fabric 支持

链路激活 & FLOGI 响应指示不支持 NPIV

无 Fabric 支持

链路激活 & 正在发送 FDISC

初始化中

禁用请求

禁用

链路断开

链路激活

未知

初始化中

FDISC ACC

活动

FDISC LS_RJT(无资源)

无 Fabric 资源

FDISC LS_RJT(pname 或 nport_id 无效)

Fabric 拒绝 WWN

FDISC LS_RJT 因其他原因失败

Vport 失败

链路断开

链路断开

禁用请求

禁用

禁用

启用请求

未知

活动

从 Fabric 收到 LOGO

Fabric 注销

链路断开

链路断开

禁用请求

禁用

Fabric 注销

链路仍激活

未知

以下 4 种错误状态都具有相同的转换:

No Fabric Support:
No Fabric Resources:
Fabric Rejected WWN:
Vport Failed:
                    Disable request                 Disable
                    Link goes down                  Linkdown

传输 <-> LLDD 接口

LLDD 对 Vport 的支持

LLDD 通过在传输模板中提供 `vport_create()` 函数来表明对 vport 的支持。此函数的存在将导致在 fc_host 上创建新的属性。作为物理端口相对于传输层完成初始化的一部分,它应该设置 `max_npiv_vports` 属性,以指示驱动程序和/或适配器支持的最大 vport 数量。

Vport 创建

LLDD `vport_create()` 语法为:

int vport_create(struct fc_vport *vport, bool disable)

其中

vport

是新分配的 vport 对象

disable

如果为“true”,vport 将在禁用状态下创建。如果为“false”,vport 将在创建时启用。

当请求创建新的 vport 时(通过 sgio/netlink,或 vport_create fc_host 属性),传输层将验证 LLDD 是否可以支持另一个 vport(例如 `max_npiv_vports` > `npiv_vports_inuse`)。如果不支持,创建请求将失败。如果仍有空间,传输层将增加 vport 计数,创建 vport 对象,然后调用 LLDD 的 `vport_create()` 函数,传入新分配的 vport 对象。

如上所述,vport 创建分为两部分:

  • 在内核和 LLDD 中创建。这意味着所有传输和驱动程序数据结构都已构建,并且设备对象已创建。这等同于驱动程序在适配器上的“附加”,这与适配器的链路状态无关。

  • 通过 ELS 流量等方式在 FC 链路上实例化 vport。这等同于“链路激活”和成功的链路初始化。

LLDD 的 `vport_create()` 函数不会在返回之前同步等待两部分完全完成。它必须验证支持 NPIV 的基础设施是否存在,并在返回之前完成 vport 创建的第一部分(数据结构构建)。我们不将 `vport_create()` 依赖于链路侧操作,主要是因为:

  • 链路可能已断开。如果链路断开,这不是故障。它仅表示 vport 处于不可操作状态,直到链路激活。这与 vport 创建后链路跳动的情况一致。

  • vport 可以在禁用状态下创建。

  • 这与以下模型一致:vport 等同于一个 FC 适配器。`vport_create` 与驱动程序附加到适配器同义,而这与链路状态无关。

注意

已定义了特殊的错误代码,以区分基础设施故障情况,从而加快解决速度。

LLDD `vport_create()` 函数的预期行为是:

  • 验证基础设施

    • 如果驱动程序或适配器不能支持另一个 vport,无论是因为

      固件不正确、(虚报)`max_npiv`,或缺少其他资源——则返回 `VPCERR_UNSUPPORTED`。

    • 如果驱动程序针对已在适配器上活动的 WWN 验证新 WWN,并检测到重叠——则返回 `VPCERR_BAD_WWN`。

      适配器并检测到重叠 - 返回 VPCERR_BAD_WWN。

    • 如果驱动程序检测到拓扑是环路、非 Fabric,或者

      FLOGI 不支持 NPIV——则返回 `VPCERR_NO_FABRIC_SUPP`。

  • 分配数据结构。如果遇到错误,例如内存不足,则返回相应的负数 `Exxx` 错误代码。

    内存状况,返回相应的负 Exxx 错误代码。

  • 如果角色是 FCP Initiator,LLDD 应:

    • 调用 scsi_host_alloc() 为 vport 分配一个 `scsi_host`。

    • 调用 `scsi_add_host(new_shost, &vport->dev)` 以启动 `scsi_host` 并将其绑定为 vport 设备的子级。

    • 初始化 `fc_host` 属性值。

  • 根据禁用标志和链路状态启动进一步的 vport 状态转换——并返回成功(零)。

    链路状态 - 并返回成功(零)。

LLDD 实现者注意事项

  • 建议物理端口和虚拟端口使用不同的 `fc_function_templates`。物理端口的模板将包含 `vport_create`、`vport_delete` 和 `vport_disable` 函数,而 vport 则不包含。

  • 建议物理端口和虚拟端口使用不同的 `scsi_host_templates`。很可能有些驱动程序属性(如链路速度、拓扑设置等)嵌入在 `scsi_host_template` 中,仅适用于物理端口。这确保了属性适用于各自的 `scsi_host`。

Vport 禁用/启用

LLDD `vport_disable()` 语法为:

int vport_disable(struct fc_vport *vport, bool disable)

其中

vport

vport 是启用还是禁用

disable

如果为“true”,vport 将被禁用。如果为“false”,vport 将被启用。

当请求更改 vport 的禁用状态时,传输层将根据现有 vport 状态验证该请求。如果请求是禁用,且 vport 已被禁用,则请求将失败。类似地,如果请求是启用,而 vport 不处于禁用状态,则请求也将失败。如果请求对于 vport 状态有效,传输层将调用 LLDD 更改 vport 的状态。

在 LLDD 内部,如果 vport 被禁用,它仍将在内核和 LLDD 中实例化,但不会以任何方式在 FC 链路上活动或可见。(参见 Vport 创建和两部分实例化讨论)。vport 将保持此状态,直到被删除或重新启用。启用 vport 时,LLDD 将在 FC 链路上重新实例化 vport——本质上是重启 LLDD 状态机(参见上面的 Vport 状态)。

Vport 删除

LLDD `vport_delete()` 语法为:

int vport_delete(struct fc_vport *vport)

其中

vport: 要删除的 vport

当请求删除 vport 时(通过 sgio/netlink,或通过 `fc_host` 或 `fc_vport` 的 `vport_delete` 属性),传输层将调用 LLDD 以终止 FC 链路上的 vport,并销毁所有其他数据结构和引用。如果 LLDD 成功完成,传输层将销毁 vport 对象并完成 vport 删除。如果 LLDD 删除请求失败,vport 对象将保留,但会处于不确定状态。

在 LLDD 内部,应遵循 `scsi_host` 拆卸的常规代码路径。例如,如果 vport 具有 FCP Initiator 角色,LLDD 将为 vport 的 `scsi_host` 调用 fc_remove_host(),然后调用 scsi_remove_host()scsi_host_put()

其他
fc_host 的 `port_type` 属性

新增了一个 `fc_host` `port_type` 值 - `FC_PORTTYPE_NPIV`。此值必须设置在所有基于 vport 的 `fc_host` 上。通常,在物理端口上,`port_type` 属性会根据拓扑类型和 Fabric 的存在设置为 NPORT、NLPORT 等。由于这不适用于 vport,因此报告用于创建 vport 的 FC 机制更有意义。

驱动程序卸载

FC 驱动程序必须在调用 scsi_remove_host() 之前调用 fc_remove_host()。这允许 `fc_host` 在 `scsi_host` 拆卸之前拆卸所有远程端口。`fc_remove_host()` 调用已更新,以同时删除 `fc_host` 的所有 vport。

传输层提供的函数

以下函数由 FC 传输层提供给 LLD 使用。

`fc_vport_create`

创建 vport

`fc_vport_terminate`

分离并移除 vport

详情

/**
* fc_vport_create - Admin App or LLDD requests creation of a vport
* @shost:     scsi host the virtual port is connected to.
* @ids:       The world wide names, FC4 port roles, etc for
*              the virtual port.
*
* Notes:
*     This routine assumes no locks are held on entry.
*/
struct fc_vport *
fc_vport_create(struct Scsi_Host *shost, struct fc_vport_identifiers *ids)

/**
* fc_vport_terminate - Admin App or LLDD requests termination of a vport
* @vport:      fc_vport to be terminated
*
* Calls the LLDD vport_delete() function, then deallocates and removes
* the vport from the shost and object tree.
*
* Notes:
*      This routine assumes no locks are held on entry.
*/
int
fc_vport_terminate(struct fc_vport *vport)

FC BSG 支持 (CT & ELS 透传等)

<< 待补充 >>

致谢

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

James Smart james.smart@broadcom.com