OpenSolaris_b135/uts/sparc/os/cpr_sparc.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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * cpr functions for supported sparc platforms
 */
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/cpr.h>
#include <sys/kmem.h>
#include <sys/errno.h>

/*
 * new_def_info is used as tmp space to store new values and write them
 * to nvram.  orig_def_info gets filled with the original nvram values,
 * gets written to disk, and later used by cprboot to restore the
 * original nvram values.
 */
static cdef_t *new_def_info;

static cdef_t orig_def_info = {
	0, 0,
	0, "boot-file",    "",		/* props[0] */
	0, "boot-device",  "",		/* props[1] */
	0, "auto-boot?",   "",		/* props[2] */
	0, "diag-file",    "",		/* props[3] */
	0, "diag-device",  "",		/* props[4] */
};

/*
 * since the above array is the only place where cprop_t content
 * is specified, these defines are provided for quick/direct access.
 */
#define	CPR_BF_IDX	0		/* index for boot-file */
#define	CPR_BD_IDX	1		/* index for boot-device */
#define	CPR_AB_IDX	2		/* index for auto-boot? */
#define	CPR_DF_IDX	3		/* index for diag-file */
#define	CPR_DD_IDX	4		/* index for diag-device */

#define	CPR_PROP_PTR(dfp, idx)	&(dfp)->props[idx]


static char *cpr_next_component(char **);
static char *cpr_get_prefix(char *);
static char *cpr_build_nodename(pnode_t);
static void cpr_abbreviate_devpath(char *, char *);
static int cpr_show_props = 0;


static int
cpr_get_options_node(pnode_t *nodep)
{
	*nodep = prom_optionsnode();
	if (*nodep == OBP_NONODE || *nodep == OBP_BADNODE) {
		cpr_err(CE_WARN, "cannot get \"options\" node");
		return (ENOENT);
	}

	return (0);
}


/*
 * returns non-zero on error, otherwise returns 0 and
 * sets the result code based on (prop value == "true")
 */
static int
cpr_get_bool_prop(char *name, int *result)
{
	char value[PROP_BOOL_LEN];
	pnode_t node;
	int len, err;

	if (err = cpr_get_options_node(&node))
		return (err);
	len = prom_getproplen(node, name);
	if (len < 0 || len >= sizeof (value))
		return (ENXIO);
	bzero(value, sizeof (value));
	if (prom_getprop(node, name, value) != len)
		return (ENOENT);
	*result = (strcmp(value, "true") == 0);
	return (0);
}


/*
 * write new or original values to nvram
 */
int
cpr_update_nvram(cprop_t *props)
{
	cprop_t *tail;
	pnode_t node;
	int len, rc;

	if (rc = cpr_get_options_node(&node))
		return (rc);

	if (cpr_show_props)
		prom_printf("\ncpr_show_props:\n");
	for (tail = props + CPR_MAXPROP; props < tail; props++) {
		if (cpr_show_props) {
			prom_printf("mod=%c, name \"%s\",\tvalue \"%s\"\n",
			    props->mod, props->name, props->value);
		}
		if (props->mod == PROP_NOMOD)
			continue;
		/*
		 * Note: When doing a prom_setprop you must include the
		 * trailing NULL in the length argument, but when calling
		 * prom_getproplen() the NULL is excluded from the count!
		 */
		len = strlen(props->value);
		rc = prom_setprop(node, props->name, props->value, len + 1);
		if (rc < 0 || prom_getproplen(node, props->name) != len) {
			cpr_err(CE_WARN, "cannot set nvram \"%s\" to \"%s\"",
			    props->name, props->value);
			return (ENXIO);
		}
	}

	return (0);
}


/*
 * update nvram with the new or original nvram values;
 * this routine provides local access to both sets
 */
int
cpr_set_properties(int new)
{
	cprop_t *props;

	props = new ? new_def_info->props : orig_def_info.props;
	return (cpr_update_nvram(props));
}



/*
 * update the .mod field in both new_def_info and orig_def_info;
 * this tells cpr and cprboot which properties to set/reset.
 * then copy the arg str into a new property value at index
 */
static void
cpr_prop_update(int index, char *str)
{
	cprop_t *prop;

	prop = CPR_PROP_PTR(&orig_def_info, index);
	prop->mod = PROP_MOD;

	prop = CPR_PROP_PTR(new_def_info, index);
	prop->mod = PROP_MOD;
	(void) strcpy(prop->value, str);
}


/*
 * setup new property values within new_def_info;
 * these are used later to udpate nvram
 */
