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 #include <stdio.h>
28 #include <unistd.h>
29 #include <fcntl.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <errno.h>
33 #include <stdarg.h>
34 #include <pthread.h>
35 #include <libintl.h>
36 #include <libdevinfo.h>
37 #include <syslog.h>
38 #include <sys/i2c/clients/i2c_client.h>
39 #include <poll.h>
40 #include "fcal_leds.h"
41 
42 static char fcal_disk_unit[] = FCAL_PICL_DISK_UNIT;
43 
44 static int update_picl(led_dtls_t *dtls, int disk);
45 static int get_drv_info(di_node_t node, led_dtls_t *dtls);
46 static int walk_disks(di_node_t node, led_dtls_t *dtls);
47 static int chk_minors(led_dtls_t *dtls);
48 static void set_led(int diskNo, token_t led_tok, led_dtls_t *dtls);
49 static int set_clr_led(int diskNo, token_t led_tok, led_dtls_t *dtls, int set);
50 void set_led(int diskNo, token_t led_tok, led_dtls_t *dtls);
51 void clr_led(int diskNo, token_t led_tok, led_dtls_t *dtls);
52 static void retry_led(led_dtls_t *dtls);
53 static void start_led_test(led_dtls_t *dtls, int disk);
54 static void end_led_test(led_dtls_t *dtls, int disk);
55 static int wait_a_while(void);
56 
57 /*
58  * variant of strerror() which guards against negative errno and null strings
59  */
60 char *
mystrerror(int err)61 mystrerror(int err)
62 {
63 	static char *unknown_errno = "unknown errno";
64 	char *ptr;
65 
66 	if ((err < 0) || ((ptr = strerror(err)) == NULL)) {
67 		ptr = unknown_errno;
68 	}
69 	return (ptr);
70 }
71 
72 void
delete_disk_unit(led_dtls_t * dtls,int disk)73 delete_disk_unit(led_dtls_t *dtls, int disk)
74 {
75 	int			r;
76 	picl_nodehdl_t		slotndh;
77 	picl_nodehdl_t		diskndh;
78 
79 	r = find_disk_slot(dtls, disk, &slotndh);
80 	if (r != PICL_SUCCESS)
81 		return;
82 
83 	/*
84 	 * is there a disk-unit node here?
85 	 */
86 	r = ptree_find_node(slotndh, PICL_PROP_NAME,
87 	    PICL_PTYPE_CHARSTRING, fcal_disk_unit,
88 	    sizeof (fcal_disk_unit), &diskndh);
89 	if (r != PICL_SUCCESS)
90 		return;
91 
92 	/*
93 	 * remove disk-unit node and its properties
94 	 */
95 	r = ptree_delete_node(diskndh);
96 	if (r != PICL_SUCCESS)
97 		return;
98 	(void) ptree_destroy_node(diskndh);
99 }
100 
101 /*
102  * update_picl
103  * Called when disk goes off-line or goes to ready status.
104  * In the case of disk ready, locate platform tree node for the disk
105  * and add a target property (if missing).
106  * (The target address is fixed for a given disk slot and is used to
107  * tie the frutree disk-unit to the correct ssd node).
108  * Returns EAGAIN for a retriable failure, otherwise 0.
109  */
110 static int
update_picl(led_dtls_t * dtls,int disk)111 update_picl(led_dtls_t *dtls, int disk)
112 {
113 	static char		trailer[] = ",0";
114 	picl_nodehdl_t		slotndh;
115 	picl_nodehdl_t		diskndh;
116 	ptree_propinfo_t	propinfo;
117 	int			r;
118 
119 	if (dtls->disk_detected[disk] != 0) {
120 		picl_nodehdl_t		fpndh;
121 		picl_nodehdl_t		ssdndh;
122 		picl_prophdl_t		tbl_h;
123 		picl_prophdl_t		tbl_prop_h;
124 		picl_prophdl_t		row_props_h[FCAL_DEVTABLE_NCOLS];
125 		char			valbuf[80];
126 		char			addr[MAXPATHLEN];
127 		char			*ptrd;
128 		const uchar_t		*ptrs;
129 		int			len;
130 		int			addr_len;
131 
132 		for (;;) {
133 			r = ptree_get_node_by_path(dtls->fcal_disk_parent,
134 			    &fpndh);
135 			if (r != PICL_SUCCESS) {
136 				return (0);
137 			}
138 			r = ptree_get_propval_by_name(fpndh,
139 			    PICL_PROP_CLASSNAME, (void *)valbuf,
140 			    sizeof (valbuf));
141 			if (r != PICL_SUCCESS) {
142 				return (0);
143 			} else if (strcmp(valbuf, "fp") == 0) {
144 				/*
145 				 * The node with class fp (if present) is a
146 				 * holding node representing no actual hardware.
147 				 * Its presence results in two nodes with the
148 				 * same effective address. (The fp class node is
149 				 * UnitAddress 0,0 and the other fp node [class
150 				 * devctl] has bus-addr 0,0). Locating the
151 				 * required fp node for dynamic reconfiguration
152 				 * then goes wrong. So, just remove it.
153 				 */
154 				SYSLOG(LOG_WARNING, EM_SPURIOUS_FP);
155 				r = ptree_delete_node(fpndh);
156 				if (r == PICL_SUCCESS) {
157 					(void) ptree_destroy_node(fpndh);
158 					continue;
159 				}
160 				return (0);
161 			} else {
162 				break;
163 			}
164 		}
165 		/*
166 		 * Got a good parent node. Look at its children for a node
167 		 * with this new port name.
168 		 *
169 		 * generate expected bus-addr property from the port-wwn
170 		 * Note: dtls->disk_port[disk] points to an array of uchar_t,
171 		 * the first character contains the length of the residue.
172 		 * The bus-addr property is formatted as follows:
173 		 *	wabcdef0123456789,0
174 		 * where the 16 hex-digits represent 8 bytes from disk_port[];
175 		 */
176 		ptrs = dtls->disk_port[disk];
177 		if (ptrs == NULL)
178 			return (0);
179 		len = *ptrs++;
180 		ptrd = addr;
181 		*ptrd++ = 'w';
182 		for (r = 0; r < len; r++, ptrd += 2) {
183 			(void) snprintf(ptrd, MAXPATHLEN - (ptrd - addr),
184 			    "%.2x", *ptrs++);
185 		}
186 		addr_len = 1 + strlcat(addr, trailer, MAXPATHLEN);
187 		if (addr_len > MAXPATHLEN)
188 			return (0);
189 		r = ptree_find_node(fpndh, FCAL_PICL_PROP_BUS_ADDR,
190 		    PICL_PTYPE_CHARSTRING, addr, addr_len, &ssdndh);
191 		/*
192 		 * If the disk node corresponding to the newly inserted disk
193 		 * cannot be found in the platform tree, we have probably
194 		 * got in too early - probably before it's up to speed. In
195 		 * this case, the WWN gleaned from devinfo may also be wrong.
196 		 * This case is worth retrying in later polls when it may
197 		 * succeed, so return EAGAIN. All other failures are probably
198 		 * terminal, so log a failure and quit.
199 		 */
200 		if (r == PICL_NODENOTFOUND)
201 			return (EAGAIN);
202 		if (r != PICL_SUCCESS) {
203 			SYSLOG(LOG_ERR, EM_NO_FP_NODE, disk);
204 			return (0);
205 		}
206 
207 		/*
208 		 * Found platform entry for disk, add target prop
209 		 */
210 		r = ptree_init_propinfo(&propinfo, PTREE_PROPINFO_VERSION,
211 		    PICL_PTYPE_INT, PICL_READ, sizeof (int),
212 		    FCAL_PICL_PROP_TARGET, NULL, NULL);
213 		if (r != PICL_SUCCESS)
214 			return (0);
215 		(void) ptree_create_and_add_prop(ssdndh, &propinfo, &disk,
216 		    NULL);
217 
218 		/*
219 		 * Remove pre-existing disk-unit node and its
220 		 * properties - maybe its reference property is
221 		 * out-of-date.
222 		 */
223 		delete_disk_unit(dtls, disk);
224 
225 		/*
226 		 * Add a disk-unit node in frutree
227 		 */
228 		r = find_disk_slot(dtls, disk, &slotndh);
229 		if (r != PICL_SUCCESS)
230 			return (0);
231 		r = ptree_create_and_add_node(slotndh, fcal_disk_unit,
232 		    PICL_CLASS_FRU, &diskndh);
233 		if (r != PICL_SUCCESS)
234 			return (0);
235 		r = create_Device_table(&tbl_h, &tbl_prop_h);
236 		if (r != PICL_SUCCESS)
237 			return (0);
238 		r = ptree_init_propinfo(&propinfo,
239 		    PTREE_PROPINFO_VERSION, PICL_PTYPE_CHARSTRING,
240 		    PICL_READ, sizeof (PICL_CLASS_BLOCK), PICL_PROP_CLASS,
241 		    NULL, NULL);
242 		if (r != PICL_SUCCESS)
243 			return (0);
244 		r = ptree_create_prop(&propinfo, PICL_CLASS_BLOCK,
245 		    &row_props_h[0]);
246 		if (r != PICL_SUCCESS)
247 			return (0);
248 		r = ptree_init_propinfo(&propinfo, PTREE_PROPINFO_VERSION,
249 		    PICL_PTYPE_REFERENCE, PICL_READ, sizeof (picl_prophdl_t),
250 		    FCAL_PICL_BLOCK_REF, NULL, NULL);
251 		if (r != PICL_SUCCESS)
252 			return (0);
253 		r = ptree_create_prop(&propinfo, &ssdndh, &row_props_h[1]);
254 		if (r != PICL_SUCCESS)
255 			return (0);
256 		r = ptree_add_row_to_table(tbl_h, FCAL_DEVTABLE_NCOLS,
257 		    row_props_h);
258 		if (r != PICL_SUCCESS)
259 			return (0);
260 		(void) ptree_add_prop(diskndh, tbl_prop_h);
261 	} else {
262 		/*
263 		 * disk gone, remove disk_unit fru from frutree
264 		 */
265 		delete_disk_unit(dtls, disk);
266 	}
267 	return (0);
268 }
269 
270 static int
get_drv_info(di_node_t node,led_dtls_t * dtls)271 get_drv_info(di_node_t node, led_dtls_t *dtls)
272 {
273 	int *target_data;
274 	uchar_t *port_data = NULL;
275 	di_minor_t min_node;
276 	int i, r;
277 	int t = -1;
278 	int *newStatus = malloc(dtls->n_disks * sizeof (int));
279 	if (newStatus == NULL)
280 		return (0);
281 
282 	for (i = 0; i < dtls->n_disks; i++) {
283 		newStatus[i] = MINORS_UNKNOWN;
284 	}
285 	r = di_prop_lookup_ints(DDI_DEV_T_ANY, node, HW_PROP_TARGET,
286 	    &target_data);
287 	for (i = 0; i < r; i++) {
288 		t = target_data[i];
289 		if ((t >= 0) && (t < dtls->n_disks)) {
290 			/* set no minors until we know */
291 			newStatus[t] = NO_MINORS;
292 			break;			/* go with this node */
293 		}
294 	}
295 	if ((t >= 0) && (t < dtls->n_disks)) {
296 		r = di_prop_lookup_bytes(
297 		    DDI_DEV_T_ANY, node, HW_PROP_PORT, &port_data);
298 		/*
299 		 * The first byte of the array dtls->disk_port[t] contains
300 		 * the length of the residue. So 255 is the maximum length
301 		 * which can be handled. Limit the property data to this.
302 		 */
303 		if (r > 255) {
304 			r = 0;
305 		}
306 		if ((r > 0) && (port_data != NULL)) {
307 			if ((dtls->disk_port[t] != NULL) &&
308 			    (*(dtls->disk_port[t]) != r)) {
309 				/*
310 				 * existing data is of different length,
311 				 * free it and malloc a fresh array.
312 				 */
313 				free(dtls->disk_port[t]);
314 				dtls->disk_port[t] = NULL;
315 			}
316 			if (dtls->disk_port[t] == NULL) {
317 				dtls->disk_port[t] = malloc(r + 1);
318 			}
319 			if (dtls->disk_port[t] != NULL) {
320 				*(dtls->disk_port[t]) = (uchar_t)r;
321 				(void) memcpy(dtls->disk_port[t] + 1,
322 				    port_data, r);
323 			}
324 		}
325 		min_node = di_minor_next(node, DI_MINOR_NIL);
326 		if (min_node != DI_MINOR_NIL) {
327 			/*
328 			 * device has minor device node(s)
329 			 */
330 			newStatus[t] = HAS_MINORS;	/* got minor(s) */
331 		}
332 	}
333 	/*
334 	 * propagate attachment status and note changes
335 	 * don't propagate to absent disks, otherwise we may not detect a
336 	 * status change when they're replugged.
337 	 */
338 	r = 0;
339 	for (i = 0; i < dtls->n_disks; i++) {
340 		if ((newStatus[i] != MINORS_UNKNOWN) &&
341 		    dtls->disk_detected[i] &&
342 		    (dtls->disk_ready[i] != newStatus[i])) {
343 			dtls->disk_ready[i] = newStatus[i];
344 			r = 1;
345 		}
346 	}
347 	free(newStatus);
348 	return (r);
349 }
350 
351 /*
352  * Nodes belonging to the configured driver (dtls->fcal_driver) are
353  * located in the device tree. A check is applied that any node found has
354  * a physical address beginning with the configured search string
355  * (dtls->fcal_disk_parent). For each suitable node found, get_drv_info()
356  * is called to determine if a change of status has occurred.
357  * Returns 1 if any status has changed - else 0.
358  */
359 static int
walk_disks(di_node_t node,led_dtls_t * dtls)360 walk_disks(di_node_t node, led_dtls_t *dtls)
361 {
362 	static char *sl_platform_sl = "/platform/";
363 	int r = 0;
364 	int len;
365 	/* find "/platform/" */
366 	char *ptr = strstr(dtls->fcal_disk_parent, sl_platform_sl);
367 
368 	if (ptr == NULL)
369 		return (0);
370 	/* skip over "/platform" */
371 	ptr += strlen(sl_platform_sl) - 1;
372 	len = strlen(ptr);
373 
374 	for (node = di_drv_first_node(dtls->fcal_driver, node);
375 	    node != DI_NODE_NIL;
376 	    node = di_drv_next_node(node)) {
377 		char *dev_path = di_devfs_path(node);
378 
379 		if (dev_path == NULL) {
380 			/* no memory, just hope things get better */
381 			continue;
382 		}
383 		if (memcmp(dev_path, ptr, len) != 0) {
384 			/*
385 			 * path name doesn't start right, skip this one
386 			 */
387 			free(dev_path);
388 			continue;
389 		}
390 		free(dev_path);
391 		if (get_drv_info(node, dtls) != 0) {
392 			r = 1;	/* change observed */
393 		}
394 	}
395 
396 	return (r);
397 }
398 
399 static int
chk_minors(led_dtls_t * dtls)400 chk_minors(led_dtls_t *dtls)
401 {
402 	/*
403 	 * sets disk_ready flags for disks with minor devices (attached)
404 	 * returns 1 if any flags have changed else 0
405 	 */
406 	int err = 0;
407 	int r = 0;
408 	di_node_t tree = di_init("/", DINFOCPYALL);
409 	if (tree == DI_NODE_NIL) {
410 		err = errno;
411 		SYSLOG(LOG_ERR, EM_DI_INIT_FAIL, mystrerror(err));
412 	}
413 	if (err == 0)
414 		r = walk_disks(tree, dtls);
415 	if (tree != DI_NODE_NIL)
416 		di_fini(tree);
417 	return (r);
418 }
419 
420 boolean_t
is_led_test(led_dtls_t * dtls)421 is_led_test(led_dtls_t *dtls)
422 {
423 	int disk;
424 	for (disk = 0; disk < dtls->n_disks; disk++) {
425 		if (dtls->led_test_end[disk] != 0)
426 			return (B_TRUE);
427 	}
428 	return (B_FALSE);
429 }
430 
431 static int
set_clr_led(int diskNo,token_t led_tok,led_dtls_t * dtls,int set)432 set_clr_led(int diskNo, token_t led_tok, led_dtls_t *dtls, int set)
433 {
434 	int err, led, disk, led_bit;
435 	i2c_port_t port;
436 	int mask = 0;
437 	int fd = open(dtls->fcal_leds, O_RDWR);
438 	if (fd < 0)
439 		return (0);
440 	/*
441 	 * generate a mask for all controlled LEDs
442 	 */
443 	for (led = 0; led < FCAL_LED_CNT; led++) {
444 		for (disk = 0; disk < dtls->n_disks; disk++) {
445 			mask |= dtls->led_addr[led][disk];
446 		}
447 	}
448 	port.value = 0;
449 	port.direction = DIR_INPUT;
450 	port.dir_mask = (uint8_t)mask;
451 	/* read current light settings */
452 	err = ioctl(fd, I2C_GET_PORT, &port);
453 	if (err < 0) {
454 		(void) close(fd);
455 		return (EAGAIN);
456 	}
457 	mask = port.value;
458 	/*
459 	 * get bit setting for led to be changed
460 	 */
461 	led = led_tok - LED_PROPS_START - 1;
462 	led_bit = dtls->led_addr[led][diskNo];
463 	if (dtls->assert_led_on == 0) {
464 		if (set == 0)
465 			mask |= led_bit;
466 		else
467 			mask &= ~led_bit;
468 	} else {
469 		if (set == 0)
470 			mask &= ~led_bit;
471 		else
472 			mask |= led_bit;
473 	}
474 
475 	/*
476 	 * re-write the leds
477 	 */
478 	port.value = (uint8_t)mask;
479 	err = ioctl(fd, I2C_SET_PORT, &port);
480 	(void) close(fd);
481 	if (err == 0)
482 		return (0);
483 	return (EAGAIN);
484 }
485 
486 static void
set_led(int diskNo,token_t led_tok,led_dtls_t * dtls)487 set_led(int diskNo, token_t led_tok, led_dtls_t *dtls)
488 {
489 	if (set_clr_led(diskNo, led_tok, dtls, 1) != 0)
490 		dtls->led_retry = B_TRUE;
491 	dtls->led_state[led_tok - LED_PROPS_START - 1][diskNo] =
492 	    ((dtls->led_test_end[diskNo] != 0) ?
493 	    LED_STATE_TEST : LED_STATE_ON);
494 }
495 
496 void
clr_led(int diskNo,token_t led_tok,led_dtls_t * dtls)497 clr_led(int diskNo, token_t led_tok, led_dtls_t *dtls)
498 {
499 	if (set_clr_led(diskNo, led_tok, dtls, 0) != 0)
500 		dtls->led_retry = B_TRUE;
501 	dtls->led_state[led_tok - LED_PROPS_START - 1][diskNo] =
502 	    LED_STATE_OFF;
503 }
504 
505 /*
506  * have another go at getting the leds in step with required values
507  */
508 static void
retry_led(led_dtls_t * dtls)509 retry_led(led_dtls_t *dtls)
510 {
511 	int		r = 0;
512 	int		onFlag;
513 	int		diskNo;
514 	int		ledNo;
515 	led_state_t	state;
516 
517 	for (diskNo = 0; diskNo < dtls->n_disks; diskNo++) {
518 		for (ledNo = 0; ledNo < FCAL_LED_CNT; ledNo++) {
519 			state = dtls->led_state[ledNo][diskNo];
520 			if ((state == LED_STATE_ON) ||
521 			    (state == LED_STATE_TEST))
522 				onFlag = 1;
523 			else
524 				onFlag = 0;
525 			r |= set_clr_led(diskNo, LED_PROPS_START + 1 + ledNo,
526 			    dtls, onFlag);
527 		}
528 	}
529 
530 	dtls->led_retry = (r != 0);
531 }
532 
533 static void
start_led_test(led_dtls_t * dtls,int disk)534 start_led_test(led_dtls_t *dtls, int disk)
535 {
536 	int			led_no;
537 
538 	/*
539 	 * if the poll thread has failed, can't do led test
540 	 */
541 	if (!dtls->polling)
542 		return;
543 
544 	/*
545 	 * set test interval - doubles as flag for LED-test in progress
546 	 */
547 	dtls->led_test_end[disk] = dtls->led_test_time;
548 	for (led_no = 1; led_no <= FCAL_LED_CNT; led_no++) {
549 		set_led(disk, LED_PROPS_START + led_no, dtls);
550 	}
551 }
552 
553 static void
end_led_test(led_dtls_t * dtls,int disk)554 end_led_test(led_dtls_t *dtls, int disk)
555 {
556 	/*
557 	 * There is a problem with a disk coming on-line.
558 	 * All its leds are lit for 10 seconds to meet the led-test
559 	 * requirement. The true state for the fault led can be determined
560 	 * immediately, but determination of whether to light blue or green
561 	 * requires a response from libdevinfo. Device reconfiguration logic
562 	 * (likely to be active at this time) holds a long term
563 	 * lock preventing devinfo calls from completing. Rather than
564 	 * leave a contradictory led indication showing during this
565 	 * period, it is better to anticipate the green led result
566 	 * and correct it when the facts are known.
567 	 */
568 	clr_led(disk, FCAL_REMOK_LED, dtls);
569 	clr_led(disk, FCAL_FAULT_LED, dtls);
570 	dtls->led_state[FCAL_READY_LED - LED_PROPS_START - 1][disk] =
571 	    LED_STATE_ON;
572 }
573 
574 /*
575  * Evaluate required wait time and wait until that time or an event.
576  * Returns 0 for a time-out, otherwise the pending event(s).
577  * If the finish_now flag is becomes set, this routine acknowledges
578  * the request and waits for it to go away,
579  * i.e. no return while finish_now is set.
580  */
581 static int
wait_a_while(void)582 wait_a_while(void)
583 {
584 	int	r;
585 	int	events;
586 	boolean_t acksent = B_FALSE;
587 
588 	do {
589 		r = pthread_mutex_lock(&g_mutex);
590 		if (r != 0) {
591 			SYSLOG(LOG_ERR, EM_MUTEX_FAIL, mystrerror(r));
592 			return (0);
593 		}
594 		if (g_finish_now && !acksent) {
595 			g_leds_thread_ack = B_TRUE;
596 			(void) pthread_cond_signal(&g_cv_ack);
597 			acksent = B_TRUE;
598 		}
599 		r = pthread_cond_wait(&g_cv, &g_mutex);
600 		if (r != 0) {
601 			SYSLOG(LOG_ERR, EM_CONDWAITFAIL, mystrerror(r));
602 			(void) pthread_mutex_unlock(&g_mutex);
603 			return (0);
604 		}
605 		/*
606 		 * whilst under the mutex, take a local copy of the events
607 		 * and clear those that we handle
608 		 */
609 		events = g_event_flag & (FCAL_EV_POLL | FCAL_EV_CONFIG);
610 		g_event_flag ^= events;
611 		(void) pthread_mutex_unlock(&g_mutex);
612 	} while (g_finish_now);
613 
614 	return (events);
615 }
616 
617 /*ARGSUSED*/
618 void *
fcal_leds_thread(void * args)619 fcal_leds_thread(void *args)
620 {
621 	led_dtls_t *dtls = g_led_dtls;
622 	int c, v;
623 	int err = 0;
624 	int events = 0;
625 	int fd_bkplane;
626 	i2c_port_t port;
627 	int lastVal = I2C_IOCTL_INIT;
628 	int ws;
629 	int mask;
630 
631 	/*
632 	 * generate a mask for presence and fault status bits
633 	 */
634 	mask = 0;
635 	for (c = 0; c < dtls->n_disks; c++) {
636 		mask |= dtls->presence[c];
637 		mask |= dtls->faults[c];
638 	}
639 
640 	/*
641 	 * enter poll loop
642 	 */
643 	for (;;) {
644 		/*
645 		 * see if a LED-test timer has expired
646 		 */
647 		for (c = 0; c < dtls->n_disks; c++) {
648 			if (dtls->led_test_end[c] > 0) {
649 				if (!dtls->polling) {
650 					/* poll thread failure, end led-test */
651 					dtls->led_test_end[c] = 0;
652 				} else if ((events & FCAL_EV_POLL) != 0) {
653 					dtls->led_test_end[c]--;
654 				}
655 				if (dtls->led_test_end[c] == 0) {
656 					/*
657 					 * clear blue and amber leds
658 					 */
659 					end_led_test(dtls, c);
660 					/* treat any status as a change */
661 					lastVal = I2C_IOCTL_INIT;
662 				}
663 			}
664 		}
665 		fd_bkplane = open(dtls->fcal_status, O_RDONLY);
666 		if (fd_bkplane < 0) {
667 			SYSLOG(LOG_ERR, EM_CANT_OPEN, dtls->fcal_status);
668 			err = errno;
669 			break;
670 		}
671 		port.value = 0;
672 		/*
673 		 * the direction and dir_mask fields are ignored,
674 		 * so one can only guess at their possible use
675 		 */
676 		port.direction = DIR_INPUT;
677 		port.dir_mask = (uint8_t)mask;
678 		c = ioctl(fd_bkplane, I2C_GET_PORT, &port);
679 		if (c < 0) {
680 			err = errno;
681 			(void) close(fd_bkplane);
682 
683 			if (lastVal != I2C_IOCTL_FAIL) {
684 				SYSLOG(LOG_ERR, EM_I2C_GET_PORT,
685 				    mystrerror(err));
686 				lastVal = I2C_IOCTL_FAIL;
687 				events |= FCAL_EV_CONFIG;
688 			}
689 		} else {
690 			(void) close(fd_bkplane);
691 			ws = port.value & mask;
692 		}
693 
694 		if ((c == 0) && (ws != lastVal)) {
695 			events |= FCAL_EV_CONFIG;
696 			lastVal = ws;
697 			for (c = 0; c < dtls->n_disks; c++) {
698 				/*
699 				 * first get the value of the relevant
700 				 * presence bit (as 0 or 1)
701 				 */
702 				v = ((lastVal & dtls->presence[c]) != 0);
703 
704 				/* hold previous presence value */
705 				ws = dtls->disk_detected[c];
706 
707 				/*
708 				 * the disk is present if the backplane
709 				 * status bit for this disk is equal to the
710 				 * configured assert_presence value
711 				 */
712 				dtls->disk_detected[c] =
713 				    (v == dtls->assert_presence);
714 				/*
715 				 * Don't add disk-unit node here for
716 				 * newly arrived disks. While the led
717 				 * test is running (and beyond)
718 				 * libdevinfo is locked out and we
719 				 * can't get port or target info.
720 				 */
721 				if ((!ws) && dtls->disk_detected[c]) {
722 					/*
723 					 * disk has just come on-line
724 					 */
725 					start_led_test(dtls, c);
726 				}
727 				/*
728 				 * clear leds and ready status
729 				 * for disks which have been removed
730 				 */
731 				if (ws && (!dtls->disk_detected[c])) {
732 					clr_led(c, FCAL_REMOK_LED, dtls);
733 					clr_led(c, FCAL_FAULT_LED, dtls);
734 					clr_led(c, FCAL_READY_LED, dtls);
735 					dtls->disk_ready[c] = NO_MINORS;
736 					dtls->disk_prev[c] = NO_MINORS;
737 					v = update_picl(dtls, c);
738 					/*
739 					 * set or clear retry flag
740 					 */
741 					dtls->picl_retry[c] = (v == EAGAIN);
742 				}
743 				/*
744 				 * for present disks which are not doing a
745 				 * led test, adjust fault LED
746 				 */
747 				if ((dtls->led_test_end[c] != 0) ||
748 				    (!dtls->disk_detected[c]))
749 					continue;
750 				v = ((lastVal & dtls->faults[c]) != 0);
751 				if (v == dtls->assert_fault)
752 					set_led(c, FCAL_FAULT_LED, dtls);
753 				else
754 					clr_led(c, FCAL_FAULT_LED, dtls);
755 			}
756 		}
757 
758 		/*
759 		 * For detected disks whose status has changed, choose between
760 		 * ready and ok to remove.
761 		 * libdevinfo can be locked out for the entire duration of a
762 		 * disk spin-up. So it is best not to seek this info while
763 		 * a led-test is in progress. Otherwise the leds can be stuck
764 		 * on for about 40 seconds.
765 		 * Note that chk_minors() returns 0 unless a status change
766 		 * has occurred.
767 		 */
768 		if (!is_led_test(dtls) && chk_minors(dtls) != 0) {
769 			events = FCAL_EV_CONFIG;
770 			for (c = 0; c < dtls->n_disks; c++) {
771 				if (!dtls->disk_detected[c])
772 					continue;
773 				/*
774 				 * When disk_ready changes, disk_prev is set
775 				 * to its previous value. This allows the
776 				 * direction of the last transistion to be
777 				 * determined.
778 				 */
779 				if ((dtls->disk_prev[c] == HAS_MINORS) &&
780 				    (dtls->disk_ready[c] == NO_MINORS)) {
781 					clr_led(c, FCAL_READY_LED, dtls);
782 					set_led(c, FCAL_REMOK_LED, dtls);
783 				} else {
784 					set_led(c, FCAL_READY_LED, dtls);
785 					clr_led(c, FCAL_REMOK_LED, dtls);
786 				}
787 			}
788 		}
789 		/*
790 		 * Update PICL (disk-unit) for newly attached disks
791 		 * ** see note in header file for significance
792 		 *    of disk_prev and disk_ready flags.
793 		 */
794 		for (c = 0; c < dtls->n_disks; c++) {
795 			if ((dtls->disk_prev[c] == NO_MINORS) &&
796 			    (dtls->disk_ready[c] == HAS_MINORS)) {
797 				dtls->disk_prev[c] = HAS_MINORS;
798 				v = update_picl(dtls, c);
799 				/*
800 				 * set or clear retry flag
801 				 */
802 				dtls->picl_retry[c] = (v == EAGAIN);
803 			}
804 		}
805 		if ((events & FCAL_EV_CONFIG) != 0) {
806 			/*
807 			 * set fast polling
808 			 */
809 			dtls->fast_poll_end = dtls->relax_time_ticks;
810 		}
811 		/*
812 		 * if updating a led failed (e.g. I2C busy), try again
813 		 */
814 		if (dtls->led_retry)
815 			retry_led(dtls);
816 
817 		events = wait_a_while();
818 
819 		/*
820 		 * when picl is recycled, wait_a_while sleeps until the
821 		 * init routine has been called again.
822 		 * This is the moment when dtls may have become stale.
823 		 */
824 		if (dtls != g_led_dtls) {
825 			dtls = g_led_dtls;
826 			lastVal = I2C_IOCTL_INIT;
827 
828 			/*
829 			 * re-generate the presence and fault status mask
830 			 * in case the .conf file has changed
831 			 */
832 			mask = 0;
833 			for (c = 0; c < dtls->n_disks; c++) {
834 				mask |= dtls->presence[c];
835 				mask |= dtls->faults[c];
836 			}
837 		}
838 
839 		/*
840 		 * count down relaxation time counter if a poll event
841 		 */
842 		if ((events & FCAL_EV_POLL) != 0) {
843 			if (dtls->fast_poll_end > 0)
844 				dtls->fast_poll_end--;
845 		}
846 
847 		/*
848 		 * if updating PICL needs retrying, try it now
849 		 */
850 		for (c = 0; c < dtls->n_disks; c++) {
851 			if (dtls->picl_retry[c]) {
852 				v = update_picl(dtls, c);
853 				dtls->picl_retry[c] = (v == EAGAIN);
854 			}
855 		}
856 	}
857 
858 	return ((void *)err);
859 }
860