Writing Device Drivers
  Cerca solo questo libro

SCSI Host Bus Adapter Drivers

12

This chapter contains information on creating SCSI Host Bus Adapter (HBA) drivers and provides sample code illustrating the use of the HBA driver interfaces provided by the Sun Common SCSI Architecture (SCSA).

Note - Understanding SCSI target drivers is an essential prerequisite to writing effective SCSI HBA drivers. Refer to Chapter 11, "SCSI Target Drivers", for more information. Target driver developers will also benefit from reading this chapter.

Overview

As described in Chapter 11, "SCSI Target Drivers", the Solaris 2.x DDI/DKI divides the software interface to SCSI devices into two major parts:
  • target devices and drivers
  • host bus adapter devices and drivers
Target device refers to a device on a SCSI bus, such as a disk or a tape drive. Target driver refers to a software component installed as a device driver. Each target device on a SCSI bus is controlled by one instance of the target driver.
Host bus adapter device refers to HBA hardware, such as an SBus or ISA SCSI adapter card. Host bus adapter driver refers to a software component installed as a device driver, such as the esp driver on a SPARCstation or the aha driver on an x86 machine. An instance of the HBA driver controls each of its host bus adapter devices configured in the system.

Note - The terms "host bus adapter" or "HBA" used in this manual are equivalent to the phrase "host adapter" as defined in the SCSI standards.

The Sun Common SCSI Architecture (SCSA) defines the interface between these target and HBA components.

SCSA Interface

SCSA is the Solaris 2.x SPARC DDI/DKI programming interface for the transmission of SCSI commands from a target driver to a host adapter driver. By conforming to the SCSA, the target driver can pass any combination of SCSI commands and sequences to a target device without knowledge of the hardware implementation of the host adapter. SCSA conceptually separates the building of a SCSI command (by the target driver) from the transporting of the command to and data to and from the SCSI bus (by the HBA driver) for the appropriate target device. SCSA manages the connections between the target and HBA drivers through a HBA transport layer.

Grafica

Figure 12-1

HBA Transport Layer

The HBA transport layer is a software and hardware layer responsible for transporting a SCSI command to a SCSI target device. The HBA driver provides resource allocation, DMA management and transport services in response to requests made by SCSI target drivers through SCSA. The host adapter driver also manages the host adapter hardware and the SCSI protocols necessary to perform the commands. When a command has completed, the HBA driver calls the target driver's SCSI pkt command completion routine.
An illustration of this flow, with emphasis placed on the transfer of information from target drivers to SCSA to HBA drivers, is displayed in Figure 12-2. Typical transport entry points and function calls are included.

Grafica

Figure 12-2

SCSA HBA Interfaces

SCSA HBA interfaces include HBA entry points, HBA data structures and an HBA framework.

SCSA HBA Entry Point Summary

SCSA defines a number of HBA driver entry points, listed in Table 12-1. These entry points are called by the system when configuring a target driver instance connected to the HBA driver, or when the target driver makes a SCSA request. See "SCSA HBA Entry Points" on page 274 for more information.
Table 12-1
Function NameCalled as a Result of:
tran_tgt_init(9E)System attaching target device instance
tran_tgt_probe(9E)Target driver calling scsi_probe(9F)
tran_tgt_free(9E)System detaching target device instance
tran_start(9E)Target driver calling scsi_transport(9F)
tran_reset(9E)Target driver calling scsi_reset(9F)
tran_abort(9E)Target driver calling scsi_abort(9F)
tran_getcap(9E)Target driver calling scsi_ifgetcap(9F)
tran_setcap(9E)Target driver calling scsi_ifsetcap(9F)
tran_init_pkt(9E)Target driver calling scsi_init_pkt(9F)
tran_destroy_pkt(9E)Target driver calling scsi_destroy_pkt(9F)
tran_dmafree(9E)Target driver calling scsi_dmafree(9F)
tran_sync_pkt(9E)Target driver calling scsi_sync_pkt(9F)
tran_reset_notify(9E)Target driver calling scsi_reset_notify(9F)

SCSA HBA Data Structures

SCSA defines data structures to enable the exchange of information between the target and HBA drivers. These data structures include:
  • scsi_hba_tran(9S)
  • scsi_address(9S)
  • scsi_device(9S)
  • scsi_pkt(9S)

scsi_hba_tran

Each instance of an HBA driver must allocate a scsi_hba_tran(9S) structure using scsi_hba_tran_alloc(9F) in the attach(9E) entry point. scsi_hba_tran_alloc(9S) zeroes the scsi_hba_tran(9S) structure before it returns. The HBA driver must initialize specific vectors in the transport structure to point to entry points within the HBA driver. Once initialized, the HBA driver exports the transport structure to SCSA by calling scsi_hba_attach_setup(9F).

Caution - Because SCSA keeps a pointer to the transport structure in the driver private field on the dev_info(9S) structure, HBA drivers must not use ddi_set_driver_private(9F). They may, however, use ddi_get_driver_private(9F) to retrieve the pointer to the transport structure.

