/*
* 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
* or http://www.opensolaris.org/os/licensing.
* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <alloca.h>
#include <libnvpair.h>
#include <libhotplug.h>
#include <libhotplug_impl.h>
#include <sys/types.h>
#include <sys/sunddi.h>
#include <sys/ddi_hp.h>
#include <sys/modctl.h>
#include "hotplugd_impl.h"
/*
* All operations affecting kernel state are serialized.
*/
static pthread_mutex_t hotplug_lock = PTHREAD_MUTEX_INITIALIZER;
/*
* Local functions.
*/
static boolean_t check_rcm_required(hp_node_t, int);
static int pack_properties(const char *, ddi_hp_property_t *);
static void unpack_properties(ddi_hp_property_t *, char **);
static void free_properties(ddi_hp_property_t *);
/*
* changestate()
*
* Perform a state change operation.
*
* NOTE: all operations are serialized, using a global lock.
*/
int
changestate(const char *path, const char *connection, int state, uint_t flags,
int *old_statep, hp_node_t *resultsp)
{
hp_node_t root = NULL;
char **rsrcs = NULL;
boolean_t use_rcm = B_FALSE;
int rv;
dprintf("changestate(path=%s, connection=%s, state=0x%x, flags=0x%x)\n",
path, connection, state, flags);
/* Initialize results */
*resultsp = NULL;
*old_statep = -1;
(void) pthread_mutex_lock(&hotplug_lock);
/* Get an information snapshot, without usage details */
if ((rv = getinfo(path, connection, 0, &root)) != 0) {
(void) pthread_mutex_unlock(&hotplug_lock);
dprintf("changestate: getinfo() failed (%s)\n", strerror(rv));
return (rv);
}
/* Record current state (used in hotplugd_door.c for auditing) */
*old_statep = hp_state(root);
/* Check if RCM interactions are required */
use_rcm = check_rcm_required(root, state);
/* If RCM is required, perform RCM offline */
if (use_rcm) {
dprintf("changestate: RCM offline is required.\n");
/* Get RCM resources */
if ((rv = rcm_resources(root, &rsrcs)) != 0) {
dprintf("changestate: rcm_resources() failed.\n");
(void) pthread_mutex_unlock(&hotplug_lock);
hp_fini(root);
return (rv);
}
/* Request RCM offline */
if ((rsrcs != NULL) &&
((rv = rcm_offline(rsrcs, flags, root)) != 0)) {
dprintf("changestate: rcm_offline() failed.\n");
rcm_online(rsrcs);
(void) pthread_mutex_unlock(&hotplug_lock);
free_rcm_resources(rsrcs);
*resultsp = root;
return (rv);
}
}
/* The information snapshot is no longer needed */
hp_fini(root);
/* Stop now if QUERY flag was specified */
if (flags & HPQUERY) {
dprintf("changestate: operation was QUERY only.\n");
rcm_online(rsrcs);
(void) pthread_mutex_unlock(&hotplug_lock);
free_rcm_resources(rsrcs);
return (0);
}
/* Do state change in kernel */
rv = 0;
if (modctl(MODHPOPS, MODHPOPS_CHANGE_STATE, path, connection, state))
rv = errno;
dprintf("changestate: modctl(MODHPOPS_CHANGE_STATE) = %d.\n", rv);
/*
* If RCM is required, then perform an RCM online or RCM remove
* operation. Which depends upon if modctl succeeded or failed.
*/
if (use_rcm && (rsrcs != NULL)) {
/* RCM online if failure, or RCM remove if successful */
if (rv == 0)
rcm_remove(rsrcs);
else
rcm_online(rsrcs);
/* RCM resources no longer required */
free_rcm_resources(rsrcs);
}
(void) pthread_mutex_unlock(&hotplug_lock);
*resultsp = NULL;
return (rv);
}
/*
* private_options()
*
* Implement set/get of bus private options.
*/
int
private_options(const char *path, const char *connection, hp_cmd_t cmd,
const char *options, char **resultsp)
{
ddi_hp_property_t prop;
ddi_hp_property_t results;
char *values = NULL;
int rv;
dprintf("private_options(path=%s, connection=%s, options='%s')\n",
path, connection, options);
/* Initialize property arguments */
if ((rv = pack_properties(options, &prop)) != 0) {
dprintf("private_options: failed to pack properties.\n");
return (rv);
}
/* Initialize results */
(void) memset(&results, 0, sizeof (ddi_hp_property_t));
results.buf_size = HP_PRIVATE_BUF_SZ;
results.nvlist_buf = (char *)calloc(1, HP_PRIVATE_BUF_SZ);
if (results.nvlist_buf == NULL) {
dprintf("private_options: failed to allocate buffer.\n");
free_properties(&prop);
return (ENOMEM);
}
/* Lock hotplug */
(void) pthread_mutex_lock(&hotplug_lock);
/* Perform the command */
rv = 0;
if (cmd == HP_CMD_GETPRIVATE) {
if (modctl(MODHPOPS, MODHPOPS_BUS_GET, path, connection,
&prop, &results))
rv = errno;
dprintf("private_options: modctl(MODHPOPS_BUS_GET) = %d\n", rv);
} else {
if (modctl(MODHPOPS, MODHPOPS_BUS_SET, path, connection,
&prop, &results))
rv = errno;
dprintf("private_options: modctl(MODHPOPS_BUS_SET) = %d\n", rv);
}
/* Unlock hotplug */
(void) pthread_mutex_unlock(&hotplug_lock);
/* Parse results */
if (rv == 0) {
unpack_properties(&results, &values);
*resultsp = values;
}
/* Cleanup */
free_properties(&prop);
free_properties(&results);
return (rv);
}
/*
* check_rcm_required()
*
* Given the root of a changestate operation and the target
* state, determine if RCM interactions will be required.
*/
static boolean_t
check_rcm_required(hp_node_t root, int target_state)
{
/*
* RCM is required when transitioning an ENABLED
* connector to a non-ENABLED state.
*/
if ((root->hp_type == HP_NODE_CONNECTOR) &&
HP_IS_ENABLED(root->hp_state) && !HP_IS_ENABLED(target_state))
return (B_TRUE);
/*
* RCM is required when transitioning an OPERATIONAL
* port to a non-OPERATIONAL state.
*/
if ((root->hp_type == HP_NODE_PORT) &&
HP_IS_ONLINE(root->hp_state) && HP_IS_OFFLINE(target_state))
return (B_TRUE);
/* RCM is not required in other cases */
return (B_FALSE);
}
/*
* pack_properties()
*
* Given a specified set/get command and an options string,
* construct the structure containing a packed nvlist that
* contains the specified options.
*/
static int
pack_properties(const char *options, ddi_hp_property_t *prop)
{
nvlist_t *nvl;
char *buf, *tmp, *name, *value, *next;
size_t len;
/* Initialize results */
(void) memset(prop, 0, sizeof (ddi_hp_property_t));
/* Do nothing if options string is empty */
if ((len = strlen(options)) == 0) {
dprintf("pack_properties: options string is empty.\n");
return (ENOENT);
}
/* Avoid modifying the input string by using a copy on the stack */
if ((tmp = (char *)alloca(len + 1)) == NULL) {
log_err("Failed to allocate buffer for private options.\n");
return (ENOMEM);
}
(void) strlcpy(tmp, options, len + 1);
/* Allocate the nvlist */
if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) {
log_err("Failed to allocate private options nvlist.\n");
return (ENOMEM);
}
/* Add each option from the string */
for (name = tmp; name != NULL; name = next) {
/* Isolate current name/value, and locate the next */
if ((next = strchr(name, ',')) != NULL) {
*next = '\0';
next++;
}
/* Split current name/value pair */
if ((value = strchr(name, '=')) != NULL) {
*value = '\0';
value++;
} else {
value = "";
}
/* Add the option to the nvlist */
if (nvlist_add_string(nvl, name, value) != 0) {
log_err("Failed to add private option to nvlist.\n");
nvlist_free(nvl);
return (EFAULT);
}
}
/* Pack the nvlist */
len = 0;
buf = NULL;
if (nvlist_pack(nvl, &buf, &len, NV_ENCODE_NATIVE, 0) != 0) {
log_err("Failed to pack private options nvlist.\n");
nvlist_free(nvl);
return (EFAULT);
}
/* Save results */
prop->nvlist_buf = buf;
prop->buf_size = len;
/* The nvlist is no longer needed */
nvlist_free(nvl);
return (0);
}
/*
* unpack_properties()
*
* Given a structure possibly containing a packed nvlist of
* bus private options, unpack the nvlist and expand its
* contents into an options string.
*/
static void
unpack_properties(ddi_hp_property_t *prop, char **optionsp)
{
nvlist_t *nvl = NULL;
nvpair_t *nvp;
boolean_t first_flag;
char *name, *value, *options;
size_t len;
/* Initialize results */
*optionsp = NULL;
/* Do nothing if properties do not exist */
if ((prop->nvlist_buf == NULL) || (prop->buf_size == 0)) {
dprintf("unpack_properties: no properties exist.\n");
return;
}
/* Unpack the nvlist */
if (nvlist_unpack(prop->nvlist_buf, prop->buf_size, &nvl, 0) != 0) {
log_err("Failed to unpack private options nvlist.\n");
return;
}
/* Compute the size of the options string */
for (len = 0, nvp = NULL; nvp = nvlist_next_nvpair(nvl, nvp); ) {
name = nvpair_name(nvp);
/* Skip the command, and anything not a string */
if ((strcmp(name, "cmd") == 0) ||
(nvpair_type(nvp) != DATA_TYPE_STRING))
continue;
(void) nvpair_value_string(nvp, &value);
/* Account for '=' signs, commas, and terminating NULL */
len += (strlen(name) + strlen(value) + 2);
}
/* Allocate the resulting options string */
if ((options = (char *)calloc(len, sizeof (char))) == NULL) {
log_err("Failed to allocate private options string.\n");
nvlist_free(nvl);
return;
}
/* Copy name/value pairs into the options string */
first_flag = B_TRUE;
for (nvp = NULL; nvp = nvlist_next_nvpair(nvl, nvp); ) {
name = nvpair_name(nvp);
/* Skip the command, and anything not a string */
if ((strcmp(name, "cmd") == 0) ||
(nvpair_type(nvp) != DATA_TYPE_STRING))
continue;
if (!first_flag)
(void) strlcat(options, ",", len);
(void) strlcat(options, name, len);
(void) nvpair_value_string(nvp, &value);
if (strlen(value) > 0) {
(void) strlcat(options, "=", len);
(void) strlcat(options, value, len);
}
first_flag = B_FALSE;
}
/* The unpacked nvlist is no longer needed */
nvlist_free(nvl);
/* Save results */
*optionsp = options;
}
/*
* free_properties()
*
* Destroy a structure containing a packed nvlist of bus
* private properties.
*/
static void
free_properties(ddi_hp_property_t *prop)
{
if (prop) {
if (prop->nvlist_buf)
free(prop->nvlist_buf);
(void) memset(prop, 0, sizeof (ddi_hp_property_t));
}
}