Writing Device Drivers

SCSA HBA Entry Points

For an HBA driver to work with target drivers using the SCSA interface, each HBA driver must supply a number of entry points, callable through the scsi_hba_tran(9S) structure.These entry points fall into five functional groups:

Table 15-3 lists the SCSA HBA entry points arranged by function groups.

Table 15-3 SCSA Entry Points

Function Groups  

Entry Points Within Group 

Description 

Target Driver Instance Initialization 

tran_tgt_init(9E)

Performs per-target initialization (optional) 

 

tran_tgt_probe(9E)

Probes SCSI bus for existence of a target (optional) 

 

tran_tgt_free(9E)

Performs per-target deallocation (optional) 

Resource Allocation  

tran_init_pkt(9E)

Allocates SCSI packet and DMA resources 

 

tran_destroy_pkt(9E)

Frees SCSI packet and DMA resources 

 

tran_sync_pkt(9E)

Synchronizes memory before and after DMA 

 

tran_dmafree(9E)

Frees DMA resources 

Command Transport  

tran_start(9E)

Transports a SCSI command 

Capability Management  

tran_getcap(9E)

Inquires about a capability's value 

 

tran_setcap(9E)

Sets a capability's value 

Abort and Reset  

tran_abort(9E)

Aborts one or all outstanding SCSI commands 

 

tran_reset(9E)

Resets a target device or the SCSI bus 

 

tran_bus_reset(9E)

Resets the SCSI bus 

 

tran_reset_notify(9E)

Request to notify target of bus reset (optional) 

Dynamic Reconfiguration 

tran_quiesce(9E)

Stops activity on the bus 

 

tran_unquiesce(9E)

Resume activity on the bus 

Target Driver Instance Initialization

The following sections explain target entry points.

tran_tgt_init(9E)

The tran_tgt_init(9E) entry point allows the HBA to allocate and/or initialize any per-target resources. It also allows the HBA to qualify the device's address as valid and supportable for that particular HBA. By returning DDI_FAILURE, the instance of the target driver for that device will not be probed or attached.

This entry point is not required, and if none is supplied, the framework will attempt to probe and attach all possible instances of the appropriate target drivers.

static int
isp_tran_tgt_init(
     dev_info_t                 *hba_dip,
     dev_info_t                 *tgt_dip,
     scsi_hba_tran_t            *tran,
     struct scsi_device         *sd)
 {
     return ((sd->sd_address.a_target < N_ISP_TARGETS_WIDE &&
             sd->sd_address.a_lun < 8) ? DDI_SUCCESS : DDI_FAILURE);
 }

tran_tgt_probe(9E)

The tran_tgt_probe(9E) entry point enables the HBA to customize the operation of scsi_probe(9F), if necessary. This entry point is called only when the target driver calls scsi_probe(9F).

The HBA driver can retain the normal operation of scsi_probe(9F) by calling scsi_hba_probe(9F) and returning its return value.

This entry point is not required, and if not needed, the HBA driver should set the tran_tgt_probe vector in the scsi_hba_tran(9S) structure to point to scsi_hba_probe(9F).

scsi_probe(9F) allocates a scsi_inquiry(9S) structure and sets the sd_inq field of the scsi_device(9S) structure to point to the data in scsi_inquiry(9S). scsi_hba_probe(9F) handles this automatically. scsi_unprobe(9F) then frees the scsi_inquiry(9S) data.

Other than during the allocation of scsi_inquiry(9S) data, normally handled by scsi_hba_probe(9F), tran_tgt_probe(9E) must be stateless, as the same SCSI device might call it multiple times.


Note -

The allocation of the scsi_inquiry(9S) structure is handled automatically by scsi_hba_probe(9F). This is only of concern if custom scsi_probe(9F) handling is what you want.


static int
 isp_tran_tgt_probe(
     struct scsi_device    *sd,
     int            (*callback)())
 {
     Perform any special probe customization needed.
     /*
      * Normal probe handling
      */
     return (scsi_hba_probe(sd, callback));
 }

tran_tgt_free(9E)

The tran_tgt_free(9E) entry point enables the HBA to perform any deallocation or clean-up procedures for an instance of a target. This entry point is optional.

static void
isp_tran_tgt_free(
     dev_info_t                  *hba_dip,
     dev_info_t                  *tgt_dip,
     scsi_hba_tran_t             *hba_tran,
     struct scsi_device          *sd)
 {
     Undo any special per-target initialization done
     earlier in tran_tgt_init(9F) and tran_tgt_probe(9F)
 }

Resource Allocation

The following sections discuss resource allocation.

tran_init_pkt(9E)

The tran_init_pkt(9E) entry point is the HBA driver function that allocates and initializes, on behalf of the target driver, a scsi_pkt(9S) structure and DMA resources for a target driver request.

The tran_init_pkt(9E) entry point is called when the target driver calls the SCSA function scsi_init_pkt(9F).

Each call of the tran_init_pkt(9E) entry point is a request to perform one or more of three possible services:

Allocation and Initialization of a scsi_pkt(9S) Structure

The tran_init_pkt(9E) entry point must allocate a scsi_pkt(9S) structure if pkt is NULL through scsi_hba_pkt_alloc(9F).

scsi_hba_pkt_alloc(9F) allocates the following:

The scsi_pkt(9S) structure members, as well as pkt itself, must be initialized to zero except for the following members: pkt_scbp (status completion), pkt_cdbp (CDB), pkt_ha_private (HBA driver private data), pkt_private (target driver private data). These members are pointers to memory space where the values of the fields are stored, as illustrated in Figure 15-5. For more information, refer to "scsi_pkt Structure".

Figure 15-5 scsi_pkt(9S) Structure Pointers

Graphic

Example 15-3 provides an example of allocation and initialization of a scsi_pkt(9S) structure.


Example 15-3 HBA Driver Initialization of a SCSI Packet Structure