The scsi_hba_tran(9S) structure contains the following fields:
dev_info_t *tran_hba_dip;               /* HBA dev_info_t ptr */
void    *tran_hba_private;              /* HBA softstate */
void    *tran_tgt_private;              /* target-specific info */
struct scsi_device*tran_sd;             /* scsi_device, if clone */
int     (*tran_tgt_init)();             /* target initialization */
int     (*tran_tgt_probe)();            /* target probing */
void    (*tran_tgt_free)();             /* target free */
int     (*tran_start)();;               /* command transport */
int     (*tran_reset)();                /* target/bus reset */
int     (*tran_abort)();                /* command abort */
int     (*tran_getcap)();               /* get capability */
int     (*tran_setcap)();               /* set capability */
struct scsi_pkt*(*tran_init_pkt)();/* allocate scsi pkt */
void    (*tran_destroy_pkt)();          /* free scsi pkt */
void    (*tran_dmafree)();              /* free dma resources */
void    (*tran_sync_pkt)();             /* sync data after dma */
void    (*tran_reset_notify)();         /* bus reset notification */


Note - Code fragments presented later in this chapter use these fields to describe practical HBA driver operations. See "SCSA HBA Entry Points" on page 274 for more information.

tran_hba_dip is a pointer to the HBA device instance dev_info structure. This field is set by the function scsi_hba_attach_setup(9F).
tran_hba_private is a pointer to private data maintained by the HBA driver. Usually, tran_hba_private contains a pointer to the state structure of the HBA driver.
tran_tgt_private is a pointer to private data maintained by the HBA driver when using cloning. By specifying SCSI_HBA_TRAN_CLONE when calling scsi_hba_attach_setup(9F), the scsi_hba_tran(9S) structure is cloned once per target, permitting the HBA to initialize this field to point to a per-target instance data structure in the tran_tgt_init(9E) entry point. If SCSI_HBA_TRAN_CLONE is not specified, tran_tgt_private is NULL and must not be referenced. Refer to "Transport Structure Cloning (optional)" on page 260 for more information.
tran_sd is a pointer to a per-target instance scsi_device(9S) structure used when cloning. If SCSI_HBA_TRAN_CLONE is passed to scsi_hba_attach_setup(9F), tran_sd is initialized to point to the per-target scsi_device structure before any HBA functions are called on behalf of that target. If SCSI_HBA_TRAN_CLONE is not specified, tran_sd is NULL and must not be referenced. Refer to "Transport Structure Cloning (optional)" on page 260 for more information.
tran_tgt_init is a pointer to the HBA driver entry point called when initializing a target device instance. If no per-target initialization is required, the HBA may leave tran_tgt_init set to NULL.
tran_tgt_probe is a pointer to the HBA driver entry point called when a target driver instance calls scsi_probe(9F) to probe for the existence of a target device. If no target probing customization is required for this HBA, the HBA should set tran_tgt_probe to scsi_hba_probe(9F).
tran_tgt_free is a pointer to the HBA driver entry point called when a target device instance is destroyed. If no per-target deallocation is necessary, the HBA may leave tran_tgt_free set to NULL.
tran_start is a pointer to the HBA driver entry point called when a target driver calls scsi_transport(9F).
tran_reset is a pointer to the HBA driver entry point called when a target driver calls scsi_reset(9F).
tran_abort is a pointer to the HBA driver entry point called when a target driver calls scsi_abort(9F).
tran_getcap is a pointer to the HBA driver entry point called when a target driver calls scsi_getcap(9F).
tran_setcap is a pointer to the HBA driver entry point called when a target driver calls scsi_setcap(9F).
tran_init_pkt is a pointer to the HBA driver entry point called when a target driver calls scsi_init_pkt(9F).
tran_destroy_pkt is a pointer to the HBA driver entry point called when a target driver calls scsi_destroy_pkt(9F).
tran_dmafree is a pointer to the HBA driver entry point called when a target driver calls scsi_dmafree(9F).
tran_sync_pkt is a pointer to the HBA driver entry point called when a target driver calls scsi_sync_pkt(9F).
tran_reset_notify is a pointer to the HBA driver entry point called when a target driver calls tran_reset_notify9F).

scsi_address

The scsi_address(9S) structure provides transport and addressing information for each SCSI command allocated and transported by a target driver instance.
The scsi_address(9S) structure contains the following fields:
scsi_hba_tran_t *a_hba_tran;       /* HBA transport vectors */
u_short           a_target;        /* Target on SCSI bus */
u_char            a_lun;           /* Lun on that Target */

a_hba_tran is a pointer to the scsi_hba_tran(9S) structure, as allocated and initialized by the HBA driver. If SCSI_HBA_TRAN_CLONE was specified as the flag to scsi_hba_attach_setup(9F), a_hba_tran points to a copy of that structure.
a_target identifies the SCSI target on the SCSI bus.
a_lun identifies the SCSI logical unit on the SCSI target.

scsi_device

