blob: 8da143270a885fa7ba18a584e777236512fb3653 [file] [log] [blame]
/*
* 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 (c) 1994, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2014 Garrett D'Amore <garrett@damore.org>
* Copyright (c) 2016 by Delphix. All rights reserved.
*/
#include <sys/note.h>
/*
* Generic SCSI Host Bus Adapter interface implementation
*/
#include <sys/scsi/scsi.h>
#include <sys/scsi/generic/sas.h>
#include <sys/file.h>
#include <sys/disp.h> /* for minclsyspri */
#include <sys/ddi_impldefs.h>
#include <sys/ndi_impldefs.h>
#include <sys/sunndi.h>
#include <sys/ddi.h>
#include <sys/sunmdi.h>
#include <sys/mdi_impldefs.h>
#include <sys/callb.h>
#include <sys/epm.h>
#include <sys/damap.h>
#include <sys/time.h>
#include <sys/sunldi.h>
#include <sys/fm/protocol.h>
extern struct scsi_pkt *scsi_init_cache_pkt(struct scsi_address *,
struct scsi_pkt *, struct buf *, int, int, int, int,
int (*)(caddr_t), caddr_t);
extern void scsi_free_cache_pkt(struct scsi_address *, struct scsi_pkt *);
extern void scsi_cache_dmafree(struct scsi_address *, struct scsi_pkt *);
extern void scsi_sync_cache_pkt(struct scsi_address *, struct scsi_pkt *);
extern int modrootloaded;
/*
* Round up all allocations so that we can guarantee
* long-long alignment. This is the same alignment
* provided by kmem_alloc().
*/
#define ROUNDUP(x) (((x) + 0x07) & ~0x07)
/* Magic number to track correct allocations in wrappers */
#define PKT_WRAPPER_MAGIC 0xa110ced /* alloced correctly */
kmutex_t scsi_flag_nointr_mutex;
kcondvar_t scsi_flag_nointr_cv;
kmutex_t scsi_log_mutex;
/* asynchronous probe barrier deletion data structures */
static kmutex_t scsi_hba_barrier_mutex;
static kcondvar_t scsi_hba_barrier_cv;
static struct scsi_hba_barrier {
struct scsi_hba_barrier *barrier_next;
clock_t barrier_endtime;
dev_info_t *barrier_probe;
} *scsi_hba_barrier_list;
static int scsi_hba_devi_is_barrier(dev_info_t *probe);
static void scsi_hba_barrier_tran_tgt_free(dev_info_t *probe);
static void scsi_hba_barrier_add(dev_info_t *probe, int seconds);
static int scsi_hba_remove_node(dev_info_t *child);
static void scsi_hba_barrier_daemon(void *arg);
/* LUN-change ASC/ASCQ processing data structures (stage1 and stage2) */
static kmutex_t scsi_lunchg1_mutex;
static kcondvar_t scsi_lunchg1_cv;
static struct scsi_pkt *scsi_lunchg1_list;
static void scsi_lunchg1_daemon(void *arg);
static kmutex_t scsi_lunchg2_mutex;
static kcondvar_t scsi_lunchg2_cv;
static struct scsi_lunchg2 {
struct scsi_lunchg2 *lunchg2_next;
char *lunchg2_path;
} *scsi_lunchg2_list;
static void scsi_lunchg2_daemon(void *arg);
static int scsi_findchild(dev_info_t *self, char *name, char *addr,
int init, dev_info_t **dchildp, mdi_pathinfo_t **pchildp, int *ppi);
/* return value defines for scsi_findchild */
#define CHILD_TYPE_NONE 0
#define CHILD_TYPE_DEVINFO 1
#define CHILD_TYPE_PATHINFO 2
/*
* Enumeration code path currently being followed. SE_BUSCONFIG results in
* DEVI_SID_NODEID, and SE_HP (hotplug) results in DEVI_SID_HP_NODEID.
*
* Since hotplug enumeration is based on information obtained from hardware
* (tgtmap/report_lun) the type/severity of enumeration error messages is
* sometimes based SE_HP (indirectly via ndi_dev_is_hotplug_node()). By
* convention, these messages are all produced by scsi_enumeration_failed().
*/
typedef enum { SE_BUSCONFIG = 0, SE_HP = 1 } scsi_enum_t;
/* compatible properties of driver to use during probe/enumeration operations */
static char *compatible_probe = "scsa,probe";
static char *compatible_nodev = "scsa,nodev";
static char *scsi_probe_ascii[] = SCSIPROBE_ASCII;
/* number of LUNs we attempt to get on the first SCMD_REPORT_LUNS command */
int scsi_lunrpt_default_max = 256;
int scsi_lunrpt_timeout = 3; /* seconds */
/*
* Only enumerate one lun if reportluns fails on a SCSI_VERSION_3 device
* (tunable based on calling context).
*/
int scsi_lunrpt_failed_do1lun = (1 << SE_HP);
/* 'scsi-binding-set' value for legacy enumerated 'spi' transports */
char *scsi_binding_set_spi = "spi";
/* enable NDI_DEVI_DEBUG for bus_[un]config operations */
int scsi_hba_bus_config_debug = 0;
/* DEBUG: enable NDI_DEVI_REMOVE for bus_unconfig of dynamic node */
int scsi_hba_bus_unconfig_remove = 0;
/* number of probe serilization messages */
int scsi_hba_wait_msg = 5;
/*
* Establish the timeout used to cache (in the probe node) the fact that the
* device does not exist. This replaces the target specific probe cache.
*/
int scsi_hba_barrier_timeout = (60); /* seconds */
#ifdef DEBUG
int scsi_hba_bus_config_failure_msg = 0;
int scsi_hba_bus_config_failure_dbg = 0;
int scsi_hba_bus_config_success_msg = 0;
int scsi_hba_bus_config_success_dbg = 0;
#endif /* DEBUG */
/*
* Structure for scsi_hba_iportmap_* implementation/wrap.
*/
typedef struct impl_scsi_iportmap {
dev_info_t *iportmap_hba_dip;
damap_t *iportmap_dam;
int iportmap_create_window;
uint64_t iportmap_create_time; /* clock64_t */
int iportmap_create_csync_usec;
int iportmap_settle_usec;
int iportmap_sync_cnt;
} impl_scsi_iportmap_t;
/*
* Structure for scsi_hba_tgtmap_* implementation/wrap.
*
* Every call to scsi_hba_tgtmap_set_begin will increment tgtmap_reports,
* and a call to scsi_hba_tgtmap_set_end will reset tgtmap_reports to zero.
* If, in scsi_hba_tgtmap_set_begin, we detect a tgtmap_reports value of
* scsi_hba_tgtmap_reports_max we produce a message to indicate that
* the caller is never completing an observation (i.e. we are not making
* any forward progress). If this message occurs, it indicates that the
* solaris hotplug ramifications at the target and lun level are no longer
* tracking.
*
* NOTE: LUNMAPSIZE OK for now, but should be dynamic in reportlun code.
*/
typedef struct impl_scsi_tgtmap {
scsi_hba_tran_t *tgtmap_tran;
int tgtmap_reports; /* _begin, no _end */
int tgtmap_noisy;
scsi_tgt_activate_cb_t tgtmap_activate_cb;
scsi_tgt_deactivate_cb_t tgtmap_deactivate_cb;
void *tgtmap_mappriv;
damap_t *tgtmap_dam[SCSI_TGT_NTYPES];
int tgtmap_create_window;
uint64_t tgtmap_create_time; /* clock64_t */
int tgtmap_create_csync_usec;
int tgtmap_settle_usec;
int tgtmap_sync_cnt;
} impl_scsi_tgtmap_t;
#define LUNMAPSIZE 256 /* 256 LUNs/target */
/* Produce warning if number of begins without an end exceed this value */
int scsi_hba_tgtmap_reports_max = 256;
static int scsi_tgtmap_sync(scsi_hba_tgtmap_t *, int);
/* Default settle_usec damap_sync factor */
int scsi_hba_map_settle_f = 10;
/* Prototype for static dev_ops devo_*() functions */
static int scsi_hba_info(
dev_info_t *self,
ddi_info_cmd_t infocmd,
void *arg,
void **result);
/* Prototypes for static bus_ops bus_*() functions */
static int scsi_hba_bus_ctl(
dev_info_t *self,
dev_info_t *child,
ddi_ctl_enum_t op,
void *arg,
void *result);
static int scsi_hba_map_fault(
dev_info_t *self,
dev_info_t *child,
struct hat *hat,
struct seg *seg,
caddr_t addr,
struct devpage *dp,
pfn_t pfn,
uint_t prot,
uint_t lock);
static int scsi_hba_get_eventcookie(
dev_info_t *self,
dev_info_t *child,
char *name,
ddi_eventcookie_t *eventp);
static int scsi_hba_add_eventcall(
dev_info_t *self,
dev_info_t *child,
ddi_eventcookie_t event,
void (*callback)(
dev_info_t *dip,
ddi_eventcookie_t event,
void *arg,
void *bus_impldata),
void *arg,
ddi_callback_id_t *cb_id);
static int scsi_hba_remove_eventcall(
dev_info_t *self,
ddi_callback_id_t id);
static int scsi_hba_post_event(
dev_info_t *self,
dev_info_t *child,
ddi_eventcookie_t event,
void *bus_impldata);
static int scsi_hba_bus_config(
dev_info_t *self,
uint_t flags,
ddi_bus_config_op_t op,
void *arg,
dev_info_t **childp);
static int scsi_hba_bus_unconfig(
dev_info_t *self,
uint_t flags,
ddi_bus_config_op_t op,
void *arg);
static int scsi_hba_fm_init_child(
dev_info_t *self,
dev_info_t *child,
int cap,
ddi_iblock_cookie_t *ibc);
static int scsi_hba_bus_power(
dev_info_t *self,
void *impl_arg,
pm_bus_power_op_t op,
void *arg,
void *result);
/* bus_ops vector for SCSI HBA's. */
static struct bus_ops scsi_hba_busops = {
BUSO_REV,
nullbusmap, /* bus_map */
NULL, /* bus_get_intrspec */
NULL, /* bus_add_intrspec */
NULL, /* bus_remove_intrspec */
scsi_hba_map_fault, /* bus_map_fault */
NULL, /* bus_dma_map */
ddi_dma_allochdl, /* bus_dma_allochdl */
ddi_dma_freehdl, /* bus_dma_freehdl */
ddi_dma_bindhdl, /* bus_dma_bindhdl */
ddi_dma_unbindhdl, /* bus_unbindhdl */
ddi_dma_flush, /* bus_dma_flush */
ddi_dma_win, /* bus_dma_win */
ddi_dma_mctl, /* bus_dma_ctl */
scsi_hba_bus_ctl, /* bus_ctl */
ddi_bus_prop_op, /* bus_prop_op */
scsi_hba_get_eventcookie, /* bus_get_eventcookie */
scsi_hba_add_eventcall, /* bus_add_eventcall */
scsi_hba_remove_eventcall, /* bus_remove_eventcall */
scsi_hba_post_event, /* bus_post_event */
NULL, /* bus_intr_ctl */
scsi_hba_bus_config, /* bus_config */
scsi_hba_bus_unconfig, /* bus_unconfig */
scsi_hba_fm_init_child, /* bus_fm_init */
NULL, /* bus_fm_fini */
NULL, /* bus_fm_access_enter */
NULL, /* bus_fm_access_exit */
scsi_hba_bus_power /* bus_power */
};
/* cb_ops for hotplug :devctl and :scsi support */
static struct cb_ops scsi_hba_cbops = {
scsi_hba_open,
scsi_hba_close,
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
scsi_hba_ioctl, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
ddi_prop_op, /* prop_op */
NULL, /* stream */
D_NEW|D_MP|D_HOTPLUG, /* cb_flag */
CB_REV, /* rev */
nodev, /* int (*cb_aread)() */
nodev /* int (*cb_awrite)() */
};
/* Prototypes for static scsi_hba.c/SCSA private lunmap interfaces */
static int scsi_lunmap_create(
dev_info_t *self,
impl_scsi_tgtmap_t *tgtmap,
char *tgt_addr);
static void scsi_lunmap_destroy(
dev_info_t *self,
impl_scsi_tgtmap_t *tgtmap,
char *tgt_addr);
static void scsi_lunmap_set_begin(
dev_info_t *self,
damap_t *lundam);
static int scsi_lunmap_set_add(
dev_info_t *self,
damap_t *lundam,
char *taddr,
scsi_lun64_t lun_num,
int lun_sfunc);
static void scsi_lunmap_set_end(
dev_info_t *self,
damap_t *lundam);
/* Prototypes for static misc. scsi_hba.c private bus_config interfaces */
static int scsi_hba_bus_config_iports(dev_info_t *self, uint_t flags,
ddi_bus_config_op_t op, void *arg, dev_info_t **childp);
static int scsi_hba_bus_config_spi(dev_info_t *self, uint_t flags,
ddi_bus_config_op_t op, void *arg, dev_info_t **childp);
static dev_info_t *scsi_hba_bus_config_port(dev_info_t *self,
char *nameaddr, scsi_enum_t se);
#ifdef sparc
static int scsi_hba_bus_config_prom_node(dev_info_t *self, uint_t flags,
void *arg, dev_info_t **childp);
#endif /* sparc */
/*
* SCSI_HBA_LOG is used for all messages. A logging level is specified when
* generating a message. Some levels correspond directly to cmn_err levels,
* some are associated with increasing levels diagnostic/debug output (LOG1-4),
* and others are associated with specific levels of interface (LOGMAP).
* For _LOG() messages, a __func__ prefix will identify the function origin
* of the message. For _LOG_NF messages, there is no function prefix or
* self/child context. Filtering of messages is provided based on logging
* level, but messages with cmn_err logging level and messages generated
* generated with _LOG_NF() are never filtered.
*
* For debugging, more complete information can be displayed with each message
* (full device path and pointer values) by adjusting scsi_hba_log_info.
*/
/* logging levels */
#define SCSI_HBA_LOGCONT CE_CONT
#define SCSI_HBA_LOGNOTE CE_NOTE
#define SCSI_HBA_LOGWARN CE_WARN
#define SCSI_HBA_LOGPANIC CE_PANIC
#define SCSI_HBA_LOGIGNORE CE_IGNORE
#define SCSI_HBA_LOG_CE_MASK 0x0000000F /* no filter for these levels */
#define SCSI_HBA_LOG1 0x00000010 /* DIAG1 level enable */
#define SCSI_HBA_LOG2 0x00000020 /* DIAG2 level enable */
#define SCSI_HBA_LOG3 0x00000040 /* DIAG3 level enable */
#define SCSI_HBA_LOG4 0x00000080 /* DIAG4 level enable */
#define SCSI_HBA_LOGMAPPHY 0x00000100 /* MAPPHY level enable */
#define SCSI_HBA_LOGMAPIPT 0x00000200 /* MAPIPT level enable */
#define SCSI_HBA_LOGMAPTGT 0x00000400 /* MAPTGT level enable */
#define SCSI_HBA_LOGMAPLUN 0x00000800 /* MAPLUN level enable */
#define SCSI_HBA_LOGMAPCFG 0x00001000 /* MAPCFG level enable */
#define SCSI_HBA_LOGMAPUNCFG 0x00002000 /* MAPUNCFG level enable */
#define SCSI_HBA_LOGTRACE 0x00010000 /* TRACE enable */
#if (CE_CONT | CE_NOTE | CE_WARN | CE_PANIC | CE_IGNORE) > SCSI_HBA_LOG_CE_MASK
Error, problem with CE_ definitions
#endif
/*
* Tunable log message augmentation and filters: filters do not apply to
* SCSI_HBA_LOG_CE_MASK level messages or LOG_NF() messages.
*
* An example set of /etc/system tunings to simplify debug a SCSA pHCI HBA
* driver called "pmcs", including "scsi_vhci" operation, by capturing
* log information in the system log might be:
*
* echo "set scsi:scsi_hba_log_filter_level=0x3ff0" >> /etc/system
* echo "set scsi:scsi_hba_log_filter_phci=\"pmcs\"" >> /etc/system
* echo "set scsi:scsi_hba_log_filter_vhci=\"scsi_vhci\"" >> /etc/system
*
* To capture information on just HBA-SCSAv3 *map operation, use
* echo "set scsi:scsi_hba_log_filter_level=0x3f10" >> /etc/system
*
* For debugging an HBA driver, you may also want to set:
*
* echo "set scsi:scsi_hba_log_align=1" >> /etc/system
* echo "set scsi:scsi_hba_log_mt_disable=0x6" >> /etc/system
* echo "set mtc_off=1" >> /etc/system
* echo "set mdi_mtc_off=1" >> /etc/system
* echo "set scsi:scsi_hba_log_fcif=0" >> /etc/system
*/
int scsi_hba_log_filter_level =
SCSI_HBA_LOG1 |
0;
char *scsi_hba_log_filter_phci = "\0\0\0\0\0\0\0\0\0\0\0\0";
char *scsi_hba_log_filter_vhci = "\0\0\0\0\0\0\0\0\0\0\0\0";
int scsi_hba_log_align = 0; /* NOTE: will not cause truncation */
int scsi_hba_log_fcif = '!'; /* "^!?" first char in format */
/* NOTE: iff level > SCSI_HBA_LOG1 */
/* '\0'0x00 -> console and system log */
/* '^' 0x5e -> console_only */
/* '!' 0x21 -> system log only */
/* '?' 0x2F -> See cmn_err(9F) */
int scsi_hba_log_info = /* augmentation: extra info output */
(0 << 0) | /* 0x0001: process information */
(0 << 1) | /* 0x0002: full /devices path */
(0 << 2); /* 0x0004: devinfo pointer */
int scsi_hba_log_mt_disable =
/* SCSI_ENUMERATION_MT_LUN_DISABLE | (ie 0x02) */
/* SCSI_ENUMERATION_MT_TARGET_DISABLE | (ie 0x04) */
0;
/* static data for HBA logging subsystem */
static kmutex_t scsi_hba_log_mutex;
static char scsi_hba_log_i[512];
static char scsi_hba_log_buf[512];
static char scsi_hba_fmt[512];
/* Macros to use in scsi_hba.c source code below */
#define SCSI_HBA_LOG(x) scsi_hba_log x
#define _LOG(level) SCSI_HBA_LOG##level, __func__
#define _MAP(map) SCSI_HBA_LOGMAP##map, __func__
#define _LOG_NF(level) SCSI_HBA_LOG##level, NULL, NULL, NULL
#define _LOG_TRACE _LOG(TRACE)
#define _LOGLUN _MAP(LUN)
#define _LOGTGT _MAP(TGT)
#define _LOGIPT _MAP(IPT)
#define _LOGPHY _MAP(PHY)
#define _LOGCFG _MAP(CFG)
#define _LOGUNCFG _MAP(UNCFG)
/*PRINTFLIKE5*/
static void
scsi_hba_log(int level, const char *func, dev_info_t *self, dev_info_t *child,
const char *fmt, ...)
{
va_list ap;
int clevel;
int align;
char *info;
char *f;
char *ua;
/* derive self from child's parent */
if ((self == NULL) && child)
self = ddi_get_parent(child);
/* no filtering of SCSI_HBA_LOG_CE_MASK or LOG_NF messages */
if (((level & SCSI_HBA_LOG_CE_MASK) != level) && (func != NULL)) {
/* scsi_hba_log_filter_level: filter on level as bitmask */
if ((level & scsi_hba_log_filter_level) == 0)
return;
/* scsi_hba_log_filter_phci/vhci: on name of driver */
if (*scsi_hba_log_filter_phci &&
((self == NULL) ||
(ddi_driver_name(self) == NULL) ||
strcmp(ddi_driver_name(self), scsi_hba_log_filter_phci))) {
/* does not match pHCI, check vHCI */
if (*scsi_hba_log_filter_vhci &&
((self == NULL) ||
(ddi_driver_name(self) == NULL) ||
strcmp(ddi_driver_name(self),
scsi_hba_log_filter_vhci))) {
/* does not match vHCI */
return;
}
}
/* passed filters, determine align */
align = scsi_hba_log_align;
/* shorten func for filtered output */
if (strncmp(func, "scsi_hba_", 9) == 0)
func += 9;
if (strncmp(func, "scsi_", 5) == 0)
func += 5;
} else {
/* don't align output that is never filtered */
align = 0;
}
/* determine the cmn_err form from the level */
clevel = ((level & SCSI_HBA_LOG_CE_MASK) == level) ? level : CE_CONT;
/* protect common buffers used to format output */
mutex_enter(&scsi_hba_log_mutex);
/* skip special first characters, we add them back below */
f = (char *)fmt;
if (*f && strchr("^!?", *f))
f++;
va_start(ap, fmt);
(void) vsprintf(scsi_hba_log_buf, f, ap);
va_end(ap);
/* augment message with 'information' */
info = scsi_hba_log_i;
*info = '\0';
if ((scsi_hba_log_info & 0x0001) && curproc && PTOU(curproc)->u_comm) {
(void) sprintf(info, "%s[%d]%p ",
PTOU(curproc)->u_comm, curproc->p_pid, (void *)curthread);
info += strlen(info);
}
if (self) {
if ((scsi_hba_log_info & 0x0004) && (child || self)) {
(void) sprintf(info, "%p ",
(void *)(child ? child : self));
info += strlen(info);
}
if (scsi_hba_log_info & 0x0002) {
(void) ddi_pathname(child ? child : self, info);
(void) strcat(info, " ");
info += strlen(info);
}
/* always provide 'default' information about self &child */
(void) sprintf(info, "%s%d ", ddi_driver_name(self),
ddi_get_instance(self));
info += strlen(info);
if (child) {
ua = ddi_get_name_addr(child);
(void) sprintf(info, "%s@%s ",
ddi_node_name(child), (ua && *ua) ? ua : "");
info += strlen(info);
}
}
/* turn off alignment if truncation would occur */
if (align && ((strlen(func) > 18) || (strlen(scsi_hba_log_i) > 36)))
align = 0;
/* adjust for aligned output */
if (align) {
if (func == NULL)
func = "";
/* remove trailing blank with align output */
if ((info != scsi_hba_log_i) && (*(info -1) == '\b'))
*(info - 1) = '\0';
}
/* special "first character in format" must be in format itself */
f = scsi_hba_fmt;
if (fmt[0] && strchr("^!?", fmt[0]))
*f++ = fmt[0];
else if (scsi_hba_log_fcif && (level > SCSI_HBA_LOG1))
*f++ = (char)scsi_hba_log_fcif; /* add global fcif */
if (align)
(void) sprintf(f, "%s", "%-18.18s: %36.36s: %s%s");
else
(void) sprintf(f, "%s", func ? "%s: %s%s%s" : "%s%s%s");
if (func)
cmn_err(clevel, scsi_hba_fmt, func, scsi_hba_log_i,
scsi_hba_log_buf, clevel == CE_CONT ? "\n" : "");
else
cmn_err(clevel, scsi_hba_fmt, scsi_hba_log_i,
scsi_hba_log_buf, clevel == CE_CONT ? "\n" : "");
mutex_exit(&scsi_hba_log_mutex);
}
int scsi_enumeration_failed_panic = 0;
int scsi_enumeration_failed_hotplug = 1;
static void
scsi_enumeration_failed(dev_info_t *child, scsi_enum_t se,
char *arg, char *when)
{
/* If 'se' is -1 the 'se' value comes from child. */
if (se == -1) {
ASSERT(child);
se = ndi_dev_is_hotplug_node(child) ? SE_HP : SE_BUSCONFIG;
}
if (scsi_enumeration_failed_panic) {
/* set scsi_enumeration_failed_panic to debug */
SCSI_HBA_LOG((_LOG(PANIC), NULL, child,
"%s%senumeration failed during %s",
arg ? arg : "", arg ? " " : "", when));
} else if (scsi_enumeration_failed_hotplug && (se == SE_HP)) {
/* set scsi_enumeration_failed_hotplug for console messages */
SCSI_HBA_LOG((_LOG(WARN), NULL, child,
"%s%senumeration failed during %s",
arg ? arg : "", arg ? " " : "", when));
} else {
/* default */
SCSI_HBA_LOG((_LOG(2), NULL, child,
"%s%senumeration failed during %s",
arg ? arg : "", arg ? " " : "", when));
}
}
/*
* scsi_hba version of [nm]di_devi_enter/[nm]di_devi_exit that detects if HBA
* is a PHCI, and chooses mdi/ndi locking implementation.
*/
static void
scsi_hba_devi_enter(dev_info_t *self, int *circp)
{
if (MDI_PHCI(self))
mdi_devi_enter(self, circp);
else
ndi_devi_enter(self, circp);
}
static int
scsi_hba_devi_tryenter(dev_info_t *self, int *circp)
{
if (MDI_PHCI(self))
return (mdi_devi_tryenter(self, circp));
else
return (ndi_devi_tryenter(self, circp));
}
static void
scsi_hba_devi_exit(dev_info_t *self, int circ)
{
if (MDI_PHCI(self))
mdi_devi_exit(self, circ);
else
ndi_devi_exit(self, circ);
}
static void
scsi_hba_devi_enter_phci(dev_info_t *self, int *circp)
{
if (MDI_PHCI(self))
mdi_devi_enter_phci(self, circp);
}
static void
scsi_hba_devi_exit_phci(dev_info_t *self, int circ)
{
if (MDI_PHCI(self))
mdi_devi_exit_phci(self, circ);
}
static int
scsi_hba_dev_is_sid(dev_info_t *child)
{
/*
* Use ndi_dev_is_persistent_node instead of ddi_dev_is_sid to avoid
* any possible locking issues in mixed nexus devctl code (like usb).
*/
return (ndi_dev_is_persistent_node(child));
}
/*
* Called from _init() when loading "scsi" module
*/
void
scsi_initialize_hba_interface()
{
SCSI_HBA_LOG((_LOG_TRACE, NULL, NULL, __func__));
/* We need "scsiprobe" and "scsinodev" as an alias or a driver. */
if (ddi_name_to_major(compatible_probe) == DDI_MAJOR_T_NONE) {
SCSI_HBA_LOG((_LOG_NF(WARN), "failed to resolve '%s' "
"driver alias, defaulting to 'nulldriver'",
compatible_probe));
/* If no "nulldriver" driver nothing will work... */
compatible_probe = "nulldriver";
if (ddi_name_to_major(compatible_probe) == DDI_MAJOR_T_NONE)
SCSI_HBA_LOG((_LOG_NF(WARN), "no probe '%s' driver, "
"system misconfigured", compatible_probe));
}
if (ddi_name_to_major(compatible_nodev) == DDI_MAJOR_T_NONE) {
SCSI_HBA_LOG((_LOG_NF(WARN), "failed to resolve '%s' "
"driver alias, defaulting to 'nulldriver'",
compatible_nodev));
/* If no "nulldriver" driver nothing will work... */
compatible_nodev = "nulldriver";
if (ddi_name_to_major(compatible_nodev) == DDI_MAJOR_T_NONE)
SCSI_HBA_LOG((_LOG_NF(WARN), "no nodev '%s' driver, "
"system misconfigured", compatible_nodev));
}
/*
* Verify our special node name "probe" will not be used in other ways.
* Don't expect things to work if they are.
*/
if (ddi_major_to_name(ddi_name_to_major("probe")))
SCSI_HBA_LOG((_LOG_NF(WARN),
"driver already using special node name 'probe'"));
mutex_init(&scsi_log_mutex, NULL, MUTEX_DRIVER, NULL);
mutex_init(&scsi_flag_nointr_mutex, NULL, MUTEX_DRIVER, NULL);
cv_init(&scsi_flag_nointr_cv, NULL, CV_DRIVER, NULL);
mutex_init(&scsi_hba_log_mutex, NULL, MUTEX_DRIVER, NULL);
/* initialize the asynchronous barrier deletion daemon */
mutex_init(&scsi_hba_barrier_mutex, NULL, MUTEX_DRIVER, NULL);
cv_init(&scsi_hba_barrier_cv, NULL, CV_DRIVER, NULL);
(void) thread_create(NULL, 0,
(void (*)())scsi_hba_barrier_daemon, NULL,
0, &p0, TS_RUN, minclsyspri);
/* initialize lun change ASC/ASCQ processing daemon (stage1 & stage2) */
mutex_init(&scsi_lunchg1_mutex, NULL, MUTEX_DRIVER, NULL);
cv_init(&scsi_lunchg1_cv, NULL, CV_DRIVER, NULL);
(void) thread_create(NULL, 0,
(void (*)())scsi_lunchg1_daemon, NULL,
0, &p0, TS_RUN, minclsyspri);
mutex_init(&scsi_lunchg2_mutex, NULL, MUTEX_DRIVER, NULL);
cv_init(&scsi_lunchg2_cv, NULL, CV_DRIVER, NULL);
(void) thread_create(NULL, 0,
(void (*)())scsi_lunchg2_daemon, NULL,
0, &p0, TS_RUN, minclsyspri);
}
int
scsi_hba_pkt_constructor(void *buf, void *arg, int kmflag)
{
struct scsi_pkt_cache_wrapper *pktw;
struct scsi_pkt *pkt;
scsi_hba_tran_t *tran = (scsi_hba_tran_t *)arg;
int pkt_len;
char *ptr;
/*
* allocate a chunk of memory for the following:
* scsi_pkt
* pcw_* fields
* pkt_ha_private
* pkt_cdbp, if needed
* (pkt_private always null)
* pkt_scbp, if needed
*/
pkt_len = tran->tran_hba_len + sizeof (struct scsi_pkt_cache_wrapper);
if (tran->tran_hba_flags & SCSI_HBA_TRAN_CDB)
pkt_len += DEFAULT_CDBLEN;
if (tran->tran_hba_flags & SCSI_HBA_TRAN_SCB)
pkt_len += DEFAULT_SCBLEN;
bzero(buf, pkt_len);
ptr = buf;
pktw = buf;
ptr += sizeof (struct scsi_pkt_cache_wrapper);
pkt = &(pktw->pcw_pkt);
pkt->pkt_ha_private = (opaque_t)ptr;
pktw->pcw_magic = PKT_WRAPPER_MAGIC; /* alloced correctly */
/*
* keep track of the granularity at the time this handle was
* allocated
*/
pktw->pcw_granular = tran->tran_dma_attr.dma_attr_granular;
if (ddi_dma_alloc_handle(tran->tran_hba_dip, &tran->tran_dma_attr,
kmflag == KM_SLEEP ? SLEEP_FUNC: NULL_FUNC, NULL,
&pkt->pkt_handle) != DDI_SUCCESS) {
return (-1);
}
ptr += tran->tran_hba_len;
if (tran->tran_hba_flags & SCSI_HBA_TRAN_CDB) {
pkt->pkt_cdbp = (opaque_t)ptr;
ptr += DEFAULT_CDBLEN;
}
pkt->pkt_private = NULL;
if (tran->tran_hba_flags & SCSI_HBA_TRAN_SCB)
pkt->pkt_scbp = (opaque_t)ptr;
if (tran->tran_pkt_constructor)
return ((*tran->tran_pkt_constructor)(pkt, arg, kmflag));
else
return (0);
}
#define P_TO_TRAN(pkt) ((pkt)->pkt_address.a_hba_tran)
void
scsi_hba_pkt_destructor(void *buf, void *arg)
{
struct scsi_pkt_cache_wrapper *pktw = buf;
struct scsi_pkt *pkt = &(pktw->pcw_pkt);
scsi_hba_tran_t *tran = (scsi_hba_tran_t *)arg;
ASSERT(pktw->pcw_magic == PKT_WRAPPER_MAGIC);
ASSERT((pktw->pcw_flags & PCW_BOUND) == 0);
if (tran->tran_pkt_destructor)
(*tran->tran_pkt_destructor)(pkt, arg);
/* make sure nobody messed with our pointers */
ASSERT(pkt->pkt_ha_private == (opaque_t)((char *)pkt +
sizeof (struct scsi_pkt_cache_wrapper)));
ASSERT(((tran->tran_hba_flags & SCSI_HBA_TRAN_SCB) == 0) ||
(pkt->pkt_scbp == (opaque_t)((char *)pkt +
tran->tran_hba_len +
(((tran->tran_hba_flags & SCSI_HBA_TRAN_CDB) == 0) ?
0 : DEFAULT_CDBLEN) +
DEFAULT_PRIVLEN + sizeof (struct scsi_pkt_cache_wrapper))));
ASSERT(((tran->tran_hba_flags & SCSI_HBA_TRAN_CDB) == 0) ||
(pkt->pkt_cdbp == (opaque_t)((char *)pkt +
tran->tran_hba_len +
sizeof (struct scsi_pkt_cache_wrapper))));
ASSERT(pkt->pkt_handle);
ddi_dma_free_handle(&pkt->pkt_handle);
pkt->pkt_handle = NULL;
pkt->pkt_numcookies = 0;
pktw->pcw_total_xfer = 0;
pktw->pcw_totalwin = 0;
pktw->pcw_curwin = 0;
}
/*
* Called by an HBA from _init() to plumb in common SCSA bus_ops and
* cb_ops for the HBA's :devctl and :scsi minor nodes.
*/
int
scsi_hba_init(struct modlinkage *modlp)
{
struct dev_ops *hba_dev_ops;
SCSI_HBA_LOG((_LOG_TRACE, NULL, NULL, __func__));
/*
* Get a pointer to the dev_ops structure of the HBA and plumb our
* bus_ops vector into the HBA's dev_ops structure.
*/
hba_dev_ops = ((struct modldrv *)(modlp->ml_linkage[0]))->drv_dev_ops;
ASSERT(hba_dev_ops->devo_bus_ops == NULL);
hba_dev_ops->devo_bus_ops = &scsi_hba_busops;
/*
* Plumb our cb_ops vector into the HBA's dev_ops structure to
* provide getinfo and hotplugging ioctl support if the HBA driver
* does not already provide this support.
*/
if (hba_dev_ops->devo_cb_ops == NULL) {
hba_dev_ops->devo_cb_ops = &scsi_hba_cbops;
}
if (hba_dev_ops->devo_cb_ops->cb_open == scsi_hba_open) {
ASSERT(hba_dev_ops->devo_cb_ops->cb_close == scsi_hba_close);
hba_dev_ops->devo_getinfo = scsi_hba_info;
}
return (0);
}
/*
* Called by an HBA attach(9E) to allocate a scsi_hba_tran(9S) structure. An
* HBA driver will then initialize the structure and then call
* scsi_hba_attach_setup(9F).
*/
/*ARGSUSED*/
scsi_hba_tran_t *
scsi_hba_tran_alloc(
dev_info_t *self,
int flags)
{
scsi_hba_tran_t *tran;
SCSI_HBA_LOG((_LOG_TRACE, self, NULL, __func__));
/* allocate SCSA flavors for self */
ndi_flavorv_alloc(self, SCSA_NFLAVORS);
tran = kmem_zalloc(sizeof (scsi_hba_tran_t),
(flags & SCSI_HBA_CANSLEEP) ? KM_SLEEP : KM_NOSLEEP);
if (tran) {
tran->tran_interconnect_type = INTERCONNECT_PARALLEL;
/*
* HBA driver called scsi_hba_tran_alloc(), so tran structure
* is proper size and unused/newer fields are zero.
*
* NOTE: We use SCSA_HBA_SCSA_TA as an obtuse form of
* versioning to detect old HBA drivers that do not use
* scsi_hba_tran_alloc, and would present garbage data
* (instead of valid/zero data) for newer tran fields.
*/
tran->tran_hba_flags |= SCSI_HBA_SCSA_TA;
}
return (tran);
}
/*
* Called by an HBA to free a scsi_hba_tran structure
*/
void
scsi_hba_tran_free(
scsi_hba_tran_t *tran)
{
SCSI_HBA_LOG((_LOG_TRACE, tran->tran_hba_dip, NULL, __func__));
kmem_free(tran, sizeof (scsi_hba_tran_t));
}
int
scsi_tran_ext_alloc(
scsi_hba_tran_t *tran,
size_t length,
int flags)
{
void *tran_ext;
int ret = DDI_FAILURE;
tran_ext = kmem_zalloc(length,
(flags & SCSI_HBA_CANSLEEP) ? KM_SLEEP : KM_NOSLEEP);
if (tran_ext != NULL) {
tran->tran_extension = tran_ext;
ret = DDI_SUCCESS;
}
return (ret);
}
void
scsi_tran_ext_free(
scsi_hba_tran_t *tran,
size_t length)
{
if (tran->tran_extension != NULL) {
kmem_free(tran->tran_extension, length);
tran->tran_extension = NULL;
}
}
/*
* Common nexus teardown code: used by both scsi_hba_detach() on SCSA HBA node
* and iport_postdetach_tran_scsi_device() on a SCSA HBA iport node (and for
* failure cleanup). Undo scsa_nexus_setup in reverse order.
*
* NOTE: Since we are in the Solaris IO framework, we can depend on
* undocumented cleanup operations performed by other parts of the framework:
* like detach_node() calling ddi_prop_remove_all() and
* ddi_remove_minor_node(,NULL).
*/
static void
scsa_nexus_teardown(dev_info_t *self, scsi_hba_tran_t *tran)
{
/* Teardown FMA. */
if (tran->tran_hba_flags & SCSI_HBA_SCSA_FM) {
ddi_fm_fini(self);
tran->tran_hba_flags &= ~SCSI_HBA_SCSA_FM;
}
}
/*
* Common nexus setup code: used by both scsi_hba_attach_setup() on SCSA HBA
* node and iport_preattach_tran_scsi_device() on a SCSA HBA iport node.
*
* This code makes no assumptions about tran use by scsi_device children.
*/
static int
scsa_nexus_setup(dev_info_t *self, scsi_hba_tran_t *tran)
{
int capable;
int scsa_minor;
/*
* NOTE: SCSA maintains an 'fm-capable' domain, in tran_fm_capable,
* that is not dependent (limited by) the capabilities of its parents.
* For example a devinfo node in a branch that is not
* DDI_FM_EREPORT_CAPABLE may report as capable, via tran_fm_capable,
* to its scsi_device children.
*
* Get 'fm-capable' property from driver.conf, if present. If not
* present, default to the scsi_fm_capable global (which has
* DDI_FM_EREPORT_CAPABLE set by default).
*/
if (tran->tran_fm_capable == DDI_FM_NOT_CAPABLE)
tran->tran_fm_capable = ddi_prop_get_int(DDI_DEV_T_ANY, self,
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
"fm-capable", scsi_fm_capable);
/*
* If an HBA is *not* doing its own fma support by calling
* ddi_fm_init() prior to scsi_hba_attach_setup(), we provide a minimal
* common SCSA implementation so that scsi_device children can generate
* ereports via scsi_fm_ereport_post(). We use ddi_fm_capable() to
* detect an HBA calling ddi_fm_init() prior to scsi_hba_attach_setup().
*/
if (tran->tran_fm_capable &&
(ddi_fm_capable(self) == DDI_FM_NOT_CAPABLE)) {
/*
* We are capable of something, pass our capabilities up the
* tree, but use a local variable so our parent can't limit
* our capabilities (we don't want our parent to clear
* DDI_FM_EREPORT_CAPABLE).
*
* NOTE: iblock cookies are not important because scsi HBAs
* always interrupt below LOCK_LEVEL.
*/
capable = tran->tran_fm_capable;
ddi_fm_init(self, &capable, NULL);
/*
* Set SCSI_HBA_SCSA_FM bit to mark us as using the common
* minimal SCSA fm implementation - we called ddi_fm_init(),
* so we are responsible for calling ddi_fm_fini() in
* scsi_hba_detach().
*
* NOTE: if ddi_fm_init fails to establish handle, SKIP cleanup.
*/
if (DEVI(self)->devi_fmhdl)
tran->tran_hba_flags |= SCSI_HBA_SCSA_FM;
}
/* If SCSA responsible for for minor nodes, create :devctl minor. */
scsa_minor = (ddi_get_driver(self)->devo_cb_ops->cb_open ==
scsi_hba_open) ? 1 : 0;
if (scsa_minor && ((ddi_create_minor_node(self, "devctl", S_IFCHR,
INST2DEVCTL(ddi_get_instance(self)), DDI_NT_SCSI_NEXUS, 0) !=
DDI_SUCCESS))) {
SCSI_HBA_LOG((_LOG(WARN), self, NULL,
"can't create :devctl minor node"));
goto fail;
}
return (DDI_SUCCESS);
fail: scsa_nexus_teardown(self, tran);
return (DDI_FAILURE);
}
/*
* Common tran teardown code: used by iport_postdetach_tran_scsi_device() on a
* SCSA HBA iport node and (possibly) by scsi_hba_detach() on SCSA HBA node
* (and for failure cleanup). Undo scsa_tran_setup in reverse order.
*
* NOTE: Since we are in the Solaris IO framework, we can depend on
* undocumented cleanup operations performed by other parts of the framework:
* like detach_node() calling ddi_prop_remove_all() and
* ddi_remove_minor_node(,NULL).
*/
static void
scsa_tran_teardown(dev_info_t *self, scsi_hba_tran_t *tran)
{
tran->tran_iport_dip = NULL;
/* Teardown pHCI registration */
if (tran->tran_hba_flags & SCSI_HBA_SCSA_PHCI) {
(void) mdi_phci_unregister(self, 0);
tran->tran_hba_flags &= ~SCSI_HBA_SCSA_PHCI;
}
}
/*
* Common tran setup code: used by iport_preattach_tran_scsi_device() on a
* SCSA HBA iport node and (possibly) by scsi_hba_attach_setup() on SCSA HBA
* node.
*/
static int
scsa_tran_setup(dev_info_t *self, scsi_hba_tran_t *tran)
{
int scsa_minor;
int id;
char *scsi_binding_set;
static const char *interconnect[] = INTERCONNECT_TYPE_ASCII;
SCSI_HBA_LOG((_LOG_TRACE, self, NULL, __func__));
/* If SCSA responsible for for minor nodes, create ":scsi" */
scsa_minor = (ddi_get_driver(self)->devo_cb_ops->cb_open ==
scsi_hba_open) ? 1 : 0;
if (scsa_minor && (ddi_create_minor_node(self, "scsi", S_IFCHR,
INST2SCSI(ddi_get_instance(self)),
DDI_NT_SCSI_ATTACHMENT_POINT, 0) != DDI_SUCCESS)) {
SCSI_HBA_LOG((_LOG(WARN), self, NULL,
"can't create :scsi minor node"));
goto fail;
}
/*
* If the property does not already exist on self then see if we can
* pull it from further up the tree and define it on self. If the
* property does not exist above (including options.conf) then use the
* default value specified (global variable). We pull things down from
* above for faster "DDI_PROP_NOTPROM | DDI_PROP_DONTPASS" runtime
* access.
*
* Future: Should we avoid creating properties when value == global?
*/
#define CONFIG_INT_PROP(s, p, dv) { \
if ((ddi_prop_exists(DDI_DEV_T_ANY, s, \
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, p) == 0) && \
(ndi_prop_update_int(DDI_DEV_T_NONE, s, p, \
ddi_prop_get_int(DDI_DEV_T_ANY, ddi_get_parent(s), \
DDI_PROP_NOTPROM, p, dv)) != DDI_PROP_SUCCESS)) \
SCSI_HBA_LOG((_LOG(WARN), NULL, s, \
"can't create property '%s'", p)); \
}
/* Decorate with scsi configuration properties */
CONFIG_INT_PROP(self, "scsi-enumeration", scsi_enumeration);
CONFIG_INT_PROP(self, "scsi-options", scsi_options);
CONFIG_INT_PROP(self, "scsi-reset-delay", scsi_reset_delay);
CONFIG_INT_PROP(self, "scsi-watchdog-tick", scsi_watchdog_tick);
CONFIG_INT_PROP(self, "scsi-selection-timeout", scsi_selection_timeout);
CONFIG_INT_PROP(self, "scsi-tag-age-limit", scsi_tag_age_limit);
/*
* Pull down the scsi-initiator-id from further up the tree, or as
* defined by OBP. Place on node for faster access. NOTE: there is
* some confusion about what the name of the property should be.
*/
id = ddi_prop_get_int(DDI_DEV_T_ANY, self, 0, "initiator-id", -1);
if (id == -1)
id = ddi_prop_get_int(DDI_DEV_T_ANY, self, 0,
"scsi-initiator-id", -1);
if (id != -1)
CONFIG_INT_PROP(self, "scsi-initiator-id", id);
/*
* If we are responsible for tran allocation, establish
* 'initiator-interconnect-type'.
*/
if ((tran->tran_hba_flags & SCSI_HBA_SCSA_TA) &&
(tran->tran_interconnect_type > 0) &&
(tran->tran_interconnect_type < INTERCONNECT_MAX)) {
if (ndi_prop_update_string(DDI_DEV_T_NONE, self,
"initiator-interconnect-type",
(char *)interconnect[tran->tran_interconnect_type])
!= DDI_PROP_SUCCESS) {
SCSI_HBA_LOG((_LOG(WARN), self, NULL,
"failed to establish "
"'initiator-interconnect-type'"));
goto fail;
}
}
/*
* The 'scsi-binding-set' property can be defined in driver.conf
* files of legacy drivers on an as-needed basis. If 'scsi-binding-set'
* is not driver.conf defined, and the HBA is not implementing its own
* private bus_config, we define scsi-binding-set to the default
* 'spi' legacy value.
*
* NOTE: This default 'spi' value will be deleted if an HBA driver
* ends up using the scsi_hba_tgtmap_create() enumeration services.
*
* NOTE: If we were ever to decide to derive 'scsi-binding-set' from
* the IEEE-1275 'device_type' property then this is where that code
* should go - there is not enough consistency in 'device_type' to do
* this correctly at this point in time.
*/
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, self,
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "scsi-binding-set",
&scsi_binding_set) == DDI_PROP_SUCCESS) {
SCSI_HBA_LOG((_LOG(2), NULL, self,
"external 'scsi-binding-set' \"%s\"", scsi_binding_set));
ddi_prop_free(scsi_binding_set);
} else if (scsi_binding_set_spi &&
((tran->tran_bus_config == NULL) ||
(tran->tran_bus_config == scsi_hba_bus_config_spi))) {
if (ndi_prop_update_string(DDI_DEV_T_NONE, self,
"scsi-binding-set", scsi_binding_set_spi) !=
DDI_PROP_SUCCESS) {
SCSI_HBA_LOG((_LOG(WARN), self, NULL,
"failed to establish 'scsi_binding_set' default"));
goto fail;
}
SCSI_HBA_LOG((_LOG(2), NULL, self,
"default 'scsi-binding-set' \"%s\"", scsi_binding_set_spi));
} else
SCSI_HBA_LOG((_LOG(2), NULL, self,
"no 'scsi-binding-set'"));
/*
* If SCSI_HBA_TRAN_PHCI is set, take care of pHCI registration of the
* initiator.
*/
if ((tran->tran_hba_flags & SCSI_HBA_TRAN_PHCI) &&
(mdi_phci_register(MDI_HCI_CLASS_SCSI, self, 0) == MDI_SUCCESS))
tran->tran_hba_flags |= SCSI_HBA_SCSA_PHCI;
/* NOTE: tran_hba_dip is for DMA operation at the HBA node level */
tran->tran_iport_dip = self; /* for iport association */
return (DDI_SUCCESS);
fail: scsa_tran_teardown(self, tran);
return (DDI_FAILURE);
}
/*
* Called by a SCSA HBA driver to attach an instance of the driver to
* SCSA HBA node enumerated by PCI.
*/
int
scsi_hba_attach_setup(
dev_info_t *self,
ddi_dma_attr_t *hba_dma_attr,
scsi_hba_tran_t *tran,
int flags)
{
int len;
char cache_name[96];
SCSI_HBA_LOG((_LOG_TRACE, self, NULL, __func__));
/*
* Verify that we are a driver so other code does not need to
* check for NULL ddi_get_driver() result.
*/
if (ddi_get_driver(self) == NULL)
return (DDI_FAILURE);
/*
* Verify that we are called on a SCSA HBA node (function enumerated
* by PCI), not on an iport node.
*/
ASSERT(scsi_hba_iport_unit_address(self) == NULL);
if (scsi_hba_iport_unit_address(self))
return (DDI_FAILURE); /* self can't be an iport */
/* Caller must provide the tran. */
ASSERT(tran);
if (tran == NULL)
return (DDI_FAILURE);
/*
* Verify correct scsi_hba_tran_t form:
*
* o Both or none of tran_get_name/tran_get_addr.
* NOTE: Older SCSA HBA drivers for SCSI transports with addressing
* that did not fit the SPI "struct scsi_address" model were required
* to implement tran_get_name and tran_get_addr. This is no longer
* true - modern transport drivers should now use common SCSA
* enumeration services. The SCSA enumeration code will represent
* the unit-address using well-known address properties
* (SCSI_ADDR_PROP_TARGET_PORT, SCSI_ADDR_PROP_LUN64) during
* devinfo/pathinfo node creation. The HBA driver can obtain values
* using scsi_device_prop_lookup_*() from its tran_tgt_init(9E).
*
*/
if ((tran->tran_get_name == NULL) ^ (tran->tran_get_bus_addr == NULL)) {
SCSI_HBA_LOG((_LOG(WARN), self, NULL,
"should support both or neither: "
"tran_get_name, tran_get_bus_addr"));
return (DDI_FAILURE);
}
/*
* Establish the devinfo context of this tran structure, preserving
* knowledge of how the tran was allocated.
*/
tran->tran_hba_dip = self; /* for DMA */
tran->tran_hba_flags = (flags & ~SCSI_HBA_SCSA_TA) |
(tran->tran_hba_flags & SCSI_HBA_SCSA_TA);
/* Establish flavor of transport (and ddi_get_driver_private()) */
ndi_flavorv_set(self, SCSA_FLAVOR_SCSI_DEVICE, tran);
/*
* Note: We only need dma_attr_minxfer and dma_attr_burstsizes
* from the DMA attributes. scsi_hba_attach(9f) only guarantees
* that these two fields are initialized properly. If this
* changes, be sure to revisit the implementation of
* scsi_hba_attach(9F).
*/
(void) memcpy(&tran->tran_dma_attr, hba_dma_attr,
sizeof (ddi_dma_attr_t));
/* Create tran_setup_pkt(9E) kmem_cache. */
if (tran->tran_setup_pkt) {
ASSERT(tran->tran_init_pkt == NULL);
ASSERT(tran->tran_destroy_pkt == NULL);
if (tran->tran_init_pkt || tran->tran_destroy_pkt)
goto fail;
tran->tran_init_pkt = scsi_init_cache_pkt;
tran->tran_destroy_pkt = scsi_free_cache_pkt;
tran->tran_sync_pkt = scsi_sync_cache_pkt;
tran->tran_dmafree = scsi_cache_dmafree;
len = sizeof (struct scsi_pkt_cache_wrapper);
len += ROUNDUP(tran->tran_hba_len);
if (tran->tran_hba_flags & SCSI_HBA_TRAN_CDB)
len += ROUNDUP(DEFAULT_CDBLEN);
if (tran->tran_hba_flags & SCSI_HBA_TRAN_SCB)
len += ROUNDUP(DEFAULT_SCBLEN);
(void) snprintf(cache_name, sizeof (cache_name),
"pkt_cache_%s_%d", ddi_driver_name(self),
ddi_get_instance(self));
tran->tran_pkt_cache_ptr = kmem_cache_create(
cache_name, len, 8, scsi_hba_pkt_constructor,
scsi_hba_pkt_destructor, NULL, tran, NULL, 0);
}
/* Perform node setup independent of initiator role */
if (scsa_nexus_setup(self, tran) != DDI_SUCCESS)
goto fail;
/*
* The SCSI_HBA_HBA flag is passed to scsi_hba_attach_setup when the
* HBA driver knows that *all* children of the SCSA HBA node will be
* 'iports'. If the SCSA HBA node can have iport children and also
* function as an initiator for xxx_device children then it should
* not specify SCSI_HBA_HBA in its scsi_hba_attach_setup call. An
* HBA driver that does not manage iports should not set SCSA_HBA_HBA.
*/
if (tran->tran_hba_flags & SCSI_HBA_HBA) {
/*
* Set the 'ddi-config-driver-node' property on the nexus
* node that notify attach_driver_nodes() to configure all
* immediate children so that nodes which bind to the
* same driver as parent are able to be added into per-driver
* list.
*/
if (ndi_prop_create_boolean(DDI_DEV_T_NONE,
self, "ddi-config-driver-node") != DDI_PROP_SUCCESS)
goto fail;
} else {
if (scsa_tran_setup(self, tran) != DDI_SUCCESS)
goto fail;
}
return (DDI_SUCCESS);
fail: (void) scsi_hba_detach(self);
return (DDI_FAILURE);
}
/*
* Called by an HBA to detach an instance of the driver. This may be called
* for SCSA HBA nodes and for SCSA iport nodes.
*/
int
scsi_hba_detach(dev_info_t *self)
{
scsi_hba_tran_t *tran;
ASSERT(scsi_hba_iport_unit_address(self) == NULL);
if (scsi_hba_iport_unit_address(self))
return (DDI_FAILURE); /* self can't be an iport */
/* Check all error return conditions upfront */
tran = ndi_flavorv_get(self, SCSA_FLAVOR_SCSI_DEVICE);
ASSERT(tran);
if (tran == NULL)
return (DDI_FAILURE);
ASSERT(tran->tran_open_flag == 0);
if (tran->tran_open_flag)
return (DDI_FAILURE);
if (!(tran->tran_hba_flags & SCSI_HBA_HBA))
scsa_tran_teardown(self, tran);
scsa_nexus_teardown(self, tran);
/* Teardown tran_setup_pkt(9E) kmem_cache. */
if (tran->tran_pkt_cache_ptr) {
kmem_cache_destroy(tran->tran_pkt_cache_ptr);
tran->tran_pkt_cache_ptr = NULL;
}
(void) memset(&tran->tran_dma_attr, 0, sizeof (ddi_dma_attr_t));
/* Teardown flavor of transport (and ddi_get_driver_private()) */
ndi_flavorv_set(self, SCSA_FLAVOR_SCSI_DEVICE, NULL);
tran->tran_hba_dip = NULL;
return (DDI_SUCCESS);
}
/*
* Called by an HBA from _fini()
*/
void
scsi_hba_fini(struct modlinkage *modlp)
{
struct dev_ops *hba_dev_ops;
SCSI_HBA_LOG((_LOG_TRACE, NULL, NULL, __func__));
/* Get the devops structure of this module and clear bus_ops vector. */
hba_dev_ops = ((struct modldrv *)(modlp->ml_linkage[0]))->drv_dev_ops;
if (hba_dev_ops->devo_cb_ops == &scsi_hba_cbops)
hba_dev_ops->devo_cb_ops = NULL;
if (hba_dev_ops->devo_getinfo == scsi_hba_info)
hba_dev_ops->devo_getinfo = NULL;
hba_dev_ops->devo_bus_ops = (struct bus_ops *)NULL;
}
/*
* SAS specific functions
*/
smp_hba_tran_t *
smp_hba_tran_alloc(dev_info_t *self)
{
/* allocate SCSA flavors for self */
ndi_flavorv_alloc(self, SCSA_NFLAVORS);
return (kmem_zalloc(sizeof (smp_hba_tran_t), KM_SLEEP));
}
void
smp_hba_tran_free(smp_hba_tran_t *tran)
{
kmem_free(tran, sizeof (smp_hba_tran_t));
}
int
smp_hba_attach_setup(
dev_info_t *self,
smp_hba_tran_t *tran)
{
ASSERT(scsi_hba_iport_unit_address(self) == NULL);
if (scsi_hba_iport_unit_address(self))
return (DDI_FAILURE); /* self can't be an iport */
/*
* The owner of the this devinfo_t was responsible
* for informing the framework already about
* additional flavors.
*/
ndi_flavorv_set(self, SCSA_FLAVOR_SMP, tran);
return (DDI_SUCCESS);
}
int
smp_hba_detach(dev_info_t *self)
{
ASSERT(scsi_hba_iport_unit_address(self) == NULL);
if (scsi_hba_iport_unit_address(self))
return (DDI_FAILURE); /* self can't be an iport */
ndi_flavorv_set(self, SCSA_FLAVOR_SMP, NULL);
return (DDI_SUCCESS);
}
/*
* SMP child flavored functions
*/
static int
smp_busctl_ua(dev_info_t *child, char *addr, int maxlen)
{
char *tport;
char *wwn;
/* limit ndi_devi_findchild_by_callback to expected flavor */
if (ndi_flavor_get(child) != SCSA_FLAVOR_SMP)
return (DDI_FAILURE);
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, child,
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
SCSI_ADDR_PROP_TARGET_PORT, &tport) == DDI_SUCCESS) {
(void) snprintf(addr, maxlen, "%s", tport);
ddi_prop_free(tport);
return (DDI_SUCCESS);
}
/*
* NOTE: the following code should be deleted when mpt is changed to
* use SCSI_ADDR_PROP_TARGET_PORT instead of SMP_WWN.
*/
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, child,
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
SMP_WWN, &wwn) == DDI_SUCCESS) {
(void) snprintf(addr, maxlen, "w%s", wwn);
ddi_prop_free(wwn);
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
static int
smp_busctl_reportdev(dev_info_t *child)
{
dev_info_t *self = ddi_get_parent(child);
char *tport;
char *wwn;
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, child,
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
SCSI_ADDR_PROP_TARGET_PORT, &tport) == DDI_SUCCESS) {
SCSI_HBA_LOG((_LOG_NF(CONT), "?%s%d at %s%d: target-port %s",
ddi_driver_name(child), ddi_get_instance(child),
ddi_driver_name(self), ddi_get_instance(self), tport));
ddi_prop_free(tport);
return (DDI_SUCCESS);
}
/*
* NOTE: the following code should be deleted when mpt is changed to
* use SCSI_ADDR_PROP_TARGET_PORT instead of SMP_WWN.
*/
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, child,
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
SMP_WWN, &wwn) == DDI_SUCCESS) {
SCSI_HBA_LOG((_LOG_NF(CONT), "?%s%d at %s%d: wwn %s",
ddi_driver_name(child), ddi_get_instance(child),
ddi_driver_name(self), ddi_get_instance(self), wwn));
ddi_prop_free(wwn);
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
static int
smp_busctl_initchild(dev_info_t *child)
{
dev_info_t *self = ddi_get_parent(child);
smp_hba_tran_t *tran;
dev_info_t *dup;
char addr[SCSI_MAXNAMELEN];
struct smp_device *smp_sd;
uint64_t wwn;
tran = ndi_flavorv_get(self, SCSA_FLAVOR_SMP);
ASSERT(tran);
if (tran == NULL)
return (DDI_FAILURE);
if (smp_busctl_ua(child, addr, sizeof (addr)) != DDI_SUCCESS)
return (DDI_NOT_WELL_FORMED);
if (scsi_wwnstr_to_wwn(addr, &wwn))
return (DDI_NOT_WELL_FORMED);
/* Prevent duplicate nodes. */
dup = ndi_devi_findchild_by_callback(self, ddi_node_name(child), addr,
smp_busctl_ua);
if (dup) {
ASSERT(ndi_flavor_get(dup) == SCSA_FLAVOR_SMP);
if (ndi_flavor_get(dup) != SCSA_FLAVOR_SMP) {
SCSI_HBA_LOG((_LOG(1), NULL, child,
"init failed: %s@%s: not SMP flavored",
ddi_node_name(child), addr));
return (DDI_FAILURE);
}
if (dup != child) {
SCSI_HBA_LOG((_LOG(4), NULL, child,
"init failed: %s@%s: detected duplicate %p",
ddi_node_name(child), addr, (void *)dup));
return (DDI_FAILURE);
}
}
/* set the node @addr string */
ddi_set_name_addr(child, addr);
/* Allocate and initialize smp_device. */
smp_sd = kmem_zalloc(sizeof (struct smp_device), KM_SLEEP);
smp_sd->smp_sd_dev = child;
smp_sd->smp_sd_address.smp_a_hba_tran = tran;
bcopy(&wwn, smp_sd->smp_sd_address.smp_a_wwn, SAS_WWN_BYTE_SIZE);
ddi_set_driver_private(child, smp_sd);
if (tran->smp_tran_init && ((*tran->smp_tran_init)(self, child,
tran, smp_sd) != DDI_SUCCESS)) {
kmem_free(smp_sd, sizeof (struct smp_device));
scsi_enumeration_failed(child, -1, NULL, "smp_tran_init");
ddi_set_driver_private(child, NULL);
ddi_set_name_addr(child, NULL);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
smp_busctl_uninitchild(dev_info_t *child)
{
dev_info_t *self = ddi_get_parent(child);
struct smp_device *smp_sd = ddi_get_driver_private(child);
smp_hba_tran_t *tran;
tran = ndi_flavorv_get(self, SCSA_FLAVOR_SMP);
ASSERT(smp_sd && tran);
if ((smp_sd == NULL) || (tran == NULL))
return (DDI_FAILURE);
if (tran->smp_tran_free)
(*tran->smp_tran_free) (self, child, tran, smp_sd);
kmem_free(smp_sd, sizeof (*smp_sd));
ddi_set_driver_private(child, NULL);
ddi_set_name_addr(child, NULL);
return (DDI_SUCCESS);
}
/* Find an "smp" child at the specified address. */
static dev_info_t *
smp_findchild(dev_info_t *self, char *addr)
{
dev_info_t *child;
/* Search "smp" devinfo child at specified address. */
ASSERT(self && DEVI_BUSY_OWNED(self) && addr);
for (child = ddi_get_child(self); child;
child = ddi_get_next_sibling(child)) {
/* skip non-"smp" nodes */
if (ndi_flavor_get(child) != SCSA_FLAVOR_SMP)
continue;
/* Attempt initchild to establish unit-address */
if (i_ddi_node_state(child) < DS_INITIALIZED)
(void) ddi_initchild(self, child);
/* Verify state and non-NULL unit-address. */
if ((i_ddi_node_state(child) < DS_INITIALIZED) ||
(ddi_get_name_addr(child) == NULL))
continue;
/* Return "smp" child if unit-address matches. */
if (strcmp(ddi_get_name_addr(child), addr) == 0)
return (child);
}
return (NULL);
}
/*
* Search for "smp" child of self at the specified address. If found, online
* and return with a hold. Unlike general SCSI configuration, we can assume
* the the device is actually there when we are called (i.e., device is
* created by hotplug, not by bus_config).
*/
int
smp_hba_bus_config(dev_info_t *self, char *addr, dev_info_t **childp)
{
dev_info_t *child;
int circ;
ASSERT(self && addr && childp);
*childp = NULL;
/* Search for "smp" child. */
scsi_hba_devi_enter(self, &circ);
if ((child = smp_findchild(self, addr)) == NULL) {
scsi_hba_devi_exit(self, circ);
return (NDI_FAILURE);
}
/* Attempt online. */
if (ndi_devi_online(child, 0) != NDI_SUCCESS) {
scsi_hba_devi_exit(self, circ);
return (NDI_FAILURE);
}
/* On success, return with active hold. */
ndi_hold_devi(child);
scsi_hba_devi_exit(self, circ);
*childp = child;
return (NDI_SUCCESS);
}
/* Create "smp" child devinfo node at specified unit-address. */
int
smp_hba_bus_config_taddr(dev_info_t *self, char *addr)
{
dev_info_t *child;
int circ;
/*
* NOTE: If we ever uses a generic node name (.vs. a driver name)
* or define a 'compatible' property, this code will need to use
* a 'probe' node (ala scsi_device support) to obtain identity
* information from the device.
*/
/* Search for "smp" child. */
scsi_hba_devi_enter(self, &circ);
child = smp_findchild(self, addr);
if (child) {
/* Child exists, note if this was a new reinsert. */
if (ndi_devi_device_insert(child))
SCSI_HBA_LOG((_LOGCFG, self, NULL,
"devinfo smp@%s device_reinsert", addr));
scsi_hba_devi_exit(self, circ);
return (NDI_SUCCESS);
}
/* Allocate "smp" child devinfo node and establish flavor of child. */
ndi_devi_alloc_sleep(self, "smp", DEVI_SID_HP_NODEID, &child);
ASSERT(child);
ndi_flavor_set(child, SCSA_FLAVOR_SMP);
/* Add unit-address property to child. */
if (ndi_prop_update_string(DDI_DEV_T_NONE, child,
SCSI_ADDR_PROP_TARGET_PORT, addr) != DDI_PROP_SUCCESS) {
(void) ndi_devi_free(child);
scsi_hba_devi_exit(self, circ);
return (NDI_FAILURE);
}
/* Attempt to online the new "smp" node. */
(void) ndi_devi_online(child, 0);
scsi_hba_devi_exit(self, circ);
return (NDI_SUCCESS);
}
/*
* Wrapper to scsi_ua_get which takes a devinfo argument instead of a
* scsi_device structure.
*/
static int
scsi_busctl_ua(dev_info_t *child, char *addr, int maxlen)
{
struct scsi_device *sd;
/* limit ndi_devi_findchild_by_callback to expected flavor */
if (ndi_flavor_get(child) != SCSA_FLAVOR_SCSI_DEVICE)
return (DDI_FAILURE);
/* nodes are named by tran_get_name or default "tgt,lun" */
sd = ddi_get_driver_private(child);
if (sd && (scsi_ua_get(sd, addr, maxlen) == 1))
return (DDI_SUCCESS);
return (DDI_FAILURE);
}
static int
scsi_busctl_reportdev(dev_info_t *child)
{
dev_info_t *self = ddi_get_parent(child);
struct scsi_device *sd = ddi_get_driver_private(child);
scsi_hba_tran_t *tran;
char ua[SCSI_MAXNAMELEN];
char ra[SCSI_MAXNAMELEN];
SCSI_HBA_LOG((_LOG_TRACE, NULL, child, __func__));
tran = ndi_flavorv_get(self, SCSA_FLAVOR_SCSI_DEVICE);
ASSERT(tran && sd);
if ((tran == NULL) || (sd == NULL))
return (DDI_FAILURE);
/* get the unit_address and bus_addr information */
if ((scsi_ua_get(sd, ua, sizeof (ua)) == 0) ||
(scsi_ua_get_reportdev(sd, ra, sizeof (ra)) == 0)) {
SCSI_HBA_LOG((_LOG(WARN), NULL, child, "REPORTDEV failure"));
return (DDI_FAILURE);
}
if (tran->tran_get_name == NULL)
SCSI_HBA_LOG((_LOG_NF(CONT), "?%s%d at %s%d: %s",
ddi_driver_name(child), ddi_get_instance(child),
ddi_driver_name(self), ddi_get_instance(self), ra));
else if (*ra)
SCSI_HBA_LOG((_LOG_NF(CONT),
"?%s%d at %s%d: unit-address %s: %s",
ddi_driver_name(child), ddi_get_instance(child),
ddi_driver_name(self), ddi_get_instance(self), ua, ra));
else
SCSI_HBA_LOG((_LOG_NF(CONT),
"?%s%d at %s%d: unit-address %s",
ddi_driver_name(child), ddi_get_instance(child),
ddi_driver_name(self), ddi_get_instance(self), ua));
return (DDI_SUCCESS);
}
/*
* scsi_busctl_initchild is called to initialize the SCSA transport for
* communication with a particular child scsi target device. Successful
* initialization requires properties on the node which describe the address
* of the target device. If the address of the target device can't be
* determined from properties then DDI_NOT_WELL_FORMED is returned. Nodes that
* are DDI_NOT_WELL_FORMED are considered an implementation artifact and
* are hidden from devinfo snapshots by calling ndi_devi_set_hidden().
* The child may be one of the following types of devinfo nodes:
*
* OBP node:
* OBP does not enumerate target devices attached a SCSI bus. These
* template/stub/wild-card nodes are a legacy artifact for support of old
* driver loading methods. Since they have no properties,
* DDI_NOT_WELL_FORMED will be returned.
*
* SID node:
* The node may be either a:
* o probe/barrier SID node
* o a dynamic SID target node
*
* driver.conf node: The situation for this nexus is different than most.
* Typically a driver.conf node definition is used to either define a
* new child devinfo node or to further decorate (via merge) a SID
* child with properties. In our case we use the nodes for *both*
* purposes.
*
* In both the SID node and driver.conf node cases we must form the nodes
* "@addr" from the well-known scsi(9P) device unit-address properties on
* the node.
*
* For HBA drivers that implement the deprecated tran_get_name interface,
* "@addr" construction involves having that driver interpret properties via
* scsi_busctl_ua -> scsi_ua_get -> tran_get_name: there is no
* requirement for the property names to be well-known.
*
* NOTE: We don't currently support "merge". When this support is added a
* specific property, like "unit-address", should *always* identify a
* driver.conf node that needs to be merged into a specific SID node. When
* enumeration is enabled, a .conf node without the "unit-address" property
* should be ignored. The best way to establish the "unit-address" property
* would be to have the system assign parent= and unit-address= from an
* instance=# driver.conf entry (by using the instance tree).
*/
static int
scsi_busctl_initchild(dev_info_t *child)
{
dev_info_t *self = ddi_get_parent(child);
dev_info_t *dup;
scsi_hba_tran_t *tran;
struct scsi_device *sd;
scsi_hba_tran_t *tran_clone;
char *class;
int tgt;
int lun;
int sfunc;
int err = DDI_FAILURE;
char addr[SCSI_MAXNAMELEN];
ASSERT(DEVI_BUSY_OWNED(self));
SCSI_HBA_LOG((_LOG(4), NULL, child, "init begin"));
/*
* For a driver like fp with multiple upper-layer-protocols
* it is possible for scsi_hba_init in _init to plumb SCSA
* and have the load of fcp (which does scsi_hba_attach_setup)
* to fail. In this case we may get here with a NULL hba.
*/
tran = ndi_flavorv_get(self, SCSA_FLAVOR_SCSI_DEVICE);
if (tran == NULL)
return (DDI_NOT_WELL_FORMED);
/*
* OBP may create template/stub/wild-card nodes for legacy driver
* loading methods. These nodes have no properties, so we lack the
* addressing properties to initchild them. Hide the node and return
* DDI_NOT_WELL_FORMED.
*
* Future: define/use a ndi_devi_has_properties(dip) type interface.
*
* NOTE: It would be nice if we could delete these ill formed nodes by
* implementing a DDI_NOT_WELL_FORMED_DELETE return code. This can't
* be done until leadville debug code removes its dependencies
* on the devinfo still being present after a failed ndi_devi_online.
*/
if ((DEVI(child)->devi_hw_prop_ptr == NULL) &&
(DEVI(child)->devi_drv_prop_ptr == NULL) &&
(DEVI(child)->devi_sys_prop_ptr == NULL)) {
SCSI_HBA_LOG((_LOG(4), NULL, child,
"init failed: no properties"));
ndi_devi_set_hidden(child);
return (DDI_NOT_WELL_FORMED);
}
/* get legacy SPI addressing properties */
if ((tgt = ddi_prop_get_int(DDI_DEV_T_ANY, child,
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
SCSI_ADDR_PROP_TARGET, -1)) == -1) {
tgt = 0;
/*
* A driver.conf node for merging always has a target= property,
* even if it is just a dummy that does not contain the real
* target address. However drivers that register devids may
* create stub driver.conf nodes without a target= property so
* that pathological devid resolution works. Hide the stub
* node and return DDI_NOT_WELL_FORMED.
*/
if (!scsi_hba_dev_is_sid(child)) {
SCSI_HBA_LOG((_LOG(4), NULL, child,
"init failed: stub .conf node"));
ndi_devi_set_hidden(child);
return (DDI_NOT_WELL_FORMED);
}
}
lun = ddi_prop_get_int(DDI_DEV_T_ANY, child,
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, SCSI_ADDR_PROP_LUN, 0);
sfunc = ddi_prop_get_int(DDI_DEV_T_ANY, child,
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, SCSI_ADDR_PROP_SFUNC, -1);
/*
* The scsi_address structure may not specify all the addressing
* information. For an old HBA that doesn't support tran_get_name
* (most pre-SCSI-3 HBAs) the scsi_address structure is still used,
* so the target property must exist and the LUN must be < 256.
*/
if ((tran->tran_get_name == NULL) &&
((tgt >= USHRT_MAX) || (lun >= 256))) {
SCSI_HBA_LOG((_LOG(1), NULL, child,
"init failed: illegal/missing properties"));
ndi_devi_set_hidden(child);
return (DDI_NOT_WELL_FORMED);
}
/*
* We need to initialize a fair amount of our environment to invoke
* tran_get_name (via scsi_busctl_ua and scsi_ua_get) to
* produce the "@addr" name from addressing properties. Allocate and
* initialize scsi device structure.
*/
sd = kmem_zalloc(sizeof (struct scsi_device), KM_SLEEP);
mutex_init(&sd->sd_mutex, NULL, MUTEX_DRIVER, NULL);
sd->sd_dev = child;
sd->sd_pathinfo = NULL;
sd->sd_uninit_prevent = 0;
ddi_set_driver_private(child, sd);
if (tran->tran_hba_flags & SCSI_HBA_ADDR_COMPLEX) {
/*
* For a SCSI_HBA_ADDR_COMPLEX transport we store a pointer to
* scsi_device in the scsi_address structure. This allows an
* HBA driver to find its per-scsi_device private data
* (accessible to the HBA given just the scsi_address by using
* scsi_address_device(9F)/scsi_device_hba_private_get(9F)).
*/
sd->sd_address.a.a_sd = sd;
tran_clone = NULL;
} else {
/*
* Initialize the scsi_address so that a SCSI-2 target driver
* talking to a SCSI-2 device on a SCSI-3 bus (spi) continues
* to work. We skew the secondary function value so that we
* can tell from the address structure if we are processing
* a secondary function request.
*/
sd->sd_address.a_target = (ushort_t)tgt;
sd->sd_address.a_lun = (uchar_t)lun;
if (sfunc == -1)
sd->sd_address.a_sublun = (uchar_t)0;
else
sd->sd_address.a_sublun = (uchar_t)sfunc + 1;
/*
* NOTE: Don't limit LUNs to scsi_options value because a
* scsi_device discovered via SPI dynamic enumeration might
* still support SCMD_REPORT_LUNS.
*/
/*
* Deprecated: Use SCSI_HBA_ADDR_COMPLEX:
* Clone transport structure if requested. Cloning allows
* an HBA to maintain target-specific information if
* necessary, such as target addressing information that
* does not adhere to the scsi_address structure format.
*/
if (tran->tran_hba_flags & SCSI_HBA_TRAN_CLONE) {
tran_clone = kmem_alloc(
sizeof (scsi_hba_tran_t), KM_SLEEP);
bcopy((caddr_t)tran,
(caddr_t)tran_clone, sizeof (scsi_hba_tran_t));
tran = tran_clone;
tran->tran_sd = sd;
} else {
tran_clone = NULL;
ASSERT(tran->tran_sd == NULL);
}
}
/* establish scsi_address pointer to the HBA's tran structure */
sd->sd_address.a_hba_tran = tran;
/*
* This is a grotty hack that allows direct-access (non-scsa) drivers
* (like chs, ata, and mlx which all make cmdk children) to put its
* own vector in the 'a_hba_tran' field. When all the drivers that do
* this are fixed, please remove this hack.
*
* NOTE: This hack is also shows up in the DEVP_TO_TRAN implementation
* in scsi_confsubr.c.
*/
sd->sd_tran_safe = tran;
/*
* If the class property is not already established, set it to "scsi".
* This is done so that parent= driver.conf nodes have class.
*/
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, child,
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "class",
&class) == DDI_PROP_SUCCESS) {
ddi_prop_free(class);
} else if (ndi_prop_update_string(DDI_DEV_T_NONE, child,
"class", "scsi") != DDI_PROP_SUCCESS) {
SCSI_HBA_LOG((_LOG(2), NULL, child, "init failed: class"));
ndi_devi_set_hidden(child);
err = DDI_NOT_WELL_FORMED;
goto failure;
}
/* Establish the @addr name of the child. */
*addr = '\0';
if (scsi_busctl_ua(child, addr, sizeof (addr)) != DDI_SUCCESS) {
/*
* Some driver.conf files add bogus target properties (relative
* to their nexus representation of target) to their stub
* nodes, causing the check above to not filter them.
*/
SCSI_HBA_LOG((_LOG(3), NULL, child,
"init failed: scsi_busctl_ua call"));
ndi_devi_set_hidden(child);
err = DDI_NOT_WELL_FORMED;
goto failure;
}
if (*addr == '\0') {
SCSI_HBA_LOG((_LOG(2), NULL, child, "init failed: ua"));
ndi_devi_set_hidden(child);
err = DDI_NOT_WELL_FORMED;
goto failure;
}
/* Prevent duplicate nodes. */
dup = ndi_devi_findchild_by_callback(self, ddi_node_name(child), addr,
scsi_busctl_ua);
if (dup) {
ASSERT(ndi_flavor_get(dup) == SCSA_FLAVOR_SCSI_DEVICE);
if (ndi_flavor_get(dup) != SCSA_FLAVOR_SCSI_DEVICE) {
SCSI_HBA_LOG((_LOG(1), NULL, child,
"init failed: %s@%s: not SCSI_DEVICE flavored",
ddi_node_name(child), addr));
goto failure;
}
if (dup != child) {
SCSI_HBA_LOG((_LOG(4), NULL, child,
"init failed: %s@%s: detected duplicate %p",
ddi_node_name(child), addr, (void *)dup));
goto failure;
}
}
/* set the node @addr string */
ddi_set_name_addr(child, addr);
/* call HBA's target init entry point if it exists */
if (tran->tran_tgt_init != NULL) {
SCSI_HBA_LOG((_LOG(4), NULL, child, "init tran_tgt_init"));
sd->sd_tran_tgt_free_done = 0;
if ((*tran->tran_tgt_init)
(self, child, tran, sd) != DDI_SUCCESS) {
scsi_enumeration_failed(child, -1, NULL,
"tran_tgt_init");
goto failure;
}
}
SCSI_HBA_LOG((_LOG(3), NULL, child, "init successful"));
return (DDI_SUCCESS);
failure:
if (tran_clone)
kmem_free(tran_clone, sizeof (scsi_hba_tran_t));
mutex_destroy(&sd->sd_mutex);
kmem_free(sd, sizeof (*sd));
ddi_set_driver_private(child, NULL);
ddi_set_name_addr(child, NULL);
return (err); /* remove the node */
}
static int
scsi_busctl_uninitchild(dev_info_t *child)
{
dev_info_t *self = ddi_get_parent(child);
struct scsi_device *sd = ddi_get_driver_private(child);
scsi_hba_tran_t *tran;
scsi_hba_tran_t *tran_clone;
ASSERT(DEVI_BUSY_OWNED(self));
tran = ndi_flavorv_get(self, SCSA_FLAVOR_SCSI_DEVICE);
ASSERT(tran && sd);
if ((tran == NULL) || (sd == NULL))
return (DDI_FAILURE);
/*
* We use sd_uninit_prevent to avoid uninitializing barrier/probe
* nodes that are still in use. Since barrier/probe nodes are not
* attached we can't prevent their state demotion via ndi_hold_devi.
*/
if (sd->sd_uninit_prevent) {
SCSI_HBA_LOG((_LOG(2), NULL, child, "uninit prevented"));
return (DDI_FAILURE);
}
/*
* Don't uninitialize a client node if it still has paths.
*/
if (MDI_CLIENT(child) && mdi_client_get_path_count(child)) {
SCSI_HBA_LOG((_LOG(2), NULL, child,
"uninit prevented, client has paths"));
return (DDI_FAILURE);
}
SCSI_HBA_LOG((_LOG(3), NULL, child, "uninit begin"));
if (tran->tran_hba_flags & SCSI_HBA_TRAN_CLONE) {
tran_clone = sd->sd_address.a_hba_tran;
/* ... grotty hack, involving sd_tran_safe, continued. */
if (tran_clone != sd->sd_tran_safe) {
tran_clone = sd->sd_tran_safe;
#ifdef DEBUG
/*
* Complain so things get fixed and hack can, at
* some point in time, be removed.
*/
SCSI_HBA_LOG((_LOG(WARN), self, NULL,
"'%s' is corrupting a_hba_tran", sd->sd_dev ?
ddi_driver_name(sd->sd_dev) : "unknown_driver"));
#endif /* DEBUG */
}
ASSERT(tran_clone->tran_hba_flags & SCSI_HBA_TRAN_CLONE);
ASSERT(tran_clone->tran_sd == sd);
tran = tran_clone;
} else {
tran_clone = NULL;
ASSERT(tran->tran_sd == NULL);
}
/*
* To simplify host adapter drivers we guarantee that multiple
* tran_tgt_init(9E) calls of the same unit address are never
* active at the same time. This requires that we always call
* tran_tgt_free on probe/barrier nodes directly prior to
* uninitchild.
*
* NOTE: To correctly support SCSI_HBA_TRAN_CLONE, we must use
* the (possibly cloned) hba_tran pointer from the scsi_device
* instead of hba_tran.
*/
if (tran->tran_tgt_free) {
if (!sd->sd_tran_tgt_free_done) {
SCSI_HBA_LOG((_LOG(4), NULL, child,
"uninit tran_tgt_free"));
(*tran->tran_tgt_free) (self, child, tran, sd);
sd->sd_tran_tgt_free_done = 1;
} else {
SCSI_HBA_LOG((_LOG(4), NULL, child,
"uninit tran_tgt_free already done"));
}
}
/*
* If a inquiry data is still allocated (by scsi_probe()) we
* free the allocation here. This keeps scsi_inq valid for the
* same duration as the corresponding inquiry properties. It
* also allows a tran_tgt_init() implementation that establishes
* sd_inq to deal with deallocation in its tran_tgt_free
* (setting sd_inq back to NULL) without upsetting the
* framework. Moving the inquiry free here also allows setting
* of sd_uninit_prevent to preserve the data for lun0 based
* scsi_get_device_type_scsi_options() calls.
*/
if (sd->sd_inq) {
kmem_free(sd->sd_inq, SUN_INQSIZE);
sd->sd_inq = (struct scsi_inquiry *)NULL;
}
mutex_destroy(&sd->sd_mutex);
if (tran_clone)
kmem_free(tran_clone, sizeof (scsi_hba_tran_t));
kmem_free(sd, sizeof (*sd));
ddi_set_driver_private(child, NULL);
SCSI_HBA_LOG((_LOG(3), NULL, child, "uninit complete"));
ddi_set_name_addr(child, NULL);
return (DDI_SUCCESS);
}
static int
iport_busctl_ua(dev_info_t *child, char *addr, int maxlen)
{
char *iport_ua;
/* limit ndi_devi_findchild_by_callback to expected flavor */
if (ndi_flavor_get(child) != SCSA_FLAVOR_IPORT)
return (DDI_FAILURE);
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, child,
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
SCSI_ADDR_PROP_IPORTUA, &iport_ua) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
(void) snprintf(addr, maxlen, "%s", iport_ua);
ddi_prop_free(iport_ua);
return (DDI_SUCCESS);
}
static int
iport_busctl_reportdev(dev_info_t *child)
{
dev_info_t *self = ddi_get_parent(child);
char *iport_ua;
char *initiator_port = NULL;
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, child,
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
SCSI_ADDR_PROP_IPORTUA, &iport_ua) != DDI_SUCCESS)
return (DDI_FAILURE);
(void) ddi_prop_lookup_string(DDI_DEV_T_ANY, child,
DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
SCSI_ADDR_PROP_INITIATOR_PORT, &initiator_port);
if (initiator_port) {
SCSI_HBA_LOG((_LOG_NF(CONT),
"?%s%d at %s%d: %s %s %s %s",
ddi_driver_name(child), ddi_get_instance(child),
ddi_driver_name(self), ddi_get_instance(self),
SCSI_ADDR_PROP_INITIATOR_PORT, initiator_port,
SCSI_ADDR_PROP_IPORTUA, iport_ua));
ddi_prop_free(initiator_port);
} else {
SCSI_HBA_LOG((_LOG_NF(CONT), "?%s%d at %s%d: %s %s",
ddi_driver_name(child), ddi_get_instance(child),
ddi_driver_name(self), ddi_get_instance(self),
SCSI_ADDR_PROP_IPORTUA, iport_ua));
}
ddi_prop_free(iport_ua);
return (DDI_SUCCESS);
}
/* initchild SCSA iport 'child' node */
static int
iport_busctl_initchild(dev_info_t *child)
{
dev_info_t *self = ddi_get_parent(child);
dev_info_t *dup = NULL;
char addr[SCSI_MAXNAMELEN];
if (iport_busctl_ua(child, addr, sizeof (addr)) != DDI_SUCCESS)
return (DDI_NOT_WELL_FORMED);
/* Prevent duplicate nodes. */
dup = ndi_devi_findchild_by_callback(self, ddi_node_name(child), addr,
iport_busctl_ua);
if (dup) {
ASSERT(ndi_flavor_get(dup) == SCSA_FLAVOR_IPORT);
if (ndi_flavor_get(dup) != SCSA_FLAVOR_IPORT) {
SCSI_HBA_LOG((_LOG(1), NULL, child,
"init failed: %s@%s: not IPORT flavored",
ddi_node_name(child), addr));
return (DDI_FAILURE);
}
if (dup != child) {
SCSI_HBA_LOG((_LOG(4), NULL, child,
"init failed: %s@%s: detected duplicate %p",
ddi_node_name(child), addr, (void *)dup));
return (DDI_FAILURE);
}
}
/* set the node @addr string */
ddi_set_name_addr(child, addr);
return (DDI_SUCCESS);
}
/* uninitchild SCSA iport 'child' node */
static int
iport_busctl_uninitchild(dev_info_t *child)
{
ddi_set_name_addr(child, NULL);
return (DDI_SUCCESS);
}
/* Uninitialize scsi_device flavor of transport on SCSA iport 'child' node. */
static void
iport_postdetach_tran_scsi_device(dev_info_t *child)
{
scsi_hba_tran_t *tran;
tran = ndi_flavorv_get(child, SCSA_FLAVOR_SCSI_DEVICE);
if (tran == NULL)
return;
scsa_tran_teardown(child, tran);
scsa_nexus_teardown(child, tran);
ndi_flavorv_set(child, SCSA_FLAVOR_SCSI_DEVICE, NULL);
scsi_hba_tran_free(tran);
}
/* Initialize scsi_device flavor of transport on SCSA iport 'child' node. */
static void
iport_preattach_tran_scsi_device(dev_info_t *child)
{
dev_info_t *hba = ddi_get_parent(child);
scsi_hba_tran_t *htran;
scsi_hba_tran_t *tran;
/* parent HBA node scsi_device tran is required */
htran = ndi_flavorv_get(hba, SCSA_FLAVOR_SCSI_DEVICE);
ASSERT(htran);
/* Allocate iport child's scsi_device transport vector */
tran = scsi_hba_tran_alloc(child, SCSI_HBA_CANSLEEP);
ASSERT(tran);
/* Structure-copy scsi_device transport of HBA to iport. */
*tran = *htran;
/*
* Reset scsi_device transport fields not shared with the
* parent, and not established below.
*/
tran->tran_open_flag = 0;
tran->tran_hba_private = NULL;
/* Establish the devinfo context of this tran structure. */
tran->tran_iport_dip = child;
/* Clear SCSI_HBA_SCSA flags (except TA) */
tran->tran_hba_flags &=
~(SCSI_HBA_SCSA_FM | SCSI_HBA_SCSA_PHCI); /* clear parent state */
tran->tran_hba_flags |= SCSI_HBA_SCSA_TA; /* always TA */
tran->tran_hba_flags &= ~SCSI_HBA_HBA; /* never HBA */
/* Establish flavor of transport (and ddi_get_driver_private()) */
ndi_flavorv_set(child, SCSA_FLAVOR_SCSI_DEVICE, tran);
/* Setup iport node */
if ((scsa_nexus_setup(child, tran) != DDI_SUCCESS) ||
(scsa_tran_setup(child, tran) != DDI_SUCCESS))
iport_postdetach_tran_scsi_device(child);
}
/* Uninitialize smp_device flavor of transport on SCSA iport 'child' node. */
static void
iport_postdetach_tran_smp_device(dev_info_t *child)
{
smp_hba_tran_t *tran;
tran = ndi_flavorv_get(child, SCSA_FLAVOR_SMP);
if (tran == NULL)
return;
ndi_flavorv_set(child, SCSA_FLAVOR_SMP, NULL);
smp_hba_tran_free(tran);
}
/* Initialize smp_device flavor of transport on SCSA iport 'child' node. */
static void
iport_preattach_tran_smp_device(dev_info_t *child)
{
dev_info_t *hba = ddi_get_parent(child);
smp_hba_tran_t *htran;
smp_hba_tran_t *tran;
/* parent HBA node smp_device tran is optional */
htran = ndi_flavorv_get(hba, SCSA_FLAVOR_SMP);
if (htran == NULL) {
ndi_flavorv_set(child, SCSA_FLAVOR_SMP, NULL);
return;
}
/* Allocate iport child's smp_device transport vector */
tran = smp_hba_tran_alloc(child);
/* Structure-copy smp_device transport of HBA to iport. */
*tran = *htran;
/* Establish flavor of transport */
ndi_flavorv_set(child, SCSA_FLAVOR_SMP, tran);
}
/*
* Generic bus_ctl operations for SCSI HBA's,
* hiding the busctl interface from the HBA.
*/
/*ARGSUSED*/
static int
scsi_hba_bus_ctl(
dev_info_t *self,
dev_info_t *child,
ddi_ctl_enum_t op,
void *arg,
void *result)
{
int child_flavor = 0;
int val;
ddi_dma_attr_t *attr;
scsi_hba_tran_t *tran;
struct attachspec *as;
struct detachspec *ds;
/* For some ops, child is 'arg'. */
if ((op == DDI_CTLOPS_INITCHILD) || (op == DDI_CTLOPS_UNINITCHILD))
child = (dev_info_t *)arg;
/* Determine the flavor of the child: scsi, smp, iport */
child_flavor = ndi_flavor_get(child);
switch (op) {
case DDI_CTLOPS_INITCHILD:
switch (child_flavor) {
case SCSA_FLAVOR_SCSI_DEVICE:
return (scsi_busctl_initchild(child));
case SCSA_FLAVOR_SMP:
return (smp_busctl_initchild(child));
case SCSA_FLAVOR_IPORT:
return (iport_busctl_initchild(child));
default:
return (DDI_FAILURE);
}
/* NOTREACHED */
case DDI_CTLOPS_UNINITCHILD:
switch (child_flavor) {
case SCSA_FLAVOR_SCSI_DEVICE:
return (scsi_busctl_uninitchild(child));
case SCSA_FLAVOR_SMP:
return (smp_busctl_uninitchild(child));
case SCSA_FLAVOR_IPORT:
return (iport_busctl_uninitchild(child));
default:
return (DDI_FAILURE);
}
/* NOTREACHED */
case DDI_CTLOPS_REPORTDEV:
switch (child_flavor) {
case SCSA_FLAVOR_SCSI_DEVICE:
return (scsi_busctl_reportdev(child));
case SCSA_FLAVOR_SMP:
return (smp_busctl_reportdev(child));
case SCSA_FLAVOR_IPORT:
return (iport_busctl_reportdev(child));
default:
return (DDI_FAILURE);
}
/* NOTREACHED */
case DDI_CTLOPS_ATTACH:
as = (struct attachspec *)arg;
if (child_flavor != SCSA_FLAVOR_IPORT)
return (DDI_SUCCESS);
/* iport processing */
if (as->when == DDI_PRE) {
/* setup pre attach(9E) */
iport_preattach_tran_scsi_device(child);
iport_preattach_tran_smp_device(child);
} else if ((as->when == DDI_POST) &&
(as->result != DDI_SUCCESS)) {
/* cleanup if attach(9E) failed */
iport_postdetach_tran_scsi_device(child);
iport_postdetach_tran_smp_device(child);
}
return (DDI_SUCCESS);
case DDI_CTLOPS_DETACH:
ds = (struct detachspec *)arg;
if (child_flavor != SCSA_FLAVOR_IPORT)
return (DDI_SUCCESS);
/* iport processing */
if ((ds->when == DDI_POST) &&
(ds->result == DDI_SUCCESS)) {
/* cleanup if detach(9E) was successful */
iport_postdetach_tran_scsi_device(child);
iport_postdetach_tran_smp_device(child);
}
return (DDI_SUCCESS);
case DDI_CTLOPS_IOMIN:
tran = ddi_get_driver_private(self);
ASSERT(tran);
if (tran == NULL)
return (DDI_FAILURE);
/*
* The 'arg' value of nonzero indicates 'streaming'
* mode. If in streaming mode, pick the largest
* of our burstsizes available and say that that
* is our minimum value (modulo what minxfer is).
*/
attr = &tran->tran_dma_attr;
val = *((int *)result);
val = maxbit(val, attr->dma_attr_minxfer);
*((int *)result) = maxbit(val, ((intptr_t)arg ?
(1<<ddi_ffs(attr->dma_attr_burstsizes)-1) :
(1<<(ddi_fls(attr->dma_attr_burstsizes)-1))));
return (ddi_ctlops(self, child, op, arg, result));
case DDI_CTLOPS_SIDDEV:
return (ndi_dev_is_persistent_node(child) ?
DDI_SUCCESS : DDI_FAILURE);
case DDI_CTLOPS_POWER:
return (DDI_SUCCESS);
/*
* These ops correspond to functions that "shouldn't" be called
* by a SCSI target driver. So we whine when we're called.
*/
case DDI_CTLOPS_DMAPMAPC:
case DDI_CTLOPS_REPORTINT:
case DDI_CTLOPS_REGSIZE:
case DDI_CTLOPS_NREGS:
case DDI_CTLOPS_SLAVEONLY:
case DDI_CTLOPS_AFFINITY:
case DDI_CTLOPS_POKE:
case DDI_CTLOPS_PEEK:
SCSI_HBA_LOG((_LOG(WARN), self, NULL, "invalid op (%d)", op));
return (DDI_FAILURE);
/* Everything else we pass up */
case DDI_CTLOPS_PTOB:
case DDI_CTLOPS_BTOP:
case DDI_CTLOPS_BTOPR:
case DDI_CTLOPS_DVMAPAGESIZE:
default:
return (ddi_ctlops(self, child, op, arg, result));
}
/* NOTREACHED */
}
/*
* Private wrapper for scsi_pkt's allocated via scsi_hba_pkt_alloc()
*/
struct scsi_pkt_wrapper {
struct scsi_pkt scsi_pkt;
int pkt_wrapper_magic;
int pkt_wrapper_len;
};
#if !defined(lint)
_NOTE(SCHEME_PROTECTS_DATA("unique per thread", scsi_pkt_wrapper))
_NOTE(SCHEME_PROTECTS_DATA("Unshared Data", dev_ops))
#endif
/*
* Called by an HBA to allocate a scsi_pkt
*/
/*ARGSUSED*/
struct scsi_pkt *
scsi_hba_pkt_alloc(
dev_info_t *self,
struct scsi_address *ap,
int cmdlen,
int statuslen,
int tgtlen,
int hbalen,
int (*callback)(caddr_t arg),
caddr_t arg)
{
struct scsi_pkt *pkt;
struct scsi_pkt_wrapper *hba_pkt;
caddr_t p;
int acmdlen, astatuslen, atgtlen, ahbalen;
int pktlen;
/* Sanity check */
if (callback != SLEEP_FUNC && callback != NULL_FUNC)
SCSI_HBA_LOG((_LOG(WARN), self, NULL,
"callback must be SLEEP_FUNC or NULL_FUNC"));
/*
* Round up so everything gets allocated on long-word boundaries
*/
acmdlen = ROUNDUP(cmdlen);
astatuslen = ROUNDUP(statuslen);
atgtlen = ROUNDUP(tgtlen);
ahbalen = ROUNDUP(hbalen);
pktlen = sizeof (struct scsi_pkt_wrapper) +
acmdlen + astatuslen + atgtlen + ahbalen;
hba_pkt = kmem_zalloc(pktlen,
(callback == SLEEP_FUNC) ? KM_SLEEP : KM_NOSLEEP);
if (hba_pkt == NULL) {
ASSERT(callback == NULL_FUNC);
return (NULL);
}
/*
* Set up our private info on this pkt
*/
hba_pkt->pkt_wrapper_len = pktlen;
hba_pkt->pkt_wrapper_magic = PKT_WRAPPER_MAGIC; /* alloced correctly */
pkt = &hba_pkt->scsi_pkt;
/*
* Set up pointers to private data areas, cdb, and status.
*/
p = (caddr_t)(hba_pkt + 1);
if (hbalen > 0) {
pkt->pkt_ha_private = (opaque_t)p;
p += ahbalen;
}
if (tgtlen > 0) {
pkt->pkt_private = (opaque_t)p;
p += atgtlen;
}
if (statuslen > 0) {
pkt->pkt_scbp = (uchar_t *)p;
p += astatuslen;
}
if (cmdlen > 0) {
pkt->pkt_cdbp = (uchar_t *)p;
}
/*
* Initialize the pkt's scsi_address
*/
pkt->pkt_address = *ap;
/*
* NB: It may not be safe for drivers, esp target drivers, to depend
* on the following fields being set until all the scsi_pkt
* allocation violations discussed in scsi_pkt.h are all resolved.
*/
pkt->pkt_cdblen = cmdlen;
pkt->pkt_tgtlen = tgtlen;
pkt->pkt_scblen = statuslen;
return (pkt);
}
/*
* Called by an HBA to free a scsi_pkt
*/
/*ARGSUSED*/
void
scsi_hba_pkt_free(
struct scsi_address *ap,
struct scsi_pkt *pkt)
{
kmem_free(pkt, ((struct scsi_pkt_wrapper *)pkt)->pkt_wrapper_len);
}
/*
* Return 1 if the scsi_pkt used a proper allocator.
*
* The DDI does not allow a driver to allocate it's own scsi_pkt(9S), a
* driver should not have *any* compiled in dependencies on "sizeof (struct
* scsi_pkt)". While this has been the case for many years, a number of
* drivers have still not been fixed. This function can be used to detect
* improperly allocated scsi_pkt structures, and produce messages identifying
* drivers that need to be fixed.
*
* While drivers in violation are being fixed, this function can also
* be used by the framework to detect packets that violated allocation
* rules.
*
* NB: It is possible, but very unlikely, for this code to return a false
* positive (finding correct magic, but for wrong reasons). Careful
* consideration is needed for callers using this interface to condition
* access to newer scsi_pkt fields (those after pkt_reason).
*
* NB: As an aid to minimizing the amount of work involved in 'fixing' legacy
* drivers that violate scsi_*(9S) allocation rules, private
* scsi_pkt_size()/scsi_size_clean() functions are available (see their
* implementation for details).
*
* *** Non-legacy use of scsi_pkt_size() is discouraged. ***
*
* NB: When supporting broken HBA drivers is not longer a concern, this
* code should be removed.
*/
int
scsi_pkt_allocated_correctly(struct scsi_pkt *pkt)
{
struct scsi_pkt_wrapper *hba_pkt = (struct scsi_pkt_wrapper *)pkt;
int magic;
major_t major;
#ifdef DEBUG
int *pspwm, *pspcwm;
/*
* We are getting scsi packets from two 'correct' wrapper schemes,
* make sure we are looking at the same place in both to detect
* proper allocation.
*/
pspwm = &((struct scsi_pkt_wrapper *)0)->pkt_wrapper_magic;
pspcwm = &((struct scsi_pkt_cache_wrapper *)0)->pcw_magic;
ASSERT(pspwm == pspcwm);
#endif /* DEBUG */
/*
* Check to see if driver is scsi_size_clean(), assume it
* is using the scsi_pkt_size() interface everywhere it needs to
* if the driver indicates it is scsi_size_clean().
*/
major = ddi_driver_major(P_TO_TRAN(pkt)->tran_hba_dip);
if (devnamesp[major].dn_flags & DN_SCSI_SIZE_CLEAN)
return (1); /* ok */
/*
* Special case crossing a page boundary. If the scsi_pkt was not
* allocated correctly, then across a page boundary we have a
* fault hazard.
*/
if ((((uintptr_t)(&hba_pkt->scsi_pkt)) & MMU_PAGEMASK) ==
(((uintptr_t)(&hba_pkt->pkt_wrapper_magic)) & MMU_PAGEMASK)) {
/* fastpath, no cross-page hazard */
magic = hba_pkt->pkt_wrapper_magic;
} else {
/* add protection for cross-page hazard */
if (ddi_peek32((dev_info_t *)NULL,
&hba_pkt->pkt_wrapper_magic, &magic) == DDI_FAILURE) {
return (0); /* violation */
}
}
/* properly allocated packet always has correct magic */
return ((magic == PKT_WRAPPER_MAGIC) ? 1 : 0);
}
/*
* Private interfaces to simplify conversion of legacy drivers so they don't
* depend on scsi_*(9S) size. Instead of using these private interface, HBA
* drivers should use DDI sanctioned allocation methods:
*
* scsi_pkt Use scsi_hba_pkt_alloc(9F), or implement
* tran_setup_pkt(9E).
*
* scsi_device You are doing something strange/special, a scsi_device
* structure should only be allocated by scsi_hba.c
* initchild code or scsi_vhci.c code.
*
* scsi_hba_tran Use scsi_hba_tran_alloc(9F).
*/
size_t
scsi_pkt_size()
{
return (sizeof (struct scsi_pkt));
}
size_t
scsi_hba_tran_size()
{
return (sizeof (scsi_hba_tran_t));
}
size_t
scsi_device_size()
{
return (sizeof (struct scsi_device));
}
/*
* Legacy compliance to scsi_pkt(9S) allocation rules through use of
* scsi_pkt_size() is detected by the 'scsi-size-clean' driver.conf property
* or an HBA driver calling to scsi_size_clean() from attach(9E). A driver
* developer should only indicate that a legacy driver is clean after using
* SCSI_SIZE_CLEAN_VERIFY to ensure compliance (see scsi_pkt.h).
*/
void
scsi_size_clean(dev_info_t *self)
{
major_t major;
struct devnames *dnp;
ASSERT(self);
major = ddi_driver_major(self);
ASSERT(major < devcnt);
if (major >= devcnt) {
SCSI_HBA_LOG((_LOG(WARN), self, NULL,
"scsi_pkt_size: bogus major: %d", major));
return;
}
/* Set DN_SCSI_SIZE_CLEAN flag in dn_flags. */
dnp = &devnamesp[major];
if ((dnp->dn_flags & DN_SCSI_SIZE_CLEAN) == 0) {
LOCK_DEV_OPS(&dnp->dn_lock);
dnp->dn_flags |= DN_SCSI_SIZE_CLEAN;
UNLOCK_DEV_OPS(&dnp->dn_lock);
}
}
/*
* Called by an HBA to map strings to capability indices
*/
int
scsi_hba_lookup_capstr(
char *capstr)
{
/*
* Capability strings: only add entries to mask the legacy
* '_' vs. '-' misery. All new capabilities should use '-',
* and be captured be added to SCSI_CAP_ASCII.
*/
static struct cap_strings {
char *cap_string;
int cap_index;
} cap_strings[] = {
{ "dma_max", SCSI_CAP_DMA_MAX },
{ "msg_out", SCSI_CAP_MSG_OUT },
{ "wide_xfer", SCSI_CAP_WIDE_XFER },
{ NULL, 0 }
};
static char *cap_ascii[]