static struct scsi_pkt                             *
isp_scsi_init_pkt(
    struct scsi_address        *ap,
    struct scsi_pkt            *pkt,
    struct buf                 *bp,
    int                        cmdlen,
    int                        statuslen,
    int                        tgtlen,
    int                        flags,
    int                        (*callback)(),
    caddr_t                    arg)
{
    struct isp_cmd             *sp;
    struct isp                 *isp;
    struct scsi_pkt            *new_pkt;

    ASSERT(callback == NULL_FUNC || callback == SLEEP_FUNC);

    isp = (struct isp *)ap->a_hba_tran->tran_hba_private;

    /*
     * First step of isp_scsi_init_pkt:  pkt allocation
     */
    if (pkt == NULL) {
           pkt = scsi_hba_pkt_alloc(isp->isp_dip, ap, cmdlen,
                statuslen, tgtlen, sizeof (struct isp_cmd),
                callback, arg);
           if (pkt == NULL) {
                return (NULL);
           }

           sp = (struct isp_cmd *)pkt->pkt_ha_private;

           /*
            * Initialize the new pkt
            */
           sp->cmd_pkt             = pkt;
           sp->cmd_flags           = 0;
           sp->cmd_scblen          = statuslen;
           sp->cmd_cdblen          = cmdlen;
           sp->cmd_dmahandle       = NULL;
           sp->cmd_ncookies        = 0;
           sp->cmd_cookie          = 0; 
           sp->cmd_cookiecnt       = 0;
           sp->cmd_nwin            = 0;
           pkt->pkt_address        = *ap;
           pkt->pkt_comp           = (void (*)())NULL;
           pkt->pkt_flags          = 0;
           pkt->pkt_time           = 0;
           pkt->pkt_resid          = 0;
           pkt->pkt_statistics     = 0;
           pkt->pkt_reason         = 0;

           new_pkt = pkt;
    } else {
           sp = (struct isp_cmd *)pkt->pkt_ha_private;
           new_pkt = NULL;
    }

    /*
     * Second step of isp_scsi_init_pkt:  dma allocation/move
     */
    if (bp && bp->b_bcount != 0) {
           if (sp->cmd_dmahandle == NULL) {
               if (isp_i_dma_alloc(isp, pkt, bp,
                   flags, callback) == 0) {
                   if (new_pkt) {
                           scsi_hba_pkt_free(ap, new_pkt);
                   }
                   return ((struct scsi_pkt *)NULL);
                }
           } else {
               ASSERT(new_pkt == NULL);
               if (isp_i_dma_move(isp, pkt, bp) == 0) {
                   return ((struct scsi_pkt *)NULL);
               }
           }
    }

    return (pkt);
}

Allocation of DMA Resources

If bp is not NULL and bp->b_bcount is not zero and DMA resources have not yet been allocated for this scsi_pkt(9S), the tran_init_pkt(9E) entry point must allocate DMA resources for a data transfer. The HBA driver needs to keep track of whether DMA resources have been allocated for a particular command with a flag bit or a DMA handle in the per-packet HBA driver private data.

By setting the PKT_DMA_PARTIAL flag in the pkt, the target driver indicates it can accept breaking up the data transfer into multiple SCSI commands to accommodate the complete request. This might be necessary if the HBA hardware scatter-gather capabilities or system DMA resources are insufficient to accommodate the complete request in a single SCSI command.

If the PKT_DMA_PARTIAL flag is set, the HBA driver can set the DDI_DMA_PARTIAL flag when allocating DMA resources (using, for example, ddi_dma_buf_bind_handle(9F)) for this SCSI command. The DMA attributes used when allocating the DMA resources should accurately describe any constraints placed on the ability of the HBA hardware to perform DMA. If the system can only allocate DMA resources for part of the request, ddi_dma_buf_bind_handle(9F) will return DDI_DMA_PARTIAL_MAP.

The tran_init_pkt(9E) entry point must return the amount of DMA resources not allocated for this transfer in the field pkt_resid.

A target driver can make one request to tran_init_pkt(9E) to simultaneously allocate both a scsi_pkt(9S) structure and DMA resources for that pkt. In this case, if the HBA driver is unable to allocate DMA resources, it must free the allocated scsi_pkt(9S) before returning. The scsi_pkt(9S) must be freed by calling scsi_hba_pkt_free(9F).

The target driver might first allocate the scsi_pkt(9S) and allocate DMA resources for this pkt at a later time. In this case, if the HBA driver is unable to allocate DMA resources, it must not free pkt. The target driver in this case is responsible for freeing the pkt.


Example 15-4 HBA Driver Allocation of DMA Resources