The HBA framework allocates and initializes a scsi_device(9S) structure for each instance of a target device before calling an HBA driver's tran_tgt_init(9E) entry point. This structure stores information about each SCSI logical unit, including pointers to information areas that contain both generic and device-specific information. There is one scsi_device(9S) structure for each target device instance attached to the system.
If the per-target initialization is successful (in other words, if either tran_tgt_init(9E) returns success or the vector is NULL), the HBA framework will set the target driver's per-instance private data to point to the scsi_device(9S) structure, using ddi_set_driver_private(9F).
The scsi_device(9S) structure contains the following fields:
struct scsi_address            sd_address;/* routing information */
dev_info_t                     *sd_dev;    /* device dev_info node */
kmutex_t                       sd_mutex; /* mutex used by device */
struct scsi_inquiry            *sd_inq;
struct scsi_extended_sense*sd_sense;
caddr_t                        sd_private;/* for driver's use */

sd_address is a data structure that is passed to the SCSI resource allocation routines.
sd_dev is a pointer to the target's dev_info structure.
sd_mutex is a mutex for use by the target driver. This is initialized by the HBA framework and can be used by the target driver as a per-device mutex. This mutex should not be held across a call to scsi_transport(9F) or scsi_poll(9F). See Chapter 4, "Multithreading," for more information on mutexes.
sd_inq is a pointer for the target device's SCSI Inquiry data. The scsi_probe(9F) routine allocates a buffer, fills it in, and attaches it to this field.
sd_sense is a pointer to a buffer to contain Request Sense data from the device. The target driver must allocate and manage this buffer itself; see the target driver's attach(9E) routine in "attach( )" on page 236 for more information.
sd_private is a pointer field for use by the target driver. It is commonly used to store a pointer to a private target driver state structure.

scsi_pkt

To execute SCSI commands, a target driver must first allocate a scsi_pkt(9S) structure for the command, specifying its own private data area length, the command status and the command length. The HBA driver is responsible for implementing the packet allocation in the tran_init_pkt(9E) entry point. The HBA driver is also responsible for freeing the packet in its tran_destroy_pkt(9E) entry point. See scsi_pkt(9S) in Chapter 11, "SCSI Target Drivers" for more information.
The scsi_pkt(9S) structure contains these fields:
opaque_t               pkt_ha_private;/* HBA private data */
struct scsi_address*pkt_address;         /* destination address */
opaque_t               pkt_private;      /* target driver private */
void                   (*pkt_comp)(); /* pkt completion routine */
long                   pkt_flags;        /* flags */
long                   pkt_time;         /* completion timeout */
u_char                 *pkt_scbp;        /* ptr to status block */
u_char                 *pkt_cdbp;        /* ptr to command block */
long                   pkt_resid;        /* bytes not transferred*/
u_long                 pkt_state;        /* state of command */
u_long                 pkt_statistics;/* statistics */
u_char                 pkt_reason;       /* pkt completion reason */

The following fields must be modified by the HBA driver during transport:
  • pkt_resid
  • pkt_state;
  • pkt_statistics
  • pkt_reason
pkt_ha_private is a pointer to per-command HBA-driver private data.
pkt_address is a pointer to the scsi_address(9S) structure providing address information for this command.
pkt_private is a pointer to per-packet target-driver private data.
pkt_comp is a pointer to the target driver completion routine called by the HBA driver when the transport layer has completed this command.
pkt_flags are the flags for the command.
pkt_time specifies the completion timeout in seconds for the command.
pkt_scbp is a pointer to the status completion block for the command.
pkt_cdbp is a pointer to the command descriptor block (CDB) for the command.
pkt_resid is a count of the data bytes not transferred when the command has been completed or the amount of data for which resources have not been allocated.
pkt_state is the state of the command.
pkt_statistics provides a history of what events the command experienced while in the transport layer.
pkt_reason is the reason for command completion.

Per-Target Instance Data

An HBA driver must allocate a scsi_hba_tran(9S) structure during attach(9E) and initialize the vectors in this transport structure to point to the required HBA driver entry points. This scsi_hba_tran(9S) structure is then passed into scsi_hba_attach_setup(9F).
The scsi_hba_tran(9S) structure contains a tran_hba_private field, which may be used to refer to the HBA driver's per-instance state.
Each scsi_address(9S) structure contains a pointer to the scsi_hba_tran(9S) structure and also provides the target (a_target) and logical unit (a_lun) addresses for the particular target device. Since every HBA driver entry point is passed a pointer to the scsi_address(9S)
structure, either directly or indirectly through the scsi_device(9S) structure, the HBA driver can reference its own state and can identify the target device being addressed.
The HBA data structures for transport operations are illustrated in Figure 12-3 on page 259.

Grafica

Figure 12-3

Transport Structure Cloning (optional)

