| /* |
| * 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 2008 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| /* |
| * Copyright Siemens 1999 |
| * All rights reserved. |
| */ |
| |
| |
| /* |
| * 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> |
| |
| /* The name of the driver, established from the module name in _init. */ |
| static char *sgen_label = NULL; |
| |
| #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 int 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 *, int); |
| 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 */ |
| ddi_quiesce_not_supported, /* devo_quiesce */ |
| }; |
| |
| static void *sgen_soft_state = NULL; |
| |
| static struct modldrv modldrv = { |
| &mod_driverops, "SCSI generic driver", &sgen_dev_ops |
| }; |
| |
| static struct modlinkage modlinkage = { |
| MODREV_1, &modldrv, NULL |
| }; |
| |
| int |
| _init(void) |
| { |
| int err; |
| |
| /* establish driver name from module name */ |
| sgen_label = (char *)mod_modname(&modlinkage); |
| |
| 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 occurrence 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, "%s%d,err", |
| sgen_label, 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); |
| |
| /* |
| * Don't allow new opens of a suspended device until the last close has |
| * happened. This is rather simplistic, but keeps the implementation |
| * straightforward. |
| */ |
| if (SGEN_IS_SUSP(sg_state)) { |
| mutex_exit(&sg_state->sgen_mutex); |
| return (EIO); |
| } |
| |
| /* |
| * Enforce exclusive access. |
| */ |
| if (SGEN_IS_EXCL(sg_state) || |
| (SGEN_IS_OPEN(sg_state) && (flag & FEXCL))) { |
| mutex_exit(&sg_state->sgen_mutex); |
| return (EBUSY); |
| } |
| |
| if (flag & FEXCL) |
| SGEN_SET_EXCL(sg_state); |
| |
| SGEN_SET_OPEN(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_EXCL(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 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); |
| |
| /* |
| * At this point, we start affecting state relevant to the target, |
| * so access needs to be serialized. |
| */ |
| if (sgen_hold_cmdbuf(sg_state) != 0) { |
| sgen_log(sg_state, SGEN_DIAG1, "sgen_uscsi_cmd: interrupted"); |
| return (EINTR); |
| } |
| |
| err = scsi_uscsi_alloc_and_copyin((intptr_t)ucmd, flag, |
| &sg_state->sgen_scsiaddr, &uscmd); |
| if (err != 0) { |
| sgen_rele_cmdbuf(sg_state); |
| 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; |
| } |
| |
| 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. |
| */ |
| sgen_rele_cmdbuf(sg_state); |
| |
| sgen_log(sg_state, SGEN_DIAG2, "done sgen_uscsi_cmd()"); |
| |
| return (err); |
| } |
| |
| /* |
| * sgen_hold_cmdbuf() |
| * Acquire a lock on the command buffer for the given target. Returns |
| * non-zero if interrupted. |
| */ |
| static int |
| sgen_hold_cmdbuf(sgen_state_t *sg_state) |
| { |
| mutex_enter(&sg_state->sgen_mutex); |
| while (SGEN_IS_BUSY(sg_state)) { |
| if (!cv_wait_sig(&sg_state->sgen_cmdbuf_cv, |
| &sg_state->sgen_mutex)) { |
| mutex_exit(&sg_state->sgen_mutex); |
| return (-1); |
| } |
| } |
| SGEN_SET_BUSY(sg_state); |
| mutex_exit(&sg_state->sgen_mutex); |
| return (0); |
| } |
| |
| /* |
| * 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; |
| } |
| |
| /* Transfer uscsi information to scsi_pkt */ |
| (void) scsi_uscsi_pktinit(ucmd, pkt); |
| |
| 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 uscsi_cmd *ucmd; |
| 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)"); |
| } |
| ucmd = (struct uscsi_cmd *)bp->b_private; |
| |
| 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); |
| |
| /* Transfer scsi_pkt information to uscsi */ |
| (void) scsi_uscsi_pktfini(pkt, ucmd); |
| |
| 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, |
| scsi_pkt_allocated_correctly(pkt) ? |
| pkt->pkt_path_instance : 0) == 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, int path_instance) |
| { |
| /* use same path_instance as command */ |
| if (scsi_pkt_allocated_correctly(sg_state->sgen_rqspkt)) |
| sg_state->sgen_rqspkt->pkt_path_instance = path_instance; |
| |
| 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_label, 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_label, CE_CONT, "%s", buf); |
| } else { |
| scsi_log(sg_state->sgen_devinfo, sgen_label, 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); |
| } |