sgen.c revision 2b098703ef923462f146ba7da47baeb859c93a4d
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright Siemens 1999
* All rights reserved.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* sgen - SCSI generic device driver
*
* The sgen driver provides user programs access to SCSI devices that
* are not supported by other drivers by providing the USCSI(7I) interface.
*/
#include <sys/modctl.h>
#include <sys/file.h>
#include <sys/scsi/scsi.h>
#include <sys/scsi/targets/sgendef.h>
#define DDI_NT_SGEN "ddi_generic:scsi"
static char *sgen_devtypes[] = {
"direct", /* 0x00 -- disks */
"sequential", /* 0x01 */
"printer", /* 0x02 */
"processor", /* 0x03 */
"worm", /* 0x04 */
"rodirect", /* 0x05 */
"scanner", /* 0x06 */
"optical", /* 0x07 */
"changer", /* 0x08 */
"comm", /* 0x09 */
"prepress1", /* 0x0a -- reserved for prepress (ASC IT8) */
"prepress2", /* 0x0b -- reserved for prepress (ASC IT8) */
"array_ctrl", /* 0x0c -- storage array */
"ses", /* 0x0d -- enclosure services */
"rbc", /* 0x0e -- simplified block */
"ocrw", /* 0x0f -- optical card read/write */
"bridge", /* 0x10 -- reserved for bridging expanders */
"type_0x11", /* 0x11 */
"type_0x12", /* 0x12 */
"type_0x13", /* 0x13 */
"type_0x14", /* 0x14 */
"type_0x15", /* 0x15 */
"type_0x16", /* 0x16 */
"type_0x17", /* 0x17 */
"type_0x18", /* 0x18 */
"type_0x19", /* 0x19 */
"type_0x1a", /* 0x1a */
"type_0x1b", /* 0x1b */
"type_0x1c", /* 0x1c */
"type_0x1d", /* 0x1d */
"type_0x1e", /* 0x1e */
"type_unknown" /* 0x1f is "no device type" or "unknown" */
};
#define SGEN_NDEVTYPES ((sizeof (sgen_devtypes) / sizeof (char *)))
#define SGEN_INQSTRLEN 24
#define SGEN_VENDID_MAX 8
#define SGEN_PRODID_MAX 16
#define FILL_SCSI1_LUN(devp, pkt) \
if ((devp)->sd_inq->inq_ansi == 0x1) { \
int _lun; \
_lun = ddi_prop_get_int(DDI_DEV_T_ANY, (devp)->sd_dev, \
DDI_PROP_DONTPASS, SCSI_ADDR_PROP_LUN, 0); \
if (_lun > 0) { \
((union scsi_cdb *)(pkt)->pkt_cdbp)->scc_lun = \
_lun; \
} \
}
#define SGEN_DO_ERRSTATS(sg_state, x) \
if (sg_state->sgen_kstats) { \
struct sgen_errstats *sp; \
sp = (struct sgen_errstats *)sg_state->sgen_kstats->ks_data; \
sp->x.value.ui32++; \
}
#define SCBP_C(pkt) ((*(pkt)->pkt_scbp) & STATUS_MASK)
/*
* Standard entrypoints
*/
static int sgen_attach(dev_info_t *, ddi_attach_cmd_t);
static int sgen_detach(dev_info_t *, ddi_detach_cmd_t);
static int sgen_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int sgen_probe(dev_info_t *);
static int sgen_open(dev_t *, int, int, cred_t *);
static int sgen_close(dev_t, int, int, cred_t *);
static int sgen_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
/*
* Configuration routines
*/
static int sgen_do_attach(dev_info_t *);
static int sgen_setup_sense(sgen_state_t *);
static void sgen_create_errstats(sgen_state_t *, int);
static int sgen_do_suspend(dev_info_t *);
static int sgen_do_detach(dev_info_t *);
static void sgen_setup_binddb(dev_info_t *);
static void sgen_cleanup_binddb();
/*
* Packet transport routines
*/
static int sgen_uscsi_cmd(dev_t, struct uscsi_cmd *, int);
static int sgen_start(struct buf *);
static void sgen_hold_cmdbuf(sgen_state_t *);
static void sgen_rele_cmdbuf(sgen_state_t *);
static int sgen_make_uscsi_cmd(sgen_state_t *, struct buf *);
static void sgen_restart(void *);
static void sgen_callback(struct scsi_pkt *);
static int sgen_handle_autosense(sgen_state_t *, struct scsi_pkt *);
static int sgen_handle_sense(sgen_state_t *);
static int sgen_handle_incomplete(sgen_state_t *, struct scsi_pkt *);
static int sgen_check_error(sgen_state_t *, struct buf *);
static int sgen_initiate_sense(sgen_state_t *);
static int sgen_scsi_transport(struct scsi_pkt *);
static int sgen_tur(dev_t);
/*
* Logging/debugging routines
*/
static void sgen_log(sgen_state_t *, int, const char *, ...);
static int sgen_diag_ok(sgen_state_t *, int);
static void sgen_dump_cdb(sgen_state_t *, const char *, union scsi_cdb *, int);
static void sgen_dump_sense(sgen_state_t *, size_t, uchar_t *);
int sgen_diag = 0;
int sgen_sporadic_failures = 0;
int sgen_force_manual_sense = 0;
struct sgen_binddb sgen_binddb;
static struct cb_ops sgen_cb_ops = {
sgen_open, /* open */
sgen_close, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
sgen_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
ddi_prop_op, /* cb_prop_op */
0, /* streamtab */
D_MP | D_NEW | D_HOTPLUG /* Driver compatibility flag */
};
static struct dev_ops sgen_dev_ops = {
DEVO_REV, /* devo_rev, */
0, /* refcnt */
sgen_getinfo, /* info */
nodev, /* identify */
sgen_probe, /* probe */
sgen_attach, /* attach */
sgen_detach, /* detach */
nodev, /* reset */
&sgen_cb_ops, /* driver operations */
(struct bus_ops *)0, /* bus operations */
NULL /* power */
};
static void *sgen_soft_state = NULL;
static struct modldrv modldrv = {
&mod_driverops, "SCSI generic driver %I%", &sgen_dev_ops
};
static struct modlinkage modlinkage = {
MODREV_1, &modldrv, NULL
};
int
_init(void)
{
int err;
sgen_log(NULL, SGEN_DIAG2, "in sgen_init()");
if ((err = ddi_soft_state_init(&sgen_soft_state,
sizeof (sgen_state_t), SGEN_ESTIMATED_NUM_DEVS)) != 0) {
goto done;
}
if ((err = mod_install(&modlinkage)) != 0) {
ddi_soft_state_fini(&sgen_soft_state);
goto done;
}
done:
sgen_log(NULL, SGEN_DIAG2, "%s sgen_init()", err ? "failed" : "done");
return (err);
}
int
_fini(void)
{
int err;
sgen_log(NULL, SGEN_DIAG2, "in sgen_fini()");
if ((err = mod_remove(&modlinkage)) != 0) {
goto done;
}
ddi_soft_state_fini(&sgen_soft_state);
sgen_cleanup_binddb();
done:
sgen_log(NULL, SGEN_DIAG2, "%s sgen_fini()", err ? "failed" : "done");
return (err);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
/*
* sgen_typename()
* return a device type's name by looking it up in the sgen_devtypes table.
*/
static char *
sgen_typename(uchar_t typeno)
{
if (typeno >= SGEN_NDEVTYPES)
return ("type_unknown");
return (sgen_devtypes[typeno]);
}
/*
* sgen_typenum()
* return a device type's number by looking it up in the sgen_devtypes
* table.
*/
static int
sgen_typenum(const char *typename, uchar_t *typenum)
{
int i;
for (i = 0; i < SGEN_NDEVTYPES; i++) {
if (strcasecmp(sgen_devtypes[i], typename) == 0) {
*typenum = (uchar_t)i;
return (0);
}
}
return (-1);
}
/*
* sgen_setup_binddb()
* initialize a data structure which stores all of the information about
* which devices and device types the driver should bind to.
*/
static void
sgen_setup_binddb(dev_info_t *dip)
{
char **strs = NULL, *cp, *pcp, *vcp;
uint_t nelems, pcplen, vcplen, idx;
ASSERT(sgen_binddb.sdb_init == 0);
ASSERT(MUTEX_HELD(&sgen_binddb.sdb_lock));
if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"device-type-config-list", &strs, &nelems) == DDI_PROP_SUCCESS) {
/*
* for each device type specifier make a copy and put it into a
* node in the binddb.
*/
for (idx = 0; idx < nelems; idx++) {
sgen_type_node_t *nodep;
uchar_t devtype;
cp = strs[idx];
if (sgen_typenum(cp, &devtype) != 0) {
sgen_log(NULL, CE_WARN,
"unknown device type '%s', "
"device unit-address @%s",
cp, ddi_get_name_addr(dip));
continue;
}
nodep = kmem_zalloc(sizeof (sgen_type_node_t),
KM_SLEEP);
nodep->node_type = devtype;
nodep->node_next = sgen_binddb.sdb_type_nodes;
sgen_binddb.sdb_type_nodes = nodep;
sgen_log(NULL, SGEN_DIAG2, "found device type "
"'%s' in device-type-config-list, "
"device unit-address @%s",
cp, ddi_get_name_addr(dip));
}
ddi_prop_free(strs);
}
/*
* for each Vendor/Product inquiry pair, build a node and put it
* into the the binddb.
*/
if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"inquiry-config-list", &strs, &nelems) == DDI_PROP_SUCCESS) {
if (nelems % 2 == 1) {
sgen_log(NULL, CE_WARN, "inquiry-config-list must "
"contain Vendor/Product pairs, "
"device unit-address @%s",
ddi_get_name_addr(dip));
nelems--;
}
for (idx = 0; idx < nelems; idx += 2) {
sgen_inq_node_t *nodep;
/*
* Grab vendor and product ID.
*/
vcp = strs[idx];
vcplen = strlen(vcp);
if (vcplen == 0 || vcplen > SGEN_VENDID_MAX) {
sgen_log(NULL, CE_WARN,
"Invalid vendor ID '%s', "
"device unit-address @%s",
vcp, ddi_get_name_addr(dip));
continue;
}
pcp = strs[idx + 1];
pcplen = strlen(pcp);
if (pcplen == 0 || pcplen > SGEN_PRODID_MAX) {
sgen_log(NULL, CE_WARN,
"Invalid product ID '%s', "
"device unit-address @%s",
pcp, ddi_get_name_addr(dip));
continue;
}
nodep = kmem_zalloc(sizeof (sgen_inq_node_t),
KM_SLEEP);
nodep->node_vendor = kmem_alloc(vcplen + 1, KM_SLEEP);
(void) strcpy(nodep->node_vendor, vcp);
nodep->node_product = kmem_alloc(pcplen + 1, KM_SLEEP);
(void) strcpy(nodep->node_product, pcp);
nodep->node_next = sgen_binddb.sdb_inq_nodes;
sgen_binddb.sdb_inq_nodes = nodep;
sgen_log(NULL, SGEN_DIAG2, "found inquiry string "
"'%s' '%s' in device-type-config-list, "
"device unit-address @%s",
nodep->node_vendor, nodep->node_product,
ddi_get_name_addr(dip));
}
ddi_prop_free(strs);
}
sgen_binddb.sdb_init = 1;
}
/*
* sgen_cleanup_binddb()
* deallocate data structures for binding database.
*/
static void
sgen_cleanup_binddb()
{
sgen_inq_node_t *inqp, *inqnextp;
sgen_type_node_t *typep, *typenextp;
mutex_enter(&sgen_binddb.sdb_lock);
if (sgen_binddb.sdb_init == 0) {
mutex_exit(&sgen_binddb.sdb_lock);
return;
}
for (inqp = sgen_binddb.sdb_inq_nodes; inqp != NULL; inqp = inqnextp) {
inqnextp = inqp->node_next;
ASSERT(inqp->node_vendor && inqp->node_product);
kmem_free(inqp->node_vendor,
strlen(inqp->node_vendor) + 1);
kmem_free(inqp->node_product,
strlen(inqp->node_product) + 1);
kmem_free(inqp, sizeof (sgen_inq_node_t));
}
for (typep = sgen_binddb.sdb_type_nodes; typep != NULL;
typep = typenextp) {
typenextp = typep->node_next;
kmem_free(typep, sizeof (sgen_type_node_t));
}
mutex_exit(&sgen_binddb.sdb_lock);
}
/*
* sgen_bind_byinq()
* lookup a device in the binding database by its inquiry data.
*/
static int
sgen_bind_byinq(dev_info_t *dip)
{
sgen_inq_node_t *nodep;
char vend_str[SGEN_VENDID_MAX+1];
char prod_str[SGEN_PRODID_MAX+1];
struct scsi_device *scsidevp;
scsidevp = ddi_get_driver_private(dip);
/*
* inq_vid and inq_pid are laid out by the protocol in order in the
* inquiry structure, and are not delimited by \0.
*/
bcopy(scsidevp->sd_inq->inq_vid, vend_str, SGEN_VENDID_MAX);
vend_str[SGEN_VENDID_MAX] = '\0';
bcopy(scsidevp->sd_inq->inq_pid, prod_str, SGEN_PRODID_MAX);
prod_str[SGEN_PRODID_MAX] = '\0';
for (nodep = sgen_binddb.sdb_inq_nodes; nodep != NULL;
nodep = nodep->node_next) {
/*
* Allow the "*" wildcard to match all vendor IDs.
*/
if (strcmp(nodep->node_vendor, "*") != 0) {
if (strncasecmp(nodep->node_vendor, vend_str,
strlen(nodep->node_vendor)) != 0) {
continue;
}
}
/*
* Using strncasecmp() with the key length allows substring
* matching for product data.
*/
if (strncasecmp(nodep->node_product, prod_str,
strlen(nodep->node_product)) == 0) {
return (0);
}
}
return (-1);
}
/*
* sgen_bind_bytype()
* lookup a device type in the binding database; if found, return a
* format string corresponding to the string in the .conf file.
*/
static int
sgen_bind_bytype(dev_info_t *dip)
{
sgen_type_node_t *nodep;
struct scsi_device *scsidevp;
scsidevp = ddi_get_driver_private(dip);
for (nodep = sgen_binddb.sdb_type_nodes; nodep != NULL;
nodep = nodep->node_next) {
if (nodep->node_type == scsidevp->sd_inq->inq_dtype) {
return (0);
}
}
return (-1);
}
/*
* sgen_get_binding()
* Check to see if the device in question matches the criteria for
* sgen to bind.
*
* Either the .conf file must specify a device_type entry which
* matches the SCSI device type of this device, or the inquiry
* string provided by the device must match an inquiry string specified
* in the .conf file. Inquiry data is matched first.
*/
static int
sgen_get_binding(dev_info_t *dip)
{
int retval = 0;
mutex_enter(&sgen_binddb.sdb_lock);
if (sgen_binddb.sdb_init == 0)
sgen_setup_binddb(dip);
mutex_exit(&sgen_binddb.sdb_lock);
/*
* Check device-type-config-list for a match by device type.
*/
if (sgen_bind_bytype(dip) == 0)
goto done;
/*
* Check inquiry-config-list for a match by Vendor/Product ID.
*/
if (sgen_bind_byinq(dip) == 0)
goto done;
retval = -1;
done:
return (retval);
}
/*
* sgen_attach()
* attach(9e) entrypoint.
*/
static int
sgen_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int err;
sgen_log(NULL, SGEN_DIAG2, "in sgen_attach(), device unit-address @%s",
ddi_get_name_addr(dip));
switch (cmd) {
case DDI_ATTACH:
err = sgen_do_attach(dip);
break;
case DDI_RESUME:
err = DDI_SUCCESS;
break;
case DDI_PM_RESUME:
default:
err = DDI_FAILURE;
break;
}
done:
sgen_log(NULL, SGEN_DIAG2, "%s sgen_attach(), device unit-address @%s",
err == DDI_SUCCESS ? "done" : "failed", ddi_get_name_addr(dip));
return (err);
}
/*
* sgen_do_attach()
* handle the nitty details of attach.
*/
static int
sgen_do_attach(dev_info_t *dip)
{
int instance;
struct scsi_device *scsidevp;
sgen_state_t *sg_state;
uchar_t devtype;
struct scsi_inquiry *inq;
instance = ddi_get_instance(dip);
scsidevp = ddi_get_driver_private(dip);
ASSERT(scsidevp);
sgen_log(NULL, SGEN_DIAG2, "sgen_do_attach: instance = %d, "
"device unit-address @%s", instance, ddi_get_name_addr(dip));
/*
* Probe the device in order to get its device type to name the minor
* node.
*/
if (scsi_probe(scsidevp, NULL_FUNC) != SCSIPROBE_EXISTS) {
scsi_unprobe(scsidevp);
return (DDI_FAILURE);
}
if (ddi_soft_state_zalloc(sgen_soft_state, instance) != DDI_SUCCESS) {
sgen_log(NULL, SGEN_DIAG1,
"sgen_do_attach: failed to allocate softstate, "
"device unit-address @%s", ddi_get_name_addr(dip));
scsi_unprobe(scsidevp);
return (DDI_FAILURE);
}
inq = scsidevp->sd_inq; /* valid while device is probed... */
devtype = inq->inq_dtype;
sg_state = ddi_get_soft_state(sgen_soft_state, instance);
sg_state->sgen_scsidev = scsidevp;
scsidevp->sd_dev = dip;
/*
* Now that sg_state->sgen_scsidev is initialized, it's ok to
* call sgen_log with sg_state instead of NULL.
*/
/*
* If the user specified the sgen_diag property, override the global
* sgen_diag setting by setting sg_state's sgen_diag value. If the
* user gave a value out of range, default to '0'.
*/
sg_state->sgen_diag = ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"sgen-diag", -1);
if (sg_state->sgen_diag != -1) {
if (sg_state->sgen_diag < 0 || sg_state->sgen_diag > 3)
sg_state->sgen_diag = 0;
}
sgen_log(sg_state, SGEN_DIAG2,
"sgen_do_attach: sgen_soft_state=0x%p, instance=%d, "
"device unit-address @%s",
sgen_soft_state, instance, ddi_get_name_addr(dip));
/*
* For simplicity, the minor number == the instance number
*/
if (ddi_create_minor_node(dip, sgen_typename(devtype), S_IFCHR,
instance, DDI_NT_SGEN, NULL) == DDI_FAILURE) {
scsi_unprobe(scsidevp);
ddi_prop_remove_all(dip);
sgen_log(sg_state, SGEN_DIAG1,
"sgen_do_attach: minor node creation failed, "
"device unit-address @%s", ddi_get_name_addr(dip));
ddi_soft_state_free(sgen_soft_state, instance);
return (DDI_FAILURE);
}
/*
* Allocate the command buffer, then create a condition variable for
* managing it; mark the command buffer as free.
*/
sg_state->sgen_cmdbuf = getrbuf(KM_SLEEP);
cv_init(&sg_state->sgen_cmdbuf_cv, NULL, CV_DRIVER, NULL);
SGEN_CLR_BUSY(sg_state);
SGEN_CLR_OPEN(sg_state);
SGEN_CLR_SUSP(sg_state);
/*
* If the hba and the target both support wide xfers, enable them.
*/
if (scsi_ifgetcap(&sg_state->sgen_scsiaddr, "wide-xfer", 1) != -1) {
int wide = 0;
if ((inq->inq_rdf == RDF_SCSI2) &&
(inq->inq_wbus16 || inq->inq_wbus32))
wide = 1;
if (scsi_ifsetcap(&sg_state->sgen_scsiaddr, "wide-xfer",
wide, 1) == 1) {
sgen_log(sg_state, SGEN_DIAG1,
"sgen_attach: wide xfer %s, "
"device unit-address @%s",
wide ? "enabled" : "disabled",
ddi_get_name_addr(dip));
}
}
/*
* This is a little debugging code-- since the codepath for auto-sense
* and 'manual' sense is split, toggling this variable will make
* sgen act as though the adapter in question can't do auto-sense.
*/
if (sgen_force_manual_sense) {
if (scsi_ifsetcap(&sg_state->sgen_scsiaddr, "auto-rqsense",
0, 1) == 1) {
sg_state->sgen_arq_enabled = 0;
} else {
sg_state->sgen_arq_enabled = 1;
}
} else {
/*
* Enable autorequest sense, if supported
*/
if (scsi_ifgetcap(&sg_state->sgen_scsiaddr,
"auto-rqsense", 1) != 1) {
if (scsi_ifsetcap(&sg_state->sgen_scsiaddr,
"auto-rqsense", 1, 1) == 1) {
sg_state->sgen_arq_enabled = 1;
sgen_log(sg_state, SGEN_DIAG1,
"sgen_attach: auto-request-sense enabled, "
"device unit-address @%s",
ddi_get_name_addr(dip));
} else {
sg_state->sgen_arq_enabled = 0;
sgen_log(sg_state, SGEN_DIAG1,
"sgen_attach: auto-request-sense disabled, "
"device unit-address @%s",
ddi_get_name_addr(dip));
}
} else {
sg_state->sgen_arq_enabled = 1; /* already enabled */
sgen_log(sg_state, SGEN_DIAG1,
"sgen_attach: auto-request-sense enabled, "
"device unit-address @%s", ddi_get_name_addr(dip));
}
}
/*
* Allocate plumbing for manually fetching sense.
*/
if (sgen_setup_sense(sg_state) != 0) {
freerbuf(sg_state->sgen_cmdbuf);
ddi_prop_remove_all(dip);
ddi_remove_minor_node(dip, NULL);
scsi_unprobe(scsidevp);
sgen_log(sg_state, SGEN_DIAG1,
"sgen_do_attach: failed to setup request-sense, "
"device unit-address @%s", ddi_get_name_addr(dip));
ddi_soft_state_free(sgen_soft_state, instance);
return (DDI_FAILURE);
}
sgen_create_errstats(sg_state, instance);
ddi_report_dev(dip);
return (DDI_SUCCESS);
}
/*
* sgen_setup_sense()
* Allocate a request sense packet so that if sgen needs to fetch sense
* data for the user, it will have a pkt ready to send.
*/
static int
sgen_setup_sense(sgen_state_t *sg_state)
{
struct buf *bp;
struct scsi_pkt *rqpkt;
if ((bp = scsi_alloc_consistent_buf(&sg_state->sgen_scsiaddr, NULL,
MAX_SENSE_LENGTH, B_READ, SLEEP_FUNC, NULL)) == NULL) {
return (-1);
}
if ((rqpkt = scsi_init_pkt(&sg_state->sgen_scsiaddr, NULL, bp,
CDB_GROUP0, 1, 0, PKT_CONSISTENT, SLEEP_FUNC, NULL)) == NULL) {
scsi_free_consistent_buf(bp);
return (-1);
}
/*
* Make the results of running a SENSE available by filling out the
* sd_sense field of the scsi device (sgen_sense is just an alias).
*/
sg_state->sgen_sense = (struct scsi_extended_sense *)bp->b_un.b_addr;
(void) scsi_setup_cdb((union scsi_cdb *)rqpkt->pkt_cdbp,
SCMD_REQUEST_SENSE, 0, MAX_SENSE_LENGTH, 0);
FILL_SCSI1_LUN(sg_state->sgen_scsidev, rqpkt);
rqpkt->pkt_comp = sgen_callback;
rqpkt->pkt_time = SGEN_IO_TIME;
rqpkt->pkt_flags |= FLAG_SENSING;
rqpkt->pkt_private = sg_state;
sg_state->sgen_rqspkt = rqpkt;
sg_state->sgen_rqsbuf = bp;
return (0);
}
/*
* sgen_create_errstats()
* create named kstats for tracking occurence of errors.
*/
static void
sgen_create_errstats(sgen_state_t *sg_state, int instance)
{
char kstatname[KSTAT_STRLEN];
struct sgen_errstats *stp;
(void) snprintf(kstatname, KSTAT_STRLEN, "sgen%d,err", instance);
sg_state->sgen_kstats = kstat_create("sgenerr", instance,
kstatname, "device_error", KSTAT_TYPE_NAMED,
sizeof (struct sgen_errstats) / sizeof (kstat_named_t),
KSTAT_FLAG_PERSISTENT);
if (sg_state->sgen_kstats == NULL)
return;
stp = (struct sgen_errstats *)sg_state->sgen_kstats->ks_data;
kstat_named_init(&stp->sgen_trans_err, "transport_errors",
KSTAT_DATA_UINT32);
kstat_named_init(&stp->sgen_restart, "command_restarts",
KSTAT_DATA_UINT32);
kstat_named_init(&stp->sgen_incmp_err, "incomplete_commands",
KSTAT_DATA_UINT32);
kstat_named_init(&stp->sgen_autosen_rcv, "autosense_occurred",
KSTAT_DATA_UINT32);
kstat_named_init(&stp->sgen_autosen_bad, "autosense_undecipherable",
KSTAT_DATA_UINT32);
kstat_named_init(&stp->sgen_sense_rcv, "sense_fetches",
KSTAT_DATA_UINT32);
kstat_named_init(&stp->sgen_sense_bad, "sense_data_undecipherable",
KSTAT_DATA_UINT32);
kstat_named_init(&stp->sgen_recov_err, "recoverable_error",
KSTAT_DATA_UINT32);
kstat_named_init(&stp->sgen_nosen_err, "NO_SENSE_sense_key",
KSTAT_DATA_UINT32);
kstat_named_init(&stp->sgen_unrecov_err, "unrecoverable_sense_error",
KSTAT_DATA_UINT32);
sg_state->sgen_kstats->ks_private = sg_state;
sg_state->sgen_kstats->ks_update = nulldev;
kstat_install(sg_state->sgen_kstats);
}
/*
* sgen_detach()
* detach(9E) entrypoint
*/
static int
sgen_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int instance;
sgen_state_t *sg_state;
instance = ddi_get_instance(dip);
sg_state = ddi_get_soft_state(sgen_soft_state, instance);
sgen_log(sg_state, SGEN_DIAG2, "in sgen_detach(), "
"device unit-address @%s", ddi_get_name_addr(dip));
if (sg_state == NULL) {
sgen_log(NULL, SGEN_DIAG1,
"sgen_detach: failed, no softstate found (%d), "
"device unit-address @%s",
instance, ddi_get_name_addr(dip));
return (DDI_FAILURE);
}
switch (cmd) {
case DDI_DETACH:
return (sgen_do_detach(dip));
case DDI_SUSPEND:
return (sgen_do_suspend(dip));
case DDI_PM_SUSPEND:
default:
return (DDI_FAILURE);
}
}
/*
* sgen_do_detach()
* detach the driver, tearing down resources.
*/
static int
sgen_do_detach(dev_info_t *dip)
{
int instance;
sgen_state_t *sg_state;
struct scsi_device *devp;
instance = ddi_get_instance(dip);
sg_state = ddi_get_soft_state(sgen_soft_state, instance);
ASSERT(sg_state);
sgen_log(sg_state, SGEN_DIAG2, "in sgen_do_detach(), "
"device unit-address @%s", ddi_get_name_addr(dip));
devp = ddi_get_driver_private(dip);
mutex_enter(&sg_state->sgen_mutex);
if (SGEN_IS_BUSY(sg_state)) {
mutex_exit(&sg_state->sgen_mutex);
sgen_log(sg_state, SGEN_DIAG1, "sgen_do_detach: failed because "
"device is busy, device unit-address @%s",
ddi_get_name_addr(dip));
return (DDI_FAILURE);
}
mutex_exit(&sg_state->sgen_mutex);
/*
* Final approach for detach. Free data allocated by scsi_probe()
* in attach.
*/
if (sg_state->sgen_restart_timeid)
(void) untimeout(sg_state->sgen_restart_timeid);
sg_state->sgen_restart_timeid = 0;
scsi_unprobe(devp);
/*
* Free auto-request plumbing.
*/
scsi_free_consistent_buf(sg_state->sgen_rqsbuf);
scsi_destroy_pkt(sg_state->sgen_rqspkt);
if (sg_state->sgen_kstats) {
kstat_delete(sg_state->sgen_kstats);
sg_state->sgen_kstats = NULL;
}
/*
* Free command buffer and clean up
*/
freerbuf(sg_state->sgen_cmdbuf);
cv_destroy(&sg_state->sgen_cmdbuf_cv);
sgen_log(sg_state, SGEN_DIAG2, "done sgen_do_detach(), "
"device unit-address @%s", ddi_get_name_addr(dip));
ddi_soft_state_free(sgen_soft_state, instance);
ddi_prop_remove_all(dip);
ddi_remove_minor_node(dip, NULL);
return (DDI_SUCCESS);
}
/*
* sgen_do_suspend()
* suspend the driver. This sets the "suspend" bit for this target if it
* is currently open; once resumed, the suspend bit will cause
* subsequent I/Os to fail. We want user programs to close and
* reopen the device to acknowledge that they need to reexamine its
* state and do the right thing.
*/
static int
sgen_do_suspend(dev_info_t *dip)
{
int instance;
sgen_state_t *sg_state;
instance = ddi_get_instance(dip);
sg_state = ddi_get_soft_state(sgen_soft_state, instance);
ASSERT(sg_state);
sgen_log(sg_state, SGEN_DIAG2, "in sgen_do_suspend(), "
"device unit-address @%s", ddi_get_name_addr(dip));
if (sg_state->sgen_restart_timeid) {
(void) untimeout(sg_state->sgen_restart_timeid);
}
sg_state->sgen_restart_timeid = 0;
mutex_enter(&sg_state->sgen_mutex);
if (SGEN_IS_OPEN(sg_state))
SGEN_SET_SUSP(sg_state);
mutex_exit(&sg_state->sgen_mutex);
sgen_log(sg_state, SGEN_DIAG2, "done sgen_do_suspend(), "
"device unit-address @%s", ddi_get_name_addr(dip));
return (DDI_SUCCESS);
}
/*
* sgen_getinfo()
* getinfo(9e) entrypoint.
*/
/*ARGSUSED*/
static int
sgen_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
dev_t dev;
sgen_state_t *sg_state;
int instance, error;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
dev = (dev_t)arg;
instance = getminor(dev);
if ((sg_state = ddi_get_soft_state(sgen_soft_state, instance))
== NULL)
return (DDI_FAILURE);
*result = (void *) sg_state->sgen_scsidev->sd_dev;
error = DDI_SUCCESS;
break;
case DDI_INFO_DEVT2INSTANCE:
dev = (dev_t)arg;
instance = getminor(dev);
*result = (void *)(uintptr_t)instance;
error = DDI_SUCCESS;
break;
default:
error = DDI_FAILURE;
}
return (error);
}
/*
* sgen_probe()
* probe(9e) entrypoint. sgen *never* returns DDI_PROBE_PARTIAL, in
* order to avoid leaving around extra devinfos. If sgen's binding
* rules indicate that it should bind, it returns DDI_PROBE_SUCCESS.
*/
static int
sgen_probe(dev_info_t *dip)
{
struct scsi_device *scsidevp;
int instance;
int rval;
scsidevp = ddi_get_driver_private(dip);
instance = ddi_get_instance(dip);
sgen_log(NULL, SGEN_DIAG2, "in sgen_probe(): instance = %d, "
"device unit-address @%s", instance, ddi_get_name_addr(dip));
if (ddi_dev_is_sid(dip) == DDI_SUCCESS)
return (DDI_PROBE_DONTCARE);
if (ddi_get_soft_state(sgen_soft_state, instance) != NULL)
return (DDI_PROBE_FAILURE);
mutex_enter(&sgen_binddb.sdb_lock);
if (sgen_binddb.sdb_init == 0) {
sgen_setup_binddb(dip);
}
mutex_exit(&sgen_binddb.sdb_lock);
/*
* A small optimization: if it's impossible for sgen to bind to
* any devices, don't bother probing, just fail.
*/
if ((sgen_binddb.sdb_inq_nodes == NULL) &&
(sgen_binddb.sdb_type_nodes == NULL)) {
return (DDI_PROBE_FAILURE);
}
if (scsi_probe(scsidevp, NULL_FUNC) == SCSIPROBE_EXISTS) {
if (sgen_get_binding(dip) == 0) {
rval = DDI_PROBE_SUCCESS;
}
} else {
rval = DDI_PROBE_FAILURE;
}
scsi_unprobe(scsidevp);
sgen_log(NULL, SGEN_DIAG2, "sgen_probe() %s, device unit-address @%s",
rval == DDI_PROBE_SUCCESS ? "succeeded" : "failed",
ddi_get_name_addr(dip));
return (rval);
}
/*
* sgen_open()
* open(9e) entrypoint. sgen enforces a strict exclusive open policy per
* target.
*/
/*ARGSUSED1*/
static int
sgen_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p)
{
dev_t dev = *dev_p;
sgen_state_t *sg_state;
int instance;
instance = getminor(dev);
if ((sg_state = ddi_get_soft_state(sgen_soft_state, instance)) == NULL)
return (ENXIO);
sgen_log(sg_state, SGEN_DIAG2, "in sgen_open(): instance = %d",
instance);
mutex_enter(&sg_state->sgen_mutex);
if (SGEN_IS_OPEN(sg_state)) {
mutex_exit(&sg_state->sgen_mutex);
return (EBUSY);
}
SGEN_SET_OPEN(sg_state);
/*
* At this point, sgen cannot have the suspended bit set,
* since each target is exclusive-access; when the previous
* close occurred, it cleared the flag.
*/
ASSERT(!SGEN_IS_SUSP(sg_state));
mutex_exit(&sg_state->sgen_mutex);
return (0);
}
/*
* sgen_close()
* close(9e) entrypoint.
*/
/*ARGSUSED1*/
static int
sgen_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
{
sgen_state_t *sg_state;
int instance;
instance = getminor(dev);
if ((sg_state = ddi_get_soft_state(sgen_soft_state, instance)) == NULL)
return (ENXIO);
sgen_log(sg_state, SGEN_DIAG2, "in sgen_close(): instance = %d",
instance);
mutex_enter(&sg_state->sgen_mutex);
SGEN_CLR_OPEN(sg_state);
SGEN_CLR_SUSP(sg_state); /* closing clears the 'I was suspended' bit */
mutex_exit(&sg_state->sgen_mutex);
sgen_log(sg_state, SGEN_DIAG2, "done sgen_close()");
return (0);
}
/*
* sgen_ioctl()
* sgen supports the USCSI(7I) ioctl interface.
*/
/*ARGSUSED4*/
static int
sgen_ioctl(dev_t dev,
int cmd, intptr_t arg, int flag, cred_t *cred_p, int *rval_p)
{
int retval = 0;
sgen_state_t *sg_state;
int instance;
instance = getminor(dev);
if ((sg_state = ddi_get_soft_state(sgen_soft_state, instance)) == NULL)
return (ENXIO);
sgen_log(sg_state, SGEN_DIAG2, "in sgen_ioctl(): instance = %d",
instance);
/*
* If the driver has been suspended since the last open, fail all
* subsequent IO's so that the userland consumer reinitializes state.
*/
mutex_enter(&sg_state->sgen_mutex);
if (SGEN_IS_SUSP(sg_state)) {
mutex_exit(&sg_state->sgen_mutex);
sgen_log(sg_state, SGEN_DIAG1, "sgen_ioctl: returning EIO: "
"driver instance %d was previously suspended", instance);
return (EIO);
}
mutex_exit(&sg_state->sgen_mutex);
switch (cmd) {
case SGEN_IOC_DIAG: {
if (arg > 3) {
arg = 0;
}
sg_state->sgen_diag = (int)arg;
retval = 0;
break;
}
case SGEN_IOC_READY: {
if (sgen_tur(dev) != 0) {
retval = EIO;
} else {
retval = 0;
}
break;
}
case USCSICMD:
retval = sgen_uscsi_cmd(dev, (struct uscsi_cmd *)arg, flag);
break;
default:
retval = ENOTTY;
}
sgen_log(sg_state, SGEN_DIAG2, "done sgen_ioctl(), returning %d",
retval);
return (retval);
}
/*
* sgen_uscsi_cmd()
* Setup, configuration and teardown for a uscsi(7I) command
*/
/*ARGSUSED*/
static int
sgen_uscsi_cmd(dev_t dev, struct uscsi_cmd *ucmd, int flag)
{
struct uscsi_cmd *uscmd;
struct buf *bp;
sgen_state_t *sg_state;
enum uio_seg uioseg;
int cmdbufhold = 0;
int instance;
int flags;
int err;
instance = getminor(dev);
sg_state = ddi_get_soft_state(sgen_soft_state, instance);
ASSERT(sg_state);
sgen_log(sg_state, SGEN_DIAG2, "in sgen_uscsi_cmd(): instance = %d",
instance);
err = scsi_uscsi_alloc_and_copyin((intptr_t)ucmd, flag,
&sg_state->sgen_scsiaddr, &uscmd);
if (err != 0) {
sgen_log(sg_state, SGEN_DIAG1, "sgen_uscsi_cmd: "
"scsi_uscsi_alloc_and_copyin failed\n");
return (err);
}
/*
* Clear out undesirable command flags
*/
flags = (uscmd->uscsi_flags & ~(USCSI_NOINTR | USCSI_NOPARITY |
USCSI_OTAG | USCSI_HTAG | USCSI_HEAD));
if (flags != uscmd->uscsi_flags) {
sgen_log(sg_state, SGEN_DIAG1, "sgen_uscsi_cmd: cleared "
"unsafe uscsi_flags 0x%x", uscmd->uscsi_flags & ~flags);
uscmd->uscsi_flags = flags;
}
/*
* At this point, we start affecting state relevant to the target,
* so access needs to be serialized.
*/
sgen_hold_cmdbuf(sg_state); /* lock command buf for this target */
cmdbufhold = 1;
if (uscmd->uscsi_cdb != NULL) {
sgen_dump_cdb(sg_state, "sgen_uscsi_cmd: ",
(union scsi_cdb *)uscmd->uscsi_cdb, uscmd->uscsi_cdblen);
}
/*
* Stash the sense buffer into sgen_rqs_sen for convenience.
*/
sg_state->sgen_rqs_sen = uscmd->uscsi_rqbuf;
bp = sg_state->sgen_cmdbuf;
bp->av_back = NULL;
bp->av_forw = NULL;
bp->b_private = (struct buf *)uscmd;
uioseg = (flag & FKIOCTL) ? UIO_SYSSPACE : UIO_USERSPACE;
err = scsi_uscsi_handle_cmd(dev, uioseg, uscmd, sgen_start, bp, NULL);
if (sg_state->sgen_cmdpkt != NULL) {
uscmd->uscsi_status = SCBP_C(sg_state->sgen_cmdpkt);
} else {
uscmd->uscsi_status = 0;
}
sgen_log(sg_state, SGEN_DIAG3, "sgen_uscsi_cmd: awake from waiting "
"for command. Status is 0x%x", uscmd->uscsi_status);
if (uscmd->uscsi_rqbuf != NULL) {
int rqlen = uscmd->uscsi_rqlen - uscmd->uscsi_rqresid;
sgen_dump_sense(sg_state, rqlen,
(uchar_t *)uscmd->uscsi_rqbuf);
}
(void) scsi_uscsi_copyout_and_free((intptr_t)ucmd, uscmd);
if (sg_state->sgen_cmdpkt != NULL) {
scsi_destroy_pkt(sg_state->sgen_cmdpkt);
sg_state->sgen_cmdpkt = NULL;
}
/*
* After this point, we can't touch per-target state.
*/
if (cmdbufhold) {
sgen_rele_cmdbuf(sg_state);
}
sgen_log(sg_state, SGEN_DIAG2, "done sgen_uscsi_cmd()");
return (err);
}
/*
* sgen_hold_cmdbuf()
* Aquire a lock on the command buffer for the given target.
*/
static void
sgen_hold_cmdbuf(sgen_state_t *sg_state)
{
mutex_enter(&sg_state->sgen_mutex);
while (SGEN_IS_BUSY(sg_state))
cv_wait(&sg_state->sgen_cmdbuf_cv, &sg_state->sgen_mutex);
SGEN_SET_BUSY(sg_state);
mutex_exit(&sg_state->sgen_mutex);
}
/*
* sgen_rele_cmdbuf()
* release the command buffer for a particular target.
*/
static void
sgen_rele_cmdbuf(sgen_state_t *sg_state)
{
mutex_enter(&sg_state->sgen_mutex);
SGEN_CLR_BUSY(sg_state);
cv_signal(&sg_state->sgen_cmdbuf_cv);
mutex_exit(&sg_state->sgen_mutex);
}
/*
* sgen_start()
* Transport a uscsi command; this is invoked by physio() or directly
* by sgen_uscsi_cmd().
*/
static int
sgen_start(struct buf *bp)
{
sgen_state_t *sg_state;
dev_t dev = bp->b_edev;
int trans_err;
if ((sg_state = ddi_get_soft_state(sgen_soft_state,
getminor(dev))) == NULL) {
bp->b_resid = bp->b_bcount;
bioerror(bp, ENXIO);
biodone(bp);
return (ENXIO);
}
/*
* Sanity checks - command should not be complete, no packet should
* be allocated, and there ought to be a uscsi cmd in b_private
*/
ASSERT(bp == sg_state->sgen_cmdbuf && sg_state->sgen_cmdpkt == NULL);
ASSERT((bp->b_flags & B_DONE) == 0);
ASSERT(bp->b_private);
if (sgen_make_uscsi_cmd(sg_state, bp) != 0) {
bp->b_resid = bp->b_bcount;
bioerror(bp, EFAULT);
biodone(bp);
return (EFAULT);
}
ASSERT(sg_state->sgen_cmdpkt != NULL);
/*
* Clear out the residual and error fields
*/
bp->b_resid = 0;
bp->b_error = 0;
trans_err = sgen_scsi_transport(sg_state->sgen_cmdpkt);
switch (trans_err) {
case TRAN_ACCEPT:
break;
case TRAN_BUSY:
sgen_log(sg_state, SGEN_DIAG2,
"sgen_start: scsi_transport() returned TRAN_BUSY");
sg_state->sgen_restart_timeid = timeout(sgen_restart, sg_state,
SGEN_BSY_TIMEOUT);
break;
default:
/*
* Indicate there has been an I/O transfer error.
* Be done with the command.
*/
mutex_enter(&sg_state->sgen_mutex);
SGEN_DO_ERRSTATS(sg_state, sgen_trans_err);
mutex_exit(&sg_state->sgen_mutex);
sgen_log(sg_state, SGEN_DIAG2, "sgen_start: scsi_transport() "
"returned %d", trans_err);
bioerror(bp, EIO);
biodone(bp);
return (EIO);
}
sgen_log(sg_state, SGEN_DIAG2, "sgen_start: b_flags 0x%x", bp->b_flags);
return (0);
}
/*
* sgen_scsi_transport()
* a simple scsi_transport() wrapper which can be configured to inject
* sporadic errors for testing.
*/
static int
sgen_scsi_transport(struct scsi_pkt *pkt)
{
int trans_err;
static int cnt = 0;
sgen_state_t *sg_state = pkt->pkt_private;
if (sgen_sporadic_failures == 0) {
return (scsi_transport(pkt));
}
cnt = (cnt * 2416 + 374441) % 1771875; /* borrowed from kmem.c */
if (cnt % 40 == 1) {
sgen_log(sg_state, SGEN_DIAG1, "sgen_scsi_transport: "
"injecting sporadic BUSY");
trans_err = TRAN_BUSY;
} else if (cnt % 40 == 2) {
sgen_log(sg_state, SGEN_DIAG1, "sgen_scsi_transport: "
"injecting sporadic BADPKT");
trans_err = TRAN_BADPKT;
} else {
/*
* Most of the time we take the normal path
*/
trans_err = scsi_transport(pkt);
}
return (trans_err);
}
/*
* sgen_make_uscsi_cmd()
* Initialize a SCSI packet usable for USCSI.
*/
static int
sgen_make_uscsi_cmd(sgen_state_t *sg_state, struct buf *bp)
{
struct scsi_pkt *pkt;
struct uscsi_cmd *ucmd;
int stat_size = 1;
int flags = 0;
ASSERT(bp);
sgen_log(sg_state, SGEN_DIAG2, "in sgen_make_uscsi_cmd()");
ucmd = (struct uscsi_cmd *)bp->b_private;
if (ucmd->uscsi_flags & USCSI_RQENABLE) {
if (ucmd->uscsi_rqlen > SENSE_LENGTH) {
stat_size = (int)(ucmd->uscsi_rqlen) +
sizeof (struct scsi_arq_status) -
sizeof (struct scsi_extended_sense);
flags = PKT_XARQ;
} else {
stat_size = sizeof (struct scsi_arq_status);
}
}
sgen_log(sg_state, SGEN_DIAG3, "sgen_make_uscsi_cmd: b_bcount = %ld",
bp->b_bcount);
pkt = scsi_init_pkt(&sg_state->sgen_scsiaddr,
NULL, /* in_pkt - null so it'll be alloc'd */
bp->b_bcount ? bp : NULL, /* buf structure for data xfer */
ucmd->uscsi_cdblen, /* cmdlen */
stat_size, /* statuslen */
0, /* privatelen */
flags, /* flags */
SLEEP_FUNC, /* callback */
(caddr_t)sg_state); /* callback_arg */
if (pkt == NULL) {
sgen_log(sg_state, SGEN_DIAG2, "failed sgen_make_uscsi_cmd()");
return (-1);
}
pkt->pkt_comp = sgen_callback;
pkt->pkt_private = sg_state;
sg_state->sgen_cmdpkt = pkt;
/*
* We *don't* call scsi_setup_cdb here, as is customary, since the
* user could specify a command from one group, but pass cdblen
* as something totally different. If cdblen is smaller than expected,
* this results in scsi_setup_cdb writing past the end of the cdb.
*/
bcopy(ucmd->uscsi_cdb, pkt->pkt_cdbp, ucmd->uscsi_cdblen);
if (ucmd->uscsi_cdblen >= CDB_GROUP0) {
FILL_SCSI1_LUN(sg_state->sgen_scsidev, pkt);
}
if (ucmd->uscsi_timeout > 0)
pkt->pkt_time = ucmd->uscsi_timeout;
else
pkt->pkt_time = SGEN_IO_TIME;
/*
* Set packet options
*/
if (ucmd->uscsi_flags & USCSI_SILENT)
pkt->pkt_flags |= FLAG_SILENT;
if (ucmd->uscsi_flags & USCSI_ISOLATE)
pkt->pkt_flags |= FLAG_ISOLATE;
if (ucmd->uscsi_flags & USCSI_DIAGNOSE)
pkt->pkt_flags |= FLAG_DIAGNOSE;
if (ucmd->uscsi_flags & USCSI_RENEGOT) {
pkt->pkt_flags |= FLAG_RENEGOTIATE_WIDE_SYNC;
}
sgen_log(sg_state, SGEN_DIAG2, "done sgen_make_uscsi_cmd()");
return (0);
}
/*
* sgen_restart()
* sgen_restart() is called after a timeout, when a command has been
* postponed due to a TRAN_BUSY response from the HBA.
*/
static void
sgen_restart(void *arg)
{
sgen_state_t *sg_state = (sgen_state_t *)arg;
struct scsi_pkt *pkt;
struct buf *bp;
sgen_log(sg_state, SGEN_DIAG2, "in sgen_restart()");
bp = sg_state->sgen_cmdbuf;
pkt = sg_state->sgen_cmdpkt;
ASSERT(bp && pkt);
SGEN_DO_ERRSTATS(sg_state, sgen_restart);
/*
* If the packet is marked with the sensing flag, sgen is off running
* a request sense, and *that packet* is what needs to be restarted.
*/
if (pkt->pkt_flags & FLAG_SENSING) {
sgen_log(sg_state, SGEN_DIAG3,
"sgen_restart: restarting REQUEST SENSE");
pkt = sg_state->sgen_rqspkt;
}
if (sgen_scsi_transport(pkt) != TRAN_ACCEPT) {
bp->b_resid = bp->b_bcount;
bioerror(bp, EIO);
biodone(bp);
}
}
/*
* sgen_callback()
* Command completion processing
*
* sgen's completion processing is very pessimistic-- it does not retry
* failed commands; instead, it allows the user application to make
* decisions about what has gone wrong.
*/
static void
sgen_callback(struct scsi_pkt *pkt)
{
sgen_state_t *sg_state;
struct buf *bp;
int action;
sg_state = pkt->pkt_private;
/*
* bp should always be the command buffer regardless of whether
* this is a command completion or a request-sense completion.
* This is because there is no need to biodone() the sense buf
* when it completes-- we want to biodone() the actual command buffer!
*/
bp = sg_state->sgen_cmdbuf;
if (pkt->pkt_flags & FLAG_SENSING) {
ASSERT(pkt == sg_state->sgen_rqspkt);
sgen_log(sg_state, SGEN_DIAG2,
"in sgen_callback() (SENSE completion callback)");
} else {
ASSERT(pkt == sg_state->sgen_cmdpkt);
sgen_log(sg_state, SGEN_DIAG2,
"in sgen_callback() (command completion callback)");
}
sgen_log(sg_state, SGEN_DIAG3, "sgen_callback: reason=0x%x resid=%ld "
"state=0x%x", pkt->pkt_reason, pkt->pkt_resid, pkt->pkt_state);
if (pkt->pkt_reason != CMD_CMPLT) {
/*
* The command did not complete.
*/
sgen_log(sg_state, SGEN_DIAG3,
"sgen_callback: command did not complete");
action = sgen_handle_incomplete(sg_state, pkt);
} else if (sg_state->sgen_arq_enabled &&
(pkt->pkt_state & STATE_ARQ_DONE)) {
/*
* The auto-rqsense happened, and the packet has a filled-in
* scsi_arq_status structure, pointed to by pkt_scbp.
*/
sgen_log(sg_state, SGEN_DIAG3,
"sgen_callback: received auto-requested sense");
action = sgen_handle_autosense(sg_state, pkt);
ASSERT(action != FETCH_SENSE);
} else if (pkt->pkt_flags & FLAG_SENSING) {
/*
* sgen was running a REQUEST SENSE. Decode the sense data and
* decide what to do next.
*
* Clear FLAG_SENSING on the original packet for completeness.
*/
sgen_log(sg_state, SGEN_DIAG3, "sgen_callback: received sense");
sg_state->sgen_cmdpkt->pkt_flags &= ~FLAG_SENSING;
action = sgen_handle_sense(sg_state);
ASSERT(action != FETCH_SENSE);
} else {
/*
* Command completed and we're not getting sense. Check for
* errors and decide what to do next.
*/
sgen_log(sg_state, SGEN_DIAG3,
"sgen_callback: command appears complete");
action = sgen_check_error(sg_state, bp);
}
switch (action) {
case FETCH_SENSE:
/*
* If there is sense to fetch, break out to prevent biodone'ing
* until the sense fetch is complete.
*/
if (sgen_initiate_sense(sg_state) == 0)
break;
/*FALLTHROUGH*/
case COMMAND_DONE_ERROR:
bp->b_resid = bp->b_bcount;
bioerror(bp, EIO);
/*FALLTHROUGH*/
case COMMAND_DONE:
biodone(bp);
break;
default:
ASSERT(0);
break;
}
sgen_log(sg_state, SGEN_DIAG2, "done sgen_callback()");
}
/*
* sgen_initiate_sense()
* Send the sgen_rqspkt to the target, thereby requesting sense data.
*/
static int
sgen_initiate_sense(sgen_state_t *sg_state)
{
switch (sgen_scsi_transport(sg_state->sgen_rqspkt)) {
case TRAN_ACCEPT:
sgen_log(sg_state, SGEN_DIAG3, "sgen_initiate_sense: "
"sense fetch transport accepted.");
return (0);
case TRAN_BUSY:
sgen_log(sg_state, SGEN_DIAG2, "sgen_initiate_sense: "
"sense fetch transport busy, setting timeout.");
sg_state->sgen_restart_timeid = timeout(sgen_restart, sg_state,
SGEN_BSY_TIMEOUT);
return (0);
default:
sgen_log(sg_state, SGEN_DIAG2, "sgen_initiate_sense: "
"sense fetch transport failed or busy.");
return (-1);
}
}
/*
* sgen_handle_incomplete()
* sgen is pessimistic, but also careful-- it doesn't try to retry
* incomplete commands, but it also doesn't go resetting devices;
* it is hard to tell if the device will be tolerant of that sort
* of prodding.
*
* This routine has been left as a guide for the future--- the
* current administration's hands-off policy may need modification.
*/
/*ARGSUSED*/
static int
sgen_handle_incomplete(sgen_state_t *sg_state, struct scsi_pkt *pkt)
{
SGEN_DO_ERRSTATS(sg_state, sgen_incmp_err);
return (COMMAND_DONE_ERROR);
}
/*
* sgen_handle_autosense()
* Deal with SENSE data acquired automatically via the auto-request-sense
* facility.
*
* Sgen takes a pessimistic view of things-- it doesn't retry commands,
* and unless the device recovered from the problem, this routine returns
* COMMAND_DONE_ERROR.
*/
static int
sgen_handle_autosense(sgen_state_t *sg_state, struct scsi_pkt *pkt)
{
struct scsi_arq_status *arqstat;
struct uscsi_cmd *ucmd =
(struct uscsi_cmd *)sg_state->sgen_cmdbuf->b_private;
int amt;
arqstat = (struct scsi_arq_status *)(pkt->pkt_scbp);
SGEN_DO_ERRSTATS(sg_state, sgen_autosen_rcv);
if (arqstat->sts_rqpkt_reason != CMD_CMPLT) {
sgen_log(sg_state, SGEN_DIAG1, "sgen_handle_autosense: ARQ"
"failed to complete.");
SGEN_DO_ERRSTATS(sg_state, sgen_autosen_bad);
return (COMMAND_DONE_ERROR);
}
if (pkt->pkt_state & STATE_XARQ_DONE) {
amt = MAX_SENSE_LENGTH - arqstat->sts_rqpkt_resid;
} else {
if (arqstat->sts_rqpkt_resid > SENSE_LENGTH) {
amt = MAX_SENSE_LENGTH - arqstat->sts_rqpkt_resid;
} else {
amt = SENSE_LENGTH - arqstat->sts_rqpkt_resid;
}
}
if (ucmd->uscsi_flags & USCSI_RQENABLE) {
ucmd->uscsi_rqstatus = *((char *)&arqstat->sts_rqpkt_status);
uchar_t rqlen = min((uchar_t)amt, ucmd->uscsi_rqlen);
ucmd->uscsi_rqresid = ucmd->uscsi_rqlen - rqlen;
ASSERT(ucmd->uscsi_rqlen && sg_state->sgen_rqs_sen);
bcopy(&(arqstat->sts_sensedata), sg_state->sgen_rqs_sen, rqlen);
sgen_log(sg_state, SGEN_DIAG2, "sgen_handle_autosense: "
"uscsi_rqstatus=0x%x uscsi_rqresid=%d\n",
ucmd->uscsi_rqstatus, ucmd->uscsi_rqresid);
}
if (arqstat->sts_rqpkt_status.sts_chk) {
sgen_log(sg_state, SGEN_DIAG1, "sgen_handle_autosense: got "
"check condition on auto request sense!");
SGEN_DO_ERRSTATS(sg_state, sgen_autosen_bad);
return (COMMAND_DONE_ERROR);
}
if (((arqstat->sts_rqpkt_state & STATE_XFERRED_DATA) == 0) ||
(amt == 0)) {
sgen_log(sg_state, SGEN_DIAG1, "sgen_handle_autosense: got "
"auto-sense, but it contains no data!");
SGEN_DO_ERRSTATS(sg_state, sgen_autosen_bad);
return (COMMAND_DONE_ERROR);
}
/*
* Stuff the sense data pointer into sgen_sense for later retrieval
*/
sg_state->sgen_sense = &arqstat->sts_sensedata;
/*
* Now, check to see whether we got enough sense data to make any
* sense out if it (heh-heh).
*/
if (amt < SUN_MIN_SENSE_LENGTH) {
sgen_log(sg_state, SGEN_DIAG1, "sgen_handle_autosense: not "
"enough auto sense data");
return (COMMAND_DONE_ERROR);
}
switch (arqstat->sts_sensedata.es_key) {
case KEY_RECOVERABLE_ERROR:
SGEN_DO_ERRSTATS(sg_state, sgen_recov_err);
break;
case KEY_NO_SENSE:
SGEN_DO_ERRSTATS(sg_state, sgen_nosen_err);
break;
default:
SGEN_DO_ERRSTATS(sg_state, sgen_unrecov_err);
break;
}
return (COMMAND_DONE);
}
/*
* sgen_handle_sense()
* Examine sense data that was manually fetched from the target.
*/
static int
sgen_handle_sense(sgen_state_t *sg_state)
{
struct scsi_pkt *rqpkt = sg_state->sgen_rqspkt;
struct scsi_status *rqstatus = (struct scsi_status *)rqpkt->pkt_scbp;
struct uscsi_cmd *ucmd =
(struct uscsi_cmd *)sg_state->sgen_cmdbuf->b_private;
int amt;
SGEN_DO_ERRSTATS(sg_state, sgen_sense_rcv);
amt = MAX_SENSE_LENGTH - rqpkt->pkt_resid;
if (ucmd->uscsi_flags & USCSI_RQENABLE) {
ucmd->uscsi_rqstatus = *((char *)rqstatus);
uchar_t rqlen = min((uchar_t)amt, ucmd->uscsi_rqlen);
ucmd->uscsi_rqresid = ucmd->uscsi_rqlen - rqlen;
ASSERT(ucmd->uscsi_rqlen && sg_state->sgen_rqs_sen);
bcopy(sg_state->sgen_sense, sg_state->sgen_rqs_sen, rqlen);
sgen_log(sg_state, SGEN_DIAG2, "sgen_handle_sense: "
"uscsi_rqstatus=0x%x uscsi_rqresid=%d\n",
ucmd->uscsi_rqstatus, ucmd->uscsi_rqresid);
}
if (rqstatus->sts_busy) {
sgen_log(sg_state, SGEN_DIAG1, "sgen_handle_sense: got busy "
"on request sense");
SGEN_DO_ERRSTATS(sg_state, sgen_sense_bad);
return (COMMAND_DONE_ERROR);
}
if (rqstatus->sts_chk) {
sgen_log(sg_state, SGEN_DIAG1, "sgen_handle_sense: got check "
"condition on request sense!");
SGEN_DO_ERRSTATS(sg_state, sgen_sense_bad);
return (COMMAND_DONE_ERROR);
}
if ((rqpkt->pkt_state & STATE_XFERRED_DATA) == 0 || amt == 0) {
sgen_log(sg_state, SGEN_DIAG1, "sgen_handle_sense: got "
"sense, but it contains no data");
SGEN_DO_ERRSTATS(sg_state, sgen_sense_bad);
return (COMMAND_DONE_ERROR);
}
/*
* Now, check to see whether we got enough sense data to make any
* sense out if it (heh-heh).
*/
if (amt < SUN_MIN_SENSE_LENGTH) {
sgen_log(sg_state, SGEN_DIAG1, "sgen_handle_sense: not "
"enough sense data");
SGEN_DO_ERRSTATS(sg_state, sgen_sense_bad);
return (COMMAND_DONE_ERROR);
}
/*
* Decode the sense data-- this was deposited here for us by the
* setup in sgen_do_attach(). (note that sgen_sense is an alias for
* the sd_sense field in the scsi_device).
*/
sgen_log(sg_state, SGEN_DIAG1, "Sense key is %s [0x%x]",
scsi_sname(sg_state->sgen_sense->es_key),
sg_state->sgen_sense->es_key);
switch (sg_state->sgen_sense->es_key) {
case KEY_RECOVERABLE_ERROR:
SGEN_DO_ERRSTATS(sg_state, sgen_recov_err);
break;
case KEY_NO_SENSE:
SGEN_DO_ERRSTATS(sg_state, sgen_nosen_err);
break;
default:
SGEN_DO_ERRSTATS(sg_state, sgen_unrecov_err);
break;
}
return (COMMAND_DONE);
}
/*
* sgen_check_error()
* examine the command packet for abnormal completion.
*
* sgen_check_error should only be called at the completion of the
* command packet.
*/
static int
sgen_check_error(sgen_state_t *sg_state, struct buf *bp)
{
struct scsi_pkt *pkt = sg_state->sgen_cmdpkt;
struct scsi_status *status = (struct scsi_status *)pkt->pkt_scbp;
struct uscsi_cmd *ucmd =
(struct uscsi_cmd *)sg_state->sgen_cmdbuf->b_private;
if (status->sts_busy) {
sgen_log(sg_state, SGEN_DIAG1,
"sgen_check_error: target is busy");
return (COMMAND_DONE_ERROR);
}
/*
* pkt_resid will reflect, at this point, a residual of how many bytes
* were not transferred; a non-zero pkt_resid is an error.
*/
if (pkt->pkt_resid) {
bp->b_resid += pkt->pkt_resid;
}
if (status->sts_chk) {
if (ucmd->uscsi_flags & USCSI_RQENABLE) {
if (sg_state->sgen_arq_enabled) {
sgen_log(sg_state, SGEN_DIAG1,
"sgen_check_error: strange: target "
"indicates CHECK CONDITION with auto-sense "
"enabled.");
}
sgen_log(sg_state, SGEN_DIAG2, "sgen_check_error: "
"target ready for sense fetch");
return (FETCH_SENSE);
} else {
sgen_log(sg_state, SGEN_DIAG2, "sgen_check_error: "
"target indicates CHECK CONDITION");
}
}
return (COMMAND_DONE);
}
/*
* sgen_tur()
* test if a target is ready to operate by sending it a TUR command.
*/
static int
sgen_tur(dev_t dev)
{
char cmdblk[CDB_GROUP0];
struct uscsi_cmd scmd;
bzero(&scmd, sizeof (scmd));
scmd.uscsi_bufaddr = 0;
scmd.uscsi_buflen = 0;
bzero(cmdblk, CDB_GROUP0);
cmdblk[0] = (char)SCMD_TEST_UNIT_READY;
scmd.uscsi_flags = USCSI_DIAGNOSE | USCSI_SILENT | USCSI_WRITE;
scmd.uscsi_cdb = cmdblk;
scmd.uscsi_cdblen = CDB_GROUP0;
return (sgen_uscsi_cmd(dev, &scmd, FKIOCTL));
}
/*
* sgen_diag_ok()
* given an sg_state and a desired diagnostic level, return true if
* it is acceptable to output a message.
*/
/*ARGSUSED*/
static int
sgen_diag_ok(sgen_state_t *sg_state, int level)
{
int diag_lvl;
switch (level) {
case CE_WARN:
case CE_NOTE:
case CE_CONT:
case CE_PANIC:
return (1);
case SGEN_DIAG1:
case SGEN_DIAG2:
case SGEN_DIAG3:
if (sg_state) {
/*
* Check to see if user overrode the diagnostics level
* for this instance (either via SGEN_IOC_DIAG or via
* .conf file). If not, fall back to the global diag
* level.
*/
if (sg_state->sgen_diag != -1)
diag_lvl = sg_state->sgen_diag;
else
diag_lvl = sgen_diag;
} else {
diag_lvl = sgen_diag;
}
if (((diag_lvl << 8) | CE_CONT) >= level) {
return (1);
} else {
return (0);
}
default:
return (1);
}
}
/*PRINTFLIKE3*/
static void
sgen_log(sgen_state_t *sg_state, int level, const char *fmt, ...)
{
va_list ap;
char buf[256];
if (!sgen_diag_ok(sg_state, level))
return;
va_start(ap, fmt);
(void) vsnprintf(buf, sizeof (buf), fmt, ap);
va_end(ap);
switch (level) {
case CE_NOTE:
case CE_CONT:
case CE_WARN:
case CE_PANIC:
if (sg_state == (sgen_state_t *)NULL) {
cmn_err(level, "%s", buf);
} else {
scsi_log(sg_state->sgen_devinfo, "sgen", level,
"%s", buf);
}
break;
case SGEN_DIAG1:
case SGEN_DIAG2:
case SGEN_DIAG3:
default:
if (sg_state == (sgen_state_t *)NULL) {
scsi_log(NULL, "sgen", CE_CONT, "%s", buf);
} else {
scsi_log(sg_state->sgen_devinfo, "sgen", CE_CONT,
"%s", buf);
}
}
}
/*
* sgen_dump_cdb()
* dump out the contents of a cdb. Take care that 'label' is not too
* large, or 'buf' could overflow.
*/
static void
sgen_dump_cdb(sgen_state_t *sg_state, const char *label,
union scsi_cdb *cdb, int cdblen)
{
static char hex[] = "0123456789abcdef";
char *buf, *p;
size_t nbytes;
int i;
uchar_t *cdbp = (uchar_t *)cdb;
/*
* fastpath-- if we're not able to print out, don't do all of this
* extra work.
*/
if (!sgen_diag_ok(sg_state, SGEN_DIAG3))
return;
/*
* 3 characters for each byte (because of the ' '), plus the size of
* the label, plus the trailing ']' and the null character.
*/
nbytes = 3 * cdblen + strlen(label) + strlen(" CDB = [") + 2;
buf = kmem_alloc(nbytes, KM_SLEEP);
(void) sprintf(buf, "%s CDB = [", label);
p = &buf[strlen(buf)];
for (i = 0; i < cdblen; i++, cdbp++) {
if (i > 0)
*p++ = ' ';
*p++ = hex[(*cdbp >> 4) & 0x0f];
*p++ = hex[*cdbp & 0x0f];
}
*p++ = ']';
*p = 0;
sgen_log(sg_state, SGEN_DIAG3, buf);
kmem_free(buf, nbytes);
}
static void
sgen_dump_sense(sgen_state_t *sg_state, size_t rqlen, uchar_t *rqbuf)
{
static char hex[] = "0123456789abcdef";
char *buf, *p;
size_t nbytes;
int i;
/*
* fastpath-- if we're not able to print out, don't do all of this
* extra work.
*/
if (!sgen_diag_ok(sg_state, SGEN_DIAG3))
return;
/*
* 3 characters for each byte (because of the ' '), plus the size of
* the label, plus the trailing ']' and the null character.
*/
nbytes = 3 * rqlen + strlen(" SENSE = [") + 2;
buf = kmem_alloc(nbytes, KM_SLEEP);
(void) sprintf(buf, "SENSE = [");
p = &buf[strlen(buf)];
for (i = 0; i < rqlen; i++, rqbuf++) {
if (i > 0)
*p++ = ' ';
*p++ = hex[(*rqbuf >> 4) & 0x0f];
*p++ = hex[*rqbuf & 0x0f];
}
*p++ = ']';
*p = 0;
sgen_log(sg_state, SGEN_DIAG3, buf);
kmem_free(buf, nbytes);
}