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