Solaris DDI/DKI 将 SCSI 设备的软件接口分成以下两个主要部分:目标驱动程序和主机总线适配器 (host bus adapter, HBA) 驱动程序。目标驱动程序指 SCSI 总线上的设备(如磁盘或磁带机)的驱动程序。主机总线适配器驱动程序指主机上的 SCSI 控制器的驱动程序。SCSA 定义了这两个组件之间的接口。本章仅讨论目标驱动程序。有关主机总线适配器驱动程序的信息,请参见第 18 章。
术语“主机总线适配器”和 "HBA" 等效于 SCSI 规范中定义的“主机适配器”。
本章介绍有关以下主题的信息:
目标驱动程序可以为字符设备驱动程序,也可以为块设备驱动程序,具体取决于设备。磁带机的驱动程序通常为字符设备驱动程序,而磁盘则由块设备驱动程序处理。本章介绍如何编写 SCSI 目标驱动程序。本章还讨论了 SCSA 对 SCSI 目标设备的块驱动程序和字符驱动程序的其他要求。
以下参考文档提供了目标驱动程序和主机总线适配器驱动程序的设计者需要的补充信息。
《Small Computer System Interface 2 (SCSI-2), ANSI/NCITS X3.131-1994》,Global Engineering Documents 发布,1998 年,ISBN 1199002488。
《The Basics of SCSI》(第四版),ANCOT Corporation 出版,1998 年,ISBN 0963743988。
另请参阅硬件供应商所提供的关于目标设备的 SCSI 命令规范。
Sun 公用 SCSI 体系结构 (Sun Common SCSI Architecture, SCSA) 是 Solaris DDI/DKI 编程接口,用于将 SCSI 命令从目标驱动程序传输到主机总线适配器驱动程序。该接口与主机总线适配器硬件的类型、平台、处理器体系结构以及通过该接口传输的 SCSI 命令无关。
只要符合 SCSA,目标驱动程序便可以将 SCSI 命令传送给目标设备,而无需了解主机总线适配器的硬件实现。
SCSA 在概念上将生成 SCSI 命令与通过 SCSI 总线传输命令和数据这两个过程分离开来。该体系结构定义了高级别软件组件与低级别软件组件之间的软件接口。较高级别的软件组件由一个或多个 SCSI 目标驱动程序组成,这些驱动程序可将 I/O 请求转换为适用于外围设备的 SCSI 命令。以下示例说明了 SCSI 体系结构。
较低级别的软件组件由 SCSA 接口层和一个或多个主机总线适配器驱动程序组成。目标驱动程序负责生成执行所需功能需要的正确 SCSI 命令,并负责处理结果。
以下步骤介绍读取或写入请求的常规控制流程(假定没有发生传输错误)。
调用目标驱动程序的 read(9E) 或 write(9E) 入口点。使用 physio(9F) 锁定内存,准备 buf 结构并调用策略例程。
目标驱动程序的 strategy(9E) 例程将检查请求。然后,strategy() 通过使用 scsi_init_pkt(9F) 来分配 scsi_pkt(9S)。目标驱动程序初始化包,并使用 scsi_setup_cdb(9F) 函数设置 SCSI 命令描述符块 (command descriptor block, CDB)。目标驱动程序还指定超时。然后,该驱动程序提供一个指向回调函数的指针。完成命令后,主机总线适配器驱动程序将调用该回调函数。buf(9S) 指针应保存在 SCSI 包的目标专用空间中。
目标驱动程序使用 scsi_transport(9F) 将包提交给主机总线适配器驱动程序。然后,目标驱动程序可以自由接受其他请求。目标驱动程序不应在包的传输过程中对其进行访问。如果主机总线适配器驱动程序或目标支持排队,则可以在传输包时提交新请求。
一旦 SCSI 总线空闲而且目标不繁忙,主机总线适配器驱动程序便会选择该目标并传递 CDB。目标驱动程序将执行命令。然后,目标执行所请求的数据传输。
目标发送完成状态并且命令完成后,主机总线适配器驱动程序会通知目标驱动程序。要执行通知,主机应调用 SCSI 包中指定的完成函数。此时,主机总线适配器驱动程序不再负责处理该包,同时目标驱动程序重新获得了对该包的拥有权。
SCSI 包的完成例程将分析返回的信息。然后,完成例程会确定 SCSI 操作是否成功。如果出现故障,目标驱动程序将再次调用 scsi_transport(9F) 以重试命令。如果主机总线适配器驱动程序不支持自动请求检测,则目标驱动程序必须在出现检查条件时,提交请求检测包才能检索检测数据。
成功完成上述操作后或者如果无法重试命令,目标驱动程序将调用 scsi_destroy_pkt(9F)。scsi_destroy_pkt () 将同步数据。然后,scsi_destroy_pkt() 释放包。如果在释放包之前目标驱动程序需要访问数据,则调用 scsi_sync_pkt(9F)。
最后,目标驱动程序将通知请求应用程序读取或写入事务已完成。该通知是在执行了字符设备的驱动程序中的 read(9E) 入口点并返回后进行的。否则,会通过 biodone(9F) 间接发出通知。
SCSA 允许以重叠方式和排队方式在进程的各个点执行多个此类操作。在该模型中,系统资源由主机总线适配器驱动程序负责管理。借助软件接口并使用不同复杂程度的 SCSI 总线适配器,可以在主机总线适配器驱动程序中执行目标驱动程序函数。
SCSA 定义了多种函数,用于管理资源的分配和释放、控制状态的检测和设置以及 SCSI 命令的传输。下表中列出了这些函数。
表 17–1 标准 SCSA 函数
函数名 |
类别 |
---|---|
错误处理 |
|
|
|
|
|
|
|
|
|
传输信息和控制 |
|
|
|
资源管理 |
|
轮询 I/O |
|
探测器函数 |
|
|
|
CDB 初始化函数 |
|
|
|
命令传输 |
|
|
如果驱动程序需要使用 SCSI-1 设备,请使用 makecom(9F)。
由于 SCSI 设备不是自标识设备,因此目标驱动程序需要硬件配置文件。有关详细信息,请参见 driver.conf(4) 和 scsi_free_consistent_buf(9F) 手册页。以下是典型的配置文件:
name="xx" class="scsi" target=2 lun=0;
系统将在自动配置期间读取该文件。系统使用 class 属性标识驱动程序可能存在的父驱动程序。然后,系统尝试将该驱动程序连接至类为 scsi 的任何父驱动程序。所有主机总线适配器驱动程序都属于此类。首选使用 class 属性,而不是 parent 属性。采用此方法,任何在指定 target 和 lun ID 中查找预期设备的主机总线适配器驱动程序都可以连接至目标。目标驱动程序负责验证其 probe(9E) 例程中的类。
目标驱动程序必须包括头文件 <sys/scsi/scsi.h>。
SCSI 目标驱动程序必须使用以下命令生成二进制模块:
ld -r xx xx.o -N"misc/scsi"
调用 probe(9E) 或 attach(9E) 例程之前,主机总线适配器驱动程序将为目标驱动程序分配并初始化 scsi_device(9S) 结构。此结构可存储有关每个 SCSI 逻辑单元的信息,包括指向信息区(包含通用信息和特定于设备的信息)的指针。对于连接到系统的每个逻辑单元,都存在一个 scsi_device(9S) 结构。目标驱动程序可以通过调用 ddi_get_driver_private(9F) 来检索指向此结构的指针。
由于主机总线适配器驱动程序使用目标设备的 dev_info 结构中的专用字段,因此目标驱动程序不能使用 ddi_set_driver_private(9F)。
scsi_device(9S) 结构包含以下字段:
struct scsi_device { struct scsi_address sd_address; /* opaque address */ dev_info_t *sd_dev; /* device node */ kmutex_t sd_mutex; void *sd_reserved; struct scsi_inquiry *sd_inq; struct scsi_extended_sense *sd_sense; caddr_t sd_private; };
其中:
为了进行 SCSI 资源分配而传递给例程的数据结构。
指向目标的 dev_info 结构的指针。
供目标驱动程序使用的互斥锁。此互斥锁由主机总线适配器驱动程序初始化,并可被目标驱动程序用作每设备互斥锁。请勿在调用 scsi_transport(9F) 或 scsi_poll(9F) 期间持有此互斥锁。有关互斥锁的更多信息,请参见第 3 章。
目标设备的 SCSI 查询数据的指针。scsi_probe(9F) 例程将分配缓冲区,使用查询数据填充该缓冲区,并将该缓冲区连接到此字段。
指向用于包含设备中的 SCSI 请求检测数据的缓冲区的指针。目标驱动程序必须分配和管理此缓冲区。请参见attach() 入口点(SCSI 目标驱动程序)。
供目标驱动程序使用的指针字段。此字段通常用于存储指向专用目标驱动程序状态结构的指针。
struct scsi_pkt { opaque_t pkt_ha_private; /* private data for host adapter */ struct scsi_address pkt_address; /* destination packet is for */ 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 */ };
其中:
scsi_init_pkt(9F) 设置的目标设备的地址。
用于存储目标驱动程序的专用数据的位置。pkt_private 通常用于保存命令的 buf(9S) 指针。
完成例程的地址。主机总线适配器驱动程序将在传输命令后调用此例程。传输命令并不表示命令已成功。目标可能处于繁忙状态。另一种可能性是在经过超时时间段之前目标未响应。请参见 pkt_time 字段的说明。目标驱动程序必须在该字段中提供有效值。如果不需要通知驱动程序,则该值可以为 NULL。
有两种不同的 SCSI 回调例程。pkt_comp 字段标识完成回调例程,该例程将在主机总线适配器完成其处理时调用。此外,还提供了资源回调例程,该例程将在当前尚不可用资源可能可用时调用。请参见 scsi_init_pkt(9F) 手册页。
提供其他控制信息,例如,在没有断开连接权限的情况下传输命令 (FLAG_NODISCON),或禁用回调 (FLAG_NOINTR)。有关详细信息,请参见 scsi_pkt(9S) 手册页。
超时值(以秒为单位)。如果命令在该时间内未完成,则主机总线适配器将调用完成例程,并将 pkt_reason 设置为 CMD_TIMEOUT。目标驱动程序应将该字段设置为大于命令可能需要的最长时间。如果超时值为零,则不请求超时。超时从在 SCSI 总线上传输命令时开始。
指向 SCSI 状态完成块的指针。该字段由主机总线适配器驱动程序填充。
指向 SCSI 命令描述符块(要发送到目标设备的实际命令)的指针。主机总线适配器驱动程序不会解释该字段。目标驱动程序必须使用目标设备可以处理的命令填充该字段。
剩余操作。pkt_resid 字段有两种不同的用途,具体取决于如何使用 pkt_resid。使用 pkt_resid 为命令 scsi_init_pkt(9F) 分配 DMA 资源时,pkt_resid 指示不可分配的字节数。由于 DMA 硬件具有分散/集中限制或其他设备限制,因此可能无法分配 DMA 资源。传输命令后,pkt_resid 指示不可传输的数据字节数。该字段由主机总线适配器驱动程序在调用完成例程之前填充。
指示命令的状态。主机总线适配器驱动程序在命令执行过程中填充该字段。该字段的每一位分别根据以下五种命令状态设置:
STATE_GOT_BUS-已获取总线
STATE_GOT_TARGET-已选定目标
STATE_SENT_CMD-已发送命令
STATE_XFERRED_DATA-已传输数据(如果适用)
STATE_GOT_STATUS-已从设备接收状态
包含与传输相关的统计信息(由主机总线适配器驱动程序设置)。
提供调用完成例程的原因。完成例程会对该字段进行解码。然后,执行相应的操作。如果命令完成(即未发生传输错误),则该字段将设置为 CMD_CMPLT。如果该字段中存在其他值,则指示发生了错误。完成命令后,目标驱动程序应检查 pkt_scbp 字段以查看条件状态。有关更多信息,请参见 scsi_pkt(9S) 手册页。
SCSI 目标驱动程序必须实现标准自动配置例程 _init(9E)、_fini(9E) 和 _info(9E)。有关更多信息,请参见可装入驱动程序接口。
此外,还需要以下例程,但这些例程必须执行特定的 SCSI 和 SCSA 处理:
SCSI 目标设备不是自标识设备,因此目标驱动程序必须具有 probe(9E) 例程。该例程必须确定所需类型的设备是否存在以及是否正在响应。
probe(9E) 例程的常规结构和返回代码与其他设备驱动程序的结构和返回代码相同。SCSI 目标驱动程序必须在其 probe(9E) 入口点中使用 scsi_probe(9F) 例程。scsi_probe(9F) 向设备发送 SCSI 查询命令并返回指示结果的代码。如果 SCSI 查询命令成功,则 scsi_probe(9F) 将分配 scsi_inquiry(9S) 结构并使用设备的查询数据填充该结构。从 scsi_probe(9F) 返回之后,scsi_device(9S) 结构的 sd_inq 字段将指向此 scsi_inquiry(9S) 结构。
由于 probe(9E) 必须是无状态的,因此目标驱动程序必须在 probe(9E) 返回之前调用 scsi_unprobe(9F),即使 scsi_probe(9F) 失败也是如此。
示例 17–1 显示了典型的 probe(9E) 例程。该示例中的例程从其 dev_info 结构的专用字段中检索 scsi_device(9S) 结构。该例程还检索设备的 SCSI 目标和逻辑单元号,以便列显在消息中。然后,probe(9E) 例程将调用 scsi_probe(9F) 以验证预期设备(在本例中为打印机)是否存在。
如果成功,scsi_probe(9F) 会将 scsi_inquiry(9S) 结构中设备的 SCSI 查询数据连接到 scsi_device(9S) 结构的 sd_inq 字段。然后,驱动程序便可以确定设备类型是否为打印机,相关情况将在 inq_dtype 字段中报告。如果设备是打印机,则使用 scsi_log(9F) 报告类型,并使用 scsi_dname(9F) 将设备类型转换为字符串。
static int xxprobe(dev_info_t *dip) { struct scsi_device *sdp; int rval, target, lun; /* * Get a pointer to the scsi_device(9S) structure */ sdp = (struct scsi_device *)ddi_get_driver_private(dip); target = sdp->sd_address.a_target; lun = sdp->sd_address.a_lun; /* * Call scsi_probe(9F) to send the Inquiry command. It will * fill in the sd_inq field of the scsi_device structure. */ switch (scsi_probe(sdp, NULL_FUNC)) { case SCSIPROBE_FAILURE: case SCSIPROBE_NORESP: case SCSIPROBE_NOMEM: /* * In these cases, device might be powered off, * in which case we might be able to successfully * probe it at some future time - referred to * as `deferred attach'. */ rval = DDI_PROBE_PARTIAL; break; case SCSIPROBE_NONCCS: default: /* * Device isn't of the type we can deal with, * and/or it will never be usable. */ rval = DDI_PROBE_FAILURE; break; case SCSIPROBE_EXISTS: /* * There is a device at the target/lun address. Check * inq_dtype to make sure that it is the right device * type. See scsi_inquiry(9S)for possible device types. */ switch (sdp->sd_inq->inq_dtype) { case DTYPE_PRINTER: scsi_log(sdp, "xx", SCSI_DEBUG, "found %s device at target%d, lun%d\n", scsi_dname((int)sdp->sd_inq->inq_dtype), target, lun); rval = DDI_PROBE_SUCCESS; break; case DTYPE_NOTPRESENT: default: rval = DDI_PROBE_FAILURE; break; } } scsi_unprobe(sdp); return (rval); }
更全面的 probe(9E) 例程可以检查 scsi_inquiry(9S) 以确保设备是特定驱动程序期望的类型。
在 probe(9E) 例程验证预期设备是否存在后,将调用 attach(9E)。attach() 执行以下任务:
分配并初始化任何每实例数据。
创建从设备节点信息。
暂停设备或系统后恢复设备的硬件状态。有关详细信息,请参见attach() 入口点。
SCSI 目标驱动程序需要再次调用 scsi_probe(9F),以检索设备的查询数据。该驱动程序还必须创建 SCSI 请求检测包。如果连接成功,则 attach() 函数不应调用 scsi_unprobe(9F)。
以下三个例程可用于创建请求检测包:scsi_alloc_consistent_buf(9F)、scsi_init_pkt(9F) 和 scsi_setup_cdb(9F)。scsi_alloc_consistent_buf(9F) 分配适用于一致 DMA 的缓冲区 。然后,scsi_alloc_consistent_buf() 返回指向 buf(9S) 结构的指针。一致缓冲区的优点在于无需显式同步数据。换句话说,目标驱动程序可以在回调之后访问数据。必须使用检测缓冲区的地址初始化设备的 scsi_device(9S) 结构的 sd_sense 元素。scsi_init_pkt(9F) 创建并部分初始化 scsi_pkt(9S) 结构。scsi_setup_cdb(9F) 创建 SCSI 命令描述符块,此时是通过创建 SCSI 请求检测命令来实现。
请注意,SCSI 设备不是自标识设备,并且没有 reg 属性。因此,驱动程序必须设置 pm-hardware-state 属性。设置 pm-hardware-state 将会通知框架需要暂停该设备然后将其恢复。
以下示例给出了 SCSI 目标驱动程序的 attach() 例程。
static int xxattach(dev_info_t *dip, ddi_attach_cmd_t cmd) { struct xxstate *xsp; struct scsi_pkt *rqpkt = NULL; struct scsi_device *sdp; struct buf *bp = NULL; int instance; instance = ddi_get_instance(dip); switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: /* For information, see the "Directory Memory Access (DMA)" */ /* chapter in this book. */ default: return (DDI_FAILURE); } /* * Allocate a state structure and initialize it. */ xsp = ddi_get_soft_state(statep, instance); sdp = (struct scsi_device *)ddi_get_driver_private(dip); /* * Cross-link the state and scsi_device(9S) structures. */ sdp->sd_private = (caddr_t)xsp; xsp->sdp = sdp; /* * Call scsi_probe(9F) again to get and validate inquiry data. * Allocate a request sense buffer. The buf(9S) structure * is set to NULL to tell the routine to allocate a new one. * The callback function is set to NULL_FUNC to tell the * routine to return failure immediately if no * resources are available. */ bp = scsi_alloc_consistent_buf(&sdp->sd_address, NULL, SENSE_LENGTH, B_READ, NULL_FUNC, NULL); if (bp == NULL) goto failed; /* * Create a Request Sense scsi_pkt(9S) structure. */ rqpkt = scsi_init_pkt(&sdp->sd_address, NULL, bp, CDB_GROUP0, 1, 0, PKT_CONSISTENT, NULL_FUNC, NULL); if (rqpkt == NULL) goto failed; /* * scsi_alloc_consistent_buf(9F) returned a buf(9S) structure. * The actual buffer address is in b_un.b_addr. */ sdp->sd_sense = (struct scsi_extended_sense *)bp->b_un.b_addr; /* * Create a Group0 CDB for the Request Sense command */ if (scsi_setup_cdb((union scsi_cdb *)rqpkt->pkt_cdbp, SCMD_REQUEST_SENSE, 0, SENSE__LENGTH, 0) == 0) goto failed;; /* * Fill in the rest of the scsi_pkt structure. * xxcallback() is the private command completion routine. */ rqpkt->pkt_comp = xxcallback; rqpkt->pkt_time = 30; /* 30 second command timeout */ rqpkt->pkt_flags |= FLAG_SENSING; xsp->rqs = rqpkt; xsp->rqsbuf = bp; /* * Create minor nodes, report device, and do any other initialization. */ * Since the device does not have the 'reg' property, * cpr will not call its DDI_SUSPEND/DDI_RESUME entries. * The following code is to tell cpr that this device * needs to be suspended and resumed. */ (void) ddi_prop_update_string(device, dip, "pm-hardware-state", "needs-suspend-resume"); xsp->open = 0; return (DDI_SUCCESS); failed: if (bp) scsi_free_consistent_buf(bp); if (rqpkt) scsi_destroy_pkt(rqpkt); sdp->sd_private = (caddr_t)NULL; sdp->sd_sense = NULL; scsi_unprobe(sdp); /* Free any other resources, such as the state structure. */ return (DDI_FAILURE); }
detach(9E) 入口点与 attach(9E) 是反向的入口点。detach() 必须释放在 attach() 中分配的所有资源。如果成功,则 detach 应调用 scsi_unprobe(9F)。以下示例给出了目标驱动程序的 detach() 例程。
static int xxdetach(dev_info_t *dip, ddi_detach_cmd_t cmd) { struct xxstate *xsp; switch (cmd) { case DDI_DETACH: /* * Normal detach(9E) operations, such as getting a * pointer to the state structure */ scsi_free_consistent_buf(xsp->rqsbuf); scsi_destroy_pkt(xsp->rqs); xsp->sdp->sd_private = (caddr_t)NULL; xsp->sdp->sd_sense = NULL; scsi_unprobe(xsp->sdp); /* * Remove minor nodes. * Free resources, such as the state structure and properties. */ return (DDI_SUCCESS); case DDI_SUSPEND: /* For information, see the "Directory Memory Access (DMA)" */ /* chapter in this book. */ default: return (DDI_FAILURE); } }
SCSI 目标驱动程序的 getinfo(9E) 例程与其他驱动程序的相应例程基本相同(有关 DDI_INFO_DEVT2INSTANCE 案例的更多信息,请参见 getinfo() 入口点)。但是,如果是 getinfo() 例程的 DDI_INFO_DEVT2DEVINFO,则目标驱动程序必须返回指向其 dev_info 节点的指针。该指针可以保存在驱动程序状态结构中,也可以从 scsi_device(9S) 结构的 sd_dev 字段中检索。以下示例给出了替换 SCSI 目标驱动程序 getinfo() 代码段。
case DDI_INFO_DEVT2DEVINFO: dev = (dev_t)arg; instance = getminor(dev); xsp = ddi_get_soft_state(statep, instance); if (xsp == NULL) return (DDI_FAILURE); *result = (void *)xsp->sdp->sd_dev; return (DDI_SUCCESS);
要向设备发送 SCSI 命令,目标驱动程序必须创建并初始化 scsi_pkt(9S) 结构。然后,必须将该结构传递到主机总线适配器驱动程序。
scsi_init_pkt(9F) 例程分配 scsi_pkt(9S) 结构并将该结构调整归零。scsi_init_pkt() 还设置指向 pkt_private、*pkt_scbp 和 *pkt_cdbp 的指针。此外,scsi_init_pkt() 还提供回调机制来处理资源不可用的情况。该函数的语法如下:
struct scsi_pkt *scsi_init_pkt(struct scsi_address *ap, struct scsi_pkt *pktp, struct buf *bp, int cmdlen, int statuslen, int privatelen, int flags, int (*callback)(caddr_t), caddr_t arg)
其中:
指向 scsi_address 结构的指针。ap 是设备的 scsi_device(9S) 结构的 sd_address 字段。
指向要初始化的 scsi_pkt(9S) 结构的指针。如果将该指针设置为 NULL,则会分配一个新包。
指向 buf(9S) 结构的指针。如果该指针为具有有效字节计数的非 null 值,则会分配 DMA 资源。
SCSI 命令描述符块的长度(以字节为单位)。
SCSI 状态完成块的必需长度(以字节为单位)。
要为 pkt_private 字段分配的字节数。
标志集:
PKT_CONSISTENT-如果 DMA 缓冲区是使用 scsi_alloc_consistent_buf(9F) 分配的,则必须设置该位。在这种情况下,主机总线适配器驱动程序将保证在执行目标驱动程序的命令完成回调之前正确同步数据传输。
PKT_DMA_PARTIAL-如果驱动程序接受部分 DMA 映射,则可以设置该位。如果设置了该位,scsi_init_pkt(9F) 将分配 DMA 资源并设置 DDI_DMA_PARTIAL 标志。可以返回 scsi_pkt(9S) 结构的 pkt_resid 字段的返回值可以是非零的剩余值。非零值表示 scsi_init_pkt(9F) 无法分配的 DMA 资源字节数。
指定资源不可用时要执行的操作。如果设置为 NULL_FUNC,scsi_init_pkt(9F) 将立即返回值 NULL。如果设置为 SLEEP_FUNC,则在资源可用之前,scsi_init_pkt() 不会返回。当资源可能可用时,会将任何其他有效的内核地址解释为要调用的函数的地址。
要传递给回调函数的参数。
传输之前,scsi_init_pkt() 例程将同步数据。如果驱动程序需要在传输后访问数据,则驱动程序应调用 scsi_sync_pkt(9F) 以刷新任何中间高速缓存。可以使用 scsi_sync_pkt() 例程来同步所有高速缓存的数据。
如果在更改数据之后目标驱动程序需要重新提交包,则必须在调用 scsi_transport(9F) 之前调用 scsi_sync_pkt(9F)。但是,如果目标驱动程序不需要访问数据,则在传输之后不需要调用 scsi_sync_pkt()。
如有必要,scsi_destroy_pkt(9F) 例程将同步与包关联的任何剩余高速缓存数据。然后,该例程会释放包以及关联的命令、状态和目标驱动程序专用的数据区。应在命令完成例程中调用该例程。
对于大多数 I/O 请求,驱动程序不直接访问传递到驱动程序入口点的数据缓冲区。该缓冲区仅传递到 scsi_init_pkt(9F)。如果某个驱动程序发送的 SCSI 命令是针对该驱动程序本身检查的缓冲区,那么这些缓冲区应该支持 DMA。SCSI 请求检测命令就是一个很好的示例。scsi_alloc_consistent_buf(9F) 例程分配 buf(9S) 结构和适用于 DMA 一致操作的数据缓冲区。HBA 首先会执行任何必需的缓冲区同步,然后再执行命令完成回调。
scsi_alloc_consistent_buf(9F) 将使用珍贵的系统资源。因此,应有节制地使用 scsi_alloc_consistent_buf()。
scsi_free_consistent_buf(9F) 释放 buf(9S) 结构和使用 scsi_alloc_consistent_buf(9F) 分配的关联数据缓冲区。有关示例,请参见attach() 入口点(SCSI 目标驱动程序)和detach() 入口点(SCSI 目标驱动程序)。
主机总线适配器驱动程序负责向设备传输命令。此外,该驱动程序还负责处理低级别的 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 字段:
该命令本身位于第 0 个字节中。该命令通过 SCMD_READ 参数进行设置。
地址字段位于第 1 个字节的 0-4 位以及第 2 个字节和第 3 个字节中。地址通过 bp->b_blkno 进行设置。
计数字段位于第 4 个字节中。计数通过最后一个参数进行设置。在本例中,count 设置为 bp->b_bcount >> DEV_BSHIFT,其中 DEV_BSHIFT 是已转换为块数的传输字节计数。
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) 返回的其他值如下所示:
TRAN_BUSY-表示已在运行针对指定目标的命令。
TRAN_BADPKT-包中的 DMA 计数太大,或者主机适配器驱动程序由于其他原因拒绝了该包。
TRAN_FATAL_ERROR-主机适配器驱动程序无法接受该包。
在调用 scsi_transport(9F) 过程中,不能持有 scsi_device(9S) 结构中的互斥锁 sd_mutex。
如果 scsi_transport(9F) 返回 TRAN_ACCEPT,则该包将由主机总线适配器驱动程序负责。调用命令完成例程之前,目标驱动程序不应该访问该包。
如果在包中设置了 FLAG_NOINTR,则在命令完成之前,scsi_transport(9F) 不会返回。不会执行回调。
请勿在中断上下文中使用 FLAG_NOINTR。
当主机总线适配器驱动程序完成命令后,将调用包的完成回调例程。然后,驱动程序会将指向 scsi_pkt(9S) 结构的指针作为参数传递。对包进行解码后,完成例程将执行相应的操作。
示例 17–5 显示了一个简单的完成回调例程。该代码检查传输是否失败。如果存在失败情况,该例程将放弃运行,而不会重试命令。如果目标繁忙,则需要额外的代码以便在以后重新提交命令。
如果命令产生检查条件,则目标驱动程序需要发送请求检测命令,除非启用了自动请求检测。
否则,命令成功。在结束命令处理时,命令将销毁包并调用 biodone(9F)。
如果发生传输错误(如总线重置或奇偶校验问题),则目标驱动程序可以使用 scsi_transport(9F) 重新提交该包。重新提交之前,无需更改包中的任何值。
以下示例不会尝试重试未完成的命令。
通常,在中断上下文中会调用目标驱动程序的回调函数。因此,回调函数绝不应处于休眠状态。
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); } } }
重新提交未更改的包。
使用 scsi_sync_pkt(9F) 同步数据。然后,处理驱动程序中的数据。最后,重新提交包。
使用 scsi_dmafree(9F) 释放 DMA 资源,然后将 pkt 指针传递给 scsi_init_pkt(9F) 以绑定到新的 bp。目标驱动程序负责重新初始化包。该 CDB 的长度必须与上一个 CDB 的长度相同。
如果首次调用 scsi_init_pkt(9F) 时仅分配了部分 DMA,则以后可以针对同一个包调用 scsi_init_pkt(9F)。同样,可以调用 bp 以调整下一个传输部分的 DMA 资源。
如果使用排队(无论是标记排队还是无标记排队),则最好使用自动请求检测模式。任何后续命令都会清除应急处理状态 (contingent allegiance condition),并因此导致检测数据丢失。大多数 HBA 驱动程序会在执行目标驱动程序回调之前开始下一条命令。其他 HBA 驱动程序可以使用单独的、较低优先级的线程执行回调。采用该方法时,如果存在检查状况,那么向目标驱动程序通知包已完成所需的时间可能会增加。在这种情况下,目标驱动程序可能无法及时提交请求检测命令以检索检测数据。
为了避免检测数据丢失,HBA 驱动程序或控制器应在检测到检查条件时发出请求检测命令。该模式称为自动请求检测模式。请注意,并非所有 HBA 驱动程序都可以使用自动请求检测模式,而某些驱动程序只能在启用了自动请求检测模式时运行。
目标驱动程序使用 scsi_ifsetcap(9F) 来启用自动请求检测模式。以下示例说明了如何启用自动请求检测。
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 cmd 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) 提交包。当该包中出现检查条件时,主机适配器驱动程序将执行以下步骤:
发出请求检测命令(如果控制器没有自动请求检测功能)
获取检测数据
在包的状态块中填充 scsi_arq_status 信息
在包的 pkt_state 字段中设置 STATE_ARQ_DONE
调用包的回调处理程序 (pkt_comp())
目标驱动程序的回调例程应通过检查 pkt_state 中的 STATE_ARQ_DONE 位,来验证检测数据是否可用。STATE_ARQ_DONE 表明出现了检查条件,并且已执行请求检测。如果包中暂时禁用了自动请求检测,则无法保证对检测数据的后续检索。
然后,目标驱动程序应验证自动请求检测命令是否已成功完成,并对检测数据进行解码。
在系统出现故障或执行检查点操作时,dump(9E) 入口点会将部分虚拟地址空间直接复制到指定的设备。请参见 cpr(7) 和 dump(9E) 手册页。dump(9E) 入口点必须在不使用中断的情况下能够执行该操作。
dump() 的参数如下所示:
转储设备的设备编号
开始转储的内核虚拟地址
设备上的第一个目标块
要转储的块数
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); }
SCSA 定义了一个全局变量 scsi_options,用于进行控制和调试。在文件 <sys/scsi/conf/autoconf.h> 中,可以找到 scsi_options 中定义的位。scsi_options 按以下方式使用这些位:
启用全局断开连接或重新连接。
启用全局 FAST SCSI 支持: 每秒传输 10 MB。除非设置了 SCSI_OPTIONS_FAST (0x100) 位,否则 HBA 不应采用 FAST SCSI 模式运行。
启用全局 FAST20 SCSI 支持: 每秒传输 20 MB。除非设置了 SCSI_OPTIONS_FAST20 (0x400) 位,否则 HBA 不应采用 FAST20 SCSI 模式运行。
启用全局 FAST40 SCSI 支持: 每秒传输 40 MB。除非设置了 SCSI_OPTIONS_FAST40 (0x800) 位,否则 HBA 不应采用 FAST40 SCSI 模式运行。
启用全局 FAST80 SCSI 支持: 每秒传输 80 MB。除非设置了 SCSI_OPTIONS_FAST80 (0x1000) 位,否则 HBA 不应采用 FAST80 SCSI 模式运行。
启用全局 FAST160 SCSI 支持: 每秒传输 160 MB。除非设置了 SCSI_OPTIONS_FAST160 (0x2000) 位,否则 HBA 不应采用 FAST160 SCSI 模式运行。
启用全局 FAST320 SCSI 支持: 每秒传输 320 MB。除非设置了 SCSI_OPTIONS_FAST320 (0x4000) 位,否则 HBA 不应采用 FAST320 SCSI 模式运行。
启用全局链接支持。
启用全局奇偶校验支持。
启用“快速仲裁选择”功能。QAS(快速仲裁选择)用于降低设备仲裁并访问总线时的协议开销。只有 Ultra4 (FAST160) SCSI 设备支持 QAS,但是并非所有此类设备都支持 QAS。除非设置了 SCSI_OPTIONS_QAS (0x100000) 位,否则 HBA 不应采用 QAS SCSI 模式运行。请查阅相应的 Sun 硬件文档,以确定您的计算机是否支持 QAS。
启用全局同步传输功能。
启用全局标记排队支持。
启用全局 WIDE SCSI。
设置 scsi_options 会影响系统中存在的所有主机总线适配器驱动程序和所有目标驱动程序。有关控制特定主机适配器的这些选项的信息,请参阅 scsi_hba_attach(9F) 手册页。