Cloning may be useful if an HBA driver wants to maintain per-target private data in the scsi_hba_tran(9S) structure, or if it wishes to maintain a more complex address than is provided in the scsi_address(9S) structure.
When cloning, the HBA driver must still allocate a scsi_hba_tran(9S) structure at attach(9E) time, and the tran_hba_private soft state pointer and HBA entry point vectors must be initialized as before. The difference occurs when the framework begins to connect an instance of a target driver to the HBA driver. Before calling the HBA driver's tran_tgt_init(9E) entry point, the framework duplicates (clones) the scsi_hba_tran(9S) structure associated with that instance of the HBA. This means that each scsi_address(9S) structure, allocated and initialized for a particular target device instance, points to a per-target instance copy of the scsi_hba_tran(9S) structure, not to the scsi_hba_tran(9S) structure allocated by the HBA driver at attach(9E) time.
Two important pointers that an HBA driver may use when it has specified cloning are contained in the scsi_hba_tran(9S) structure.The first pointer is the tran_tgt_private field, which may be used to point to per-target HBA private data. This is useful, for example, if an HBA driver needs to maintain a more complex address than the a_target and a_lun fields in the scsi_address(9S) structure allow. The second pointer is the tran_sd field, which is a pointer to the scsi_device(9S) structure referring to the particular target device.
When specifying cloning, the HBA driver must allocate and initialize the per-target data and initialize the tran_tgt_private field to point to this data during its tran_tgt_init(9E) entry point. The HBA driver must free this per-target data during its tran_tgt_free(9E) entry point.
When cloning, the tran_sd field is initialized by the framework to point to the scsi_device(9S) structure before the HBA driver tran_tgt_init(9E) entry point is called.
Cloning is requested by passing the SCSI_HBA_TRAN_CLONE flag to scsi_hba_attach_setup(9F).
HBA data structures for cloning transport operations are illustrated in Figure 12-4 on page 261.

Grafica

Figure 12-4

SCSA HBA Functions

SCSA also provides a number of functions, listed in Table 12-2, intended for use by HBA drivers.
Table 12-2
Function NameCalled by Driver Entry Point
scsi_hba_init(9F)_init(9E)
scsi_hba_fini(9F)_fini(9E)
scsi_hba_attach_setup(9F)attach(9E)
scsi_hba_detach(9F)detach(9E)
scsi_hba_tran_alloc(9F)attach(9E)
scsi_hba_tran_free(9F)detach(9E)
scsi_hba_probe(9F)tran_tgt_probe(9E)
scsi_hba_pkt_alloc(9F)tran_init_pkt(9E)
scsi_hba_pkt_free(9F)tran_destroy_pkt(9E)
scsi_hba_lookup_capstr(9F)tran_getcap(9E) and tran_setcap(9E)

HBA Driver Dependency and Configuration Issues

In addition to incorporating SCSA HBA entry points, structures and functions into a driver, HBA driver developers must also concern themselves with issues surrounding driver dependency and configuration. These issues are summarized in the following list:
  • Configuration properties
  • Dependency declarations
  • State structure and per-command structure
  • Module initialization entry points
  • Autoconfiguration entry points

Configuration Properties

When attaching an instance of an HBA device, scsi_hba_attach_setup(9F) creates a number of SCSI configuration parameter properties for that HBA instance. A particular property is only created if there is no existing property of the same name already attached to the HBA instance, permitting a default property value to be overridden in an HBA configuration file.
An HBA driver must use ddi_prop_get_int(9F) to retrieve each property. The HBA driver then modifies (or accepts the default value of) the properties to configure its specific operation.

scsi-reset-delay

The scsi-reset-delay property is an integer specifying the SCSI bus or device reset delay recovery time in milliseconds.

scsi-options

The scsi-options property is an integer specifying a number of options through individually defined bits. The bits in scsi_options are:
  • SCSI_OPTIONS_DR (0x008)

    If not set, the HBA should not grant disconnect privileges to a target device.

  • SCSI_OPTIONS_LINK (0x010)

    If not set, the HBA should not enable linked commands.

  • SCSI_OPTIONS_SYNC (0x020)

    If not set, the HBA should not negotiate synchronous data transfer, and should reject any attempt to negotiate synchronous data transfer initiated by a target.

  • SCSI_OPTIONS_PARITY (0x040)

    If not set, the HBA should run the SCSI bus without parity.

  • SCSI_OPTIONS_TAG (0x080)

    If not set, the HBA should not operate in Command Tagged Queuing mode.

  • SCSI_OPTIONS_FAST (0x100)
If not set, the HBA should not operate the bus in FAST SCSI mode.
  • SCSI_OPTIONS_WIDE (0x200)

    If not set, the HBA should not operate the bus in WIDE SCSI mode.

Per-target scsi-options

An HBA driver may support a per-target scsi-options feature in the following format:
target<n>-scsi-options=<hex value>

In this example,< n> is the target ID. If the per-target scsi-options property is defined for a particular target, the HBA driver uses the value of the per-target scsi-options property for that target rather than the per-HBA driver instance scsi-options property. This can provide more fine-grained control if, for example, synchronous data transfer needs to be disabled for just one particular target device. The per-target scsi-options property may be defined in the driver.conf(4) file.
Here is an example of a per-target scsi-options property definition to disable synchronous data transfer for target device 3:
target3-scsi-options=0x2d8

