编写设备驱动程序

第 18 章 SCSI 主机总线适配器驱动程序

本章介绍有关创建 SCSI 主机总线适配器 (host bus adapter, HBA) 驱动程序的信息。本章提供了用于说明典型 HBA 驱动程序的结构的样例代码。样例代码说明了如何使用 Sun 公用 SCSI 体系结构 (Sun Common SCSI Architecture, SCSA) 提供的 HBA 驱动程序接口。本章介绍有关以下主题的信息:

主机总线适配器驱动程序介绍

第 17 章中所述,DDI/DKI 可将 SCSI 设备的软件接口分成以下两个主要部分:

目标设备是指连接到 SCSI 总线上的设备,如磁盘或磁带机。目标驱动程序是指作为设备驱动程序安装的软件组件。SCSI 总线上的每个目标设备都由一个目标驱动程序实例控制。

主机总线适配器设备是指 HBA 硬件,如 SBus 或 PCI SCSI 适配卡。主机总线适配器驱动程序是指作为设备驱动程序安装的软件组件。例如,SPARC 计算机中的 esp 驱动程序、x86 计算机中的 ncrs 驱动程序以及适用于这两种体系结构的 isp 驱动程序。一个 HBA 驱动程序实例可控制系统中配置的它的各个主机总线适配器设备。

Sun 公用 SCSI 体系结构 (Sun Common SCSI Architecture, SCSA) 定义了目标组件和 HBA 组件之间的接口。


注 –

了解 SCSI 目标驱动程序是编写有效的 SCSI HBA 驱动程序的基本先决条件。有关 SCSI 目标驱动程序的信息,请参见第 17 章。目标驱动程序开发者通过阅读本章也会有所收益。


主机总线适配器驱动程序负责执行以下任务:

SCSI 接口

SCSA 是 DDI/DKI 编程接口,用于将 SCSI 命令从目标驱动程序传送到主机适配器驱动程序。通过与 SCSA 保持兼容,目标驱动程序可以将 SCSI 命令和序列的任何组合轻松传递到目标设备。无需了解主机适配器的硬件实现。从概念上讲,SCSA 会将生成 SCSI 命令与将命令(和数据)传输到 SCSI 总线这两个过程分离开来。SCSA 通过 HBA 传输层管理目标驱动程序与 HBA 驱动程序之间的连接,如下图所示:

图 18–1 SCSA 接口

图中显示了目标驱动程序与 SCSI 设备之间的主机总线适配器传输层。

HBA 传输层是一个硬件和软件层,负责将 SCSI 命令传输到 SCSI 目标设备。HBA 驱动程序会响应 SCSI 目标驱动程序通过 SCSA 发出的请求,提供资源分配、DMA 管理和传输服务。另外,主机适配器驱动程序还会管理主机适配器硬件以及执行命令所需的 SCSI 协议。完成命令后,HBA 驱动程序将调用目标驱动程序的 SCSI pkt 命令完成例程。

以下示例说明了此流程,并重点介绍了如何将信息从目标驱动程序传送到 SCSA,然后再传送到 HBA 驱动程序。下图还说明了典型的传输入口点和函数调用。

图 18–2 传输层流程

图中显示了命令如何通过 HBA 传输层。

SCSA HBA 接口

SCSA HBA 接口包括 HBA 入口点、HBA 数据结构和 HBA 框架。

SCSA HBA 入口点摘要

SCSA 定义了许多 HBA 驱动程序入口点。下表中列出了这些入口点。配置连接到 HBA 驱动程序的目标驱动程序实例时,系统将会调用这些入口点。另外,目标驱动程序发出 SCSA 请求时,也会调用这些入口点。有关更多信息,请参见SCSA HBA 驱动程序入口点

表 18–1 SCSA HBA 入口点摘要

函数名 

调用原因 

tran_abort(9E)

目标驱动程序调用 scsi_abort(9F)

tran_bus_reset(9E)

系统重置总线 

tran_destroy_pkt(9E)

目标驱动程序调用 scsi_destroy_pkt(9F)

tran_dmafree(9E)

目标驱动程序调用 scsi_dmafree(9F)

tran_getcap(9E)

目标驱动程序调用 scsi_ifgetcap(9F)

tran_init_pkt(9E)

目标驱动程序调用 scsi_init_pkt(9F)

tran_quiesce(9E)

系统使总线处于静止状态 

tran_reset(9E)

目标驱动程序调用 scsi_reset(9F)

tran_reset_notify(9E)

目标驱动程序调用 scsi_reset_notify(9F)

tran_setcap(9E)

目标驱动程序调用 scsi_ifsetcap(9F)

tran_start(9E)

目标驱动程序调用 scsi_transport(9F)

tran_sync_pkt(9E)

目标驱动程序调用 scsi_sync_pkt(9F)

tran_tgt_free(9E)

系统分离目标设备实例 

tran_tgt_init(9E)

系统连接目标设备实例 

tran_tgt_probe(9E)

目标驱动程序调用 scsi_probe(9F)

tran_unquiesce(9E)

系统恢复总线上的活动 

SCSA HBA 数据结构

SCSA 定义了多种数据结构,以便可在目标驱动程序和 HBA 驱动程序之间交换信息。其中包括以下数据结构:

scsi_hba_tran() 结构

HBA 驱动程序的每个实例都必须在 attach(9E) 入口点中使用 scsi_hba_tran_alloc(9F) 函数分配 scsi_hba_tran(9S) 结构。scsi_hba_tran_alloc() 函数可初始化 scsi_hba_tran 结构。HBA 驱动程序必须初始化传输结构中的特定向量才能指向 HBA 驱动程序中的入口点。初始化 scsi_hba_tran 结构后,HBA 驱动程序通过调用 scsi_hba_attach_setup(9F) 函数将传输结构导出到 SCSA。


注意 – 注意 –

由于 SCSA 将指向传输结构的指针保存在 devinfo 节点的驱动程序专用字段中,因此 HBA 驱动程序决不能使用 ddi_set_driver_private(9F)。但是,HBA 驱动程序可以使用 ddi_get_driver_private(9F) 来检索指向传输结构的指针。


SCSA 接口要求 HBA 驱动程序提供许多可通过 scsi_hba_tran 结构调用的入口点。有关更多信息,请参见SCSA HBA 驱动程序入口点

scsi_hba_tran 结构包含以下字段:

struct scsi_hba_tran {
    dev_info_t          *tran_hba_dip;          /* HBAs dev_info pointer */
    void                *tran_hba_private;      /* HBA softstate */
    void                *tran_tgt_private;      /* HBA target private pointer */
    struct scsi_device  *tran_sd;               /* scsi_device */
    int                 (*tran_tgt_init)();     /* Transport target */
                                                /* Initialization */
    int                 (*tran_tgt_probe)();    /* Transport target probe */
    void                (*tran_tgt_free)();     /* Transport target free */
    int                 (*tran_start)();        /* Transport start */
    int                 (*tran_reset)();        /* Transport reset */
    int                 (*tran_abort)();        /* Transport abort */
    int                 (*tran_getcap)();       /* Capability retrieval */
    int                 (*tran_setcap)();       /* Capability establishment */
    struct scsi_pkt     *(*tran_init_pkt)();    /* Packet and DMA allocation */
    void                (*tran_destroy_pkt)();  /* Packet and DMA */
                                                /* Deallocation */
    void                (*tran_dmafree)();      /* DMA deallocation */
    void                (*tran_sync_pkt)();     /* Sync DMA */
    void                (*tran_reset_notify)(); /* Bus reset notification */
    int                 (*tran_bus_reset)();    /* Reset bus only */
    int                 (*tran_quiesce)();      /* Quiesce a bus */
    int                 (*tran_unquiesce)();    /* Unquiesce a bus */
    int                 tran_interconnect_type; /* transport interconnect */
};

下面的描述提供了有关这些 scsi_hba_tran 结构字段的更多信息:

tran_hba_dip

指向 HBA 设备实例 dev_info 结构的指针。函数 scsi_hba_attach_setup(9F) 可用于设置此字段。

tran_hba_private

指向 HBA 驱动程序维护的专用数据的指针。通常,tran_hba_private 包含指向 HBA 驱动程序状态结构的指针。

tran_tgt_private

指向使用克隆时 HBA 驱动程序维护的专用数据的指针。通过在调用 scsi_hba_attach_setup(9F) 时指定 SCSI_HBA_TRAN_CLONE,可对每个目标克隆一次 scsi_hba_tran(9S) 结构。借助该方法,HBA 可将此字段初始化为指向 tran_tgt_init(9E) 入口点中按目标实例的数据结构。如果未指定 SCSI_HBA_TRAN_CLONE,则 tran_tgt_privateNULL,并且决不能引用 tran_tgt_private。有关更多信息,请参见传输结构克隆

tran_sd

指向克隆时使用的按目标实例的 scsi_device(9S) 结构的指针。如果将 SCSI_HBA_TRAN_CLONE 传递给 scsi_hba_attach_setup(9F),则 tran_sd 会初始化指向按目标的 scsi_device 结构。在代表目标调用任何 HBA 函数之前,将进行此初始化。如果未指定 SCSI_HBA_TRAN_CLONE,则 tran_sdNULL,并且决不能引用 tran_sd。有关更多信息,请参见传输结构克隆

tran_tgt_init

指向初始化目标设备实例时调用的 HBA 驱动程序入口点的指针。如果无需进行按目标的初始化,则 HBA 可保持将 tran_tgt_init 设置为 NULL

tran_tgt_probe

指向在目标驱动程序实例调用 scsi_probe(9F) 时调用的 HBA 驱动程序入口点的指针。调用该例程可探测目标设备是否存在。如果此 HBA 无需进行目标探测自定义,则 HBA 应将 tran_tgt_probe 设置为 scsi_hba_probe(9F)

tran_tgt_free

指向在目标设备实例被销毁时调用的 HBA 驱动程序入口点的指针。如果无需进行按目标的取消分配,则 HBA 可保持将 tran_tgt_free 设置为 NULL

tran_start

指向在目标驱动程序调用 scsi_transport(9F) 时调用的 HBA 驱动程序入口点的指针。

tran_reset

指向在目标驱动程序调用 scsi_reset(9F) 时调用的 HBA 驱动程序入口点的指针。

tran_abort

指向在目标驱动程序调用 scsi_abort(9F) 时调用的 HBA 驱动程序入口点的指针。

tran_getcap

指向在目标驱动程序调用 scsi_ifgetcap(9F) 时调用的 HBA 驱动程序入口点的指针。

tran_setcap

指向在目标驱动程序调用 scsi_ifsetcap(9F) 时调用的 HBA 驱动程序入口点的指针。

