1724365fsethg/*
2724365fsethg * CDDL HEADER START
3724365fsethg *
4724365fsethg * The contents of this file are subject to the terms of the
5724365fsethg * Common Development and Distribution License (the "License").
6724365fsethg * You may not use this file except in compliance with the License.
7724365fsethg *
8724365fsethg * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9724365fsethg * or http://www.opensolaris.org/os/licensing.
10724365fsethg * See the License for the specific language governing permissions
11724365fsethg * and limitations under the License.
12724365fsethg *
13724365fsethg * When distributing Covered Code, include this CDDL HEADER in each
14724365fsethg * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15724365fsethg * If applicable, add the following below this CDDL HEADER, with the
16724365fsethg * fields enclosed by brackets "[]" replaced with your own identifying
17724365fsethg * information: Portions Copyright [yyyy] [name of copyright owner]
18724365fsethg *
19724365fsethg * CDDL HEADER END
20724365fsethg */
21724365fsethg
22724365fsethg/*
23e5dcf7bRobert Johnston * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24724365fsethg * Use is subject to license terms.
25724365fsethg */
26724365fsethg
27724365fsethg#include <sys/types.h>
28724365fsethg#include <sys/sysevent/dr.h>
29724365fsethg#include <sys/sysevent/eventdefs.h>
30724365fsethg#include <sys/sunddi.h>	/* for the EC's for DEVFS */
31724365fsethg
32724365fsethg#include <errno.h>
33724365fsethg#include <string.h>
34724365fsethg#include <strings.h>
35724365fsethg#include <stdio.h>
36724365fsethg#include <unistd.h>
37724365fsethg#include <time.h>
38724365fsethg#include <pthread.h>
39724365fsethg
40724365fsethg#include <libsysevent.h>
41724365fsethg#include <sys/sysevent_impl.h>
42724365fsethg
43724365fsethg#include <libnvpair.h>
44724365fsethg#include <config_admin.h>
45724365fsethg
46184cd04cth#include "disk_monitor.h"
47724365fsethg#include "hotplug_mgr.h"
48724365fsethg#include "schg_mgr.h"
499113a79eschrock#include "dm_platform.h"
50724365fsethg
51724365fsethgtypedef struct sysevent_event {
52724365fsethg	sysevent_t	*evp;
53724365fsethg} sysevent_event_t;
54724365fsethg
55724365fsethg/* Lock guarantees the ordering of the incoming sysevents */
56724365fsethgstatic pthread_t g_sysev_tid;
57724365fsethgstatic pthread_mutex_t g_event_handler_lock = PTHREAD_MUTEX_INITIALIZER;
58724365fsethgstatic pthread_cond_t g_event_handler_cond = PTHREAD_COND_INITIALIZER;
59724365fsethgstatic qu_t *g_sysev_queue = NULL;
60724365fsethgstatic thread_state_t g_sysev_thread_state = TS_NOT_RUNNING;
61724365fsethg/*
62724365fsethg * The sysevent handle is bound to the main sysevent handler
63724365fsethg * (event_handler), for each of the hotplug sysevents.
64724365fsethg */
65724365fsethgstatic sysevent_handle_t *sysevent_handle = NULL;
66724365fsethg
67724365fsethgstatic void free_sysevent_event(void *p);
68724365fsethg
69724365fsethgstatic int
70724365fsethgnsleep(int seconds)
71724365fsethg{
72724365fsethg	struct timespec tspec;
73724365fsethg
74724365fsethg	tspec.tv_sec = seconds;
75724365fsethg	tspec.tv_nsec = 0;
76724365fsethg
77724365fsethg	return (nanosleep(&tspec, NULL));
78724365fsethg}
79724365fsethg
80724365fsethgstatic int
81724365fsethgconfig_list_ext_poll(int num, char * const *path,
82d95706amyers    cfga_list_data_t **list_array, int *nlist, int flag)
83724365fsethg{
84724365fsethg	boolean_t done = B_FALSE;
85724365fsethg	boolean_t timedout = B_FALSE;
86724365fsethg	boolean_t interrupted = B_FALSE;
87724365fsethg	int timeout = 0;
88724365fsethg	int e;
89724365fsethg#define	TIMEOUT_MAX 60
90724365fsethg
91724365fsethg	do {
92724365fsethg		switch ((e = config_list_ext(num, path, list_array,
93d95706amyers		    nlist, NULL, NULL, NULL, flag))) {
94724365fsethg
95724365fsethg		case CFGA_OK:
96724365fsethg
97724365fsethg			return (CFGA_OK);
98724365fsethg
99724365fsethg		case CFGA_BUSY:
100724365fsethg		case CFGA_SYSTEM_BUSY:
101724365fsethg
102724365fsethg			if (timeout++ >= TIMEOUT_MAX)
103724365fsethg				timedout = B_TRUE;
104724365fsethg			else {
105724365fsethg				if (nsleep(1) < 0)
106724365fsethg					interrupted = (errno == EINTR);
107724365fsethg			}
108724365fsethg			break;
109724365fsethg
110724365fsethg		default:
111724365fsethg			done = B_TRUE;
112724365fsethg			break;
113724365fsethg
114724365fsethg		}
115724365fsethg	} while (!done && !timedout && !interrupted);
116724365fsethg
117724365fsethg	return (e);
118724365fsethg}
119724365fsethg
120724365fsethg/*
121d95706amyers * Given a physical attachment point with a dynamic component
122d95706amyers * (as in the case of SCSI APs), ensure the 'controller'
123d95706amyers * portion of the dynamic component matches the physical portion.
124d95706amyers * Argument 'adjusted' must point to a buffer of at least
125d95706amyers * MAXPATHLEN bytes.
126d95706amyers */
127d95706amyersvoid
128d95706amyersadjust_dynamic_ap(const char *apid, char *adjusted)
129d95706amyers{
130d95706amyers	cfga_list_data_t *list_array = NULL;
131d95706amyers	int nlist;
132d95706amyers	char *ap_path[1];
133d95706amyers	char phys[MAXPATHLEN];
134d95706amyers	char dev_phys[MAXPATHLEN];
135d95706amyers	char *dyn;
136d95706amyers	int c, t, d;
137d95706amyers
138d95706amyers	dm_assert((strlen(apid) + 8 /* strlen("/devices") */) < MAXPATHLEN);
139d95706amyers
140d95706amyers	/* In the case of any error, return the unadjusted APID */
141d95706amyers	(void) strcpy(adjusted, apid);
142d95706amyers
143d95706amyers	/* if AP is not dynamic or not a disk node, no need to adjust it */
144d95706amyers	dyn = strstr(apid, "::");
145d95706amyers	if ((dyn == NULL) || (dyn == apid) ||
146d95706amyers	    (sscanf(dyn, "::dsk/c%dt%dd%d", &c, &t, &d) != 3))
147d95706amyers		return;
148d95706amyers
149d95706amyers	/*
150d95706amyers	 * Copy the AP_ID and terminate it at the '::' that we know
151d95706amyers	 * for a fact it contains.  Pre-pend '/devices' for the sake
152d95706amyers	 * of cfgadm_scsi, and get the cfgadm data for the controller.
153d95706amyers	 */
154d95706amyers	(void) strcpy(phys, apid);
155d95706amyers	*strstr(phys, "::") = '\0';
156d95706amyers	(void) snprintf(dev_phys, MAXPATHLEN, "/devices%s", phys);
157d95706amyers	ap_path[0] = dev_phys;
158d95706amyers
159d95706amyers	if (config_list_ext_poll(1, ap_path, &list_array, &nlist, 0)
160d95706amyers	    != CFGA_OK)
161d95706amyers		return;
162d95706amyers
163d95706amyers	dm_assert(nlist == 1);
164d95706amyers
165d95706amyers	if (sscanf(list_array[0].ap_log_id, "c%d", &c) == 1)
166d95706amyers		(void) snprintf(adjusted, MAXPATHLEN, "%s::dsk/c%dt%dd%d",
167d95706amyers		    phys, c, t, d);
168d95706amyers
169d95706amyers	free(list_array);
170d95706amyers}
171d95706amyers
172d95706amyersstatic int
173d95706amyersdisk_ap_is_scsi(const char *ap_path)
174d95706amyers{
175d95706amyers	return (strstr(ap_path, ":scsi:") != NULL);
176d95706amyers}
177d95706amyers
178d95706amyers/*
179724365fsethg * Looks up the attachment point's state and returns it in one of
180724365fsethg * the hotplug states that the state change manager understands.
181724365fsethg */
182724365fsethghotplug_state_t
183724365fsethgdisk_ap_state_to_hotplug_state(diskmon_t *diskp)
184724365fsethg{
185724365fsethg	hotplug_state_t state = HPS_UNKNOWN;
186724365fsethg	cfga_list_data_t *list_array = NULL;
187d95706amyers	int rv, nlist;
188724365fsethg	char *app = (char *)dm_prop_lookup(diskp->app_props,
189724365fsethg	    DISK_AP_PROP_APID);
190d95706amyers	char adj_app[MAXPATHLEN];
191724365fsethg	char *ap_path[1];
192724365fsethg	char *devices_app;
193724365fsethg	int len;
194724365fsethg	boolean_t list_valid = B_FALSE;
195724365fsethg
1967a0b67esethg	dm_assert(app != NULL);
197724365fsethg
198d95706amyers	adjust_dynamic_ap(app, adj_app);
199d95706amyers	ap_path[0] = adj_app;
200d95706amyers	devices_app = NULL;
201724365fsethg
202d95706amyers	rv = config_list_ext_poll(1, ap_path, &list_array, &nlist,
203d95706amyers	    CFGA_FLAG_LIST_ALL);
204724365fsethg
205d95706amyers	if (rv != CFGA_OK) {
206724365fsethg		/*
207d95706amyers		 * The SATA and SCSI libcfgadm plugins add a
208724365fsethg		 * /devices to the phys id; to use it, we must
209724365fsethg		 * prepend this string before the call.
210724365fsethg		 */
211d95706amyers		len = 8 /* strlen("/devices") */ + strlen(adj_app) + 1;
212724365fsethg		devices_app = dmalloc(len);
213724365fsethg		(void) snprintf(devices_app, len, "/devices%s",
214d95706amyers		    adj_app);
215724365fsethg		ap_path[0] = devices_app;
216724365fsethg
217d95706amyers		rv = config_list_ext_poll(1, ap_path, &list_array, &nlist,
218d95706amyers		    CFGA_FLAG_LIST_ALL);
219d95706amyers	}
220724365fsethg
221d95706amyers	/*
222d95706amyers	 * cfgadm_scsi will return an error for an absent target,
223d95706amyers	 * so treat an error as "absent"; otherwise, make sure
224d95706amyers	 * cfgadm_xxx has returned a list of 1 item
225d95706amyers	 */
226d95706amyers	if (rv == CFGA_OK) {
227d95706amyers		dm_assert(nlist == 1);
228d95706amyers		list_valid = B_TRUE;
229d95706amyers	} else if (disk_ap_is_scsi(ap_path[0]))
230d95706amyers		state = HPS_ABSENT;
231724365fsethg
232d95706amyers	if (devices_app != NULL)
233724365fsethg		dfree(devices_app, len);
234724365fsethg
235724365fsethg	if (list_valid) {
236724365fsethg		/*
237724365fsethg		 * The following truth table defines how each state is
238724365fsethg		 * computed:
239724365fsethg		 *
240724365fsethg		 * +----------------------------------------------+
241724365fsethg		 * |		  | o_state | r_state | condition |
242724365fsethg		 * |		  +---------+---------+-----------|
243724365fsethg		 * | Absent	  |Don'tCare|Disc/Empt|	Don'tCare |
244724365fsethg		 * | Present	  |Unconfgrd|Connected|	 unknown  |
245724365fsethg		 * | Configured	  |Configred|Connected|	Don'tCare |
246724365fsethg		 * | Unconfigured |Unconfgrd|Connected|	   OK	  |
247724365fsethg		 * +--------------+---------+---------+-----------+
248724365fsethg		 */
249724365fsethg
250724365fsethg		if (list_array[0].ap_r_state == CFGA_STAT_EMPTY ||
251724365fsethg		    list_array[0].ap_r_state == CFGA_STAT_DISCONNECTED)
252724365fsethg			state = HPS_ABSENT;
253724365fsethg		else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED &&
254724365fsethg		    list_array[0].ap_o_state == CFGA_STAT_UNCONFIGURED &&
255724365fsethg		    list_array[0].ap_cond == CFGA_COND_UNKNOWN)
256724365fsethg			state = HPS_PRESENT;
257724365fsethg		else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED &&
258724365fsethg		    list_array[0].ap_o_state == CFGA_STAT_UNCONFIGURED &&
259724365fsethg		    list_array[0].ap_cond != CFGA_COND_UNKNOWN)
260724365fsethg			state = HPS_UNCONFIGURED;
261724365fsethg		else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED &&
262724365fsethg		    list_array[0].ap_o_state == CFGA_STAT_CONFIGURED)
263724365fsethg			state = HPS_CONFIGURED;
264724365fsethg
265724365fsethg		free(list_array);
266724365fsethg	}
267724365fsethg
268724365fsethg	return (state);
269724365fsethg}
270724365fsethg
271724365fsethg/*
272724365fsethg * Examine the sysevent passed in and returns the hotplug state that
273724365fsethg * the sysevent states (or implies, in the case of attachment point
274724365fsethg * events).
275724365fsethg */
276724365fsethgstatic hotplug_state_t
277724365fsethgdisk_sysev_to_state(diskmon_t *diskp, sysevent_t *evp)
278724365fsethg{
279724365fsethg	const char *class_name, *subclass;
280724365fsethg	hotplug_state_t state = HPS_UNKNOWN;
281724365fsethg	sysevent_value_t se_val;
282724365fsethg
283724365fsethg	/*
284724365fsethg	 * The state mapping is as follows:
285724365fsethg	 *
286724365fsethg	 * Sysevent				State
287724365fsethg	 * --------------------------------------------------------
288724365fsethg	 * EC_DEVFS/ESC_DEVFS_DEVI_ADD		Configured
289724365fsethg	 * EC_DEVFS/ESC_DEVFS_DEVI_REMOVE	Unconfigured
290724365fsethg	 * EC_DR/ESC_DR_AP_STATE_CHANGE		*[Absent/Present]
291724365fsethg	 *
292724365fsethg	 * (The EC_DR event requires a probe of the attachment point
293724365fsethg	 * to determine the AP's state if there is no usable HINT)
294724365fsethg	 *
295724365fsethg	 */
296724365fsethg
297724365fsethg	class_name = sysevent_get_class_name(evp);
298724365fsethg	subclass = sysevent_get_subclass_name(evp);
299724365fsethg
300724365fsethg	if (strcmp(class_name, EC_DEVFS) == 0) {
301724365fsethg		if (strcmp(subclass, ESC_DEVFS_DEVI_ADD) == 0) {
302724365fsethg
303724365fsethg			state = HPS_CONFIGURED;
304724365fsethg
305724365fsethg		} else if (strcmp(subclass, ESC_DEVFS_DEVI_REMOVE) == 0) {
306724365fsethg
307724365fsethg			state = HPS_UNCONFIGURED;
308724365fsethg
309724365fsethg		}
310724365fsethg
311724365fsethg	} else if (strcmp(class_name, EC_DR) == 0 &&
312d95706amyers	    ((strcmp(subclass, ESC_DR_AP_STATE_CHANGE) == 0) ||
313d95706amyers	    (strcmp(subclass, ESC_DR_TARGET_STATE_CHANGE) == 0))) {
314724365fsethg
315724365fsethg		if (sysevent_lookup_attr(evp, DR_HINT, SE_DATA_TYPE_STRING,
316724365fsethg		    &se_val) == 0 && se_val.value.sv_string != NULL) {
317724365fsethg
318724365fsethg			if (strcmp(se_val.value.sv_string, DR_HINT_INSERT)
319724365fsethg			    == 0) {
320724365fsethg
321724365fsethg				state = HPS_PRESENT;
322724365fsethg
323724365fsethg			} else if (strcmp(se_val.value.sv_string,
324724365fsethg			    DR_HINT_REMOVE) == 0) {
325724365fsethg
326724365fsethg				state = HPS_ABSENT;
327724365fsethg			}
328724365fsethg
329724365fsethg		}
330724365fsethg
331724365fsethg		/*
332724365fsethg		 * If the state could not be determined by the hint
333724365fsethg		 * (or there was no hint), ask the AP directly.
334d95706amyers		 * SCSI HBAs may send an insertion sysevent
335d95706amyers		 * *after* configuring the target node, so double-
336d95706amyers		 * check HPS_PRESENT
337724365fsethg		 */
338d95706amyers		if ((state == HPS_UNKNOWN) || (state = HPS_PRESENT))
339724365fsethg			state = disk_ap_state_to_hotplug_state(diskp);
340724365fsethg	}
341724365fsethg
342724365fsethg	return (state);
343724365fsethg}
344724365fsethg
345d95706amyersstatic void
346d95706amyersdisk_split_ap_path_sata(const char *ap_path, char *device, int *target)
347d95706amyers{
348d95706amyers	char *p;
349d95706amyers	int n;
350d95706amyers
351d95706amyers	/*
352d95706amyers	 *  /devices/rootnode/.../device:target
353d95706amyers	 */
354d95706amyers	(void) strncpy(device, ap_path, MAXPATHLEN);
355d95706amyers	p = strrchr(device, ':');
356d95706amyers	dm_assert(p != NULL);
357d95706amyers	n = sscanf(p, ":%d", target);
358d95706amyers	dm_assert(n == 1);
359d95706amyers	*p = '\0';
360d95706amyers}
361d95706amyers
362d95706amyersstatic void
363d95706amyersdisk_split_ap_path_scsi(const char *ap_path, char *device, int *target)
364d95706amyers{
365d95706amyers	char *p;
366d95706amyers	int n;
367d95706amyers
368d95706amyers	/*
369d95706amyers	 *  /devices/rootnode/.../device:scsi::dsk/cXtXdX
370d95706amyers	 */
371d95706amyers
372d95706amyers	(void) strncpy(device, ap_path, MAXPATHLEN);
373d95706amyers	p = strrchr(device, ':');
374d95706amyers	dm_assert(p != NULL);
375d95706amyers
376d95706amyers	n = sscanf(p, ":dsk/c%*dt%dd%*d", target);
377d95706amyers	dm_assert(n == 1);
378d95706amyers
379d95706amyers	*strchr(device, ':') = '\0';
380d95706amyers}
381d95706amyers
382d95706amyersstatic void
383d95706amyersdisk_split_ap_path(const char *ap_path, char *device, int *target)
384d95706amyers{
385d95706amyers	/*
386d95706amyers	 * The AP path comes in two forms; for SATA devices,
387d95706amyers	 * is is of the form:
388d95706amyers	 *   /devices/rootnode/.../device:portnum
389d95706amyers	 * and for SCSI devices, it is of the form:
390d95706amyers	 *  /devices/rootnode/.../device:scsi::dsk/cXtXdX
391d95706amyers	 */
392d95706amyers
393d95706amyers	if (disk_ap_is_scsi(ap_path))
394d95706amyers		disk_split_ap_path_scsi(ap_path, device, target);
395d95706amyers	else
396d95706amyers		disk_split_ap_path_sata(ap_path, device, target);
397d95706amyers}
398d95706amyers
399d95706amyersstatic void
400d95706amyersdisk_split_device_path(const char *dev_path, char *device, int *target)
401d95706amyers{
402d95706amyers	char *t, *p, *e;
403d95706amyers
404d95706amyers	/*
405d95706amyers	 * The disk device path is of the form:
406d95706amyers	 * /rootnode/.../device/target@tgtid,tgtlun
407d95706amyers	 */
408d95706amyers
409d95706amyers	(void) strncpy(device, dev_path, MAXPATHLEN);
410d95706amyers	e = t = strrchr(device, '/');
411d95706amyers	dm_assert(t != NULL);
412d95706amyers
413d95706amyers	t = strchr(t, '@');
414d95706amyers	dm_assert(t != NULL);
415d95706amyers	t += 1;
416d95706amyers
417d95706amyers	if ((p = strchr(t, ',')) != NULL)
418d95706amyers		*p = '\0';
419d95706amyers
420d95706amyers	*target = strtol(t, 0, 16);
421d95706amyers	*e = '\0';
422d95706amyers}
423d95706amyers
424724365fsethg/*
425724365fsethg * Returns the diskmon that corresponds to the physical disk path
426724365fsethg * passed in.
427724365fsethg */
428724365fsethgstatic diskmon_t *
429724365fsethgdisk_match_by_device_path(diskmon_t *disklistp, const char *dev_path)
430724365fsethg{
431d95706amyers	char dev_device[MAXPATHLEN];
432d95706amyers	int dev_target;
433d95706amyers	char ap_device[MAXPATHLEN];
434d95706amyers	int ap_target;
435d95706amyers
4367a0b67esethg	dm_assert(disklistp != NULL);
4377a0b67esethg	dm_assert(dev_path != NULL);
438724365fsethg
439724365fsethg	if (strncmp(dev_path, DEVICES_PREFIX, 8) == 0)
440724365fsethg		dev_path += 8;
441724365fsethg
442d95706amyers	/* pare dev_path into device and target components */
443d95706amyers	disk_split_device_path(dev_path, (char *)&dev_device, &dev_target);
444d95706amyers
445724365fsethg	/*
446724365fsethg	 * The AP path specified in the configuration properties is
447724365fsethg	 * the path to an attachment point minor node whose port number is
448724365fsethg	 * equal to the target number on the disk "major" node sent by the
449724365fsethg	 * sysevent.  To match them, we need to extract the target id and
450724365fsethg	 * construct an AP string to compare to the AP path in the diskmon.
451724365fsethg	 */
452724365fsethg	while (disklistp != NULL) {
453724365fsethg		char *app = (char *)dm_prop_lookup(disklistp->app_props,
454724365fsethg		    DISK_AP_PROP_APID);
4557a0b67esethg		dm_assert(app != NULL);
456724365fsethg
457d95706amyers		/* Not necessary to adjust the APID here */
458724365fsethg		if (strncmp(app, DEVICES_PREFIX, 8) == 0)
459724365fsethg			app += 8;
460724365fsethg
461d95706amyers		disk_split_ap_path(app, (char *)&ap_device, &ap_target);
462724365fsethg
463d95706amyers		if ((strcmp(dev_device, ap_device) == 0) &&
464d95706amyers		    (dev_target == ap_target))
465724365fsethg			return (disklistp);
466724365fsethg
467724365fsethg		disklistp = disklistp->next;
468724365fsethg	}
469724365fsethg	return (NULL);
470724365fsethg}
471724365fsethg
472724365fsethgstatic diskmon_t *
473724365fsethgdisk_match_by_ap_id(diskmon_t *disklistp, const char *ap_id)
474724365fsethg{
475724365fsethg	const char *disk_ap_id;
4767a0b67esethg	dm_assert(disklistp != NULL);
4777a0b67esethg	dm_assert(ap_id != NULL);
478724365fsethg
479724365fsethg	/* Match only the device-tree portion of the name */
480724365fsethg	if (strncmp(ap_id, DEVICES_PREFIX, 8 /* strlen("/devices") */) == 0)
481724365fsethg		ap_id += 8;
482724365fsethg
483724365fsethg	while (disklistp != NULL) {
484724365fsethg		disk_ap_id = dm_prop_lookup(disklistp->app_props,
485724365fsethg		    DISK_AP_PROP_APID);
486724365fsethg
4877a0b67esethg		dm_assert(disk_ap_id != NULL);
488724365fsethg
489724365fsethg		if (strcmp(disk_ap_id, ap_id) == 0)
490724365fsethg			return (disklistp);
491724365fsethg
492724365fsethg		disklistp = disklistp->next;
493724365fsethg	}
494724365fsethg	return (NULL);
495724365fsethg}
496724365fsethg
497724365fsethgstatic diskmon_t *
498d95706amyersdisk_match_by_target_id(diskmon_t *disklistp, const char *target_path)
499d95706amyers{
500d95706amyers	const char *disk_ap_id;
501d95706amyers
502d95706amyers	char match_device[MAXPATHLEN];
503d95706amyers	int match_target;
504d95706amyers
505d95706amyers	char ap_device[MAXPATHLEN];
506d95706amyers	int ap_target;
507d95706amyers
508d95706amyers
509d95706amyers	/* Match only the device-tree portion of the name */
510d95706amyers	if (strncmp(target_path, DEVICES_PREFIX, 8) == 0)
511d95706amyers		target_path += 8;
512d95706amyers	disk_split_ap_path(target_path, (char *)&match_device, &match_target);
513d95706amyers
514d95706amyers	while (disklistp != NULL) {
515d95706amyers
516d95706amyers		disk_ap_id = dm_prop_lookup(disklistp->app_props,
517d95706amyers		    DISK_AP_PROP_APID);
518d95706amyers		dm_assert(disk_ap_id != NULL);
519d95706amyers
520d95706amyers		disk_split_ap_path(disk_ap_id, (char *)&ap_device, &ap_target);
521d95706amyers		if ((match_target == ap_target) &&
522d95706amyers		    (strcmp(match_device, ap_device) == 0))
523d95706amyers			return (disklistp);
524d95706amyers
525d95706amyers		disklistp = disklistp->next;
526d95706amyers	}
527d95706amyers	return (NULL);
528d95706amyers}
529d95706amyers
530d95706amyersstatic diskmon_t *
531724365fsethgmatch_sysevent_to_disk(diskmon_t *disklistp, sysevent_t *evp)
532724365fsethg{
533724365fsethg	diskmon_t *dmp = NULL;
534724365fsethg	sysevent_value_t se_val;
535724365fsethg	char *class_name = sysevent_get_class_name(evp);
536724365fsethg	char *subclass = sysevent_get_subclass_name(evp);
537724365fsethg
538724365fsethg	se_val.value.sv_string = NULL;
539724365fsethg
540724365fsethg	if (strcmp(class_name, EC_DEVFS) == 0) {
541724365fsethg		/* EC_DEVFS-class events have a `DEVFS_PATHNAME' property */
542724365fsethg		if (sysevent_lookup_attr(evp, DEVFS_PATHNAME,
543724365fsethg		    SE_DATA_TYPE_STRING, &se_val) == 0 &&
544724365fsethg		    se_val.value.sv_string != NULL) {
545724365fsethg
546724365fsethg			dmp = disk_match_by_device_path(disklistp,
547724365fsethg			    se_val.value.sv_string);
548724365fsethg
549724365fsethg		}
550724365fsethg
551724365fsethg	} else if (strcmp(class_name, EC_DR) == 0 &&
552724365fsethg	    strcmp(subclass, ESC_DR_AP_STATE_CHANGE) == 0) {
553724365fsethg
554724365fsethg		/* EC_DR-class events have a `DR_AP_ID' property */
555724365fsethg		if (sysevent_lookup_attr(evp, DR_AP_ID, SE_DATA_TYPE_STRING,
556724365fsethg		    &se_val) == 0 && se_val.value.sv_string != NULL) {
557724365fsethg
558724365fsethg			dmp = disk_match_by_ap_id(disklistp,
559724365fsethg			    se_val.value.sv_string);
560724365fsethg		}
561d95706amyers	} else if (strcmp(class_name, EC_DR) == 0 &&
562d95706amyers	    strcmp(subclass, ESC_DR_TARGET_STATE_CHANGE) == 0) {
563d95706amyers		/* get DR_TARGET_ID */
564d95706amyers		if (sysevent_lookup_attr(evp, DR_TARGET_ID,
565d95706amyers		    SE_DATA_TYPE_STRING, &se_val) == 0 &&
566d95706amyers		    se_val.value.sv_string != NULL) {
567d95706amyers			dmp = disk_match_by_target_id(disklistp,
568d95706amyers			    se_val.value.sv_string);
569d95706amyers		}
570724365fsethg	}
571724365fsethg
572724365fsethg	if (se_val.value.sv_string)
573724365fsethg		log_msg(MM_HPMGR, "match_sysevent_to_disk: device/ap: %s\n",
574724365fsethg		    se_val.value.sv_string);
575724365fsethg
576724365fsethg	return (dmp);
577724365fsethg}
578724365fsethg
579724365fsethg
580724365fsethg/*
581724365fsethg * The disk hotplug monitor (DHPM) listens for disk hotplug events and calls the
582724365fsethg * state-change functionality when a disk's state changes.  The DHPM listens for
583724365fsethg * hotplug events via sysevent subscriptions to the following sysevent
584724365fsethg * classes/subclasses: { EC_DEVFS/ESC_DEVFS_BRANCH_ADD,
585724365fsethg * EC_DEVFS/ESC_DEVFS_BRANCH_REMOVE, EC_DEVFS/ESC_DEVFS_DEVI_ADD,
586724365fsethg * EC_DEVFS/ESC_DEVFS_DEVI_REMOVE, EC_DR/ESC_DR_AP_STATE_CHANGE }.  Once the
587724365fsethg * event is received, the device path sent as part of the event is matched
588724365fsethg * to one of the disks described by the configuration data structures.
589724365fsethg */
590724365fsethgstatic void
591724365fsethgdm_process_sysevent(sysevent_t *dupev)
592724365fsethg{
593724365fsethg	char		*class_name;
594724365fsethg	char		*pub;
5959113a79eschrock	char		*subclass = sysevent_get_subclass_name(dupev);
596724365fsethg	diskmon_t	*diskp;
597724365fsethg
598724365fsethg	class_name = sysevent_get_class_name(dupev);
599724365fsethg	log_msg(MM_HPMGR, "****EVENT: %s %s (by %s)\n", class_name,
6009113a79eschrock	    subclass,
601724365fsethg	    ((pub = sysevent_get_pub_name(dupev)) != NULL) ? pub : "UNKNOWN");
602724365fsethg
603724365fsethg	if (pub)
604724365fsethg		free(pub);
605724365fsethg
6069113a79eschrock	if (strcmp(class_name, EC_PLATFORM) == 0 &&
6079113a79eschrock	    strcmp(subclass, ESC_PLATFORM_SP_RESET) == 0) {
6089113a79eschrock		if (dm_platform_resync() != 0)
6099113a79eschrock			log_warn("failed to resync SP platform\n");
610e5dcf7bRobert Johnston		sysevent_free(dupev);
6119113a79eschrock		return;
6129113a79eschrock	}
6139113a79eschrock
614724365fsethg	/*
615724365fsethg	 * We will handle this event if the event's target matches one of the
616724365fsethg	 * disks we're monitoring
617724365fsethg	 */
618724365fsethg	if ((diskp = match_sysevent_to_disk(config_data->disk_list, dupev))
619724365fsethg	    != NULL) {
620724365fsethg
621724365fsethg		dm_state_change(diskp, disk_sysev_to_state(diskp, dupev));
622724365fsethg	}
623724365fsethg
624724365fsethg	sysevent_free(dupev);
625724365fsethg}
626724365fsethg
627724365fsethgstatic void
628724365fsethgdm_fmd_sysevent_thread(void *queuep)
629724365fsethg{
630724365fsethg	qu_t			*qp = (qu_t *)queuep;
631724365fsethg	sysevent_event_t	*sevevp;
632724365fsethg
633724365fsethg	/* Signal the thread spawner that we're running */
6347a0b67esethg	dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
635724365fsethg	if (g_sysev_thread_state != TS_EXIT_REQUESTED)
636724365fsethg		g_sysev_thread_state = TS_RUNNING;
637724365fsethg	(void) pthread_cond_broadcast(&g_event_handler_cond);
6387a0b67esethg	dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
639724365fsethg
640724365fsethg	while (g_sysev_thread_state != TS_EXIT_REQUESTED) {
641724365fsethg		if ((sevevp = (sysevent_event_t *)queue_remove(qp)) == NULL)
642724365fsethg			continue;
643724365fsethg
644724365fsethg		dm_process_sysevent(sevevp->evp);
645724365fsethg
646724365fsethg		free_sysevent_event(sevevp);
647724365fsethg	}
648724365fsethg
649724365fsethg	/* Signal the thread spawner that we've exited */
6507a0b67esethg	dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
651724365fsethg	g_sysev_thread_state = TS_EXITED;
652724365fsethg	(void) pthread_cond_broadcast(&g_event_handler_cond);
6537a0b67esethg	dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
654724365fsethg
655724365fsethg	log_msg(MM_HPMGR, "FMD sysevent handler thread exiting...");
656724365fsethg}
657724365fsethg
658724365fsethgstatic sysevent_event_t *
659724365fsethgnew_sysevent_event(sysevent_t *ev)
660724365fsethg{
661724365fsethg	/*
662724365fsethg	 * Cannot use dmalloc for this because the thread isn't a FMD-created
663724365fsethg	 * thread!
664724365fsethg	 */
665724365fsethg	sysevent_event_t *sevevp = malloc(sizeof (sysevent_event_t));
666724365fsethg	sevevp->evp = ev;
667724365fsethg	return (sevevp);
668724365fsethg}
669724365fsethg
670724365fsethgstatic void
671724365fsethgfree_sysevent_event(void *p)
672724365fsethg{
673724365fsethg	/* the sysevent_event was allocated with malloc(): */
674724365fsethg	free(p);
675724365fsethg}
676724365fsethg
677724365fsethgstatic void
678724365fsethgevent_handler(sysevent_t *ev)
679724365fsethg{
680724365fsethg	/* The duplicated sysevent will be freed in the child thread */
681724365fsethg	sysevent_t	*dupev = sysevent_dup(ev);
682724365fsethg
683724365fsethg	/*
684724365fsethg	 * Add this sysevent to the work queue of our FMA thread so we can
685724365fsethg	 * handle the sysevent and use the FMA API (e.g. for memory
686724365fsethg	 * allocation, etc.) in the sysevent handler.
687724365fsethg	 */
688724365fsethg	queue_add(g_sysev_queue, new_sysevent_event(dupev));
689724365fsethg}
690724365fsethg
691724365fsethgstatic void
692724365fsethgfini_sysevents(void)
693724365fsethg{
694724365fsethg	sysevent_unsubscribe_event(sysevent_handle, EC_ALL);
695724365fsethg}
696724365fsethg
697724365fsethgstatic int
698724365fsethginit_sysevents(void)
699724365fsethg{
700724365fsethg	int rv = 0;
701724365fsethg	const char *devfs_subclasses[] = {
702724365fsethg		ESC_DEVFS_DEVI_ADD,
703724365fsethg		ESC_DEVFS_DEVI_REMOVE
704724365fsethg	};
705724365fsethg	const char *dr_subclasses[] = {
706d95706amyers		ESC_DR_AP_STATE_CHANGE,
707d95706amyers		ESC_DR_TARGET_STATE_CHANGE
708724365fsethg	};
7099113a79eschrock	const char *platform_subclasses[] = {
7109113a79eschrock		ESC_PLATFORM_SP_RESET
7119113a79eschrock	};
712724365fsethg
713724365fsethg	if ((sysevent_handle = sysevent_bind_handle(event_handler)) == NULL) {
714724365fsethg		rv = errno;
715724365fsethg		log_err("Could not initialize the hotplug manager ("
716724365fsethg		    "sysevent_bind_handle failure");
717724365fsethg	}
718724365fsethg
719724365fsethg	if (sysevent_subscribe_event(sysevent_handle, EC_DEVFS,
720d95706amyers	    devfs_subclasses,
721d95706amyers	    sizeof (devfs_subclasses)/sizeof (devfs_subclasses[0])) != 0) {
722724365fsethg
723724365fsethg		log_err("Could not initialize the hotplug manager "
724724365fsethg		    "sysevent_subscribe_event(event class = EC_DEVFS) "
725724365fsethg		    "failure");
726724365fsethg
727724365fsethg		rv = -1;
728724365fsethg
729724365fsethg	} else if (sysevent_subscribe_event(sysevent_handle, EC_DR,
730d95706amyers	    dr_subclasses,
731d95706amyers	    sizeof (dr_subclasses)/sizeof (dr_subclasses[0])) != 0) {
732724365fsethg
733724365fsethg		log_err("Could not initialize the hotplug manager "
734724365fsethg		    "sysevent_subscribe_event(event class = EC_DR) "
735724365fsethg		    "failure");
736724365fsethg
737724365fsethg		/* Unsubscribe from all sysevents in the event of a failure */
738724365fsethg		fini_sysevents();
739724365fsethg
740724365fsethg		rv = -1;
7419113a79eschrock	} else if (sysevent_subscribe_event(sysevent_handle, EC_PLATFORM,
742d95706amyers	    platform_subclasses,
743d95706amyers	    sizeof (platform_subclasses)/sizeof (platform_subclasses[0]))
744d95706amyers	    != 0) {
7459113a79eschrock
7469113a79eschrock		log_err("Could not initialize the hotplug manager "
7479113a79eschrock		    "sysevent_subscribe_event(event class = EC_PLATFORM) "
7489113a79eschrock		    "failure");
7499113a79eschrock
7509113a79eschrock		/* Unsubscribe from all sysevents in the event of a failure */
7519113a79eschrock		fini_sysevents();
7529113a79eschrock
7539113a79eschrock		rv = -1;
754724365fsethg	}
755724365fsethg
7569113a79eschrock
757724365fsethg	return (rv);
758724365fsethg}
759724365fsethg
760724365fsethg/*ARGSUSED*/
761724365fsethgstatic void
762724365fsethgstdfree(void *p, size_t sz)
763724365fsethg{
764724365fsethg	free(p);
765724365fsethg}
766724365fsethg
767724365fsethg/*
768724365fsethg * Assumptions: Each disk's current state was determined and stored in
769724365fsethg * its diskmon_t.
770724365fsethg */
771724365fsethghotplug_mgr_init_err_t
772724365fsethginit_hotplug_manager()
773724365fsethg{
774724365fsethg	/* Create the queue to which we'll add sysevents */
775724365fsethg	g_sysev_queue = new_queue(B_TRUE, malloc, stdfree, free_sysevent_event);
776724365fsethg
777724365fsethg	/*
778724365fsethg	 * Grab the event handler lock before spawning the thread so we can
779724365fsethg	 * wait for the thread to transition to the running state.
780724365fsethg	 */
7817a0b67esethg	dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
782724365fsethg
783724365fsethg	/* Create the sysevent handling thread */
784724365fsethg	g_sysev_tid = fmd_thr_create(g_fm_hdl, dm_fmd_sysevent_thread,
785724365fsethg	    g_sysev_queue);
786724365fsethg
787724365fsethg	/* Wait for the thread's acknowledgement */
788724365fsethg	while (g_sysev_thread_state != TS_RUNNING)
789724365fsethg		(void) pthread_cond_wait(&g_event_handler_cond,
790724365fsethg		    &g_event_handler_lock);
7917a0b67esethg	dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
792724365fsethg
793724365fsethg	if (init_sysevents() != 0) {
794724365fsethg		log_warn_e("Error initializing sysevents");
795724365fsethg		return (HPM_ERR_SYSEVENT_INIT);
796724365fsethg	}
797724365fsethg
798724365fsethg	return (0);
799724365fsethg}
800724365fsethg
801724365fsethgvoid
802724365fsethgcleanup_hotplug_manager()
803724365fsethg{
804724365fsethg	/* Unsubscribe from the sysevents */
805724365fsethg	fini_sysevents();
806724365fsethg
807724365fsethg	/*
808724365fsethg	 * Wait for the thread to exit before we can destroy
809724365fsethg	 * the event queue.
810724365fsethg	 */
8117a0b67esethg	dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
812724365fsethg	g_sysev_thread_state = TS_EXIT_REQUESTED;
813724365fsethg	queue_add(g_sysev_queue, NULL);
814724365fsethg	while (g_sysev_thread_state != TS_EXITED)
815724365fsethg		(void) pthread_cond_wait(&g_event_handler_cond,
816724365fsethg		    &g_event_handler_lock);
8177a0b67esethg	dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
818724365fsethg	(void) pthread_join(g_sysev_tid, NULL);
819724365fsethg	fmd_thr_destroy(g_fm_hdl, g_sysev_tid);
820724365fsethg
821724365fsethg	/* Finally, destroy the event queue and reset the thread state */
822724365fsethg	queue_free(&g_sysev_queue);
823724365fsethg	g_sysev_thread_state = TS_NOT_RUNNING;
824724365fsethg}
825