xref: /onnv-gate/usr/src/cmd/fm/modules/common/disk-monitor/hotplug_mgr.c (revision 8526:8159d305568c)
14582Scth /*
24582Scth  * CDDL HEADER START
34582Scth  *
44582Scth  * The contents of this file are subject to the terms of the
54582Scth  * Common Development and Distribution License (the "License").
64582Scth  * You may not use this file except in compliance with the License.
74582Scth  *
84582Scth  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
94582Scth  * or http://www.opensolaris.org/os/licensing.
104582Scth  * See the License for the specific language governing permissions
114582Scth  * and limitations under the License.
124582Scth  *
134582Scth  * When distributing Covered Code, include this CDDL HEADER in each
144582Scth  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
154582Scth  * If applicable, add the following below this CDDL HEADER, with the
164582Scth  * fields enclosed by brackets "[]" replaced with your own identifying
174582Scth  * information: Portions Copyright [yyyy] [name of copyright owner]
184582Scth  *
194582Scth  * CDDL HEADER END
204582Scth  */
214582Scth 
224582Scth /*
23*8526SRobert.Johnston@Sun.COM  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
244582Scth  * Use is subject to license terms.
254582Scth  */
264582Scth 
274582Scth #include <sys/types.h>
284582Scth #include <sys/sysevent/dr.h>
294582Scth #include <sys/sysevent/eventdefs.h>
304582Scth #include <sys/sunddi.h>	/* for the EC's for DEVFS */
314582Scth 
324582Scth #include <errno.h>
334582Scth #include <string.h>
344582Scth #include <strings.h>
354582Scth #include <stdio.h>
364582Scth #include <unistd.h>
374582Scth #include <time.h>
384582Scth #include <pthread.h>
394582Scth 
404582Scth #include <libsysevent.h>
414582Scth #include <sys/sysevent_impl.h>
424582Scth 
434582Scth #include <libnvpair.h>
444582Scth #include <config_admin.h>
454582Scth 
464582Scth #include "disk_monitor.h"
474582Scth #include "hotplug_mgr.h"
484582Scth #include "schg_mgr.h"
494582Scth #include "dm_platform.h"
504582Scth 
514582Scth typedef struct sysevent_event {
524582Scth 	sysevent_t	*evp;
534582Scth } sysevent_event_t;
544582Scth 
554582Scth /* Lock guarantees the ordering of the incoming sysevents */
564582Scth static pthread_t g_sysev_tid;
574582Scth static pthread_mutex_t g_event_handler_lock = PTHREAD_MUTEX_INITIALIZER;
584582Scth static pthread_cond_t g_event_handler_cond = PTHREAD_COND_INITIALIZER;
594582Scth static qu_t *g_sysev_queue = NULL;
604582Scth static thread_state_t g_sysev_thread_state = TS_NOT_RUNNING;
614582Scth /*
624582Scth  * The sysevent handle is bound to the main sysevent handler
634582Scth  * (event_handler), for each of the hotplug sysevents.
644582Scth  */
654582Scth static sysevent_handle_t *sysevent_handle = NULL;
664582Scth 
674582Scth static void free_sysevent_event(void *p);
684582Scth 
694582Scth static int
nsleep(int seconds)704582Scth nsleep(int seconds)
714582Scth {
724582Scth 	struct timespec tspec;
734582Scth 
744582Scth 	tspec.tv_sec = seconds;
754582Scth 	tspec.tv_nsec = 0;
764582Scth 
774582Scth 	return (nanosleep(&tspec, NULL));
784582Scth }
794582Scth 
804582Scth static int
config_list_ext_poll(int num,char * const * path,cfga_list_data_t ** list_array,int * nlist,int flag)814582Scth config_list_ext_poll(int num, char * const *path,
825117Smyers     cfga_list_data_t **list_array, int *nlist, int flag)
834582Scth {
844582Scth 	boolean_t done = B_FALSE;
854582Scth 	boolean_t timedout = B_FALSE;
864582Scth 	boolean_t interrupted = B_FALSE;
874582Scth 	int timeout = 0;
884582Scth 	int e;
894582Scth #define	TIMEOUT_MAX 60
904582Scth 
914582Scth 	do {
924582Scth 		switch ((e = config_list_ext(num, path, list_array,
935117Smyers 		    nlist, NULL, NULL, NULL, flag))) {
944582Scth 
954582Scth 		case CFGA_OK:
964582Scth 
974582Scth 			return (CFGA_OK);
984582Scth 
994582Scth 		case CFGA_BUSY:
1004582Scth 		case CFGA_SYSTEM_BUSY:
1014582Scth 
1024582Scth 			if (timeout++ >= TIMEOUT_MAX)
1034582Scth 				timedout = B_TRUE;
1044582Scth 			else {
1054582Scth 				if (nsleep(1) < 0)
1064582Scth 					interrupted = (errno == EINTR);
1074582Scth 			}
1084582Scth 			break;
1094582Scth 
1104582Scth 		default:
1114582Scth 			done = B_TRUE;
1124582Scth 			break;
1134582Scth 
1144582Scth 		}
1154582Scth 	} while (!done && !timedout && !interrupted);
1164582Scth 
1174582Scth 	return (e);
1184582Scth }
1194582Scth 
1204582Scth /*
1215117Smyers  * Given a physical attachment point with a dynamic component
1225117Smyers  * (as in the case of SCSI APs), ensure the 'controller'
1235117Smyers  * portion of the dynamic component matches the physical portion.
1245117Smyers  * Argument 'adjusted' must point to a buffer of at least
1255117Smyers  * MAXPATHLEN bytes.
1265117Smyers  */
1275117Smyers void
adjust_dynamic_ap(const char * apid,char * adjusted)1285117Smyers adjust_dynamic_ap(const char *apid, char *adjusted)
1295117Smyers {
1305117Smyers 	cfga_list_data_t *list_array = NULL;
1315117Smyers 	int nlist;
1325117Smyers 	char *ap_path[1];
1335117Smyers 	char phys[MAXPATHLEN];
1345117Smyers 	char dev_phys[MAXPATHLEN];
1355117Smyers 	char *dyn;
1365117Smyers 	int c, t, d;
1375117Smyers 
1385117Smyers 	dm_assert((strlen(apid) + 8 /* strlen("/devices") */) < MAXPATHLEN);
1395117Smyers 
1405117Smyers 	/* In the case of any error, return the unadjusted APID */
1415117Smyers 	(void) strcpy(adjusted, apid);
1425117Smyers 
1435117Smyers 	/* if AP is not dynamic or not a disk node, no need to adjust it */
1445117Smyers 	dyn = strstr(apid, "::");
1455117Smyers 	if ((dyn == NULL) || (dyn == apid) ||
1465117Smyers 	    (sscanf(dyn, "::dsk/c%dt%dd%d", &c, &t, &d) != 3))
1475117Smyers 		return;
1485117Smyers 
1495117Smyers 	/*
1505117Smyers 	 * Copy the AP_ID and terminate it at the '::' that we know
1515117Smyers 	 * for a fact it contains.  Pre-pend '/devices' for the sake
1525117Smyers 	 * of cfgadm_scsi, and get the cfgadm data for the controller.
1535117Smyers 	 */
1545117Smyers 	(void) strcpy(phys, apid);
1555117Smyers 	*strstr(phys, "::") = '\0';
1565117Smyers 	(void) snprintf(dev_phys, MAXPATHLEN, "/devices%s", phys);
1575117Smyers 	ap_path[0] = dev_phys;
1585117Smyers 
1595117Smyers 	if (config_list_ext_poll(1, ap_path, &list_array, &nlist, 0)
1605117Smyers 	    != CFGA_OK)
1615117Smyers 		return;
1625117Smyers 
1635117Smyers 	dm_assert(nlist == 1);
1645117Smyers 
1655117Smyers 	if (sscanf(list_array[0].ap_log_id, "c%d", &c) == 1)
1665117Smyers 		(void) snprintf(adjusted, MAXPATHLEN, "%s::dsk/c%dt%dd%d",
1675117Smyers 		    phys, c, t, d);
1685117Smyers 
1695117Smyers 	free(list_array);
1705117Smyers }
1715117Smyers 
1725117Smyers static int
disk_ap_is_scsi(const char * ap_path)1735117Smyers disk_ap_is_scsi(const char *ap_path)
1745117Smyers {
1755117Smyers 	return (strstr(ap_path, ":scsi:") != NULL);
1765117Smyers }
1775117Smyers 
1785117Smyers /*
1794582Scth  * Looks up the attachment point's state and returns it in one of
1804582Scth  * the hotplug states that the state change manager understands.
1814582Scth  */
1824582Scth hotplug_state_t
disk_ap_state_to_hotplug_state(diskmon_t * diskp)1834582Scth disk_ap_state_to_hotplug_state(diskmon_t *diskp)
1844582Scth {
1854582Scth 	hotplug_state_t state = HPS_UNKNOWN;
1864582Scth 	cfga_list_data_t *list_array = NULL;
1875117Smyers 	int rv, nlist;
1884582Scth 	char *app = (char *)dm_prop_lookup(diskp->app_props,
1894582Scth 	    DISK_AP_PROP_APID);
1905117Smyers 	char adj_app[MAXPATHLEN];
1914582Scth 	char *ap_path[1];
1924582Scth 	char *devices_app;
1934582Scth 	int len;
1944582Scth 	boolean_t list_valid = B_FALSE;
1954582Scth 
1964582Scth 	dm_assert(app != NULL);
1974582Scth 
1985117Smyers 	adjust_dynamic_ap(app, adj_app);
1995117Smyers 	ap_path[0] = adj_app;
2005117Smyers 	devices_app = NULL;
2014582Scth 
2025117Smyers 	rv = config_list_ext_poll(1, ap_path, &list_array, &nlist,
2035117Smyers 	    CFGA_FLAG_LIST_ALL);
2044582Scth 
2055117Smyers 	if (rv != CFGA_OK) {
2064582Scth 		/*
2075117Smyers 		 * The SATA and SCSI libcfgadm plugins add a
2084582Scth 		 * /devices to the phys id; to use it, we must
2094582Scth 		 * prepend this string before the call.
2104582Scth 		 */
2115117Smyers 		len = 8 /* strlen("/devices") */ + strlen(adj_app) + 1;
2124582Scth 		devices_app = dmalloc(len);
2134582Scth 		(void) snprintf(devices_app, len, "/devices%s",
2145117Smyers 		    adj_app);
2154582Scth 		ap_path[0] = devices_app;
2164582Scth 
2175117Smyers 		rv = config_list_ext_poll(1, ap_path, &list_array, &nlist,
2185117Smyers 		    CFGA_FLAG_LIST_ALL);
2195117Smyers 	}
2204582Scth 
2215117Smyers 	/*
2225117Smyers 	 * cfgadm_scsi will return an error for an absent target,
2235117Smyers 	 * so treat an error as "absent"; otherwise, make sure
2245117Smyers 	 * cfgadm_xxx has returned a list of 1 item
2255117Smyers 	 */
2265117Smyers 	if (rv == CFGA_OK) {
2275117Smyers 		dm_assert(nlist == 1);
2285117Smyers 		list_valid = B_TRUE;
2295117Smyers 	} else if (disk_ap_is_scsi(ap_path[0]))
2305117Smyers 		state = HPS_ABSENT;
2314582Scth 
2325117Smyers 	if (devices_app != NULL)
2334582Scth 		dfree(devices_app, len);
2344582Scth 
2354582Scth 	if (list_valid) {
2364582Scth 		/*
2374582Scth 		 * The following truth table defines how each state is
2384582Scth 		 * computed:
2394582Scth 		 *
2404582Scth 		 * +----------------------------------------------+
2414582Scth 		 * |		  | o_state | r_state | condition |
2424582Scth 		 * |		  +---------+---------+-----------|
2434582Scth 		 * | Absent	  |Don'tCare|Disc/Empt|	Don'tCare |
2444582Scth 		 * | Present	  |Unconfgrd|Connected|	 unknown  |
2454582Scth 		 * | Configured	  |Configred|Connected|	Don'tCare |
2464582Scth 		 * | Unconfigured |Unconfgrd|Connected|	   OK	  |
2474582Scth 		 * +--------------+---------+---------+-----------+
2484582Scth 		 */
2494582Scth 
2504582Scth 		if (list_array[0].ap_r_state == CFGA_STAT_EMPTY ||
2514582Scth 		    list_array[0].ap_r_state == CFGA_STAT_DISCONNECTED)
2524582Scth 			state = HPS_ABSENT;
2534582Scth 		else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED &&
2544582Scth 		    list_array[0].ap_o_state == CFGA_STAT_UNCONFIGURED &&
2554582Scth 		    list_array[0].ap_cond == CFGA_COND_UNKNOWN)
2564582Scth 			state = HPS_PRESENT;
2574582Scth 		else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED &&
2584582Scth 		    list_array[0].ap_o_state == CFGA_STAT_UNCONFIGURED &&
2594582Scth 		    list_array[0].ap_cond != CFGA_COND_UNKNOWN)
2604582Scth 			state = HPS_UNCONFIGURED;
2614582Scth 		else if (list_array[0].ap_r_state == CFGA_STAT_CONNECTED &&
2624582Scth 		    list_array[0].ap_o_state == CFGA_STAT_CONFIGURED)
2634582Scth 			state = HPS_CONFIGURED;
2644582Scth 
2654582Scth 		free(list_array);
2664582Scth 	}
2674582Scth 
2684582Scth 	return (state);
2694582Scth }
2704582Scth 
2714582Scth /*
2724582Scth  * Examine the sysevent passed in and returns the hotplug state that
2734582Scth  * the sysevent states (or implies, in the case of attachment point
2744582Scth  * events).
2754582Scth  */
2764582Scth static hotplug_state_t
disk_sysev_to_state(diskmon_t * diskp,sysevent_t * evp)2774582Scth disk_sysev_to_state(diskmon_t *diskp, sysevent_t *evp)
2784582Scth {
2794582Scth 	const char *class_name, *subclass;
2804582Scth 	hotplug_state_t state = HPS_UNKNOWN;
2814582Scth 	sysevent_value_t se_val;
2824582Scth 
2834582Scth 	/*
2844582Scth 	 * The state mapping is as follows:
2854582Scth 	 *
2864582Scth 	 * Sysevent				State
2874582Scth 	 * --------------------------------------------------------
2884582Scth 	 * EC_DEVFS/ESC_DEVFS_DEVI_ADD		Configured
2894582Scth 	 * EC_DEVFS/ESC_DEVFS_DEVI_REMOVE	Unconfigured
2904582Scth 	 * EC_DR/ESC_DR_AP_STATE_CHANGE		*[Absent/Present]
2914582Scth 	 *
2924582Scth 	 * (The EC_DR event requires a probe of the attachment point
2934582Scth 	 * to determine the AP's state if there is no usable HINT)
2944582Scth 	 *
2954582Scth 	 */
2964582Scth 
2974582Scth 	class_name = sysevent_get_class_name(evp);
2984582Scth 	subclass = sysevent_get_subclass_name(evp);
2994582Scth 
3004582Scth 	if (strcmp(class_name, EC_DEVFS) == 0) {
3014582Scth 		if (strcmp(subclass, ESC_DEVFS_DEVI_ADD) == 0) {
3024582Scth 
3034582Scth 			state = HPS_CONFIGURED;
3044582Scth 
3054582Scth 		} else if (strcmp(subclass, ESC_DEVFS_DEVI_REMOVE) == 0) {
3064582Scth 
3074582Scth 			state = HPS_UNCONFIGURED;
3084582Scth 
3094582Scth 		}
3104582Scth 
3114582Scth 	} else if (strcmp(class_name, EC_DR) == 0 &&
3125117Smyers 	    ((strcmp(subclass, ESC_DR_AP_STATE_CHANGE) == 0) ||
3135117Smyers 	    (strcmp(subclass, ESC_DR_TARGET_STATE_CHANGE) == 0))) {
3144582Scth 
3154582Scth 		if (sysevent_lookup_attr(evp, DR_HINT, SE_DATA_TYPE_STRING,
3164582Scth 		    &se_val) == 0 && se_val.value.sv_string != NULL) {
3174582Scth 
3184582Scth 			if (strcmp(se_val.value.sv_string, DR_HINT_INSERT)
3194582Scth 			    == 0) {
3204582Scth 
3214582Scth 				state = HPS_PRESENT;
3224582Scth 
3234582Scth 			} else if (strcmp(se_val.value.sv_string,
3244582Scth 			    DR_HINT_REMOVE) == 0) {
3254582Scth 
3264582Scth 				state = HPS_ABSENT;
3274582Scth 			}
3284582Scth 
3294582Scth 		}
3304582Scth 
3314582Scth 		/*
3324582Scth 		 * If the state could not be determined by the hint
3334582Scth 		 * (or there was no hint), ask the AP directly.
3345117Smyers 		 * SCSI HBAs may send an insertion sysevent
3355117Smyers 		 * *after* configuring the target node, so double-
3365117Smyers 		 * check HPS_PRESENT
3374582Scth 		 */
3385117Smyers 		if ((state == HPS_UNKNOWN) || (state = HPS_PRESENT))
3394582Scth 			state = disk_ap_state_to_hotplug_state(diskp);
3404582Scth 	}
3414582Scth 
3424582Scth 	return (state);
3434582Scth }
3444582Scth 
3455117Smyers static void
disk_split_ap_path_sata(const char * ap_path,char * device,int * target)3465117Smyers disk_split_ap_path_sata(const char *ap_path, char *device, int *target)
3475117Smyers {
3485117Smyers 	char *p;
3495117Smyers 	int n;
3505117Smyers 
3515117Smyers 	/*
3525117Smyers 	 *  /devices/rootnode/.../device:target
3535117Smyers 	 */
3545117Smyers 	(void) strncpy(device, ap_path, MAXPATHLEN);
3555117Smyers 	p = strrchr(device, ':');
3565117Smyers 	dm_assert(p != NULL);
3575117Smyers 	n = sscanf(p, ":%d", target);
3585117Smyers 	dm_assert(n == 1);
3595117Smyers 	*p = '\0';
3605117Smyers }
3615117Smyers 
3625117Smyers static void
disk_split_ap_path_scsi(const char * ap_path,char * device,int * target)3635117Smyers disk_split_ap_path_scsi(const char *ap_path, char *device, int *target)
3645117Smyers {
3655117Smyers 	char *p;
3665117Smyers 	int n;
3675117Smyers 
3685117Smyers 	/*
3695117Smyers 	 *  /devices/rootnode/.../device:scsi::dsk/cXtXdX
3705117Smyers 	 */
3715117Smyers 
3725117Smyers 	(void) strncpy(device, ap_path, MAXPATHLEN);
3735117Smyers 	p = strrchr(device, ':');
3745117Smyers 	dm_assert(p != NULL);
3755117Smyers 
3765117Smyers 	n = sscanf(p, ":dsk/c%*dt%dd%*d", target);
3775117Smyers 	dm_assert(n == 1);
3785117Smyers 
3795117Smyers 	*strchr(device, ':') = '\0';
3805117Smyers }
3815117Smyers 
3825117Smyers static void
disk_split_ap_path(const char * ap_path,char * device,int * target)3835117Smyers disk_split_ap_path(const char *ap_path, char *device, int *target)
3845117Smyers {
3855117Smyers 	/*
3865117Smyers 	 * The AP path comes in two forms; for SATA devices,
3875117Smyers 	 * is is of the form:
3885117Smyers 	 *   /devices/rootnode/.../device:portnum
3895117Smyers 	 * and for SCSI devices, it is of the form:
3905117Smyers 	 *  /devices/rootnode/.../device:scsi::dsk/cXtXdX
3915117Smyers 	 */
3925117Smyers 
3935117Smyers 	if (disk_ap_is_scsi(ap_path))
3945117Smyers 		disk_split_ap_path_scsi(ap_path, device, target);
3955117Smyers 	else
3965117Smyers 		disk_split_ap_path_sata(ap_path, device, target);
3975117Smyers }
3985117Smyers 
3995117Smyers static void
disk_split_device_path(const char * dev_path,char * device,int * target)4005117Smyers disk_split_device_path(const char *dev_path, char *device, int *target)
4015117Smyers {
4025117Smyers 	char *t, *p, *e;
4035117Smyers 
4045117Smyers 	/*
4055117Smyers 	 * The disk device path is of the form:
4065117Smyers 	 * /rootnode/.../device/target@tgtid,tgtlun
4075117Smyers 	 */
4085117Smyers 
4095117Smyers 	(void) strncpy(device, dev_path, MAXPATHLEN);
4105117Smyers 	e = t = strrchr(device, '/');
4115117Smyers 	dm_assert(t != NULL);
4125117Smyers 
4135117Smyers 	t = strchr(t, '@');
4145117Smyers 	dm_assert(t != NULL);
4155117Smyers 	t += 1;
4165117Smyers 
4175117Smyers 	if ((p = strchr(t, ',')) != NULL)
4185117Smyers 		*p = '\0';
4195117Smyers 
4205117Smyers 	*target = strtol(t, 0, 16);
4215117Smyers 	*e = '\0';
4225117Smyers }
4235117Smyers 
4244582Scth /*
4254582Scth  * Returns the diskmon that corresponds to the physical disk path
4264582Scth  * passed in.
4274582Scth  */
4284582Scth static diskmon_t *
disk_match_by_device_path(diskmon_t * disklistp,const char * dev_path)4294582Scth disk_match_by_device_path(diskmon_t *disklistp, const char *dev_path)
4304582Scth {
4315117Smyers 	char dev_device[MAXPATHLEN];
4325117Smyers 	int dev_target;
4335117Smyers 	char ap_device[MAXPATHLEN];
4345117Smyers 	int ap_target;
4355117Smyers 
4364582Scth 	dm_assert(disklistp != NULL);
4374582Scth 	dm_assert(dev_path != NULL);
4384582Scth 
4394582Scth 	if (strncmp(dev_path, DEVICES_PREFIX, 8) == 0)
4404582Scth 		dev_path += 8;
4414582Scth 
4425117Smyers 	/* pare dev_path into device and target components */
4435117Smyers 	disk_split_device_path(dev_path, (char *)&dev_device, &dev_target);
4445117Smyers 
4454582Scth 	/*
4464582Scth 	 * The AP path specified in the configuration properties is
4474582Scth 	 * the path to an attachment point minor node whose port number is
4484582Scth 	 * equal to the target number on the disk "major" node sent by the
4494582Scth 	 * sysevent.  To match them, we need to extract the target id and
4504582Scth 	 * construct an AP string to compare to the AP path in the diskmon.
4514582Scth 	 */
4524582Scth 	while (disklistp != NULL) {
4534582Scth 		char *app = (char *)dm_prop_lookup(disklistp->app_props,
4544582Scth 		    DISK_AP_PROP_APID);
4554582Scth 		dm_assert(app != NULL);
4564582Scth 
4575117Smyers 		/* Not necessary to adjust the APID here */
4584582Scth 		if (strncmp(app, DEVICES_PREFIX, 8) == 0)
4594582Scth 			app += 8;
4604582Scth 
4615117Smyers 		disk_split_ap_path(app, (char *)&ap_device, &ap_target);
4624582Scth 
4635117Smyers 		if ((strcmp(dev_device, ap_device) == 0) &&
4645117Smyers 		    (dev_target == ap_target))
4654582Scth 			return (disklistp);
4664582Scth 
4674582Scth 		disklistp = disklistp->next;
4684582Scth 	}
4694582Scth 	return (NULL);
4704582Scth }
4714582Scth 
4724582Scth static diskmon_t *
disk_match_by_ap_id(diskmon_t * disklistp,const char * ap_id)4734582Scth disk_match_by_ap_id(diskmon_t *disklistp, const char *ap_id)
4744582Scth {
4754582Scth 	const char *disk_ap_id;
4764582Scth 	dm_assert(disklistp != NULL);
4774582Scth 	dm_assert(ap_id != NULL);
4784582Scth 
4794582Scth 	/* Match only the device-tree portion of the name */
4804582Scth 	if (strncmp(ap_id, DEVICES_PREFIX, 8 /* strlen("/devices") */) == 0)
4814582Scth 		ap_id += 8;
4824582Scth 
4834582Scth 	while (disklistp != NULL) {
4844582Scth 		disk_ap_id = dm_prop_lookup(disklistp->app_props,
4854582Scth 		    DISK_AP_PROP_APID);
4864582Scth 
4874582Scth 		dm_assert(disk_ap_id != NULL);
4884582Scth 
4894582Scth 		if (strcmp(disk_ap_id, ap_id) == 0)
4904582Scth 			return (disklistp);
4914582Scth 
4924582Scth 		disklistp = disklistp->next;
4934582Scth 	}
4944582Scth 	return (NULL);
4954582Scth }
4964582Scth 
4974582Scth static diskmon_t *
disk_match_by_target_id(diskmon_t * disklistp,const char * target_path)4985117Smyers disk_match_by_target_id(diskmon_t *disklistp, const char *target_path)
4995117Smyers {
5005117Smyers 	const char *disk_ap_id;
5015117Smyers 
5025117Smyers 	char match_device[MAXPATHLEN];
5035117Smyers 	int match_target;
5045117Smyers 
5055117Smyers 	char ap_device[MAXPATHLEN];
5065117Smyers 	int ap_target;
5075117Smyers 
5085117Smyers 
5095117Smyers 	/* Match only the device-tree portion of the name */
5105117Smyers 	if (strncmp(target_path, DEVICES_PREFIX, 8) == 0)
5115117Smyers 		target_path += 8;
5125117Smyers 	disk_split_ap_path(target_path, (char *)&match_device, &match_target);
5135117Smyers 
5145117Smyers 	while (disklistp != NULL) {
5155117Smyers 
5165117Smyers 		disk_ap_id = dm_prop_lookup(disklistp->app_props,
5175117Smyers 		    DISK_AP_PROP_APID);
5185117Smyers 		dm_assert(disk_ap_id != NULL);
5195117Smyers 
5205117Smyers 		disk_split_ap_path(disk_ap_id, (char *)&ap_device, &ap_target);
5215117Smyers 		if ((match_target == ap_target) &&
5225117Smyers 		    (strcmp(match_device, ap_device) == 0))
5235117Smyers 			return (disklistp);
5245117Smyers 
5255117Smyers 		disklistp = disklistp->next;
5265117Smyers 	}
5275117Smyers 	return (NULL);
5285117Smyers }
5295117Smyers 
5305117Smyers static diskmon_t *
match_sysevent_to_disk(diskmon_t * disklistp,sysevent_t * evp)5314582Scth match_sysevent_to_disk(diskmon_t *disklistp, sysevent_t *evp)
5324582Scth {
5334582Scth 	diskmon_t *dmp = NULL;
5344582Scth 	sysevent_value_t se_val;
5354582Scth 	char *class_name = sysevent_get_class_name(evp);
5364582Scth 	char *subclass = sysevent_get_subclass_name(evp);
5374582Scth 
5384582Scth 	se_val.value.sv_string = NULL;
5394582Scth 
5404582Scth 	if (strcmp(class_name, EC_DEVFS) == 0) {
5414582Scth 		/* EC_DEVFS-class events have a `DEVFS_PATHNAME' property */
5424582Scth 		if (sysevent_lookup_attr(evp, DEVFS_PATHNAME,
5434582Scth 		    SE_DATA_TYPE_STRING, &se_val) == 0 &&
5444582Scth 		    se_val.value.sv_string != NULL) {
5454582Scth 
5464582Scth 			dmp = disk_match_by_device_path(disklistp,
5474582Scth 			    se_val.value.sv_string);
5484582Scth 
5494582Scth 		}
5504582Scth 
5514582Scth 	} else if (strcmp(class_name, EC_DR) == 0 &&
5524582Scth 	    strcmp(subclass, ESC_DR_AP_STATE_CHANGE) == 0) {
5534582Scth 
5544582Scth 		/* EC_DR-class events have a `DR_AP_ID' property */
5554582Scth 		if (sysevent_lookup_attr(evp, DR_AP_ID, SE_DATA_TYPE_STRING,
5564582Scth 		    &se_val) == 0 && se_val.value.sv_string != NULL) {
5574582Scth 
5584582Scth 			dmp = disk_match_by_ap_id(disklistp,
5594582Scth 			    se_val.value.sv_string);
5604582Scth 		}
5615117Smyers 	} else if (strcmp(class_name, EC_DR) == 0 &&
5625117Smyers 	    strcmp(subclass, ESC_DR_TARGET_STATE_CHANGE) == 0) {
5635117Smyers 		/* get DR_TARGET_ID */
5645117Smyers 		if (sysevent_lookup_attr(evp, DR_TARGET_ID,
5655117Smyers 		    SE_DATA_TYPE_STRING, &se_val) == 0 &&
5665117Smyers 		    se_val.value.sv_string != NULL) {
5675117Smyers 			dmp = disk_match_by_target_id(disklistp,
5685117Smyers 			    se_val.value.sv_string);
5695117Smyers 		}
5704582Scth 	}
5714582Scth 
5724582Scth 	if (se_val.value.sv_string)
5734582Scth 		log_msg(MM_HPMGR, "match_sysevent_to_disk: device/ap: %s\n",
5744582Scth 		    se_val.value.sv_string);
5754582Scth 
5764582Scth 	return (dmp);
5774582Scth }
5784582Scth 
5794582Scth 
5804582Scth /*
5814582Scth  * The disk hotplug monitor (DHPM) listens for disk hotplug events and calls the
5824582Scth  * state-change functionality when a disk's state changes.  The DHPM listens for
5834582Scth  * hotplug events via sysevent subscriptions to the following sysevent
5844582Scth  * classes/subclasses: { EC_DEVFS/ESC_DEVFS_BRANCH_ADD,
5854582Scth  * EC_DEVFS/ESC_DEVFS_BRANCH_REMOVE, EC_DEVFS/ESC_DEVFS_DEVI_ADD,
5864582Scth  * EC_DEVFS/ESC_DEVFS_DEVI_REMOVE, EC_DR/ESC_DR_AP_STATE_CHANGE }.  Once the
5874582Scth  * event is received, the device path sent as part of the event is matched
5884582Scth  * to one of the disks described by the configuration data structures.
5894582Scth  */
5904582Scth static void
dm_process_sysevent(sysevent_t * dupev)5914582Scth dm_process_sysevent(sysevent_t *dupev)
5924582Scth {
5934582Scth 	char		*class_name;
5944582Scth 	char		*pub;
5954582Scth 	char		*subclass = sysevent_get_subclass_name(dupev);
5964582Scth 	diskmon_t	*diskp;
5974582Scth 
5984582Scth 	class_name = sysevent_get_class_name(dupev);
5994582Scth 	log_msg(MM_HPMGR, "****EVENT: %s %s (by %s)\n", class_name,
6004582Scth 	    subclass,
6014582Scth 	    ((pub = sysevent_get_pub_name(dupev)) != NULL) ? pub : "UNKNOWN");
6024582Scth 
6034582Scth 	if (pub)
6044582Scth 		free(pub);
6054582Scth 
6064582Scth 	if (strcmp(class_name, EC_PLATFORM) == 0 &&
6074582Scth 	    strcmp(subclass, ESC_PLATFORM_SP_RESET) == 0) {
6084582Scth 		if (dm_platform_resync() != 0)
6094582Scth 			log_warn("failed to resync SP platform\n");
610*8526SRobert.Johnston@Sun.COM 		sysevent_free(dupev);
6114582Scth 		return;
6124582Scth 	}
6134582Scth 
6144582Scth 	/*
6154582Scth 	 * We will handle this event if the event's target matches one of the
6164582Scth 	 * disks we're monitoring
6174582Scth 	 */
6184582Scth 	if ((diskp = match_sysevent_to_disk(config_data->disk_list, dupev))
6194582Scth 	    != NULL) {
6204582Scth 
6214582Scth 		dm_state_change(diskp, disk_sysev_to_state(diskp, dupev));
6224582Scth 	}
6234582Scth 
6244582Scth 	sysevent_free(dupev);
6254582Scth }
6264582Scth 
6274582Scth static void
dm_fmd_sysevent_thread(void * queuep)6284582Scth dm_fmd_sysevent_thread(void *queuep)
6294582Scth {
6304582Scth 	qu_t			*qp = (qu_t *)queuep;
6314582Scth 	sysevent_event_t	*sevevp;
6324582Scth 
6334582Scth 	/* Signal the thread spawner that we're running */
6344582Scth 	dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
6354582Scth 	if (g_sysev_thread_state != TS_EXIT_REQUESTED)
6364582Scth 		g_sysev_thread_state = TS_RUNNING;
6374582Scth 	(void) pthread_cond_broadcast(&g_event_handler_cond);
6384582Scth 	dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
6394582Scth 
6404582Scth 	while (g_sysev_thread_state != TS_EXIT_REQUESTED) {
6414582Scth 		if ((sevevp = (sysevent_event_t *)queue_remove(qp)) == NULL)
6424582Scth 			continue;
6434582Scth 
6444582Scth 		dm_process_sysevent(sevevp->evp);
6454582Scth 
6464582Scth 		free_sysevent_event(sevevp);
6474582Scth 	}
6484582Scth 
6494582Scth 	/* Signal the thread spawner that we've exited */
6504582Scth 	dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
6514582Scth 	g_sysev_thread_state = TS_EXITED;
6524582Scth 	(void) pthread_cond_broadcast(&g_event_handler_cond);
6534582Scth 	dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
6544582Scth 
6554582Scth 	log_msg(MM_HPMGR, "FMD sysevent handler thread exiting...");
6564582Scth }
6574582Scth 
6584582Scth static sysevent_event_t *
new_sysevent_event(sysevent_t * ev)6594582Scth new_sysevent_event(sysevent_t *ev)
6604582Scth {
6614582Scth 	/*
6624582Scth 	 * Cannot use dmalloc for this because the thread isn't a FMD-created
6634582Scth 	 * thread!
6644582Scth 	 */
6654582Scth 	sysevent_event_t *sevevp = malloc(sizeof (sysevent_event_t));
6664582Scth 	sevevp->evp = ev;
6674582Scth 	return (sevevp);
6684582Scth }
6694582Scth 
6704582Scth static void
free_sysevent_event(void * p)6714582Scth free_sysevent_event(void *p)
6724582Scth {
6734582Scth 	/* the sysevent_event was allocated with malloc(): */
6744582Scth 	free(p);
6754582Scth }
6764582Scth 
6774582Scth static void
event_handler(sysevent_t * ev)6784582Scth event_handler(sysevent_t *ev)
6794582Scth {
6804582Scth 	/* The duplicated sysevent will be freed in the child thread */
6814582Scth 	sysevent_t	*dupev = sysevent_dup(ev);
6824582Scth 
6834582Scth 	/*
6844582Scth 	 * Add this sysevent to the work queue of our FMA thread so we can
6854582Scth 	 * handle the sysevent and use the FMA API (e.g. for memory
6864582Scth 	 * allocation, etc.) in the sysevent handler.
6874582Scth 	 */
6884582Scth 	queue_add(g_sysev_queue, new_sysevent_event(dupev));
6894582Scth }
6904582Scth 
6914582Scth static void
fini_sysevents(void)6924582Scth fini_sysevents(void)
6934582Scth {
6944582Scth 	sysevent_unsubscribe_event(sysevent_handle, EC_ALL);
6954582Scth }
6964582Scth 
6974582Scth static int
init_sysevents(void)6984582Scth init_sysevents(void)
6994582Scth {
7004582Scth 	int rv = 0;
7014582Scth 	const char *devfs_subclasses[] = {
7024582Scth 		ESC_DEVFS_DEVI_ADD,
7034582Scth 		ESC_DEVFS_DEVI_REMOVE
7044582Scth 	};
7054582Scth 	const char *dr_subclasses[] = {
7065117Smyers 		ESC_DR_AP_STATE_CHANGE,
7075117Smyers 		ESC_DR_TARGET_STATE_CHANGE
7084582Scth 	};
7094582Scth 	const char *platform_subclasses[] = {
7104582Scth 		ESC_PLATFORM_SP_RESET
7114582Scth 	};
7124582Scth 
7134582Scth 	if ((sysevent_handle = sysevent_bind_handle(event_handler)) == NULL) {
7144582Scth 		rv = errno;
7154582Scth 		log_err("Could not initialize the hotplug manager ("
7164582Scth 		    "sysevent_bind_handle failure");
7174582Scth 	}
7184582Scth 
7194582Scth 	if (sysevent_subscribe_event(sysevent_handle, EC_DEVFS,
7205117Smyers 	    devfs_subclasses,
7215117Smyers 	    sizeof (devfs_subclasses)/sizeof (devfs_subclasses[0])) != 0) {
7224582Scth 
7234582Scth 		log_err("Could not initialize the hotplug manager "
7244582Scth 		    "sysevent_subscribe_event(event class = EC_DEVFS) "
7254582Scth 		    "failure");
7264582Scth 
7274582Scth 		rv = -1;
7284582Scth 
7294582Scth 	} else if (sysevent_subscribe_event(sysevent_handle, EC_DR,
7305117Smyers 	    dr_subclasses,
7315117Smyers 	    sizeof (dr_subclasses)/sizeof (dr_subclasses[0])) != 0) {
7324582Scth 
7334582Scth 		log_err("Could not initialize the hotplug manager "
7344582Scth 		    "sysevent_subscribe_event(event class = EC_DR) "
7354582Scth 		    "failure");
7364582Scth 
7374582Scth 		/* Unsubscribe from all sysevents in the event of a failure */
7384582Scth 		fini_sysevents();
7394582Scth 
7404582Scth 		rv = -1;
7414582Scth 	} else if (sysevent_subscribe_event(sysevent_handle, EC_PLATFORM,
7425117Smyers 	    platform_subclasses,
7435117Smyers 	    sizeof (platform_subclasses)/sizeof (platform_subclasses[0]))
7445117Smyers 	    != 0) {
7454582Scth 
7464582Scth 		log_err("Could not initialize the hotplug manager "
7474582Scth 		    "sysevent_subscribe_event(event class = EC_PLATFORM) "
7484582Scth 		    "failure");
7494582Scth 
7504582Scth 		/* Unsubscribe from all sysevents in the event of a failure */
7514582Scth 		fini_sysevents();
7524582Scth 
7534582Scth 		rv = -1;
7544582Scth 	}
7554582Scth 
7564582Scth 
7574582Scth 	return (rv);
7584582Scth }
7594582Scth 
7604582Scth /*ARGSUSED*/
7614582Scth static void
stdfree(void * p,size_t sz)7624582Scth stdfree(void *p, size_t sz)
7634582Scth {
7644582Scth 	free(p);
7654582Scth }
7664582Scth 
7674582Scth /*
7684582Scth  * Assumptions: Each disk's current state was determined and stored in
7694582Scth  * its diskmon_t.
7704582Scth  */
7714582Scth hotplug_mgr_init_err_t
init_hotplug_manager()7724582Scth init_hotplug_manager()
7734582Scth {
7744582Scth 	/* Create the queue to which we'll add sysevents */
7754582Scth 	g_sysev_queue = new_queue(B_TRUE, malloc, stdfree, free_sysevent_event);
7764582Scth 
7774582Scth 	/*
7784582Scth 	 * Grab the event handler lock before spawning the thread so we can
7794582Scth 	 * wait for the thread to transition to the running state.
7804582Scth 	 */
7814582Scth 	dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
7824582Scth 
7834582Scth 	/* Create the sysevent handling thread */
7844582Scth 	g_sysev_tid = fmd_thr_create(g_fm_hdl, dm_fmd_sysevent_thread,
7854582Scth 	    g_sysev_queue);
7864582Scth 
7874582Scth 	/* Wait for the thread's acknowledgement */
7884582Scth 	while (g_sysev_thread_state != TS_RUNNING)
7894582Scth 		(void) pthread_cond_wait(&g_event_handler_cond,
7904582Scth 		    &g_event_handler_lock);
7914582Scth 	dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
7924582Scth 
7934582Scth 	if (init_sysevents() != 0) {
7944582Scth 		log_warn_e("Error initializing sysevents");
7954582Scth 		return (HPM_ERR_SYSEVENT_INIT);
7964582Scth 	}
7974582Scth 
7984582Scth 	return (0);
7994582Scth }
8004582Scth 
8014582Scth void
cleanup_hotplug_manager()8024582Scth cleanup_hotplug_manager()
8034582Scth {
8044582Scth 	/* Unsubscribe from the sysevents */
8054582Scth 	fini_sysevents();
8064582Scth 
8074582Scth 	/*
8084582Scth 	 * Wait for the thread to exit before we can destroy
8094582Scth 	 * the event queue.
8104582Scth 	 */
8114582Scth 	dm_assert(pthread_mutex_lock(&g_event_handler_lock) == 0);
8124582Scth 	g_sysev_thread_state = TS_EXIT_REQUESTED;
8134582Scth 	queue_add(g_sysev_queue, NULL);
8144582Scth 	while (g_sysev_thread_state != TS_EXITED)
8154582Scth 		(void) pthread_cond_wait(&g_event_handler_cond,
8164582Scth 		    &g_event_handler_lock);
8174582Scth 	dm_assert(pthread_mutex_unlock(&g_event_handler_lock) == 0);
8184582Scth 	(void) pthread_join(g_sysev_tid, NULL);
8194582Scth 	fmd_thr_destroy(g_fm_hdl, g_sysev_tid);
8204582Scth 
8214582Scth 	/* Finally, destroy the event queue and reset the thread state */
8224582Scth 	queue_free(&g_sysev_queue);
8234582Scth 	g_sysev_thread_state = TS_NOT_RUNNING;
8244582Scth }
825