tran_init_pkt

指向在目标驱动程序调用 scsi_init_pkt(9F) 时调用的 HBA 驱动程序入口点的指针。

tran_destroy_pkt

指向在目标驱动程序调用 scsi_destroy_pkt(9F) 时调用的 HBA 驱动程序入口点的指针。

tran_dmafree

指向在目标驱动程序调用 scsi_dmafree(9F) 时调用的 HBA 驱动程序入口点的指针。

tran_sync_pkt

指向在目标驱动程序调用 scsi_sync_pkt(9F) 时调用的 HBA 驱动程序入口点的指针。

tran_reset_notify

指向在目标驱动程序调用 tran_reset_notify(9E) 时调用的 HBA 驱动程序入口点的指针。

tran_bus_reset

重置 SCSI 总线但不重置目标的函数项。

tran_quiesce

等待所有未完成的命令完成并阻塞(或排队)任何发出的 I/O 请求的函数项。

tran_unquiesce

允许 I/O 活动在 SCSI 总线上恢复的函数项。

tran_interconnect_type

表示 services.h 头文件中定义的传输互连类型的整数值。

scsi_address 结构

scsi_address(9S) 结构可为目标驱动程序实例分配和传输的各个 SCSI 命令提供传输及寻址信息。

scsi_address 结构包含以下字段:

struct scsi_address {
    struct scsi_hba_tran    *a_hba_tran;    /* Transport vectors */
    ushort_t                a_target;       /* Target identifier */
    uchar_t                 a_lun;          /* LUN on that target */
    uchar_t                 a_sublun;       /* Sub LUN on that LUN */
                                            /* Not used */
};
a_hba_tran

指向 HBA 驱动程序分配和初始化的 scsi_hba_tran(9S) 结构的指针。如果将 SCSI_HBA_TRAN_CLONE 指定为 scsi_hba_attach_setup(9F) 的标志, 则 a_hba_tran 指向该结构的副本。

a_target

标识 SCSI 总线上的 SCSI 目标。

a_lun

标识 SCSI 目标的 SCSI 逻辑单元。

scsi_device 结构

HBA 框架可为目标设备的各个实例分配和初始化 scsi_device(9S) 结构。该框架调用 HBA 驱动程序的 tran_tgt_init(9E) 入口点之前,将进行分配和初始化。此结构可存储有关每个 SCSI 逻辑单元的信息,包括指向信息区(包含通用信息和特定于设备的信息)的指针。对于连接到系统的每个目标设备实例,都存在一个 scsi_device(9S) 结构。

如果按目标的初始化成功,则 HBA 框架会使用 ddi_set_driver_private(9F) 将目标驱动程序的按实例的专用数据设置为指向 scsi_device(9S) 结构。请注意,如果 tran_tgt_init () 返回成功信息或该向量为 null,则表明初始化成功。

scsi_device(9S) 结构包含以下字段:

struct scsi_device {
    struct scsi_address           sd_address;    /* routing information */
    dev_info_t                    *sd_dev;       /* device dev_info node */
    kmutex_t                      sd_mutex;      /* mutex used by device */
    void                          *sd_reserved;
    struct scsi_inquiry           *sd_inq;
    struct scsi_extended_sense    *sd_sense;
    caddr_t                       sd_private;    /* for driver's use */
};

其中:

sd_address

为了进行 SCSI 资源分配而传递给例程的数据结构。

sd_dev

指向目标的 dev_info 结构的指针。

sd_mutex

供目标驱动程序使用的互斥锁。此互斥锁通过 HBA 框架进行初始化。目标驱动程序可将此互斥锁用作按设备的互斥锁。在调用 scsi_transport(9F)scsi_poll(9F) 期间,不应持有此互斥锁。有关互斥锁的更多信息,请参见第 3 章

sd_inq

目标设备的 SCSI 查询数据的指针。scsi_probe(9F) 例程可用于分配缓冲区、填充该缓冲区并将该缓冲区附加到此字段。

sd_sense

指向用于包含设备中的请求检测数据的缓冲区的指针。目标驱动程序必须分配和管理此缓冲区本身。有关更多信息,请参见attach() 入口点中目标驱动程序的 attach(9E) 例程。

sd_private

供目标驱动程序使用的指针字段。此字段通常用于存储指向专用目标驱动程序状态结构的指针。

scsi_pkt 结构 (HBA)

要执行 SCSI 命令,目标驱动程序必须首先为该命令分配 scsi_pkt(9S) 结构。然后,目标驱动程序必须指定其自身的专用数据区长度、命令状态和命令长度。HBA 驱动程序负责实现 tran_init_pkt(9E) 入口点中的包分配。另外,HBA 驱动程序还负责释放其 tran_destroy_pkt(9E) 入口点中的包。有关更多信息,请参见scsi_pkt 结构(目标驱动程序)

scsi_pkt(9S) 结构包含以下字段:

struct scsi_pkt {
    opaque_t pkt_ha_private;             /* private data for host adapter */
    struct scsi_address pkt_address;     /* destination address */
    opaque_t pkt_private;                /* private data for target driver */
    void (*pkt_comp)(struct scsi_pkt *); /* completion routine */
    uint_t  pkt_flags;                   /* flags */
    int     pkt_time;                    /* time allotted to complete command */
    uchar_t *pkt_scbp;                   /* pointer to status block */
    uchar_t *pkt_cdbp;                   /* pointer to command block */
    ssize_t pkt_resid;                   /* data bytes not transferred */
    uint_t  pkt_state;                   /* state of command */
    uint_t  pkt_statistics;              /* statistics */
    uchar_t pkt_reason;                  /* reason completion called */
};

其中:

pkt_ha_private

指向按命令的 HBA 驱动程序专用数据的指针。

pkt_address

指向用于为此命令提供地址信息的 scsi_address(9S) 结构的指针。

pkt_private

指向按包的目标驱动程序专用数据的指针。

pkt_comp

指向在传输层完成此命令时 HBA 驱动程序调用的目标驱动程序完成例程的指针。

pkt_flags

命令的标志。

pkt_time

指定命令的完成超时时间(以秒为单位)。

pkt_scbp

指向命令的状态完成块的指针。

pkt_cdbp

指向命令的命令描述符块 (command descriptor block, CDB) 的指针。

pkt_resid

命令完成时传送的数据字节计数。此字段也可能会用于指定尚未分配资源的数据量。在传输过程中,HBA 必须修改此字段。

pkt_state

命令的状态。在传输过程中,HBA 必须修改此字段。

pkt_statistics

提供命令在传输层中发生的事件的历史记录。在传输过程中,HBA 必须修改此字段。

pkt_reason

命令完成的原因。在传输过程中,HBA 必须修改此字段。

按目标实例的数据

在执行 attach(9E) 期间,HBA 驱动程序必须分配 scsi_hba_tran(9S) 结构。然后,HBA 驱动程序必须将此传输结构中的向量初始化为指向 HBA 驱动程序所需的入口点。此 scsi_hba_tran 结构随后将传递给 scsi_hba_attach_setup(9F)

scsi_hba_tran 结构包含 tran_hba_private 字段,该字段可用于引用 HBA 驱动程序的按实例状态。

每个 scsi_address(9S) 结构都包含一个指向 scsi_hba_tran 结构的指针。此外,scsi_address 结构还为特定的目标设备提供了目标(即 a_target)和逻辑单元 (a_lun) 地址。 通过 scsi_device(9S) 结构可直接或间接向 HBA 驱动程序的每个入口点传递一个指向 scsi_address 结构的指针。因此,HBA 驱动程序可以引用其自身的状态。HBA 驱动程序还可以标识已寻址的目标设备。

下图说明了用于传输操作的 HBA 数据结构。

图 18–3 HBA 传输结构

图中显示了 HBA 传输层中涉及的结构的关系。

传输结构克隆

如果 HBA 驱动程序需要维护 scsi_hba_tran(9S) 结构中按目标的专用数据,则克隆可能会非常有用。克隆还可用于维护比 scsi_address(9S) 结构中所提供的更为复杂的地址。

在克隆过程中,HBA 驱动程序仍必须在执行 attach(9E) 期间分配 scsi_hba_tran 结构。此外,HBA 驱动程序还必须初始化 HBA 驱动程序的 tran_hba_private 软状态指针和入口点向量。当框架开始将目标驱动程序实例连接到 HBA 驱动程序时,将会产生差异。调用 HBA 驱动程序的 tran_tgt_init(9E) 入口点之前,框架会克隆与 HBA 的该实例关联的 scsi_hba_tran 结构。相应地,为特定目标设备实例分配和初始化的每个 scsi_address 结构都会指向 scsi_hba_tran 结构的按目标实例的副本scsi_address 结构不会指向 HBA 驱动程序在执行 attach() 期间分配的 scsi_hba_tran 结构。

指定克隆时,HBA 驱动程序可以使用两个重要的指针。这些指针包含在 scsi_hba_tran 结构中。第一个指针是 tran_tgt_private 字段,驱动程序可以使用该指针指向按目标的 HBA 专用数据。tran_tgt_private 指针非常有用,例如在 HBA 驱动程序需要维护比 a_targeta_lun 所提供的更为复杂的地址的情况下。第二个指针是 tran_sd 字段,该指针指向引用特定目标设备的 scsi_device(9S) 结构。

指定克隆时,HBA 驱动程序必须分配和初始化按目标的数据。HBA 驱动程序随后必须在执行其 tran_tgt_init(9E) 入口点过程中将 tran_tgt_private 字段初始化为指向此数据。 HBA 驱动程序必须在执行其 tran_tgt_free(9E) 入口点过程中释放按目标的数据。

克隆时,框架会在调用 HBA 驱动程序 tran_tgt_init() 入口点之前将 tran_sd 字段初始化为指向 scsi_device 结构。该驱动程序通过将 SCSI_HBA_TRAN_CLONE 标志传递给 scsi_hba_attach_setup(9F) 来请求克隆。下图说明了用于克隆传输操作的 HBA 数据结构。

图 18–4 克隆传输操作

图中显示了克隆的 HBA 结构的示例。

SCSA HBA 函数

SCSA 还提供了许多函数。下表中列出了这些函数,供 HBA 驱动程序使用。

表 18–2 SCSA HBA 函数

函数名 

进行调用的驱动程序入口点 

scsi_hba_init(9F)

_init(9E)

scsi_hba_fini(9F)

_fini(9E)

scsi_hba_attach_setup(9F)

attach(9E)

scsi_hba_detach(9F)

detach(9E)

scsi_hba_tran_alloc(9F)

attach(9E)

scsi_hba_tran_free(9F)