Declarations and Structures

HBA drivers must include the following header files, along with a declaration of dependency upon the scsi module:
#include <sys/scsi/scsi.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>

char _depends_on[] = "misc/scsi";

This declaration informs the system that the module depends on SCSA routines (see "SCSA HBA Interfaces" on page 252 for more information). This construct is used only for SCSI drivers and should not be used elsewhere.
Code fragments are presented below that illustrate the structure of a typical HBA driver. The code samples are derived from a simplified isp driver for the QLogic Intelligent SCSI Peripheral device. The complete isp source code is available as a sample driver on the DDK.
The isp driver supports WIDE SCSI, with up to 15 target devices and 8 logical units (LUNs) per target.

State Structure

This chapter adds the following fields to the state structure. See "State Structure" on page 67 of the WDD for more information.
scsi_hba_tran_t       *isp_tran;
dev_info_t            *isp_dip;
ddi_iblock_cookie_tisp_iblock;
int                   isp_target_scsi_options[N_ISP_TARGETS_WIDE];
int                   isp_scsi_tag_age_limit;
u_int                 isp_scsi_reset_delay;
u_short               isp_cap[N_ISP_TARGETS_WIDE];
u_short               isp_synch[N_ISP_TARGETS_WIDE];

struct ispregs        *isp_reg;
ddi_acc_handle_t      isp_acc_handle;

Per-Command Structure

An HBA driver will usually need to define a structure to maintain state for each command submitted by a target driver. The layout of this per-command structure is entirely up to the device driver writer and needs to reflect the capabilities and features of the hardware and the software algorithms used in the driver.
The following structure is an example of a per-command structure. It is used in the remaining code fragments of this chapter to illustrate the HBA interfaces:
struct isp_cmd {
    struct isp_request        cmd_isp_request;
    struct isp_response       cmd_isp_response;

    struct scsi_pkt           *cmd_pkt;
    struct isp_cmd            *cmd_forw;

    u_long                  cmd_dmacount;
    ddi_dma_handle_t        cmd_dmahandle;
    uint_t                  cmd_cookie;
    uint_t                  cmd_ncookies;

    uint_t                  cmd_cookiecnt;
    uint_t                  cmd_nwin;
    uint_t                  cmd_curwin;
    off_t                   cmd_dma_offset;
    uint_t                  cmd_dma_len;
    ddi_dma_cookie_t        cmd_dmacookies[ISP_NDATASEGS];

    u_long                  cmd_flags;

    u_short                 cmd_slot;
    u_int                   cmd_cdblen;
    u_int                   cmd_scblen;
};

Module Initialization Entry Points

Drivers for different types of devices have different sets of entry points, depending on the operations they perform. Some operations, however, are common to all drivers, such as the as _init(9E), _info(9E) and _fini(9E) entry points for module initialization. A complete description of these loadable module routines is given in Chapter 3, "Overview of SunOS Device Drivers". In this section, only those entry points associated with operations performed by SCSI HBA drivers are described.
The following code for a SCSI HBA driver illustrates a representative dev_ops(9S) structure. The devo_bus_ops field in this structure must be initialized to NULL. A SCSI HBA driver may provide leaf driver interfaces for special purposes, in which case the devo_cb_ops field may point to a cb_ops(9S) structure. In this example, no leaf driver interfaces are exported, so the devo_cb_ops field is initialized to NULL.
static struct dev_ops isp_dev_ops = {
    DEVO_REV,                 /* devo_rev */
    0,                        /* refcnt  */
    isp_getinfo,              /* getinfo */
    isp_identify,             /* identify */
    nulldev,                  /* probe */
    isp_attach,               /* attach */
    isp_detach,               /* detach */
    nodev,                    /* reset */
    NULL,                     /* driver operations */
    NULL,                     /* bus operations */
};

_init()

The _init(9E) function initializes a loadable module and is called before any other routine in the loadable module.
In a SCSI HBA, the _init() function must call scsi_hba_init(9F) to inform the framework of the existence of the HBA driver before calling mod_install(9F). If scsi_hba_init(9F) returns a non-zero value, _init(9E) should return this value. Otherwise, _init(9E) must return the value returned by mod_install(9F).
Any global state required by the driver should be initialized before calling mod_install(9F).
Should mod_install(9F) fail, the _init(9E) function must free any global resources allocated and must call scsi_hba_fini(9F) before returning.
The following code sample uses a global mutex to show how to allocate data that is global to all instances of a driver. The code declares global mutex and soft-state structure information. The global mutex and soft state are initialized during _init(9E).
    /*
     * Local static data
    */
    static kmutex_t       isp_global_mutex;
    static void           *isp_state;

The _init(9E) function in the following code example shows how a SCSI HBA driver initializes a global mutex.
/*
 * Loadable module initialization entry point
 */