static int
isp_i_dma_alloc(
    struct isp         *isp,
    struct scsi_pkt    *pkt,
    struct buf         *bp,
    int                flags,
    int                (*callback)())
{
    struct isp_cmd    *sp  = (struct isp_cmd *)pkt->pkt_ha_private;
    int                dma_flags;
    ddi_dma_attr_t     tmp_dma_attr;
    int                (*cb)(caddr_t);
    int                i;

    ASSERT(callback == NULL_FUNC || callback == SLEEP_FUNC);

    if (bp->b_flags & B_READ) {
           sp->cmd_flags &= ~CFLAG_DMASEND;
           dma_flags = DDI_DMA_READ;
    } else {
           sp->cmd_flags |= CFLAG_DMASEND;
           dma_flags = DDI_DMA_WRITE;
    }
    if (flags & PKT_CONSISTENT) {
           sp->cmd_flags |= CFLAG_CMDIOPB;
           dma_flags |= DDI_DMA_CONSISTENT;
    }
    if (flags & PKT_DMA_PARTIAL) {
           dma_flags |= DDI_DMA_PARTIAL;
    }

    tmp_dma_attr = isp_dma_attr;
    tmp_dma_attr.dma_attr_burstsizes = isp->isp_burst_size;

    cb = (callback == NULL_FUNC) ? DDI_DMA_DONTWAIT :
DDI_DMA_SLEEP;

    if ((i = ddi_dma_alloc_handle(isp->isp_dip, &tmp_dma_attr,
          cb, 0, &sp->cmd_dmahandle)) != DDI_SUCCESS) {

        switch (i) {
        case DDI_DMA_BADATTR:
            bioerror(bp, EFAULT);
            return (0);

        case DDI_DMA_NORESOURCES:
            bioerror(bp, 0);
            return (0);
        }
    }

    i = ddi_dma_buf_bind_handle(sp->cmd_dmahandle, bp, dma_flags,
        cb, 0, &sp->cmd_dmacookies[0], &sp->cmd_ncookies);

    switch (i) {
    case DDI_DMA_PARTIAL_MAP:
        if (ddi_dma_numwin(sp->cmd_dmahandle, &sp->cmd_nwin) ==
                    DDI_FAILURE) {
            cmn_err(CE_PANIC, "ddi_dma_numwin() failed\n");
        }

        if (ddi_dma_getwin(sp->cmd_dmahandle, sp->cmd_curwin,
            &sp->cmd_dma_offset, &sp->cmd_dma_len,
            &sp->cmd_dmacookies[0], &sp->cmd_ncookies) ==
                   DDI_FAILURE) {
            cmn_err(CE_PANIC, "ddi_dma_getwin() failed\n");
        }
        goto get_dma_cookies;

    case DDI_DMA_MAPPED:
        sp->cmd_nwin = 1;
        sp->cmd_dma_len = 0;
        sp->cmd_dma_offset = 0;

get_dma_cookies:
        i = 0;
        sp->cmd_dmacount = 0;
        for (;;) {
            sp->cmd_dmacount += sp->cmd_dmacookies[i++].dmac_size;

            if (i == ISP_NDATASEGS || i == sp->cmd_ncookies)
                break;
            ddi_dma_nextcookie(sp->cmd_dmahandle,
                &sp->cmd_dmacookies[i]);
        }
        sp->cmd_cookie = i;
        sp->cmd_cookiecnt = i;

        sp->cmd_flags |= CFLAG_DMAVALID;
        pkt->pkt_resid = bp->b_bcount - sp->cmd_dmacount;
        return (1);

    case DDI_DMA_NORESOURCES:
        bioerror(bp, 0);
        break;

    case DDI_DMA_NOMAPPING:
        bioerror(bp, EFAULT);
        break;

    case DDI_DMA_TOOBIG:
        bioerror(bp, EINVAL);
        break;

    case DDI_DMA_INUSE:
        cmn_err(CE_PANIC, "ddi_dma_buf_bind_handle:"
            " DDI_DMA_INUSE impossible\n");

    default:
        cmn_err(CE_PANIC, "ddi_dma_buf_bind_handle:"
            " 0x%x impossible\n", i);
    }

    ddi_dma_free_handle(&sp->cmd_dmahandle);
    sp->cmd_dmahandle = NULL;
    sp->cmd_flags &= ~CFLAG_DMAVALID;
    return (0);
}

Reallocation of DMA Resources for Next Portion of Data Transfer

For a previously allocated packet with data remaining to be transferred, the tran_init_pkt(9E) entry point must reallocate DMA resources when the following conditions apply:

When reallocating DMA resources to the next portion of the transfer, tran_init_pkt(9E) must return the amount of DMA resources not allocated for this transfer in the field pkt_resid.

If an error occurs while attempting to move DMA resources, tran_init_pkt(9E) must not free the scsi_pkt(9S). The target driver in this case is responsible for freeing the pkt.

If the callback parameter is NULL_FUNC, the tran_init_pkt(9E) entry point must not sleep or call any function that might sleep. If the callback parameter is SLEEP_FUNC and resources are not immediately available, the tran_init_pkt(9E) entry point should sleep until resources are available, unless the request is impossible to satisfy.


Example 15-5 HBA Driver DMA Resource Reallocation

static int
isp_i_dma_move(
    struct isp            *isp,
    struct scsi_pkt       *pkt,
    struct buf            *bp)
{
    struct isp_cmd        *sp  = (struct isp_cmd *)pkt->pkt_ha_private;
    int        i;

    ASSERT(sp->cmd_flags & CFLAG_COMPLETED);
    sp->cmd_flags &= ~CFLAG_COMPLETED;

    /*
     * If there are no more cookies remaining in this window,
     * must move to the next window first.
     */
    if (sp->cmd_cookie == sp->cmd_ncookies) {
        /*
         * For small pkts, leave things where they are
         */
        if (sp->cmd_curwin == sp->cmd_nwin && sp->cmd_nwin == 1)
            return (1);

        /*
         * At last window, cannot move
         */
        if (++sp->cmd_curwin >= sp->cmd_nwin)
            return (0);

        if (ddi_dma_getwin(sp->cmd_dmahandle, sp->cmd_curwin,
            &sp->cmd_dma_offset, &sp->cmd_dma_len,
            &sp->cmd_dmacookies[0], &sp->cmd_ncookies) ==
                DDI_FAILURE)
            return (0);

        sp->cmd_cookie = 0;
    } else {
        /*
         * Still more cookies in this window - get the next one
         */
        ddi_dma_nextcookie(sp->cmd_dmahandle,
            &sp->cmd_dmacookies[0]);
    }

    /*
     * Get remaining cookies in this window, up to our maximum
     */
    i = 0;
    for (;;) {
        sp->cmd_dmacount += sp->cmd_dmacookies[i++].dmac_size;
        sp->cmd_cookie++;
        if (i == ISP_NDATASEGS ||
            sp->cmd_cookie == sp->cmd_ncookies)
            break;
        ddi_dma_nextcookie(sp->cmd_dmahandle,
            &sp->cmd_dmacookies[i]);
    }
    sp->cmd_cookiecnt = i;