detach(9E)

scsi_hba_probe(9F)

tran_tgt_probe(9E)

scsi_hba_pkt_alloc(9F)

tran_init_pkt(9E)

scsi_hba_pkt_free(9F)

tran_destroy_pkt(9E)

scsi_hba_lookup_capstr(9F)

tran_getcap(9E)tran_setcap(9E)

HBA 驱动程序的相关性和配置问题

除将 SCSA HBA 入口点、结构和函数合并到驱动程序中外,开发者还必须处理驱动程序的相关性和配置问题。这些问题涉及配置属性、相关性声明、状态结构和按命令的结构、模块初始化入口点及自动配置入口点。

声明和结构

HBA 驱动程序必须包含以下头文件:

#include <sys/scsi/scsi.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

要向系统通知模块依赖于 SCSA 例程,必须使用以下命令生成驱动程序二进制代码。有关 SCSA 例程的更多信息,请参见SCSA HBA 接口


% ld -r xx.o -o xx -N "misc/scsi"

样例代码是由 QLogic Intelligent SCSI 外围设备的简化 isp 驱动程序派生而来。isp 驱动程序支持 WIDE SCSI,每个目标最多 15 个目标设备和 8 个逻辑单元 (logical unit, LUN)。

每个命令的结构

通常,HBA驱动程序需要定义结构以维护目标驱动程序提交的各个命令的状态。按命令的结构的布局完全取决于设备驱动程序编写者。该布局需要反映硬件的功能和特征以及驱动程序使用的软件算法。

以下结构是每个命令的结构的示例。本章中的其余代码段将使用此结构说明 HBA 接口。

struct isp_cmd {
     struct isp_request     cmd_isp_request;
     struct isp_response    cmd_isp_response;
     struct scsi_pkt        *cmd_pkt;
     struct isp_cmd         *cmd_forw;
     uint32_t               cmd_dmacount;
     ddi_dma_handle_t       cmd_dmahandle;
     uint_t                 cmd_cookie;
     uint_t                 cmd_ncookies;
     uint_t                 cmd_cookiecnt;
     uint_t                 cmd_nwin;
     uint_t                 cmd_curwin;
     off_t                  cmd_dma_offset;
     uint_t                 cmd_dma_len;
     ddi_dma_cookie_t       cmd_dmacookies[ISP_NDATASEGS];
     u_int                  cmd_flags;
     u_short                cmd_slot;
     u_int                  cmd_cdblen;
     u_int                  cmd_scblen;
 };

模块初始化入口点

本节介绍 SCSI HBA 驱动程序执行的操作的入口点。

以下 SCSI HBA 驱动程序代码说明了典型的 dev_ops(9S) 结构。该驱动程序必须将此结构中的 devo_bus_ops 字段初始化为 NULL。SCSI HBA 驱动程序可提供特殊用途的叶驱动程序接口,在这种情况下,devo_cb_ops 字段可能会指向 cb_ops(9S) 结构。在此示例中,由于未导出任何叶驱动程序接口,因此 devo_cb_ops 字段会初始化为 NULL

_init() 入口点(SCSI HBA 驱动程序)

_init(9E) 函数用于初始化可装入模块。_init() 在可装入模块中的其他任何例程之前调用。

在 SCSI HBA 中,_init() 函数在调用 mod_install(9F) 之前,必须先调用 scsi_hba_init(9F) 来通知框架是否存在 HBA 驱动程序。如果 scsi_hba__init () 返回非零值,则 _init() 应返回该值。否则,_init() 必须返回 mod_install(9F) 所返回的值。

该驱动程序在调用 mod_install(9F) 之前应初始化任何必需的全局状态。

如果 mod_install() 失败,则 _init() 函数必须释放分配的所有全局资源。_init() 必须在返回之前调用 scsi_hba_fini(9F)

以下示例使用全局互斥锁说明如何分配对驱动程序的所有实例而言具有全局性的数据。该代码声明了全局互斥锁和软状态结构信息。全局互斥锁和软状态是在执行 _init() 的过程中初始化的。

_fini() 入口点(SCSI HBA 驱动程序)

如果系统准备尝试卸载 SCSI HBA 驱动程序,则会调用 _fini(9E) 函数。 _fini() 函数必须调用 mod_remove(9F) 来确定是否可以卸载该驱动程序。如果 mod_remove() 返回 0,则可以卸载该模块。HBA 驱动程序必须取消分配 _init(9E) 中分配的所有全局资源。HBA 驱动程序还必须调用 scsi_hba_fini(9F)

_fini() 必须返回 mod_remove() 所返回的值。


注 –

除非 mod_remove(9F) 返回 0,否则 HBA 驱动程序决不能释放任何资源或调用 scsi_hba_fini(9F)


示例 18–1 说明了 SCSI HBA 的模块初始化。


示例 18–1 SCSI HBA 的模块初始化

static struct dev_ops isp_dev_ops = {
    DEVO_REV,       /* devo_rev */
    0,              /* refcnt  */
    isp_getinfo,    /* getinfo */
    nulldev,        /* probe */
    isp_attach,     /* attach */
    isp_detach,     /* detach */
    nodev,          /* reset */
    NULL,           /* driver operations */
    NULL,           /* bus operations */
    isp_power,      /* power management */
};

/*
 * Local static data
 */
static kmutex_t      isp_global_mutex;
static void          *isp_state;

int
_init(void)
{
    int     err;
    
    if ((err = ddi_soft_state_init(&isp_state,
        sizeof (struct isp), 0)) != 0) {
        return (err);
    }
    if ((err = scsi_hba_init(&modlinkage)) == 0) {
        mutex_init(&isp_global_mutex, "isp global mutex",
        MUTEX_DRIVER, NULL);
        if ((err = mod_install(&modlinkage)) != 0) {
            mutex_destroy(&isp_global_mutex);
            scsi_hba_fini(&modlinkage);
            ddi_soft_state_fini(&isp_state);    
        }
    }
    return (err);
}

int
_fini(void)
{
    int     err;
    
    if ((err = mod_remove(&modlinkage)) == 0) {
        mutex_destroy(&isp_global_mutex);
        scsi_hba_fini(&modlinkage);
        ddi_soft_state_fini(&isp_state);
    }
    return (err);
}

自动配置入口点

dev_ops(9S) 结构与每个设备驱动程序关联。通过该结构,内核可以查找驱动程序的自动配置入口点。有关这些自动配置例程的完整说明,请参见第 6 章。本节仅介绍与 SCSI HBA 驱动程序执行的操作关联的那些入口点。这些入口点包括 attach(9E)detach(9E)

attach() 入口点(SCSI HBA 驱动程序)

在为设备配置和附加驱动程序实例时,SCSI HBA 驱动程序的 attach(9E) 入口点将执行多个任务。对于实际设备的典型驱动程序,必须处理以下操作系统和硬件问题:

软状态结构

分配按设备实例的软状态结构时,如果发生错误,驱动程序必须仔细清理。

DMA

HBA 驱动程序必须通过正确初始化 ddi_dma_attr_t 结构来描述其 DMA 引擎的属性。

static ddi_dma_attr_t isp_dma_attr = {
     DMA_ATTR_V0,        /* ddi_dma_attr version */
     0,                  /* low address */
     0xffffffff,         /* high address */
     0x00ffffff,         /* counter upper bound */
     1,                  /* alignment requirements */
     0x3f,               /* burst sizes */
     1,                  /* minimum DMA access */
     0xffffffff,         /* maximum DMA access */
     (1<<24)-1,          /* segment boundary restrictions */
     1,                  /* scatter-gather list length */
     512,                /* device granularity */
     0                   /* DMA flags */
};

如果该驱动程序提供 DMA,则还应检查其硬件是否已安装在支持 DMA 的槽中:

if (ddi_slaveonly(dip) == DDI_SUCCESS) {
    return (DDI_FAILURE);
}

传输结构

驱动程序应进一步分配和初始化此实例的传输结构。tran_hba_private 字段会设置为指向此实例的软状态结构。如果无需特殊的探测自定义,则可将 tran_tgt_probe 字段设置为 NULL 以实现缺省行为。

tran = scsi_hba_tran_alloc(dip, SCSI_HBA_CANSLEEP);

isp->isp_tran                   = tran;
isp->isp_dip                    = dip;

tran->tran_hba_private          = isp;
tran->tran_tgt_private          = NULL;
tran->tran_tgt_init             = isp_tran_tgt_init;
tran->tran_tgt_probe            = scsi_hba_probe;
tran->tran_tgt_free             = (void (*)())NULL;

tran->tran_start                = isp_scsi_start;
tran->tran_abort                = isp_scsi_abort;
tran->tran_reset                = isp_scsi_reset;
tran->tran_getcap               = isp_scsi_getcap;
tran->tran_setcap               = isp_scsi_setcap;
tran->tran_init_pkt             = isp_scsi_init_pkt;
tran->tran_destroy_pkt          = isp_scsi_destroy_pkt;
tran->tran_dmafree              = isp_scsi_dmafree;
tran->tran_sync_pkt             = isp_scsi_sync_pkt;
tran->tran_reset_notify         = isp_scsi_reset_notify;
tran->tran_bus_quiesce          = isp_tran_bus_quiesce
tran->tran_bus_unquiesce        = isp_tran_bus_unquiesce
tran->tran_bus_reset            = isp_tran_bus_reset
tran->tran_interconnect_type    = isp_tran_interconnect_type

附加 HBA 驱动程序

驱动程序应附加此设备实例并执行错误清理(如有必要)。

i = scsi_hba_attach_setup(dip, &isp_dma_attr, tran, 0);
if (i != DDI_SUCCESS) {
    /* do error recovery */
    return (DDI_FAILURE);
}

寄存器映射

驱动程序应在其设备的寄存器中进行映射。驱动程序需要指定以下项:

ddi_device_acc_attr_t    dev_attributes;

     dev_attributes.devacc_attr_version = DDI_DEVICE_ATTR_V0;
     dev_attributes.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
     dev_attributes.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;

     if (ddi_regs_map_setup(dip, 0, (caddr_t *)&isp->isp_reg,
     0, sizeof (struct ispregs), &dev_attributes,
     &isp->isp_acc_handle) != DDI_SUCCESS) {
        /* do error recovery */
        return (DDI_FAILURE);
     }

添加中断处理程序

驱动程序必须首先获取 iblock cookie 才能初始化驱动程序处理程序中使用的所有互斥锁。仅当初始化这些互斥锁后才能添加中断处理程序。

i = ddi_get_iblock_cookie(dip, 0, &isp->iblock_cookie};
if (i != DDI_SUCCESS) {
    /* do error recovery */
    return (DDI_FAILURE);
}

