SCSI Target Drivers
The illumos DDI/DKI divides the software interface to SCSI devices into two major parts: target drivers and host bus adapter (HBA) drivers. Target refers to a driver for a device on a SCSI bus, such as a disk or a tape drive. Host bus adapter refers to the driver for the SCSI controller on the host machine. SCSA defines the interface between these two components. This chapter discusses target drivers only. See SCSI Host Bus Adapter Drivers for information on host bus adapter drivers.
The terms “host bus adapter” and “HBA” are equivalent to “host adapter,” which is defined in SCSI specifications.
This chapter provides information on the following subjects:
17.1. Introduction to Target Drivers
Target drivers can be either character or block device drivers, depending on the device. Drivers for tape drives are usually character device drivers, while disks are handled by block device drivers. This chapter describes how to write a SCSI target driver. The chapter discusses the additional requirements that SCSA places on block and character drivers for SCSI target devices.
The following reference documents provide supplemental information needed by the designers of target drivers and host bus adapter drivers.
Small Computer System Interface 2 (SCSI-2), ANSI/NCITS X3.131-1994, Global Engineering Documents, 1998. ISBN 1199002488.
The Basics of SCSI, Fourth Edition, ANCOT Corporation, 1998. ISBN 0963743988.
Refer also to the SCSI command specification for the target device, provided by the hardware vendor.
17.2. Sun Common SCSI Architecture Overview
The Sun Common SCSI Architecture (SCSA) is the illumos DDI/DKI programming interface for the transmission of SCSI commands from a target driver to a host bus adapter driver. This interface is independent of the type of host bus adapter hardware, the platform, the processor architecture, and the SCSI command being transported across the interface.
Conforming to the SCSA enables the target driver to pass SCSI commands to target devices without knowledge of the hardware implementation of the host bus adapter.
The SCSA conceptually separates building the SCSI command from transporting the command with data across the SCSI bus. The architecture defines the software interface between high-level and low-level software components. The higher level software component consists of one or more SCSI target drivers, which translate I/O requests into SCSI commands appropriate for the peripheral device. The following example illustrates the SCSI architecture.