int
_init(void)
{
        int     err;

         if ((err = ddi_soft_state_init(&isp_state,
                          sizeof (struct isp), 0)) != 0) {
                 return (err);
         }
        if ((err = scsi_hba_init(&modlinkage)) == 0) {
             mutex_init(&isp_global_mutex, "isp global mutex",
                 MUTEX_DRIVER, NULL);

             if ((err = mod_install(&modlinkage)) != 0) {
                   mutex_destroy(&isp_global_mutex);
                   scsi_hba_fini(&modlinkage);
                  ddi_soft_state_fini(&isp_state);
             }
        }
        return (err);
}

_fini()

The _fini(9E) function is called when the system is about to try to unload the SCSI HBA driver. The _fini(9E) function must call mod_remove(9F) to determine if the driver can be unloaded. If mod_remove(9F) returns 0, the module can be unloaded, and the HBA driver must deallocate any global resources allocated in _init(9E) and must call scsi_hba_fini(9F).
_fini(9E) must return the value returned by mod_remove(9F).

Note - The HBA driver must not free any resources or call scsi_hba_fini(9F) unless mod_remove(9F) returns 0.

The _fini(9E) function in the following code example shows how a SCSI HBA driver deallocates a global mutex initialized in _init(9E)
/* Module unloading entry point */

    int
    _fini(void)
    {
            int     err;

            if ((err = mod_remove(&modlinkage)) == 0) {
                      mutex_destroy(&isp_global_mutex);
                      scsi_hba_fini(&modlinkage);
                      ddi_soft_state_fini(&isp_state);
            }
            return (err);
    }

Autoconfiguration Entry Points

Associated with each device driver is a dev_ops(9S) structure, which allows the kernel to locate the autoconfiguration entry points of the driver. A complete description of these autoconfiguration routines is given in Chapter 5, "Autoconfiguration". In this section, only those entry points associated with operations performed by SCSI HBA drivers are described. These include attach(9E), detach(9E), getinfo(9E) and identify(9E).

attach()

The attach(9E) entry point for a SCSI HBA driver must perform a number of tasks to configure and attach an instance of the driver for the device. For a typical driver of real devices, the following operating system and hardware concerns must be addressed:
  • Soft state structure
  • DMA
  • Transport structure
  • Attaching an HBA driver
  • Register mapping
  • Interrupt specification
  • Interrupt handling
  • Report attachment status
Soft State Structure The driver should allocate the per-device-instance soft state structure, being careful to clean up properly if an error occurs.
instance = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(isp_state,
        instance) != DDI_SUCCESS) {
    return (DDI_FAILURE);
}
isp = ddi_get_soft_state(isp_state, instance);

DMA If the driver provides DMA, for example, it must specify DMA attributes describing the capabilities and limitations of its DMA engine.

Note - In Solaris 2.5, the HBA driver must provide DMA.

    static ddi_dma_attr_t isp_dma_attr = {
        DMA_ATTR_V0,          /* ddi_dma_attr version */
        0,                    /* low address */
        0xffffffff,           /* high address */
        0x00ffffff,           /* counter upper bound */
        1,                    /* alignment requirements */
        1,                    /* burst sizes */
        1,                    /* minimum DMA access */
        0xffffffff,           /* maximum DMA access */
        (1<<24)-1,            /* segment boundary restrictions */
        1,                    /* scatter/gather list length */
        512,                  /* segment granularity */
        0                     /* flags - must be 0 */
    };

The driver, if providing DMA, should also check that its hardware is installed in a DMA-capable slot:
if (ddi_slaveonly(dip) == DDI_SUCCESS) {
    return (DDI_FAILURE);
}

Transport Structure The driver should further allocate and initialize a transport structure for this instance. The tran_hba_private field is set to point to this instance's soft-state structure. tran_tgt_probe may be set to NULL to achieve the default behavior, if no special probe customization is needed.
tran = scsi_hba_tran_alloc(dip, SCSI_HBA_CANSLEEP);

isp->isp_tran                  = tran;
isp->isp_dip                   = dip;

tran->tran_hba_private         = isp;
tran->tran_tgt_private         = NULL;
tran->tran_tgt_init            = isp_tran_tgt_init;
tran->tran_tgt_probe           = scsi_hba_probe;
tran->tran_tgt_free            = (void (*)())NULL;

tran->tran_start               = isp_scsi_start;
tran->tran_abort               = isp_scsi_abort;
tran->tran_reset               = isp_scsi_reset;

    tran->tran_getcap              = isp_scsi_getcap;
    tran->tran_setcap              = isp_scsi_setcap;
    tran->tran_init_pkt            = isp_scsi_init_pkt;
    tran->tran_destroy_pkt         = isp_scsi_destroy_pkt;
    tran->tran_dmafree             = isp_scsi_dmafree;
    tran->tran_sync_pkt            = isp_scsi_sync_pkt;
    tran->tran_reset_notify        = isp_scsi_reset_notify;

Attaching an HBA Driver The driver should attach this instance of the device and perform error cleanup if necessary.
        i = scsi_hba_attach_setup(dip, &isp_dma_attr, tran, 0);
        if (i != DDI_SUCCESS) {
             do error recovery
             return (DDI_FAILURE);
        }