mutex_init(&isp->mutex, "isp_mutex", MUTEX_DRIVER,
(void *)isp->iblock_cookie);
i = ddi_add_intr(dip, 0, &isp->iblock_cookie,
0, isp_intr, (caddr_t)isp);
if (i != DDI_SUCCESS) {
    /* do error recovery */
    return (DDI_FAILURE);
}

如果需要高级处理程序,则应对驱动程序进行编码以提供此类处理程序。否则,驱动程序必须能够停止附加操作。有关高级中断处理的说明,请参见处理高级别中断

创建可管理电源的组件

如果主机总线适配器只需要在所有目标适配器的电源级别为 0 时关闭电源,则使用电源管理,HBA 驱动程序只需提供 power(9E) 入口点。请参阅第 12 章。另外,HBA 驱动程序还需要创建 pm-components(9P) 属性,用于描述设备实现的组件。

由于这些组件将缺省为空闲,并且电源管理框架的缺省相关性处理会确保主机总线适配器在目标适配器每次通电时也随之通电,因此无需再执行任何操作。如果自动启用自动电源管理,则该处理还将在所有目标适配器都断电时关闭主机总线适配器电源。

报告附加状态

最后,驱动程序应报告已附加的此驱动程序实例并返回成功信息。

ddi_report_dev(dip);
    return (DDI_SUCCESS);

detach() 入口点(SCSI HBA 驱动程序)

驱动程序会执行标准分离操作,包括调用 scsi_hba_detach(9F)

SCSA HBA 驱动程序入口点

HBA 驱动程序可以通过 SCSA 接口与目标驱动程序协同工作。SCSA 接口要求 HBA 驱动程序提供许多可通过 scsi_hba_tran(9S) 结构调用的入口点。

这些入口点分为以下五个功能组:

下表按功能组列出了 SCSA HBA 入口点。

表 18–3 SCSA 入口点

功能组 

组内入口点 

说明 

目标驱动程序实例初始化 

tran_tgt_init(9E)

执行按目标的初始化(可选) 

 

tran_tgt_probe(9E)

探测 SCSI 总线是否存在目标(可选) 

 

tran_tgt_free(9E)

执行按目标的取消分配(可选) 

资源分配 

tran_init_pkt(9E)

分配 SCSI 包和 DMA 资源 

 

tran_destroy_pkt(9E)

释放 SCSI 包和 DMA 资源 

 

tran_sync_pkt(9E)

执行 DMA 前后同步内存 

 

tran_dmafree(9E)

释放 DMA 资源 

命令传输 

tran_start(9E)

传输 SCSI 命令 

功能管理 

tran_getcap(9E)

查询功能值 

 

tran_setcap(9E)

设置功能值 

中止和重置 

tran_abort(9E)

中止未完成的 SCSI 命令 

 

tran_reset(9E)

重置目标设备或 SCSI 总线 

 

tran_bus_reset(9E)

重置 SCSI 总线 

 

tran_reset_notify(9E)

请求向目标发出总线重置通知(可选) 

动态重新配置 

tran_quiesce(9E)

停止总线上的活动 

 

tran_unquiesce(9E)

恢复总线上的活动 

目标驱动程序实例初始化

以下各节介绍了目标入口点。

tran_tgt_init() 入口点

使用 tran_tgt_init(9E) 入口点,HBA 可以分配和初始化按目标的任何资源。此外,tran_tgt_init() 还允许 HBA 将设备地址限定为该特定 HBA 的有效且可支持的地址。如果返回 DDI_FAILURE,则不会探测或附加该设备的目标驱动程序实例。

无需使用 tran_tgt_init()。如果未提供 tran_tgt_init(),则框架会尝试探测和附加相应目标驱动程序的所有可能实例。

static int
isp_tran_tgt_init(
    dev_info_t            *hba_dip,
    dev_info_t            *tgt_dip,
    scsi_hba_tran_t       *tran,
    struct scsi_device    *sd)
{
    return ((sd->sd_address.a_target < N_ISP_TARGETS_WIDE &&
        sd->sd_address.a_lun < 8) ? DDI_SUCCESS : DDI_FAILURE);
}

tran_tgt_probe() 入口点

使用 tran_tgt_probe(9E) 入口点,HBA 可以自定义 scsi_probe(9F) 的操作(如有必要)。仅当目标驱动程序调用 scsi_probe() 时,才会调用此入口点。

HBA 驱动程序可以通过调用 scsi_hba_probe(9F) 并返回其返回值来保留 scsi_probe () 的正常操作。

无需使用此入口点。如果不需要此入口点,则 HBA 驱动程序应将 scsi_hba_tran(9S) 结构中的 tran_tgt_probe 向量设置为指向 scsi_hba_probe()

scsi_probe() 可用于分配 scsi_inquiry(9S) 结构,并将 scsi_device(9S) 结构的 sd_inq 字段设置为指向 scsi_inquiry 中的数据。scsi_hba_probe() 可自动处理此任务。scsi_unprobe(9F) 随后将释放 scsi_inquiry 数据。

除分配 scsi_inquiry 数据以外,tran_tgt_probe() 必须是无状态的,因为同一 SCSI 设备可能会多次调用 tran_tgt_probe()。通常,scsi_inquiry 数据的分配通过 scsi_hba_probe() 来处理。


注 –

scsi_inquiry(9S) 结构的分配通过 scsi_hba_probe() 自动处理。此信息仅在需要自定义 scsi_probe() 处理时才有意义。


static int
isp_tran_tgt_probe(
    struct scsi_device    *sd,
    int                   (*callback)())
{
    /*
     * Perform any special probe customization needed.
     * Normal probe handling.
     */
    return (scsi_hba_probe(sd, callback));
}

tran_tgt_free() 入口点

使用 tran_tgt_free(9E) 入口点,HBA 可以执行目标实例的所有取消分配或清理过程。此入口点是可选的。

static void
isp_tran_tgt_free(
    dev_info_t            *hba_dip,
    dev_info_t            *tgt_dip,
    scsi_hba_tran_t       *hba_tran,
    struct scsi_device    *sd)
{
    /*
     * Undo any special per-target initialization done
     * earlier in tran_tgt_init(9F) and tran_tgt_probe(9F)
     */
}

资源分配

以下各节讨论了资源分配。

tran_init_pkt() 入口点

tran_init_pkt(9E) 入口点可为目标驱动程序请求分配和初始化 scsi_pkt(9S) 结构和 DMA 资源。

目标驱动程序调用 SCSA 函数 scsi_init_pkt(9F) 时,将会调用 tran_init_pkt(9E) 入口点。

每次调用 tran_init_pkt(9E) 入口点时,都会请求执行以下三种可能服务中的一种或多种:

分配和初始化 scsi_pkt(9S) 结构

如果 pktNULL,则 tran_init_pkt(9E) 入口点必须通过 scsi_hba_pkt_alloc(9F) 分配 scsi_pkt(9S) 结构。

scsi_hba_pkt_alloc(9F) 可为以下各项分配空间:

除以下成员外,必须将 scsi_pkt(9S) 结构成员(包括 pkt)初始化为零:

这些成员是指向存储字段值的内存空间的指针,如下图所示。有关更多信息,请参阅scsi_pkt 结构 (HBA)

图 18–5 scsi_pkt(9S) 结构指针

图中显示了包含指向值而未初始化为零的那些成员的 scsi_pkt 结构。

以下示例说明了 scsi_pkt 结构的分配和初始化。


示例 18–2 SCSI 包结构的 HBA 驱动程序初始化

static struct scsi_pkt                 *
isp_scsi_init_pkt(
    struct scsi_address    *ap,
    struct scsi_pkt        *pkt,
    struct buf             *bp,
    int                    cmdlen,
    int                    statuslen,
    int                    tgtlen,
    int                    flags,
    int                    (*callback)(),
    caddr_t                arg)
{
    struct isp_cmd         *sp;
    struct isp             *isp;
    struct scsi_pkt        *new_pkt;

    ASSERT(callback == NULL_FUNC || callback == SLEEP_FUNC);

    isp = (struct isp *)ap->a_hba_tran->tran_hba_private;
   /*
    * First step of isp_scsi_init_pkt:  pkt allocation
    */
    if (pkt == NULL) {
        pkt = scsi_hba_pkt_alloc(isp->isp_dip, ap, cmdlen,
            statuslen, tgtlen, sizeof (struct isp_cmd),
            callback, arg);
        if (pkt == NULL) {
            return (NULL);
       }

       sp = (struct isp_cmd *)pkt->pkt_ha_private;
      /*
       * Initialize the new pkt
       */
       sp->cmd_pkt         = pkt;
       sp->cmd_flags       = 0;
       sp->cmd_scblen      = statuslen;
       sp->cmd_cdblen      = cmdlen;
       sp->cmd_dmahandle   = NULL;
       sp->cmd_ncookies    = 0;
       sp->cmd_cookie      = 0; 
       sp->cmd_cookiecnt   = 0;
       sp->cmd_nwin        = 0;
       pkt->pkt_address    = *ap;
       pkt->pkt_comp       = (void (*)())NULL;
       pkt->pkt_flags      = 0;
       pkt->pkt_time       = 0;
       pkt->pkt_resid      = 0;
       pkt->pkt_statistics = 0;
       pkt->pkt_reason     = 0;

       new_pkt = pkt;
    } else {
       sp = (struct isp_cmd *)pkt->pkt_ha_private;
       new_pkt = NULL;
    }
   /*
    * Second step of isp_scsi_init_pkt:  dma allocation/move
    */
    if (bp && bp->b_bcount != 0) {
        if (sp->cmd_dmahandle == NULL) {
            if (isp_i_dma_alloc(isp, pkt, bp,
            flags, callback) == 0) {
            if (new_pkt) {
                scsi_hba_pkt_free(ap, new_pkt);
            }
            return ((struct scsi_pkt *)NULL);
        }
        } else {
            ASSERT(new_pkt == NULL);
            if (isp_i_dma_move(isp, pkt, bp) == 0) {
                return ((struct scsi_pkt *)NULL);
            }
        }
    }
    return (pkt);
}

分配 DMA 资源

如果符合以下条件,则 tran_init_pkt(9E) 入口点必须分配用于数据传送的 DMA 资源:

HBA 驱动程序需要跟踪如何为特定命令分配 DMA 资源。按包的 HBA 驱动程序专用数据的标志位或 DMA 句柄可能会进行此分配。

使用 pkt 中的 PKT_DMA_PARTIAL 标志,目标驱动程序可以将数据传送按多个 SCSI 命令分类以适应整个请求。如果 HBA 硬件的分散/集中功能或系统 DMA 资源无法完成单个 SCSI 命令的请求,则此方法会非常有用。

