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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <sys/types.h>
30 #include <sys/sysevent/dr.h>
31 #include <sys/sysevent/eventdefs.h>
32 #include <sys/sunddi.h>	/* for the EC's for DEVFS */
33 
34 #include <errno.h>
35 #include <string.h>
36 #include <strings.h>
37 #include <stdio.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <pthread.h>
41 
42 #include <libsysevent.h>
43 #include <sys/sysevent_impl.h>
44 
45 #include <libnvpair.h>
46 #include <config_admin.h>
47 
48 #include "disk_monitor.h"
49 #include "hotplug_mgr.h"
50 #include "schg_mgr.h"
51 #include "dm_platform.h"
52 
53 typedef struct sysevent_event {
54 	sysevent_t	*evp;
55 } sysevent_event_t;
56 
57 /* Lock guarantees the ordering of the incoming sysevents */
58 static pthread_t g_sysev_tid;
59 static pthread_mutex_t g_event_handler_lock = PTHREAD_MUTEX_INITIALIZER;
60 static pthread_cond_t g_event_handler_cond = PTHREAD_COND_INITIALIZER;
61 static qu_t *g_sysev_queue = NULL;
62 static thread_state_t g_sysev_thread_state = TS_NOT_RUNNING;
63 /*
64  * The sysevent handle is bound to the main sysevent handler
65  * (event_handler), for each of the hotplug sysevents.
66  */
67 static sysevent_handle_t *sysevent_handle = NULL;
68 
69 static void free_sysevent_event(void *p);
70 
71 static int
72 nsleep(int seconds)
73 {
74 	struct timespec tspec;
75 
76 	tspec.tv_sec = seconds;
77 	tspec.tv_nsec = 0;
78 
79 	return (nanosleep(&tspec, NULL));
80 }
81 
82 static int
83 config_list_ext_poll(int num, char * const *path,
84     cfga_list_data_t **list_array, int *nlist, int flag)
85 {
86 	boolean_t done = B_FALSE;
87 	boolean_t timedout = B_FALSE;
88 	boolean_t interrupted = B_FALSE;
89 	int timeout = 0;
90 	int e;
91 #define	TIMEOUT_MAX 60
92 
93 	do {
94 		switch ((e = config_list_ext(num, path, list_array,
95 		    nlist, NULL, NULL, NULL, flag))) {
96 
97 		case CFGA_OK:
98 
99 			return (CFGA_OK);
100 
101 		case CFGA_BUSY:
102 		case CFGA_SYSTEM_BUSY:
103 
104 			if (timeout++ >= TIMEOUT_MAX)
105 				timedout = B_TRUE;
106 			else {
107 				if (nsleep(1) < 0)
108 					interrupted = (errno == EINTR);
109 			}
110 			break;
111 
112 		default:
113 			done = B_TRUE;
114 			break;
115 
116 		}
117 	} while (!done && !timedout && !interrupted);
118 
119 	return (e);
120 }
121 
122 /*
123  * Given a physical attachment point with a dynamic component
124  * (as in the case of SCSI APs), ensure the 'controller'
125  * portion of the dynamic component matches the physical portion.
126  * Argument 'adjusted' must point to a buffer of at least
127  * MAXPATHLEN bytes.
128  */
129 void
130 adjust_dynamic_ap(const char *apid, char *adjusted)
131 {
132 	cfga_list_data_t *list_array = NULL;
133 	int nlist;
134 	char *ap_path[1];
135 	char phys[MAXPATHLEN];
136 	char dev_phys[MAXPATHLEN];
137 	char *dyn;
138 	int c, t, d;
139 
140 	dm_assert((strlen(apid) + 8 /* strlen("/devices") */) < MAXPATHLEN);
141 
142 	/* In the case of any error, return the unadjusted APID */
143 	(void) strcpy(adjusted, apid);
144 
145 	/* if AP is not dynamic or not a disk node, no need to adjust it */
146 	dyn = strstr(apid, "::");
147 	if ((dyn == NULL) || (dyn == apid) ||
148 	    (sscanf(dyn, "::dsk/c%dt%dd%d", &c, &t, &d) != 3))
149 		return;
150 
151 	/*
152 	 * Copy the AP_ID and terminate it at the '::' that we know
153 	 * for a fact it contains.  Pre-pend '/devices' for the sake
154 	 * of cfgadm_scsi, and get the cfgadm data for the controller.
155 	 */
156 	(void) strcpy(phys, apid);
157 	*strstr(phys, "::") = '\0';
158 	(void) snprintf(dev_phys, MAXPATHLEN, "/devices%s", phys);
159 	ap_path[0] = dev_phys;
160 
161 	if (config_list_ext_poll(1, ap_path, &list_array, &nlist, 0)
162 	    != CFGA_OK)
163 		return;
164 
165 	dm_assert(nlist == 1);
166 
167 	if (sscanf(list_array[0].ap_log_id, "c%d", &c) == 1)
168 		(void) snprintf(adjusted, MAXPATHLEN, "%s::dsk/c%dt%dd%d",
169 		    phys, c, t, d);
170 
171 	free(list_array);
172 }
173 
174 static int
175 disk_ap_is_scsi(const char *ap_path)
176 {
177 	return (strstr(ap_path, ":scsi:") != NULL);
178 }
179 
180 /*
181  * Looks up the attachment point's state and returns it in one of
182  * the hotplug states that the state change manager understands.
183  */
184 hotplug_state_t
185 disk_ap_state_to_hotplug_state(diskmon_t *diskp)
186 {
187 	hotplug_state_t state = HPS_UNKNOWN;
188 	cfga_list_data_t *list_array = NULL;
189 	int rv, nlist;
190 	char *app = (char *)dm_prop_lookup(diskp->app_props,
191 	    DISK_AP_PROP_APID);
192 	char adj_app[MAXPATHLEN];
193 	char *ap_path[1];
194 	char *devices_app;
195 	int len;
196 	boolean_t list_valid = B_FALSE;
197 
198 	dm_assert(app != NULL);
199 
200 	adjust_dynamic_ap(app, adj_app);
201 	ap_path[0] = adj_app;
202 	devices_app = NULL;
203 
204 	rv = config_list_ext_poll(1, ap_path, &list_array, &nlist,
205 	    CFGA_FLAG_LIST_ALL);
206 
207 	if (rv != CFGA_OK) {
208 		/*
209 		 * The SATA and SCSI libcfgadm plugins add a
210 		 * /devices to the phys id; to use it, we must
211 		 * prepend this string before the call.
212 		 */
213 		len = 8 /* strlen("/devices") */ + strlen(adj_app) + 1;
214 		devices_app = dmalloc(len);
215 		(void) snprintf(devices_app, len, "/devices%s",
216 		    adj_app);
217 		ap_path[0] = devices_app;
218 
219 		rv = config_list_ext_poll(1, ap_path, &list_array, &nlist,
220 		    CFGA_FLAG_LIST_ALL);
221 	}
222 
223 	/*
224 	 * cfgadm_scsi will return an error for an absent target,
225 	 * so treat an error as "absent"; otherwise, make sure
226 	 * cfgadm_xxx has returned a list of 1 item
227 	 */
228 	if (rv == CFGA_OK) {
229 		dm_assert(nlist == 1);
230 		list_valid = B_TRUE;
231 	} else if (disk_ap_is_scsi(ap_path[0]))
232 		state = HPS_ABSENT;
233 
234 	if (devices_app != NULL)
235 		dfree(devices_app, len);
236 
237 	if (list_valid) {
238 		/*
239 		 * The following truth table defines how each state is
240 		 * computed:
241 		 *
242 		 * +----------------------------------------------+
243 		 * |		  | o_state | r_state | condition |
244 		 * |		  +---------+---------+-----------|
245 		 * | Absent	  |Don'tCare|Disc/Empt|	Don'tCare |
246 		 * | Present	  |Unconfgrd|Connected|	 unknown  |
247 		 * | Configured	  |Configred|Connected|	Don'tCare |
248 		 * | Unconfigured |Unconfgrd|Connected|	   OK	  |
249 		 * +--------------+---------+---------+-----------+
250 		 */
251 
252 		if (list_array[0].ap_r_state == CFGA_STAT_EMPTY ||
253 		    list_array[0].ap_r_state == CFGA_STAT_DISCONNECTED)
254 			state = HPS_ABSENT;
255 		else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED &&
256 		    list_array[0].ap_o_state == CFGA_STAT_UNCONFIGURED &&
257 		    list_array[0].ap_cond == CFGA_COND_UNKNOWN)
258 			state = HPS_PRESENT;
259 		else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED &&
260 		    list_array[0].ap_o_state == CFGA_STAT_UNCONFIGURED &&
261 		    list_array[0].ap_cond != CFGA_COND_UNKNOWN)
262 			state = HPS_UNCONFIGURED;
263 		else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED &&
264 		    list_array[0].ap_o_state == CFGA_STAT_CONFIGURED)
265 			state = HPS_CONFIGURED;
266 
267 		free(list_array);
268 	}
269 
270 	return (state);
271 }
272 
273 /*
274  * Examine the sysevent passed in and returns the hotplug state that
275  * the sysevent states (or implies, in the case of attachment point
276  * events).
277  */
278 static hotplug_state_t
279 disk_sysev_to_state(diskmon_t *diskp, sysevent_t *evp)
280 {
281 	const char *class_name, *subclass;
282 	hotplug_state_t state = HPS_UNKNOWN;
283 	sysevent_value_t se_val;
284 
285 	/*
286 	 * The state mapping is as follows:
287 	 *
288 	 * Sysevent				State
289 	 * --------------------------------------------------------
290 	 * EC_DEVFS/ESC_DEVFS_DEVI_ADD		Configured
291 	 * EC_DEVFS/ESC_DEVFS_DEVI_REMOVE	Unconfigured
292 	 * EC_DR/ESC_DR_AP_STATE_CHANGE		*[Absent/Present]
293 	 *
294 	 * (The EC_DR event requires a probe of the attachment point
295 	 * to determine the AP's state if there is no usable HINT)
296 	 *
297 	 */
298 
299 	class_name = sysevent_get_class_name(evp);
300 	subclass = sysevent_get_subclass_name(evp);
301 
302 	if (strcmp(class_name, EC_DEVFS) == 0) {
303 		if (strcmp(subclass, ESC_DEVFS_DEVI_ADD) == 0) {
304 
305 			state = HPS_CONFIGURED;
306 
307 		} else if (strcmp(subclass, ESC_DEVFS_DEVI_REMOVE) == 0) {
308 
309 			state = HPS_UNCONFIGURED;
310 
311 		}
312 
313 	} else if (strcmp(class_name, EC_DR) == 0 &&
314 	    ((strcmp(subclass, ESC_DR_AP_STATE_CHANGE) == 0) ||
315 	    (strcmp(subclass, ESC_DR_TARGET_STATE_CHANGE) == 0))) {
316 
317 		if (sysevent_lookup_attr(evp, DR_HINT, SE_DATA_TYPE_STRING,
318 		    &se_val) == 0 && se_val.value.sv_string != NULL) {
319 
320 			if (strcmp(se_val.value.sv_string, DR_HINT_INSERT)
321 			    == 0) {
322 
323 				state = HPS_PRESENT;
324 
325 			} else if (strcmp(se_val.value.sv_string,
326 			    DR_HINT_REMOVE) == 0) {
327 
328 				state = HPS_ABSENT;
329 			}
330 
331 		}
332 
333 		/*
334 		 * If the state could not be determined by the hint
335 		 * (or there was no hint), ask the AP directly.
336 		 * SCSI HBAs may send an insertion sysevent
337 		 * *after* configuring the target node, so double-
338 		 * check HPS_PRESENT
339 		 */
340 		if ((state == HPS_UNKNOWN) || (state = HPS_PRESENT))
341 			state = disk_ap_state_to_hotplug_state(diskp);
342 	}
343 
344 	return (state);
345 }
346 
347 static void
348 disk_split_ap_path_sata(const char *ap_path, char *device, int *target)
349 {
350 	char *p;
351 	int n;
352 
353 	/*
354 	 *  /devices/rootnode/.../device:target
355 	 */
356 	(void) strncpy(device, ap_path, MAXPATHLEN);
357 	p = strrchr(device, ':');
358 	dm_assert(p != NULL);
359 	n = sscanf(p, ":%d", target);
360 	dm_assert(n == 1);
361 	*p = '\0';
362 }
363 
364 static void
365 disk_split_ap_path_scsi(const char *ap_path, char *device, int *target)
366 {
367 	char *p;
368 	int n;
369 
370 	/*
371 	 *  /devices/rootnode/.../device:scsi::dsk/cXtXdX
372 	 */
373 
374 	(void) strncpy(device, ap_path, MAXPATHLEN);
375 	p = strrchr(device, ':');
376 	dm_assert(p != NULL);
377 
378 	n = sscanf(p, ":dsk/c%*dt%dd%*d", target);
379 	dm_assert(n == 1);
380 
381 	*strchr(device, ':') = '\0';
382 }
383 
384 static void
385 disk_split_ap_path(const char *ap_path, char *device, int *target)
386 {
387 	/*
388 	 * The AP path comes in two forms; for SATA devices,
389 	 * is is of the form:
390 	 *   /devices/rootnode/.../device:portnum
391 	 * and for SCSI devices, it is of the form:
392 	 *  /devices/rootnode/.../device:scsi::dsk/cXtXdX
393 	 */
394 
395 	if (disk_ap_is_scsi(ap_path))
396 		disk_split_ap_path_scsi(ap_path, device, target);
397 	else
398 		disk_split_ap_path_sata(ap_path, device, target);
399 }
400 
401 static void
402 disk_split_device_path(const char *dev_path, char *device, int *target)
403 {
404 	char *t, *p, *e;
405 
406 	/*
407 	 * The disk device path is of the form:
408 	 * /rootnode/.../device/target@tgtid,tgtlun
409 	 */
410 
411 	(void) strncpy(device, dev_path, MAXPATHLEN);
412 	e = t = strrchr(device, '/');
413 	dm_assert(t != NULL);
414 
415 	t = strchr(t, '@');
416 	dm_assert(t != NULL);
417 	t += 1;
418 
419 	if ((p = strchr(t, ',')) != NULL)
420 		*p = '\0';
421 
422 	*target = strtol(t, 0, 16);
423 	*e = '\0';
424 }
425 
426 /*
427  * Returns the diskmon that corresponds to the physical disk path
428  * passed in.
429  */
430 static diskmon_t *
431 disk_match_by_device_path(diskmon_t *disklistp, const char *dev_path)
432 {
433 	char dev_device[MAXPATHLEN];
434 	int dev_target;
435 	char ap_device[MAXPATHLEN];
436 	int ap_target;
437 
438 	dm_assert(disklistp != NULL);
439 	dm_assert(dev_path != NULL);
440 
441 	if (strncmp(dev_path, DEVICES_PREFIX, 8) == 0)
442 		dev_path += 8;
443 
444 	/* pare dev_path into device and target components */
445 	disk_split_device_path(dev_path, (char *)&dev_device, &dev_target);
446 
447 	/*
448 	 * The AP path specified in the configuration properties is
449 	 * the path to an attachment point minor node whose port number is
450 	 * equal to the target number on the disk "major" node sent by the
451 	 * sysevent.  To match them, we need to extract the target id and
452 	 * construct an AP string to compare to the AP path in the diskmon.
453 	 */
454 	while (disklistp != NULL) {
455 		char *app = (char *)dm_prop_lookup(disklistp->app_props,
456 		    DISK_AP_PROP_APID);
457 		dm_assert(app != NULL);
458 
459 		/* Not necessary to adjust the APID here */
460 		if (strncmp(app, DEVICES_PREFIX, 8) == 0)
461 			app += 8;
462 
463 		disk_split_ap_path(app, (char *)&ap_device, &ap_target);
464 
465 		if ((strcmp(dev_device, ap_device) == 0) &&
466 		    (dev_target == ap_target))
467 			return (disklistp);
468 
469 		disklistp = disklistp->next;
470 	}
471 	return (NULL);
472 }
473 
474 static diskmon_t *
475 disk_match_by_ap_id(diskmon_t *disklistp, const char *ap_id)
476 {
477 	const char *disk_ap_id;
478 	dm_assert(disklistp != NULL);
479 	dm_assert(ap_id != NULL);
480 
481 	/* Match only the device-tree portion of the name */
482 	if (strncmp(ap_id, DEVICES_PREFIX, 8 /* strlen("/devices") */) == 0)
483 		ap_id += 8;
484 
485 	while (disklistp != NULL) {
486 		disk_ap_id = dm_prop_lookup(disklistp->app_props,
487 		    DISK_AP_PROP_APID);
488 
489 		dm_assert(disk_ap_id != NULL);
490 
491 		if (strcmp(disk_ap_id, ap_id) == 0)
492 			return (disklistp);
493 
494 		disklistp = disklistp->next;
495 	}
496 	return (NULL);
497 }
498 
499 static diskmon_t *
500 disk_match_by_target_id(diskmon_t *disklistp, const char *target_path)
501 {
502 	const char *disk_ap_id;
503 
504 	char match_device[MAXPATHLEN];
505 	int match_target;
506 
507 	char ap_device[MAXPATHLEN];
508 	int ap_target;
509 
510 
511 	/* Match only the device-tree portion of the name */
512 	if (strncmp(target_path, DEVICES_PREFIX, 8) == 0)
513 		target_path += 8;
514 	disk_split_ap_path(target_path, (char *)&match_device, &match_target);
515 
516 	while (disklistp != NULL) {
517 
518 		disk_ap_id = dm_prop_lookup(disklistp->app_props,
519 		    DISK_AP_PROP_APID);
520 		dm_assert(disk_ap_id != NULL);
521 
522 		disk_split_ap_path(disk_ap_id, (char *)&ap_device, &ap_target);
523 		if ((match_target == ap_target) &&
524 		    (strcmp(match_device, ap_device) == 0))
525 			return (disklistp);
526 
527 		disklistp = disklistp->next;
528 	}
529 	return (NULL);
530 }
531 
532 static diskmon_t *
533 match_sysevent_to_disk(diskmon_t *disklistp, sysevent_t *evp)
534 {
535 	diskmon_t *dmp = NULL;
536 	sysevent_value_t se_val;
537 	char *class_name = sysevent_get_class_name(evp);
538 	char *subclass = sysevent_get_subclass_name(evp);
539 
540 	se_val.value.sv_string = NULL;
541 
542 	if (strcmp(class_name, EC_DEVFS) == 0) {
543 		/* EC_DEVFS-class events have a `DEVFS_PATHNAME' property */
544 		if (sysevent_lookup_attr(evp, DEVFS_PATHNAME,
545 		    SE_DATA_TYPE_STRING, &se_val) == 0 &&
546 		    se_val.value.sv_string != NULL) {
547 
548 			dmp = disk_match_by_device_path(disklistp,
549 			    se_val.value.sv_string);
550 
551 		}
552 
553 	} else if (strcmp(class_name, EC_DR) == 0 &&
554 	    strcmp(subclass, ESC_DR_AP_STATE_CHANGE) == 0) {
555 
556 		/* EC_DR-class events have a `DR_AP_ID' property */
557 		if (sysevent_lookup_attr(evp, DR_AP_ID, SE_DATA_TYPE_STRING,
558 		    &se_val) == 0 && se_val.value.sv_string != NULL) {
559 
560 			dmp = disk_match_by_ap_id(disklistp,
561 			    se_val.value.sv_string);
562 		}
563 	} else if (strcmp(class_name, EC_DR) == 0 &&
564 	    strcmp(subclass, ESC_DR_TARGET_STATE_CHANGE) == 0) {
565 		/* get DR_TARGET_ID */
566 		if (sysevent_lookup_attr(evp, DR_TARGET_ID,
567 		    SE_DATA_TYPE_STRING, &se_val) == 0 &&
568 		    se_val.value.sv_string != NULL) {
569 			dmp = disk_match_by_target_id(disklistp,
570 			    se_val.value.sv_string);
571 		}
572 	}
573 
574 	if (se_val.value.sv_string)
575 		log_msg(MM_HPMGR, "match_sysevent_to_disk: device/ap: %s\n",
576 		    se_val.value.sv_string);
577 
578 	return (dmp);
579 }
580 
581 
582 /*
583  * The disk hotplug monitor (DHPM) listens for disk hotplug events and calls the
584  * state-change functionality when a disk's state changes.  The DHPM listens for
585  * hotplug events via sysevent subscriptions to the following sysevent
586  * classes/subclasses: { EC_DEVFS/ESC_DEVFS_BRANCH_ADD,
587  * EC_DEVFS/ESC_DEVFS_BRANCH_REMOVE, EC_DEVFS/ESC_DEVFS_DEVI_ADD,
588  * EC_DEVFS/ESC_DEVFS_DEVI_REMOVE, EC_DR/ESC_DR_AP_STATE_CHANGE }.  Once the
589  * event is received, the device path sent as part of the event is matched
590  * to one of the disks described by the configuration data structures.
591  */
592 static void
593 dm_process_sysevent(sysevent_t *dupev)
594 {
595 	char		*class_name;
596 	char		*pub;
597 	char		*subclass = sysevent_get_subclass_name(dupev);
598 	diskmon_t	*diskp;
599 
600 	class_name = sysevent_get_class_name(dupev);
601 	log_msg(MM_HPMGR, "****EVENT: %s %s (by %s)\n", class_name,
602 	    subclass,
603 	    ((pub = sysevent_get_pub_name(dupev)) != NULL) ? pub : "UNKNOWN");
604 
605 	if (pub)
606 		free(pub);
607 
608 	if (strcmp(class_name, EC_PLATFORM) == 0 &&
609 	    strcmp(subclass, ESC_PLATFORM_SP_RESET) == 0) {
610 		if (dm_platform_resync() != 0)
611 			log_warn("failed to resync SP platform\n");
612 		return;
613 	}
614 
615 	/*
616 	 * We will handle this event if the event's target matches one of the
617 	 * disks we're monitoring
618 	 */
619 	if ((diskp = match_sysevent_to_disk(config_data->disk_list, dupev))
620 	    != NULL) {
621 
622 		dm_state_change(diskp, disk_sysev_to_state(diskp, dupev));
623 	}
624 
625 	sysevent_free(dupev);
626 }
627 
628 static void
629 dm_fmd_sysevent_thread(void *queuep)
630 {
631 	qu_t			*qp = (qu_t *)queuep;
632 	sysevent_event_t	*sevevp;
633 
634 	/* Signal the thread spawner that we're running */
635 	dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
636 	if (g_sysev_thread_state != TS_EXIT_REQUESTED)
637 		g_sysev_thread_state = TS_RUNNING;
638 	(void) pthread_cond_broadcast(&g_event_handler_cond);
639 	dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
640 
641 	while (g_sysev_thread_state != TS_EXIT_REQUESTED) {
642 		if ((sevevp = (sysevent_event_t *)queue_remove(qp)) == NULL)
643 			continue;
644 
645 		dm_process_sysevent(sevevp->evp);
646 
647 		free_sysevent_event(sevevp);
648 	}
649 
650 	/* Signal the thread spawner that we've exited */
651 	dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
652 	g_sysev_thread_state = TS_EXITED;
653 	(void) pthread_cond_broadcast(&g_event_handler_cond);
654 	dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
655 
656 	log_msg(MM_HPMGR, "FMD sysevent handler thread exiting...");
657 }
658 
659 static sysevent_event_t *
660 new_sysevent_event(sysevent_t *ev)
661 {
662 	/*
663 	 * Cannot use dmalloc for this because the thread isn't a FMD-created
664 	 * thread!
665 	 */
666 	sysevent_event_t *sevevp = malloc(sizeof (sysevent_event_t));
667 	sevevp->evp = ev;
668 	return (sevevp);
669 }
670 
671 static void
672 free_sysevent_event(void *p)
673 {
674 	/* the sysevent_event was allocated with malloc(): */
675 	free(p);
676 }
677 
678 static void
679 event_handler(sysevent_t *ev)
680 {
681 	/* The duplicated sysevent will be freed in the child thread */
682 	sysevent_t	*dupev = sysevent_dup(ev);
683 
684 	/*
685 	 * Add this sysevent to the work queue of our FMA thread so we can
686 	 * handle the sysevent and use the FMA API (e.g. for memory
687 	 * allocation, etc.) in the sysevent handler.
688 	 */
689 	queue_add(g_sysev_queue, new_sysevent_event(dupev));
690 }
691 
692 static void
693 fini_sysevents(void)
694 {
695 	sysevent_unsubscribe_event(sysevent_handle, EC_ALL);
696 }
697 
698 static int
699 init_sysevents(void)
700 {
701 	int rv = 0;
702 	const char *devfs_subclasses[] = {
703 		ESC_DEVFS_DEVI_ADD,
704 		ESC_DEVFS_DEVI_REMOVE
705 	};
706 	const char *dr_subclasses[] = {
707 		ESC_DR_AP_STATE_CHANGE,
708 		ESC_DR_TARGET_STATE_CHANGE
709 	};
710 	const char *platform_subclasses[] = {
711 		ESC_PLATFORM_SP_RESET
712 	};
713 
714 	if ((sysevent_handle = sysevent_bind_handle(event_handler)) == NULL) {
715 		rv = errno;
716 		log_err("Could not initialize the hotplug manager ("
717 		    "sysevent_bind_handle failure");
718 	}
719 
720 	if (sysevent_subscribe_event(sysevent_handle, EC_DEVFS,
721 	    devfs_subclasses,
722 	    sizeof (devfs_subclasses)/sizeof (devfs_subclasses[0])) != 0) {
723 
724 		log_err("Could not initialize the hotplug manager "
725 		    "sysevent_subscribe_event(event class = EC_DEVFS) "
726 		    "failure");
727 
728 		rv = -1;
729 
730 	} else if (sysevent_subscribe_event(sysevent_handle, EC_DR,
731 	    dr_subclasses,
732 	    sizeof (dr_subclasses)/sizeof (dr_subclasses[0])) != 0) {
733 
734 		log_err("Could not initialize the hotplug manager "
735 		    "sysevent_subscribe_event(event class = EC_DR) "
736 		    "failure");
737 
738 		/* Unsubscribe from all sysevents in the event of a failure */
739 		fini_sysevents();
740 
741 		rv = -1;
742 	} else if (sysevent_subscribe_event(sysevent_handle, EC_PLATFORM,
743 	    platform_subclasses,
744 	    sizeof (platform_subclasses)/sizeof (platform_subclasses[0]))
745 	    != 0) {
746 
747 		log_err("Could not initialize the hotplug manager "
748 		    "sysevent_subscribe_event(event class = EC_PLATFORM) "
749 		    "failure");
750 
751 		/* Unsubscribe from all sysevents in the event of a failure */
752 		fini_sysevents();
753 
754 		rv = -1;
755 	}
756 
757 
758 	return (rv);
759 }
760 
761 /*ARGSUSED*/
762 static void
763 stdfree(void *p, size_t sz)
764 {
765 	free(p);
766 }
767 
768 /*
769  * Assumptions: Each disk's current state was determined and stored in
770  * its diskmon_t.
771  */
772 hotplug_mgr_init_err_t
773 init_hotplug_manager()
774 {
775 	/* Create the queue to which we'll add sysevents */
776 	g_sysev_queue = new_queue(B_TRUE, malloc, stdfree, free_sysevent_event);
777 
778 	/*
779 	 * Grab the event handler lock before spawning the thread so we can
780 	 * wait for the thread to transition to the running state.
781 	 */
782 	dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
783 
784 	/* Create the sysevent handling thread */
785 	g_sysev_tid = fmd_thr_create(g_fm_hdl, dm_fmd_sysevent_thread,
786 	    g_sysev_queue);
787 
788 	/* Wait for the thread's acknowledgement */
789 	while (g_sysev_thread_state != TS_RUNNING)
790 		(void) pthread_cond_wait(&g_event_handler_cond,
791 		    &g_event_handler_lock);
792 	dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
793 
794 	if (init_sysevents() != 0) {
795 		log_warn_e("Error initializing sysevents");
796 		return (HPM_ERR_SYSEVENT_INIT);
797 	}
798 
799 	return (0);
800 }
801 
802 void
803 cleanup_hotplug_manager()
804 {
805 	/* Unsubscribe from the sysevents */
806 	fini_sysevents();
807 
808 	/*
809 	 * Wait for the thread to exit before we can destroy
810 	 * the event queue.
811 	 */
812 	dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
813 	g_sysev_thread_state = TS_EXIT_REQUESTED;
814 	queue_add(g_sysev_queue, NULL);
815 	while (g_sysev_thread_state != TS_EXITED)
816 		(void) pthread_cond_wait(&g_event_handler_cond,
817 		    &g_event_handler_lock);
818 	dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
819 	(void) pthread_join(g_sysev_tid, NULL);
820 	fmd_thr_destroy(g_fm_hdl, g_sysev_tid);
821 
822 	/* Finally, destroy the event queue and reset the thread state */
823 	queue_free(&g_sysev_queue);
824 	g_sysev_thread_state = TS_NOT_RUNNING;
825 }
826