JavaScript is required to for searching.
跳过导航链接
退出打印视图
编写设备驱动程序     Oracle Solaris 11.1 Information Library (简体中文)
为本文档评分
search filter icon
search icon

文档信息

前言

第 1 部分针对 Oracle Solaris 平台设计设备驱动程序

1.  Oracle Solaris 设备驱动程序概述

2.  Oracle Solaris 内核和设备树

3.  多线程

4.  属性

5.  管理事件和排队任务

6.  驱动程序自动配置

7.  设备访问:程控 I/O

8.  中断处理程序

9.  直接内存访问 (Direct Memory Access, DMA)

10.  映射设备和内核内存

11.  设备上下文管理

12.  电源管理

13.  强化 Oracle Solaris 驱动程序

14.  分层驱动程序接口 (Layered Driver Interface, LDI)

第 2 部分设计特定种类的设备驱动程序

15.  字符设备驱动程序

16.  块设备驱动程序

17.  SCSI 目标驱动程序

目标驱动程序介绍

Sun 公用 SCSI 体系结构概述

常规控制流程

SCSA 函数

硬件配置文件

声明和数据结构

scsi_device 结构

scsi_pkt 结构(目标驱动程序)

SCSI 目标驱动程序的自动配置

probe() 入口点(SCSI 目标驱动程序)

attach() 入口点(SCSI 目标驱动程序)

detach() 入口点(SCSI 目标驱动程序)

getinfo() 入口点(SCSI 目标驱动程序)

资源分配

scsi_init_pkt() 函数

scsi_sync_pkt() 函数

scsi_destroy_pkt() 函数

scsi_alloc_consistent_buf() 函数

scsi_free_consistent_buf() 函数

生成和传输命令

生成命令

设置目标功能

传输命令

同步 scsi_transport() 函数

命令完成

重新使用包

自动请求检测模式

转储处理

SCSI 选项

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

19.  网络设备驱动程序

20.  USB 驱动程序

21.  SR-IOV 驱动程序

第 3 部分生成设备驱动程序

22.  编译、装入、打包和测试驱动程序

23.  调试、测试和调优设备驱动程序

24.  推荐的编码方法

第 4 部分附录

A.  硬件概述

B.  Oracle Solaris DDI/DKI 服务汇总

C.  使设备驱动程序支持 64 位

D.  控制台帧缓存器驱动程序

E.  pci.conf 文件

索引

请告诉我们如何提高我们的文档:
过于简略
不易阅读或难以理解
重要信息缺失
错误的内容
需要翻译的版本
其他
Your rating has been updated
感谢您的反馈!

您的反馈将非常有助于我们提供更好的文档。 您是否愿意参与我们的内容改进并提供进一步的意见?

生成和传输命令

主机总线适配器驱动程序负责向设备传输命令。此外,该驱动程序还负责处理低级别的 SCSI 协议。scsi_transport(9F) 例程将包提交给主机总线适配器驱动程序以进行传输。目标驱动程序负责创建有效的 scsi_pkt(9S) 结构。

生成命令

scsi_init_pkt(9F) 例程可为 SCSI CDB 分配空间,在必要时分配 DMA 资源以及设置 pkt_flags 字段,如以下示例所示:

pkt = scsi_init_pkt(&sdp->sd_address, NULL, bp,
    CDB_GROUP0, 1, 0, 0, SLEEP_FUNC, NULL);

该示例在按传递的 buf(9S) 结构指针中的指定分配 DMA 资源的同时创建了一个新包。为 Group 0(6 字节)命令分配了 SCSI CDB。pkt_flags 字段被设置为零,但没有为 pkt_private 字段分配空间。由于设置了 SLEEP_FUNC 参数,对 scsi_init_pkt(9F) 的调用将会在当前没有可用资源的情况下无限期等待资源。

下一步是使用 scsi_setup_cdb(9F) 函数初始化 SCSI CDB。

if (scsi_setup_cdb((union scsi_cdb *)pkt->pkt_cdbp,
    SCMD_READ, bp->b_blkno, bp->b_bcount >> DEV_BSHIFT, 0) == 0)
    goto failed;

该示例将生成 Group 0 命令描述符块。该示例按如下所示填充 pkt_cdbp 字段:


注 - scsi_setup_cdb(9F) 不支持在 SCSI 命令块的第 1 个字节的 5-7 位中设置目标设备的逻辑单元号 (logical unit number, LUN)。此要求由 SCSI-1 定义。对于需要在命令块中设置 LUN 位的 SCSI-1 设备,请使用 makecom_g0(9F) 或某些等效的函数,而不是使用 scsi_setup_cdb(9F)


初始化 SCSI CDB 之后,应初始化包中的三个其他字段,并在状态结构中存储为指向包的指针。

