xref: /illumos-gate/usr/src/cmd/picl/plugins/sun4u/lw2plus/fcal_leds/fcal_leds.c (revision 440a8a36792bdf9ef51639066aab0b7771ffcab8)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright (c) 2001 by Sun Microsystems, Inc.
24  * All rights reserved.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * This plugin checks the status of FC-AL disks periodically and
31  * in response to PICL events. It adjusts the state of the FC-AL LEDs
32  * to match the disk status.
33  */
34 
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <limits.h>
38 #include <unistd.h>
39 #include <fcntl.h>
40 #include <alloca.h>
41 #include <syslog.h>
42 #include <string.h>
43 #include <libintl.h>
44 #include <pthread.h>
45 #include <sys/types.h>
46 #include <sys/systeminfo.h>
47 #include <sys/param.h>
48 #include <poll.h>
49 #include <errno.h>
50 #include <libnvpair.h>
51 #include "fcal_leds.h"
52 
53 static void fcal_leds_register(void);
54 static void fcal_leds_init(void);
55 static void fcal_leds_fini(void);
56 static void *fcal_poll_thread(void *args);
57 static FILE *open_config(void);
58 static int read_led_state(ptree_rarg_t *parg, void *buf);
59 static void add_led_refs(led_dtls_t *dtls);
60 static void delete_led_refs(led_dtls_t *dtls);
61 static void piclfcal_evhandler(const char *ename, const void *earg,
62     size_t size, void *cookie);
63 
64 /*
65  * Global thread data
66  */
67 led_dtls_t		*g_led_dtls = NULL;
68 pthread_cond_t		g_cv;
69 pthread_cond_t		g_cv_ack;
70 pthread_mutex_t		g_mutex;
71 volatile int		g_event_flag;
72 volatile boolean_t	g_finish_now = B_FALSE;
73 volatile boolean_t	g_leds_thread_ack = B_FALSE;
74 
75 static picld_plugin_reg_t  my_reg_info = {
76 	PICLD_PLUGIN_VERSION_1,
77 	PICLD_PLUGIN_NON_CRITICAL,
78 	"SUNW_fcal_leds",
79 	fcal_leds_init,
80 	fcal_leds_fini
81 };
82 
83 static boolean_t	cvAndMutexInit = B_FALSE;
84 static pthread_t	ledsthr_tid;
85 static pthread_attr_t	ledsthr_attr;
86 static boolean_t	ledsthr_created = B_FALSE;
87 static pthread_t	pollthr_tid;
88 static pthread_attr_t	pollthr_attr;
89 static boolean_t	pollthr_created = B_FALSE;
90 static volatile boolean_t poll_thread_ack = B_FALSE;
91 
92 /*
93  * look up table for LED state
94  */
95 static struct {
96 	const led_state_t	led_state;
97 	const char		*state_str;
98 } state_lookup[] = {
99 	{ LED_STATE_OFF,	FCAL_PICL_LED_OFF	},
100 	{ LED_STATE_ON,		FCAL_PICL_LED_ON	},
101 	{ LED_STATE_TEST,	FCAL_PICL_LED_TEST	}
102 };
103 
104 #define	state_lkup_len	(sizeof (state_lookup) / sizeof (state_lookup[0]))
105 
106 /*
107  * executed as part of .init when the plugin is dlopen()ed
108  */
109 #pragma	init(fcal_leds_register)
110 
111 static void
112 fcal_leds_register(void)
113 {
114 	(void) picld_plugin_register(&my_reg_info);
115 }
116 
117 /* ARGSUSED */
118 static void
119 piclfcal_evhandler(const char *ename, const void *earg, size_t size,
120     void *cookie)
121 {
122 	int r;
123 
124 	if (earg == NULL)
125 		return;
126 
127 	r = pthread_mutex_lock(&g_mutex);
128 
129 	if (r != 0) {
130 		SYSLOG(LOG_ERR, EM_MUTEX_FAIL, mystrerror(r));
131 		return;
132 	}
133 	g_event_flag |= FCAL_EV_CONFIG;
134 	(void) pthread_cond_signal(&g_cv);
135 
136 	(void) pthread_mutex_unlock(&g_mutex);
137 }
138 
139 /*
140  * Locate and open relevant config file
141  */
142 static FILE *
143 open_config(void)
144 {
145 	FILE	*fp = NULL;
146 	char	nmbuf[SYS_NMLN];
147 	char	fname[PATH_MAX];
148 
149 	if (sysinfo(SI_PLATFORM, nmbuf, sizeof (nmbuf)) == -1)
150 		return (NULL);
151 	(void) snprintf(fname, sizeof (fname), PICLD_PLAT_PLUGIN_DIRF, nmbuf);
152 	(void) strlcat(fname, FCAL_LEDS_CONF_FILE, sizeof (fname));
153 	fp = fopen(fname, "r");
154 	if (fp == NULL) {
155 		SYSLOG(LOG_ERR, EM_CANT_OPEN, fname);
156 	}
157 	return (fp);
158 }
159 
160 /*
161  * read volatile property function for led State
162  */
163 static int
164 read_led_state(ptree_rarg_t *parg, void *buf)
165 {
166 	led_dtls_t *dtls = g_led_dtls;
167 	picl_nodehdl_t nodeh = parg->nodeh;
168 	/*
169 	 * valbuf has space for a unit address at the end
170 	 */
171 	char valbuf[MAX_LEN_UNIT_ADDRESS];
172 	char *ptr;
173 	uint_t addr;
174 	int disk, led;
175 	led_state_t stat;
176 	/*
177 	 * each led-unit node has a UnitAddress property set to the bit
178 	 * value associated with the led. Read that property
179 	 */
180 	int r = ptree_get_propval_by_name(nodeh, PICL_PROP_UNIT_ADDRESS,
181 	    valbuf, sizeof (valbuf));
182 	if (r != PICL_SUCCESS)
183 		return (r);
184 	valbuf[sizeof (valbuf) - 1] = '\0';	/* ensure null terminated */
185 	/* UnitAddress is a string of hex digits, convert to an int */
186 	addr = strtoul(valbuf, &ptr, 16);
187 	if (dtls == NULL)
188 		return (PICL_PROPVALUNAVAILABLE);
189 	/*
190 	 * search the leds of each disk for a match with this UnitAddress
191 	 */
192 	for (disk = 0; disk < dtls->n_disks; disk++) {
193 		for (led = 0; led < FCAL_LED_CNT; led++) {
194 			if (addr == dtls->led_addr[led][disk])
195 				break;
196 		}
197 		if (led < FCAL_LED_CNT)
198 			break;
199 	}
200 	if (disk == dtls->n_disks)
201 		return (PICL_PROPVALUNAVAILABLE);
202 	stat = dtls->led_state[led][disk];
203 	/*
204 	 * state_lookup is a table relating led-state enums to equivalent
205 	 * text strings. Locate the string for the current state.
206 	 */
207 	for (r = 0; r < state_lkup_len; r++) {
208 		if (state_lookup[r].led_state == stat) {
209 			(void) strlcpy(buf, state_lookup[r].state_str,
210 			    MAX_LEN_LED_STATE);
211 			return (PICL_SUCCESS);
212 		}
213 	}
214 	return (PICL_PROPVALUNAVAILABLE);
215 }
216 
217 int
218 find_disk_slot(led_dtls_t *dtls, int disk, picl_nodehdl_t *nodeh)
219 {
220 	int		r;
221 	int		unitlen;
222 	char		unitstr[MAXPATHLEN];
223 
224 	if (dtls->disk_unit_parent == NULL) {
225 		return (PICL_NODENOTFOUND);
226 	}
227 	unitlen = strlen(dtls->disk_unit_parent);
228 	/*
229 	 * get search string buffer, allow space for address
230 	 */
231 	(void) strlcpy(unitstr, dtls->disk_unit_parent, MAXPATHLEN);
232 	(void) snprintf(unitstr + unitlen, MAXPATHLEN - unitlen, "%x", disk);
233 	r = ptree_get_node_by_path(unitstr, nodeh);
234 	return (r);
235 }
236 
237 int
238 create_Device_table(picl_prophdl_t *tbl_h, picl_prophdl_t *tableh)
239 {
240 	int			r;
241 	ptree_propinfo_t	propinfo;
242 
243 	r = ptree_init_propinfo(&propinfo, PTREE_PROPINFO_VERSION,
244 	    PICL_PTYPE_TABLE, PICL_READ, sizeof (picl_prophdl_t),
245 	    PICL_PROP_DEVICES, NULL, NULL);
246 	if (r != PICL_SUCCESS) {
247 		return (r);
248 	}
249 	r = ptree_create_table(tbl_h);
250 	if (r != PICL_SUCCESS) {
251 		return (r);
252 	}
253 	r = ptree_create_prop(&propinfo, tbl_h, tableh);
254 	return (r);
255 }
256 
257 /*
258  * Locate disk-slot nodes and add DeviceTable of LED references
259  * Also add a volatile State property to each LED node
260  */
261 static void
262 add_led_refs(led_dtls_t *dtls)
263 {
264 	int		d, i, r;
265 	int		ledlen;
266 	char		ledstr[MAXPATHLEN];
267 	picl_nodehdl_t  slot_node;
268 
269 	if (dtls->disk_led_nodes == NULL) {
270 		return;
271 	}
272 	ledlen = strlen(dtls->disk_led_nodes);
273 	/* set up search string in buffer with space to append address */
274 	(void) strlcpy(ledstr, dtls->disk_led_nodes, MAXPATHLEN);
275 	for (d = 0; d < dtls->n_disks; d++) {
276 		picl_prophdl_t tbl_hdl;
277 		picl_prophdl_t tbl_prop_hdl;
278 		picl_nodehdl_t led_node_hdl;
279 		picl_prophdl_t tbl_prop[FCAL_DEVTABLE_NCOLS];
280 		ptree_propinfo_t propinfo;
281 
282 		r = create_Device_table(&tbl_hdl, &tbl_prop_hdl);
283 		if (r != PICL_SUCCESS)
284 			break;
285 
286 		/*
287 		 * locate disk-slot node in frutree
288 		 */
289 		if (find_disk_slot(dtls, d, &slot_node) != PICL_SUCCESS)
290 			break;
291 
292 		for (i = 0; i < FCAL_LED_CNT; i++) {
293 			/*
294 			 * For each disk-slot in frutree, add a device
295 			 * table of references to relevant LEDs.
296 			 * En passant, add a volatile State property to
297 			 * each LED node found.
298 			 */
299 			/*
300 			 * append led address to search string
301 			 */
302 			(void) snprintf(ledstr + ledlen, MAXPATHLEN - ledlen,
303 			    "%x", dtls->led_addr[i][d]);
304 			r = ptree_get_node_by_path(ledstr, &led_node_hdl);
305 			if (r != PICL_SUCCESS) {
306 				break;
307 			}
308 			r = ptree_init_propinfo(&propinfo,
309 			    PTREE_PROPINFO_VERSION, PICL_PTYPE_CHARSTRING,
310 			    PICL_READ | PICL_VOLATILE, MAX_LEN_LED_STATE,
311 			    PICL_PROP_STATE, read_led_state, NULL);
312 			if (r != PICL_SUCCESS) {
313 				break;
314 			}
315 			r = ptree_create_and_add_prop(led_node_hdl,
316 			    &propinfo, NULL, NULL);
317 			if (r != PICL_SUCCESS) {
318 				break;
319 			}
320 			r = ptree_init_propinfo(&propinfo,
321 			    PTREE_PROPINFO_VERSION, PICL_PTYPE_CHARSTRING,
322 			    PICL_READ, sizeof (PICL_CLASS_LED),
323 			    PICL_PROP_CLASS, NULL, NULL);
324 			if (r != PICL_SUCCESS) {
325 				break;
326 			}
327 			r = ptree_create_prop(&propinfo, PICL_CLASS_LED,
328 			    &tbl_prop[0]);
329 			if (r != PICL_SUCCESS) {
330 				break;
331 			}
332 			r = ptree_init_propinfo(&propinfo,
333 			    PTREE_PROPINFO_VERSION, PICL_PTYPE_REFERENCE,
334 			    PICL_READ, sizeof (picl_prophdl_t),
335 			    FCAL_PICL_LED_REF, NULL, NULL);
336 			if (r != PICL_SUCCESS) {
337 				break;
338 			}
339 			r = ptree_create_prop(&propinfo, &led_node_hdl,
340 			    &tbl_prop[1]);
341 			if (r != PICL_SUCCESS) {
342 				break;
343 			}
344 			r = ptree_add_row_to_table(tbl_hdl,
345 			    FCAL_DEVTABLE_NCOLS, tbl_prop);
346 			if (r != PICL_SUCCESS) {
347 				break;
348 			}
349 		}
350 		if (r != PICL_SUCCESS)
351 			break;
352 		(void) ptree_add_prop(slot_node, tbl_prop_hdl);
353 	}
354 }
355 
356 /*
357  * This is an undo function to match add_led_refs()
358  * Locate disk-slot nodes and remove Devices table of LED references
359  * Also remove volatile State property to each LED node
360  */
361 static void
362 delete_led_refs(led_dtls_t *dtls)
363 {
364 	int		d;
365 	int		i;
366 	int		r;
367 	int		ledlen;
368 	picl_nodehdl_t  node_hdl;
369 	picl_prophdl_t	prop_hdl;
370 	char		ledstr[MAXPATHLEN];
371 
372 	if (dtls->disk_led_nodes == NULL)
373 		return;
374 
375 	for (d = 0; d < dtls->n_disks; d++) {
376 		if (find_disk_slot(dtls, d, &node_hdl) != PICL_SUCCESS)
377 			continue;
378 		if (ptree_get_prop_by_name(node_hdl, PICL_PROP_DEVICES,
379 		    &prop_hdl) != PICL_SUCCESS)
380 			continue;
381 		if (ptree_delete_prop(prop_hdl) != PICL_SUCCESS)
382 			continue;
383 		(void) ptree_destroy_prop(prop_hdl);
384 	}
385 
386 	ledlen = strlen(dtls->disk_led_nodes);
387 	(void) strlcpy(ledstr, dtls->disk_led_nodes, MAXPATHLEN);
388 
389 	for (d = 0; d < dtls->n_disks; d++) {
390 		for (i = 0; i < FCAL_LED_CNT; i++) {
391 			/*
392 			 * find each led node
393 			 */
394 			(void) snprintf(ledstr + ledlen, MAXPATHLEN - ledlen,
395 			    "%x", dtls->led_addr[i][d]);
396 			r = ptree_get_node_by_path(ledstr, &node_hdl);
397 			if (r != PICL_SUCCESS)
398 				continue;
399 			/*
400 			 * locate and delete the volatile State property
401 			 */
402 			if (ptree_get_prop_by_name(node_hdl,
403 			    PICL_PROP_STATE, &prop_hdl) != PICL_SUCCESS)
404 				continue;
405 			if (ptree_delete_prop(prop_hdl) != PICL_SUCCESS)
406 				continue;
407 			(void) ptree_destroy_prop(prop_hdl);
408 		}
409 	}
410 }
411 
412 /*
413  * Poll thread.
414  * This thread sits on a poll() call for the fast poll interval.
415  * At each wake up it determines if a time event should be passed on.
416  * Poll seems to be reliable when the realtime clock is wound backwards,
417  * whereas pthread_cond_timedwait() is not.
418  */
419 /*ARGSUSED*/
420 static void *
421 fcal_poll_thread(void *args)
422 {
423 	led_dtls_t	*dtls = NULL;
424 	int		c;
425 	int		slow_poll_count;
426 	boolean_t	do_event;
427 
428 	for (;;) {
429 		if (g_finish_now) {
430 			c = pthread_mutex_lock(&g_mutex);
431 
432 			if (c != 0) {
433 				SYSLOG(LOG_ERR, EM_MUTEX_FAIL, mystrerror(c));
434 				break;
435 			}
436 			poll_thread_ack = B_TRUE;
437 			(void) pthread_cond_signal(&g_cv_ack);
438 			(void) pthread_cond_wait(&g_cv, &g_mutex);
439 
440 			(void) pthread_mutex_unlock(&g_mutex);
441 			continue;
442 		}
443 
444 		/*
445 		 * If picld has been recycled, or if this is the initial
446 		 * entry, dtls will not match g_led_dtls.
447 		 * In this case, do some resetting.
448 		 */
449 		if (dtls != g_led_dtls) {
450 			dtls = g_led_dtls;
451 			slow_poll_count = dtls->slow_poll_ticks;
452 			dtls->polling = B_TRUE;
453 		}
454 
455 		c = poll(NULL, 0, dtls->fast_poll * 1000);
456 		if (c == -1) {
457 			SYSLOG(LOG_ERR, EM_POLL_FAIL, mystrerror(errno));
458 			break;
459 		}
460 		/*
461 		 * dtls->fast_poll_end is the number of fast poll times left
462 		 * before we revert to slow polling. If it is non-zero, the
463 		 * fcal_leds thread is do fast polling and we pass on every
464 		 * poll wakeup.
465 		 */
466 		do_event = (dtls->fast_poll_end != 0);
467 		/*
468 		 * If a LED test is underway, fast polling would normally be
469 		 * set also. Just in case the timers are configured unusually,
470 		 * pass on all poll wakeups while a LED test is current.
471 		 */
472 		if ((!do_event) && is_led_test(dtls))
473 			do_event = B_TRUE;
474 		if (!do_event) {
475 			/*
476 			 * If we get here, the fcal_leds thread is only doing
477 			 * slow polls. Count down the slow_poll_count and set
478 			 * an event if it expires.
479 			 */
480 			if (--slow_poll_count == 0) {
481 				slow_poll_count = dtls->slow_poll_ticks;
482 				do_event = B_TRUE;
483 			}
484 		}
485 		if (do_event) {
486 			c = pthread_mutex_lock(&g_mutex);
487 
488 			if (c != 0) {
489 				SYSLOG(LOG_ERR, EM_MUTEX_FAIL, mystrerror(c));
490 				break;
491 			}
492 			/*
493 			 * indicate in the event flag that this is a time event
494 			 */
495 			g_event_flag |= FCAL_EV_POLL;
496 			(void) pthread_cond_signal(&g_cv);
497 
498 			(void) pthread_mutex_unlock(&g_mutex);
499 		}
500 	}
501 
502 	dtls->polling = B_FALSE;
503 
504 	/*
505 	 * if picld restarted, allow this thread to be recreated
506 	 */
507 	pollthr_created = B_FALSE;
508 
509 	return ((void *)errno);
510 }
511 
512 /*
513  * Init entry point of the plugin
514  * Opens and parses config file.
515  * Establishes an interrupt routine to catch DEVICE ADDED/REMOVED events
516  * and starts a new thread for polling FC-AL disk status information.
517  */
518 static void
519 fcal_leds_init(void)
520 {
521 	FILE *fp;
522 	int err = 0;
523 
524 	if ((fp = open_config()) == NULL)
525 		return;
526 	if (fc_led_parse(fp, &g_led_dtls) != 0) {
527 		(void) fclose(fp);
528 		return;
529 	}
530 	(void) fclose(fp);
531 	g_finish_now = B_FALSE;
532 	g_event_flag = 0;
533 
534 	if (!cvAndMutexInit) {
535 		if ((pthread_cond_init(&g_cv, NULL) == 0) &&
536 		    (pthread_cond_init(&g_cv_ack, NULL) == 0) &&
537 		    (pthread_mutex_init(&g_mutex, NULL) == 0)) {
538 			cvAndMutexInit = B_TRUE;
539 		} else {
540 			return;
541 		}
542 	}
543 
544 	add_led_refs(g_led_dtls);
545 
546 	(void) ptree_register_handler(PICLEVENT_SYSEVENT_DEVICE_ADDED,
547 	    piclfcal_evhandler, NULL);
548 	(void) ptree_register_handler(PICLEVENT_SYSEVENT_DEVICE_REMOVED,
549 	    piclfcal_evhandler, NULL);
550 
551 	if (ledsthr_created || pollthr_created) {
552 		/*
553 		 * so this is a restart, wake up sleeping threads
554 		 */
555 		err = pthread_mutex_lock(&g_mutex);
556 
557 		if (err != 0) {
558 			SYSLOG(LOG_ERR, EM_MUTEX_FAIL, mystrerror(err));
559 			return;
560 		}
561 		g_leds_thread_ack = B_FALSE;
562 		poll_thread_ack = B_FALSE;
563 		(void) pthread_cond_broadcast(&g_cv);
564 
565 		(void) pthread_mutex_unlock(&g_mutex);
566 	}
567 	if (!ledsthr_created) {
568 		if ((pthread_attr_init(&ledsthr_attr) != 0) ||
569 		    (pthread_attr_setscope(&ledsthr_attr,
570 		    PTHREAD_SCOPE_SYSTEM) != 0))
571 			return;
572 
573 		if ((err = pthread_create(&ledsthr_tid, &ledsthr_attr,
574 		    fcal_leds_thread, g_led_dtls)) != 0) {
575 			SYSLOG(LOG_ERR, EM_THREAD_CREATE_FAILED,
576 			    mystrerror(err));
577 			return;
578 		}
579 
580 		ledsthr_created = B_TRUE;
581 	}
582 
583 	if (pollthr_created == B_FALSE) {
584 		if ((pthread_attr_init(&pollthr_attr) != 0) ||
585 		    (pthread_attr_setscope(&pollthr_attr,
586 		    PTHREAD_SCOPE_SYSTEM) != 0))
587 			return;
588 
589 		if ((err = pthread_create(&pollthr_tid, &pollthr_attr,
590 		    fcal_poll_thread, g_led_dtls)) != 0) {
591 			g_led_dtls->polling = B_FALSE;
592 			SYSLOG(LOG_ERR, EM_THREAD_CREATE_FAILED,
593 			    mystrerror(err));
594 			return;
595 		}
596 
597 		pollthr_created = B_TRUE;
598 	}
599 }
600 
601 /*
602  * fini entry point of the plugin
603  */
604 static void
605 fcal_leds_fini(void)
606 {
607 	int	c;
608 
609 	/* unregister event handlers */
610 	(void) ptree_unregister_handler(PICLEVENT_SYSEVENT_DEVICE_ADDED,
611 	    piclfcal_evhandler, NULL);
612 	(void) ptree_unregister_handler(PICLEVENT_SYSEVENT_DEVICE_REMOVED,
613 	    piclfcal_evhandler, NULL);
614 	/*
615 	 * it's very confusing to leave uncontrolled leds on, so clear them.
616 	 */
617 	if (g_led_dtls != NULL) {
618 		int ledNo;
619 		int diskNo;
620 		for (ledNo = 0; ledNo < FCAL_LED_CNT; ledNo++) {
621 			if ((g_led_dtls->led_addr[ledNo] == NULL) ||
622 			    (g_led_dtls->led_state[ledNo] == NULL)) {
623 				break;	/* incomplete setup */
624 			}
625 			for (diskNo = 0; diskNo < g_led_dtls->n_disks;
626 			    diskNo++) {
627 				clr_led(diskNo, LED_PROPS_START + 1 + ledNo,
628 				    g_led_dtls);
629 			}
630 		}
631 	}
632 	/*
633 	 * tell other threads to stop
634 	 */
635 	if (cvAndMutexInit && (ledsthr_created || pollthr_created)) {
636 		g_finish_now = B_TRUE;
637 		c = pthread_mutex_lock(&g_mutex);
638 		if (c != 0) {
639 			SYSLOG(LOG_ERR, EM_MUTEX_FAIL, mystrerror(c));
640 		} else {
641 			(void) pthread_cond_broadcast(&g_cv);
642 			(void) pthread_mutex_unlock(&g_mutex);
643 
644 			/*
645 			 * and wait for them to acknowledge
646 			 */
647 			while ((ledsthr_created && !g_leds_thread_ack) ||
648 			    (pollthr_created && !poll_thread_ack)) {
649 				c = pthread_mutex_lock(&g_mutex);
650 				if (c != 0) {
651 					SYSLOG(LOG_ERR, EM_MUTEX_FAIL,
652 					    mystrerror(c));
653 					break;
654 				}
655 				(void) pthread_cond_wait(&g_cv_ack, &g_mutex);
656 				(void) pthread_mutex_unlock(&g_mutex);
657 			}
658 		}
659 	}
660 	/*
661 	 * remove picl nodes created by this plugin
662 	 */
663 	if (g_led_dtls != NULL) {
664 		for (c = 0; c < g_led_dtls->n_disks; c++) {
665 			/*
666 			 * remove all disk unit nodes from frutree
667 			 */
668 			delete_disk_unit(g_led_dtls, c);
669 		}
670 		/*
671 		 * remove Devices tables of references to leds
672 		 * and led State properties
673 		 */
674 		delete_led_refs(g_led_dtls);
675 		/*
676 		 * finally free the led details
677 		 */
678 		free_led_dtls(g_led_dtls);
679 		g_led_dtls = NULL;
680 	}
681 }
682