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