使用 PKT_DMA_PARTIAL 标志,HBA 驱动程序可以设置 DDI_DMA_PARTIAL 标志。DDI_DMA_PARTIAL 标志有助于分配此 SCSI 命令的 DMA 资源。例如,ddi_dma_buf_bind_handle(9F) 命令可用于分配 DMA 资源。分配 DMA 资源时使用的 DMA 属性应准确说明针对 HBA 硬件执行 DMA 的能力设定的约束。如果系统只能为部分请求分配 DMA 资源,则 ddi_dma_buf_bind_handle(9F) 将返回 DDI_DMA_PARTIAL_MAP

tran_init_pkt(9E) 入口点必须在字段 pkt_resid 中返回未为此传送分配的 DMA 资源量。

目标驱动程序可以请求 tran_init_pkt(9E) 同时为该 pkt 分配 scsi_pkt(9S) 结构和 DMA 资源。在这种情况下,如果 HBA 驱动程序无法分配 DMA 资源,则该驱动程序必须在返回前释放已分配的 scsi_pkt(9S)scsi_pkt(9S) 必须通过调用 scsi_hba_pkt_free(9F) 进行释放。

目标驱动程序可能会首先分配 scsi_pkt(9S),随后再为此 pkt 分配 DMA 资源。在这种情况下,如果 HBA 驱动程序无法分配 DMA 资源,则该驱动程序决不能释放 pkt。在这种情况下,目标驱动程序负责释放 pkt


示例 18–3 HBA 驱动程序的 DMA 资源分配

static int
isp_i_dma_alloc(
    struct isp         *isp,
    struct scsi_pkt    *pkt,
    struct buf         *bp,
    int                flags,
    int                (*callback)())
{
    struct isp_cmd     *sp  = (struct isp_cmd *)pkt->pkt_ha_private;
    int                dma_flags;
    ddi_dma_attr_t     tmp_dma_attr;
    int                (*cb)(caddr_t);
    int                i;

    ASSERT(callback == NULL_FUNC || callback == SLEEP_FUNC);

    if (bp->b_flags & B_READ) {
        sp->cmd_flags &= ~CFLAG_DMASEND;
        dma_flags = DDI_DMA_READ;
    } else {
        sp->cmd_flags |= CFLAG_DMASEND;
        dma_flags = DDI_DMA_WRITE;
    }
    if (flags & PKT_CONSISTENT) {
        sp->cmd_flags |= CFLAG_CMDIOPB;
        dma_flags |= DDI_DMA_CONSISTENT;
    }
    if (flags & PKT_DMA_PARTIAL) {
        dma_flags |= DDI_DMA_PARTIAL;
    }

    tmp_dma_attr = isp_dma_attr;
    tmp_dma_attr.dma_attr_burstsizes = isp->isp_burst_size;

    cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT :
DDI_DMA_SLEEP;

    if ((i = ddi_dma_alloc_handle(isp->isp_dip, &tmp_dma_attr,
      cb, 0, &sp->cmd_dmahandle)) != DDI_SUCCESS) {
        switch (i) {
        case DDI_DMA_BADATTR:
            bioerror(bp, EFAULT);
            return (0);
        case DDI_DMA_NORESOURCES:
            bioerror(bp, 0);
            return (0);
        }
    }

    i = ddi_dma_buf_bind_handle(sp->cmd_dmahandle, bp, dma_flags,
    cb, 0, &sp->cmd_dmacookies[0], &sp->cmd_ncookies);

    switch (i) {
    case DDI_DMA_PARTIAL_MAP:
    if (ddi_dma_numwin(sp->cmd_dmahandle, &sp->cmd_nwin) ==
            DDI_FAILURE) {
        cmn_err(CE_PANIC, "ddi_dma_numwin() failed\n");
    }

    if (ddi_dma_getwin(sp->cmd_dmahandle, sp->cmd_curwin,
        &sp->cmd_dma_offset, &sp->cmd_dma_len,
        &sp->cmd_dmacookies[0], &sp->cmd_ncookies) ==
           DDI_FAILURE) {
        cmn_err(CE_PANIC, "ddi_dma_getwin() failed\n");
    }
    goto get_dma_cookies;

    case DDI_DMA_MAPPED:
    sp->cmd_nwin = 1;
    sp->cmd_dma_len = 0;
    sp->cmd_dma_offset = 0;

get_dma_cookies:
    i = 0;
    sp->cmd_dmacount = 0;
    for (;;) {
        sp->cmd_dmacount += sp->cmd_dmacookies[i++].dmac_size;

        if (i == ISP_NDATASEGS || i == sp->cmd_ncookies)
        break;
        ddi_dma_nextcookie(sp->cmd_dmahandle,
        &sp->cmd_dmacookies[i]);
    }
    sp->cmd_cookie = i;
    sp->cmd_cookiecnt = i;

    sp->cmd_flags |= CFLAG_DMAVALID;
    pkt->pkt_resid = bp->b_bcount - sp->cmd_dmacount;
    return (1);

    case DDI_DMA_NORESOURCES:
    bioerror(bp, 0);
    break;

    case DDI_DMA_NOMAPPING:
    bioerror(bp, EFAULT);
    break;

    case DDI_DMA_TOOBIG:
    bioerror(bp, EINVAL);
    break;

    case DDI_DMA_INUSE:
    cmn_err(CE_PANIC, "ddi_dma_buf_bind_handle:"
        " DDI_DMA_INUSE impossible\n");

    default:
    cmn_err(CE_PANIC, "ddi_dma_buf_bind_handle:"
        " 0x%x impossible\n", i);
    }

    ddi_dma_free_handle(&sp->cmd_dmahandle);
    sp->cmd_dmahandle = NULL;
    sp->cmd_flags &= ~CFLAG_DMAVALID;
    return (0);
}

重新分配用于数据传送的 DMA 资源

对于先前分配的包含待传送数据的包,tran_init_pkt(9E) 入口点在满足以下条件时必须重新分配 DMA 资源:

重新分配下一个传送部分的 DMA 资源时,tran_init_pkt(9E) 必须在字段 pkt_resid 中返回未为此传送分配的 DMA 资源量。

如果在尝试移动 DMA 资源时出现错误,则 tran_init_pkt(9E) 决不能释放 scsi_pkt(9S)。在这种情况下,目标驱动程序负责释放包。

如果回调参数为 NULL_FUNC,则 tran_init_pkt(9E) 入口点决不能休眠或调用可能会休眠的任何函数。如果回调参数为 SLEEP_FUNC 并且资源不会立即可用,则 tran_init_pkt(9E) 入口点会休眠。除非无法满足请求,否则 tran_init_pkt() 将休眠,直到资源可用为止。


示例 18–4 HBA 驱动程序的 DMA 资源重新分配

static int
isp_i_dma_move(
    struct isp         *isp,
    struct scsi_pkt    *pkt,
    struct buf         *bp)
{
    struct isp_cmd     *sp  = (struct isp_cmd *)pkt->pkt_ha_private;
    int                i;

    ASSERT(sp->cmd_flags & CFLAG_COMPLETED);
    sp->cmd_flags &= ~CFLAG_COMPLETED;
   /*
    * If there are no more cookies remaining in this window,
    * must move to the next window first.
    */
    if (sp->cmd_cookie == sp->cmd_ncookies) {
   /*
    * For small pkts, leave things where they are
    */
    if (sp->cmd_curwin == sp->cmd_nwin && sp->cmd_nwin == 1)
        return (1);
   /*
    * At last window, cannot move
    */
    if (++sp->cmd_curwin >= sp->cmd_nwin)
        return (0);
    if (ddi_dma_getwin(sp->cmd_dmahandle, sp->cmd_curwin,
        &sp->cmd_dma_offset, &sp->cmd_dma_len,
        &sp->cmd_dmacookies[0], &sp->cmd_ncookies) ==
        DDI_FAILURE)
        return (0);
        sp->cmd_cookie = 0;
    } else {
   /*
    * Still more cookies in this window - get the next one
    */
    ddi_dma_nextcookie(sp->cmd_dmahandle,
        &sp->cmd_dmacookies[0]);
    }
   /*
    * Get remaining cookies in this window, up to our maximum
    */
    i = 0;
    for (;;) {
    sp->cmd_dmacount += sp->cmd_dmacookies[i++].dmac_size;
    sp->cmd_cookie++;
    if (i == ISP_NDATASEGS ||
        sp->cmd_cookie == sp->cmd_ncookies)
            break;
    ddi_dma_nextcookie(sp->cmd_dmahandle,
        &sp->cmd_dmacookies[i]);
    }
    sp->cmd_cookiecnt = i;
    pkt->pkt_resid = bp->b_bcount - sp->cmd_dmacount;
    return (1);
}

tran_destroy_pkt() 入口点

tran_destroy_pkt(9E) 入口点是用于取消分配 scsi_pkt(9S) 结构的 HBA 驱动程序函数。目标驱动程序调用 scsi_destroy_pkt(9F) 时,将会调用 tran_destroy_pkt() 入口点。

tran_destroy_pkt() 入口点必须释放已为包分配的所有 DMA 资源。如果释放了 DMA 资源并且所有高速缓存的数据在完成传送后仍然保留,则会进行隐式 DMA 同步。tran_destroy_pkt() 入口点通过调用 scsi_hba_pkt_free(9F) 释放 SCSI 包。


示例 18–5 HBA 驱动程序 tran_destroy_pkt (9E) 入口点

static void
isp_scsi_destroy_pkt(
    struct scsi_address    *ap,
    struct scsi_pkt    *pkt)
{
    struct isp_cmd *sp = (struct isp_cmd *)pkt->pkt_ha_private;
   /*
    * Free the DMA, if any
    */
    if (sp->cmd_flags & CFLAG_DMAVALID) {
        sp->cmd_flags &= ~CFLAG_DMAVALID;
        (void) ddi_dma_unbind_handle(sp->cmd_dmahandle);
        ddi_dma_free_handle(&sp->cmd_dmahandle);
        sp->cmd_dmahandle = NULL;
    }
   /*
    * Free the pkt
    */
    scsi_hba_pkt_free(ap, pkt);
}

tran_sync_pkt() 入口点

tran_sync_pkt(9E) 入口点可在 DMA 传送前后同步为 scsi_pkt(9S) 结构分配的 DMA 对象。目标驱动程序调用 scsi_sync_pkt(9F) 时,将会调用 tran_sync_pkt() 入口点。