static int
cpr_prop_setup(void)
{
	int len, err, ds_ival, dev_idx, file_idx;
	char bootdev[OBP_MAXPATHLEN], bootfile[OBP_MAXPATHLEN];
	char *cp, *sp;

	/*
	 * create a new boot-device value.  for some older prom revs,
	 * a fully qualified device path can be truncated when stored
	 * to nvram.  this call generates the shortest equivalent.
	 * using devaliases could be simpler in most cases.
	 */
	cpr_abbreviate_devpath(prom_bootpath(), bootdev);

	/*
	 * create a new boot-file value; flags get appended when
	 * not reusable and when the statefile is a block device
	 */
	(void) strcpy(bootfile, CPRBOOT);
	if (!cpr_reusable_mode && cpr_statefile_is_spec())
		sp = " -S ";
	else
		sp = NULL;
	if (sp) {
		(void) strcat(bootfile, sp);
		len = strlen(bootfile);
		sp = cpr_get_statefile_prom_path();
		cpr_abbreviate_devpath(sp, &bootfile[len]);
	}

	/*
	 * record property info for booting with cprboot based on
	 * the value of diag-switch?.  when "false", set boot-device
	 * and boot-file; when "true", set diag-device and diag-file
	 */
	if (err = cpr_get_bool_prop("diag-switch?", &ds_ival))
		return (err);
	else if (ds_ival == 0) {
		dev_idx  = CPR_BD_IDX;
		file_idx = CPR_BF_IDX;
	} else {
		dev_idx  = CPR_DD_IDX;
		file_idx = CPR_DF_IDX;
	}
	cpr_prop_update(dev_idx,  bootdev);

	if (!cpr_reusable_mode)
		cpr_prop_update(file_idx, bootfile);

	/*
	 * check/set auto-boot?
	 */
	sp = orig_def_info.props[CPR_AB_IDX].value;
	cp = "true";
	if (strcmp(sp, cp))
		cpr_prop_update(CPR_AB_IDX, cp);

	return (0);
}


/*
 * setup the original and new sets of property names/values
 */
int
cpr_default_setup(int alloc)
{
	cprop_t *orig, *new, *tail;
	int len, err = 0;
	pnode_t node;
	char *fmt;

	if (alloc == 0) {
		ASSERT(new_def_info);
		kmem_free(new_def_info, sizeof (*new_def_info));
		new_def_info = NULL;
		return (0);
	}

	if (err = cpr_get_options_node(&node))
		return (err);

	/*
	 * allocate space for new properties, get the original nvram
	 * property values, mark both property sets with PROP_NOMOD,
	 * and copy the original prop names to the new set.
	 */
	ASSERT(new_def_info == NULL);
	new_def_info = kmem_zalloc(sizeof (*new_def_info), KM_SLEEP);
	new = new_def_info->props;

	for (orig = orig_def_info.props, tail = orig + CPR_MAXPROP;
	    orig < tail; orig++, new++) {
		len = prom_getproplen(node, orig->name);
		if (len < 0 || len >= (int)sizeof (orig->value)) {
			fmt = "invalid property or length for \"%s\"";
			err = ENXIO;
			break;
		}
		bzero(orig->value, sizeof (orig->value));
		if (prom_getprop(node, orig->name, orig->value) < 0) {
			fmt = "cannot get \"%s\" value";
			err = ENXIO;
			break;
		}

		new->mod = orig->mod = PROP_NOMOD;
		(void) strcpy(new->name, orig->name);
	}

	if (err) {
		kmem_free(new_def_info, sizeof (*new_def_info));
		new_def_info = NULL;
		cpr_err(CE_WARN, fmt, orig->name);
	} else
		err = cpr_prop_setup();

	return (err);
}


int
cpr_validate_definfo(int reusable)
{
	orig_def_info.mini.magic = CPR->c_cprboot_magic = CPR_DEFAULT_MAGIC;
	orig_def_info.mini.reusable = reusable;
	return (cpr_write_deffile(&orig_def_info));
}


void
cpr_send_notice(void)
{
	static char cstr[] = "\014" "\033[1P" "\033[18;21H";

	prom_printf(cstr);
	prom_printf("Saving System State. Please Wait... ");
}

void
cpr_spinning_bar(void)
{
	static char *spin_strings[] = { "|\b", "/\b", "-\b", "\\\b" };
	static int idx;

	prom_printf(spin_strings[idx]);
	if (++idx == 4)
		idx = 0;
}

void
cpr_resume_notice(void)
{
	static char cstr[] = "\014" "\033[1P" "\033[18;21H";

	prom_printf(cstr);
	prom_printf("Restoring System State. Please Wait... ");
}

/*
 * Convert a full device path to its shortest unambiguous equivalent.
 * For example, a path which starts out /iommu@x,y/sbus@i,j/espdma . . .
 * might be converted to /iommu/sbus/espdma . . .  If we encounter
 * problems at any point, just output the unabbreviated path.
 */
