summaryrefslogtreecommitdiff
path: root/usr/src/cmd
diff options
context:
space:
mode:
authorsethg <none@none>2006-05-19 20:53:18 -0700
committersethg <none@none>2006-05-19 20:53:18 -0700
commit724365f7556fc4201fdb11766ebc6bd918523130 (patch)
treed8151f209d5f1c1b7a9624c63ccb85dcfa13d1d9 /usr/src/cmd
parenta66004590c61514e788e33c934c7fe7c0ec4c1d4 (diff)
downloadillumos-gate-724365f7556fc4201fdb11766ebc6bd918523130.tar.gz
PSARC 2006/221 /dev/bmc interface upgrade
PSARC 2006/322 FMA for Sun Fire X4500 SATA Disks 6414182 bmc_intf.h should be shareable with ON 6416735 Sun Fire X4500 systems need a disk diagnosis engine 6419218 Need a way of enumerating sata disks on the Sun Fire X4500 6419771 memory leak from sata.so.1`setup_for_devctl_cmd+0x20 6421449 fmtopo failure paths do not clean up properly 6421627 use after free in topo_tree_destroy causes unpredictable behavior 6421650 topo_modhash_load does not observe proper locking; deadlock ensues 6421659 topo modules' use of global variables causes unpredictable behavior during concurrent usage 6422221 race to populate _topo_* globals causes libumem to abort process 6423975 cfgadm sata plugin wastes tons of time using libdevinfo 6424520 fmd should remove faulty resources from the R$ when they are removed 6424523 hc scheme should do more homework before pronouncing a resource present 6425600 fmd can deadlock when aux threads call fmd_xprt_post at fmd shutdown time 6426514 topo_fmri_compare compares the same scheme leading to erroneous compare results
Diffstat (limited to 'usr/src/cmd')
-rw-r--r--usr/src/cmd/fm/dicts/DISK.dict33
-rw-r--r--usr/src/cmd/fm/dicts/DISK.po75
-rw-r--r--usr/src/cmd/fm/dicts/Makefile1
-rw-r--r--usr/src/cmd/fm/fmd/common/fmd_dr.c42
-rw-r--r--usr/src/cmd/fm/fmd/common/fmd_xprt.c16
-rw-r--r--usr/src/cmd/fm/fmtopo/common/fmtopo.c3
-rw-r--r--usr/src/cmd/fm/modules/Makefile2
-rw-r--r--usr/src/cmd/fm/modules/i86pc/Makefile29
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/Makefile38
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/diskmon_conf.c926
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/diskmon_conf.h330
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/dm_plugin.h81
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/dm_types.h76
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fault_analyze.c1701
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fault_analyze.h154
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fault_mgr.c261
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fault_mgr.h48
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fm_disk_events.h70
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/hotplug_mgr.c638
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/hotplug_mgr.h54
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/ipmi_plugin.c1743
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/ipmi_plugin.h52
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/plugin_mgr.c476
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/plugin_mgr.h78
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/schg_mgr.c714
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/schg_mgr.h56
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/scsi_util.c1840
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/scsi_util.h400
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/sfx4500-disk.c508
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/sfx4500-disk.conf32
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/sfx4500-disk.h56
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/topo_gather.c921
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/topo_gather.h56
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/util.c420
-rw-r--r--usr/src/cmd/fm/modules/i86pc/sfx4500-disk/util.h119
-rw-r--r--usr/src/cmd/fm/schemes/hc/amd64/Makefile4
-rw-r--r--usr/src/cmd/fm/schemes/hc/i386/Makefile4
-rw-r--r--usr/src/cmd/fm/schemes/hc/scheme.c149
-rw-r--r--usr/src/cmd/fm/schemes/hc/sparc/Makefile4
-rw-r--r--usr/src/cmd/fm/schemes/hc/sparcv9/Makefile4
40 files changed, 12196 insertions, 18 deletions
diff --git a/usr/src/cmd/fm/dicts/DISK.dict b/usr/src/cmd/fm/dicts/DISK.dict
new file mode 100644
index 0000000000..8c760cb238
--- /dev/null
+++ b/usr/src/cmd/fm/dicts/DISK.dict
@@ -0,0 +1,33 @@
+#
+# Copyright 2006 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# 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
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+# DO NOT EDIT -- this file is generated by the Event Registry.
+#
+
+FMDICT: name=DISK version=1 maxkey=1 dictid=0x534b
+
+fault.io.disk.predictive-failure=0
+fault.io.disk.over-temperature=1
+fault.io.disk.self-test-failure=2
diff --git a/usr/src/cmd/fm/dicts/DISK.po b/usr/src/cmd/fm/dicts/DISK.po
new file mode 100644
index 0000000000..33c7b22af7
--- /dev/null
+++ b/usr/src/cmd/fm/dicts/DISK.po
@@ -0,0 +1,75 @@
+#
+# Copyright 2006 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# 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
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+# DO NOT EDIT -- this file is generated by the Event Registry.
+#
+#
+# code: DISK-8000-0X
+# keys: fault.io.disk.predictive-failure
+#
+msgid "DISK-8000-0X.type"
+msgstr "Fault"
+msgid "DISK-8000-0X.severity"
+msgstr "Major"
+msgid "DISK-8000-0X.description"
+msgstr "SMART health-monitoring firmware reported that a disk\nfailure is imminent.\n Refer to %s for more information."
+msgid "DISK-8000-0X.response"
+msgstr "None.\n"
+msgid "DISK-8000-0X.impact"
+msgstr "It is likely that the continued operation of\nthis disk will result in data loss.\n"
+msgid "DISK-8000-0X.action"
+msgstr "Schedule a repair procedure to replace the affected disk.\nUse fmdump -v -u <EVENT_ID> to identify the disk.\n"
+#
+# code: DISK-8000-12
+# keys: fault.io.disk.over-temperature
+#
+msgid "DISK-8000-12.type"
+msgstr "Fault"
+msgid "DISK-8000-12.severity"
+msgstr "Major"
+msgid "DISK-8000-12.description"
+msgstr "A disk's temperature exceeded the limits established by\nits manufacturer.\n Refer to %s for more information."
+msgid "DISK-8000-12.response"
+msgstr "None.\n"
+msgid "DISK-8000-12.impact"
+msgstr "Performance degradation is likely and continued disk operation\nbeyond the temperature threshold can result in disk\ndamage and potential data loss.\n"
+msgid "DISK-8000-12.action"
+msgstr "Ensure that the system is properly cooled, that all fans are\nfunctional, and that there are no obstructions of airflow to the affected\ndisk.\n"
+#
+# code: DISK-8000-2J
+# keys: fault.io.disk.self-test-failure
+#
+msgid "DISK-8000-2J.type"
+msgstr "Fault"
+msgid "DISK-8000-2J.severity"
+msgstr "Critical"
+msgid "DISK-8000-2J.description"
+msgstr "One or more disk self tests failed.\n Refer to %s for more information."
+msgid "DISK-8000-2J.response"
+msgstr "None.\n"
+msgid "DISK-8000-2J.impact"
+msgstr "The disk has failed.\n"
+msgid "DISK-8000-2J.action"
+msgstr "Schedule a repair procedure to replace the affected disk.\nUse fmdump -v -u <EVENT_ID> to identify the disk.\n"
diff --git a/usr/src/cmd/fm/dicts/Makefile b/usr/src/cmd/fm/dicts/Makefile
index 1123af80d9..95d2610925 100644
--- a/usr/src/cmd/fm/dicts/Makefile
+++ b/usr/src/cmd/fm/dicts/Makefile
@@ -27,6 +27,7 @@
include ../../Makefile.cmd
common_DCNAMES = \
+ DISK \
FMD \
SMF \
SUNOS \
diff --git a/usr/src/cmd/fm/fmd/common/fmd_dr.c b/usr/src/cmd/fm/fmd/common/fmd_dr.c
index 85d054ceb7..d5fc48e23f 100644
--- a/usr/src/cmd/fm/fmd/common/fmd_dr.c
+++ b/usr/src/cmd/fm/fmd/common/fmd_dr.c
@@ -2,9 +2,8 @@
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License"). You may not use this file except in compliance
- * with the License.
+ * 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.
@@ -20,7 +19,7 @@
* CDDL HEADER END
*/
/*
- * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
@@ -55,15 +54,50 @@
#undef RW_READ_HELD
#undef RW_WRITE_HELD
+#include <fmd_asru.h>
#include <fmd_error.h>
+#include <fmd_fmri.h>
#include <fmd_subr.h>
#include <fmd.h>
static void
+fmd_dr_repair_containee(fmd_asru_t *ee, void *er)
+{
+ if ((ee->asru_flags & FMD_ASRU_FAULTY) &&
+ fmd_fmri_contains(er, ee->asru_fmri) > 0)
+ (void) fmd_asru_clrflags(ee, FMD_ASRU_FAULTY, NULL, NULL);
+}
+
+/*ARGSUSED*/
+static void
+fmd_dr_rcache_sync(fmd_asru_t *ap, void *arg)
+{
+ if (fmd_fmri_present(ap->asru_fmri) != 0)
+ return;
+
+ if (!fmd_asru_clrflags(ap, FMD_ASRU_FAULTY, NULL, NULL))
+ return;
+
+ /*
+ * We've located the requested ASRU, and have repaired it. Now
+ * traverse the ASRU cache, looking for any faulty entries that
+ * are contained by this one. If we find any, repair them too.
+ */
+ fmd_asru_hash_apply(fmd.d_asrus, fmd_dr_repair_containee,
+ ap->asru_fmri);
+}
+
+static void
fmd_dr_event(sysevent_t *sep)
{
uint64_t gen;
+ /*
+ * If the event target is in the R$ and this sysevent indicates it was
+ * removed, remove it from the R$ also.
+ */
+ (void) fmd_asru_hash_apply(fmd.d_asrus, fmd_dr_rcache_sync, NULL);
+
(void) pthread_mutex_lock(&fmd.d_stats_lock);
gen = fmd.d_stats->ds_dr_gen.fmds_value.ui64++;
(void) pthread_mutex_unlock(&fmd.d_stats_lock);
diff --git a/usr/src/cmd/fm/fmd/common/fmd_xprt.c b/usr/src/cmd/fm/fmd/common/fmd_xprt.c
index a6e7594a4e..72e4d944a9 100644
--- a/usr/src/cmd/fm/fmd/common/fmd_xprt.c
+++ b/usr/src/cmd/fm/fmd/common/fmd_xprt.c
@@ -2,9 +2,8 @@
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License"). You may not use this file except in compliance
- * with the License.
+ * 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.
@@ -21,7 +20,7 @@
*/
/*
- * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
@@ -1020,8 +1019,15 @@ fmd_xprt_recv(fmd_xprt_t *xp, nvlist_t *nvl, hrtime_t hrt)
*/
(void) pthread_mutex_lock(&xip->xi_lock);
- while (xip->xi_flags & (FMD_XPRT_DSUSPENDED | FMD_XPRT_ISUSPENDED))
+ while (xip->xi_flags & (FMD_XPRT_DSUSPENDED | FMD_XPRT_ISUSPENDED)) {
+
+ if (fmd.d_signal != 0) {
+ (void) pthread_mutex_unlock(&xip->xi_lock);
+ return; /* fmd_destroy() is in progress */
+ }
+
(void) pthread_cond_wait(&xip->xi_cv, &xip->xi_lock);
+ }
xip->xi_busy++;
ASSERT(xip->xi_busy != 0);
diff --git a/usr/src/cmd/fm/fmtopo/common/fmtopo.c b/usr/src/cmd/fm/fmtopo/common/fmtopo.c
index 22c58b7649..63c614f9ab 100644
--- a/usr/src/cmd/fm/fmtopo/common/fmtopo.c
+++ b/usr/src/cmd/fm/fmtopo/common/fmtopo.c
@@ -287,6 +287,7 @@ main(int argc, char *argv[])
" %s\n", g_pname, opt_s, topo_strerror(err));
topo_hdl_strfree(thp, uuid);
+ topo_snap_release(thp);
topo_close(thp);
return (err ? FMTOPO_EXIT_ERROR : FMTOPO_EXIT_SUCCESS);
@@ -300,6 +301,8 @@ main(int argc, char *argv[])
if (topo_walk_step(twp, TOPO_WALK_CHILD) == TOPO_WALK_ERR) {
(void) fprintf(stderr, "%s: failed to walk topology\n",
g_pname);
+ topo_walk_fini(twp);
+ topo_snap_release(thp);
topo_close(thp);
return (FMTOPO_EXIT_ERROR);
}
diff --git a/usr/src/cmd/fm/modules/Makefile b/usr/src/cmd/fm/modules/Makefile
index ae81c5585a..23c2d4554c 100644
--- a/usr/src/cmd/fm/modules/Makefile
+++ b/usr/src/cmd/fm/modules/Makefile
@@ -25,7 +25,7 @@
#ident "%Z%%M% %I% %E% SMI"
sparc_SUBDIRS = sun4u sun4v SUNW,SPARC-Enterprise
-i386_SUBDIRS =
+i386_SUBDIRS = i86pc
SUBDIRS = common $($(MACH)_SUBDIRS)
diff --git a/usr/src/cmd/fm/modules/i86pc/Makefile b/usr/src/cmd/fm/modules/i86pc/Makefile
new file mode 100644
index 0000000000..92aa1f3118
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/Makefile
@@ -0,0 +1,29 @@
+#
+# 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 2006 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#ident "%Z%%M% %I% %E% SMI"
+
+SUBDIRS = sfx4500-disk
+
+include ../../Makefile.subdirs
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/Makefile b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/Makefile
new file mode 100644
index 0000000000..f3a8b01a38
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/Makefile
@@ -0,0 +1,38 @@
+#
+# 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 2006 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#ident "%Z%%M% %I% %E% SMI"
+
+MODULE = sfx4500-disk
+CLASS = arch
+ARCH = i86pc
+SRCS = sfx4500-disk.c diskmon_conf.c topo_gather.c \
+ fault_mgr.c hotplug_mgr.c ipmi_plugin.c \
+ plugin_mgr.c schg_mgr.c fault_analyze.c scsi_util.c util.c
+
+include ../../Makefile.plugin
+LINTFLAGS += -I. -I$(SRC)/lib/fm/topo/modules/i86pc/sata -L$(ROOT)/usr/lib/fm
+CFLAGS += -I. -I$(SRC)/lib/fm/topo/modules/i86pc/sata
+LDLIBS += -lsysevent -lnvpair -lsmbios -ldl -lrt -lcfgadm -ltopo -luutil
+LDFLAGS += -L$(ROOT)/usr/lib/fm -R/usr/lib/fm
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/diskmon_conf.c b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/diskmon_conf.c
new file mode 100644
index 0000000000..7274cc39c5
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/diskmon_conf.c
@@ -0,0 +1,926 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Disk & Indicator Monitor configuration file support routines
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+#include <assert.h>
+#include <limits.h>
+#include <pthread.h>
+
+#include "sfx4500-disk.h"
+#include "util.h"
+#include "topo_gather.h"
+#include "sata.h"
+
+extern log_class_t g_verbose;
+
+const char *
+hotplug_state_string(hotplug_state_t state)
+{
+ switch (state & ~HPS_FAULTED) {
+ default:
+ case HPS_UNKNOWN:
+ return ("Unknown");
+ case HPS_ABSENT:
+ return ("Absent");
+ case HPS_PRESENT:
+ return ("Present");
+ case HPS_CONFIGURED:
+ return ("Configured");
+ case HPS_UNCONFIGURED:
+ return ("Unconfigured");
+ }
+}
+
+void
+conf_error_msg(conf_err_t err, char *buf, int buflen, void *arg)
+{
+ switch (err) {
+ case E_MULTIPLE_IND_LISTS_DEFINED:
+ (void) snprintf(buf, buflen, "Multiple Indicator lists "
+ "defined");
+ break;
+ case E_MULTIPLE_INDRULE_LISTS_DEFINED:
+ (void) snprintf(buf, buflen, "Multiple Indicator rule lists "
+ "defined");
+ break;
+ case E_INVALID_STATE_CHANGE:
+ (void) snprintf(buf, buflen, "Invalid state change");
+ break;
+ case E_IND_MULTIPLY_DEFINED:
+ (void) snprintf(buf, buflen,
+ "Multiple Indicator definitions (name & state) detected");
+ break;
+ case E_IND_ACTION_REDUNDANT:
+ (void) snprintf(buf, buflen, "Redundant Indicator actions "
+ "specified");
+ break;
+ case E_IND_ACTION_CONFLICT:
+ (void) snprintf(buf, buflen, "Indicator action conflict (+/- "
+ "same Indicator) found");
+ break;
+ case E_IND_MISSING_FAULT_ON:
+ (void) snprintf(buf, buflen, "Missing declaration of `+"
+ INDICATOR_FAULT_IDENTIFIER "'");
+ break;
+ case E_IND_MISSING_FAULT_OFF:
+ (void) snprintf(buf, buflen, "Missing declaration of `-"
+ INDICATOR_FAULT_IDENTIFIER "'");
+ break;
+ case E_INDRULE_REFERENCES_NONEXISTENT_IND_ACTION:
+ (void) snprintf(buf, buflen, "`%c%s': Undefined Indicator in "
+ SATA_IND_ACTION " property",
+ (((ind_action_t *)arg)->ind_state == INDICATOR_ON)
+ ? '+' : '-',
+ ((ind_action_t *)arg)->ind_name);
+ break;
+ case E_DUPLICATE_STATE_TRANSITION:
+ (void) snprintf(buf, buflen, "Duplicate state transition "
+ "(%s -> %s)",
+ hotplug_state_string(((state_transition_t *)arg)->begin),
+ hotplug_state_string(((state_transition_t *)arg)->end));
+ break;
+ default:
+ (void) snprintf(buf, buflen, "Unknown error");
+ break;
+ }
+}
+
+static int
+string_to_integer(const char *prop, int *value)
+{
+ long val;
+
+ errno = 0;
+
+ val = strtol(prop, NULL, 0);
+
+ if (val == 0 && errno != 0)
+ return (-1);
+ else if (val > INT_MAX || val < INT_MIN) {
+ errno = ERANGE;
+ return (-1);
+ }
+
+ if (value != NULL)
+ *value = (int)val;
+
+ return (0);
+}
+
+const char *
+dm_prop_lookup(nvlist_t *props, const char *prop_name)
+{
+ char *str;
+
+ if (nvlist_lookup_string(props, prop_name, &str) == 0)
+ return ((const char *)str);
+ else
+ return (NULL);
+}
+
+int
+dm_prop_lookup_int(nvlist_t *props, const char *prop_name, int *value)
+{
+ const char *prop = dm_prop_lookup(props, prop_name);
+
+ if (prop == NULL)
+ return (-1);
+
+ return (string_to_integer(prop, value));
+}
+
+nvlist_t *
+namevalpr_to_nvlist(namevalpr_t *nvprp)
+{
+ nvlist_t *nvlp = NULL;
+
+ if (nvlist_alloc(&nvlp, NV_UNIQUE_NAME, 0) != 0) {
+ return (NULL);
+ }
+
+ if (nvlist_add_string(nvlp, nvprp->name, nvprp->value) != 0) {
+ nvlist_free(nvlp);
+ return (NULL);
+ }
+
+ return (nvlp);
+}
+
+indicator_t *
+new_indicator(ind_state_t lstate, char *namep, char *actionp)
+{
+ indicator_t *newindicator =
+ (indicator_t *)dmalloc(sizeof (indicator_t));
+ newindicator->ind_state = lstate;
+ newindicator->ind_name = namep ? dstrdup(namep) : NULL;
+ newindicator->ind_instr_spec = actionp ? dstrdup(actionp) : NULL;
+ newindicator->next = NULL;
+ return (newindicator);
+}
+
+void
+link_indicator(indicator_t **first, indicator_t *to_add)
+{
+ indicator_t *travptr;
+ assert(first != NULL);
+
+ if (*first == NULL)
+ *first = to_add;
+ else {
+ travptr = *first;
+ while (travptr->next != NULL) {
+ travptr = travptr->next;
+ }
+ travptr->next = to_add;
+ }
+}
+
+void
+ind_free(indicator_t *indp)
+{
+ indicator_t *nextp;
+
+ while (indp != NULL) {
+ nextp = indp->next;
+ if (indp->ind_name)
+ dstrfree(indp->ind_name);
+ if (indp->ind_instr_spec)
+ dstrfree(indp->ind_instr_spec);
+ dfree(indp, sizeof (indicator_t));
+ indp = nextp;
+ }
+}
+
+ind_action_t *
+new_indaction(ind_state_t state, char *namep)
+{
+ ind_action_t *lap = (ind_action_t *)dmalloc(sizeof (ind_action_t));
+ lap->ind_state = state;
+ lap->ind_name = namep ? dstrdup(namep) : NULL;
+ lap->next = NULL;
+ return (lap);
+}
+
+void
+link_indaction(ind_action_t **first, ind_action_t *to_add)
+{
+ ind_action_t *travptr;
+ assert(first != NULL);
+
+ if (*first == NULL)
+ *first = to_add;
+ else {
+ travptr = *first;
+ while (travptr->next != NULL) {
+ travptr = travptr->next;
+ }
+ travptr->next = to_add;
+ }
+}
+
+void
+indaction_free(ind_action_t *lap)
+{
+ ind_action_t *nextp;
+
+ /* Free the whole list */
+ while (lap != NULL) {
+ nextp = lap->next;
+ if (lap->ind_name)
+ dstrfree(lap->ind_name);
+ dfree(lap, sizeof (ind_action_t));
+ lap = nextp;
+ }
+}
+
+indrule_t *
+new_indrule(state_transition_t *st, ind_action_t *actionp)
+{
+ indrule_t *lrp = (indrule_t *)dmalloc(sizeof (indrule_t));
+ if (st != NULL)
+ lrp->strans = *st;
+ lrp->action_list = actionp;
+ lrp->next = NULL;
+ return (lrp);
+}
+
+void
+link_indrule(indrule_t **first, indrule_t *to_add)
+{
+ indrule_t *travptr;
+ assert(first != NULL);
+
+ if (*first == NULL)
+ *first = to_add;
+ else {
+ travptr = *first;
+ while (travptr->next != NULL) {
+ travptr = travptr->next;
+ }
+ travptr->next = to_add;
+ }
+}
+
+void
+indrule_free(indrule_t *lrp)
+{
+ indrule_t *nextp;
+
+ /* Free the whole list */
+ while (lrp != NULL) {
+ nextp = lrp->next;
+ if (lrp->action_list)
+ indaction_free(lrp->action_list);
+ dfree(lrp, sizeof (indrule_t));
+ lrp = nextp;
+ }
+}
+
+dm_fru_t *
+new_dmfru(char *manu, char *modl, char *firmrev, char *serno, uint64_t capa)
+{
+ dm_fru_t *frup = (dm_fru_t *)dzmalloc(sizeof (dm_fru_t));
+
+ bcopy(manu, frup->manuf, MIN(sizeof (frup->manuf), strlen(manu) + 1));
+ bcopy(modl, frup->model, MIN(sizeof (frup->model), strlen(modl) + 1));
+ bcopy(firmrev, frup->rev, MIN(sizeof (frup->rev), strlen(firmrev) + 1));
+ bcopy(serno, frup->serial,
+ MIN(sizeof (frup->serial), strlen(serno) + 1));
+ frup->size_in_bytes = capa;
+ return (frup);
+}
+
+void
+dmfru_free(dm_fru_t *frup)
+{
+ dfree(frup, sizeof (dm_fru_t));
+}
+
+diskmon_t *
+new_diskmon(nvlist_t *app_props, indicator_t *indp, indrule_t *indrp,
+ nvlist_t *nvlp)
+{
+ diskmon_t *dmp = (diskmon_t *)dmalloc(sizeof (diskmon_t));
+
+ if (nvlp != NULL)
+ dmp->props = nvlp;
+ else
+ (void) nvlist_alloc(&dmp->props, NV_UNIQUE_NAME, 0);
+
+ if (app_props)
+ dmp->app_props = app_props;
+ else
+ (void) nvlist_alloc(&dmp->app_props, NV_UNIQUE_NAME, 0);
+ dmp->ind_list = indp;
+ dmp->indrule_list = indrp;
+
+ assert(pthread_mutex_init(&dmp->manager_mutex, NULL) == 0);
+
+ dmp->state = HPS_UNKNOWN;
+
+ dmp->initial_configuration = B_TRUE;
+
+ dmp->fmip = NULL;
+
+ dmp->faults_outstanding = B_FALSE;
+ assert(pthread_mutex_init(&dmp->fault_indicator_mutex, NULL) == 0);
+ dmp->fault_indicator_state = INDICATOR_UNKNOWN;
+ assert(pthread_mutex_init(&dmp->disk_faults_mutex, NULL) == 0);
+ dmp->disk_faults = DISK_FAULT_SOURCE_NONE;
+ dmp->due = (time_t)0;
+ dmp->fault_inject_count = 0;
+ dmp->analysis_generation = 0;
+
+ dmp->configured_yet = B_FALSE;
+ dmp->state_change_count = 0;
+
+ dmp->disk_res_fmri = NULL;
+ dmp->asru_fmri = NULL;
+ dmp->fru_fmri = NULL;
+
+ assert(pthread_mutex_init(&dmp->fru_mutex, NULL) == 0);
+ dmp->frup = NULL;
+
+ dmp->next = NULL;
+ return (dmp);
+}
+
+void
+diskmon_free(diskmon_t *dmp)
+{
+ diskmon_t *nextp;
+
+ /* Free the whole list */
+ while (dmp != NULL) {
+ nextp = dmp->next;
+
+ if (dmp->props)
+ nvlist_free(dmp->props);
+ if (dmp->location)
+ dstrfree(dmp->location);
+ if (dmp->ind_list)
+ ind_free(dmp->ind_list);
+ if (dmp->indrule_list)
+ indrule_free(dmp->indrule_list);
+ if (dmp->app_props)
+ nvlist_free(dmp->app_props);
+ if (dmp->fmip)
+ disk_fault_uninit(dmp);
+ if (dmp->disk_res_fmri)
+ nvlist_free(dmp->disk_res_fmri);
+ if (dmp->asru_fmri)
+ nvlist_free(dmp->asru_fmri);
+ if (dmp->fru_fmri)
+ nvlist_free(dmp->fru_fmri);
+ if (dmp->frup)
+ dmfru_free(dmp->frup);
+ dfree(dmp, sizeof (diskmon_t));
+
+ dmp = nextp;
+ }
+}
+
+static cfgdata_t *
+new_cfgdata(namevalpr_t *nvp, diskmon_t *dmp)
+{
+ cfgdata_t *cdp = (cfgdata_t *)dzmalloc(sizeof (cfgdata_t));
+
+ if (nvp != NULL)
+ cdp->props = namevalpr_to_nvlist(nvp);
+ else if (nvlist_alloc(&cdp->props, NV_UNIQUE_NAME, 0) != 0) {
+ return (NULL);
+ }
+
+ if (dmp != NULL)
+ cdp->disk_list = dmp;
+ return (cdp);
+
+}
+
+static void
+cfgdata_add_namevalpr(cfgdata_t *cfgp, namevalpr_t *nvp)
+{
+ if (cfgp->props == NULL) {
+ (void) nvlist_alloc(&cfgp->props, NV_UNIQUE_NAME, 0);
+ }
+ (void) nvlist_add_string(cfgp->props, nvp->name, nvp->value);
+}
+
+void
+cfgdata_add_diskmon(cfgdata_t *cfgp, diskmon_t *dmp)
+{
+ if (cfgp->disk_list == NULL) {
+ cfgp->disk_list = dmp;
+ } else {
+ diskmon_t *disklist = cfgp->disk_list;
+
+ while (disklist->next != NULL)
+ disklist = disklist->next;
+
+ disklist->next = dmp;
+ }
+}
+
+static void
+cfgdata_free(cfgdata_t *cdp)
+{
+ nvlist_free(cdp->props);
+ diskmon_free(cdp->disk_list);
+ dfree(cdp, sizeof (cfgdata_t));
+}
+
+void
+diskmon_add_asru(diskmon_t *dmp, nvlist_t *fmri)
+{
+ if (dmp->asru_fmri) {
+ nvlist_free(dmp->asru_fmri);
+ dmp->asru_fmri = NULL;
+ }
+ (void) nvlist_dup(fmri, &dmp->asru_fmri, 0);
+}
+
+void
+diskmon_add_fru(diskmon_t *dmp, nvlist_t *fmri)
+{
+ if (dmp->fru_fmri) {
+ nvlist_free(dmp->fru_fmri);
+ dmp->fru_fmri = NULL;
+ }
+ (void) nvlist_dup(fmri, &dmp->fru_fmri, 0);
+}
+
+void
+diskmon_add_disk_fmri(diskmon_t *dmp, nvlist_t *fmri)
+{
+ if (dmp->disk_res_fmri) {
+ nvlist_free(dmp->disk_res_fmri);
+ dmp->disk_res_fmri = NULL;
+ }
+ (void) nvlist_dup(fmri, &dmp->disk_res_fmri, 0);
+}
+
+conf_err_t
+check_indactions(ind_action_t *indrp)
+{
+ char *buf;
+ conf_err_t rv = E_NO_ERROR;
+ nvlist_t *nvp = NULL;
+ int len;
+
+ (void) nvlist_alloc(&nvp, NV_UNIQUE_NAME, 0);
+
+ /*
+ * Check indicator actions for conflicts
+ */
+ while (indrp != NULL && rv == E_NO_ERROR) {
+ len = strlen(indrp->ind_name) + 2;
+ buf = dmalloc(len);
+ (void) snprintf(buf, len, "%c%s",
+ indrp->ind_state == INDICATOR_ON ? '+' : '-',
+ indrp->ind_name);
+ switch (nvlist_lookup_boolean(nvp, buf)) {
+ case ENOENT:
+ (void) nvlist_add_boolean(nvp, buf);
+ break;
+ case 0:
+ rv = E_IND_ACTION_REDUNDANT;
+ break;
+ default:
+ break;
+ }
+
+ /* Look for the opposite action. If found, that's an error */
+ (void) snprintf(buf, len, "%c%s",
+ indrp->ind_state == INDICATOR_ON ? '-' : '+',
+ indrp->ind_name);
+ switch (nvlist_lookup_boolean(nvp, buf)) {
+ case ENOENT:
+ break;
+ case 0:
+ rv = E_IND_ACTION_CONFLICT;
+ break;
+ default:
+ break;
+ }
+ dfree(buf, len);
+ indrp = indrp->next;
+ }
+
+ nvlist_free(nvp);
+ return (rv);
+}
+
+conf_err_t
+check_inds(indicator_t *indp)
+{
+ char *buf;
+ conf_err_t rv = E_NO_ERROR;
+ nvlist_t *nvp = NULL;
+ int len;
+ boolean_t fault_on = B_FALSE, fault_off = B_FALSE;
+
+ (void) nvlist_alloc(&nvp, NV_UNIQUE_NAME, 0);
+
+ /*
+ * Check inds for multiple definitions (same identifier or same action)
+ */
+ while (indp != NULL && rv == E_NO_ERROR) {
+ len = strlen(indp->ind_name) + 2;
+ buf = dmalloc(len);
+ (void) snprintf(buf, len, "%c%s",
+ indp->ind_state == INDICATOR_ON ? '+' : '-',
+ indp->ind_name);
+
+ /* Keep track of the +/-FAULT for checking later */
+ if (strcasecmp(buf, "+" INDICATOR_FAULT_IDENTIFIER) == 0)
+ fault_on = B_TRUE;
+ else if (strcasecmp(buf, "-" INDICATOR_FAULT_IDENTIFIER) == 0)
+ fault_off = B_TRUE;
+
+ switch (nvlist_lookup_boolean(nvp, buf)) {
+ case ENOENT:
+ (void) nvlist_add_boolean(nvp, buf);
+ break;
+ case 0:
+ rv = E_IND_MULTIPLY_DEFINED;
+ break;
+ default:
+ break;
+ }
+ dfree(buf, len);
+ indp = indp->next;
+ }
+
+ /*
+ * Make sure we have a -FAULT and +FAULT
+ */
+ if (!fault_on)
+ rv = E_IND_MISSING_FAULT_ON;
+ else if (!fault_off)
+ rv = E_IND_MISSING_FAULT_OFF;
+
+ nvlist_free(nvp);
+ return (rv);
+}
+
+conf_err_t
+check_indrules(indrule_t *indrp, state_transition_t **offender)
+{
+ char buf[32];
+ conf_err_t rv = E_NO_ERROR;
+ nvlist_t *nvp = NULL;
+
+ /*
+ * Ensure that no two rules have the same state transitions.
+ */
+
+ (void) nvlist_alloc(&nvp, NV_UNIQUE_NAME, 0);
+
+ while (indrp != NULL && rv == E_NO_ERROR) {
+ (void) snprintf(buf, sizeof (buf), "%d-%d",
+ (int)indrp->strans.begin, (int)indrp->strans.end);
+ switch (nvlist_lookup_boolean(nvp, buf)) {
+ case 0:
+ *offender = &indrp->strans;
+ rv = E_DUPLICATE_STATE_TRANSITION;
+ break;
+ case ENOENT:
+ (void) nvlist_add_boolean(nvp, buf);
+ break;
+ default:
+ break;
+ }
+ indrp = indrp->next;
+ }
+
+ nvlist_free(nvp);
+ return (rv);
+}
+
+
+conf_err_t
+check_consistent_ind_indrules(indicator_t *indp, indrule_t *indrp,
+ ind_action_t **offender)
+{
+ char *buf;
+ conf_err_t rv = E_NO_ERROR;
+ nvlist_t *nvp = NULL;
+ ind_action_t *alp;
+ int len;
+
+ /*
+ * Ensure that every indicator action referenced in each ruleset
+ * exists in the indicator list given.
+ */
+
+ (void) nvlist_alloc(&nvp, NV_UNIQUE_NAME, 0);
+
+ while (indp != NULL) {
+ len = strlen(indp->ind_name) + 2;
+ buf = dmalloc(len);
+ (void) snprintf(buf, len, "%c%s",
+ indp->ind_state == INDICATOR_ON ? '+' : '-',
+ indp->ind_name);
+ (void) nvlist_add_boolean(nvp, buf);
+ dfree(buf, len);
+ indp = indp->next;
+ }
+
+ while (indrp != NULL && rv == E_NO_ERROR) {
+ alp = indrp->action_list;
+ while (alp != NULL && rv == E_NO_ERROR) {
+ len = strlen(alp->ind_name) + 2;
+ buf = dmalloc(len);
+ (void) snprintf(buf, len, "%c%s",
+ alp->ind_state == INDICATOR_ON ? '+' : '-',
+ alp->ind_name);
+
+ switch (nvlist_lookup_boolean(nvp, buf)) {
+ case 0: /* Normal case */
+ break;
+ case ENOENT:
+ *offender = alp;
+ rv =
+ E_INDRULE_REFERENCES_NONEXISTENT_IND_ACTION;
+ break;
+ default:
+ break;
+ }
+ dfree(buf, len);
+ alp = alp->next;
+ }
+ indrp = indrp->next;
+ }
+
+ nvlist_free(nvp);
+ return (rv);
+}
+
+conf_err_t
+check_state_transition(hotplug_state_t s1, hotplug_state_t s2)
+{
+ /*
+ * The following are valid transitions:
+ *
+ * HPS_ABSENT -> HPS_PRESENT
+ * HPS_ABSENT -> HPS_CONFIGURED
+ * HPS_PRESENT -> HPS_CONFIGURED
+ * HPS_PRESENT -> HPS_ABSENT
+ * HPS_CONFIGURED -> HPS_UNCONFIGURED
+ * HPS_CONFIGURED -> HPS_ABSENT
+ * HPS_UNCONFIGURED -> HPS_ABSENT
+ * HPS_UNCONFIGURED -> HPS_CONFIGURED
+ *
+ */
+ if (s1 == HPS_ABSENT && s2 != HPS_PRESENT && s2 != HPS_CONFIGURED)
+ return (E_INVALID_STATE_CHANGE);
+ else if (s1 == HPS_PRESENT && (s2 != HPS_CONFIGURED &&
+ s2 != HPS_ABSENT))
+ return (E_INVALID_STATE_CHANGE);
+ else if (s1 == HPS_CONFIGURED && (s2 != HPS_UNCONFIGURED &&
+ s2 != HPS_ABSENT))
+ return (E_INVALID_STATE_CHANGE);
+ else if (s1 == HPS_UNCONFIGURED && (s2 != HPS_ABSENT &&
+ s2 != HPS_CONFIGURED))
+ return (E_INVALID_STATE_CHANGE);
+ else
+ return (E_NO_ERROR);
+}
+
+static void
+print_inds(indicator_t *indp, FILE *fp, char *prefix)
+{
+ char plusminus;
+
+ (void) fprintf(fp, "%sindicators {\n", prefix);
+ while (indp != NULL) {
+ plusminus = (indp->ind_state == INDICATOR_ON) ? '+' : '-';
+ (void) fprintf(fp, "%s\t%c%s = \"%s\"\n", prefix, plusminus,
+ indp->ind_name, indp->ind_instr_spec);
+ indp = indp->next;
+ }
+ (void) fprintf(fp, "%s}\n", prefix);
+}
+
+static void
+print_indrules(indrule_t *lrp, FILE *fp, char *prefix)
+{
+ char plusminus;
+ ind_action_t *lap;
+
+ (void) fprintf(fp, "%sindicator_rules {\n", prefix);
+ while (lrp != NULL) {
+ (void) fprintf(fp, "%s\t%12s -> %12s\t{ ", prefix,
+ hotplug_state_string(lrp->strans.begin),
+ hotplug_state_string(lrp->strans.end));
+ lap = lrp->action_list;
+ while (lap != NULL) {
+ plusminus = (lap->ind_state == INDICATOR_ON)
+ ? '+' : '-';
+ (void) fprintf(fp, "%c%s", plusminus, lap->ind_name);
+ lap = lap->next;
+ if (lap != NULL)
+ (void) fprintf(fp, ", ");
+ }
+ (void) fprintf(fp, " }\n");
+ lrp = lrp->next;
+ }
+ (void) fprintf(fp, "%s}\n", prefix);
+}
+
+static void
+print_props(nvlist_t *nvlp, FILE *fp, char *prefix)
+{
+ nvpair_t *nvp = nvlist_next_nvpair(nvlp, NULL);
+ char *name, *str;
+
+ while (nvp != NULL) {
+ assert(nvpair_type(nvp) == DATA_TYPE_STRING);
+ name = nvpair_name(nvp);
+ (void) nvlist_lookup_string(nvlp, name, &str);
+ (void) fprintf(fp, "%s%s = \"%s\"\n", prefix, name, str);
+ nvp = nvlist_next_nvpair(nvlp, nvp);
+ }
+}
+
+static void
+print_ap(nvlist_t *dpp, FILE *fp, char *prefix)
+{
+ int len = strlen(prefix) + 2;
+ char *buf = dmalloc(len);
+
+ (void) snprintf(buf, len, "%s\t", prefix);
+
+ (void) fprintf(fp, "%sap_props {\n", prefix);
+ print_props(dpp, fp, buf);
+ (void) fprintf(fp, "%s}\n", prefix);
+
+ dfree(buf, len);
+}
+
+static void
+print_disks(diskmon_t *dmp, FILE *fp, char *prefix)
+{
+ int len = strlen(prefix) + 2;
+ char *buf = dmalloc(len);
+
+ (void) snprintf(buf, len, "%s\t", prefix);
+
+ while (dmp != NULL) {
+ (void) fprintf(fp, "%sdisk \"%s\" {\n", prefix, dmp->location);
+ if (dmp->props) {
+ print_props(dmp->props, fp, buf);
+ }
+ if (dmp->app_props) {
+ print_ap(dmp->app_props, fp, buf);
+ }
+ (void) fprintf(fp, "%s\n", prefix);
+ print_inds(dmp->ind_list, fp, buf);
+ (void) fprintf(fp, "%s\n", prefix);
+ print_indrules(dmp->indrule_list, fp, buf);
+ (void) fprintf(fp, "%s}\n", prefix);
+
+ if (dmp->next != NULL)
+ (void) fprintf(fp, "%s\n", prefix);
+
+ dmp = dmp->next;
+ }
+
+ dfree(buf, len);
+}
+
+static void
+print_cfgdata(cfgdata_t *cfgp, FILE *fp, char *prefix)
+{
+ /* First, print the properties, then the disks */
+
+ print_props(cfgp->props, fp, prefix);
+ (void) fprintf(fp, "%s\n", prefix);
+ print_disks(cfgp->disk_list, fp, prefix);
+}
+
+int
+config_init(void)
+{
+ if (topo_init_configuration() == 0) {
+ config_data = new_cfgdata(NULL, NULL);
+ return (0);
+ }
+ return (-1);
+}
+
+int
+config_get(fmd_hdl_t *hdl, const fmd_prop_t *fmd_props)
+{
+ int err, i = 0;
+ char *str = NULL;
+ namevalpr_t nvp;
+ uint64_t u64;
+ boolean_t intfound = B_FALSE, strfound = B_FALSE;
+#define INT64_BUF_LEN 128
+ char buf[INT64_BUF_LEN];
+
+ u64 = fmd_prop_get_int32(hdl, GLOBAL_PROP_LOG_LEVEL);
+ g_verbose = (int)u64;
+
+ err = topo_update_configuration(NULL);
+
+ /* Pull in the properties from the DE configuration file */
+ while (fmd_props[i].fmdp_name != NULL) {
+
+ nvp.name = (char *)fmd_props[i].fmdp_name;
+
+ switch (fmd_props[i].fmdp_type) {
+ case FMD_TYPE_UINT32:
+ case FMD_TYPE_INT32:
+ intfound = B_TRUE;
+ u64 = fmd_prop_get_int32(hdl, fmd_props[i].fmdp_name);
+ break;
+ case FMD_TYPE_UINT64:
+ case FMD_TYPE_INT64:
+ intfound = B_TRUE;
+ u64 = fmd_prop_get_int64(hdl, fmd_props[i].fmdp_name);
+ break;
+ case FMD_TYPE_STRING:
+ strfound = B_TRUE;
+ str = fmd_prop_get_string(hdl, fmd_props[i].fmdp_name);
+ break;
+
+ }
+
+ if (intfound) {
+ (void) snprintf(buf, INT64_BUF_LEN, "0x%llx", u64);
+ nvp.value = buf;
+ intfound = B_FALSE;
+ } else if (strfound) {
+ nvp.value = str;
+ }
+
+ log_msg(MM_CONF, "Adding property `%s' with value `%s'\n",
+ nvp.name, nvp.value);
+
+ cfgdata_add_namevalpr(config_data, &nvp);
+
+ if (strfound) {
+ strfound = B_FALSE;
+ fmd_prop_free_string(hdl, str);
+ }
+
+
+ i++;
+ }
+
+ if ((g_verbose & (MM_CONF|MM_OTHER)) == (MM_CONF|MM_OTHER))
+ print_cfgdata(config_data, stderr, "");
+
+ return (err);
+}
+
+void
+config_fini(void)
+{
+ topo_fini_configuration();
+ cfgdata_free(config_data);
+ config_data = NULL;
+}
+
+nvlist_t *
+dm_global_proplist(void)
+{
+ return (config_data->props);
+}
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/diskmon_conf.h b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/diskmon_conf.h
new file mode 100644
index 0000000000..e8623dad48
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/diskmon_conf.h
@@ -0,0 +1,330 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _DISKMOND_CONF_H
+#define _DISKMOND_CONF_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Configuration File data
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+#include <pthread.h>
+#include <libnvpair.h>
+#include <fm/fmd_api.h>
+#include "dm_types.h"
+#include "scsi_util.h"
+#include "fault_analyze.h"
+#include "util.h"
+
+#ifndef MIN
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#endif
+
+#ifndef MAX
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+#endif
+
+#define DEVICES_PREFIX "/devices"
+
+#define GLOBAL_PROP_FAULT_POLL "fault-polling-interval"
+#define GLOBAL_PROP_FAULT_INJ "fault-inject-max-reps"
+#define GLOBAL_PROP_FAULT_OPTIONS "fault-analyze-options"
+#define GLOBAL_PROP_LOG_LEVEL "log-level"
+
+/* Property names (and values) for the disk configuration file entity */
+#define DISK_PROP_DEVPATH "dev-path"
+#define DISK_PROP_LOGNAME "logical-path"
+#define DISK_PROP_FRUACTION "fru-update-action"
+#define DISK_PROP_OTEMPACTION "overtemp-action"
+#define DISK_PROP_STFAILACTION "selftest-fail-action"
+
+/* Properties for the "ap" subentity */
+#define DISK_AP_PROP_APID "path"
+
+#define DEVPATH_MINOR_SEPARATOR ':'
+
+#define DEFAULT_FAULT_POLLING_INTERVAL 3600 /* seconds */
+
+#define INDICATOR_FAULT_IDENTIFIER "FAULT"
+
+typedef enum conf_err_e {
+ E_NO_ERROR = 0,
+ E_MULTIPLE_IND_LISTS_DEFINED,
+ E_MULTIPLE_INDRULE_LISTS_DEFINED,
+ E_INVALID_STATE_CHANGE,
+ E_IND_MULTIPLY_DEFINED,
+ E_IND_ACTION_REDUNDANT,
+ E_IND_ACTION_CONFLICT,
+ E_IND_MISSING_FAULT_ON,
+ E_IND_MISSING_FAULT_OFF,
+ E_INDRULE_REFERENCES_NONEXISTENT_IND_ACTION,
+ E_DUPLICATE_STATE_TRANSITION
+} conf_err_t;
+
+typedef enum {
+ INDICATOR_UNKNOWN,
+ INDICATOR_ON,
+ INDICATOR_OFF
+} ind_state_t;
+
+typedef enum {
+ TS_NOT_RUNNING,
+ TS_RUNNING,
+ TS_EXIT_REQUESTED,
+ TS_EXITED
+} thread_state_t;
+
+typedef struct ind_action {
+ ind_state_t ind_state;
+ char *ind_name;
+ struct ind_action *next;
+} ind_action_t;
+
+typedef struct state_transition {
+ hotplug_state_t begin;
+ hotplug_state_t end;
+} state_transition_t;
+
+typedef struct indrule {
+ state_transition_t strans;
+ ind_action_t *action_list;
+ struct indrule *next;
+} indrule_t;
+
+typedef struct indicator {
+ ind_state_t ind_state;
+ char *ind_name;
+ char *ind_instr_spec;
+ struct indicator *next;
+} indicator_t;
+
+typedef struct fault_monitor_info {
+ int mode_length;
+ logpage_supp_e log_pages_supported;
+ modepage_supp_e mode_pages_supported;
+ disk_option_e options;
+ disk_extension_e extensions; /* Vendor extensions supported */
+ uint_t update_interval;
+
+ /* Protects fault_list and disk_fault_srcs: */
+ pthread_mutex_t fault_data_mutex;
+ disk_flt_src_e disk_fault_srcs;
+ struct disk_fault *fault_list;
+
+ uint_t reference_temp;
+
+ uint_t last_rs_key;
+ uint_t last_rs_asc;
+ uint_t last_rs_ascq;
+
+ /* XXX - may not need these long-term: */
+ struct scsi_ms_hdrs hdrs;
+ struct info_except_page iec_current;
+ struct info_except_page iec_changeable;
+} fault_monitor_info_t;
+
+typedef struct diskmon {
+ /*
+ * Static configuration data
+ */
+ nvlist_t *props;
+ char *location; /* descriptive location */
+ nvlist_t *app_props;
+ indicator_t *ind_list;
+ indrule_t *indrule_list;
+ /*
+ * Dynamic data
+ */
+ hotplug_state_t state;
+
+ /*
+ * Only one manager can be manipulating the
+ * state in the diskmon at one time (either the
+ * state-change manager or the fault-polling manager)
+ */
+ pthread_mutex_t manager_mutex;
+
+ /*
+ * Set to true only during initialization, and
+ * cleared the next time a fru update needs to
+ * occur, this flag enabled an optimization of
+ * NOT calling libtopo for a configuration update
+ * when the DE starts up. This allows a HUGE
+ * savings (since only a single snapshot-- the
+ * initial snapshot) is used as the source of
+ * the FRU information.
+ */
+ boolean_t initial_configuration;
+
+
+ /* For the fault manager: */
+
+ /*
+ * Set to TRUE when the fault manager adds faults to the diskmon
+ * that are processed by the state manager. Once the state
+ * manager generates ereports and clears the disk_faults member,
+ * it clears this flag, allowing the fault manager to add new
+ * faults, when they are detected.
+ */
+ boolean_t faults_outstanding;
+ pthread_mutex_t fault_indicator_mutex;
+ ind_state_t fault_indicator_state;
+
+ /* Bitmap of accumulated faults: */
+ pthread_mutex_t disk_faults_mutex;
+ disk_flt_src_e disk_faults;
+
+ /* The time the next fault analysis is due: */
+ time_t due;
+ /*
+ * The number of analysis generations after which fake faults
+ * are injected.
+ */
+ uint_t fault_inject_count;
+ /*
+ * The current analysis generation (number of times the fault
+ * analysis algorithm was run). Used to determine when to do
+ * fault injection, when fault injection is enabled.
+ */
+ uint_t analysis_generation;
+
+ /* For the state-change manager: */
+
+ /*
+ * Set to TRUE when a disk transitions to the CONFIGURED state
+ * and remains TRUE until the disk is physically removed. Used
+ * to detect the first configuration of a disk so that fault
+ * state can be collected.
+ */
+ boolean_t configured_yet;
+
+ /*
+ * The number of disk hotplug state transitions since the disk
+ * was inserted.
+ */
+ uint_t state_change_count;
+
+ /*
+ * FMRI (nvlist and string versions) for populating
+ * ereports and faults
+ */
+ nvlist_t *disk_res_fmri;
+ nvlist_t *asru_fmri;
+ nvlist_t *fru_fmri;
+
+ /*
+ * The following member holds details about what faults were
+ * detected, and their details (see above for the structure
+ * definition)
+ */
+ fault_monitor_info_t *fmip;
+
+ /* Disk FRU (model, manufacturer, etc) information */
+ pthread_mutex_t fru_mutex;
+ dm_fru_t *frup;
+
+ struct diskmon *next;
+} diskmon_t;
+
+typedef struct cfgdata {
+ nvlist_t *props;
+ diskmon_t *disk_list;
+} cfgdata_t;
+
+typedef struct namevalpr {
+ char *name;
+ char *value;
+} namevalpr_t;
+
+
+extern indicator_t *new_indicator(ind_state_t lstate, char *namep,
+ char *actionp);
+extern void link_indicator(indicator_t **first,
+ indicator_t *to_add);
+extern void ind_free(indicator_t *indp);
+
+extern ind_action_t *new_indaction(ind_state_t state, char *namep);
+extern void link_indaction(ind_action_t **first,
+ ind_action_t *to_add);
+extern void indaction_free(ind_action_t *lap);
+
+extern indrule_t *new_indrule(state_transition_t *st,
+ ind_action_t *actionp);
+extern void link_indrule(indrule_t **first, indrule_t *to_add);
+extern void indrule_free(indrule_t *lrp);
+
+extern diskmon_t *new_diskmon(nvlist_t *app_props, indicator_t *indp,
+ indrule_t *indrp, nvlist_t *nvlp);
+extern void diskmon_free(diskmon_t *dmp);
+
+extern dm_fru_t *new_dmfru(char *manu, char *modl, char *firmrev,
+ char *serno, uint64_t capa);
+extern void dmfru_free(dm_fru_t *frup);
+
+extern nvlist_t *namevalpr_to_nvlist(namevalpr_t *nvprp);
+
+extern conf_err_t check_state_transition(hotplug_state_t s1,
+ hotplug_state_t s2);
+extern conf_err_t check_inds(indicator_t *indp);
+extern conf_err_t check_indactions(ind_action_t *indap);
+extern conf_err_t check_indrules(indrule_t *indrp,
+ state_transition_t **offender);
+extern conf_err_t check_consistent_ind_indrules(indicator_t *indp,
+ indrule_t *indrp, ind_action_t **offender);
+
+extern void diskmon_add_asru(diskmon_t *dmp, nvlist_t *fmri);
+extern void diskmon_add_fru(diskmon_t *dmp, nvlist_t *fmri);
+extern void diskmon_add_disk_fmri(diskmon_t *dmp, nvlist_t *fmri);
+
+extern void cfgdata_add_diskmon(cfgdata_t *cfgp, diskmon_t *dmp);
+
+extern void conf_error_msg(conf_err_t err, char *buf, int buflen,
+ void *arg);
+
+extern const char *dm_prop_lookup(nvlist_t *props, const char *prop_name);
+extern int dm_prop_lookup_int(nvlist_t *props,
+ const char *prop_name, int *value);
+
+extern int config_init(void);
+extern int config_get(fmd_hdl_t *hdl, const fmd_prop_t *fmd_props);
+extern void config_fini(void);
+
+extern const char *hotplug_state_string(hotplug_state_t state);
+
+extern nvlist_t *dm_global_proplist(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DISKMOND_CONF_H */
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/dm_plugin.h b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/dm_plugin.h
new file mode 100644
index 0000000000..ee13afecb7
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/dm_plugin.h
@@ -0,0 +1,81 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _DM_PLUGIN_H
+#define _DM_PLUGIN_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Declarations for the disk monitor plugin interface
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "dm_types.h"
+
+/*
+ * The name of the symbol that is of type dm_plugin_ops_t that points to the
+ * implementation of the plugin.
+ */
+#define DM_PLUGIN_OPS_NAME "dm_plugin_ops"
+
+#define DM_PLUGIN_VERSION_1 1
+#define DM_PLUGIN_VERSION DM_PLUGIN_VERSION_1
+
+typedef enum {
+ DMPE_SUCCESS,
+ DMPE_FAILURE
+} dm_plugin_error_t;
+
+typedef void *dm_plugin_action_handle_t;
+
+typedef struct dm_plugin_ops {
+ int version;
+ dm_plugin_error_t (*_init)(void);
+ dm_plugin_error_t (*indicator_fru_update)(
+ const char *actionString, dm_fru_t *frup);
+ dm_plugin_error_t (*indicator_bind_handle)(
+ const char *actionString, dm_plugin_action_handle_t *hdlp);
+ dm_plugin_error_t (*indicator_execute)(
+ dm_plugin_action_handle_t hdl);
+ dm_plugin_error_t (*indicator_free_handle)(
+ dm_plugin_action_handle_t *hdlp);
+ dm_plugin_error_t (*_fini)(void);
+} dm_plugin_ops_t;
+
+extern const char *dm_plugin_prop_lookup(const char *propname);
+extern pthread_t dm_plugin_thr_create(void (*fn)(void *), void *);
+extern void dm_plugin_thr_signal(pthread_t);
+extern void dm_plugin_thr_destroy(pthread_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DM_PLUGIN_H */
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/dm_types.h b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/dm_types.h
new file mode 100644
index 0000000000..f19a258873
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/dm_types.h
@@ -0,0 +1,76 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _DM_TYPES_H
+#define _DM_TYPES_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Common types for the disk monitor
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+
+#define NSEC_PER_POLL_POSITION 250000000UL /* 0.25 of a second */
+
+typedef enum {
+ HPS_UNKNOWN = 0x0000,
+ HPS_ABSENT = 0x0001,
+ HPS_PRESENT = 0x0002,
+ HPS_UNCONFIGURED = 0x0003,
+ HPS_CONFIGURED = 0x0004,
+ HPS_FAULTED = 0x1000, /* This state can be OR'ed in */
+ HPS_REPAIRED = 0x2000
+} hotplug_state_t;
+
+typedef struct {
+ /*
+ * Each field is the size of the corresponding field in the scsi
+ * inquiry structure + 1 byte for the terminating NUL.
+ */
+ char manuf[9]; /* 8 characters */
+ char model[33]; /* 32 characters to fo ATA ident$ */
+ char rev[9]; /* 8 characters */
+ /*
+ * SCSI Serial number is 12 bytes from the main INQUIRY
+ * page, but it may be longer in the Unit Serial Number
+ * VPD page, so save space for up to 20 bytes of it (ATA
+ * serial numbers may be up to 20-bytes long).
+ */
+ char serial[21];
+ uint64_t size_in_bytes;
+} dm_fru_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DM_TYPES_H */
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fault_analyze.c b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fault_analyze.c
new file mode 100644
index 0000000000..e608ee94f7
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fault_analyze.c
@@ -0,0 +1,1701 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <sys/byteorder.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <errno.h>
+#include <utility.h>
+
+#include "util.h"
+#include "sfx4500-disk.h"
+
+/*
+ * The functions defined below are used to query SCSI (or SCSI-like)
+ * disk devices for their Information Exceptions (IE) page via LOG SENSE.
+ * SATA disks in Solaris implement command translation that transforms
+ * the SATA SMART information into the appropriate IE page data.
+ *
+ * The general algorithm for determining if a disk has detected an imminent
+ * failure via the IE mechanism is as follows:
+ *
+ * STEP 1 - INITIALIZATION
+ * 1) Check to see if the IE mechanism is enabled via MODE SENSE for the
+ * IE Control page (page 0x1C), checking the DEXCPT field (1 = IE is
+ * disabled). If it is enabled, goto step 3; else if there was an error
+ * getting the mode page, abort IE processing, otherwise, continue to
+ * step 2.
+ * 2) Enable the IE mechanism by sending a MODE SELECT for page 0x1C
+ * with the DEXCPT = 0, PERF = 1, MRIE = 6, EWASC = 1, TEST = 0,
+ * REPORT COUNT = 0001h, LOGERR = 1 (enable IE, minimize delay associated
+ * with SMART processing, only report IE condition on request,
+ * enable warnings, testing disabled, limit to 1 the number
+ * of times to report each IE, and enable logging of errors).
+ * 3) Check to see if the IE log page is supported by issuing a LOG
+ * SENSE with page == 0x2F. If the page list returned includes the
+ * IE page, examine the log page and ensure that the parameter 0 length
+ * is at least 4 (some drives that are pre-SCSI3 return smaller lengths
+ * with non-sensical values for parameter 0).
+ * Check for the IBM extensions to the IE log page (the first byte of the
+ * vendor-specific area is non-zero if the temperature is present).
+ * and make a note of it.
+ * If there is no support for the IE Log page, we can still check SMART
+ * status by issuing a REQUEST SENSE by itself (since that's how we
+ * configured the MRIE field in the IE Control mode page). The presence
+ * of the IE log page makes life easier by aggregating almost all the
+ * information we need (the ASC/ASCQ of the predictive failure mode and
+ * the temperature information).
+ * 4) Check for self-test logs by issuing a LOG SENSE for page 0x10 and
+ * examining the returned page. If the page makes sense, make a note
+ * of it.
+ * 5) Check for a temperature log page. If it exists, make a note of it.
+ * (Prefer the temperature log page for monitoring because the SCSI-3 spec
+ * specifies an optional threshold temperature parameter (and most
+ * drives that support the temperature log page include the threshold).
+ * [Relying on the IE Log page for temperature constraint information
+ * is not reliable because the threshold information in the IE log
+ * page is an IBM extension and is not present on all drives. Plus,
+ * not many drives actually implement the IE log page.)]
+ * 6) Clear the GLTSD bit in Control mode page 0xA. This will allow the
+ * drive to save each of the log pages described above to nonvolatile
+ * storage. This is essential if the drive is to remember its failures
+ * across power-offs (it would be very bad for a previously-bad drive to
+ * go through another set of failures, just to recognize its badness after
+ * a power cycle). If the MODE SELECT for this operation fails, issue a
+ * warning, but continue anyway.
+ *
+ * STEP 2 - MONITORING
+ * 1) To determine if a predictable failure is imminent, either send the
+ * device an unsolicited REQUEST SENSE command or a LOG SENSE for the
+ * Informational Exceptions page, and use the sense information from
+ * either of the sources to determine if a failure is imminent.
+ * (SK=NO SENSE/ASC=0x5D/ASCQ=[0..0x6C] (real impending failures) or
+ * SK=NO SENSE/ASC=0x5D/ASCQ=0xFF (FALSE impending failure)).
+ * 2) If self-test logs are present, check them. If a self-test occurred
+ * since the last time the monitoring function was called, check to see its
+ * results. If there was a self-test failure, a self-test failure is
+ * returned.
+ * 3) Based on the available temperature information from the drive (either
+ * from the temperature log page or from the temperature information
+ * available on the IE page), determine if the drive has exceeded its
+ * maximum operating temperature. If so, a drive over-temp failure is
+ * returned. (If the drive is within 5% of its maximum operating
+ * temperature, return a warning). If there is no threshold, use the
+ * threshold value passed in.
+ *
+ */
+
+#define RQBUF_LEN 255 /* Length of the request-sense buffer (max) */
+
+static int logpage_ie_param_verify(diskmon_t *diskinfop,
+ struct log_parameter_header *lphp);
+static int logpage_temp_param_verify(diskmon_t *diskinfop,
+ struct log_parameter_header *lphp);
+static int logpage_selftest_param_verify(diskmon_t *diskinfop,
+ struct log_parameter_header *lphp);
+
+static int logpage_ie_param_analyze(diskmon_t *diskinfop,
+ struct log_parameter_header *lphp);
+static int logpage_temp_param_analyze(diskmon_t *diskinfop,
+ struct log_parameter_header *lphp);
+static int logpage_selftest_param_analyze(diskmon_t *diskinfop,
+ struct log_parameter_header *lphp);
+
+
+static struct logpage_validation_entry logpage_validation_list[] = {
+ { LOGPAGE_IE, LOGPAGE_SUPP_IE, PC_CUMULATIVE,
+ "Informational Exceptions", B_TRUE,
+ logpage_ie_param_verify, logpage_ie_param_analyze },
+
+ { LOGPAGE_TEMP, LOGPAGE_SUPP_TEMP, PC_CUMULATIVE,
+ "Temperature", B_TRUE,
+ logpage_temp_param_verify, logpage_temp_param_analyze },
+
+ { LOGPAGE_SELFTEST, LOGPAGE_SUPP_SELFTEST, PC_CUMULATIVE,
+ "Self-test", B_TRUE,
+ logpage_selftest_param_verify, logpage_selftest_param_analyze },
+
+ { 0xFF, 0, 0,
+ NULL, B_FALSE,
+ NULL, NULL }
+};
+
+static char *
+dm_get_disk_logphys(diskmon_t *diskp, int *buflen)
+{
+ char *path, *sep;
+
+ path = (char *)dm_prop_lookup(diskp->props, DISK_PROP_LOGNAME);
+ if (path != NULL) {
+ *buflen = strlen(path) + 1;
+ return (dstrdup(path));
+ }
+
+ path = (char *)dm_prop_lookup(diskp->props, DISK_PROP_DEVPATH);
+
+ assert(path != NULL);
+
+ *buflen = strlen(path) + 1;
+ path = dstrdup(path);
+
+ if ((sep = strchr(path, DEVPATH_MINOR_SEPARATOR)) != NULL)
+ *sep = 0;
+
+ return (path);
+}
+
+static void
+disk_err(diskmon_t *diskinfop, const char *fmt, ...)
+{
+ va_list ap;
+ char *path;
+ int pathlen;
+
+ path = dm_get_disk_logphys(diskinfop, &pathlen);
+
+ log_msg(MM_ERR|MM_SCSI, "ERROR: Disk %s (location: %s): ",
+ path,
+ diskinfop->location);
+
+ va_start(ap, fmt);
+ vcont(MM_ERR|MM_SCSI, fmt, ap);
+ va_end(ap);
+
+ dfree(path, pathlen);
+}
+
+static void
+disk_warn(diskmon_t *diskinfop, const char *fmt, ...)
+{
+ va_list ap;
+ char *path;
+ int pathlen;
+
+ path = dm_get_disk_logphys(diskinfop, &pathlen);
+
+ log_msg(MM_WARN|MM_SCSI, "WARNING: Disk %s (location: %s): ",
+ path,
+ diskinfop->location);
+
+ va_start(ap, fmt);
+ vcont(MM_WARN|MM_SCSI, fmt, ap);
+ va_end(ap);
+
+ dfree(path, pathlen);
+}
+
+static void
+disk_note(diskmon_t *diskinfop, const char *fmt, ...)
+{
+ va_list ap;
+ char *path;
+ int pathlen;
+
+ path = dm_get_disk_logphys(diskinfop, &pathlen);
+
+ log_msg(MM_SCSI, "NOTICE: Disk %s (location: %s): ",
+ path,
+ diskinfop->location);
+
+ va_start(ap, fmt);
+ vcont(MM_SCSI, fmt, ap);
+ va_end(ap);
+
+ dfree(path, pathlen);
+}
+
+static int
+disk_mode_select(int cdb_len, int fd, uchar_t page_code, int options,
+ void *buf, uint_t buflen, struct scsi_ms_hdrs *headers, uint_t *skp,
+ uint_t *ascp, uint_t *ascqp)
+{
+ int result;
+ struct scsi_extended_sense sense;
+ int senselen = sizeof (struct scsi_extended_sense);
+ struct mode_page *mp = (struct mode_page *)buf;
+
+ assert(cdb_len == MODE_CMD_LEN_6 || cdb_len == MODE_CMD_LEN_10);
+ assert(headers->length == cdb_len);
+
+ bzero(&sense, sizeof (struct scsi_extended_sense));
+
+ if (mp->ps) {
+ options |= MODE_SELECT_SP;
+ mp->ps = 0;
+ } else
+ options &= ~MODE_SELECT_SP;
+
+
+ if (cdb_len == MODE_CMD_LEN_6) {
+
+ /* The following fields are reserved during mode select: */
+ headers->h.g0.mode_header.length = 0;
+ headers->h.g0.mode_header.device_specific = 0;
+
+ result = uscsi_mode_select(fd, page_code, options, buf,
+ buflen, &headers->h.g0, &sense, &senselen);
+
+ } else if (cdb_len == MODE_CMD_LEN_10) {
+
+ /* The following fields are reserved during mode select: */
+ headers->h.g1.mode_header.length = 0;
+ headers->h.g1.mode_header.device_specific = 0;
+
+ result = uscsi_mode_select_10(fd, page_code, options, buf,
+ buflen, &headers->h.g1, &sense, &senselen);
+ }
+
+ if (result != 0)
+ scsi_translate_error(&sense, skp, ascp, ascqp);
+
+ return (result);
+}
+
+static int
+disk_mode_sense(int cdb_len, int fd, uchar_t page_code, uchar_t pc,
+ void *buf, uint_t buflen, struct scsi_ms_hdrs *headers, uint_t *skp,
+ uint_t *ascp, uint_t *ascqp)
+{
+ int result;
+ struct scsi_extended_sense sense;
+ int senselen = sizeof (struct scsi_extended_sense);
+
+ assert(cdb_len == MODE_CMD_LEN_6 || cdb_len == MODE_CMD_LEN_10);
+
+ bzero(&sense, sizeof (struct scsi_extended_sense));
+
+ (void) memset(headers, 0, sizeof (struct scsi_ms_hdrs));
+ headers->length = cdb_len;
+
+ if (cdb_len == MODE_CMD_LEN_6) {
+ result = uscsi_mode_sense(fd, page_code, pc, buf, buflen,
+ &headers->h.g0, &sense, &senselen);
+ } else if (cdb_len == MODE_CMD_LEN_10) {
+ result = uscsi_mode_sense_10(fd, page_code, pc, buf, buflen,
+ &headers->h.g1, &sense, &senselen);
+ }
+
+ if (result != 0)
+ scsi_translate_error(&sense, skp, ascp, ascqp);
+
+ return (result);
+}
+
+static int
+disk_request_sense(int fd, uint_t *skp, uint_t *ascp, uint_t *ascqp)
+{
+ struct scsi_extended_sense sense, sensebuf;
+ int senselen = sizeof (struct scsi_extended_sense);
+ int sensebuflen = sizeof (struct scsi_extended_sense);
+ int result;
+
+ bzero(&sense, sizeof (struct scsi_extended_sense));
+ bzero(&sensebuf, sizeof (struct scsi_extended_sense));
+
+ result = uscsi_request_sense(fd, (caddr_t)&sensebuf, sensebuflen,
+ &sense, &senselen);
+
+ if (result == 0)
+ scsi_translate_error(&sensebuf, skp, ascp, ascqp);
+ else
+ scsi_translate_error(&sense, skp, ascp, ascqp);
+
+ return (result);
+}
+
+static int
+scsi_enable_ie(int fd, diskmon_t *diskinfop, uint_t *skp, uint_t *ascp,
+ uint_t *ascqp, int test_mode, int perf_mode, boolean_t *changed)
+{
+ struct info_except_page new_iec_page;
+ struct scsi_ms_hdrs hdrs;
+ fault_monitor_info_t *fip = diskinfop->fmip;
+ int result;
+
+ bzero(&new_iec_page, sizeof (struct info_except_page));
+ bzero(&hdrs, sizeof (struct scsi_ms_hdrs));
+
+ (void) memcpy(&new_iec_page, &fip->iec_current,
+ sizeof (struct info_except_page));
+
+ /*
+ * Enable IE reporting:
+ *
+ * (1) DEXCPT = 0
+ * (2) PERF = <as passed in> (minimize delay due to IE processing)
+ * (3) MRIE = 6 (IE_REPORT_ON_REQUEST)
+ * (4) EWASC = 1
+ * (5) TEST = <as passed in>
+ * (6) REPORT COUNT = 0x0001
+ * (7) LOGERR = 1
+ *
+ */
+
+ new_iec_page.dexcpt = 0;
+ new_iec_page.mrie = IE_REPORT_ON_REQUEST;
+
+ if (IEC_PERF_CHANGEABLE(fip->iec_changeable))
+ new_iec_page.perf = perf_mode ? 1 : 0;
+
+ if (IEC_EWASC_CHANGEABLE(fip->iec_changeable))
+ new_iec_page.ewasc = 1;
+
+ if (IEC_TEST_CHANGEABLE(fip->iec_changeable))
+ new_iec_page.test = test_mode ? 1 : 0;
+
+ if (IEC_RPTCNT_CHANGEABLE(fip->iec_changeable))
+ new_iec_page.report_count = BE_32(1);
+
+ if (IEC_LOGERR_CHANGEABLE(fip->iec_changeable))
+ new_iec_page.logerr = 1;
+
+ /*
+ * Now compare the new mode page with the existing one.
+ * if there's no difference, there's no need for a mode select
+ */
+ if (memcmp(&new_iec_page, &fip->iec_current,
+ MODEPAGE_INFO_EXCPT_LEN) == 0) {
+ *changed = B_FALSE;
+ result = 0;
+ } else {
+
+ (void) memcpy(&hdrs, &fip->hdrs, sizeof (struct scsi_ms_hdrs));
+
+ if ((result = disk_mode_select(fip->mode_length, fd,
+ MODEPAGE_INFO_EXCPT, MODE_SELECT_PF, &new_iec_page,
+ MODEPAGE_INFO_EXCPT_LEN, &hdrs, skp, ascp, ascqp)) == 0) {
+
+ *changed = B_TRUE;
+ }
+ }
+
+ return (result);
+}
+
+static boolean_t
+modepagelist_find(uchar_t *pgdata, uint_t pgdatalen, uchar_t pagecode)
+{
+ uint_t i = 0;
+ struct mode_page *pg;
+ boolean_t found = B_FALSE;
+
+ /*
+ * The mode page list contains all mode pages supported by
+ * the device, one after the other. Since the pages have headers
+ * that describe the page code and their length, we can use pointer
+ * arithmetic to hop to the next page.
+ */
+ while (i < pgdatalen) {
+ pg = (struct mode_page *)&pgdata[i];
+
+ if (pg->code == pagecode) {
+ found = B_TRUE;
+ break;
+ }
+
+ i += MODESENSE_PAGE_LEN(pg);
+ }
+
+ return (found);
+}
+
+/*
+ * Figure out which MODE SENSE/SELECT to use (the 6-byte or 10-byte
+ * version) by executing a MODE SENSE command for a page that should be
+ * implemented by the lun. If the lun doesn't support the Return All Pages
+ * mode page (0x3F), then that information is returned as an invalid field in
+ * cdb error. This function updates the diskinfo structure with the command
+ * length that's supported.
+ */
+static int
+modepages_init(int fd, diskmon_t *diskinfop, uint_t *skeyp,
+ uint_t *ascp, uint_t *ascqp)
+{
+ /*
+ * allpages_buflen is USHRT_MAX - size of the header because some luns
+ * return nothing if the buffer length is too big -- it must be sized
+ * properly (the maximum buffer size is therefore the maximum that
+ * will fit in a 16-bit integer minus the size of the header.)
+ */
+ int allpages_buflen = USHRT_MAX - sizeof (struct scsi_ms_header_g1);
+ uchar_t *allpages = (uchar_t *)dzmalloc(allpages_buflen);
+ fault_monitor_info_t *fip = diskinfop->fmip;
+ struct scsi_ms_header smh;
+ struct scsi_ms_header_g1 smh_g1;
+ struct scsi_extended_sense sense;
+ int resid;
+ int result;
+ uint_t sk, a, aq;
+ uint_t datalength = 0;
+
+ bzero(&smh, sizeof (struct scsi_ms_header));
+ bzero(&smh_g1, sizeof (struct scsi_ms_header_g1));
+ bzero(&sense, sizeof (struct scsi_extended_sense));
+
+ /*
+ * Attempt a mode sense(6). If that fails, try a mode sense(10)
+ *
+ * allpages is allocated to be of the maximum size for either a
+ * mode sense(6) or mode sense(10) MODEPAGE_ALLPAGES response.
+ *
+ * Note that the length passed into uscsi_mode_sense should be
+ * set to the maximum size of the parameter response, which in
+ * this case is UCHAR_MAX - the size of the headers/block descriptors.
+ *
+ */
+
+ resid = sizeof (struct scsi_extended_sense);
+ if ((result = uscsi_mode_sense(fd, MODEPAGE_ALLPAGES, PC_CURRENT,
+ (caddr_t)allpages, UCHAR_MAX - sizeof (struct scsi_ms_header),
+ &smh, &sense, &resid)) == 0) {
+
+ fip->mode_length = MODE_CMD_LEN_6;
+
+ /*
+ * Compute the data length of the page that contains all
+ * mode sense pages. This is a bit tricky because the
+ * format of the response from the lun is:
+ *
+ * header: <length> <medium type byte> <dev specific byte>
+ * <block descriptor length>
+ * [<optional block descriptor>]
+ * data: [<mode page data> <mode page data> ...]
+ *
+ * Since the length field in the header describes the
+ * length of the entire response (including the header,
+ * but NOT including itself (1 or 2 bytes depending on
+ * which mode sense type (6- or 10- byte) being executed).
+ *
+ * So, the data length equals the length value in the header
+ * plus 1 (because the length byte was not included in the
+ * length count), minus [[the sum of the length of the
+ * header and the length of the block descriptor]].
+ */
+
+ datalength = (smh.mode_header.length +
+ sizeof (smh.mode_header.length)) -
+ (sizeof (struct mode_header) +
+ smh.mode_header.bdesc_length);
+
+ } else {
+ scsi_translate_error(&sense, &sk, &a, &aq);
+ if (SCSI_INVALID_OPCODE(sk, a, aq)) {
+
+ resid = sizeof (struct scsi_extended_sense);
+ result = uscsi_mode_sense_10(fd, MODEPAGE_ALLPAGES,
+ PC_CURRENT, (caddr_t)allpages, allpages_buflen,
+ &smh_g1, &sense, &resid);
+
+ if (result == 0) {
+ fip->mode_length = MODE_CMD_LEN_10;
+
+ datalength =
+ (BE_16(smh_g1.mode_header.length) +
+ sizeof (smh_g1.mode_header.length)) -
+ (sizeof (struct mode_header_g1) +
+ BE_16(smh_g1.mode_header.bdesc_length));
+
+ } else
+ fip->mode_length = MODE_CMD_LEN_UNKNOWN;
+ }
+ }
+
+ if (result == 0) {
+
+ /*
+ * One of the sets of the commands (above) succeeded, so now
+ * look for the mode pages we need and record them appropriately
+ */
+
+ if (modepagelist_find(allpages, datalength,
+ MODEPAGE_INFO_EXCPT))
+ fip->mode_pages_supported |= MODEPAGE_SUPP_IEC;
+
+ } else /* result != 0 */
+ scsi_translate_error(&sense, skeyp, ascp, ascqp);
+
+ dfree(allpages, allpages_buflen);
+ return (result);
+}
+
+static int
+load_iec_modepages(int fd, diskmon_t *diskinfop, uint_t *skeyp,
+ uint_t *ascp, uint_t *ascqp)
+{
+ fault_monitor_info_t *fip = diskinfop->fmip;
+ struct scsi_ms_hdrs junk_hdrs;
+ int result;
+
+ (void) memset(&fip->iec_current, 0,
+ sizeof (struct info_except_page));
+ (void) memset(&fip->iec_changeable, 0,
+ sizeof (struct info_except_page));
+
+ if ((result = disk_mode_sense(fip->mode_length, fd,
+ MODEPAGE_INFO_EXCPT, PC_CURRENT, &fip->iec_current,
+ MODEPAGE_INFO_EXCPT_LEN, &fip->hdrs, skeyp, ascp, ascqp))
+ == 0) {
+
+ result = disk_mode_sense(fip->mode_length, fd,
+ MODEPAGE_INFO_EXCPT, PC_CHANGEABLE,
+ &fip->iec_changeable,
+ MODEPAGE_INFO_EXCPT_LEN, &junk_hdrs, skeyp, ascp, ascqp);
+ }
+
+ return (result);
+}
+
+static int
+clear_gltsd(int fd, diskmon_t *diskinfop, uint_t *skp, uint_t *ascp,
+ uint_t *ascqp)
+{
+ fault_monitor_info_t *fip = diskinfop->fmip;
+ struct scsi_ms_hdrs hdrs, junk_hdrs;
+ struct mode_control_scsi3 control_pg_cur, control_pg_chg;
+ int result;
+
+ bzero(&hdrs, sizeof (struct scsi_ms_hdrs));
+ bzero(&control_pg_cur, sizeof (struct mode_control_scsi3));
+ bzero(&control_pg_chg, sizeof (struct mode_control_scsi3));
+
+ result = disk_mode_sense(fip->mode_length, fd,
+ MODEPAGE_CTRL_MODE, PC_CURRENT, &control_pg_cur,
+ MODEPAGE_CTRL_MODE_LEN, &hdrs, skp, ascp, ascqp);
+
+ if (result != 0) {
+
+ disk_note(diskinfop, "Mode sense failed for the "
+ "current Control mode page -- skipping GLTSD "
+ "initialization.\n");
+
+ } else if (control_pg_cur.mode_page.length !=
+ PAGELENGTH_MODE_CONTROL_SCSI3) {
+
+ disk_note(diskinfop, "Disk does not support SCSI-3 "
+ "Control mode page -- skipping GLTSD "
+ "initialization.\n");
+
+ } else if ((result = disk_mode_sense(fip->mode_length, fd,
+ MODEPAGE_CTRL_MODE, PC_CHANGEABLE, &control_pg_chg,
+ MODEPAGE_CTRL_MODE_LEN, &junk_hdrs, skp, ascp, ascqp))
+ != 0) {
+
+ disk_note(diskinfop, "Mode sense failed for the "
+ "changeable Control mode page -- skipping GLTSD "
+ "initialization.\n");
+
+ } else if (control_pg_cur.gltsd && !GLTSD_CHANGEABLE(control_pg_chg)) {
+
+ disk_note(diskinfop, "GLTSD is set and is not "
+ "changeable. This disk will not save log "
+ "parameters implicitly.\n");
+
+ } else if (control_pg_cur.gltsd) {
+ control_pg_cur.gltsd = 0;
+ result = disk_mode_select(fip->mode_length, fd,
+ MODEPAGE_CTRL_MODE, MODE_SELECT_PF, &control_pg_cur,
+ MODEPAGE_CTRL_MODE_LEN, &hdrs, skp, ascp, ascqp);
+ }
+
+ return (result);
+}
+
+static int
+ie_enable_and_save(int fd, diskmon_t *diskinfop, uint_t *skp,
+ uint_t *ascp, uint_t *ascqp, int test_mode, int perf_mode)
+{
+ fault_monitor_info_t *fip = diskinfop->fmip;
+ int return_code = IE_SUCCESS;
+ boolean_t changed;
+
+ /*
+ * Now that we know we can execute a valid mode sense command
+ * (and that the IE control mode page is supported), load the IEC page
+ * so we can check is IE is disabled. If it is disabled and it's
+ * NOT changeable, then we can't do anything else here.
+ */
+ if (load_iec_modepages(fd, diskinfop, skp, ascp, ascqp) != 0) {
+
+ /*
+ * Something went wrong when grabbing the IEC mode page,
+ * so bail out.
+ */
+
+ return_code = IE_OTHER_ERROR;
+
+ } else if ((!IEC_IE_ENABLED(fip->iec_current) &&
+ !IEC_IE_CHANGEABLE(fip->iec_changeable)) ||
+ (fip->iec_current.mrie != IE_REPORT_ON_REQUEST &&
+ !IEC_MRIE_CHANGEABLE(fip->iec_changeable))) {
+
+ /*
+ * We need to be able to change the IE disable bit if
+ * IEs are currently disabled. We also need to be able to
+ * change the MRIE bits if they're not set to the right values,
+ * so if we can't enable IEs properly, we're done here.
+ */
+ return_code = IE_CANNOT_BE_ENABLED;
+
+ } else if (scsi_enable_ie(fd, diskinfop, skp, ascp, ascqp,
+ test_mode, perf_mode, &changed) != 0) {
+
+ return_code = IE_ENABLE_FAILED;
+
+ } else if (changed && load_iec_modepages(fd, diskinfop, skp, ascp,
+ ascqp) != 0) {
+
+ /*
+ * Something went wrong when grabbing the IEC mode page (again),
+ * so bail out.
+ */
+
+ return_code = IE_OTHER_ERROR;
+
+ } else if (!IEC_IE_ENABLED(fip->iec_current)) {
+
+ return_code = IE_ENABLE_DIDNT_STICK;
+
+ } else if (clear_gltsd(fd, diskinfop, skp, ascp, ascqp) != 0) {
+
+ /*
+ * NOTE: Failed to clear the GLTSD bit in the control page;
+ * it's OK if the asc/ascq indicates invalid field in cdb,
+ * meaning this disk doesn't support the GLTSD flag.
+ */
+ if (*ascp != ASC_INVALID_CDB_FIELD)
+ disk_note(diskinfop, "Could not clear the GLTSD bit "
+ "[KEY=0x%x ASC=0x%x ASCQ=0x%x]. Disk "
+ "failures may not be recognized after a power "
+ "cycle.\n", *skp, *ascp, *ascqp);
+ }
+
+ if (return_code == IE_SUCCESS) {
+ /* Save the update interval */
+ fip->update_interval =
+ BE_32(fip->iec_current.interval_timer);
+ }
+
+ return (return_code);
+}
+
+static int
+log_page_to_supp_bit(uchar_t logpage)
+{
+ int i;
+
+ for (i = 0; logpage_validation_list[i].analyze_fn != NULL; i++) {
+ if (logpage_validation_list[i].logpage_code == logpage)
+ return (logpage_validation_list[i].supp_bit);
+ }
+
+ return (0);
+}
+
+
+static logpage_validation_fn_t
+lookup_logpage_validation_fn(uchar_t logpage_code)
+{
+ int i;
+
+ for (i = 0; logpage_validation_list[i].analyze_fn != NULL; i++) {
+ if (logpage_validation_list[i].logpage_code == logpage_code)
+ return (logpage_validation_list[i].validate_fn);
+ }
+
+ return (NULL);
+}
+
+static logpage_analyze_fn_t
+lookup_logpage_analyze_fn(uchar_t logpage_code)
+{
+ int i;
+
+ for (i = 0; logpage_validation_list[i].analyze_fn != NULL; i++) {
+ if (logpage_validation_list[i].logpage_code == logpage_code)
+ return (logpage_validation_list[i].analyze_fn);
+ }
+
+ return (NULL);
+}
+
+static uchar_t
+logpage_pc_for_verify(uchar_t logpage_code)
+{
+ int i;
+
+ for (i = 0; logpage_validation_list[i].analyze_fn != NULL; i++) {
+ if (logpage_validation_list[i].logpage_code == logpage_code)
+ return (logpage_validation_list[i].pc);
+ }
+
+ /* If no PC is specifically defined for this page code, use current */
+ return (PC_CURRENT);
+}
+
+static int
+supported_log_pages(int fd, diskmon_t *diskinfop, uint_t *skp,
+ uint_t *ascp, uint_t *ascqp)
+{
+ /*
+ * buflen is USHRT_MAX - size of the header because some luns
+ * return nothing if the buffer length is too big -- it must be sized
+ * properly (the maximum buffer size is therefore the maximum that
+ * will fit in a 16-bit integer minus the size of the header.)
+ */
+ int buflen = USHRT_MAX - sizeof (struct log_header);
+ struct supported_log_pages *sp = dzmalloc(buflen);
+ struct scsi_extended_sense sense;
+ int resid = sizeof (struct scsi_extended_sense);
+ fault_monitor_info_t *fip = diskinfop->fmip;
+ int result;
+ int bitset;
+
+ bzero(&sense, sizeof (struct scsi_extended_sense));
+
+ if ((result = uscsi_log_sense(fd, LOGPAGE_SUPP_LIST, PC_CUMULATIVE,
+ (caddr_t)sp, buflen, &sense, &resid)) == 0) {
+
+ int pagecount = BE_16(sp->hdr.length);
+ int i = 0;
+
+ while (i < pagecount) {
+
+ bitset = log_page_to_supp_bit(sp->pages[i]);
+
+ fip->log_pages_supported |= bitset;
+
+ i++;
+ }
+ }
+
+ dfree(sp, buflen);
+ if (result != 0)
+ scsi_translate_error(&sense, skp, ascp, ascqp);
+ return (result);
+}
+
+static int
+logpage_ie_param_verify(diskmon_t *diskinfop, struct log_parameter_header *lphp)
+{
+ fault_monitor_info_t *fip = diskinfop->fmip;
+ struct info_excpt_log_param *iep;
+ int result = 0;
+
+ iep = (struct info_excpt_log_param *)lphp;
+
+ /*
+ * Ensure that parameter code 0 has a length of
+ * at LEAST 4 as per the SCSI SPC3 spec. If it
+ * does not, don't use this log page (its format
+ * is unknown).
+ */
+ if (BE_16(lphp->param_code) == LOGPARAM_IE) {
+ if (lphp->length < LOGPARAM_IE_MIN_LEN) {
+
+ disk_note(diskinfop, "IE log page format is unknown -- "
+ "not using it.\n");
+
+ result = -1;
+
+ } else if (lphp->length > LOGPARAM_IE_WITH_TEMP_MIN_LEN) {
+
+ /*
+ * Determine if the vendor-specific area lists a
+ * temperature threshold
+ */
+ if (iep->ex_temp_threshold != 0)
+ fip->extensions |= EXTN_IE_TEMP_THRESHOLD;
+ }
+ }
+
+ return (result);
+}
+
+static int
+logpage_temp_param_verify(diskmon_t *diskinfop,
+ struct log_parameter_header *lphp)
+{
+ int result = 0;
+ fault_monitor_info_t *fip = diskinfop->fmip;
+ ushort_t param_code = BE_16(lphp->param_code);
+ struct temperature_log_param_reftemp *rtp;
+
+ /* The temperature log page has two parameter codes defined: 0 & 1 */
+ /* 0 is current temperature, and 1 is the threshold (but is optional) */
+
+ /*
+ * Don't compare the current temperature to 0xff; we don't flag that
+ * as an error now because the condition that caused the drive not to
+ * be able to report a temperature reading could be transitory.
+ */
+
+ switch (param_code) {
+ case LOGPARAM_TEMP_CURTEMP:
+ if (lphp->length != LOGPARAM_TEMP_CURTEMP_LEN) {
+ result = -1;
+ }
+ break;
+
+ case LOGPARAM_TEMP_REFTEMP:
+ rtp = (struct temperature_log_param_reftemp *)lphp;
+
+ if (lphp->length != LOGPARAM_TEMP_REFTEMP_LEN) {
+ result = -1;
+ } else if (rtp->reference_temp != REFTEMP_INVALID) {
+ fip->extensions |= EXTN_TEMPLOG_TEMP_THRESHOLD;
+ fip->reference_temp = rtp->reference_temp;
+ }
+ break;
+ }
+
+ if (result < 0)
+ disk_note(diskinfop, "Temperature log page format is unknown "
+ "-- not using it.\n");
+
+ return (result);
+}
+
+static int
+logpage_selftest_param_verify(diskmon_t *diskinfop,
+ struct log_parameter_header *lphp)
+{
+ int result = 0;
+ ushort_t param_code = BE_16(lphp->param_code);
+
+ /* Parameter codes range from 0x01-0x14 */
+ if (param_code < LOGPAGE_SELFTEST_MIN_PARAM_CODE ||
+ param_code > LOGPAGE_SELFTEST_MAX_PARAM_CODE) {
+
+ result = -1;
+
+ } else if (lphp->length != LOGPAGE_SELFTEST_PARAM_LEN) {
+
+ disk_note(diskinfop, "Bad parameter length for self-test "
+ "parameter %d\n", lphp->param_code);
+
+ result = -1;
+ }
+
+ return (result);
+}
+
+static fault_monitor_info_t *
+new_disk_fault_info(void)
+{
+ int opts;
+ fault_monitor_info_t *fmi =
+ (fault_monitor_info_t *)dzmalloc(sizeof (fault_monitor_info_t));
+
+ /*
+ * This will always succeed. See sfx4500-disk.c for the default values.
+ */
+ (void) dm_prop_lookup_int(dm_global_proplist(),
+ GLOBAL_PROP_FAULT_OPTIONS, &opts);
+
+ fmi->options = opts;
+
+ assert(pthread_mutex_init(&fmi->fault_data_mutex, NULL) == 0);
+ fmi->fault_list = NULL;
+
+ return (fmi);
+}
+
+void
+free_disk_fault_list(fault_monitor_info_t *fmip)
+{
+ struct disk_fault *cur, *next;
+
+ cur = fmip->fault_list;
+
+ while (cur != NULL) {
+ next = cur->next;
+ if (cur->msg != NULL)
+ dstrfree(cur->msg);
+ dfree(cur, sizeof (struct disk_fault));
+ cur = next;
+ }
+ fmip->fault_list = NULL;
+ fmip->disk_fault_srcs = DISK_FAULT_SOURCE_NONE;
+}
+
+static void
+free_disk_fault_info(fault_monitor_info_t **fmipp)
+{
+ free_disk_fault_list(*fmipp);
+ assert(pthread_mutex_destroy(&(*fmipp)->fault_data_mutex) == 0);
+ dfree(*fmipp, sizeof (fault_monitor_info_t));
+ *fmipp = NULL;
+}
+
+static void
+add_disk_fault(diskmon_t *diskinfop, disk_flt_src_e fltsrc,
+ const char *msg, uchar_t sensekey, uchar_t asc, uchar_t ascq,
+ uint16_t selftestresultcode, int curtemp, int threshtemp)
+{
+ fault_monitor_info_t *fip = diskinfop->fmip;
+ struct disk_fault *flt;
+ struct disk_fault *newflt;
+
+ /* Do not add duplicate faults */
+ if (diskinfop->disk_faults & fltsrc ||
+ fip->disk_fault_srcs & fltsrc)
+ return;
+
+ newflt = (struct disk_fault *)
+ dzmalloc(sizeof (struct disk_fault));
+
+ newflt->fault_src = fltsrc;
+ /* If the message is NULL, look it up by asc/ascq */
+
+ newflt->selftest_code = selftestresultcode;
+ newflt->cur_temp = curtemp;
+ newflt->thresh_temp = threshtemp;
+
+ if (asc != 0 || ascq != 0) {
+ newflt->sense_valid = B_TRUE;
+ newflt->sense_key = sensekey;
+ newflt->asc = asc;
+ newflt->ascq = ascq;
+ if (msg == NULL) {
+ const char *scsi_msg = scsi_asc_ascq_string(asc, ascq);
+ newflt->msg = (scsi_msg == NULL) ? NULL :
+ dstrdup(scsi_msg);
+ } else
+ newflt->msg = dstrdup(msg);
+ } else {
+ newflt->sense_valid = B_FALSE;
+ newflt->msg = (msg == NULL) ? NULL : dstrdup(msg);
+ }
+
+ assert(pthread_mutex_lock(&fip->fault_data_mutex) == 0);
+ fip->disk_fault_srcs |= fltsrc;
+
+ if (fip->fault_list == NULL)
+ fip->fault_list = newflt;
+ else {
+ flt = fip->fault_list;
+
+ while (flt->next != NULL)
+ flt = flt->next;
+
+ flt->next = newflt;
+ }
+ assert(pthread_mutex_unlock(&fip->fault_data_mutex) == 0);
+}
+
+void
+create_fake_faults(diskmon_t *diskp)
+{
+ add_disk_fault(diskp, DISK_FAULT_SOURCE_INFO_EXCPT,
+ "Fake SMART impending failure fault", 0,
+ 0x5D /* IE Failure threshold exceeded */,
+ 0xFF /* false positive */, 0, 0, 0);
+
+ add_disk_fault(diskp, DISK_FAULT_SOURCE_SELFTEST,
+ "Fake self-test failure fault",
+ 0, 0, 0, SELFTEST_FAILURE_SEG_FIRST, 0, 0);
+
+ add_disk_fault(diskp, DISK_FAULT_SOURCE_OVERTEMP,
+ "Fake disk overtemp fault",
+ 0,
+ 0xb /* Warning */,
+ 1 /* specified temperature exceeded */, 0,
+ 0xff /* curtemp */, 0xfe /* threshold */);
+}
+
+static int
+logpage_ie_param_analyze(diskmon_t *diskinfop,
+ struct log_parameter_header *lphp)
+{
+ fault_monitor_info_t *fip = diskinfop->fmip;
+ struct info_excpt_log_param *iep;
+ int result = 0;
+ char buf[MSG_BUFLEN];
+ ushort_t length = BE_16(lphp->length);
+
+ iep = (struct info_excpt_log_param *)lphp;
+
+ if (lphp->param_code == LOGPARAM_IE) {
+ /*
+ * There are two faults that the IE parameter helps
+ * detect -- the general IE predictive failure, and
+ * an overtemp failure (but only if the temperature
+ * threshold information is included.
+ */
+ if (iep->ie_asc != 0) {
+ add_disk_fault(diskinfop, DISK_FAULT_SOURCE_INFO_EXCPT,
+ NULL, INVALID_SENSE_KEY, iep->ie_asc, iep->ie_ascq,
+ 0, 0, 0);
+
+ result = -1;
+ }
+
+ /*
+ * If the length of this parameter includes the temperature
+ * threshold, use it to compare the temperature, but only if
+ * there is no temperature log page supported (or, if there
+ * is a temperature log page but no reference temperature in
+ * the temperature log page).
+ */
+
+ if ((!LOG_PAGE_SUPPORTED(fip, LOGPAGE_SUPP_TEMP) ||
+ !EXTN_SUPPORTED(fip, EXTN_TEMPLOG_TEMP_THRESHOLD)) &&
+ (length > LOGPARAM_IE_WITH_TEMP_MIN_LEN) &&
+ (iep->ex_temp_threshold != 0) &&
+ (iep->ex_temp_threshold != INVALID_TEMPERATURE) &&
+ (iep->last_temp >= iep->ex_temp_threshold)) {
+
+ (void) snprintf(buf, MSG_BUFLEN, "Disk temperature (%d "
+ "celsius) is above the threshold (%d celsius)",
+ iep->last_temp, iep->ex_temp_threshold);
+
+ add_disk_fault(diskinfop, DISK_FAULT_SOURCE_OVERTEMP,
+ buf, INVALID_SENSE_KEY, 0, 0, 0, iep->last_temp,
+ iep->ex_temp_threshold);
+
+ result = -1;
+ }
+ }
+
+ return (result);
+}
+
+static int
+logpage_temp_param_analyze(diskmon_t *diskinfop,
+ struct log_parameter_header *lphp)
+{
+ char buf[MSG_BUFLEN];
+ int result = 0;
+ fault_monitor_info_t *fip = diskinfop->fmip;
+ ushort_t param_code = BE_16(lphp->param_code);
+ struct temperature_log_param_curtemp *ctp =
+ (struct temperature_log_param_curtemp *)lphp;
+
+ /*
+ * If this log page has a reference temperature, it must have
+ * been recorded in the diskinfo structure, so use it
+ * to compare the current temperature reading (if the
+ * reading is valid).
+ */
+
+ /* The temperature log page has two parameter codes defined: 0 & 1 */
+ /* 0 is current temperature, and 1 is the threshold (but is optional) */
+
+ /*
+ * Don't compare the current temperature to 0xff; we don't flag that
+ * as an error now because the condition that caused the drive not to
+ * be able to report a temperature reading could be transitory.
+ */
+
+ if (param_code == LOGPARAM_TEMP_CURTEMP &&
+ ctp->current_temp != INVALID_TEMPERATURE &&
+ EXTN_SUPPORTED(fip, EXTN_TEMPLOG_TEMP_THRESHOLD) &&
+ ctp->current_temp >= fip->reference_temp) {
+
+ (void) snprintf(buf, MSG_BUFLEN, "Disk temperature (%d "
+ "celsius) is above the threshold (%d celsius)",
+ ctp->current_temp, fip->reference_temp);
+
+ add_disk_fault(diskinfop, DISK_FAULT_SOURCE_OVERTEMP,
+ buf, INVALID_SENSE_KEY, 0, 0, 0, ctp->current_temp,
+ fip->reference_temp);
+
+ result = -1;
+ }
+
+ return (result);
+}
+
+static char *
+disk_selftest_result_string(struct selftest_log_parameter *stlp, char *buf,
+ int buflen)
+{
+ const char *s;
+
+ switch (stlp->results) {
+ case SELFTEST_FAILURE_INCOMPLETE:
+ s = "An unknown error occurred while the "
+ "device server was processing the self-test "
+ "and the device server was unable to complete "
+ "the self-test.";
+ (void) snprintf(buf, buflen, s);
+ break;
+
+ case SELFTEST_FAILURE_SEG_UNKNOWN:
+ s = "The self-test completed with a failure in a test "
+ "segment, and the test segment that failed is not known.";
+ (void) snprintf(buf, buflen, s);
+ break;
+
+ case SELFTEST_FAILURE_SEG_FIRST:
+ s = "The first segment of the self-test failed.";
+ (void) snprintf(buf, buflen, s);
+ break;
+
+ case SELFTEST_FAILURE_SEG_SECOND:
+ s = "The second segment of the self-test failed.";
+ (void) snprintf(buf, buflen, s);
+ break;
+
+ case SELFTEST_FAILURE_SEG_OTHER:
+ /* If the test number was 0, the failure segment is unknown */
+ if (stlp->test_number == 0)
+ s = "The self-test failed in an unknown test segment.";
+ else
+ s = "The self-test failed in test segment %d.";
+ (void) snprintf(buf, buflen, s, stlp->test_number);
+ break;
+
+ default:
+ s = "Unknown self-test result code (0x%x (%d))";
+ (void) snprintf(buf, buflen, s, stlp->results, stlp->results);
+ break;
+ }
+
+ return (buf);
+}
+
+static int
+logpage_selftest_param_analyze(diskmon_t *diskinfop,
+ struct log_parameter_header *lphp)
+{
+ struct selftest_log_parameter *stlp =
+ (struct selftest_log_parameter *)lphp;
+ int result = 0;
+ const char *fmt;
+ char buf[MSG_BUFLEN];
+ char tsstring[MSG_BUFLEN];
+ char lbastring[MSG_BUFLEN];
+ char stcause[MSG_BUFLEN];
+ ushort_t param_code = BE_16(lphp->param_code);
+
+ /*
+ * If the self-test failed, log a fault.
+ */
+ if (param_code >= LOGPAGE_SELFTEST_MIN_PARAM_CODE &&
+ param_code <= LOGPAGE_SELFTEST_MAX_PARAM_CODE &&
+ stlp->results >= SELFTEST_FAILURE_INCOMPLETE &&
+ stlp->results <= SELFTEST_FAILURE_SEG_OTHER) {
+
+ uint16_t timestamp = BE_16(stlp->timestamp);
+ uint64_t lbaaddr = BE_64(stlp->lba_of_first_failure);
+
+ fmt = (timestamp == UINT16_MAX) ? ">= %u disk-hours" :
+ "%u disk-hours";
+ (void) snprintf(tsstring, MSG_BUFLEN, fmt, timestamp);
+
+ /* The lba failure field is only valid if it's not all 1's */
+ fmt = (lbaaddr != UINT64_MAX) ? " LBA address of first "
+ "failure: 0x%llx (%llu)" : "";
+ (void) snprintf(lbastring, MSG_BUFLEN, fmt, lbaaddr, lbaaddr);
+
+ (void) snprintf(buf, MSG_BUFLEN, "Disk self-test failed "
+ "[self-test parameter #%d, time of failure: %s%s]: %s",
+ param_code, tsstring, lbastring,
+ disk_selftest_result_string(stlp, stcause, MSG_BUFLEN));
+
+ add_disk_fault(diskinfop, DISK_FAULT_SOURCE_SELFTEST,
+ buf, stlp->sense_key, stlp->asc, stlp->ascq,
+ stlp->results, 0, 0);
+
+ result = -1;
+ }
+
+ return (result);
+}
+
+
+static int
+verify_logpage(uchar_t logpage_code, int fd, diskmon_t *diskinfop,
+ uint_t *skp, uint_t *ascp, uint_t *ascqp)
+{
+ fault_monitor_info_t *fip = diskinfop->fmip;
+ struct log_header *lhp;
+ struct log_parameter_header *lphp;
+ struct scsi_extended_sense sense;
+ logpage_validation_fn_t validate_fn;
+ int buflen;
+ int resid;
+ int log_length;
+ int result = 0;
+ int i = 0;
+ int this_param_len = 0;
+
+ /*
+ * buflen is USHRT_MAX - size of the header because some luns
+ * return nothing if the buffer length is too big -- it must be sized
+ * properly (the maximum buffer size is therefore the maximum that
+ * will fit in a 16-bit integer minus the size of the header.)
+ */
+ buflen = USHRT_MAX - sizeof (struct log_header);
+ resid = sizeof (struct scsi_extended_sense);
+ lhp = dzmalloc(buflen);
+ validate_fn = lookup_logpage_validation_fn(logpage_code);
+ bzero(&sense, sizeof (struct scsi_extended_sense));
+
+ if ((validate_fn != NULL) &&
+ ((result = uscsi_log_sense(fd, logpage_code,
+ logpage_pc_for_verify(logpage_code),
+ (caddr_t)lhp, buflen, &sense, &resid)) == 0) &&
+ ((log_length = BE_16(lhp->length)) > 0)) {
+
+ lphp = (struct log_parameter_header *)(((uchar_t *)lhp) +
+ sizeof (struct log_header));
+
+ while (i < log_length) {
+
+ lphp = (struct log_parameter_header *)
+ (((uchar_t *)lphp) + this_param_len);
+
+ /*
+ * If the validation fn returns a negative value,
+ * that's the signal to clear the supported bit
+ * for this log page and break out of the loop.
+ */
+ if ((*validate_fn)(diskinfop, lphp) < 0) {
+ fip->log_pages_supported &=
+ ~log_page_to_supp_bit(logpage_code);
+ break;
+ }
+
+ this_param_len = lphp->length +
+ sizeof (struct log_parameter_header);
+
+ i += this_param_len;
+ }
+ }
+
+ dfree(lhp, buflen);
+ if (result != 0)
+ scsi_translate_error(&sense, skp, ascp, ascqp);
+ return (result);
+}
+
+static int
+verify_logpages(int fd, diskmon_t *diskinfop, uint_t *skp,
+ uint_t *ascp, uint_t *ascqp)
+{
+ fault_monitor_info_t *fip = diskinfop->fmip;
+ int result = 0;
+ int i;
+
+ for (i = 0; logpage_validation_list[i].analyze_fn != NULL; i++) {
+
+ if ((fip->log_pages_supported &
+ logpage_validation_list[i].supp_bit) == 0) {
+
+ continue;
+ }
+
+ /*
+ * verify_logpage will clear the bit from
+ * log_pages_supported if verification fails
+ * (which means that the page is not usable)
+ */
+ if (verify_logpage(logpage_validation_list[i].logpage_code,
+ fd, diskinfop, skp, ascp, ascqp) != 0) {
+ /*
+ * If something goes wrong here, this is not a fatal
+ * error -- just log the error and continue.
+ */
+ log_warn("Error during %s log page verification: "
+ "KEY=0x%x ASC=0x%x ASCQ=0x%x",
+ logpage_validation_list[i].descr, *skp, *ascp,
+ *ascqp);
+
+ result -= 1;
+ }
+ }
+
+ return (result);
+}
+
+/*
+ * This function calls the analysis function that corresponds to the log page
+ * passed-in. If the analysis function detects a fault in the log page
+ * parameter it was called with, it fills-in the disk_fault structure passed-in
+ * with the fault specifics, and log parameter processing stops.
+ */
+static int
+fault_analyze_logpage(uchar_t logpage_code, int fd, diskmon_t *diskinfop,
+ uint_t *skp, uint_t *ascp, uint_t *ascqp)
+{
+ struct log_header *lhp;
+ struct log_parameter_header *lphp;
+ struct scsi_extended_sense sense;
+ logpage_analyze_fn_t analyze_fn;
+ int buflen;
+ int resid;
+ int log_length;
+ int result = 0;
+ int i = 0;
+ int this_param_len = 0;
+
+ /*
+ * buflen is USHRT_MAX - size of the header because some luns
+ * return nothing if the buffer length is too big -- it must be sized
+ * properly (the maximum buffer size is therefore the maximum that
+ * will fit in a 16-bit integer minus the size of the header.)
+ */
+ buflen = USHRT_MAX - sizeof (struct log_header);
+ resid = sizeof (struct scsi_extended_sense);
+ lhp = dzmalloc(buflen);
+ analyze_fn = lookup_logpage_analyze_fn(logpage_code);
+ bzero(&sense, sizeof (struct scsi_extended_sense));
+
+ if ((analyze_fn != NULL) &&
+ ((result = uscsi_log_sense(fd, logpage_code,
+ logpage_pc_for_verify(logpage_code),
+ (caddr_t)lhp, buflen, &sense, &resid)) == 0) &&
+ ((log_length = BE_16(lhp->length)) > 0)) {
+
+ lphp = (struct log_parameter_header *)(((uchar_t *)lhp) +
+ sizeof (struct log_header));
+
+ while (i < log_length) {
+
+ lphp = (struct log_parameter_header *)
+ (((uchar_t *)lphp) + this_param_len);
+
+ /*
+ * If the analysis fn returns a negative value,
+ * then a disk fault identified with this page
+ * has been identified.
+ */
+ if ((*analyze_fn)(diskinfop, lphp) < 0)
+ disk_warn(diskinfop, "fault found: log page "
+ "0x%x, parameter 0x%x.\n", logpage_code,
+ BE_16(lphp->param_code));
+
+ this_param_len = lphp->length +
+ sizeof (struct log_parameter_header);
+
+ i += this_param_len;
+ }
+ }
+
+ dfree(lhp, buflen);
+ if (result != 0)
+ scsi_translate_error(&sense, skp, ascp, ascqp);
+ return (result);
+}
+
+static int
+fault_analyze_logpages(int fd, diskmon_t *diskinfop, int *failidx,
+ uint_t *skp, uint_t *ascp, uint_t *ascqp)
+{
+ fault_monitor_info_t *fip = diskinfop->fmip;
+ int result = 0;
+ int i;
+
+ for (i = 0; logpage_validation_list[i].analyze_fn != NULL; i++) {
+
+ if ((fip->log_pages_supported &
+ logpage_validation_list[i].supp_bit) == 0) {
+
+ continue;
+ }
+
+ /*
+ * analyze_logpage will return a negative value if something
+ * went wrong during a LOG SENSE of the current log page.
+ */
+ if (fault_analyze_logpage(
+ logpage_validation_list[i].logpage_code, fd, diskinfop,
+ skp, ascp, ascqp) != 0) {
+
+ /*
+ * If something goes wrong here, this is not a fatal
+ * error -- just remember it and continue.
+ */
+ *failidx = i;
+ result -= 1;
+ }
+ }
+
+ return (result);
+}
+
+static int
+scsi_request_sense(int fd, diskmon_t *diskinfop, uint_t *skp,
+ uint_t *ascp, uint_t *ascqp)
+{
+ fault_monitor_info_t *fip = diskinfop->fmip;
+ int result;
+
+ result = disk_request_sense(fd, skp, ascp, ascqp);
+
+ /*
+ * Save the result of a successful REQUEST SENSE
+ * because error information is cleared after it's
+ * sent to the host.
+ */
+ if (result == 0) {
+ fip->last_rs_key = *skp;
+ fip->last_rs_asc = *ascp;
+ fip->last_rs_ascq = *ascqp;
+ }
+
+ return (result);
+}
+
+void
+disk_fault_uninit(diskmon_t *diskinfop)
+{
+ /*
+ * The only thing that consumes memory is the fault list, so free
+ * that now:
+ */
+ if (diskinfop->fmip != NULL) {
+ free_disk_fault_list(diskinfop->fmip);
+ free_disk_fault_info(&diskinfop->fmip);
+ }
+ assert(pthread_mutex_lock(&diskinfop->disk_faults_mutex) == 0);
+ diskinfop->disk_faults = DISK_FAULT_SOURCE_NONE;
+ assert(pthread_mutex_unlock(&diskinfop->disk_faults_mutex) == 0);
+}
+
+int
+disk_fault_init(diskmon_t *diskinfop)
+{
+ fault_monitor_info_t *fip;
+ int fd;
+ char path[MAXPATHLEN];
+ uint_t sense_key = 0, asc = 0, ascq = 0;
+ int return_code = IE_SUCCESS;
+
+ (void) snprintf(path, MAXPATHLEN, "/devices%s",
+ dm_prop_lookup(diskinfop->props, DISK_PROP_DEVPATH));
+
+ if ((fd = open(path, O_RDWR)) < 0) {
+ disk_warn(diskinfop, "disk_fault_init: Error opening disk "
+ "node");
+ return (-1);
+ }
+
+ /* Reset fault-tracking statistics */
+ diskinfop->due = (time_t)0;
+ diskinfop->analysis_generation = 0;
+
+ diskinfop->fmip = new_disk_fault_info();
+
+ fip = diskinfop->fmip;
+
+ /* Initialize key fields: */
+ /* Assume we support no extensions */
+ fip->extensions = 0;
+ /* Assume we support no log pages */
+ fip->log_pages_supported = 0;
+ /* Assume we support no mode pages */
+ fip->mode_pages_supported = 0;
+
+ if (modepages_init(fd, diskinfop, &sense_key, &asc, &ascq)
+ != 0) {
+
+ /*
+ * If the error was an invalid opcode, then mode sense
+ * isn't supported, and, by extension, IE isn't supported.
+ * If the error is "mode page unsupported", then this lun
+ * is equally as useless.
+ */
+ if (SCSI_INVALID_OPCODE(sense_key, asc, ascq) ||
+ MODE_PAGE_UNSUPPORTED(sense_key, asc, ascq))
+ return_code = IE_NOT_SUPPORTED;
+ else {
+ log_err("modepages_init failed: "
+ "KEY=0x%x ASC=0x%x ASCQ=0x%x", sense_key, asc,
+ ascq);
+ return_code = IE_OTHER_ERROR;
+ }
+
+ } else if (!MODE_PAGE_SUPPORTED(fip, MODEPAGE_SUPP_IEC)) {
+
+ disk_note(diskinfop,
+ "No IEC mode page present -- IE (SMART) not supported.\n");
+ return_code = IE_NOT_SUPPORTED;
+
+ } else if ((return_code = ie_enable_and_save(fd, diskinfop, &sense_key,
+ &asc, &ascq, OPT_ENABLED(fip, OPTION_TEST_MODE),
+ OPT_ENABLED(fip, OPTION_PERF_MODE))) != 0) {
+
+ log_err("Error during IEC mode page read/update: "
+ "KEY=0x%x ASC=0x%x ASCQ=0x%x", sense_key, asc, ascq);
+
+ } else if (supported_log_pages(fd, diskinfop, &sense_key, &asc, &ascq)
+ != 0) {
+
+ /*
+ * If there's an error retrieving the list of supported log
+ * pages, then continue with a warning.
+ */
+
+ disk_warn(diskinfop,
+ "Error during LOG SENSE of supported pages: "
+ "KEY=0x%x ASC=0x%x ASCQ=0x%x -- not using any "
+ "log pages for fault monitoring.\n", sense_key, asc, ascq);
+
+ } else {
+ (void) verify_logpages(fd, diskinfop, &sense_key, &asc, &ascq);
+ }
+
+ (void) close(fd);
+
+ return (return_code);
+}
+
+static int
+disk_check_ie_sense(int fd, diskmon_t *diskinfop, uint_t *skp,
+ uint_t *ascp, uint_t *ascqp)
+{
+ int result;
+ uint_t sense_key, asc, ascq;
+
+ if (scsi_request_sense(fd, diskinfop, &sense_key, &asc, &ascq) != 0) {
+
+ *skp = sense_key;
+ *ascp = asc;
+ *ascqp = ascq;
+
+ result = -1;
+ } else {
+
+ /*
+ * If the sense key is NO SENSE, and the ASC is
+ * any nonzero value, then we have an impending failure
+ */
+ if (sense_key == KEY_NO_SENSE && asc != 0) {
+
+ add_disk_fault(diskinfop, DISK_FAULT_SOURCE_INFO_EXCPT,
+ NULL, sense_key, asc, ascq, 0, 0, 0);
+ }
+
+ result = 0;
+ }
+
+ return (result);
+}
+
+/*
+ * Returns n>0 if there are disk faults (n faults)
+ * 0 if there are no disk faults
+ * <0 if there was a problem accessing the disk
+ */
+int
+disk_fault_analyze(diskmon_t *diskinfop)
+{
+ int i, fd;
+ int faults = 0;
+ uint_t sk, asc, ascq;
+ struct disk_fault *flt;
+ boolean_t print_msg;
+ fault_monitor_info_t *fip = diskinfop->fmip;
+ char path[MAXPATHLEN];
+ disk_flt_src_e before_disk_fault_srcs = fip->disk_fault_srcs;
+
+ (void) snprintf(path, MAXPATHLEN, "/devices%s",
+ dm_prop_lookup(diskinfop->props, DISK_PROP_DEVPATH));
+
+ if ((fd = open(path, O_RDWR)) < 0) {
+ disk_err(diskinfop, "disk_fault_analyze: Error opening disk "
+ "node");
+ return (-1);
+ }
+
+ /*
+ * Grab the fault list mutex here because any of the functions below
+ * can add to it.
+ */
+ assert(pthread_mutex_lock(&fip->fault_data_mutex) == 0);
+
+ if (fault_analyze_logpages(fd, diskinfop, &i, &sk, &asc, &ascq) != 0) {
+ disk_warn(diskinfop, "Error during %s log page analysis: "
+ "KEY=0x%x ASC=0x%x ASCQ=0x%x\n",
+ logpage_validation_list[i].descr, sk, asc, ascq);
+ }
+
+ /*
+ * We only need the unsolicited request-sense if we don't have the
+ * IE log page.
+ */
+ if (!LOG_PAGE_SUPPORTED(fip, LOGPAGE_SUPP_IE) &&
+ disk_check_ie_sense(fd, diskinfop, &sk, &asc, &ascq) != 0) {
+
+ disk_err(diskinfop, "Request Sense failure: "
+ "KEY=0x%x ASC=0x%x ASCQ=0x%x\n", sk, asc, ascq);
+ }
+
+ (void) close(fd);
+
+ /*
+ * If any disk faults were added to the diskinfo structure, then
+ * we may have a disk fault condition.
+ */
+
+ if (before_disk_fault_srcs == fip->disk_fault_srcs) {
+ assert(pthread_mutex_unlock(&fip->fault_data_mutex) == 0);
+ return (0);
+ }
+
+
+ flt = fip->fault_list;
+ while (flt != NULL) {
+ if (OPT_ENABLED(fip, OPTION_SELFTEST_ERRS_ARE_FATAL) &&
+ flt->fault_src == DISK_FAULT_SOURCE_SELFTEST) {
+
+ faults++;
+ print_msg = B_TRUE;
+
+ } else if (OPT_ENABLED(fip,
+ OPTION_OVERTEMP_ERRS_ARE_FATAL) &&
+ flt->fault_src == DISK_FAULT_SOURCE_OVERTEMP) {
+
+ faults++;
+ print_msg = B_TRUE;
+
+ } else if (flt->fault_src == DISK_FAULT_SOURCE_INFO_EXCPT) {
+
+ faults++;
+ print_msg = B_TRUE;
+ } else
+ print_msg = B_FALSE;
+
+ if (print_msg)
+ disk_err(diskinfop, "%s\n", flt->msg);
+
+ flt = flt->next;
+ }
+ assert(pthread_mutex_unlock(&fip->fault_data_mutex) == 0);
+
+ return (faults);
+}
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fault_analyze.h b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fault_analyze.h
new file mode 100644
index 0000000000..5a8833462d
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fault_analyze.h
@@ -0,0 +1,154 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _FAULT_ANALYZE_H
+#define _FAULT_ANALYZE_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Definitions for data structures used in the SCSI IE module
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "dm_types.h"
+
+#define MSG_BUFLEN 256 /* Message buffer length */
+
+#define IE_SUCCESS 0
+#define IE_NOT_SUPPORTED 1
+#define IE_CANNOT_BE_ENABLED 2
+#define IE_ENABLE_FAILED 3
+#define IE_ENABLE_DIDNT_STICK 4
+#define IE_OTHER_ERROR 10
+
+#define MODE_CMD_LEN_UNKNOWN 0
+#define MODE_CMD_LEN_6 1
+#define MODE_CMD_LEN_10 2
+
+typedef enum {
+ MODEPAGE_SUPP_IEC = 0x00000001
+} modepage_supp_e;
+
+typedef enum {
+ LOGPAGE_SUPP_IE = 0x00000001,
+ LOGPAGE_SUPP_TEMP = 0x00000002,
+ LOGPAGE_SUPP_SELFTEST = 0x00000004
+} logpage_supp_e;
+
+typedef enum {
+ EXTN_IE_TEMP_THRESHOLD = 0x00000001,
+ EXTN_TEMPLOG_TEMP_THRESHOLD = 0x00000002
+} disk_extension_e;
+
+typedef enum {
+ OPTION_PERF_MODE = 0x00000001,
+ OPTION_TEST_MODE = 0x00000002,
+ OPTION_SELFTEST_ERRS_ARE_FATAL = 0x00000004,
+ OPTION_OVERTEMP_ERRS_ARE_FATAL = 0x00000008
+} disk_option_e;
+
+/* This MUST be a MASK: */
+typedef enum {
+ DISK_FAULT_SOURCE_NONE = 0x00000000,
+ DISK_FAULT_SOURCE_SELFTEST = 0x00000001,
+ DISK_FAULT_SOURCE_OVERTEMP = 0x00000002,
+ DISK_FAULT_SOURCE_INFO_EXCPT = 0x00000004
+} disk_flt_src_e;
+
+#define LOG_PAGE_SUPPORTED(di, supp) ((di)->log_pages_supported & (supp))
+#define MODE_PAGE_SUPPORTED(di, supp) ((di)->mode_pages_supported & (supp))
+#define EXTN_SUPPORTED(di, extn) ((di)->extensions & (extn))
+#define OPT_ENABLED(di, opt) ((di)->options & (opt))
+
+struct scsi_ms_hdrs {
+ int length;
+ union {
+ struct scsi_ms_header g0;
+ struct scsi_ms_header_g1 g1;
+ } h;
+};
+
+/* Yea, this can be a union... */
+struct disk_fault {
+ disk_flt_src_e fault_src;
+ char *msg;
+
+ /* Predictive failure information */
+ boolean_t sense_valid;
+ uchar_t sense_key;
+ uchar_t asc;
+ uchar_t ascq;
+
+ /* Self-test failure information */
+ uint16_t selftest_code;
+
+ /* Temperature information */
+ int cur_temp;
+ int thresh_temp;
+ struct disk_fault *next;
+};
+
+struct diskmon;
+struct fault_monitor_info;
+
+/*
+ * Each of the following validation or analysis functions are called once
+ * for each parameter type in the log page for which the function is
+ * written. If there's a problem with the parameter passed-in, the
+ * function returns a negative number.
+ */
+
+typedef int (*logpage_validation_fn_t)(struct diskmon *,
+ struct log_parameter_header *);
+
+typedef int (*logpage_analyze_fn_t)(struct diskmon *,
+ struct log_parameter_header *);
+
+struct logpage_validation_entry {
+ uchar_t logpage_code;
+ int supp_bit;
+ uchar_t pc;
+ const char *descr;
+ boolean_t enabled;
+ logpage_validation_fn_t validate_fn;
+ logpage_analyze_fn_t analyze_fn;
+};
+
+extern int disk_fault_init(struct diskmon *diskp);
+extern void disk_fault_uninit(struct diskmon *diskp);
+extern int disk_fault_analyze(struct diskmon *diskp);
+extern void free_disk_fault_list(struct fault_monitor_info *fmip);
+extern void create_fake_faults(struct diskmon *diskp);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FAULT_ANALYZE_H */
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fault_mgr.c b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fault_mgr.c
new file mode 100644
index 0000000000..f331a5509f
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fault_mgr.c
@@ -0,0 +1,261 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <assert.h>
+#include <atomic.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include "sfx4500-disk.h"
+#include "fault_mgr.h"
+#include "schg_mgr.h"
+
+/* Fault-polling thread data */
+static pthread_t g_fmt_tid;
+static thread_state_t g_fmt_req_state = TS_NOT_RUNNING;
+static pthread_cond_t g_fmt_cvar = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t g_fmt_mutex = PTHREAD_MUTEX_INITIALIZER;
+static boolean_t g_fmt_spawned = B_FALSE;
+
+static boolean_t
+disk_is_faulty(diskmon_t *diskp)
+{
+ /*
+ * Errors accessing the disk are not counted as faults:
+ */
+ return (disk_fault_analyze(diskp) > 0 ? B_TRUE : B_FALSE);
+}
+
+static void
+setup_fault_injection(diskmon_t *disklistp, int i)
+{
+ uint_t seed;
+
+ while (disklistp != NULL) {
+ /* We just want the low bits of hrtime anyway */
+ seed = (uint_t)gethrtime();
+
+ disklistp->fault_inject_count = (rand_r(&seed) % (i + 1)) + 1;
+
+ log_msg(MM_FAULTMGR, "[%s] Injecting a fault every %u "
+ "analyses.\n", disklistp->location,
+ disklistp->fault_inject_count);
+
+ disklistp = disklistp->next;
+ }
+}
+
+static void
+disk_fault_monitor_analyze_disk(diskmon_t *diskp)
+{
+ atomic_inc_uint(&diskp->analysis_generation);
+
+ log_msg(MM_FAULTMGR, "[%s] Analyzing disk for faults\n",
+ diskp->location);
+
+ if (diskp->fmip && disk_is_faulty(diskp)) {
+
+ diskp->faults_outstanding = B_TRUE;
+ log_msg(MM_FAULTMGR, "[%s] Disk fault(s) detected...\n",
+ diskp->location);
+ dm_state_change(diskp, HPS_FAULTED);
+
+ } else if (diskp->fault_inject_count != 0 &&
+ (diskp->analysis_generation % diskp->fault_inject_count) == 0) {
+
+ diskp->analysis_generation = 0;
+
+ log_msg(MM_FAULTMGR, "[%s] FAULT INJECTED\n", diskp->location);
+
+ create_fake_faults(diskp);
+ dm_state_change(diskp, HPS_FAULTED);
+
+ } else {
+ log_msg(MM_FAULTMGR, "[%s] No faults detected\n",
+ diskp->location);
+ }
+}
+
+/*
+ * The fault monitor thread polls each disk in the disk list, at the
+ * fault polling frequency specified in the global property (or the default
+ * if no such property exists). This thread is also responsible for injecting
+ * fake faults, in accordance with the global fault injection property.
+ *
+ * When the thread starts, it performs a fault analysis on each disk whose
+ * `due' time is 0 (disks that have not yet been analyzed), then sets the
+ * due time to the current time + the fault polling interval.
+ */
+static void
+disk_fault_monitor_thread(void *vdisklistp)
+{
+ diskmon_t *disklistp = (diskmon_t *)vdisklistp;
+ diskmon_t *diskp;
+ time_t fault_polling_interval = (time_t)DEFAULT_FAULT_POLLING_INTERVAL;
+ time_t earliest_due;
+ time_t curtime;
+ time_t nexttime;
+ struct timespec tspec;
+ int i;
+
+ if (dm_prop_lookup_int(dm_global_proplist(), GLOBAL_PROP_FAULT_POLL, &i)
+ == 0)
+ fault_polling_interval = (time_t)i;
+
+ if (dm_prop_lookup_int(dm_global_proplist(), GLOBAL_PROP_FAULT_INJ, &i)
+ == 0 && i > 0) {
+ setup_fault_injection(disklistp, i);
+ }
+
+ assert(pthread_mutex_lock(&g_fmt_mutex) == 0);
+ while (g_fmt_req_state != TS_EXIT_REQUESTED) {
+
+ /*
+ * Analyze all disks that are due for analysis
+ */
+ diskp = disklistp;
+ earliest_due = -1;
+ while (g_fmt_req_state != TS_EXIT_REQUESTED && diskp != NULL) {
+
+ curtime = time(0);
+ assert(pthread_mutex_lock(&diskp->manager_mutex) == 0);
+
+ /*
+ * If the disk is configured (it has a device node
+ * associated with it that we can talk to), and if
+ * there are no faults outstanding (faults that we
+ * previously informed the state-change thread about
+ * but that may not have been consumed yet), and
+ * if we're due for a fault analysis, then do one.
+ */
+ if (DISK_STATE(diskp->state) == HPS_CONFIGURED &&
+ !diskp->faults_outstanding &&
+ (diskp->due == 0 || diskp->due <= curtime)) {
+
+ log_msg(MM_FAULTMGR, "Analyzing disk %s...\n",
+ diskp->location);
+
+ disk_fault_monitor_analyze_disk(diskp);
+ diskp->due = time(0) + fault_polling_interval;
+ }
+
+ /* Keep track of the earliest next due time */
+ if (diskp->due > 0)
+ earliest_due = (earliest_due < 0) ? diskp->due :
+ MIN(earliest_due, diskp->due);
+
+ assert(pthread_mutex_unlock(&diskp->manager_mutex)
+ == 0);
+
+ diskp = diskp->next;
+ }
+
+ /*
+ * earliest_due can be < 0 (if no disks were fault-analyzed)
+ * but it should NEVER be == 0.
+ */
+ if (earliest_due < 0) {
+ nexttime = time(0) + fault_polling_interval;
+ earliest_due = nexttime;
+ } else if (earliest_due == 0) {
+ nexttime = time(0) + fault_polling_interval;
+ log_warn("BUG: earliest_due time is == 0-- resetting "
+ "to %ld\n", nexttime);
+ earliest_due = nexttime;
+ }
+
+ tspec.tv_sec = earliest_due;
+ tspec.tv_nsec = 0;
+ (void) pthread_cond_timedwait(&g_fmt_cvar,
+ &g_fmt_mutex, &tspec);
+ }
+ assert(pthread_mutex_unlock(&g_fmt_mutex) == 0);
+
+ log_msg(MM_FAULTMGR, "Fault monitor polling thread exiting...\n");
+}
+
+static int
+create_fault_monitor_thread(diskmon_t *disklistp)
+{
+ /* fmt_thr_create() is guaranteed to succeed or abort */
+ g_fmt_tid = fmd_thr_create(g_fm_hdl, disk_fault_monitor_thread,
+ disklistp);
+ g_fmt_spawned = B_TRUE;
+
+ return (0);
+}
+
+static void
+collect_fault_monitor_thread(void)
+{
+ if (g_fmt_spawned) {
+
+ g_fmt_req_state = TS_EXIT_REQUESTED;
+ assert(pthread_mutex_lock(&g_fmt_mutex) == 0);
+ assert(pthread_cond_broadcast(&g_fmt_cvar) == 0);
+ assert(pthread_mutex_unlock(&g_fmt_mutex) == 0);
+ fmd_thr_signal(g_fm_hdl, g_fmt_tid);
+ fmd_thr_destroy(g_fm_hdl, g_fmt_tid);
+ g_fmt_req_state = TS_NOT_RUNNING;
+ g_fmt_tid = NULL;
+ g_fmt_spawned = B_FALSE;
+ }
+}
+
+int
+init_fault_manager(cfgdata_t *cfgdatap)
+{
+ int i;
+
+ if (dm_prop_lookup_int(dm_global_proplist(), GLOBAL_PROP_FAULT_POLL, &i)
+ == 0 && i > 0)
+ return (create_fault_monitor_thread(cfgdatap->disk_list));
+ else {
+ g_fmt_spawned = B_FALSE;
+ return (0);
+ }
+}
+
+/*
+ * fault_manager_poke wakes up the fault manager thread so it can
+ * perform initial fault analysis on new disks.
+ */
+void
+fault_manager_poke(void)
+{
+ assert(pthread_mutex_lock(&g_fmt_mutex) == 0);
+ assert(pthread_cond_broadcast(&g_fmt_cvar) == 0);
+ assert(pthread_mutex_unlock(&g_fmt_mutex) == 0);
+}
+
+/*ARGSUSED*/
+void
+cleanup_fault_manager(cfgdata_t *cfgdatap)
+{
+ collect_fault_monitor_thread();
+}
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fault_mgr.h b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fault_mgr.h
new file mode 100644
index 0000000000..5a0a31b099
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fault_mgr.h
@@ -0,0 +1,48 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _FAULT_MGR_H
+#define _FAULT_MGR_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Fault Manager declarations
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int init_fault_manager(cfgdata_t *cfgdatap);
+void fault_manager_poke(void);
+void cleanup_fault_manager(cfgdata_t *cfgdatap);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FAULT_MGR_H */
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fm_disk_events.h b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fm_disk_events.h
new file mode 100644
index 0000000000..b5140c65f8
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/fm_disk_events.h
@@ -0,0 +1,70 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _FM_DISK_EVENTS_H
+#define _FM_DISK_EVENTS_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Event class names and payload member name definitions
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/fm/protocol.h>
+
+/*
+ * SATA Disk EREPORTS and their Payload members
+ */
+#define EREPORT_SATA FM_EREPORT_CLASS "." FM_ERROR_IO ".sata"
+
+#define EREPORT_SATA_PREDFAIL EREPORT_SATA ".predictive-failure"
+#define EV_PAYLOAD_ASC "additional-sense-code"
+#define EV_PAYLOAD_ASCQ "additional-sense-code-qualifier"
+
+#define EREPORT_SATA_OVERTEMP EREPORT_SATA ".over-temperature"
+#define EV_PAYLOAD_CURTEMP "current-temp"
+#define EV_PAYLOAD_THRESH "threshold-temp"
+
+#define EREPORT_SATA_STFAIL EREPORT_SATA ".self-test-failure"
+#define EV_PAYLOAD_STCODE "self-test-result-code"
+
+/*
+ * Disk FAULT events
+ */
+#define FAULT_DISK FM_FAULT_CLASS "." FM_ERROR_IO ".disk"
+#define FAULT_DISK_PREDFAIL FAULT_DISK ".predictive-failure"
+#define FAULT_DISK_OVERTEMP FAULT_DISK ".over-temperature"
+#define FAULT_DISK_STFAIL FAULT_DISK ".self-test-failure"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FM_DISK_EVENTS_H */
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/hotplug_mgr.c b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/hotplug_mgr.c
new file mode 100644
index 0000000000..bef93fa7ef
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/hotplug_mgr.c
@@ -0,0 +1,638 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <sys/sysevent/dr.h>
+#include <sys/sysevent/eventdefs.h>
+#include <sys/sunddi.h> /* for the EC's for DEVFS */
+
+#include <errno.h>
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <time.h>
+#include <pthread.h>
+#include <assert.h>
+
+#include <libsysevent.h>
+#include <sys/sysevent_impl.h>
+
+#include <libnvpair.h>
+#include <config_admin.h>
+
+#include "sfx4500-disk.h"
+#include "hotplug_mgr.h"
+#include "schg_mgr.h"
+
+typedef struct sysevent_event {
+ sysevent_t *evp;
+} sysevent_event_t;
+
+/* Lock guarantees the ordering of the incoming sysevents */
+static pthread_t g_sysev_tid;
+static pthread_mutex_t g_event_handler_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t g_event_handler_cond = PTHREAD_COND_INITIALIZER;
+static qu_t *g_sysev_queue = NULL;
+static thread_state_t g_sysev_thread_state = TS_NOT_RUNNING;
+/*
+ * The sysevent handle is bound to the main sysevent handler
+ * (event_handler), for each of the hotplug sysevents.
+ */
+static sysevent_handle_t *sysevent_handle = NULL;
+
+static void free_sysevent_event(void *p);
+
+static int
+nsleep(int seconds)
+{
+ struct timespec tspec;
+
+ tspec.tv_sec = seconds;
+ tspec.tv_nsec = 0;
+
+ return (nanosleep(&tspec, NULL));
+}
+
+static int
+config_list_ext_poll(int num, char * const *path,
+ cfga_list_data_t **list_array, int *nlist)
+{
+ boolean_t done = B_FALSE;
+ boolean_t timedout = B_FALSE;
+ boolean_t interrupted = B_FALSE;
+ int timeout = 0;
+ int e;
+#define TIMEOUT_MAX 60
+
+ do {
+ switch ((e = config_list_ext(num, path, list_array,
+ nlist, NULL, NULL, NULL, CFGA_FLAG_LIST_ALL))) {
+
+ case CFGA_OK:
+
+ return (CFGA_OK);
+
+ case CFGA_BUSY:
+ case CFGA_SYSTEM_BUSY:
+
+ if (timeout++ >= TIMEOUT_MAX)
+ timedout = B_TRUE;
+ else {
+ if (nsleep(1) < 0)
+ interrupted = (errno == EINTR);
+ }
+ break;
+
+ default:
+ done = B_TRUE;
+ break;
+
+ }
+ } while (!done && !timedout && !interrupted);
+
+ return (e);
+}
+
+/*
+ * Looks up the attachment point's state and returns it in one of
+ * the hotplug states that the state change manager understands.
+ */
+hotplug_state_t
+disk_ap_state_to_hotplug_state(diskmon_t *diskp)
+{
+ hotplug_state_t state = HPS_UNKNOWN;
+ cfga_list_data_t *list_array = NULL;
+ int nlist;
+ char *app = (char *)dm_prop_lookup(diskp->app_props,
+ DISK_AP_PROP_APID);
+ char *ap_path[1];
+ char *devices_app;
+ int len;
+ boolean_t list_valid = B_FALSE;
+
+ assert(app != NULL);
+
+ ap_path[0] = app;
+
+ if (config_list_ext_poll(1, ap_path, &list_array, &nlist)
+ == CFGA_OK) {
+
+ assert(nlist == 1);
+ assert(strcmp(app, list_array[0].ap_phys_id) == 0);
+
+ list_valid = B_TRUE;
+
+ } else {
+ /*
+ * The SATA libcfgadm plugin adds a
+ * /devices to the phys id; to use it, we must
+ * prepend this string before the call.
+ */
+ len = 8 /* strlen("/devices") */ + strlen(app) + 1;
+ devices_app = dmalloc(len);
+
+ (void) snprintf(devices_app, len, "/devices%s",
+ app);
+
+ ap_path[0] = devices_app;
+
+ if (config_list_ext_poll(1, ap_path, &list_array, &nlist)
+ == CFGA_OK) {
+
+ assert(nlist == 1);
+ assert(strcmp(devices_app, list_array[0].ap_phys_id)
+ == 0);
+
+ list_valid = B_TRUE;
+ }
+
+ dfree(devices_app, len);
+ }
+
+ if (list_valid) {
+ /*
+ * The following truth table defines how each state is
+ * computed:
+ *
+ * +----------------------------------------------+
+ * | | o_state | r_state | condition |
+ * | +---------+---------+-----------|
+ * | Absent |Don'tCare|Disc/Empt| Don'tCare |
+ * | Present |Unconfgrd|Connected| unknown |
+ * | Configured |Configred|Connected| Don'tCare |
+ * | Unconfigured |Unconfgrd|Connected| OK |
+ * +--------------+---------+---------+-----------+
+ */
+
+ if (list_array[0].ap_r_state == CFGA_STAT_EMPTY ||
+ list_array[0].ap_r_state == CFGA_STAT_DISCONNECTED)
+ state = HPS_ABSENT;
+ else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED &&
+ list_array[0].ap_o_state == CFGA_STAT_UNCONFIGURED &&
+ list_array[0].ap_cond == CFGA_COND_UNKNOWN)
+ state = HPS_PRESENT;
+ else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED &&
+ list_array[0].ap_o_state == CFGA_STAT_UNCONFIGURED &&
+ list_array[0].ap_cond != CFGA_COND_UNKNOWN)
+ state = HPS_UNCONFIGURED;
+ else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED &&
+ list_array[0].ap_o_state == CFGA_STAT_CONFIGURED)
+ state = HPS_CONFIGURED;
+
+ free(list_array);
+ }
+
+ return (state);
+}
+
+/*
+ * Examine the sysevent passed in and returns the hotplug state that
+ * the sysevent states (or implies, in the case of attachment point
+ * events).
+ */
+static hotplug_state_t
+disk_sysev_to_state(diskmon_t *diskp, sysevent_t *evp)
+{
+ const char *class_name, *subclass;
+ hotplug_state_t state = HPS_UNKNOWN;
+ sysevent_value_t se_val;
+
+ /*
+ * The state mapping is as follows:
+ *
+ * Sysevent State
+ * --------------------------------------------------------
+ * EC_DEVFS/ESC_DEVFS_DEVI_ADD Configured
+ * EC_DEVFS/ESC_DEVFS_DEVI_REMOVE Unconfigured
+ * EC_DR/ESC_DR_AP_STATE_CHANGE *[Absent/Present]
+ *
+ * (The EC_DR event requires a probe of the attachment point
+ * to determine the AP's state if there is no usable HINT)
+ *
+ */
+
+ class_name = sysevent_get_class_name(evp);
+ subclass = sysevent_get_subclass_name(evp);
+
+ if (strcmp(class_name, EC_DEVFS) == 0) {
+ if (strcmp(subclass, ESC_DEVFS_DEVI_ADD) == 0) {
+
+ state = HPS_CONFIGURED;
+
+ } else if (strcmp(subclass, ESC_DEVFS_DEVI_REMOVE) == 0) {
+
+ state = HPS_UNCONFIGURED;
+
+ }
+
+ } else if (strcmp(class_name, EC_DR) == 0 &&
+ strcmp(subclass, ESC_DR_AP_STATE_CHANGE) == 0) {
+
+ if (sysevent_lookup_attr(evp, DR_HINT, SE_DATA_TYPE_STRING,
+ &se_val) == 0 && se_val.value.sv_string != NULL) {
+
+ if (strcmp(se_val.value.sv_string, DR_HINT_INSERT)
+ == 0) {
+
+ state = HPS_PRESENT;
+
+ } else if (strcmp(se_val.value.sv_string,
+ DR_HINT_REMOVE) == 0) {
+
+ state = HPS_ABSENT;
+ }
+
+ }
+
+ /*
+ * If the state could not be determined by the hint
+ * (or there was no hint), ask the AP directly.
+ */
+ if (state == HPS_UNKNOWN)
+ state = disk_ap_state_to_hotplug_state(diskp);
+ }
+
+ return (state);
+}
+
+/*
+ * Returns the diskmon that corresponds to the physical disk path
+ * passed in.
+ */
+static diskmon_t *
+disk_match_by_device_path(diskmon_t *disklistp, const char *dev_path)
+{
+ char *p;
+ int targetid;
+ char tgtnum[MAXNAMELEN];
+ char finalpath[MAXPATHLEN];
+ char devicepath[MAXPATHLEN];
+ assert(disklistp != NULL);
+ assert(dev_path != NULL);
+
+ if (strncmp(dev_path, DEVICES_PREFIX, 8) == 0)
+ dev_path += 8;
+
+ /*
+ * The AP path specified in the configuration properties is
+ * the path to an attachment point minor node whose port number is
+ * equal to the target number on the disk "major" node sent by the
+ * sysevent. To match them, we need to extract the target id and
+ * construct an AP string to compare to the AP path in the diskmon.
+ */
+ while (disklistp != NULL) {
+ char *app = (char *)dm_prop_lookup(disklistp->app_props,
+ DISK_AP_PROP_APID);
+ assert(app != NULL);
+
+ /*
+ * The disk device path is of the form:
+ * /rootnode/.../device/target@tgtid,tgtlun
+ * The AP path is of the form:
+ * /devices/rootnode/.../device:portnum
+ */
+
+ if (strncmp(app, DEVICES_PREFIX, 8) == 0)
+ app += 8;
+
+ /* Get the target number from the disk path: */
+ p = strrchr(dev_path, '/');
+ assert(p != NULL);
+
+ p = strchr(p, '@');
+ assert(p != NULL);
+
+ bzero(tgtnum, MAXNAMELEN);
+ (void) strlcpy(tgtnum, p + 1, MAXNAMELEN);
+
+ if ((p = strchr(tgtnum, ',')) != NULL)
+ *p = 0;
+
+ targetid = strtol(tgtnum, 0, 16);
+
+ /*
+ * Now copy the last part of the disk path and create the
+ * string we want to match.
+ */
+ (void) strlcpy(devicepath, dev_path, MAXPATHLEN);
+ if ((p = strrchr(devicepath, '/')) != NULL)
+ *p = 0;
+ (void) snprintf(finalpath, MAXPATHLEN, "%s:%x",
+ devicepath, targetid);
+
+ if (strcmp(finalpath, app) == 0)
+ return (disklistp);
+
+ disklistp = disklistp->next;
+ }
+ return (NULL);
+}
+
+static diskmon_t *
+disk_match_by_ap_id(diskmon_t *disklistp, const char *ap_id)
+{
+ const char *disk_ap_id;
+ assert(disklistp != NULL);
+ assert(ap_id != NULL);
+
+ /* Match only the device-tree portion of the name */
+ if (strncmp(ap_id, DEVICES_PREFIX, 8 /* strlen("/devices") */) == 0)
+ ap_id += 8;
+
+ while (disklistp != NULL) {
+ disk_ap_id = dm_prop_lookup(disklistp->app_props,
+ DISK_AP_PROP_APID);
+
+ assert(disk_ap_id != NULL);
+
+ if (strcmp(disk_ap_id, ap_id) == 0)
+ return (disklistp);
+
+ disklistp = disklistp->next;
+ }
+ return (NULL);
+}
+
+static diskmon_t *
+match_sysevent_to_disk(diskmon_t *disklistp, sysevent_t *evp)
+{
+ diskmon_t *dmp = NULL;
+ sysevent_value_t se_val;
+ char *class_name = sysevent_get_class_name(evp);
+ char *subclass = sysevent_get_subclass_name(evp);
+
+ se_val.value.sv_string = NULL;
+
+ if (strcmp(class_name, EC_DEVFS) == 0) {
+ /* EC_DEVFS-class events have a `DEVFS_PATHNAME' property */
+ if (sysevent_lookup_attr(evp, DEVFS_PATHNAME,
+ SE_DATA_TYPE_STRING, &se_val) == 0 &&
+ se_val.value.sv_string != NULL) {
+
+ dmp = disk_match_by_device_path(disklistp,
+ se_val.value.sv_string);
+
+ }
+
+ } else if (strcmp(class_name, EC_DR) == 0 &&
+ strcmp(subclass, ESC_DR_AP_STATE_CHANGE) == 0) {
+
+ /* EC_DR-class events have a `DR_AP_ID' property */
+ if (sysevent_lookup_attr(evp, DR_AP_ID, SE_DATA_TYPE_STRING,
+ &se_val) == 0 && se_val.value.sv_string != NULL) {
+
+ dmp = disk_match_by_ap_id(disklistp,
+ se_val.value.sv_string);
+ }
+ }
+
+ if (se_val.value.sv_string)
+ log_msg(MM_HPMGR, "match_sysevent_to_disk: device/ap: %s\n",
+ se_val.value.sv_string);
+
+ return (dmp);
+}
+
+
+/*
+ * The disk hotplug monitor (DHPM) listens for disk hotplug events and calls the
+ * state-change functionality when a disk's state changes. The DHPM listens for
+ * hotplug events via sysevent subscriptions to the following sysevent
+ * classes/subclasses: { EC_DEVFS/ESC_DEVFS_BRANCH_ADD,
+ * EC_DEVFS/ESC_DEVFS_BRANCH_REMOVE, EC_DEVFS/ESC_DEVFS_DEVI_ADD,
+ * EC_DEVFS/ESC_DEVFS_DEVI_REMOVE, EC_DR/ESC_DR_AP_STATE_CHANGE }. Once the
+ * event is received, the device path sent as part of the event is matched
+ * to one of the disks described by the configuration data structures.
+ */
+static void
+dm_process_sysevent(sysevent_t *dupev)
+{
+ char *class_name;
+ char *pub;
+ diskmon_t *diskp;
+
+ class_name = sysevent_get_class_name(dupev);
+ log_msg(MM_HPMGR, "****EVENT: %s %s (by %s)\n", class_name,
+ sysevent_get_subclass_name(dupev),
+ ((pub = sysevent_get_pub_name(dupev)) != NULL) ? pub : "UNKNOWN");
+
+ if (pub)
+ free(pub);
+
+ /*
+ * We will handle this event if the event's target matches one of the
+ * disks we're monitoring
+ */
+ if ((diskp = match_sysevent_to_disk(config_data->disk_list, dupev))
+ != NULL) {
+
+ dm_state_change(diskp, disk_sysev_to_state(diskp, dupev));
+ }
+
+ sysevent_free(dupev);
+}
+
+static void
+dm_fmd_sysevent_thread(void *queuep)
+{
+ qu_t *qp = (qu_t *)queuep;
+ sysevent_event_t *sevevp;
+
+ /* Signal the thread spawner that we're running */
+ assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
+ if (g_sysev_thread_state != TS_EXIT_REQUESTED)
+ g_sysev_thread_state = TS_RUNNING;
+ (void) pthread_cond_broadcast(&g_event_handler_cond);
+ assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
+
+ while (g_sysev_thread_state != TS_EXIT_REQUESTED) {
+ if ((sevevp = (sysevent_event_t *)queue_remove(qp)) == NULL)
+ continue;
+
+ dm_process_sysevent(sevevp->evp);
+
+ free_sysevent_event(sevevp);
+ }
+
+ /* Signal the thread spawner that we've exited */
+ assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
+ g_sysev_thread_state = TS_EXITED;
+ (void) pthread_cond_broadcast(&g_event_handler_cond);
+ assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
+
+ log_msg(MM_HPMGR, "FMD sysevent handler thread exiting...");
+}
+
+static sysevent_event_t *
+new_sysevent_event(sysevent_t *ev)
+{
+ /*
+ * Cannot use dmalloc for this because the thread isn't a FMD-created
+ * thread!
+ */
+ sysevent_event_t *sevevp = malloc(sizeof (sysevent_event_t));
+ sevevp->evp = ev;
+ return (sevevp);
+}
+
+static void
+free_sysevent_event(void *p)
+{
+ /* the sysevent_event was allocated with malloc(): */
+ free(p);
+}
+
+static void
+event_handler(sysevent_t *ev)
+{
+ /* The duplicated sysevent will be freed in the child thread */
+ sysevent_t *dupev = sysevent_dup(ev);
+
+ /*
+ * Add this sysevent to the work queue of our FMA thread so we can
+ * handle the sysevent and use the FMA API (e.g. for memory
+ * allocation, etc.) in the sysevent handler.
+ */
+ queue_add(g_sysev_queue, new_sysevent_event(dupev));
+}
+
+static void
+fini_sysevents(void)
+{
+ sysevent_unsubscribe_event(sysevent_handle, EC_ALL);
+}
+
+static int
+init_sysevents(void)
+{
+ int rv = 0;
+ const char *devfs_subclasses[] = {
+ ESC_DEVFS_DEVI_ADD,
+ ESC_DEVFS_DEVI_REMOVE
+ };
+ const char *dr_subclasses[] = {
+ ESC_DR_AP_STATE_CHANGE
+ };
+
+ if ((sysevent_handle = sysevent_bind_handle(event_handler)) == NULL) {
+ rv = errno;
+ log_err("Could not initialize the hotplug manager ("
+ "sysevent_bind_handle failure");
+ }
+
+ if (sysevent_subscribe_event(sysevent_handle, EC_DEVFS,
+ devfs_subclasses, 2) != 0) {
+
+ log_err("Could not initialize the hotplug manager "
+ "sysevent_subscribe_event(event class = EC_DEVFS) "
+ "failure");
+
+ rv = -1;
+
+ } else if (sysevent_subscribe_event(sysevent_handle, EC_DR,
+ dr_subclasses, 1) != 0) {
+
+ log_err("Could not initialize the hotplug manager "
+ "sysevent_subscribe_event(event class = EC_DR) "
+ "failure");
+
+ /* Unsubscribe from all sysevents in the event of a failure */
+ fini_sysevents();
+
+ rv = -1;
+ }
+
+ return (rv);
+}
+
+/*ARGSUSED*/
+static void
+stdfree(void *p, size_t sz)
+{
+ free(p);
+}
+
+/*
+ * Assumptions: Each disk's current state was determined and stored in
+ * its diskmon_t.
+ */
+hotplug_mgr_init_err_t
+init_hotplug_manager()
+{
+ /* Create the queue to which we'll add sysevents */
+ g_sysev_queue = new_queue(B_TRUE, malloc, stdfree, free_sysevent_event);
+
+ /*
+ * Grab the event handler lock before spawning the thread so we can
+ * wait for the thread to transition to the running state.
+ */
+ assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
+
+ /* Create the sysevent handling thread */
+ g_sysev_tid = fmd_thr_create(g_fm_hdl, dm_fmd_sysevent_thread,
+ g_sysev_queue);
+
+ /* Wait for the thread's acknowledgement */
+ while (g_sysev_thread_state != TS_RUNNING)
+ (void) pthread_cond_wait(&g_event_handler_cond,
+ &g_event_handler_lock);
+ assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
+
+ if (init_sysevents() != 0) {
+ log_warn_e("Error initializing sysevents");
+ return (HPM_ERR_SYSEVENT_INIT);
+ }
+
+ return (0);
+}
+
+void
+cleanup_hotplug_manager()
+{
+ /* Unsubscribe from the sysevents */
+ fini_sysevents();
+
+ /*
+ * Wait for the thread to exit before we can destroy
+ * the event queue.
+ */
+ assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
+ g_sysev_thread_state = TS_EXIT_REQUESTED;
+ queue_add(g_sysev_queue, NULL);
+ while (g_sysev_thread_state != TS_EXITED)
+ (void) pthread_cond_wait(&g_event_handler_cond,
+ &g_event_handler_lock);
+ assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
+ (void) pthread_join(g_sysev_tid, NULL);
+ fmd_thr_destroy(g_fm_hdl, g_sysev_tid);
+
+ /* Finally, destroy the event queue and reset the thread state */
+ queue_free(&g_sysev_queue);
+ g_sysev_thread_state = TS_NOT_RUNNING;
+}
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/hotplug_mgr.h b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/hotplug_mgr.h
new file mode 100644
index 0000000000..7636a3329f
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/hotplug_mgr.h
@@ -0,0 +1,54 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _HOTPLUG_MGR_H
+#define _HOTPLUG_MGR_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Hotplug Manager declarations
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* These errors are OR'able */
+typedef enum {
+ HPM_ERR_POLLTHR_CREATION_FAILURES = 1,
+ HPM_ERR_SYSEVENT_INIT = 2
+} hotplug_mgr_init_err_t;
+
+extern hotplug_state_t disk_ap_state_to_hotplug_state(diskmon_t *diskp);
+extern hotplug_mgr_init_err_t init_hotplug_manager(void);
+extern void cleanup_hotplug_manager(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _HOTPLUG_MGR_H */
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/ipmi_plugin.c b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/ipmi_plugin.c
new file mode 100644
index 0000000000..6800618502
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/ipmi_plugin.c
@@ -0,0 +1,1743 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * IPMI Plugin for the disk hotplug & fault monitor
+ */
+
+#include <sys/types.h>
+#include <sys/byteorder.h>
+#include <sys/stat.h>
+#include <sys/stropts.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <stddef.h>
+#include <stropts.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <pthread.h>
+#include <ctype.h>
+#include <limits.h>
+#include <utility.h>
+#include <libnvpair.h>
+#include <sys/bmc_intf.h>
+#include <libuutil.h>
+
+#include "dm_plugin.h"
+#include "util.h"
+#include "ipmi_plugin.h"
+
+#define BMC_CHECK_UPTIME_INTERVAL 60 /* seconds */
+#define MESSAGE_BUFSIZE 1024
+#define BMC_DEV "/dev/bmc"
+
+#define STRUCT_MIN_SIZE(t, o) offsetof(t, o)
+#define TDZMALLOC(sz) ((sz *)dzmalloc(sizeof (sz)))
+#define TDMALLOC(sz) ((sz *)dmalloc(sizeof (sz)))
+
+/* For the purposes of disk capacity, a <X>B is 1000x, not 1024x */
+#define ONE_KILOBYTE 1000.0
+#define ONE_MEGABYTE (ONE_KILOBYTE * 1000)
+#define ONE_GIGABYTE (ONE_MEGABYTE * 1000)
+#define ONE_TERABYTE (ONE_GIGABYTE * 1000)
+#define ONE_PETABYTE (ONE_TERABYTE * 1000)
+
+/* IPMI Command Code definitions */
+#define IPMI_NETFN_OEM 0x2E
+#define IPMI_CMD_GET_UPTIME 0x08
+#define IPMI_CMD_FRU_UPDATE 0x16
+#define IPMI_CMD_GET_SENSOR_READING 0x2d
+#define IPMI_CMD_SET_SENSOR_READING 0x30
+#define IPMI_CMD_ADD_SEL_ENTRY 0x44
+
+/* IPMI Request types supported by this plugin: */
+#pragma pack(1)
+struct ipmi_sensor_control {
+ uint8_t sensor_number;
+ uint8_t operation; /* ASSERT_OP | DEASSERT_OP | Both */
+#define SC_ASSERT_OP 0x20
+#define SC_DEASSERT_OP 0x08
+ uint8_t sensor_reading; /* UNUSED */
+ /*
+ * The following two fields are stored and sent to the bmc in
+ * little-endian form
+ */
+ uint16_t assert_states;
+ uint16_t deassert_states;
+#define STATE_RESERVED_BITS ((uint16_t)0x8000)
+};
+
+/*
+ * Virtual sensor format for FRU data (Sun OEM)
+ */
+struct ipmi_fru_update {
+ uint8_t global_id;
+ uint8_t disk_number; /* Disk number 0-47 on the X4500 */
+ uint8_t data_length;
+ char d_manuf[16];
+ char d_model[28];
+ char d_serial[20];
+ char d_firmware[8];
+ char d_capacity[16];
+};
+
+struct ipmi_sel_entry {
+ uint16_t recid; /* Don't care -- bmc will overwrite */
+ uint8_t type; /* 0xc0 = OEM SEL Entry */
+#define SEL_TYPE_OEM 0xC0
+ uint32_t timestamp; /* Don't care -- bmc will overwrite */
+ uint8_t manuf_id[3];
+ uint8_t oem_defined[6];
+};
+
+struct ipmi_sensor_reading {
+ uint8_t reading;
+
+ uint8_t reserved : 5,
+ data_unavailable : 1,
+ scanning_enabled : 1,
+ event_messages_enabled : 1;
+
+ uint8_t states_0_7;
+ uint8_t states_8_14; /* High bit is reserved */
+#define sensor_reading_optional_field_start states_0_7
+};
+
+/*
+ * The following structure's members is returned in BIG-ENDIAN form.
+ */
+struct bmc_uptime_info {
+ uint32_t uptime_seconds;
+ uint32_t incarnation;
+};
+#pragma pack()
+/* End of request types supported */
+
+typedef dm_plugin_error_t (*ipmi_packet_setup_fn_t)(nvlist_t *props,
+ void **databpp, int *datablen, void *arg);
+
+struct ipmi_cmd_setup {
+ const char *name;
+ ipmi_packet_setup_fn_t setupfn;
+ uint8_t netfn;
+ uint8_t lun;
+ uint8_t cmd;
+};
+
+typedef struct ipmi_action_handle {
+ uint8_t netfn;
+ uint8_t lun;
+ uint8_t cmd;
+ void *databp;
+ int datablen;
+} ipmi_action_handle_t;
+
+typedef enum {
+ CACHE_ENT_FIRST,
+ CACHE_ENT_FRUINFO,
+ CACHE_ENT_SENSORCTL,
+ CACHE_ENT_LAST
+} bmc_cache_ent_type_t;
+
+typedef struct bmc_cache_ent {
+ bmc_cache_ent_type_t type;
+ union {
+ struct ipmi_fru_update fru_Info;
+ /*
+ * The deasserted field is not used
+ * to cache data in the sensor_control
+ * structure (we cache asserted states):
+ */
+ struct ipmi_sensor_control sensor_Ctl;
+ } u;
+ uu_list_node_t un_node;
+#define fruInfo u.fru_Info
+#define sensorCtl u.sensor_Ctl
+} bmc_cache_ent_t;
+
+typedef struct bmc_replay_list_ent {
+ uint8_t netfn;
+ uint8_t lun;
+ uint8_t cmd;
+ uint8_t *databp;
+ int datablen;
+ uu_list_node_t un_node;
+} bmc_replay_list_ent_t;
+
+/*
+ * The ipmi_mutex protects the bmc state$ and serializes bmc device access
+ */
+static pthread_mutex_t ipmi_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t ipmi_cond = PTHREAD_COND_INITIALIZER;
+static dm_plugin_error_t (*sendrecv_fn)(int fd, uint8_t netfn, uint8_t lun,
+ uint8_t cmd, uint8_t *datap, int datalen, bmc_rsp_t *rspp) = NULL;
+
+
+static int bmc_method(int fd, int *if_type);
+static void dump_request(bmc_req_t *request);
+static void dump_response(bmc_rsp_t *response);
+static dm_plugin_error_t ipmi_bmc_send_cmd_ioctl(int fd, uint8_t netfn,
+ uint8_t lun, uint8_t cmd, uint8_t *datap, int datalen, bmc_rsp_t *rspp);
+static dm_plugin_error_t ipmi_bmc_send_cmd_putmsg(int fd, uint8_t netfn,
+ uint8_t lun, uint8_t cmd, uint8_t *datap, int datalen, bmc_rsp_t *rspp);
+static dm_plugin_error_t ipmi_bmc_send_cmd(uint8_t netfn, uint8_t lun,
+ uint8_t cmd, uint8_t *datap, int datalen, bmc_rsp_t *rspp);
+
+/* IPMI Command Buffer-Setup Functions: */
+static dm_plugin_error_t fru_setupfn(nvlist_t *props, void **databpp,
+ int *datablen, void *arg);
+static dm_plugin_error_t state_setupfn(nvlist_t *props, void **databpp,
+ int *datablen, void *arg);
+static dm_plugin_error_t sel_setupfn(nvlist_t *props, void **databpp,
+ int *datablen, void *arg);
+
+/* BMC Monitor and BMC Cache functions: */
+static int bmc_cache_init(void);
+static void bmc_cache_fini(void);
+static int bmc_state_refresh(boolean_t *refreshed);
+static int bmc_state_refresh_from_cache(void);
+static bmc_cache_ent_t *bmc_state_cache_lookup(uint8_t netfn, uint8_t lun,
+ uint8_t cmd, uint8_t *databp, int datablen);
+static void bmc_state_cache_update(uint8_t netfn, uint8_t lun, uint8_t cmd,
+ uint8_t *databp, int datablen);
+static void bmc_monitor_thread(void *arg);
+
+/* BMC Replay List functions: */
+static int bmc_replay_list_init(void);
+static void bmc_replay_list_fini(void);
+static int bmc_replay_list_execute(void);
+static void bmc_replay_list_add(uint8_t netfn, uint8_t lun, uint8_t cmd,
+ uint8_t *databp, int datablen);
+
+/* IPMI commands used internally */
+static dm_plugin_error_t bmc_get_uptime(uint32_t *uptime, uint32_t *bootgen);
+static dm_plugin_error_t bmc_get_sensor(uint8_t sensor_id, uint16_t *assrtd,
+ boolean_t *updated_flag);
+
+/* plugin entry points: */
+static dm_plugin_error_t ipmi_plugin_init(void);
+static dm_plugin_error_t ipmi_plugin_fru_update(const char *actionString,
+ dm_fru_t *frup);
+static dm_plugin_error_t ipmi_plugin_bind_handle(const char *actionString,
+ dm_plugin_action_handle_t *hdlp);
+static dm_plugin_error_t ipmi_plugin_execute(dm_plugin_action_handle_t hdl);
+static dm_plugin_error_t ipmi_plugin_free_handle(
+ dm_plugin_action_handle_t *hdlp);
+static dm_plugin_error_t ipmi_plugin_fini(void);
+
+dm_plugin_ops_t ipmi_dm_plugin_ops = {
+ DM_PLUGIN_VERSION,
+ ipmi_plugin_init,
+ ipmi_plugin_fru_update,
+ ipmi_plugin_bind_handle,
+ ipmi_plugin_execute,
+ ipmi_plugin_free_handle,
+ ipmi_plugin_fini
+};
+
+static struct ipmi_cmd_setup ipmi_cmd_tab[] = {
+ { "fru", fru_setupfn, IPMI_NETFN_OEM,
+ 0, IPMI_CMD_FRU_UPDATE },
+ { "state", state_setupfn, BMC_NETFN_SE,
+ 0, IPMI_CMD_SET_SENSOR_READING },
+ { "sel", sel_setupfn, BMC_NETFN_STORAGE,
+ 0, IPMI_CMD_ADD_SEL_ENTRY },
+ { NULL, NULL, 0, 0, 0 }
+};
+
+static pthread_t g_bmcmon_tid;
+static boolean_t g_bmc_monitor_active;
+static boolean_t g_bmcmon_done;
+static boolean_t g_need_exec_replay = B_FALSE;
+static uu_list_pool_t *g_uu_pool_cache = NULL;
+static uu_list_pool_t *g_uu_pool_replay = NULL;
+static uu_list_t *g_uu_cachelist = NULL;
+static uu_list_t *g_uu_replaylist = NULL;
+static int g_BMCErrorInjectionRate = 0;
+static int g_bmc_fd = -1;
+
+/*
+ * The textual strings that are used in the actions may be one of the
+ * following forms:
+ *
+ * [1] `fru gid=<n> hdd=<m>'
+ * [2] `sensor id=<x> assert=<y> deassert=<z>'
+ *
+ * The generic parser will take a string and spit out the first token
+ * (e.g. `fru' or `sensor') and an nvlist that contains the key-value
+ * pairs in the rest of the string. The assumption is that there are
+ * no embedded spaces or tabs in the keys or values.
+ */
+
+static boolean_t
+isnumber(const char *str)
+{
+ boolean_t hex = B_FALSE;
+ int digits = 0;
+
+ if (strncasecmp(str, "0x", 2) == 0) {
+ hex = B_TRUE;
+ str += 2;
+ } else if (*str == '-' || *str == '+') {
+ str++;
+ }
+
+ while (*str != 0) {
+ if ((hex && !isxdigit(*str)) ||
+ (!hex && !isdigit(*str))) {
+ return (B_FALSE);
+ }
+
+ str++;
+ digits++;
+ }
+
+ return ((digits == 0) ? B_FALSE : B_TRUE);
+}
+
+static void
+tolowerString(char *str)
+{
+ while (*str != 0) {
+ *str = tolower(*str);
+ str++;
+ }
+}
+
+static boolean_t
+parse_action_string(const char *actionString, char **cmdp, nvlist_t **propsp)
+{
+ char *action;
+ char *tok, *lasts, *eq;
+ int actionlen;
+ boolean_t rv = B_TRUE;
+
+ if (nvlist_alloc(propsp, NV_UNIQUE_NAME, 0) != 0)
+ return (B_FALSE);
+
+ actionlen = strlen(actionString) + 1;
+ action = dstrdup(actionString);
+
+ *cmdp = NULL;
+
+ if ((tok = strtok_r(action, " \t", &lasts)) != NULL) {
+
+ *cmdp = dstrdup(tok);
+
+ while (rv && (tok = strtok_r(NULL, " \t", &lasts)) != NULL) {
+
+ /* Look for a name=val construct */
+ if ((eq = strchr(tok, '=')) != NULL && eq[1] != 0) {
+
+ *eq = 0;
+ eq++;
+
+ /*
+ * Convert token to lowercase to preserve
+ * case-insensitivity, because nvlist doesn't
+ * do case-insensitive lookups
+ */
+ tolowerString(tok);
+
+ if (isnumber(eq)) {
+ /* Integer property */
+
+ if (nvlist_add_uint64(*propsp, tok,
+ strtoull(eq, NULL, 0)) != 0)
+ rv = B_FALSE;
+ } else {
+ /* String property */
+
+ if (nvlist_add_string(*propsp, tok,
+ eq) != 0)
+ rv = B_FALSE;
+ }
+ } else if (eq == NULL) {
+ /* Boolean property */
+ if (nvlist_add_boolean(*propsp, tok) != 0)
+ rv = B_FALSE;
+ } else /* Parse error (`X=' is invalid) */
+ rv = B_FALSE;
+ }
+ } else
+ rv = B_FALSE;
+
+ dfree(action, actionlen);
+ if (!rv) {
+ if (*cmdp) {
+ dstrfree(*cmdp);
+ *cmdp = NULL;
+ }
+ nvlist_free(*propsp);
+ *propsp = NULL;
+ }
+ return (rv);
+}
+
+static ipmi_action_handle_t *
+new_ipmi_action_handle(uint8_t netfn, uint8_t lun, uint8_t cmd, void *databp,
+ int datablen)
+{
+ ipmi_action_handle_t *ret = TDMALLOC(ipmi_action_handle_t);
+
+ ret->netfn = netfn;
+ ret->lun = lun;
+ ret->cmd = cmd;
+ ret->databp = databp;
+ ret->datablen = datablen;
+
+ return (ret);
+}
+
+static void
+bmc_reopen(void)
+{
+ if (g_bmc_fd >= 0)
+ (void) close(g_bmc_fd);
+ if ((g_bmc_fd = open(BMC_DEV, O_RDWR)) <= 0) {
+ log_warn_e("Could not reopen bmc device");
+ }
+}
+
+static void
+free_ipmi_action_handle(ipmi_action_handle_t **hdlpp)
+{
+ ipmi_action_handle_t *hdlp = *hdlpp;
+
+ if (hdlp) {
+ dfree(hdlp->databp, hdlp->datablen);
+ dfree(hdlp, sizeof (ipmi_action_handle_t));
+ *hdlpp = NULL;
+ }
+}
+
+static boolean_t
+cmd_setup_entry_exists(uint8_t netfn, uint8_t lun, uint8_t cmd)
+{
+ int i;
+
+ for (i = 0; ipmi_cmd_tab[i].name != NULL; i++) {
+
+ if (ipmi_cmd_tab[i].netfn == netfn &&
+ ipmi_cmd_tab[i].lun == lun &&
+ ipmi_cmd_tab[i].cmd == cmd)
+ return (B_TRUE);
+ }
+ return (B_FALSE);
+}
+
+static dm_plugin_error_t
+ipmi_exec_action_with_replay(uint8_t netfn, uint8_t lun, uint8_t cmd,
+ uint8_t *databp, int datablen)
+{
+ bmc_rsp_t rsp;
+ dm_plugin_error_t rv;
+
+ (void) bmc_state_refresh(NULL);
+
+ if (g_need_exec_replay)
+ g_need_exec_replay = (bmc_replay_list_execute() != 0);
+
+ if (!g_need_exec_replay) {
+ rv = (ipmi_bmc_send_cmd(netfn, lun, cmd, databp, datablen, &rsp)
+ == DMPE_SUCCESS && rsp.ccode == 0)
+ ? DMPE_SUCCESS : DMPE_FAILURE;
+ }
+
+ /*
+ * If the command failed (or we couldn't execute the command because
+ * we couldn't execute the replay list), and the failure is due to a
+ * timeout error, save the command's result for later replay
+ */
+ if (g_need_exec_replay || (rv == DMPE_FAILURE &&
+ rsp.ccode == BMC_IPMI_COMMAND_TIMEOUT)) {
+
+ /*
+ * Fake the return value as success (since we queued the
+ * command for later execution).
+ */
+ rv = DMPE_SUCCESS;
+ bmc_replay_list_add(netfn, lun, cmd, databp, datablen);
+ g_need_exec_replay = B_TRUE;
+
+ } else if (!g_need_exec_replay && rv == DMPE_SUCCESS) {
+
+ /* Apply the command to the bmc state$ */
+ bmc_state_cache_update(netfn, lun, cmd, databp, datablen);
+ }
+
+ return (rv);
+}
+
+static dm_plugin_error_t
+exec_action_handle(ipmi_action_handle_t *hdlp)
+{
+ dm_plugin_error_t rv;
+
+ /*
+ * Sanity check this handle -- the netfn/lun/cmd should match one
+ * of those defined in the ipmi_cmd_tab:
+ */
+ if (!cmd_setup_entry_exists(hdlp->netfn, hdlp->lun, hdlp->cmd)) {
+ log_warn("Possible corrupt handle @%p (netfn/lun/cmd does "
+ "not match any known commands.\n", (void *)hdlp);
+ return (DMPE_FAILURE);
+ }
+
+ assert(pthread_mutex_lock(&ipmi_mutex) == 0);
+
+ rv = ipmi_exec_action_with_replay(hdlp->netfn, hdlp->lun, hdlp->cmd,
+ hdlp->databp, hdlp->datablen);
+
+ assert(pthread_mutex_unlock(&ipmi_mutex) == 0);
+
+ return (rv);
+}
+
+static dm_plugin_error_t
+action_do(const char *actionString, void *arg, boolean_t exec,
+ ipmi_action_handle_t **hdlpp)
+{
+ nvlist_t *props;
+ char *cmd;
+ int found_index;
+ int datablen, i;
+ void *databp;
+ uint8_t netfn, lun, cmdno;
+ dm_plugin_error_t rv = DMPE_FAILURE; /* Be pessimistic */
+
+ if (parse_action_string(actionString, &cmd, &props)) {
+ for (found_index = -1, i = 0;
+ found_index == -1 && ipmi_cmd_tab[i].name != NULL; i++) {
+ if (strcasecmp(cmd, ipmi_cmd_tab[i].name) == 0) {
+ assert(ipmi_cmd_tab[i].setupfn != NULL);
+ rv = ipmi_cmd_tab[i].setupfn(props,
+ &databp, &datablen, arg);
+ found_index = i;
+ }
+ }
+
+ dstrfree(cmd);
+ nvlist_free(props);
+
+ netfn = ipmi_cmd_tab[found_index].netfn;
+ lun = ipmi_cmd_tab[found_index].lun;
+ cmdno = ipmi_cmd_tab[found_index].cmd;
+
+ if (exec && found_index != -1 && rv == DMPE_SUCCESS) {
+
+ assert(pthread_mutex_lock(&ipmi_mutex) == 0);
+
+ rv = ipmi_exec_action_with_replay(netfn, lun,
+ cmdno, databp, datablen);
+
+ assert(pthread_mutex_unlock(&ipmi_mutex) == 0);
+
+ dfree(databp, datablen);
+
+ } else if (found_index != -1 && rv == DMPE_SUCCESS) {
+ assert(hdlpp != NULL);
+
+ *hdlpp = new_ipmi_action_handle(netfn, lun, cmdno,
+ databp, datablen);
+ }
+ }
+
+ return (rv);
+}
+
+static dm_plugin_error_t
+fru_setupfn(nvlist_t *props, void **databpp, int *datablen,
+ void *arg)
+{
+ uint64_t gid, hdd;
+ struct ipmi_fru_update *fup;
+ char *buf;
+ dm_fru_t *frup = (dm_fru_t *)arg;
+
+ /* We need 2 properties: `gid' and `hdd': */
+ if (nvlist_lookup_uint64(props, "gid", &gid) != 0 ||
+ nvlist_lookup_uint64(props, "hdd", &hdd) != 0) {
+ return (DMPE_FAILURE);
+ }
+
+ fup = TDZMALLOC(struct ipmi_fru_update);
+ buf = (char *)dzmalloc(sizeof (fup->d_capacity) + 1);
+
+ *datablen = sizeof (struct ipmi_fru_update);
+ *databpp = fup;
+
+ fup->global_id = (uint8_t)gid;
+ fup->disk_number = (uint8_t)hdd;
+ fup->data_length = sizeof (fup->d_manuf) + sizeof (fup->d_model) +
+ sizeof (fup->d_serial) + sizeof (fup->d_firmware) +
+ sizeof (fup->d_capacity);
+ (void) memcpy(fup->d_manuf, frup->manuf,
+ MIN(sizeof (fup->d_manuf), sizeof (frup->manuf)));
+ (void) memcpy(fup->d_model, frup->model,
+ MIN(sizeof (fup->d_model), sizeof (frup->model)));
+ (void) memcpy(fup->d_serial, frup->serial,
+ MIN(sizeof (fup->d_serial), sizeof (frup->serial)));
+ (void) memcpy(fup->d_firmware, frup->rev,
+ MIN(sizeof (fup->d_firmware), sizeof (frup->rev)));
+ /*
+ * Print the size of the disk to a temporary buffer whose size is
+ * 1 more than the size of the buffer in the ipmi request data
+ * structure, so we can get the full 8 characters (instead of 7 + NUL)
+ */
+ (void) snprintf(buf, sizeof (fup->d_capacity) + 1,
+ "%.1f%s",
+ frup->size_in_bytes >= ONE_PETABYTE ?
+ (frup->size_in_bytes / ONE_PETABYTE) :
+ (frup->size_in_bytes >= ONE_TERABYTE ?
+ (frup->size_in_bytes / ONE_TERABYTE) :
+ (frup->size_in_bytes >= ONE_GIGABYTE ?
+ (frup->size_in_bytes / ONE_GIGABYTE) :
+ (frup->size_in_bytes >= ONE_MEGABYTE ?
+ (frup->size_in_bytes / ONE_MEGABYTE) :
+ (frup->size_in_bytes / ONE_KILOBYTE)))),
+
+ frup->size_in_bytes >= ONE_PETABYTE ? "PB" :
+ (frup->size_in_bytes >= ONE_TERABYTE ? "TB" :
+ (frup->size_in_bytes >= ONE_GIGABYTE ? "GB" :
+ (frup->size_in_bytes >= ONE_MEGABYTE ? "MB" :
+ "KB"))));
+ (void) memcpy(fup->d_capacity, buf, sizeof (fup->d_capacity));
+
+ dfree(buf, sizeof (fup->d_capacity) + 1);
+ return (DMPE_SUCCESS);
+}
+
+/*ARGSUSED*/
+static dm_plugin_error_t
+state_setupfn(nvlist_t *props, void **databpp, int *datablen,
+ void *arg)
+{
+ uint64_t assertmask = 0, deassertmask = 0, sid;
+ boolean_t am_present, dam_present;
+ struct ipmi_sensor_control *scp;
+
+ /* We need at least 2 properties: `sid' and (`amask' || `dmask'): */
+ am_present = nvlist_lookup_uint64(props, "amask", &assertmask) == 0;
+ dam_present = nvlist_lookup_uint64(props, "dmask", &deassertmask) == 0;
+
+ if (nvlist_lookup_uint64(props, "sid", &sid) != 0 ||
+ (!am_present && !dam_present)) {
+ return (DMPE_FAILURE);
+ }
+
+ if (sid > UINT8_MAX) {
+ log_warn("IPMI Plugin: Invalid sensor id `0x%" PRIx64 "'.\n",
+ sid);
+ return (DMPE_FAILURE);
+ } else if (assertmask > UINT16_MAX) {
+ log_warn("IPMI Plugin: Invalid assertion mask `0x%" PRIx64
+ "'.\n", assertmask);
+ return (DMPE_FAILURE);
+ } else if (assertmask > UINT16_MAX) {
+ log_warn("IPMI Plugin: Invalid deassertion mask `0x%" PRIx64
+ "'.\n", deassertmask);
+ return (DMPE_FAILURE);
+ }
+
+ scp = TDZMALLOC(struct ipmi_sensor_control);
+
+ scp->sensor_number = (uint8_t)sid;
+ scp->operation = (am_present ? SC_ASSERT_OP : 0) |
+ (dam_present ? SC_DEASSERT_OP : 0);
+ scp->assert_states = (uint16_t)assertmask;
+ scp->deassert_states = (uint16_t)deassertmask;
+
+ *datablen = sizeof (struct ipmi_sensor_control);
+ *databpp = scp;
+
+ return (DMPE_SUCCESS);
+}
+
+/*ARGSUSED*/
+static dm_plugin_error_t
+sel_setupfn(nvlist_t *props, void **databpp, int *datablen,
+ void *arg)
+{
+ uint64_t oem_data, manuf_id;
+ struct ipmi_sel_entry *sep;
+
+ /* We need 2 properties: `oem' and `manu': */
+ if (nvlist_lookup_uint64(props, "oem", &oem_data) != 0 ||
+ nvlist_lookup_uint64(props, "manu", &manuf_id) != 0) {
+
+ return (DMPE_FAILURE);
+ }
+
+ if ((manuf_id & ~0xFFFFFFULL) != 0) {
+ log_warn("IPMI Plugin: Invalid manuf field `0x%" PRIx64 "'.\n",
+ manuf_id);
+ return (DMPE_FAILURE);
+ } else if ((oem_data & ~0xFFFFFFFFFFFFULL) != 0) {
+ log_warn("IPMI Plugin: Invalid oemd field `0x%" PRIx64
+ "'.\n", oem_data);
+ return (DMPE_FAILURE);
+ }
+
+ sep = TDZMALLOC(struct ipmi_sel_entry);
+
+ sep->type = SEL_TYPE_OEM;
+ sep->manuf_id[0] = (uint8_t)(manuf_id & 0xFFULL);
+ sep->manuf_id[1] = (uint8_t)((manuf_id & 0xFF00ULL) >> 8);
+ sep->manuf_id[2] = (uint8_t)((manuf_id & 0xFF0000ULL) >> 16);
+ sep->oem_defined[0] = (uint8_t)((oem_data & 0xFFULL) >> 8);
+ sep->oem_defined[1] = (uint8_t)((oem_data & 0xFF00ULL) >> 16);
+ sep->oem_defined[2] = (uint8_t)((oem_data & 0xFF0000ULL) >> 24);
+ sep->oem_defined[3] = (uint8_t)((oem_data & 0xFF000000ULL) >> 32);
+ sep->oem_defined[4] = (uint8_t)((oem_data & 0xFF00000000ULL) >> 40);
+ sep->oem_defined[5] = (uint8_t)((oem_data & 0xFF0000000000ULL) >> 48);
+
+ *datablen = sizeof (struct ipmi_sel_entry);
+ *databpp = sep;
+
+ return (DMPE_SUCCESS);
+}
+
+static dm_plugin_error_t
+bmc_get_sensor(uint8_t sensor_id, uint16_t *assrtd, boolean_t *updated_flag)
+{
+ dm_plugin_error_t rv;
+ bmc_rsp_t rsp;
+ struct ipmi_sensor_reading *srp;
+
+ rv = ipmi_bmc_send_cmd(IPMI_NETFN_OEM, 0, IPMI_CMD_GET_SENSOR_READING,
+ &sensor_id, 1, &rsp);
+
+ /* The command must return precisely the size of the data we expect */
+ if (rsp.ccode ||
+ rsp.datalength > sizeof (struct ipmi_sensor_reading) ||
+ rsp.datalength < STRUCT_MIN_SIZE(struct ipmi_sensor_reading,
+ sensor_reading_optional_field_start))
+ rv = DMPE_FAILURE;
+
+ srp = (struct ipmi_sensor_reading *)&rsp.data[0];
+
+ if (rv == DMPE_SUCCESS &&
+ rsp.datalength == sizeof (struct ipmi_sensor_reading) &&
+ !srp->data_unavailable && srp->scanning_enabled) {
+
+ if (assrtd) {
+ *assrtd = (srp->states_8_14 << 8) | srp->states_0_7;
+ if (updated_flag)
+ *updated_flag = B_TRUE;
+ }
+ }
+ return (rv);
+}
+
+static dm_plugin_error_t
+bmc_get_uptime(uint32_t *uptime, uint32_t *bootgen)
+{
+ dm_plugin_error_t rv;
+ uint8_t junk = 0;
+ bmc_rsp_t rsp;
+ struct bmc_uptime_info *utinfop;
+
+ rv = ipmi_bmc_send_cmd(IPMI_NETFN_OEM, 0, IPMI_CMD_GET_UPTIME, &junk,
+ 1, &rsp);
+
+ /* The command must return precisely the size of the data we expect */
+ if (rsp.ccode ||
+ rsp.datalength != sizeof (struct bmc_uptime_info))
+ rv = DMPE_FAILURE;
+
+ if (rv == DMPE_SUCCESS) {
+ utinfop = (struct bmc_uptime_info *)&rsp.data[0];
+ if (uptime)
+ *uptime = BE_32(utinfop->uptime_seconds);
+ if (bootgen)
+ *bootgen = BE_32(utinfop->incarnation);
+ }
+ return (rv);
+}
+
+/* ****** B M C R E P L A Y L I S T I M P L E M E N T A T I O N ****** */
+
+/*
+ * The reasoning behind the replay list is to try to ensure that commands are
+ * reliably sent to the BMC. In the case of the replay list, any commands that
+ * fail because they timed out are added tothe replay list. Then, the next time
+ * a command is attempted, the replay list is sent to the BMC first, then the
+ * new command (to preserve ordering). Currently, the only commands that are
+ * supported by this plugin are write-oriented commands, where information is
+ * sent to the BMC. If, if the future, read-oriented commands are desired,
+ * The replay mechanism will need to be enhanced to force all pending commands
+ * in the replay list out to the BMC before executing the read-oriented
+ * command (similar to a write cache that's flushed when a read is requested).
+ */
+
+static void
+bmc_replay_list_ent_destroy(bmc_replay_list_ent_t *p)
+{
+ if (p->databp)
+ dfree(p->databp, p->datablen);
+ dfree(p, sizeof (bmc_replay_list_ent_t));
+}
+
+static int
+bmc_replay_list_init(void)
+{
+ if ((g_uu_pool_replay = uu_list_pool_create(
+ "bmc_replay_list_pool", sizeof (bmc_replay_list_ent_t),
+ offsetof(bmc_replay_list_ent_t, un_node), NULL, 0)) == NULL)
+ return (DMPE_FAILURE);
+
+ if ((g_uu_replaylist = uu_list_create(g_uu_pool_replay, NULL, 0))
+ == NULL) {
+ uu_list_pool_destroy(g_uu_pool_replay);
+ return (DMPE_FAILURE);
+ }
+
+ return (DMPE_SUCCESS);
+}
+
+static void
+bmc_replay_list_fini(void)
+{
+ void *cookie = NULL;
+ bmc_replay_list_ent_t *p;
+
+ while ((p = (bmc_replay_list_ent_t *)uu_list_teardown(g_uu_replaylist,
+ &cookie)) != NULL) {
+ bmc_replay_list_ent_destroy(p);
+ }
+
+ uu_list_destroy(g_uu_replaylist);
+ uu_list_pool_destroy(g_uu_pool_replay);
+ g_uu_replaylist = NULL;
+ g_uu_pool_replay = NULL;
+}
+
+/*
+ * The caller must hold the ipmi_mutex!
+ */
+static void
+bmc_replay_list_add(uint8_t netfn, uint8_t lun, uint8_t cmd, uint8_t *databp,
+ int datablen)
+{
+ bmc_replay_list_ent_t *p = TDMALLOC(bmc_replay_list_ent_t);
+
+ p->netfn = netfn;
+ p->lun = lun;
+ p->cmd = cmd;
+ /*
+ * Make a deep copy of the data buffer, since we can't assume
+ * anything about when it will be deallocated.
+ */
+ if (datablen > 0) {
+ p->databp = (uint8_t *)dmalloc(datablen);
+ (void) memcpy(p->databp, databp, datablen);
+ }
+ p->datablen = datablen;
+
+ assert(g_uu_pool_replay != NULL);
+ assert(g_uu_replaylist != NULL);
+ uu_list_node_init(p, &p->un_node, g_uu_pool_replay);
+ /* The replay list is a queue, so add to its tail: */
+ (void) uu_list_insert_before(g_uu_replaylist, NULL, p);
+}
+
+/*
+ * The caller must hold the ipmi_mutex!
+ *
+ * Returns < 0 if the replay list should be executed at a later time
+ * (due to transient errors)
+ */
+static int
+bmc_replay_list_execute(void)
+{
+ uu_list_walk_t *walkp;
+ bmc_replay_list_ent_t *p = NULL;
+ boolean_t timedout_err = B_FALSE;
+ bmc_rsp_t rsp;
+ dm_plugin_error_t rv;
+
+ if ((walkp = uu_list_walk_start(g_uu_replaylist, 0)) == NULL)
+ return (-1);
+
+ /*
+ * On the first timeout error, abort the replay; We cannot execute
+ * commands later in the list because they may depend on the state
+ * set by earlier commands. We'll retry the command that failed
+ * later. (Note that non-timeout-related failures do not cause
+ * aborts because the assumption is that the original command caller
+ * would not behave differently if a command were to fail.) If this
+ * assumption does not remain valid in the future, an enhancement to
+ * the plugin API would be required to introduce a synchronous flag
+ * that would result in the blocking of the calling thread until
+ * BOTH the replay list is fully executed AND the user's current
+ * command is executed (at which point the status can be examined
+ * by the caller).
+ */
+ while (!timedout_err && (p = uu_list_walk_next(walkp)) != NULL) {
+ rv = ipmi_bmc_send_cmd(p->netfn, p->lun, p->cmd, p->databp,
+ p->datablen, &rsp);
+
+ if (rv == DMPE_SUCCESS ||
+ (rv == DMPE_FAILURE &&
+ rsp.ccode != BMC_IPMI_COMMAND_TIMEOUT)) {
+
+ if (rsp.ccode != 0) {
+ log_msg(MM_PLUGIN, "ipmi plugin: netfn 0x%x "
+ "cmd 0x%x ccode=0x%x\n", p->netfn, p->cmd,
+ rsp.ccode);
+ }
+ if (rv == DMPE_SUCCESS) {
+ /* Add the command to the bmc state$ */
+ bmc_state_cache_update(p->netfn, p->lun, p->cmd,
+ p->databp, p->datablen);
+ }
+ uu_list_remove(g_uu_replaylist, p);
+ bmc_replay_list_ent_destroy(p);
+
+ } else if (rv == DMPE_FAILURE &&
+ rsp.ccode == BMC_IPMI_COMMAND_TIMEOUT) {
+
+ timedout_err = B_TRUE;
+ }
+ }
+
+ uu_list_walk_end(walkp);
+ return (timedout_err ? -1 : 0);
+}
+
+/* ************** B M C C A C H E I M P L E M E N T A T I O N ************* */
+
+/*
+ * The reasoning behind the cache is to maintain a mirror of the BMC's state
+ * as it pertains to the commands that were sent from the plugin. For Sun's
+ * BMC implementations, the sensor and FRU information is not currently
+ * preserved when the BMC (or service processor) is reset (or rebooted). To
+ * maintain consistency from the user/administrator's perspective, once the
+ * BMC comes back online after a reset, the information from the state cache
+ * is sent, all at once, in particular order, to the BMC.
+ */
+
+static int
+bmc_cache_init(void)
+{
+ if ((g_uu_pool_cache = uu_list_pool_create(
+ "bmc_cache_entry_pool", sizeof (bmc_cache_ent_t),
+ offsetof(bmc_cache_ent_t, un_node), NULL, 0)) == NULL)
+ return (DMPE_FAILURE);
+
+ if ((g_uu_cachelist = uu_list_create(g_uu_pool_cache, NULL, 0))
+ == NULL) {
+ uu_list_pool_destroy(g_uu_pool_cache);
+ return (DMPE_FAILURE);
+ }
+
+ return (DMPE_SUCCESS);
+}
+
+static void
+bmc_cache_fini(void)
+{
+ void *cookie = NULL;
+ void *p;
+
+ while ((p = uu_list_teardown(g_uu_cachelist, &cookie)) != NULL)
+ dfree(p, sizeof (bmc_cache_ent_t));
+
+ uu_list_destroy(g_uu_cachelist);
+ uu_list_pool_destroy(g_uu_pool_cache);
+
+ g_uu_cachelist = NULL;
+ g_uu_pool_cache = NULL;
+}
+
+static void
+bmc_cache_member_init_sensorctl(bmc_cache_ent_t *p, void *databp)
+{
+ struct ipmi_sensor_control *tgt;
+ uint16_t assrtd;
+ boolean_t was_assrtd_updated = B_FALSE;
+
+ tgt = (struct ipmi_sensor_control *)databp;
+
+ /*
+ * operation is initted here so that when we do the bmc update from
+ * the cache, the structure will ready to send directly from the cache
+ */
+ p->sensorCtl.sensor_number = tgt->sensor_number;
+ p->sensorCtl.operation = SC_ASSERT_OP;
+ p->sensorCtl.assert_states = tgt->assert_states;
+
+ /*
+ * If the command fails, we'll still have the asserted
+ * states that were set by the command that just finished
+ */
+ if (bmc_get_sensor(p->sensorCtl.sensor_number, &assrtd,
+ &was_assrtd_updated) == DMPE_SUCCESS &&
+ was_assrtd_updated == B_TRUE) {
+
+ /*
+ * If the states that were just asserted are not when we
+ * check, issues a warning, but only if the verbosity is
+ * jacked up -- this could be OK (if another user updates
+ * the sensor's state between the time we executed the
+ * update sensor command and the time we check the sensor's
+ * value.
+ */
+ if ((p->sensorCtl.assert_states & assrtd)
+ != p->sensorCtl.assert_states) {
+
+ log_msg(MM_PLUGIN,
+ "Asserted state(s) set before cache addition "
+ "(0x%x) didn't stick -- caching them anyway\n",
+ p->sensorCtl.assert_states);
+ }
+
+ p->sensorCtl.assert_states |= assrtd;
+ }
+}
+
+static void
+bmc_cache_member_update_sensorctl(bmc_cache_ent_t *p, void *databp)
+{
+ struct ipmi_sensor_control *tgt = (struct ipmi_sensor_control *)databp;
+
+ /*
+ * It's not possible for the same bits to be set in the assert and
+ * deassert masks- it would have cause an IPMI error when the
+ * command was originally executed (and the cache update would
+ * therefore not have occurred)
+ */
+ p->sensorCtl.assert_states |= tgt->assert_states;
+ p->sensorCtl.assert_states &= ~tgt->deassert_states;
+}
+
+static boolean_t
+bmc_cache_member_match_sensorctl(bmc_cache_ent_t *p, void *databp)
+{
+ struct ipmi_sensor_control *tgt = (struct ipmi_sensor_control *)databp;
+
+ return (p->sensorCtl.sensor_number == tgt->sensor_number);
+}
+
+static void
+bmc_cache_member_bufsetup_sensorctl(bmc_cache_ent_t *p, void **bufpp,
+ int *buflenp)
+{
+ /* Mask off bits that shouldn't be set according to the spec */
+ p->sensorCtl.operation = SC_ASSERT_OP|SC_DEASSERT_OP;
+ p->sensorCtl.assert_states &= ~STATE_RESERVED_BITS;
+ p->sensorCtl.deassert_states =
+ (~p->sensorCtl.assert_states & ~STATE_RESERVED_BITS);
+
+ *bufpp = &p->sensorCtl;
+ *buflenp = sizeof (struct ipmi_sensor_control);
+}
+
+static void
+bmc_cache_member_init_fru_update(bmc_cache_ent_t *p, void *databp)
+{
+ (void) memcpy(&p->fruInfo, databp, sizeof (struct ipmi_fru_update));
+}
+
+static void
+bmc_cache_member_update_fru_update(bmc_cache_ent_t *p, void *databp)
+{
+ (void) memcpy(&p->fruInfo, databp, sizeof (struct ipmi_fru_update));
+}
+
+
+static boolean_t
+bmc_cache_member_match_fru_update(bmc_cache_ent_t *p, void *databp)
+{
+ struct ipmi_fru_update *frup = (struct ipmi_fru_update *)databp;
+
+ return (p->fruInfo.global_id == frup->global_id &&
+ p->fruInfo.disk_number == frup->disk_number);
+}
+
+static void
+bmc_cache_member_bufsetup_fru_update(bmc_cache_ent_t *p, void **bufpp,
+ int *buflenp)
+{
+ *bufpp = &p->fruInfo;
+ *buflenp = sizeof (struct ipmi_fru_update);
+}
+
+/*
+ * Different elements in the cache need to be restored in order
+ * (e.g. sensor state information must be populated before FRU information
+ * is populated because the FRU information won't "stick" if the right
+ * state isn't asserted)
+ * The g_restoreOrder array is indexed by cache entry type
+ */
+static const bmc_cache_ent_type_t g_restoreOrder[] = {
+ CACHE_ENT_SENSORCTL,
+ CACHE_ENT_FRUINFO,
+ CACHE_ENT_LAST
+};
+
+static struct bmc_cache_member {
+ uint8_t netfn;
+ uint8_t lun;
+ uint8_t cmd;
+ int dataszmin;
+ boolean_t (*matchfn)(bmc_cache_ent_t *, void *);
+ void (*updatefn)(bmc_cache_ent_t *, void *);
+ void (*initfn)(bmc_cache_ent_t *, void *);
+ void (*bufsetupfn)(bmc_cache_ent_t *,
+ void **, int *);
+ void (*bufdonefn)(bmc_cache_ent_t *, void *,
+ int);
+
+} g_cachemembers[] = {
+
+ /* CACHE_ENT_FIRST */
+ { 0, 0, 0, 0, NULL },
+
+ /* CACHE_ENT_FRUINFO */
+ { IPMI_NETFN_OEM, 0, IPMI_CMD_FRU_UPDATE,
+ sizeof (struct ipmi_fru_update),
+ bmc_cache_member_match_fru_update,
+ bmc_cache_member_update_fru_update,
+ bmc_cache_member_init_fru_update,
+ bmc_cache_member_bufsetup_fru_update,
+ NULL },
+
+ /* CACHE_ENT_SENSORCTL */
+ { BMC_NETFN_SE, 0, IPMI_CMD_SET_SENSOR_READING,
+ sizeof (struct ipmi_sensor_control),
+ bmc_cache_member_match_sensorctl,
+ bmc_cache_member_update_sensorctl,
+ bmc_cache_member_init_sensorctl,
+ bmc_cache_member_bufsetup_sensorctl,
+ NULL },
+
+ /* CACHE_ENT_LAST */
+ { 0, 0, 0, 0, NULL }
+};
+
+static bmc_cache_ent_t *
+bmc_state_cache_lookup(uint8_t netfn, uint8_t lun, uint8_t cmd,
+ uint8_t *databp, int datablen)
+{
+ uu_list_walk_t *walkp;
+ bmc_cache_ent_t *p = NULL;
+ boolean_t found = B_FALSE;
+
+ if ((walkp = uu_list_walk_start(g_uu_cachelist, 0)) == NULL)
+ return (NULL);
+
+ while (!found && (p = uu_list_walk_next(walkp)) != NULL) {
+
+ if (g_cachemembers[p->type].netfn == netfn &&
+ g_cachemembers[p->type].lun == lun &&
+ g_cachemembers[p->type].cmd == cmd &&
+ datablen >= g_cachemembers[p->type].dataszmin &&
+ (*(g_cachemembers[p->type].matchfn))(p, databp) == B_TRUE) {
+
+ found = B_TRUE;
+ }
+ }
+
+ uu_list_walk_end(walkp);
+ return (found ? p : NULL);
+}
+
+static void
+bmc_state_cache_add(uint8_t netfn, uint8_t lun, uint8_t cmd, uint8_t *databp,
+ int datablen)
+{
+ boolean_t found_initfn = B_FALSE;
+ int i;
+ bmc_cache_ent_t *p;
+
+ p = (bmc_cache_ent_t *)dzmalloc(sizeof (bmc_cache_ent_t));
+ for (i = CACHE_ENT_FIRST + 1; !found_initfn && i < CACHE_ENT_LAST;
+ i++) {
+
+ if (g_cachemembers[i].netfn == netfn &&
+ g_cachemembers[i].lun == lun &&
+ g_cachemembers[i].cmd == cmd &&
+ datablen >= g_cachemembers[i].dataszmin) {
+
+ p->type = i;
+ (*(g_cachemembers[i].initfn))(p, databp);
+ found_initfn = B_TRUE;
+ }
+ }
+
+ if (found_initfn) {
+
+ assert(g_uu_pool_cache != NULL);
+ assert(g_uu_cachelist != NULL);
+ uu_list_node_init(p, &p->un_node, g_uu_pool_cache);
+ uu_list_insert(g_uu_cachelist, p, 0);
+
+ } else {
+ log_msg(MM_PLUGIN, "Not adding netfn=0x%x cmd=0x%x to the "
+ "bmc$\n", netfn, cmd);
+
+ dfree(p, sizeof (bmc_cache_ent_t));
+ }
+}
+
+/*
+ * The caller must hold the ipmi_mutex!
+ */
+static void
+bmc_state_cache_update(uint8_t netfn, uint8_t lun, uint8_t cmd,
+ uint8_t *databp, int datablen)
+{
+ bmc_cache_ent_t *p;
+
+ /*
+ * Do a lookup to see if we have an entry for this entity.
+ * If so, update it, otherwise, create a new entry in the cache.
+ */
+
+
+ if ((p = bmc_state_cache_lookup(netfn, lun, cmd, databp, datablen))
+ != NULL) {
+
+ /* Update the cache with the command payload */
+ (*(g_cachemembers[p->type].updatefn))(p, databp);
+
+ } else {
+
+ /* Add the item to the cache */
+ bmc_state_cache_add(netfn, lun, cmd, databp, datablen);
+ }
+}
+
+/*
+ * Caller MUST hold the ipmi_lock
+ */
+static int
+bmc_state_refresh_from_cache(void)
+{
+ int i;
+ uu_list_walk_t *walkp;
+ bmc_cache_ent_t *p = NULL;
+ boolean_t bail = B_FALSE;
+ void *databp;
+ int datablen;
+ dm_plugin_error_t rv;
+ bmc_rsp_t rsp;
+
+ /*
+ * Since cached state needs to be restored in a particular
+ * order, make several passes through the cache list, restoring
+ * the state in pass-order. If this becomes performance-limiting,
+ * the cache list can be populated in sorted order (in pass order)
+ */
+ for (i = 0; !bail && g_restoreOrder[i] != CACHE_ENT_LAST; i++) {
+
+ if ((walkp = uu_list_walk_start(g_uu_cachelist, 0)) == NULL)
+ return (-1);
+
+ while (!bail && (p = uu_list_walk_next(walkp)) != NULL) {
+
+ if (p->type == g_restoreOrder[i]) {
+
+ (*(g_cachemembers[p->type].bufsetupfn))
+ (p, &databp, &datablen);
+
+ rv = ipmi_bmc_send_cmd(
+ g_cachemembers[p->type].netfn,
+ g_cachemembers[p->type].lun,
+ g_cachemembers[p->type].cmd,
+ databp, datablen, &rsp);
+
+ if (rv == DMPE_FAILURE &&
+ rsp.ccode != BMC_IPMI_COMMAND_TIMEOUT)
+ bail = B_TRUE;
+
+ if (g_cachemembers[p->type].bufdonefn)
+ (*(g_cachemembers[p->type].bufdonefn))
+ (p, databp, datablen);
+ }
+ }
+
+ uu_list_walk_end(walkp);
+ }
+
+ return (bail ? -1 : 0);
+}
+
+/*
+ * Caller MUST hold the ipmi_lock
+ */
+static int
+bmc_state_refresh(boolean_t *refreshed)
+{
+ static uint32_t last_utime = 0;
+ static uint32_t last_iter = 0;
+ static boolean_t initted = B_FALSE;
+ uint32_t utime;
+ uint32_t iter;
+ dm_plugin_error_t rv;
+
+ if (!g_bmc_monitor_active)
+ return (0);
+
+ rv = bmc_get_uptime(&utime, &iter);
+
+ if (refreshed)
+ *refreshed = B_FALSE;
+
+ if (rv == DMPE_SUCCESS) {
+
+ /*
+ * This also handles the wrap-around case (when utime is
+ * less than last_utime, but iter == last_iter), and
+ * also the case when the BMC's configuration is
+ * reset after a reboot (e.g. the reboot iteration #
+ * is reset to 0).
+ */
+ if (initted &&
+ (utime < last_utime || iter != last_iter)) {
+ /* BMC Reboot/Reset Detected */
+ log_msg(MM_PLUGIN, "BMC refresh in progress...");
+ if (bmc_state_refresh_from_cache() < 0) {
+ log_msg(MM_PLUGIN, "BMC refresh failed!\n");
+ return (-1);
+ } else {
+ if (refreshed)
+ *refreshed = B_TRUE;
+ }
+ }
+
+ last_utime = utime;
+ last_iter = iter;
+ initted = B_TRUE;
+ }
+
+ return (0);
+}
+
+/*ARGSUSED*/
+static void
+bmc_monitor_thread(void *arg)
+{
+ struct timespec tspec;
+ boolean_t refreshed;
+
+ assert(pthread_mutex_lock(&ipmi_mutex) == 0);
+ while (!g_bmcmon_done) {
+
+ if (bmc_state_refresh(&refreshed) == 0 && refreshed) {
+ /*
+ * If the state was successfully refreshed, and there's
+ * replay list, execute that list.
+ */
+ if (g_need_exec_replay) {
+ g_need_exec_replay =
+ (bmc_replay_list_execute() != 0);
+ }
+
+ log_msg(MM_PLUGIN, "BMC successfully refreshed with "
+ "cached state!\n");
+ }
+
+ /* Poll the BMC for any changes in its state every minute */
+ tspec.tv_sec = time(0) + BMC_CHECK_UPTIME_INTERVAL;
+ tspec.tv_nsec = 0;
+
+ (void) pthread_cond_timedwait(&ipmi_cond,
+ &ipmi_mutex, &tspec);
+ }
+ assert(pthread_mutex_unlock(&ipmi_mutex) == 0);
+
+ log_msg(MM_PLUGIN, "BMC monitoring thread exiting...");
+}
+
+/* ***************** P L U G I N E N T R Y P O I N T S ******************* */
+
+static dm_plugin_error_t
+ipmi_plugin_init(void)
+{
+ int method;
+ const char *monpropval =
+ dm_plugin_prop_lookup(GLOBAL_PROP_IPMI_BMC_MON);
+ const char *errinjprop =
+ dm_plugin_prop_lookup(GLOBAL_PROP_IPMI_ERR_INJ);
+ boolean_t bmcmon_enabled;
+
+ if ((g_bmc_fd = open(BMC_DEV, O_RDWR)) <= 0) {
+ log_warn_e("Could not open bmc device");
+ return (DMPE_FAILURE);
+ }
+
+ if (bmc_method(g_bmc_fd, &method) < 0) {
+ (void) close(g_bmc_fd);
+ log_warn("IPMI plugin: Could not determine bmc messaging "
+ "interface!\n");
+ return (DMPE_FAILURE);
+ }
+
+ /*
+ * Keep the bmc device open to prevent the driver from unloading
+ * at a critical moment (e.g. when the BMC is not available). If
+ * we didn't do this, subsequent attempt at opening the bmc device
+ * would fail because the bmc driver would not be able to find
+ * the BMC (if it's resetting), and once the bmc's probe fails,
+ * the system will not reload it automatically.
+ */
+
+ sendrecv_fn = (method == BMC_PUTMSG_METHOD) ?
+ ipmi_bmc_send_cmd_putmsg : ipmi_bmc_send_cmd_ioctl;
+
+ if (bmc_replay_list_init() != 0) {
+ return (DMPE_FAILURE);
+ }
+
+ if (errinjprop != NULL)
+ g_BMCErrorInjectionRate = strtol(errinjprop, 0, 0);
+
+ bmcmon_enabled = (monpropval != NULL && strtol(monpropval, 0, 0) != 0);
+
+ /*
+ * Check to see if the BMC supports the Sun OEM uptime command
+ * If it does, spawn a monitoring thread that will periodically poll
+ * the bmc and check for bmc resets (since the bmc does not retain
+ * the state across resets)
+ */
+ if (bmcmon_enabled && bmc_get_uptime(NULL, NULL) == 0) {
+ if (bmc_cache_init() != 0) {
+ bmc_replay_list_fini();
+ return (DMPE_FAILURE);
+ }
+
+ g_bmc_monitor_active = B_TRUE;
+ g_bmcmon_done = B_FALSE;
+ g_bmcmon_tid = dm_plugin_thr_create(bmc_monitor_thread, NULL);
+ } else
+ g_bmc_monitor_active = B_FALSE;
+
+ return (DMPE_SUCCESS);
+}
+
+static dm_plugin_error_t
+ipmi_plugin_fru_update(const char *actionString, dm_fru_t *frup)
+{
+ return (action_do(actionString, frup, B_TRUE, NULL));
+}
+
+static dm_plugin_error_t
+ipmi_plugin_bind_handle(const char *actionString,
+ dm_plugin_action_handle_t *hdlp)
+{
+ return (action_do(actionString, NULL, B_FALSE,
+ (ipmi_action_handle_t **)hdlp));
+}
+
+static dm_plugin_error_t
+ipmi_plugin_execute(dm_plugin_action_handle_t hdl)
+{
+ return (exec_action_handle((ipmi_action_handle_t *)hdl));
+}
+
+static dm_plugin_error_t
+ipmi_plugin_free_handle(dm_plugin_action_handle_t *hdlp)
+{
+ free_ipmi_action_handle((ipmi_action_handle_t **)hdlp);
+ return (DMPE_SUCCESS);
+}
+
+static dm_plugin_error_t
+ipmi_plugin_fini(void)
+{
+ if (g_bmc_monitor_active) {
+ g_bmcmon_done = B_TRUE;
+ assert(pthread_mutex_lock(&ipmi_mutex) == 0);
+ (void) pthread_cond_broadcast(&ipmi_cond);
+ assert(pthread_mutex_unlock(&ipmi_mutex) == 0);
+
+ /* Signal the thread just in case it's blocked doing BMC I/O */
+ dm_plugin_thr_signal(g_bmcmon_tid);
+ dm_plugin_thr_destroy(g_bmcmon_tid);
+
+ /* Clean up cache lists */
+ bmc_cache_fini();
+ }
+ bmc_replay_list_fini();
+ (void) close(g_bmc_fd);
+ return (DMPE_SUCCESS);
+}
+
+/* ************** I P M I S U P P O R T F U N C T I O N S **************** */
+
+static dm_plugin_error_t
+ipmi_bmc_send_cmd(uint8_t netfn, uint8_t lun, uint8_t cmd,
+ uint8_t *datap, int datalen, bmc_rsp_t *rspp)
+{
+ dm_plugin_error_t rv;
+ static int inject_rep = 0;
+
+ if (g_BMCErrorInjectionRate > 0 &&
+ (++inject_rep % g_BMCErrorInjectionRate) == 0) {
+ inject_rep = 0;
+ rspp->ccode = BMC_IPMI_COMMAND_TIMEOUT;
+ return (DMPE_FAILURE);
+ }
+
+ if (g_bmc_fd < 0)
+ bmc_reopen();
+
+ /* sendrecv_fn cannot be NULL at this point */
+ assert(sendrecv_fn != NULL);
+ rv = (*sendrecv_fn)(g_bmc_fd, netfn, lun, cmd, datap, datalen, rspp);
+
+ return (rv);
+}
+
+static dm_plugin_error_t
+ipmi_bmc_send_cmd_ioctl(int fd, uint8_t netfn, uint8_t lun, uint8_t cmd,
+ uint8_t *datap, int datalen, bmc_rsp_t *rspp)
+{
+ struct strioctl istr;
+ struct bmc_reqrsp reqrsp;
+
+ if (datalen > SEND_MAX_PAYLOAD_SIZE) {
+ log_warn("IPMI Plugin: Data payload length (%d) is too "
+ "large; it cannot be processed by this version of "
+ "the bmc driver.\n", datalen);
+ return (DMPE_FAILURE);
+ }
+
+ (void) memset(&reqrsp, 0, sizeof (reqrsp));
+ reqrsp.req.fn = netfn;
+ reqrsp.req.lun = lun;
+ reqrsp.req.cmd = cmd;
+ reqrsp.req.datalength = (uint8_t)datalen;
+ (void) memcpy(reqrsp.req.data, datap, datalen);
+ reqrsp.rsp.datalength = RECV_MAX_PAYLOAD_SIZE;
+
+ istr.ic_cmd = IOCTL_IPMI_KCS_ACTION;
+ istr.ic_timout = 0;
+ istr.ic_dp = (char *)&reqrsp;
+ istr.ic_len = sizeof (struct bmc_reqrsp);
+
+ log_msg(MM_PLUGIN, "--\n");
+ dump_request(&reqrsp.req);
+ log_msg(MM_PLUGIN, "--\n");
+
+ if (ioctl(fd, I_STR, &istr) < 0) {
+ log_warn_e("IPMI Plugin: ioctl failure");
+ return (DMPE_FAILURE);
+ }
+
+ dump_response(&reqrsp.rsp);
+ log_msg(MM_PLUGIN, "--\n");
+
+ (void) memcpy(rspp, &reqrsp.rsp, sizeof (bmc_rsp_t));
+
+ /* Decrement for sizeof lun, cmd and ccode */
+ if (rspp->ccode || rspp->datalength == 0)
+ (void) memset(rspp->data, 0, sizeof (rspp->data));
+ else if (rspp->datalength > 3)
+ rspp->datalength -= 3;
+
+ return (DMPE_SUCCESS);
+}
+
+static dm_plugin_error_t
+ipmi_bmc_send_cmd_putmsg(int fd, uint8_t netfn, uint8_t lun, uint8_t cmd,
+ uint8_t *datap, int datalen, bmc_rsp_t *rspp)
+{
+ struct strbuf sb;
+ int flags = 0;
+ static uint32_t msg_seq = 0;
+
+ /*
+ * The length of the message structure is equal to the size of the
+ * bmc_req_t structure, PLUS any additional data space in excess of
+ * the data space already reserved in the data member + <n> for
+ * the rest of the members in the bmc_msg_t structure.
+ */
+ int msgsz = offsetof(bmc_msg_t, msg) + sizeof (bmc_req_t) +
+ ((datalen > SEND_MAX_PAYLOAD_SIZE) ?
+ (datalen - SEND_MAX_PAYLOAD_SIZE) : 0);
+ bmc_msg_t *msg = (bmc_msg_t *)dzmalloc(msgsz);
+ bmc_req_t *request = (bmc_req_t *)&msg->msg[0];
+ bmc_rsp_t *response;
+
+ msg->m_type = BMC_MSG_REQUEST;
+ msg->m_id = msg_seq++;
+ request->fn = netfn;
+ request->lun = lun;
+ request->cmd = cmd;
+ request->datalength = (uint8_t)datalen;
+ (void) memcpy(request->data, datap, datalen);
+
+ sb.len = msgsz;
+ sb.buf = (char *)msg;
+
+ log_msg(MM_PLUGIN, "--\n");
+ dump_request(request);
+ log_msg(MM_PLUGIN, "--\n");
+
+ if (putmsg(fd, NULL, &sb, 0) < 0) {
+ log_warn_e("IPMI Plugin: putmsg failure");
+ dfree(msg, msgsz);
+
+ /*
+ * As a workaround for a bug in bmc, if an error was returned
+ * from putmsg, we need to close the fd and reopen it to clear
+ * the error state.
+ */
+ bmc_reopen();
+
+ return (DMPE_FAILURE);
+ }
+
+ dfree(msg, msgsz);
+
+ sb.buf = dzmalloc(MESSAGE_BUFSIZE);
+ sb.maxlen = MESSAGE_BUFSIZE;
+
+ if (getmsg(fd, NULL, &sb, &flags) < 0) {
+ log_warn_e("IPMI Plugin: getmsg failure");
+ dfree(sb.buf, MESSAGE_BUFSIZE);
+ return (DMPE_FAILURE);
+ }
+
+ /*LINTED*/
+ msg = (bmc_msg_t *)sb.buf;
+
+ log_msg(MM_PLUGIN, "Got msg (id 0x%x) type 0x%x\n", msg->m_id,
+ msg->m_type);
+
+
+ /* Did we get an error back from the stream? */
+ switch (msg->m_type) {
+
+ case BMC_MSG_RESPONSE:
+ response = (bmc_rsp_t *)&msg->msg[0];
+
+ dump_response(response);
+ log_msg(MM_PLUGIN, "--\n");
+
+ (void) memcpy(rspp, response, sizeof (bmc_rsp_t));
+
+ if (rspp->ccode || rspp->datalength == 0)
+ (void) memset(rspp->data, 0, sizeof (rspp->data));
+
+ break;
+
+ case BMC_MSG_ERROR:
+ /* In case of an error, msg->msg[0] has the error code */
+ log_warn("IPMI Plugin: bmc_send_cmd error: %s\n",
+ strerror(msg->msg[0]));
+ break;
+
+ }
+
+ dfree(sb.buf, MESSAGE_BUFSIZE);
+ return (DMPE_SUCCESS);
+}
+
+/*
+ * Determine which interface to use. Returns the interface method
+ * to use.
+ */
+static int
+bmc_method(int fd, int *if_type)
+{
+ struct strioctl istr;
+ int retval = 0;
+ uint8_t method = BMC_PUTMSG_METHOD;
+
+ istr.ic_cmd = IOCTL_IPMI_INTERFACE_METHOD;
+ istr.ic_timout = 0;
+ istr.ic_dp = (char *)&method;
+ istr.ic_len = 1;
+
+ /*
+ * If the ioctl doesn't exist, we should get an EINVAL back.
+ * Bail out on any other error.
+ */
+ if (ioctl(fd, I_STR, &istr) < 0) {
+
+ if (errno != EINVAL)
+ retval = -1;
+ else
+ method = BMC_IOCTL_METHOD;
+ }
+
+ if (retval == 0)
+ *if_type = method;
+
+ return (retval);
+}
+
+static void
+dump_request(bmc_req_t *request)
+{
+ int i;
+
+ log_msg(MM_PLUGIN, "BMC req.fn : 0x%x\n", request->fn);
+ log_msg(MM_PLUGIN, "BMC req.lun : 0x%x\n", request->lun);
+ log_msg(MM_PLUGIN, "BMC req.cmd : 0x%x\n", request->cmd);
+ log_msg(MM_PLUGIN, "BMC req.datalength : 0x%x\n", request->datalength);
+ log_msg(MM_PLUGIN, "BMC req.data : ");
+
+ if (request->datalength > 0) {
+ for (i = 0; i < request->datalength; i++)
+ log_msg(MM_PLUGIN, "0x%x ", request->data[i]);
+ } else {
+ log_msg(MM_PLUGIN, "<NONE>");
+ }
+ log_msg(MM_PLUGIN, "\n");
+}
+
+static void
+dump_response(bmc_rsp_t *response)
+{
+ int i;
+
+ log_msg(MM_PLUGIN, "BMC rsp.fn : 0x%x\n", response->fn);
+ log_msg(MM_PLUGIN, "BMC rsp.lun : 0x%x\n", response->lun);
+ log_msg(MM_PLUGIN, "BMC rsp.cmd : 0x%x\n", response->cmd);
+ log_msg(MM_PLUGIN, "BMC rsp.ccode : 0x%x\n", response->ccode);
+ log_msg(MM_PLUGIN, "BMC rsp.datalength : 0x%x\n", response->datalength);
+ log_msg(MM_PLUGIN, "BMC rsp.data : ");
+
+ if (response->datalength > 0) {
+ for (i = 0; i < response->datalength; i++)
+ log_msg(MM_PLUGIN, "0x%x ", response->data[i]);
+ } else {
+ log_msg(MM_PLUGIN, "<NONE>");
+ }
+ log_msg(MM_PLUGIN, "\n");
+}
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/ipmi_plugin.h b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/ipmi_plugin.h
new file mode 100644
index 0000000000..b97f930197
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/ipmi_plugin.h
@@ -0,0 +1,52 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _IPMI_PLUGIN_H
+#define _IPMI_PLUGIN_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * IPMI Plugin definitions
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Controls whether to fire-up a thread to monitor the BMC's state
+ * and to update it with cached state information when a BMC reset
+ * is detected.
+ */
+#define GLOBAL_PROP_IPMI_BMC_MON "ipmi-bmc-monitor-enable"
+#define GLOBAL_PROP_IPMI_ERR_INJ "ipmi-error-inj-rate"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IPMI_PLUGIN_H */
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/plugin_mgr.c b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/plugin_mgr.c
new file mode 100644
index 0000000000..03125827bc
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/plugin_mgr.c
@@ -0,0 +1,476 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Block comment that describes the contents of this file.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <dlfcn.h>
+#include <link.h>
+#include <assert.h>
+#include <pthread.h>
+
+#include "util.h"
+#include "sfx4500-disk.h"
+#include "plugin_mgr.h"
+
+static dm_plugin_t *plugin_list = NULL;
+static pthread_mutex_t plugin_list_mutex = PTHREAD_MUTEX_INITIALIZER;
+static dm_plugin_action_handle_impl_t *handle_list = NULL;
+static pthread_mutex_t handle_list_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static boolean_t
+safe_protocol_string(const char *protocol)
+{
+ while (*protocol != 0) {
+
+ if (!isalnum(*protocol))
+ return (B_FALSE);
+
+ protocol++;
+ }
+ return (B_TRUE);
+}
+
+/*
+ * Initialize the plugin.
+ * Returns DMPE_SUCCESS if the _init entry point of the plugin
+ * executed successfully, otherwise returns DMPE_FAILURE.
+ */
+static dm_plugin_error_t
+init_dm_plugin(dm_plugin_t *dmpip)
+{
+ dm_plugin_error_t ec;
+
+ if (dmpip->state != DMPS_INITED && dmpip->ops->_init) {
+ if ((ec = dmpip->ops->_init()) == DMPE_SUCCESS) {
+ dmpip->state = DMPS_INITED;
+ } else {
+ log_warn("_init failed for %s plugin; unloading it\n",
+ dmpip->protocol);
+ }
+ } else if (dmpip->ops->_init == NULL) /* No _init, no problem */
+ dmpip->state = DMPS_INITED;
+
+ return (ec);
+}
+
+static dm_plugin_t *
+new_dm_plugin(const char *protocol)
+{
+ dm_plugin_t *newpi = (dm_plugin_t *)dmalloc(sizeof (dm_plugin_t));
+
+ newpi->protocol = dstrdup(protocol);
+ newpi->state = DMPS_NONE;
+ newpi->ops = NULL;
+ newpi->next = NULL;
+
+ return (newpi);
+}
+
+static void
+unload_dm_plugin(dm_plugin_t *pluginp)
+{
+ pluginp->ops = NULL;
+}
+
+static void
+free_dm_plugin(dm_plugin_t **dmpipp)
+{
+ dm_plugin_t *dmpip = *dmpipp;
+ dm_plugin_error_t ec;
+
+ if (dmpip) {
+ if (dmpip->state == DMPS_INITED && dmpip->ops->_fini) {
+ ec = dmpip->ops->_fini();
+ if (ec != DMPE_SUCCESS) {
+ log_warn("_fini failed for plugin %s.\n",
+ dmpip->protocol);
+ }
+ }
+
+ unload_dm_plugin(dmpip);
+
+ if (dmpip->protocol)
+ dstrfree(dmpip->protocol);
+
+ dmpip->state = DMPS_NONE;
+ dmpip->ops = NULL;
+
+ dfree(dmpip, sizeof (dm_plugin_t));
+ *dmpipp = NULL;
+ }
+}
+
+static boolean_t
+do_load_dm_plugin(dm_plugin_t *dmpip)
+{
+ boolean_t plugin_loaded = B_FALSE;
+ int len;
+ char *buf;
+
+ len = strlen(dmpip->protocol) + strlen(DM_PLUGIN_OPS_NAME) + 2;
+ buf = (char *)dmalloc(len);
+
+ /*
+ * Currently, plugins are baked into the module, and the name of the
+ * ops structure is formed by concatenating the plugin's protocol
+ * with a static string:
+ */
+
+ (void) snprintf(buf, len, "%s_%s", dmpip->protocol,
+ DM_PLUGIN_OPS_NAME);
+
+ dmpip->ops = (dm_plugin_ops_t *)dlsym(RTLD_SELF, buf);
+
+ if (dmpip->ops != NULL) {
+ if (dmpip->ops->version > DM_PLUGIN_VERSION) {
+ log_warn("Plugin error: cannot handle "
+ "plugin %s with version %d.\n",
+ dmpip->protocol, dmpip->ops->version);
+ } else
+ plugin_loaded = B_TRUE;
+ } else {
+ log_warn("Plugin error: dlsym(%s) = NULL\n",
+ buf);
+ unload_dm_plugin(dmpip);
+ }
+
+ dfree(buf, len);
+ return (plugin_loaded);
+}
+
+static dm_plugin_t *
+load_dm_plugin(const char *protocol)
+{
+ dm_plugin_t *dmpip = NULL;
+ boolean_t plugin_loaded = B_FALSE;
+
+ /*
+ * Validate the protocol string -- if there are any non-alphanumeric
+ * characters, it's not a valid protocol string.
+ */
+ if (safe_protocol_string(protocol) == B_FALSE) {
+ log_warn("Invalid characters in plugin protocol `%s'.\n",
+ protocol);
+ goto fpi_out;
+ }
+
+ dmpip = new_dm_plugin(protocol);
+
+ plugin_loaded = do_load_dm_plugin(dmpip);
+
+fpi_out:
+ if (plugin_loaded) {
+ assert(dmpip != NULL);
+ dmpip->state = DMPS_LOADED;
+ } else if (!plugin_loaded && dmpip != NULL)
+ free_dm_plugin(&dmpip);
+
+ return (dmpip);
+}
+
+static char *
+extract_protocol(const char *action)
+{
+ char *s = strchr(action, PROTOCOL_SEPARATOR);
+ char *proto = NULL;
+ int len;
+ int i = 0;
+
+ /* The protocol is the string before the separator, but in lower-case */
+ if (s) {
+ len = (uintptr_t)s - (uintptr_t)action;
+ proto = (char *)dmalloc(len + 1);
+ while (i < len) {
+ proto[i] = tolower(action[i]);
+ i++;
+ }
+ proto[len] = 0;
+ }
+
+ return (proto);
+}
+
+static char *
+extract_action(const char *action)
+{
+ /* The action is the string after the separator */
+ char *s = strchr(action, PROTOCOL_SEPARATOR);
+
+ return (s ? (s + 1) : NULL);
+}
+
+static dm_plugin_t *
+load_and_init_dm_plugin(const char *protocol)
+{
+ dm_plugin_t *plugin = load_dm_plugin(protocol);
+
+ if (plugin) {
+ /* If _init succeeded, add the plugin to the list */
+ if (init_dm_plugin(plugin) == DMPE_SUCCESS) {
+ plugin->next = plugin_list;
+ plugin_list = plugin;
+ } else {
+ /* Otherwise, free it. */
+ free_dm_plugin(&plugin);
+ }
+ } else {
+ log_warn("Could not load `%s' plugin!\n",
+ protocol);
+ }
+
+ return (plugin);
+}
+
+static dm_plugin_t *
+protocol_to_dm_plugin(const char *protocol)
+{
+ dm_plugin_t *plugin;
+
+ /*
+ * Traversing the plugin list must be atomic with
+ * respect to plugin loads
+ */
+ assert(pthread_mutex_lock(&plugin_list_mutex) == 0);
+
+ plugin = plugin_list;
+
+ while (plugin != NULL) {
+ if (strcmp(plugin->protocol, protocol) == 0) {
+ break;
+ }
+
+ plugin = plugin->next;
+ }
+
+ /* Wasn't found -- load, initialize, and return it */
+ plugin = (plugin == NULL) ? load_and_init_dm_plugin(protocol) :
+ plugin;
+
+ assert(pthread_mutex_unlock(&plugin_list_mutex) == 0);
+
+ return (plugin);
+}
+
+static dm_plugin_action_handle_impl_t *
+new_action_handle(const char *action, dm_plugin_t *pluginp)
+{
+ dm_plugin_action_handle_impl_t *hip;
+
+ hip = (dm_plugin_action_handle_impl_t *)dmalloc(
+ sizeof (dm_plugin_action_handle_impl_t));
+
+ hip->actionString = dstrdup(action);
+ hip->plugin = pluginp;
+ hip->handle = (dm_plugin_action_handle_t)NULL;
+
+ /* Add the handle to the global list */
+ assert(pthread_mutex_lock(&handle_list_mutex) == 0);
+ hip->next = handle_list;
+ handle_list = hip;
+ assert(pthread_mutex_unlock(&handle_list_mutex) == 0);
+
+ return (hip);
+}
+
+static void
+free_action_handle(dm_plugin_action_handle_impl_t **hipp)
+{
+ dm_plugin_action_handle_impl_t *hip = *hipp;
+ dm_plugin_t *dmpip;
+
+ if (hip) {
+ if (hip->actionString)
+ dstrfree(hip->actionString);
+
+ dmpip = hip->plugin;
+
+ if (dmpip->state == DMPS_INITED &&
+ dmpip->ops->indicator_free_handle)
+ if (dmpip->ops->indicator_free_handle(&hip->handle)
+ != DMPE_SUCCESS) {
+ log_warn("indicator_free_handle failed for %s"
+ " plugin\n",
+ dmpip->protocol);
+ }
+
+ dfree(hip, sizeof (dm_plugin_action_handle_impl_t));
+ *hipp = NULL;
+ }
+}
+
+static dm_plugin_action_handle_impl_t *
+lookup_handle_by_action(const char *action)
+{
+ dm_plugin_action_handle_impl_t *handle;
+
+ assert(pthread_mutex_lock(&handle_list_mutex) == 0);
+
+ handle = handle_list;
+
+ while (handle != NULL) {
+ if (strcmp(handle->actionString, action) == 0)
+ break;
+
+ handle = handle->next;
+ }
+
+ assert(pthread_mutex_unlock(&handle_list_mutex) == 0);
+
+ return (handle);
+}
+
+int
+init_plugin_manager(void)
+{
+ return (0);
+}
+
+void
+cleanup_plugin_manager(void)
+{
+ dm_plugin_t *next_plugin;
+ dm_plugin_action_handle_impl_t *next_handle;
+
+ while (handle_list != NULL) {
+ next_handle = handle_list->next;
+
+ free_action_handle(&handle_list);
+
+ handle_list = next_handle;
+ }
+
+ while (plugin_list != NULL) {
+
+ next_plugin = plugin_list->next;
+
+ free_dm_plugin(&plugin_list);
+
+ plugin_list = next_plugin;
+ }
+}
+
+static dm_plugin_error_t
+bind_action_handle(dm_plugin_t *dmpip, const char *action,
+ dm_plugin_action_handle_t *hdlp)
+{
+ dm_plugin_action_handle_impl_t *hip;
+
+ hip = new_action_handle(action, dmpip);
+ *hdlp = hip;
+
+ assert(dmpip->state == DMPS_INITED);
+ if (dmpip->ops->indicator_bind_handle)
+ return (dmpip->ops->indicator_bind_handle(action,
+ &hip->handle));
+
+ return (DMPE_FAILURE);
+}
+
+dm_plugin_error_t
+dm_pm_update_fru(const char *action, dm_fru_t *frup)
+{
+ char *protocol = extract_protocol(action); /* mem alloced here */
+ char *actionp = extract_action(action);
+ dm_plugin_t *dmpip;
+
+ if (protocol == NULL) {
+ log_warn("FRU update: Invalid protocol specified in action "
+ "`%s'\n", action);
+ return (DMPE_FAILURE);
+ }
+
+ dmpip = protocol_to_dm_plugin(protocol);
+ dstrfree(protocol);
+
+ if (dmpip != NULL) {
+ assert(dmpip->state == DMPS_INITED);
+ if (dmpip->ops->indicator_fru_update)
+ return (dmpip->ops->indicator_fru_update(actionp,
+ frup));
+ }
+
+ return (DMPE_FAILURE);
+}
+
+dm_plugin_error_t
+dm_pm_indicator_execute(const char *action)
+{
+ dm_plugin_t *dmpip;
+ char *protocol = extract_protocol(action); /* memory allocated here */
+ char *actionp = extract_action(action);
+ dm_plugin_action_handle_impl_t *hip;
+
+ dmpip = protocol_to_dm_plugin(protocol);
+ dstrfree(protocol);
+
+ if (dmpip != NULL) {
+
+ if ((hip = lookup_handle_by_action(actionp)) == NULL) {
+ if (bind_action_handle(dmpip, actionp,
+ (dm_plugin_action_handle_t *)&hip) != DMPE_SUCCESS)
+ return (DMPE_FAILURE);
+ }
+
+ assert(dmpip->state == DMPS_INITED);
+ if (dmpip->ops->indicator_execute)
+ return (dmpip->ops->indicator_execute(hip->handle));
+ }
+
+ return (DMPE_FAILURE);
+}
+
+pthread_t
+dm_plugin_thr_create(void (*fn)(void *), void *arg)
+{
+ return (fmd_thr_create(g_fm_hdl, fn, arg));
+}
+
+void
+dm_plugin_thr_signal(pthread_t tid)
+{
+ fmd_thr_signal(g_fm_hdl, tid);
+}
+
+void
+dm_plugin_thr_destroy(pthread_t tid)
+{
+ fmd_thr_destroy(g_fm_hdl, tid);
+}
+
+const char *
+dm_plugin_prop_lookup(const char *propname)
+{
+ return (dm_prop_lookup(dm_global_proplist(), propname));
+}
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/plugin_mgr.h b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/plugin_mgr.h
new file mode 100644
index 0000000000..3f0dccee33
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/plugin_mgr.h
@@ -0,0 +1,78 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _PLUGIN_MGR_H
+#define _PLUGIN_MGR_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Declarations for the disk monitor plugin manager
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "dm_plugin.h"
+#include "ipmi_plugin.h"
+
+#define DM_PLUGIN_DIR "/usr/lib/fm/fmd/plugins/dm"
+#define DM_PLUGIN_PREFIX "dmpi_"
+#define PROTOCOL_SEPARATOR ':'
+
+typedef enum {
+ DMPS_NONE,
+ DMPS_LOADED,
+ DMPS_INITED
+} dm_plugin_state_t;
+
+typedef struct dm_plugin {
+ char *protocol;
+ dm_plugin_state_t state;
+ dm_plugin_ops_t *ops;
+ pthread_mutex_t *mutex;
+ struct dm_plugin *next;
+} dm_plugin_t;
+
+typedef struct dm_plugin_action_handle_impl {
+ dm_plugin_t *plugin;
+ char *actionString;
+ dm_plugin_action_handle_t handle;
+ struct dm_plugin_action_handle_impl *next;
+} dm_plugin_action_handle_impl_t;
+
+extern int init_plugin_manager(void);
+extern void cleanup_plugin_manager(void);
+
+extern dm_plugin_error_t dm_pm_update_fru(const char *action, dm_fru_t *frup);
+extern dm_plugin_error_t dm_pm_indicator_execute(const char *action);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PLUGIN_MGR_H */
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/schg_mgr.c b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/schg_mgr.c
new file mode 100644
index 0000000000..a1d342d5ee
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/schg_mgr.c
@@ -0,0 +1,714 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <string.h>
+#include <inttypes.h>
+#include <atomic.h>
+#include <assert.h>
+#include <fm/fmd_api.h>
+#include <sys/fm/protocol.h>
+
+#include "sfx4500-disk.h"
+#include "schg_mgr.h"
+#include "hotplug_mgr.h"
+#include "plugin_mgr.h"
+#include "fault_mgr.h"
+#include "fault_analyze.h"
+#include "topo_gather.h"
+#include "fm_disk_events.h"
+
+/* State-change event processing thread data */
+static pthread_t g_schg_tid;
+static thread_state_t g_schgt_state = TS_NOT_RUNNING;
+static pthread_mutex_t g_schgt_state_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t g_schgt_state_cvar = PTHREAD_COND_INITIALIZER;
+static pthread_mutex_t g_schgt_add_mutex = PTHREAD_MUTEX_INITIALIZER;
+static qu_t *g_schg_queue = NULL;
+
+static void dm_state_change_nolock(diskmon_t *diskp, hotplug_state_t newstate);
+
+/*
+ * Each disk state change is described by an instance of the following
+ * structure (which includes the disk object and the new state)
+ */
+typedef struct disk_statechg {
+ diskmon_t *diskp;
+ hotplug_state_t newstate;
+} disk_statechg_t;
+
+static disk_statechg_t *
+new_statechange(diskmon_t *diskp, hotplug_state_t state)
+{
+ disk_statechg_t *dscp =
+ (disk_statechg_t *)dmalloc(sizeof (disk_statechg_t));
+
+ /*
+ * The states are additive -- we don't need to preserve
+ * the current faulted state in the newstate:
+ */
+ dscp->diskp = diskp;
+ dscp->newstate = state;
+
+ return (dscp);
+}
+
+static void
+free_statechange(void *dscp)
+{
+ dfree(dscp, sizeof (disk_statechg_t));
+}
+
+static void
+add_to_statechange_queue(diskmon_t *diskp, hotplug_state_t newstate)
+{
+ queue_add(g_schg_queue, new_statechange(diskp, newstate));
+}
+
+static const char *
+lookup_action_string(indicator_t *ind_listp, ind_state_t state, char *name)
+{
+ const char *str = NULL;
+
+ while (ind_listp != NULL) {
+
+ if (state == ind_listp->ind_state &&
+ strcasecmp(ind_listp->ind_name, name) == 0) {
+
+ str = ind_listp->ind_instr_spec;
+ break;
+ }
+
+ ind_listp = ind_listp->next;
+ }
+
+ return (str);
+}
+
+void
+dm_fault_indicator_set(diskmon_t *diskp, ind_state_t istate)
+{
+ const char *astring;
+
+ assert(pthread_mutex_lock(&diskp->fault_indicator_mutex) == 0);
+
+ /*
+ * No need to execute redundant indicator actions
+ */
+ if (istate == INDICATOR_UNKNOWN ||
+ diskp->fault_indicator_state == istate) {
+ assert(pthread_mutex_unlock(&diskp->fault_indicator_mutex)
+ == 0);
+ return;
+ }
+
+ astring = lookup_action_string(diskp->ind_list, istate,
+ INDICATOR_FAULT_IDENTIFIER);
+
+ if (astring != NULL) {
+ log_msg(MM_SCHGMGR, "Executing action `%s'\n", astring);
+
+ if (dm_pm_indicator_execute(astring)
+ != DMPE_SUCCESS) {
+ log_warn("[Disk in %s] Action `%s' did not complete "
+ "successfully.\n",
+ diskp->location,
+ astring);
+ } else {
+
+ diskp->fault_indicator_state = istate;
+
+ log_msg(MM_SCHGMGR, "Action `%s' executed "
+ "successfully\n", astring);
+ }
+ }
+
+ assert(pthread_mutex_unlock(&diskp->fault_indicator_mutex) == 0);
+}
+
+static void
+schg_execute_state_change_action(diskmon_t *diskp, hotplug_state_t oldstate,
+ hotplug_state_t newstate)
+{
+ indrule_t *rulelist;
+ ind_action_t *actions;
+ const char *astring;
+
+ log_msg(MM_SCHGMGR, "[Disk in %s] State change action: %s -> %s\n",
+ diskp->location,
+ hotplug_state_string(oldstate),
+ hotplug_state_string(newstate));
+
+ /*
+ * Find the list of actions that correspond to this state change.
+ * If the old state is UNKNOWN, then we'll match to first action
+ * whose transition state is the new state.
+ */
+ rulelist = diskp->indrule_list;
+
+ while (rulelist != NULL) {
+
+ if ((oldstate == HPS_UNKNOWN ||
+ rulelist->strans.begin == oldstate) &&
+ rulelist->strans.end == newstate)
+ break;
+
+ rulelist = rulelist->next;
+ }
+
+ if (rulelist != NULL) {
+ /* Now we have a set of actions to perform: */
+ actions = rulelist->action_list;
+
+ while (actions != NULL) {
+
+ astring = lookup_action_string(diskp->ind_list,
+ actions->ind_state, actions->ind_name);
+
+ assert(astring != NULL);
+
+ log_msg(MM_SCHGMGR, "Executing action `%s'\n", astring);
+
+ if (dm_pm_indicator_execute(astring)
+ != DMPE_SUCCESS) {
+ log_warn("[Disk in %s][State transition from "
+ "%s to %s] Action `%s' did not complete "
+ "successfully.\n",
+ diskp->location,
+ hotplug_state_string(oldstate),
+ hotplug_state_string(newstate),
+ astring);
+
+ } else
+ log_msg(MM_SCHGMGR,
+ "Action `%s' executed successfully\n",
+ astring);
+
+ actions = actions->next;
+ }
+ }
+
+}
+
+static void
+schg_send_fru_to_plugin(diskmon_t *diskp, dm_fru_t *frup)
+{
+ const char *action = dm_prop_lookup(diskp->props, DISK_PROP_FRUACTION);
+
+ if (action == NULL) {
+ log_msg(MM_SCHGMGR|MM_NOTE, "No FRU update action for disk "
+ "in %s\n", diskp->location);
+ return;
+ }
+
+ if (dm_pm_update_fru(action, frup) != DMPE_SUCCESS) {
+ log_warn("Error updating FRU information for disk in %s.\n",
+ diskp->location);
+ }
+}
+
+static void
+schg_update_fru_info(diskmon_t *diskp)
+{
+ if (diskp->initial_configuration ||
+ topo_update_configuration(diskp) == TOPO_SUCCESS) {
+ diskp->initial_configuration = B_FALSE;
+ assert(pthread_mutex_lock(&diskp->fru_mutex) == 0);
+ if (diskp->frup != NULL)
+ schg_send_fru_to_plugin(diskp, diskp->frup);
+ else
+ log_warn("frup unexpectedly went away: not updating "
+ "FRU information for disk %s!\n", diskp->location);
+ assert(pthread_mutex_unlock(&diskp->fru_mutex) == 0);
+ } else {
+ log_warn_e("Error retrieving FRU information "
+ "for disk in %s", diskp->location);
+ }
+}
+
+/*
+ * [Lifted from fmd]
+ * Create a local ENA value for fmd-generated ereports. We use ENA Format 1
+ * with the low bits of gethrtime() and pthread_self() as the processor ID.
+ */
+static uint64_t
+dm_gen_ena(void)
+{
+ hrtime_t hrt = gethrtime();
+
+ return ((uint64_t)((FM_ENA_FMT1 & ENA_FORMAT_MASK) |
+ ((pthread_self() << ENA_FMT1_CPUID_SHFT) & ENA_FMT1_CPUID_MASK) |
+ ((hrt << ENA_FMT1_TIME_SHFT) & ENA_FMT1_TIME_MASK)));
+}
+
+
+static void
+dm_send_ereport(char *class, uint64_t ena, nvlist_t *detector,
+ nvlist_t *payload)
+{
+ nvlist_t *nvl;
+ int e = 0;
+
+ if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) == 0) {
+ /*
+ * An ereport nvlist consists of:
+ *
+ * FM_CLASS class
+ * FM_VERSION FM_EREPORT_VERSION
+ * FM_EREPORT_ENA ena
+ * FM_EREPORT_DETECTOR detector
+ * <Other payload members>
+ *
+ */
+ e |= nvlist_add_string(nvl, FM_CLASS, class);
+ e |= nvlist_add_uint8(nvl, FM_VERSION, FM_EREPORT_VERSION);
+ e |= nvlist_add_uint64(nvl, FM_EREPORT_ENA, ena);
+ e |= nvlist_add_nvlist(nvl, FM_EREPORT_DETECTOR, detector);
+ e |= nvlist_merge(nvl, payload, 0);
+
+ if (e == 0)
+ fmd_xprt_post(g_fm_hdl, g_xprt_hdl, nvl, 0);
+ else
+ nvlist_free(nvl);
+ }
+}
+
+static void
+schg_consume_faults(diskmon_t *diskp)
+{
+ struct disk_fault *faults;
+ nvlist_t *nvl;
+ uint64_t ena;
+ char *er_class;
+ int e;
+
+ assert(diskp->fmip != NULL);
+
+ /* Use the same ENA for all current faults */
+ ena = dm_gen_ena();
+
+ assert(pthread_mutex_lock(&diskp->fmip->fault_data_mutex) == 0);
+ faults = diskp->fmip->fault_list;
+
+ /* Go through the list of faults, executing actions if present */
+ while (faults != NULL) {
+
+ assert(pthread_mutex_lock(&diskp->disk_faults_mutex) == 0);
+ diskp->disk_faults |= faults->fault_src;
+ assert(pthread_mutex_unlock(&diskp->disk_faults_mutex) == 0);
+
+ er_class = NULL;
+ e = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0);
+
+ switch (faults->fault_src) {
+ case DISK_FAULT_SOURCE_SELFTEST:
+ if (e == 0) {
+ er_class = EREPORT_SATA_STFAIL;
+ e |= nvlist_add_uint16(nvl,
+ EV_PAYLOAD_STCODE,
+ faults->selftest_code);
+ }
+ break;
+
+ case DISK_FAULT_SOURCE_OVERTEMP:
+ if (e == 0) {
+ er_class = EREPORT_SATA_OVERTEMP;
+ e |= nvlist_add_uint16(nvl,
+ EV_PAYLOAD_CURTEMP,
+ faults->cur_temp);
+ e |= nvlist_add_uint16(nvl,
+ EV_PAYLOAD_THRESH,
+ faults->thresh_temp);
+ }
+ break;
+
+ case DISK_FAULT_SOURCE_INFO_EXCPT:
+ er_class = EREPORT_SATA_PREDFAIL;
+ if (e == 0 && faults->sense_valid) {
+ e |= nvlist_add_uint16(nvl,
+ EV_PAYLOAD_ASC,
+ faults->asc);
+ e |= nvlist_add_uint16(nvl,
+ EV_PAYLOAD_ASCQ,
+ faults->ascq);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (e == 0 && er_class != NULL) {
+ log_msg(MM_SCHGMGR, "[%s] Sending ereport %s\n",
+ diskp->location, er_class);
+
+ dm_send_ereport(er_class, ena,
+ diskp->disk_res_fmri, nvl);
+ }
+
+ if (nvl)
+ nvlist_free(nvl);
+
+ faults = faults->next;
+ }
+
+ free_disk_fault_list(diskp->fmip);
+
+ assert(pthread_mutex_unlock(&diskp->fmip->fault_data_mutex) == 0);
+}
+
+void
+block_state_change_events(void)
+{
+ assert(pthread_mutex_lock(&g_schgt_add_mutex) == 0);
+}
+
+void
+unblock_state_change_events(void)
+{
+ assert(pthread_mutex_unlock(&g_schgt_add_mutex) == 0);
+}
+
+static void
+disk_state_change_first_time(diskmon_t *diskp)
+{
+ hotplug_state_t firststate;
+
+ /*
+ * Grab the current state of the attachment point to initialize the
+ * initial disk state. Create a disk state change with this new
+ * state so it will be processed in the loop below. If we can't get
+ * the initial state for some reason, then we'll just end up doing it
+ * later when we get a state change from the hotplug monitor or the
+ * fault monitor.
+ */
+ firststate = disk_ap_state_to_hotplug_state(diskp);
+ if (firststate != HPS_UNKNOWN)
+ dm_state_change_nolock(diskp, firststate);
+
+ /*
+ * The fault indicators will be updated when faults are replayed
+ * based on the state of the disk as faulty in the fmd resource cache.
+ * A FAULTED state change will come from the _recv function when the
+ * fault component event is replayed.
+ */
+}
+
+static void
+disk_state_change_thread(void *vdisklistp)
+{
+ diskmon_t *disklistp = (diskmon_t *)vdisklistp;
+ diskmon_t *diskp;
+ disk_statechg_t *dscp;
+ int err;
+ hotplug_state_t nextstate;
+ boolean_t poke_fault_manager;
+ const char *pth;
+
+ /*
+ * Perform startup activities to initialize the state of the
+ * indicators for each disk.
+ */
+ diskp = disklistp;
+ while (diskp != NULL) {
+ disk_state_change_first_time(diskp);
+ diskp = diskp->next;
+ }
+
+ unblock_state_change_events();
+
+ assert(pthread_mutex_lock(&g_schgt_state_mutex) == 0);
+ if (g_schgt_state != TS_EXIT_REQUESTED) {
+ g_schgt_state = TS_RUNNING;
+ assert(pthread_cond_broadcast(&g_schgt_state_cvar) == 0);
+ }
+ assert(pthread_mutex_unlock(&g_schgt_state_mutex) == 0);
+
+ while (g_schgt_state != TS_EXIT_REQUESTED) {
+
+ if ((dscp = (disk_statechg_t *)queue_remove(g_schg_queue))
+ == NULL) {
+ assert(g_schgt_state == TS_EXIT_REQUESTED);
+ continue;
+ }
+
+ poke_fault_manager = B_FALSE;
+
+ diskp = dscp->diskp;
+
+ /*
+ * If the new state is the faulted state, add that state to
+ * the disk's current state.
+ */
+ if (dscp->newstate == HPS_FAULTED) {
+
+ /*
+ * If the disk wasn't previously in the faulted state,
+ * execute the generic fault action. Even if we're
+ * in the faulted state, accept additional faults.
+ */
+ nextstate = DISK_STATE(diskp->state) | HPS_FAULTED;
+
+ } else if (dscp->newstate == HPS_REPAIRED) {
+ nextstate = DISK_STATE(diskp->state);
+
+ } else if (dscp->newstate == HPS_ABSENT) {
+ /*
+ * If the new state is ABSENT, forget any faults
+ */
+
+ nextstate = HPS_ABSENT;
+ } else
+ nextstate = dscp->newstate | DISK_FAULTED(diskp->state);
+
+ /*
+ * When a new disk is inserted and reaches the CONFIGURED state,
+ * the following actions must be done in the following order:
+ *
+ * (1) Execute the configuration-specified action on the
+ * state change.
+ * (2) Retreive the FRU information from the disk and execute
+ * the FRU-update action specified,
+ * (3) Initialize the fault monitor state associated with
+ * the new drive.
+ *
+ * Once the disk is no longer "new" (a disk is "new" when it
+ * has not yet reached the CONFIGURED state), subsequent
+ * transitions away and back to CONFIGURED (as long as the
+ * disk is not physically removed) will result in the
+ * execution of the predefined action ONLY.
+ *
+ */
+
+ if (dscp->newstate == HPS_FAULTED) {
+
+ /*
+ * fmip can be NULL here if the DE is processing
+ * replayed faults. In this case, no need to
+ * do anything.
+ */
+ if (diskp->fmip) {
+ schg_consume_faults(diskp);
+
+ diskp->faults_outstanding = B_FALSE;
+ }
+
+ } else if (dscp->newstate != HPS_FAULTED &&
+ DISK_STATE(nextstate) != HPS_UNKNOWN &&
+ dscp->newstate != HPS_REPAIRED) {
+
+ schg_execute_state_change_action(diskp,
+ DISK_STATE(diskp->state), DISK_STATE(nextstate));
+ }
+
+ if (!diskp->configured_yet &&
+ DISK_STATE(nextstate) == HPS_CONFIGURED) {
+
+ schg_update_fru_info(diskp);
+
+ /*
+ * If this state transition is lagging the true
+ * state of the system (e.g. if the true state of
+ * the disk is UNCONFIGURED, there's another
+ * state change somewhere later in the queue), then
+ * it's possible for the disk path property to not
+ * exist -- so check it, and if it doesn't exist,
+ * do not try to do disk_fault_init.
+ */
+ if (dm_prop_lookup(diskp->props,
+ DISK_PROP_DEVPATH) == NULL) {
+
+ log_msg(MM_SCHGMGR,
+ "Processed stale state change "
+ "for disk %s\n", diskp->location);
+
+ } else if ((err = disk_fault_init(diskp)) != 0) {
+
+ if (err != IE_NOT_SUPPORTED)
+ log_warn("Error initializing fault "
+ "monitor for disk in %s.\n",
+ diskp->location);
+ } else {
+ diskp->configured_yet = B_TRUE;
+
+ /*
+ * Now that the fault info for the disk is
+ * initialized, we can request that the fault
+ * manager do an analysis immediately. This is
+ * an initial analysis, so it will only happen
+ * the first time the disk enters the CONFIGURED
+ * state. After that, it is polled at the
+ * fault-polling interval.
+ * Note that the poking of the fault manager
+ * MUST occur AFTER the disk state is set
+ * (below), otherwise, the fault manager could
+ * ignore the disk if it analyzes it before
+ * this thread can set the state to configured.
+ */
+ poke_fault_manager = B_TRUE;
+ }
+
+ }
+
+ assert(pthread_mutex_lock(&diskp->manager_mutex) == 0);
+
+ /*
+ * Make the new state visible to all observers
+ */
+ diskp->state = nextstate;
+
+ /*
+ * Now, update the diskmon if the disk is now absent -- it's
+ * essential to do this after the state is set (above) so that
+ * state observers in other threads don't try to access the
+ * data structures that we're freeing here.
+ */
+
+ if (diskp->configured_yet &&
+ DISK_STATE(nextstate) == HPS_ABSENT) {
+ /*
+ * When the disk is removed, the fault monitor state is
+ * useless, so discard it.
+ */
+ assert(DISK_STATE(nextstate) != HPS_CONFIGURED);
+
+ disk_fault_uninit(diskp);
+
+ diskp->configured_yet = B_FALSE;
+
+ } else if (dscp->newstate == HPS_REPAIRED) {
+ /*
+ * Drop the current list of faults. There may be
+ * other faults pending in the fault list, and that's
+ * OK.
+ */
+ assert(pthread_mutex_lock(&diskp->disk_faults_mutex)
+ == 0);
+ diskp->disk_faults = DISK_FAULT_SOURCE_NONE;
+ assert(pthread_mutex_unlock(&diskp->disk_faults_mutex)
+ == 0);
+ }
+ assert(pthread_mutex_unlock(&diskp->manager_mutex) == 0);
+
+
+ if (poke_fault_manager) {
+ /*
+ * Now we can wake up the fault monitor thread
+ * to do an initial analysis of the disk:
+ */
+ poke_fault_manager = B_FALSE;
+ fault_manager_poke();
+ }
+
+ pth = dm_prop_lookup(diskp->props, DISK_PROP_DEVPATH);
+
+ log_msg(MM_SCHGMGR,
+ "[State change #%d][%s]: Disk path = %s\n",
+ diskp->state_change_count,
+ diskp->location, pth == NULL ? "Unknown" : pth);
+
+ log_msg(MM_SCHGMGR,
+ "[State change #%d][%s]: New state = %s%s\n",
+ diskp->state_change_count, diskp->location,
+ hotplug_state_string(diskp->state),
+ DISK_FAULTED(diskp->state) ? "+FAULTED" : "");
+
+ atomic_inc_uint(&diskp->state_change_count);
+
+ /* The caller is responsible for freeing the state change: */
+ free_statechange(dscp);
+ }
+ assert(pthread_mutex_lock(&g_schgt_state_mutex) == 0);
+ g_schgt_state = TS_EXITED;
+ assert(pthread_cond_broadcast(&g_schgt_state_cvar) == 0);
+ assert(pthread_mutex_unlock(&g_schgt_state_mutex) == 0);
+
+ log_msg(MM_SCHGMGR, "State change thread exiting...\n");
+}
+
+static void
+dm_state_change_nolock(diskmon_t *diskp, hotplug_state_t newstate)
+{
+ /* Enqueue a new state change for the state-change thread */
+ add_to_statechange_queue(diskp, newstate);
+}
+
+void
+dm_state_change(diskmon_t *diskp, hotplug_state_t newstate)
+{
+ assert(pthread_mutex_lock(&g_schgt_add_mutex) == 0);
+ dm_state_change_nolock(diskp, newstate);
+ assert(pthread_mutex_unlock(&g_schgt_add_mutex) == 0);
+}
+
+int
+init_state_change_manager(cfgdata_t *cfgdatap)
+{
+ /* new_queue() is guaranteed to succeed */
+ g_schg_queue = new_queue(B_TRUE, dmalloc, dfree, free_statechange);
+
+ assert(pthread_mutex_lock(&g_schgt_state_mutex) == 0);
+ g_schg_tid = fmd_thr_create(g_fm_hdl, disk_state_change_thread,
+ cfgdatap->disk_list);
+
+ /*
+ * Now, wait for the thread to enter the TS_RUNNING state. This
+ * is important because we want the state-change thread to pull the
+ * initial state of the disks on startup (without the wait, we could
+ * have the hotplug event handler race and deliver a state change
+ * before the state-change thread initialized the initial disk state).
+ */
+
+ while (g_schgt_state != TS_RUNNING) {
+ (void) pthread_cond_wait(&g_schgt_state_cvar,
+ &g_schgt_state_mutex);
+ }
+
+ assert(pthread_mutex_unlock(&g_schgt_state_mutex) == 0);
+
+ return (0);
+}
+
+/*ARGSUSED*/
+void
+cleanup_state_change_manager(cfgdata_t *cfgdatap)
+{
+ if (g_schgt_state != TS_RUNNING)
+ return;
+
+ g_schgt_state = TS_EXIT_REQUESTED;
+ queue_add(g_schg_queue, NULL);
+ assert(pthread_mutex_lock(&g_schgt_state_mutex) == 0);
+ while (g_schgt_state != TS_EXITED)
+ assert(pthread_cond_wait(&g_schgt_state_cvar,
+ &g_schgt_state_mutex) == 0);
+ assert(pthread_mutex_unlock(&g_schgt_state_mutex) == 0);
+ (void) pthread_join(g_schg_tid, NULL);
+ fmd_thr_destroy(g_fm_hdl, g_schg_tid);
+ queue_free(&g_schg_queue);
+ g_schgt_state = TS_NOT_RUNNING;
+}
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/schg_mgr.h b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/schg_mgr.h
new file mode 100644
index 0000000000..f63b59f597
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/schg_mgr.h
@@ -0,0 +1,56 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SCHG_MGR_H
+#define _SCHG_MGR_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * State Change Manager declarations
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "dm_types.h"
+
+#define DISK_STATE(d) ((d) & (~HPS_FAULTED))
+#define DISK_FAULTED(d) ((d) & HPS_FAULTED)
+
+extern int init_state_change_manager(cfgdata_t *cfgdatap);
+extern void cleanup_state_change_manager(cfgdata_t *cfgdatap);
+extern void dm_state_change(diskmon_t *diskp, hotplug_state_t newstate);
+extern void dm_fault_indicator_set(diskmon_t *diskp, ind_state_t istate);
+extern void block_state_change_events(void);
+extern void unblock_state_change_events(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SCHG_MGR_H */
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/scsi_util.c b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/scsi_util.c
new file mode 100644
index 0000000000..c446aa4d37
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/scsi_util.c
@@ -0,0 +1,1840 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <utility.h>
+#include <unistd.h>
+#include <stropts.h>
+#include <alloca.h>
+#include <assert.h>
+
+#include "util.h"
+#include "sfx4500-disk.h"
+#include "scsi_util.h"
+
+extern log_class_t g_verbose;
+
+#define MSGBUFLEN 64
+#define USCSI_DEFAULT_TIMEOUT 45
+#define USCSI_TIMEOUT_MAX INT_MAX
+
+static diskaddr_t scsi_extract_sense_info_descr(
+ struct scsi_descr_sense_hdr *sdsp, int rqlen);
+static void scsi_print_extended_sense(struct scsi_extended_sense *rq,
+ int rqlen);
+static void scsi_print_descr_sense(struct scsi_descr_sense_hdr *rq, int rqlen);
+
+/*
+ * Strings for printing mode sense page control values
+ */
+static slist_t page_control_strings[] = {
+ { "current", PC_CURRENT },
+ { "changeable", PC_CHANGEABLE },
+ { "default", PC_DEFAULT },
+ { "saved", PC_SAVED },
+ { NULL, 0 }
+};
+
+/*
+ * Strings for printing the mode select options
+ */
+static slist_t mode_select_strings[] = {
+ { "", 0 },
+ { "(pf)", MODE_SELECT_PF },
+ { "(sp)", MODE_SELECT_SP },
+ { "(pf,sp)", MODE_SELECT_PF|MODE_SELECT_SP },
+ { NULL, 0 }
+};
+
+static slist_t sensekey_strings[] = {
+ { "No sense error", KEY_NO_SENSE },
+ { "Recoverable error", KEY_RECOVERABLE_ERROR },
+ { "Not ready error", KEY_NOT_READY },
+ { "Medium error", KEY_MEDIUM_ERROR },
+ { "Hardware error", KEY_HARDWARE_ERROR },
+ { "Illegal request", KEY_ILLEGAL_REQUEST },
+ { "Unit attention error", KEY_UNIT_ATTENTION },
+ { "Write protect error", KEY_WRITE_PROTECT },
+ { "Blank check error", KEY_BLANK_CHECK },
+ { "Vendor unique error", KEY_VENDOR_UNIQUE },
+ { "Copy aborted error", KEY_COPY_ABORTED },
+ { "Aborted command", KEY_ABORTED_COMMAND },
+ { "Equal error", KEY_EQUAL },
+ { "Volume overflow", KEY_VOLUME_OVERFLOW },
+ { "Miscompare error", KEY_MISCOMPARE },
+ { "Reserved error", KEY_RESERVED },
+ { NULL, 0 }
+};
+
+static slist_t scsi_cmdname_strings[] = {
+ { "mode select", SCMD_MODE_SELECT },
+ { "mode sense", SCMD_MODE_SENSE },
+ { "mode select(10)", SCMD_MODE_SELECT_G1 },
+ { "mode sense(10)", SCMD_MODE_SENSE_G1 },
+ { "log sense", SCMD_LOG_SENSE_G1 },
+ { "request sense", SCMD_REQUEST_SENSE },
+ { NULL, 0 }
+};
+
+static struct _scsi_asq_key_strings {
+ uint_t asc;
+ uint_t ascq;
+ const char *message;
+} extended_sense_list[] = {
+ { 0x00, 0x00, "no additional sense info" },
+ { 0x00, 0x01, "filemark detected" },
+ { 0x00, 0x02, "end of partition/medium detected" },
+ { 0x00, 0x03, "setmark detected" },
+ { 0x00, 0x04, "begining of partition/medium detected" },
+ { 0x00, 0x05, "end of data detected" },
+ { 0x00, 0x06, "i/o process terminated" },
+ { 0x00, 0x11, "audio play operation in progress" },
+ { 0x00, 0x12, "audio play operation paused" },
+ { 0x00, 0x13, "audio play operation successfully completed" },
+ { 0x00, 0x14, "audio play operation stopped due to error" },
+ { 0x00, 0x15, "no current audio status to return" },
+ { 0x00, 0x16, "operation in progress" },
+ { 0x00, 0x17, "cleaning requested" },
+ { 0x00, 0x18, "erase operation in progress" },
+ { 0x00, 0x19, "locate operation in progress" },
+ { 0x00, 0x1A, "rewind operation in progress" },
+ { 0x00, 0x1B, "set capacity operation in progress" },
+ { 0x00, 0x1C, "verify operation in progress" },
+ { 0x01, 0x00, "no index/sector signal" },
+ { 0x02, 0x00, "no seek complete" },
+ { 0x03, 0x00, "peripheral device write fault" },
+ { 0x03, 0x01, "no write current" },
+ { 0x03, 0x02, "excessive write errors" },
+ { 0x04, 0x00, "LUN not ready" },
+ { 0x04, 0x01, "LUN is becoming ready" },
+ { 0x04, 0x02, "LUN initializing command required" },
+ { 0x04, 0x03, "LUN not ready intervention required" },
+ { 0x04, 0x04, "LUN not ready format in progress" },
+ { 0x04, 0x05, "LUN not ready, rebuild in progress" },
+ { 0x04, 0x06, "LUN not ready, recalculation in progress" },
+ { 0x04, 0x07, "LUN not ready, operation in progress" },
+ { 0x04, 0x08, "LUN not ready, long write in progress" },
+ { 0x04, 0x09, "LUN not ready, self-test in progress" },
+ { 0x04, 0x0A, "LUN not accessible, asymmetric access state "
+ "transition" },
+ { 0x04, 0x0B, "LUN not accessible, target port in standby state" },
+ { 0x04, 0x0C, "LUN not accessible, target port in unavailable state" },
+ { 0x04, 0x10, "LUN not ready, auxiliary memory not accessible" },
+ { 0x05, 0x00, "LUN does not respond to selection" },
+ { 0x06, 0x00, "reference position found" },
+ { 0x07, 0x00, "multiple peripheral devices selected" },
+ { 0x08, 0x00, "LUN communication failure" },
+ { 0x08, 0x01, "LUN communication time-out" },
+ { 0x08, 0x02, "LUN communication parity error" },
+ { 0x08, 0x03, "LUN communication crc error (ultra-DMA/32)" },
+ { 0x08, 0x04, "unreachable copy target" },
+ { 0x09, 0x00, "track following error" },
+ { 0x09, 0x01, "tracking servo failure" },
+ { 0x09, 0x02, "focus servo failure" },
+ { 0x09, 0x03, "spindle servo failure" },
+ { 0x09, 0x04, "head select fault" },
+ { 0x0a, 0x00, "error log overflow" },
+ { 0x0b, 0x00, "warning" },
+ { 0x0b, 0x01, "warning - specified temperature exceeded" },
+ { 0x0b, 0x02, "warning - enclosure degraded" },
+ { 0x0c, 0x00, "write error" },
+ { 0x0c, 0x01, "write error - recovered with auto reallocation" },
+ { 0x0c, 0x02, "write error - auto reallocation failed" },
+ { 0x0c, 0x03, "write error - recommend reassignment" },
+ { 0x0c, 0x04, "compression check miscompare error" },
+ { 0x0c, 0x05, "data expansion occurred during compression" },
+ { 0x0c, 0x06, "block not compressible" },
+ { 0x0c, 0x07, "write error - recovery needed" },
+ { 0x0c, 0x08, "write error - recovery failed" },
+ { 0x0c, 0x09, "write error - loss of streaming" },
+ { 0x0c, 0x0a, "write error - padding blocks added" },
+ { 0x0c, 0x0b, "auxiliary memory write error" },
+ { 0x0c, 0x0c, "write error - unexpected unsolicited data" },
+ { 0x0c, 0x0d, "write error - not enough unsolicited data" },
+ { 0x0d, 0x00, "error detected by third party temporary initiator" },
+ { 0x0d, 0x01, "third party device failure" },
+ { 0x0d, 0x02, "copy target device not reachable" },
+ { 0x0d, 0x03, "incorrect copy target device type" },
+ { 0x0d, 0x04, "copy target device data underrun" },
+ { 0x0d, 0x05, "copy target device data overrun" },
+ { 0x0e, 0x00, "invalid information unit" },
+ { 0x0e, 0x01, "information unit too short" },
+ { 0x0e, 0x02, "information unit too long" },
+ { 0x10, 0x00, "ID CRC or ECC error" },
+ { 0x11, 0x00, "unrecovered read error" },
+ { 0x11, 0x01, "read retries exhausted" },
+ { 0x11, 0x02, "error too long to correct" },
+ { 0x11, 0x03, "multiple read errors" },
+ { 0x11, 0x04, "unrecovered read error - auto reallocate failed" },
+ { 0x11, 0x05, "L-EC uncorrectable error" },
+ { 0x11, 0x06, "CIRC unrecovered error" },
+ { 0x11, 0x07, "data re-synchronization error" },
+ { 0x11, 0x08, "incomplete block read" },
+ { 0x11, 0x09, "no gap found" },
+ { 0x11, 0x0a, "miscorrected error" },
+ { 0x11, 0x0b, "unrecovered read error - recommend reassignment" },
+ { 0x11, 0x0c, "unrecovered read error - recommend rewrite the data" },
+ { 0x11, 0x0d, "de-compression crc error" },
+ { 0x11, 0x0e, "cannot decompress using declared algorithm" },
+ { 0x11, 0x0f, "error reading UPC/EAN number" },
+ { 0x11, 0x10, "error reading ISRC number" },
+ { 0x11, 0x11, "read error - loss of streaming" },
+ { 0x11, 0x12, "auxiliary memory read error" },
+ { 0x11, 0x13, "read error - failed retransmission request" },
+ { 0x12, 0x00, "address mark not found for ID field" },
+ { 0x13, 0x00, "address mark not found for data field" },
+ { 0x14, 0x00, "recorded entity not found" },
+ { 0x14, 0x01, "record not found" },
+ { 0x14, 0x02, "filemark or setmark not found" },
+ { 0x14, 0x03, "end-of-data not found" },
+ { 0x14, 0x04, "block sequence error" },
+ { 0x14, 0x05, "record not found - recommend reassignment" },
+ { 0x14, 0x06, "record not found - data auto-reallocated" },
+ { 0x14, 0x07, "locate operation failure" },
+ { 0x15, 0x00, "random positioning error" },
+ { 0x15, 0x01, "mechanical positioning error" },
+ { 0x15, 0x02, "positioning error detected by read of medium" },
+ { 0x16, 0x00, "data sync mark error" },
+ { 0x16, 0x01, "data sync error - data rewritten" },
+ { 0x16, 0x02, "data sync error - recommend rewrite" },
+ { 0x16, 0x03, "data sync error - data auto-reallocated" },
+ { 0x16, 0x04, "data sync error - recommend reassignment" },
+ { 0x17, 0x00, "recovered data with no error correction" },
+ { 0x17, 0x01, "recovered data with retries" },
+ { 0x17, 0x02, "recovered data with positive head offset" },
+ { 0x17, 0x03, "recovered data with negative head offset" },
+ { 0x17, 0x04, "recovered data with retries and/or CIRC applied" },
+ { 0x17, 0x05, "recovered data using previous sector id" },
+ { 0x17, 0x06, "recovered data without ECC - data auto-reallocated" },
+ { 0x17, 0x07, "recovered data without ECC - recommend reassignment" },
+ { 0x17, 0x08, "recovered data without ECC - recommend rewrite" },
+ { 0x17, 0x09, "recovered data without ECC - data rewritten" },
+ { 0x18, 0x00, "recovered data with error correction" },
+ { 0x18, 0x01, "recovered data with error corr. & retries applied" },
+ { 0x18, 0x02, "recovered data - data auto-reallocated" },
+ { 0x18, 0x03, "recovered data with CIRC" },
+ { 0x18, 0x04, "recovered data with L-EC" },
+ { 0x18, 0x05, "recovered data - recommend reassignment" },
+ { 0x18, 0x06, "recovered data - recommend rewrite" },
+ { 0x18, 0x07, "recovered data with ECC - data rewritten" },
+ { 0x18, 0x08, "recovered data with linking" },
+ { 0x19, 0x00, "defect list error" },
+ { 0x1a, 0x00, "parameter list length error" },
+ { 0x1b, 0x00, "synchronous data xfer error" },
+ { 0x1c, 0x00, "defect list not found" },
+ { 0x1c, 0x01, "primary defect list not found" },
+ { 0x1c, 0x02, "grown defect list not found" },
+ { 0x1d, 0x00, "miscompare during verify" },
+ { 0x1e, 0x00, "recovered ID with ECC" },
+ { 0x1f, 0x00, "partial defect list transfer" },
+ { 0x20, 0x00, "invalid command operation code" },
+ { 0x20, 0x01, "access denied - initiator pending-enrolled" },
+ { 0x20, 0x02, "access denied - no access rights" },
+ { 0x20, 0x03, "access denied - invalid mgmt id key" },
+ { 0x20, 0x04, "illegal command while in write capable state" },
+ { 0x20, 0x06, "illegal command while in explicit address mode" },
+ { 0x20, 0x07, "illegal command while in implicit address mode" },
+ { 0x20, 0x08, "access denied - enrollment conflict" },
+ { 0x20, 0x09, "access denied - invalid lu identifier" },
+ { 0x20, 0x0a, "access denied - invalid proxy token" },
+ { 0x20, 0x0b, "access denied - ACL LUN conflict" },
+ { 0x21, 0x00, "logical block address out of range" },
+ { 0x21, 0x01, "invalid element address" },
+ { 0x21, 0x02, "invalid address for write" },
+ { 0x22, 0x00, "illegal function" },
+ { 0x24, 0x00, "invalid field in cdb" },
+ { 0x24, 0x01, "cdb decryption error" },
+ { 0x25, 0x00, "LUN not supported" },
+ { 0x26, 0x00, "invalid field in param list" },
+ { 0x26, 0x01, "parameter not supported" },
+ { 0x26, 0x02, "parameter value invalid" },
+ { 0x26, 0x03, "threshold parameters not supported" },
+ { 0x26, 0x04, "invalid release of persistent reservation" },
+ { 0x26, 0x05, "data decryption error" },
+ { 0x26, 0x06, "too many target descriptors" },
+ { 0x26, 0x07, "unsupported target descriptor type code" },
+ { 0x26, 0x08, "too many segment descriptors" },
+ { 0x26, 0x09, "unsupported segment descriptor type code" },
+ { 0x26, 0x0a, "unexpected inexact segment" },
+ { 0x26, 0x0b, "inline data length exceeded" },
+ { 0x26, 0x0c, "invalid operation for copy source or destination" },
+ { 0x26, 0x0d, "copy segment granularity violation" },
+ { 0x27, 0x00, "write protected" },
+ { 0x27, 0x01, "hardware write protected" },
+ { 0x27, 0x02, "LUN software write protected" },
+ { 0x27, 0x03, "associated write protect" },
+ { 0x27, 0x04, "persistent write protect" },
+ { 0x27, 0x05, "permanent write protect" },
+ { 0x27, 0x06, "conditional write protect" },
+ { 0x28, 0x00, "medium may have changed" },
+ { 0x28, 0x01, "import or export element accessed" },
+ { 0x29, 0x00, "power on, reset, or bus reset occurred" },
+ { 0x29, 0x01, "power on occurred" },
+ { 0x29, 0x02, "scsi bus reset occurred" },
+ { 0x29, 0x03, "bus device reset message occurred" },
+ { 0x29, 0x04, "device internal reset" },
+ { 0x29, 0x05, "transceiver mode changed to single-ended" },
+ { 0x29, 0x06, "transceiver mode changed to LVD" },
+ { 0x29, 0x07, "i_t nexus loss occurred" },
+ { 0x2a, 0x00, "parameters changed" },
+ { 0x2a, 0x01, "mode parameters changed" },
+ { 0x2a, 0x02, "log parameters changed" },
+ { 0x2a, 0x03, "reservations preempted" },
+ { 0x2a, 0x04, "reservations released" },
+ { 0x2a, 0x05, "registrations preempted" },
+ { 0x2a, 0x06, "asymmetric access state changed" },
+ { 0x2a, 0x07, "implicit asymmetric access state transition failed" },
+ { 0x2b, 0x00, "copy cannot execute since host cannot disconnect" },
+ { 0x2c, 0x00, "command sequence error" },
+ { 0x2c, 0x03, "current program area is not empty" },
+ { 0x2c, 0x04, "current program area is empty" },
+ { 0x2c, 0x06, "persistent prevent conflict" },
+ { 0x2c, 0x07, "previous busy status" },
+ { 0x2c, 0x08, "previous task set full status" },
+ { 0x2c, 0x09, "previous reservation conflict status" },
+ { 0x2d, 0x00, "overwrite error on update in place" },
+ { 0x2e, 0x00, "insufficient time for operation" },
+ { 0x2f, 0x00, "commands cleared by another initiator" },
+ { 0x30, 0x00, "incompatible medium installed" },
+ { 0x30, 0x01, "cannot read medium - unknown format" },
+ { 0x30, 0x02, "cannot read medium - incompatible format" },
+ { 0x30, 0x03, "cleaning cartridge installed" },
+ { 0x30, 0x04, "cannot write medium - unknown format" },
+ { 0x30, 0x05, "cannot write medium - incompatible format" },
+ { 0x30, 0x06, "cannot format medium - incompatible medium" },
+ { 0x30, 0x07, "cleaning failure" },
+ { 0x30, 0x08, "cannot write - application code mismatch" },
+ { 0x30, 0x09, "current session not fixated for append" },
+ { 0x30, 0x10, "medium not formatted" },
+ { 0x31, 0x00, "medium format corrupted" },
+ { 0x31, 0x01, "format command failed" },
+ { 0x31, 0x02, "zoned formatting failed due to spare linking" },
+ { 0x32, 0x00, "no defect spare location available" },
+ { 0x32, 0x01, "defect list update failure" },
+ { 0x33, 0x00, "tape length error" },
+ { 0x34, 0x00, "enclosure failure" },
+ { 0x35, 0x00, "enclosure services failure" },
+ { 0x35, 0x01, "unsupported enclosure function" },
+ { 0x35, 0x02, "enclosure services unavailable" },
+ { 0x35, 0x03, "enclosure services transfer failure" },
+ { 0x35, 0x04, "enclosure services transfer refused" },
+ { 0x36, 0x00, "ribbon, ink, or toner failure" },
+ { 0x37, 0x00, "rounded parameter" },
+ { 0x39, 0x00, "saving parameters not supported" },
+ { 0x3a, 0x00, "medium not present" },
+ { 0x3a, 0x01, "medium not present - tray closed" },
+ { 0x3a, 0x02, "medium not present - tray open" },
+ { 0x3a, 0x03, "medium not present - loadable" },
+ { 0x3a, 0x04, "medium not present - medium auxiliary memory "
+ "accessible" },
+ { 0x3b, 0x00, "sequential positioning error" },
+ { 0x3b, 0x01, "tape position error at beginning-of-medium" },
+ { 0x3b, 0x02, "tape position error at end-of-medium" },
+ { 0x3b, 0x08, "reposition error" },
+ { 0x3b, 0x0c, "position past beginning of medium" },
+ { 0x3b, 0x0d, "medium destination element full" },
+ { 0x3b, 0x0e, "medium source element empty" },
+ { 0x3b, 0x0f, "end of medium reached" },
+ { 0x3b, 0x11, "medium magazine not accessible" },
+ { 0x3b, 0x12, "medium magazine removed" },
+ { 0x3b, 0x13, "medium magazine inserted" },
+ { 0x3b, 0x14, "medium magazine locked" },
+ { 0x3b, 0x15, "medium magazine unlocked" },
+ { 0x3b, 0x16, "mechanical positioning or changer error" },
+ { 0x3d, 0x00, "invalid bits in indentify message" },
+ { 0x3e, 0x00, "LUN has not self-configured yet" },
+ { 0x3e, 0x01, "LUN failure" },
+ { 0x3e, 0x02, "timeout on LUN" },
+ { 0x3e, 0x03, "LUN failed self-test" },
+ { 0x3e, 0x04, "LUN unable to update self-test log" },
+ { 0x3f, 0x00, "target operating conditions have changed" },
+ { 0x3f, 0x01, "microcode has been changed" },
+ { 0x3f, 0x02, "changed operating definition" },
+ { 0x3f, 0x03, "inquiry data has changed" },
+ { 0x3f, 0x04, "component device attached" },
+ { 0x3f, 0x05, "device identifier changed" },
+ { 0x3f, 0x06, "redundancy group created or modified" },
+ { 0x3f, 0x07, "redundancy group deleted" },
+ { 0x3f, 0x08, "spare created or modified" },
+ { 0x3f, 0x09, "spare deleted" },
+ { 0x3f, 0x0a, "volume set created or modified" },
+ { 0x3f, 0x0b, "volume set deleted" },
+ { 0x3f, 0x0c, "volume set deassigned" },
+ { 0x3f, 0x0d, "volume set reassigned" },
+ { 0x3f, 0x0e, "reported LUNs data has changed" },
+ { 0x3f, 0x0f, "echo buffer overwritten" },
+ { 0x3f, 0x10, "medium loadable" },
+ { 0x3f, 0x11, "medium auxiliary memory accessible" },
+ { 0x40, 0x00, "ram failure" },
+ { 0x41, 0x00, "data path failure" },
+ { 0x42, 0x00, "power-on or self-test failure" },
+ { 0x43, 0x00, "message error" },
+ { 0x44, 0x00, "internal target failure" },
+ { 0x45, 0x00, "select or reselect failure" },
+ { 0x46, 0x00, "unsuccessful soft reset" },
+ { 0x47, 0x00, "scsi parity error" },
+ { 0x47, 0x01, "data phase crc error detected" },
+ { 0x47, 0x02, "scsi parity error detected during st data phase" },
+ { 0x47, 0x03, "information unit iucrc error detected" },
+ { 0x47, 0x04, "asynchronous information protection error detected" },
+ { 0x47, 0x05, "protocol service crc error" },
+ { 0x47, 0x7f, "some commands cleared by iscsi protocol event" },
+ { 0x48, 0x00, "initiator detected error message received" },
+ { 0x49, 0x00, "invalid message error" },
+ { 0x4a, 0x00, "command phase error" },
+ { 0x4b, 0x00, "data phase error" },
+ { 0x4b, 0x01, "invalid target port transfer tag received" },
+ { 0x4b, 0x02, "too much write data" },
+ { 0x4b, 0x03, "ack/nak timeout" },
+ { 0x4b, 0x04, "nak received" },
+ { 0x4b, 0x05, "data offset error" },
+ { 0x4c, 0x00, "logical unit failed self-configuration" },
+ { 0x4d, 0x00, "tagged overlapped commands (ASCQ = queue tag)" },
+ { 0x4e, 0x00, "overlapped commands attempted" },
+ { 0x50, 0x00, "write append error" },
+ { 0x51, 0x00, "erase failure" },
+ { 0x52, 0x00, "cartridge fault" },
+ { 0x53, 0x00, "media load or eject failed" },
+ { 0x53, 0x01, "unload tape failure" },
+ { 0x53, 0x02, "medium removal prevented" },
+ { 0x54, 0x00, "scsi to host system interface failure" },
+ { 0x55, 0x00, "system resource failure" },
+ { 0x55, 0x01, "system buffer full" },
+ { 0x55, 0x02, "insufficient reservation resources" },
+ { 0x55, 0x03, "insufficient resources" },
+ { 0x55, 0x04, "insufficient registration resources" },
+ { 0x55, 0x05, "insufficient access control resources" },
+ { 0x55, 0x06, "auxiliary memory out of space" },
+ { 0x57, 0x00, "unable to recover TOC" },
+ { 0x58, 0x00, "generation does not exist" },
+ { 0x59, 0x00, "updated block read" },
+ { 0x5a, 0x00, "operator request or state change input" },
+ { 0x5a, 0x01, "operator medium removal request" },
+ { 0x5a, 0x02, "operator selected write protect" },
+ { 0x5a, 0x03, "operator selected write permit" },
+ { 0x5b, 0x00, "log exception" },
+ { 0x5b, 0x01, "threshold condition met" },
+ { 0x5b, 0x02, "log counter at maximum" },
+ { 0x5b, 0x03, "log list codes exhausted" },
+ { 0x5c, 0x00, "RPL status change" },
+ { 0x5c, 0x01, "spindles synchronized" },
+ { 0x5c, 0x02, "spindles not synchronized" },
+ { 0x5d, 0x00, "drive operation marginal, service immediately"
+ " (failure prediction threshold exceeded)" },
+ { 0x5d, 0x01, "media failure prediction threshold exceeded" },
+ { 0x5d, 0x02, "LUN failure prediction threshold exceeded" },
+ { 0x5d, 0x03, "spare area exhaustion prediction threshold exceeded" },
+ { 0x5d, 0x10, "hardware impending failure general hard drive failure" },
+ { 0x5d, 0x11, "hardware impending failure drive error rate too high" },
+ { 0x5d, 0x12, "hardware impending failure data error rate too high" },
+ { 0x5d, 0x13, "hardware impending failure seek error rate too high" },
+ { 0x5d, 0x14, "hardware impending failure too many block reassigns" },
+ { 0x5d, 0x15, "hardware impending failure access times too high" },
+ { 0x5d, 0x16, "hardware impending failure start unit times too high" },
+ { 0x5d, 0x17, "hardware impending failure channel parametrics" },
+ { 0x5d, 0x18, "hardware impending failure controller detected" },
+ { 0x5d, 0x19, "hardware impending failure throughput performance" },
+ { 0x5d, 0x1a, "hardware impending failure seek time performance" },
+ { 0x5d, 0x1b, "hardware impending failure spin-up retry count" },
+ { 0x5d, 0x1c, "hardware impending failure drive calibration retry "
+ "count" },
+ { 0x5d, 0x20, "controller impending failure general hard drive "
+ "failure" },
+ { 0x5d, 0x21, "controller impending failure drive error rate too "
+ "high" },
+ { 0x5d, 0x22, "controller impending failure data error rate too high" },
+ { 0x5d, 0x23, "controller impending failure seek error rate too high" },
+ { 0x5d, 0x24, "controller impending failure too many block reassigns" },
+ { 0x5d, 0x25, "controller impending failure access times too high" },
+ { 0x5d, 0x26, "controller impending failure start unit times too "
+ "high" },
+ { 0x5d, 0x27, "controller impending failure channel parametrics" },
+ { 0x5d, 0x28, "controller impending failure controller detected" },
+ { 0x5d, 0x29, "controller impending failure throughput performance" },
+ { 0x5d, 0x2a, "controller impending failure seek time performance" },
+ { 0x5d, 0x2b, "controller impending failure spin-up retry count" },
+ { 0x5d, 0x2c, "controller impending failure drive calibration retry "
+ "cnt" },
+ { 0x5d, 0x30, "data channel impending failure general hard drive "
+ "failure" },
+ { 0x5d, 0x31, "data channel impending failure drive error rate too "
+ "high" },
+ { 0x5d, 0x32, "data channel impending failure data error rate too "
+ "high" },
+ { 0x5d, 0x33, "data channel impending failure seek error rate too "
+ "high" },
+ { 0x5d, 0x34, "data channel impending failure too many block "
+ "reassigns" },
+ { 0x5d, 0x35, "data channel impending failure access times too high" },
+ { 0x5d, 0x36, "data channel impending failure start unit times too "
+ "high" },
+ { 0x5d, 0x37, "data channel impending failure channel parametrics" },
+ { 0x5d, 0x38, "data channel impending failure controller detected" },
+ { 0x5d, 0x39, "data channel impending failure throughput performance" },
+ { 0x5d, 0x3a, "data channel impending failure seek time performance" },
+ { 0x5d, 0x3b, "data channel impending failure spin-up retry count" },
+ { 0x5d, 0x3c, "data channel impending failure drive calibrate retry "
+ "cnt" },
+ { 0x5d, 0x40, "servo impending failure general hard drive failure" },
+ { 0x5d, 0x41, "servo impending failure drive error rate too high" },
+ { 0x5d, 0x42, "servo impending failure data error rate too high" },
+ { 0x5d, 0x43, "servo impending failure seek error rate too high" },
+ { 0x5d, 0x44, "servo impending failure too many block reassigns" },
+ { 0x5d, 0x45, "servo impending failure access times too high" },
+ { 0x5d, 0x46, "servo impending failure start unit times too high" },
+ { 0x5d, 0x47, "servo impending failure channel parametrics" },
+ { 0x5d, 0x48, "servo impending failure controller detected" },
+ { 0x5d, 0x49, "servo impending failure throughput performance" },
+ { 0x5d, 0x4a, "servo impending failure seek time performance" },
+ { 0x5d, 0x4b, "servo impending failure spin-up retry count" },
+ { 0x5d, 0x4c, "servo impending failure drive calibration retry count" },
+ { 0x5d, 0x50, "spindle impending failure general hard drive failure" },
+ { 0x5d, 0x51, "spindle impending failure drive error rate too high" },
+ { 0x5d, 0x52, "spindle impending failure data error rate too high" },
+ { 0x5d, 0x53, "spindle impending failure seek error rate too high" },
+ { 0x5d, 0x54, "spindle impending failure too many block reassigns" },
+ { 0x5d, 0x55, "spindle impending failure access times too high" },
+ { 0x5d, 0x56, "spindle impending failure start unit times too high" },
+ { 0x5d, 0x57, "spindle impending failure channel parametrics" },
+ { 0x5d, 0x58, "spindle impending failure controller detected" },
+ { 0x5d, 0x59, "spindle impending failure throughput performance" },
+ { 0x5d, 0x5a, "spindle impending failure seek time performance" },
+ { 0x5d, 0x5b, "spindle impending failure spin-up retry count" },
+ { 0x5d, 0x5c, "spindle impending failure drive calibration retry "
+ "count" },
+ { 0x5d, 0x60, "firmware impending failure general hard drive failure" },
+ { 0x5d, 0x61, "firmware impending failure drive error rate too high" },
+ { 0x5d, 0x62, "firmware impending failure data error rate too high" },
+ { 0x5d, 0x63, "firmware impending failure seek error rate too high" },
+ { 0x5d, 0x64, "firmware impending failure too many block reassigns" },
+ { 0x5d, 0x65, "firmware impending failure access times too high" },
+ { 0x5d, 0x66, "firmware impending failure start unit times too high" },
+ { 0x5d, 0x67, "firmware impending failure channel parametrics" },
+ { 0x5d, 0x68, "firmware impending failure controller detected" },
+ { 0x5d, 0x69, "firmware impending failure throughput performance" },
+ { 0x5d, 0x6a, "firmware impending failure seek time performance" },
+ { 0x5d, 0x6b, "firmware impending failure spin-up retry count" },
+ { 0x5d, 0x6c, "firmware impending failure drive calibration retry "
+ "count" },
+ { 0x5d, 0xff, "failure prediction threshold exceeded (false)" },
+ { 0x5e, 0x00, "low power condition active" },
+ { 0x5e, 0x01, "idle condition activated by timer" },
+ { 0x5e, 0x02, "standby condition activated by timer" },
+ { 0x5e, 0x03, "idle condition activated by command" },
+ { 0x5e, 0x04, "standby condition activated by command" },
+ { 0x60, 0x00, "lamp failure" },
+ { 0x61, 0x00, "video aquisition error" },
+ { 0x62, 0x00, "scan head positioning error" },
+ { 0x63, 0x00, "end of user area encountered on this track" },
+ { 0x63, 0x01, "packet does not fit in available space" },
+ { 0x64, 0x00, "illegal mode for this track" },
+ { 0x64, 0x01, "invalid packet size" },
+ { 0x65, 0x00, "voltage fault" },
+ { 0x66, 0x00, "automatic document feeder cover up" },
+ { 0x67, 0x00, "configuration failure" },
+ { 0x67, 0x01, "configuration of incapable LUNs failed" },
+ { 0x67, 0x02, "add LUN failed" },
+ { 0x67, 0x03, "modification of LUN failed" },
+ { 0x67, 0x04, "exchange of LUN failed" },
+ { 0x67, 0x05, "remove of LUN failed" },
+ { 0x67, 0x06, "attachment of LUN failed" },
+ { 0x67, 0x07, "creation of LUN failed" },
+ { 0x67, 0x08, "assign failure occurred" },
+ { 0x67, 0x09, "multiply assigned LUN" },
+ { 0x67, 0x0a, "set target port groups command failed" },
+ { 0x68, 0x00, "logical unit not configured" },
+ { 0x69, 0x00, "data loss on logical unit" },
+ { 0x69, 0x01, "multiple LUN failures" },
+ { 0x69, 0x02, "parity/data mismatch" },
+ { 0x6a, 0x00, "informational, refer to log" },
+ { 0x6b, 0x00, "state change has occured" },
+ { 0x6b, 0x01, "redundancy level got better" },
+ { 0x6b, 0x02, "redundancy level got worse" },
+ { 0x6c, 0x00, "rebuild failure occured" },
+ { 0x6d, 0x00, "recalculate failure occured" },
+ { 0x6e, 0x00, "command to logical unit failed" },
+ { 0x6f, 0x00, "copy protect key exchange failure authentication "
+ "failure" },
+ { 0x6f, 0x01, "copy protect key exchange failure key not present" },
+ { 0x6f, 0x02, "copy protect key exchange failure key not established" },
+ { 0x6f, 0x03, "read of scrambled sector without authentication" },
+ { 0x6f, 0x04, "media region code is mismatched to LUN region" },
+ { 0x6f, 0x05, "drive region must be permanent/region reset count "
+ "error" },
+ { 0x70, 0xffff, "decompression exception short algorithm id of ASCQ" },
+ { 0x71, 0x00, "decompression exception long algorithm id" },
+ { 0x72, 0x00, "session fixation error" },
+ { 0x72, 0x01, "session fixation error writing lead-in" },
+ { 0x72, 0x02, "session fixation error writing lead-out" },
+ { 0x72, 0x03, "session fixation error - incomplete track in session" },
+ { 0x72, 0x04, "empty or partially written reserved track" },
+ { 0x72, 0x05, "no more track reservations allowed" },
+ { 0x73, 0x00, "cd control error" },
+ { 0x73, 0x01, "power calibration area almost full" },
+ { 0x73, 0x02, "power calibration area is full" },
+ { 0x73, 0x03, "power calibration area error" },
+ { 0x73, 0x04, "program memory area update failure" },
+ { 0x73, 0x05, "program memory area is full" },
+ { 0x73, 0x06, "rma/pma is almost full" },
+ { 0xffff, 0xffff, NULL }
+};
+
+static char *
+scsi_util_asc_ascq_name(uint_t asc, uint_t ascq, char *buf, int buflen)
+{
+ int i = 0;
+
+ while (extended_sense_list[i].asc != 0xffff) {
+ if ((asc == extended_sense_list[i].asc) &&
+ ((ascq == extended_sense_list[i].ascq) ||
+ (extended_sense_list[i].ascq == 0xffff))) {
+ return ((char *)extended_sense_list[i].message);
+ }
+ i++;
+ }
+ (void) snprintf(buf, buflen, "<vendor unique code 0x%x>", asc);
+ return (buf);
+}
+
+const char *
+scsi_asc_ascq_string(uint_t asc, uint_t ascq)
+{
+ int i = 0;
+
+ while (extended_sense_list[i].asc != 0xffff) {
+ if ((asc == extended_sense_list[i].asc) &&
+ ((ascq == extended_sense_list[i].ascq) ||
+ (extended_sense_list[i].ascq == 0xffff))) {
+ return (extended_sense_list[i].message);
+ }
+ i++;
+ }
+ return (NULL);
+}
+
+static void
+scsi_printerr(ucmd, rq, rqlen)
+ struct uscsi_cmd *ucmd;
+ struct scsi_extended_sense *rq;
+ int rqlen;
+{
+ diskaddr_t blkno;
+ struct scsi_descr_sense_hdr *sdsp =
+ (struct scsi_descr_sense_hdr *)rq;
+ char msgbuf[MSGBUFLEN];
+
+ if (find_string(sensekey_strings, rq->es_key) == NULL) {
+ log_warn("Unknown error");
+ }
+
+ log_warn("During %s:",
+ find_string(scsi_cmdname_strings, ucmd->uscsi_cdb[0]));
+
+ /*
+ * Get asc, ascq and info field from sense data. There are two
+ * possible formats (fixed sense data and descriptor sense data)
+ * depending on the value of es_code.
+ */
+ switch (rq->es_code) {
+ case CODE_FMT_DESCR_CURRENT:
+ case CODE_FMT_DESCR_DEFERRED:
+ blkno =
+ (diskaddr_t)scsi_extract_sense_info_descr(sdsp, rqlen);
+ if (blkno != (diskaddr_t)-1)
+ log_warn(": block %lld (0x%llx)", blkno, blkno);
+
+ log_warn("\n");
+
+ log_warn("ASC: 0x%x ASCQ: 0x%x (%s)\n",
+ sdsp->ds_add_code,
+ sdsp->ds_qual_code,
+ scsi_util_asc_ascq_name(sdsp->ds_add_code,
+ sdsp->ds_qual_code, msgbuf, MSGBUFLEN));
+
+ break;
+ case CODE_FMT_FIXED_CURRENT:
+ case CODE_FMT_FIXED_DEFERRED:
+ default:
+ if (rq->es_valid) {
+ blkno = (rq->es_info_1 << 24) |
+ (rq->es_info_2 << 16) |
+ (rq->es_info_3 << 8) | rq->es_info_4;
+ log_warn(": block %lld (0x%llx)", blkno, blkno);
+ }
+
+ log_warn("\n");
+
+ if (rq->es_add_len >= 6) {
+ log_warn("ASC: 0x%x ASCQ: 0x%x (%s)\n",
+ rq->es_add_code,
+ rq->es_qual_code,
+ scsi_util_asc_ascq_name(rq->es_add_code,
+ rq->es_qual_code, msgbuf, MSGBUFLEN));
+ }
+ break;
+ }
+
+ if (rq->es_key == KEY_ILLEGAL_REQUEST) {
+ log_dump(MM_SCSI, "cmd:", (caddr_t)ucmd,
+ sizeof (struct uscsi_cmd));
+ log_dump(MM_SCSI, "cdb:", (caddr_t)ucmd->uscsi_cdb,
+ ucmd->uscsi_cdblen);
+ }
+ log_dump(MM_SCSI, "sense:", (caddr_t)rq, rqlen);
+
+ switch (rq->es_code) {
+ case CODE_FMT_DESCR_CURRENT:
+ case CODE_FMT_DESCR_DEFERRED:
+ scsi_print_descr_sense(sdsp, rqlen);
+ break;
+ case CODE_FMT_FIXED_CURRENT:
+ case CODE_FMT_FIXED_DEFERRED:
+ default:
+ scsi_print_extended_sense(rq, rqlen);
+ break;
+ }
+}
+
+void
+scsi_translate_error(struct scsi_extended_sense *rq, uint_t *skeyp,
+ uint_t *ascp, uint_t *ascqp)
+{
+ struct scsi_descr_sense_hdr *sdsp =
+ (struct scsi_descr_sense_hdr *)rq;
+
+ *skeyp = rq->es_key;
+
+ /*
+ * Get asc, ascq and info field from sense data. There are two
+ * possible formats (fixed sense data and descriptor sense data)
+ * depending on the value of es_code.
+ */
+ switch (rq->es_code) {
+ case CODE_FMT_DESCR_CURRENT:
+ case CODE_FMT_DESCR_DEFERRED:
+
+ *ascp = sdsp->ds_add_code;
+ *ascqp = sdsp->ds_qual_code;
+ break;
+
+ case CODE_FMT_FIXED_CURRENT:
+ case CODE_FMT_FIXED_DEFERRED:
+ default:
+
+ if (rq->es_add_len >= 6) {
+ *ascp = rq->es_add_code;
+ *ascqp = rq->es_qual_code;
+ } else {
+ *ascp = 0xff;
+ *ascqp = 0xff;
+ }
+ break;
+ }
+}
+
+
+/*
+ * Retrieve "information" field from descriptor format
+ * sense data. Iterates through each sense descriptor
+ * looking for the information descriptor and returns
+ * the information field from that descriptor.
+ */
+static diskaddr_t
+scsi_extract_sense_info_descr(struct scsi_descr_sense_hdr *sdsp, int rqlen)
+{
+ diskaddr_t result;
+ uint8_t *descr_offset;
+ int valid_sense_length;
+ struct scsi_information_sense_descr *isd;
+
+ /*
+ * Initialize result to -1 indicating there is no information
+ * descriptor
+ */
+ result = (diskaddr_t)-1;
+
+ /*
+ * The first descriptor will immediately follow the header
+ */
+ descr_offset = (uint8_t *)(sdsp+1); /* Pointer arithmetic */
+
+ /*
+ * Calculate the amount of valid sense data
+ */
+ valid_sense_length =
+ MIN((sizeof (struct scsi_descr_sense_hdr) +
+ sdsp->ds_addl_sense_length),
+ rqlen);
+
+ /*
+ * Iterate through the list of descriptors, stopping when we
+ * run out of sense data
+ */
+ while ((descr_offset + sizeof (struct scsi_information_sense_descr)) <=
+ (uint8_t *)sdsp + valid_sense_length) {
+ /*
+ * Check if this is an information descriptor. We can
+ * use the scsi_information_sense_descr structure as a
+ * template since the first two fields are always the
+ * same
+ */
+ isd = (struct scsi_information_sense_descr *)descr_offset;
+ if (isd->isd_descr_type == DESCR_INFORMATION) {
+ /*
+ * Found an information descriptor. Copy the
+ * information field. There will only be one
+ * information descriptor so we can stop looking.
+ */
+ result =
+ (((diskaddr_t)isd->isd_information[0] << 56) |
+ ((diskaddr_t)isd->isd_information[1] << 48) |
+ ((diskaddr_t)isd->isd_information[2] << 40) |
+ ((diskaddr_t)isd->isd_information[3] << 32) |
+ ((diskaddr_t)isd->isd_information[4] << 24) |
+ ((diskaddr_t)isd->isd_information[5] << 16) |
+ ((diskaddr_t)isd->isd_information[6] << 8) |
+ ((diskaddr_t)isd->isd_information[7]));
+ break;
+ }
+
+ /*
+ * Get pointer to the next descriptor. The "additional
+ * length" field holds the length of the descriptor except
+ * for the "type" and "additional length" fields, so
+ * we need to add 2 to get the total length.
+ */
+ descr_offset += (isd->isd_addl_length + 2);
+ }
+
+ return (result);
+}
+
+/*
+ * Display the full scsi_extended_sense as returned by the device
+ */
+static void
+scsi_print_extended_sense(struct scsi_extended_sense *rq, int rqlen)
+{
+ static char *scsi_extended_sense_labels[] = {
+ "Request sense valid: ",
+ "Error class and code: ",
+ "Segment number: ",
+ "Filemark: ",
+ "End-of-medium: ",
+ "Incorrect length indicator: ",
+ "Sense key: ",
+ "Information field: ",
+ "Additional sense length: ",
+ "Command-specific information: ",
+ "Additional sense code: ",
+ "Additional sense code qualifier: ",
+ "Field replaceable unit code: ",
+ "Sense-key specific: ",
+ "Additional sense bytes: "
+ };
+
+ char **p = scsi_extended_sense_labels;
+
+ if (rqlen < (sizeof (*rq) - 2) || !rq->es_valid) {
+ /*
+ * target should be capable of returning at least 18
+ * bytes of data, i.e upto rq->es_skey_specific field.
+ * The additional sense bytes (2 or more ...) are optional.
+ */
+ return;
+ }
+
+ log_msg(MM_SCSI, "\n%s%s\n", *p++, rq->es_valid ? "yes" : "no");
+ log_msg(MM_SCSI, "%s0x%02x\n", *p++, (rq->es_class << 4) + rq->es_code);
+ log_msg(MM_SCSI, "%s%d\n", *p++, rq->es_segnum);
+ log_msg(MM_SCSI, "%s%s\n", *p++, rq->es_filmk ? "yes" : "no");
+ log_msg(MM_SCSI, "%s%s\n", *p++, rq->es_eom ? "yes" : "no");
+ log_msg(MM_SCSI, "%s%s\n", *p++, rq->es_ili ? "yes" : "no");
+ log_msg(MM_SCSI, "%s%d\n", *p++, rq->es_key);
+
+ log_msg(MM_SCSI, "%s0x%02x 0x%02x 0x%02x 0x%02x\n", *p++, rq->es_info_1,
+ rq->es_info_2, rq->es_info_3, rq->es_info_4);
+ log_msg(MM_SCSI, "%s%d\n", *p++, rq->es_add_len);
+ log_msg(MM_SCSI, "%s0x%02x 0x%02x 0x%02x 0x%02x\n", *p++,
+ rq->es_cmd_info[0], rq->es_cmd_info[1], rq->es_cmd_info[2],
+ rq->es_cmd_info[3]);
+ log_msg(MM_SCSI, "%s0x%02x = %d\n", *p++, rq->es_add_code,
+ rq->es_add_code);
+ log_msg(MM_SCSI, "%s0x%02x = %d\n", *p++, rq->es_qual_code,
+ rq->es_qual_code);
+ log_msg(MM_SCSI, "%s%d\n", *p++, rq->es_fru_code);
+ log_msg(MM_SCSI, "%s0x%02x 0x%02x 0x%02x\n", *p++,
+ rq->es_skey_specific[0], rq->es_skey_specific[1],
+ rq->es_skey_specific[2]);
+ if (rqlen >= sizeof (*rq)) {
+ log_msg(MM_SCSI, "%s0x%02x 0x%02x%s\n", *p, rq->es_add_info[0],
+ rq->es_add_info[1], (rqlen > sizeof (*rq)) ? " ..." : "");
+ }
+
+ log_msg(MM_SCSI, "\n");
+}
+
+/*
+ * Display the full descriptor sense data as returned by the device
+ */
+
+static void
+scsi_print_descr_sense(struct scsi_descr_sense_hdr *rq, int rqlen)
+{
+/*
+ * Labels for the various fields of the scsi_descr_sense_hdr structure
+ */
+ static char *scsi_descr_sense_labels[] = {
+ "Error class and code: ",
+ "Sense key: ",
+ "Additional sense length: ",
+ "Additional sense code: ",
+ "Additional sense code qualifier: ",
+ "Additional sense bytes: "
+ };
+
+ struct scsi_information_sense_descr *isd;
+ uint8_t *descr_offset;
+ int valid_sense_length;
+ char **p = scsi_descr_sense_labels;
+
+
+ if (rqlen < sizeof (struct scsi_descr_sense_hdr)) {
+ /*
+ * target must return at least 8 bytes of data
+ */
+ return;
+ }
+
+ /* Print descriptor sense header */
+ log_msg(MM_SCSI, "%s0x%02x\n", *p++, (rq->ds_class << 4) + rq->ds_code);
+ log_msg(MM_SCSI, "%s%d\n", *p++, rq->ds_key);
+
+ log_msg(MM_SCSI, "%s%d\n", *p++, rq->ds_addl_sense_length);
+ log_msg(MM_SCSI, "%s0x%02x = %d\n", *p++, rq->ds_add_code,
+ rq->ds_add_code);
+ log_msg(MM_SCSI, "%s0x%02x = %d\n", *p++, rq->ds_qual_code,
+ rq->ds_qual_code);
+ log_msg(MM_SCSI, "\n");
+
+ /*
+ * Now print any sense descriptors. The first descriptor will
+ * immediately follow the header
+ */
+ descr_offset = (uint8_t *)(rq+1); /* Pointer arithmetic */
+
+ /*
+ * Calculate the amount of valid sense data
+ */
+ valid_sense_length =
+ MIN((sizeof (struct scsi_descr_sense_hdr) +
+ rq->ds_addl_sense_length), rqlen);
+
+ /*
+ * Iterate through the list of descriptors, stopping when we
+ * run out of sense data. Descriptor format is:
+ *
+ * <Descriptor type> <Descriptor length> <Descriptor data> ...
+ */
+ while ((descr_offset + *(descr_offset + 1)) <=
+ (uint8_t *)rq + valid_sense_length) {
+ /*
+ * Determine descriptor type. We can use the
+ * scsi_information_sense_descr structure as a
+ * template since the first two fields are always the
+ * same.
+ */
+ isd = (struct scsi_information_sense_descr *)descr_offset;
+ switch (isd->isd_descr_type) {
+ case DESCR_INFORMATION: {
+ uint64_t information;
+
+ information =
+ (((uint64_t)isd->isd_information[0] << 56) |
+ ((uint64_t)isd->isd_information[1] << 48) |
+ ((uint64_t)isd->isd_information[2] << 40) |
+ ((uint64_t)isd->isd_information[3] << 32) |
+ ((uint64_t)isd->isd_information[4] << 24) |
+ ((uint64_t)isd->isd_information[5] << 16) |
+ ((uint64_t)isd->isd_information[6] << 8) |
+ ((uint64_t)isd->isd_information[7]));
+ log_msg(MM_SCSI, "Information field: "
+ "%0" PRIx64 "\n", information);
+ break;
+ }
+ case DESCR_COMMAND_SPECIFIC: {
+ struct scsi_cmd_specific_sense_descr *c =
+ (struct scsi_cmd_specific_sense_descr *)isd;
+ uint64_t cmd_specific;
+
+ cmd_specific =
+ (((uint64_t)c->css_cmd_specific_info[0] << 56) |
+ ((uint64_t)c->css_cmd_specific_info[1] << 48) |
+ ((uint64_t)c->css_cmd_specific_info[2] << 40) |
+ ((uint64_t)c->css_cmd_specific_info[3] << 32) |
+ ((uint64_t)c->css_cmd_specific_info[4] << 24) |
+ ((uint64_t)c->css_cmd_specific_info[5] << 16) |
+ ((uint64_t)c->css_cmd_specific_info[6] << 8) |
+ ((uint64_t)c->css_cmd_specific_info[7]));
+ log_msg(MM_SCSI, "Command-specific information: "
+ "%0" PRIx64 "\n", cmd_specific);
+ break;
+ }
+ case DESCR_SENSE_KEY_SPECIFIC: {
+ struct scsi_sk_specific_sense_descr *ssd =
+ (struct scsi_sk_specific_sense_descr *)isd;
+ uint8_t *sk_spec_ptr = (uint8_t *)&ssd->sss_data;
+ log_msg(MM_SCSI, "Sense-key specific: "
+ "0x%02x 0x%02x 0x%02x\n", sk_spec_ptr[0],
+ sk_spec_ptr[1], sk_spec_ptr[2]);
+ break;
+ }
+ case DESCR_FRU: {
+ struct scsi_fru_sense_descr *fsd =
+ (struct scsi_fru_sense_descr *)isd;
+ log_msg(MM_SCSI, "Field replaceable unit code: "
+ "%d\n", fsd->fs_fru_code);
+ break;
+ }
+ case DESCR_BLOCK_COMMANDS: {
+ struct scsi_block_cmd_sense_descr *bsd =
+ (struct scsi_block_cmd_sense_descr *)isd;
+ log_msg(MM_SCSI, "Incorrect length indicator: "
+ "%s\n", bsd->bcs_ili ? "yes" : "no");
+ break;
+ }
+ default:
+ /* Ignore */
+ break;
+ }
+
+ /*
+ * Get pointer to the next descriptor. The "additional
+ * length" field holds the length of the descriptor except
+ * for the "type" and "additional length" fields, so
+ * we need to add 2 to get the total length.
+ */
+ descr_offset += (isd->isd_addl_length + 2);
+ }
+
+ log_msg(MM_SCSI, "\n");
+}
+
+static int
+uscsi_timeout(void)
+{
+ const char *env = getenv("USCSI_TIMEOUT");
+ static int timeo = -1;
+ int i;
+
+ if (timeo > 0)
+ return (timeo);
+
+ if (env != NULL) {
+ i = atoi(env);
+ if (i > USCSI_TIMEOUT_MAX)
+ i = USCSI_TIMEOUT_MAX;
+ else if (i < 0)
+ i = USCSI_DEFAULT_TIMEOUT;
+ } else
+ i = USCSI_DEFAULT_TIMEOUT;
+
+ timeo = i;
+ return (i);
+}
+
+/*
+ * Execute a command and determine the result.
+ * Uses the "uscsi" ioctl interface, which is
+ * fully supported.
+ *
+ * If the user wants request sense data to be returned
+ * in case of error then , the "uscsi_cmd" structure
+ * should have the request sense buffer allocated in
+ * uscsi_rqbuf.
+ *
+ */
+static int
+uscsi_cmd(int fd, struct uscsi_cmd *ucmd, int flags, void *rqbuf, int *rqlen)
+{
+ struct scsi_extended_sense *rq;
+ int status;
+
+ /*
+ * Set function flags for driver.
+ */
+ ucmd->uscsi_flags = USCSI_ISOLATE;
+ if (flags & F_SILENT) {
+ ucmd->uscsi_flags |= USCSI_SILENT;
+ }
+ if (flags & F_RQENABLE) {
+ ucmd->uscsi_flags |= USCSI_RQENABLE;
+ }
+
+ /*
+ * If this command will perform a read, set the USCSI_READ flag
+ */
+ if (ucmd->uscsi_buflen > 0) {
+ /*
+ * uscsi_cdb is declared as a caddr_t, so any CDB
+ * command byte with the MSB set will result in a
+ * compiler error unless we cast to an unsigned value.
+ */
+ switch ((uint8_t)ucmd->uscsi_cdb[0]) {
+ case SCMD_MODE_SENSE:
+ case SCMD_MODE_SENSE_G1:
+ case SCMD_LOG_SENSE_G1:
+ case SCMD_REQUEST_SENSE:
+ ucmd->uscsi_flags |= USCSI_READ;
+ break;
+
+ case SCMD_MODE_SELECT:
+ case SCMD_MODE_SELECT_G1:
+ /*LINTED*/
+ ucmd->uscsi_flags |= USCSI_WRITE;
+ break;
+ }
+ }
+
+ /* Set timeout */
+ ucmd->uscsi_timeout = uscsi_timeout();
+
+ /*
+ * Set up Request Sense buffer
+ */
+ ucmd->uscsi_flags |= USCSI_RQENABLE;
+
+ if (ucmd->uscsi_rqbuf == NULL) {
+ ucmd->uscsi_rqbuf = rqbuf;
+ ucmd->uscsi_rqlen = *rqlen;
+ ucmd->uscsi_rqresid = *rqlen;
+ }
+ ucmd->uscsi_rqstatus = IMPOSSIBLE_SCSI_STATUS;
+
+ if (ucmd->uscsi_rqbuf != NULL && ucmd->uscsi_rqlen > 0)
+ (void) memset(ucmd->uscsi_rqbuf, 0, ucmd->uscsi_rqlen);
+
+ /*
+ * Execute the ioctl
+ */
+ status = ioctl(fd, USCSICMD, ucmd);
+ if (status == 0 && ucmd->uscsi_status == 0) {
+ return (status);
+ }
+
+ /*
+ * If an automatic Request Sense gave us valid
+ * info about the error, we may be able to use
+ * that to print a reasonable error msg.
+ */
+ if (ucmd->uscsi_rqstatus == IMPOSSIBLE_SCSI_STATUS) {
+ log_msg(MM_SCSI, "No request sense for command %s\n",
+ find_string(scsi_cmdname_strings,
+ ucmd->uscsi_cdb[0]));
+ return (-1);
+ }
+ if (ucmd->uscsi_rqstatus != STATUS_GOOD) {
+ log_msg(MM_SCSI, "Request sense status for command %s: 0x%x\n",
+ find_string(scsi_cmdname_strings,
+ ucmd->uscsi_cdb[0]),
+ ucmd->uscsi_rqstatus);
+
+ return (-1);
+ }
+ rq = (struct scsi_extended_sense *)ucmd->uscsi_rqbuf;
+ *rqlen = ucmd->uscsi_rqlen - ucmd->uscsi_rqresid;
+ if ((((int)rq->es_add_len) + 8) < MIN_REQUEST_SENSE_LEN ||
+ rq->es_class != CLASS_EXTENDED_SENSE ||
+ *rqlen < MIN_REQUEST_SENSE_LEN) {
+
+ log_msg(MM_SCSI,
+ "Request sense for command %s failed\n",
+ find_string(scsi_cmdname_strings,
+ ucmd->uscsi_cdb[0]));
+
+ log_msg(MM_SCSI, "Sense data:\n");
+ log_dump(MM_SCSI, NULL, (caddr_t)rqbuf, *rqlen);
+
+ return (-1);
+ }
+
+ /*
+ * If the failed command is a Mode Select, and the
+ * target is indicating that it has rounded one of
+ * the mode select parameters, as defined in the SCSI-2
+ * specification, then we should accept the command
+ * as successful.
+ */
+ if (ucmd->uscsi_cdb[0] == SCMD_MODE_SELECT ||
+ ucmd->uscsi_cdb[0] == SCMD_MODE_SELECT_G1) {
+ if (rq->es_key == KEY_RECOVERABLE_ERROR &&
+ rq->es_add_code == ROUNDED_PARAMETER &&
+ rq->es_qual_code == 0) {
+ return (0);
+ }
+ }
+
+ if (!(flags & F_SILENT)) {
+ scsi_printerr(ucmd, rq, *rqlen);
+ }
+ if ((rq->es_key != KEY_RECOVERABLE_ERROR) || (flags & F_ALLERRS)) {
+ return (-1);
+ }
+ return (0);
+}
+
+int
+uscsi_request_sense(int fd, caddr_t buf, int buflen, void *rqbuf, int *rqblen)
+{
+ struct uscsi_cmd ucmd;
+ union scsi_cdb cdb;
+ int status;
+
+ /*
+ * Build and execute the uscsi ioctl
+ */
+ (void) memset(buf, 0, buflen);
+ (void) memset((char *)&ucmd, 0, sizeof (ucmd));
+ (void) memset((char *)&cdb, 0, sizeof (union scsi_cdb));
+ cdb.scc_cmd = SCMD_REQUEST_SENSE;
+ FORMG0COUNT(&cdb, (uchar_t)buflen);
+ ucmd.uscsi_cdb = (caddr_t)&cdb;
+ ucmd.uscsi_cdblen = CDB_GROUP0;
+ ucmd.uscsi_bufaddr = buf;
+ ucmd.uscsi_buflen = buflen;
+ status = uscsi_cmd(fd, &ucmd,
+ (g_verbose & MM_SCSI) ? F_NORMAL : F_SILENT, rqbuf, rqblen);
+ if (status) {
+ log_msg(MM_SCSI, "Request sense failed\n");
+ }
+ if (status == 0)
+ log_dump(MM_SCSI, "Request Sense data:", buf, buflen);
+
+ return (status);
+}
+
+
+/*
+ * Execute a uscsi mode sense command.
+ * This can only be used to return one page at a time.
+ * Return the mode header/block descriptor and the actual
+ * page data separately - this allows us to support
+ * devices which return either 0 or 1 block descriptors.
+ * Whatever a device gives us in the mode header/block descriptor
+ * will be returned to it upon subsequent mode selects.
+ */
+int
+uscsi_mode_sense(int fd, int page_code, int page_control, caddr_t page_data,
+ int page_size, struct scsi_ms_header *header, void *rqbuf, int *rqblen)
+{
+ caddr_t mode_sense_buf;
+ struct mode_header *hdr;
+ struct mode_page *pg;
+ int nbytes;
+ struct uscsi_cmd ucmd;
+ union scsi_cdb cdb;
+ int status;
+ int maximum;
+ char *pc;
+
+ assert(page_size >= 0 && page_size < 256);
+ assert(page_control == PC_CURRENT ||
+ page_control == PC_CHANGEABLE ||
+ page_control == PC_DEFAULT ||
+ page_control == PC_SAVED);
+ /*
+ * Allocate a buffer for the mode sense headers
+ * and mode sense data itself.
+ */
+ nbytes = sizeof (struct scsi_ms_header) + page_size;
+
+ if ((mode_sense_buf = alloca((uint_t)nbytes)) == NULL) {
+ log_warn("cannot alloca %d bytes\n", nbytes);
+ return (-1);
+ }
+
+ /*
+ * Build and execute the uscsi ioctl
+ */
+ (void) memset(mode_sense_buf, 0, nbytes);
+ (void) memset((char *)&ucmd, 0, sizeof (ucmd));
+ (void) memset((char *)&cdb, 0, sizeof (union scsi_cdb));
+ cdb.scc_cmd = SCMD_MODE_SENSE;
+ FORMG0COUNT(&cdb, (uchar_t)nbytes);
+ cdb.cdb_opaque[2] = page_control | page_code;
+ ucmd.uscsi_cdb = (caddr_t)&cdb;
+ ucmd.uscsi_cdblen = CDB_GROUP0;
+ ucmd.uscsi_bufaddr = mode_sense_buf;
+ ucmd.uscsi_buflen = nbytes;
+ status = uscsi_cmd(fd, &ucmd,
+ (g_verbose & MM_SCSI) ? F_NORMAL : F_SILENT, rqbuf, rqblen);
+ if (status) {
+ log_msg(MM_SCSI, "Mode sense page 0x%x failed\n", page_code);
+ return (-1);
+ }
+
+ log_dump(MM_SCSI, "RAW MODE SENSE BUFFER", mode_sense_buf, nbytes);
+
+ /*
+ * Verify that the returned data looks reasonable,
+ * find the actual page data, and copy it into the
+ * user's buffer. Copy the mode_header and block_descriptor
+ * into the header structure, which can then be used to
+ * return the same data to the drive when issuing a mode select.
+ */
+ hdr = (struct mode_header *)mode_sense_buf;
+ (void) memset((caddr_t)header, 0, sizeof (struct scsi_ms_header));
+ if (hdr->bdesc_length != sizeof (struct block_descriptor) &&
+ hdr->bdesc_length != 0) {
+
+ log_msg(MM_SCSI, "\
+\nMode sense page 0x%x: block descriptor length %d incorrect\n",
+ page_code, hdr->bdesc_length);
+ log_dump(MM_SCSI, "Mode sense:", mode_sense_buf, nbytes);
+ return (-1);
+ }
+ (void) memcpy((caddr_t)header, mode_sense_buf,
+ (int)(MODE_HEADER_LENGTH + hdr->bdesc_length));
+ pg = (struct mode_page *)((ulong_t)mode_sense_buf +
+ MODE_HEADER_LENGTH + hdr->bdesc_length);
+
+ if (page_code == MODEPAGE_ALLPAGES) { /* special case */
+
+ (void) memcpy(page_data, (caddr_t)pg,
+ (hdr->length + sizeof (header->mode_header.length)) -
+ (MODE_HEADER_LENGTH + hdr->bdesc_length));
+
+ pc = find_string(page_control_strings, page_control);
+ log_msg(MM_SCSI, "\nMode sense page 0x%x (%s):\n", page_code,
+ pc != NULL ? pc : "");
+ log_dump(MM_SCSI, "header:", (caddr_t)header,
+ sizeof (struct scsi_ms_header));
+ log_dump(MM_SCSI, "data:", page_data,
+ (hdr->length +
+ sizeof (header->mode_header.length)) -
+ (MODE_HEADER_LENGTH + hdr->bdesc_length));
+
+ return (0);
+ }
+
+ if (pg->code != page_code) {
+
+ log_msg(MM_SCSI, "\
+\nMode sense page 0x%x: incorrect page code 0x%x\n",
+ page_code, pg->code);
+ log_dump(MM_SCSI,
+ "Mode sense:", mode_sense_buf, nbytes);
+ return (-1);
+ }
+ /*
+ * Accept up to "page_size" bytes of mode sense data.
+ * This allows us to accept both CCS and SCSI-2
+ * structures, as long as we request the greater
+ * of the two.
+ */
+ maximum = page_size - sizeof (struct mode_page);
+ if (((int)pg->length) > maximum) {
+ log_msg(MM_SCSI, "\
+Mode sense page 0x%x: incorrect page length %d - expected max %d\n",
+ page_code, pg->length, maximum);
+ log_dump(MM_SCSI, "Mode sense:", mode_sense_buf,
+ nbytes);
+ return (-1);
+ }
+
+ (void) memcpy(page_data, (caddr_t)pg, MODESENSE_PAGE_LEN(pg));
+
+ pc = find_string(page_control_strings, page_control);
+ log_msg(MM_SCSI, "\nMode sense page 0x%x (%s):\n", page_code,
+ pc != NULL ? pc : "");
+ log_dump(MM_SCSI, "header:", (caddr_t)header,
+ sizeof (struct scsi_ms_header));
+ log_dump(MM_SCSI, "data:", page_data,
+ MODESENSE_PAGE_LEN(pg));
+
+ return (0);
+}
+
+/*
+ * Execute a uscsi MODE SENSE(10) command.
+ * This can only be used to return one page at a time.
+ * Return the mode header/block descriptor and the actual
+ * page data separately - this allows us to support
+ * devices which return either 0 or 1 block descriptors.
+ * Whatever a device gives us in the mode header/block descriptor
+ * will be returned to it upon subsequent mode selects.
+ */
+int
+uscsi_mode_sense_10(int fd, int page_code, int page_control,
+ caddr_t page_data, int page_size, struct scsi_ms_header_g1 *header,
+ void *rqbuf, int *rqblen)
+{
+ caddr_t mode_sense_buf;
+ struct mode_header_g1 *hdr;
+ struct mode_page *pg;
+ int nbytes;
+ struct uscsi_cmd ucmd;
+ union scsi_cdb cdb;
+ int status;
+ int maximum;
+ ushort_t length, bdesc_length;
+ char *pc;
+
+ assert(page_size >= 0 && page_size < UINT16_MAX);
+ assert(page_control == PC_CURRENT ||
+ page_control == PC_CHANGEABLE ||
+ page_control == PC_DEFAULT ||
+ page_control == PC_SAVED);
+ /*
+ * Allocate a buffer for the mode sense headers
+ * and mode sense data itself.
+ */
+ nbytes = sizeof (struct scsi_ms_header_g1) + page_size;
+
+ if ((mode_sense_buf = alloca((uint_t)nbytes)) == NULL) {
+ log_warn("cannot alloca %d bytes\n", nbytes);
+ return (-1);
+ }
+
+ /*
+ * Build and execute the uscsi ioctl
+ */
+ (void) memset(mode_sense_buf, 0, nbytes);
+ (void) memset((char *)&ucmd, 0, sizeof (ucmd));
+ (void) memset((char *)&cdb, 0, sizeof (union scsi_cdb));
+ cdb.scc_cmd = SCMD_MODE_SENSE_G1;
+ FORMG1COUNT(&cdb, (uint16_t)nbytes);
+ cdb.cdb_opaque[2] = page_control | page_code;
+ ucmd.uscsi_cdb = (caddr_t)&cdb;
+ ucmd.uscsi_cdblen = CDB_GROUP1;
+ ucmd.uscsi_bufaddr = mode_sense_buf;
+ ucmd.uscsi_buflen = nbytes;
+
+ status = uscsi_cmd(fd, &ucmd,
+ (g_verbose & MM_SCSI) ? F_NORMAL : F_SILENT, rqbuf, rqblen);
+ if (status) {
+ log_msg(MM_SCSI, "Mode sense(10) page 0x%x failed\n",
+ page_code);
+ return (-1);
+ }
+
+ log_dump(MM_SCSI, "RAW MODE SENSE(10) BUFFER", mode_sense_buf,
+ nbytes);
+
+ /*
+ * Verify that the returned data looks reasonable,
+ * find the actual page data, and copy it into the
+ * user's buffer. Copy the mode_header and block_descriptor
+ * into the header structure, which can then be used to
+ * return the same data to the drive when issuing a mode select.
+ */
+ /*LINTED*/
+ hdr = (struct mode_header_g1 *)mode_sense_buf;
+
+ length = BE_16(hdr->length);
+ bdesc_length = BE_16(hdr->bdesc_length);
+
+ (void) memset((caddr_t)header, 0, sizeof (struct scsi_ms_header_g1));
+ if (bdesc_length != sizeof (struct block_descriptor) &&
+ bdesc_length != 0) {
+
+ log_msg(MM_SCSI, "\
+\nMode sense(10) page 0x%x: block descriptor length %d incorrect\n",
+ page_code, bdesc_length);
+ log_dump(MM_SCSI, "Mode sense(10):", mode_sense_buf, nbytes);
+ return (-1);
+ }
+ (void) memcpy((caddr_t)header, mode_sense_buf,
+ (int)(MODE_HEADER_LENGTH_G1 + bdesc_length));
+ pg = (struct mode_page *)((ulong_t)mode_sense_buf +
+ MODE_HEADER_LENGTH_G1 + bdesc_length);
+
+
+ if (page_code == MODEPAGE_ALLPAGES) { /* special case */
+
+ (void) memcpy(page_data, (caddr_t)pg,
+ (length + sizeof (header->mode_header.length)) -
+ (MODE_HEADER_LENGTH_G1 + bdesc_length));
+
+ pc = find_string(page_control_strings,
+ page_control);
+ log_msg(MM_SCSI, "\nMode sense(10) page 0x%x (%s):\n",
+ page_code, pc != NULL ? pc : "");
+ log_dump(MM_SCSI, "header:", (caddr_t)header,
+ MODE_HEADER_LENGTH_G1 + bdesc_length);
+
+ log_dump(MM_SCSI, "data:", page_data,
+ (length + sizeof (header->mode_header.length)) -
+ (MODE_HEADER_LENGTH_G1 + bdesc_length));
+
+ return (0);
+ }
+
+ if (pg->code != page_code) {
+ log_msg(MM_SCSI, "\
+\nMode sense(10) page 0x%x: incorrect page code 0x%x\n",
+ page_code, pg->code);
+ log_dump(MM_SCSI, "Mode sense(10):", mode_sense_buf, nbytes);
+
+ return (-1);
+ }
+ /*
+ * Accept up to "page_size" bytes of mode sense data.
+ * This allows us to accept both CCS and SCSI-2
+ * structures, as long as we request the greater
+ * of the two.
+ */
+ maximum = page_size - sizeof (struct mode_page);
+ if (((int)pg->length) > maximum) {
+
+ log_msg(MM_SCSI, "\
+Mode sense(10) page 0x%x: incorrect page length %d - expected max %d\n",
+ page_code, pg->length, maximum);
+ log_dump(MM_SCSI, "Mode sense(10):", mode_sense_buf,
+ nbytes);
+
+ return (-1);
+ }
+
+ (void) memcpy(page_data, (caddr_t)pg, MODESENSE_PAGE_LEN(pg));
+
+ pc = find_string(page_control_strings, page_control);
+ log_msg(MM_SCSI, "\nMode sense(10) page 0x%x (%s):\n", page_code,
+ pc != NULL ? pc : "");
+ log_dump(MM_SCSI, "header:", (caddr_t)header,
+ sizeof (struct scsi_ms_header_g1));
+ log_dump(MM_SCSI, "data:", page_data, MODESENSE_PAGE_LEN(pg));
+
+ return (0);
+}
+
+
+/*
+ * Execute a uscsi mode select command.
+ */
+int
+uscsi_mode_select(int fd, int page_code, int options, caddr_t page_data,
+ int page_size, struct scsi_ms_header *header, void *rqbuf, int *rqblen)
+{
+ caddr_t mode_select_buf;
+ int nbytes;
+ struct uscsi_cmd ucmd;
+ union scsi_cdb cdb;
+ int status;
+ char *s;
+
+ assert(((struct mode_page *)page_data)->ps == 0);
+ assert(header->mode_header.length == 0);
+ assert(header->mode_header.device_specific == 0);
+ assert((options & ~(MODE_SELECT_SP|MODE_SELECT_PF)) == 0);
+
+ /*
+ * Allocate a buffer for the mode select header and data
+ */
+ nbytes = sizeof (struct scsi_ms_header) + page_size;
+ if ((mode_select_buf = alloca((uint_t)nbytes)) == NULL) {
+ log_warn("cannot alloca %d bytes\n", nbytes);
+ return (-1);
+ }
+
+ /*
+ * Build the mode select data out of the header and page data
+ * This allows us to support devices which return either
+ * 0 or 1 block descriptors.
+ */
+ (void) memset(mode_select_buf, 0, nbytes);
+ nbytes = MODE_HEADER_LENGTH;
+ if (header->mode_header.bdesc_length ==
+ sizeof (struct block_descriptor)) {
+ nbytes += sizeof (struct block_descriptor);
+ }
+
+ /*
+ * Dump the structures if anyone's interested
+ */
+ s = find_string(mode_select_strings,
+ options & (MODE_SELECT_SP|MODE_SELECT_PF));
+ log_msg(MM_SCSI, "\nMode select page 0x%x%s:\n", page_code,
+ s != NULL ? s : "");
+ log_dump(MM_SCSI, "header:", (caddr_t)header, nbytes);
+ log_dump(MM_SCSI, "data:", (caddr_t)page_data, page_size);
+
+ /*
+ * Fix the code for byte ordering -- all other page types are
+ * assumed to be formatted properly (byte-order-wise)
+ */
+
+ switch (page_code) {
+ case MODEPAGE_PDEVICE:
+ {
+ struct mode_pdevice *pd;
+ pd = (struct mode_pdevice *)(void *)page_data;
+ pd->if_ident = BE_16(pd->if_ident);
+ break;
+ }
+ case MODEPAGE_CTRL_MODE:
+ {
+ struct mode_control *pd;
+ pd = (struct mode_control *)(void *)page_data;
+ pd->ready_aen_holdoff = BE_16(pd->ready_aen_holdoff);
+ break;
+ }
+ }
+
+ /*
+ * Put the header and data together
+ */
+ (void) memcpy(mode_select_buf, (caddr_t)header, nbytes);
+ (void) memcpy(mode_select_buf + nbytes, page_data, page_size);
+ nbytes += page_size;
+
+ /*
+ * Build and execute the uscsi ioctl
+ */
+ (void) memset((char *)&ucmd, 0, sizeof (ucmd));
+ (void) memset((char *)&cdb, 0, sizeof (union scsi_cdb));
+ cdb.scc_cmd = SCMD_MODE_SELECT;
+ FORMG0COUNT(&cdb, (uchar_t)nbytes);
+ cdb.cdb_opaque[1] = (uchar_t)options;
+ ucmd.uscsi_cdb = (caddr_t)&cdb;
+ ucmd.uscsi_cdblen = CDB_GROUP0;
+ ucmd.uscsi_bufaddr = mode_select_buf;
+ ucmd.uscsi_buflen = nbytes;
+ status = uscsi_cmd(fd, &ucmd,
+ (g_verbose & MM_SCSI) ? F_NORMAL : F_SILENT, rqbuf, rqblen);
+
+ if (status) {
+ log_msg(MM_SCSI, "Mode select page 0x%x failed\n", page_code);
+ }
+
+ return (status);
+}
+
+
+/*
+ * Execute a uscsi mode select(10) command.
+ */
+int
+uscsi_mode_select_10(int fd, int page_code, int options,
+ caddr_t page_data, int page_size, struct scsi_ms_header_g1 *header,
+ void *rqbuf, int *rqblen)
+{
+ caddr_t mode_select_buf;
+ int nbytes;
+ struct uscsi_cmd ucmd;
+ union scsi_cdb cdb;
+ int status;
+ char *s;
+
+ assert(((struct mode_page *)page_data)->ps == 0);
+ assert(header->mode_header.length == 0);
+ assert(header->mode_header.device_specific == 0);
+ assert((options & ~(MODE_SELECT_SP|MODE_SELECT_PF)) == 0);
+
+ /*
+ * Allocate a buffer for the mode select header and data
+ */
+ nbytes = sizeof (struct scsi_ms_header_g1) + page_size;
+ if ((mode_select_buf = alloca((uint_t)nbytes)) == NULL) {
+ log_warn("cannot alloca %d bytes\n", nbytes);
+ return (-1);
+ }
+
+ /*
+ * Build the mode select data out of the header and page data
+ * This allows us to support devices which return either
+ * 0 or 1 block descriptors.
+ */
+ (void) memset(mode_select_buf, 0, nbytes);
+ nbytes = sizeof (struct mode_header_g1);
+ if (BE_16(header->mode_header.bdesc_length) ==
+ sizeof (struct block_descriptor)) {
+ nbytes += sizeof (struct block_descriptor);
+ }
+
+ /*
+ * Dump the structures if anyone's interested
+ */
+ s = find_string(mode_select_strings,
+ options & (MODE_SELECT_SP|MODE_SELECT_PF));
+ log_msg(MM_SCSI, "\nMode select(10) page 0x%x%s:\n", page_code,
+ s != NULL ? s : "");
+ log_dump(MM_SCSI, "header:", (caddr_t)header, nbytes);
+ log_dump(MM_SCSI, "data:", (caddr_t)page_data, page_size);
+
+ /*
+ * Fix the code for byte ordering -- all other page types are
+ * assumed to be formatted properly (byte-order-wise)
+ */
+
+ switch (page_code) {
+ case MODEPAGE_PDEVICE:
+ {
+ struct mode_pdevice *pd;
+ pd = (struct mode_pdevice *)(void *)page_data;
+ pd->if_ident = BE_16(pd->if_ident);
+ break;
+ }
+ case MODEPAGE_CTRL_MODE:
+ {
+ struct mode_control *pd;
+ pd = (struct mode_control *)(void *)page_data;
+ pd->ready_aen_holdoff = BE_16(pd->ready_aen_holdoff);
+ break;
+ }
+ }
+
+ /*
+ * Put the header and data together
+ */
+ (void) memcpy(mode_select_buf, (caddr_t)header, nbytes);
+ (void) memcpy(mode_select_buf + nbytes, page_data, page_size);
+ nbytes += page_size;
+
+ /*
+ * Build and execute the uscsi ioctl
+ */
+ (void) memset((char *)&ucmd, 0, sizeof (ucmd));
+ (void) memset((char *)&cdb, 0, sizeof (union scsi_cdb));
+ cdb.scc_cmd = SCMD_MODE_SELECT_G1;
+ FORMG1COUNT(&cdb, (uint16_t)nbytes);
+ cdb.cdb_opaque[1] = (uchar_t)options;
+ ucmd.uscsi_cdb = (caddr_t)&cdb;
+ ucmd.uscsi_cdblen = CDB_GROUP1;
+ ucmd.uscsi_bufaddr = mode_select_buf;
+ ucmd.uscsi_buflen = nbytes;
+ status = uscsi_cmd(fd, &ucmd,
+ (g_verbose & MM_SCSI) ? F_NORMAL : F_SILENT, rqbuf, rqblen);
+
+ if (status) {
+ log_msg(MM_SCSI, "Mode select(10) page 0x%x failed\n",
+ page_code);
+ }
+
+ return (status);
+}
+
+int
+uscsi_log_sense(int fd, int page_code, int page_control, caddr_t page_data,
+ int page_size, void *rqbuf, int *rqblen)
+{
+ caddr_t log_sense_buf;
+ struct log_header *hdr;
+ struct uscsi_cmd ucmd;
+ union scsi_cdb cdb;
+ int status;
+ ushort_t len;
+ char *pc;
+
+ assert(page_size >= 0 && page_size < UINT16_MAX);
+ assert(page_control == PC_CURRENT ||
+ page_control == PC_CHANGEABLE ||
+ page_control == PC_DEFAULT ||
+ page_control == PC_SAVED);
+
+ if (page_size < sizeof (struct log_header))
+ return (-1);
+
+ /*
+ * Allocate a buffer for the log sense header
+ * and log sense data.
+ */
+ if ((log_sense_buf = alloca((uint_t)page_size)) == NULL) {
+ log_warn("cannot alloca %d bytes\n", page_size);
+ return (-1);
+ }
+
+ /*
+ * Build and execute the uscsi ioctl
+ */
+ (void) memset(log_sense_buf, 0, page_size);
+ (void) memset((char *)&ucmd, 0, sizeof (ucmd));
+ (void) memset((char *)&cdb, 0, sizeof (union scsi_cdb));
+ cdb.scc_cmd = SCMD_LOG_SENSE_G1;
+ FORMG1COUNT(&cdb, (uint16_t)page_size);
+ cdb.cdb_opaque[2] = page_control | page_code;
+ ucmd.uscsi_cdb = (caddr_t)&cdb;
+ ucmd.uscsi_cdblen = CDB_GROUP1;
+ ucmd.uscsi_bufaddr = log_sense_buf;
+ ucmd.uscsi_buflen = page_size;
+ status = uscsi_cmd(fd, &ucmd,
+ (g_verbose & MM_SCSI) ? F_NORMAL : F_SILENT, rqbuf, rqblen);
+ if (status) {
+ log_msg(MM_SCSI, "Log sense page 0x%x failed\n", page_code);
+ return (-1);
+ }
+
+ /*
+ * Verify that the returned data looks reasonable,
+ * then copy it into the user's buffer.
+ */
+ hdr = (struct log_header *)log_sense_buf;
+
+ /*
+ * Ensure we have a host-understandable length field
+ */
+ len = BE_16(hdr->length);
+
+ if (hdr->code != page_code) {
+ log_msg(MM_SCSI, "\
+\nLog sense page 0x%x: incorrect page code 0x%x\n",
+ page_code, hdr->code);
+ log_dump(MM_SCSI, "Log sense:", log_sense_buf, page_size);
+ return (-1);
+ }
+
+ log_dump(MM_SCSI, "LOG SENSE RAW OUTPUT", log_sense_buf,
+ sizeof (struct log_header) + len);
+
+ /*
+ * Accept up to "page_size" bytes of mode sense data.
+ * This allows us to accept both CCS and SCSI-2
+ * structures, as long as we request the greater
+ * of the two.
+ */
+ (void) memcpy(page_data, (caddr_t)hdr, len +
+ sizeof (struct log_header));
+
+ pc = find_string(page_control_strings, page_control);
+ log_msg(MM_SCSI, "\nLog sense page 0x%x (%s):\n", page_code,
+ pc != NULL ? pc : "");
+ log_dump(MM_SCSI, "header:", (caddr_t)hdr,
+ sizeof (struct log_header));
+ log_dump(MM_SCSI, "data:", (caddr_t)hdr +
+ sizeof (struct log_header), len);
+
+ return (0);
+}
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/scsi_util.h b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/scsi_util.h
new file mode 100644
index 0000000000..c42e168459
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/scsi_util.h
@@ -0,0 +1,400 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SCSI_UTIL_H
+#define _SCSI_UTIL_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Definitions for data structures used in the SCSI IE module
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+#include <sys/byteorder.h>
+#include <sys/scsi/scsi.h>
+
+/*
+ * Log page structures
+ */
+#pragma pack(1)
+
+struct log_header {
+
+#if defined(_BIT_FIELDS_LTOH)
+
+ uint8_t code : 6,
+ rsrvd : 2;
+
+#elif defined(_BIT_FIELDS_HTOL)
+
+ uint8_t rsrvd : 2,
+ code : 6;
+
+#else
+#error One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined
+#endif /* _BIT_FIELDS_LTOH */
+
+ uint8_t reserved;
+ uint16_t length;
+};
+
+struct log_parameter_header {
+
+ uint16_t param_code;
+
+#if defined(_BIT_FIELDS_LTOH)
+
+ uint8_t lp : 1,
+ lbin : 1,
+ tmc : 2,
+ etc : 1,
+ tsd : 1,
+ ds : 1,
+ du : 1;
+
+#elif defined(_BIT_FIELDS_HTOL)
+
+ uint8_t du : 1,
+ ds : 1,
+ tsd : 1,
+ etc : 1,
+ tmc : 2,
+ lbin : 1,
+ lp : 1;
+
+#else
+#error One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined
+#endif /* _BIT_FIELDS_LTOH */
+
+ uint8_t length;
+};
+
+
+struct supported_log_pages {
+ struct log_header hdr;
+ uchar_t pages[1];
+};
+
+/*
+ * Specific log page parameters:
+ */
+struct info_excpt_log_param {
+ struct log_parameter_header hdr;
+ uchar_t ie_asc;
+ uchar_t ie_ascq;
+ uchar_t last_temp;
+
+ /*
+ * The following fields may or may not exist -- depending on the
+ * length specified in the log parameter header.
+ */
+ union {
+ struct ie_ibm_extensions {
+ uchar_t ibm_temp_threshold;
+ } ibm_e;
+ } vnd;
+#define ex_temp_threshold vnd.ibm_e.ibm_temp_threshold
+};
+
+/*
+ * The SCSI-3 SPC document states that IE log page (0x2F) parameter 0
+ * must have a length of at least 4 (including the length byte).
+ * Drives that provide 5 bytes use the 5th byte as the temperature
+ * threshold (reference) value.
+ */
+#define LOGPARAM_IE_MIN_LEN 2 /* the asc and ascq fields */
+#define LOGPARAM_IE_WITH_TEMP_MIN_LEN (LOGPARAM_IE_MIN_LEN + 1)
+
+#define INVALID_TEMPERATURE 0xff
+
+#define LOGPARAM_IE 0x0000
+
+
+struct temperature_log_param_curtemp {
+ struct log_parameter_header hdr;
+ uchar_t rsrvd;
+ uchar_t current_temp;
+};
+
+struct temperature_log_param_reftemp {
+ struct log_parameter_header hdr;
+ uchar_t rsrvd2;
+ uchar_t reference_temp;
+
+#define REFTEMP_INVALID 0xff
+};
+
+struct selftest_log_parameter {
+ struct log_parameter_header hdr;
+
+#if defined(_BIT_FIELDS_LTOH)
+
+ uint8_t results : 4,
+ rsrvd : 1,
+ testcode : 3;
+
+#elif defined(_BIT_FIELDS_HTOL)
+
+ uint8_t testcode : 3,
+ rsrvd : 1,
+ results : 4;
+
+#endif /* _BIT_FIELDS_LTOH */
+
+ uint8_t test_number;
+ uint16_t timestamp;
+ uint64_t lba_of_first_failure;
+
+#if defined(_BIT_FIELDS_LTOH)
+
+ uint8_t sense_key : 4,
+ rsrvd1 : 4;
+
+#elif defined(_BIT_FIELDS_HTOL)
+
+ uint8_t rsrvd1 : 4,
+ sense_key : 4;
+
+#endif /* _BIT_FIELDS_LTOH */
+
+ uint8_t asc;
+ uint8_t ascq;
+ uint8_t vendor_specific;
+};
+
+/* The results field of the self-test log parameter */
+#define SELFTEST_FAILURE_INCOMPLETE 3
+#define SELFTEST_FAILURE_SEG_UNKNOWN 4
+#define SELFTEST_FAILURE_SEG_FIRST 5
+#define SELFTEST_FAILURE_SEG_SECOND 6
+#define SELFTEST_FAILURE_SEG_OTHER 7
+
+#define LOGPARAM_TEMP_CURTEMP 0x0000
+#define LOGPARAM_TEMP_REFTEMP 0x0001
+
+#define LOGPARAM_TEMP_CURTEMP_LEN \
+ (sizeof (struct temperature_log_param_curtemp) - \
+ sizeof (struct log_parameter_header))
+
+#define LOGPARAM_TEMP_REFTEMP_LEN \
+ (sizeof (struct temperature_log_param_reftemp) - \
+ sizeof (struct log_parameter_header))
+
+/*
+ * Mode sense/select page header information
+ */
+struct scsi_ms_header {
+ struct mode_header mode_header;
+ struct block_descriptor block_descriptor;
+};
+
+struct scsi_ms_header_g1 {
+ struct mode_header_g1 mode_header;
+ struct block_descriptor block_descriptor;
+};
+
+struct info_except_page {
+ struct mode_page mp;
+
+#if defined(_BIT_FIELDS_LTOH)
+
+ uint8_t logerr : 1, /* Errors should be logged */
+ rsrvd1 : 1,
+ test : 1, /* Enable test gen of IEs */
+ dexcpt : 1, /* Disable exceptions */
+ ewasc : 1, /* Enable warning generation */
+ ebf : 1, /* enable backgrnd functions */
+ rsrvd2 : 1,
+ perf : 1; /* No delays during excptns */
+
+ uint8_t mrie : 4, /* Method/reporting excptons */
+ rsrvd3 : 4;
+
+#elif defined(_BIT_FIELDS_HTOL)
+
+ uint8_t perf : 1; /* No delays during excptons */
+ rsrvd2 : 1,
+ ebf : 1, /* enable background funcs */
+ ewasc : 1, /* Enable warning generation */
+ dexcpt : 1, /* Disable exceptions */
+ test : 1, /* Enable test gen of IEs */
+ rsrvd1 : 1,
+ logerr : 1, /* Errors should be logged */
+
+ uint8_t rsrvd3 : 4;
+ mrie : 4, /* Method of report excptns */
+
+
+#else
+#error One of _BIT_FIELDS_LTOH or _BIT_FIELDS_HTOL must be defined
+#endif /* _BIT_FIELDS_LTOH */
+
+ uint32_t interval_timer; /* reporting grnulrty for IEs */
+ uint32_t report_count; /* # of times to report an IE */
+};
+
+#pragma pack()
+
+#define MODEPAGE_INFO_EXCPT_LEN (sizeof (struct info_except_page))
+
+#define IEC_IE_ENABLED(ies) ((ies).dexcpt == 0)
+#define IEC_IE_CHANGEABLE(ies) ((ies).dexcpt == 1)
+#define IEC_MRIE_CHANGEABLE(ies) ((ies).mrie == 0xf)
+#define IEC_PERF_CHANGEABLE(ies) ((ies).perf == 1)
+#define IEC_EWASC_CHANGEABLE(ies) ((ies).ewasc == 1)
+#define IEC_TEST_CHANGEABLE(ies) ((ies).test == 1)
+#define IEC_RPTCNT_CHANGEABLE(ies) ((ies).report_count == BE_32(0xffffffff))
+#define IEC_LOGERR_CHANGEABLE(ies) ((ies).logerr == 1)
+
+/*
+ * Values for the MSIE field of the informational exceptions control mode page
+ */
+#define IE_REPORT_NONE 0
+#define IE_REPORT_ASYNCH 1
+#define IE_REPORT_UNIT_ATTN 2
+#define IE_REPORT_RECOV_ERR_COND 3
+#define IE_REPORT_RECOV_ERR_ALWAYS 4
+#define IE_REPORT_NO_SENSE 5
+#define IE_REPORT_ON_REQUEST 6
+
+/*
+ * Constants in support of the CONTROL MODE mode page (page 0xA)
+ */
+#define MODEPAGE_CTRL_MODE_LEN (sizeof (struct mode_control_scsi3))
+#define GLTSD_CHANGEABLE(chg) ((chg).gltsd == 1)
+
+#define LOGPAGE_SELFTEST_MIN_PARAM_CODE 0x0001
+#define LOGPAGE_SELFTEST_MAX_PARAM_CODE 0x0014
+
+#define LOGPAGE_SELFTEST_PARAM_LEN \
+ ((sizeof (struct selftest_log_parameter)) - \
+ (sizeof (struct log_parameter_header)))
+
+/*
+ * Macro to extract the length of a mode sense page
+ * as returned by a target.
+ */
+#define MODESENSE_PAGE_LEN(p) (((int)((struct mode_page *)p)->length) + \
+ sizeof (struct mode_page))
+
+/*
+ * Mode Select options
+ */
+#define MODE_SELECT_SP 0x01
+#define MODE_SELECT_PF 0x10
+
+
+/*
+ * Mode Sense Page Control
+ */
+#define PC_CURRENT (0 << 6)
+#define PC_CHANGEABLE (1 << 6)
+#define PC_DEFAULT (2 << 6)
+#define PC_SAVED (3 << 6)
+
+/*
+ * Log Sense Page Control
+ */
+#define PC_CUMULATIVE (1 << 6)
+
+/*
+ * These flags are used to control disk command execution.
+ */
+#define F_NORMAL 0x00 /* normal operation */
+#define F_SILENT 0x01 /* no error msgs at all */
+#define F_ALLERRS 0x02 /* return any error, not just fatal */
+#define F_RQENABLE 0x04 /* no error msgs at all */
+
+#define INVALID_SENSE_KEY 0x10 /* An impossible sense key */
+
+/*
+ * LOG page codes
+ */
+#define LOGPAGE_SUPP_LIST 0x00
+#define LOGPAGE_TEMP 0x0d
+#define LOGPAGE_SELFTEST 0x10
+#define LOGPAGE_IE 0x2f
+
+/*
+ * "impossible" status value
+ */
+#define IMPOSSIBLE_SCSI_STATUS 0xff
+
+/*
+ * Minimum length of Request Sense data that we can accept
+ */
+#define MIN_REQUEST_SENSE_LEN 18
+
+/*
+ * Rounded parameter, as returned in Extended Sense information
+ */
+#define ROUNDED_PARAMETER 0x37
+
+
+/* ASC constants */
+#define ASC_INVALID_OPCODE 0x20
+#define ASC_INVALID_CDB_FIELD 0x24
+#define ASC_FAILURE_PREDICTION_THRESHOLD_EXCEEDED 0x5d
+
+/* ASCQ constants */
+#define ASCQ_INVALID_OPCODE 0
+
+/* Error tests: */
+#define SCSI_INVALID_OPCODE(s, a, aq) \
+ (((s) == KEY_ILLEGAL_REQUEST) && ((a) == ASC_INVALID_OPCODE) && \
+ ((aq) == ASCQ_INVALID_OPCODE))
+
+#define MODE_PAGE_UNSUPPORTED(s, a, aq) \
+ (((s) == KEY_ILLEGAL_REQUEST) && ((a) == ASC_INVALID_CDB_FIELD))
+
+int uscsi_mode_sense(int fd, int page_code, int page_control, caddr_t page_data,
+ int page_size, struct scsi_ms_header *header, void *rqbuf, int *rqblen);
+int uscsi_mode_sense_10(int fd, int page_code, int page_control,
+ caddr_t page_data, int page_size, struct scsi_ms_header_g1 *header,
+ void *rqbuf, int *rqblen);
+int uscsi_mode_select(int fd, int page_code, int options, caddr_t page_data,
+ int page_size, struct scsi_ms_header *header, void *rqbuf, int *rqblen);
+int uscsi_mode_select_10(int fd, int page_code, int options,
+ caddr_t page_data, int page_size, struct scsi_ms_header_g1 *header,
+ void *rqbuf, int *rqblen);
+int uscsi_log_sense(int fd, int page_code, int page_control, caddr_t page_data,
+ int page_size, void *rqbuf, int *rqblen);
+int uscsi_request_sense(int fd, caddr_t buf, int buflen, void *rqbuf,
+ int *rqblen);
+void scsi_translate_error(struct scsi_extended_sense *rq, uint_t *skeyp,
+ uint_t *ascp, uint_t *ascqp);
+const char *scsi_asc_ascq_string(uint_t asc, uint_t ascq);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SCSI_UTIL_H */
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/sfx4500-disk.c b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/sfx4500-disk.c
new file mode 100644
index 0000000000..1c25c874ee
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/sfx4500-disk.c
@@ -0,0 +1,508 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Sun Fire X4500 Disk Diagnosis Engine
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <signal.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <assert.h>
+#include <libnvpair.h>
+#include <fm/fmd_api.h>
+#include <fm/fmd_fmri.h>
+#include <sys/fm/protocol.h>
+#include <fm/libtopo.h>
+
+#include "sfx4500-disk.h"
+#include "fault_mgr.h"
+#include "hotplug_mgr.h"
+#include "schg_mgr.h"
+#include "plugin_mgr.h"
+#include "fm_disk_events.h"
+#include "topo_gather.h"
+
+#define THIS_FMD_MODULE_NAME "sfx4500-disk"
+
+static enum sfx4500_init_state {
+ INIT_STATE_NONE = 0,
+ PLUGIN_MGR_INITTED = 1,
+ STATE_CHANGE_MGR_INITTED = 2,
+ HOTPLUG_MGR_INITTED = 4,
+ FAULT_MGR_INITTED = 8
+} g_init_state = INIT_STATE_NONE;
+
+typedef enum {
+ LT_SUSPECT,
+ LT_REPAIRED
+} fm_list_type_t;
+
+/*
+ * Global verbosity flag -- controls chattiness of debug messages and
+ * warnings. Its value is determined by the fmd property "log-level"
+ * settable in the DE's .conf file.
+ */
+log_class_t g_verbose = 0;
+cfgdata_t *config_data = NULL;
+fmd_xprt_t *g_xprt_hdl = NULL;
+fmd_hdl_t *g_fm_hdl = NULL;
+
+static const fmd_prop_t fmd_props[];
+
+static void
+diskmon_teardown_all(void)
+{
+ /*
+ * Cleanup the fault manager first -- it depends
+ * on the state change manager and may race with
+ * its cleanup if left around.
+ */
+ cleanup_fault_manager(config_data);
+ cleanup_hotplug_manager();
+ cleanup_state_change_manager(config_data);
+ cleanup_plugin_manager();
+ config_fini();
+}
+
+static int
+count_disks(diskmon_t *disklistp)
+{
+ int i = 0;
+
+ while (disklistp != NULL) {
+ i++;
+ disklistp = disklistp->next;
+ }
+
+ return (i);
+}
+
+static int
+diskmon_init(void)
+{
+ if (init_plugin_manager() != 0)
+ goto cleanup;
+ else
+ g_init_state |= PLUGIN_MGR_INITTED;
+
+ block_state_change_events();
+
+ if (init_hotplug_manager() != 0)
+ goto cleanup;
+ else
+ g_init_state |= HOTPLUG_MGR_INITTED;
+
+ if (init_state_change_manager(config_data) != 0)
+ goto cleanup;
+ else
+ g_init_state |= STATE_CHANGE_MGR_INITTED;
+
+ if (init_fault_manager(config_data) != 0)
+ goto cleanup;
+ else
+ g_init_state |= FAULT_MGR_INITTED;
+
+ return (E_SUCCESS);
+
+cleanup:
+
+ unblock_state_change_events();
+
+ /*
+ * The cleanup order here does matter, due to dependencies between the
+ * managers.
+ */
+ if (g_init_state & FAULT_MGR_INITTED)
+ cleanup_fault_manager(config_data);
+ if (g_init_state & HOTPLUG_MGR_INITTED)
+ cleanup_hotplug_manager();
+ if (g_init_state & STATE_CHANGE_MGR_INITTED)
+ cleanup_state_change_manager(config_data);
+ if (g_init_state & PLUGIN_MGR_INITTED)
+ cleanup_plugin_manager();
+
+ return (E_ERROR);
+}
+
+static boolean_t
+dm_suspect_de_is_me(nvlist_t *suspectnvl)
+{
+ nvlist_t *de_nvl;
+ char *modname;
+
+ if (nvlist_lookup_nvlist(suspectnvl, FM_SUSPECT_DE, &de_nvl) != 0 ||
+ nvlist_lookup_string(de_nvl, FM_FMRI_FMD_NAME, &modname) != 0 ||
+ strcmp(modname, THIS_FMD_MODULE_NAME) != 0)
+ return (B_FALSE);
+ else
+ return (B_TRUE);
+}
+
+static disk_flt_src_e
+nvl2fltsrc(fmd_hdl_t *hdl, nvlist_t *nvl)
+{
+ if (fmd_nvl_class_match(hdl, nvl, FAULT_DISK_OVERTEMP))
+ return (DISK_FAULT_SOURCE_OVERTEMP);
+
+ if (fmd_nvl_class_match(hdl, nvl, FAULT_DISK_STFAIL))
+ return (DISK_FAULT_SOURCE_SELFTEST);
+
+ if (fmd_nvl_class_match(hdl, nvl, FAULT_DISK_PREDFAIL))
+ return (DISK_FAULT_SOURCE_INFO_EXCPT);
+
+ return (DISK_FAULT_SOURCE_NONE);
+}
+
+static void
+dm_fault_execute_actions(diskmon_t *diskp, disk_flt_src_e fsrc)
+{
+ const char *action_prop = NULL;
+ const char *action_string;
+ dm_plugin_error_t rv;
+
+ /*
+ * The predictive failure action is the activation of the fault
+ * indicator.
+ */
+ switch (fsrc) {
+ case DISK_FAULT_SOURCE_OVERTEMP:
+
+ action_prop = DISK_PROP_OTEMPACTION;
+ break;
+
+ case DISK_FAULT_SOURCE_SELFTEST:
+
+ action_prop = DISK_PROP_STFAILACTION;
+ break;
+ }
+
+ dm_fault_indicator_set(diskp, INDICATOR_ON);
+
+ if (action_prop != NULL &&
+ (action_string = dm_prop_lookup(diskp->props, action_prop))
+ != NULL) {
+
+ rv = dm_pm_indicator_execute(action_string);
+ if (rv != DMPE_SUCCESS) {
+ log_warn("Fault action `%s' did not successfully "
+ "complete.\n", action_string);
+ }
+ }
+}
+
+static void
+diskmon_agent(fmd_hdl_t *hdl, nvlist_t *nvl, fm_list_type_t list_type)
+{
+ char *uuid = NULL;
+ nvlist_t **nva;
+ uint_t nvc;
+ diskmon_t *diskp;
+ nvlist_t *fmri;
+ nvlist_t *fltnvl;
+ int err = 0;
+
+ switch (list_type) {
+
+ case LT_REPAIRED:
+
+ err |= nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid);
+ err |= nvlist_lookup_nvlist_array(nvl, FM_SUSPECT_FAULT_LIST,
+ &nva, &nvc);
+ if (err != 0)
+ return;
+
+ while (nvc-- != 0) {
+
+ fltnvl = *nva++;
+
+ if (nvlist_lookup_nvlist(fltnvl, FM_FAULT_RESOURCE,
+ &fmri) != 0)
+ continue;
+
+ if ((diskp = dm_fmri_to_diskmon(hdl, fmri)) == NULL)
+ continue;
+
+ log_msg(MM_MAIN, "Disk %s repaired!\n",
+ diskp->location);
+
+ dm_fault_indicator_set(diskp, INDICATOR_OFF);
+
+ dm_state_change(diskp, HPS_REPAIRED);
+ }
+
+ break;
+
+ case LT_SUSPECT:
+
+
+ err |= nvlist_lookup_string(nvl, FM_SUSPECT_UUID, &uuid);
+ err |= nvlist_lookup_nvlist_array(nvl, FM_SUSPECT_FAULT_LIST,
+ &nva, &nvc);
+ if (err != 0)
+ return;
+
+ while (nvc-- != 0 && !fmd_case_uuclosed(hdl, uuid)) {
+
+ fltnvl = *nva++;
+
+ if (nvlist_lookup_nvlist(fltnvl, FM_FAULT_RESOURCE,
+ &fmri) != 0)
+ continue;
+
+ if ((diskp = dm_fmri_to_diskmon(hdl, fmri)) == NULL)
+ continue;
+
+ /* Execute the actions associated with this fault */
+ dm_fault_execute_actions(diskp,
+ nvl2fltsrc(hdl, fltnvl));
+
+ /*
+ * If the fault wasn't generated by this module, send a
+ * state change event to the state change manager
+ */
+ if (!dm_suspect_de_is_me(nvl))
+ dm_state_change(diskp, HPS_FAULTED);
+
+ /* Case is closed */
+ fmd_case_uuclose(hdl, uuid);
+ }
+
+ break;
+ }
+}
+
+/*ARGSUSED*/
+static void
+diskmon_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl, const char *class)
+{
+ diskmon_t *diskp;
+ nvlist_t *fmri;
+ nvlist_t *fltnvl;
+ boolean_t ismyereport;
+ nvlist_t *det_nvl;
+ fmd_case_t *cs;
+ const char *fltclass;
+ disk_flt_src_e fsrc;
+
+ if (g_verbose & MM_MAIN)
+ nvlist_print(stderr, nvl);
+
+ ismyereport = fmd_nvl_class_match(hdl, nvl, EREPORT_SATA ".*");
+
+ /*
+ * Act on the fault suspect list or repaired list (embedded agent
+ * action).
+ */
+ if (fmd_nvl_class_match(hdl, nvl, "list.repaired")) {
+
+ diskmon_agent(hdl, nvl, LT_REPAIRED);
+ return;
+
+ } else if (fmd_nvl_class_match(hdl, nvl, "list.suspect")) {
+
+ diskmon_agent(hdl, nvl, LT_SUSPECT);
+ return;
+ }
+
+ /*
+ * If we get any replayed faults, set the diskmon's faulted
+ * flag for the appropriate fault, then change the diskmon's state
+ * to faulted.
+ */
+ if (fmd_nvl_class_match(hdl, nvl, FAULT_DISK ".*")) {
+
+ if (nvlist_lookup_nvlist(nvl, FM_FAULT_RESOURCE,
+ &fmri) != 0)
+ return;
+
+ if ((diskp = dm_fmri_to_diskmon(hdl, fmri)) == NULL)
+ return;
+
+ fsrc = nvl2fltsrc(hdl, nvl);
+
+ /* Execute the actions associated with this fault */
+ dm_fault_execute_actions(diskp, fsrc);
+
+ assert(pthread_mutex_lock(&diskp->disk_faults_mutex) == 0);
+ diskp->disk_faults |= fsrc;
+ assert(pthread_mutex_unlock(&diskp->disk_faults_mutex) == 0);
+
+ /*
+ * If the fault wasn't generated by this module, send a
+ * state change event to the state change manager
+ */
+ dm_state_change(diskp, HPS_FAULTED);
+ return;
+ }
+
+ if (!ismyereport) {
+ log_msg(MM_MAIN, "Unknown class = %s\n", class);
+ return;
+ }
+
+ if (nvlist_lookup_nvlist(nvl, FM_EREPORT_DETECTOR, &det_nvl) != 0) {
+ log_msg(MM_MAIN, "Cannot handle event %s: Couldn't get "
+ FM_EREPORT_DETECTOR "\n", class);
+ return;
+ }
+
+ if ((diskp = dm_fmri_to_diskmon(hdl, det_nvl)) == NULL)
+ return;
+
+ fltclass =
+ fmd_nvl_class_match(hdl, nvl, EREPORT_SATA_PREDFAIL) ?
+ FAULT_DISK_PREDFAIL :
+ fmd_nvl_class_match(hdl, nvl, EREPORT_SATA_OVERTEMP) ?
+ FAULT_DISK_OVERTEMP :
+ fmd_nvl_class_match(hdl, nvl, EREPORT_SATA_STFAIL) ?
+ FAULT_DISK_STFAIL : NULL;
+
+ if (fltclass != NULL) {
+
+ /*
+ * Create and solve a new case by adding the ereport
+ * and its corresponding fault to the case and
+ * solving it. It'll be closed when we get the
+ * list.suspect event later.
+ */
+ cs = fmd_case_open(hdl, NULL);
+ fmd_case_add_ereport(hdl, cs, ep);
+ fltnvl = fmd_nvl_create_fault(hdl, fltclass, 100,
+ diskp->asru_fmri, diskp->fru_fmri,
+ diskp->disk_res_fmri);
+ fmd_case_add_suspect(hdl, cs, fltnvl);
+ fmd_case_solve(hdl, cs);
+ }
+}
+
+static const fmd_hdl_ops_t fmd_ops = {
+ diskmon_recv, /* fmdo_recv */
+ NULL, /* fmdo_timeout */
+ NULL, /* fmdo_close */
+ NULL, /* fmdo_stats */
+ NULL, /* fmdo_gc */
+};
+
+static const fmd_prop_t fmd_props[] = {
+ { GLOBAL_PROP_FAULT_POLL, FMD_TYPE_UINT32, "3600" },
+ { GLOBAL_PROP_FAULT_INJ, FMD_TYPE_UINT32, "0" },
+ { GLOBAL_PROP_LOG_LEVEL, FMD_TYPE_UINT32, "0" },
+ /*
+ * Default fault monitoring options are:
+ * (OPTION_SELFTEST_ERRS_ARE_FATAL |
+ * OPTION_OVERTEMP_ERRS_ARE_FATAL) == 0xC
+ */
+ { GLOBAL_PROP_FAULT_OPTIONS, FMD_TYPE_UINT32, "0xC" },
+ { GLOBAL_PROP_IPMI_BMC_MON, FMD_TYPE_UINT32, "1" },
+ { GLOBAL_PROP_IPMI_ERR_INJ, FMD_TYPE_UINT32, "0" },
+ { NULL, 0, NULL }
+};
+
+static const fmd_hdl_info_t fmd_info = {
+ "Sun Fire X4500 Disk Diagnosis Engine",
+ SFX4500_DISK_MODULE_VERSION,
+ &fmd_ops,
+ fmd_props
+};
+
+void
+_fmd_init(fmd_hdl_t *hdl)
+{
+ fmd_case_t *cp;
+ int disk_count;
+
+ g_fm_hdl = hdl;
+
+ if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0) {
+ return;
+ }
+
+ if (config_init()) {
+ log_err("Could not initialize configuration!\n");
+ fmd_hdl_unregister(hdl);
+ return;
+ }
+
+ if (config_get(hdl, fmd_props)) {
+ config_fini();
+ log_err("Could not retrieve configuration from libtopo!\n");
+ fmd_hdl_unregister(hdl);
+ return;
+ }
+
+ /*
+ * If there are no disks to monitor, bail out
+ */
+ if ((disk_count = count_disks(config_data->disk_list)) == 0) {
+ config_fini();
+ fmd_hdl_unregister(hdl);
+ return;
+ }
+
+ g_xprt_hdl = fmd_xprt_open(hdl, FMD_XPRT_RDONLY, NULL, NULL);
+ if (diskmon_init() == E_ERROR) {
+ fmd_xprt_close(hdl, g_xprt_hdl);
+ g_xprt_hdl = NULL;
+ config_fini();
+ fmd_hdl_unregister(hdl);
+ return;
+ }
+
+ log_msg(MM_MAIN, "Monitoring %d disks.\n", disk_count);
+
+ /*
+ * Iterate over all active cases.
+ * Since we automatically solve all cases, these cases must have
+ * had the fault added, but the DE must have been interrupted
+ * before they were solved.
+ */
+ for (cp = fmd_case_next(hdl, NULL);
+ cp != NULL; cp = fmd_case_next(hdl, cp)) {
+
+ if (!fmd_case_solved(hdl, cp))
+ fmd_case_solve(hdl, cp);
+ }
+}
+
+void
+_fmd_fini(fmd_hdl_t *hdl)
+{
+ diskmon_teardown_all();
+ fmd_xprt_close(hdl, g_xprt_hdl);
+ g_fm_hdl = NULL;
+ g_xprt_hdl = NULL;
+}
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/sfx4500-disk.conf b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/sfx4500-disk.conf
new file mode 100644
index 0000000000..a8fefa5b1b
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/sfx4500-disk.conf
@@ -0,0 +1,32 @@
+#
+# 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 2006 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#ident "%Z%%M% %I% %E% SMI"
+#
+# fmd configuration file for the sfx4500-disk.so diagnosis engine.
+#
+subscribe ereport.io.sata.*
+subscribe fault.io.disk.*
+subscribe list.repaired
+dictionary DISK
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/sfx4500-disk.h b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/sfx4500-disk.h
new file mode 100644
index 0000000000..4528980f59
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/sfx4500-disk.h
@@ -0,0 +1,56 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SFX4500_DISK_H
+#define _SFX4500_DISK_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Externs for sfx4500-disk
+ */
+
+#include <diskmon_conf.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SFX4500_DISK_MODULE_VERSION "1.0"
+
+#define E_SUCCESS 0
+#define E_ERROR 1
+
+extern cfgdata_t *config_data;
+extern fmd_xprt_t *g_xprt_hdl;
+extern fmd_hdl_t *g_fm_hdl;
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SFX4500_DISK_H */
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/topo_gather.c b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/topo_gather.c
new file mode 100644
index 0000000000..d3db871ed8
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/topo_gather.c
@@ -0,0 +1,921 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Gathers properties exported by libtopo and uses them to construct diskmon
+ * data structures, which hold the configuration information for the
+ * DE.
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <ctype.h>
+#include <pthread.h>
+#include <libnvpair.h>
+#include <config_admin.h>
+#include <sys/fm/protocol.h>
+#include <fm/libtopo.h>
+
+#include "sata.h"
+#include "sfx4500_props.h"
+#include "sfx4500-disk.h"
+#include "topo_gather.h"
+
+#define TOPO_PGROUP_IO "io" /* duplicated from did_props.h */
+#define MAX_CONF_MSG_LEN 256
+
+static nvlist_t *g_topo2diskmon = NULL;
+
+/*
+ * The following function template is required for nvlists that were
+ * create with no flags (so there can be multiple identical name or name-value
+ * pairs). The function defined below returns the first match for the name
+ * provided.
+ */
+#define NONUNIQUE_NVLIST_FN(suffix, type, atype) \
+static int \
+nonunique_nvlist_lookup_##suffix(nvlist_t *nvlp, const char *n, atype *rpp) \
+{ \
+ nvpair_t *nvp = NULL; \
+ while ((nvp = nvlist_next_nvpair(nvlp, nvp)) != NULL) { \
+ if (nvpair_type(nvp) != type) \
+ continue; \
+ if (strcmp(nvpair_name(nvp), n) == 0) \
+ return (nvpair_value_##suffix(nvp, rpp)); \
+ } \
+ return (ENOENT); \
+}
+
+NONUNIQUE_NVLIST_FN(string, DATA_TYPE_STRING, char *)
+
+static diskmon_t *
+dm_fmristring_to_diskmon(char *str)
+{
+ diskmon_t *p = NULL;
+ uint64_t u64val;
+ char ch;
+ char *lastsl = strrchr(str, '/');
+
+ ch = *lastsl;
+ *lastsl = 0;
+
+ if (nvlist_lookup_uint64(g_topo2diskmon, str, &u64val) == 0) {
+
+ p = (diskmon_t *)(uintptr_t)u64val;
+ }
+
+ *lastsl = ch;
+
+ return (p);
+}
+
+diskmon_t *
+dm_fmri_to_diskmon(fmd_hdl_t *hdl, nvlist_t *fmri)
+{
+ topo_hdl_t *thdl;
+ nvlist_t *dupfmri;
+ diskmon_t *diskp;
+ char *buf;
+ int err;
+
+ if (nvlist_dup(fmri, &dupfmri, 0) != 0)
+ return (NULL);
+
+ (void) nvlist_remove(dupfmri, FM_FMRI_HC_REVISION, DATA_TYPE_STRING);
+ (void) nvlist_remove(dupfmri, FM_FMRI_HC_SERIAL_ID, DATA_TYPE_STRING);
+ (void) nvlist_remove(dupfmri, FM_FMRI_HC_PART, DATA_TYPE_STRING);
+
+ thdl = fmd_hdl_topology(hdl, TOPO_VERSION);
+ if (thdl == NULL || topo_fmri_nvl2str(thdl, dupfmri, &buf, &err) != 0) {
+ nvlist_free(dupfmri);
+ return (NULL);
+ }
+
+ diskp = dm_fmristring_to_diskmon(buf);
+
+ nvlist_free(dupfmri);
+ topo_hdl_strfree(thdl, buf);
+
+ return (diskp);
+}
+
+static nvlist_t *
+find_sfx4500_private_pgroup(topo_hdl_t *thp, tnode_t *node)
+{
+ nvlist_t *list_of_lists, *nvlp, *dupnvlp;
+ nvlist_t *sfx4500_pgrp = NULL;
+ nvpair_t *nvp = NULL;
+ char *pgroup_name;
+
+ /*
+ * topo_prop_get_all() returns an nvlist that contains other
+ * nvlists (some of which are property groups). Since the private
+ * property group we need will be among the list of property
+ * groups returned (hopefully), we need to walk the list of nvlists
+ * in the topo node's properties to find the property groups, then
+ * check inside each embedded nvlist to see if it's the pgroup we're
+ * looking for.
+ */
+ if ((list_of_lists = topo_prop_get_all(thp, node)) != NULL) {
+ /*
+ * Go through the list of nvlists, looking for the
+ * property group we need.
+ */
+ while ((nvp = nvlist_next_nvpair(list_of_lists, nvp)) != NULL) {
+
+ if (nvpair_type(nvp) != DATA_TYPE_NVLIST ||
+ strcmp(nvpair_name(nvp), TOPO_PROP_GROUP) != 0 ||
+ nvpair_value_nvlist(nvp, &nvlp) != 0)
+ continue;
+
+ assert(nvlp != NULL);
+ pgroup_name = NULL;
+
+ if (nonunique_nvlist_lookup_string(nvlp,
+ TOPO_PROP_GROUP_NAME, &pgroup_name) != 0 ||
+ strcmp(pgroup_name, SUN_FIRE_X4500_PROPERTIES) != 0)
+ continue;
+ else {
+ /*
+ * Duplicate the nvlist so that when the
+ * master nvlist is freed (below), we will
+ * still refer to allocated memory.
+ */
+ if (nvlist_dup(nvlp, &dupnvlp, 0) == 0)
+ sfx4500_pgrp = dupnvlp;
+ else
+ sfx4500_pgrp = NULL;
+ break;
+ }
+ }
+
+ nvlist_free(list_of_lists);
+ }
+
+ return (sfx4500_pgrp);
+}
+
+/*
+ * Look up the FMRI corresponding to the node in the global
+ * hash table and return the pointer stored (if any). Save the
+ * FMRI string in *str if str is non-NULL.
+ */
+static void *
+fmri2ptr(topo_hdl_t *thp, tnode_t *node, char **str, int *err)
+{
+ nvlist_t *fmri = NULL;
+ char *cstr = NULL;
+ uint64_t u64val;
+ void *p = NULL;
+
+ if (topo_node_resource(node, &fmri, err) != 0)
+ return (NULL);
+
+ if (topo_fmri_nvl2str(thp, fmri, &cstr, err) != 0) {
+ nvlist_free(fmri);
+ return (NULL);
+ }
+
+ if (nvlist_lookup_uint64(g_topo2diskmon, cstr, &u64val) == 0) {
+
+ p = (void *)(uintptr_t)u64val;
+ }
+
+ nvlist_free(fmri);
+ if (str != NULL)
+ *str = dstrdup(cstr);
+ topo_hdl_strfree(thp, cstr);
+ return (p);
+}
+
+static void
+transform_model_string(char *manuf, char *model, char **finalstring,
+ int *finalstringbuflen)
+{
+ int buflen = 0;
+ int i = 0;
+ int j = 0;
+ char *buf;
+ char *finalmodelstring;
+
+ if (manuf != NULL)
+ buflen += strlen(manuf);
+ if (model != NULL)
+ buflen += strlen(model);
+ buflen += 2;
+
+ buf = dmalloc(buflen);
+ finalmodelstring = dmalloc(buflen);
+ if (manuf != NULL && model != NULL)
+ (void) snprintf(buf, buflen, "%s-%s", manuf, model);
+ else
+ (void) snprintf(buf, buflen, "%s",
+ manuf == NULL ? model : manuf);
+
+ do {
+ if (isspace(buf[i]))
+ finalmodelstring[j++] = '-';
+ else
+ finalmodelstring[j++] = buf[i];
+
+ while (buf[i] != 0 && isspace(buf[i]))
+ i++;
+ } while (buf[i++] != 0);
+
+ finalmodelstring[j] = 0;
+
+ dfree(buf, buflen);
+ *finalstring = finalmodelstring;
+ *finalstringbuflen = buflen;
+}
+
+static int
+topo_add_disk(topo_hdl_t *thp, tnode_t *node, diskmon_t *target_diskp)
+{
+ nvlist_t *fmri = NULL;
+ nvlist_t *asru_fmri;
+ nvlist_t *fru_fmri;
+ char *devpath = NULL;
+ char *capacity = NULL;
+ char *firmrev = NULL;
+ char *serial = NULL;
+ char *manuf = NULL;
+ char *model = NULL;
+ char *cstr = NULL;
+ char *buf;
+ char *label;
+ char *p;
+ uint64_t ptr = 0;
+ int buflen;
+ int err;
+ int orig_cstr_len;
+ dm_fru_t *frup;
+ diskmon_t *diskp;
+
+ /*
+ * Match this node to a disk in the configuration by looking at
+ * our parent's fmri (and do that by getting our FMRI and chopping
+ * off the last part).
+ */
+ if (topo_node_resource(node, &fmri, &err) != 0) {
+ log_msg(MM_TOPO, "topo_add_disk: Could not generate FMRI for "
+ "node %p!\n", (void *)node);
+ return (-1);
+ }
+
+ if (topo_fmri_nvl2str(thp, fmri, &cstr, &err) != 0) {
+ log_msg(MM_TOPO, "topo_add_disk: Could not create string for "
+ "node %p's FMRI!\n", (void *)node);
+ nvlist_free(fmri);
+ return (-1);
+ }
+
+ nvlist_free(fmri);
+
+ /*
+ * Chop off all but last path (since there's no way to get
+ * the node's parent in the libtopo API).
+ */
+ orig_cstr_len = strlen(cstr) + 1;
+ p = strrchr(cstr, '/');
+ assert(p != NULL);
+ *p = 0;
+ if (nvlist_lookup_uint64(g_topo2diskmon, cstr, &ptr) != 0) {
+ log_msg(MM_TOPO, "Could not find parent's diskmon for node "
+ "%p!\n", node);
+ topo_hdl_free(thp, cstr, orig_cstr_len);
+ return (-1);
+ }
+
+ topo_hdl_free(thp, cstr, orig_cstr_len);
+
+ diskp = (diskmon_t *)(uintptr_t)ptr;
+
+ /* If we were called upon to update a particular disk, do it */
+ if (target_diskp != NULL && diskp != target_diskp) {
+ return (0);
+ }
+
+ /*
+ * Update the diskmon's ASRU and FRU with our information (this
+ * information is cached in the diskmon so we don't have to do a
+ * time-consuming topo traversal when we get an ereport).
+ */
+ if (topo_prop_get_fmri(node, TOPO_PGROUP_PROTOCOL, TOPO_PROP_ASRU,
+ &asru_fmri, &err) == 0) {
+ diskmon_add_asru(diskp, asru_fmri);
+ nvlist_free(asru_fmri);
+ }
+ if (topo_prop_get_fmri(node, TOPO_PGROUP_PROTOCOL, TOPO_PROP_FRU,
+ &fru_fmri, &err) == 0) {
+ diskmon_add_fru(diskp, fru_fmri);
+ nvlist_free(fru_fmri);
+ }
+ if (topo_node_resource(node, &fmri, &err) == 0) {
+ diskmon_add_disk_fmri(diskp, fmri);
+ nvlist_free(fmri);
+ }
+
+ /*
+ * Update the diskmon's location field with the disk's label
+ */
+ if (diskp->location)
+ dstrfree(diskp->location);
+ if (topo_node_label(node, &label, &err) == 0) {
+ diskp->location = dstrdup(label);
+ topo_hdl_strfree(thp, label);
+ } else
+ diskp->location = dstrdup("unknown location");
+
+ /*
+ * Check for a device path property (if the disk is configured,
+ * it will be present) and add it to the diskmon's properties)
+ */
+ if (topo_prop_get_string(node, TOPO_PGROUP_IO, TOPO_IO_DEV_PATH,
+ &devpath, &err) == 0) {
+ char devp[PATH_MAX];
+ /*
+ * Consumers of the DISK_PROP_DEVPATH property expect a raw
+ * minor device node
+ */
+ (void) snprintf(devp, PATH_MAX, "%s:q,raw", devpath);
+ (void) nvlist_add_string(diskp->props, DISK_PROP_DEVPATH,
+ devp);
+ topo_hdl_strfree(thp, devpath);
+ }
+
+ /*
+ * Add the logical disk node, if it exists
+ */
+ if (topo_prop_get_string(node, TOPO_STORAGE_PGROUP,
+ TOPO_STORAGE_LOGICAL_DISK_NAME, &devpath, &err) == 0) {
+ (void) nvlist_add_string(diskp->props, DISK_PROP_LOGNAME,
+ devpath);
+ topo_hdl_strfree(thp, devpath);
+ }
+
+ /*
+ * Add the FRU information (if present in the node) to the diskmon's
+ * fru data structure:
+ */
+ (void) topo_prop_get_string(node, TOPO_STORAGE_PGROUP,
+ TOPO_STORAGE_MODEL, &model, &err);
+
+ (void) topo_prop_get_string(node, TOPO_STORAGE_PGROUP,
+ TOPO_STORAGE_MANUFACTURER, &manuf, &err);
+
+ (void) topo_prop_get_string(node, TOPO_STORAGE_PGROUP,
+ TOPO_STORAGE_SERIAL_NUM, &serial, &err);
+
+ (void) topo_prop_get_string(node, TOPO_STORAGE_PGROUP,
+ TOPO_STORAGE_FIRMWARE_REV, &firmrev, &err);
+
+ (void) topo_prop_get_string(node, TOPO_STORAGE_PGROUP,
+ TOPO_STORAGE_CAPACITY, &capacity, &err);
+
+ frup = new_dmfru(manuf, model, firmrev, serial,
+ capacity == NULL ? 0 : strtoull(capacity, 0, 0));
+
+ /*
+ * Update the disk's resource FMRI with the
+ * SunService-required members:
+ * FM_FMRI_HC_SERIAL_ID, FM_FMRI_HC_PART, and
+ * FM_FMRI_HC_REVISION
+ */
+ (void) nvlist_add_string(diskp->disk_res_fmri,
+ FM_FMRI_HC_SERIAL_ID, serial);
+
+ transform_model_string(manuf, model, &buf, &buflen);
+
+ (void) nvlist_add_string(diskp->disk_res_fmri,
+ FM_FMRI_HC_PART, buf);
+
+ /*
+ * Add the serial number to the ASRU so that when the resource
+ * is marked faulty in the fmd resource cache, the hc scheme
+ * plugin can detect when the disk is no longer installed (and so,
+ * can clear the faulty state automatically across fmd restarts).
+ *
+ * The serial number is only updated when a disk comes online
+ * because that's when the disk node exists in the topo tree.
+ * It's ok to keep a stale value in the ASRU when the disk is removed
+ * because it's only used as part of fault creation when the disk
+ * is configured (online), at which point it will be updated with
+ * the (current) serial number of the disk inserted.
+ */
+ (void) nvlist_add_string(diskp->asru_fmri,
+ FM_FMRI_HC_SERIAL_ID, serial);
+
+ dfree(buf, buflen);
+
+ (void) nvlist_add_string(diskp->disk_res_fmri,
+ FM_FMRI_HC_REVISION, firmrev);
+
+ if (model) {
+ topo_hdl_strfree(thp, model);
+ }
+
+ if (manuf) {
+ topo_hdl_strfree(thp, manuf);
+ }
+
+ if (serial) {
+ topo_hdl_strfree(thp, serial);
+ }
+
+ if (firmrev) {
+ topo_hdl_strfree(thp, firmrev);
+ }
+
+ if (capacity) {
+ topo_hdl_strfree(thp, capacity);
+ }
+
+ /* Add the fru information to the diskmon: */
+ assert(pthread_mutex_lock(&diskp->fru_mutex) == 0);
+ assert(diskp->frup == NULL);
+ diskp->frup = frup;
+ assert(pthread_mutex_unlock(&diskp->fru_mutex) == 0);
+
+ return (0);
+}
+
+static int
+indicator_breakup(char *identifier, ind_state_t *state, char **name)
+{
+ if (identifier[0] != '+' && identifier[0] != '-') {
+ log_msg(MM_CONF, "Invalid indicator name `%s'\n", identifier);
+ return (-1);
+ }
+
+ *state = (identifier[0] == '+') ? INDICATOR_ON : INDICATOR_OFF;
+ *name = &identifier[1];
+ return (0);
+}
+
+static int
+topoprop_indicator_add(indicator_t **indp, char *ind_name, char *ind_action)
+{
+ /* The Indicator name is of the form: "[+-][A-Za-z][A-Za-z0-9]+" */
+ indicator_t *newindp;
+ ind_state_t state;
+ char *name;
+
+ if (indicator_breakup(ind_name, &state, &name) != 0)
+ return (-1);
+ newindp = new_indicator(state, name, ind_action);
+
+ link_indicator(indp, newindp);
+
+ return (0);
+}
+
+static hotplug_state_t
+str2dmstate(char *str)
+{
+ if (strcasecmp("configured", str) == 0) {
+ return (HPS_CONFIGURED);
+ } else if (strcasecmp("unconfigured", str) == 0) {
+ return (HPS_UNCONFIGURED);
+ } else if (strcasecmp("absent", str) == 0) {
+ return (HPS_ABSENT);
+ } else if (strcasecmp("present", str) == 0) {
+ return (HPS_PRESENT);
+ } else
+ return (HPS_UNKNOWN);
+}
+
+static int
+topoprop_indrule_add(indrule_t **indrp, char *sts, char *acts)
+{
+ ind_action_t *indactp = NULL;
+ ind_state_t state;
+ char *name, *lasts, *p;
+ int stateslen = strlen(sts) + 1;
+ int actionslen = strlen(acts) + 1;
+ char *states = dstrdup(sts);
+ char *actions = dstrdup(acts);
+ state_transition_t strans;
+ boolean_t failed = B_FALSE;
+ conf_err_t err;
+ char msgbuf[MAX_CONF_MSG_LEN];
+
+ /* The state string is of the form "{STATE}>{STATE}" */
+ p = strchr(states, '>');
+ assert(p != NULL);
+ *p = 0;
+ strans.begin = str2dmstate(states);
+ *p = '>';
+ strans.end = str2dmstate(p + 1);
+
+ if (strans.begin == HPS_UNKNOWN || strans.end == HPS_UNKNOWN) {
+ log_msg(MM_CONF, "Invalid states property `%s'\n", sts);
+ failed = B_TRUE;
+ } else if ((err = check_state_transition(strans.begin, strans.end))
+ != E_NO_ERROR) {
+ conf_error_msg(err, msgbuf, MAX_CONF_MSG_LEN, &strans);
+ log_msg(MM_CONF, "%s: Not adding disk to list!\n", msgbuf);
+ failed = B_TRUE;
+ }
+
+ /* Actions are of the form "{ACTION}[&{ACTION}]" */
+ if (!failed && (p = strtok_r(actions, "&", &lasts)) != NULL) {
+ /* At least 2 tokens */
+ do {
+ if (indicator_breakup(p, &state, &name) != 0) {
+ failed = B_TRUE;
+ break;
+ }
+
+ link_indaction(&indactp, new_indaction(state, name));
+
+ } while ((p = strtok_r(NULL, "&", &lasts)) != NULL);
+ } else if (!failed) {
+ /* One token */
+ if (indicator_breakup(actions, &state, &name) != 0)
+ return (-1);
+ indactp = new_indaction(state, name);
+ }
+
+ dfree(states, stateslen);
+ dfree(actions, actionslen);
+
+ if (!failed && (err = check_indactions(indactp)) != E_NO_ERROR) {
+ conf_error_msg(err, msgbuf, MAX_CONF_MSG_LEN, NULL);
+ log_msg(MM_CONF, "%s: Not adding disk to list!\n", msgbuf);
+ failed = B_TRUE;
+ }
+
+ if (failed) {
+ indaction_free(indactp);
+ return (-1);
+ } else
+ link_indrule(indrp, new_indrule(&strans, indactp));
+ return (0);
+}
+
+
+static int
+topo_add_sata_port(topo_hdl_t *thp, tnode_t *node, diskmon_t *target_diskp)
+{
+ nvlist_t *nvlp = find_sfx4500_private_pgroup(thp, node);
+ nvlist_t *prop_nvlp;
+ nvpair_t *nvp = NULL;
+ char *prop_name, *prop_value;
+#define PNAME_MAX 128
+ char pname[PNAME_MAX];
+ char msgbuf[MAX_CONF_MSG_LEN];
+ char *indicator_name, *indicator_action;
+ char *indrule_states, *indrule_actions;
+ int err = 0, i;
+ conf_err_t conferr;
+ boolean_t conf_failure = B_FALSE;
+ char *physid = NULL;
+ char *label;
+ nvlist_t *diskprops = NULL;
+ char *cstr = NULL;
+ indicator_t *indp = NULL;
+ indrule_t *indrp = NULL;
+ void *p;
+ diskmon_t *diskp;
+ void *ptr;
+
+ /* No private properties -- just ignore the port */
+ if (nvlp == NULL)
+ return (0);
+
+ /*
+ * Look for a diskmon based on this node's FMRI string.
+ * Once a diskmon has been created, it's not re-created. This is
+ * essential for the times when the tree-walk is called after a
+ * disk is inserted (or removed) -- in that case, the disk node
+ * handler simply updates the FRU information in the diskmon.
+ */
+ if ((p = fmri2ptr(thp, node, &cstr, &err)) != NULL) {
+
+ diskp = (diskmon_t *)p;
+
+ /*
+ * Delete the FRU information from the diskmon. If a disk
+ * is connected, its FRU information will be refreshed by
+ * the disk node code.
+ */
+ if (diskp->frup && (target_diskp == NULL ||
+ diskp == target_diskp)) {
+ assert(pthread_mutex_lock(&diskp->fru_mutex) == 0);
+ dmfru_free(diskp->frup);
+ diskp->frup = NULL;
+ assert(pthread_mutex_unlock(&diskp->fru_mutex) == 0);
+ }
+
+ dstrfree(cstr);
+ nvlist_free(nvlp);
+ return (0);
+ }
+
+ /*
+ * Determine the physical path to the attachment point
+ */
+ if (topo_prop_get_string(node, TOPO_PGROUP_IO,
+ TOPO_IO_AP_PATH, &physid, &err) != 0) {
+
+ /* physid cannot have been allocated */
+ if (cstr)
+ dstrfree(cstr);
+ nvlist_free(nvlp);
+ return (-1);
+ }
+
+ /*
+ * Process the properties. If we encounter a property that
+ * is not an indicator name, action, or rule, add it to the
+ * disk's props list.
+ */
+
+ /* Process indicators */
+ i = 0;
+ indicator_name = NULL;
+ indicator_action = NULL;
+ do {
+ if (indicator_name != NULL && indicator_action != NULL) {
+
+ if (topoprop_indicator_add(&indp, indicator_name,
+ indicator_action) != 0) {
+
+ conf_failure = B_TRUE;
+ }
+
+ topo_hdl_strfree(thp, indicator_name);
+ topo_hdl_strfree(thp, indicator_action);
+ }
+
+ (void) snprintf(pname, PNAME_MAX, SATA_IND_NAME "-%d", i);
+ if (topo_prop_get_string(node, SUN_FIRE_X4500_PROPERTIES,
+ pname, &indicator_name, &err) != 0)
+ break;
+
+ (void) snprintf(pname, PNAME_MAX, SATA_IND_ACTION "-%d", i);
+ if (topo_prop_get_string(node, SUN_FIRE_X4500_PROPERTIES,
+ pname, &indicator_action, &err) != 0)
+ break;
+
+ i++;
+ } while (!conf_failure && indicator_name != NULL &&
+ indicator_action != NULL);
+
+ if (!conf_failure && indp != NULL &&
+ (conferr = check_inds(indp)) != E_NO_ERROR) {
+ conf_error_msg(conferr, msgbuf, MAX_CONF_MSG_LEN, NULL);
+ log_msg(MM_CONF, "%s: Not adding disk to list\n", msgbuf);
+ conf_failure = B_TRUE;
+ }
+
+ /* Process state rules and indicator actions */
+ i = 0;
+ indrule_states = NULL;
+ indrule_actions = NULL;
+ do {
+ if (indrule_states != NULL && indrule_actions != NULL) {
+
+ if (topoprop_indrule_add(&indrp, indrule_states,
+ indrule_actions) != 0) {
+
+ conf_failure = B_TRUE;
+ }
+
+ topo_hdl_strfree(thp, indrule_states);
+ topo_hdl_strfree(thp, indrule_actions);
+ }
+
+ (void) snprintf(pname, PNAME_MAX, SATA_INDRULE_STATES "-%d", i);
+ if (topo_prop_get_string(node, SUN_FIRE_X4500_PROPERTIES,
+ pname, &indrule_states, &err) != 0)
+ break;
+
+ (void) snprintf(pname, PNAME_MAX, SATA_INDRULE_ACTIONS "-%d",
+ i);
+ if (topo_prop_get_string(node, SUN_FIRE_X4500_PROPERTIES,
+ pname, &indrule_actions, &err) != 0)
+ break;
+
+ i++;
+ } while (!conf_failure && indrule_states != NULL &&
+ indrule_actions != NULL);
+
+ if (!conf_failure && indrp != NULL && indp != NULL &&
+ ((conferr = check_indrules(indrp, (state_transition_t **)&ptr))
+ != E_NO_ERROR ||
+ (conferr = check_consistent_ind_indrules(indp, indrp,
+ (ind_action_t **)&ptr)) != E_NO_ERROR)) {
+
+ conf_error_msg(conferr, msgbuf, MAX_CONF_MSG_LEN, ptr);
+ log_msg(MM_CONF, "%s: Not adding disk to list\n", msgbuf);
+ conf_failure = B_TRUE;
+
+ }
+
+ /*
+ * Now collect miscellaneous properties.
+ * Each property is stored as an embedded nvlist named
+ * TOPO_PROP_VAL. The property name is stored in the value for
+ * key=TOPO_PROP_VAL_NAME and the property's value is
+ * stored in the value for key=TOPO_PROP_VAL_VAL. This is all
+ * necessary so we can subtractively decode the properties that
+ * we do not directly handle (so that these properties are added to
+ * the per-disk properties nvlist), increasing flexibility.
+ */
+ (void) nvlist_alloc(&diskprops, NV_UNIQUE_NAME, 0);
+ while ((nvp = nvlist_next_nvpair(nvlp, nvp)) != NULL) {
+ /* Only care about embedded nvlists named TOPO_PROP_VAL */
+ if (nvpair_type(nvp) != DATA_TYPE_NVLIST ||
+ strcmp(nvpair_name(nvp), TOPO_PROP_VAL) != 0 ||
+ nvpair_value_nvlist(nvp, &prop_nvlp) != 0)
+ continue;
+
+ if (nonunique_nvlist_lookup_string(prop_nvlp,
+ TOPO_PROP_VAL_NAME, &prop_name) != 0)
+ continue;
+
+ /* Filter out indicator properties */
+ if (strstr(prop_name, SATA_IND_NAME) != NULL ||
+ strstr(prop_name, SATA_IND_ACTION) != NULL ||
+ strstr(prop_name, SATA_INDRULE_STATES) != NULL ||
+ strstr(prop_name, SATA_INDRULE_ACTIONS) != NULL)
+ continue;
+
+ if (nonunique_nvlist_lookup_string(prop_nvlp, TOPO_PROP_VAL_VAL,
+ &prop_value) != 0)
+ continue;
+
+ /* Add the property to the disk's prop list: */
+ if (nvlist_add_string(diskprops, prop_name, prop_value) != 0)
+ log_msg(MM_TOPO,
+ "Could not add disk property `%s' with "
+ "value `%s'\n", prop_name, prop_value);
+ }
+
+ nvlist_free(nvlp);
+
+ if (cstr != NULL) {
+ namevalpr_t nvpr;
+ nvlist_t *dmap_nvl;
+
+ nvpr.name = DISK_AP_PROP_APID;
+ nvpr.value = strncmp(physid, "/devices", 8) == 0 ?
+ (physid + 8) : physid;
+
+ /*
+ * Set the diskmon's location to the value in this port's label.
+ * If there's a disk plugged in, the location will be updated
+ * to be the disk label (e.g. HD_ID_00). Until a disk is
+ * inserted, though, there won't be a disk libtopo node
+ * created.
+ */
+
+ /* Pass physid without the leading "/devices": */
+ dmap_nvl = namevalpr_to_nvlist(&nvpr);
+
+ diskp = new_diskmon(dmap_nvl, indp, indrp, diskprops);
+
+ if (topo_node_label(node, &label, &err) == 0) {
+ diskp->location = dstrdup(label);
+ topo_hdl_strfree(thp, label);
+ } else
+ diskp->location = dstrdup("unknown location");
+
+ if (!conf_failure && diskp != NULL) {
+ /* Add this diskmon to the disk list */
+ cfgdata_add_diskmon(config_data, diskp);
+ if (nvlist_add_uint64(g_topo2diskmon, cstr,
+ (uint64_t)(uintptr_t)diskp) != 0) {
+ log_msg(MM_TOPO,
+ "Could not add pointer to nvlist "
+ "for `%s'!\n", cstr);
+ }
+ } else if (diskp != NULL) {
+ diskmon_free(diskp);
+ } else {
+ if (dmap_nvl)
+ nvlist_free(dmap_nvl);
+ if (indp)
+ ind_free(indp);
+ if (indrp)
+ indrule_free(indrp);
+ if (diskprops)
+ nvlist_free(diskprops);
+ }
+
+ dstrfree(cstr);
+ }
+
+
+ topo_hdl_strfree(thp, physid);
+ return (0);
+}
+
+/*ARGSUSED*/
+static int
+gather_topo_cfg(topo_hdl_t *thp, tnode_t *node, void *arg)
+{
+ char *nodename = topo_node_name(node);
+ if (strcmp(SATA_DISK, nodename) == 0)
+ return (topo_add_disk(thp, node, (diskmon_t *)arg)
+ ? TOPO_WALK_ERR : TOPO_WALK_NEXT);
+ else if (strcmp(SATA_PORT, nodename) == 0)
+ return (topo_add_sata_port(thp, node, (diskmon_t *)arg)
+ ? TOPO_WALK_ERR : TOPO_WALK_NEXT);
+
+ return (TOPO_WALK_NEXT);
+}
+
+
+int
+topo_update_configuration(diskmon_t *diskp)
+{
+ int err;
+ topo_hdl_t *thp;
+ topo_walk_t *twp;
+ char *uuid;
+
+ if ((thp = topo_open(TOPO_VERSION, NULL, &err)) == NULL) {
+
+ return (TOPO_OPEN_ERROR);
+ }
+
+ if ((uuid = topo_snap_hold(thp, NULL, &err)) == NULL) {
+
+ topo_close(thp);
+ return (TOPO_SNAP_ERROR);
+ }
+
+
+ if ((twp = topo_walk_init(thp, FM_FMRI_SCHEME_HC, gather_topo_cfg,
+ diskp, &err)) == NULL) {
+
+ topo_snap_release(thp);
+ topo_hdl_strfree(thp, uuid);
+ topo_close(thp);
+
+ return (err ? TOPO_WALK_INIT_ERROR : TOPO_SUCCESS);
+ }
+
+ topo_hdl_strfree(thp, uuid);
+
+ if (topo_walk_step(twp, TOPO_WALK_CHILD) == TOPO_WALK_ERR) {
+
+ topo_walk_fini(twp);
+ topo_snap_release(thp);
+ topo_close(thp);
+
+ return (TOPO_WALK_ERROR);
+ }
+
+ topo_walk_fini(twp);
+ topo_snap_release(thp);
+ topo_close(thp);
+
+ return (TOPO_SUCCESS);
+}
+
+int
+topo_init_configuration(void)
+{
+ return (nvlist_alloc(&g_topo2diskmon, NV_UNIQUE_NAME, 0));
+}
+
+void
+topo_fini_configuration(void)
+{
+ if (g_topo2diskmon) {
+ nvlist_free(g_topo2diskmon);
+ }
+}
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/topo_gather.h b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/topo_gather.h
new file mode 100644
index 0000000000..75028b4368
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/topo_gather.h
@@ -0,0 +1,56 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _TOPO_GATHER_H
+#define _TOPO_GATHER_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * sata libtopo plugin property consumer
+ * (creates diskmon configuration objects)
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define TOPO_SUCCESS 0
+#define TOPO_WALK_ERROR 1
+#define TOPO_WALK_INIT_ERROR 2
+#define TOPO_SNAP_ERROR 3
+#define TOPO_OPEN_ERROR 4
+
+int topo_update_configuration(diskmon_t *diskp);
+int topo_init_configuration(void);
+void topo_fini_configuration(void);
+diskmon_t *dm_fmri_to_diskmon(fmd_hdl_t *hdl, nvlist_t *fmri);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _TOPO_GATHER_H */
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/util.c b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/util.c
new file mode 100644
index 0000000000..964157fbb5
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/util.c
@@ -0,0 +1,420 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <assert.h>
+#include <smbios.h>
+
+#include <fm/fmd_api.h>
+
+#include "util.h"
+#include "sfx4500-disk.h"
+
+extern log_class_t g_verbose;
+
+static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static void log_msg_nl(log_class_t cl, const char *fmt, ...);
+
+/*
+ * Search a string list for a particular value.
+ * Return the string associated with that value.
+ */
+char *
+find_string(slist_t *slist, int match_value)
+{
+ for (; slist->str != NULL; slist++) {
+ if (slist->value == match_value) {
+ return (slist->str);
+ }
+ }
+
+ return ((char *)NULL);
+}
+
+/*
+ * Produce a pretty dump of memory
+ * <X> <Byte_i-1> <Byte_i> <Byte_i+1> ... [TEXT REPRESENTATION]
+ */
+static void
+dump(log_class_t cl, char *label, char *start, unsigned length)
+{
+ int byte_count;
+ int i;
+#define LINEBUFLEN 128
+ char linebuf[LINEBUFLEN];
+ char *linep;
+ int bufleft, len;
+
+ if (label != NULL)
+ log_msg_nl(cl, "%s\n", label);
+
+ linep = linebuf;
+ bufleft = LINEBUFLEN;
+
+ for (byte_count = 0; byte_count < length; byte_count += i) {
+
+ (void) snprintf(linep, bufleft, "%s0x%08" PRIX64 " ", "",
+ (uint64_t)byte_count);
+ len = strlen(linep);
+ bufleft -= len;
+ linep += len;
+
+ /*
+ * Inner loop processes 16 bytes at a time, or less
+ * if we have less than 16 bytes to go
+ */
+ for (i = 0; (i < 16) && ((byte_count + i) < length); i++) {
+ (void) snprintf(linep, bufleft, "%02X", (unsigned int)
+ (unsigned char) start[byte_count + i]);
+
+ len = strlen(linep);
+ bufleft -= len;
+ linep += len;
+
+ if (bufleft >= 2) {
+ if (i == 7)
+ *linep = '-';
+ else
+ *linep = ' ';
+
+ --bufleft;
+ ++linep;
+ }
+ }
+
+ /*
+ * If i is less than 16, then we had less than 16 bytes
+ * written to the output. We need to fixup the alignment
+ * to allow the "text" output to be aligned
+ */
+ if (i < 16) {
+ int numspaces = (16 - i) * 3;
+ while (numspaces-- > 0) {
+ if (bufleft >= 2) {
+ *linep = ' ';
+ --bufleft;
+ linep++;
+ }
+ }
+ }
+
+ if (bufleft >= 2) {
+ *linep = ' ';
+ --bufleft;
+ ++linep;
+ }
+
+ for (i = 0; (i < 16) && ((byte_count + i) < length); i++) {
+ int subscript = byte_count + i;
+ char ch = (isprint(start[subscript]) ?
+ start[subscript] : '.');
+
+ if (bufleft >= 2) {
+ *linep = ch;
+ --bufleft;
+ ++linep;
+ }
+ }
+
+ linebuf[LINEBUFLEN - bufleft] = 0;
+
+ log_msg_nl(cl, "%s\n", linebuf);
+
+ linep = linebuf;
+ bufleft = LINEBUFLEN;
+ }
+}
+
+void
+log_dump(log_class_t cl, char *label, char *start, unsigned length)
+{
+ if ((g_verbose & cl) != cl)
+ return;
+
+ assert(pthread_mutex_lock(&log_mutex) == 0);
+ dump(cl, label, start, length);
+ assert(pthread_mutex_unlock(&log_mutex) == 0);
+}
+
+static void
+verror(const char *fmt, va_list ap)
+{
+ int error = errno;
+
+ assert(pthread_mutex_lock(&log_mutex) == 0);
+ fmd_hdl_vdebug(g_fm_hdl, fmt, ap);
+
+ if (fmt[strlen(fmt) - 1] != '\n')
+ fmd_hdl_debug(g_fm_hdl, ": %s\n", strerror(error));
+
+ assert(pthread_mutex_unlock(&log_mutex) == 0);
+}
+
+static void
+vwarn_e(const char *fmt, va_list ap)
+{
+ int error = errno;
+
+ assert(pthread_mutex_lock(&log_mutex) == 0);
+ fmd_hdl_debug(g_fm_hdl, "WARNING: ");
+ fmd_hdl_vdebug(g_fm_hdl, fmt, ap);
+
+ if (fmt[strlen(fmt) - 1] != '\n')
+ fmd_hdl_debug(g_fm_hdl, ": %s\n", strerror(error));
+
+ assert(pthread_mutex_unlock(&log_mutex) == 0);
+}
+
+static void
+vwarn(const char *fmt, va_list ap)
+{
+ assert(pthread_mutex_lock(&log_mutex) == 0);
+ fmd_hdl_debug(g_fm_hdl, "WARNING: ");
+ fmd_hdl_vdebug(g_fm_hdl, fmt, ap);
+ assert(pthread_mutex_unlock(&log_mutex) == 0);
+}
+
+void
+vcont(log_class_t cl, const char *fmt, va_list ap)
+{
+ int error = errno;
+
+ if ((g_verbose & cl) != cl)
+ return;
+
+ assert(pthread_mutex_lock(&log_mutex) == 0);
+ fmd_hdl_vdebug(g_fm_hdl, fmt, ap);
+
+ if (fmt[strlen(fmt) - 1] != '\n')
+ fmd_hdl_debug(g_fm_hdl, ": %s\n", strerror(error));
+
+ assert(pthread_mutex_unlock(&log_mutex) == 0);
+}
+
+static void
+log_msg_nl(log_class_t cl, const char *fmt, ...)
+{
+ va_list ap;
+
+ if ((g_verbose & cl) != cl)
+ return;
+
+ va_start(ap, fmt);
+ fmd_hdl_vdebug(g_fm_hdl, fmt, ap);
+ va_end(ap);
+}
+
+void
+log_msg(log_class_t cl, const char *fmt, ...)
+{
+ va_list ap;
+
+ if ((g_verbose & cl) != cl)
+ return;
+
+ assert(pthread_mutex_lock(&log_mutex) == 0);
+ va_start(ap, fmt);
+ fmd_hdl_vdebug(g_fm_hdl, fmt, ap);
+ va_end(ap);
+ assert(pthread_mutex_unlock(&log_mutex) == 0);
+}
+
+/*PRINTFLIKE1*/
+void
+log_err(const char *fmt, ...)
+{
+ va_list ap;
+
+ if ((g_verbose & MM_ERR) != MM_ERR)
+ return;
+
+ va_start(ap, fmt);
+ verror(fmt, ap);
+ va_end(ap);
+}
+
+/*PRINTFLIKE1*/
+void
+log_warn(const char *fmt, ...)
+{
+ va_list ap;
+
+ if ((g_verbose & MM_WARN) != MM_WARN)
+ return;
+
+ va_start(ap, fmt);
+ vwarn(fmt, ap);
+ va_end(ap);
+}
+
+/*PRINTFLIKE1*/
+void
+log_warn_e(const char *fmt, ...)
+{
+ va_list ap;
+
+ if ((g_verbose & MM_WARN) != MM_WARN)
+ return;
+
+ va_start(ap, fmt);
+ vwarn_e(fmt, ap);
+ va_end(ap);
+}
+
+void
+dfree(void *p, size_t sz)
+{
+ fmd_hdl_free(g_fm_hdl, p, sz);
+}
+
+void
+dstrfree(char *s)
+{
+ fmd_hdl_strfree(g_fm_hdl, s);
+}
+
+void *
+dmalloc(size_t sz)
+{
+ return (fmd_hdl_alloc(g_fm_hdl, sz, FMD_SLEEP));
+}
+
+void *
+dzmalloc(size_t sz)
+{
+ return (fmd_hdl_zalloc(g_fm_hdl, sz, FMD_SLEEP));
+}
+
+
+char *
+dstrdup(const char *s)
+{
+ return (fmd_hdl_strdup(g_fm_hdl, s, FMD_SLEEP));
+}
+
+void
+queue_add(qu_t *qp, void *data)
+{
+ struct q_node *qnp =
+ (struct q_node *)qp->nalloc(sizeof (struct q_node));
+ struct q_node *nodep;
+
+ qnp->data = data;
+ qnp->next = NULL;
+ assert(pthread_mutex_lock(&qp->mutex) == 0);
+
+ if (qp->nodep == NULL)
+ qp->nodep = qnp;
+ else {
+ nodep = qp->nodep;
+
+ while (nodep->next != NULL)
+ nodep = nodep->next;
+
+ nodep->next = qnp;
+ }
+
+ /* If the queue was empty, we need to wake people up */
+ if (qp->boe && qp->nodep == qnp)
+ assert(pthread_cond_broadcast(&qp->cvar) == 0);
+ assert(pthread_mutex_unlock(&qp->mutex) == 0);
+}
+
+void *
+queue_remove(qu_t *qp)
+{
+ void *rv = NULL;
+ struct q_node *nextnode;
+
+ assert(pthread_mutex_lock(&qp->mutex) == 0);
+
+ /* Wait while the queue is empty */
+ while (qp->boe && qp->nodep == NULL) {
+ (void) pthread_cond_wait(&qp->cvar, &qp->mutex);
+ }
+
+ /*
+ * If Block-On-Empty is false, the queue may be empty
+ */
+ if (qp->nodep != NULL) {
+ rv = qp->nodep->data;
+ nextnode = qp->nodep->next;
+ qp->nfree(qp->nodep, sizeof (struct q_node));
+ qp->nodep = nextnode;
+ }
+
+ assert(pthread_mutex_unlock(&qp->mutex) == 0);
+ return (rv);
+}
+
+qu_t *
+new_queue(boolean_t block_on_empty, void *(*nodealloc)(size_t),
+ void (*nodefree)(void *, size_t), void (*data_deallocator)(void *))
+{
+ qu_t *newqp = (qu_t *)dmalloc(sizeof (qu_t));
+
+ newqp->boe = block_on_empty;
+ newqp->nalloc = nodealloc;
+ newqp->nfree = nodefree;
+ newqp->data_dealloc = data_deallocator;
+ assert(pthread_mutex_init(&newqp->mutex, NULL) == 0);
+ assert(pthread_cond_init(&newqp->cvar, NULL) == 0);
+ newqp->nodep = NULL;
+
+ return (newqp);
+}
+
+void
+queue_free(qu_t **qpp)
+{
+ qu_t *qp = *qpp;
+ void *item;
+
+ assert(pthread_mutex_destroy(&qp->mutex) == 0);
+ assert(pthread_cond_destroy(&qp->cvar) == 0);
+
+ qp->boe = B_FALSE;
+
+ while ((item = queue_remove(qp)) != NULL) {
+ qp->data_dealloc(item);
+ }
+
+ assert(qp->nodep == NULL);
+
+ dfree(qp, sizeof (qu_t));
+ *qpp = NULL;
+}
diff --git a/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/util.h b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/util.h
new file mode 100644
index 0000000000..4848093835
--- /dev/null
+++ b/usr/src/cmd/fm/modules/i86pc/sfx4500-disk/util.h
@@ -0,0 +1,119 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _UTIL_H
+#define _UTIL_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Definitions for data structures used in the SCSI IE module
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <time.h>
+
+/*
+ * List of strings with arbitrary matching values
+ */
+typedef struct slist {
+ char *str;
+ int value;
+} slist_t;
+
+/*
+ * The following structures comprise the implementation of the
+ * queue structure that's used to construct the list of state
+ * changes. Removals from the queue are blocking operations that
+ * cause the thread to wait until new entries are added.
+ */
+struct q_node {
+ void *data;
+ struct q_node *next;
+};
+
+typedef struct q_head {
+ /*
+ * Block On Empty (when queue is empty, the calling thread will be
+ * blocked until something is added)
+ */
+ boolean_t boe;
+ pthread_mutex_t mutex;
+ pthread_cond_t cvar;
+ void *(*nalloc)(size_t);
+ void (*nfree)(void *, size_t);
+ void (*data_dealloc)(void *);
+ struct q_node *nodep;
+} qu_t;
+
+typedef enum log_class {
+ MM_CONF = 0x0001,
+ MM_FAULTMGR = 0x0002,
+ MM_HPMGR = 0x0004,
+ MM_SCHGMGR = 0x0008,
+ MM_FLTANALYZE = 0x0010,
+ MM_SCSI = 0x0020,
+ MM_MAIN = 0x0040,
+ MM_PLUGIN = 0x0080,
+ MM_TOPO = 0x0100,
+ MM_ERR = 0x0200,
+ MM_WARN = 0x0400,
+ MM_NOTE = 0x0800,
+ MM_OTHER = 0x1000
+} log_class_t;
+
+extern void queue_add(qu_t *qp, void *data);
+extern void *queue_remove(qu_t *qp);
+extern qu_t *new_queue(boolean_t block_on_empty, void *(*nodealloc)(size_t),
+ void (*nodefree)(void *, size_t), void (*deallocator)(void *));
+extern void queue_free(qu_t **qp);
+
+extern char *find_string(slist_t *slist, int match_value);
+
+extern void log_dump(log_class_t cl, char *label, char *start, unsigned length);
+
+extern void *dmalloc(size_t sz);
+extern void *dzmalloc(size_t sz);
+extern char *dstrdup(const char *s);
+extern void dfree(void *p, size_t sz);
+extern void dstrfree(char *s);
+
+extern void log_msg(log_class_t cl, const char *fmt, ...);
+extern void log_err(const char *fmt, ...);
+extern void log_warn(const char *fmt, ...);
+extern void log_warn_e(const char *fmt, ...);
+extern void vcont(log_class_t cl, const char *fmt, va_list val);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _UTIL_H */
diff --git a/usr/src/cmd/fm/schemes/hc/amd64/Makefile b/usr/src/cmd/fm/schemes/hc/amd64/Makefile
index b12450b1d8..204e86f0c0 100644
--- a/usr/src/cmd/fm/schemes/hc/amd64/Makefile
+++ b/usr/src/cmd/fm/schemes/hc/amd64/Makefile
@@ -30,4 +30,8 @@ include ../../Makefile.com
include $(SRC)/Makefile.master.64
include ../../Makefile.targ
+LDLIBS += -ltopo
+LDFLAGS += -L$(ROOT)/usr/lib/fm/amd64 -R/usr/lib/fm/amd64
+LINTFLAGS += -L$(ROOT)/usr/lib/fm/amd64
+
install: all $(ROOTPROG64)
diff --git a/usr/src/cmd/fm/schemes/hc/i386/Makefile b/usr/src/cmd/fm/schemes/hc/i386/Makefile
index b2953a27d9..76b53aefdc 100644
--- a/usr/src/cmd/fm/schemes/hc/i386/Makefile
+++ b/usr/src/cmd/fm/schemes/hc/i386/Makefile
@@ -29,4 +29,8 @@
include ../../Makefile.com
include ../../Makefile.targ
+LDLIBS += -ltopo
+LDFLAGS += -L$(ROOT)/usr/lib/fm -R/usr/lib/fm
+LINTFLAGS += -L$(ROOT)/usr/lib/fm
+
install: all $(ROOTPROG)
diff --git a/usr/src/cmd/fm/schemes/hc/scheme.c b/usr/src/cmd/fm/schemes/hc/scheme.c
index 0cfa7d81e9..a8defec38e 100644
--- a/usr/src/cmd/fm/schemes/hc/scheme.c
+++ b/usr/src/cmd/fm/schemes/hc/scheme.c
@@ -2,9 +2,8 @@
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License"). You may not use this file except in compliance
- * with the License.
+ * 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.
@@ -20,13 +19,91 @@
* CDDL HEADER END
*/
/*
- * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
+#include <fm/topo_mod.h>
#include <fm/fmd_fmri.h>
+#include <fm/libtopo.h>
+
+typedef struct hc_walk_arg {
+ void *p;
+ int *resultp;
+} hc_walk_arg_t;
+
+static topo_hdl_t *HC_thp = NULL;
+static char *HC_uuid = NULL;
+
+int
+fmd_fmri_init(void)
+{
+ int err;
+
+ if ((HC_thp = topo_open(TOPO_VERSION, NULL, &err)) == NULL)
+ return (-1);
+
+ return (0);
+}
+
+void
+fmd_fmri_fini(void)
+{
+ if (HC_uuid) {
+ topo_snap_release(HC_thp);
+ topo_hdl_strfree(HC_thp, HC_uuid);
+ HC_uuid = NULL;
+ }
+ topo_close(HC_thp);
+}
+
+static int
+hc_update_topology(void)
+{
+ static uint64_t lastgen = 0;
+ int err;
+ uint64_t curgen;
+
+ if (HC_uuid == NULL ||
+ (curgen = fmd_fmri_get_drgen()) > lastgen) {
+
+ lastgen = curgen;
+
+ if (HC_uuid) {
+ topo_snap_release(HC_thp);
+ topo_hdl_strfree(HC_thp, HC_uuid);
+ HC_uuid = NULL;
+ }
+
+ if ((HC_uuid = topo_snap_hold(HC_thp, NULL, &err)) == NULL) {
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+static int
+hc_topo_walk(topo_walk_cb_t fn, void *arg, int *resultp)
+{
+ int err, rv;
+ topo_walk_t *twp;
+ hc_walk_arg_t hcarg;
+
+ hcarg.p = arg;
+ hcarg.resultp = resultp;
+
+ if ((twp = topo_walk_init(HC_thp, FM_FMRI_SCHEME_HC, fn,
+ &hcarg, &err)) == NULL)
+ return (-1);
+
+ rv = (topo_walk_step(twp, TOPO_WALK_CHILD) == TOPO_WALK_ERR)
+ ? -1 : 0;
+
+ topo_walk_fini(twp);
+ return (rv);
+}
/*
* buf_append -- Append str to buf (if it's non-NULL). Place prepend
@@ -176,18 +253,74 @@ fmd_fmri_nvl2str(nvlist_t *nvl, char *buf, size_t buflen)
return (size);
}
+/*ARGSUSED*/
+static int
+hc_topo_present(topo_hdl_t *thp, tnode_t *node, void *arg)
+{
+ int cmp, err;
+ nvlist_t *out = NULL;
+ nvlist_t *asru;
+ hc_walk_arg_t *hcargp = (hc_walk_arg_t *)arg;
+
+ if (topo_node_asru(node, &asru, NULL, &err) != 0 ||
+ asru == NULL) {
+ return (TOPO_WALK_NEXT);
+ }
+
+ /*
+ * Check if the ASRU of this node matches the ASRU passed in
+ */
+ cmp = topo_fmri_compare(thp, asru, (nvlist_t *)hcargp->p, &err);
+
+ nvlist_free(asru);
+
+ if (cmp <= 0)
+ return (TOPO_WALK_NEXT);
+
+ /*
+ * Yes, so try to execute the topo-present method.
+ */
+ cmp = topo_method_invoke(node, TOPO_METH_PRESENT,
+ TOPO_METH_PRESENT_VERSION, (nvlist_t *)hcargp->p, &out, &err);
+
+ if (out)
+ nvlist_free(out);
+
+ if (cmp == 1) {
+ *(hcargp->resultp) = 1;
+ return (TOPO_WALK_TERMINATE);
+ } else if (cmp == 0) {
+ *(hcargp->resultp) = 0;
+ return (TOPO_WALK_TERMINATE);
+ }
+
+ return (TOPO_WALK_NEXT);
+}
+
+
/*
* fmd_fmri_present() is called by fmadm to determine if a faulty ASRU
* is still present in the system. In general we don't expect to get
* ASRUs in this scheme, so it's unlikely this routine will get called.
- * In case it does, though, we just return true by default, as we have no
- * real way to look up the component in the system configuration.
+ * In case it does, though, we just traverse our libtopo snapshot,
+ * looking for a matching ASRU (minus the serial number information),
+ * then invoke the "topo_present" method to determine presence.
*/
-/*ARGSUSED*/
int
fmd_fmri_present(nvlist_t *nvl)
{
- return (1);
+ int ispresent = 1;
+
+ /*
+ * If there's an error during the topology update, punt by
+ * indicating presence.
+ */
+ if (hc_update_topology() < 0)
+ return (1);
+
+ (void) hc_topo_walk(hc_topo_present, nvl, &ispresent);
+
+ return (ispresent);
}
/*
diff --git a/usr/src/cmd/fm/schemes/hc/sparc/Makefile b/usr/src/cmd/fm/schemes/hc/sparc/Makefile
index b2953a27d9..76b53aefdc 100644
--- a/usr/src/cmd/fm/schemes/hc/sparc/Makefile
+++ b/usr/src/cmd/fm/schemes/hc/sparc/Makefile
@@ -29,4 +29,8 @@
include ../../Makefile.com
include ../../Makefile.targ
+LDLIBS += -ltopo
+LDFLAGS += -L$(ROOT)/usr/lib/fm -R/usr/lib/fm
+LINTFLAGS += -L$(ROOT)/usr/lib/fm
+
install: all $(ROOTPROG)
diff --git a/usr/src/cmd/fm/schemes/hc/sparcv9/Makefile b/usr/src/cmd/fm/schemes/hc/sparcv9/Makefile
index b12450b1d8..40f689eb05 100644
--- a/usr/src/cmd/fm/schemes/hc/sparcv9/Makefile
+++ b/usr/src/cmd/fm/schemes/hc/sparcv9/Makefile
@@ -30,4 +30,8 @@ include ../../Makefile.com
include $(SRC)/Makefile.master.64
include ../../Makefile.targ
+LDLIBS += -ltopo
+LDFLAGS += -L$(ROOT)/usr/lib/fm/sparcv9 -R/usr/lib/fm/sparcv9
+LINTFLAGS += -L$(ROOT)/usr/lib/fm/sparcv9
+
install: all $(ROOTPROG64)