    pkt->pkt_resid = bp->b_bcount - sp->cmd_dmacount;
    return (1);
}

tran_destroy_pkt(9E)

The tran_destroy_pkt(9E) entry point is the HBA driver function that deallocates scsi_pkt(9S) structures. The tran_destroy_pkt(9E) entry point is called when the target driver calls scsi_destroy_pkt(9F).

The tran_destroy_pkt(9E) entry point must free any DMA resources allocated for the packet. Freeing the DMA resources causes an implicit DMA synchronization if any cached data remained after the completion of the transfer. The tran_destroy_pkt(9E) entry point frees the SCSI packet itself by calling scsi_hba_pkt_free(9F).


Example 15-6 HBA Driver tran_destroy_pkt(9E) Entry Point

static void
isp_scsi_destroy_pkt(
    struct scsi_address    *ap,
    struct scsi_pkt        *pkt)
{
    struct isp_cmd *sp = (struct isp_cmd *)pkt->pkt_ha_private;

    /*
     * Free the DMA, if any
     */
    if (sp->cmd_flags & CFLAG_DMAVALID) {
        sp->cmd_flags &= ~CFLAG_DMAVALID;
        (void) ddi_dma_unbind_handle(sp->cmd_dmahandle);
        ddi_dma_free_handle(&sp->cmd_dmahandle);
        sp->cmd_dmahandle = NULL;
    }
    /*
     * Free the pkt
     */
    scsi_hba_pkt_free(ap, pkt);
}

tran_sync_pkt(9E)

The tran_sync_pkt(9E) entry point is the HBA driver function that synchronizes the DMA object allocated for the scsi_pkt(9S) structure before or after a DMA transfer. The tran_sync_pkt(9E) entry point is called when the target driver calls scsi_sync_pkt(9F).

If the data transfer direction is a DMA read from device to memory, tran_sync_pkt(9E) must synchronize the CPU's view of the data. If the data transfer direction is a DMA write from memory to device, tran_sync_pkt(9E) must synchronize the device's view of the data.


Example 15-7 HBA Driver tran_sync_pkt(9E) Entry Point

static void
isp_scsi_sync_pkt(
    struct scsi_address    *ap,
    struct scsi_pkt        *pkt)
{
    struct isp_cmd *sp = (struct isp_cmd *)pkt->pkt_ha_private;

    if (sp->cmd_flags & CFLAG_DMAVALID) {
           (void)ddi_dma_sync(sp->cmd_dmahandle, sp->cmd_dma_offset,
            sp->cmd_dma_len,
            (sp->cmd_flags & CFLAG_DMASEND) ?
            DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU);
        }
    }
}

tran_dmafree(9E)

The tran_dmafree(9E) entry point is the HBA driver function that deallocates DMA resources allocated for a scsi_pkt(9S) structure. The tran_dmafree(9E) entry point is called when the target driver calls scsi_dmafree(9F).

tran_dmafree(9E) must free only DMA resources allocated for a scsi_pkt(9S) structure, not the scsi_pkt(9S) itself. Freeing the DMA resources implicitly performs a DMA synchronization.


Note -

The scsi_pkt(9S) will be freed in a separate request to tran_destroy_pkt(9E). Because tran_destroy_pkt(9E) must also free DMA resources, it is important that the HBA driver keep accurate note of whether scsi_pkt(9S) structures have DMA resources allocated.



Example 15-8 HBA Driver tran_dmafree(9E) Entry Point

static void
isp_scsi_dmafree(
    struct scsi_address    *ap,
    struct scsi_pkt        *pkt)
{
    struct isp_cmd         *sp = (struct isp_cmd *)pkt->pkt_ha_private;

    if (sp->cmd_flags & CFLAG_DMAVALID) {
           sp->cmd_flags &= ~CFLAG_DMAVALID;
           (void)ddi_dma_unbind_handle(sp->cmd_dmahandle);
           ddi_dma_free_handle(&sp->cmd_dmahandle);
           sp->cmd_dmahandle = NULL;
    }
}

Command Transport

As part of command transport, the HBA driver accepts a command from the target driver, issues the command to the device hardware, services any interrupts that occur, and manages timeouts.

tran_start(9E)

The tran_start(9E) entry point for a SCSI HBA driver is called to transport a SCSI command to the addressed target. The SCSI command is described entirely within the scsi_pkt(9S) structure, which the target driver allocated through the HBA driver's tran_init_pkt(9E) entry point. If the command involves a data transfer, DMA resources must also have been allocated for the scsi_pkt(9S) structure.

The tran_start(9E) entry point is called when a target driver calls scsi_transport(9F).

tran_start(9E) should perform basic error checking along with whatever initialization the command requires. If the flag FLAG_NOINTR is not set in the pkt_flags field of the scsi_packet(9S) structure, tran_start(9E) must queue the command for execution on the hardware and return immediately. Upon completion of the command, the HBA driver should call the pkt completion routine.

For commands with the FLAG_NOINTR bit set in the pkt_flags field of the scsi_pkt(9S) structure, tran_start(9E) should not return until the command has been completed, and the HBA driver should not call the pkt completion routine.

Example 15-9 demonstrates how to handle the tran_start(9E) entry point. The ISP hardware provides a queue per-target device. For devices that can manage only one active outstanding command, the driver itself is typically required to manage a per-target queue and starts up a new command upon completion of the current command in a round-robin fashion.


Example 15-9 HBA Driver tran_start(9E) Entry Point

static int
isp_scsi_start(
    struct scsi_address        *ap,
    struct scsi_pkt            *pkt)
{
    struct isp_cmd             *sp;
    struct isp                 *isp;
    struct isp_request         *req;
    u_long                     cur_lbolt;
    int                        xfercount;
    int                        rval    = TRAN_ACCEPT;
    int                        i;

