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
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
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 through scsi_hba_pkt_alloc(9F)
if pkt is NULL.
scsi_hba_pkt_alloc(9F) allocates space for the following items:
scsi_pkt(9S)
SCSI CDB of length cmdlen
Completion area for SCSI status of length statuslen
Per-packet target driver private data area of length tgtlen
Per-packet HBA driver private data area of length hbalen
The scsi_pkt(9S) structure members, including pkt, must be initialized to zero except for the
following members:
pkt_scbp – Status completion
pkt_cdbp – CDB
pkt_ha_private – HBA driver private data
pkt_private – Target driver private data
These members are pointers to memory space where the values of the
fields are stored, as shown in the following figure. For more information, refer to
scsi_pkt Structure (HBA).
Figure 18-5 scsi_pkt(9S) Structure Pointers
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:
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 zero.
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:
Accept a command from the target driver.
Issue the command to the device hardware.
Service any interrupts that occur.
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:
If a new command is queued up, start the command on the hardware as quickly as possible.
Call the command completion callback. The callback has been set up in the scsi_pkt(9S) structure by the target driver to notify the target driver when the command is complete.
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:
-1 for undefined capabilities
0 if the HBA driver cannot set the capability to the requested value
1 if the HBA driver is able to set the capability to the requested value
The HBA driver can use the function scsi_hba_lookup_capstr(9F) to compare the capability
string against the canonical set of defined capabilities.
Example 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.