diff options
author | sethg <none@none> | 2006-05-19 20:53:18 -0700 |
---|---|---|
committer | sethg <none@none> | 2006-05-19 20:53:18 -0700 |
commit | 724365f7556fc4201fdb11766ebc6bd918523130 (patch) | |
tree | d8151f209d5f1c1b7a9624c63ccb85dcfa13d1d9 /usr/src/cmd | |
parent | a66004590c61514e788e33c934c7fe7c0ec4c1d4 (diff) | |
download | illumos-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')
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) |