| /* |
| * 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[] |