JavaScript is required to for searching.
Skip Navigation Links
Exit Print View
Writing Device Drivers     Oracle Solaris 11.1 Information Library
search filter icon
search icon

Document Information

Preface

Part I Designing Device Drivers for the Oracle Solaris Platform

1.  Overview of Oracle Solaris Device Drivers

2.  Oracle Solaris Kernel and Device Tree

3.  Multithreading

4.  Properties

5.  Managing Events and Queueing Tasks

6.  Driver Autoconfiguration

7.  Device Access: Programmed I/O

8.  Interrupt Handlers

9.  Direct Memory Access (DMA)

10.  Mapping Device and Kernel Memory

11.  Device Context Management

12.  Power Management

13.  Hardening Oracle Solaris Drivers

14.  Layered Driver Interface (LDI)

Part II Designing Specific Kinds of Device Drivers

15.  Drivers for Character Devices

16.  Drivers for Block Devices

17.  SCSI Target Drivers

18.  SCSI Host Bus Adapter Drivers

Introduction to Host Bus Adapter Drivers

SCSI Interface

SCSA HBA Interfaces

SCSA HBA Entry Point Summary

SCSA HBA Data Structures

scsi_hba_tran() Structure

scsi_address Structure

scsi_device Structure

scsi_pkt Structure (HBA)

Per-Target Instance Data

Transport Structure Cloning

SCSA HBA Functions

HBA Driver Dependency and Configuration Issues

Declarations and Structures

Per-Command Structure

Entry Points for Module Initialization

_init() Entry Point (SCSI HBA Drivers)

_fini() Entry Point (SCSI HBA Drivers)

Autoconfiguration Entry Points

attach() Entry Point (SCSI HBA Drivers)

detach() Entry Point (SCSI HBA Drivers)

Entry Points for SCSA HBA Drivers

Target Driver Instance Initialization

tran_tgt_init() Entry Point

tran_tgt_probe() Entry Point

tran_tgt_free() Entry Point

Resource Allocation

tran_init_pkt() Entry Point

Allocation and Initialization of a scsi_pkt(9S) Structure

Allocation of DMA Resources

Reallocation of DMA Resources for Data Transfer

tran_destroy_pkt() Entry Point

tran_sync_pkt() Entry Point

tran_dmafree() Entry Point

Command Transport

tran_start() Entry Point

Interrupt Handler and Command Completion

Timeout Handler

Capability Management

tran_getcap() Entry Point

tran_setcap() Entry Point

Abort and Reset Management

tran_abort() Entry Point

tran_reset() Entry Point

tran_bus_reset() Entry Point

tran_reset_notify() Entry Point

Dynamic Reconfiguration

SCSI HBA Driver Specific Issues

Installing HBA Drivers

HBA Configuration Properties

scsi-reset-delay Property

scsi-options Property

Per-Target scsi-options

x86 Target Driver Configuration Properties

Support for Queuing

19.  Drivers for Network Devices

20.  USB Drivers

21.  SR-IOV Drivers

Part III Building a Device Driver

22.  Compiling, Loading, Packaging, and Testing Drivers

23.  Debugging, Testing, and Tuning Device Drivers

24.  Recommended Coding Practices

Part IV Appendixes

A.  Hardware Overview

B.  Summary of Oracle Solaris DDI/DKI Services

C.  Making a Device Driver 64-Bit Ready

D.  Console Frame Buffer Drivers

E.  pci.conf File

Index

Entry Points for SCSA HBA Drivers

An HBA driver can work with target drivers through the SCSA interface. The SCSA interfaces require the HBA driver to supply a number of entry points that are callable through the scsi_hba_tran(9S) structure.

These entry points fall into five functional groups:

The following table lists the entry points for SCSA HBA by function groups.

Table 18-3 SCSA Entry Points

