4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
27 #include <sys/types.h>
28 #include <sys/sysevent/dr.h>
29 #include <sys/sysevent/eventdefs.h>
30 #include <sys/sunddi.h> /* for the EC's for DEVFS */
40 #include <libsysevent.h>
41 #include <sys/sysevent_impl.h>
43 #include <libnvpair.h>
44 #include <config_admin.h>
46 #include "disk_monitor.h"
47 #include "hotplug_mgr.h"
49 #include "dm_platform.h"
51 typedef struct sysevent_event
{
55 /* Lock guarantees the ordering of the incoming sysevents */
56 static pthread_t g_sysev_tid
;
57 static pthread_mutex_t g_event_handler_lock
= PTHREAD_MUTEX_INITIALIZER
;
58 static pthread_cond_t g_event_handler_cond
= PTHREAD_COND_INITIALIZER
;
59 static qu_t
*g_sysev_queue
= NULL
;
60 static thread_state_t g_sysev_thread_state
= TS_NOT_RUNNING
;
62 * The sysevent handle is bound to the main sysevent handler
63 * (event_handler), for each of the hotplug sysevents.
65 static sysevent_handle_t
*sysevent_handle
= NULL
;
67 static void free_sysevent_event(void *p
);
72 struct timespec tspec
;
74 tspec
.tv_sec
= seconds
;
77 return (nanosleep(&tspec
, NULL
));
81 config_list_ext_poll(int num
, char * const *path
,
82 cfga_list_data_t
**list_array
, int *nlist
, int flag
)
84 boolean_t done
= B_FALSE
;
85 boolean_t timedout
= B_FALSE
;
86 boolean_t interrupted
= B_FALSE
;
89 #define TIMEOUT_MAX 60
92 switch ((e
= config_list_ext(num
, path
, list_array
,
93 nlist
, NULL
, NULL
, NULL
, flag
))) {
100 case CFGA_SYSTEM_BUSY
:
102 if (timeout
++ >= TIMEOUT_MAX
)
106 interrupted
= (errno
== EINTR
);
115 } while (!done
&& !timedout
&& !interrupted
);
121 * Given a physical attachment point with a dynamic component
122 * (as in the case of SCSI APs), ensure the 'controller'
123 * portion of the dynamic component matches the physical portion.
124 * Argument 'adjusted' must point to a buffer of at least
128 adjust_dynamic_ap(const char *apid
, char *adjusted
)
130 cfga_list_data_t
*list_array
= NULL
;
133 char phys
[MAXPATHLEN
];
134 char dev_phys
[MAXPATHLEN
];
138 dm_assert((strlen(apid
) + 8 /* strlen("/devices") */) < MAXPATHLEN
);
140 /* In the case of any error, return the unadjusted APID */
141 (void) strcpy(adjusted
, apid
);
143 /* if AP is not dynamic or not a disk node, no need to adjust it */
144 dyn
= strstr(apid
, "::");
145 if ((dyn
== NULL
) || (dyn
== apid
) ||
146 (sscanf(dyn
, "::dsk/c%dt%dd%d", &c
, &t
, &d
) != 3))
150 * Copy the AP_ID and terminate it at the '::' that we know
151 * for a fact it contains. Pre-pend '/devices' for the sake
152 * of cfgadm_scsi, and get the cfgadm data for the controller.
154 (void) strcpy(phys
, apid
);
155 *strstr(phys
, "::") = '\0';
156 (void) snprintf(dev_phys
, MAXPATHLEN
, "/devices%s", phys
);
157 ap_path
[0] = dev_phys
;
159 if (config_list_ext_poll(1, ap_path
, &list_array
, &nlist
, 0)
163 dm_assert(nlist
== 1);
165 if (sscanf(list_array
[0].ap_log_id
, "c%d", &c
) == 1)
166 (void) snprintf(adjusted
, MAXPATHLEN
, "%s::dsk/c%dt%dd%d",
173 disk_ap_is_scsi(const char *ap_path
)
175 return (strstr(ap_path
, ":scsi:") != NULL
);
179 * Looks up the attachment point's state and returns it in one of
180 * the hotplug states that the state change manager understands.
183 disk_ap_state_to_hotplug_state(diskmon_t
*diskp
)
185 hotplug_state_t state
= HPS_UNKNOWN
;
186 cfga_list_data_t
*list_array
= NULL
;
188 char *app
= (char *)dm_prop_lookup(diskp
->app_props
,
190 char adj_app
[MAXPATHLEN
];
194 boolean_t list_valid
= B_FALSE
;
196 dm_assert(app
!= NULL
);
198 adjust_dynamic_ap(app
, adj_app
);
199 ap_path
[0] = adj_app
;
202 rv
= config_list_ext_poll(1, ap_path
, &list_array
, &nlist
,
207 * The SATA and SCSI libcfgadm plugins add a
208 * /devices to the phys id; to use it, we must
209 * prepend this string before the call.
211 len
= 8 /* strlen("/devices") */ + strlen(adj_app
) + 1;
212 devices_app
= dmalloc(len
);
213 (void) snprintf(devices_app
, len
, "/devices%s",
215 ap_path
[0] = devices_app
;
217 rv
= config_list_ext_poll(1, ap_path
, &list_array
, &nlist
,
222 * cfgadm_scsi will return an error for an absent target,
223 * so treat an error as "absent"; otherwise, make sure
224 * cfgadm_xxx has returned a list of 1 item
227 dm_assert(nlist
== 1);
229 } else if (disk_ap_is_scsi(ap_path
[0]))
232 if (devices_app
!= NULL
)
233 dfree(devices_app
, len
);
237 * The following truth table defines how each state is
240 * +----------------------------------------------+
241 * | | o_state | r_state | condition |
242 * | +---------+---------+-----------|
243 * | Absent |Don'tCare|Disc/Empt| Don'tCare |
244 * | Present |Unconfgrd|Connected| unknown |
245 * | Configured |Configred|Connected| Don'tCare |
246 * | Unconfigured |Unconfgrd|Connected| OK |
247 * +--------------+---------+---------+-----------+
250 if (list_array
[0].ap_r_state
== CFGA_STAT_EMPTY
||
251 list_array
[0].ap_r_state
== CFGA_STAT_DISCONNECTED
)
253 else if (list_array
[0].ap_r_state
== CFGA_STAT_CONNECTED
&&
254 list_array
[0].ap_o_state
== CFGA_STAT_UNCONFIGURED
&&
255 list_array
[0].ap_cond
== CFGA_COND_UNKNOWN
)
257 else if (list_array
[0].ap_r_state
== CFGA_STAT_CONNECTED
&&
258 list_array
[0].ap_o_state
== CFGA_STAT_UNCONFIGURED
&&
259 list_array
[0].ap_cond
!= CFGA_COND_UNKNOWN
)
260 state
= HPS_UNCONFIGURED
;
261 else if (list_array
[0].ap_r_state
== CFGA_STAT_CONNECTED
&&
262 list_array
[0].ap_o_state
== CFGA_STAT_CONFIGURED
)
263 state
= HPS_CONFIGURED
;
272 * Examine the sysevent passed in and returns the hotplug state that
273 * the sysevent states (or implies, in the case of attachment point
276 static hotplug_state_t
277 disk_sysev_to_state(diskmon_t
*diskp
, sysevent_t
*evp
)
279 const char *class_name
, *subclass
;
280 hotplug_state_t state
= HPS_UNKNOWN
;
281 sysevent_value_t se_val
;
284 * The state mapping is as follows:
287 * --------------------------------------------------------
288 * EC_DEVFS/ESC_DEVFS_DEVI_ADD Configured
289 * EC_DEVFS/ESC_DEVFS_DEVI_REMOVE Unconfigured
290 * EC_DR/ESC_DR_AP_STATE_CHANGE *[Absent/Present]
292 * (The EC_DR event requires a probe of the attachment point
293 * to determine the AP's state if there is no usable HINT)
297 class_name
= sysevent_get_class_name(evp
);
298 subclass
= sysevent_get_subclass_name(evp
);
300 if (strcmp(class_name
, EC_DEVFS
) == 0) {
301 if (strcmp(subclass
, ESC_DEVFS_DEVI_ADD
) == 0) {
303 state
= HPS_CONFIGURED
;
305 } else if (strcmp(subclass
, ESC_DEVFS_DEVI_REMOVE
) == 0) {
307 state
= HPS_UNCONFIGURED
;
311 } else if (strcmp(class_name
, EC_DR
) == 0 &&
312 ((strcmp(subclass
, ESC_DR_AP_STATE_CHANGE
) == 0) ||
313 (strcmp(subclass
, ESC_DR_TARGET_STATE_CHANGE
) == 0))) {
315 if (sysevent_lookup_attr(evp
, DR_HINT
, SE_DATA_TYPE_STRING
,
316 &se_val
) == 0 && se_val
.value
.sv_string
!= NULL
) {
318 if (strcmp(se_val
.value
.sv_string
, DR_HINT_INSERT
)
323 } else if (strcmp(se_val
.value
.sv_string
,
324 DR_HINT_REMOVE
) == 0) {
332 * If the state could not be determined by the hint
333 * (or there was no hint), ask the AP directly.
334 * SCSI HBAs may send an insertion sysevent
335 * *after* configuring the target node, so double-
338 if ((state
== HPS_UNKNOWN
) || (state
= HPS_PRESENT
))
339 state
= disk_ap_state_to_hotplug_state(diskp
);
346 disk_split_ap_path_sata(const char *ap_path
, char *device
, int *target
)
352 * /devices/rootnode/.../device:target
354 (void) strncpy(device
, ap_path
, MAXPATHLEN
);
355 p
= strrchr(device
, ':');
356 dm_assert(p
!= NULL
);
357 n
= sscanf(p
, ":%d", target
);
363 disk_split_ap_path_scsi(const char *ap_path
, char *device
, int *target
)
369 * /devices/rootnode/.../device:scsi::dsk/cXtXdX
372 (void) strncpy(device
, ap_path
, MAXPATHLEN
);
373 p
= strrchr(device
, ':');
374 dm_assert(p
!= NULL
);
376 n
= sscanf(p
, ":dsk/c%*dt%dd%*d", target
);
379 *strchr(device
, ':') = '\0';
383 disk_split_ap_path(const char *ap_path
, char *device
, int *target
)
386 * The AP path comes in two forms; for SATA devices,
388 * /devices/rootnode/.../device:portnum
389 * and for SCSI devices, it is of the form:
390 * /devices/rootnode/.../device:scsi::dsk/cXtXdX
393 if (disk_ap_is_scsi(ap_path
))
394 disk_split_ap_path_scsi(ap_path
, device
, target
);
396 disk_split_ap_path_sata(ap_path
, device
, target
);
400 disk_split_device_path(const char *dev_path
, char *device
, int *target
)
405 * The disk device path is of the form:
406 * /rootnode/.../device/target@tgtid,tgtlun
409 (void) strncpy(device
, dev_path
, MAXPATHLEN
);
410 e
= t
= strrchr(device
, '/');
411 dm_assert(t
!= NULL
);
414 dm_assert(t
!= NULL
);
417 if ((p
= strchr(t
, ',')) != NULL
)
420 *target
= strtol(t
, 0, 16);
425 * Returns the diskmon that corresponds to the physical disk path
429 disk_match_by_device_path(diskmon_t
*disklistp
, const char *dev_path
)
431 char dev_device
[MAXPATHLEN
];
433 char ap_device
[MAXPATHLEN
];
436 dm_assert(disklistp
!= NULL
);
437 dm_assert(dev_path
!= NULL
);
439 if (strncmp(dev_path
, DEVICES_PREFIX
, 8) == 0)
442 /* pare dev_path into device and target components */
443 disk_split_device_path(dev_path
, (char *)&dev_device
, &dev_target
);
446 * The AP path specified in the configuration properties is
447 * the path to an attachment point minor node whose port number is
448 * equal to the target number on the disk "major" node sent by the
449 * sysevent. To match them, we need to extract the target id and
450 * construct an AP string to compare to the AP path in the diskmon.
452 while (disklistp
!= NULL
) {
453 char *app
= (char *)dm_prop_lookup(disklistp
->app_props
,
455 dm_assert(app
!= NULL
);
457 /* Not necessary to adjust the APID here */
458 if (strncmp(app
, DEVICES_PREFIX
, 8) == 0)
461 disk_split_ap_path(app
, (char *)&ap_device
, &ap_target
);
463 if ((strcmp(dev_device
, ap_device
) == 0) &&
464 (dev_target
== ap_target
))
467 disklistp
= disklistp
->next
;
473 disk_match_by_ap_id(diskmon_t
*disklistp
, const char *ap_id
)
475 const char *disk_ap_id
;
476 dm_assert(disklistp
!= NULL
);
477 dm_assert(ap_id
!= NULL
);
479 /* Match only the device-tree portion of the name */
480 if (strncmp(ap_id
, DEVICES_PREFIX
, 8 /* strlen("/devices") */) == 0)
483 while (disklistp
!= NULL
) {
484 disk_ap_id
= dm_prop_lookup(disklistp
->app_props
,
487 dm_assert(disk_ap_id
!= NULL
);
489 if (strcmp(disk_ap_id
, ap_id
) == 0)
492 disklistp
= disklistp
->next
;
498 disk_match_by_target_id(diskmon_t
*disklistp
, const char *target_path
)
500 const char *disk_ap_id
;
502 char match_device
[MAXPATHLEN
];
505 char ap_device
[MAXPATHLEN
];
509 /* Match only the device-tree portion of the name */
510 if (strncmp(target_path
, DEVICES_PREFIX
, 8) == 0)
512 disk_split_ap_path(target_path
, (char *)&match_device
, &match_target
);
514 while (disklistp
!= NULL
) {
516 disk_ap_id
= dm_prop_lookup(disklistp
->app_props
,
518 dm_assert(disk_ap_id
!= NULL
);
520 disk_split_ap_path(disk_ap_id
, (char *)&ap_device
, &ap_target
);
521 if ((match_target
== ap_target
) &&
522 (strcmp(match_device
, ap_device
) == 0))
525 disklistp
= disklistp
->next
;
531 match_sysevent_to_disk(diskmon_t
*disklistp
, sysevent_t
*evp
)
533 diskmon_t
*dmp
= NULL
;
534 sysevent_value_t se_val
;
535 char *class_name
= sysevent_get_class_name(evp
);
536 char *subclass
= sysevent_get_subclass_name(evp
);
538 se_val
.value
.sv_string
= NULL
;
540 if (strcmp(class_name
, EC_DEVFS
) == 0) {
541 /* EC_DEVFS-class events have a `DEVFS_PATHNAME' property */
542 if (sysevent_lookup_attr(evp
, DEVFS_PATHNAME
,
543 SE_DATA_TYPE_STRING
, &se_val
) == 0 &&
544 se_val
.value
.sv_string
!= NULL
) {
546 dmp
= disk_match_by_device_path(disklistp
,
547 se_val
.value
.sv_string
);
551 } else if (strcmp(class_name
, EC_DR
) == 0 &&
552 strcmp(subclass
, ESC_DR_AP_STATE_CHANGE
) == 0) {
554 /* EC_DR-class events have a `DR_AP_ID' property */
555 if (sysevent_lookup_attr(evp
, DR_AP_ID
, SE_DATA_TYPE_STRING
,
556 &se_val
) == 0 && se_val
.value
.sv_string
!= NULL
) {
558 dmp
= disk_match_by_ap_id(disklistp
,
559 se_val
.value
.sv_string
);
561 } else if (strcmp(class_name
, EC_DR
) == 0 &&
562 strcmp(subclass
, ESC_DR_TARGET_STATE_CHANGE
) == 0) {
563 /* get DR_TARGET_ID */
564 if (sysevent_lookup_attr(evp
, DR_TARGET_ID
,
565 SE_DATA_TYPE_STRING
, &se_val
) == 0 &&
566 se_val
.value
.sv_string
!= NULL
) {
567 dmp
= disk_match_by_target_id(disklistp
,
568 se_val
.value
.sv_string
);
572 if (se_val
.value
.sv_string
)
573 log_msg(MM_HPMGR
, "match_sysevent_to_disk: device/ap: %s\n",
574 se_val
.value
.sv_string
);
581 * The disk hotplug monitor (DHPM) listens for disk hotplug events and calls the
582 * state-change functionality when a disk's state changes. The DHPM listens for
583 * hotplug events via sysevent subscriptions to the following sysevent
584 * classes/subclasses: { EC_DEVFS/ESC_DEVFS_BRANCH_ADD,
585 * EC_DEVFS/ESC_DEVFS_BRANCH_REMOVE, EC_DEVFS/ESC_DEVFS_DEVI_ADD,
586 * EC_DEVFS/ESC_DEVFS_DEVI_REMOVE, EC_DR/ESC_DR_AP_STATE_CHANGE }. Once the
587 * event is received, the device path sent as part of the event is matched
588 * to one of the disks described by the configuration data structures.
591 dm_process_sysevent(sysevent_t
*dupev
)
595 char *subclass
= sysevent_get_subclass_name(dupev
);
598 class_name
= sysevent_get_class_name(dupev
);
599 log_msg(MM_HPMGR
, "****EVENT: %s %s (by %s)\n", class_name
,
601 ((pub
= sysevent_get_pub_name(dupev
)) != NULL
) ? pub
: "UNKNOWN");
605 if (strcmp(class_name
, EC_PLATFORM
) == 0 &&
606 strcmp(subclass
, ESC_PLATFORM_SP_RESET
) == 0) {
607 if (dm_platform_resync() != 0)
608 log_warn("failed to resync SP platform\n");
609 sysevent_free(dupev
);
614 * We will handle this event if the event's target matches one of the
615 * disks we're monitoring
617 if ((diskp
= match_sysevent_to_disk(config_data
->disk_list
, dupev
))
620 dm_state_change(diskp
, disk_sysev_to_state(diskp
, dupev
));
623 sysevent_free(dupev
);
627 dm_fmd_sysevent_thread(void *queuep
)
629 qu_t
*qp
= (qu_t
*)queuep
;
630 sysevent_event_t
*sevevp
;
632 /* Signal the thread spawner that we're running */
633 dm_assert(pthread_mutex_lock(&g_event_handler_lock
) == 0);
634 if (g_sysev_thread_state
!= TS_EXIT_REQUESTED
)
635 g_sysev_thread_state
= TS_RUNNING
;
636 (void) pthread_cond_broadcast(&g_event_handler_cond
);
637 dm_assert(pthread_mutex_unlock(&g_event_handler_lock
) == 0);
639 while (g_sysev_thread_state
!= TS_EXIT_REQUESTED
) {
640 if ((sevevp
= (sysevent_event_t
*)queue_remove(qp
)) == NULL
)
643 dm_process_sysevent(sevevp
->evp
);
645 free_sysevent_event(sevevp
);
648 /* Signal the thread spawner that we've exited */
649 dm_assert(pthread_mutex_lock(&g_event_handler_lock
) == 0);
650 g_sysev_thread_state
= TS_EXITED
;
651 (void) pthread_cond_broadcast(&g_event_handler_cond
);
652 dm_assert(pthread_mutex_unlock(&g_event_handler_lock
) == 0);
654 log_msg(MM_HPMGR
, "FMD sysevent handler thread exiting...");
657 static sysevent_event_t
*
658 new_sysevent_event(sysevent_t
*ev
)
661 * Cannot use dmalloc for this because the thread isn't a FMD-created
664 sysevent_event_t
*sevevp
= malloc(sizeof (sysevent_event_t
));
670 free_sysevent_event(void *p
)
672 /* the sysevent_event was allocated with malloc(): */
677 event_handler(sysevent_t
*ev
)
679 /* The duplicated sysevent will be freed in the child thread */
680 sysevent_t
*dupev
= sysevent_dup(ev
);
683 * Add this sysevent to the work queue of our FMA thread so we can
684 * handle the sysevent and use the FMA API (e.g. for memory
685 * allocation, etc.) in the sysevent handler.
687 queue_add(g_sysev_queue
, new_sysevent_event(dupev
));
693 sysevent_unsubscribe_event(sysevent_handle
, EC_ALL
);
700 const char *devfs_subclasses
[] = {
702 ESC_DEVFS_DEVI_REMOVE
704 const char *dr_subclasses
[] = {
705 ESC_DR_AP_STATE_CHANGE
,
706 ESC_DR_TARGET_STATE_CHANGE
708 const char *platform_subclasses
[] = {
709 ESC_PLATFORM_SP_RESET
712 if ((sysevent_handle
= sysevent_bind_handle(event_handler
)) == NULL
) {
714 log_err("Could not initialize the hotplug manager ("
715 "sysevent_bind_handle failure");
718 if (sysevent_subscribe_event(sysevent_handle
, EC_DEVFS
,
720 sizeof (devfs_subclasses
)/sizeof (devfs_subclasses
[0])) != 0) {
722 log_err("Could not initialize the hotplug manager "
723 "sysevent_subscribe_event(event class = EC_DEVFS) "
728 } else if (sysevent_subscribe_event(sysevent_handle
, EC_DR
,
730 sizeof (dr_subclasses
)/sizeof (dr_subclasses
[0])) != 0) {
732 log_err("Could not initialize the hotplug manager "
733 "sysevent_subscribe_event(event class = EC_DR) "
736 /* Unsubscribe from all sysevents in the event of a failure */
740 } else if (sysevent_subscribe_event(sysevent_handle
, EC_PLATFORM
,
742 sizeof (platform_subclasses
)/sizeof (platform_subclasses
[0]))
745 log_err("Could not initialize the hotplug manager "
746 "sysevent_subscribe_event(event class = EC_PLATFORM) "
749 /* Unsubscribe from all sysevents in the event of a failure */
761 stdfree(void *p
, size_t sz
)
767 * Assumptions: Each disk's current state was determined and stored in
770 hotplug_mgr_init_err_t
771 init_hotplug_manager()
773 /* Create the queue to which we'll add sysevents */
774 g_sysev_queue
= new_queue(B_TRUE
, malloc
, stdfree
, free_sysevent_event
);
777 * Grab the event handler lock before spawning the thread so we can
778 * wait for the thread to transition to the running state.
780 dm_assert(pthread_mutex_lock(&g_event_handler_lock
) == 0);
782 /* Create the sysevent handling thread */
783 g_sysev_tid
= fmd_thr_create(g_fm_hdl
, dm_fmd_sysevent_thread
,
786 /* Wait for the thread's acknowledgement */
787 while (g_sysev_thread_state
!= TS_RUNNING
)
788 (void) pthread_cond_wait(&g_event_handler_cond
,
789 &g_event_handler_lock
);
790 dm_assert(pthread_mutex_unlock(&g_event_handler_lock
) == 0);
792 if (init_sysevents() != 0) {
793 log_warn_e("Error initializing sysevents");
794 return (HPM_ERR_SYSEVENT_INIT
);
801 cleanup_hotplug_manager()
803 /* Unsubscribe from the sysevents */
807 * Wait for the thread to exit before we can destroy
810 dm_assert(pthread_mutex_lock(&g_event_handler_lock
) == 0);
811 g_sysev_thread_state
= TS_EXIT_REQUESTED
;
812 queue_add(g_sysev_queue
, NULL
);
813 while (g_sysev_thread_state
!= TS_EXITED
)
814 (void) pthread_cond_wait(&g_event_handler_cond
,
815 &g_event_handler_lock
);
816 dm_assert(pthread_mutex_unlock(&g_event_handler_lock
) == 0);
817 (void) pthread_join(g_sysev_tid
, NULL
);
818 fmd_thr_destroy(g_fm_hdl
, g_sysev_tid
);
820 /* Finally, destroy the event queue and reset the thread state */
821 queue_free(&g_sysev_queue
);
822 g_sysev_thread_state
= TS_NOT_RUNNING
;