pkt->pkt_private = (opaque_t)bp;
pkt->pkt_comp = xxcallback;
pkt->pkt_time = 30;
xsp->pkt = pkt;

buf(9S) 指针保存在 pkt_private 字段中,以备将来在完成例程中使用。

设置目标功能

目标驱动程序使用 scsi_ifsetcap(9F) 设置主机适配器驱动程序的功能。上限是一个名称/值对,由一个以 null 结尾的字符串和一个整数值组成。可以使用 scsi_ifgetcap(9F) 检索功能的当前值。scsi_ifsetcap(9F) 允许为总线上的所有目标设置功能。

但是,通常建议不要设置非目标驱动程序拥有的目标的功能。并非所有 HBA 驱动程序都支持这种做法。缺省情况下,某些功能(如断开连接和同步)可以由 HBA 驱动程序设置。其他功能可能需要由目标驱动程序显式设置。例如,Wide-xfer 和标记排队功能必须由目标驱动器设置。

传输命令

填充 scsi_pkt(9S) 结构之后,应使用 scsi_transport(9F) 将该结构提交给总线适配器驱动程序:

if (scsi_transport(pkt) != TRAN_ACCEPT) {
    bp->b_resid = bp->b_bcount;
    bioerror(bp, EIO);
    biodone(bp);
}

scsi_transport(9F) 返回的其他值如下所示:


注 - 在调用 scsi_transport(9F) 过程中,不能持有 scsi_device(9S) 结构中的互斥锁 sd_mutex


如果 scsi_transport(9F) 返回 TRAN_ACCEPT,则该包将由主机总线适配器驱动程序负责。调用命令完成例程之前,目标驱动程序不应该访问该包。

同步 scsi_transport() 函数

如果在包中设置了 FLAG_NOINTR,则在命令完成之前,scsi_transport(9F) 不会返回。不会执行回调。


注 - 请勿在中断上下文中使用 FLAG_NOINTR


命令完成

当主机总线适配器驱动程序完成命令后,将调用包的完成回调例程。然后,驱动程序会将指向 scsi_pkt(9S) 结构的指针作为参数传递。对包进行解码后,完成例程将执行相应的操作。

示例 17-5 显示了一个简单的完成回调例程。该代码检查传输是否失败。如果存在失败情况,该例程将放弃运行,而不会重试命令。如果目标繁忙,则需要额外的代码以便在以后重新提交命令。

如果命令产生检查条件,则目标驱动程序需要发送请求检测命令,除非启用了自动请求检测。

否则,命令成功。在结束命令处理时,命令将销毁包并调用 biodone(9F)

如果发生传输错误(如总线重置或奇偶校验问题),则目标驱动程序可以使用 scsi_transport(9F) 重新提交该包。重新提交之前,无需更改包中的任何值。

以下示例不会尝试重试未完成的命令。


注 - 通常,在中断上下文中会调用目标驱动程序的回调函数。因此,回调函数绝不应处于休眠状态。


示例 17-5 SCSI 驱动程序的完成例程

static void
xxcallback(struct scsi_pkt *pkt)
{
    struct buf        *bp;
    struct xxstate    *xsp;
    minor_t           instance;
    struct scsi_status *ssp;
    /*
     * Get a pointer to the buf(9S) structure for the command
     * and to the per-instance data structure.
     */
    bp = (struct buf *)pkt->pkt_private;
    instance = getminor(bp->b_edev);
    xsp = ddi_get_soft_state(statep, instance);
    /*
     * Figure out why this callback routine was called
     */
    if (pkt->pkt_reason != CMP_CMPLT) {
        bp->b_resid = bp->b_bcount;
        bioerror(bp, EIO);
        scsi_destroy_pkt(pkt);          /* Release resources */
        biodone(bp);                    /* Notify waiting threads */ ;
    } else {
        /*
         * Command completed, check status.
         * See scsi_status(9S)
         */
        ssp = (struct scsi_status *)pkt->pkt_scbp;
        if (ssp->sts_busy) {
            /* error, target busy or reserved */
        } else if (ssp->sts_chk) {
            /* Send a request sense command. */
        } else {
            bp->b_resid = pkt->pkt_resid;  /* Packet completed OK */
            scsi_destroy_pkt(pkt);
            biodone(bp);
       }
    }
}

重新使用包

目标驱动程序可以采用以下方式重新使用包:

自动请求检测模式

如果使用排队(无论是标记排队还是无标记排队),则最好使用自动请求检测模式。任何后续命令都会清除应急处理状态 (contingent allegiance condition),并因此导致检测数据丢失。大多数 HBA 驱动程序会在执行目标驱动程序回调之前开始下一条命令。其他 HBA 驱动程序可以使用单独的、较低优先级的线程执行回调。采用该方法时,如果存在检查状况,那么向目标驱动程序通知包已完成所需的时间可能会增加。在这种情况下,目标驱动程序可能无法及时提交请求检测命令以检索检测数据。

