xref: /illumos-gate/usr/src/cmd/fm/modules/common/disk-monitor/schg_mgr.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
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 /*
23  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <string.h>
30 #include <inttypes.h>
31 #include <atomic.h>
32 #include <fm/fmd_api.h>
33 #include <sys/fm/protocol.h>
34 
35 #include "disk_monitor.h"
36 #include "schg_mgr.h"
37 #include "hotplug_mgr.h"
38 #include "topo_gather.h"
39 #include "dm_platform.h"
40 
41 /* State-change event processing thread data */
42 static pthread_t	g_schg_tid;
43 static thread_state_t	g_schgt_state = TS_NOT_RUNNING;
44 static pthread_mutex_t	g_schgt_state_mutex = PTHREAD_MUTEX_INITIALIZER;
45 static pthread_cond_t	g_schgt_state_cvar = PTHREAD_COND_INITIALIZER;
46 static pthread_mutex_t	g_schgt_add_mutex = PTHREAD_MUTEX_INITIALIZER;
47 static qu_t		*g_schg_queue = NULL;
48 
49 static void dm_state_change_nolock(diskmon_t *diskp, hotplug_state_t newstate);
50 
51 /*
52  * Each disk state change is described by an instance of the following
53  * structure (which includes the disk object and the new state)
54  */
55 typedef struct disk_statechg {
56 	diskmon_t	*diskp;
57 	hotplug_state_t	newstate;
58 } disk_statechg_t;
59 
60 static disk_statechg_t *
61 new_statechange(diskmon_t *diskp, hotplug_state_t state)
62 {
63 	disk_statechg_t *dscp =
64 	    (disk_statechg_t *)dmalloc(sizeof (disk_statechg_t));
65 
66 	/*
67 	 * The states are additive -- we don't need to preserve
68 	 * the current faulted state in the newstate:
69 	 */
70 	dscp->diskp = diskp;
71 	dscp->newstate = state;
72 
73 	return (dscp);
74 }
75 
76 static void
77 free_statechange(void *dscp)
78 {
79 	dfree(dscp, sizeof (disk_statechg_t));
80 }
81 
82 static void
83 add_to_statechange_queue(diskmon_t *diskp, hotplug_state_t newstate)
84 {
85 	queue_add(g_schg_queue, new_statechange(diskp, newstate));
86 }
87 
88 static const char *
89 lookup_action_string(indicator_t *ind_listp, ind_state_t state, char *name)
90 {
91 	const char *str = NULL;
92 
93 	while (ind_listp != NULL) {
94 
95 		if (state == ind_listp->ind_state &&
96 		    strcasecmp(ind_listp->ind_name, name) == 0) {
97 
98 			str = ind_listp->ind_instr_spec;
99 			break;
100 		}
101 
102 		ind_listp = ind_listp->next;
103 	}
104 
105 	return (str);
106 }
107 
108 void
109 dm_fault_indicator_set(diskmon_t *diskp, ind_state_t istate)
110 {
111 	const char *astring;
112 
113 	dm_assert(pthread_mutex_lock(&diskp->fault_indicator_mutex) == 0);
114 
115 	/*
116 	 * No need to execute redundant indicator actions
117 	 */
118 	if (istate == INDICATOR_UNKNOWN ||
119 	    diskp->fault_indicator_state == istate) {
120 		dm_assert(pthread_mutex_unlock(&diskp->fault_indicator_mutex)
121 		    == 0);
122 		return;
123 	}
124 
125 	astring = lookup_action_string(diskp->ind_list, istate,
126 	    INDICATOR_FAULT_IDENTIFIER);
127 
128 	if (astring != NULL) {
129 		log_msg(MM_SCHGMGR, "Executing action `%s'\n", astring);
130 
131 		if (dm_platform_indicator_execute(astring) != 0) {
132 			log_warn("[Disk in %s] Action `%s' did not complete "
133 			    "successfully.\n",
134 			    diskp->location,
135 			    astring);
136 		} else  {
137 
138 			diskp->fault_indicator_state = istate;
139 
140 			log_msg(MM_SCHGMGR, "Action `%s' executed "
141 			    "successfully\n", astring);
142 		}
143 	}
144 
145 	dm_assert(pthread_mutex_unlock(&diskp->fault_indicator_mutex) == 0);
146 }
147 
148 static void
149 schg_execute_state_change_action(diskmon_t *diskp, hotplug_state_t oldstate,
150     hotplug_state_t newstate)
151 {
152 	indrule_t *rulelist;
153 	ind_action_t *actions;
154 	const char *astring;
155 
156 	log_msg(MM_SCHGMGR, "[Disk in %s] State change action: %s -> %s\n",
157 	    diskp->location,
158 	    hotplug_state_string(oldstate),
159 	    hotplug_state_string(newstate));
160 
161 	/*
162 	 * Find the list of actions that correspond to this state change.
163 	 * If the old state is UNKNOWN, then we'll match to first action
164 	 * whose transition state is the new state.
165 	 */
166 	rulelist = diskp->indrule_list;
167 
168 	while (rulelist != NULL) {
169 
170 		if ((oldstate == HPS_UNKNOWN ||
171 		    rulelist->strans.begin == oldstate) &&
172 		    rulelist->strans.end == newstate)
173 			break;
174 
175 		rulelist = rulelist->next;
176 	}
177 
178 	if (rulelist != NULL) {
179 		/* Now we have a set of actions to perform: */
180 		actions = rulelist->action_list;
181 
182 		while (actions != NULL) {
183 
184 			astring = lookup_action_string(diskp->ind_list,
185 			    actions->ind_state, actions->ind_name);
186 
187 			dm_assert(astring != NULL);
188 
189 			log_msg(MM_SCHGMGR, "Executing action `%s'\n", astring);
190 
191 			if (dm_platform_indicator_execute(astring) != 0) {
192 				log_warn("[Disk in %s][State transition from "
193 				    "%s to %s] Action `%s' did not complete "
194 				    "successfully.\n",
195 				    diskp->location,
196 				    hotplug_state_string(oldstate),
197 				    hotplug_state_string(newstate),
198 				    astring);
199 
200 			} else
201 				log_msg(MM_SCHGMGR,
202 				    "Action `%s' executed successfully\n",
203 				    astring);
204 
205 			actions = actions->next;
206 		}
207 	}
208 
209 }
210 
211 static void
212 schg_send_fru_update(diskmon_t *diskp, dm_fru_t *frup)
213 {
214 	const char *action = dm_prop_lookup(diskp->props, DISK_PROP_FRUACTION);
215 
216 	if (action == NULL) {
217 		log_msg(MM_SCHGMGR|MM_NOTE, "No FRU update action for disk "
218 		    "in %s\n", diskp->location);
219 		return;
220 	}
221 
222 	if (dm_platform_update_fru(action, frup) != 0) {
223 		log_warn("Error updating FRU information for disk in %s.\n",
224 		    diskp->location);
225 	}
226 }
227 
228 static void
229 schg_update_fru_info(diskmon_t *diskp)
230 {
231 	if (diskp->initial_configuration ||
232 	    update_configuration_from_topo(g_fm_hdl, diskp) == TOPO_SUCCESS) {
233 		diskp->initial_configuration = B_FALSE;
234 		dm_assert(pthread_mutex_lock(&diskp->fru_mutex) == 0);
235 		if (diskp->frup != NULL)
236 			schg_send_fru_update(diskp, diskp->frup);
237 		else
238 			log_warn("frup unexpectedly went away: not updating "
239 			    "FRU information for disk %s!\n", diskp->location);
240 		dm_assert(pthread_mutex_unlock(&diskp->fru_mutex) == 0);
241 	} else {
242 		log_warn_e("Error retrieving FRU information "
243 		    "for disk in %s", diskp->location);
244 	}
245 }
246 
247 void
248 block_state_change_events(void)
249 {
250 	dm_assert(pthread_mutex_lock(&g_schgt_add_mutex) == 0);
251 }
252 
253 void
254 unblock_state_change_events(void)
255 {
256 	dm_assert(pthread_mutex_unlock(&g_schgt_add_mutex) == 0);
257 }
258 
259 static void
260 disk_state_change_first_time(diskmon_t *diskp)
261 {
262 	hotplug_state_t firststate;
263 
264 	/*
265 	 * Grab the current state of the attachment point to initialize the
266 	 * initial disk state.  Create a disk state change with this new
267 	 * state so it will be processed in the loop below.  If we can't get
268 	 * the initial state for some reason, then we'll just end up doing it
269 	 * later when we get a state change from the hotplug monitor or the
270 	 * fault monitor.
271 	 */
272 	firststate = disk_ap_state_to_hotplug_state(diskp);
273 	if (firststate != HPS_UNKNOWN)
274 		dm_state_change_nolock(diskp, firststate);
275 
276 	/*
277 	 * The fault indicators will be updated when faults are replayed
278 	 * based on the state of the disk as faulty in the fmd resource cache.
279 	 * A FAULTED state change will come from the _recv function when the
280 	 * fault component event is replayed.
281 	 */
282 }
283 
284 static void
285 disk_state_change_thread(void *vdisklistp)
286 {
287 	diskmon_t	*disklistp = (diskmon_t *)vdisklistp;
288 	diskmon_t	*diskp;
289 	disk_statechg_t	*dscp;
290 	hotplug_state_t	nextstate;
291 	const char	*pth;
292 
293 	/*
294 	 * Perform startup activities to initialize the state of the
295 	 * indicators for each disk.
296 	 */
297 	diskp = disklistp;
298 	while (diskp != NULL) {
299 		disk_state_change_first_time(diskp);
300 		diskp = diskp->next;
301 	}
302 
303 	unblock_state_change_events();
304 
305 	dm_assert(pthread_mutex_lock(&g_schgt_state_mutex) == 0);
306 	if (g_schgt_state != TS_EXIT_REQUESTED) {
307 		g_schgt_state = TS_RUNNING;
308 		dm_assert(pthread_cond_broadcast(&g_schgt_state_cvar) == 0);
309 	}
310 	dm_assert(pthread_mutex_unlock(&g_schgt_state_mutex) == 0);
311 
312 	while (g_schgt_state != TS_EXIT_REQUESTED) {
313 
314 		if ((dscp = (disk_statechg_t *)queue_remove(g_schg_queue))
315 		    == NULL) {
316 			dm_assert(g_schgt_state == TS_EXIT_REQUESTED);
317 			continue;
318 		}
319 
320 		diskp = dscp->diskp;
321 
322 		/*
323 		 * If the new state is the faulted state, add that state to
324 		 * the disk's current state.
325 		 */
326 		if (dscp->newstate == HPS_FAULTED) {
327 
328 			/*
329 			 * If the disk wasn't previously in the faulted state,
330 			 * execute the generic fault action.  Even if we're
331 			 * in the faulted state, accept additional faults.
332 			 */
333 			nextstate = DISK_STATE(diskp->state) | HPS_FAULTED;
334 
335 		} else if (dscp->newstate == HPS_REPAIRED) {
336 			nextstate = DISK_STATE(diskp->state);
337 
338 		} else if (dscp->newstate == HPS_ABSENT) {
339 			/*
340 			 * If the new state is ABSENT, forget any faults
341 			 */
342 
343 			nextstate = HPS_ABSENT;
344 		} else
345 			nextstate = dscp->newstate | DISK_FAULTED(diskp->state);
346 
347 		/*
348 		 * When a new disk is inserted and reaches the CONFIGURED state,
349 		 * the following actions must be done in the following order:
350 		 *
351 		 * (1) Execute the configuration-specified action on the
352 		 * state change.
353 		 * (2) Retreive the FRU information from the disk and execute
354 		 * the FRU-update action specified,
355 		 * (3) Initialize the fault monitor state associated with
356 		 * the new drive.
357 		 *
358 		 * Once the disk is no longer "new" (a disk is "new" when it
359 		 * has not yet reached the CONFIGURED state), subsequent
360 		 * transitions away and back to CONFIGURED (as long as the
361 		 * disk is not physically removed) will result in the
362 		 * execution of the predefined action ONLY.
363 		 *
364 		 */
365 
366 		if (dscp->newstate != HPS_FAULTED &&
367 		    DISK_STATE(nextstate) != HPS_UNKNOWN &&
368 		    dscp->newstate != HPS_REPAIRED) {
369 
370 			schg_execute_state_change_action(diskp,
371 			    DISK_STATE(diskp->state), DISK_STATE(nextstate));
372 		}
373 
374 		if (!diskp->configured_yet &&
375 		    DISK_STATE(nextstate) == HPS_CONFIGURED) {
376 
377 			schg_update_fru_info(diskp);
378 
379 			/*
380 			 * If this state transition is lagging the true
381 			 * state of the system (e.g. if the true state of
382 			 * the disk is UNCONFIGURED, there's another
383 			 * state change somewhere later in the queue), then
384 			 * it's possible for the disk path property to not
385 			 * exist.
386 			 */
387 			if (dm_prop_lookup(diskp->props,
388 			    DISK_PROP_DEVPATH) == NULL) {
389 
390 				log_msg(MM_SCHGMGR,
391 				    "Processed stale state change "
392 				    "for disk %s\n", diskp->location);
393 
394 			} else {
395 				diskp->configured_yet = B_TRUE;
396 			}
397 
398 		}
399 
400 		dm_assert(pthread_mutex_lock(&diskp->manager_mutex) == 0);
401 
402 		/*
403 		 * Make the new state visible to all observers
404 		 */
405 		diskp->state = nextstate;
406 
407 		/*
408 		 * Now, update the diskmon if the disk is now absent -- it's
409 		 * essential to do this after the state is set (above) so that
410 		 * state observers in other threads don't try to access the
411 		 * data structures that we're freeing here.
412 		 */
413 
414 		if (diskp->configured_yet &&
415 		    DISK_STATE(nextstate) == HPS_ABSENT) {
416 			/*
417 			 * When the disk is removed, the fault monitor state is
418 			 * useless, so discard it.
419 			 */
420 			dm_assert(DISK_STATE(nextstate) != HPS_CONFIGURED);
421 
422 			diskp->configured_yet = B_FALSE;
423 
424 		}
425 		dm_assert(pthread_mutex_unlock(&diskp->manager_mutex) == 0);
426 
427 		pth = dm_prop_lookup(diskp->props, DISK_PROP_DEVPATH);
428 
429 		log_msg(MM_SCHGMGR,
430 		    "[State change #%d][%s]: Disk path = %s\n",
431 		    diskp->state_change_count,
432 		    diskp->location, pth == NULL ? "Unknown" : pth);
433 
434 		log_msg(MM_SCHGMGR,
435 		    "[State change #%d][%s]: New state = %s%s\n",
436 		    diskp->state_change_count, diskp->location,
437 		    hotplug_state_string(diskp->state),
438 		    DISK_FAULTED(diskp->state) ? "+FAULTED" : "");
439 
440 		atomic_inc_uint(&diskp->state_change_count);
441 
442 		/* The caller is responsible for freeing the state change: */
443 		free_statechange(dscp);
444 	}
445 	dm_assert(pthread_mutex_lock(&g_schgt_state_mutex) == 0);
446 	g_schgt_state = TS_EXITED;
447 	dm_assert(pthread_cond_broadcast(&g_schgt_state_cvar) == 0);
448 	dm_assert(pthread_mutex_unlock(&g_schgt_state_mutex) == 0);
449 
450 	log_msg(MM_SCHGMGR, "State change thread exiting...\n");
451 }
452 
453 static void
454 dm_state_change_nolock(diskmon_t *diskp, hotplug_state_t newstate)
455 {
456 	/* Enqueue a new state change for the state-change thread */
457 	add_to_statechange_queue(diskp, newstate);
458 }
459 
460 void
461 dm_state_change(diskmon_t *diskp, hotplug_state_t newstate)
462 {
463 	dm_assert(pthread_mutex_lock(&g_schgt_add_mutex) == 0);
464 	dm_state_change_nolock(diskp, newstate);
465 	dm_assert(pthread_mutex_unlock(&g_schgt_add_mutex) == 0);
466 }
467 
468 int
469 init_state_change_manager(cfgdata_t *cfgdatap)
470 {
471 	/* new_queue() is guaranteed to succeed */
472 	g_schg_queue = new_queue(B_TRUE, dmalloc, dfree, free_statechange);
473 
474 	dm_assert(pthread_mutex_lock(&g_schgt_state_mutex) == 0);
475 	g_schg_tid = fmd_thr_create(g_fm_hdl, disk_state_change_thread,
476 	    cfgdatap->disk_list);
477 
478 	/*
479 	 * Now, wait for the thread to enter the TS_RUNNING state.  This
480 	 * is important because we want the state-change thread to pull the
481 	 * initial state of the disks on startup (without the wait, we could
482 	 * have the hotplug event handler race and deliver a state change
483 	 * before the state-change thread initialized the initial disk state).
484 	 */
485 
486 	while (g_schgt_state != TS_RUNNING) {
487 		(void) pthread_cond_wait(&g_schgt_state_cvar,
488 		    &g_schgt_state_mutex);
489 	}
490 
491 	dm_assert(pthread_mutex_unlock(&g_schgt_state_mutex) == 0);
492 
493 	return (0);
494 }
495 
496 /*ARGSUSED*/
497 void
498 cleanup_state_change_manager(cfgdata_t *cfgdatap)
499 {
500 	if (g_schgt_state != TS_RUNNING)
501 		return;
502 
503 	g_schgt_state = TS_EXIT_REQUESTED;
504 	queue_add(g_schg_queue, NULL);
505 	dm_assert(pthread_mutex_lock(&g_schgt_state_mutex) == 0);
506 	while (g_schgt_state != TS_EXITED)
507 		dm_assert(pthread_cond_wait(&g_schgt_state_cvar,
508 		    &g_schgt_state_mutex) == 0);
509 	dm_assert(pthread_mutex_unlock(&g_schgt_state_mutex) == 0);
510 	(void) pthread_join(g_schg_tid, NULL);
511 	fmd_thr_destroy(g_fm_hdl, g_schg_tid);
512 	queue_free(&g_schg_queue);
513 	g_schgt_state = TS_NOT_RUNNING;
514 }
515