Skip Navigation Links | |
Exit Print View | |
Writing Device Drivers Oracle Solaris 11.1 Information Library |
Part I Designing Device Drivers for the Oracle Solaris Platform
1. Overview of Oracle Solaris Device Drivers
2. Oracle Solaris Kernel and Device Tree
5. Managing Events and Queueing Tasks
7. Device Access: Programmed I/O
10. Mapping Device and Kernel Memory
13. Hardening Oracle Solaris Drivers
14. Layered Driver Interface (LDI)
Part II Designing Specific Kinds of Device Drivers
15. Drivers for Character Devices
Introduction to Target Drivers
Sun Common SCSI Architecture Overview
Declarations and Data Structures
scsi_pkt Structure (Target Drivers)
Autoconfiguration for SCSI Target Drivers
probe() Entry Point (SCSI Target Drivers)
attach() Entry Point (SCSI Target Drivers)
detach() Entry Point (SCSI Target Drivers)
getinfo() Entry Point (SCSI Target Drivers)
scsi_alloc_consistent_buf() Function
scsi_free_consistent_buf() Function
18. SCSI Host Bus Adapter Drivers
19. Drivers for Network Devices
Part III Building a Device Driver
22. Compiling, Loading, Packaging, and Testing Drivers
23. Debugging, Testing, and Tuning Device Drivers
24. Recommended Coding Practices
B. Summary of Oracle Solaris DDI/DKI Services
C. Making a Device Driver 64-Bit Ready
The host bus adapter driver is responsible for transmitting the command to the device. Furthermore, the driver is responsible for handling the low-level SCSI protocol. The scsi_transport(9F) routine hands a packet to the host bus adapter driver for transmission. The target driver has the responsibility to create a valid scsi_pkt(9S) structure.
The routine scsi_init_pkt(9F) allocates space for a SCSI CDB, allocates DMA resources if necessary, and sets the pkt_flags field, as shown in this example:
pkt = scsi_init_pkt(&sdp->sd_address, NULL, bp, CDB_GROUP0, 1, 0, 0, SLEEP_FUNC, NULL);
This example creates a new packet along with allocating DMA resources as specified in the passed buf(9S) structure pointer. A SCSI CDB is allocated for a Group 0 (6-byte) command. The pkt_flags field is set to zero, but no space is allocated for the pkt_private field. This call to scsi_init_pkt(9F), because of the SLEEP_FUNC parameter, waits indefinitely for resources if no resources are currently available.
The next step is to initialize the SCSI CDB, using the scsi_setup_cdb(9F) function:
if (scsi_setup_cdb((union scsi_cdb *)pkt->pkt_cdbp, SCMD_READ, bp->b_blkno, bp->b_bcount >> DEV_BSHIFT, 0) == 0) goto failed;
This example builds a Group 0 command descriptor block. The example fills in the pkt_cdbp field as follows:
The command itself is in byte 0. The command is set from the parameter SCMD_READ.
The address field is in bits 0-4 of byte 1 and bytes 2 and 3. The address is set from bp->b_blkno.
The count field is in byte 4. The count is set from the last parameter. In this case, count is set to bp->b_bcount >> DEV_BSHIFT, where DEV_BSHIFT is the byte count of the transfer converted to the number of blocks.
Note - scsi_setup_cdb(9F) does not support setting a target device's logical unit number (LUN) in bits 5-7 of byte 1 of the SCSI command block. This requirement is defined by SCSI-1. For SCSI-1 devices that require the LUN bits set in the command block, use makecom_g0(9F) or some equivalent rather than scsi_setup_cdb(9F).
After initializing the SCSI CDB, initialize three other fields in the packet and store as a pointer to the packet in the state structure.
pkt->pkt_private = (opaque_t)bp; pkt->pkt_comp = xxcallback; pkt->pkt_time = 30; xsp->pkt = pkt;
The buf(9S) pointer is saved in the pkt_private field for later use in the completion routine.
The target drivers use scsi_ifsetcap(9F) to set the capabilities of the host adapter driver. A cap is a name-value pair, consisting of a null-terminated character string and an integer value. The current value of a capability can be retrieved using scsi_ifgetcap(9F). scsi_ifsetcap(9F) allows capabilities to be set for all targets on the bus.
In general, however, setting capabilities of targets that are not owned by the target driver is not recommended. This practice is not universally supported by HBA drivers. Some capabilities, such as disconnect and synchronous, can be set by default by the HBA driver. Other capabilities might need to be set explicitly by the target driver. Wide-xfer and tagged-queueing must be set by the target drive, for example.
After the scsi_pkt(9S) structure is filled in, use scsi_transport(9F) to hand the structure to the bus adapter driver:
if (scsi_transport(pkt) != TRAN_ACCEPT) { bp->b_resid = bp->b_bcount; bioerror(bp, EIO); biodone(bp); }
The other return values from scsi_transport(9F) are as follows:
TRAN_BUSY – A command for the specified target is already in progress.
TRAN_BADPKT – The DMA count in the packet was too large, or the host adapter driver rejected this packet for other reasons.
TRAN_FATAL_ERROR – The host adapter driver is unable to accept this packet.
Note - The mutex sd_mutex in the scsi_device(9S) structure must not be held across a call to scsi_transport(9F).
If scsi_transport(9F) returns TRAN_ACCEPT, the packet becomes the responsibility of the host bus adapter driver. The packet should not be accessed by the target driver until the command completion routine is called.
If FLAG_NOINTR is set in the packet, then scsi_transport(9F) does not return until the command is complete. No callback is performed.
Note - Do not use FLAG_NOINTR in interrupt context.
When the host bus adapter driver is through with the command, the driver invokes the packet's completion callback routine. The driver then passes a pointer to the scsi_pkt(9S) structure as a parameter. After decoding the packet, the completion routine takes the appropriate action.
Example 17-5 presents a simple completion callback routine. This code checks for transport failures. In case of failure, the routine gives up rather than retrying the command. If the target is busy, extra code is required to resubmit the command at a later time.
If the command results in a check condition, the target driver needs to send a request sense command unless auto request sense has been enabled.
Otherwise, the command succeeded. At the end of processing for the command, the command destroys the packet and calls biodone(9F).
In the event of a transport error, such as a bus reset or parity problem, the target driver can resubmit the packet by using scsi_transport(9F). No values in the packet need to be changed prior to resubmitting.
The following example does not attempt to retry incomplete commands.
Note - Normally, the target driver's callback function is called in interrupt context. Consequently, the callback function should never sleep.
Example 17-5 Completion Routine for a SCSI Driver
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); } } }
A target driver can reuse packets in the following ways:
Resubmit the packet unchanged.
Use scsi_sync_pkt(9F) to synchronize the data. Then, process the data in the driver. Finally, resubmit the packet.
Free DMA resources, using scsi_dmafree(9F), and pass the pkt pointer to scsi_init_pkt(9F) for binding to a new bp. The target driver is responsible for reinitializing the packet. The CDB has to have the same length as the previous CDB.
If only partial DMA is allocated during the first call to scsi_init_pkt(9F), subsequent calls to scsi_init_pkt(9F) can be made for the same packet. Calls can be made to bp as well to adjust the DMA resources to the next portion of the transfer.
Auto-request sense mode is most desirable if queuing is used, whether the queuing is tagged or untagged. A contingent allegiance condition is cleared by any subsequent command and, consequently, the sense data is lost. Most HBA drivers start the next command before performing the target driver callback. Other HBA drivers can use a separate, lower-priority thread to perform the callbacks. This approach might increase the time needed to notify the target driver that the packet completed with a check condition. In this case, the target driver might not be able to submit a request sense command in time to retrieve the sense data.
To avoid this loss of sense data, the HBA driver, or controller, should issue a request sense command if a check condition has been detected. This mode is known as auto-request sense mode. Note that not all HBA drivers are capable of auto-request sense mode, and some drivers can only operate with auto-request sense mode enabled.
A target driver enables auto-request-sense mode by using scsi_ifsetcap(9F). The following example shows auto-request sense enabling.
Example 17-6 Enabling Auto-Request Sense Mode
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); /* ... */ }
If a packet is allocated using scsi_init_pkt(9F) and auto-request sense is desired on this packet, additional space is needed. The target driver must request this space for the status block to hold the auto-request sense structure. The sense length used in the request sense command is sizeof, from struct scsi_extended_sense. Auto-request sense can be disabled per individual packet by allocating sizeof, from struct scsi_status, for the status block.
The packet is submitted using scsi_transport(9F) as usual. When a check condition occurs on this packet, the host adapter driver takes the following steps:
Issues a request sense command if the controller does not have auto-request sense capability
Obtains the sense data
Fills in the scsi_arq_status information in the packet's status block
Sets STATE_ARQ_DONE in the packet's pkt_state field
Calls the packet's callback handler (pkt_comp())
The target driver's callback routine should verify that sense data is available by checking the STATE_ARQ_DONE bit in pkt_state. STATE_ARQ_DONE implies that a check condition has occurred and that a request sense has been performed. If auto-request sense has been temporarily disabled in a packet, subsequent retrieval of the sense data cannot be guaranteed.
The target driver should then verify whether the auto-request sense command completed successfully and decode the sense data.
The dump(9E) entry point copies a portion of virtual address space directly to the specified device in the case of system failure or checkpoint operation. See the cpr(7) and dump(9E) man pages. The dump(9E) entry point must be capable of performing this operation without the use of interrupts.
The arguments for dump() are as follows:
Device number of the dump device
Kernel virtual address at which to start the dump
First destination block on the device
Number of blocks to dump
Example 17-7 dump(9E) Routine
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); }