SAS层

SAS层是一个管理SAS LLDD的管理基础设施。它位于SCSI核心和SAS LLDD之间。布局如下:SCSI核心关注SAM/SPC问题,而SAS LLDD+定序器关注phy/OOB/链路管理,SAS层关注以下内容:

  • SAS Phy/端口/HA事件管理(LLDD生成,SAS层处理),

  • SAS端口管理(创建/销毁),

  • SAS域发现和重新验证,

  • SAS域设备管理,

  • SCSI主机注册/注销,

  • 向SCSI核心(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;
};

然后,所有phy都是HA结构中my_phy的数组(如下所示)。

然后,在初始化phy时,您也初始化sas_phy结构以及自己的phy结构。

通常,phy由LLDD管理,端口由SAS层管理。因此,phy由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可能希望在该phy/端口上伪造或提供不同的SAS地址,这允许它执行此操作。最好从IDENTIFY帧复制sas地址,或者可能为直接连接的SATA设备生成SAS地址。发现过程可能会稍后更改此值。

frame_rcvd
  • 这是您在获取IDENTIFY/FIS帧时复制帧的位置;您锁定,复制,设置frame_rcvd_size并解锁锁定,然后调用事件。它是一个指针,因为无法_精确_知道硬件帧大小,因此您在phy结构中定义实际数组,并让此指针指向它。您将帧从可DMA的内存复制到持有锁定的区域。

sas_prim
  • 这是接收到原语时它们所在的位置。请参阅sas.h。获取锁,设置原语,释放锁,通知。

port
  • 如果phy属于端口,则此项指向sas_port -- LLDD仅读取此项。它指向此phy所属的sas_port。由SAS层设置。

ha
  • 可以设置;SAS层无论如何都会设置它。

lldd_phy
  • 您应将其设置为指向您的phy,以便当SAS层调用您的回调之一并将phy传递给您时,您可以更快地找到您要查找的内容。如果嵌入了sas_phy,您也可以使用container_of -- 无论您喜欢什么。

struct sas_port

LLDD不设置此结构的任何字段 -- 它只读取它们。它们应该是不言自明的。

phy_mask是32位的,这应该足够了,因为我还没有听说过HA拥有超过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) 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数组中存在的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管理

/* Phy management */
int (*lldd_control_phy)(struct sas_phy *, enum phy_func);
lldd_ha
  • 将其设置为指向您的HA结构。如果像上面那样嵌入它,您也可以使用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);
}
  1. 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(强制)的事件是强制性的(只有一个),

  • 标记为E(扩展器)的事件,如果它希望SAS层处理域重新验证(只有一个这样的事件)。

  • 未标记的事件是可选的。

含义

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树具有以下目的

  1. 它向您显示当前SAS域的物理布局,即,域现在在物理世界中的样子。

  2. 显示_在发现时_的一些设备参数。

这是指向tree(1)程序的链接,该程序对于查看SAS域非常有用:ftp://mama.indstate.edu/linux/tree/

我希望用户空间应用程序实际创建此图形界面。

也就是说,如果您例如更改READY LED MEANING设置的含义,则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)

  1. 以RW模式打开扩展器的SMP门户sysfs文件。

write(2)

  1. 写入您在1中构建的帧。

read(2)

  1. 读取你为构建的帧所期望接收的数据量。如果你接收到的数据量与预期不同,那么就存在某种错误。

close(2)

所有这些过程都在函数 do_smp_func() 及其调用者中详细展示,这些代码位于文件 “expander_conf.c” 中。

内核功能在文件 “sas_expander.c” 中实现。

程序 “expander_conf.c” 实现了这些。它接收一个参数,即指向扩展器的 SMP 端口的 sysfs 文件名,并给出扩展器信息,包括路由表。

SMP 端口让你完全控制扩展器,因此请务必小心。