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 * Copyright 2016 Nexenta Systems, Inc. All rights reserved.
25 */
26
27 /*
28 * Disk error transport module
29 *
30 * This transport module is responsible for translating between disk errors
31 * and FMA ereports. It is a read-only transport module, and checks for the
32 * following failures:
33 *
34 * - overtemp
35 * - predictive failure
36 * - self-test failure
37 * - solid state media wearout
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 nvpair_t *elem;
117 boolean_t fault;
118 nvlist_t *details;
119 char *fmristr;
120 nvlist_t *in = NULL;
121
122 if (topo_node_resource(node, &fmri, &err) != 0) {
123 fmd_hdl_error(dmp->dm_hdl, "failed to get fmri: %s\n",
124 topo_strerror(err));
125 return (TOPO_WALK_ERR);
126 }
127
128 if (topo_hdl_nvalloc(thp, &in, NV_UNIQUE_NAME) != 0) {
129 nvlist_free(fmri);
130 return (TOPO_WALK_ERR);
131 }
132
133 if (dmp->dm_sim_search) {
134 fmristr = NULL;
135 if (topo_fmri_nvl2str(thp, fmri, &fmristr, &err) == 0 &&
136 strstr(fmristr, dmp->dm_sim_search) != 0)
137 (void) nvlist_add_string(in, "path", dmp->dm_sim_file);
138 topo_hdl_strfree(thp, fmristr);
139 }
140
141 /*
142 * Try to invoke the method. If this fails (most likely because the
143 * method is not supported), then ignore this node.
144 */
145 if (topo_method_invoke(node, TOPO_METH_DISK_STATUS,
146 TOPO_METH_DISK_STATUS_VERSION, in, &result, &err) != 0) {
147 nvlist_free(fmri);
148 nvlist_free(in);
149 return (TOPO_WALK_NEXT);
150 }
151
152 nvlist_free(in);
153
154 /*
155 * Check for faults and post ereport(s) if needed
156 */
157 if (nvlist_lookup_nvlist(result, "faults", &faults) == 0 &&
158 nvlist_lookup_string(result, "protocol", &protocol) == 0) {
159 elem = NULL;
160 while ((elem = nvlist_next_nvpair(faults, elem)) != NULL) {
161 if (nvpair_type(elem) != DATA_TYPE_BOOLEAN_VALUE)
162 continue;
163
164 (void) nvpair_value_boolean_value(elem, &fault);
165 if (!fault ||
166 nvlist_lookup_nvlist(result, nvpair_name(elem),
167 &details) != 0)
168 continue;
169
170 if (strcmp(nvpair_name(elem),
171 FM_EREPORT_SCSI_OVERTEMP) == 0 &&
172 fmd_prop_get_int32(dmp->dm_hdl,
173 "ignore-overtemp") == FMD_B_TRUE)
174 continue;
175
176 if (strcmp(nvpair_name(elem),
177 FM_EREPORT_SCSI_SSMWEAROUT) == 0 &&
178 fmd_prop_get_int32(dmp->dm_hdl,
179 "ignore-ssm-wearout") == FMD_B_TRUE)
180 continue;
181
182 dt_post_ereport(dmp->dm_hdl, dmp->dm_xprt, protocol,
183 nvpair_name(elem),
184 fmd_event_ena_create(dmp->dm_hdl), fmri, details);
185 }
186 }
187
188 nvlist_free(result);
189 nvlist_free(fmri);
190
191 return (TOPO_WALK_NEXT);
192 }
193
194 /*
195 * Periodic timeout. Iterates over all hc:// topo nodes, calling
196 * dt_analyze_disk() for each one.
197 */
198 /*ARGSUSED*/
199 static void
200 dt_timeout(fmd_hdl_t *hdl, id_t id, void *data)
201 {
202 topo_hdl_t *thp;
203 topo_walk_t *twp;
204 int err;
205 disk_monitor_t *dmp = fmd_hdl_getspecific(hdl);
206
207 dmp->dm_hdl = hdl;
208
209 thp = fmd_hdl_topo_hold(hdl, TOPO_VERSION);
210 if ((twp = topo_walk_init(thp, FM_FMRI_SCHEME_HC, dt_analyze_disk,
211 dmp, &err)) == NULL) {
212 fmd_hdl_topo_rele(hdl, thp);
213 fmd_hdl_error(hdl, "failed to get topology: %s\n",
214 topo_strerror(err));
215 return;
216 }
217
218 if (topo_walk_step(twp, TOPO_WALK_CHILD) == TOPO_WALK_ERR) {
219 topo_walk_fini(twp);
220 fmd_hdl_topo_rele(hdl, thp);
221 fmd_hdl_error(hdl, "failed to walk topology\n");
222 return;
223 }
224
225 topo_walk_fini(twp);
226 fmd_hdl_topo_rele(hdl, thp);
227
228 dmp->dm_timer = fmd_timer_install(hdl, NULL, NULL, dmp->dm_interval);
229 dmp->dm_timer_istopo = B_FALSE;
230 }
231
232 /*
233 * Called when the topology may have changed. We want to examine all disks in
234 * case a new one has been inserted, but we don't want to overwhelm the system
235 * in the event of a flurry of topology changes, as most likely only a small
236 * number of disks are changing. To avoid this, we set the timer for a small
237 * but non-trivial interval (by default 1 minute), and ignore intervening
238 * changes during this period. This still gives us a reasonable response time
239 * to newly inserted devices without overwhelming the system if lots of hotplug
240 * activity is going on.
241 */
242 /*ARGSUSED*/
243 static void
244 dt_topo_change(fmd_hdl_t *hdl, topo_hdl_t *thp)
245 {
246 disk_monitor_t *dmp = fmd_hdl_getspecific(hdl);
247
248 if (dmp->dm_timer_istopo)
249 return;
250
251 fmd_timer_remove(hdl, dmp->dm_timer);
252 dmp->dm_timer = fmd_timer_install(hdl, NULL, NULL,
253 fmd_prop_get_int64(hdl, "min-interval"));
254 dmp->dm_timer_istopo = B_TRUE;
255 }
256
257 static const fmd_prop_t fmd_props[] = {
258 { "interval", FMD_TYPE_TIME, "1h" },
259 { "min-interval", FMD_TYPE_TIME, "1min" },
260 { "simulate", FMD_TYPE_STRING, "" },
261 { "ignore-overtemp", FMD_TYPE_BOOL, "true"},
262 { "ignore-ssm-wearout", FMD_TYPE_BOOL, "false"},
263 { NULL, 0, NULL }
264 };
265
266 static const fmd_hdl_ops_t fmd_ops = {
267 NULL, /* fmdo_recv */
268 dt_timeout, /* fmdo_timeout */
269 NULL, /* fmdo_close */
270 NULL, /* fmdo_stats */
271 NULL, /* fmdo_gc */
272 NULL, /* fmdo_send */
273 dt_topo_change, /* fmdo_topo_change */
274 };
275
276 static const fmd_hdl_info_t fmd_info = {
277 "Disk Transport Agent", "1.1", &fmd_ops, fmd_props
278 };
279
280 void
281 _fmd_init(fmd_hdl_t *hdl)
282 {
283 disk_monitor_t *dmp;
284 char *simulate;
285
286 if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0)
287 return;
288
289 (void) fmd_stat_create(hdl, FMD_STAT_NOALLOC,
290 sizeof (dt_stats) / sizeof (fmd_stat_t),
291 (fmd_stat_t *)&dt_stats);
292
293 dmp = fmd_hdl_zalloc(hdl, sizeof (disk_monitor_t), FMD_SLEEP);
294 fmd_hdl_setspecific(hdl, dmp);
295
296 dmp->dm_xprt = fmd_xprt_open(hdl, FMD_XPRT_RDONLY, NULL, NULL);
297 dmp->dm_interval = fmd_prop_get_int64(hdl, "interval");
298
299 /*
300 * Determine if we have the simulate property set. This property allows
301 * the developer to substitute a faulty device based off all or part of
302 * an FMRI string. For example, one could do:
303 *
304 * setprop simulate "bay=4/disk=4 /path/to/sim.so"
305 *
306 * When the transport module encounters an FMRI containing the given
307 * string, then it will open the simulator file instead of the
308 * corresponding device. This can be any file, but is intended to be a
309 * libdiskstatus simulator shared object, capable of faking up SCSI
310 * responses.
311 *
312 * The property consists of two strings, an FMRI fragment and an
313 * absolute path, separated by whitespace.
314 */
315 simulate = fmd_prop_get_string(hdl, "simulate");
316 if (simulate[0] != '\0') {
317 const char *sep;
318 size_t len;
319
320 for (sep = simulate; *sep != '\0'; sep++) {
321 if (isspace(*sep))
322 break;
323 }
324
325 if (*sep != '\0') {
326 len = sep - simulate;
327
328 dmp->dm_sim_search = fmd_hdl_alloc(hdl,
329 len + 1, FMD_SLEEP);
330 (void) memcpy(dmp->dm_sim_search, simulate, len);
331 dmp->dm_sim_search[len] = '\0';
332 }
333
334 for (; *sep != '\0'; sep++) {
335 if (!isspace(*sep))
336 break;
337 }
338
339 if (*sep != '\0') {
340 dmp->dm_sim_file = fmd_hdl_strdup(hdl, sep, FMD_SLEEP);
341 } else if (dmp->dm_sim_search) {
342 fmd_hdl_strfree(hdl, dmp->dm_sim_search);
343 dmp->dm_sim_search = NULL;
344 }
345 }
346 fmd_prop_free_string(hdl, simulate);
347
348 /*
349 * Call our initial timer routine. This will do an initial check of all
350 * the disks, and then start the periodic timeout.
351 */
352 dmp->dm_timer = fmd_timer_install(hdl, NULL, NULL, 0);
353 }
354
355 void
356 _fmd_fini(fmd_hdl_t *hdl)
357 {
358 disk_monitor_t *dmp;
359
360 dmp = fmd_hdl_getspecific(hdl);
361 if (dmp) {
362 fmd_xprt_close(hdl, dmp->dm_xprt);
363 fmd_hdl_strfree(hdl, dmp->dm_sim_search);
364 fmd_hdl_strfree(hdl, dmp->dm_sim_file);
365 fmd_hdl_free(hdl, dmp, sizeof (*dmp));
366 }
367 }