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