如果数据传送方向是从设备到内存的 DMA 读取,则 tran_sync_pkt() 必须同步 CPU 的数据视图。如果数据传送方向是从内存到设备的 DMA 写入,则 tran_sync_pkt() 必须同步设备的数据视图。


示例 18–6 HBA 驱动程序 tran_sync_pkt (9E) 入口点

static void
isp_scsi_sync_pkt(
    struct scsi_address    *ap,
    struct scsi_pkt        *pkt)
{
    struct isp_cmd *sp = (struct isp_cmd *)pkt->pkt_ha_private;

    if (sp->cmd_flags & CFLAG_DMAVALID) {
        (void)ddi_dma_sync(sp->cmd_dmahandle, sp->cmd_dma_offset,
        sp->cmd_dma_len,
        (sp->cmd_flags & CFLAG_DMASEND) ?
        DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU);
    }
}

tran_dmafree() 入口点

tran_dmafree(9E) 入口点可取消分配已为 scsi_pkt(9S) 结构分配的 DMA 资源。目标驱动程序调用 scsi_dmafree(9F) 时,将会调用 tran_dmafree() 入口点。

tran_dmafree() 必须仅释放为 scsi_pkt(9S) 结构分配的 DMA 资源,而不释放 scsi_pkt(9S) 本身。释放 DMA 资源时,将隐式执行 DMA 同步。


注 –

scsi_pkt(9S) 在单独请求 tran_destroy_pkt(9E) 时释放。由于 tran_destroy_pkt() 还必须释放 DMA 资源,因此 HBA 驱动程序必须准确记录 scsi_pkt() 结构是否分配了 DMA 资源。



示例 18–7 HBA 驱动程序 tran_dmafree (9E) 入口点

static void
isp_scsi_dmafree(
    struct scsi_address    *ap,
    struct scsi_pkt        *pkt)
{
    struct isp_cmd    *sp = (struct isp_cmd *)pkt->pkt_ha_private;

    if (sp->cmd_flags & CFLAG_DMAVALID) {
        sp->cmd_flags &= ~CFLAG_DMAVALID;
        (void)ddi_dma_unbind_handle(sp->cmd_dmahandle);
        ddi_dma_free_handle(&sp->cmd_dmahandle);
        sp->cmd_dmahandle = NULL;
    }
}

命令传输

在命令传输过程中,HBA 驱动程序将执行以下步骤:

  1. 接受来自目标驱动程序的命令。

  2. 向设备硬件发出命令。

  3. 传送出现的所有中断。

  4. 管理超时。

tran_start() 入口点

调用 SCSI HBA 驱动程序的 tran_start(9E) 入口点可将 SCSI 命令传输到指定地址的目标。在目标驱动程序通过 HBA 驱动程序的 tran_init_pkt(9E) 入口点分配的 scsi_pkt(9S) 结构中,对 SCSI 命令进行了完整描述。如果该命令涉及数据传送,则还必须为 scsi_pkt(9S) 结构分配 DMA 资源。

目标驱动程序调用 scsi_transport(9F) 时,将会调用 tran_start() 入口点。

tran_start() 应执行基本错误检查以及命令要求的任何初始化操作。scsi_pkt(9S) 结构的 pkt_flags 字段中的 FLAG_NOINTR 标志会影响 tran_start() 的行为。如果未设置 FLAG_NOINTR ,则 tran_start() 必须将命令排队以在硬件上执行并立即返回。完成命令后,HBA 驱动程序应调用 pkt 完成例程。

如果设置了 FLAG_NOINTR,则 HBA 驱动程序不会调用 pkt 完成例程。

以下示例说明如何处理 tran_start(9E) 入口点。ISP 硬件按目标设备提供了队列。对于只能管理一个活动的未完成命令的设备,驱动程序通常需要管理按目标的队列。然后,驱动程序会在完成当前命令后以循环方式启动新命令。


示例 18–8 HBA 驱动程序 tran_start (9E) 入口点

static int
isp_scsi_start(
    struct scsi_address    *ap,
    struct scsi_pkt        *pkt)
{
    struct isp_cmd         *sp;
    struct isp             *isp;
    struct isp_request     *req;
    u_long                 cur_lbolt;
    int                    xfercount;
    int                    rval = TRAN_ACCEPT;
    int                    i;

    sp = (struct isp_cmd *)pkt->pkt_ha_private;
    isp = (struct isp *)ap->a_hba_tran->tran_hba_private;

    sp->cmd_flags = (sp->cmd_flags & ~CFLAG_TRANFLAG) |
                CFLAG_IN_TRANSPORT;
    pkt->pkt_reason = CMD_CMPLT;
   /*
    * set up request in cmd_isp_request area so it is ready to
    * go once we have the request mutex
    */
    req = &sp->cmd_isp_request;

    req->req_header.cq_entry_type = CQ_TYPE_REQUEST;
    req->req_header.cq_entry_count = 1;
    req->req_header.cq_flags        = 0;
    req->req_header.cq_seqno = 0;
    req->req_reserved = 0;
    req->req_token = (opaque_t)sp;
    req->req_target = TGT(sp);
    req->req_lun_trn = LUN(sp);
    req->req_time = pkt->pkt_time;
    ISP_SET_PKT_FLAGS(pkt->pkt_flags, req->req_flags);
   /*
    * Set up data segments for dma transfers.
    */
    if (sp->cmd_flags & CFLAG_DMAVALID) {

        if (sp->cmd_flags & CFLAG_CMDIOPB) {
            (void) ddi_dma_sync(sp->cmd_dmahandle,
            sp->cmd_dma_offset, sp->cmd_dma_len,
            DDI_DMA_SYNC_FORDEV);
        }

        ASSERT(sp->cmd_cookiecnt > 0 &&
            sp->cmd_cookiecnt <= ISP_NDATASEGS);

        xfercount = 0;
        req->req_seg_count = sp->cmd_cookiecnt;
        for (i = 0; i < sp->cmd_cookiecnt; i++) {
            req->req_dataseg[i].d_count =
            sp->cmd_dmacookies[i].dmac_size;
            req->req_dataseg[i].d_base =
            sp->cmd_dmacookies[i].dmac_address;
            xfercount +=
            sp->cmd_dmacookies[i].dmac_size;
        }

        for (; i < ISP_NDATASEGS; i++) {
            req->req_dataseg[i].d_count = 0;
            req->req_dataseg[i].d_base = 0;
        }

        pkt->pkt_resid = xfercount;

        if (sp->cmd_flags & CFLAG_DMASEND) {
            req->req_flags |= ISP_REQ_FLAG_DATA_WRITE;
        } else {
            req->req_flags |= ISP_REQ_FLAG_DATA_READ;
        }
    } else {
        req->req_seg_count = 0;
        req->req_dataseg[0].d_count = 0;
    }
   /*
    * Set up cdb in the request
    */
    req->req_cdblen = sp->cmd_cdblen;
    bcopy((caddr_t)pkt->pkt_cdbp, (caddr_t)req->req_cdb,
    sp->cmd_cdblen);
   /*
    * Start the cmd.  If NO_INTR, must poll for cmd completion.
    */
    if ((pkt->pkt_flags & FLAG_NOINTR) == 0) {
        mutex_enter(ISP_REQ_MUTEX(isp));
        rval = isp_i_start_cmd(isp, sp);
        mutex_exit(ISP_REQ_MUTEX(isp));
    } else {
        rval = isp_i_polled_cmd_start(isp, sp);
    }
    return (rval);
}

中断处理程序和命令完成

中断处理程序必须检查设备状态,以确保设备正在生成相关中断。另外,中断处理程序还必须检查出现的全部错误,并传送设备生成的所有中断。

如果传送了数据,则应检查硬件以确定实际传送的数据量。scsi_pkt(9S) 结构中的 pkt_resid 字段应该设置为剩余未传送的数据量。

通过 tran_init_pkt(9E) 分配 DMA 资源时,使用 PKT_CONSISTENT 标志标记的命令需要特殊处理。HBA 驱动程序必须确保在执行目标驱动程序的命令完成回调之前,正确同步针对该命令的数据传送。

完成命令后,需要按照以下两个要求执行操作:

如有可能,在调用 PKT_COMP 命令完成回调之前,请在硬件上启动新命令。该命令完成处理可能需要大量时间。通常,目标驱动程序会调用函数(如 biodone(9F) 和可能会调用的 scsi_transport(9F))来启动新命令。

如果此中断是由该驱动程序请求的,则中断处理程序必须返回 DDI_INTR_CLAIMED。否则,处理程序会返回 DDI_INTR_UNCLAIMED

以下示例说明了 SCSI HBA isp 驱动程序的中断处理程序。如果 attach(9E) 中添加了中断处理程序,则应设置 caddr_t 参数。此参数通常是一个指向按实例分配的状态结构的指针。


示例 18–9 HBA 驱动程序中断处理程序

static u_int
isp_intr(caddr_t arg)
{
    struct isp_cmd         *sp;
    struct isp_cmd         *head, *tail;
    u_short                response_in;
    struct isp_response    *resp;
    struct isp             *isp = (struct isp *)arg;
    struct isp_slot        *isp_slot;
    int                    n;

    if (ISP_INT_PENDING(isp) == 0) {
        return (DDI_INTR_UNCLAIMED);
    }

    do {
again:
       /*
        * head list collects completed packets for callback later
        */
        head = tail = NULL;
       /*
        * Assume no mailbox events (e.g., mailbox cmds, asynch
        * events, and isp dma errors) as common case.
        */
        if (ISP_CHECK_SEMAPHORE_LOCK(isp) == 0) {
            mutex_enter(ISP_RESP_MUTEX(isp));
           /*
            * Loop through completion response queue and post
            * completed pkts.  Check response queue again
            * afterwards in case there are more.
            */
            isp->isp_response_in =
            response_in = ISP_GET_RESPONSE_IN(isp);
           /*
            * Calculate the number of requests in the queue
            */
            n = response_in - isp->isp_response_out;
            if (n < 0) {
                n = ISP_MAX_REQUESTS -
                isp->isp_response_out + response_in;
            }
            while (n-- > 0) {
                ISP_GET_NEXT_RESPONSE_OUT(isp, resp);
                sp = (struct isp_cmd *)resp->resp_token;
               /*
                * Copy over response packet in sp
                */
                isp_i_get_response(isp, resp, sp);
            }
            if (head) {
                tail->cmd_forw = sp;
                tail = sp;
                tail->cmd_forw = NULL;
            } else {
                tail = head = sp;
                sp->cmd_forw = NULL;
            }
            ISP_SET_RESPONSE_OUT(isp);
            ISP_CLEAR_RISC_INT(isp);
            mutex_exit(ISP_RESP_MUTEX(isp));

            if (head) {
                isp_i_call_pkt_comp(isp, head);
            }
        } else {
            if (isp_i_handle_mbox_cmd(isp) != ISP_AEN_SUCCESS) {
                return (DDI_INTR_CLAIMED);
            }
           /*
            * if there was a reset then check the response
            * queue again
            */
            goto again;    
        }

    } while (ISP_INT_PENDING(isp));

    return (DDI_INTR_CLAIMED);
}

