SAS 层¶
SAS 层是一个管理基础架构,用于管理 SAS LLDD。它位于 SCSI Core 和 SAS LLDD 之间。布局如下:SCSI Core 关注 SAM/SPC 问题,SAS LLDD+Sequencer 关注物理层/OOB/链路管理,而 SAS 层则关注
SAS Phy/Port/HA 事件管理(LLDD 生成,SAS 层处理),
SAS 端口管理(创建/销毁),
SAS 域发现和重新验证,
SAS 域设备管理,
SCSI 主机注册/取消注册,
向 SCSI Core (SAS) 或 libata (SATA) 注册设备,以及
扩展器管理以及将扩展器控制导出到用户空间。
SAS LLDD 是一个 PCI 设备驱动程序。它关注物理层/OOB 管理和供应商特定任务,并向 SAS 层生成事件。
SAS 层执行 SAS 1.1 规范中概述的大部分 SAS 任务。
sas_ha_struct 描述了 SAS LLDD 到 SAS 层。它的大部分由 SAS 层使用,但一些字段需要由 LLDD 初始化。
初始化硬件后,从 probe() 函数中调用 sas_register_ha()。它会将您的 LLDD 注册到 SCSI 子系统,创建一个 SCSI 主机,并将您的 SAS 驱动程序注册到它创建的 sysfs SAS 树。然后它将返回。然后启用您的物理层以实际启动 OOB(此时您的驱动程序将开始调用 notify_* 事件回调)。
结构体描述¶
struct sas_phy
¶
通常,这会静态嵌入到驱动程序的 phy 结构中
struct my_phy {
blah;
struct sas_phy sas_phy;
bleh;
};
然后所有的物理层都是 HA 结构中的 my_phy 数组(如下所示)。
然后,随着您的进行并初始化您的物理层,您还会初始化 sas_phy 结构体,以及您自己的物理层结构。
一般来说,物理层由 LLDD 管理,端口由 SAS 层管理。因此,物理层由 LLDD 初始化和更新,端口由 SAS 层初始化和更新。
有一种方案,LLDD 可以读写某些字段,而 SAS 层只能读取这些字段,反之亦然。这样做的目的是避免不必要的锁定。
- enabled
必须设置 (0/1)
- id
必须设置 [0,MAX_PHYS)]
- class, proto, type, role, oob_mode, linkrate
必须设置
- oob_mode
您在 OOB 完成后设置此项,然后通知 SAS 层。
- sas_addr
这通常指向一个数组,该数组保存物理层的 SAS 地址,可能在您的 my_phy 结构中的某个位置。
- attached_sas_addr
当您 (LLDD) 收到 IDENTIFY 帧或 FIS 帧时,在通知 SAS 层 _之前_ 设置此项。这样做的想法是,有时 LLDD 可能希望伪造或在该物理层/端口上提供不同的 SAS 地址,这允许它这样做。最好的情况是,您应该从 IDENTIFY 帧复制 SAS 地址,或者可能为直接连接的 SATA 设备生成 SAS 地址。Discover 过程可能会稍后更改此地址。
- frame_rcvd
这是您在收到 IDENTIFY/FIS 帧时复制帧的地方;您锁定、复制、设置 frame_rcvd_size 并释放锁定,然后调用事件。它是一个指针,因为无法 _完全_ 知道您的硬件帧大小,因此您在 phy 结构中定义实际数组,并让此指针指向它。您将帧从 DMAable 内存复制到保存锁定的区域。
- sas_prim
这是接收到基元时基元的去向。请参阅 sas.h。获取锁定,设置基元,释放锁定,通知。
- port
如果物理层属于端口,则这指向 sas_port -- LLDD 仅读取此项。它指向此物理层所属的 sas_port。由 SAS 层设置。
- ha
可以设置;SAS 层无论如何都会设置它。
- lldd_phy
您应该将其设置为指向您的 phy,以便在 SAS 层调用您的回调之一并将 phy 传递给您时,您可以更快地找到您的 phy。如果嵌入了 sas_phy,您也可以使用 container_of -- 无论您喜欢什么。
struct sas_port
¶
LLDD 不设置此结构体的任何字段 -- 它只读取它们。它们应该是自我解释的。
phy_mask 是 32 位,这现在应该足够了,因为我还没有听说过 HA 拥有超过 8 个物理层。
- 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) If your LLDD doesn't have its own port representation.
需要初始化什么(下面给出了示例函数)。
pcidev¶
- sas_addr
由于 SAS 层不想弄乱内存分配等,因此这指向静态分配的数组中的某个位置(例如,在您的主机适配器结构中),并保存您或制造商等给出的主机适配器的 SAS 地址。
sas_port¶
- sas_phy
指向结构体的指针数组。(请参阅上面关于 sas_addr 的注释)。这些必须设置。请参阅下面的更多注释。
- num_phys
sas_phy 数组中存在的物理层数量和 sas_port 数组中存在的端口数量。最多可以有 num_phys 个端口(每个端口一个),因此我们删除 num_ports,仅使用 num_phys。
事件接口
/* LLDD calls these to notify the class of an event. */
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);
端口通知
/* The class calls these to notify the LLDD of an event. */
void (*lldd_port_formed)(struct sas_phy *);
void (*lldd_port_deformed)(struct sas_phy *);
如果 LLDD 希望在形成或变形端口时收到通知,它会将这些设置为满足类型的函数。
SAS LLDD 还应至少实现 SAM 中描述的一项任务管理功能 (TMF)
/* Task Management Functions. Must be called from process context. */
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 上的 SAM。
端口和适配器管理
/* Port and Adapter management */
int (*lldd_clear_nexus_port)(struct sas_port *);
int (*lldd_clear_nexus_ha)(struct sas_ha_struct *);
SAS LLDD 应至少实现其中一项。
物理层管理
/* Phy management */
int (*lldd_control_phy)(struct sas_phy *, enum phy_func);
- lldd_ha
将其设置为指向您的 HA 结构。如果如上所示嵌入它,您也可以使用 container_of。
示例初始化和注册函数可以如下所示(从 probe() 中最后调用)但是 在您启用物理层以执行 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);
}
SAS 1.1 未定义 I_T Nexus Reset TMF。
事件¶
事件是 SAS LLDD 通知 SAS 层任何事情的 唯一方式。没有其他方法或方式 LLDD 告诉 SAS 层内部或 SAS 域中发生的任何事情。
物理层事件
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 层处理域重新验证,则标记为 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
接收到硬重置基元。
- PHYE_LOSS_OF_SIGNAL
设备已消失[1]
- PHYE_OOB_DONE
OOB 运行良好,并且 oob_mode 有效
- PHYE_OOB_ERROR
执行 OOB 时出错,设备可能已断开连接。[1]
- PHYE_SPINUP_HOLD
SATA 存在,未发送 COMWAKE。
执行命令 SCSI RPC
int (*lldd_execute_task)(struct sas_task *, gfp_t gfp_flags);
用于将任务排队到 SAS LLDD。@task 是要执行的任务。@gfp_mask 是定义调用者上下文的 gfp_mask。
此函数应实现执行命令 SCSI RPC,
也就是说,当调用 lldd_execute_task() 时,命令会 立即 在传输层上发出。在 SAS LLDD 中 没有 任何类型的排队,也没有任何级别。
返回值
-SAS_QUEUE_FULL, -ENOMEM, 未排队任何内容;
0,任务已排队。
struct sas_task {
dev -- the device this task is destined to
task_proto -- _one_ of enum sas_proto
scatter -- pointer to scatter gather list array
num_scatter -- number of elements in scatter
total_xfer_len -- total number of bytes expected to be transferred
data_dir -- PCI_DMA_...
task_done -- callback when the task has finished execution
};
发现¶
sysfs 树具有以下目的
它向您显示当前 SAS 域的物理布局,即域在物理世界中的当前外观。
显示一些 _在_发现_时_ 的设备参数。
这是一个指向 tree(1) 程序的链接,它在查看 SAS 域时非常有用:ftp://mama.indstate.edu/linux/tree/
我希望用户空间应用程序实际创建此图形界面。
也就是说,sysfs 域树不会显示或保持状态,例如,如果您更改 READY LED MEANING 设置的含义,但它会向您显示域设备的当前连接状态。
保持内部设备状态更改是上层(命令集驱动程序)和用户空间的责任。
当一个或多个设备从域中拔出时,这会立即反映在 sysfs 树中,并且设备将从系统中删除。
结构体 domain_device 描述了 SAS 域中的任何设备。它完全由 SAS 层管理。任务指向域设备,这是 SAS LLDD 知道将任务发送到哪里。SAS LLDD 仅读取 domain_device 结构的内容,但它永远不会创建或销毁一个。
来自用户空间的扩展器管理¶
在 sysfs 的每个扩展器目录中,都有一个名为“smp_portal”的文件。它是一个二进制 sysfs 属性文件,它实现了一个 SMP 门户(注意:这 不是 SMP 端口),用户空间应用程序可以向其发送 SMP 请求并接收 SMP 响应。
功能非常简单
构建您要发送的 SMP 帧。格式和布局在 SAS 规范中描述。将 CRC 字段设置为 0。
open(2)
以 RW 模式打开扩展器的 SMP 门户 sysfs 文件。
write(2)
写入您在 1 中构建的帧。
read(2)
读取您期望接收到的帧的数据量。如果您收到与您期望接收到的数据量不同的数据,则出现某种错误。
close(2)
所有这些过程都在文件“expander_conf.c”中的函数 do_smp_func() 及其调用者中详细显示。
内核功能在文件“sas_expander.c”中实现。
程序“expander_conf.c”实现了这一点。它接受一个参数,即扩展器的 SMP 门户的 sysfs 文件名,并提供扩展器信息,包括路由表。
SMP 门户让您完全控制扩展器,因此请小心。