    sp = (struct isp_cmd *)pkt->pkt_ha_private;
    isp = (struct isp *)ap->a_hba_tran->tran_hba_private;

    sp->cmd_flags = (sp->cmd_flags & ~CFLAG_TRANFLAG) |
                            CFLAG_IN_TRANSPORT;
    pkt->pkt_reason = CMD_CMPLT;

    /*
     * set up request in cmd_isp_request area so it is ready to
     * go once we have the request mutex
     */
    req = &sp->cmd_isp_request;

    req->req_header.cq_entry_type = CQ_TYPE_REQUEST;
    req->req_header.cq_entry_count = 1;
    req->req_header.cq_flags            = 0;
    req->req_header.cq_seqno = 0;
    req->req_reserved = 0;
    req->req_token = (opaque_t)sp;
    req->req_target = TGT(sp);
    req->req_lun_trn = LUN(sp);
    req->req_time = pkt->pkt_time;
    ISP_SET_PKT_FLAGS(pkt->pkt_flags, req->req_flags);

    /*
     * Set up dma transfers data segments.
     */
    if (sp->cmd_flags & CFLAG_DMAVALID) {

        if (sp->cmd_flags & CFLAG_CMDIOPB) {
            (void) ddi_dma_sync(sp->cmd_dmahandle,
                sp->cmd_dma_offset, sp->cmd_dma_len,
                DDI_DMA_SYNC_FORDEV);
        }

        ASSERT(sp->cmd_cookiecnt > 0 &&
            sp->cmd_cookiecnt <= ISP_NDATASEGS);

        xfercount = 0;
        req->req_seg_count = sp->cmd_cookiecnt;
        for (i = 0; i < sp->cmd_cookiecnt; i++) {
            req->req_dataseg[i].d_count =
                sp->cmd_dmacookies[i].dmac_size;
            req->req_dataseg[i].d_base =
                sp->cmd_dmacookies[i].dmac_address;
            xfercount +=
                sp->cmd_dmacookies[i].dmac_size;
        }

        for (; i < ISP_NDATASEGS; i++) {
            req->req_dataseg[i].d_count = 0;
            req->req_dataseg[i].d_base = 0;
        }

        pkt->pkt_resid = xfercount;

        if (sp->cmd_flags & CFLAG_DMASEND) {
            req->req_flags |= ISP_REQ_FLAG_DATA_WRITE;
        } else {
            req->req_flags |= ISP_REQ_FLAG_DATA_READ;
        }
    } else {
        req->req_seg_count = 0;
        req->req_dataseg[0].d_count = 0;
    }

    /*
     * Set up cdb in the request
     */
    req->req_cdblen = sp->cmd_cdblen;
    bcopy((caddr_t)pkt->pkt_cdbp, (caddr_t)req->req_cdb,
        sp->cmd_cdblen);

    /*
     * Start the cmd.  If NO_INTR, must poll for cmd completion.
     */
    if ((pkt->pkt_flags & FLAG_NOINTR) == 0) {
        mutex_enter(ISP_REQ_MUTEX(isp));
        rval = isp_i_start_cmd(isp, sp);
        mutex_exit(ISP_REQ_MUTEX(isp));
    } else {
        rval = isp_i_polled_cmd_start(isp, sp);
    }

    return (rval);
}

Interrupt Handler and Command Completion

The interrupt handler must check the status of the device to be sure the device is generating the interrupt in question. It must also check for any errors that have occurred and service any interrupts generated by the device.

If data was transferred, the hardware should be checked to determine how much data was actually transferred, and the pkt_resid field in the scsi_pkt(9S) structure should be set to the residual of the transfer.

For commands marked with the PKT_CONSISTENT flag when DMA resources were allocated through tran_init_pkt(9E), the HBA driver must ensure that the data transfer for the command is correctly synchronized before the target driver's command completion callback is performed.

Once a command has completed, there are two requirements:

It is important to start a new command on the hardware, if possible, before calling the PKT_COMP command completion callback. The command completion handling can take considerable time, as the target driver will typically call functions such as biodone(9F) and possibly scsi_transport(9F) to begin a new command.

The interrupt handler must return DDI_INTR_CLAIMED if this interrupt is claimed by this driver; otherwise, the handler returns DDI_INTR_UNCLAIMED.

Example 15-10 shows an interrupt handler for the SCSI HBA isp driver. The caddr_t argument is the parameter set up when the interrupt handler was added in attach(9E) and is typically a pointer to the state structure allocated per instance.


Example 15-10 HBA Driver Interrupt Handler

static u_int
isp_intr(caddr_t arg)
{
    struct isp_cmd             *sp;
    struct isp_cmd             *head,     *tail;
    u_short                    response_in;
    struct isp_response        *resp;
    struct isp                 *isp     = (struct isp *)arg;
    struct isp_slot            *isp_slot;
    int                        n;

    if (ISP_INT_PENDING(isp) == 0) {
        return (DDI_INTR_UNCLAIMED);
    }

    do {
again:
        /*
         * head list collects completed packets for callback later
         */
        head = tail = NULL;

        /*
         * Assume no mailbox events (e.g. mailbox cmds, asynch
         * events, and isp dma errors) as common case.
         */
        if (ISP_CHECK_SEMAPHORE_LOCK(isp) == 0) {
            mutex_enter(ISP_RESP_MUTEX(isp));

            /*
             * Loop through completion response queue and post
             * completed pkts.  Check response queue again
             * afterwards in case there are more
             */
            isp->isp_response_in =
                response_in = ISP_GET_RESPONSE_IN(isp);

            /*
             * Calculate the number of requests in the queue
             */
            n = response_in - isp->isp_response_out;
            if (n < 0) {
                n = ISP_MAX_REQUESTS -
                    isp->isp_response_out + response_in;
            }

            while (n-- > 0) {
                ISP_GET_NEXT_RESPONSE_OUT(isp, resp);
                sp = (struct isp_cmd *)resp->resp_token;

                /*
                 * copy over response packet in sp
                 */
                isp_i_get_response(isp, resp, sp);
                }

                if (head) {
                    tail->cmd_forw = sp;
                    tail = sp;
                    tail->cmd_forw = NULL;
                } else {
                    tail = head = sp;
                    sp->cmd_forw = NULL;
                }
            }

            ISP_SET_RESPONSE_OUT(isp);
            ISP_CLEAR_RISC_INT(isp);
            mutex_exit(ISP_RESP_MUTEX(isp));

            if (head) {
                isp_i_call_pkt_comp(isp, head);
            }
        } else {
            if (isp_i_handle_mbox_cmd(isp) != ISP_AEN_SUCCESS) {
                return (DDI_INTR_CLAIMED);
            }
            /*
             * if there was a reset then check the response
             * queue again
             */
            goto again;    
        }

    } while (ISP_INT_PENDING(isp));

    return (DDI_INTR_CLAIMED);
}

