Home | History | Annotate | Download | only in disk-transport
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*
     22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     27 
     28 /*
     29  * Disk error transport module
     30  *
     31  * This transport module is responsible for translating between disk errors
     32  * and FMA ereports.  It is a read-only transport module, and checks for the
     33  * following failures:
     34  *
     35  * 	- overtemp
     36  * 	- predictive failure
     37  * 	- self-test failure
     38  *
     39  * These failures are detected via the TOPO_METH_DISK_STATUS method, which
     40  * leverages libdiskstatus to do the actual analysis.  This transport module is
     41  * in charge of the following tasks:
     42  *
     43  * 	- discovering available devices
     44  * 	- periodically checking devices
     45  * 	- managing device addition/removal
     46  */
     47 
     48 #include <ctype.h>
     49 #include <fm/fmd_api.h>
     50 #include <fm/libdiskstatus.h>
     51 #include <fm/libtopo.h>
     52 #include <fm/topo_hc.h>
     53 #include <fm/topo_mod.h>
     54 #include <limits.h>
     55 #include <string.h>
     56 #include <sys/fm/io/scsi.h>
     57 #include <sys/fm/protocol.h>
     58 
     59 static struct dt_stat {
     60 	fmd_stat_t dropped;
     61 } dt_stats = {
     62 	{ "dropped", FMD_TYPE_UINT64, "number of dropped ereports" }
     63 };
     64 
     65 typedef struct disk_monitor {
     66 	fmd_hdl_t	*dm_hdl;
     67 	fmd_xprt_t	*dm_xprt;
     68 	id_t		dm_timer;
     69 	hrtime_t	dm_interval;
     70 	char		*dm_sim_search;
     71 	char		*dm_sim_file;
     72 	boolean_t	dm_timer_istopo;
     73 } disk_monitor_t;
     74 
     75 static void
     76 dt_post_ereport(fmd_hdl_t *hdl, fmd_xprt_t *xprt, const char *protocol,
     77     const char *faultname, uint64_t ena, nvlist_t *detector, nvlist_t *payload)
     78 {
     79 	nvlist_t *nvl;
     80 	int e = 0;
     81 	char fullclass[PATH_MAX];
     82 
     83 	(void) snprintf(fullclass, sizeof (fullclass), "%s.io.%s.disk.%s",
     84 	    FM_EREPORT_CLASS, protocol, faultname);
     85 
     86 	if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) == 0) {
     87 		e |= nvlist_add_string(nvl, FM_CLASS, fullclass);
     88 		e |= nvlist_add_uint8(nvl, FM_VERSION, FM_EREPORT_VERSION);
     89 		e |= nvlist_add_uint64(nvl, FM_EREPORT_ENA, ena);
     90 		e |= nvlist_add_nvlist(nvl, FM_EREPORT_DETECTOR, detector);
     91 		e |= nvlist_merge(nvl, payload, 0);
     92 
     93 		if (e == 0) {
     94 			fmd_xprt_post(hdl, xprt, nvl, 0);
     95 		} else {
     96 			nvlist_free(nvl);
     97 			dt_stats.dropped.fmds_value.ui64++;
     98 		}
     99 	} else {
    100 		dt_stats.dropped.fmds_value.ui64++;
    101 	}
    102 }
    103 
    104 /*
    105  * Check a single topo node for failure.  This simply invokes the disk status
    106  * method, and generates any ereports as necessary.
    107  */
    108 static int
    109 dt_analyze_disk(topo_hdl_t *thp, tnode_t *node, void *arg)
    110 {
    111 	nvlist_t *result;
    112 	nvlist_t *fmri, *faults;
    113 	char *protocol;
    114 	int err;
    115 	disk_monitor_t *dmp = arg;
    116 	uint64_t ena;
    117 	nvpair_t *elem;
    118 	boolean_t fault;
    119 	nvlist_t *details;
    120 	char *fmristr;
    121 	nvlist_t *in = NULL;
    122 
    123 	if (topo_node_resource(node, &fmri, &err) != 0) {
    124 		fmd_hdl_error(dmp->dm_hdl, "failed to get fmri: %s\n",
    125 		    topo_strerror(err));
    126 		return (TOPO_WALK_ERR);
    127 	}
    128 
    129 	if (topo_hdl_nvalloc(thp, &in, NV_UNIQUE_NAME) != 0) {
    130 		nvlist_free(fmri);
    131 		return (TOPO_WALK_ERR);
    132 	}
    133 
    134 	if (dmp->dm_sim_search) {
    135 		fmristr = NULL;
    136 		if (topo_fmri_nvl2str(thp, fmri, &fmristr, &err) == 0 &&
    137 		    strstr(fmristr, dmp->dm_sim_search) != 0)
    138 			(void) nvlist_add_string(in, "path", dmp->dm_sim_file);
    139 		topo_hdl_strfree(thp, fmristr);
    140 	}
    141 
    142 	/*
    143 	 * Try to invoke the method.  If this fails (most likely because the
    144 	 * method is not supported), then ignore this node.
    145 	 */
    146 	if (topo_method_invoke(node, TOPO_METH_DISK_STATUS,
    147 	    TOPO_METH_DISK_STATUS_VERSION, in, &result, &err) != 0) {
    148 		nvlist_free(fmri);
    149 		nvlist_free(in);
    150 		return (TOPO_WALK_NEXT);
    151 	}
    152 
    153 	nvlist_free(in);
    154 
    155 	ena = fmd_event_ena_create(dmp->dm_hdl);
    156 
    157 	/*
    158 	 * Add any faults.
    159 	 */
    160 	if (nvlist_lookup_nvlist(result, "faults", &faults) == 0 &&
    161 	    nvlist_lookup_string(result, "protocol", &protocol) == 0) {
    162 		elem = NULL;
    163 		while ((elem = nvlist_next_nvpair(faults, elem)) != NULL) {
    164 			if (nvpair_type(elem) != DATA_TYPE_BOOLEAN_VALUE)
    165 				continue;
    166 
    167 			(void) nvpair_value_boolean_value(elem, &fault);
    168 			if (!fault ||
    169 			    nvlist_lookup_nvlist(result, nvpair_name(elem),
    170 			    &details) != 0)
    171 				continue;
    172 
    173 			dt_post_ereport(dmp->dm_hdl, dmp->dm_xprt, protocol,
    174 			    nvpair_name(elem), ena, fmri, details);
    175 		}
    176 	}
    177 
    178 	nvlist_free(result);
    179 	nvlist_free(fmri);
    180 
    181 	return (TOPO_WALK_NEXT);
    182 }
    183 
    184 /*
    185  * Periodic timeout.  Iterates over all hc:// topo nodes, calling
    186  * dt_analyze_disk() for each one.
    187  */
    188 /*ARGSUSED*/
    189 static void
    190 dt_timeout(fmd_hdl_t *hdl, id_t id, void *data)
    191 {
    192 	topo_hdl_t *thp;
    193 	topo_walk_t *twp;
    194 	int err;
    195 	disk_monitor_t *dmp = fmd_hdl_getspecific(hdl);
    196 
    197 	dmp->dm_hdl = hdl;
    198 
    199 	thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION);
    200 	if ((twp = topo_walk_init(thp, FM_FMRI_SCHEME_HC, dt_analyze_disk,
    201 	    dmp, &err)) == NULL) {
    202 		fmd_hdl_topo_rele(hdl, thp);
    203 		fmd_hdl_error(hdl, "failed to get topology: %s\n",
    204 		    topo_strerror(err));
    205 		return;
    206 	}
    207 
    208 	if (topo_walk_step(twp, TOPO_WALK_CHILD) == TOPO_WALK_ERR) {
    209 		topo_walk_fini(twp);
    210 		fmd_hdl_topo_rele(hdl, thp);
    211 		fmd_hdl_error(hdl, "failed to walk topology\n");
    212 		return;
    213 	}
    214 
    215 	topo_walk_fini(twp);
    216 	fmd_hdl_topo_rele(hdl, thp);
    217 
    218 	dmp->dm_timer = fmd_timer_install(hdl, NULL, NULL, dmp->dm_interval);
    219 	dmp->dm_timer_istopo = B_FALSE;
    220 }
    221 
    222 /*
    223  * Called when the topology may have changed.  We want to examine all disks in
    224  * case a new one has been inserted, but we don't want to overwhelm the system
    225  * in the event of a flurry of topology changes, as most likely only a small
    226  * number of disks are changing.  To avoid this, we set the timer for a small
    227  * but non-trivial interval (by default 1 minute), and ignore intervening
    228  * changes during this period.  This still gives us a reasonable response time
    229  * to newly inserted devices without overwhelming the system if lots of hotplug
    230  * activity is going on.
    231  */
    232 /*ARGSUSED*/
    233 static void
    234 dt_topo_change(fmd_hdl_t *hdl, topo_hdl_t *thp)
    235 {
    236 	disk_monitor_t *dmp = fmd_hdl_getspecific(hdl);
    237 
    238 	if (dmp->dm_timer_istopo)
    239 		return;
    240 
    241 	fmd_timer_remove(hdl, dmp->dm_timer);
    242 	dmp->dm_timer = fmd_timer_install(hdl, NULL, NULL,
    243 	    fmd_prop_get_int64(hdl, "min-interval"));
    244 	dmp->dm_timer_istopo = B_TRUE;
    245 }
    246 
    247 static const fmd_prop_t fmd_props[] = {
    248 	{ "interval", FMD_TYPE_TIME, "1h" },
    249 	{ "min-interval", FMD_TYPE_TIME, "1min" },
    250 	{ "simulate", FMD_TYPE_STRING, "" },
    251 	{ NULL, 0, NULL }
    252 };
    253 
    254 static const fmd_hdl_ops_t fmd_ops = {
    255 	NULL,			/* fmdo_recv */
    256 	dt_timeout,		/* fmdo_timeout */
    257 	NULL, 			/* fmdo_close */
    258 	NULL,			/* fmdo_stats */
    259 	NULL,			/* fmdo_gc */
    260 	NULL,			/* fmdo_send */
    261 	dt_topo_change,		/* fmdo_topo_change */
    262 };
    263 
    264 static const fmd_hdl_info_t fmd_info = {
    265 	"Disk Transport Agent", "1.0", &fmd_ops, fmd_props
    266 };
    267 
    268 void
    269 _fmd_init(fmd_hdl_t *hdl)
    270 {
    271 	disk_monitor_t *dmp;
    272 	char *simulate;
    273 
    274 	if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0)
    275 		return;
    276 
    277 	(void) fmd_stat_create(hdl, FMD_STAT_NOALLOC,
    278 	    sizeof (dt_stats) / sizeof (fmd_stat_t),
    279 	    (fmd_stat_t *)&dt_stats);
    280 
    281 	dmp = fmd_hdl_zalloc(hdl, sizeof (disk_monitor_t), FMD_SLEEP);
    282 	fmd_hdl_setspecific(hdl, dmp);
    283 
    284 	dmp->dm_xprt = fmd_xprt_open(hdl, FMD_XPRT_RDONLY, NULL, NULL);
    285 	dmp->dm_interval = fmd_prop_get_int64(hdl, "interval");
    286 
    287 	/*
    288 	 * Determine if we have the simulate property set.  This property allows
    289 	 * the developer to substitute a faulty device based off all or part of
    290 	 * an FMRI string.  For example, one could do:
    291 	 *
    292 	 * 	setprop simulate "bay=4/disk=4	/path/to/sim.so"
    293 	 *
    294 	 * When the transport module encounters an FMRI containing the given
    295 	 * string, then it will open the simulator file instead of the
    296 	 * corresponding device.  This can be any file, but is intended to be a
    297 	 * libdiskstatus simulator shared object, capable of faking up SCSI
    298 	 * responses.
    299 	 *
    300 	 * The property consists of two strings, an FMRI fragment and an
    301 	 * absolute path, separated by whitespace.
    302 	 */
    303 	simulate = fmd_prop_get_string(hdl, "simulate");
    304 	if (simulate[0] != '\0') {
    305 		const char *sep;
    306 		size_t len;
    307 
    308 		for (sep = simulate; *sep != '\0'; sep++) {
    309 			if (isspace(*sep))
    310 				break;
    311 		}
    312 
    313 		if (*sep != '\0') {
    314 			len = sep - simulate;
    315 
    316 			dmp->dm_sim_search = fmd_hdl_alloc(hdl,
    317 			    len + 1, FMD_SLEEP);
    318 			(void) memcpy(dmp->dm_sim_search, simulate, len);
    319 			dmp->dm_sim_search[len] = '\0';
    320 		}
    321 
    322 		for (; *sep != '\0'; sep++) {
    323 			if (!isspace(*sep))
    324 				break;
    325 		}
    326 
    327 		if (*sep != '\0') {
    328 			dmp->dm_sim_file = fmd_hdl_strdup(hdl, sep, FMD_SLEEP);
    329 		} else if (dmp->dm_sim_search) {
    330 			fmd_hdl_strfree(hdl, dmp->dm_sim_search);
    331 			dmp->dm_sim_search = NULL;
    332 		}
    333 	}
    334 	fmd_prop_free_string(hdl, simulate);
    335 
    336 	/*
    337 	 * Call our initial timer routine.  This will do an initial check of all
    338 	 * the disks, and then start the periodic timeout.
    339 	 */
    340 	dmp->dm_timer = fmd_timer_install(hdl, NULL, NULL, 0);
    341 }
    342 
    343 void
    344 _fmd_fini(fmd_hdl_t *hdl)
    345 {
    346 	disk_monitor_t *dmp;
    347 
    348 	dmp = fmd_hdl_getspecific(hdl);
    349 	if (dmp) {
    350 		fmd_xprt_close(hdl, dmp->dm_xprt);
    351 		fmd_hdl_strfree(hdl, dmp->dm_sim_search);
    352 		fmd_hdl_strfree(hdl, dmp->dm_sim_file);
    353 		fmd_hdl_free(hdl, dmp, sizeof (*dmp));
    354 	}
    355 }
    356