sf.c revision 193974072f41a843678abf5f61979c748687e66b
/*
* 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.
*/
/*
* sf - Solaris Fibre Channel driver
*
* This module implements some of the Fibre Channel FC-4 layer, converting
* from FC frames to SCSI and back. (Note: no sequence management is done
* here, though.)
*/
#if defined(lint) && !defined(DEBUG)
#define DEBUG 1
#endif
/*
* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
* Need to use the ugly RAID LUN mappings in FCP Annex D
* to prevent SCSA from barfing. This *REALLY* needs to
* be addressed by the standards committee.
*/
#define RAID_LUNS 1
#ifdef DEBUG
static int sfdebug = 0;
#include <sys/debug.h>
#define SF_DEBUG(level, args) \
if (sfdebug >= (level)) sf_log args
#else
#define SF_DEBUG(level, args)
#endif
static int sf_bus_config_debug = 0;
/* Why do I have to do this? */
#define offsetof(s, m) (size_t)(&(((s *)0)->m))
#include <sys/scsi/scsi.h>
#include <sys/fc4/fcal.h>
#include <sys/fc4/fcp.h>
#include <sys/fc4/fcal_linkapp.h>
#include <sys/socal_cq_defs.h>
#include <sys/fc4/fcal_transport.h>
#include <sys/fc4/fcio.h>
#include <sys/scsi/adapters/sfvar.h>
#include <sys/scsi/impl/scsi_reset_notify.h>
#include <sys/stat.h>
#include <sys/varargs.h>
#include <sys/var.h>
#include <sys/thread.h>
#include <sys/proc.h>
#include <sys/kstat.h>
#include <sys/devctl.h>
#include <sys/scsi/targets/ses.h>
#include <sys/callb.h>
static int sf_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int sf_attach(dev_info_t *, ddi_attach_cmd_t);
static int sf_detach(dev_info_t *, ddi_detach_cmd_t);
static void sf_softstate_unlink(struct sf *);
static int sf_scsi_bus_config(dev_info_t *parent, uint_t flag,
ddi_bus_config_op_t op, void *arg, dev_info_t **childp);
static int sf_scsi_bus_unconfig(dev_info_t *parent, uint_t flag,
ddi_bus_config_op_t op, void *arg);
static int sf_scsi_tgt_init(dev_info_t *, dev_info_t *,
scsi_hba_tran_t *, struct scsi_device *);
static void sf_scsi_tgt_free(dev_info_t *, dev_info_t *,
scsi_hba_tran_t *, struct scsi_device *);
static int sf_pkt_alloc_extern(struct sf *, struct sf_pkt *,
int, int, int);
static void sf_pkt_destroy_extern(struct sf *, struct sf_pkt *);
static struct scsi_pkt *sf_scsi_init_pkt(struct scsi_address *,
struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t);
static void sf_scsi_destroy_pkt(struct scsi_address *, struct scsi_pkt *);
static void sf_scsi_dmafree(struct scsi_address *, struct scsi_pkt *);
static void sf_scsi_sync_pkt(struct scsi_address *, struct scsi_pkt *);
static int sf_scsi_reset_notify(struct scsi_address *, int,
void (*)(caddr_t), caddr_t);
static int sf_scsi_get_name(struct scsi_device *, char *, int);
static int sf_scsi_get_bus_addr(struct scsi_device *, char *, int);
static int sf_add_cr_pool(struct sf *);
static int sf_cr_alloc(struct sf *, struct sf_pkt *, int (*)());
static void sf_cr_free(struct sf_cr_pool *, struct sf_pkt *);
static void sf_crpool_free(struct sf *);
static int sf_kmem_cache_constructor(void *, void *, int);
static void sf_kmem_cache_destructor(void *, void *);
static void sf_statec_callback(void *, int);
static int sf_login(struct sf *, uchar_t, uchar_t, uint_t, int);
static int sf_els_transport(struct sf *, struct sf_els_hdr *);
static void sf_els_callback(struct fcal_packet *);
static int sf_do_prli(struct sf *, struct sf_els_hdr *, struct la_els_logi *);
static int sf_do_adisc(struct sf *, struct sf_els_hdr *);
static int sf_do_reportlun(struct sf *, struct sf_els_hdr *,
struct sf_target *);
static void sf_reportlun_callback(struct fcal_packet *);
static int sf_do_inquiry(struct sf *, struct sf_els_hdr *,
struct sf_target *);
static void sf_inq_callback(struct fcal_packet *);
static struct fcal_packet *sf_els_alloc(struct sf *, uchar_t, int, int,
int, caddr_t *, caddr_t *);
static void sf_els_free(struct fcal_packet *);
static struct sf_target *sf_create_target(struct sf *,
struct sf_els_hdr *, int, int64_t);
#ifdef RAID_LUNS
static struct sf_target *sf_lookup_target(struct sf *, uchar_t *, int);
#else
static struct sf_target *sf_lookup_target(struct sf *, uchar_t *, int64_t);
#endif
static void sf_finish_init(struct sf *, int);
static void sf_offline_target(struct sf *, struct sf_target *);
static void sf_create_devinfo(struct sf *, struct sf_target *, int);
static int sf_create_props(dev_info_t *, struct sf_target *, int);
static int sf_commoncap(struct scsi_address *, char *, int, int, int);
static int sf_getcap(struct scsi_address *, char *, int);
static int sf_setcap(struct scsi_address *, char *, int, int);
static int sf_abort(struct scsi_address *, struct scsi_pkt *);
static int sf_reset(struct scsi_address *, int);
static void sf_abort_all(struct sf *, struct sf_target *, int, int, int);
static int sf_start(struct scsi_address *, struct scsi_pkt *);
static int sf_start_internal(struct sf *, struct sf_pkt *);
static void sf_fill_ids(struct sf *, struct sf_pkt *, struct sf_target *);
static int sf_prepare_pkt(struct sf *, struct sf_pkt *, struct sf_target *);
static int sf_dopoll(struct sf *, struct sf_pkt *);
static void sf_cmd_callback(struct fcal_packet *);
static void sf_throttle(struct sf *);
static void sf_watch(void *);
static void sf_throttle_start(struct sf *);
static void sf_check_targets(struct sf *);
static void sf_check_reset_delay(void *);
static int sf_target_timeout(struct sf *, struct sf_pkt *);
static void sf_force_lip(struct sf *);
static void sf_unsol_els_callback(void *, soc_response_t *, caddr_t);
static struct sf_els_hdr *sf_els_timeout(struct sf *, struct sf_els_hdr *);
/*PRINTFLIKE3*/
static void sf_log(struct sf *, int, const char *, ...);
static int sf_kstat_update(kstat_t *, int);
static int sf_open(dev_t *, int, int, cred_t *);
static int sf_close(dev_t, int, int, cred_t *);
static int sf_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
static struct sf_target *sf_get_target_from_dip(struct sf *, dev_info_t *);
static int sf_bus_get_eventcookie(dev_info_t *, dev_info_t *, char *,
ddi_eventcookie_t *);
static int sf_bus_add_eventcall(dev_info_t *, dev_info_t *,
ddi_eventcookie_t, void (*)(), void *, ddi_callback_id_t *cb_id);
static int sf_bus_remove_eventcall(dev_info_t *devi, ddi_callback_id_t cb_id);
static int sf_bus_post_event(dev_info_t *, dev_info_t *,
ddi_eventcookie_t, void *);
static void sf_hp_daemon(void *);
/*
* this is required to be able to supply a control node
* where ioctls can be executed
*/
struct cb_ops sf_cb_ops = {
sf_open, /* open */
sf_close, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
sf_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 flags */
};
/*
* autoconfiguration routines.
*/
static struct dev_ops sf_ops = {
DEVO_REV, /* devo_rev, */
0, /* refcnt */
sf_info, /* info */
nulldev, /* identify */
nulldev, /* probe */
sf_attach, /* attach */
sf_detach, /* detach */
nodev, /* reset */
&sf_cb_ops, /* driver operations */
NULL, /* bus operations */
NULL, /* power management */
ddi_quiesce_not_supported, /* devo_quiesce */
};
/* to ensure this module gets loaded in memory when we do */
char _depends_on[] = "misc/scsi";
#define SF_NAME "FC-AL FCP Nexus Driver" /* Name of the module. */
static char sf_version[] = "1.72 08/19/2008"; /* version of the module */
static struct modldrv modldrv = {
&mod_driverops, /* Type of module. This one is a driver */
SF_NAME,
&sf_ops, /* driver ops */
};
static struct modlinkage modlinkage = {
MODREV_1, (void *)&modldrv, NULL
};
/* XXXXXX The following is here to handle broken targets -- remove it later */
static int sf_reportlun_forever = 0;
/* XXXXXX */
static int sf_lip_on_plogo = 0;
static int sf_els_retries = SF_ELS_RETRIES;
static struct sf *sf_head = NULL;
static int sf_target_scan_cnt = 4;
static int sf_pkt_scan_cnt = 5;
static int sf_pool_scan_cnt = 1800;
static void *sf_state = NULL;
static int sf_watchdog_init = 0;
static int sf_watchdog_time = 0;
static int sf_watchdog_timeout = 1;
static int sf_watchdog_tick;
static int sf_watch_running = 0;
static timeout_id_t sf_watchdog_id;
static timeout_id_t sf_reset_timeout_id;
static int sf_max_targets = SF_MAX_TARGETS;
static kmutex_t sf_global_mutex;
static int sf_core = 0;
int *sf_token = NULL; /* Must not be static or lint complains. */
static kcondvar_t sf_watch_cv;
extern pri_t minclsyspri;
static ddi_eventcookie_t sf_insert_eid;
static ddi_eventcookie_t sf_remove_eid;
static ndi_event_definition_t sf_event_defs[] = {
{ SF_EVENT_TAG_INSERT, FCAL_INSERT_EVENT, EPL_KERNEL, 0 },
{ SF_EVENT_TAG_REMOVE, FCAL_REMOVE_EVENT, EPL_INTERRUPT, 0 }
};
#define SF_N_NDI_EVENTS \
(sizeof (sf_event_defs) / sizeof (ndi_event_definition_t))
#ifdef DEBUG
static int sf_lip_flag = 1; /* bool: to allow LIPs */
static int sf_reset_flag = 1; /* bool: to allow reset after LIP */
static int sf_abort_flag = 0; /* bool: to do just one abort */
#endif
extern volatile int64_t lbolt64;
/*
* for converting between target number (switch) and hard address/AL_PA
*/
static uchar_t sf_switch_to_alpa[] = {
0xef, 0xe8, 0xe4, 0xe2, 0xe1, 0xe0, 0xdc, 0xda, 0xd9, 0xd6,
0xd5, 0xd4, 0xd3, 0xd2, 0xd1, 0xce, 0xcd, 0xcc, 0xcb, 0xca,
0xc9, 0xc7, 0xc6, 0xc5, 0xc3, 0xbc, 0xba, 0xb9, 0xb6, 0xb5,
0xb4, 0xb3, 0xb2, 0xb1, 0xae, 0xad, 0xac, 0xab, 0xaa, 0xa9,
0xa7, 0xa6, 0xa5, 0xa3, 0x9f, 0x9e, 0x9d, 0x9b, 0x98, 0x97,
0x90, 0x8f, 0x88, 0x84, 0x82, 0x81, 0x80, 0x7c, 0x7a, 0x79,
0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x6e, 0x6d, 0x6c, 0x6b,
0x6a, 0x69, 0x67, 0x66, 0x65, 0x63, 0x5c, 0x5a, 0x59, 0x56,
0x55, 0x54, 0x53, 0x52, 0x51, 0x4e, 0x4d, 0x4c, 0x4b, 0x4a,
0x49, 0x47, 0x46, 0x45, 0x43, 0x3c, 0x3a, 0x39, 0x36, 0x35,
0x34, 0x33, 0x32, 0x31, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29,
0x27, 0x26, 0x25, 0x23, 0x1f, 0x1e, 0x1d, 0x1b, 0x18, 0x17,
0x10, 0x0f, 0x08, 0x04, 0x02, 0x01
};
static uchar_t sf_alpa_to_switch[] = {
0x00, 0x7d, 0x7c, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x7a, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x78, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x77, 0x76, 0x00, 0x00, 0x75, 0x00, 0x74,
0x73, 0x72, 0x00, 0x00, 0x00, 0x71, 0x00, 0x70, 0x6f, 0x6e,
0x00, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, 0x00, 0x00, 0x67,
0x66, 0x65, 0x64, 0x63, 0x62, 0x00, 0x00, 0x61, 0x60, 0x00,
0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x5d,
0x5c, 0x5b, 0x00, 0x5a, 0x59, 0x58, 0x57, 0x56, 0x55, 0x00,
0x00, 0x54, 0x53, 0x52, 0x51, 0x50, 0x4f, 0x00, 0x00, 0x4e,
0x4d, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4b,
0x00, 0x4a, 0x49, 0x48, 0x00, 0x47, 0x46, 0x45, 0x44, 0x43,
0x42, 0x00, 0x00, 0x41, 0x40, 0x3f, 0x3e, 0x3d, 0x3c, 0x00,
0x00, 0x3b, 0x3a, 0x00, 0x39, 0x00, 0x00, 0x00, 0x38, 0x37,
0x36, 0x00, 0x35, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x33, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x31, 0x30, 0x00, 0x00, 0x2f, 0x00, 0x2e, 0x2d, 0x2c,
0x00, 0x00, 0x00, 0x2b, 0x00, 0x2a, 0x29, 0x28, 0x00, 0x27,
0x26, 0x25, 0x24, 0x23, 0x22, 0x00, 0x00, 0x21, 0x20, 0x1f,
0x1e, 0x1d, 0x1c, 0x00, 0x00, 0x1b, 0x1a, 0x00, 0x19, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x17, 0x16, 0x15,
0x00, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x00, 0x00, 0x0e,
0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x00, 0x00, 0x08, 0x07, 0x00,
0x06, 0x00, 0x00, 0x00, 0x05, 0x04, 0x03, 0x00, 0x02, 0x00,
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
/*
* these macros call the proper transport-layer function given
* a particular transport
*/
#define soc_transport(a, b, c, d) (*a->fcal_ops->fcal_transport)(b, c, d)
#define soc_transport_poll(a, b, c, d)\
(*a->fcal_ops->fcal_transport_poll)(b, c, d)
#define soc_get_lilp_map(a, b, c, d, e)\
(*a->fcal_ops->fcal_lilp_map)(b, c, d, e)
#define soc_force_lip(a, b, c, d, e)\
(*a->fcal_ops->fcal_force_lip)(b, c, d, e)
#define soc_abort(a, b, c, d, e)\
(*a->fcal_ops->fcal_abort_cmd)(b, c, d, e)
#define soc_force_reset(a, b, c, d)\
(*a->fcal_ops->fcal_force_reset)(b, c, d)
#define soc_add_ulp(a, b, c, d, e, f, g, h)\
(*a->fcal_ops->fcal_add_ulp)(b, c, d, e, f, g, h)
#define soc_remove_ulp(a, b, c, d, e)\
(*a->fcal_ops->fcal_remove_ulp)(b, c, d, e)
#define soc_take_core(a, b) (*a->fcal_ops->fcal_take_core)(b)
/* power management property defines (should be in a common include file?) */
#define PM_HARDWARE_STATE_PROP "pm-hardware-state"
#define PM_NEEDS_SUSPEND_RESUME "needs-suspend-resume"
/* node properties */
#define NODE_WWN_PROP "node-wwn"
#define PORT_WWN_PROP "port-wwn"
#define LIP_CNT_PROP "lip-count"
#define TARGET_PROP "target"
#define LUN_PROP "lun"
/*
* initialize this driver and install this module
*/
int
_init(void)
{
int i;
i = ddi_soft_state_init(&sf_state, sizeof (struct sf),
SF_INIT_ITEMS);
if (i != 0)
return (i);
if ((i = scsi_hba_init(&modlinkage)) != 0) {
ddi_soft_state_fini(&sf_state);
return (i);
}
mutex_init(&sf_global_mutex, NULL, MUTEX_DRIVER, NULL);
sf_watch_running = 0;
cv_init(&sf_watch_cv, NULL, CV_DRIVER, NULL);
if ((i = mod_install(&modlinkage)) != 0) {
mutex_destroy(&sf_global_mutex);
cv_destroy(&sf_watch_cv);
scsi_hba_fini(&modlinkage);
ddi_soft_state_fini(&sf_state);
return (i);
}
return (i);
}
/*
* remove this driver module from the system
*/
int
_fini(void)
{
int i;
if ((i = mod_remove(&modlinkage)) == 0) {
scsi_hba_fini(&modlinkage);
mutex_destroy(&sf_global_mutex);
cv_destroy(&sf_watch_cv);
ddi_soft_state_fini(&sf_state);
}
return (i);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
/*
* Given the device number return the devinfo pointer or instance
*/
/*ARGSUSED*/
static int
sf_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
int instance = SF_MINOR2INST(getminor((dev_t)arg));
struct sf *sf;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
sf = ddi_get_soft_state(sf_state, instance);
if (sf != NULL)
*result = sf->sf_dip;
else {
*result = NULL;
return (DDI_FAILURE);
}
break;
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)(uintptr_t)instance;
break;
default:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* either attach or resume this driver
*/
static int
sf_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int instance;
int mutex_initted = FALSE;
uint_t ccount;
size_t i, real_size;
struct fcal_transport *handle;
char buf[64];
struct sf *sf, *tsf;
scsi_hba_tran_t *tran = NULL;
int handle_bound = FALSE;
kthread_t *tp;
switch ((int)cmd) {
case DDI_RESUME:
/*
* we've previously been SF_STATE_OFFLINEd by a DDI_SUSPEND,
* so time to undo that and get going again by forcing a
* lip
*/
instance = ddi_get_instance(dip);
sf = ddi_get_soft_state(sf_state, instance);
SF_DEBUG(2, (sf, CE_CONT,
"sf_attach: DDI_RESUME for sf%d\n", instance));
if (sf == NULL) {
cmn_err(CE_WARN, "sf%d: bad soft state", instance);
return (DDI_FAILURE);
}
/*
* clear suspended flag so that normal operations can resume
*/
mutex_enter(&sf->sf_mutex);
sf->sf_state &= ~SF_STATE_SUSPENDED;
mutex_exit(&sf->sf_mutex);
/*
* force a login by setting our state to offline
*/
sf->sf_timer = sf_watchdog_time + SF_OFFLINE_TIMEOUT;
sf->sf_state = SF_STATE_OFFLINE;
/*
* call transport routine to register state change and
* ELS callback routines (to register us as a ULP)
*/
soc_add_ulp(sf->sf_sochandle, sf->sf_socp,
sf->sf_sochandle->fcal_portno, TYPE_SCSI_FCP,
sf_statec_callback, sf_unsol_els_callback, NULL, sf);
/*
* call transport routine to force loop initialization
*/
(void) soc_force_lip(sf->sf_sochandle, sf->sf_socp,
sf->sf_sochandle->fcal_portno, 0, FCAL_NO_LIP);
/*
* increment watchdog init flag, setting watchdog timeout
* if we are the first (since somebody has to do it)
*/
mutex_enter(&sf_global_mutex);
if (!sf_watchdog_init++) {
mutex_exit(&sf_global_mutex);
sf_watchdog_id = timeout(sf_watch,
(caddr_t)0, sf_watchdog_tick);
} else {
mutex_exit(&sf_global_mutex);
}
return (DDI_SUCCESS);
case DDI_ATTACH:
/*
* this instance attaching for the first time
*/
instance = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(sf_state, instance) !=
DDI_SUCCESS) {
cmn_err(CE_WARN, "sf%d: failed to allocate soft state",
instance);
return (DDI_FAILURE);
}
sf = ddi_get_soft_state(sf_state, instance);
SF_DEBUG(4, (sf, CE_CONT,
"sf_attach: DDI_ATTACH for sf%d\n", instance));
if (sf == NULL) {
/* this shouldn't happen since we just allocated it */
cmn_err(CE_WARN, "sf%d: bad soft state", instance);
return (DDI_FAILURE);
}
/*
* from this point on, if there's an error, we must de-allocate
* soft state before returning DDI_FAILURE
*/
if ((handle = ddi_get_parent_data(dip)) == NULL) {
cmn_err(CE_WARN,
"sf%d: failed to obtain transport handle",
instance);
goto fail;
}
/* fill in our soft state structure */
sf->sf_dip = dip;
sf->sf_state = SF_STATE_INIT;
sf->sf_throttle = handle->fcal_cmdmax;
sf->sf_sochandle = handle;
sf->sf_socp = handle->fcal_handle;
sf->sf_check_n_close = 0;
/* create a command/response buffer pool for this instance */
if (sf_add_cr_pool(sf) != DDI_SUCCESS) {
cmn_err(CE_WARN,
"sf%d: failed to allocate command/response pool",
instance);
goto fail;
}
/* create a a cache for this instance */
(void) sprintf(buf, "sf%d_cache", instance);
sf->sf_pkt_cache = kmem_cache_create(buf,
sizeof (fcal_packet_t) + sizeof (struct sf_pkt) +
scsi_pkt_size(), 8,
sf_kmem_cache_constructor, sf_kmem_cache_destructor,
NULL, NULL, NULL, 0);
if (sf->sf_pkt_cache == NULL) {
cmn_err(CE_WARN, "sf%d: failed to allocate kmem cache",
instance);
goto fail;
}
/* set up a handle and allocate memory for DMA */
if (ddi_dma_alloc_handle(sf->sf_dip, sf->sf_sochandle->
fcal_dmaattr, DDI_DMA_DONTWAIT, NULL, &sf->
sf_lilp_dmahandle) != DDI_SUCCESS) {
cmn_err(CE_WARN,
"sf%d: failed to allocate dma handle for lilp map",
instance);
goto fail;
}
i = sizeof (struct fcal_lilp_map) + 1;
if (ddi_dma_mem_alloc(sf->sf_lilp_dmahandle,
i, sf->sf_sochandle->
fcal_accattr, DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL,
(caddr_t *)&sf->sf_lilp_map, &real_size,
&sf->sf_lilp_acchandle) != DDI_SUCCESS) {
cmn_err(CE_WARN, "sf%d: failed to allocate lilp map",
instance);
goto fail;
}
if (real_size < i) {
/* no error message ??? */
goto fail; /* trouble allocating memory */
}
/*
* set up the address for the DMA transfers (getting a cookie)
*/
if (ddi_dma_addr_bind_handle(sf->sf_lilp_dmahandle, NULL,
(caddr_t)sf->sf_lilp_map, real_size,
DDI_DMA_READ | DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL,
&sf->sf_lilp_dmacookie, &ccount) != DDI_DMA_MAPPED) {
cmn_err(CE_WARN,
"sf%d: failed to bind dma handle for lilp map",
instance);
goto fail;
}
handle_bound = TRUE;
/* ensure only one cookie was allocated */
if (ccount != 1) {
goto fail;
}
/* ensure LILP map and DMA cookie addresses are even?? */
sf->sf_lilp_map = (struct fcal_lilp_map *)(((uintptr_t)sf->
sf_lilp_map + 1) & ~1);
sf->sf_lilp_dmacookie.dmac_address = (sf->
sf_lilp_dmacookie.dmac_address + 1) & ~1;
/* set up all of our mutexes and condition variables */
mutex_init(&sf->sf_mutex, NULL, MUTEX_DRIVER, NULL);
mutex_init(&sf->sf_cmd_mutex, NULL, MUTEX_DRIVER, NULL);
mutex_init(&sf->sf_cr_mutex, NULL, MUTEX_DRIVER, NULL);
mutex_init(&sf->sf_hp_daemon_mutex, NULL, MUTEX_DRIVER, NULL);
cv_init(&sf->sf_cr_cv, NULL, CV_DRIVER, NULL);
cv_init(&sf->sf_hp_daemon_cv, NULL, CV_DRIVER, NULL);
mutex_initted = TRUE;
/* create our devctl minor node */
if (ddi_create_minor_node(dip, "devctl", S_IFCHR,
SF_INST2DEVCTL_MINOR(instance),
DDI_NT_NEXUS, 0) != DDI_SUCCESS) {
cmn_err(CE_WARN, "sf%d: ddi_create_minor_node failed"
" for devctl", instance);
goto fail;
}
/* create fc minor node */
if (ddi_create_minor_node(dip, "fc", S_IFCHR,
SF_INST2FC_MINOR(instance), DDI_NT_FC_ATTACHMENT_POINT,
0) != DDI_SUCCESS) {
cmn_err(CE_WARN, "sf%d: ddi_create_minor_node failed"
" for fc", instance);
goto fail;
}
/* allocate a SCSI transport structure */
tran = scsi_hba_tran_alloc(dip, 0);
if (tran == NULL) {
/* remove all minor nodes created */
ddi_remove_minor_node(dip, NULL);
cmn_err(CE_WARN, "sf%d: scsi_hba_tran_alloc failed",
instance);
goto fail;
}
/* Indicate that we are 'sizeof (scsi_*(9S))' clean. */
scsi_size_clean(dip); /* SCSI_SIZE_CLEAN_VERIFY ok */
/* save ptr to new transport structure and fill it in */
sf->sf_tran = tran;
tran->tran_hba_private = sf;
tran->tran_tgt_private = NULL;
tran->tran_tgt_init = sf_scsi_tgt_init;
tran->tran_tgt_probe = NULL;
tran->tran_tgt_free = sf_scsi_tgt_free;
tran->tran_start = sf_start;
tran->tran_abort = sf_abort;
tran->tran_reset = sf_reset;
tran->tran_getcap = sf_getcap;
tran->tran_setcap = sf_setcap;
tran->tran_init_pkt = sf_scsi_init_pkt;
tran->tran_destroy_pkt = sf_scsi_destroy_pkt;
tran->tran_dmafree = sf_scsi_dmafree;
tran->tran_sync_pkt = sf_scsi_sync_pkt;
tran->tran_reset_notify = sf_scsi_reset_notify;
/*
* register event notification routines with scsa
*/
tran->tran_get_eventcookie = sf_bus_get_eventcookie;
tran->tran_add_eventcall = sf_bus_add_eventcall;
tran->tran_remove_eventcall = sf_bus_remove_eventcall;
tran->tran_post_event = sf_bus_post_event;
/*
* register bus configure/unconfigure
*/
tran->tran_bus_config = sf_scsi_bus_config;
tran->tran_bus_unconfig = sf_scsi_bus_unconfig;
/*
* allocate an ndi event handle
*/
sf->sf_event_defs = (ndi_event_definition_t *)
kmem_zalloc(sizeof (sf_event_defs), KM_SLEEP);
bcopy(sf_event_defs, sf->sf_event_defs,
sizeof (sf_event_defs));
(void) ndi_event_alloc_hdl(dip, NULL,
&sf->sf_event_hdl, NDI_SLEEP);
sf->sf_events.ndi_events_version = NDI_EVENTS_REV1;
sf->sf_events.ndi_n_events = SF_N_NDI_EVENTS;
sf->sf_events.ndi_event_defs = sf->sf_event_defs;
if (ndi_event_bind_set(sf->sf_event_hdl,
&sf->sf_events, NDI_SLEEP) != NDI_SUCCESS) {
goto fail;
}
tran->tran_get_name = sf_scsi_get_name;
tran->tran_get_bus_addr = sf_scsi_get_bus_addr;
/* setup and attach SCSI hba transport */
if (scsi_hba_attach_setup(dip, sf->sf_sochandle->
fcal_dmaattr, tran, SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) {
cmn_err(CE_WARN, "sf%d: scsi_hba_attach_setup failed",
instance);
goto fail;
}
/* set up kstats */
if ((sf->sf_ksp = kstat_create("sf", instance, "statistics",
"controller", KSTAT_TYPE_RAW, sizeof (struct sf_stats),
KSTAT_FLAG_VIRTUAL)) == NULL) {
cmn_err(CE_WARN, "sf%d: failed to create kstat",
instance);
} else {
sf->sf_stats.version = 2;
(void) sprintf(sf->sf_stats.drvr_name,
"%s: %s", SF_NAME, sf_version);
sf->sf_ksp->ks_data = (void *)&sf->sf_stats;
sf->sf_ksp->ks_private = sf;
sf->sf_ksp->ks_update = sf_kstat_update;
kstat_install(sf->sf_ksp);
}
/* create the hotplug thread */
mutex_enter(&sf->sf_hp_daemon_mutex);
tp = thread_create(NULL, 0,
(void (*)())sf_hp_daemon, sf, 0, &p0, TS_RUN, minclsyspri);
sf->sf_hp_tid = tp->t_did;
mutex_exit(&sf->sf_hp_daemon_mutex);
/* add this soft state instance to the head of the list */
mutex_enter(&sf_global_mutex);
sf->sf_next = sf_head;
tsf = sf_head;
sf_head = sf;
/*
* find entry in list that has the same FC-AL handle (if any)
*/
while (tsf != NULL) {
if (tsf->sf_socp == sf->sf_socp) {
break; /* found matching entry */
}
tsf = tsf->sf_next;
}
if (tsf != NULL) {
/* if we found a matching entry keep track of it */
sf->sf_sibling = tsf;
}
/*
* increment watchdog init flag, setting watchdog timeout
* if we are the first (since somebody has to do it)
*/
if (!sf_watchdog_init++) {
mutex_exit(&sf_global_mutex);
sf_watchdog_tick = sf_watchdog_timeout *
drv_usectohz(1000000);
sf_watchdog_id = timeout(sf_watch,
NULL, sf_watchdog_tick);
} else {
mutex_exit(&sf_global_mutex);
}
if (tsf != NULL) {
/*
* set up matching entry to be our sibling
*/
mutex_enter(&tsf->sf_mutex);
tsf->sf_sibling = sf;
mutex_exit(&tsf->sf_mutex);
}
/*
* create this property so that PM code knows we want
* to be suspended at PM time
*/
(void) ddi_prop_update_string(DDI_DEV_T_NONE, dip,
PM_HARDWARE_STATE_PROP, PM_NEEDS_SUSPEND_RESUME);
/* log the fact that we have a new device */
ddi_report_dev(dip);
/*
* force a login by setting our state to offline
*/
sf->sf_timer = sf_watchdog_time + SF_OFFLINE_TIMEOUT;
sf->sf_state = SF_STATE_OFFLINE;
/*
* call transport routine to register state change and
* ELS callback routines (to register us as a ULP)
*/
soc_add_ulp(sf->sf_sochandle, sf->sf_socp,
sf->sf_sochandle->fcal_portno, TYPE_SCSI_FCP,
sf_statec_callback, sf_unsol_els_callback, NULL, sf);
/*
* call transport routine to force loop initialization
*/
(void) soc_force_lip(sf->sf_sochandle, sf->sf_socp,
sf->sf_sochandle->fcal_portno, 0, FCAL_NO_LIP);
sf->sf_reset_time = lbolt64;
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
fail:
cmn_err(CE_WARN, "sf%d: failed to attach", instance);
/*
* Unbind and free event set
*/
if (sf->sf_event_hdl) {
(void) ndi_event_unbind_set(sf->sf_event_hdl,
&sf->sf_events, NDI_SLEEP);
(void) ndi_event_free_hdl(sf->sf_event_hdl);
}
if (sf->sf_event_defs) {
kmem_free(sf->sf_event_defs, sizeof (sf_event_defs));
}
if (sf->sf_tran != NULL) {
scsi_hba_tran_free(sf->sf_tran);
}
while (sf->sf_cr_pool != NULL) {
sf_crpool_free(sf);
}
if (sf->sf_lilp_dmahandle != NULL) {
if (handle_bound) {
(void) ddi_dma_unbind_handle(sf->sf_lilp_dmahandle);
}
ddi_dma_free_handle(&sf->sf_lilp_dmahandle);
}
if (sf->sf_pkt_cache != NULL) {
kmem_cache_destroy(sf->sf_pkt_cache);
}
if (sf->sf_lilp_map != NULL) {
ddi_dma_mem_free(&sf->sf_lilp_acchandle);
}
if (sf->sf_ksp != NULL) {
kstat_delete(sf->sf_ksp);
}
if (mutex_initted) {
mutex_destroy(&sf->sf_mutex);
mutex_destroy(&sf->sf_cmd_mutex);
mutex_destroy(&sf->sf_cr_mutex);
mutex_destroy(&sf->sf_hp_daemon_mutex);
cv_destroy(&sf->sf_cr_cv);
cv_destroy(&sf->sf_hp_daemon_cv);
}
mutex_enter(&sf_global_mutex);
/*
* kill off the watchdog if we are the last instance
*/
if (!--sf_watchdog_init) {
timeout_id_t tid = sf_watchdog_id;
mutex_exit(&sf_global_mutex);
(void) untimeout(tid);
} else {
mutex_exit(&sf_global_mutex);
}
ddi_soft_state_free(sf_state, instance);
if (tran != NULL) {
/* remove all minor nodes */
ddi_remove_minor_node(dip, NULL);
}
return (DDI_FAILURE);
}
/* ARGSUSED */
static int
sf_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
struct sf *sf;
int instance;
int i;
struct sf_target *target;
timeout_id_t tid;
/* NO OTHER THREADS ARE RUNNING */
instance = ddi_get_instance(dip);
if ((sf = ddi_get_soft_state(sf_state, instance)) == NULL) {
cmn_err(CE_WARN, "sf_detach, sf%d: bad soft state", instance);
return (DDI_FAILURE);
}
switch (cmd) {
case DDI_SUSPEND:
/*
* suspend our instance
*/
SF_DEBUG(2, (sf, CE_CONT,
"sf_detach: DDI_SUSPEND for sf%d\n", instance));
/*
* There is a race condition in socal where while doing
* callbacks if a ULP removes it self from the callback list
* the for loop in socal may panic as cblist is junk and
* while trying to get cblist->next the system will panic.
*/
/* call transport to remove our unregister our callbacks */
soc_remove_ulp(sf->sf_sochandle, sf->sf_socp,
sf->sf_sochandle->fcal_portno, TYPE_SCSI_FCP, sf);
/*
* begin process of clearing outstanding commands
* by issuing a lip
*/
sf_force_lip(sf);
/*
* toggle the device OFFLINE in order to cause
* outstanding commands to drain
*/
mutex_enter(&sf->sf_mutex);
sf->sf_lip_cnt++;
sf->sf_timer = sf_watchdog_time + SF_OFFLINE_TIMEOUT;
sf->sf_state = (SF_STATE_OFFLINE | SF_STATE_SUSPENDED);
for (i = 0; i < sf_max_targets; i++) {
target = sf->sf_targets[i];
if (target != NULL) {
struct sf_target *ntarget;
mutex_enter(&target->sft_mutex);
if (!(target->sft_state & SF_TARGET_OFFLINE)) {
target->sft_state |=
(SF_TARGET_BUSY | SF_TARGET_MARK);
}
/* do this for all LUNs as well */
for (ntarget = target->sft_next_lun;
ntarget;
ntarget = ntarget->sft_next_lun) {
mutex_enter(&ntarget->sft_mutex);
if (!(ntarget->sft_state &
SF_TARGET_OFFLINE)) {
ntarget->sft_state |=
(SF_TARGET_BUSY |
SF_TARGET_MARK);
}
mutex_exit(&ntarget->sft_mutex);
}
mutex_exit(&target->sft_mutex);
}
}
mutex_exit(&sf->sf_mutex);
mutex_enter(&sf_global_mutex);
/*
* kill off the watchdog if we are the last instance
*/
if (!--sf_watchdog_init) {
tid = sf_watchdog_id;
mutex_exit(&sf_global_mutex);
(void) untimeout(tid);
} else {
mutex_exit(&sf_global_mutex);
}
return (DDI_SUCCESS);
case DDI_DETACH:
/*
* detach this instance
*/
SF_DEBUG(2, (sf, CE_CONT,
"sf_detach: DDI_DETACH for sf%d\n", instance));
/* remove this "sf" from the list of sf softstates */
sf_softstate_unlink(sf);
/*
* prior to taking any DDI_DETACH actions, toggle the
* device OFFLINE in order to cause outstanding
* commands to drain
*/
mutex_enter(&sf->sf_mutex);
sf->sf_lip_cnt++;
sf->sf_timer = sf_watchdog_time + SF_OFFLINE_TIMEOUT;
sf->sf_state = SF_STATE_OFFLINE;
for (i = 0; i < sf_max_targets; i++) {
target = sf->sf_targets[i];
if (target != NULL) {
struct sf_target *ntarget;
mutex_enter(&target->sft_mutex);
if (!(target->sft_state & SF_TARGET_OFFLINE)) {
target->sft_state |=
(SF_TARGET_BUSY | SF_TARGET_MARK);
}
for (ntarget = target->sft_next_lun;
ntarget;
ntarget = ntarget->sft_next_lun) {
mutex_enter(&ntarget->sft_mutex);
if (!(ntarget->sft_state &
SF_TARGET_OFFLINE)) {
ntarget->sft_state |=
(SF_TARGET_BUSY |
SF_TARGET_MARK);
}
mutex_exit(&ntarget->sft_mutex);
}
mutex_exit(&target->sft_mutex);
}
}
mutex_exit(&sf->sf_mutex);
/* call transport to remove and unregister our callbacks */
soc_remove_ulp(sf->sf_sochandle, sf->sf_socp,
sf->sf_sochandle->fcal_portno, TYPE_SCSI_FCP, sf);
/*
* kill off the watchdog if we are the last instance
*/
mutex_enter(&sf_global_mutex);
if (!--sf_watchdog_init) {
tid = sf_watchdog_id;
mutex_exit(&sf_global_mutex);
(void) untimeout(tid);
} else {
mutex_exit(&sf_global_mutex);
}
/* signal sf_hp_daemon() to exit and wait for exit */
mutex_enter(&sf->sf_hp_daemon_mutex);
ASSERT(sf->sf_hp_tid);
sf->sf_hp_exit = 1; /* flag exit */
cv_signal(&sf->sf_hp_daemon_cv);
mutex_exit(&sf->sf_hp_daemon_mutex);
thread_join(sf->sf_hp_tid); /* wait for hotplug to exit */
/*
* Unbind and free event set
*/
if (sf->sf_event_hdl) {
(void) ndi_event_unbind_set(sf->sf_event_hdl,
&sf->sf_events, NDI_SLEEP);
(void) ndi_event_free_hdl(sf->sf_event_hdl);
}
if (sf->sf_event_defs) {
kmem_free(sf->sf_event_defs, sizeof (sf_event_defs));
}
/* detach this instance of the HBA driver */
scsi_hba_detach(dip);
scsi_hba_tran_free(sf->sf_tran);
/* deallocate/unbind DMA handle for lilp map */
if (sf->sf_lilp_map != NULL) {
(void) ddi_dma_unbind_handle(sf->sf_lilp_dmahandle);
if (sf->sf_lilp_dmahandle != NULL) {
ddi_dma_free_handle(&sf->sf_lilp_dmahandle);
}
ddi_dma_mem_free(&sf->sf_lilp_acchandle);
}
/*
* the kmem cache must be destroyed before free'ing
* up the crpools
*
* our finagle of "ntot" and "nfree"
* causes an ASSERT failure in "sf_cr_free()"
* if the kmem cache is free'd after invoking
* "sf_crpool_free()".
*/
kmem_cache_destroy(sf->sf_pkt_cache);
SF_DEBUG(2, (sf, CE_CONT,
"sf_detach: sf_crpool_free() for instance 0x%x\n",
instance));
while (sf->sf_cr_pool != NULL) {
/*
* set ntot to nfree for this particular entry
*
* this causes sf_crpool_free() to update
* the cr_pool list when deallocating this entry
*/
sf->sf_cr_pool->ntot = sf->sf_cr_pool->nfree;
sf_crpool_free(sf);
}
/*
* now that the cr_pool's are gone it's safe
* to destroy all softstate mutex's and cv's
*/
mutex_destroy(&sf->sf_mutex);
mutex_destroy(&sf->sf_cmd_mutex);
mutex_destroy(&sf->sf_cr_mutex);
mutex_destroy(&sf->sf_hp_daemon_mutex);
cv_destroy(&sf->sf_cr_cv);
cv_destroy(&sf->sf_hp_daemon_cv);
/* remove all minor nodes from the device tree */
ddi_remove_minor_node(dip, NULL);
/* remove properties created during attach() */
ddi_prop_remove_all(dip);
/* remove kstat's if present */
if (sf->sf_ksp != NULL) {
kstat_delete(sf->sf_ksp);
}
SF_DEBUG(2, (sf, CE_CONT,
"sf_detach: ddi_soft_state_free() for instance 0x%x\n",
instance));
ddi_soft_state_free(sf_state, instance);
return (DDI_SUCCESS);
default:
SF_DEBUG(2, (sf, CE_CONT, "sf_detach: sf%d unknown cmd %x\n",
instance, (int)cmd));
return (DDI_FAILURE);
}
}
/*
* sf_softstate_unlink() - remove an sf instance from the list of softstates
*/
static void
sf_softstate_unlink(struct sf *sf)
{
struct sf *sf_ptr;
struct sf *sf_found_sibling;
struct sf *sf_reposition = NULL;
mutex_enter(&sf_global_mutex);
while (sf_watch_running) {
/* Busy working the list -- wait */
cv_wait(&sf_watch_cv, &sf_global_mutex);
}
if ((sf_found_sibling = sf->sf_sibling) != NULL) {
/*
* we have a sibling so NULL out its reference to us
*/
mutex_enter(&sf_found_sibling->sf_mutex);
sf_found_sibling->sf_sibling = NULL;
mutex_exit(&sf_found_sibling->sf_mutex);
}
/* remove our instance from the global list */
if (sf == sf_head) {
/* we were at at head of the list */
sf_head = sf->sf_next;
} else {
/* find us in the list */
for (sf_ptr = sf_head;
sf_ptr != NULL;
sf_ptr = sf_ptr->sf_next) {
if (sf_ptr == sf) {
break;
}
/* remember this place */
sf_reposition = sf_ptr;
}
ASSERT(sf_ptr == sf);
ASSERT(sf_reposition != NULL);
sf_reposition->sf_next = sf_ptr->sf_next;
}
mutex_exit(&sf_global_mutex);
}
static int
sf_scsi_bus_config(dev_info_t *parent, uint_t flag,
ddi_bus_config_op_t op, void *arg, dev_info_t **childp)
{
int64_t reset_delay;
struct sf *sf;
sf = ddi_get_soft_state(sf_state, ddi_get_instance(parent));
ASSERT(sf);
reset_delay = (int64_t)(USEC_TO_TICK(SF_INIT_WAIT_TIMEOUT)) -
(lbolt64 - sf->sf_reset_time);
if (reset_delay < 0)
reset_delay = 0;
if (sf_bus_config_debug)
flag |= NDI_DEVI_DEBUG;
return (ndi_busop_bus_config(parent, flag, op,
arg, childp, (clock_t)reset_delay));
}
static int
sf_scsi_bus_unconfig(dev_info_t *parent, uint_t flag,
ddi_bus_config_op_t op, void *arg)
{
if (sf_bus_config_debug)
flag |= NDI_DEVI_DEBUG;
return (ndi_busop_bus_unconfig(parent, flag, op, arg));
}
/*
* called by transport to initialize a SCSI target
*/
/* ARGSUSED */
static int
sf_scsi_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip,
scsi_hba_tran_t *hba_tran, struct scsi_device *sd)
{
#ifdef RAID_LUNS
int lun;
#else
int64_t lun;
#endif
struct sf_target *target;
struct sf *sf = (struct sf *)hba_tran->tran_hba_private;
int i, t_len;
unsigned int lip_cnt;
unsigned char wwn[FC_WWN_SIZE];
/* get and validate our SCSI target ID */
i = sd->sd_address.a_target;
if (i >= sf_max_targets) {
return (DDI_NOT_WELL_FORMED);
}
/* get our port WWN property */
t_len = sizeof (wwn);
if (ddi_prop_op(DDI_DEV_T_ANY, tgt_dip, PROP_LEN_AND_VAL_BUF,
DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, PORT_WWN_PROP,
(caddr_t)&wwn, &t_len) != DDI_SUCCESS) {
/* no port WWN property - ignore the OBP stub node */
return (DDI_NOT_WELL_FORMED);
}
/* get our LIP count property */
t_len = sizeof (lip_cnt);
if (ddi_prop_op(DDI_DEV_T_ANY, tgt_dip, PROP_LEN_AND_VAL_BUF,
DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, LIP_CNT_PROP,
(caddr_t)&lip_cnt, &t_len) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
/* and our LUN property */
t_len = sizeof (lun);
if (ddi_prop_op(DDI_DEV_T_ANY, tgt_dip, PROP_LEN_AND_VAL_BUF,
DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, "lun",
(caddr_t)&lun, &t_len) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
/* find the target structure for this instance */
mutex_enter(&sf->sf_mutex);
if ((target = sf_lookup_target(sf, wwn, lun)) == NULL) {
mutex_exit(&sf->sf_mutex);
return (DDI_FAILURE);
}
mutex_enter(&target->sft_mutex);
if ((sf->sf_lip_cnt == lip_cnt) && !(target->sft_state
& SF_TARGET_INIT_DONE)) {
/*
* set links between HBA transport and target structures
* and set done flag
*/
hba_tran->tran_tgt_private = target;
target->sft_tran = hba_tran;
target->sft_state |= SF_TARGET_INIT_DONE;
} else {
/* already initialized ?? */
mutex_exit(&target->sft_mutex);
mutex_exit(&sf->sf_mutex);
return (DDI_FAILURE);
}
mutex_exit(&target->sft_mutex);
mutex_exit(&sf->sf_mutex);
return (DDI_SUCCESS);
}
/*
* called by transport to free a target
*/
/* ARGSUSED */
static void
sf_scsi_tgt_free(dev_info_t *hba_dip, dev_info_t *tgt_dip,
scsi_hba_tran_t *hba_tran, struct scsi_device *sd)
{
struct sf_target *target = hba_tran->tran_tgt_private;
if (target != NULL) {
mutex_enter(&target->sft_mutex);
target->sft_tran = NULL;
target->sft_state &= ~SF_TARGET_INIT_DONE;
mutex_exit(&target->sft_mutex);
}
}
/*
* allocator for non-std size cdb/pkt_private/status -- return TRUE iff
* success, else return FALSE
*/
/*ARGSUSED*/
static int
sf_pkt_alloc_extern(struct sf *sf, struct sf_pkt *cmd,
int tgtlen, int statuslen, int kf)
{
caddr_t scbp, tgt;
int failure = FALSE;
struct scsi_pkt *pkt = CMD2PKT(cmd);
tgt = scbp = NULL;
if (tgtlen > PKT_PRIV_LEN) {
if ((tgt = kmem_zalloc(tgtlen, kf)) == NULL) {
failure = TRUE;
} else {
cmd->cmd_flags |= CFLAG_PRIVEXTERN;
pkt->pkt_private = tgt;
}
}
if (statuslen > EXTCMDS_STATUS_SIZE) {
if ((scbp = kmem_zalloc((size_t)statuslen, kf)) == NULL) {
failure = TRUE;
} else {
cmd->cmd_flags |= CFLAG_SCBEXTERN;
pkt->pkt_scbp = (opaque_t)scbp;
}
}
if (failure) {
sf_pkt_destroy_extern(sf, cmd);
}
return (failure);
}
/*
* deallocator for non-std size cdb/pkt_private/status
*/
static void
sf_pkt_destroy_extern(struct sf *sf, struct sf_pkt *cmd)
{
struct scsi_pkt *pkt = CMD2PKT(cmd);
if (cmd->cmd_flags & CFLAG_FREE) {
cmn_err(CE_PANIC,
"sf_scsi_impl_pktfree: freeing free packet");
_NOTE(NOT_REACHED)
/* NOTREACHED */
}
if (cmd->cmd_flags & CFLAG_SCBEXTERN) {
kmem_free((caddr_t)pkt->pkt_scbp,
(size_t)cmd->cmd_scblen);
}
if (cmd->cmd_flags & CFLAG_PRIVEXTERN) {
kmem_free((caddr_t)pkt->pkt_private,
(size_t)cmd->cmd_privlen);
}
cmd->cmd_flags = CFLAG_FREE;
kmem_cache_free(sf->sf_pkt_cache, (void *)cmd);
}
/*
* create or initialize a SCSI packet -- called internally and
* by the transport
*/
static struct scsi_pkt *
sf_scsi_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt,
struct buf *bp, int cmdlen, int statuslen, int tgtlen,
int flags, int (*callback)(), caddr_t arg)
{
int kf;
int failure = FALSE;
struct sf_pkt *cmd;
struct sf *sf = ADDR2SF(ap);
struct sf_target *target = ADDR2TARGET(ap);
struct sf_pkt *new_cmd = NULL;
struct fcal_packet *fpkt;
fc_frame_header_t *hp;
struct fcp_cmd *fcmd;
/*
* If we've already allocated a pkt once,
* this request is for dma allocation only.
*/
if (pkt == NULL) {
/*
* First step of sf_scsi_init_pkt: pkt allocation
*/
if (cmdlen > FCP_CDB_SIZE) {
return (NULL);
}
kf = (callback == SLEEP_FUNC)? KM_SLEEP: KM_NOSLEEP;
if ((cmd = kmem_cache_alloc(sf->sf_pkt_cache, kf)) != NULL) {
/*
* Selective zeroing of the pkt.
*/
cmd->cmd_flags = 0;
cmd->cmd_forw = 0;
cmd->cmd_back = 0;
cmd->cmd_next = 0;
cmd->cmd_pkt = (struct scsi_pkt *)((char *)cmd +
sizeof (struct sf_pkt) + sizeof (struct
fcal_packet));
cmd->cmd_fp_pkt = (struct fcal_packet *)((char *)cmd +
sizeof (struct sf_pkt));
cmd->cmd_fp_pkt->fcal_pkt_private = (opaque_t)cmd;
cmd->cmd_state = SF_STATE_IDLE;
cmd->cmd_pkt->pkt_ha_private = (opaque_t)cmd;
cmd->cmd_pkt->pkt_scbp = (opaque_t)cmd->cmd_scsi_scb;
cmd->cmd_pkt->pkt_comp = NULL;
cmd->cmd_pkt->pkt_flags = 0;
cmd->cmd_pkt->pkt_time = 0;
cmd->cmd_pkt->pkt_resid = 0;
cmd->cmd_pkt->pkt_reason = 0;
cmd->cmd_cdblen = (uchar_t)cmdlen;
cmd->cmd_scblen = statuslen;
cmd->cmd_privlen = tgtlen;
cmd->cmd_pkt->pkt_address = *ap;
/* zero pkt_private */
(int *)(cmd->cmd_pkt->pkt_private =
cmd->cmd_pkt_private);
bzero((caddr_t)cmd->cmd_pkt->pkt_private,
PKT_PRIV_LEN);
} else {
failure = TRUE;
}
if (failure ||
(tgtlen > PKT_PRIV_LEN) ||
(statuslen > EXTCMDS_STATUS_SIZE)) {
if (!failure) {
/* need to allocate more space */
failure = sf_pkt_alloc_extern(sf, cmd,
tgtlen, statuslen, kf);
}
if (failure) {
return (NULL);
}
}
fpkt = cmd->cmd_fp_pkt;
if (cmd->cmd_block == NULL) {
/* allocate cmd/response pool buffers */
if (sf_cr_alloc(sf, cmd, callback) == DDI_FAILURE) {
sf_pkt_destroy_extern(sf, cmd);
return (NULL);
}
/* fill in the FC-AL packet */
fpkt->fcal_pkt_cookie = sf->sf_socp;
fpkt->fcal_pkt_comp = sf_cmd_callback;
fpkt->fcal_pkt_flags = 0;
fpkt->fcal_magic = FCALP_MAGIC;
fpkt->fcal_socal_request.sr_soc_hdr.sh_flags =
(ushort_t)(SOC_FC_HEADER |
sf->sf_sochandle->fcal_portno);
fpkt->fcal_socal_request.sr_soc_hdr.sh_class = 3;
fpkt->fcal_socal_request.sr_cqhdr.cq_hdr_count = 1;
fpkt->fcal_socal_request.sr_cqhdr.cq_hdr_flags = 0;
fpkt->fcal_socal_request.sr_cqhdr.cq_hdr_seqno = 0;
fpkt->fcal_socal_request.sr_dataseg[0].fc_base =
(uint32_t)cmd->cmd_dmac;
fpkt->fcal_socal_request.sr_dataseg[0].fc_count =
sizeof (struct fcp_cmd);
fpkt->fcal_socal_request.sr_dataseg[1].fc_base =
(uint32_t)cmd->cmd_rsp_dmac;
fpkt->fcal_socal_request.sr_dataseg[1].fc_count =
FCP_MAX_RSP_IU_SIZE;
/* Fill in the Fabric Channel Header */
hp = &fpkt->fcal_socal_request.sr_fc_frame_hdr;
hp->r_ctl = R_CTL_COMMAND;
hp->type = TYPE_SCSI_FCP;
hp->f_ctl = F_CTL_SEQ_INITIATIVE | F_CTL_FIRST_SEQ;
hp->reserved1 = 0;
hp->seq_id = 0;
hp->df_ctl = 0;
hp->seq_cnt = 0;
hp->ox_id = 0xffff;
hp->rx_id = 0xffff;
hp->ro = 0;
/* Establish the LUN */
bcopy((caddr_t)&target->sft_lun.b,
(caddr_t)&cmd->cmd_block->fcp_ent_addr,
FCP_LUN_SIZE);
*((int32_t *)&cmd->cmd_block->fcp_cntl) = 0;
}
cmd->cmd_pkt->pkt_cdbp = cmd->cmd_block->fcp_cdb;
mutex_enter(&target->sft_pkt_mutex);
target->sft_pkt_tail->cmd_forw = cmd;
cmd->cmd_back = target->sft_pkt_tail;
cmd->cmd_forw = (struct sf_pkt *)&target->sft_pkt_head;
target->sft_pkt_tail = cmd;
mutex_exit(&target->sft_pkt_mutex);
new_cmd = cmd; /* for later cleanup if needed */
} else {
/* pkt already exists -- just a request for DMA allocation */
cmd = PKT2CMD(pkt);
fpkt = cmd->cmd_fp_pkt;
}
/* zero cdb (bzero is too slow) */
bzero((caddr_t)cmd->cmd_pkt->pkt_cdbp, cmdlen);
/*
* Second step of sf_scsi_init_pkt: dma allocation
* Set up dma info
*/
if ((bp != NULL) && (bp->b_bcount != 0)) {
int cmd_flags, dma_flags;
int rval = 0;
uint_t dmacookie_count;
/* there is a buffer and some data to transfer */
/* set up command and DMA flags */
cmd_flags = cmd->cmd_flags;
if (bp->b_flags & B_READ) {
/* a read */
cmd_flags &= ~CFLAG_DMASEND;
dma_flags = DDI_DMA_READ;
} else {
/* a write */
cmd_flags |= CFLAG_DMASEND;
dma_flags = DDI_DMA_WRITE;
}
if (flags & PKT_CONSISTENT) {
cmd_flags |= CFLAG_CMDIOPB;
dma_flags |= DDI_DMA_CONSISTENT;
}
/* ensure we have a DMA handle */
if (cmd->cmd_dmahandle == NULL) {
rval = ddi_dma_alloc_handle(sf->sf_dip,
sf->sf_sochandle->fcal_dmaattr, callback, arg,
&cmd->cmd_dmahandle);
}
if (rval == 0) {
/* bind our DMA handle to our buffer */
rval = ddi_dma_buf_bind_handle(cmd->cmd_dmahandle, bp,
dma_flags, callback, arg, &cmd->cmd_dmacookie,
&dmacookie_count);
}
if (rval != 0) {
/* DMA failure */
SF_DEBUG(2, (sf, CE_CONT, "ddi_dma_buf.. failed\n"));
switch (rval) {
case DDI_DMA_NORESOURCES:
bioerror(bp, 0);
break;
case DDI_DMA_BADATTR:
case DDI_DMA_NOMAPPING:
bioerror(bp, EFAULT);
break;
case DDI_DMA_TOOBIG:
default:
bioerror(bp, EINVAL);
break;
}
/* clear valid flag */
cmd->cmd_flags = cmd_flags & ~CFLAG_DMAVALID;
if (new_cmd != NULL) {
/* destroy packet if we just created it */
sf_scsi_destroy_pkt(ap, new_cmd->cmd_pkt);
}
return (NULL);
}
ASSERT(dmacookie_count == 1);
/* set up amt to transfer and set valid flag */
cmd->cmd_dmacount = bp->b_bcount;
cmd->cmd_flags = cmd_flags | CFLAG_DMAVALID;
ASSERT(cmd->cmd_dmahandle != NULL);
}
/* set up FC-AL packet */
fcmd = cmd->cmd_block;
if (cmd->cmd_flags & CFLAG_DMAVALID) {
if (cmd->cmd_flags & CFLAG_DMASEND) {
/* DMA write */
fcmd->fcp_cntl.cntl_read_data = 0;
fcmd->fcp_cntl.cntl_write_data = 1;
fpkt->fcal_socal_request.sr_cqhdr.cq_hdr_type =
CQ_TYPE_IO_WRITE;
} else {
/* DMA read */
fcmd->fcp_cntl.cntl_read_data = 1;
fcmd->fcp_cntl.cntl_write_data = 0;
fpkt->fcal_socal_request.sr_cqhdr.cq_hdr_type =
CQ_TYPE_IO_READ;
}
fpkt->fcal_socal_request.sr_dataseg[2].fc_base =
(uint32_t)cmd->cmd_dmacookie.dmac_address;
fpkt->fcal_socal_request.sr_dataseg[2].fc_count =
cmd->cmd_dmacookie.dmac_size;
fpkt->fcal_socal_request.sr_soc_hdr.sh_seg_cnt = 3;
fpkt->fcal_socal_request.sr_soc_hdr.sh_byte_cnt =
cmd->cmd_dmacookie.dmac_size;
fcmd->fcp_data_len = cmd->cmd_dmacookie.dmac_size;
} else {
/* not a read or write */
fcmd->fcp_cntl.cntl_read_data = 0;
fcmd->fcp_cntl.cntl_write_data = 0;
fpkt->fcal_socal_request.sr_cqhdr.cq_hdr_type = CQ_TYPE_SIMPLE;
fpkt->fcal_socal_request.sr_soc_hdr.sh_seg_cnt = 2;
fpkt->fcal_socal_request.sr_soc_hdr.sh_byte_cnt =
sizeof (struct fcp_cmd);
fcmd->fcp_data_len = 0;
}
fcmd->fcp_cntl.cntl_qtype = FCP_QTYPE_SIMPLE;
return (cmd->cmd_pkt);
}
/*
* destroy a SCSI packet -- called internally and by the transport
*/
static void
sf_scsi_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt)
{
struct sf_pkt *cmd = PKT2CMD(pkt);
struct sf *sf = ADDR2SF(ap);
struct sf_target *target = ADDR2TARGET(ap);
struct fcal_packet *fpkt = cmd->cmd_fp_pkt;
if (cmd->cmd_flags & CFLAG_DMAVALID) {
/* DMA was set up -- clean up */
(void) ddi_dma_unbind_handle(cmd->cmd_dmahandle);
cmd->cmd_flags ^= CFLAG_DMAVALID;
}
/* take this packet off the doubly-linked list */
mutex_enter(&target->sft_pkt_mutex);
cmd->cmd_back->cmd_forw = cmd->cmd_forw;
cmd->cmd_forw->cmd_back = cmd->cmd_back;
mutex_exit(&target->sft_pkt_mutex);
fpkt->fcal_pkt_flags = 0;
/* free the packet */
if ((cmd->cmd_flags &
(CFLAG_FREE | CFLAG_PRIVEXTERN | CFLAG_SCBEXTERN)) == 0) {
/* just a regular packet */
ASSERT(cmd->cmd_state != SF_STATE_ISSUED);
cmd->cmd_flags = CFLAG_FREE;
kmem_cache_free(sf->sf_pkt_cache, (void *)cmd);
} else {
/* a packet with extra memory */
sf_pkt_destroy_extern(sf, cmd);
}
}
/*
* called by transport to unbind DMA handle
*/
/* ARGSUSED */
static void
sf_scsi_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt)
{
struct sf_pkt *cmd = PKT2CMD(pkt);
if (cmd->cmd_flags & CFLAG_DMAVALID) {
(void) ddi_dma_unbind_handle(cmd->cmd_dmahandle);
cmd->cmd_flags ^= CFLAG_DMAVALID;
}
}
/*
* called by transport to synchronize CPU and I/O views of memory
*/
/* ARGSUSED */
static void
sf_scsi_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt)
{
struct sf_pkt *cmd = PKT2CMD(pkt);
if (cmd->cmd_flags & CFLAG_DMAVALID) {
if (ddi_dma_sync(cmd->cmd_dmahandle, (off_t)0, (size_t)0,
(cmd->cmd_flags & CFLAG_DMASEND) ?
DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU) !=
DDI_SUCCESS) {
cmn_err(CE_WARN, "sf: sync pkt failed");
}
}
}
/*
* routine for reset notification setup, to register or cancel. -- called
* by transport
*/
static int
sf_scsi_reset_notify(struct scsi_address *ap, int flag,
void (*callback)(caddr_t), caddr_t arg)
{
struct sf *sf = ADDR2SF(ap);
return (scsi_hba_reset_notify_setup(ap, flag, callback, arg,
&sf->sf_mutex, &sf->sf_reset_notify_listf));
}
/*
* called by transport to get port WWN property (except sun4u)
*/
/* ARGSUSED */
static int
sf_scsi_get_name(struct scsi_device *sd, char *name, int len)
{
char tbuf[(FC_WWN_SIZE*2)+1];
unsigned char wwn[FC_WWN_SIZE];
int i, lun;
dev_info_t *tgt_dip;
tgt_dip = sd->sd_dev;
i = sizeof (wwn);
if (ddi_prop_op(DDI_DEV_T_ANY, tgt_dip, PROP_LEN_AND_VAL_BUF,
DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, PORT_WWN_PROP,
(caddr_t)&wwn, &i) != DDI_SUCCESS) {
name[0] = '\0';
return (0);
}
i = sizeof (lun);
if (ddi_prop_op(DDI_DEV_T_ANY, tgt_dip, PROP_LEN_AND_VAL_BUF,
DDI_PROP_DONTPASS | DDI_PROP_CANSLEEP, "lun",
(caddr_t)&lun, &i) != DDI_SUCCESS) {
name[0] = '\0';
return (0);
}
for (i = 0; i < FC_WWN_SIZE; i++)
(void) sprintf(&tbuf[i << 1], "%02x", wwn[i]);
(void) sprintf(name, "w%s,%x", tbuf, lun);
return (1);
}
/*
* called by transport to get target soft AL-PA (except sun4u)
*/
/* ARGSUSED */
static int
sf_scsi_get_bus_addr(struct scsi_device *sd, char *name, int len)
{
struct sf_target *target = ADDR2TARGET(&sd->sd_address);
if (target == NULL)
return (0);
(void) sprintf(name, "%x", target->sft_al_pa);
return (1);
}
/*
* add to the command/response buffer pool for this sf instance
*/
static int
sf_add_cr_pool(struct sf *sf)
{
int cmd_buf_size;
size_t real_cmd_buf_size;
int rsp_buf_size;
size_t real_rsp_buf_size;
uint_t i, ccount;
struct sf_cr_pool *ptr;
struct sf_cr_free_elem *cptr;
caddr_t dptr, eptr;
ddi_dma_cookie_t cmd_cookie;
ddi_dma_cookie_t rsp_cookie;
int cmd_bound = FALSE, rsp_bound = FALSE;
/* allocate room for the pool */
if ((ptr = kmem_zalloc(sizeof (struct sf_cr_pool), KM_NOSLEEP)) ==
NULL) {
return (DDI_FAILURE);
}
/* allocate a DMA handle for the command pool */
if (ddi_dma_alloc_handle(sf->sf_dip, sf->sf_sochandle->fcal_dmaattr,
DDI_DMA_DONTWAIT, NULL, &ptr->cmd_dma_handle) != DDI_SUCCESS) {
goto fail;
}
/*
* Get a piece of memory in which to put commands
*/
cmd_buf_size = (sizeof (struct fcp_cmd) * SF_ELEMS_IN_POOL + 7) & ~7;
if (ddi_dma_mem_alloc(ptr->cmd_dma_handle, cmd_buf_size,
sf->sf_sochandle->fcal_accattr, DDI_DMA_CONSISTENT,
DDI_DMA_DONTWAIT, NULL, (caddr_t *)&ptr->cmd_base,
&real_cmd_buf_size, &ptr->cmd_acc_handle) != DDI_SUCCESS) {
goto fail;
}
/* bind the DMA handle to an address */
if (ddi_dma_addr_bind_handle(ptr->cmd_dma_handle, NULL,
ptr->cmd_base, real_cmd_buf_size,
DDI_DMA_WRITE | DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT,
NULL, &cmd_cookie, &ccount) != DDI_DMA_MAPPED) {
goto fail;
}
cmd_bound = TRUE;
/* ensure only one cookie was allocated */
if (ccount != 1) {
goto fail;
}
/* allocate a DMA handle for the response pool */
if (ddi_dma_alloc_handle(sf->sf_dip, sf->sf_sochandle->fcal_dmaattr,
DDI_DMA_DONTWAIT, NULL, &ptr->rsp_dma_handle) != DDI_SUCCESS) {
goto fail;
}
/*
* Get a piece of memory in which to put responses
*/
rsp_buf_size = FCP_MAX_RSP_IU_SIZE * SF_ELEMS_IN_POOL;
if (ddi_dma_mem_alloc(ptr->rsp_dma_handle, rsp_buf_size,
sf->sf_sochandle->fcal_accattr, DDI_DMA_CONSISTENT,
DDI_DMA_DONTWAIT, NULL, (caddr_t *)&ptr->rsp_base,
&real_rsp_buf_size, &ptr->rsp_acc_handle) != DDI_SUCCESS) {
goto fail;
}
/* bind the DMA handle to an address */
if (ddi_dma_addr_bind_handle(ptr->rsp_dma_handle, NULL,
ptr->rsp_base, real_rsp_buf_size,
DDI_DMA_READ | DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT,
NULL, &rsp_cookie, &ccount) != DDI_DMA_MAPPED) {
goto fail;
}
rsp_bound = TRUE;
/* ensure only one cookie was allocated */
if (ccount != 1) {
goto fail;
}
/*
* Generate a (cmd/rsp structure) free list
*/
/* ensure ptr points to start of long word (8-byte block) */
dptr = (caddr_t)((uintptr_t)(ptr->cmd_base) + 7 & ~7);
/* keep track of actual size after moving pointer */
real_cmd_buf_size -= (dptr - ptr->cmd_base);
eptr = ptr->rsp_base;
/* set actual total number of entries */
ptr->ntot = min((real_cmd_buf_size / sizeof (struct fcp_cmd)),
(real_rsp_buf_size / FCP_MAX_RSP_IU_SIZE));
ptr->nfree = ptr->ntot;
ptr->free = (struct sf_cr_free_elem *)ptr->cmd_base;
ptr->sf = sf;
/* set up DMA for each pair of entries */
i = 0;
while (i < ptr->ntot) {
cptr = (struct sf_cr_free_elem *)dptr;
dptr += sizeof (struct fcp_cmd);
cptr->next = (struct sf_cr_free_elem *)dptr;
cptr->rsp = eptr;
cptr->cmd_dmac = cmd_cookie.dmac_address +
(uint32_t)((caddr_t)cptr - ptr->cmd_base);
cptr->rsp_dmac = rsp_cookie.dmac_address +
(uint32_t)((caddr_t)eptr - ptr->rsp_base);
eptr += FCP_MAX_RSP_IU_SIZE;
i++;
}
/* terminate the list */
cptr->next = NULL;
/* add this list at front of current one */
mutex_enter(&sf->sf_cr_mutex);
ptr->next = sf->sf_cr_pool;
sf->sf_cr_pool = ptr;
sf->sf_cr_pool_cnt++;
mutex_exit(&sf->sf_cr_mutex);
return (DDI_SUCCESS);
fail:
/* we failed so clean up */
if (ptr->cmd_dma_handle != NULL) {
if (cmd_bound) {
(void) ddi_dma_unbind_handle(ptr->cmd_dma_handle);
}
ddi_dma_free_handle(&ptr->cmd_dma_handle);
}
if (ptr->rsp_dma_handle != NULL) {
if (rsp_bound) {
(void) ddi_dma_unbind_handle(ptr->rsp_dma_handle);
}
ddi_dma_free_handle(&ptr->rsp_dma_handle);
}
if (ptr->cmd_base != NULL) {
ddi_dma_mem_free(&ptr->cmd_acc_handle);
}
if (ptr->rsp_base != NULL) {
ddi_dma_mem_free(&ptr->rsp_acc_handle);
}
kmem_free((caddr_t)ptr, sizeof (struct sf_cr_pool));
return (DDI_FAILURE);
}
/*
* allocate a command/response buffer from the pool, allocating more
* in the pool as needed
*/
static int
sf_cr_alloc(struct sf *sf, struct sf_pkt *cmd, int (*func)())
{
struct sf_cr_pool *ptr;
struct sf_cr_free_elem *cptr;
mutex_enter(&sf->sf_cr_mutex);
try_again:
/* find a free buffer in the existing pool */
ptr = sf->sf_cr_pool;
while (ptr != NULL) {
if (ptr->nfree != 0) {
ptr->nfree--;
break;
} else {
ptr = ptr->next;
}
}
/* did we find a free buffer ? */
if (ptr != NULL) {
/* we found a free buffer -- take it off the free list */
cptr = ptr->free;
ptr->free = cptr->next;
mutex_exit(&sf->sf_cr_mutex);
/* set up the command to use the buffer pair */
cmd->cmd_block = (struct fcp_cmd *)cptr;
cmd->cmd_dmac = cptr->cmd_dmac;
cmd->cmd_rsp_dmac = cptr->rsp_dmac;
cmd->cmd_rsp_block = (struct fcp_rsp *)cptr->rsp;
cmd->cmd_cr_pool = ptr;
return (DDI_SUCCESS); /* success */
}
/* no free buffer available -- can we allocate more ? */
if (sf->sf_cr_pool_cnt < SF_CR_POOL_MAX) {
/* we need to allocate more buffer pairs */
if (sf->sf_cr_flag) {
/* somebody already allocating for this instance */
if (func == SLEEP_FUNC) {
/* user wants to wait */
cv_wait(&sf->sf_cr_cv, &sf->sf_cr_mutex);
/* we've been woken so go try again */
goto try_again;
}
/* user does not want to wait */
mutex_exit(&sf->sf_cr_mutex);
sf->sf_stats.cralloc_failures++;
return (DDI_FAILURE); /* give up */
}
/* set flag saying we're allocating */
sf->sf_cr_flag = 1;
mutex_exit(&sf->sf_cr_mutex);
/* add to our pool */
if (sf_add_cr_pool(sf) != DDI_SUCCESS) {
/* couldn't add to our pool for some reason */
mutex_enter(&sf->sf_cr_mutex);
sf->sf_cr_flag = 0;
cv_broadcast(&sf->sf_cr_cv);
mutex_exit(&sf->sf_cr_mutex);
sf->sf_stats.cralloc_failures++;
return (DDI_FAILURE); /* give up */
}
/*
* clear flag saying we're allocating and tell all other
* that care
*/
mutex_enter(&sf->sf_cr_mutex);
sf->sf_cr_flag = 0;
cv_broadcast(&sf->sf_cr_cv);
/* now that we have more buffers try again */
goto try_again;
}
/* we don't have room to allocate any more buffers */
mutex_exit(&sf->sf_cr_mutex);
sf->sf_stats.cralloc_failures++;
return (DDI_FAILURE); /* give up */
}
/*
* free a cmd/response buffer pair in our pool
*/
static void
sf_cr_free(struct sf_cr_pool *cp, struct sf_pkt *cmd)
{
struct sf *sf = cp->sf;
struct sf_cr_free_elem *elem;
elem = (struct sf_cr_free_elem *)cmd->cmd_block;
elem->rsp = (caddr_t)cmd->cmd_rsp_block;
elem->cmd_dmac = cmd->cmd_dmac;
elem->rsp_dmac = cmd->cmd_rsp_dmac;
mutex_enter(&sf->sf_cr_mutex);
cp->nfree++;
ASSERT(cp->nfree <= cp->ntot);
elem->next = cp->free;
cp->free = elem;
mutex_exit(&sf->sf_cr_mutex);
}
/*
* free our pool of cmd/response buffers
*/
static void
sf_crpool_free(struct sf *sf)
{
struct sf_cr_pool *cp, *prev;
prev = NULL;
mutex_enter(&sf->sf_cr_mutex);
cp = sf->sf_cr_pool;
while (cp != NULL) {
if (cp->nfree == cp->ntot) {
if (prev != NULL) {
prev->next = cp->next;
} else {
sf->sf_cr_pool = cp->next;
}
sf->sf_cr_pool_cnt--;
mutex_exit(&sf->sf_cr_mutex);
(void) ddi_dma_unbind_handle(cp->cmd_dma_handle);
ddi_dma_free_handle(&cp->cmd_dma_handle);
(void) ddi_dma_unbind_handle(cp->rsp_dma_handle);
ddi_dma_free_handle(&cp->rsp_dma_handle);
ddi_dma_mem_free(&cp->cmd_acc_handle);
ddi_dma_mem_free(&cp->rsp_acc_handle);
kmem_free((caddr_t)cp, sizeof (struct sf_cr_pool));
return;
}
prev = cp;
cp = cp->next;
}
mutex_exit(&sf->sf_cr_mutex);
}
/* ARGSUSED */
static int
sf_kmem_cache_constructor(void *buf, void *arg, int size)
{
struct sf_pkt *cmd = buf;
mutex_init(&cmd->cmd_abort_mutex, NULL, MUTEX_DRIVER, NULL);
cmd->cmd_block = NULL;
cmd->cmd_dmahandle = NULL;
return (0);
}
/* ARGSUSED */
static void
sf_kmem_cache_destructor(void *buf, void *size)
{
struct sf_pkt *cmd = buf;
if (cmd->cmd_dmahandle != NULL) {
ddi_dma_free_handle(&cmd->cmd_dmahandle);
}
if (cmd->cmd_block != NULL) {
sf_cr_free(cmd->cmd_cr_pool, cmd);
}
mutex_destroy(&cmd->cmd_abort_mutex);
}
/*
* called by transport when a state change occurs
*/
static void
sf_statec_callback(void *arg, int msg)
{
struct sf *sf = (struct sf *)arg;
struct sf_target *target;
int i;
struct sf_pkt *cmd;
struct scsi_pkt *pkt;
switch (msg) {
case FCAL_STATUS_LOOP_ONLINE: {
uchar_t al_pa; /* to save AL-PA */
int ret; /* ret value from getmap */
int lip_cnt; /* to save current count */
int cnt; /* map length */
/*
* the loop has gone online
*/
SF_DEBUG(1, (sf, CE_CONT, "sf%d: loop online\n",
ddi_get_instance(sf->sf_dip)));
mutex_enter(&sf->sf_mutex);
sf->sf_lip_cnt++;
sf->sf_state = SF_STATE_ONLINING;
mutex_exit(&sf->sf_mutex);
/* scan each target hash queue */
for (i = 0; i < SF_NUM_HASH_QUEUES; i++) {
target = sf->sf_wwn_lists[i];
while (target != NULL) {
/*
* foreach target, if it's not offline then
* mark it as busy
*/
mutex_enter(&target->sft_mutex);
if (!(target->sft_state & SF_TARGET_OFFLINE))
target->sft_state |= (SF_TARGET_BUSY
| SF_TARGET_MARK);
#ifdef DEBUG
/*
* for debugging, print out info on any
* pending commands (left hanging)
*/
cmd = target->sft_pkt_head;
while (cmd != (struct sf_pkt *)&target->
sft_pkt_head) {
if (cmd->cmd_state ==
SF_STATE_ISSUED) {
SF_DEBUG(1, (sf, CE_CONT,
"cmd 0x%p pending "
"after lip\n",
(void *)cmd->cmd_fp_pkt));
}
cmd = cmd->cmd_forw;
}
#endif
mutex_exit(&target->sft_mutex);
target = target->sft_next;
}
}
/*
* since the loop has just gone online get a new map from
* the transport
*/
if ((ret = soc_get_lilp_map(sf->sf_sochandle, sf->sf_socp,
sf->sf_sochandle->fcal_portno, (uint32_t)sf->
sf_lilp_dmacookie.dmac_address, 1)) != FCAL_SUCCESS) {
if (sf_core && (sf_core & SF_CORE_LILP_FAILED)) {
(void) soc_take_core(sf->sf_sochandle,
sf->sf_socp);
sf_core = 0;
}
sf_log(sf, CE_WARN,
"!soc lilp map failed status=0x%x\n", ret);
mutex_enter(&sf->sf_mutex);
sf->sf_timer = sf_watchdog_time + SF_OFFLINE_TIMEOUT;
sf->sf_lip_cnt++;
sf->sf_state = SF_STATE_OFFLINE;
mutex_exit(&sf->sf_mutex);
return;
}
/* ensure consistent view of DMA memory */
(void) ddi_dma_sync(sf->sf_lilp_dmahandle, (off_t)0, (size_t)0,
DDI_DMA_SYNC_FORKERNEL);
/* how many entries in map ? */
cnt = sf->sf_lilp_map->lilp_length;
if (cnt >= SF_MAX_LILP_ENTRIES) {
sf_log(sf, CE_WARN, "invalid lilp map\n");
return;
}
mutex_enter(&sf->sf_mutex);
sf->sf_device_count = cnt - 1;
sf->sf_al_pa = sf->sf_lilp_map->lilp_myalpa;
lip_cnt = sf->sf_lip_cnt;
al_pa = sf->sf_al_pa;
SF_DEBUG(1, (sf, CE_CONT,
"!lilp map has %d entries, al_pa is %x\n", cnt, al_pa));
/*
* since the last entry of the map may be mine (common) check
* for that, and if it is we have one less entry to look at
*/
if (sf->sf_lilp_map->lilp_alpalist[cnt-1] == al_pa) {
cnt--;
}
/* If we didn't get a valid loop map enable all targets */
if (sf->sf_lilp_map->lilp_magic == FCAL_BADLILP_MAGIC) {
for (i = 0; i < sizeof (sf_switch_to_alpa); i++)
sf->sf_lilp_map->lilp_alpalist[i] =
sf_switch_to_alpa[i];
cnt = i;
sf->sf_device_count = cnt - 1;
}
if (sf->sf_device_count == 0) {
sf_finish_init(sf, lip_cnt);
mutex_exit(&sf->sf_mutex);
break;
}
mutex_exit(&sf->sf_mutex);
SF_DEBUG(2, (sf, CE_WARN,
"!statec_callback: starting with %d targets\n",
sf->sf_device_count));
/* scan loop map, logging into all ports (except mine) */
for (i = 0; i < cnt; i++) {
SF_DEBUG(1, (sf, CE_CONT,
"!lilp map entry %d = %x,%x\n", i,
sf->sf_lilp_map->lilp_alpalist[i],
sf_alpa_to_switch[
sf->sf_lilp_map->lilp_alpalist[i]]));
/* is this entry for somebody else ? */
if (sf->sf_lilp_map->lilp_alpalist[i] != al_pa) {
/* do a PLOGI to this port */
if (!sf_login(sf, LA_ELS_PLOGI,
sf->sf_lilp_map->lilp_alpalist[i],
sf->sf_lilp_map->lilp_alpalist[cnt-1],
lip_cnt)) {
/* a problem logging in */
mutex_enter(&sf->sf_mutex);
if (lip_cnt == sf->sf_lip_cnt) {
/*
* problem not from a new LIP
*/
sf->sf_device_count--;
ASSERT(sf->sf_device_count
>= 0);
if (sf->sf_device_count == 0) {
sf_finish_init(sf,
lip_cnt);
}
}
mutex_exit(&sf->sf_mutex);
}
}
}
break;
}
case FCAL_STATUS_ERR_OFFLINE:
/*
* loop has gone offline due to an error
*/
SF_DEBUG(1, (sf, CE_CONT, "sf%d: loop offline\n",
ddi_get_instance(sf->sf_dip)));
mutex_enter(&sf->sf_mutex);
sf->sf_lip_cnt++;
sf->sf_timer = sf_watchdog_time + SF_OFFLINE_TIMEOUT;
if (!sf->sf_online_timer) {
sf->sf_online_timer = sf_watchdog_time +
SF_ONLINE_TIMEOUT;
}
/*
* if we are suspended, preserve the SF_STATE_SUSPENDED flag,
* since throttling logic in sf_watch() depends on
* preservation of this flag while device is suspended
*/
if (sf->sf_state & SF_STATE_SUSPENDED) {
sf->sf_state |= SF_STATE_OFFLINE;
SF_DEBUG(1, (sf, CE_CONT,
"sf_statec_callback, sf%d: "
"got FCAL_STATE_OFFLINE during DDI_SUSPEND\n",
ddi_get_instance(sf->sf_dip)));
} else {
sf->sf_state = SF_STATE_OFFLINE;
}
/* scan each possible target on the loop */
for (i = 0; i < sf_max_targets; i++) {
target = sf->sf_targets[i];
while (target != NULL) {
mutex_enter(&target->sft_mutex);
if (!(target->sft_state & SF_TARGET_OFFLINE))
target->sft_state |= (SF_TARGET_BUSY
| SF_TARGET_MARK);
mutex_exit(&target->sft_mutex);
target = target->sft_next_lun;
}
}
mutex_exit(&sf->sf_mutex);
break;
case FCAL_STATE_RESET: {
struct sf_els_hdr *privp; /* ptr to private list */
struct sf_els_hdr *tmpp1; /* tmp prev hdr ptr */
struct sf_els_hdr *tmpp2; /* tmp next hdr ptr */
struct sf_els_hdr *head; /* to save our private list */
struct fcal_packet *fpkt; /* ptr to pkt in hdr */
/*
* a transport reset
*/
SF_DEBUG(1, (sf, CE_CONT, "!sf%d: soc reset\n",
ddi_get_instance(sf->sf_dip)));
tmpp1 = head = NULL;
mutex_enter(&sf->sf_mutex);
sf->sf_lip_cnt++;
sf->sf_timer = sf_watchdog_time + SF_RESET_TIMEOUT;
/*
* if we are suspended, preserve the SF_STATE_SUSPENDED flag,
* since throttling logic in sf_watch() depends on
* preservation of this flag while device is suspended
*/
if (sf->sf_state & SF_STATE_SUSPENDED) {
sf->sf_state |= SF_STATE_OFFLINE;
SF_DEBUG(1, (sf, CE_CONT,
"sf_statec_callback, sf%d: "
"got FCAL_STATE_RESET during DDI_SUSPEND\n",
ddi_get_instance(sf->sf_dip)));
} else {
sf->sf_state = SF_STATE_OFFLINE;
}
/*
* scan each possible target on the loop, looking for targets
* that need callbacks ran
*/
for (i = 0; i < sf_max_targets; i++) {
target = sf->sf_targets[i];
while (target != NULL) {
if (!(target->sft_state & SF_TARGET_OFFLINE)) {
target->sft_state |= (SF_TARGET_BUSY
| SF_TARGET_MARK);
mutex_exit(&sf->sf_mutex);
/*
* run remove event callbacks for lun
*
* We have a nasty race condition here
* 'cause we're dropping this mutex to
* run the callback and expect the
* linked list to be the same.
*/
(void) ndi_event_retrieve_cookie(
sf->sf_event_hdl, target->sft_dip,
FCAL_REMOVE_EVENT, &sf_remove_eid,
NDI_EVENT_NOPASS);
(void) ndi_event_run_callbacks(
sf->sf_event_hdl,
target->sft_dip,
sf_remove_eid, NULL);
mutex_enter(&sf->sf_mutex);
}
target = target->sft_next_lun;
}
}
/*
* scan for ELS commands that are in transport, not complete,
* and have a valid timeout, building a private list
*/
privp = sf->sf_els_list;
while (privp != NULL) {
fpkt = privp->fpkt;
if ((fpkt->fcal_cmd_state & FCAL_CMD_IN_TRANSPORT) &&
(!(fpkt->fcal_cmd_state & FCAL_CMD_COMPLETE)) &&
(privp->timeout != SF_INVALID_TIMEOUT)) {
/*
* cmd in transport && not complete &&
* timeout valid
*
* move this entry from ELS input list to our
* private list
*/
tmpp2 = privp->next; /* save ptr to next */
/* push this on private list head */
privp->next = head;
head = privp;
/* remove this entry from input list */
if (tmpp1 != NULL) {
/*
* remove this entry from somewhere in
* the middle of the list
*/
tmpp1->next = tmpp2;
if (tmpp2 != NULL) {
tmpp2->prev = tmpp1;
}
} else {
/*
* remove this entry from the head
* of the list
*/
sf->sf_els_list = tmpp2;
if (tmpp2 != NULL) {
tmpp2->prev = NULL;
}
}
privp = tmpp2; /* skip to next entry */
} else {
tmpp1 = privp; /* save ptr to prev entry */
privp = privp->next; /* skip to next entry */
}
}
mutex_exit(&sf->sf_mutex);
/*
* foreach cmd in our list free the ELS packet associated
* with it
*/
privp = head;
while (privp != NULL) {
fpkt = privp->fpkt;
privp = privp->next;
sf_els_free(fpkt);
}
/*
* scan for commands from each possible target
*/
for (i = 0; i < sf_max_targets; i++) {
target = sf->sf_targets[i];
while (target != NULL) {
/*
* scan all active commands for this target,
* looking for commands that have been issued,
* are in transport, and are not yet complete
* (so we can terminate them because of the
* reset)
*/
mutex_enter(&target->sft_pkt_mutex);
cmd = target->sft_pkt_head;
while (cmd != (struct sf_pkt *)&target->
sft_pkt_head) {
fpkt = cmd->cmd_fp_pkt;
mutex_enter(&cmd->cmd_abort_mutex);
if ((cmd->cmd_state ==
SF_STATE_ISSUED) &&
(fpkt->fcal_cmd_state &
FCAL_CMD_IN_TRANSPORT) &&
(!(fpkt->fcal_cmd_state &
FCAL_CMD_COMPLETE))) {
/* a command to be reset */
pkt = cmd->cmd_pkt;
pkt->pkt_reason = CMD_RESET;
pkt->pkt_statistics |=
STAT_BUS_RESET;
cmd->cmd_state = SF_STATE_IDLE;
mutex_exit(&cmd->
cmd_abort_mutex);
mutex_exit(&target->
sft_pkt_mutex);
if (pkt->pkt_comp != NULL) {
(*pkt->pkt_comp)(pkt);
}
mutex_enter(&target->
sft_pkt_mutex);
cmd = target->sft_pkt_head;
} else {
mutex_exit(&cmd->
cmd_abort_mutex);
/* get next command */
cmd = cmd->cmd_forw;
}
}
mutex_exit(&target->sft_pkt_mutex);
target = target->sft_next_lun;
}
}
/*
* get packet queue for this target, resetting all remaining
* commands
*/
mutex_enter(&sf->sf_mutex);
cmd = sf->sf_pkt_head;
sf->sf_pkt_head = NULL;
mutex_exit(&sf->sf_mutex);
while (cmd != NULL) {
pkt = cmd->cmd_pkt;
cmd = cmd->cmd_next;
pkt->pkt_reason = CMD_RESET;
pkt->pkt_statistics |= STAT_BUS_RESET;
if (pkt->pkt_comp != NULL) {
(*pkt->pkt_comp)(pkt);
}
}
break;
}
default:
break;
}
}
/*
* called to send a PLOGI (N_port login) ELS request to a destination ID,
* returning TRUE upon success, else returning FALSE
*/
static int
sf_login(struct sf *sf, uchar_t els_code, uchar_t dest_id, uint_t arg1,
int lip_cnt)
{
struct la_els_logi *logi;
struct sf_els_hdr *privp;
if (sf_els_alloc(sf, dest_id, sizeof (struct sf_els_hdr),
sizeof (union sf_els_cmd), sizeof (union sf_els_rsp),
(caddr_t *)&privp, (caddr_t *)&logi) == NULL) {
sf_log(sf, CE_WARN, "Cannot allocate PLOGI for target %x "
"due to DVMA shortage.\n", sf_alpa_to_switch[dest_id]);
return (FALSE);
}
privp->lip_cnt = lip_cnt;
if (els_code == LA_ELS_PLOGI) {
bcopy((caddr_t)sf->sf_sochandle->fcal_loginparms,
(caddr_t)&logi->common_service, sizeof (struct la_els_logi)
- 4);
bcopy((caddr_t)&sf->sf_sochandle->fcal_p_wwn,
(caddr_t)&logi->nport_ww_name, sizeof (la_wwn_t));
bcopy((caddr_t)&sf->sf_sochandle->fcal_n_wwn,
(caddr_t)&logi->node_ww_name, sizeof (la_wwn_t));
bzero((caddr_t)&logi->reserved, 16);
} else if (els_code == LA_ELS_LOGO) {
bcopy((caddr_t)&sf->sf_sochandle->fcal_p_wwn,
(caddr_t)&(((struct la_els_logo *)logi)->nport_ww_name), 8);
((struct la_els_logo *)logi)->reserved = 0;
((struct la_els_logo *)logi)->nport_id[0] = 0;
((struct la_els_logo *)logi)->nport_id[1] = 0;
((struct la_els_logo *)logi)->nport_id[2] = arg1;
}
privp->els_code = els_code;
logi->ls_code = els_code;
logi->mbz[0] = 0;
logi->mbz[1] = 0;
logi->mbz[2] = 0;
privp->timeout = sf_watchdog_time + SF_ELS_TIMEOUT;
return (sf_els_transport(sf, privp));
}
/*
* send an ELS IU via the transport,
* returning TRUE upon success, else returning FALSE
*/
static int
sf_els_transport(struct sf *sf, struct sf_els_hdr *privp)
{
struct fcal_packet *fpkt = privp->fpkt;
(void) ddi_dma_sync(privp->cmd_dma_handle, (off_t)0, (size_t)0,
DDI_DMA_SYNC_FORDEV);
privp->prev = NULL;
mutex_enter(&sf->sf_mutex);
privp->next = sf->sf_els_list;
if (sf->sf_els_list != NULL) {
sf->sf_els_list->prev = privp;
}
sf->sf_els_list = privp;
mutex_exit(&sf->sf_mutex);
/* call the transport to send a packet */
if (soc_transport(sf->sf_sochandle, fpkt, FCAL_NOSLEEP,
CQ_REQUEST_1) != FCAL_TRANSPORT_SUCCESS) {
mutex_enter(&sf->sf_mutex);
if (privp->prev != NULL) {
privp->prev->next = privp->next;
}
if (privp->next != NULL) {
privp->next->prev = privp->prev;
}
if (sf->sf_els_list == privp) {
sf->sf_els_list = privp->next;
}
mutex_exit(&sf->sf_mutex);
sf_els_free(fpkt);
return (FALSE); /* failure */
}
return (TRUE); /* success */
}
/*
* called as the pkt_comp routine for ELS FC packets
*/
static void
sf_els_callback(struct fcal_packet *fpkt)
{
struct sf_els_hdr *privp = fpkt->fcal_pkt_private;
struct sf *sf = privp->sf;
struct sf *tsf;
int tgt_id;
struct la_els_logi *ptr = (struct la_els_logi *)privp->rsp;
struct la_els_adisc *adisc = (struct la_els_adisc *)ptr;
struct sf_target *target;
short ncmds;
short free_pkt = TRUE;
/*
* we've received an ELS callback, i.e. an ELS packet has arrived
*/
/* take the current packet off of the queue */
mutex_enter(&sf->sf_mutex);
if (privp->timeout == SF_INVALID_TIMEOUT) {
mutex_exit(&sf->sf_mutex);
return;
}
if (privp->prev != NULL) {
privp->prev->next = privp->next;
}
if (privp->next != NULL) {
privp->next->prev = privp->prev;
}
if (sf->sf_els_list == privp) {
sf->sf_els_list = privp->next;
}
privp->prev = privp->next = NULL;
mutex_exit(&sf->sf_mutex);
/* get # pkts in this callback */
ncmds = fpkt->fcal_ncmds;
ASSERT(ncmds >= 0);
mutex_enter(&sf->sf_cmd_mutex);
sf->sf_ncmds = ncmds;
mutex_exit(&sf->sf_cmd_mutex);
/* sync idea of memory */
(void) ddi_dma_sync(privp->rsp_dma_handle, (off_t)0, (size_t)0,
DDI_DMA_SYNC_FORKERNEL);
/* was this an OK ACC msg ?? */
if ((fpkt->fcal_pkt_status == FCAL_STATUS_OK) &&
(ptr->ls_code == LA_ELS_ACC)) {
/*
* this was an OK ACC pkt
*/
switch (privp->els_code) {
case LA_ELS_PLOGI:
/*
* was able to to an N_port login
*/
SF_DEBUG(2, (sf, CE_CONT,
"!PLOGI to al_pa %x succeeded, wwn %x%x\n",
privp->dest_nport_id,
*((int *)&ptr->nport_ww_name.raw_wwn[0]),
*((int *)&ptr->nport_ww_name.raw_wwn[4])));
/* try to do a process login */
if (!sf_do_prli(sf, privp, ptr)) {
free_pkt = FALSE;
goto fail; /* PRLI failed */
}
break;
case LA_ELS_PRLI:
/*
* was able to do a process login
*/
SF_DEBUG(2, (sf, CE_CONT,
"!PRLI to al_pa %x succeeded\n",
privp->dest_nport_id));
/* try to do address discovery */
if (sf_do_adisc(sf, privp) != 1) {
free_pkt = FALSE;
goto fail; /* ADISC failed */
}
break;
case LA_ELS_ADISC:
/*
* found a target via ADISC
*/
SF_DEBUG(2, (sf, CE_CONT,
"!ADISC to al_pa %x succeeded\n",
privp->dest_nport_id));
/* create the target info */
if ((target = sf_create_target(sf, privp,
sf_alpa_to_switch[(uchar_t)adisc->hard_address],
(int64_t)0))
== NULL) {
goto fail; /* can't create target */
}
/*
* ensure address discovered matches what we thought
* it would be
*/
if ((uchar_t)adisc->hard_address !=
privp->dest_nport_id) {
sf_log(sf, CE_WARN,
"target 0x%x, AL-PA 0x%x and "
"hard address 0x%x don't match\n",
sf_alpa_to_switch[
(uchar_t)privp->dest_nport_id],
privp->dest_nport_id,
(uchar_t)adisc->hard_address);
mutex_enter(&sf->sf_mutex);
sf_offline_target(sf, target);
mutex_exit(&sf->sf_mutex);
goto fail; /* addr doesn't match */
}
/*
* get inquiry data from the target
*/
if (!sf_do_reportlun(sf, privp, target)) {
mutex_enter(&sf->sf_mutex);
sf_offline_target(sf, target);
mutex_exit(&sf->sf_mutex);
free_pkt = FALSE;
goto fail; /* inquiry failed */
}
break;
default:
SF_DEBUG(2, (sf, CE_CONT,
"!ELS %x to al_pa %x succeeded\n",
privp->els_code, privp->dest_nport_id));
sf_els_free(fpkt);
break;
}
} else {
/*
* oh oh -- this was not an OK ACC packet
*/
/* get target ID from dest loop address */
tgt_id = sf_alpa_to_switch[(uchar_t)privp->dest_nport_id];
/* keep track of failures */
sf->sf_stats.tstats[tgt_id].els_failures++;
if (++(privp->retries) < sf_els_retries &&
fpkt->fcal_pkt_status != FCAL_STATUS_OPEN_FAIL) {
if (fpkt->fcal_pkt_status ==
FCAL_STATUS_MAX_XCHG_EXCEEDED) {
tsf = sf->sf_sibling;
if (tsf != NULL) {
mutex_enter(&tsf->sf_cmd_mutex);
tsf->sf_flag = 1;
tsf->sf_throttle = SF_DECR_DELTA;
mutex_exit(&tsf->sf_cmd_mutex);
}
}
privp->timeout = sf_watchdog_time + SF_ELS_TIMEOUT;
privp->prev = NULL;
mutex_enter(&sf->sf_mutex);
if (privp->lip_cnt == sf->sf_lip_cnt) {
SF_DEBUG(1, (sf, CE_WARN,
"!ELS %x to al_pa %x failed, retrying",
privp->els_code, privp->dest_nport_id));
privp->next = sf->sf_els_list;
if (sf->sf_els_list != NULL) {
sf->sf_els_list->prev = privp;
}
sf->sf_els_list = privp;
mutex_exit(&sf->sf_mutex);
/* device busy? wait a bit ... */
if (fpkt->fcal_pkt_status ==
FCAL_STATUS_MAX_XCHG_EXCEEDED) {
privp->delayed_retry = 1;
return;
}
/* call the transport to send a pkt */
if (soc_transport(sf->sf_sochandle, fpkt,
FCAL_NOSLEEP, CQ_REQUEST_1) !=
FCAL_TRANSPORT_SUCCESS) {
mutex_enter(&sf->sf_mutex);
if (privp->prev != NULL) {
privp->prev->next =
privp->next;
}
if (privp->next != NULL) {
privp->next->prev =
privp->prev;
}
if (sf->sf_els_list == privp) {
sf->sf_els_list = privp->next;
}
mutex_exit(&sf->sf_mutex);
goto fail;
} else
return;
} else {
mutex_exit(&sf->sf_mutex);
goto fail;
}
} else {
#ifdef DEBUG
if (fpkt->fcal_pkt_status != 0x36 || sfdebug > 4) {
SF_DEBUG(2, (sf, CE_NOTE, "ELS %x to al_pa %x failed",
privp->els_code, privp->dest_nport_id));
if (fpkt->fcal_pkt_status == FCAL_STATUS_OK) {
SF_DEBUG(2, (sf, CE_NOTE,
"els reply code = %x", ptr->ls_code));
if (ptr->ls_code == LA_ELS_RJT)
SF_DEBUG(1, (sf, CE_CONT,
"LS_RJT reason = %x\n",
*(((uint_t *)ptr) + 1)));
} else
SF_DEBUG(2, (sf, CE_NOTE,
"fc packet status = %x",
fpkt->fcal_pkt_status));
}
#endif
goto fail;
}
}
return; /* success */
fail:
mutex_enter(&sf->sf_mutex);
if (sf->sf_lip_cnt == privp->lip_cnt) {
sf->sf_device_count--;
ASSERT(sf->sf_device_count >= 0);
if (sf->sf_device_count == 0) {
sf_finish_init(sf, privp->lip_cnt);
}
}
mutex_exit(&sf->sf_mutex);
if (free_pkt) {
sf_els_free(fpkt);
}
}
/*
* send a PRLI (process login) ELS IU via the transport,
* returning TRUE upon success, else returning FALSE
*/
static int
sf_do_prli(struct sf *sf, struct sf_els_hdr *privp, struct la_els_logi *ptr)
{
struct la_els_prli *prli = (struct la_els_prli *)privp->cmd;
struct fcp_prli *fprli;
struct fcal_packet *fpkt = privp->fpkt;
fpkt->fcal_socal_request.sr_dataseg[0].fc_count =
sizeof (struct la_els_prli);
privp->els_code = LA_ELS_PRLI;
fprli = (struct fcp_prli *)prli->service_params;
prli->ls_code = LA_ELS_PRLI;
prli->page_length = 0x10;
prli->payload_length = sizeof (struct la_els_prli);
fprli->type = 0x08; /* no define here? */
fprli->resvd1 = 0;
fprli->orig_process_assoc_valid = 0;
fprli->resp_process_assoc_valid = 0;
fprli->establish_image_pair = 1;
fprli->resvd2 = 0;
fprli->resvd3 = 0;
fprli->data_overlay_allowed = 0;
fprli->initiator_fn = 1;
fprli->target_fn = 0;
fprli->cmd_data_mixed = 0;
fprli->data_resp_mixed = 0;
fprli->read_xfer_rdy_disabled = 1;
fprli->write_xfer_rdy_disabled = 0;
bcopy((caddr_t)&ptr->nport_ww_name, (caddr_t)&privp->port_wwn,
sizeof (privp->port_wwn));
bcopy((caddr_t)&ptr->node_ww_name, (caddr_t)&privp->node_wwn,
sizeof (privp->node_wwn));
privp->timeout = sf_watchdog_time + SF_ELS_TIMEOUT;
return (sf_els_transport(sf, privp));
}
/*
* send an ADISC (address discovery) ELS IU via the transport,
* returning TRUE upon success, else returning FALSE
*/
static int
sf_do_adisc(struct sf *sf, struct sf_els_hdr *privp)
{
struct la_els_adisc *adisc = (struct la_els_adisc *)privp->cmd;
struct fcal_packet *fpkt = privp->fpkt;
privp->els_code = LA_ELS_ADISC;
adisc->ls_code = LA_ELS_ADISC;
adisc->mbz[0] = 0;
adisc->mbz[1] = 0;
adisc->mbz[2] = 0;
adisc->hard_address = 0; /* ??? */
fpkt->fcal_socal_request.sr_dataseg[0].fc_count =
sizeof (struct la_els_adisc);
bcopy((caddr_t)&sf->sf_sochandle->fcal_p_wwn,
(caddr_t)&adisc->port_wwn, sizeof (adisc->port_wwn));
bcopy((caddr_t)&sf->sf_sochandle->fcal_n_wwn,
(caddr_t)&adisc->node_wwn, sizeof (adisc->node_wwn));
adisc->nport_id = sf->sf_al_pa;
privp->timeout = sf_watchdog_time + SF_ELS_TIMEOUT;
return (sf_els_transport(sf, privp));
}
static struct fcal_packet *
sf_els_alloc(struct sf *sf, uchar_t dest_id, int priv_size, int cmd_size,
int rsp_size, caddr_t *rprivp, caddr_t *cmd_buf)
{
struct fcal_packet *fpkt;
ddi_dma_cookie_t pcookie;
ddi_dma_cookie_t rcookie;
struct sf_els_hdr *privp;
ddi_dma_handle_t cmd_dma_handle = NULL;
ddi_dma_handle_t rsp_dma_handle = NULL;
ddi_acc_handle_t cmd_acc_handle = NULL;
ddi_acc_handle_t rsp_acc_handle = NULL;
size_t real_size;
uint_t ccount;
fc_frame_header_t *hp;
int cmd_bound = FALSE, rsp_bound = FALSE;
caddr_t cmd = NULL;
caddr_t rsp = NULL;
if ((fpkt = (struct fcal_packet *)kmem_zalloc(
sizeof (struct fcal_packet), KM_NOSLEEP)) == NULL) {
SF_DEBUG(1, (sf, CE_WARN,
"Could not allocate fcal_packet for ELS\n"));
return (NULL);
}
if ((privp = (struct sf_els_hdr *)kmem_zalloc(priv_size,
KM_NOSLEEP)) == NULL) {
SF_DEBUG(1, (sf, CE_WARN,
"Could not allocate sf_els_hdr for ELS\n"));
goto fail;
}
privp->size = priv_size;
fpkt->fcal_pkt_private = (caddr_t)privp;
if (ddi_dma_alloc_handle(sf->sf_dip, sf->sf_sochandle->fcal_dmaattr,
DDI_DMA_DONTWAIT, NULL, &cmd_dma_handle) != DDI_SUCCESS) {
SF_DEBUG(1, (sf, CE_WARN,
"Could not allocate DMA handle for ELS\n"));
goto fail;
}
if (ddi_dma_mem_alloc(cmd_dma_handle, cmd_size,
sf->sf_sochandle->fcal_accattr, DDI_DMA_CONSISTENT,
DDI_DMA_DONTWAIT, NULL, &cmd,
&real_size, &cmd_acc_handle) != DDI_SUCCESS) {
SF_DEBUG(1, (sf, CE_WARN,
"Could not allocate DMA memory for ELS\n"));
goto fail;
}
if (real_size < cmd_size) {
SF_DEBUG(1, (sf, CE_WARN,
"DMA memory too small for ELS\n"));
goto fail;
}
if (ddi_dma_addr_bind_handle(cmd_dma_handle, NULL,
cmd, real_size, DDI_DMA_WRITE | DDI_DMA_CONSISTENT,
DDI_DMA_DONTWAIT, NULL, &pcookie, &ccount) != DDI_DMA_MAPPED) {
SF_DEBUG(1, (sf, CE_WARN,
"Could not bind DMA memory for ELS\n"));
goto fail;
}
cmd_bound = TRUE;
if (ccount != 1) {
SF_DEBUG(1, (sf, CE_WARN,
"Wrong cookie count for ELS\n"));
goto fail;
}
if (ddi_dma_alloc_handle(sf->sf_dip, sf->sf_sochandle->fcal_dmaattr,
DDI_DMA_DONTWAIT, NULL, &rsp_dma_handle) != DDI_SUCCESS) {
SF_DEBUG(1, (sf, CE_WARN,
"Could not allocate DMA handle for ELS rsp\n"));
goto fail;
}
if (ddi_dma_mem_alloc(rsp_dma_handle, rsp_size,
sf->sf_sochandle->fcal_accattr, DDI_DMA_CONSISTENT,
DDI_DMA_DONTWAIT, NULL, &rsp,
&real_size, &rsp_acc_handle) != DDI_SUCCESS) {
SF_DEBUG(1, (sf, CE_WARN,
"Could not allocate DMA memory for ELS rsp\n"));
goto fail;
}
if (real_size < rsp_size) {
SF_DEBUG(1, (sf, CE_WARN,
"DMA memory too small for ELS rsp\n"));
goto fail;
}
if (ddi_dma_addr_bind_handle(rsp_dma_handle, NULL,
rsp, real_size, DDI_DMA_READ | DDI_DMA_CONSISTENT,
DDI_DMA_DONTWAIT, NULL, &rcookie, &ccount) != DDI_DMA_MAPPED) {
SF_DEBUG(1, (sf, CE_WARN,
"Could not bind DMA memory for ELS rsp\n"));
goto fail;
}
rsp_bound = TRUE;
if (ccount != 1) {
SF_DEBUG(1, (sf, CE_WARN,
"Wrong cookie count for ELS rsp\n"));
goto fail;
}
privp->cmd = cmd;
privp->sf = sf;
privp->cmd_dma_handle = cmd_dma_handle;
privp->cmd_acc_handle = cmd_acc_handle;
privp->rsp = rsp;
privp->rsp_dma_handle = rsp_dma_handle;
privp->rsp_acc_handle = rsp_acc_handle;
privp->dest_nport_id = dest_id;
privp->fpkt = fpkt;
fpkt->fcal_pkt_cookie = sf->sf_socp;
fpkt->fcal_pkt_comp = sf_els_callback;
fpkt->fcal_magic = FCALP_MAGIC;
fpkt->fcal_pkt_flags = 0;
fpkt->fcal_socal_request.sr_soc_hdr.sh_flags =
(ushort_t)(SOC_FC_HEADER | sf->sf_sochandle->fcal_portno);
fpkt->fcal_socal_request.sr_soc_hdr.sh_class = 3;
fpkt->fcal_socal_request.sr_soc_hdr.sh_seg_cnt = 2;
fpkt->fcal_socal_request.sr_soc_hdr.sh_byte_cnt = cmd_size;
fpkt->fcal_socal_request.sr_cqhdr.cq_hdr_count = 1;
fpkt->fcal_socal_request.sr_cqhdr.cq_hdr_flags = 0;
fpkt->fcal_socal_request.sr_cqhdr.cq_hdr_seqno = 0;
fpkt->fcal_socal_request.sr_cqhdr.cq_hdr_type = CQ_TYPE_SIMPLE;
fpkt->fcal_socal_request.sr_dataseg[0].fc_base = (uint32_t)
pcookie.dmac_address;
fpkt->fcal_socal_request.sr_dataseg[0].fc_count = cmd_size;
fpkt->fcal_socal_request.sr_dataseg[1].fc_base = (uint32_t)
rcookie.dmac_address;
fpkt->fcal_socal_request.sr_dataseg[1].fc_count = rsp_size;
/* Fill in the Fabric Channel Header */
hp = &fpkt->fcal_socal_request.sr_fc_frame_hdr;
hp->r_ctl = R_CTL_ELS_REQ;
hp->d_id = dest_id;
hp->s_id = sf->sf_al_pa;
hp->type = TYPE_EXTENDED_LS;
hp->reserved1 = 0;
hp->f_ctl = F_CTL_SEQ_INITIATIVE | F_CTL_FIRST_SEQ;
hp->seq_id = 0;
hp->df_ctl = 0;
hp->seq_cnt = 0;
hp->ox_id = 0xffff;
hp->rx_id = 0xffff;
hp->ro = 0;
*rprivp = (caddr_t)privp;
*cmd_buf = cmd;
return (fpkt);
fail:
if (cmd_dma_handle != NULL) {
if (cmd_bound) {
(void) ddi_dma_unbind_handle(cmd_dma_handle);
}
ddi_dma_free_handle(&cmd_dma_handle);
privp->cmd_dma_handle = NULL;
}
if (rsp_dma_handle != NULL) {
if (rsp_bound) {
(void) ddi_dma_unbind_handle(rsp_dma_handle);
}
ddi_dma_free_handle(&rsp_dma_handle);
privp->rsp_dma_handle = NULL;
}
sf_els_free(fpkt);
return (NULL);
}
static void
sf_els_free(struct fcal_packet *fpkt)
{
struct sf_els_hdr *privp = fpkt->fcal_pkt_private;
if (privp != NULL) {
if (privp->cmd_dma_handle != NULL) {
(void) ddi_dma_unbind_handle(privp->cmd_dma_handle);
ddi_dma_free_handle(&privp->cmd_dma_handle);
}
if (privp->cmd != NULL) {
ddi_dma_mem_free(&privp->cmd_acc_handle);
}
if (privp->rsp_dma_handle != NULL) {
(void) ddi_dma_unbind_handle(privp->rsp_dma_handle);
ddi_dma_free_handle(&privp->rsp_dma_handle);
}
if (privp->rsp != NULL) {
ddi_dma_mem_free(&privp->rsp_acc_handle);
}
if (privp->data_dma_handle) {
(void) ddi_dma_unbind_handle(privp->data_dma_handle);
ddi_dma_free_handle(&privp->data_dma_handle);
}
if (privp->data_buf) {
ddi_dma_mem_free(&privp->data_acc_handle);
}
kmem_free(privp, privp->size);
}
kmem_free(fpkt, sizeof (struct fcal_packet));
}
static struct sf_target *
sf_create_target(struct sf *sf, struct sf_els_hdr *privp, int tnum, int64_t lun)
{
struct sf_target *target, *ntarget, *otarget, *ptarget;
int hash;
#ifdef RAID_LUNS
int64_t orig_lun = lun;
/* XXXX Work around SCSA limitations. */
lun = *((short *)&lun);
#endif
ntarget = kmem_zalloc(sizeof (struct sf_target), KM_NOSLEEP);
mutex_enter(&sf->sf_mutex);
if (sf->sf_lip_cnt != privp->lip_cnt) {
mutex_exit(&sf->sf_mutex);
if (ntarget != NULL)
kmem_free(ntarget, sizeof (struct sf_target));
return (NULL);
}
target = sf_lookup_target(sf, privp->port_wwn, lun);
if (lun != 0) {
/*
* Since LUNs != 0 are queued up after LUN == 0, find LUN == 0
* and enqueue the new LUN.
*/
if ((ptarget = sf_lookup_target(sf, privp->port_wwn,
(int64_t)0)) == NULL) {
/*
* Yeep -- no LUN 0?
*/
mutex_exit(&sf->sf_mutex);
sf_log(sf, CE_WARN, "target 0x%x "
"lun %" PRIx64 ": No LUN 0\n", tnum, lun);
if (ntarget != NULL)
kmem_free(ntarget, sizeof (struct sf_target));
return (NULL);
}
mutex_enter(&ptarget->sft_mutex);
if (target != NULL && ptarget->sft_lip_cnt == sf->sf_lip_cnt &&
ptarget->sft_state&SF_TARGET_OFFLINE) {
/* LUN 0 already finished, duplicate its state */
mutex_exit(&ptarget->sft_mutex);
sf_offline_target(sf, target);
mutex_exit(&sf->sf_mutex);
if (ntarget != NULL)
kmem_free(ntarget, sizeof (struct sf_target));
return (target);
} else if (target != NULL) {
/*
* LUN 0 online or not examined yet.
* Try to bring the LUN back online
*/
mutex_exit(&ptarget->sft_mutex);
mutex_enter(&target->sft_mutex);
target->sft_lip_cnt = privp->lip_cnt;
target->sft_state |= SF_TARGET_BUSY;
target->sft_state &= ~(SF_TARGET_OFFLINE|
SF_TARGET_MARK);
target->sft_al_pa = (uchar_t)privp->dest_nport_id;
target->sft_hard_address = sf_switch_to_alpa[tnum];
mutex_exit(&target->sft_mutex);
mutex_exit(&sf->sf_mutex);
if (ntarget != NULL)
kmem_free(ntarget, sizeof (struct sf_target));
return (target);
}
mutex_exit(&ptarget->sft_mutex);
if (ntarget == NULL) {
mutex_exit(&sf->sf_mutex);
return (NULL);
}
/* Initialize new target structure */
bcopy((caddr_t)&privp->node_wwn,
(caddr_t)&ntarget->sft_node_wwn, sizeof (privp->node_wwn));
bcopy((caddr_t)&privp->port_wwn,
(caddr_t)&ntarget->sft_port_wwn, sizeof (privp->port_wwn));
ntarget->sft_lun.l = lun;
#ifdef RAID_LUNS
ntarget->sft_lun.l = orig_lun;
ntarget->sft_raid_lun = (uint_t)lun;
#endif
mutex_init(&ntarget->sft_mutex, NULL, MUTEX_DRIVER, NULL);
mutex_init(&ntarget->sft_pkt_mutex, NULL, MUTEX_DRIVER, NULL);
/* Don't let anyone use this till we finishup init. */
mutex_enter(&ntarget->sft_mutex);
mutex_enter(&ntarget->sft_pkt_mutex);
hash = SF_HASH(privp->port_wwn, lun);
ntarget->sft_next = sf->sf_wwn_lists[hash];
sf->sf_wwn_lists[hash] = ntarget;
ntarget->sft_lip_cnt = privp->lip_cnt;
ntarget->sft_al_pa = (uchar_t)privp->dest_nport_id;
ntarget->sft_hard_address = sf_switch_to_alpa[tnum];
ntarget->sft_device_type = DTYPE_UNKNOWN;
ntarget->sft_state = SF_TARGET_BUSY;
ntarget->sft_pkt_head = (struct sf_pkt *)&ntarget->
sft_pkt_head;
ntarget->sft_pkt_tail = (struct sf_pkt *)&ntarget->
sft_pkt_head;
mutex_enter(&ptarget->sft_mutex);
/* Traverse the list looking for this target */
for (target = ptarget; target->sft_next_lun;
target = target->sft_next_lun) {
otarget = target->sft_next_lun;
}
ntarget->sft_next_lun = target->sft_next_lun;
target->sft_next_lun = ntarget;
mutex_exit(&ptarget->sft_mutex);
mutex_exit(&ntarget->sft_pkt_mutex);
mutex_exit(&ntarget->sft_mutex);
mutex_exit(&sf->sf_mutex);
return (ntarget);
}
if (target != NULL && target->sft_lip_cnt == sf->sf_lip_cnt) {
/* It's been touched this LIP -- duplicate WWNs */
sf_offline_target(sf, target); /* And all the baby targets */
mutex_exit(&sf->sf_mutex);
sf_log(sf, CE_WARN, "target 0x%x, duplicate port wwns\n",
tnum);
if (ntarget != NULL) {
kmem_free(ntarget, sizeof (struct sf_target));
}
return (NULL);
}
if ((otarget = sf->sf_targets[tnum]) != NULL) {
/* Someone else is in our slot */
mutex_enter(&otarget->sft_mutex);
if (otarget->sft_lip_cnt == sf->sf_lip_cnt) {
mutex_exit(&otarget->sft_mutex);
sf_offline_target(sf, otarget);
if (target != NULL)
sf_offline_target(sf, target);
mutex_exit(&sf->sf_mutex);
sf_log(sf, CE_WARN,
"target 0x%x, duplicate switch settings\n", tnum);
if (ntarget != NULL)
kmem_free(ntarget, sizeof (struct sf_target));
return (NULL);
}
mutex_exit(&otarget->sft_mutex);
if (bcmp((caddr_t)&privp->port_wwn, (caddr_t)&otarget->
sft_port_wwn, sizeof (privp->port_wwn))) {
sf_offline_target(sf, otarget);
mutex_exit(&sf->sf_mutex);
sf_log(sf, CE_WARN, "wwn changed on target 0x%x\n",
tnum);
bzero((caddr_t)&sf->sf_stats.tstats[tnum],
sizeof (struct sf_target_stats));
mutex_enter(&sf->sf_mutex);
}
}
sf->sf_targets[tnum] = target;
if ((target = sf->sf_targets[tnum]) == NULL) {
if (ntarget == NULL) {
mutex_exit(&sf->sf_mutex);
return (NULL);
}
bcopy((caddr_t)&privp->node_wwn,
(caddr_t)&ntarget->sft_node_wwn, sizeof (privp->node_wwn));
bcopy((caddr_t)&privp->port_wwn,
(caddr_t)&ntarget->sft_port_wwn, sizeof (privp->port_wwn));
ntarget->sft_lun.l = lun;
#ifdef RAID_LUNS
ntarget->sft_lun.l = orig_lun;
ntarget->sft_raid_lun = (uint_t)lun;
#endif
mutex_init(&ntarget->sft_mutex, NULL, MUTEX_DRIVER, NULL);
mutex_init(&ntarget->sft_pkt_mutex, NULL, MUTEX_DRIVER, NULL);
mutex_enter(&ntarget->sft_mutex);
mutex_enter(&ntarget->sft_pkt_mutex);
hash = SF_HASH(privp->port_wwn, lun); /* lun 0 */
ntarget->sft_next = sf->sf_wwn_lists[hash];
sf->sf_wwn_lists[hash] = ntarget;
target = ntarget;
target->sft_lip_cnt = privp->lip_cnt;
target->sft_al_pa = (uchar_t)privp->dest_nport_id;
target->sft_hard_address = sf_switch_to_alpa[tnum];
target->sft_device_type = DTYPE_UNKNOWN;
target->sft_state = SF_TARGET_BUSY;
target->sft_pkt_head = (struct sf_pkt *)&target->
sft_pkt_head;
target->sft_pkt_tail = (struct sf_pkt *)&target->
sft_pkt_head;
sf->sf_targets[tnum] = target;
mutex_exit(&ntarget->sft_mutex);
mutex_exit(&ntarget->sft_pkt_mutex);
mutex_exit(&sf->sf_mutex);
} else {
mutex_enter(&target->sft_mutex);
target->sft_lip_cnt = privp->lip_cnt;
target->sft_state |= SF_TARGET_BUSY;
target->sft_state &= ~(SF_TARGET_OFFLINE|SF_TARGET_MARK);
target->sft_al_pa = (uchar_t)privp->dest_nport_id;
target->sft_hard_address = sf_switch_to_alpa[tnum];
mutex_exit(&target->sft_mutex);
mutex_exit(&sf->sf_mutex);
if (ntarget != NULL)
kmem_free(ntarget, sizeof (struct sf_target));
}
return (target);
}
/*
* find the target for a given sf instance
*/
/* ARGSUSED */
static struct sf_target *
#ifdef RAID_LUNS
sf_lookup_target(struct sf *sf, uchar_t *wwn, int lun)
#else
sf_lookup_target(struct sf *sf, uchar_t *wwn, int64_t lun)
#endif
{
int hash;
struct sf_target *target;
ASSERT(mutex_owned(&sf->sf_mutex));
hash = SF_HASH(wwn, lun);
target = sf->sf_wwn_lists[hash];
while (target != NULL) {
#ifndef RAID_LUNS
if (bcmp((caddr_t)wwn, (caddr_t)&target->sft_port_wwn,
sizeof (target->sft_port_wwn)) == 0 &&
target->sft_lun.l == lun)
break;
#else
if (bcmp((caddr_t)wwn, (caddr_t)&target->sft_port_wwn,
sizeof (target->sft_port_wwn)) == 0 &&
target->sft_raid_lun == lun)
break;
#endif
target = target->sft_next;
}
return (target);
}
/*
* Send out a REPORT_LUNS command.
*/
static int
sf_do_reportlun(struct sf *sf, struct sf_els_hdr *privp,
struct sf_target *target)
{
struct fcal_packet *fpkt = privp->fpkt;
ddi_dma_cookie_t pcookie;
ddi_dma_handle_t lun_dma_handle = NULL;
ddi_acc_handle_t lun_acc_handle;
uint_t ccount;
size_t real_size;
caddr_t lun_buf = NULL;
int handle_bound = 0;
fc_frame_header_t *hp = &fpkt->fcal_socal_request.sr_fc_frame_hdr;
struct fcp_cmd *reportlun = (struct fcp_cmd *)privp->cmd;
char *msg = "Transport";
if (ddi_dma_alloc_handle(sf->sf_dip, sf->sf_sochandle->fcal_dmaattr,
DDI_DMA_DONTWAIT, NULL, &lun_dma_handle) != DDI_SUCCESS) {
msg = "ddi_dma_alloc_handle()";
goto fail;
}
if (ddi_dma_mem_alloc(lun_dma_handle, REPORT_LUNS_SIZE,
sf->sf_sochandle->fcal_accattr, DDI_DMA_CONSISTENT,
DDI_DMA_DONTWAIT, NULL, &lun_buf,
&real_size, &lun_acc_handle) != DDI_SUCCESS) {
msg = "ddi_dma_mem_alloc()";
goto fail;
}
if (real_size < REPORT_LUNS_SIZE) {
msg = "DMA mem < REPORT_LUNS_SIZE";
goto fail;
}
if (ddi_dma_addr_bind_handle(lun_dma_handle, NULL,
lun_buf, real_size, DDI_DMA_READ |
DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT,
NULL, &pcookie, &ccount) != DDI_DMA_MAPPED) {
msg = "ddi_dma_addr_bind_handle()";
goto fail;
}
handle_bound = 1;
if (ccount != 1) {
msg = "ccount != 1";
goto fail;
}
privp->els_code = 0;
privp->target = target;
privp->data_dma_handle = lun_dma_handle;
privp->data_acc_handle = lun_acc_handle;
privp->data_buf = lun_buf;
fpkt->fcal_pkt_comp = sf_reportlun_callback;
fpkt->fcal_socal_request.sr_soc_hdr.sh_seg_cnt = 3;
fpkt->fcal_socal_request.sr_cqhdr.cq_hdr_type = CQ_TYPE_IO_READ;
fpkt->fcal_socal_request.sr_dataseg[0].fc_count =
sizeof (struct fcp_cmd);
fpkt->fcal_socal_request.sr_dataseg[2].fc_base =
(uint32_t)pcookie.dmac_address;
fpkt->fcal_socal_request.sr_dataseg[2].fc_count = pcookie.dmac_size;
fpkt->fcal_socal_request.sr_soc_hdr.sh_byte_cnt = pcookie.dmac_size;
hp->r_ctl = R_CTL_COMMAND;
hp->type = TYPE_SCSI_FCP;
bzero((caddr_t)reportlun, sizeof (struct fcp_cmd));
((union scsi_cdb *)reportlun->fcp_cdb)->scc_cmd = SCMD_REPORT_LUNS;
/* Now set the buffer size. If DDI gave us extra, that's O.K. */
((union scsi_cdb *)reportlun->fcp_cdb)->scc5_count0 =
(real_size&0x0ff);
((union scsi_cdb *)reportlun->fcp_cdb)->scc5_count1 =
(real_size>>8)&0x0ff;
((union scsi_cdb *)reportlun->fcp_cdb)->scc5_count2 =
(real_size>>16)&0x0ff;
((union scsi_cdb *)reportlun->fcp_cdb)->scc5_count3 =
(real_size>>24)&0x0ff;
reportlun->fcp_cntl.cntl_read_data = 1;
reportlun->fcp_cntl.cntl_write_data = 0;
reportlun->fcp_data_len = pcookie.dmac_size;
reportlun->fcp_cntl.cntl_qtype = FCP_QTYPE_SIMPLE;
(void) ddi_dma_sync(lun_dma_handle, 0, 0, DDI_DMA_SYNC_FORDEV);
/* We know he's there, so this should be fast */
privp->timeout = sf_watchdog_time + SF_FCP_TIMEOUT;
if (sf_els_transport(sf, privp) == 1)
return (1);
fail:
sf_log(sf, CE_WARN,
"%s failure for REPORTLUN to target 0x%x\n",
msg, sf_alpa_to_switch[privp->dest_nport_id]);
sf_els_free(fpkt);
if (lun_dma_handle != NULL) {
if (handle_bound)
(void) ddi_dma_unbind_handle(lun_dma_handle);
ddi_dma_free_handle(&lun_dma_handle);
}
if (lun_buf != NULL) {
ddi_dma_mem_free(&lun_acc_handle);
}
return (0);
}
/*
* Handle the results of a REPORT_LUNS command:
* Create additional targets if necessary
* Initiate INQUIRYs on all LUNs.
*/
static void
sf_reportlun_callback(struct fcal_packet *fpkt)
{
struct sf_els_hdr *privp = (struct sf_els_hdr *)fpkt->
fcal_pkt_private;
struct scsi_report_luns *ptr =
(struct scsi_report_luns *)privp->data_buf;
struct sf *sf = privp->sf;
struct sf_target *target = privp->target;
struct fcp_rsp *rsp = NULL;
int delayed_retry = 0;
int tid = sf_alpa_to_switch[target->sft_hard_address];
int i, free_pkt = 1;
short ncmds;
mutex_enter(&sf->sf_mutex);
/* use as temporary state variable */
if (privp->timeout == SF_INVALID_TIMEOUT) {
mutex_exit(&sf->sf_mutex);
return;
}
if (privp->prev)
privp->prev->next = privp->next;
if (privp->next)
privp->next->prev = privp->prev;
if (sf->sf_els_list == privp)
sf->sf_els_list = privp->next;
privp->prev = privp->next = NULL;
mutex_exit(&sf->sf_mutex);
ncmds = fpkt->fcal_ncmds;
ASSERT(ncmds >= 0);
mutex_enter(&sf->sf_cmd_mutex);
sf->sf_ncmds = ncmds;
mutex_exit(&sf->sf_cmd_mutex);
if (fpkt->fcal_pkt_status == FCAL_STATUS_OK) {
(void) ddi_dma_sync(privp->rsp_dma_handle, 0,
0, DDI_DMA_SYNC_FORKERNEL);
rsp = (struct fcp_rsp *)privp->rsp;
}
SF_DEBUG(1, (sf, CE_CONT,
"!REPORTLUN to al_pa %x pkt status %x scsi status %x\n",
privp->dest_nport_id,
fpkt->fcal_pkt_status,
rsp?rsp->fcp_u.fcp_status.scsi_status:0));
/* See if target simply does not support REPORT_LUNS. */
if (rsp && rsp->fcp_u.fcp_status.scsi_status == STATUS_CHECK &&
rsp->fcp_u.fcp_status.sense_len_set &&
rsp->fcp_sense_len >=
offsetof(struct scsi_extended_sense, es_qual_code)) {
struct scsi_extended_sense *sense;
sense = (struct scsi_extended_sense *)
((caddr_t)rsp + sizeof (struct fcp_rsp)
+ rsp->fcp_response_len);
if (sense->es_key == KEY_ILLEGAL_REQUEST) {
if (sense->es_add_code == 0x20) {
/* Fake LUN 0 */
SF_DEBUG(1, (sf, CE_CONT,
"!REPORTLUN Faking good "
"completion for alpa %x\n",
privp->dest_nport_id));
ptr->lun_list_len = FCP_LUN_SIZE;
ptr->lun[0] = 0;
rsp->fcp_u.fcp_status.scsi_status =
STATUS_GOOD;
} else if (sense->es_add_code == 0x25) {
SF_DEBUG(1, (sf, CE_CONT,
"!REPORTLUN device alpa %x "
"key %x code %x\n",
privp->dest_nport_id,
sense->es_key, sense->es_add_code));
goto fail;
}
} else if (sense->es_key ==
KEY_UNIT_ATTENTION &&
sense->es_add_code == 0x29) {
SF_DEBUG(1, (sf, CE_CONT,
"!REPORTLUN device alpa %x was reset\n",
privp->dest_nport_id));
} else {
SF_DEBUG(1, (sf, CE_CONT,
"!REPORTLUN device alpa %x "
"key %x code %x\n",
privp->dest_nport_id,
sense->es_key, sense->es_add_code));
/* XXXXXX The following is here to handle broken targets -- remove it later */
if (sf_reportlun_forever &&
sense->es_key == KEY_UNIT_ATTENTION)
goto retry;
/* XXXXXX */
if (sense->es_key == KEY_NOT_READY)
delayed_retry = 1;
}
}
if (rsp && rsp->fcp_u.fcp_status.scsi_status == STATUS_GOOD) {
struct fcp_rsp_info *bep;
bep = (struct fcp_rsp_info *)(&rsp->
fcp_response_len + 1);
if (!rsp->fcp_u.fcp_status.rsp_len_set ||
bep->rsp_code == FCP_NO_FAILURE) {
(void) ddi_dma_sync(privp->data_dma_handle,
0, 0, DDI_DMA_SYNC_FORKERNEL);
/* Convert from #bytes to #ints */
ptr->lun_list_len = ptr->lun_list_len >> 3;
SF_DEBUG(2, (sf, CE_CONT,
"!REPORTLUN to al_pa %x succeeded: %d LUNs\n",
privp->dest_nport_id, ptr->lun_list_len));
if (!ptr->lun_list_len) {
/* No LUNs? Ya gotta be kidding... */
sf_log(sf, CE_WARN,
"SCSI violation -- "
"target 0x%x reports no LUNs\n",
sf_alpa_to_switch[
privp->dest_nport_id]);
ptr->lun_list_len = 1;
ptr->lun[0] = 0;
}
mutex_enter(&sf->sf_mutex);
if (sf->sf_lip_cnt == privp->lip_cnt) {
sf->sf_device_count += ptr->lun_list_len - 1;
}
mutex_exit(&sf->sf_mutex);
for (i = 0; i < ptr->lun_list_len && privp->lip_cnt ==
sf->sf_lip_cnt; i++) {
struct sf_els_hdr *nprivp;
struct fcal_packet *nfpkt;
/* LUN 0 is already in `target' */
if (ptr->lun[i] != 0) {
target = sf_create_target(sf,
privp, tid, ptr->lun[i]);
}
nprivp = NULL;
nfpkt = NULL;
if (target) {
nfpkt = sf_els_alloc(sf,
target->sft_al_pa,
sizeof (struct sf_els_hdr),
sizeof (union sf_els_cmd),
sizeof (union sf_els_rsp),
(caddr_t *)&nprivp,
(caddr_t *)&rsp);
if (nprivp)
nprivp->lip_cnt =
privp->lip_cnt;
}
if (nfpkt && nprivp &&
(sf_do_inquiry(sf, nprivp, target) ==
0)) {
mutex_enter(&sf->sf_mutex);
if (sf->sf_lip_cnt == privp->
lip_cnt) {
sf->sf_device_count --;
}
sf_offline_target(sf, target);
mutex_exit(&sf->sf_mutex);
}
}
sf_els_free(fpkt);
return;
} else {
SF_DEBUG(1, (sf, CE_CONT,
"!REPORTLUN al_pa %x fcp failure, "
"fcp_rsp_code %x scsi status %x\n",
privp->dest_nport_id, bep->rsp_code,
rsp ? rsp->fcp_u.fcp_status.scsi_status:0));
goto fail;
}
}
if (rsp && ((rsp->fcp_u.fcp_status.scsi_status == STATUS_BUSY) ||
(rsp->fcp_u.fcp_status.scsi_status == STATUS_QFULL))) {
delayed_retry = 1;
}
if (++(privp->retries) < sf_els_retries ||
(delayed_retry && privp->retries < SF_BSY_RETRIES)) {
/* XXXXXX The following is here to handle broken targets -- remove it later */
retry:
/* XXXXXX */
if (delayed_retry) {
privp->retries--;
privp->timeout = sf_watchdog_time + SF_BSY_TIMEOUT;
privp->delayed_retry = 1;
} else {
privp->timeout = sf_watchdog_time + SF_FCP_TIMEOUT;
}
privp->prev = NULL;
mutex_enter(&sf->sf_mutex);
if (privp->lip_cnt == sf->sf_lip_cnt) {
if (!delayed_retry)
SF_DEBUG(1, (sf, CE_WARN,
"!REPORTLUN to al_pa %x failed, retrying\n",
privp->dest_nport_id));
privp->next = sf->sf_els_list;
if (sf->sf_els_list != NULL)
sf->sf_els_list->prev = privp;
sf->sf_els_list = privp;
mutex_exit(&sf->sf_mutex);
if (!delayed_retry && soc_transport(sf->sf_sochandle,
fpkt, FCAL_NOSLEEP, CQ_REQUEST_1) !=
FCAL_TRANSPORT_SUCCESS) {
mutex_enter(&sf->sf_mutex);
if (privp->prev)
privp->prev->next = privp->next;
if (privp->next)
privp->next->prev = privp->prev;
if (sf->sf_els_list == privp)
sf->sf_els_list = privp->next;
mutex_exit(&sf->sf_mutex);
goto fail;
} else
return;
} else {
mutex_exit(&sf->sf_mutex);
}
} else {
fail:
/* REPORT_LUN failed -- try inquiry */
if (sf_do_inquiry(sf, privp, target) != 0) {
return;
} else {
free_pkt = 0;
}
mutex_enter(&sf->sf_mutex);
if (sf->sf_lip_cnt == privp->lip_cnt) {
sf_log(sf, CE_WARN,
"!REPORTLUN to target 0x%x failed\n",
sf_alpa_to_switch[privp->dest_nport_id]);
sf_offline_target(sf, target);
sf->sf_device_count--;
ASSERT(sf->sf_device_count >= 0);
if (sf->sf_device_count == 0)
sf_finish_init(sf, privp->lip_cnt);
}
mutex_exit(&sf->sf_mutex);
}
if (free_pkt) {
sf_els_free(fpkt);
}
}
static int
sf_do_inquiry(struct sf *sf, struct sf_els_hdr *privp,
struct sf_target *target)
{
struct fcal_packet *fpkt = privp->fpkt;
ddi_dma_cookie_t pcookie;
ddi_dma_handle_t inq_dma_handle = NULL;
ddi_acc_handle_t inq_acc_handle;
uint_t ccount;
size_t real_size;
caddr_t inq_buf = NULL;
int handle_bound = FALSE;
fc_frame_header_t *hp = &fpkt->fcal_socal_request.sr_fc_frame_hdr;
struct fcp_cmd *inq = (struct fcp_cmd *)privp->cmd;
char *msg = "Transport";
if (ddi_dma_alloc_handle(sf->sf_dip, sf->sf_sochandle->fcal_dmaattr,
DDI_DMA_DONTWAIT, NULL, &inq_dma_handle) != DDI_SUCCESS) {
msg = "ddi_dma_alloc_handle()";
goto fail;
}
if (ddi_dma_mem_alloc(inq_dma_handle, SUN_INQSIZE,
sf->sf_sochandle->fcal_accattr, DDI_DMA_CONSISTENT,
DDI_DMA_DONTWAIT, NULL, &inq_buf,
&real_size, &inq_acc_handle) != DDI_SUCCESS) {
msg = "ddi_dma_mem_alloc()";
goto fail;
}
if (real_size < SUN_INQSIZE) {
msg = "DMA mem < inquiry size";
goto fail;
}
if (ddi_dma_addr_bind_handle(inq_dma_handle, NULL,
inq_buf, real_size, DDI_DMA_READ | DDI_DMA_CONSISTENT,
DDI_DMA_DONTWAIT, NULL, &pcookie, &ccount) != DDI_DMA_MAPPED) {
msg = "ddi_dma_addr_bind_handle()";
goto fail;
}
handle_bound = TRUE;
if (ccount != 1) {
msg = "ccount != 1";
goto fail;
}
privp->els_code = 0; /* not an ELS command */
privp->target = target;
privp->data_dma_handle = inq_dma_handle;
privp->data_acc_handle = inq_acc_handle;
privp->data_buf = inq_buf;
fpkt->fcal_pkt_comp = sf_inq_callback;
fpkt->fcal_socal_request.sr_soc_hdr.sh_seg_cnt = 3;
fpkt->fcal_socal_request.sr_cqhdr.cq_hdr_type = CQ_TYPE_IO_READ;
fpkt->fcal_socal_request.sr_dataseg[0].fc_count =
sizeof (struct fcp_cmd);
fpkt->fcal_socal_request.sr_dataseg[2].fc_base =
(uint32_t)pcookie.dmac_address;
fpkt->fcal_socal_request.sr_dataseg[2].fc_count = pcookie.dmac_size;
fpkt->fcal_socal_request.sr_soc_hdr.sh_byte_cnt = pcookie.dmac_size;
hp->r_ctl = R_CTL_COMMAND;
hp->type = TYPE_SCSI_FCP;
bzero((caddr_t)inq, sizeof (struct fcp_cmd));
((union scsi_cdb *)inq->fcp_cdb)->scc_cmd = SCMD_INQUIRY;
((union scsi_cdb *)inq->fcp_cdb)->g0_count0 = SUN_INQSIZE;
bcopy((caddr_t)&target->sft_lun.b, (caddr_t)&inq->fcp_ent_addr,
FCP_LUN_SIZE);
inq->fcp_cntl.cntl_read_data = 1;
inq->fcp_cntl.cntl_write_data = 0;
inq->fcp_data_len = pcookie.dmac_size;
inq->fcp_cntl.cntl_qtype = FCP_QTYPE_SIMPLE;
(void) ddi_dma_sync(inq_dma_handle, (off_t)0, (size_t)0,
DDI_DMA_SYNC_FORDEV);
privp->timeout = sf_watchdog_time + SF_FCP_TIMEOUT;
SF_DEBUG(5, (sf, CE_WARN,
"!Sending INQUIRY to al_pa %x lun %" PRIx64 "\n",
privp->dest_nport_id,
SCSA_LUN(target)));
return (sf_els_transport(sf, privp));
fail:
sf_log(sf, CE_WARN,
"%s failure for INQUIRY to target 0x%x\n",
msg, sf_alpa_to_switch[privp->dest_nport_id]);
sf_els_free(fpkt);
if (inq_dma_handle != NULL) {
if (handle_bound) {
(void) ddi_dma_unbind_handle(inq_dma_handle);
}
ddi_dma_free_handle(&inq_dma_handle);
}
if (inq_buf != NULL) {
ddi_dma_mem_free(&inq_acc_handle);
}
return (FALSE);
}
/*
* called as the pkt_comp routine for INQ packets
*/
static void
sf_inq_callback(struct fcal_packet *fpkt)
{
struct sf_els_hdr *privp = (struct sf_els_hdr *)fpkt->
fcal_pkt_private;
struct scsi_inquiry *prt = (struct scsi_inquiry *)privp->data_buf;
struct sf *sf = privp->sf;
struct sf *tsf;
struct sf_target *target = privp->target;
struct fcp_rsp *rsp;
int delayed_retry = FALSE;
short ncmds;
mutex_enter(&sf->sf_mutex);
/* use as temporary state variable */
if (privp->timeout == SF_INVALID_TIMEOUT) {
mutex_exit(&sf->sf_mutex);
return;
}
if (privp->prev != NULL) {
privp->prev->next = privp->next;
}
if (privp->next != NULL) {
privp->next->prev = privp->prev;
}
if (sf->sf_els_list == privp) {
sf->sf_els_list = privp->next;
}
privp->prev = privp->next = NULL;
mutex_exit(&sf->sf_mutex);
ncmds = fpkt->fcal_ncmds;
ASSERT(ncmds >= 0);
mutex_enter(&sf->sf_cmd_mutex);
sf->sf_ncmds = ncmds;
mutex_exit(&sf->sf_cmd_mutex);
if (fpkt->fcal_pkt_status == FCAL_STATUS_OK) {
(void) ddi_dma_sync(privp->rsp_dma_handle, (off_t)0,
(size_t)0, DDI_DMA_SYNC_FORKERNEL);
rsp = (struct fcp_rsp *)privp->rsp;
SF_DEBUG(2, (sf, CE_CONT,
"!INQUIRY to al_pa %x scsi status %x",
privp->dest_nport_id, rsp->fcp_u.fcp_status.scsi_status));
if ((rsp->fcp_u.fcp_status.scsi_status == STATUS_GOOD) &&
!rsp->fcp_u.fcp_status.resid_over &&
(!rsp->fcp_u.fcp_status.resid_under ||
((SUN_INQSIZE - rsp->fcp_resid) >= SUN_MIN_INQLEN))) {
struct fcp_rsp_info *bep;
bep = (struct fcp_rsp_info *)(&rsp->
fcp_response_len + 1);
if (!rsp->fcp_u.fcp_status.rsp_len_set ||
(bep->rsp_code == FCP_NO_FAILURE)) {
SF_DEBUG(2, (sf, CE_CONT,
"!INQUIRY to al_pa %x lun %" PRIx64
" succeeded\n",
privp->dest_nport_id, SCSA_LUN(target)));
(void) ddi_dma_sync(privp->data_dma_handle,
(off_t)0, (size_t)0,
DDI_DMA_SYNC_FORKERNEL);
mutex_enter(&sf->sf_mutex);
if (sf->sf_lip_cnt == privp->lip_cnt) {
mutex_enter(&target->sft_mutex);
target->sft_device_type =
prt->inq_dtype;
bcopy(prt, &target->sft_inq,
sizeof (*prt));
mutex_exit(&target->sft_mutex);
sf->sf_device_count--;
ASSERT(sf->sf_device_count >= 0);
if (sf->sf_device_count == 0) {
sf_finish_init(sf,
privp->lip_cnt);
}
}
mutex_exit(&sf->sf_mutex);
sf_els_free(fpkt);
return;
}
} else if ((rsp->fcp_u.fcp_status.scsi_status ==
STATUS_BUSY) ||
(rsp->fcp_u.fcp_status.scsi_status == STATUS_QFULL) ||
(rsp->fcp_u.fcp_status.scsi_status == STATUS_CHECK)) {
delayed_retry = TRUE;
}
} else {
SF_DEBUG(2, (sf, CE_CONT, "!INQUIRY to al_pa %x fc status %x",
privp->dest_nport_id, fpkt->fcal_pkt_status));
}
if (++(privp->retries) < sf_els_retries ||
(delayed_retry && privp->retries < SF_BSY_RETRIES)) {
if (fpkt->fcal_pkt_status == FCAL_STATUS_MAX_XCHG_EXCEEDED) {
tsf = sf->sf_sibling;
if (tsf != NULL) {
mutex_enter(&tsf->sf_cmd_mutex);
tsf->sf_flag = 1;
tsf->sf_throttle = SF_DECR_DELTA;
mutex_exit(&tsf->sf_cmd_mutex);
}
delayed_retry = 1;
}
if (delayed_retry) {
privp->retries--;
privp->timeout = sf_watchdog_time + SF_BSY_TIMEOUT;
privp->delayed_retry = TRUE;
} else {
privp->timeout = sf_watchdog_time + SF_FCP_TIMEOUT;
}
privp->prev = NULL;
mutex_enter(&sf->sf_mutex);
if (privp->lip_cnt == sf->sf_lip_cnt) {
if (!delayed_retry) {
SF_DEBUG(1, (sf, CE_WARN,
"INQUIRY to al_pa %x failed, retrying",
privp->dest_nport_id));
}
privp->next = sf->sf_els_list;
if (sf->sf_els_list != NULL) {
sf->sf_els_list->prev = privp;
}
sf->sf_els_list = privp;
mutex_exit(&sf->sf_mutex);
/* if not delayed call transport to send a pkt */
if (!delayed_retry &&
(soc_transport(sf->sf_sochandle, fpkt,
FCAL_NOSLEEP, CQ_REQUEST_1) !=
FCAL_TRANSPORT_SUCCESS)) {
mutex_enter(&sf->sf_mutex);
if (privp->prev != NULL) {
privp->prev->next = privp->next;
}
if (privp->next != NULL) {
privp->next->prev = privp->prev;
}
if (sf->sf_els_list == privp) {
sf->sf_els_list = privp->next;
}
mutex_exit(&sf->sf_mutex);
goto fail;
}
return;
}
mutex_exit(&sf->sf_mutex);
} else {
fail:
mutex_enter(&sf->sf_mutex);
if (sf->sf_lip_cnt == privp->lip_cnt) {
sf_offline_target(sf, target);
sf_log(sf, CE_NOTE,
"INQUIRY to target 0x%x lun %" PRIx64 " failed. "
"Retry Count: %d\n",
sf_alpa_to_switch[privp->dest_nport_id],
SCSA_LUN(target),
privp->retries);
sf->sf_device_count--;
ASSERT(sf->sf_device_count >= 0);
if (sf->sf_device_count == 0) {
sf_finish_init(sf, privp->lip_cnt);
}
}
mutex_exit(&sf->sf_mutex);
}
sf_els_free(fpkt);
}
static void
sf_finish_init(struct sf *sf, int lip_cnt)
{
int i; /* loop index */
int cflag;
struct sf_target *target; /* current target */
dev_info_t *dip;
struct sf_hp_elem *elem; /* hotplug element created */
SF_DEBUG(1, (sf, CE_WARN, "!sf_finish_init\n"));
ASSERT(mutex_owned(&sf->sf_mutex));
/* scan all hash queues */
for (i = 0; i < SF_NUM_HASH_QUEUES; i++) {
target = sf->sf_wwn_lists[i];
while (target != NULL) {
mutex_enter(&target->sft_mutex);
/* see if target is not offline */
if ((target->sft_state & SF_TARGET_OFFLINE)) {
/*
* target already offline
*/
mutex_exit(&target->sft_mutex);
goto next_entry;
}
/*
* target is not already offline -- see if it has
* already been marked as ready to go offline
*/
if (target->sft_state & SF_TARGET_MARK) {
/*
* target already marked, so take it offline
*/
mutex_exit(&target->sft_mutex);
sf_offline_target(sf, target);
goto next_entry;
}
/* clear target busy flag */
target->sft_state &= ~SF_TARGET_BUSY;
/* is target init not yet done ?? */
cflag = !(target->sft_state & SF_TARGET_INIT_DONE);
/* get pointer to target dip */
dip = target->sft_dip;
mutex_exit(&target->sft_mutex);
mutex_exit(&sf->sf_mutex);
if (cflag && (dip == NULL)) {
/*
* target init not yet done &&
* devinfo not yet created
*/
sf_create_devinfo(sf, target, lip_cnt);
mutex_enter(&sf->sf_mutex);
goto next_entry;
}
/*
* target init already done || devinfo already created
*/
ASSERT(dip != NULL);
if (!sf_create_props(dip, target, lip_cnt)) {
/* a problem creating properties */
mutex_enter(&sf->sf_mutex);
goto next_entry;
}
/* create a new element for the hotplug list */
if ((elem = kmem_zalloc(sizeof (struct sf_hp_elem),
KM_NOSLEEP)) != NULL) {
/* fill in the new element */
elem->dip = dip;
elem->target = target;
elem->what = SF_ONLINE;
/* add the new element into the hotplug list */
mutex_enter(&sf->sf_hp_daemon_mutex);
if (sf->sf_hp_elem_tail != NULL) {
sf->sf_hp_elem_tail->next = elem;
sf->sf_hp_elem_tail = elem;
} else {
/* this is the first element in list */
sf->sf_hp_elem_head =
sf->sf_hp_elem_tail =
elem;
}
cv_signal(&sf->sf_hp_daemon_cv);
mutex_exit(&sf->sf_hp_daemon_mutex);
} else {
/* could not allocate memory for element ?? */
(void) ndi_devi_online_async(dip, 0);
}
mutex_enter(&sf->sf_mutex);
next_entry:
/* ensure no new LIPs have occurred */
if (sf->sf_lip_cnt != lip_cnt) {
return;
}
target = target->sft_next;
}
/* done scanning all targets in this queue */
}
/* done with all hash queues */
sf->sf_state = SF_STATE_ONLINE;
sf->sf_online_timer = 0;
}
/*
* create devinfo node
*/
static void
sf_create_devinfo(struct sf *sf, struct sf_target *target, int lip_cnt)
{
dev_info_t *cdip = NULL;
char *nname = NULL;
char **compatible = NULL;
int ncompatible;
struct scsi_inquiry *inq = &target->sft_inq;
char *scsi_binding_set;
/* get the 'scsi-binding-set' property */
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, sf->sf_dip,
DDI_PROP_NOTPROM | DDI_PROP_DONTPASS, "scsi-binding-set",
&scsi_binding_set) != DDI_PROP_SUCCESS)
scsi_binding_set = NULL;
/* determine the node name and compatible */
scsi_hba_nodename_compatible_get(inq, scsi_binding_set,
inq->inq_dtype, NULL, &nname, &compatible, &ncompatible);
if (scsi_binding_set)
ddi_prop_free(scsi_binding_set);
/* if nodename can't be determined then print a message and skip it */
if (nname == NULL) {
#ifndef RAID_LUNS
sf_log(sf, CE_WARN, "%s%d: no driver for device "
"@w%02x%02x%02x%02x%02x%02x%02x%02x,%x\n"
" compatible: %s",
ddi_driver_name(sf->sf_dip), ddi_get_instance(sf->sf_dip),
target->sft_port_wwn[0], target->sft_port_wwn[1],
target->sft_port_wwn[2], target->sft_port_wwn[3],
target->sft_port_wwn[4], target->sft_port_wwn[5],
target->sft_port_wwn[6], target->sft_port_wwn[7],
target->sft_lun.l, *compatible);
#else
sf_log(sf, CE_WARN, "%s%d: no driver for device "
"@w%02x%02x%02x%02x%02x%02x%02x%02x,%x\n"
" compatible: %s",
ddi_driver_name(sf->sf_dip), ddi_get_instance(sf->sf_dip),
target->sft_port_wwn[0], target->sft_port_wwn[1],
target->sft_port_wwn[2], target->sft_port_wwn[3],
target->sft_port_wwn[4], target->sft_port_wwn[5],
target->sft_port_wwn[6], target->sft_port_wwn[7],
target->sft_raid_lun, *compatible);
#endif
goto fail;
}
/* allocate the node */
if (ndi_devi_alloc(sf->sf_dip, nname,
DEVI_SID_NODEID, &cdip) != NDI_SUCCESS) {
goto fail;
}
/* decorate the node with compatible */
if (ndi_prop_update_string_array(DDI_DEV_T_NONE, cdip,
"compatible", compatible, ncompatible) != DDI_PROP_SUCCESS) {
goto fail;
}
/* add addressing properties to the node */
if (sf_create_props(cdip, target, lip_cnt) != 1) {
goto fail;
}
mutex_enter(&target->sft_mutex);
if (target->sft_dip != NULL) {
mutex_exit(&target->sft_mutex);
goto fail;
}
target->sft_dip = cdip;
mutex_exit(&target->sft_mutex);
if (ndi_devi_online_async(cdip, 0) != DDI_SUCCESS) {
goto fail;
}
scsi_hba_nodename_compatible_free(nname, compatible);
return;
fail:
scsi_hba_nodename_compatible_free(nname, compatible);
if (cdip != NULL) {
(void) ndi_prop_remove(DDI_DEV_T_NONE, cdip, NODE_WWN_PROP);
(void) ndi_prop_remove(DDI_DEV_T_NONE, cdip, PORT_WWN_PROP);
(void) ndi_prop_remove(DDI_DEV_T_NONE, cdip, LIP_CNT_PROP);
(void) ndi_prop_remove(DDI_DEV_T_NONE, cdip, TARGET_PROP);
(void) ndi_prop_remove(DDI_DEV_T_NONE, cdip, LUN_PROP);
if (ndi_devi_free(cdip) != NDI_SUCCESS) {
sf_log(sf, CE_WARN, "ndi_devi_free failed\n");
} else {
mutex_enter(&target->sft_mutex);
if (cdip == target->sft_dip) {
target->sft_dip = NULL;
}
mutex_exit(&target->sft_mutex);
}
}
}
/*
* create required properties, returning TRUE iff we succeed, else
* returning FALSE
*/
static int
sf_create_props(dev_info_t *cdip, struct sf_target *target, int lip_cnt)
{
int tgt_id = sf_alpa_to_switch[target->sft_al_pa];
if (ndi_prop_update_byte_array(DDI_DEV_T_NONE,
cdip, NODE_WWN_PROP, target->sft_node_wwn, FC_WWN_SIZE) !=
DDI_PROP_SUCCESS) {
return (FALSE);
}
if (ndi_prop_update_byte_array(DDI_DEV_T_NONE,
cdip, PORT_WWN_PROP, target->sft_port_wwn, FC_WWN_SIZE) !=
DDI_PROP_SUCCESS) {
return (FALSE);
}
if (ndi_prop_update_int(DDI_DEV_T_NONE,
cdip, LIP_CNT_PROP, lip_cnt) != DDI_PROP_SUCCESS) {
return (FALSE);
}
if (ndi_prop_update_int(DDI_DEV_T_NONE,
cdip, TARGET_PROP, tgt_id) != DDI_PROP_SUCCESS) {
return (FALSE);
}
#ifndef RAID_LUNS
if (ndi_prop_update_int(DDI_DEV_T_NONE,
cdip, LUN_PROP, target->sft_lun.l) != DDI_PROP_SUCCESS) {
return (0);
}
#else
if (ndi_prop_update_int(DDI_DEV_T_NONE,
cdip, LUN_PROP, target->sft_raid_lun) != DDI_PROP_SUCCESS) {
return (0);
}
#endif
return (TRUE);
}
/*
* called by the transport to offline a target
*/
/* ARGSUSED */
static void
sf_offline_target(struct sf *sf, struct sf_target *target)
{
dev_info_t *dip;
struct sf_target *next_target = NULL;
struct sf_hp_elem *elem;
ASSERT(mutex_owned(&sf->sf_mutex));
if (sf_core && (sf_core & SF_CORE_OFFLINE_TARGET)) {
(void) soc_take_core(sf->sf_sochandle, sf->sf_socp);
sf_core = 0;
}
while (target != NULL) {
sf_log(sf, CE_NOTE,
"!target 0x%x al_pa 0x%x lun %" PRIx64 " offlined\n",
sf_alpa_to_switch[target->sft_al_pa],
target->sft_al_pa, SCSA_LUN(target));
mutex_enter(&target->sft_mutex);
target->sft_state &= ~(SF_TARGET_BUSY|SF_TARGET_MARK);
target->sft_state |= SF_TARGET_OFFLINE;
mutex_exit(&target->sft_mutex);
mutex_exit(&sf->sf_mutex);
/* XXXX if this is LUN 0, offline all other LUNs */
if (next_target || target->sft_lun.l == 0)
next_target = target->sft_next_lun;
/* abort all cmds for this target */
sf_abort_all(sf, target, FALSE, sf->sf_lip_cnt, FALSE);
mutex_enter(&sf->sf_mutex);
mutex_enter(&target->sft_mutex);
if (target->sft_state & SF_TARGET_INIT_DONE) {
dip = target->sft_dip;
mutex_exit(&target->sft_mutex);
mutex_exit(&sf->sf_mutex);
(void) ndi_prop_remove(DDI_DEV_T_NONE, dip,
TARGET_PROP);
(void) ndi_event_retrieve_cookie(sf->sf_event_hdl,
dip, FCAL_REMOVE_EVENT, &sf_remove_eid,
NDI_EVENT_NOPASS);
(void) ndi_event_run_callbacks(sf->sf_event_hdl,
target->sft_dip, sf_remove_eid, NULL);
elem = kmem_zalloc(sizeof (struct sf_hp_elem),
KM_NOSLEEP);
if (elem != NULL) {
elem->dip = dip;
elem->target = target;
elem->what = SF_OFFLINE;
mutex_enter(&sf->sf_hp_daemon_mutex);
if (sf->sf_hp_elem_tail != NULL) {
sf->sf_hp_elem_tail->next = elem;
sf->sf_hp_elem_tail = elem;
} else {
sf->sf_hp_elem_head =
sf->sf_hp_elem_tail =
elem;
}
cv_signal(&sf->sf_hp_daemon_cv);
mutex_exit(&sf->sf_hp_daemon_mutex);
} else {
/* don't do NDI_DEVI_REMOVE for now */
if (ndi_devi_offline(dip, 0) != NDI_SUCCESS) {
SF_DEBUG(1, (sf, CE_WARN,
"target %x lun %" PRIx64 ", "
"device offline failed",
sf_alpa_to_switch[target->
sft_al_pa],
SCSA_LUN(target)));
} else {
SF_DEBUG(1, (sf, CE_NOTE,
"target %x, lun %" PRIx64 ", "
"device offline succeeded\n",
sf_alpa_to_switch[target->
sft_al_pa],
SCSA_LUN(target)));
}
}
mutex_enter(&sf->sf_mutex);
} else {
mutex_exit(&target->sft_mutex);
}
target = next_target;
}
}
/*
* routine to get/set a capability
*
* returning:
* 1 (TRUE) boolean capability is true (on get)
* 0 (FALSE) invalid capability, can't set capability (on set),
* or boolean capability is false (on get)
* -1 (UNDEFINED) can't find capability (SCSA) or unsupported capability
* 3 when getting SCSI version number
* AL_PA when getting port initiator ID
*/
static int
sf_commoncap(struct scsi_address *ap, char *cap,
int val, int tgtonly, int doset)
{
struct sf *sf = ADDR2SF(ap);
int cidx;
int rval = FALSE;
if (cap == NULL) {
SF_DEBUG(3, (sf, CE_WARN, "sf_commoncap: invalid arg"));
return (rval);
}
/* get index of capability string */
if ((cidx = scsi_hba_lookup_capstr(cap)) == -1) {
/* can't find capability */
return (UNDEFINED);
}
if (doset) {
/*
* Process setcap request.
*/
/*
* At present, we can only set binary (0/1) values
*/
switch (cidx) {
case SCSI_CAP_ARQ: /* can't set this capability */
break;
default:
SF_DEBUG(3, (sf, CE_WARN,
"sf_setcap: unsupported %d", cidx));
rval = UNDEFINED;
break;
}
SF_DEBUG(4, (sf, CE_NOTE,
"set cap: cap=%s,val=0x%x,tgtonly=0x%x"
",doset=0x%x,rval=%d\n",
cap, val, tgtonly, doset, rval));
} else {
/*
* Process getcap request.
*/
switch (cidx) {
case SCSI_CAP_DMA_MAX:
break; /* don't' have this capability */
case SCSI_CAP_INITIATOR_ID:
rval = sf->sf_al_pa;
break;
case SCSI_CAP_ARQ:
rval = TRUE; /* do have this capability */
break;
case SCSI_CAP_RESET_NOTIFICATION:
case SCSI_CAP_TAGGED_QING:
rval = TRUE; /* do have this capability */
break;
case SCSI_CAP_SCSI_VERSION:
rval = 3;
break;
case SCSI_CAP_INTERCONNECT_TYPE:
rval = INTERCONNECT_FIBRE;
break;
default:
SF_DEBUG(4, (sf, CE_WARN,
"sf_scsi_getcap: unsupported"));
rval = UNDEFINED;
break;
}
SF_DEBUG(4, (sf, CE_NOTE,
"get cap: cap=%s,val=0x%x,tgtonly=0x%x,"
"doset=0x%x,rval=%d\n",
cap, val, tgtonly, doset, rval));
}
return (rval);
}
/*
* called by the transport to get a capability
*/
static int
sf_getcap(struct scsi_address *ap, char *cap, int whom)
{
return (sf_commoncap(ap, cap, 0, whom, FALSE));
}
/*
* called by the transport to set a capability
*/
static int
sf_setcap(struct scsi_address *ap, char *cap, int value, int whom)
{
return (sf_commoncap(ap, cap, value, whom, TRUE));
}
/*
* called by the transport to abort a target
*/
static int
sf_abort(struct scsi_address *ap, struct scsi_pkt *pkt)
{
struct sf *sf = ADDR2SF(ap);
struct sf_target *target = ADDR2TARGET(ap);
struct sf_pkt *cmd, *ncmd, *pcmd;
struct fcal_packet *fpkt;
int rval = 0, t, my_rval = FALSE;
int old_target_state;
int lip_cnt;
int tgt_id;
fc_frame_header_t *hp;
int deferred_destroy;
deferred_destroy = 0;
if (pkt != NULL) {
cmd = PKT2CMD(pkt);
fpkt = cmd->cmd_fp_pkt;
SF_DEBUG(2, (sf, CE_NOTE, "sf_abort packet %p\n",
(void *)fpkt));
pcmd = NULL;
mutex_enter(&sf->sf_cmd_mutex);
ncmd = sf->sf_pkt_head;
while (ncmd != NULL) {
if (ncmd == cmd) {
if (pcmd != NULL) {
pcmd->cmd_next = cmd->cmd_next;
} else {
sf->sf_pkt_head = cmd->cmd_next;
}
cmd->cmd_flags &= ~CFLAG_IN_QUEUE;
cmd->cmd_state = SF_STATE_IDLE;
pkt->pkt_reason = CMD_ABORTED;
pkt->pkt_statistics |= STAT_ABORTED;
my_rval = TRUE;
break;
} else {
pcmd = ncmd;
ncmd = ncmd->cmd_next;
}
}
mutex_exit(&sf->sf_cmd_mutex);
if (ncmd == NULL) {
mutex_enter(&cmd->cmd_abort_mutex);
if (cmd->cmd_state == SF_STATE_ISSUED) {
cmd->cmd_state = SF_STATE_ABORTING;
cmd->cmd_timeout = sf_watchdog_time + 20;
mutex_exit(&cmd->cmd_abort_mutex);
/* call transport to abort command */
if (((rval = soc_abort(sf->sf_sochandle,
sf->sf_socp, sf->sf_sochandle->fcal_portno,
fpkt, 1)) == FCAL_ABORTED) ||
(rval == FCAL_ABORT_FAILED)) {
my_rval = TRUE;
pkt->pkt_reason = CMD_ABORTED;
pkt->pkt_statistics |= STAT_ABORTED;
cmd->cmd_state = SF_STATE_IDLE;
} else if (rval == FCAL_BAD_ABORT) {
cmd->cmd_timeout = sf_watchdog_time
+ 20;
my_rval = FALSE;
} else {
SF_DEBUG(1, (sf, CE_NOTE,
"Command Abort failed\n"));
}
} else {
mutex_exit(&cmd->cmd_abort_mutex);
}
}
} else {
SF_DEBUG(2, (sf, CE_NOTE, "sf_abort target\n"));
mutex_enter(&sf->sf_mutex);
lip_cnt = sf->sf_lip_cnt;
mutex_enter(&target->sft_mutex);
if (target->sft_state & (SF_TARGET_BUSY |
SF_TARGET_OFFLINE)) {
mutex_exit(&target->sft_mutex);
return (rval);
}
old_target_state = target->sft_state;
target->sft_state |= SF_TARGET_BUSY;
mutex_exit(&target->sft_mutex);
mutex_exit(&sf->sf_mutex);
if ((pkt = sf_scsi_init_pkt(ap, NULL, NULL, 0,
0, 0, 0, NULL, 0)) != NULL) {
cmd = PKT2CMD(pkt);
cmd->cmd_block->fcp_cntl.cntl_abort_tsk = 1;
cmd->cmd_fp_pkt->fcal_pkt_comp = NULL;
cmd->cmd_pkt->pkt_flags |= FLAG_NOINTR;
/* prepare the packet for transport */
if (sf_prepare_pkt(sf, cmd, target) == TRAN_ACCEPT) {
cmd->cmd_state = SF_STATE_ISSUED;
/*
* call transport to send a pkt polled
*
* if that fails call the transport to abort it
*/
if (soc_transport_poll(sf->sf_sochandle,
cmd->cmd_fp_pkt, SF_ABORT_TIMEOUT,
CQ_REQUEST_1) == FCAL_TRANSPORT_SUCCESS) {
(void) ddi_dma_sync(
cmd->cmd_cr_pool->rsp_dma_handle,
(off_t)
((caddr_t)cmd->cmd_rsp_block -
cmd->cmd_cr_pool->rsp_base),
FCP_MAX_RSP_IU_SIZE,
DDI_DMA_SYNC_FORKERNEL);
if (((struct fcp_rsp_info *)
(&cmd->cmd_rsp_block->
fcp_response_len + 1))->
rsp_code == FCP_NO_FAILURE) {
/* abort cmds for this targ */
sf_abort_all(sf, target, TRUE,
lip_cnt, TRUE);
} else {
hp = &cmd->cmd_fp_pkt->
fcal_socal_request.
sr_fc_frame_hdr;
tgt_id = sf_alpa_to_switch[
(uchar_t)hp->d_id];
sf->sf_stats.tstats[tgt_id].
task_mgmt_failures++;
SF_DEBUG(1, (sf, CE_NOTE,
"Target %d Abort Task "
"Set failed\n", hp->d_id));
}
} else {
mutex_enter(&cmd->cmd_abort_mutex);
if (cmd->cmd_state == SF_STATE_ISSUED) {
cmd->cmd_state = SF_STATE_ABORTING;
cmd->cmd_timeout = sf_watchdog_time
+ 20;
mutex_exit(&cmd->cmd_abort_mutex);
if ((t = soc_abort(sf->sf_sochandle,
sf->sf_socp, sf->sf_sochandle->
fcal_portno, cmd->cmd_fp_pkt, 1)) !=
FCAL_ABORTED &&
(t != FCAL_ABORT_FAILED)) {
sf_log(sf, CE_NOTE,
"sf_abort failed, "
"initiating LIP\n");
sf_force_lip(sf);
deferred_destroy = 1;
}
} else {
mutex_exit(&cmd->cmd_abort_mutex);
}
}
}
if (!deferred_destroy) {
cmd->cmd_fp_pkt->fcal_pkt_comp =
sf_cmd_callback;
cmd->cmd_block->fcp_cntl.cntl_abort_tsk = 0;
sf_scsi_destroy_pkt(ap, pkt);
my_rval = TRUE;
}
}
mutex_enter(&sf->sf_mutex);
if (lip_cnt == sf->sf_lip_cnt) {
mutex_enter(&target->sft_mutex);
target->sft_state = old_target_state;
mutex_exit(&target->sft_mutex);
}
mutex_exit(&sf->sf_mutex);
}
return (my_rval);
}
/*
* called by the transport and internally to reset a target
*/
static int
sf_reset(struct scsi_address *ap, int level)
{
struct scsi_pkt *pkt;
struct fcal_packet *fpkt;
struct sf *sf = ADDR2SF(ap);
struct sf_target *target = ADDR2TARGET(ap), *ntarget;
struct sf_pkt *cmd;
int rval = FALSE, t;
int lip_cnt;
int tgt_id, ret;
fc_frame_header_t *hp;
int deferred_destroy;
/* We don't support RESET_LUN yet. */
if (level == RESET_TARGET) {
struct sf_reset_list *p;
if ((p = kmem_alloc(sizeof (struct sf_reset_list), KM_NOSLEEP))
== NULL)
return (rval);
SF_DEBUG(2, (sf, CE_NOTE, "sf_reset target\n"));
mutex_enter(&sf->sf_mutex);
/* All target resets go to LUN 0 */
if (target->sft_lun.l) {
target = sf_lookup_target(sf, target->sft_port_wwn, 0);
}
mutex_enter(&target->sft_mutex);
if (target->sft_state & (SF_TARGET_BUSY |
SF_TARGET_OFFLINE)) {
mutex_exit(&target->sft_mutex);
mutex_exit(&sf->sf_mutex);
kmem_free(p, sizeof (struct sf_reset_list));
return (rval);
}
lip_cnt = sf->sf_lip_cnt;
target->sft_state |= SF_TARGET_BUSY;
for (ntarget = target->sft_next_lun;
ntarget;
ntarget = ntarget->sft_next_lun) {
mutex_enter(&ntarget->sft_mutex);
/*
* XXXX If we supported RESET_LUN we should check here
* to see if any LUN were being reset and somehow fail
* that operation.
*/
ntarget->sft_state |= SF_TARGET_BUSY;
mutex_exit(&ntarget->sft_mutex);
}
mutex_exit(&target->sft_mutex);
mutex_exit(&sf->sf_mutex);
deferred_destroy = 0;
if ((pkt = sf_scsi_init_pkt(ap, NULL, NULL, 0,
0, 0, 0, NULL, 0)) != NULL) {
cmd = PKT2CMD(pkt);
cmd->cmd_block->fcp_cntl.cntl_reset = 1;
cmd->cmd_fp_pkt->fcal_pkt_comp = NULL;
cmd->cmd_pkt->pkt_flags |= FLAG_NOINTR;
/* prepare the packet for transport */
if (sf_prepare_pkt(sf, cmd, target) == TRAN_ACCEPT) {
/* call transport to send a pkt polled */
cmd->cmd_state = SF_STATE_ISSUED;
if ((ret = soc_transport_poll(sf->sf_sochandle,
cmd->cmd_fp_pkt, SF_ABORT_TIMEOUT,
CQ_REQUEST_1)) == FCAL_TRANSPORT_SUCCESS) {
(void) ddi_dma_sync(cmd->cmd_cr_pool->
rsp_dma_handle, (caddr_t)cmd->
cmd_rsp_block - cmd->cmd_cr_pool->
rsp_base, FCP_MAX_RSP_IU_SIZE,
DDI_DMA_SYNC_FORKERNEL);
fpkt = cmd->cmd_fp_pkt;
if ((fpkt->fcal_pkt_status ==
FCAL_STATUS_OK) &&
(((struct fcp_rsp_info *)
(&cmd->cmd_rsp_block->
fcp_response_len + 1))->
rsp_code == FCP_NO_FAILURE)) {
sf_log(sf, CE_NOTE,
"!sf%d: Target 0x%x Reset "
"successful\n",
ddi_get_instance(\
sf->sf_dip),
sf_alpa_to_switch[
target->sft_al_pa]);
rval = TRUE;
} else {
hp = &cmd->cmd_fp_pkt->
fcal_socal_request.
sr_fc_frame_hdr;
tgt_id = sf_alpa_to_switch[
(uchar_t)hp->d_id];
sf->sf_stats.tstats[tgt_id].
task_mgmt_failures++;
sf_log(sf, CE_NOTE,
"!sf%d: Target 0x%x "
"Reset failed."
"Status code 0x%x "
"Resp code 0x%x\n",
ddi_get_instance(\
sf->sf_dip),
tgt_id,
fpkt->fcal_pkt_status,
((struct fcp_rsp_info *)
(&cmd->cmd_rsp_block->
fcp_response_len + 1))->
rsp_code);
}
} else {
sf_log(sf, CE_NOTE, "!sf%d: Target "
"0x%x Reset Failed. Ret=%x\n",
ddi_get_instance(sf->sf_dip),
sf_alpa_to_switch[
target->sft_al_pa], ret);
mutex_enter(&cmd->cmd_abort_mutex);
if (cmd->cmd_state == SF_STATE_ISSUED) {
/* call the transport to abort a cmd */
cmd->cmd_timeout = sf_watchdog_time
+ 20;
cmd->cmd_state = SF_STATE_ABORTING;
mutex_exit(&cmd->cmd_abort_mutex);
if (((t = soc_abort(sf->sf_sochandle,
sf->sf_socp,
sf->sf_sochandle->fcal_portno,
cmd->cmd_fp_pkt, 1)) !=
FCAL_ABORTED) &&
(t != FCAL_ABORT_FAILED)) {
sf_log(sf, CE_NOTE,
"!sf%d: Target 0x%x Reset "
"failed. Abort Failed, "
"forcing LIP\n",
ddi_get_instance(
sf->sf_dip),
sf_alpa_to_switch[
target->sft_al_pa]);
sf_force_lip(sf);
rval = TRUE;
deferred_destroy = 1;
}
} else {
mutex_exit
(&cmd->cmd_abort_mutex);
}
}
}
/*
* Defer releasing the packet if we abort returned with
* a BAD_ABORT or timed out, because there is a
* possibility that the ucode might return it.
* We wait for at least 20s and let it be released
* by the sf_watch thread
*/
if (!deferred_destroy) {
cmd->cmd_block->fcp_cntl.cntl_reset = 0;
cmd->cmd_fp_pkt->fcal_pkt_comp =
sf_cmd_callback;
cmd->cmd_state = SF_STATE_IDLE;
/* for cache */
sf_scsi_destroy_pkt(ap, pkt);
}
} else {
cmn_err(CE_WARN, "!sf%d: Target 0x%x Reset Failed. "
"Resource allocation error.\n",
ddi_get_instance(sf->sf_dip),
sf_alpa_to_switch[target->sft_al_pa]);
}
mutex_enter(&sf->sf_mutex);
if ((rval == TRUE) && (lip_cnt == sf->sf_lip_cnt)) {
p->target = target;
p->lip_cnt = lip_cnt;
p->timeout = ddi_get_lbolt() +
drv_usectohz(SF_TARGET_RESET_DELAY);
p->next = sf->sf_reset_list;
sf->sf_reset_list = p;
mutex_exit(&sf->sf_mutex);
mutex_enter(&sf_global_mutex);
if (sf_reset_timeout_id == 0) {
sf_reset_timeout_id = timeout(
sf_check_reset_delay, NULL,
drv_usectohz(SF_TARGET_RESET_DELAY));
}
mutex_exit(&sf_global_mutex);
} else {
if (lip_cnt == sf->sf_lip_cnt) {
mutex_enter(&target->sft_mutex);
target->sft_state &= ~SF_TARGET_BUSY;
for (ntarget = target->sft_next_lun;
ntarget;
ntarget = ntarget->sft_next_lun) {
mutex_enter(&ntarget->sft_mutex);
ntarget->sft_state &= ~SF_TARGET_BUSY;
mutex_exit(&ntarget->sft_mutex);
}
mutex_exit(&target->sft_mutex);
}
mutex_exit(&sf->sf_mutex);
kmem_free(p, sizeof (struct sf_reset_list));
}
} else {
mutex_enter(&sf->sf_mutex);
if ((sf->sf_state == SF_STATE_OFFLINE) &&
(sf_watchdog_time < sf->sf_timer)) {
/*
* We are currently in a lip, so let this one
* finish before forcing another one.
*/
mutex_exit(&sf->sf_mutex);
return (TRUE);
}
mutex_exit(&sf->sf_mutex);
sf_log(sf, CE_NOTE, "!sf:Target driver initiated lip\n");
sf_force_lip(sf);
rval = TRUE;
}
return (rval);
}
/*
* abort all commands for a target
*
* if try_abort is set then send an abort
* if abort is set then this is abort, else this is a reset
*/
static void
sf_abort_all(struct sf *sf, struct sf_target *target, int abort, int
lip_cnt, int try_abort)
{
struct sf_target *ntarget;
struct sf_pkt *cmd, *head = NULL, *tail = NULL, *pcmd = NULL, *tcmd;
struct fcal_packet *fpkt;
struct scsi_pkt *pkt;
int rval = FCAL_ABORTED;
/*
* First pull all commands for all LUNs on this target out of the
* overflow list. We can tell it's the same target by comparing
* the node WWN.
*/
mutex_enter(&sf->sf_mutex);
if (lip_cnt == sf->sf_lip_cnt) {
mutex_enter(&sf->sf_cmd_mutex);
cmd = sf->sf_pkt_head;
while (cmd != NULL) {
ntarget = ADDR2TARGET(&cmd->cmd_pkt->
pkt_address);
if (ntarget == target) {
if (pcmd != NULL)
pcmd->cmd_next = cmd->cmd_next;
else
sf->sf_pkt_head = cmd->cmd_next;
if (sf->sf_pkt_tail == cmd) {
sf->sf_pkt_tail = pcmd;
if (pcmd != NULL)
pcmd->cmd_next = NULL;
}
tcmd = cmd->cmd_next;
if (head == NULL) {
head = cmd;
tail = cmd;
} else {
tail->cmd_next = cmd;
tail = cmd;
}
cmd->cmd_next = NULL;
cmd = tcmd;
} else {
pcmd = cmd;
cmd = cmd->cmd_next;
}
}
mutex_exit(&sf->sf_cmd_mutex);
}
mutex_exit(&sf->sf_mutex);
/*
* Now complete all the commands on our list. In the process,
* the completion routine may take the commands off the target
* lists.
*/
cmd = head;
while (cmd != NULL) {
pkt = cmd->cmd_pkt;
if (abort) {
pkt->pkt_reason = CMD_ABORTED;
pkt->pkt_statistics |= STAT_ABORTED;
} else {
pkt->pkt_reason = CMD_RESET;
pkt->pkt_statistics |= STAT_DEV_RESET;
}
cmd->cmd_flags &= ~CFLAG_IN_QUEUE;
cmd->cmd_state = SF_STATE_IDLE;
cmd = cmd->cmd_next;
/*
* call the packet completion routine only for
* non-polled commands. Ignore the polled commands as
* they timeout and will be handled differently
*/
if ((pkt->pkt_comp) && !(pkt->pkt_flags & FLAG_NOINTR))
(*pkt->pkt_comp)(pkt);
}
/*
* Finally get all outstanding commands for each LUN, and abort them if
* they've been issued, and call the completion routine.
* For the case where sf_offline_target is called from sf_watch
* due to a Offline Timeout, it is quite possible that the soc+
* ucode is hosed and therefore cannot return the commands.
* Clear up all the issued commands as well.
* Try_abort will be false only if sf_abort_all is coming from
* sf_target_offline.
*/
if (try_abort || sf->sf_state == SF_STATE_OFFLINE) {
mutex_enter(&target->sft_pkt_mutex);
cmd = tcmd = target->sft_pkt_head;
while (cmd != (struct sf_pkt *)&target->sft_pkt_head) {
fpkt = cmd->cmd_fp_pkt;
pkt = cmd->cmd_pkt;
mutex_enter(&cmd->cmd_abort_mutex);
if ((cmd->cmd_state == SF_STATE_ISSUED) &&
(fpkt->fcal_cmd_state &
FCAL_CMD_IN_TRANSPORT) &&
((fpkt->fcal_cmd_state & FCAL_CMD_COMPLETE) ==
0) && !(pkt->pkt_flags & FLAG_NOINTR)) {
cmd->cmd_state = SF_STATE_ABORTING;
cmd->cmd_timeout = sf_watchdog_time +
cmd->cmd_pkt->pkt_time + 20;
mutex_exit(&cmd->cmd_abort_mutex);
mutex_exit(&target->sft_pkt_mutex);
if (try_abort) {
/* call the transport to abort a pkt */
rval = soc_abort(sf->sf_sochandle,
sf->sf_socp,
sf->sf_sochandle->fcal_portno,
fpkt, 1);
}
if ((rval == FCAL_ABORTED) ||
(rval == FCAL_ABORT_FAILED)) {
if (abort) {
pkt->pkt_reason = CMD_ABORTED;
pkt->pkt_statistics |=
STAT_ABORTED;
} else {
pkt->pkt_reason = CMD_RESET;
pkt->pkt_statistics |=
STAT_DEV_RESET;
}
cmd->cmd_state = SF_STATE_IDLE;
if (pkt->pkt_comp)
(*pkt->pkt_comp)(pkt);
}
mutex_enter(&sf->sf_mutex);
if (lip_cnt != sf->sf_lip_cnt) {
mutex_exit(&sf->sf_mutex);
return;
}
mutex_exit(&sf->sf_mutex);
mutex_enter(&target->sft_pkt_mutex);
cmd = target->sft_pkt_head;
} else {
mutex_exit(&cmd->cmd_abort_mutex);
cmd = cmd->cmd_forw;
}
}
mutex_exit(&target->sft_pkt_mutex);
}
}
/*
* called by the transport to start a packet
*/
static int
sf_start(struct scsi_address *ap, struct scsi_pkt *pkt)
{
struct sf *sf = ADDR2SF(ap);
struct sf_target *target = ADDR2TARGET(ap);
struct sf_pkt *cmd = PKT2CMD(pkt);
int rval;
SF_DEBUG(6, (sf, CE_NOTE, "sf_start\n"));
if (cmd->cmd_state == SF_STATE_ISSUED) {
cmn_err(CE_PANIC, "sf: issuing packet twice 0x%p\n",
(void *)cmd);
}
/* prepare the packet for transport */
if ((rval = sf_prepare_pkt(sf, cmd, target)) != TRAN_ACCEPT) {
return (rval);
}
if (target->sft_state & (SF_TARGET_BUSY|SF_TARGET_OFFLINE)) {
if (target->sft_state & SF_TARGET_OFFLINE) {
return (TRAN_FATAL_ERROR);
}
if (pkt->pkt_flags & FLAG_NOINTR) {
return (TRAN_BUSY);
}
mutex_enter(&sf->sf_cmd_mutex);
sf->sf_use_lock = TRUE;
goto enque;
}
/* if no interrupts then do polled I/O */
if (pkt->pkt_flags & FLAG_NOINTR) {
return (sf_dopoll(sf, cmd));
}
/* regular interrupt-driven I/O */
if (!sf->sf_use_lock) {
/* locking no needed */
cmd->cmd_timeout = cmd->cmd_pkt->pkt_time ?
sf_watchdog_time + cmd->cmd_pkt->pkt_time : 0;
cmd->cmd_state = SF_STATE_ISSUED;
/* call the transport to send a pkt */
if (soc_transport(sf->sf_sochandle, cmd->cmd_fp_pkt,
FCAL_NOSLEEP, CQ_REQUEST_1) != FCAL_TRANSPORT_SUCCESS) {
cmd->cmd_state = SF_STATE_IDLE;
return (TRAN_BADPKT);
}
return (TRAN_ACCEPT);
}
/* regular I/O using locking */
mutex_enter(&sf->sf_cmd_mutex);
if ((sf->sf_ncmds >= sf->sf_throttle) ||
(sf->sf_pkt_head != NULL)) {
enque:
/*
* either we're throttling back or there are already commands
* on the queue, so enqueue this one for later
*/
cmd->cmd_flags |= CFLAG_IN_QUEUE;
if (sf->sf_pkt_head != NULL) {
/* add to the queue */
sf->sf_pkt_tail->cmd_next = cmd;
cmd->cmd_next = NULL;
sf->sf_pkt_tail = cmd;
} else {
/* this is the first entry in the queue */
sf->sf_pkt_head = sf->sf_pkt_tail = cmd;
cmd->cmd_next = NULL;
}
mutex_exit(&sf->sf_cmd_mutex);
return (TRAN_ACCEPT);
}
/*
* start this packet now
*/
/* still have cmd mutex */
return (sf_start_internal(sf, cmd));
}
/*
* internal routine to start a packet from the queue now
*
* enter with cmd mutex held and leave with it released
*/
static int
sf_start_internal(struct sf *sf, struct sf_pkt *cmd)
{
/* we have the cmd mutex */
sf->sf_ncmds++;
mutex_exit(&sf->sf_cmd_mutex);
ASSERT(cmd->cmd_state != SF_STATE_ISSUED);
SF_DEBUG(6, (sf, CE_NOTE, "sf_start_internal\n"));
cmd->cmd_timeout = cmd->cmd_pkt->pkt_time ? sf_watchdog_time +
cmd->cmd_pkt->pkt_time : 0;
cmd->cmd_state = SF_STATE_ISSUED;
/* call transport to send the pkt */
if (soc_transport(sf->sf_sochandle, cmd->cmd_fp_pkt, FCAL_NOSLEEP,
CQ_REQUEST_1) != FCAL_TRANSPORT_SUCCESS) {
cmd->cmd_state = SF_STATE_IDLE;
mutex_enter(&sf->sf_cmd_mutex);
sf->sf_ncmds--;
mutex_exit(&sf->sf_cmd_mutex);
return (TRAN_BADPKT);
}
return (TRAN_ACCEPT);
}
/*
* prepare a packet for transport
*/
static int
sf_prepare_pkt(struct sf *sf, struct sf_pkt *cmd, struct sf_target *target)
{
struct fcp_cmd *fcmd = cmd->cmd_block;
/* XXXX Need to set the LUN ? */
bcopy((caddr_t)&target->sft_lun.b,
(caddr_t)&fcmd->fcp_ent_addr,
FCP_LUN_SIZE);
cmd->cmd_pkt->pkt_reason = CMD_CMPLT;
cmd->cmd_pkt->pkt_state = 0;
cmd->cmd_pkt->pkt_statistics = 0;
if ((cmd->cmd_pkt->pkt_comp == NULL) &&
((cmd->cmd_pkt->pkt_flags & FLAG_NOINTR) == 0)) {
return (TRAN_BADPKT);
}
/* invalidate imp field(s) of rsp block */
cmd->cmd_rsp_block->fcp_u.i_fcp_status = SF_BAD_DMA_MAGIC;
/* set up amt of I/O to do */
if (cmd->cmd_flags & CFLAG_DMAVALID) {
cmd->cmd_pkt->pkt_resid = cmd->cmd_dmacount;
if (cmd->cmd_flags & CFLAG_CMDIOPB) {
(void) ddi_dma_sync(cmd->cmd_dmahandle, 0, 0,
DDI_DMA_SYNC_FORDEV);
}
} else {
cmd->cmd_pkt->pkt_resid = 0;
}
/* set up the Tagged Queuing type */
if (cmd->cmd_pkt->pkt_flags & FLAG_HTAG) {
fcmd->fcp_cntl.cntl_qtype = FCP_QTYPE_HEAD_OF_Q;
} else if (cmd->cmd_pkt->pkt_flags & FLAG_OTAG) {
fcmd->fcp_cntl.cntl_qtype = FCP_QTYPE_ORDERED;
}
/*
* Sync the cmd segment
*/
(void) ddi_dma_sync(cmd->cmd_cr_pool->cmd_dma_handle,
(caddr_t)fcmd - cmd->cmd_cr_pool->cmd_base,
sizeof (struct fcp_cmd), DDI_DMA_SYNC_FORDEV);
sf_fill_ids(sf, cmd, target);
return (TRAN_ACCEPT);
}
/*
* fill in packet hdr source and destination IDs and hdr byte count
*/
static void
sf_fill_ids(struct sf *sf, struct sf_pkt *cmd, struct sf_target *target)
{
struct fcal_packet *fpkt = cmd->cmd_fp_pkt;
fc_frame_header_t *hp;
hp = &fpkt->fcal_socal_request.sr_fc_frame_hdr;
hp->d_id = target->sft_al_pa;
hp->s_id = sf->sf_al_pa;
fpkt->fcal_socal_request.sr_soc_hdr.sh_byte_cnt =
cmd->cmd_dmacookie.dmac_size;
}
/*
* do polled I/O using transport
*/
static int
sf_dopoll(struct sf *sf, struct sf_pkt *cmd)
{
int timeout;
int rval;
mutex_enter(&sf->sf_cmd_mutex);
sf->sf_ncmds++;
mutex_exit(&sf->sf_cmd_mutex);
timeout = cmd->cmd_pkt->pkt_time ? cmd->cmd_pkt->pkt_time
: SF_POLL_TIMEOUT;
cmd->cmd_timeout = 0;
cmd->cmd_fp_pkt->fcal_pkt_comp = NULL;
cmd->cmd_state = SF_STATE_ISSUED;
/* call transport to send a pkt polled */
rval = soc_transport_poll(sf->sf_sochandle, cmd->cmd_fp_pkt,
timeout*1000000, CQ_REQUEST_1);
mutex_enter(&cmd->cmd_abort_mutex);
cmd->cmd_fp_pkt->fcal_pkt_comp = sf_cmd_callback;
if (rval != FCAL_TRANSPORT_SUCCESS) {
if (rval == FCAL_TRANSPORT_TIMEOUT) {
cmd->cmd_state = SF_STATE_ABORTING;
mutex_exit(&cmd->cmd_abort_mutex);
(void) sf_target_timeout(sf, cmd);
} else {
mutex_exit(&cmd->cmd_abort_mutex);
}
cmd->cmd_state = SF_STATE_IDLE;
cmd->cmd_fp_pkt->fcal_pkt_comp = sf_cmd_callback;
mutex_enter(&sf->sf_cmd_mutex);
sf->sf_ncmds--;
mutex_exit(&sf->sf_cmd_mutex);
return (TRAN_BADPKT);
}
mutex_exit(&cmd->cmd_abort_mutex);
cmd->cmd_fp_pkt->fcal_pkt_comp = sf_cmd_callback;
sf_cmd_callback(cmd->cmd_fp_pkt);
return (TRAN_ACCEPT);
}
/* a shortcut for defining debug messages below */
#ifdef DEBUG
#define SF_DMSG1(s) msg1 = s
#else
#define SF_DMSG1(s) /* do nothing */
#endif
/*
* the pkt_comp callback for command packets
*/
static void
sf_cmd_callback(struct fcal_packet *fpkt)
{
struct sf_pkt *cmd = (struct sf_pkt *)fpkt->fcal_pkt_private;
struct scsi_pkt *pkt = cmd->cmd_pkt;
struct sf *sf = ADDR2SF(&pkt->pkt_address);
struct sf_target *target = ADDR2TARGET(&pkt->pkt_address);
struct fcp_rsp *rsp;
char *msg1 = NULL;
char *msg2 = NULL;
short ncmds;
int tgt_id;
int good_scsi_status = TRUE;
if (cmd->cmd_state == SF_STATE_IDLE) {
cmn_err(CE_PANIC, "sf: completing idle packet 0x%p\n",
(void *)cmd);
}
mutex_enter(&cmd->cmd_abort_mutex);
if (cmd->cmd_state == SF_STATE_ABORTING) {
/* cmd already being aborted -- nothing to do */
mutex_exit(&cmd->cmd_abort_mutex);
return;
}
cmd->cmd_state = SF_STATE_IDLE;
mutex_exit(&cmd->cmd_abort_mutex);
if (fpkt->fcal_pkt_status == FCAL_STATUS_OK) {
(void) ddi_dma_sync(cmd->cmd_cr_pool->rsp_dma_handle,
(caddr_t)cmd->cmd_rsp_block - cmd->cmd_cr_pool->rsp_base,
FCP_MAX_RSP_IU_SIZE, DDI_DMA_SYNC_FORKERNEL);
rsp = (struct fcp_rsp *)cmd->cmd_rsp_block;
if (rsp->fcp_u.i_fcp_status == SF_BAD_DMA_MAGIC) {
if (sf_core && (sf_core & SF_CORE_BAD_DMA)) {
sf_token = (int *)(uintptr_t)
fpkt->fcal_socal_request.\
sr_soc_hdr.sh_request_token;
(void) soc_take_core(sf->sf_sochandle,
sf->sf_socp);
}
pkt->pkt_reason = CMD_INCOMPLETE;
pkt->pkt_state = STATE_GOT_BUS;
pkt->pkt_statistics |= STAT_ABORTED;
} else {
pkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_GOT_STATUS;
pkt->pkt_resid = 0;
if (cmd->cmd_flags & CFLAG_DMAVALID) {
pkt->pkt_state |= STATE_XFERRED_DATA;
}
if ((pkt->pkt_scbp != NULL) &&
((*(pkt->pkt_scbp) =
rsp->fcp_u.fcp_status.scsi_status)
!= STATUS_GOOD)) {
good_scsi_status = FALSE;
/*
* The next two checks make sure that if there
* is no sense data or a valid response and
* the command came back with check condition,
* the command should be retried
*/
if (!rsp->fcp_u.fcp_status.rsp_len_set &&
!rsp->fcp_u.fcp_status.sense_len_set) {
pkt->pkt_state &= ~STATE_XFERRED_DATA;
pkt->pkt_resid = cmd->cmd_dmacount;
}
}
if ((cmd->cmd_flags & CFLAG_CMDIOPB) &&
(pkt->pkt_state & STATE_XFERRED_DATA)) {
(void) ddi_dma_sync(cmd->cmd_dmahandle, 0,
(uint_t)0, DDI_DMA_SYNC_FORCPU);
}
/*
* Update the transfer resid, if appropriate
*/
if (rsp->fcp_u.fcp_status.resid_over ||
rsp->fcp_u.fcp_status.resid_under)
pkt->pkt_resid = rsp->fcp_resid;
/*
* Check to see if the SCSI command failed.
*
*/
/*
* First see if we got a FCP protocol error.
*/
if (rsp->fcp_u.fcp_status.rsp_len_set) {
struct fcp_rsp_info *bep;
bep = (struct fcp_rsp_info *)
(&rsp->fcp_response_len + 1);
if (bep->rsp_code != FCP_NO_FAILURE) {
pkt->pkt_reason = CMD_TRAN_ERR;
tgt_id = pkt->pkt_address.a_target;
switch (bep->rsp_code) {
case FCP_CMND_INVALID:
SF_DMSG1("FCP_RSP FCP_CMND "
"fields invalid");
break;
case FCP_TASK_MGMT_NOT_SUPPTD:
SF_DMSG1("FCP_RSP Task"
"Management Function"
"Not Supported");
break;
case FCP_TASK_MGMT_FAILED:
SF_DMSG1("FCP_RSP Task "
"Management Function"
"Failed");
sf->sf_stats.tstats[tgt_id].
task_mgmt_failures++;
break;
case FCP_DATA_RO_MISMATCH:
SF_DMSG1("FCP_RSP FCP_DATA RO "
"mismatch with "
"FCP_XFER_RDY DATA_RO");
sf->sf_stats.tstats[tgt_id].
data_ro_mismatches++;
break;
case FCP_DL_LEN_MISMATCH:
SF_DMSG1("FCP_RSP FCP_DATA "
"length "
"different than BURST_LEN");
sf->sf_stats.tstats[tgt_id].
dl_len_mismatches++;
break;
default:
SF_DMSG1("FCP_RSP invalid "
"RSP_CODE");
break;
}
}
}
/*
* See if we got a SCSI error with sense data
*/
if (rsp->fcp_u.fcp_status.sense_len_set) {
uchar_t rqlen = min(rsp->fcp_sense_len,
sizeof (struct scsi_extended_sense));
caddr_t sense = (caddr_t)rsp +
sizeof (struct fcp_rsp) +
rsp->fcp_response_len;
struct scsi_arq_status *arq;
struct scsi_extended_sense *sensep =
(struct scsi_extended_sense *)sense;
if (rsp->fcp_u.fcp_status.scsi_status !=
STATUS_GOOD) {
if (rsp->fcp_u.fcp_status.scsi_status
== STATUS_CHECK) {
if (sensep->es_key ==
KEY_RECOVERABLE_ERROR)
good_scsi_status = 1;
if (sensep->es_key ==
KEY_UNIT_ATTENTION &&
sensep->es_add_code == 0x3f &&
sensep->es_qual_code == 0x0e) {
/* REPORT_LUNS_HAS_CHANGED */
sf_log(sf, CE_NOTE,
"!REPORT_LUNS_HAS_CHANGED\n");
sf_force_lip(sf);
}
}
}
if ((pkt->pkt_scbp != NULL) &&
(cmd->cmd_scblen >=
sizeof (struct scsi_arq_status))) {
pkt->pkt_state |= STATE_ARQ_DONE;
arq = (struct scsi_arq_status *)pkt->pkt_scbp;
/*
* copy out sense information
*/
bcopy(sense, (caddr_t)&arq->sts_sensedata,
rqlen);
arq->sts_rqpkt_resid =
sizeof (struct scsi_extended_sense) -
rqlen;
*((uchar_t *)&arq->sts_rqpkt_status) =
STATUS_GOOD;
arq->sts_rqpkt_reason = 0;
arq->sts_rqpkt_statistics = 0;
arq->sts_rqpkt_state = STATE_GOT_BUS |
STATE_GOT_TARGET | STATE_SENT_CMD |
STATE_GOT_STATUS | STATE_ARQ_DONE |
STATE_XFERRED_DATA;
}
target->sft_alive = TRUE;
}
/*
* The firmware returns the number of bytes actually
* xfered into/out of host. Compare this with what
* we asked and if it is different, we lost frames ?
*/
if ((pkt->pkt_reason == 0) && (pkt->pkt_resid == 0) &&
(good_scsi_status) &&
(pkt->pkt_state & STATE_XFERRED_DATA) &&
(!(cmd->cmd_flags & CFLAG_CMDIOPB)) &&
(target->sft_device_type != DTYPE_ESI)) {
int byte_cnt =
fpkt->fcal_socal_request.
sr_soc_hdr.sh_byte_cnt;
if (cmd->cmd_flags & CFLAG_DMASEND) {
if (byte_cnt != 0) {
sf_log(sf, CE_NOTE,
"!sf_cmd_callback: Lost Frame: "
"(write) received 0x%x expected"
" 0x%x target 0x%x\n",
byte_cnt, cmd->cmd_dmacount,
sf_alpa_to_switch[
target->sft_al_pa]);
pkt->pkt_reason = CMD_INCOMPLETE;
pkt->pkt_statistics |= STAT_ABORTED;
}
} else if (byte_cnt < cmd->cmd_dmacount) {
sf_log(sf, CE_NOTE,
"!sf_cmd_callback: "
"Lost Frame: (read) "
"received 0x%x expected 0x%x "
"target 0x%x\n", byte_cnt,
cmd->cmd_dmacount,
sf_alpa_to_switch[
target->sft_al_pa]);
pkt->pkt_reason = CMD_INCOMPLETE;
pkt->pkt_statistics |= STAT_ABORTED;
}
}
}
} else {
/* pkt status was not ok */
switch (fpkt->fcal_pkt_status) {
case FCAL_STATUS_ERR_OFFLINE:
SF_DMSG1("Fibre Channel Offline");
mutex_enter(&target->sft_mutex);
if (!(target->sft_state & SF_TARGET_OFFLINE)) {
target->sft_state |= (SF_TARGET_BUSY
| SF_TARGET_MARK);
}
mutex_exit(&target->sft_mutex);
(void) ndi_event_retrieve_cookie(sf->sf_event_hdl,
target->sft_dip, FCAL_REMOVE_EVENT,
&sf_remove_eid, NDI_EVENT_NOPASS);
(void) ndi_event_run_callbacks(sf->sf_event_hdl,
target->sft_dip, sf_remove_eid, NULL);
pkt->pkt_reason = CMD_TRAN_ERR;
pkt->pkt_statistics |= STAT_BUS_RESET;
break;
case FCAL_STATUS_MAX_XCHG_EXCEEDED:
sf_throttle(sf);
sf->sf_use_lock = TRUE;
pkt->pkt_reason = CMD_TRAN_ERR;
pkt->pkt_state = STATE_GOT_BUS;
pkt->pkt_statistics |= STAT_ABORTED;
break;
case FCAL_STATUS_TIMEOUT:
SF_DMSG1("Fibre Channel Timeout");
pkt->pkt_reason = CMD_TIMEOUT;
break;
case FCAL_STATUS_ERR_OVERRUN:
SF_DMSG1("CMD_DATA_OVR");
pkt->pkt_reason = CMD_DATA_OVR;
break;
case FCAL_STATUS_UNKNOWN_CQ_TYPE:
SF_DMSG1("Unknown CQ type");
pkt->pkt_reason = CMD_TRAN_ERR;
break;
case FCAL_STATUS_BAD_SEG_CNT:
SF_DMSG1("Bad SEG CNT");
pkt->pkt_reason = CMD_TRAN_ERR;
break;
case FCAL_STATUS_BAD_XID:
SF_DMSG1("Fibre Channel Invalid X_ID");
pkt->pkt_reason = CMD_TRAN_ERR;
break;
case FCAL_STATUS_XCHG_BUSY:
SF_DMSG1("Fibre Channel Exchange Busy");
pkt->pkt_reason = CMD_TRAN_ERR;
break;
case FCAL_STATUS_INSUFFICIENT_CQES:
SF_DMSG1("Insufficient CQEs");
pkt->pkt_reason = CMD_TRAN_ERR;
break;
case FCAL_STATUS_ALLOC_FAIL:
SF_DMSG1("ALLOC FAIL");
pkt->pkt_reason = CMD_TRAN_ERR;
break;
case FCAL_STATUS_BAD_SID:
SF_DMSG1("Fibre Channel Invalid S_ID");
pkt->pkt_reason = CMD_TRAN_ERR;
break;
case FCAL_STATUS_INCOMPLETE_DMA_ERR:
if (sf_core && (sf_core & SF_CORE_INCOMPLETE_DMA)) {
sf_token = (int *)(uintptr_t)
fpkt->fcal_socal_request.\
sr_soc_hdr.sh_request_token;
(void) soc_take_core(sf->sf_sochandle,
sf->sf_socp);
sf_core = 0;
}
msg2 =
"INCOMPLETE DMA XFER due to bad SOC+ card, replace HBA";
pkt->pkt_reason = CMD_INCOMPLETE;
pkt->pkt_state = STATE_GOT_BUS;
pkt->pkt_statistics |= STAT_ABORTED;
break;
case FCAL_STATUS_CRC_ERR:
msg2 = "Fibre Channel CRC Error on frames";
pkt->pkt_reason = CMD_INCOMPLETE;
pkt->pkt_state = STATE_GOT_BUS;
pkt->pkt_statistics |= STAT_ABORTED;
break;
case FCAL_STATUS_NO_SEQ_INIT:
SF_DMSG1("Fibre Channel Seq Init Error");
pkt->pkt_reason = CMD_TRAN_ERR;
break;
case FCAL_STATUS_OPEN_FAIL:
pkt->pkt_reason = CMD_TRAN_ERR;
SF_DMSG1("Fibre Channel Open Failure");
if ((target->sft_state & (SF_TARGET_BUSY |
SF_TARGET_MARK | SF_TARGET_OFFLINE)) == 0) {
sf_log(sf, CE_NOTE,
"!Open failure to target 0x%x "
"forcing LIP\n",
sf_alpa_to_switch[target->sft_al_pa]);
sf_force_lip(sf);
}
break;
case FCAL_STATUS_ONLINE_TIMEOUT:
SF_DMSG1("Fibre Channel Online Timeout");
pkt->pkt_reason = CMD_TRAN_ERR;
break;
default:
SF_DMSG1("Unknown FC Status");
pkt->pkt_reason = CMD_TRAN_ERR;
break;
}
}
#ifdef DEBUG
/*
* msg1 will be non-NULL if we've detected some sort of error
*/
if (msg1 != NULL && sfdebug >= 4) {
sf_log(sf, CE_WARN,
"!Transport error on cmd=0x%p target=0x%x: %s\n",
(void *)fpkt, pkt->pkt_address.a_target, msg1);
}
#endif
if (msg2 != NULL) {
sf_log(sf, CE_WARN, "!Transport error on target=0x%x: %s\n",
pkt->pkt_address.a_target, msg2);
}
ncmds = fpkt->fcal_ncmds;
ASSERT(ncmds >= 0);
if (ncmds >= (sf->sf_throttle - SF_HI_CMD_DELTA)) {
#ifdef DEBUG
if (!sf->sf_use_lock) {
SF_DEBUG(4, (sf, CE_NOTE, "use lock flag on\n"));
}
#endif
sf->sf_use_lock = TRUE;
}
mutex_enter(&sf->sf_cmd_mutex);
sf->sf_ncmds = ncmds;
sf_throttle_start(sf);
mutex_exit(&sf->sf_cmd_mutex);
if (!msg1 && !msg2)
SF_DEBUG(6, (sf, CE_NOTE, "Completing pkt 0x%p\n",
(void *)pkt));
if (pkt->pkt_comp != NULL) {
(*pkt->pkt_comp)(pkt);
}
}
#undef SF_DMSG1
/*
* start throttling for this instance
*/
static void
sf_throttle_start(struct sf *sf)
{
struct sf_pkt *cmd, *prev_cmd = NULL;
struct scsi_pkt *pkt;
struct sf_target *target;
ASSERT(mutex_owned(&sf->sf_cmd_mutex));
cmd = sf->sf_pkt_head;
while ((cmd != NULL) &&
(sf->sf_state == SF_STATE_ONLINE) &&
(sf->sf_ncmds < sf->sf_throttle)) {
pkt = CMD2PKT(cmd);
target = ADDR2TARGET(&pkt->pkt_address);
if (target->sft_state & SF_TARGET_BUSY) {
/* this command is busy -- go to next */
ASSERT(cmd->cmd_state != SF_STATE_ISSUED);
prev_cmd = cmd;
cmd = cmd->cmd_next;
continue;
}
ASSERT(cmd->cmd_state != SF_STATE_ISSUED);
/* this cmd not busy and not issued */
/* remove this packet from the queue */
if (sf->sf_pkt_head == cmd) {
/* this was the first packet */
sf->sf_pkt_head = cmd->cmd_next;
} else if (sf->sf_pkt_tail == cmd) {
/* this was the last packet */
sf->sf_pkt_tail = prev_cmd;
if (prev_cmd != NULL) {
prev_cmd->cmd_next = NULL;
}
} else {
/* some packet in the middle of the queue */
ASSERT(prev_cmd != NULL);
prev_cmd->cmd_next = cmd->cmd_next;
}
cmd->cmd_flags &= ~CFLAG_IN_QUEUE;
if (target->sft_state & SF_TARGET_OFFLINE) {
mutex_exit(&sf->sf_cmd_mutex);
pkt->pkt_reason = CMD_TRAN_ERR;
if (pkt->pkt_comp != NULL) {
(*pkt->pkt_comp)(cmd->cmd_pkt);
}
} else {
sf_fill_ids(sf, cmd, target);
if (sf_start_internal(sf, cmd) != TRAN_ACCEPT) {
pkt->pkt_reason = CMD_TRAN_ERR;
if (pkt->pkt_comp != NULL) {
(*pkt->pkt_comp)(cmd->cmd_pkt);
}
}
}
mutex_enter(&sf->sf_cmd_mutex);
cmd = sf->sf_pkt_head;
prev_cmd = NULL;
}
}
/*
* called when the max exchange value is exceeded to throttle back commands
*/
static void
sf_throttle(struct sf *sf)
{
int cmdmax = sf->sf_sochandle->fcal_cmdmax;
mutex_enter(&sf->sf_cmd_mutex);
sf->sf_flag = TRUE;
if (sf->sf_ncmds > (cmdmax / 2)) {
sf->sf_throttle = cmdmax / 2;
} else {
if (sf->sf_ncmds > SF_DECR_DELTA) {
sf->sf_throttle = sf->sf_ncmds - SF_DECR_DELTA;
} else {
/*
* This case is just a safeguard, should not really
* happen(ncmds < SF_DECR_DELTA and MAX_EXCHG exceed
*/
sf->sf_throttle = SF_DECR_DELTA;
}
}
mutex_exit(&sf->sf_cmd_mutex);
sf = sf->sf_sibling;
if (sf != NULL) {
mutex_enter(&sf->sf_cmd_mutex);
sf->sf_flag = TRUE;
if (sf->sf_ncmds >= (cmdmax / 2)) {
sf->sf_throttle = cmdmax / 2;
} else {
if (sf->sf_ncmds > SF_DECR_DELTA) {
sf->sf_throttle = sf->sf_ncmds - SF_DECR_DELTA;
} else {
sf->sf_throttle = SF_DECR_DELTA;
}
}
mutex_exit(&sf->sf_cmd_mutex);
}
}
/*
* sf watchdog routine, called for a timeout
*/
/*ARGSUSED*/
static void
sf_watch(void *arg)
{
struct sf *sf;
struct sf_els_hdr *privp;
static int count = 0, pscan_count = 0;
int cmdmax, i, mescount = 0;
struct sf_target *target;
sf_watchdog_time += sf_watchdog_timeout;
count++;
pscan_count++;
mutex_enter(&sf_global_mutex);
sf_watch_running = 1;
for (sf = sf_head; sf != NULL; sf = sf->sf_next) {
mutex_exit(&sf_global_mutex);
/* disable throttling while we're suspended */
mutex_enter(&sf->sf_mutex);
if (sf->sf_state & SF_STATE_SUSPENDED) {
mutex_exit(&sf->sf_mutex);
SF_DEBUG(1, (sf, CE_CONT,
"sf_watch, sf%d:throttle disabled "
"due to DDI_SUSPEND\n",
ddi_get_instance(sf->sf_dip)));
mutex_enter(&sf_global_mutex);
continue;
}
mutex_exit(&sf->sf_mutex);
cmdmax = sf->sf_sochandle->fcal_cmdmax;
if (sf->sf_take_core) {
(void) soc_take_core(sf->sf_sochandle, sf->sf_socp);
}
mutex_enter(&sf->sf_cmd_mutex);
if (!sf->sf_flag) {
if (sf->sf_throttle < (cmdmax / 2)) {
sf->sf_throttle = cmdmax / 2;
} else if ((sf->sf_throttle += SF_INCR_DELTA) >
cmdmax) {
sf->sf_throttle = cmdmax;
}
} else {
sf->sf_flag = FALSE;
}
sf->sf_ncmds_exp_avg = (sf->sf_ncmds + sf->sf_ncmds_exp_avg)
>> 2;
if ((sf->sf_ncmds <= (sf->sf_throttle - SF_LO_CMD_DELTA)) &&
(sf->sf_pkt_head == NULL)) {
#ifdef DEBUG
if (sf->sf_use_lock) {
SF_DEBUG(4, (sf, CE_NOTE,
"use lock flag off\n"));
}
#endif
sf->sf_use_lock = FALSE;
}
if (sf->sf_state == SF_STATE_ONLINE && sf->sf_pkt_head &&
sf->sf_ncmds < sf->sf_throttle) {
sf_throttle_start(sf);
}
mutex_exit(&sf->sf_cmd_mutex);
if (pscan_count >= sf_pool_scan_cnt) {
if (sf->sf_ncmds_exp_avg < (sf->sf_cr_pool_cnt <<
SF_LOG2_ELEMS_IN_POOL) - SF_FREE_CR_EPSILON) {
sf_crpool_free(sf);
}
}
mutex_enter(&sf->sf_mutex);
privp = sf->sf_els_list;
while (privp != NULL) {
if (privp->timeout < sf_watchdog_time) {
/* timeout this command */
privp = sf_els_timeout(sf, privp);
} else if ((privp->timeout == SF_INVALID_TIMEOUT) &&
(privp->lip_cnt != sf->sf_lip_cnt)) {
if (privp->prev != NULL) {
privp->prev->next = privp->next;
}
if (sf->sf_els_list == privp) {
sf->sf_els_list = privp->next;
}
if (privp->next != NULL) {
privp->next->prev = privp->prev;
}
mutex_exit(&sf->sf_mutex);
sf_els_free(privp->fpkt);
mutex_enter(&sf->sf_mutex);
privp = sf->sf_els_list;
} else {
privp = privp->next;
}
}
if (sf->sf_online_timer && sf->sf_online_timer <
sf_watchdog_time) {
for (i = 0; i < sf_max_targets; i++) {
target = sf->sf_targets[i];
if (target != NULL) {
if (!mescount && target->sft_state &
SF_TARGET_BUSY) {
sf_log(sf, CE_WARN, "!Loop "
"Unstable: Failed to bring "
"Loop Online\n");
mescount = 1;
}
target->sft_state |= SF_TARGET_MARK;
}
}
sf_finish_init(sf, sf->sf_lip_cnt);
sf->sf_state = SF_STATE_INIT;
sf->sf_online_timer = 0;
}
if (sf->sf_state == SF_STATE_ONLINE) {
mutex_exit(&sf->sf_mutex);
if (count >= sf_pkt_scan_cnt) {
sf_check_targets(sf);
}
} else if ((sf->sf_state == SF_STATE_OFFLINE) &&
(sf->sf_timer < sf_watchdog_time)) {
for (i = 0; i < sf_max_targets; i++) {
target = sf->sf_targets[i];
if ((target != NULL) &&
(target->sft_state &
SF_TARGET_BUSY)) {
sf_log(sf, CE_WARN,
"!Offline Timeout\n");
if (sf_core && (sf_core &
SF_CORE_OFFLINE_TIMEOUT)) {
(void) soc_take_core(
sf->sf_sochandle,
sf->sf_socp);
sf_core = 0;
}
break;
}
}
sf_finish_init(sf, sf->sf_lip_cnt);
sf->sf_state = SF_STATE_INIT;
mutex_exit(&sf->sf_mutex);
} else {
mutex_exit(&sf->sf_mutex);
}
mutex_enter(&sf_global_mutex);
}
mutex_exit(&sf_global_mutex);
if (count >= sf_pkt_scan_cnt) {
count = 0;
}
if (pscan_count >= sf_pool_scan_cnt) {
pscan_count = 0;
}
/* reset timeout */
sf_watchdog_id = timeout(sf_watch, (caddr_t)0, sf_watchdog_tick);
/* signal waiting thread */
mutex_enter(&sf_global_mutex);
sf_watch_running = 0;
cv_broadcast(&sf_watch_cv);
mutex_exit(&sf_global_mutex);
}
/*
* called during a timeout to check targets
*/
static void
sf_check_targets(struct sf *sf)
{
struct sf_target *target;
int i;
struct sf_pkt *cmd;
struct scsi_pkt *pkt;
int lip_cnt;
mutex_enter(&sf->sf_mutex);
lip_cnt = sf->sf_lip_cnt;
mutex_exit(&sf->sf_mutex);
/* check scan all possible targets */
for (i = 0; i < sf_max_targets; i++) {
target = sf->sf_targets[i];
while (target != NULL) {
mutex_enter(&target->sft_pkt_mutex);
if (target->sft_alive && target->sft_scan_count !=
sf_target_scan_cnt) {
target->sft_alive = 0;
target->sft_scan_count++;
mutex_exit(&target->sft_pkt_mutex);
return;
}
target->sft_alive = 0;
target->sft_scan_count = 0;
cmd = target->sft_pkt_head;
while (cmd != (struct sf_pkt *)&target->sft_pkt_head) {
mutex_enter(&cmd->cmd_abort_mutex);
if (cmd->cmd_state == SF_STATE_ISSUED &&
((cmd->cmd_timeout && sf_watchdog_time >
#ifdef DEBUG
cmd->cmd_timeout) || sf_abort_flag)) {
sf_abort_flag = 0;
#else
cmd->cmd_timeout))) {
#endif
cmd->cmd_timeout = 0;
/* prevent reset from getting at this packet */
cmd->cmd_state = SF_STATE_ABORTING;
mutex_exit(&cmd->cmd_abort_mutex);
mutex_exit(&target->sft_pkt_mutex);
sf->sf_stats.tstats[i].timeouts++;
if (sf_target_timeout(sf, cmd))
return;
else {
if (lip_cnt != sf->sf_lip_cnt) {
return;
} else {
mutex_enter(&target->
sft_pkt_mutex);
cmd = target->
sft_pkt_head;
}
}
/*
* if the abort and lip fail, a reset will be carried out.
* But the reset will ignore this packet. We have waited at least
* 20 seconds after the initial timeout. Now, complete it here.
* This also takes care of spurious bad aborts.
*/
} else if ((cmd->cmd_state ==
SF_STATE_ABORTING) && (cmd->cmd_timeout
<= sf_watchdog_time)) {
cmd->cmd_state = SF_STATE_IDLE;
mutex_exit(&cmd->cmd_abort_mutex);
mutex_exit(&target->sft_pkt_mutex);
SF_DEBUG(1, (sf, CE_NOTE,
"Command 0x%p to sft 0x%p"
" delayed release\n",
(void *)cmd, (void *)target));
pkt = cmd->cmd_pkt;
pkt->pkt_statistics |=
(STAT_TIMEOUT|STAT_ABORTED);
pkt->pkt_reason = CMD_TIMEOUT;
if (pkt->pkt_comp) {
(*pkt->pkt_comp)(pkt);
/* handle deferred_destroy case */
} else {
if ((cmd->cmd_block->fcp_cntl.
cntl_reset == 1) ||
(cmd->cmd_block->
fcp_cntl.cntl_abort_tsk ==
1)) {
cmd->cmd_block->
fcp_cntl.
cntl_reset = 0;
cmd->cmd_block->
fcp_cntl.
cntl_abort_tsk = 0;
cmd->cmd_fp_pkt->
fcal_pkt_comp =
sf_cmd_callback;
/* for cache */
sf_scsi_destroy_pkt
(&pkt->pkt_address,
pkt);
}
}
mutex_enter(&target->sft_pkt_mutex);
cmd = target->sft_pkt_head;
} else {
mutex_exit(&cmd->cmd_abort_mutex);
cmd = cmd->cmd_forw;
}
}
mutex_exit(&target->sft_pkt_mutex);
target = target->sft_next_lun;
}
}
}
/*
* a command to a target has timed out
* return TRUE iff cmd abort failed or timed out, else return FALSE
*/
static int
sf_target_timeout(struct sf *sf, struct sf_pkt *cmd)
{
int rval;
struct scsi_pkt *pkt;
struct fcal_packet *fpkt;
int tgt_id;
int retval = FALSE;
SF_DEBUG(1, (sf, CE_NOTE, "Command 0x%p to target %x timed out\n",
(void *)cmd->cmd_fp_pkt, cmd->cmd_pkt->pkt_address.a_target));
fpkt = cmd->cmd_fp_pkt;
if (sf_core && (sf_core & SF_CORE_CMD_TIMEOUT)) {
sf_token = (int *)(uintptr_t)
fpkt->fcal_socal_request.sr_soc_hdr.\
sh_request_token;
(void) soc_take_core(sf->sf_sochandle, sf->sf_socp);
sf_core = 0;
}
/* call the transport to abort a command */
rval = soc_abort(sf->sf_sochandle, sf->sf_socp,
sf->sf_sochandle->fcal_portno, fpkt, 1);
switch (rval) {
case FCAL_ABORTED:
SF_DEBUG(1, (sf, CE_NOTE, "Command Abort succeeded\n"));
pkt = cmd->cmd_pkt;
cmd->cmd_state = SF_STATE_IDLE;
pkt->pkt_statistics |= (STAT_TIMEOUT|STAT_ABORTED);
pkt->pkt_reason = CMD_TIMEOUT;
if (pkt->pkt_comp != NULL) {
(*pkt->pkt_comp)(pkt);
}
break; /* success */
case FCAL_ABORT_FAILED:
SF_DEBUG(1, (sf, CE_NOTE, "Command Abort failed at target\n"));
pkt = cmd->cmd_pkt;
cmd->cmd_state = SF_STATE_IDLE;
pkt->pkt_reason = CMD_TIMEOUT;
pkt->pkt_statistics |= STAT_TIMEOUT;
tgt_id = pkt->pkt_address.a_target;
sf->sf_stats.tstats[tgt_id].abts_failures++;
if (pkt->pkt_comp != NULL) {
(*pkt->pkt_comp)(pkt);
}
break;
case FCAL_BAD_ABORT:
if (sf_core && (sf_core & SF_CORE_BAD_ABORT)) {
sf_token = (int *)(uintptr_t)fpkt->fcal_socal_request.\
sr_soc_hdr.sh_request_token;
(void) soc_take_core(sf->sf_sochandle, sf->sf_socp);
sf_core = 0;
}
SF_DEBUG(1, (sf, CE_NOTE, "Command Abort bad abort\n"));
cmd->cmd_timeout = sf_watchdog_time + cmd->cmd_pkt->pkt_time
+ 20;
break;
case FCAL_TIMEOUT:
retval = TRUE;
break;
default:
pkt = cmd->cmd_pkt;
tgt_id = pkt->pkt_address.a_target;
sf_log(sf, CE_WARN,
"Command Abort failed target 0x%x, forcing a LIP\n", tgt_id);
if (sf_core && (sf_core & SF_CORE_ABORT_TIMEOUT)) {
sf_token = (int *)(uintptr_t)fpkt->fcal_socal_request.\
sr_soc_hdr.sh_request_token;
(void) soc_take_core(sf->sf_sochandle, sf->sf_socp);
sf_core = 0;
}
sf_force_lip(sf);
retval = TRUE;
break;
}
return (retval);
}
/*
* an ELS command has timed out
* return ???
*/
static struct sf_els_hdr *
sf_els_timeout(struct sf *sf, struct sf_els_hdr *privp)
{
struct fcal_packet *fpkt;
int rval, dflag, timeout = SF_ELS_TIMEOUT;
uint_t lip_cnt = privp->lip_cnt;
uchar_t els_code = privp->els_code;
struct sf_target *target = privp->target;
char what[64];
fpkt = privp->fpkt;
dflag = privp->delayed_retry;
/* use as temporary state variable */
privp->timeout = SF_INVALID_TIMEOUT;
mutex_exit(&sf->sf_mutex);
if (privp->fpkt->fcal_pkt_comp == sf_els_callback) {
/*
* take socal core if required. Timeouts for IB and hosts
* are not very interesting, so we take socal core only
* if the timeout is *not* for a IB or host.
*/
if (sf_core && (sf_core & SF_CORE_ELS_TIMEOUT) &&
((sf_alpa_to_switch[privp->dest_nport_id] &
0x0d) != 0x0d) && ((privp->dest_nport_id != 1) ||
(privp->dest_nport_id != 2) ||
(privp->dest_nport_id != 4) ||
(privp->dest_nport_id != 8) ||
(privp->dest_nport_id != 0xf))) {
sf_token = (int *)(uintptr_t)fpkt->fcal_socal_request.\
sr_soc_hdr.sh_request_token;
(void) soc_take_core(sf->sf_sochandle, sf->sf_socp);
sf_core = 0;
}
(void) sprintf(what, "ELS 0x%x", privp->els_code);
} else if (privp->fpkt->fcal_pkt_comp == sf_reportlun_callback) {
if (sf_core && (sf_core & SF_CORE_REPORTLUN_TIMEOUT)) {
sf_token = (int *)(uintptr_t)fpkt->fcal_socal_request.\
sr_soc_hdr.sh_request_token;
(void) soc_take_core(sf->sf_sochandle, sf->sf_socp);
sf_core = 0;
}
timeout = SF_FCP_TIMEOUT;
(void) sprintf(what, "REPORT_LUNS");
} else if (privp->fpkt->fcal_pkt_comp == sf_inq_callback) {
if (sf_core && (sf_core & SF_CORE_INQUIRY_TIMEOUT)) {
sf_token = (int *)(uintptr_t)
fpkt->fcal_socal_request.\
sr_soc_hdr.sh_request_token;
(void) soc_take_core(sf->sf_sochandle, sf->sf_socp);
sf_core = 0;
}
timeout = SF_FCP_TIMEOUT;
(void) sprintf(what, "INQUIRY to LUN 0x%lx",
(long)SCSA_LUN(target));
} else {
(void) sprintf(what, "UNKNOWN OPERATION");
}
if (dflag) {
/* delayed retry */
SF_DEBUG(2, (sf, CE_CONT,
"!sf%d: %s to target %x delayed retry\n",
ddi_get_instance(sf->sf_dip), what,
sf_alpa_to_switch[privp->dest_nport_id]));
privp->delayed_retry = FALSE;
goto try_again;
}
sf_log(sf, CE_NOTE, "!%s to target 0x%x alpa 0x%x timed out\n",
what, sf_alpa_to_switch[privp->dest_nport_id],
privp->dest_nport_id);
rval = soc_abort(sf->sf_sochandle, sf->sf_socp, sf->sf_sochandle
->fcal_portno, fpkt, 1);
if (rval == FCAL_ABORTED || rval == FCAL_ABORT_FAILED) {
SF_DEBUG(1, (sf, CE_NOTE, "!%s abort to al_pa %x succeeded\n",
what, privp->dest_nport_id));
try_again:
mutex_enter(&sf->sf_mutex);
if (privp->prev != NULL) {
privp->prev->next = privp->next;
}
if (sf->sf_els_list == privp) {
sf->sf_els_list = privp->next;
}
if (privp->next != NULL) {
privp->next->prev = privp->prev;
}
privp->prev = privp->next = NULL;
if (lip_cnt == sf->sf_lip_cnt) {
privp->timeout = sf_watchdog_time + timeout;
if ((++(privp->retries) < sf_els_retries) ||
(dflag && (privp->retries < SF_BSY_RETRIES))) {
mutex_exit(&sf->sf_mutex);
sf_log(sf, CE_NOTE,
"!%s to target 0x%x retrying\n",
what,
sf_alpa_to_switch[privp->dest_nport_id]);
if (sf_els_transport(sf, privp) == 1) {
mutex_enter(&sf->sf_mutex);
return (sf->sf_els_list); /* success */
}
mutex_enter(&sf->sf_mutex);
fpkt = NULL;
}
if ((lip_cnt == sf->sf_lip_cnt) &&
(els_code != LA_ELS_LOGO)) {
if (target != NULL) {
sf_offline_target(sf, target);
}
if (sf->sf_lip_cnt == lip_cnt) {
sf->sf_device_count--;
ASSERT(sf->sf_device_count >= 0);
if (sf->sf_device_count == 0) {
sf_finish_init(sf,
sf->sf_lip_cnt);
}
}
}
privp = sf->sf_els_list;
mutex_exit(&sf->sf_mutex);
if (fpkt != NULL) {
sf_els_free(fpkt);
}
} else {
mutex_exit(&sf->sf_mutex);
sf_els_free(privp->fpkt);
privp = NULL;
}
} else {
if (sf_core && (sf_core & SF_CORE_ELS_FAILED)) {
sf_token = (int *)(uintptr_t)
fpkt->fcal_socal_request.\
sr_soc_hdr.sh_request_token;
(void) soc_take_core(sf->sf_sochandle, sf->sf_socp);
sf_core = 0;
}
sf_log(sf, CE_NOTE, "%s abort to target 0x%x failed. "
"status=0x%x, forcing LIP\n", what,
sf_alpa_to_switch[privp->dest_nport_id], rval);
privp = NULL;
if (sf->sf_lip_cnt == lip_cnt) {
sf_force_lip(sf);
}
}
mutex_enter(&sf->sf_mutex);
return (privp);
}
/*
* called by timeout when a reset times out
*/
/*ARGSUSED*/
static void
sf_check_reset_delay(void *arg)
{
struct sf *sf;
struct sf_target *target;
struct sf_reset_list *rp, *tp;
uint_t lip_cnt, reset_timeout_flag = FALSE;
clock_t lb;
lb = ddi_get_lbolt();
mutex_enter(&sf_global_mutex);
sf_reset_timeout_id = 0;
for (sf = sf_head; sf != NULL; sf = sf->sf_next) {
mutex_exit(&sf_global_mutex);
mutex_enter(&sf->sf_mutex);
/* is this type cast needed? */
tp = (struct sf_reset_list *)&sf->sf_reset_list;
rp = sf->sf_reset_list;
while (rp != NULL) {
if (((rp->timeout - lb) < 0) &&
(rp->lip_cnt == sf->sf_lip_cnt)) {
tp->next = rp->next;
mutex_exit(&sf->sf_mutex);
target = rp->target;
lip_cnt = rp->lip_cnt;
kmem_free(rp, sizeof (struct sf_reset_list));
/* abort all cmds for this target */
while (target) {
sf_abort_all(sf, target, FALSE,
lip_cnt, TRUE);
mutex_enter(&target->sft_mutex);
if (lip_cnt == sf->sf_lip_cnt) {
target->sft_state &=
~SF_TARGET_BUSY;
}
mutex_exit(&target->sft_mutex);
target = target->sft_next_lun;
}
mutex_enter(&sf->sf_mutex);
tp = (struct sf_reset_list *)
&sf->sf_reset_list;
rp = sf->sf_reset_list;
lb = ddi_get_lbolt();
} else if (rp->lip_cnt != sf->sf_lip_cnt) {
tp->next = rp->next;
kmem_free(rp, sizeof (struct sf_reset_list));
rp = tp->next;
} else {
reset_timeout_flag = TRUE;
tp = rp;
rp = rp->next;
}
}
mutex_exit(&sf->sf_mutex);
mutex_enter(&sf_global_mutex);
}
if (reset_timeout_flag && (sf_reset_timeout_id == 0)) {
sf_reset_timeout_id = timeout(sf_check_reset_delay,
NULL, drv_usectohz(SF_TARGET_RESET_DELAY));
}
mutex_exit(&sf_global_mutex);
}
/*
* called to "reset the bus", i.e. force loop initialization (and address
* re-negotiation)
*/
static void
sf_force_lip(struct sf *sf)
{
int i;
struct sf_target *target;
/* disable restart of lip if we're suspended */
mutex_enter(&sf->sf_mutex);
if (sf->sf_state & SF_STATE_SUSPENDED) {
mutex_exit(&sf->sf_mutex);
SF_DEBUG(1, (sf, CE_CONT,
"sf_force_lip, sf%d: lip restart disabled "
"due to DDI_SUSPEND\n",
ddi_get_instance(sf->sf_dip)));
return;
}
sf_log(sf, CE_NOTE, "Forcing lip\n");
for (i = 0; i < sf_max_targets; i++) {
target = sf->sf_targets[i];
while (target != NULL) {
mutex_enter(&target->sft_mutex);
if (!(target->sft_state & SF_TARGET_OFFLINE))
target->sft_state |= SF_TARGET_BUSY;
mutex_exit(&target->sft_mutex);
target = target->sft_next_lun;
}
}
sf->sf_lip_cnt++;
sf->sf_timer = sf_watchdog_time + SF_OFFLINE_TIMEOUT;
sf->sf_state = SF_STATE_OFFLINE;
mutex_exit(&sf->sf_mutex);
sf->sf_stats.lip_count++; /* no mutex for this? */
#ifdef DEBUG
/* are we allowing LIPs ?? */
if (sf_lip_flag != 0) {
#endif
/* call the transport to force loop initialization */
if (((i = soc_force_lip(sf->sf_sochandle, sf->sf_socp,
sf->sf_sochandle->fcal_portno, 1,
FCAL_FORCE_LIP)) != FCAL_SUCCESS) &&
(i != FCAL_TIMEOUT)) {
/* force LIP failed */
if (sf_core && (sf_core & SF_CORE_LIP_FAILED)) {
(void) soc_take_core(sf->sf_sochandle,
sf->sf_socp);
sf_core = 0;
}
#ifdef DEBUG
/* are we allowing reset after LIP failed ?? */
if (sf_reset_flag != 0) {
#endif
/* restart socal after resetting it */
sf_log(sf, CE_NOTE,
"!Force lip failed Status code 0x%x."
" Reseting\n", i);
/* call transport to force a reset */
soc_force_reset(sf->sf_sochandle, sf->sf_socp,
sf->sf_sochandle->fcal_portno, 1);
#ifdef DEBUG
}
#endif
}
#ifdef DEBUG
}
#endif
}
/*
* called by the transport when an unsolicited ELS is received
*/
static void
sf_unsol_els_callback(void *arg, soc_response_t *srp, caddr_t payload)
{
struct sf *sf = (struct sf *)arg;
els_payload_t *els = (els_payload_t *)payload;
struct la_els_rjt *rsp;
int i, tgt_id;
uchar_t dest_id;
struct fcal_packet *fpkt;
fc_frame_header_t *hp;
struct sf_els_hdr *privp;
if ((els == NULL) || ((i = srp->sr_soc_hdr.sh_byte_cnt) == 0)) {
return;
}
if (i > SOC_CQE_PAYLOAD) {
i = SOC_CQE_PAYLOAD;
}
dest_id = (uchar_t)srp->sr_fc_frame_hdr.s_id;
tgt_id = sf_alpa_to_switch[dest_id];
switch (els->els_cmd.c.ls_command) {
case LA_ELS_LOGO:
/*
* logout received -- log the fact
*/
sf->sf_stats.tstats[tgt_id].logouts_recvd++;
sf_log(sf, CE_NOTE, "!LOGO recvd from target %x, %s\n",
tgt_id,
sf_lip_on_plogo ? "Forcing LIP...." : "");
if (sf_lip_on_plogo) {
sf_force_lip(sf);
}
break;
default: /* includes LA_ELS_PLOGI */
/*
* something besides a logout received -- we don't handle
* this so send back a reject saying its unsupported
*/
sf_log(sf, CE_NOTE, "!ELS 0x%x recvd from target 0x%x\n",
els->els_cmd.c.ls_command, tgt_id);
/* allocate room for a response */
if (sf_els_alloc(sf, dest_id, sizeof (struct sf_els_hdr),
sizeof (struct la_els_rjt), sizeof (union sf_els_rsp),
(caddr_t *)&privp, (caddr_t *)&rsp) == NULL) {
break;
}
fpkt = privp->fpkt;
/* fill in pkt header */
hp = &fpkt->fcal_socal_request.sr_fc_frame_hdr;
hp->r_ctl = R_CTL_ELS_RSP;
hp->f_ctl = F_CTL_LAST_SEQ | F_CTL_XCHG_CONTEXT;
hp->ox_id = srp->sr_fc_frame_hdr.ox_id;
hp->rx_id = srp->sr_fc_frame_hdr.rx_id;
fpkt->fcal_socal_request.sr_cqhdr.cq_hdr_type =
CQ_TYPE_OUTBOUND;
fpkt->fcal_socal_request.sr_soc_hdr.sh_seg_cnt = 1;
/* fill in response */
rsp->ls_code = LA_ELS_RJT; /* reject this ELS */
rsp->mbz[0] = 0;
rsp->mbz[1] = 0;
rsp->mbz[2] = 0;
((struct la_els_logi *)privp->rsp)->ls_code = LA_ELS_ACC;
*((int *)&rsp->reserved) = 0;
rsp->reason_code = RJT_UNSUPPORTED;
privp->retries = sf_els_retries;
privp->els_code = LA_ELS_RJT;
privp->timeout = (unsigned)0xffffffff;
(void) sf_els_transport(sf, privp);
break;
}
}
/*
* Error logging, printing, and debug print routines
*/
/*PRINTFLIKE3*/
static void
sf_log(struct sf *sf, int level, const char *fmt, ...)
{
char buf[256];
dev_info_t *dip;
va_list ap;
if (sf != NULL) {
dip = sf->sf_dip;
} else {
dip = NULL;
}
va_start(ap, fmt);
(void) vsprintf(buf, fmt, ap);
va_end(ap);
scsi_log(dip, "sf", level, buf);
}
/*
* called to get some sf kstats -- return 0 on success else return errno
*/
static int
sf_kstat_update(kstat_t *ksp, int rw)
{
struct sf *sf;
if (rw == KSTAT_WRITE) {
/* can't write */
return (EACCES);
}
sf = ksp->ks_private;
sf->sf_stats.ncmds = sf->sf_ncmds;
sf->sf_stats.throttle_limit = sf->sf_throttle;
sf->sf_stats.cr_pool_size = sf->sf_cr_pool_cnt;
return (0); /* success */
}
/*
* Unix Entry Points
*/
/*
* driver entry point for opens on control device
*/
/* ARGSUSED */
static int
sf_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p)
{
dev_t dev = *dev_p;
struct sf *sf;
/* just ensure soft state exists for this device */
sf = ddi_get_soft_state(sf_state, SF_MINOR2INST(getminor(dev)));
if (sf == NULL) {
return (ENXIO);
}
++(sf->sf_check_n_close);
return (0);
}
/*
* driver entry point for last close on control device
*/
/* ARGSUSED */
static int
sf_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
{
struct sf *sf;
sf = ddi_get_soft_state(sf_state, SF_MINOR2INST(getminor(dev)));
if (sf == NULL) {
return (ENXIO);
}
if (!sf->sf_check_n_close) { /* if this flag is zero */
cmn_err(CE_WARN, "sf%d: trying to close unopened instance",
SF_MINOR2INST(getminor(dev)));
return (ENODEV);
} else {
--(sf->sf_check_n_close);
}
return (0);
}
/*
* driver entry point for sf ioctl commands
*/
/* ARGSUSED */
static int
sf_ioctl(dev_t dev,
int cmd, intptr_t arg, int mode, cred_t *cred_p, int *rval_p)
{
struct sf *sf;
struct sf_target *target;
uchar_t al_pa;
struct sf_al_map map;
int cnt, i;
int retval; /* return value */
struct devctl_iocdata *dcp;
dev_info_t *cdip;
struct scsi_address ap;
scsi_hba_tran_t *tran;
sf = ddi_get_soft_state(sf_state, SF_MINOR2INST(getminor(dev)));
if (sf == NULL) {
return (ENXIO);
}
/* handle all ioctls */
switch (cmd) {
/*
* We can use the generic implementation for these ioctls
*/
case DEVCTL_DEVICE_GETSTATE:
case DEVCTL_DEVICE_ONLINE:
case DEVCTL_DEVICE_OFFLINE:
case DEVCTL_BUS_GETSTATE:
return (ndi_devctl_ioctl(sf->sf_dip, cmd, arg, mode, 0));
/*
* return FC map
*/
case SFIOCGMAP:
if ((sf->sf_lilp_map->lilp_magic != FCAL_LILP_MAGIC &&
sf->sf_lilp_map->lilp_magic != FCAL_BADLILP_MAGIC) ||
sf->sf_state != SF_STATE_ONLINE) {
retval = ENOENT;
goto dun;
}
mutex_enter(&sf->sf_mutex);
if (sf->sf_lilp_map->lilp_magic == FCAL_BADLILP_MAGIC) {
int i, j = 0;
/* Need to generate a fake lilp map */
for (i = 0; i < sf_max_targets; i++) {
if (sf->sf_targets[i])
sf->sf_lilp_map->lilp_alpalist[j++] =
sf->sf_targets[i]->
sft_hard_address;
}
sf->sf_lilp_map->lilp_length = (uchar_t)j;
}
cnt = sf->sf_lilp_map->lilp_length;
map.sf_count = (short)cnt;
bcopy((caddr_t)&sf->sf_sochandle->fcal_n_wwn,
(caddr_t)&map.sf_hba_addr.sf_node_wwn,
sizeof (la_wwn_t));
bcopy((caddr_t)&sf->sf_sochandle->fcal_p_wwn,
(caddr_t)&map.sf_hba_addr.sf_port_wwn,
sizeof (la_wwn_t));
map.sf_hba_addr.sf_al_pa = sf->sf_al_pa;
map.sf_hba_addr.sf_hard_address = 0;
map.sf_hba_addr.sf_inq_dtype = DTYPE_UNKNOWN;
for (i = 0; i < cnt; i++) {
al_pa = sf->sf_lilp_map->lilp_alpalist[i];
map.sf_addr_pair[i].sf_al_pa = al_pa;
if (al_pa == sf->sf_al_pa) {
(void) bcopy((caddr_t)&sf->sf_sochandle
->fcal_n_wwn, (caddr_t)&map.
sf_addr_pair[i].sf_node_wwn,
sizeof (la_wwn_t));
(void) bcopy((caddr_t)&sf->sf_sochandle
->fcal_p_wwn, (caddr_t)&map.
sf_addr_pair[i].sf_port_wwn,
sizeof (la_wwn_t));
map.sf_addr_pair[i].sf_hard_address =
al_pa;
map.sf_addr_pair[i].sf_inq_dtype =
DTYPE_PROCESSOR;
continue;
}
target = sf->sf_targets[sf_alpa_to_switch[
al_pa]];
if (target != NULL) {
mutex_enter(&target->sft_mutex);
if (!(target->sft_state &
(SF_TARGET_OFFLINE |
SF_TARGET_BUSY))) {
bcopy((caddr_t)&target->
sft_node_wwn,
(caddr_t)&map.sf_addr_pair
[i].sf_node_wwn,
sizeof (la_wwn_t));
bcopy((caddr_t)&target->
sft_port_wwn,
(caddr_t)&map.sf_addr_pair
[i].sf_port_wwn,
sizeof (la_wwn_t));
map.sf_addr_pair[i].
sf_hard_address
= target->sft_hard_address;
map.sf_addr_pair[i].
sf_inq_dtype
= target->sft_device_type;
mutex_exit(&target->sft_mutex);
continue;
}
mutex_exit(&target->sft_mutex);
}
bzero((caddr_t)&map.sf_addr_pair[i].
sf_node_wwn, sizeof (la_wwn_t));
bzero((caddr_t)&map.sf_addr_pair[i].
sf_port_wwn, sizeof (la_wwn_t));
map.sf_addr_pair[i].sf_inq_dtype =
DTYPE_UNKNOWN;
}
mutex_exit(&sf->sf_mutex);
if (ddi_copyout((caddr_t)&map, (caddr_t)arg,
sizeof (struct sf_al_map), mode) != 0) {
retval = EFAULT;
goto dun;
}
break;
/*
* handle device control ioctls
*/
case DEVCTL_DEVICE_RESET:
if (ndi_dc_allochdl((void *)arg, &dcp) != NDI_SUCCESS) {
retval = EFAULT;
goto dun;
}
if ((ndi_dc_getname(dcp) == NULL) ||
(ndi_dc_getaddr(dcp) == NULL)) {
ndi_dc_freehdl(dcp);
retval = EINVAL;
goto dun;
}
cdip = ndi_devi_find(sf->sf_dip,
ndi_dc_getname(dcp), ndi_dc_getaddr(dcp));
ndi_dc_freehdl(dcp);
if (cdip == NULL) {
retval = ENXIO;
goto dun;
}
if ((target = sf_get_target_from_dip(sf, cdip)) == NULL) {
retval = ENXIO;
goto dun;
}
mutex_enter(&target->sft_mutex);
if (!(target->sft_state & SF_TARGET_INIT_DONE)) {
mutex_exit(&target->sft_mutex);
retval = ENXIO;
goto dun;
}
/* This is ugly */
tran = kmem_zalloc(scsi_hba_tran_size(), KM_SLEEP);
bcopy(target->sft_tran, tran, scsi_hba_tran_size());
mutex_exit(&target->sft_mutex);
ap.a_hba_tran = tran;
ap.a_target = sf_alpa_to_switch[target->sft_al_pa];
if (sf_reset(&ap, RESET_TARGET) == FALSE) {
retval = EIO;
} else {
retval = 0;
}
kmem_free(tran, scsi_hba_tran_size());
goto dun;
case DEVCTL_BUS_QUIESCE:
case DEVCTL_BUS_UNQUIESCE:
retval = ENOTSUP;
goto dun;
case DEVCTL_BUS_RESET:
case DEVCTL_BUS_RESETALL:
sf_force_lip(sf);
break;
default:
retval = ENOTTY;
goto dun;
}
retval = 0; /* success */
dun:
return (retval);
}
/*
* get the target given a DIP
*/
static struct sf_target *
sf_get_target_from_dip(struct sf *sf, dev_info_t *dip)
{
int i;
struct sf_target *target;
/* scan each hash queue for the DIP in question */
for (i = 0; i < SF_NUM_HASH_QUEUES; i++) {
target = sf->sf_wwn_lists[i];
while (target != NULL) {
if (target->sft_dip == dip) {
return (target); /* success: target found */
}
target = target->sft_next;
}
}
return (NULL); /* failure: target not found */
}
/*
* called by the transport to get an event cookie
*/
static int
sf_bus_get_eventcookie(dev_info_t *dip, dev_info_t *rdip, char *name,
ddi_eventcookie_t *event_cookiep)
{
struct sf *sf;
sf = ddi_get_soft_state(sf_state, ddi_get_instance(dip));
if (sf == NULL) {
/* can't find instance for this device */
return (DDI_FAILURE);
}
return (ndi_event_retrieve_cookie(sf->sf_event_hdl, rdip, name,
event_cookiep, NDI_EVENT_NOPASS));
}
/*
* called by the transport to add an event callback
*/
static int
sf_bus_add_eventcall(dev_info_t *dip, dev_info_t *rdip,
ddi_eventcookie_t eventid, void (*callback)(dev_info_t *dip,
ddi_eventcookie_t event, void *arg, void *impl_data), void *arg,
ddi_callback_id_t *cb_id)
{
struct sf *sf;
sf = ddi_get_soft_state(sf_state, ddi_get_instance(dip));
if (sf == NULL) {
/* can't find instance for this device */
return (DDI_FAILURE);
}
return (ndi_event_add_callback(sf->sf_event_hdl, rdip,
eventid, callback, arg, NDI_SLEEP, cb_id));
}
/*
* called by the transport to remove an event callback
*/
static int
sf_bus_remove_eventcall(dev_info_t *devi, ddi_callback_id_t cb_id)
{
struct sf *sf;
sf = ddi_get_soft_state(sf_state, ddi_get_instance(devi));
if (sf == NULL) {
/* can't find instance for this device */
return (DDI_FAILURE);
}
return (ndi_event_remove_callback(sf->sf_event_hdl, cb_id));
}
/*
* called by the transport to post an event
*/
static int
sf_bus_post_event(dev_info_t *dip, dev_info_t *rdip,
ddi_eventcookie_t eventid, void *impldata)
{
ddi_eventcookie_t remove_cookie, cookie;
/* is this a remove event ?? */
struct sf *sf = ddi_get_soft_state(sf_state, ddi_get_instance(dip));
remove_cookie = ndi_event_tag_to_cookie(sf->sf_event_hdl,
SF_EVENT_TAG_REMOVE);
if (remove_cookie == eventid) {
struct sf_target *target;
/* handle remove event */
if (sf == NULL) {
/* no sf instance for this device */
return (NDI_FAILURE);
}
/* get the target for this event */
if ((target = sf_get_target_from_dip(sf, rdip)) != NULL) {
/*
* clear device info for this target and mark as
* not done
*/
mutex_enter(&target->sft_mutex);
target->sft_dip = NULL;
target->sft_state &= ~SF_TARGET_INIT_DONE;
mutex_exit(&target->sft_mutex);
return (NDI_SUCCESS); /* event handled */
}
/* no target for this event */
return (NDI_FAILURE);
}
/* an insertion event */
if (ndi_busop_get_eventcookie(dip, rdip, FCAL_INSERT_EVENT, &cookie)
!= NDI_SUCCESS) {
return (NDI_FAILURE);
}
return (ndi_post_event(dip, rdip, cookie, impldata));
}
/*
* the sf hotplug daemon, one thread per sf instance
*/
static void
sf_hp_daemon(void *arg)
{
struct sf *sf = (struct sf *)arg;
struct sf_hp_elem *elem;
struct sf_target *target;
int tgt_id;
callb_cpr_t cprinfo;
CALLB_CPR_INIT(&cprinfo, &sf->sf_hp_daemon_mutex,
callb_generic_cpr, "sf_hp_daemon");
mutex_enter(&sf->sf_hp_daemon_mutex);
do {
while (sf->sf_hp_elem_head != NULL) {
/* save ptr to head of list */
elem = sf->sf_hp_elem_head;
/* take element off of list */
if (sf->sf_hp_elem_head == sf->sf_hp_elem_tail) {
/* element only one in list -- list now empty */
sf->sf_hp_elem_head = NULL;
sf->sf_hp_elem_tail = NULL;
} else {
/* remove element from head of list */
sf->sf_hp_elem_head = sf->sf_hp_elem_head->next;
}
mutex_exit(&sf->sf_hp_daemon_mutex);
switch (elem->what) {
case SF_ONLINE:
/* online this target */
target = elem->target;
(void) ndi_devi_online(elem->dip, 0);
(void) ndi_event_retrieve_cookie(
sf->sf_event_hdl,
target->sft_dip, FCAL_INSERT_EVENT,
&sf_insert_eid, NDI_EVENT_NOPASS);
(void) ndi_event_run_callbacks(sf->sf_event_hdl,
target->sft_dip, sf_insert_eid, NULL);
break;
case SF_OFFLINE:
/* offline this target */
target = elem->target;
tgt_id = sf_alpa_to_switch[target->sft_al_pa];
/* don't do NDI_DEVI_REMOVE for now */
if (ndi_devi_offline(elem->dip, 0) !=
NDI_SUCCESS) {
SF_DEBUG(1, (sf, CE_WARN, "target %x, "
"device offline failed", tgt_id));
} else {
SF_DEBUG(1, (sf, CE_NOTE, "target %x, "
"device offline succeeded\n",
tgt_id));
}
break;
}
kmem_free(elem, sizeof (struct sf_hp_elem));
mutex_enter(&sf->sf_hp_daemon_mutex);
}
/* if exit is not already signaled */
if (sf->sf_hp_exit == 0) {
/* wait to be signaled by work or exit */
CALLB_CPR_SAFE_BEGIN(&cprinfo);
cv_wait(&sf->sf_hp_daemon_cv, &sf->sf_hp_daemon_mutex);
CALLB_CPR_SAFE_END(&cprinfo, &sf->sf_hp_daemon_mutex);
}
} while (sf->sf_hp_exit == 0);
/* sf_hp_daemon_mutex is dropped by CALLB_CPR_EXIT */
CALLB_CPR_EXIT(&cprinfo);
thread_exit(); /* no more hotplug thread */
/* NOTREACHED */
}