static void
isp_i_call_pkt_comp(
    struct isp             *isp,
    struct isp_cmd         *head)
{
    struct isp             *isp;
    struct isp_cmd         *sp;
    struct scsi_pkt        *pkt;
    struct isp_response    *resp;
    u_char                 status;

    while (head) {
        sp = head;
        pkt = sp->cmd_pkt;
        head = sp->cmd_forw;

        ASSERT(sp->cmd_flags & CFLAG_FINISHED);

        resp = &sp->cmd_isp_response;

        pkt->pkt_scbp[0] = (u_char)resp->resp_scb;
        pkt->pkt_state = ISP_GET_PKT_STATE(resp->resp_state);
        pkt->pkt_statistics = (u_long)
            ISP_GET_PKT_STATS(resp->resp_status_flags);
        pkt->pkt_resid = (long)resp->resp_resid;

        /*
         * if data was xferred and this is a consistent pkt,
         * we need to do a dma sync
         */
        if ((sp->cmd_flags & CFLAG_CMDIOPB) &&
            (pkt->pkt_state & STATE_XFERRED_DATA)) {

            (void) ddi_dma_sync(sp->cmd_dmahandle,
                sp->cmd_dma_offset, sp->cmd_dma_len,
                DDI_DMA_SYNC_FORCPU);
        }

        sp->cmd_flags = (sp->cmd_flags & ~CFLAG_IN_TRANSPORT) |
                CFLAG_COMPLETED;

        /*
         * Call packet completion routine if FLAG_NOINTR is not set.
         */
        if (((pkt->pkt_flags & FLAG_NOINTR) == 0) &&
            pkt->pkt_comp) {
            (*pkt->pkt_comp)(pkt);
        }
    }
}

Timeout Handler

The HBA driver should be prepared to time out the command if it is not complete within a specified time unless a zero timeout was specified in the scsi_pkt(9S) structure.

When a command times out, the HBA driver should mark the scsi_pkt(9S) with pkt_reason set to CMD_TIMEOUT and pkt_statistics OR'd with STAT_TIMEOUT. The HBA driver should also attempt to recover the target and/or bus and, if this recovery can be performed successfully, mark the scsi_pkt(9S) with pkt_statistics OR'd with either STAT_BUS_RESET or STAT_DEV_RESET.

Once the command has timed out and the target and bus recovery attempt has completed, the HBA driver should call the command completion callback.


Note -

If recovery was unsuccessful or not attempted, the target driver might attempt to recover from the timeout by calling scsi_reset(9F).


The ISP hardware manages command timeout directly and returns timed-out commands with the necessary status, so the isp sample driver timeout handler checks active commands for timeout state only once every 60 seconds.

The isp sample driver uses the timeout(9F) facility to arrange for the kernel to call the timeout handler every 60 seconds. The caddr_t argument is the parameter set up when the timeout is initialized at attach(9E) time. In this case, the caddr_t argument is a pointer to the state structure allocated per driver instance.

If the driver discovers timed-out commands that have not been returned as timed-out by the ISP hardware, the hardware is not functioning correctly and needs to be reset.

Capability Management

The following sections discuss capability management.

tran_getcap(9E)

The tran_getcap(9E) entry point for a SCSI HBA driver is called when a target driver calls scsi_ifgetcap(9F) to determine the current value of one of a set of SCSA-defined capabilities.

The target driver can request the current setting of the capability for a particular target by setting the whom parameter to nonzero. A whom value of 0 means the request is for the current setting of the capability for the SCSI bus or for adapter hardware in general.

tran_getcap(9E) should return -1 for undefined capabilities or the current value of the requested capability.

The HBA driver can use the function scsi_hba_lookup_capstr(9F) to compare the capability string against the canonical set of defined capabilities.


Example 15-11 HBA Driver tran_getcap(9E) Entry Point

