OpenSolaris_b135/cmd/hotplugd/hotplugd_impl.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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));
	}
}