为了避免检测数据丢失,HBA 驱动程序或控制器应在检测到检查条件时发出请求检测命令。该模式称为自动请求检测模式。请注意,并非所有 HBA 驱动程序都可以使用自动请求检测模式,而某些驱动程序只能在启用了自动请求检测模式时运行。

目标驱动程序使用 scsi_ifsetcap(9F) 来启用自动请求检测模式。以下示例说明了如何启用自动请求检测。

示例 17-6 启用自动请求检测模式

static int
xxattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
    struct xxstate *xsp;
    struct scsi_device *sdp = (struct scsi_device *)
    ddi_get_driver_private(dip);
    /*
     * Enable auto-request-sense. An auto-request-sense command might
     * fail due to a BUSY condition or transport error. Therefore,
     * it is recommended to allocate a separate request sense
     * packet as well.
     * Note that scsi_ifsetcap(9F) can return -1, 0, or 1
     */
    xsp->sdp_arq_enabled =
    ((scsi_ifsetcap(ROUTE, "auto-rqsense", 1, 1) == 1) ? 1 : 0);
    /*
     * If the HBA driver supports auto request sense then the
     * status blocks should be sizeof (struct scsi_arq_status).
     * Else, one byte is sufficient.
     */
    xsp->sdp_cmd_stat_size =  (xsp->sdp_arq_enabled ?
    sizeof (struct scsi_arq_status) : 1);
    /* ... */
}

如果使用 scsi_init_pkt(9F) 分配了一个包并且该包需要自动请求检测,则需要增加空间。目标驱动程序必须为状态块请求此空间,以便存储自动请求检测结构。请求检测命令中使用的检测长度即 struct scsi_extended_sense 中的 sizeof。通过为状态块分配 struct scsi_status 中的 sizeof,可以针对各个包禁用自动请求检测。

可像往常一样使用 scsi_transport(9F) 提交包。当该包中出现检查条件时,主机适配器驱动程序将执行以下步骤:

目标驱动程序的回调例程应通过检查 pkt_state 中的 STATE_ARQ_DONE 位,来验证检测数据是否可用。STATE_ARQ_DONE 表明出现了检查条件,并且已执行请求检测。如果包中暂时禁用了自动请求检测,则无法保证对检测数据的后续检索。

然后,目标驱动程序应验证自动请求检测命令是否已成功完成,并对检测数据进行解码。

转储处理

在系统出现故障或执行检查点操作时,dump(9E) 入口点会将部分虚拟地址空间直接复制到指定的设备。请参见 cpr(7)dump(9E) 手册页。dump(9E) 入口点必须在不使用中断的情况下能够执行该操作。

dump() 的参数如下所示:

dev

转储设备的设备编号

addr

开始转储的内核虚拟地址

blkno

设备上的第一个目标块

nblk

要转储的块数

示例 17-7 dump(9E) 例程

static int
xxdump(dev_t dev, caddr_t addr, daddr_t blkno, int nblk)
{
    struct xxstate     *xsp;
    struct buf         *bp;
    struct scsi_pkt    *pkt;
    int    rval;
    int    instance;

    instance = getminor(dev);
    xsp = ddi_get_soft_state(statep, instance);

    if (tgt->suspended) {
        (void) pm_raise_power(DEVINFO(tgt), 0, 1);
    }

    bp = getrbuf(KM_NOSLEEP);
    if (bp == NULL) {
        return (EIO);
    }

/* Calculate block number relative to partition. */
    bp->b_un.b_addr = addr;
    bp->b_edev = dev;
    bp->b_bcount = nblk * DEV_BSIZE;
    bp->b_flags = B_WRITE | B_BUSY;
    bp->b_blkno = blkno;

    pkt = scsi_init_pkt(ROUTE(tgt), NULL, bp, CDB_GROUP1,
    sizeof (struct scsi_arq_status),
    sizeof (struct bst_pkt_private), 0, NULL_FUNC, NULL);
    if (pkt == NULL) {
        freerbuf(bp);
        return (EIO);
    }
    (void) scsi_setup_cdb((union scsi_cdb *)pkt->pkt_cdbp,
        SCMD_WRITE_G1, blkno, nblk, 0);
    /*
     * While dumping in polled mode, other cmds might complete
     * and these should not be resubmitted. we set the
     * dumping flag here which prevents requeueing cmds.
     */
    tgt->dumping = 1;
    rval = scsi_poll(pkt);
    tgt->dumping = 0;

    scsi_destroy_pkt(pkt);
    freerbuf(bp);

    if (rval != DDI_SUCCESS) {
        rval = EIO;
    }
    return (rval);
}