static void
cpr_abbreviate_devpath(char *in_path, char *out_path)
{
	static pnode_t cur_node;
	char *position = in_path + 1;	/* Skip the leading slash. */
	char *cmpt;

	cur_node = prom_nextnode(0);
	*out_path = '\0';

	while ((cmpt = cpr_next_component(&position)) != NULL) {
		pnode_t long_match = NULL;
		pnode_t short_match = NULL;
		int short_hits = 0;
		char *name;
		char *prefix = cpr_get_prefix(cmpt);

		/* Go to next tree level by getting first child. */
		if ((cur_node = prom_childnode(cur_node)) == 0) {
			(void) strcpy(out_path, in_path);
			return;
		}

		/*
		 * Traverse the current level and remember the node (if any)
		 * where we match on the fully qualified component name.
		 * Also remember the node of the most recent prefix match
		 * and the number of such matches.
		 */
		do {
			name = cpr_build_nodename(cur_node);
			if (strcmp(name, cmpt) == 0)
				long_match = cur_node;
			if (strncmp(prefix, name, strlen(prefix)) == 0) {
				short_match = cur_node;
				short_hits++;
			}
		} while ((cur_node = prom_nextnode(cur_node)) != 0);

		/*
		 * We don't want to be too dependent on what we know
		 * about how the names are stored.  We just assume that
		 * if there is only one match on the prefix, we can
		 * use it, otherwise we need to use a fully qualified
		 * name.  In the "impossible" cases we just give up
		 * and use the complete input devpath.
		 */
		(void) strcat(out_path, "/");
		if (short_hits == 1) {
			(void) strcat(out_path, prefix);
			cur_node = short_match;
		}
		else
			if (long_match) {
				(void) strcat(out_path, cmpt);
				cur_node = long_match;
			} else {
				(void) strcpy(out_path, in_path);
				return;
			}
	}
	/* We need to copy the target and slice info manually. */
	(void) strcat(out_path, strrchr(in_path, '@'));
}

/*
 * Return a pointer to the next component of a device path or NULL if
 * the entire path has been consumed.  Note that we update the caller's
 * pointer to the current position in the full pathname buffer.
 */
static char *
cpr_next_component(char **path)
{
	static char obuf[64];
	char *slash;
	int len = strlen(*path);

	if (len == 0)
		return (NULL);

	if ((slash = strchr(*path, '/'))) {
		len = slash - *path;
		(void) strncpy(obuf, *path, len);
		obuf[len] = '\0';
		*path += len + 1;	/* Position beyond the slash. */
	} else {
		(void) strcpy(obuf, *path);
		*path += len;		/* Position at the terminal NULL. */
	}

	return (obuf);
}

/*
 * Return a pointer to the prefix (i.e., the basic unqualified node name)
 * Basically, this is the part of the fully qualified name before the @.
 */
static char *
cpr_get_prefix(char *cmpt)
{
	static char	prefix[OBP_MAXDRVNAME];
	char		*at_sign = strchr(cmpt, '@');
	int		len = at_sign ? at_sign - cmpt : strlen(cmpt);

	(void) strncpy(prefix, cmpt, len);
	prefix[len] = '\0';

	return (prefix);
}

/*
 * Build the unambiguous name for the current node, like iommu@f,e10000000.
 * The prefix is just the "name" property, and the qualifier is constructed
 * from the first two (binary) words of the "reg" property.
 */
static char *
cpr_build_nodename(pnode_t node)
{
	static char	name[OBP_MAXPATHLEN];
	int		reg[512];
	char		buf[32]; /* must contain expansion of @%x,%x */
	int		prop_len = prom_getproplen(node, OBP_NAME);

	if (prop_len < 0 || prop_len >= sizeof (name) ||
	    prom_getprop(node, OBP_NAME, name) < 0)
		return ("");
	name[prop_len] = '\0';

	if ((prop_len = prom_getproplen(node, OBP_REG)) <
	    2 * sizeof (int) || prop_len >= sizeof (reg))
		return (name);

	if (prom_getprop(node, OBP_REG, (caddr_t)reg) < 0)
		return (name);

	(void) sprintf(buf, "@%x,%x", reg[0], reg[1]);
	(void) strcat(name, buf);

	return (name);
}

/*
 * Makes a printable list of prom_prop names for error messages
 * Caller must free space.
 */
char *
cpr_enumerate_promprops(char **bufp, size_t *len)
{
	cprop_t *prop, *tail;
	size_t size = 2;	/* for "." */
	char *buf;

	tail = &orig_def_info.props[CPR_MAXPROP];
	for (prop = orig_def_info.props; prop < tail; prop++)
		size += strlen(prop->name) + 2;	/* + ", " */

	buf = kmem_alloc(size, KM_SLEEP);
	*buf = '\0';

	for (prop = orig_def_info.props; prop < tail; prop++) {
		if (strlen(buf))
			(void) strcat(buf, ", ");
		(void) strcat(buf, prop->name);
	}
	(void) strcat(buf, ".");

	*bufp = buf;
	*len = size;
	return (buf);
}