1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright (c) 2013, Joyent, Inc. All rights reserved.
14  */
15 
16 /*
17  * Disk Lights Agent (FMA)
18  *
19  * This Fault Management Daemon (fmd) module periodically scans the topology
20  * tree, enumerates all disks with associated fault indicators, and then
21  * synchronises the fault status of resources in the FMA Resource Cache with
22  * the indicators.  In short: it turns the fault light on for befallen disks.
23  *
24  * Presently, we recognise associated fault indicators for disks by looking
25  * for the following structure in the topology tree:
26  *
27  *    /bay=N
28  *      |
29  *      +---- /disk=0   <---------------- our Disk
30  *      |
31  *      +---- /bay=N?indicator=fail <---- the Fault Light
32  *      \---- /bay=N?indicator=ident
33  *
34  * That is: a DISK node will have a parent BAY; that BAY will itself have
35  * child Facility nodes, one of which will be called "fail".  If any of the
36  * above does not hold, we simply do nothing for this disk.
37  */
38 
39 #include <string.h>
40 #include <strings.h>
41 #include <libnvpair.h>
42 #include <fm/libtopo.h>
43 #include <fm/topo_list.h>
44 #include <fm/topo_hc.h>
45 #include <fm/fmd_api.h>
46 #include <sys/fm/protocol.h>
47 
48 
49 typedef struct disk_lights {
50 	fmd_hdl_t *dl_fmd;
51 	uint64_t dl_poll_interval;
52 	uint64_t dl_coalesce_interval;
53 	id_t dl_timer;
54 	boolean_t dl_triggered;
55 } disk_lights_t;
56 
57 static void disklights_topo(fmd_hdl_t *, topo_hdl_t *);
58 static void disklights_recv(fmd_hdl_t *, fmd_event_t *, nvlist_t *,
59     const char *);
60 static void disklights_timeout(fmd_hdl_t *, id_t, void *);
61 
62 static const fmd_hdl_ops_t fmd_ops = {
63 	disklights_recv,	/* fmdo_recv */
64 	disklights_timeout,	/* fmdo_timeout */
65 	NULL,			/* fmdo_close */
66 	NULL,			/* fmdo_stats */
67 	NULL,			/* fmdo_gc */
68 	NULL,			/* fmdo_send */
69 	disklights_topo,	/* fmdo_topo */
70 };
71 
72 /*
73  * POLL_INTERVAL is the period after which we perform an unsolicited poll
74  * to ensure we remain in sync with reality.
75  */
76 #define	DL_PROP_POLL_INTERVAL		"poll-interval"
77 
78 /*
79  * COALESCE_INTERVAL is how long we wait after we are trigged by either a
80  * topology change or a relevant list.* event, in order to allow a series
81  * of events to coalesce.
82  */
83 #define	DL_PROP_COALESCE_INTERVAL	"coalesce-interval"
84 
85 static const fmd_prop_t fmd_props[] = {
86 	{ DL_PROP_POLL_INTERVAL, FMD_TYPE_TIME, "5min" },
87 	{ DL_PROP_COALESCE_INTERVAL, FMD_TYPE_TIME, "3s" },
88 	{ NULL, 0, NULL }
89 };
90 
91 static const fmd_hdl_info_t fmd_info = {
92 	"Disk Lights Agent",
93 	"1.0",
94 	&fmd_ops,
95 	fmd_props
96 };
97 
98 /*
99  * Fetch the Facility Node properties (name, type) from the FMRI
100  * for this node, or return -1 if we can't.
101  */
102 static int
get_facility_props(topo_hdl_t * hdl,tnode_t * node,char ** facname,char ** factype)103 get_facility_props(topo_hdl_t *hdl, tnode_t *node, char **facname,
104     char **factype)
105 {
106 	int e, ret = -1;
107 	nvlist_t *fmri = NULL, *fnvl;
108 	char *nn = NULL, *tt = NULL;
109 
110 	if (topo_node_resource(node, &fmri, &e) != 0)
111 		goto out;
112 
113 	if (nvlist_lookup_nvlist(fmri, FM_FMRI_FACILITY, &fnvl) != 0)
114 		goto out;
115 
116 	if (nvlist_lookup_string(fnvl, FM_FMRI_FACILITY_NAME, &nn) != 0)
117 		goto out;
118 
119 	if (nvlist_lookup_string(fnvl, FM_FMRI_FACILITY_TYPE, &tt) != 0)
120 		goto out;
121 
122 	*facname = topo_hdl_strdup(hdl, nn);
123 	*factype = topo_hdl_strdup(hdl, tt);
124 	ret = 0;
125 
126 out:
127 	nvlist_free(fmri);
128 	return (ret);
129 }
130 
131 typedef struct dl_fault_walk_inner {
132 	char *fwi_name;
133 	uint32_t fwi_mode;
134 } dl_fault_walk_inner_t;
135 
136 static int
dl_fault_walk_inner(topo_hdl_t * thp,tnode_t * node,void * arg)137 dl_fault_walk_inner(topo_hdl_t *thp, tnode_t *node, void *arg)
138 {
139 	dl_fault_walk_inner_t *fwi = arg;
140 	char *facname = NULL, *factype = NULL;
141 	int err;
142 
143 	/*
144 	 * We're only interested in BAY children that are valid Facility Nodes.
145 	 */
146 	if (topo_node_flags(node) != TOPO_NODE_FACILITY ||
147 	    get_facility_props(thp, node, &facname, &factype) != 0) {
148 		goto out;
149 	}
150 
151 	if (strcmp(fwi->fwi_name, facname) != 0)
152 		goto out;
153 
154 	/*
155 	 * Attempt to set the LED mode appropriately.  If this fails, give up
156 	 * and move on.
157 	 */
158 	(void) topo_prop_set_uint32(node, TOPO_PGROUP_FACILITY, TOPO_LED_MODE,
159 	    TOPO_PROP_MUTABLE, fwi->fwi_mode, &err);
160 
161 out:
162 	topo_hdl_strfree(thp, facname);
163 	topo_hdl_strfree(thp, factype);
164 	return (TOPO_WALK_NEXT);
165 }
166 
167 static int
dl_fault_walk_outer(topo_hdl_t * thp,tnode_t * node,void * arg)168 dl_fault_walk_outer(topo_hdl_t *thp, tnode_t *node, void *arg)
169 {
170 	disk_lights_t *dl = arg;
171 	dl_fault_walk_inner_t fwi;
172 	tnode_t *pnode;
173 	int err, has_fault;
174 	nvlist_t *fmri = NULL;
175 
176 	bzero(&fwi, sizeof (fwi));
177 
178 	/*
179 	 * We are only looking for DISK nodes in the topology that have a parent
180 	 * BAY.
181 	 */
182 	if (strcmp(DISK, topo_node_name(node)) != 0 ||
183 	    (pnode = topo_node_parent(node)) == NULL ||
184 	    strcmp(BAY, topo_node_name(pnode)) != 0) {
185 		return (TOPO_WALK_NEXT);
186 	}
187 
188 	/*
189 	 * Check to see if the Resource this FMRI describes is Faulty:
190 	 */
191 	if (topo_node_resource(node, &fmri, &err) != 0)
192 		return (TOPO_WALK_NEXT);
193 	has_fault = fmd_nvl_fmri_has_fault(dl->dl_fmd, fmri,
194 	    FMD_HAS_FAULT_RESOURCE, NULL);
195 	nvlist_free(fmri);
196 
197 	/*
198 	 * Walk the children of this BAY and flush out our fault status if
199 	 * we find an appropriate indicator node.
200 	 */
201 	fwi.fwi_name = "fail";
202 	fwi.fwi_mode = has_fault ? TOPO_LED_STATE_ON : TOPO_LED_STATE_OFF;
203 	(void) topo_node_child_walk(thp, pnode, dl_fault_walk_inner, &fwi,
204 	    &err);
205 
206 	return (TOPO_WALK_NEXT);
207 }
208 
209 /*
210  * Walk all of the topology nodes looking for DISKs that match the structure
211  * described in the overview.  Once we find them, check their fault status
212  * and update their fault indiciator accordingly.
213  */
214 static void
dl_examine_topo(disk_lights_t * dl)215 dl_examine_topo(disk_lights_t *dl)
216 {
217 	int err;
218 	topo_hdl_t *thp = NULL;
219 	topo_walk_t *twp = NULL;
220 
221 	thp = fmd_hdl_topo_hold(dl->dl_fmd, TOPO_VERSION);
222 	if ((twp = topo_walk_init(thp, FM_FMRI_SCHEME_HC, dl_fault_walk_outer,
223 	    dl, &err)) == NULL) {
224 		fmd_hdl_error(dl->dl_fmd, "failed to get topology: %s\n",
225 		    topo_strerror(err));
226 		goto out;
227 	}
228 
229 	if (topo_walk_step(twp, TOPO_WALK_CHILD) == TOPO_WALK_ERR) {
230 		fmd_hdl_error(dl->dl_fmd, "failed to walk topology: %s\n",
231 		    topo_strerror(err));
232 		goto out;
233 	}
234 
235 out:
236 	if (twp != NULL)
237 		topo_walk_fini(twp);
238 	if (thp != NULL)
239 		fmd_hdl_topo_rele(dl->dl_fmd, thp);
240 }
241 
242 static void
dl_trigger_enum(disk_lights_t * dl)243 dl_trigger_enum(disk_lights_t *dl)
244 {
245 	/*
246 	 * If we're already on the short-poll coalesce timer, then return
247 	 * immediately.
248 	 */
249 	if (dl->dl_triggered == B_TRUE)
250 		return;
251 	dl->dl_triggered = B_TRUE;
252 
253 	/*
254 	 * Replace existing poll timer with coalesce timer:
255 	 */
256 	if (dl->dl_timer != 0)
257 		fmd_timer_remove(dl->dl_fmd, dl->dl_timer);
258 	dl->dl_timer = fmd_timer_install(dl->dl_fmd, NULL, NULL,
259 	    dl->dl_coalesce_interval);
260 }
261 
262 /*ARGSUSED*/
263 static void
disklights_timeout(fmd_hdl_t * hdl,id_t id,void * data)264 disklights_timeout(fmd_hdl_t *hdl, id_t id, void *data)
265 {
266 	disk_lights_t *dl = fmd_hdl_getspecific(hdl);
267 
268 	dl->dl_triggered = B_FALSE;
269 
270 	dl_examine_topo(dl);
271 
272 	/*
273 	 * Install the long-interval timer for the next poll.
274 	 */
275 	dl->dl_timer = fmd_timer_install(hdl, NULL, NULL, dl->dl_poll_interval);
276 }
277 
278 /*ARGSUSED*/
279 static void
disklights_topo(fmd_hdl_t * hdl,topo_hdl_t * thp)280 disklights_topo(fmd_hdl_t *hdl, topo_hdl_t *thp)
281 {
282 	disk_lights_t *dl = fmd_hdl_getspecific(hdl);
283 
284 	dl_trigger_enum(dl);
285 }
286 
287 /*ARGSUSED*/
288 static void
disklights_recv(fmd_hdl_t * hdl,fmd_event_t * ep,nvlist_t * nvl,const char * class)289 disklights_recv(fmd_hdl_t *hdl, fmd_event_t *ep, nvlist_t *nvl,
290     const char *class)
291 {
292 	disk_lights_t *dl = fmd_hdl_getspecific(hdl);
293 
294 	dl_trigger_enum(dl);
295 }
296 
297 void
_fmd_init(fmd_hdl_t * hdl)298 _fmd_init(fmd_hdl_t *hdl)
299 {
300 	disk_lights_t *dl;
301 
302 	if (fmd_hdl_register(hdl, FMD_API_VERSION, &fmd_info) != 0)
303 		return;
304 
305 	dl = fmd_hdl_zalloc(hdl, sizeof (*dl), FMD_SLEEP);
306 	fmd_hdl_setspecific(hdl, dl);
307 
308 	/*
309 	 * Load Configuration:
310 	 */
311 	dl->dl_fmd = hdl;
312 	dl->dl_poll_interval = fmd_prop_get_int64(hdl, DL_PROP_POLL_INTERVAL);
313 	dl->dl_coalesce_interval = fmd_prop_get_int64(hdl,
314 	    DL_PROP_COALESCE_INTERVAL);
315 
316 	/*
317 	 * Schedule the initial enumeration:
318 	 */
319 	dl_trigger_enum(dl);
320 }
321 
322 void
_fmd_fini(fmd_hdl_t * hdl)323 _fmd_fini(fmd_hdl_t *hdl)
324 {
325 	disk_lights_t *dl = fmd_hdl_getspecific(hdl);
326 
327 	fmd_hdl_free(hdl, dl, sizeof (*dl));
328 }
329