static int
isp_scsi_getcap(
    struct scsi_address    *ap,
    char                   *cap,
    int                    whom)
{
    struct isp             *isp;
    int                    rval = 0;
    u_char                 tgt = ap->a_target;

    /*
     * We don't allow getting capabilities for other targets
     */
    if (cap == NULL || whom  == 0)    {
        return (-1);
    }

    isp = (struct isp *)ap->a_hba_tran->tran_hba_private;

    ISP_MUTEX_ENTER(isp);

    switch (scsi_hba_lookup_capstr(cap)) {

    case SCSI_CAP_DMA_MAX:
        rval = 1 << 24; /* Limit to 16MB max transfer */
        break;
    case SCSI_CAP_MSG_OUT:
        rval = 1;
        break;
    case SCSI_CAP_DISCONNECT:
        if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_DR) == 0) {
            break;
        } else if (
            (isp->isp_cap[tgt] & ISP_CAP_DISCONNECT) == 0) {
            break;
        }
        rval = 1;
        break;
    case SCSI_CAP_SYNCHRONOUS:
        if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_SYNC) == 0) {
            break;
        } else if (
            (isp->isp_cap[tgt] & ISP_CAP_SYNC) == 0) {
            break;
        }
        rval = 1;
        break;
    case SCSI_CAP_WIDE_XFER:
        if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_WIDE) == 0) {
            break;
        } else if (
            (isp->isp_cap[tgt] & ISP_CAP_WIDE) == 0) {
            break;
        }
        rval = 1;
        break;
    case SCSI_CAP_TAGGED_QING:
        if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_DR) == 0 ||
            (isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_TAG) == 0) {
            break;
        } else if (
            (isp->isp_cap[tgt] & ISP_CAP_TAG) == 0) {
            break;
        }
        rval = 1;
        break;
    case SCSI_CAP_UNTAGGED_QING:
        rval = 1;
        break;
    case SCSI_CAP_PARITY:
        if (isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_PARITY) {
            rval = 1;
        }
        break;
    case SCSI_CAP_INITIATOR_ID:
        rval = isp->isp_initiator_id;
        break;
    case SCSI_CAP_ARQ:
        if (isp->isp_cap[tgt] & ISP_CAP_AUTOSENSE) {
            rval = 1;
        }
        break;
    case SCSI_CAP_LINKED_CMDS:
        break;
    case SCSI_CAP_RESET_NOTIFICATION:
        rval = 1;
        break;
    case SCSI_CAP_GEOMETRY:
        rval = (64 << 16) | 32;
        break;

    default:
        rval = -1;
        break;
    }

    ISP_MUTEX_EXIT(isp);

    return (rval);
} 

tran_setcap(9E)

The tran_setcap(9E) entry point for a SCSI HBA driver is called when a target driver calls scsi_ifsetcap(9F) to change the current one of a set of SCSA-defined capabilities.

The target driver might request that the new value be set for a particular target by setting the whom parameter to nonzero. A whom value of 0 means the request is to set the new value for the SCSI bus or for adapter hardware in general.

tran_setcap(9E) should return -1 for undefined capabilities, 0 if the HBA driver cannot set the capability to the requested value, or 1 if the HBA driver is able to set the capability to the requested value.

The HBA driver can use the function scsi_hba_lookup_capstr(9F) to compare the capability string against the canonical set of defined capabilities.


Example 15-12 HBA Driver tran_setcap(9E) Entry Point

static int
isp_scsi_setcap(
    struct scsi_address    *ap,
    char                   *cap,
    int                    value,
    int                    whom)
{
    struct isp             *isp;
    int                    rval = 0;
    u_char                 tgt = ap->a_target;
    int                    update_isp = 0;

    /*
     * We don't allow setting capabilities for other targets
     */
    if (cap == NULL || whom == 0) {
        return (-1);
    }

    isp = (struct isp *)ap->a_hba_tran->tran_hba_private;

    ISP_MUTEX_ENTER(isp);

    switch (scsi_hba_lookup_capstr(cap)) {

    case SCSI_CAP_DMA_MAX:
    case SCSI_CAP_MSG_OUT:
    case SCSI_CAP_PARITY:
    case SCSI_CAP_UNTAGGED_QING:
    case SCSI_CAP_LINKED_CMDS:
    case SCSI_CAP_RESET_NOTIFICATION:
        /*
         * None of these are settable via
         * the capability interface.
         */
        break;
    case SCSI_CAP_DISCONNECT:
        if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_DR) == 0) {
            break;
        } else {
            if (value) {
                isp->isp_cap[tgt] |= ISP_CAP_DISCONNECT;
            } else {
                isp->isp_cap[tgt] &= ~ISP_CAP_DISCONNECT;
            }
        }
        rval = 1;
        break;
    case SCSI_CAP_SYNCHRONOUS:
        if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_SYNC) == 0) {
            break;
        } else {
            if (value) {
                isp->isp_cap[tgt] |= ISP_CAP_SYNC;
            } else {
                isp->isp_cap[tgt] &= ~ISP_CAP_SYNC;
            }
        }
        rval = 1;
        break;
    case SCSI_CAP_TAGGED_QING:
        if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_DR) == 0 ||
            (isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_TAG) == 0) {
            break;
        } else {
            if (value) {
                isp->isp_cap[tgt] |= ISP_CAP_TAG;
            } else {
                isp->isp_cap[tgt] &= ~ISP_CAP_TAG;
            }
        }
        rval = 1;
        break;
    case SCSI_CAP_WIDE_XFER:
        if ((isp->isp_target_scsi_options[tgt] &
            SCSI_OPTIONS_WIDE) == 0) {
            break;
        } else {
            if (value) {
                isp->isp_cap[tgt] |= ISP_CAP_WIDE;
            } else {
                isp->isp_cap[tgt] &= ~ISP_CAP_WIDE;
            }
        }
        rval = 1;
        break;
    case SCSI_CAP_INITIATOR_ID:
        if (value < N_ISP_TARGETS_WIDE) {
            struct isp_mbox_cmd mbox_cmd;

            isp->isp_initiator_id = (u_short) value;

            /*
             * set Initiator SCSI ID
             */
            isp_i_mbox_cmd_init(isp, &mbox_cmd, 2, 2,
                ISP_MBOX_CMD_SET_SCSI_ID,
                isp->isp_initiator_id,
                0, 0, 0, 0);
            if (isp_i_mbox_cmd_start(isp, &mbox_cmd) == 0) {
                rval = 1;
            }
        }
        break;
    case SCSI_CAP_ARQ:
        if (value) {
            isp->isp_cap[tgt] |= ISP_CAP_AUTOSENSE;
        } else {
            isp->isp_cap[tgt] &= ~ISP_CAP_AUTOSENSE;
        }
        rval = 1;
        break;

    default:
        rval = -1;
        break;
    }
    ISP_MUTEX_EXIT(isp);

    return (rval);
} 

