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