Function Groups
Entry Points Within Group
Description
Target Driver Instance Initialization
Performs per-target initialization (optional)
Probes SCSI bus for existence of a target (optional)
Performs per-target deallocation (optional)
Resource Allocation
Allocates SCSI packet and DMA resources
Frees SCSI packet and DMA resources
Synchronizes memory before and after DMA
Frees DMA resources
Command Transport
Transports a SCSI command
Capability Management
Inquires about a capability's value
Sets a capability's value
Abort and Reset
Aborts outstanding SCSI commands
Resets a target device or the SCSI bus
Resets the SCSI bus
Request to notify target of bus reset (optional)
Dynamic Reconfiguration
Stops activity on the bus
Resumes activity on the bus

Target Driver Instance Initialization

The following sections describe target entry points.

tran_tgt_init() Entry Point

The tran_tgt_init(9E) entry point enables the HBA to allocate and initialize any per-target resources. tran_tgt_init() also enables 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 is not probed or attached.

tran_tgt_init() is not required. If tran_tgt_init() is not supplied, the framework attempts 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() Entry Point

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().

The HBA driver can retain the normal operation of scsi_probe() 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().

scsi_probe() 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. scsi_hba_probe() handles this task automatically. scsi_unprobe(9F) then frees the scsi_inquiry data.

Except for the allocation of scsi_inquiry data, tran_tgt_probe() must be stateless, because the same SCSI device might call tran_tgt_probe() several times. Normally, allocation of scsi_inquiry data is handled by scsi_hba_probe().


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


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() Entry Point

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() Entry Point

The tran_init_pkt(9E) entry point allocates and initializes 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 through scsi_hba_pkt_alloc(9F) if pkt is NULL.

scsi_hba_pkt_alloc(9F) allocates space for the following items:

The scsi_pkt(9S) structure members, including pkt, must be initialized to zero except for the following members:

These members are pointers to memory space where the values of the fields are stored, as shown in the following figure. For more information, refer to scsi_pkt Structure (HBA).

Figure 18-5 scsi_pkt(9S) Structure Pointers

image:Diagram shows the scsi_pkt structure with those members that point to values rather than being initialized to zero.

The following example shows allocation and initialization of a scsi_pkt structure.

Example 18-2 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

The tran_init_pkt(9E) entry point must allocate DMA resources for a data transfer if the following conditions are true:

The HBA driver needs to track how DMA resources are allocated for a particular command. This allocation can take place with a flag bit or a DMA handle in the per-packet HBA driver private data.

The PKT_DMA_PARTIAL flag in the pkt enables the target driver to break up a data transfer into multiple SCSI commands to accommodate the complete request. This approach is useful when the HBA hardware scatter-gather capabilities or system DMA resources cannot complete a request in a single SCSI command.

The PKT_DMA_PARTIAL flag enables the HBA driver to set the DDI_DMA_PARTIAL flag. The DDI_DMA_PARTIAL flag is useful when the DMA resources for this SCSI command are allocated. For example the ddi_dma_buf_bind_handle(9F)) command can be used to allocate DMA resources. 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) returns 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, that driver 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, the driver must not free pkt. The target driver in this case is responsible for freeing the pkt.

Example 18-3 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 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 packet.

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. Unless the request is impossible to satisfy, tran_init_pkt() should sleep until resources become available.

Example 18-4 DMA Resource Reallocation for HBA Drivers

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() Entry Point

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

The tran_destroy_pkt() entry point must free any DMA resources that have been allocated for the packet. An implicit DMA synchronization occurs if the DMA resources are freed and any cached data remains after the completion of the transfer. The tran_destroy_pkt() entry point frees the SCSI packet by calling scsi_hba_pkt_free(9F).

Example 18-5 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() Entry Point

The tran_sync_pkt(9E) entry point synchronizes the DMA object allocated for the scsi_pkt(9S) structure before or after a DMA transfer. The tran_sync_pkt() 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() 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() must synchronize the device's view of the data.

Example 18-6 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() Entry Point

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

tran_dmafree() must free only DMA resources allocated for a scsi_pkt(9S) structure, not the scsi_pkt(9S) itself. When DMA resources are freed, a DMA synchronization is implicitly performed.


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


Example 18-7 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