Register Mapping The driver should map in its device's registers, specifying the index of the register set, the data access characteristics of the device and the size of the register set to be mapped.
ddi_device_acc_attr_tdev_attributes;

dev_attributes.devacc_attr_version = DDI_DEVICE_ATTR_V0;
dev_attributes.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
dev_attributes.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;

if (ddi_regs_map_setup(dip, 0, (caddr_t *)&isp->isp_reg,
    0, sizeof (struct ispregs), &dev_attributes,
    &isp->isp_acc_handle) != DDI_SUCCESS) {
    do error recovery
    return (DDI_FAILURE);
}

Adding an Interrupt Handler The driver should determine if a high-level interrupt handler is required. If a high-level handler is required and the driver is not coded to provide one, the driver must be rewritten to either include a high-level interrupt or fail the attach.
In the following example, a high-level interrupt is required but not provided by the driver. Consequently, the driver fails the attach.
        if (ddi_intr_hilevel(dip, 0) != 0) {
             return (DDI_FAILURE);
        }

The driver must first obtain the iblock cookie to initialize mutexes used in the driver handler. Only after those mutexes have been initialized can the interrupt handler be added.
        i = ddi_get_iblock_cookie(dip, 0, &isp->iblock_cookie};
        if (i != DDI_SUCCESS) {
             do error recovery
             return (DDI_FAILURE);
        }

        mutex_init(&isp->mutex, "isp_mutex", MUTEX_DRIVER,
             (void *)isp->iblock_cookie);
        i = ddi_add_intr(dip, 0, &isp->iblock_cookie,
             0, isp_intr, (caddr_t)isp);
        if (i != DDI_SUCCESS) {
             do error recovery
             return (DDI_FAILURE);
        }

Report Attachment Status Finally, the driver should report that this instance of the device is attached and return success.
ddi_report_dev(dip);
return (DDI_SUCCESS);

detach()

The Solaris 2.5 DDI/DKI does not support detaching an HBA driver, although target driver children of an HBA can detach. For the present, it's recommended that the HBA driver fail a detach request. It's better to fail the detach than to include code here that cannot be tested.
The following code provides an example of the xx_detach(9E) function.
static int
isp_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{

    switch (cmd) {
    case DDI_DETACH:
        /*
         * At the present, detaching HBA drivers is not supported
         */
        return (DDI_FAILURE);

    default:
        return (DDI_FAILURE);
    }
}

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:
  • Target driver instance initialization
  • Resource allocation
  • Command transport
  • Capability management
  • Abort and reset
Table 12-3
Function GroupsEntry Points within GroupDescription
Target Driver Instance Initializationtran_tgt_init(9E)Perform per-target initialization (optional)
tran_tgt_probe(9E)Probe SCSI bus for existence of a target (optional)
tran_tgt_free(9E)Perform per-target deallocation (optional)
Resource Allocationtran_init_pkt(9E)Allocate SCSI packet and DMA resources
tran_destroy_pkt(9E)Free SCSI packet and DMA resources
tran_sync_pkt(9E)Synchronize memory before/after DMA
tran_dmafree(9E)Free DMA resources
Command Transporttran_start(9E)Transport a SCSI command
Capability Managementtran_getcap(9E)Inquire about a capability's value
tran_setcap(9E)Set a capability's value
Abort and Resettran_abort(9E)Abort one or all outstanding SCSI commands
tran_reset(9E)Reset a target device or the SCSI bus
tran_reset_notify(9E)Request to notify target of bus reset (optional)

Target Driver Instance Initialization

tran_tgt_init()

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

The tran_tgt_probe(9E) entry point allows 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, since it may be called multiple times from the same scsi device.

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 desired.

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

The tran_tgt_free(9E) entry point allows 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

tran_init_pkt()

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
  • Allocation of DMA resources for data transfer
  • Reallocation of DMA resources for the next portion of the data transfer

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:
  • a scsi_pkt(9S)
  • a SCSI CDB of length cmdlen
  • a SCSI status completion area of length statuslen
  • a per-packet target driver private data area of length tgtlen
  • a per-packet HBA driver private data area of length hbalen
The scsi_pkt(9S) structure members, as well as the 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. This is illustrated in Figure 12-5. For more information refer to "scsi_pkt" on page 257.

scsi_address

CDB
pkt_cdbp

pkt_scbp

STATUS

pkt_private pkt_ha_private

TGT DRIVER PER PKT DATA

HBA DRIVER
scsi_pkt structure
PER PKT DATA
Figure 12-5 scsi_pkt(9S) structure pointers
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, 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 may 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 may 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 may 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 before returning. The scsi_pkt must be freed by calling scsi_hba_pkt_free(9F).
The target driver may 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.
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:
  • partial DMA resources have already been allocated
  • a non-zero pkt_resid was returned in the previous call to tran_init_pkt(9E)
  • bp is not NULL
  • bp->b_bcount is not 0
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. 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 which may 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.
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()

The tran_destroy_pkt(9E) entry point is the HBA driver function which 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 sync 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).
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()