Abort and Reset Management

The following sections discuss the abort and reset entry points for SCSI HBA.

tran_abort(9E)

The tran_abort(9E) entry point for a SCSI HBA driver is called to abort one or all the commands currently in transport for a particular target. This entry point is called when a target driver calls scsi_abort(9F).

The tran_abort(9E) entry point should attempt to abort the command denoted by the pkt parameter. If the pkt parameter is NULL, tran_abort(9E) should attempt to abort all outstanding commands in the transport layer for the particular target or logical unit.

Each command successfully aborted must be marked with pkt_reason CMD_ABORTED and pkt_statistics OR'd with STAT_ABORTED.

tran_reset(9E)

The tran_reset(9E) entry point for a SCSI HBA driver is called to reset either the SCSI bus or a particular SCSI target device. This entry point is called when a target driver calls scsi_reset(9F).

The tran_reset(9E) entry point must reset the SCSI bus if level is RESET_ALL. If level is RESET_TARGET, just the particular target or logical unit must be reset.

Active commands affected by the reset must be marked with pkt_reason CMD_RESET, and with pkt_statistics OR'd with either STAT_BUS_RESET or STAT_DEV_RESET, depending on the type of reset.

Commands in the transport layer, but not yet active on the target, must be marked with pkt_reason CMD_RESET, and pkt_statistics OR'd with STAT_ABORTED.

tran_bus_reset(9E)

tran_bus_reset(9E) must reset the SCSI bus without resetting targets.

#include <sys/scsi/scsi.h>

int tran_bus_reset(dev_info_t *hba_dip, int level);

Where level must be the following:

RESET_BUS

Reset the SCSI bus only, not the targets

The tran_bus_reset() vector in the scsi_hba_tran(9S) structure should be initialized during the HBA driver's attach(9E) to point to an HBA entry point to be called when a user initiates a bus reset.

Implementation is hardware specific. If it is not possible to reset the SCSI bus without affecting the targets, the HBA driver should fail RESET_BUS or not initialize this vector.

tran_reset_notify(9E)

The tran_reset_notify(9E) entry point for a SCSI HBA driver is called to request that the HBA driver notify the target driver by callback when a SCSI bus reset occurs.


Example 15-13 HBA Driver tran_reset_notify(9E) Entry Point

isp_scsi_reset_notify(
    struct scsi_address     *ap,
    int                     flag,
    void                    (*callback)(caddr_t),
    caddr_t                 arg)
{
    struct isp              *isp;
    struct isp_reset_notify_entry    *p, *beforep;
    int                              rval = DDI_FAILURE;

    isp = (struct isp *)ap->a_hba_tran->tran_hba_private;

    mutex_enter(ISP_REQ_MUTEX(isp));

    /*
     * Try to find an existing entry for this target
     */
    p = isp->isp_reset_notify_listf;
    beforep = NULL;

    while (p) {
        if (p->ap == ap)
            break;
        beforep = p;
        p = p->next;
    }

    if ((flag & SCSI_RESET_CANCEL) && (p != NULL)) {
        if (beforep == NULL) {
            isp->isp_reset_notify_listf = p->next;
        } else {
            beforep->next = p->next;
        }
        kmem_free((caddr_t)p, sizeof (struct
                    isp_reset_notify_entry));
        rval = DDI_SUCCESS;

    } else if ((flag & SCSI_RESET_NOTIFY) && (p == NULL)) {
        p = kmem_zalloc(sizeof (struct isp_reset_notify_entry),
                KM_SLEEP);
        p->ap = ap;
        p->callback = callback;
        p->arg = arg;
        p->next = isp->isp_reset_notify_listf;
        isp->isp_reset_notify_listf = p;
        rval = DDI_SUCCESS;
    }

    mutex_exit(ISP_REQ_MUTEX(isp));

    return (rval);
} 

Dynamic Reconfiguration

To support the minimal set of hot-plugging operations, drivers might need to implement support for bus quiesce, bus unquiesce, and reset. The scsi_hba_tran(9S) structure has been extended to support these new operations. If quiesce/unquiesce/reset is not required by hardware, no driver changes are needed.

The following new fields have been added to the scsi_hba_tran structure:

        int (*tran_quiesce)(dev_info_t *hba_dip);
        int (*tran_unquiesce)(dev_info_t *hba_dip);
        int (*tran_bus_reset)(dev_info_t *hba_dip, int level);

The new driver entry points are introduced in the following sections.

tran_quiesce(9E) and tran_unquiesce(9E)

Quiesce and unquiesce a SCSI bus.

        #include <sys/scsi/scsi.h>

        int prefixtran_quiesce(dev_info_t *hba_dip);

        int prefixtran_unquiesce(dev_info_t *hba_dip);

tran_quiesce(9E) and tran_unquiesce(9E) are required to be implemented by an HBA driver to support dynamic reconfiguration (DR) of SCSI devices on buses that were not designed to support hot-plugging.

The tran_quiesce() and tran_unquiesce() vectors in the scsi_hba_tran(9S) structure should be initialized during the HBA driver's attach(9E) to point to HBA entry points so they are called when a user initiates quiesce and unquiesce operations.

tran_quiesce(9E) is called by the SCSA framework to stop all activity on a SCSI bus prior to and during the reconfiguration of devices attached to the SCSI bus. tran_unquiesce(9E) is called by the SCSA framework to resume activity on the SCSI bus after the reconfiguration operation has been completed.

HBA drivers are required to handle tran_quiesce(9E) by waiting for all outstanding commands to complete before returning success. After the HBA has quiesced the bus, it must queue any new I/O requests from target drivers until the SCSA framework calls the corresponding tran_unquiesce(9E) entrypoint.

HBA drivers handle calls to tran_unquiesce(9E) by starting any target driver I/O requests that were queued by the HBA during the time the bus was quiesced.