The lower-level software component consists of a SCSA interface layer and one or more host bus adapter drivers. The target driver is responsible for the generation of the proper SCSI commands required to execute the desired function and for processing the results.
17.2.1. General Flow of Control
Assuming no transport errors occur, the following steps describe the general flow of control for a read or write request.
-
The target driver's read(9E) or write(9E) entry point is invoked. physio(9F) is used to lock down memory, prepare a
buf
structure, and call the strategy routine. -
The target driver's strategy(9E) routine checks the request.
strategy
then allocates a scsi_pkt(9S) by using scsi_init_pkt(9F). The target driver initializes the packet and sets the SCSI command descriptor block (CDB) using the scsi_setup_cdb(9F) function. The target driver also specifies a timeout. Then, the driver provides a pointer to a callback function. The callback function is called by the host bus adapter driver on completion of the command. The buf(9S) pointer should be saved in the SCSI packet's target-private space. -
The target driver submits the packet to the host bus adapter driver by using scsi_transport(9F). The target driver is then free to accept other requests. The target driver should not access the packet while the packet is in transport. If either the host bus adapter driver or the target supports queueing, new requests can be submitted while the packet is in transport.
-
As soon as the SCSI bus is free and the target not busy, the host bus adapter driver selects the target and passes the CDB. The target driver executes the command. The target then performs the requested data transfers.
-
After the target sends completion status and the command completes, the host bus adapter driver notifies the target driver. To perform the notification, the host calls the completion function that was specified in the SCSI packet. At this time the host bus adapter driver is no longer responsible for the packet, and the target driver has regained ownership of the packet.
-
The SCSI packet's completion routine analyzes the returned information. The completion routine then determines whether the SCSI operation was successful. If a failure has occurred, the target driver retries the command by calling scsi_transport(9F) again. If the host bus adapter driver does not support auto request sense, the target driver must submit a request sense packet to retrieve the sense data in the event of a check condition.
-
After successful completion or if the command cannot be retried, the target driver calls scsi_destroy_pkt(9F).
scsi_destroy_pkt
synchronizes the data.scsi_destroy_pkt
then frees the packet. If the target driver needs to access the data before freeing the packet, scsi_sync_pkt(9F) is called. -
Finally, the target driver notifies the requesting application that the read or write transaction is complete. This notification is made by returning from the read(9E) entry point in the driver for character devices. Otherwise, notification is made indirectly through biodone(9F).
SCSA allows the execution of many of such operations, both overlapped and queued, at various points in the process. The model places the management of system resources on the host bus adapter driver. The software interface enables the execution of target driver functions on host bus adapter drivers by using SCSI bus adapters of varying degrees of sophistication.
17.2.2. SCSA Functions
SCSA defines functions to manage the allocation and freeing of resources, the sensing and setting of control states, and the transport of SCSI commands. These functions are listed in the following table.
Function Name |
Category |
---|---|
Error handling |
|
Transport information and control |
|
Resource management |
|
Polled I/O |
|
Probe functions |
|
CDB initialization function |
|
Command transport |
|
If your driver needs to work with a SCSI-1 device, use the makecom(9F).
17.3. Hardware Configuration File
Because SCSI devices are not self-identifying, a hardware configuration file is required for a target driver. See the driver.conf(4) and scsi_free_consistent_buf(9F) man pages for details. The following is a typical configuration file:
name="xx" class="scsi" target=2 lun=0;
The system reads the file during autoconfiguration. The system uses the class property to identify the driver's possible parent. Then, the system attempts to attach the driver to any parent driver that is of class scsi. All host bus adapter drivers are of this class. Using the class property rather than the parent property is preferred. This approach enables any host bus adapter driver that finds the expected device at the specified target and lun IDs to attach to the target. The target driver is responsible for verifying the class in its probe(9E) routine.
17.4. Declarations and Data Structures
Target drivers must include the header file <sys/scsi/scsi.h>.
SCSI target drivers must use the following command to generate a binary module:
ld -r xx xx.o -N"misc/scsi"
17.4.1. scsi_device Structure
The host bus adapter driver allocates and initializes a scsi_device(9S) structure for the target driver before either the probe(9E) or attach(9E) routine is called. This structure stores information about each SCSI logical unit, including pointers to information areas that contain both generic and device-specific information. One scsi_device(9S) structure exists for each logical unit that is attached to the system. The target driver can retrieve a pointer to this structure by calling ddi_get_driver_private(9F).
Because the host bus adapter driver uses the private
field in the target device's dev_info
structure, target
drivers must not use ddi_set_driver_private(9F).
The scsi_device(9S) structure contains the following fields:
struct scsi_device {
struct scsi_address sd_address; /* opaque address */
dev_info_t *sd_dev; /* device node */
kmutex_t sd_mutex;
void *sd_reserved;
struct scsi_inquiry *sd_inq;
struct scsi_extended_sense *sd_sense;
caddr_t sd_private;
};
where:
sd_address
-
Data structure that is passed to the routines for SCSI resource allocation.
sd_dev
-
Pointer to the target's
dev_info
structure. sd_mutex
-
Mutex for use by the target driver. This mutex is initialized by the host bus adapter driver and can be used by the target driver as a per-device mutex. Do not hold this mutex across a call to scsi_transport(9F) or scsi_poll(9F). See Multithreading for more information on mutexes.
sd_inq
-
Pointer for the target device's SCSI inquiry data. The scsi_probe(9F) routine allocates a buffer, fills the buffer in with inquiry data, and attaches the buffer to this field.
sd_sense
-
Pointer to a buffer to contain SCSI request sense data from the device. The target driver must allocate and manage this buffer. See attach Entry Point (SCSI Target Drivers).
sd_private
-
Pointer field for use by the target driver. This field is commonly used to store a pointer to a private target driver state structure.
17.4.2. scsi_pkt Structure (Target Drivers)
The scsi_pkt
structure contains
the following fields:
struct scsi_pkt {
opaque_t pkt_ha_private; /* private data for host adapter */
struct scsi_address pkt_address; /* destination packet is for */
opaque_t pkt_private; /* private data for target driver */
void (*pkt_comp)(struct scsi_pkt *); /* completion routine */
uint_t pkt_flags; /* flags */
int pkt_time; /* time allotted to complete command */
uchar_t *pkt_scbp; /* pointer to status block */
uchar_t *pkt_cdbp; /* pointer to command block */
ssize_t pkt_resid; /* data bytes not transferred */
uint_t pkt_state; /* state of command */
uint_t pkt_statistics; /* statistics */
uchar_t pkt_reason; /* reason completion called */
};
where:
pkt_address
-
Target device's address set by scsi_init_pkt(9F).
pkt_private
-
Place to store private data for the target driver. pkt_private is commonly used to save the buf(9S) pointer for the command.
pkt_comp
-
Address of the completion routine. The host bus adapter driver calls this routine when the driver has transported the command. Transporting the command does not mean that the command succeeded. The target might have been busy. Another possibility is that the target might not have responded before the time out period elapsed. See the description for
pkt_time
field. The target driver must supply a valid value in this field. This value can beNULL
if the driver does not want to be notified.
Two different SCSI callback routines are provided. The pkt_comp
field identifies a completion callback routine,
which is called when the host bus adapter completes its processing. Aresource callback routine is also available, which is called when currently
unavailable resources are likely to be available. See the scsi_init_pkt(9F) man page.
pkt_flags
-
Provides additional control information, for example, to transport the command without disconnect privileges (
FLAG_NODISCON
) or to disable callbacks (FLAG_NOINTR
). See the scsi_pkt(9S) man page for details. pkt_time
-
Time out value in seconds. If the command is not completed within this time, the host bus adapter calls the completion routine with
pkt_reason
set toCMD_TIMEOUT
. The target driver should set this field to longer than the maximum time the command might take. If the timeout is zero, no timeout is requested. Timeout starts when the command is transmitted on the SCSI bus. pkt_scbp
-
Pointer to the block for SCSI status completion. This field is filled in by the host bus adapter driver.
pkt_cdbp
-
Pointer to the SCSI command descriptor block, the actual command to be sent to the target device. The host bus adapter driver does not interpret this field. The target driver must fill the field in with a command that the target device can process.
pkt_resid
-
Residual of the operation. The
pkt_resid
field has two different uses depending on howpkt_resid
is used. Whenpkt_resid
is used to allocate DMA resources for a command scsi_init_pkt(9F),pkt_resid
indicates the number of unallocable bytes. DMA resources might not be allocated due to DMA hardware scatter-gather or other device limitations. After command transport,pkt_resid
indicates the number of non-transferable data bytes. The field is filled in by the host bus adapter driver before the completion routine is called. pkt_state
-
Indicates the state of the command. The host bus adapter driver fills in this field as the command progresses. One bit is set in this field for each of the five following command states:
-
STATE_GOT_BUS
– Acquired the bus -
STATE_GOT_TARGET
– Selected the target -
STATE_SENT_CMD
– Sent the command -
STATE_XFERRED_DATA
– Transferred data, if appropriate -
STATE_GOT_STATUS
– Received status from the device
-
pkt_statistics
-
Contains transport-related statistics set by the host bus adapter driver.
pkt_reason
-
Gives the reason the completion routine was called. The completion routine decodes this field. The routine then takes the appropriate action. If the command completes, that is, no transport errors occur, this field is set to
CMD_CMPLT
. Other values in this field indicate an error. After a command is completed, the target driver should examine thepkt_scbp
field for a check condition status. See the scsi_pkt(9S) man page for more information.
17.5. Autoconfiguration for SCSI Target Drivers
SCSI target drivers must implement the standard autoconfiguration routines _init(9E), _fini(9E), and _info(9E). See for more information.
The following routines are also required, but these routines must perform specific SCSI and SCSA processing:
17.5.1. probe Entry Point (SCSI Target Drivers)
SCSI target devices are not self-identifying, so target drivers must have a probe(9E) routine. This routine must determine whether the expected type of device is present and responding.
The general structure and the return codes of the probe(9E) routine are the same as the
structure and return codes for other device drivers. SCSI target drivers must
use the scsi_probe(9F) routine in their probe(9E) entry point. scsi_probe(9F) sends a SCSI
inquiry command to the device and returns a code that indicates the result.
If the SCSI inquiry command is successful, scsi_probe(9F) allocates a scsi_inquiry(9S) structure
and fills the structure in with the device's inquiry data. Upon return from
scsi_probe(9F), the sd_inq
field of the scsi_device(9S) structure
points to this scsi_inquiry(9S) structure.
Because probe(9E) must be stateless, the target driver must call scsi_unprobe(9F) before probe(9E) returns, even if scsi_probe(9F) fails.
shows a typical probe(9E) routine.
The routine in the example retrieves the scsi_device(9S) structure from the private
field of its dev_info
structure. The routine also retrieves
the device's SCSI target and logical unit numbers for printing in messages.
The probe(9E) routine
then calls scsi_probe(9F) to verify that the expected device, a printer in this
case, is present.
If successful, scsi_probe(9F) attaches the device's SCSI inquiry data in a scsi_inquiry(9S) structure
to the sd_inq
field of the scsi_device(9S) structure. The driver can
then determine whether the device type is a printer, which is reported in
the inq_dtype
field. If the device is a printer,
the type is reported with scsi_log(9F),
using scsi_dname(9F) to convert the device type into a string.
static int
xxprobe(dev_info_t *dip)
{
struct scsi_device *sdp;
int rval, target, lun;
/*
* Get a pointer to the scsi_device(9S) structure
*/
sdp = (struct scsi_device *)ddi_get_driver_private(dip);
target = sdp->sd_address.a_target;
lun = sdp->sd_address.a_lun;
/*
* Call scsi_probe(9F) to send the Inquiry command. It will
* fill in the sd_inq field of the scsi_device structure.
*/
switch (scsi_probe(sdp, NULL_FUNC)) {
case SCSIPROBE_FAILURE:
case SCSIPROBE_NORESP:
case SCSIPROBE_NOMEM:
/*
* In these cases, device might be powered off,
* in which case we might be able to successfully
* probe it at some future time - referred to
* as `deferred attach'.
*/
rval = DDI_PROBE_PARTIAL;
break;
case SCSIPROBE_NONCCS:
default:
/*
* Device isn't of the type we can deal with,
* and/or it will never be usable.
*/
rval = DDI_PROBE_FAILURE;
break;
case SCSIPROBE_EXISTS:
/*
* There is a device at the target/lun address. Check
* inq_dtype to make sure that it is the right device
* type. See scsi_inquiry(9S)for possible device types.
*/
switch (sdp->sd_inq->inq_dtype) {
case DTYPE_PRINTER:
scsi_log(sdp, "xx", SCSI_DEBUG,
"found %s device at target%d, lun%d\n",
scsi_dname((int)sdp->sd_inq->inq_dtype),
target, lun);
rval = DDI_PROBE_SUCCESS;
break;
case DTYPE_NOTPRESENT:
default:
rval = DDI_PROBE_FAILURE;
break;
}
}
scsi_unprobe(sdp);
return (rval);
}
A more thorough probe(9E) routine could check scsi_inquiry(9S) to make sure that the device is of the type expected by a particular driver.
17.5.2. attach Entry Point (SCSI Target Drivers)
After the probe(9E) routine
has verified that the expected device is present, attach(9E) is called. attach
performs
these tasks:
-
Allocates and initializes any per-instance data.
-
Creates minor device node information.
-
Restores the hardware state of a device after a suspension of the device or the system. See attach Entry Point for details.
A SCSI target driver needs to call scsi_probe(9F) again to retrieve the device's
inquiry data. The driver must also create a SCSI request sense packet. If
the attach is successful, the attach
function should
not call scsi_unprobe(9F).
Three routines are used to create the request sense packet: scsi_alloc_consistent_buf(9F), scsi_init_pkt(9F), and scsi_setup_cdb(9F). scsi_alloc_consistent_buf(9F) allocates
a buffer that is suitable for consistent DMA. scsi_alloc_consistent_buf
then returns a pointer to a buf(9S) structure. The advantage of a consistent buffer is
that no explicit synchronization of the data is required. In other words,
the target driver can access the data after the callback. The sd_sense
element of the device's scsi_device(9S) structure must be initialized
with the address of the sense buffer. scsi_init_pkt(9F) creates and partially initializes
a scsi_pkt(9S) structure. scsi_setup_cdb(9F) creates a
SCSI command descriptor block, in this case by creating a SCSI request sense command.
Note that a SCSI device is not self-identifying and
does not have a reg
property. As a result, the driver must
set the pm-hardware-state
property. Setting pm-hardware-state
informs the framework that this device needs to be suspended and
then resumed.
The following example shows the SCSI target driver's attach
routine.
static int
xxattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
struct xxstate *xsp;
struct scsi_pkt *rqpkt = NULL;
struct scsi_device *sdp;
struct buf *bp = NULL;
int instance;
instance = ddi_get_instance(dip);
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
/* For information, see the "Directory Memory Access (DMA)" */
/* chapter in this book. */
default:
return (DDI_FAILURE);
}
/*
* Allocate a state structure and initialize it.
*/
xsp = ddi_get_soft_state(statep, instance);
sdp = (struct scsi_device *)ddi_get_driver_private(dip);
/*
* Cross-link the state and scsi_device(9S) structures.
*/
sdp->sd_private = (caddr_t)xsp;
xsp->sdp = sdp;
/*
* Call scsi_probe(9F) again to get and validate inquiry data.
* Allocate a request sense buffer. The buf(9S) structure
* is set to NULL to tell the routine to allocate a new one.
* The callback function is set to NULL_FUNC to tell the
* routine to return failure immediately if no
* resources are available.
*/
bp = scsi_alloc_consistent_buf(&sdp->sd_address, NULL,
SENSE_LENGTH, B_READ, NULL_FUNC, NULL);
if (bp == NULL)
goto failed;
/*
* Create a Request Sense scsi_pkt(9S) structure.
*/
rqpkt = scsi_init_pkt(&sdp->sd_address, NULL, bp,
CDB_GROUP0, 1, 0, PKT_CONSISTENT, NULL_FUNC, NULL);
if (rqpkt == NULL)
goto failed;
/*
* scsi_alloc_consistent_buf(9F) returned a buf(9S) structure.
* The actual buffer address is in b_un.b_addr.
*/
sdp->sd_sense = (struct scsi_extended_sense *)bp->b_un.b_addr;
/*
* Create a Group0 CDB for the Request Sense command
*/
if (scsi_setup_cdb((union scsi_cdb *)rqpkt->pkt_cdbp,
SCMD_REQUEST_SENSE, 0, SENSE__LENGTH, 0) == 0)
goto failed;;
/*
* Fill in the rest of the scsi_pkt structure.
* xxcallback() is the private command completion routine.
*/
rqpkt->pkt_comp = xxcallback;
rqpkt->pkt_time = 30; /* 30 second command timeout */
rqpkt->pkt_flags |= FLAG_SENSING;
xsp->rqs = rqpkt;
xsp->rqsbuf = bp;
/*
* Create minor nodes, report device, and do any other initialization. */
* Since the device does not have the 'reg' property,
* cpr will not call its DDI_SUSPEND/DDI_RESUME entries.
* The following code is to tell cpr that this device
* needs to be suspended and resumed.
*/
(void) ddi_prop_update_string(device, dip,
"pm-hardware-state", "needs-suspend-resume");
xsp->open = 0;
return (DDI_SUCCESS);
failed:
if (bp)
scsi_free_consistent_buf(bp);
if (rqpkt)
scsi_destroy_pkt(rqpkt);
sdp->sd_private = (caddr_t)NULL;
sdp->sd_sense = NULL;
scsi_unprobe(sdp);
/* Free any other resources, such as the state structure. */
return (DDI_FAILURE);
}
17.5.3. detach Entry Point (SCSI Target Drivers)
The detach(9E) entry
point is the inverse of attach(9E). detach
must free all resources that were allocated in attach
. If successful, the detach should call scsi_unprobe(9F). The following example shows
a target driver detach
routine.
static int
xxdetach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
struct xxstate *xsp;
switch (cmd) {
case DDI_DETACH:
/*
* Normal detach(9E) operations, such as getting a
* pointer to the state structure
*/
scsi_free_consistent_buf(xsp->rqsbuf);
scsi_destroy_pkt(xsp->rqs);
xsp->sdp->sd_private = (caddr_t)NULL;
xsp->sdp->sd_sense = NULL;
scsi_unprobe(xsp->sdp);
/*
* Remove minor nodes.
* Free resources, such as the state structure and properties.
*/
return (DDI_SUCCESS);
case DDI_SUSPEND:
/* For information, see the "Directory Memory Access (DMA)" */
/* chapter in this book. */
default:
return (DDI_FAILURE);
}
}
17.5.4. getinfo Entry Point (SCSI Target Drivers)
The getinfo(9E) routine
for SCSI target drivers is much the same as for other drivers (see getinfo Entry Point for more information
on DDI_INFO_DEVT2INSTANCE
case). However, in the DDI_INFO_DEVT2DEVINFO
case of the getinfo
routine, the target driver
must return a pointer to its dev_info
node. This pointer
can be saved in the driver state structure or can be retrieved from the sd_dev
field of the scsi_device(9S) structure. The following
example shows an alternative SCSI target driver getinfo
code
fragment.
case DDI_INFO_DEVT2DEVINFO:
dev = (dev_t)arg;
instance = getminor(dev);
xsp = ddi_get_soft_state(statep, instance);
if (xsp == NULL)
return (DDI_FAILURE);
*result = (void *)xsp->sdp->sd_dev;
return (DDI_SUCCESS);
17.6. Resource Allocation
To send a SCSI command to the device, the target driver must create and initialize a scsi_pkt(9S) structure. This structure must then be passed to the host bus adapter driver.
17.6.1. scsi_init_pkt Function
The scsi_init_pkt(9F) routine allocates
and zeroes a scsi_pkt(9S) structure. scsi_init_pkt
also sets pointers to pkt_private
, *pkt_scbp
, and *pkt_cdbp
. Additionally, scsi_init_pkt
provides a callback mechanism to handle the case where resources
are not available. This function has the following syntax:
struct scsi_pkt *scsi_init_pkt(struct scsi_address *ap,
struct scsi_pkt *pktp, struct buf *bp, int cmdlen,
int statuslen, int privatelen, int flags,
int (*callback)(caddr_t), caddr_t arg)
where:
- ap
-
Pointer to a
scsi_address
structure.ap
is thesd_address
field of the device's scsi_device(9S) structure. - pktp
-
Pointer to the scsi_pkt(9S) structure to be initialized. If this pointer is set to
NULL
, a new packet is allocated. - bp
-
Pointer to a buf(9S) structure. If this pointer is not null and has a valid byte count, DMA resources are allocated.
- cmdlen
-
Length of the SCSI command descriptor block in bytes.
- statuslen
-
Required length of the SCSI status completion block in bytes.
- privatelen
-
Number of bytes to allocate for the
pkt_private
field. - flags
-
Set of flags:
-
PKT_CONSISTENT
– This bit must be set if the DMA buffer was allocated using scsi_alloc_consistent_buf(9F). In this case, the host bus adapter driver guarantees that the data transfer is properly synchronized before performing the target driver's command completion callback. -
PKT_DMA_PARTIAL
– This bit can be set if the driver accepts a partial DMA mapping. If set, scsi_init_pkt(9F) allocates DMA resources with theDDI_DMA_PARTIAL
flag set. Thepkt_resid
field of the scsi_pkt(9S) structure can be returned with a nonzero residual. A nonzero value indicates the number of bytes for which scsi_init_pkt(9F) was unable to allocate DMA resources.
-
- callback
-
Specifies the action to take if resources are not available. If set to
NULL_FUNC
, scsi_init_pkt(9F) returns the valueNULL
immediately. If set toSLEEP_FUNC
,scsi_init_pkt
does not return until resources are available. Any other valid kernel address is interpreted as the address of a function to be called when resources are likely to be available. - arg
-
Parameter to pass to the callback function.
The scsi_init_pkt
routine synchronizes the data
prior to transport. If the driver needs to access the data after transport,
the driver should call scsi_sync_pkt(9F) to flush any intermediate caches. The scsi_sync_pkt
routine can be used to synchronize any cached data.
17.6.2. scsi_sync_pkt Function
If
the target driver needs to resubmit the packet after changing the data, scsi_sync_pkt(9F) must be called
before calling scsi_transport(9F). However, if the target driver does not need to access
the data, scsi_sync_pkt
does not need to be called after
the transport.
17.6.3. scsi_destroy_pkt Function
The scsi_destroy_pkt(9F) routine synchronizes any remaining cached data that is associated with the packet, if necessary. The routine then frees the packet and associated command, status, and target driver-private data areas. This routine should be called in the command completion routine.
17.6.4. scsi_alloc_consistent_buf Function
For most I/O requests, the data buffer passed to the driver entry points is not accessed directly by the driver. The buffer is just passed on to scsi_init_pkt(9F). If a driver sends SCSI commands that operate on buffers that the driver itself examines, the buffers should be DMA consistent. The SCSI request sense command is a good example. The scsi_alloc_consistent_buf(9F) routine allocates a buf(9S) structure and a data buffer that is suitable for DMA-consistent operations. The HBA performs any necessary synchronization of the buffer before performing the command completion callback.
scsi_alloc_consistent_buf(9F) uses scarce system resources. Thus, you should use scsi_alloc_consistent_buf
sparingly.
17.6.5. scsi_free_consistent_buf Function
scsi_free_consistent_buf(9F) releases a buf(9S) structure and the associated data buffer allocated with scsi_alloc_consistent_buf(9F). See attach Entry Point (SCSI Target Drivers) and detach Entry Point (SCSI Target Drivers) for examples.
17.7. Building and Transporting a Command
The host bus adapter driver is responsible for transmitting the command to the device. Furthermore, the driver is responsible for handling the low-level SCSI protocol. The scsi_transport(9F) routine hands a packet to the host bus adapter driver for transmission. The target driver has the responsibility to create a valid scsi_pkt(9S) structure.
17.7.1. Building a Command
The routine scsi_init_pkt(9F) allocates
space for a SCSI CDB, allocates DMA resources if necessary, and sets the pkt_flags
field, as shown in this example:
pkt = scsi_init_pkt(&sdp->sd_address, NULL, bp,
CDB_GROUP0, 1, 0, 0, SLEEP_FUNC, NULL);
This example creates a new packet along with allocating DMA resources
as specified in the passed buf(9S) structure
pointer. A SCSI CDB is allocated for a Group 0 (6-byte) command. The pkt_flags
field is set to zero, but no space is allocated for the pkt_private
field. This call to scsi_init_pkt(9F), because of the SLEEP_FUNC
parameter, waits indefinitely for resources if no resources are
currently available.
The next step is to initialize the SCSI CDB, using the scsi_setup_cdb(9F) function:
if (scsi_setup_cdb((union scsi_cdb *)pkt->pkt_cdbp,
SCMD_READ, bp->b_blkno, bp->b_bcount >> DEV_BSHIFT, 0) == 0)
goto failed;
This example builds a Group 0 command descriptor block. The example fills in the pkt_cdbp
field as follows:
-
The command itself is in byte 0. The command is set from the parameter
SCMD_READ
. -
The address field is in bits 0-4 of byte 1 and bytes 2 and 3. The address is set from
bp->b_blkno
. -
The count field is in byte 4. The count is set from the last parameter. In this case,
count
is set tobp->b_bcount
>>
DEV_BSHIFT
, whereDEV_BSHIFT
is the byte count of the transfer converted to the number of blocks.
scsi_setup_cdb(9F) does not support setting a target device's logical unit number (LUN) in bits 5-7 of byte 1 of the SCSI command block. This requirement is defined by SCSI-1. For SCSI-1 devices that require the LUN bits set in the command block, use makecom_g0(9F) or some equivalent rather than scsi_setup_cdb(9F).
After initializing the SCSI CDB, initialize three other fields in the packet and store as a pointer to the packet in the state structure.
pkt->pkt_private = (opaque_t)bp;
pkt->pkt_comp = xxcallback;
pkt->pkt_time = 30;
xsp->pkt = pkt;
The buf(9S) pointer
is saved in the pkt_private
field for later use in the
completion routine.
17.7.2. Setting Target Capabilities
The target drivers use scsi_ifsetcap(9F) to set the capabilities of the host adapter driver. A cap is a name-value pair, consisting of a null-terminated character string and an integer value. The current value of a capability can be retrieved using scsi_ifgetcap(9F). scsi_ifsetcap(9F) allows capabilities to be set for all targets on the bus.
In general, however, setting capabilities of targets that are not owned by the target driver is not recommended. This practice is not universally supported by HBA drivers. Some capabilities, such as disconnect and synchronous, can be set by default by the HBA driver. Other capabilities might need to be set explicitly by the target driver. Wide-xfer and tagged-queueing must be set by the target drive, for example.
17.7.3. Transporting a Command
After the scsi_pkt(9S) structure is filled in, use scsi_transport(9F) to hand the structure to the bus adapter driver:
if (scsi_transport(pkt) != TRAN_ACCEPT) {
bp->b_resid = bp->b_bcount;
bioerror(bp, EIO);
biodone(bp);
}
The other return values from scsi_transport(9F) are as follows:
-
TRAN_BUSY
– A command for the specified target is already in progress. -
TRAN_BADPKT
– The DMA count in the packet was too large, or the host adapter driver rejected this packet for other reasons.
-
TRAN_FATAL_ERROR
– The host adapter driver is unable to accept this packet.
The mutex sd_mutex
in the scsi_device(9S) structure
must not be held across a call to scsi_transport(9F).
If scsi_transport(9F) returns TRAN_ACCEPT
, the
packet becomes the responsibility of the host bus adapter driver. The packet
should not be accessed by the target driver until the command completion routine
is called.
Synchronous scsi_transport Function
If FLAG_NOINTR
is set in the packet, then scsi_transport(9F) does not
return until the command is complete. No callback is performed.
Do not use FLAG_NOINTR
in interrupt context.
17.7.4. Command Completion
When the host bus adapter driver is through with the command, the driver invokes the packet's completion callback routine. The driver then passes a pointer to the scsi_pkt(9S) structure as a parameter. After decoding the packet, the completion routine takes the appropriate action.
Completion Routine for a SCSI Driver presents a simple completion callback routine. This code checks for transport failures. In case of failure, the routine gives up rather than retrying the command. If the target is busy, extra code is required to resubmit the command at a later time.
If the command results in a check condition, the target driver needs to send a request sense command unless auto request sense has been enabled.
Otherwise, the command succeeded. At the end of processing for the command, the command destroys the packet and calls biodone(9F).
In the event of a transport error, such as a bus reset or parity problem, the target driver can resubmit the packet by using scsi_transport(9F). No values in the packet need to be changed prior to resubmitting.
The following example does not attempt to retry incomplete commands.
Normally, the target driver's callback function is called in interrupt context. Consequently, the callback function should never sleep.
static void
xxcallback(struct scsi_pkt *pkt)
{
struct buf *bp;
struct xxstate *xsp;
minor_t instance;
struct scsi_status *ssp;
/*
* Get a pointer to the buf(9S) structure for the command
* and to the per-instance data structure.
*/
bp = (struct buf *)pkt->pkt_private;
instance = getminor(bp->b_edev);
xsp = ddi_get_soft_state(statep, instance);
/*
* Figure out why this callback routine was called
*/
if (pkt->pkt_reason != CMP_CMPLT) {
bp->b_resid = bp->b_bcount;
bioerror(bp, EIO);
scsi_destroy_pkt(pkt); /* Release resources */
biodone(bp); /* Notify waiting threads */ ;
} else {
/*
* Command completed, check status.
* See scsi_status(9S)
*/
ssp = (struct scsi_status *)pkt->pkt_scbp;
if (ssp->sts_busy) {
/* error, target busy or reserved */
} else if (ssp->sts_chk) {
/* Send a request sense command. */
} else {
bp->b_resid = pkt->pkt_resid; /* Packet completed OK */
scsi_destroy_pkt(pkt);
biodone(bp);
}
}
}
17.7.5. Reuse of Packets
A target driver can reuse packets in the following ways:
-
Resubmit the packet unchanged.
-
Use scsi_sync_pkt(9F) to synchronize the data. Then, process the data in the driver. Finally, resubmit the packet.
-
Free DMA resources, using scsi_dmafree(9F), and pass the
pkt
pointer to scsi_init_pkt(9F) for binding to a newbp
. The target driver is responsible for reinitializing the packet. The CDB has to have the same length as the previous CDB. -
If only partial DMA is allocated during the first call to scsi_init_pkt(9F), subsequent calls to scsi_init_pkt(9F) can be made for the same packet. Calls can be made to
bp
as well to adjust the DMA resources to the next portion of the transfer.
17.7.6. Auto-Request Sense Mode
Auto-request sense mode is most desirable if queuing is used, whether the queuing is tagged or untagged. A contingent allegiance condition is cleared by any subsequent command and, consequently, the sense data is lost. Most HBA drivers start the next command before performing the target driver callback. Other HBA drivers can use a separate, lower-priority thread to perform the callbacks. This approach might increase the time needed to notify the target driver that the packet completed with a check condition. In this case, the target driver might not be able to submit a request sense command in time to retrieve the sense data.
To avoid this loss of sense data, the HBA driver, or controller, should issue a request sense command if a check condition has been detected. This mode is known as auto-request sense mode. Note that not all HBA drivers are capable of auto-request sense mode, and some drivers can only operate with auto-request sense mode enabled.
A target driver enables auto-request-sense mode by using scsi_ifsetcap(9F). The following example shows auto-request sense enabling.
static int
xxattach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
struct xxstate *xsp;
struct scsi_device *sdp = (struct scsi_device *)
ddi_get_driver_private(dip);
/*
* Enable auto-request-sense; an auto-request-sense cmd might
* fail due to a BUSY condition or transport error. Therefore,
* it is recommended to allocate a separate request sense
* packet as well.
* Note that scsi_ifsetcap(9F) can return -1, 0, or 1
*/
xsp->sdp_arq_enabled =
((scsi_ifsetcap(ROUTE, “auto-rqsense”, 1, 1) == 1) ? 1 : 0);
/*
* If the HBA driver supports auto request sense then the
* status blocks should be sizeof (struct scsi_arq_status);
* else
* One byte is sufficient
*/
xsp->sdp_cmd_stat_size = (xsp->sdp_arq_enabled ?
sizeof (struct scsi_arq_status) : 1);
/* ... */
}
If a packet is allocated using scsi_init_pkt(9F) and auto-request sense is
desired on this packet, additional space is needed. The target driver must
request this space for the status block to hold the auto-request sense structure.
The sense length used in the request sense command is sizeof
,
from struct
scsi_extended_sense
. Auto-request
sense can be disabled per individual packet by allocating sizeof
,
from struct
scsi_status
, for the status
block.
The packet is submitted using scsi_transport(9F) as usual. When a check condition occurs on this packet, the host adapter driver takes the following steps:
-
Issues a request sense command if the controller does not have auto-request sense capability
-
Obtains the sense data
-
Fills in the
scsi_arq_status
information in the packet's status block -
Sets
STATE_ARQ_DONE
in the packet'spkt_state
field -
Calls the packet's callback handler (
pkt_comp
)
The target driver's callback routine should verify that sense data is
available by checking the STATE_ARQ_DONE
bit in pkt_state
. STATE_ARQ_DONE
implies that a check condition
has occurred and that a request sense has been performed. If auto-request
sense has been temporarily disabled in a packet, subsequent retrieval of the
sense data cannot be guaranteed.
The target driver should then verify whether the auto-request sense command completed successfully and decode the sense data.
17.7.7. Dump Handling
The dump(9E) entry point copies a portion of virtual address space directly to the specified device in the case of system failure or checkpoint operation. See the cpr(7) and dump(9E) man pages. The dump(9E) entry point must be capable of performing this operation without the use of interrupts.
The arguments for dump
are as follows:
- dev
-
Device number of the dump device
- addr
-
Kernel virtual address at which to start the dump
- blkno
-
First destination block on the device
- nblk
-
Number of blocks to dump
static int
xxdump(dev_t dev, caddr_t addr, daddr_t blkno, int nblk)
{
struct xxstate *xsp;
struct buf *bp;
struct scsi_pkt *pkt;
int rval;
int instance;
instance = getminor(dev);
xsp = ddi_get_soft_state(statep, instance);
if (tgt->suspended) {
(void) pm_raise_power(DEVINFO(tgt), 0, 1);
}
bp = getrbuf(KM_NOSLEEP);
if (bp == NULL) {
return (EIO);
}
/* Calculate block number relative to partition. */
bp->b_un.b_addr = addr;
bp->b_edev = dev;
bp->b_bcount = nblk * DEV_BSIZE;
bp->b_flags = B_WRITE | B_BUSY;
bp->b_blkno = blkno;
pkt = scsi_init_pkt(ROUTE(tgt), NULL, bp, CDB_GROUP1,
sizeof (struct scsi_arq_status),
sizeof (struct bst_pkt_private), 0, NULL_FUNC, NULL);
if (pkt == NULL) {
freerbuf(bp);
return (EIO);
}
(void) scsi_setup_cdb((union scsi_cdb *)pkt->pkt_cdbp,
SCMD_WRITE_G1, blkno, nblk, 0);
/*
* While dumping in polled mode, other cmds might complete
* and these should not be resubmitted. we set the
* dumping flag here which prevents requeueing cmds.
*/
tgt->dumping = 1;
rval = scsi_poll(pkt);
tgt->dumping = 0;
scsi_destroy_pkt(pkt);
freerbuf(bp);
if (rval != DDI_SUCCESS) {
rval = EIO;
}
return (rval);
}
17.8. SCSI Options
SCSA defines a global variable, scsi_options, for control and debugging. The defined bits in scsi_options can be found in the file <sys/scsi/conf/autoconf.h>. The scsi_options uses the bits as follows:
SCSI_OPTIONS_DR
-
Enables global disconnect or reconnect.
SCSI_OPTIONS_FAST
-
Enables global FAST SCSI support: 10 Mbytes/sec transfers. The HBA should not operate in FAST SCSI mode unless the SCSI_OPTIONS_FAST (0x100) bit is set.
SCSI_OPTIONS_FAST20
-
Enables global FAST20 SCSI support: 20 Mbytes/sec transfers. The HBA should not operate in FAST20 SCSI mode unless the SCSI_OPTIONS_FAST20 (0x400) bit is set.
SCSI_OPTIONS_FAST40
-
Enables global FAST40 SCSI support: 40 Mbytes/sec transfers. The HBA should not operate in FAST40 SCSI mode unless the SCSI_OPTIONS_FAST40 (0x800) bit is set.
SCSI_OPTIONS_FAST80
-
Enables global FAST80 SCSI support: 80 Mbytes/sec transfers. The HBA should not operate in FAST80 SCSI mode unless the SCSI_OPTIONS_FAST80 (0x1000) bit is set.
SCSI_OPTIONS_FAST160
-
Enables global FAST160 SCSI support: 160 Mbytes/sec transfers. The HBA should not operate in FAST160 SCSI mode unless the SCSI_OPTIONS_FAST160 (0x2000) bit is set.
SCSI_OPTIONS_FAST320
-
Enables global FAST320 SCSI support: 320 Mbytes/sec transfers. The HBA should not operate in FAST320 SCSI mode unless the SCSI_OPTIONS_FAST320 (0x4000) bit is set.
SCSI_OPTIONS_LINK
-
Enables global link support.
SCSI_OPTIONS_PARITY
-
Enables global parity support.
SCSI_OPTIONS_QAS
-
Enables the Quick Arbitration Select feature. QAS is used to decrease protocol overhead when devices arbitrate for and access the bus. QAS is only supported on Ultra4 (FAST160) SCSI devices, although not all such devices support QAS. The HBA should not operate in QAS SCSI mode unless the SCSI_OPTIONS_QAS (0x100000) bit is set. Consult the appropriate Sun hardware documentation to determine whether your machine supports QAS.
SCSI_OPTIONS_SYNC
-
Enables global synchronous transfer capability.
SCSI_OPTIONS_TAG
-
Enables global tagged queuing support.
SCSI_OPTIONS_WIDE
-
Enables global WIDE SCSI.
The setting of scsi_options affects all host bus adapter drivers and all target drivers that are present on the system. Refer to the scsi_hba_attach(9F) man page for information on controlling these options for a particular host adapter.