第 1 部分针对 Oracle Solaris 平台设计设备驱动程序
9. 直接内存访问 (Direct Memory Access, DMA)
14. 分层驱动程序接口 (Layered Driver Interface, LDI)
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) 将设备类型转换为字符串。
示例 17-1 SCSI 目标驱动程序 probe(9E) 例程
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() 例程。
示例 17-2 SCSI 目标驱动程序 attach(9E) 例程
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() 例程。
示例 17-3 SCSI 目标驱动程序 detach(9E) 例程
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() 代码段。
示例 17-4 替代 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);