The tran_sync_pkt(9E) entry point is the HBA driver function which 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.
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()

The tran_dmafree(9E) entry point is the HBA driver function which 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 structure, not the scsi_pkt itself. Freeing the DMA resources implicitly performs a DMA sync.

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

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

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 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 sanity checks along with whatever initialization the command requires, queue the command for execution on the HBA hardware, and return without blocking. If the hardware is idle, the command may be started immediately.
For commands with the FLAG_NOINTR bit set in the pkt_flags field of the scsi_packet(9S) structure, tran_start(9E) should not return until the command has completed, and the HBA driver should not call the pkt completion routine.
The following sample code demonstrates how to handle the tran_start(9E) entry point. The ISP hardware provides a queue per target device. For devices which can only manage 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.
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 may 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 via 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:
  • start a new command (if one is queued up) on the hardware as quickly as possible
  • call the command completion callback as set up in the scsi_pkt(9S) structure by the target driver to notify the target driver that the command is now complete
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 may 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.
The following sample code 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.
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 does 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 with pkt_statistics OR'd with either STAT_BUS_RESET or STAT_DEV_RESET.
Once the command has timed out and the target/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 may 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

tran_getcap()

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 may 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 may use the function scsi_hba_lookup_capstr(9F) to compare the capability string against the canonical set of defined capabilities.
static int
isp_scsi_setcap(
    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()

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 may 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 may use the function scsi_hba_lookup_capstr(9F) to compare the capability string against the canonical set of defined capabilities.
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

tran_abort()

The tran_abort(9E) entry point for a SCSI HBA driver is called to abort one or all of the commands currently in transport for a particular target.
The tran_abort(9E) entry point is called when a target driver calls scsi_abort(9E).
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/ logical unit.
Each command successfully aborted must be marked with pkt_reason CMD_ABORTED and pkt_statistics OR'd with STAT_ABORTED.

tran_reset()

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.
The tran_reset(9E) 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/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 with pkt_statistics OR'd with either STAT_ABORTED.

tran_reset_notify()

The tran_reset_notify(9E) entry point for a SCSI HBA driver is called to request that the HBA driver notify the target driver via callback when a SCSI bus reset occurs.
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);
}

Driver Installation

Hardware Configuration File

SCSI HBA drivers have configuration requirements similar to those for standard device drivers. See Chapter 2, "Hardware Overview" for more information.

Installing the Driver

Before an HBA driver can be used, it must first be properly installed on the system. The add_drv(1M) utility must be used to correctly install the HBA driver.
For example, to install the isp sample driver, first copy the driver to the /kernel/drv directory as displayed below:
    $ su
    Password:
    # cp isp /kernel/drv
    # cp isp.conf /kernel/drv

Next, run add_drv(1M) to install the driver. For SCSI HBA drivers, specify class as scsi to permit SCSI target drivers to use the HBA driver to communicate with a target device.
# add_drv -m" * 0666 root root" -i'"pci1077,1020"' -c scsi isp

Once the HBA driver is installed, a reconfiguration boot is necessary in order to create and attach driver instances for target devices attached on the SCSI bus controlled by the HBA device.
Refer to "Installing and Removing Drivers" on page 309 of the WDD for more information about driver installation.

x86 Target Driver Configuration Properties

Some SunSoft x86 SCSI target drivers (such as the cmdk disk target driver) use the following configuration properties:
· disk
· queue
· flow_control

When using the cmdk sample driver to write an HBA driver for an x86 platform, one or more of these properties (as appropriate to the HBA driver and hardware) may need to be defined in the driver.conf(4) file.

Note - These property definitions should only appear in an HBA driver's driver.conf(4) file. The HBA driver itself should not inspect or attempt to interpret these properties in any way. These properties are advisory only and serve as an adjunct to the cmdk driver. They should not be relied upon in any way. The property definitions may or may not be used in future releases.

The disk property may be used to define the type of disk supported by cmdk. For a SCSI HBA, the only possible value for the disk property is:
  • disk="scdk"

    Disk type is a SCSI disk.

The queue property defines how the disk driver sorts the queue of incoming requests during strategy(9E). There are two possible values:
  • queue="qsort"

    One-way elevator queueing model, provided by disksort(9F)

  • queue="qfifo"

    FIFO (first in, first out) queuing model

The flow_control property defines how commands are transported to the HBA driver. There are three possible values:
  • flow_control="dsngl"

    Single command per HBA driver

  • flow_control="dmult"
Multiple commands per HBA driver--when the HBA queue is full, the driver returns TRAN_BUSY
  • flow_control="duplx"

    The HBA can support separate read and write queues, with multiple commands per queue. FIFO ordering is used for the write queue; the queueing model used for the read queue is described by the queue property. When an HBA queue is full, the driver returns TRAN_BUSY.

Here is an example of a driver.conf(4) file for use with an x86 HBA PCI device designed for use with the cmdk sample driver:
#
# config file for ISP 1020 SCSI HBA driver
#
     flow_control="dsngl" queue="qsort" disk="scdk"
    scsi-initiator-id=7;