static void
isp_i_call_pkt_comp(
    struct isp             *isp,
    struct isp_cmd         *head)
{
    struct isp             *isp;
    struct isp_cmd         *sp;
    struct scsi_pkt        *pkt;
    struct isp_response    *resp;
    u_char                 status;

    while (head) {
        sp = head;
        pkt = sp->cmd_pkt;
        head = sp->cmd_forw;

        ASSERT(sp->cmd_flags & CFLAG_FINISHED);

        resp = &sp->cmd_isp_response;

        pkt->pkt_scbp[0] = (u_char)resp->resp_scb;
        pkt->pkt_state = ISP_GET_PKT_STATE(resp->resp_state);
        pkt->pkt_statistics = (u_long)
            ISP_GET_PKT_STATS(resp->resp_status_flags);
        pkt->pkt_resid = (long)resp->resp_resid;
       /*
        * If data was xferred and this is a consistent pkt,
        * do a dma sync
        */
        if ((sp->cmd_flags & CFLAG_CMDIOPB) &&
            (pkt->pkt_state & STATE_XFERRED_DATA)) {
                (void) ddi_dma_sync(sp->cmd_dmahandle,
                sp->cmd_dma_offset, sp->cmd_dma_len,
                DDI_DMA_SYNC_FORCPU);
        }

        sp->cmd_flags = (sp->cmd_flags & ~CFLAG_IN_TRANSPORT) |
            CFLAG_COMPLETED;
       /*
        * Call packet completion routine if FLAG_NOINTR is not set.
        */
        if (((pkt->pkt_flags & FLAG_NOINTR) == 0) &&
            pkt->pkt_comp) {
                (*pkt->pkt_comp)(pkt);
        }
    }
}

超时处理程序

HBA 驱动程序负责强制执行超时设置。除非在 scsi_pkt(9S) 结构中指定了零超时,否则必须在指定时间内完成命令。

如果命令超时,则 HBA 驱动程序应将 scsi_pkt(9S) 标记为 pkt_reason=CMD_TIMEOUT,而且将 pkt_statistics 的值设置为与 STAT_TIMEOUT 进行或运算所得的值。另外,HBA 驱动程序还应尝试恢复目标和总线。如果恢复能够成功执行,则驱动程序应使用 pkt_statisticsSTAT_BUS_RESETSTAT_DEV_RESET 进行或运算所得的值标记 scsi_pkt(9S)

完成恢复尝试后,HBA 驱动程序应调用命令完成回调。


注 –

如果恢复不成功或未尝试恢复,则目标驱动程序可能会通过调用 scsi_reset(9F) 尝试从超时中恢复。


ISP 硬件直接管理命令超时,并会返回超时命令的必需状态。isp 样例驱动程序的超时处理程序每 60 秒才检查一次活动命令的超时状态。

isp 样例驱动程序使用 timeout(9F) 功能来安排内核每 60 秒调用一次超时处理程序。caddr_t 参数是在执行 attach(9E) 期间初始化超时值时设置的参数。在这种情况下,caddr_t 参数是一个指向按驱动程序实例分配的状态结构的指针。

如果 ISP 硬件未将超时命令作为超时项返回,则表明出现了问题。该硬件将无法正常工作并需要重置。

功能管理

以下各节讨论了功能管理。

tran_getcap() 入口点

SCSI HBA 驱动程序的 tran_getcap(9E) 入口点通过 scsi_ifgetcap(9F) 进行调用。目标驱动程序调用 scsi_ifgetcap() 可确定 SCSA 定义的一组功能中其中一个的当前值。

目标驱动程序可以通过将 whom 参数设置为非零值来请求特定目标的功能的当前设置。whom 值为零表明请求 SCSI 总线或适配器硬件的一般功能的当前设置。

对于未定义的功能或所请求的功能的当前值,tran_getcap() 入口点应返回 -1

HBA 驱动程序可以使用函数 scsi_hba_lookup_capstr(9F) 来比较功能字符串和已定义功能的标准集。


示例 18–10 HBA 驱动程序 tran_getcap (9E) 入口点

static int
isp_scsi_getcap(
    struct scsi_address    *ap,
    char                   *cap,
    int                    whom)
{
    struct isp             *isp;
    int                    rval = 0;
    u_char                 tgt = ap->a_target;
   /*
    * We don't allow getting capabilities for other targets
    */
    if (cap == NULL || whom  == 0) {
        return (-1);
    }
    isp = (struct isp *)ap->a_hba_tran->tran_hba_private;
    ISP_MUTEX_ENTER(isp);

    switch (scsi_hba_lookup_capstr(cap)) {
    case SCSI_CAP_DMA_MAX:
        rval = 1 << 24; /* Limit to 16MB max transfer */
        break;
    case SCSI_CAP_MSG_OUT:
        rval = 1;
        break;
    case SCSI_CAP_DISCONNECT:
        if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_DR) == 0) {
            break;
        } else if (
            (isp->isp_cap[tgt] & ISP_CAP_DISCONNECT) == 0) {
            break;
        }
        rval = 1;
        break;
    case SCSI_CAP_SYNCHRONOUS:
        if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_SYNC) == 0) {
            break;
        } else if (
            (isp->isp_cap[tgt] & ISP_CAP_SYNC) == 0) {
            break;
        }
        rval = 1;
        break;
    case SCSI_CAP_WIDE_XFER:
        if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_WIDE) == 0) {
            break;
        } else if (
            (isp->isp_cap[tgt] & ISP_CAP_WIDE) == 0) {
            break;
        }
        rval = 1;
        break;
    case SCSI_CAP_TAGGED_QING:
        if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_DR) == 0 ||
            (isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_TAG) == 0) {
            break;
        } else if (
            (isp->isp_cap[tgt] & ISP_CAP_TAG) == 0) {
            break;
        }
        rval = 1;
        break;
    case SCSI_CAP_UNTAGGED_QING:
        rval = 1;
        break;
    case SCSI_CAP_PARITY:
        if (isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_PARITY) {
            rval = 1;
        }
        break;
    case SCSI_CAP_INITIATOR_ID:
        rval = isp->isp_initiator_id;
        break;
    case SCSI_CAP_ARQ:
        if (isp->isp_cap[tgt] & ISP_CAP_AUTOSENSE) {
            rval = 1;
        }
        break;
    case SCSI_CAP_LINKED_CMDS:
        break;
    case SCSI_CAP_RESET_NOTIFICATION:
        rval = 1;
        break;
    case SCSI_CAP_GEOMETRY:
        rval = (64 << 16) | 32;
        break;
    default:
        rval = -1;
        break;
    }

    ISP_MUTEX_EXIT(isp);
    return (rval);
}

tran_setcap() 入口点

SCSI HBA 驱动程序的 tran_setcap(9E) 入口点通过 scsi_ifsetcap(9F) 进行调用。目标驱动程序调用 scsi_ifsetcap() 可更改 SCSA 定义的一组功能中其中一个的当前值。

目标驱动程序可能会通过将 whom 参数设置为非零值来请求为特定目标设置新值。whom 值为零通常表明请求为 SCSI 总线或适配器硬件设置新值。

tran_setcap() 应相应地返回以下值:

HBA 驱动程序可以使用函数 scsi_hba_lookup_capstr(9F) 来比较功能字符串和已定义功能的标准集。


示例 18–11 HBA 驱动程序 tran_setcap (9E) 入口点

static int
isp_scsi_setcap(
    struct scsi_address    *ap,
    char                   *cap,
    int                    value,
    int                    whom)
{
    struct isp             *isp;
    int                    rval = 0;
    u_char                 tgt = ap->a_target;
    int                    update_isp = 0;
   /*
    * We don't allow setting capabilities for other targets
    */
    if (cap == NULL || whom == 0) {
        return (-1);
    }

    isp = (struct isp *)ap->a_hba_tran->tran_hba_private;
    ISP_MUTEX_ENTER(isp);

    switch (scsi_hba_lookup_capstr(cap)) {
    case SCSI_CAP_DMA_MAX:
    case SCSI_CAP_MSG_OUT:
    case SCSI_CAP_PARITY:
    case SCSI_CAP_UNTAGGED_QING:
    case SCSI_CAP_LINKED_CMDS:
    case SCSI_CAP_RESET_NOTIFICATION:
   /*
    * None of these are settable through
    * the capability interface.
    */
        break;
    case SCSI_CAP_DISCONNECT:
        if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_DR) == 0) {
                break;
        } else {
            if (value) {
                isp->isp_cap[tgt] |= ISP_CAP_DISCONNECT;
            } else {
                isp->isp_cap[tgt] &= ~ISP_CAP_DISCONNECT;
            }
        }
        rval = 1;
        break;
    case SCSI_CAP_SYNCHRONOUS:
        if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_SYNC) == 0) {
                break;
        } else {
            if (value) {
                isp->isp_cap[tgt] |= ISP_CAP_SYNC;
            } else {
                isp->isp_cap[tgt] &= ~ISP_CAP_SYNC;
            }
        }
        rval = 1;
        break;
    case SCSI_CAP_TAGGED_QING:
        if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_DR) == 0 ||
            (isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_TAG) == 0) {
                break;
        } else {
            if (value) {
                isp->isp_cap[tgt] |= ISP_CAP_TAG;
            } else {
            isp->isp_cap[tgt] &= ~ISP_CAP_TAG;
            }
        }
        rval = 1;
        break;
    case SCSI_CAP_WIDE_XFER:
        if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_WIDE) == 0) {
                break;
        } else {
            if (value) {
                isp->isp_cap[tgt] |= ISP_CAP_WIDE;
            } else {
                isp->isp_cap[tgt] &= ~ISP_CAP_WIDE;
            }
        }
        rval = 1;
        break;
    case SCSI_CAP_INITIATOR_ID:
        if (value < N_ISP_TARGETS_WIDE) {
            struct isp_mbox_cmd mbox_cmd;
            isp->isp_initiator_id = (u_short) value;
           /*
            * set Initiator SCSI ID
            */
            isp_i_mbox_cmd_init(isp, &mbox_cmd, 2, 2,
            ISP_MBOX_CMD_SET_SCSI_ID,
            isp->isp_initiator_id,
            0, 0, 0, 0);
            if (isp_i_mbox_cmd_start(isp, &mbox_cmd) == 0) {
                rval = 1;
            }
        }
        break;
    case SCSI_CAP_ARQ:
        if (value) {
            isp->isp_cap[tgt] |= ISP_CAP_AUTOSENSE;
        } else {
            isp->isp_cap[tgt] &= ~ISP_CAP_AUTOSENSE;
        }
        rval = 1;
        break;
    default:
        rval = -1;
        break;
    }
    ISP_MUTEX_EXIT(isp);
    return (rval);
}

