Writing Device Drivers

Building and Transporting a Command

The host bus adapter driver is responsible for transmitting the command to the device and handling the low-level SCSI protocol. The scsi_transport(9F) routine hands a packet to the host bus adapter driver for transmission. It is the target driver's responsibility to create a valid scsi_pkt(9S) structure.

Building a Command

The routine scsi_init_pkt(9F) allocates space for a SCSI CDB, allocates DMA resources if necessary, and sets the pkt_flags field:

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

This example creates a new packet and allocates 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 none 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 and fills in the pkt_cdbp field as follows:


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, as defined by SCSI-1. For SCSI-1 devices requiring the LUN bits set in the command block, use makecom_g0(9F) (or 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.

Setting Target Capabilities

The target drivers use scsi_ifsetcap(9F) to set the capabilities of the host adapter driver. A cap is a name-value pair whose name is a null terminated character string and whose value is an integer. 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 and is not universally supported by HBA drivers. Some capabilities (such as disconnect and synchronous) may be set by default by the HBA driver but others may need to be explicitly set by the target driver (wide-xfer or tagged-queing, for example).

Transporting a Command

After creating and filling in the scsi_pkt(9S) structure, the final step is to hand it to the host bus adapter driver using scsi_transport(9F):

	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:


Caution - Caution -

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 is the responsibility of the host bus adapter driver and should not be accessed by the target driver until the command completion routine is called.

Synchronous scsi_transport(9F)

If FLAG_NOINTR is set in the packet, then scsi_transport(9F) will not return until the command is complete, and no callback will be performed.


Note -

FLAG_NOINTR should never be used in interrupt context.


Command Completion

Once the host bus adapter driver has done all it can with the command, it invokes the packet's completion callback routine, passing a pointer to the scsi_pkt(9S) structure as a parameter. The completion routine decodes the packet and takes the appropriate action.

Example 13-5 presents a very simple completion callback routine. This code checks for transport failures and gives up rather than retry 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 not been enabled.


Note -

Normally, the target driver's callback function is called in interrupt context. Consequently, the callback function should never sleep.



Example 13-5 SCSI Driver Completion Routine

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);
	   	}
	}
}

Otherwise, the command succeeded. If this is the end of processing for the command, it 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 may resubmit the packet using scsi_transport(9E). There is no need to change any values in the packet prior to resubmitting.

This example does not attempt to retry incomplete commands. See Appendix G, Advanced Topics, for further information.

Reuse of Packets

A target driver may reuse packets in the following ways:

Auto-Request Sense Mode

Auto-request sense mode is most desirable if tagged or untagged queuing is used. A contingent allegiance condition is cleared by any subsequent command and, consequently, the sense data is lost. Most HBA drivers will start the next command before performing the target driver callback. Other HBA drivers may use a separate and lower-priority thread to perform the callbacks, which may increase the time it takes to notify the target driver that the packet completed with a check condition. In this case, the target driver may 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 as soon as 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 can only operate with auto-request-sense mode enabled.

A target driver enables auto-request-sense mode by using scsi_ifsetcap(9F). Example 13-6 shows enabling auto request sense.


Example 13-6 Enabling Auto Request Sense

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 may
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) may 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);
	...
}

When a packet is allocated using scsi_init_pkt(9F) and auto request sense is desired on this packet then the target driver must request additional space for the status block to hold the auto request sense structure (as Example 13-7 illustrates). The sense length used in the request sense command is sizeof (struct scsi_extended_sense).

The scsi_arq_status structure contains the following members:

	struct scsi_status					sts_status;
 	struct scsi_status					sts_rqpkt_status;
 	u_char					sts_rqpkt_reason;	/* reason completion */
 	u_char					sts_rqpkt_resid;	/* residue */
 	u_int					sts_rqpkt_state;	/* state of command */
 	u_int					sts_rqpkt_statistics;	/* statistics */
 	struct scsi_extended_sense sts_sensedata;

Auto request sense can be disabled per individual packet by just allocating sizeof (struct scsi_status) for the status block.


Example 13-7 Allocating a Packet With Auto Request Sense

	pkt = scsi_init_pkt(ROUTE, NULL, bp, CDB_GROUP1,
	    xsp->sdp_cmd_stat_size, PP_LEN, 0, func, (caddr_t) xsp);

The packet is submitted using scsi_transport(9F) as usual. When a check condition occurs on this packet, the host adapter driver:

The target driver's callback routine should verify that sense data is available by checking the STATE_ARQ_DONE bit in pkt_state, which implies that a check condition has occurred and a request sense has been performed. If auto-request-sense has been temporarily disabled in a packet, there is no guarantee that the sense data can be retrieved at a later time.

The target driver should then verify whether the auto request sense command completed successfully and decode the sense data.


Example 13-8 Checking for Auto Request Sense

static void
xxcallback(struct scsi_pkt *pkt)
{
	...
	if (pkt->pkt_state & STATE_ARQ_DONE) {
	   	/*
		    * The transport layer successfully completed an
		    * auto-request-sense.
		    * Decode the auto request sense data here
		    */
	    	....
	}
	...
}          

Dump Handling

dump(9E)

The dump(9E) entry point is used to copy a portion of virtual address space directly to the specified device in the case of system failure or checkpoint operation. See cpr(7) and dump(9E). The dump(9E) entry point must be capable of performing this operation without the use of interrupts.

dev is the device number of the dump device, addr is the kernel virtual address at which to start the dump, blkno is the first destination block on the device and nblk is the number of blocks to dump.


Example 13-9 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 || tgt->pm_suspended) {
		(void) ddi_dev_is_needed(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 requeuing 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);
}