Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Mail Systems
Eclipse Documentation

How To Guides
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Problem Solutions
Privacy Policy




Writing Device Drivers
Previous Next

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

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.

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 */

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 *)
     * 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:


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


    if (rval != DDI_SUCCESS) {
        rval = EIO;
    return (rval);
Previous Next

  Published under the terms fo the Public Documentation License Version 1.01. Design by Interspire