中止和重置管理

以下各节讨论了 SCSI HBA 的中止入口点和重置入口点。

tran_abort() 入口点

调用 SCSI HBA 驱动程序的 tran_abort(9E) 入口点可中止当前正在传输给特定目标的所有命令。目标驱动程序调用 scsi_abort(9F) 时,将会调用此入口点。

tran_abort() 入口点会尝试中止 pkt 参数表示的命令。如果 pkt 参数为 NULL,则 tran_abort() 会尝试中止传输层中针对特定目标或逻辑单元的所有未完成命令。

每个已成功中止的命令都必须标记为 pkt_reason CMD_ABORTED 以及 pkt_statisticsSTAT_ABORTED 进行或运算所得的值。

tran_reset() 入口点

调用 SCSI HBA 驱动程序的 tran_reset(9E) 入口点可重置 SCSI 总线或特定的 SCSI 目标设备。目标驱动程序调用 scsi_reset(9F) 时,将会调用此入口点。

如果级别为 RESET_ALL(),则 tran_reset 入口点必须重置 SCSI 总线。如果级别为 RESET_TARGET,则仅有特定目标或逻辑单元必须重置。

受重置影响的活动命令必须带有 pkt_reason CMD_RESET 标记。重置类型可确定应使用 STAT_BUS_RESET 还是使用 STAT_DEV_RESETpkt_statistics 进行或运算。

在目标上尚未处于活动状态的传输层中的命令必须标记为 pkt_reason CMD_RESET 以及 pkt_statisticsSTAT_ABORTED 进行或运算所得的值。

tran_bus_reset() 入口点

tran_bus_reset(9E) 必须重置 SCSI 总线而不重置目标。

#include <sys/scsi/scsi.h>

int tran_bus_reset(dev_info_t *hba-dip, int level);

其中:

*hba-dip

与 SCSI HBA 关联的指针

level

必须设置为 RESET_BUS,以便仅重置 SCSI 总线而不重置目标。

执行 HBA 驱动程序的 attach(9E) 的过程中,应初始化 scsi_hba_tran(9S) 结构中的 tran_bus_reset() 向量。该向量应指向用户启动总线重置时将调用的 HBA 入口点。

实现特定于硬件。如果 HBA 驱动程序无法在不影响目标的情况下重置 SCSI 总线,则驱动程序将无法执行 RESET_BUS 或不会初始化此向量。

tran_reset_notify() 入口点

重置 SCSI 总线时,请使用 tran_reset_notify(9E) 入口点。此函数将请求 SCSI HBA 驱动程序通过回调来通知目标驱动程序。


示例 18–12 HBA 驱动程序 tran_reset_notify (9E) 入口点

isp_scsi_reset_notify(
    struct scsi_address    *ap,
    int                    flag,
    void                   (*callback)(caddr_t),
    caddr_t                arg)
{
    struct isp                       *isp;
    struct isp_reset_notify_entry    *p, *beforep;
    int                              rval = DDI_FAILURE;

    isp = (struct isp *)ap->a_hba_tran->tran_hba_private;
    mutex_enter(ISP_REQ_MUTEX(isp));
   /*
    * Try to find an existing entry for this target
    */
    p = isp->isp_reset_notify_listf;
    beforep = NULL;

    while (p) {
        if (p->ap == ap)
            break;
        beforep = p;
        p = p->next;
    }

    if ((flag & SCSI_RESET_CANCEL) && (p != NULL)) {
        if (beforep == NULL) {
            isp->isp_reset_notify_listf = p->next;
        } else {
            beforep->next = p->next;
        }
        kmem_free((caddr_t)p, sizeof (struct
            isp_reset_notify_entry));
        rval = DDI_SUCCESS;
    } else if ((flag & SCSI_RESET_NOTIFY) && (p == NULL)) {
        p = kmem_zalloc(sizeof (struct isp_reset_notify_entry),
            KM_SLEEP);
        p->ap = ap;
        p->callback = callback;
        p->arg = arg;
        p->next = isp->isp_reset_notify_listf;
        isp->isp_reset_notify_listf = p;
        rval = DDI_SUCCESS;
    }

    mutex_exit(ISP_REQ_MUTEX(isp));
    return (rval);
}

动态重新配置

要支持最少的一组热插拔操作,驱动程序可能需要实现对总线静止、总线取消静止和总线重置的支持。scsi_hba_tran(9S) 结构支持这些操作。如果硬件不要求静止、取消静止或重置等操作,则无需对驱动程序进行任何更改。

scsi_hba_tran 结构包含以下字段:

int (*tran_quiesce)(dev_info_t *hba-dip);
int (*tran_unquiesce)(dev_info_t *hba-dip);
int (*tran_bus_reset)(dev_info_t *hba-dip, int level);

这些接口将使 SCSI 总线处于静止状态和取消其静止状态。

#include <sys/scsi/scsi.h>

int prefixtran_quiesce(dev_info_t *hba-dip);
int prefixtran_unquiesce(dev_info_t *hba-dip);

tran_quiesce(9E)tran_unquiesce(9E) 用于不适于热插拔的 SCSI 设备。HBA 驱动程序必须实现这些函数才能支持动态重新配置 (dynamic reconfiguration, DR)。

在执行 attach(9E) 的过程中,应将 scsi_hba_tran(9S) 结构中的 tran_quiesce()tran_unquiesce() 向量初始化为指向 HBA 入口点。用户启动静止和取消静止操作时,将会调用这些函数。

tran_quiesce() 入口点用于在重新配置连接到 SCSI 总线的设备之前和期间停止 SCSI 总线上的所有活动。完成重新配置操作后,SCSA 框架可调用 tran_unquiesce() 入口点来恢复 SCSI 总线上的活动。

返回成功信息之前,HBA 驱动程序需要处理 tran_quiesce(),方法是等待所有未完成的命令完成。 驱动程序使总线处于静止状态后,必须将任何新的 I/O 请求排入队列,直到 SCSA 框架调用对应的 tran_unquiesce() 入口点为止。

HBA 驱动程序通过启动队列中的所有目标驱动程序 I/O 请求来处理对 tran_unquiesce() 的调用。

SCSI HBA 驱动程序特定问题

本节介绍特定于 SCSI HBA 驱动程序的问题。

安装 HBA 驱动程序

SCSI HBA 驱动程序的安装方式与叶驱动程序类似。请参见第 21 章。差别在于 add_drv(1M) 命令必须将驱动程序类指定为 SCSI,如:

# add_drv -m" * 0666 root root" -i'"pci1077,1020"' -c scsi isp

HBA 配置属性

连接 HBA 设备实例时,scsi_hba_attach_setup(9F) 会为该 HBA 实例创建许多 SCSI 配置属性。仅当没有现有同名属性附加到 HBA 实例时,才会创建特定属性。此限制可避免覆盖 HBA 配置文件中的任何缺省属性值。

HBA 驱动程序必须使用 ddi_prop_get_int(9F) 检索每个属性。然后,HBA 驱动程序会修改或接受这些属性的缺省值来配置其特定操作。

scsi-reset-delay 属性

scsi-reset-delay 属性是一个整数,用于指定由 SCSI 总线或 SCSI 设备造成的重置延迟的恢复时间(以毫秒为单位)。

scsi-options 属性

scsi-options 属性是一个整数,用于通过单独定义的位来指定多个选项:

按目标的 scsi-options

HBA 驱动程序可能支持以下格式的按目标的 scsi-options 功能:

target<n>-scsi-options=<hex value>

在此示例中,< n> 是指目标 ID。如果定义了按目标的 scsi-options 属性,则 HBA 驱动程序将使用该值,而不使用按 HBA 驱动程序实例的 scsi-options 属性。例如,如果仅需针对某个特定目标设备禁用同步数据传送,则该方法可提供更准确的控制。按目标的 scsi-options 属性可以在 driver.conf(4) 文件中定义。

以下示例说明了按目标的 scsi-options 属性定义,用于禁用目标设备 3 的同步数据传送:

target3-scsi-options=0x2d8

x86 目标驱动程序配置属性

某些 x86 SCSI 目标驱动程序(如 cmdk 磁盘驱动程序)使用以下配置属性:

如果使用 cmdk 样例驱动程序编写适用于 x86 平台的 HBA 驱动程序,则必须在 driver.conf(4) 文件中定义所有相应属性。


注 –

这些属性定义应仅显示在 HBA 驱动程序的 driver.conf(4) 文件中。HBA 驱动程序本身不应以任何方式检查或尝试解释这些属性。这些属性仅是建议性的,并且用作 cmdk 驱动程序的附件。不应以任何方式依赖这些属性。在将来的发行版中可能不会使用这些属性定义。


disk 属性可用于定义 cmdk 支持的磁盘类型。对于 SCSI HBA,disk 属性的唯一可能值是:

queue 属性用于定义磁盘驱动程序如何在执行 strategy(9E) 的过程中对传入请求的队列进行排序。以下是两个可能的值:

flow_control 属性用于定义如何将命令传输到 HBA 驱动程序。以下是三个可能的值:

以下示例是一个供 x86 HBA PCI 设备使用的 driver.conf(4) 文件,该设备设计用于 cmdk 样例驱动程序:

#
# config file for ISP 1020 SCSI HBA driver     
#
       flow_control="dsngl" queue="qsort" disk="scdk"
       scsi-initiator-id=7;

排队支持

有关标记排队的定义,请参阅 SCSI-2 规范。要支持标记排队,请首先检查 scsi_options 标志 SCSI_OPTIONS_TAG,查看是否在全局范围内启用了标记排队。接下来,检查目标是否为 SCSI-2 设备以及目标是否启用了标记排队。如果这些条件全部符合,请通过 scsi_ifsetcap(9F) 尝试启用标记排队。

如果标记排队失败,则可尝试设置无标记排队。在此模式下,可提交主机适配器驱动程序所需的或对其最适用的尽可能多的命令。与标记排队不同,主机适配器随后将按照一次一个命令的方式对目标命令进行排队。在标记排队中,主机适配器将提交尽可能多的命令,直到目标指示队列已满为止。