/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#include <libsysevent.h>
#include <sys/sysevent_impl.h>
#include <libnvpair.h>
#include <config_admin.h>
#include "disk_monitor.h"
#include "hotplug_mgr.h"
#include "schg_mgr.h"
#include "dm_platform.h"
typedef struct sysevent_event {
/* Lock guarantees the ordering of the incoming sysevents */
/*
* The sysevent handle is bound to the main sysevent handler
* (event_handler), for each of the hotplug sysevents.
*/
static void free_sysevent_event(void *p);
static int
{
}
static int
{
int timeout = 0;
int e;
do {
case CFGA_OK:
return (CFGA_OK);
case CFGA_BUSY:
case CFGA_SYSTEM_BUSY:
if (timeout++ >= TIMEOUT_MAX)
else {
if (nsleep(1) < 0)
}
break;
default:
break;
}
return (e);
}
/*
* Given a physical attachment point with a dynamic component
* (as in the case of SCSI APs), ensure the 'controller'
* portion of the dynamic component matches the physical portion.
* Argument 'adjusted' must point to a buffer of at least
* MAXPATHLEN bytes.
*/
void
{
int nlist;
char *dyn;
int c, t, d;
/* In the case of any error, return the unadjusted APID */
/* if AP is not dynamic or not a disk node, no need to adjust it */
return;
/*
* Copy the AP_ID and terminate it at the '::' that we know
* for a fact it contains. Pre-pend '/devices' for the sake
* of cfgadm_scsi, and get the cfgadm data for the controller.
*/
!= CFGA_OK)
return;
phys, c, t, d);
}
static int
{
}
/*
* Looks up the attachment point's state and returns it in one of
* the hotplug states that the state change manager understands.
*/
{
char *devices_app;
int len;
devices_app = NULL;
/*
* The SATA and SCSI libcfgadm plugins add a
* /devices to the phys id; to use it, we must
* prepend this string before the call.
*/
adj_app);
ap_path[0] = devices_app;
}
/*
* cfgadm_scsi will return an error for an absent target,
* so treat an error as "absent"; otherwise, make sure
* cfgadm_xxx has returned a list of 1 item
*/
list_valid = B_TRUE;
} else if (disk_ap_is_scsi(ap_path[0]))
state = HPS_ABSENT;
if (devices_app != NULL)
if (list_valid) {
/*
* The following truth table defines how each state is
* computed:
*
* +----------------------------------------------+
* | | o_state | r_state | condition |
* | +---------+---------+-----------|
* | Present |Unconfgrd|Connected| unknown |
* | Configured |Configred|Connected| Don'tCare |
* | Unconfigured |Unconfgrd|Connected| OK |
* +--------------+---------+---------+-----------+
*/
state = HPS_ABSENT;
state = HPS_PRESENT;
}
return (state);
}
/*
* Examine the sysevent passed in and returns the hotplug state that
* the sysevent states (or implies, in the case of attachment point
* events).
*/
static hotplug_state_t
{
/*
* The state mapping is as follows:
*
* Sysevent State
* --------------------------------------------------------
* EC_DEVFS/ESC_DEVFS_DEVI_ADD Configured
* EC_DEVFS/ESC_DEVFS_DEVI_REMOVE Unconfigured
*
* (The EC_DR event requires a probe of the attachment point
* to determine the AP's state if there is no usable HINT)
*
*/
}
== 0) {
state = HPS_PRESENT;
DR_HINT_REMOVE) == 0) {
state = HPS_ABSENT;
}
}
/*
* If the state could not be determined by the hint
* (or there was no hint), ask the AP directly.
* SCSI HBAs may send an insertion sysevent
* *after* configuring the target node, so double-
* check HPS_PRESENT
*/
}
return (state);
}
static void
{
char *p;
int n;
/*
*/
dm_assert(n == 1);
*p = '\0';
}
static void
{
char *p;
int n;
/*
*/
dm_assert(n == 1);
}
static void
{
/*
* The AP path comes in two forms; for SATA devices,
* is is of the form:
* and for SCSI devices, it is of the form:
*/
if (disk_ap_is_scsi(ap_path))
else
}
static void
{
char *t, *p, *e;
/*
* The disk device path is of the form:
*/
t = strchr(t, '@');
t += 1;
*p = '\0';
*e = '\0';
}
/*
* Returns the diskmon that corresponds to the physical disk path
* passed in.
*/
static diskmon_t *
{
int dev_target;
int ap_target;
dev_path += 8;
/* pare dev_path into device and target components */
/*
* The AP path specified in the configuration properties is
* the path to an attachment point minor node whose port number is
* equal to the target number on the disk "major" node sent by the
* sysevent. To match them, we need to extract the target id and
* construct an AP string to compare to the AP path in the diskmon.
*/
/* Not necessary to adjust the APID here */
app += 8;
(dev_target == ap_target))
return (disklistp);
}
return (NULL);
}
static diskmon_t *
{
const char *disk_ap_id;
/* Match only the device-tree portion of the name */
ap_id += 8;
return (disklistp);
}
return (NULL);
}
static diskmon_t *
{
const char *disk_ap_id;
int match_target;
int ap_target;
/* Match only the device-tree portion of the name */
target_path += 8;
if ((match_target == ap_target) &&
return (disklistp);
}
return (NULL);
}
static diskmon_t *
{
/* EC_DEVFS-class events have a `DEVFS_PATHNAME' property */
SE_DATA_TYPE_STRING, &se_val) == 0 &&
}
/* EC_DR-class events have a `DR_AP_ID' property */
}
/* get DR_TARGET_ID */
SE_DATA_TYPE_STRING, &se_val) == 0 &&
}
}
return (dmp);
}
/*
* The disk hotplug monitor (DHPM) listens for disk hotplug events and calls the
* state-change functionality when a disk's state changes. The DHPM listens for
* hotplug events via sysevent subscriptions to the following sysevent
* EC_DEVFS/ESC_DEVFS_DEVI_REMOVE, EC_DR/ESC_DR_AP_STATE_CHANGE }. Once the
* event is received, the device path sent as part of the event is matched
* to one of the disks described by the configuration data structures.
*/
static void
{
char *class_name;
char *pub;
if (pub)
if (dm_platform_resync() != 0)
log_warn("failed to resync SP platform\n");
return;
}
/*
* We will handle this event if the event's target matches one of the
* disks we're monitoring
*/
!= NULL) {
}
}
static void
{
/* Signal the thread spawner that we're running */
(void) pthread_cond_broadcast(&g_event_handler_cond);
while (g_sysev_thread_state != TS_EXIT_REQUESTED) {
continue;
}
/* Signal the thread spawner that we've exited */
(void) pthread_cond_broadcast(&g_event_handler_cond);
}
static sysevent_event_t *
{
/*
* Cannot use dmalloc for this because the thread isn't a FMD-created
* thread!
*/
return (sevevp);
}
static void
free_sysevent_event(void *p)
{
/* the sysevent_event was allocated with malloc(): */
free(p);
}
static void
{
/* The duplicated sysevent will be freed in the child thread */
/*
* Add this sysevent to the work queue of our FMA thread so we can
* handle the sysevent and use the FMA API (e.g. for memory
* allocation, etc.) in the sysevent handler.
*/
}
static void
fini_sysevents(void)
{
}
static int
init_sysevents(void)
{
int rv = 0;
const char *devfs_subclasses[] = {
};
const char *dr_subclasses[] = {
};
const char *platform_subclasses[] = {
};
log_err("Could not initialize the hotplug manager ("
"sysevent_bind_handle failure");
}
sizeof (devfs_subclasses)/sizeof (devfs_subclasses[0])) != 0) {
log_err("Could not initialize the hotplug manager "
"sysevent_subscribe_event(event class = EC_DEVFS) "
"failure");
rv = -1;
sizeof (dr_subclasses)/sizeof (dr_subclasses[0])) != 0) {
log_err("Could not initialize the hotplug manager "
"sysevent_subscribe_event(event class = EC_DR) "
"failure");
/* Unsubscribe from all sysevents in the event of a failure */
rv = -1;
sizeof (platform_subclasses)/sizeof (platform_subclasses[0]))
!= 0) {
log_err("Could not initialize the hotplug manager "
"sysevent_subscribe_event(event class = EC_PLATFORM) "
"failure");
/* Unsubscribe from all sysevents in the event of a failure */
rv = -1;
}
return (rv);
}
/*ARGSUSED*/
static void
{
free(p);
}
/*
* Assumptions: Each disk's current state was determined and stored in
* its diskmon_t.
*/
{
/* Create the queue to which we'll add sysevents */
/*
* Grab the event handler lock before spawning the thread so we can
* wait for the thread to transition to the running state.
*/
/* Create the sysevent handling thread */
/* Wait for the thread's acknowledgement */
while (g_sysev_thread_state != TS_RUNNING)
(void) pthread_cond_wait(&g_event_handler_cond,
if (init_sysevents() != 0) {
log_warn_e("Error initializing sysevents");
return (HPM_ERR_SYSEVENT_INIT);
}
return (0);
}
void
{
/* Unsubscribe from the sysevents */
/*
* Wait for the thread to exit before we can destroy
* the event queue.
*/
while (g_sysev_thread_state != TS_EXITED)
(void) pthread_cond_wait(&g_event_handler_cond,
/* Finally, destroy the event queue and reset the thread state */
}