An HBA driver goes through the following steps as part of command transport:

  1. Accept a command from the target driver.

  2. Issue the command to the device hardware.

  3. Service any interrupts that occur.

  4. Manage time outs.

tran_start() Entry Point

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() entry point is called when a target driver calls scsi_transport(9F).

tran_start() should perform basic error checking along with any initialization that is required by the command. The FLAG_NOINTR flag in the pkt_flags field of the scsi_pkt(9S) structure can affect the behavior of tran_start(). If FLAG_NOINTR is not set, tran_start() 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.

If the FLAG_NOINTR is set, then the HBA driver should not call the pkt completion routine.

The following example 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 is typically required to manage a per-target queue. The driver then starts up a new command upon completion of the current command in a round-robin fashion.

Example 18-8 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 data segments for dma transfers.
     */
    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. The interrupt handler must also check for any errors that have occurred and service any interrupts generated by the device.

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

Commands that are marked with the PKT_CONSISTENT flag when DMA resources are allocated through tran_init_pkt(9E) take special handling. 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, you need to act on two requirements:

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. Typically, the target driver calls 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.

The following example shows an interrupt handler for the SCSI HBA isp driver. The caddr_t parameter is set up when the interrupt handler is added in attach(9E). This parameter is typically a pointer to the state structure, which is allocated on a per instance basis.

Example 18-9 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,
         * 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 is responsible for enforcing time outs. A command must be complete within a specified time unless a zero time out has been 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 bus. If this recovery can be performed successfully, the driver should mark the scsi_pkt(9S) using pkt_statistics OR'd with either STAT_BUS_RESET or STAT_DEV_RESET.

After the 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. The timeout handler for the isp sample driver checks active commands for the time out 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 timed-out commands have not been returned as timed-out by the ISP hardware, a problem has occurred. The hardware is not functioning correctly and needs to be reset.

Capability Management

The following sections discuss capability management.

tran_getcap() Entry Point

The tran_getcap(9E) entry point for a SCSI HBA driver is called by scsi_ifgetcap(9F). The target driver calls scsi_ifgetcap() 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 zero indicates a request for the current setting of the general capability for the SCSI bus or for adapter hardware.

The tran_getcap() entry point 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 18-10 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() Entry Point

The tran_setcap(9E) entry point for a SCSI HBA driver is called by scsi_ifsetcap(9F). A target driver calls scsi_ifsetcap() 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 zero means the request is to set the new value for the SCSI bus or for adapter hardware in general.

tran_setcap() should return the following values as appropriate:

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 18-11 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 through
           * 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() Entry Point

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

The tran_abort() entry point should attempt to abort the command denoted by the pkt parameter. If the pkt parameter is NULL, tran_abort() 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() Entry Point

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() 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. The type of reset determines whether STAT_BUS_RESET or STAT_DEV_RESET should be used to OR pkt_statistics.

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() Entry Point

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:

*hba-dip

Pointer associated with the SCSI HBA

level

Must be set to RESET_BUS so that only the SCSI bus is reset, 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). The vector should point to an HBA entry point that is to be called when a user initiates a bus reset.

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

tran_reset_notify() Entry Point

Use the tran_reset_notify(9E) entry point when a SCSI bus reset occurs. This function requests the SCSI HBA driver to notify the target driver by callback.

Example 18-12 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 bus reset. The scsi_hba_tran(9S) structure supports these operations. If quiesce, unquiesce, or reset are not required by hardware, no driver changes are needed.

The scsi_hba_tran structure includes the following fields:

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

These interfaces 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 used for SCSI devices that are not designed for hot-plugging. These functions must be implemented by an HBA driver to support dynamic reconfiguration (DR).

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

The tran_quiesce() entry point stops all activity on a SCSI bus prior to and during the reconfiguration of devices that are attached to the SCSI bus. The tran_unquiesce() entry point 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() by waiting for all outstanding commands to complete before returning success. After the driver has quiesced the bus, any new I/O requests must be queued until the SCSA framework calls the corresponding tran_unquiesce() entry point.

HBA drivers handle calls to tran_unquiesce() by starting any target driver I/O requests in the queue.