编写设备驱动程序

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)