Building and Transporting a Command
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.
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, 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.
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, 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.
Transporting a Command
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.
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.
Synchronous scsi_transport() Function
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.
Command Completion
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);
}
}
}
Reuse of Packets
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
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 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);
/* ... */
}
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.
Dump Handling
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:
- dev
Device number of the dump device
- addr
Kernel virtual address at which to start the dump
- blkno
First destination block on the device
- nblk
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);
}