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
53static void fcal_leds_register(void);
54static void fcal_leds_init(void);
55static void fcal_leds_fini(void);
56static void *fcal_poll_thread(void *args);
57static FILE *open_config(void);
58static int read_led_state(ptree_rarg_t *parg, void *buf);
59static void add_led_refs(led_dtls_t *dtls);
60static void delete_led_refs(led_dtls_t *dtls);
61static void piclfcal_evhandler(const char *ename, const void *earg,
62    size_t size, void *cookie);
63
64/*
65 * Global thread data
66 */
67led_dtls_t		*g_led_dtls = NULL;
68pthread_cond_t		g_cv;
69pthread_cond_t		g_cv_ack;
70pthread_mutex_t		g_mutex;
71volatile int		g_event_flag;
72volatile boolean_t	g_finish_now = B_FALSE;
73volatile boolean_t	g_leds_thread_ack = B_FALSE;
74
75static 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
83static boolean_t	cvAndMutexInit = B_FALSE;
84static pthread_t	ledsthr_tid;
85static pthread_attr_t	ledsthr_attr;
86static boolean_t	ledsthr_created = B_FALSE;
87static pthread_t	pollthr_tid;
88static pthread_attr_t	pollthr_attr;
89static boolean_t	pollthr_created = B_FALSE;
90static volatile boolean_t poll_thread_ack = B_FALSE;
91
92/*
93 * look up table for LED state
94 */
95static 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
111static void
112fcal_leds_register(void)
113{
114	(void) picld_plugin_register(&my_reg_info);
115}
116
117/* ARGSUSED */
118static void
119piclfcal_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 */
142static FILE *
143open_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 */
163static int
164read_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
217int
218find_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
237int
238create_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 */
261static void
262add_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 */
361static void
362delete_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*/
420static void *
421fcal_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 */
518static void
519fcal_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 */
604static void
605fcal_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