OpenSolaris_b135/cmd/prtconf/pdevinfo.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.
 */

/*
 * For machines that support the openprom, fetch and print the list
 * of devices that the kernel has fetched from the prom or conjured up.
 */

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <fcntl.h>
#include <ctype.h>
#include <strings.h>
#include <unistd.h>
#include <stropts.h>
#include <sys/types.h>
#include <sys/mkdev.h>
#include <sys/sunddi.h>
#include <sys/openpromio.h>
#include <sys/modctl.h>
#include <sys/stat.h>
#include <zone.h>
#include <libnvpair.h>
#include "prtconf.h"


typedef char *(*dump_propname_t)(void *);
typedef int (*dump_proptype_t)(void *);
typedef int (*dump_propints_t)(void *, int **);
typedef int (*dump_propint64_t)(void *, int64_t **);
typedef int (*dump_propstrings_t)(void *, char **);
typedef int (*dump_propbytes_t)(void *, uchar_t **);
typedef int (*dump_proprawdata_t)(void *, uchar_t **);

typedef struct dumpops_common {
	dump_propname_t doc_propname;
	dump_proptype_t doc_proptype;
	dump_propints_t doc_propints;
	dump_propint64_t doc_propint64;
	dump_propstrings_t doc_propstrings;
	dump_propbytes_t doc_propbytes;
	dump_proprawdata_t doc_proprawdata;
} dumpops_common_t;

static const dumpops_common_t prop_dumpops = {
	(dump_propname_t)di_prop_name,
	(dump_proptype_t)di_prop_type,
	(dump_propints_t)di_prop_ints,
	(dump_propint64_t)di_prop_int64,
	(dump_propstrings_t)di_prop_strings,
	(dump_propbytes_t)di_prop_bytes,
	(dump_proprawdata_t)di_prop_rawdata
}, pathprop_common_dumpops = {
	(dump_propname_t)di_path_prop_name,
	(dump_proptype_t)di_path_prop_type,
	(dump_propints_t)di_path_prop_ints,
	(dump_propint64_t)di_path_prop_int64s,
	(dump_propstrings_t)di_path_prop_strings,
	(dump_propbytes_t)di_path_prop_bytes,
	(dump_proprawdata_t)di_path_prop_bytes
};

typedef void *(*dump_nextprop_t)(void *, void *);
typedef dev_t (*dump_propdevt_t)(void *);

typedef struct dumpops {
	const dumpops_common_t *dop_common;
	dump_nextprop_t dop_nextprop;
	dump_propdevt_t dop_propdevt;
} dumpops_t;

typedef struct di_args {
	di_prom_handle_t	prom_hdl;
	di_devlink_handle_t	devlink_hdl;
} di_arg_t;

static const dumpops_t sysprop_dumpops = {
	&prop_dumpops,
	(dump_nextprop_t)di_prop_sys_next,
	NULL
}, globprop_dumpops = {
	&prop_dumpops,
	(dump_nextprop_t)di_prop_global_next,
	NULL
}, drvprop_dumpops = {
	&prop_dumpops,
	(dump_nextprop_t)di_prop_drv_next,
	(dump_propdevt_t)di_prop_devt
}, hwprop_dumpops = {
	&prop_dumpops,
	(dump_nextprop_t)di_prop_hw_next,
	NULL
}, pathprop_dumpops = {
	&pathprop_common_dumpops,
	(dump_nextprop_t)di_path_prop_next,
	NULL
};

#define	PROPNAME(ops) (ops->dop_common->doc_propname)
#define	PROPTYPE(ops) (ops->dop_common->doc_proptype)
#define	PROPINTS(ops) (ops->dop_common->doc_propints)
#define	PROPINT64(ops) (ops->dop_common->doc_propint64)
#define	PROPSTRINGS(ops) (ops->dop_common->doc_propstrings)
#define	PROPBYTES(ops) (ops->dop_common->doc_propbytes)
#define	PROPRAWDATA(ops) (ops->dop_common->doc_proprawdata)
#define	NEXTPROP(ops) (ops->dop_nextprop)
#define	PROPDEVT(ops) (ops->dop_propdevt)
#define	NUM_ELEMENTS(A) (sizeof (A) / sizeof (A[0]))

static int prop_type_guess(const dumpops_t *, void *, void **, int *);
static void walk_driver(di_node_t, di_arg_t *);
static int dump_devs(di_node_t, void *);
static int dump_prop_list(const dumpops_t *, const char *,
				int, void *, dev_t, int *);
static int _error(const char *, ...);
static int is_openprom();
static void walk(uchar_t *, uint_t, int);
static void dump_node(nvlist_t *, int);
static void dump_prodinfo(di_prom_handle_t, di_node_t, const char **,
				char *, int);
static di_node_t find_node_by_name(di_prom_handle_t, di_node_t, char *);
static int get_propval_by_name(di_prom_handle_t, di_node_t,
				const char *, uchar_t **);
static int dump_compatible(char *, int, di_node_t);
static void dump_pathing_data(int, di_node_t);
static void dump_minor_data(int, di_node_t, di_devlink_handle_t);
static void dump_link_data(int, di_node_t, di_devlink_handle_t);
static int print_composite_string(const char *, char *, int);
static void print_one(nvpair_t *, int);
static int unprintable(char *, int);
static int promopen(int);
static void promclose();
static di_node_t find_target_node(di_node_t);
static void node_display_set(di_node_t);

void
prtconf_devinfo(void)
{
	struct di_priv_data	fetch;
	di_arg_t		di_arg;
	di_prom_handle_t	prom_hdl = DI_PROM_HANDLE_NIL;
	di_devlink_handle_t	devlink_hdl = NULL;
	di_node_t		root_node;
	uint_t			flag;
	char			*rootpath;

	dprintf("verbosemode %s\n", opts.o_verbose ? "on" : "off");

	/* determine what info we need to get from kernel */
	flag = DINFOSUBTREE;
	rootpath = "/";

	if (opts.o_target) {
		flag |= (DINFOMINOR | DINFOPATH);
	}

	if (opts.o_pciid) {
		flag |= DINFOPROP;
		if ((prom_hdl = di_prom_init()) == DI_PROM_HANDLE_NIL)
			exit(_error("di_prom_init() failed."));
	}

	if (opts.o_forcecache) {
		if (dbg.d_forceload) {
			exit(_error(NULL, "option combination not supported"));
		}
		if (strcmp(rootpath, "/") != 0) {
			exit(_error(NULL, "invalid root path for option"));
		}
		flag = DINFOCACHE;
	} else if (opts.o_verbose) {
		flag |= (DINFOPROP | DINFOMINOR |
		    DINFOPRIVDATA | DINFOPATH | DINFOLYR);
	}

	if (dbg.d_forceload) {
		flag |= DINFOFORCE;
	}

	if (opts.o_verbose) {
		init_priv_data(&fetch);
		root_node = di_init_impl(rootpath, flag, &fetch);

		/* get devlink (aka aliases) data */
		if ((devlink_hdl = di_devlink_init(NULL, 0)) == NULL)
			exit(_error("di_devlink_init() failed."));
	} else
		root_node = di_init(rootpath, flag);

	if (root_node == DI_NODE_NIL) {
		(void) _error(NULL, "devinfo facility not available");
		/* not an error if this isn't the global zone */
		if (getzoneid() == GLOBAL_ZONEID)
			exit(-1);
		else
			exit(0);
	}

	di_arg.prom_hdl = prom_hdl;
	di_arg.devlink_hdl = devlink_hdl;

	/*
	 * ...and walk all nodes to report them out...
	 */
	if (dbg.d_bydriver) {
		opts.o_target = 0;
		walk_driver(root_node, &di_arg);
		if (prom_hdl != DI_PROM_HANDLE_NIL)
			di_prom_fini(prom_hdl);
		if (devlink_hdl != NULL)
			(void) di_devlink_fini(&devlink_hdl);
		di_fini(root_node);
		return;
	}

	if (opts.o_target) {
		di_node_t target_node, node;

		target_node = find_target_node(root_node);
		if (target_node == DI_NODE_NIL) {
			(void) fprintf(stderr, "%s: "
			    "invalid device path specified\n",
			    opts.o_progname);
			exit(1);
		}

		/* mark the target node so we display it */
		node_display_set(target_node);

		if (opts.o_ancestors) {
			/*
			 * mark the ancestors of this node so we display
			 * them as well
			 */
			node = target_node;
			while (node = di_parent_node(node))
				node_display_set(node);
		} else {
			/*
			 * when we display device tree nodes the indentation
			 * level is based off of tree depth.
			 *
			 * here we increment o_target to reflect the
			 * depth of the target node in the tree.  we do
			 * this so that when we calculate the indentation
			 * level we can subtract o_target so that the
			 * target node starts with an indentation of zero.
			 */
			node = target_node;
			while (node = di_parent_node(node))
				opts.o_target++;
		}

		if (opts.o_children) {
			/*
			 * mark the children of this node so we display
			 * them as well
			 */
			(void) di_walk_node(target_node, DI_WALK_CLDFIRST,
			    (void *)1,
			    (int (*)(di_node_t, void *))
			    node_display_set);
		}
	}

	(void) di_walk_node(root_node, DI_WALK_CLDFIRST, &di_arg,
	    dump_devs);

	if (prom_hdl != DI_PROM_HANDLE_NIL)
		di_prom_fini(prom_hdl);
	if (devlink_hdl != NULL)
		(void) di_devlink_fini(&devlink_hdl);
	di_fini(root_node);
}

/*
 * utility routines
 */
static int
i_find_target_node(di_node_t node, void *arg)
{
	di_node_t *target = (di_node_t *)arg;

	if (opts.o_devices_path != NULL) {
		char *path;

		if ((path = di_devfs_path(node)) == NULL)
			exit(_error("failed to allocate memory"));

		if (strcmp(opts.o_devices_path, path) == 0) {
			di_devfs_path_free(path);
			*target = node;
			return (DI_WALK_TERMINATE);
		}

		di_devfs_path_free(path);
	} else if (opts.o_devt != DDI_DEV_T_NONE) {
		di_minor_t	minor = DI_MINOR_NIL;

		while ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) {
			if (opts.o_devt == di_minor_devt(minor)) {
				*target = node;
				return (DI_WALK_TERMINATE);
			}
		}
	} else {
		/* we should never get here */
		exit(_error(NULL, "internal error"));
	}
	return (DI_WALK_CONTINUE);
}

static di_node_t
find_target_node(di_node_t root_node)
{
	di_node_t target = DI_NODE_NIL;

	/* special case to allow displaying of the root node */
	if (opts.o_devices_path != NULL) {
		if (strlen(opts.o_devices_path) == 0)
			return (root_node);
		if (strcmp(opts.o_devices_path, ".") == 0)
			return (root_node);
	}

	(void) di_walk_node(root_node, DI_WALK_CLDFIRST, &target,
	    i_find_target_node);
	return (target);
}

#define	NODE_DISPLAY		(1<<0)

static long
node_display(di_node_t node)
{
	long data = (long)di_node_private_get(node);
	return (data & NODE_DISPLAY);
}

static void
node_display_set(di_node_t node)
{
	long data = (long)di_node_private_get(node);
	data |= NODE_DISPLAY;
	di_node_private_set(node, (void *)data);
}

#define	LNODE_DISPLAYED		(1<<0)

static long
lnode_displayed(di_lnode_t lnode)
{
	long data = (long)di_lnode_private_get(lnode);
	return (data & LNODE_DISPLAYED);
}

static void
lnode_displayed_set(di_lnode_t lnode)
{
	long data = (long)di_lnode_private_get(lnode);
	data |= LNODE_DISPLAYED;
	di_lnode_private_set(lnode, (void *)data);
}

static void
lnode_displayed_clear(di_lnode_t lnode)
{
	long data = (long)di_lnode_private_get(lnode);
	data &= ~LNODE_DISPLAYED;
	di_lnode_private_set(lnode, (void *)data);
}

#define	MINOR_DISPLAYED		(1<<0)
#define	MINOR_PTR		(~(0x3))

static long
minor_displayed(di_minor_t minor)
{
	long data = (long)di_minor_private_get(minor);
	return (data & MINOR_DISPLAYED);
}

static void
minor_displayed_set(di_minor_t minor)
{
	long data = (long)di_minor_private_get(minor);
	data |= MINOR_DISPLAYED;
	di_minor_private_set(minor, (void *)data);
}

static void
minor_displayed_clear(di_minor_t minor)
{
	long data = (long)di_minor_private_get(minor);
	data &= ~MINOR_DISPLAYED;
	di_minor_private_set(minor, (void *)data);
}

static void *
minor_ptr(di_minor_t minor)
{
	long data = (long)di_minor_private_get(minor);
	return ((void *)(data & MINOR_PTR));
}

static void
minor_ptr_set(di_minor_t minor, void *ptr)
{
	long data = (long)di_minor_private_get(minor);
	data = (data & ~MINOR_PTR) | (((long)ptr) & MINOR_PTR);
	di_minor_private_set(minor, (void *)data);
}

/*
 * In this comment typed properties are those of type DI_PROP_TYPE_UNDEF_IT,
 * DI_PROP_TYPE_BOOLEAN, DI_PROP_TYPE_INT, DI_PROP_TYPE_INT64,
 * DI_PROP_TYPE_BYTE, and DI_PROP_TYPE_STRING.
 *
 * The guessing algorithm is:
 * 1. If the property is typed and the type is consistent with the value of
 *    the property, then the property is of that type. If the type is not
 *    consistent with value of the property, then the type is treated as
 *    alien to prtconf.
 * 2. If the property is of type DI_PROP_TYPE_UNKNOWN the following steps
 *    are carried out.
 *    a. If the value of the property is consistent with a string property,
 *       the type of the property is DI_PROP_TYPE_STRING.
 *    b. Otherwise, if the value of the property is consistent with an integer
 *       property, the type of the property is DI_PROP_TYPE_INT.
 *    c. Otherwise, the property type is treated as alien to prtconf.
 * 3. If the property type is alien to prtconf, then the property value is
 *    read by the appropriate routine for untyped properties and the following
 *    steps are carried out.
 *    a. If the length that the property routine returned is zero, the
 *       property is of type DI_PROP_TYPE_BOOLEAN.
 *    b. Otherwise, if the length that the property routine returned is
 *       positive, then the property value is treated as raw data of type
 *       DI_PROP_TYPE_UNKNOWN.
 *    c. Otherwise, if the length that the property routine returned is
 *       negative, then there is some internal inconsistency and this is
 *       treated as an error and no type is determined.
 */
static int
prop_type_guess(const dumpops_t *propops, void *prop, void **prop_data,
    int *prop_type)
{
	int len, type;

	type = PROPTYPE(propops)(prop);
	switch (type) {
	case DI_PROP_TYPE_UNDEF_IT:
	case DI_PROP_TYPE_BOOLEAN:
		*prop_data = NULL;
		*prop_type = type;
		return (0);
	case DI_PROP_TYPE_INT:
		len = PROPINTS(propops)(prop, (int **)prop_data);
		break;
	case DI_PROP_TYPE_INT64:
		len = PROPINT64(propops)(prop, (int64_t **)prop_data);
		break;
	case DI_PROP_TYPE_BYTE:
		len = PROPBYTES(propops)(prop, (uchar_t **)prop_data);
		break;
	case DI_PROP_TYPE_STRING:
		len = PROPSTRINGS(propops)(prop, (char **)prop_data);
		break;
	case DI_PROP_TYPE_UNKNOWN:
		len = PROPSTRINGS(propops)(prop, (char **)prop_data);
		if ((len > 0) && ((*(char **)prop_data)[0] != 0)) {
			*prop_type = DI_PROP_TYPE_STRING;
			return (len);
		}

		len = PROPINTS(propops)(prop, (int **)prop_data);
		type = DI_PROP_TYPE_INT;

		break;
	default:
		len = -1;
	}

	if (len > 0) {
		*prop_type = type;
		return (len);
	}

	len = PROPRAWDATA(propops)(prop, (uchar_t **)prop_data);
	if (len < 0) {
		return (-1);
	} else if (len == 0) {
		*prop_type = DI_PROP_TYPE_BOOLEAN;
		return (0);
	}

	*prop_type = DI_PROP_TYPE_UNKNOWN;
	return (len);
}

/*
 * Returns 0 if nothing is printed, 1 otherwise
 */
static int
dump_prop_list(const dumpops_t *dumpops, const char *name, int ilev,
    void *node, dev_t dev, int *compat_printed)
{
	void		*prop = DI_PROP_NIL, *prop_data;
	di_minor_t	minor;
	char		*p;
	int		i, prop_type, nitems;
	dev_t		pdev;
	int		nprop = 0;

	if (compat_printed)
		*compat_printed = 0;

	while ((prop = NEXTPROP(dumpops)(node, prop)) != DI_PROP_NIL) {

		/* Skip properties a dev_t oriented caller is not requesting */
		if (PROPDEVT(dumpops)) {
			pdev = PROPDEVT(dumpops)(prop);

			if (dev == DDI_DEV_T_ANY) {
				/*
				 * Caller requesting print all properties
				 */
				goto print;
			} else if (dev == DDI_DEV_T_NONE) {
				/*
				 * Caller requesting print of properties
				 * associated with devinfo (not minor).
				 */
				if ((pdev == DDI_DEV_T_ANY) ||
				    (pdev == DDI_DEV_T_NONE))
					goto print;

				/*
				 * Property has a minor association, see if
				 * we have a minor with this dev_t. If there
				 * is no such minor we print the property now
				 * so it gets displayed.
				 */
				minor = DI_MINOR_NIL;
				while ((minor = di_minor_next((di_node_t)node,
				    minor)) != DI_MINOR_NIL) {
					if (di_minor_devt(minor) == pdev)
						break;
				}
				if (minor == DI_MINOR_NIL)
					goto print;
			} else if (dev == pdev) {
				/*
				 * Caller requesting print of properties
				 * associated with a specific matching minor
				 * node.
				 */
				goto print;
			}

			/* otherwise skip print */
			continue;
		}

print:		nitems = prop_type_guess(dumpops, prop, &prop_data, &prop_type);
		if (nitems < 0)
			continue;

		if (nprop == 0) {
			if (name) {
				indent_to_level(ilev);
				(void) printf("%s properties:\n", name);
			}
			ilev++;
		}
		nprop++;

		indent_to_level(ilev);
		(void) printf("name='%s' type=", PROPNAME(dumpops)(prop));

		/* report 'compatible' as processed */
		if (compat_printed &&
		    (strcmp(PROPNAME(dumpops)(prop), "compatible") == 0))
			*compat_printed = 1;

		switch (prop_type) {
		case DI_PROP_TYPE_UNDEF_IT:
			(void) printf("undef");
			break;
		case DI_PROP_TYPE_BOOLEAN:
			(void) printf("boolean");
			break;
		case DI_PROP_TYPE_INT:
			(void) printf("int");
			break;
		case DI_PROP_TYPE_INT64:
			(void) printf("int64");
			break;
		case DI_PROP_TYPE_BYTE:
			(void) printf("byte");
			break;
		case DI_PROP_TYPE_STRING:
			(void) printf("string");
			break;
		case DI_PROP_TYPE_UNKNOWN:
			(void) printf("unknown");
			break;
		default:
			/* Should never be here */
			(void) printf("0x%x", prop_type);
		}

		if (nitems != 0)
			(void) printf(" items=%i", nitems);

		/* print the major and minor numbers for a device property */
		if (PROPDEVT(dumpops)) {
			if ((pdev == DDI_DEV_T_NONE) ||
			    (pdev == DDI_DEV_T_ANY)) {
				(void) printf(" dev=none");
			} else {
				(void) printf(" dev=(%u,%u)",
				    (uint_t)major(pdev), (uint_t)minor(pdev));
			}
		}

		(void) putchar('\n');

		if (nitems == 0)
			continue;

		indent_to_level(ilev);

		(void) printf("    value=");

		switch (prop_type) {
		case DI_PROP_TYPE_INT:
			for (i = 0; i < nitems - 1; i++)
				(void) printf("%8.8x.", ((int *)prop_data)[i]);
			(void) printf("%8.8x", ((int *)prop_data)[i]);
			break;
		case DI_PROP_TYPE_INT64:
			for (i = 0; i < nitems - 1; i++)
				(void) printf("%16.16llx.",
				    ((long long *)prop_data)[i]);
			(void) printf("%16.16llx", ((long long *)prop_data)[i]);
			break;
		case DI_PROP_TYPE_STRING:
			p = (char *)prop_data;
			for (i = 0; i < nitems - 1; i++) {
				(void) printf("'%s' + ", p);
				p += strlen(p) + 1;
			}
			(void) printf("'%s'", p);
			break;
		default:
			for (i = 0; i < nitems - 1; i++)
				(void) printf("%2.2x.",
				    ((uint8_t *)prop_data)[i]);
			(void) printf("%2.2x", ((uint8_t *)prop_data)[i]);
		}

		(void) putchar('\n');
	}

	return (nprop ? 1 : 0);
}

/*
 * walk_driver is a debugging facility.
 */
static void
walk_driver(di_node_t root, di_arg_t *di_arg)
{
	di_node_t node;

	node = di_drv_first_node(dbg.d_drivername, root);

	while (node != DI_NODE_NIL) {
		(void) dump_devs(node, di_arg);
		node = di_drv_next_node(node);
	}
}

/*
 * print out information about this node, returns appropriate code.
 */
/*ARGSUSED1*/
static int
dump_devs(di_node_t node, void *arg)
{
	di_arg_t		*di_arg = arg;
	di_devlink_handle_t	devlink_hdl = di_arg->devlink_hdl;
	int			ilev = 0;	/* indentation level */
	char			*driver_name;
	di_node_t		root_node, tmp;
	int			compat_printed;
	int			printed;

	if (dbg.d_debug) {
		char *path = di_devfs_path(node);
		dprintf("Dump node %s\n", path);
		di_devfs_path_free(path);
	}

	if (dbg.d_bydriver) {
		ilev = 1;
	} else {
		/* figure out indentation level */
		tmp = node;
		while ((tmp = di_parent_node(tmp)) != DI_NODE_NIL)
			ilev++;

		if (opts.o_target && !opts.o_ancestors) {
			ilev -= opts.o_target - 1;
		}
	}

	if (opts.o_target && !node_display(node)) {
		/*
		 * if we're only displaying certain nodes and this one
		 * isn't flagged, skip it.
		 */
		return (DI_WALK_CONTINUE);
	}

	indent_to_level(ilev);

	(void) printf("%s", di_node_name(node));
	if (opts.o_pciid)
		(void) print_pciid(node, di_arg->prom_hdl);

	/*
	 * if this node does not have an instance number or is the
	 * root node (1229946), we don't print an instance number
	 */
	root_node = tmp = node;
	while ((tmp = di_parent_node(tmp)) != DI_NODE_NIL)
		root_node = tmp;
	if ((di_instance(node) >= 0) && (node != root_node))
		(void) printf(", instance #%d", di_instance(node));

	if (opts.o_drv_name) {
		driver_name = di_driver_name(node);
		if (driver_name != NULL)
			(void) printf(" (driver name: %s)", driver_name);
	} else if (di_retired(node)) {
		(void) printf(" (retired)");
	} else if (di_state(node) & DI_DRIVER_DETACHED)
		(void) printf(" (driver not attached)");
	(void) printf("\n");

	if (opts.o_verbose)  {
		if (dump_prop_list(&sysprop_dumpops, "System", ilev + 1,
		    node, DDI_DEV_T_ANY, NULL)) {
			(void) dump_prop_list(&globprop_dumpops, NULL, ilev + 1,
			    node, DDI_DEV_T_ANY, NULL);
		} else {
			(void) dump_prop_list(&globprop_dumpops,
			    "System software", ilev + 1,
			    node, DDI_DEV_T_ANY, NULL);
		}
		(void) dump_prop_list(&drvprop_dumpops, "Driver", ilev + 1,
		    node, DDI_DEV_T_NONE, NULL);

		printed = dump_prop_list(&hwprop_dumpops, "Hardware",
		    ilev + 1, node, DDI_DEV_T_ANY, &compat_printed);

		/* Ensure that 'compatible' is printed under Hardware header */
		if (!compat_printed)
			(void) dump_compatible(printed ? NULL : "Hardware",
			    ilev + 1, node);

		dump_priv_data(ilev + 1, node);
		dump_pathing_data(ilev + 1, node);
		dump_link_data(ilev + 1, node, devlink_hdl);
		dump_minor_data(ilev + 1, node, devlink_hdl);
	}

	if (opts.o_target)
		return (DI_WALK_CONTINUE);

	if (!opts.o_pseudodevs && (strcmp(di_node_name(node), "pseudo") == 0))
		return (DI_WALK_PRUNECHILD);

	return (DI_WALK_CONTINUE);
}

/* _error([no_perror, ] fmt [, arg ...]) */
static int
_error(const char *opt_noperror, ...)
{
	int saved_errno;
	va_list ap;
	int no_perror = 0;
	const char *fmt;

	saved_errno = errno;

	(void) fprintf(stderr, "%s: ", opts.o_progname);

	va_start(ap, opt_noperror);
	if (opt_noperror == NULL) {
		no_perror = 1;
		fmt = va_arg(ap, char *);
	} else
		fmt = opt_noperror;
	(void) vfprintf(stderr, fmt, ap);
	va_end(ap);

	if (no_perror)
		(void) fprintf(stderr, "\n");
	else {
		(void) fprintf(stderr, ": ");
		errno = saved_errno;
		perror("");
	}

	return (-1);
}


/*
 * The rest of the routines handle printing the raw prom devinfo (-p option).
 *
 * 128 is the size of the largest (currently) property name
 * 16k - MAXNAMESZ - sizeof (int) is the size of the largest
 * (currently) property value that is allowed.
 * the sizeof (uint_t) is from struct openpromio
 */

#define	MAXNAMESZ	128
#define	MAXVALSIZE	(16384 - MAXNAMESZ - sizeof (uint_t))
#define	BUFSIZE		(MAXNAMESZ + MAXVALSIZE + sizeof (uint_t))
typedef union {
	char buf[BUFSIZE];
	struct openpromio opp;
} Oppbuf;

static int prom_fd;
static uchar_t *prom_snapshot;

static int
is_openprom(void)
{
	Oppbuf	oppbuf;
	struct openpromio *opp = &(oppbuf.opp);
	unsigned int i;

	opp->oprom_size = MAXVALSIZE;
	if (ioctl(prom_fd, OPROMGETCONS, opp) < 0)
		exit(_error("OPROMGETCONS"));

	i = (unsigned int)((unsigned char)opp->oprom_array[0]);
	return ((i & OPROMCONS_OPENPROM) == OPROMCONS_OPENPROM);
}

int
do_prominfo(void)
{
	uint_t arg = opts.o_verbose;

	if (promopen(O_RDONLY))  {
		exit(_error("openeepr device open failed"));
	}

	if (is_openprom() == 0)  {
		(void) fprintf(stderr, "System architecture does not "
		    "support this option of this command.\n");
		return (1);
	}

	/* OPROMSNAPSHOT returns size in arg */
	if (ioctl(prom_fd, OPROMSNAPSHOT, &arg) < 0)
		exit(_error("OPROMSNAPSHOT"));

	if (arg == 0)
		return (1);

	if ((prom_snapshot = malloc(arg)) == NULL)
		exit(_error("failed to allocate memory"));

	/* copy out the snapshot for printing */
	/*LINTED*/
	*(uint_t *)prom_snapshot = arg;
	if (ioctl(prom_fd, OPROMCOPYOUT, prom_snapshot) < 0)
		exit(_error("OPROMCOPYOUT"));

	promclose();

	/* print out information */
	walk(prom_snapshot, arg, 0);
	free(prom_snapshot);

	return (0);
}

static void
walk(uchar_t *buf, uint_t size, int level)
{
	int error;
	nvlist_t *nvl, *cnvl;
	nvpair_t *child = NULL;
	uchar_t *cbuf = NULL;
	uint_t csize;

	/* Expand to an nvlist */
	if (nvlist_unpack((char *)buf, size, &nvl, 0))
		exit(_error("error processing snapshot"));

	/* print current node */
	dump_node(nvl, level);

	/* print children */
	error = nvlist_lookup_byte_array(nvl, "@child", &cbuf, &csize);
	if ((error == ENOENT) || (cbuf == NULL))
		return;		/* no child exists */

	if (error || nvlist_unpack((char *)cbuf, csize, &cnvl, 0))
		exit(_error("error processing snapshot"));

	while (child = nvlist_next_nvpair(cnvl, child)) {
		char *name = nvpair_name(child);
		data_type_t type = nvpair_type(child);
		uchar_t *nodebuf;
		uint_t nodesize;
		if (strcmp("node", name) != 0) {
			dprintf("unexpected nvpair name %s != name\n", name);
			continue;
		}
		if (type != DATA_TYPE_BYTE_ARRAY) {
			dprintf("unexpected nvpair type %d, not byte array \n",
			    type);
			continue;
		}

		(void) nvpair_value_byte_array(child,
		    (uchar_t **)&nodebuf, &nodesize);
		walk(nodebuf, nodesize, level + 1);
	}

	nvlist_free(nvl);
}

/*
 * Print all properties and values
 */
static void
dump_node(nvlist_t *nvl, int level)
{
	int id = 0;
	char *name = NULL;
	nvpair_t *nvp = NULL;

	indent_to_level(level);
	(void) printf("Node");
	if (!opts.o_verbose) {
		if (nvlist_lookup_string(nvl, "name", &name))
			(void) printf("data not available");
		else
			(void) printf(" '%s'", name);
		(void) putchar('\n');
		return;
	}
	(void) nvlist_lookup_int32(nvl, "@nodeid", &id);
	(void) printf(" %#08x\n", id);

	while (nvp = nvlist_next_nvpair(nvl, nvp)) {
		name = nvpair_name(nvp);
		if (name[0] == '@')
			continue;

		print_one(nvp, level + 1);
	}
	(void) putchar('\n');
}

static const char *
path_state_name(di_path_state_t st)
{
	switch (st) {
		case DI_PATH_STATE_ONLINE:
			return ("online");
		case DI_PATH_STATE_STANDBY:
			return ("standby");
		case DI_PATH_STATE_OFFLINE:
			return ("offline");
		case DI_PATH_STATE_FAULT:
			return ("faulted");
	}
	return ("unknown");
}

/*
 * Print all phci's each client is connected to.
 */
static void
dump_pathing_data(int ilev, di_node_t node)
{
	di_path_t	pi = DI_PATH_NIL;
	di_node_t	phci_node;
	char		*phci_path;
	int		path_instance;
	int		firsttime = 1;

	if (node == DI_PATH_NIL)
		return;

	while ((pi = di_path_client_next_path(node, pi)) != DI_PATH_NIL) {

		/* It is not really a path if we failed to capture the pHCI */
		phci_node = di_path_phci_node(pi);
		if (phci_node == DI_NODE_NIL)
			continue;

		/* Print header for the first path */
		if (firsttime) {
			indent_to_level(ilev);
			firsttime = 0;
			ilev++;
			(void) printf("Paths from multipath bus adapters:\n");
		}

		/*
		 * Print the path instance and full "pathinfo" path, which is
		 * the same as the /devices devifo path had the device been
		 * enumerated under pHCI.
		 */
		phci_path = di_devfs_path(phci_node);
		if (phci_path) {
			path_instance = di_path_instance(pi);
			if (path_instance > 0) {
				indent_to_level(ilev);
				(void) printf("Path %d: %s/%s@%s\n",
				    path_instance, phci_path,
				    di_node_name(node),
				    di_path_bus_addr(pi));
			}
			di_devfs_path_free(phci_path);
		}

		/* print phci driver, instance, and path state information */
		indent_to_level(ilev);
		(void) printf("%s#%d (%s)\n", di_driver_name(phci_node),
		    di_instance(phci_node), path_state_name(di_path_state(pi)));

		(void) dump_prop_list(&pathprop_dumpops, NULL, ilev + 1,
		    pi, DDI_DEV_T_ANY, NULL);
	}
}

static int
dump_minor_data_links(di_devlink_t devlink, void *arg)
{
	int ilev = (intptr_t)arg;
	indent_to_level(ilev);
	(void) printf("dev_link=%s\n", di_devlink_path(devlink));
	return (DI_WALK_CONTINUE);
}

static void
dump_minor_data_paths(int ilev, di_minor_t minor,
    di_devlink_handle_t devlink_hdl)
{
	char	*path, *type;
	int	spec_type;

	/* get the path to the device and the minor node name */
	if ((path = di_devfs_minor_path(minor)) == NULL)
		exit(_error("failed to allocate memory"));

	/* display the path to this minor node */
	indent_to_level(ilev);
	(void) printf("dev_path=%s\n", path);

	if (devlink_hdl != NULL) {

		/* get the device minor node information */
		spec_type = di_minor_spectype(minor);
		switch (di_minor_type(minor)) {
			case DDM_MINOR:
				type = "minor";
				break;
			case DDM_ALIAS:
				type = "alias";
				break;
			case DDM_DEFAULT:
				type = "default";
				break;
			case DDM_INTERNAL_PATH:
				type = "internal";
				break;
			default:
				type = "unknown";
				break;
		}

		/* display the device minor node information */
		indent_to_level(ilev + 1);
		(void) printf("spectype=%s type=%s\n",
		    (spec_type == S_IFBLK) ? "blk" : "chr", type);

		/* display all the devlinks for this device minor node */
		(void) di_devlink_walk(devlink_hdl, NULL, path,
		    0, (void *)(intptr_t)(ilev + 1), dump_minor_data_links);
	}

	di_devfs_path_free(path);
}

static void
create_minor_list(di_node_t node)
{
	di_minor_t	minor, minor_head, minor_tail, minor_prev, minor_walk;
	int		major;

	/* if there are no minor nodes, bail */
	if (di_minor_next(node, DI_MINOR_NIL) == DI_MINOR_NIL)
		return;

	/*
	 * here we want to create lists of minor nodes with the same
	 * dev_t.  to do this we first sort all the minor nodes by devt.
	 *
	 * the algorithm used here is a bubble sort, so performance sucks.
	 * but it's probably ok here because most device instances don't
	 * have that many minor nodes.  also we're doing this as we're
	 * displaying each node so it doesn't look like we're pausing
	 * output for a long time.
	 */
	major = di_driver_major(node);
	minor_head = minor_tail = minor = DI_MINOR_NIL;
	while ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) {
		dev_t	dev = di_minor_devt(minor);

		/* skip /pseudo/clone@0 minor nodes */
		if (major != major(dev))
			continue;

		minor_ptr_set(minor, DI_MINOR_NIL);
		if (minor_head == DI_MINOR_NIL) {
			/* this is the first minor node we're looking at */
			minor_head = minor_tail = minor;
			continue;
		}

		/*
		 * if the new dev is less than the old dev, update minor_head
		 * so it points to the beginning of the list.  ie it points
		 * to the node with the lowest dev value
		 */
		if (dev <= di_minor_devt(minor_head)) {
			minor_ptr_set(minor, minor_head);
			minor_head = minor;
			continue;
		}

		minor_prev = minor_head;
		minor_walk = minor_ptr(minor_head);
		while ((minor_walk != DI_MINOR_NIL) &&
		    (dev > di_minor_devt(minor_walk))) {
			minor_prev = minor_walk;
			minor_walk = minor_ptr(minor_walk);
		}
		minor_ptr_set(minor, minor_walk);
		minor_ptr_set(minor_prev, minor);
		if (minor_walk == NULL)
			minor_tail = minor;
	}

	/* check if there were any non /pseudo/clone@0 nodes.  if not, bail */
	if (minor_head == DI_MINOR_NIL)
		return;

	/*
	 * now that we have a list of minor nodes sorted by devt
	 * we walk through the list and break apart the entire list
	 * to create circular lists of minor nodes with matching devts.
	 */
	minor_prev = minor_head;
	minor_walk = minor_ptr(minor_head);
	while (minor_walk != DI_MINOR_NIL) {
		if (di_minor_devt(minor_prev) != di_minor_devt(minor_walk)) {
			minor_ptr_set(minor_prev, minor_head);
			minor_head = minor_walk;
		}
		minor_prev = minor_walk;
		minor_walk = minor_ptr(minor_walk);
	}
	minor_ptr_set(minor_tail, minor_head);
}

static void
link_lnode_disp(di_link_t link, uint_t endpoint, int ilev,
    di_devlink_handle_t devlink_hdl)
{
	di_lnode_t	lnode;
	char		*name, *path;
	int		displayed_path, spec_type;
	di_node_t	node = DI_NODE_NIL;
	dev_t		devt = DDI_DEV_T_NONE;

	lnode = di_link_to_lnode(link, endpoint);

	indent_to_level(ilev);
	name = di_lnode_name(lnode);
	spec_type = di_link_spectype(link);

	(void) printf("mod=%s", name);

	/*
	 * if we're displaying the source of a link, we should display
	 * the target access mode.  (either block or char.)
	 */
	if (endpoint == DI_LINK_SRC)
		(void) printf(" accesstype=%s",
		    (spec_type == S_IFBLK) ? "blk" : "chr");

	/*
	 * check if the lnode is bound to a specific device
	 * minor node (i.e.  if it's bound to a dev_t) and
	 * if so display the dev_t value and any possible
	 * minor node pathing information.
	 */
	displayed_path = 0;
	if (di_lnode_devt(lnode, &devt) == 0) {
		di_minor_t	minor = DI_MINOR_NIL;

		(void) printf(" dev=(%u,%u)\n",
		    (uint_t)major(devt), (uint_t)minor(devt));

		/* display paths to the src devt minor node */
		while (minor = di_minor_next(node, minor)) {
			if (devt != di_minor_devt(minor))
				continue;

			if ((endpoint == DI_LINK_TGT) &&
			    (spec_type != di_minor_spectype(minor)))
				continue;

			dump_minor_data_paths(ilev + 1, minor, devlink_hdl);
			displayed_path = 1;
		}
	} else {
		(void) printf("\n");
	}

	if (displayed_path)
		return;

	/*
	 * This device lnode is not did not have any minor node
	 * pathing information so display the path to device node.
	 */
	node = di_lnode_devinfo(lnode);
	if ((path = di_devfs_path(node)) == NULL)
		exit(_error("failed to allocate memory"));

	indent_to_level(ilev + 1);
	(void) printf("dev_path=%s\n", path);
	di_devfs_path_free(path);
}

static void
dump_minor_link_data(int ilev, di_node_t node, dev_t devt,
    di_devlink_handle_t devlink_hdl)
{
	int		first = 1;
	di_link_t	link;

	link = DI_LINK_NIL;
	while (link = di_link_next_by_node(node, link, DI_LINK_TGT)) {
		di_lnode_t	tgt_lnode;
		dev_t		tgt_devt = DDI_DEV_T_NONE;

		tgt_lnode = di_link_to_lnode(link, DI_LINK_TGT);

		if (di_lnode_devt(tgt_lnode, &tgt_devt) != 0)
			continue;

		if (devt != tgt_devt)
			continue;

		if (first) {
			first = 0;
			indent_to_level(ilev);
			(void) printf("Device Minor Layered Under:\n");
		}

		/* displayed this lnode */
		lnode_displayed_set(tgt_lnode);
		link_lnode_disp(link, DI_LINK_SRC, ilev + 1, devlink_hdl);
	}

	link = DI_LINK_NIL;
	while (link = di_link_next_by_node(node, link, DI_LINK_SRC)) {
		di_lnode_t	src_lnode;
		dev_t		src_devt = DDI_DEV_T_NONE;

		src_lnode = di_link_to_lnode(link, DI_LINK_SRC);

		if (di_lnode_devt(src_lnode, &src_devt) != 0)
			continue;

		if (devt != src_devt)
			continue;

		if (first) {
			first = 0;
			indent_to_level(ilev);
			(void) printf("Device Minor Layered Over:\n");
		}

		/* displayed this lnode */
		lnode_displayed_set(src_lnode);
		link_lnode_disp(link, DI_LINK_TGT, ilev + 1, devlink_hdl);
	}
}

static void
dump_minor_data(int ilev, di_node_t node, di_devlink_handle_t devlink_hdl)
{
	di_minor_t	minor, minor_next;
	di_lnode_t	lnode;
	di_link_t	link;
	int		major, firstminor = 1;

	/*
	 * first go through and mark all lnodes and minor nodes for this
	 * node as undisplayed
	 */
	lnode = DI_LNODE_NIL;
	while (lnode = di_lnode_next(node, lnode))
		lnode_displayed_clear(lnode);
	minor = DI_MINOR_NIL;
	while (minor = di_minor_next(node, minor)) {
		minor_displayed_clear(minor);
	}

	/*
	 * when we display the minor nodes we want to coalesce nodes
	 * that have the same dev_t.  we do this by creating circular
	 * lists of minor nodes with the same devt.
	 */
	create_minor_list(node);

	/* now we display the driver defined minor nodes */
	major = di_driver_major(node);
	minor = DI_MINOR_NIL;
	while ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) {
		dev_t	devt;

		/*
		 * skip /pseudo/clone@0 minor nodes.
		 * these are only created for DLPIv2 network devices.
		 * since these minor nodes are associated with a driver
		 * and are only bound to a device instance after they
		 * are opened and attached we don't print them out
		 * here.
		 */
		devt = di_minor_devt(minor);
		if (major != major(devt))
			continue;

		/* skip nodes that may have already been displayed */
		if (minor_displayed(minor))
			continue;

		if (firstminor) {
			firstminor = 0;
			indent_to_level(ilev++);
			(void) printf("Device Minor Nodes:\n");
		}

		/* display the device minor node information */
		indent_to_level(ilev);
		(void) printf("dev=(%u,%u)\n",
		    (uint_t)major(devt), (uint_t)minor(devt));

		minor_next = minor;
		do {
			/* display device minor node path info */
			minor_displayed_set(minor_next);
			dump_minor_data_paths(ilev + 1, minor_next,
			    devlink_hdl);

			/* get a pointer to the next node */
			minor_next = minor_ptr(minor_next);
		} while (minor_next != minor);

		/* display who has this device minor node open */
		dump_minor_link_data(ilev + 1, node, devt, devlink_hdl);

		/* display properties associated with this devt */
		(void) dump_prop_list(&drvprop_dumpops, "Minor",
		    ilev + 1, node, devt, NULL);
	}

	/*
	 * now go through all the target lnodes for this node and
	 * if they haven't yet been displayed, display them now.
	 *
	 * this happens in the case of clone opens when an "official"
	 * minor node does not exist for the opened devt
	 */
	link = DI_LINK_NIL;
	while (link = di_link_next_by_node(node, link, DI_LINK_TGT)) {
		dev_t		devt;

		lnode = di_link_to_lnode(link, DI_LINK_TGT);

		/* if we've already displayed this target lnode, skip it */
		if (lnode_displayed(lnode))
			continue;

		if (firstminor) {
			firstminor = 0;
			indent_to_level(ilev++);
			(void) printf("Device Minor Nodes:\n");
		}

		/* display the device minor node information */
		indent_to_level(ilev);
		(void) di_lnode_devt(lnode, &devt);
		(void) printf("dev=(%u,%u)\n",
		    (uint_t)major(devt), (uint_t)minor(devt));

		indent_to_level(ilev + 1);
		(void) printf("dev_path=<clone>\n");

		/* display who has this cloned device minor node open */
		dump_minor_link_data(ilev + 1, node, devt, devlink_hdl);

		/* mark node as displayed */
		lnode_displayed_set(lnode);
	}
}

static void
dump_link_data(int ilev, di_node_t node, di_devlink_handle_t devlink_hdl)
{
	int		first = 1;
	di_link_t	link;

	link = DI_LINK_NIL;
	while (link = di_link_next_by_node(node, link, DI_LINK_SRC)) {
		di_lnode_t	src_lnode;
		dev_t		src_devt = DDI_DEV_T_NONE;

		src_lnode = di_link_to_lnode(link, DI_LINK_SRC);

		/*
		 * here we only want to print out layering information
		 * if we are the source and our source lnode is not
		 * associated with any particular dev_t.  (which means
		 * we won't display this link while dumping minor node
		 * info.)
		 */
		if (di_lnode_devt(src_lnode, &src_devt) != -1)
			continue;

		if (first) {
			first = 0;
			indent_to_level(ilev);
			(void) printf("Device Layered Over:\n");
		}

		/* displayed this lnode */
		link_lnode_disp(link, DI_LINK_TGT, ilev + 1, devlink_hdl);
	}
}

/*
 * certain 'known' property names may contain 'composite' strings.
 * Handle them here, and print them as 'string1' + 'string2' ...
 */
static int
print_composite_string(const char *var, char *value, int size)
{
	char *p, *q;
	char *firstp;

	if ((strcmp(var, "version") != 0) &&
	    (strcmp(var, "compatible") != 0))
		return (0);	/* Not a known composite string */

	/*
	 * Verify that each string in the composite string is non-NULL,
	 * is within the bounds of the property length, and contains
	 * printable characters or white space. Otherwise let the
	 * caller deal with it.
	 */
	for (firstp = p = value; p < (value + size); p += strlen(p) + 1) {
		if (strlen(p) == 0)
			return (0);		/* NULL string */
		for (q = p; *q; q++) {
			if (!(isascii(*q) && (isprint(*q) || isspace(*q))))
				return (0);	/* Not printable or space */
		}
		if (q > (firstp + size))
			return (0);		/* Out of bounds */
	}

	for (firstp = p = value; p < (value + size); p += strlen(p) + 1) {
		if (p == firstp)
			(void) printf("'%s'", p);
		else
			(void) printf(" + '%s'", p);
	}
	(void) putchar('\n');
	return (1);
}

/*
 * Print one property and its value. Handle the verbose case.
 */
static void
print_one(nvpair_t *nvp, int level)
{
	int i;
	int endswap = 0;
	uint_t valsize;
	char *value;
	char *var = nvpair_name(nvp);

	indent_to_level(level);
	(void) printf("%s: ", var);

	switch (nvpair_type(nvp)) {
	case DATA_TYPE_BOOLEAN:
		(void) printf(" \n");
		return;
	case DATA_TYPE_BYTE_ARRAY:
		if (nvpair_value_byte_array(nvp, (uchar_t **)&value,
		    &valsize)) {
			(void) printf("data not available.\n");
			return;
		}
		valsize--;	/* take out null added by driver */

		/*
		 * Do not print valsize > MAXVALSIZE, to be compatible
		 * with old behavior. E.g. intel's eisa-nvram property
		 * has a size of 65 K.
		 */
		if (valsize > MAXVALSIZE) {
			(void) printf(" \n");
			return;
		}
		break;
	default:
		(void) printf("data type unexpected.\n");
		return;
	}

	/*
	 * Handle printing verbosely
	 */
	if (print_composite_string(var, value, valsize)) {
		return;
	}

	if (!unprintable(value, valsize)) {
		(void) printf(" '%s'\n", value);
		return;
	}

	(void) printf(" ");
#ifdef	__x86
	/*
	 * Due to backwards compatibility constraints x86 int
	 * properties are not in big-endian (ieee 1275) byte order.
	 * If we have a property that is a multiple of 4 bytes,
	 * let's assume it is an array of ints and print the bytes
	 * in little endian order to make things look nicer for
	 * the user.
	 */
	endswap = (valsize % 4) == 0;
#endif	/* __x86 */
	for (i = 0; i < valsize; i++) {
		int out;
		if (i && (i % 4 == 0))
			(void) putchar('.');
		if (endswap)
			out = value[i + (3 - 2 * (i % 4))] & 0xff;
		else
			out = value[i] & 0xff;

		(void) printf("%02x", out);
	}
	(void) putchar('\n');
}

static int
unprintable(char *value, int size)
{
	int i;

	/*
	 * Is this just a zero?
	 */
	if (size == 0 || value[0] == '\0')
		return (1);
	/*
	 * If any character is unprintable, or if a null appears
	 * anywhere except at the end of a string, the whole
	 * property is "unprintable".
	 */
	for (i = 0; i < size; ++i) {
		if (value[i] == '\0')
			return (i != (size - 1));
		if (!isascii(value[i]) || iscntrl(value[i]))
			return (1);
	}
	return (0);
}

static int
promopen(int oflag)
{
	for (;;)  {
		if ((prom_fd = open(opts.o_promdev, oflag)) < 0)  {
			if (errno == EAGAIN)   {
				(void) sleep(5);
				continue;
			}
			if (errno == ENXIO)
				return (-1);
			if (getzoneid() == GLOBAL_ZONEID) {
				_exit(_error("cannot open %s",
				    opts.o_promdev));
			}
			/* not an error if this isn't the global zone */
			(void) _error(NULL, "openprom facility not available");
			exit(0);
		} else
			return (0);
	}
}

static void
promclose(void)
{
	if (close(prom_fd) < 0)
		exit(_error("close error on %s", opts.o_promdev));
}

/*
 * Get and print the name of the frame buffer device.
 */
int
do_fbname(void)
{
	int	retval;
	char fbuf_path[MAXPATHLEN];

	retval =  modctl(MODGETFBNAME, (caddr_t)fbuf_path);

	if (retval == 0) {
		(void) printf("%s\n", fbuf_path);
	} else {
		if (retval == EFAULT) {
			(void) fprintf(stderr,
			"Error copying fb path to userland\n");
		} else {
			(void) fprintf(stderr,
			"Console output device is not a frame buffer\n");
		}
		return (1);
	}
	return (0);
}

/*
 * Get and print the PROM version.
 */
int
do_promversion(void)
{
	Oppbuf	oppbuf;
	struct openpromio *opp = &(oppbuf.opp);

	if (promopen(O_RDONLY))  {
		(void) fprintf(stderr, "Cannot open openprom device\n");
		return (1);
	}

	opp->oprom_size = MAXVALSIZE;
	if (ioctl(prom_fd, OPROMGETVERSION, opp) < 0)
		exit(_error("OPROMGETVERSION"));

	(void) printf("%s\n", opp->oprom_array);
	promclose();
	return (0);
}

int
do_prom_version64(void)
{
#ifdef	sparc
	Oppbuf	oppbuf;
	struct openpromio *opp = &(oppbuf.opp);
	/*LINTED*/
	struct openprom_opr64 *opr = (struct openprom_opr64 *)opp->oprom_array;

	static const char msg[] =
	    "NOTICE: The firmware on this system does not support the "
	    "64-bit OS.\n"
	    "\tPlease upgrade to at least the following version:\n"
	    "\t\t%s\n\n";

	if (promopen(O_RDONLY))  {
		(void) fprintf(stderr, "Cannot open openprom device\n");
		return (-1);
	}

	opp->oprom_size = MAXVALSIZE;
	if (ioctl(prom_fd, OPROMREADY64, opp) < 0)
		exit(_error("OPROMREADY64"));

	if (opr->return_code == 0)
		return (0);

	(void) printf(msg, opr->message);

	promclose();
	return (opr->return_code);
#else
	return (0);
#endif
}

int
do_productinfo(void)
{
	di_node_t root, next_node;
	di_prom_handle_t promh;
	static const char *root_prop[] = { "name", "model", "banner-name",
					"compatible" };
	static const char *root_propv[] = { "name", "model", "banner-name",
					"compatible", "idprom" };
	static const char *oprom_prop[] = { "model", "version" };


	root = di_init("/", DINFOCPYALL);

	if (root == DI_NODE_NIL) {
		(void) fprintf(stderr, "di_init() failed\n");
		return (1);
	}

	promh = di_prom_init();

	if (promh == DI_PROM_HANDLE_NIL) {
		(void) fprintf(stderr, "di_prom_init() failed\n");
		return (1);
	}

	if (opts.o_verbose) {
		dump_prodinfo(promh, root, root_propv, "root",
		    NUM_ELEMENTS(root_propv));

		/* Get model and version properties under node "openprom" */
		next_node = find_node_by_name(promh, root, "openprom");
		if (next_node != DI_NODE_NIL)
			dump_prodinfo(promh, next_node, oprom_prop,
			    "openprom", NUM_ELEMENTS(oprom_prop));

	} else
		dump_prodinfo(promh, root, root_prop, "root",
		    NUM_ELEMENTS(root_prop));
	di_prom_fini(promh);
	di_fini(root);
	return (0);
}

di_node_t
find_node_by_name(di_prom_handle_t promh, di_node_t parent,
		char *node_name)
{
	di_node_t next_node;
	uchar_t *prop_valp;

	for (next_node = di_child_node(parent); next_node != DI_NODE_NIL;
	    next_node = di_sibling_node(next_node)) {
		int len;

		len = get_propval_by_name(promh, next_node, "name", &prop_valp);
		if ((len != -1) && (strcmp((char *)prop_valp, node_name) == 0))
			return (next_node);
	}
	return (DI_NODE_NIL);
}


int
get_propval_by_name(di_prom_handle_t promh, di_node_t node, const char *name,
			uchar_t **valp)
{
	int len;
	uchar_t *bufp;

	len = di_prom_prop_lookup_bytes(promh, node, name,
	    (uchar_t **)&bufp);
	if (len != -1) {
		*valp = (uchar_t *)malloc(len);
		(void) memcpy(*valp, bufp, len);
	}
	return (len);
}


static void
dump_prodinfo(di_prom_handle_t promh, di_node_t node, const char **propstr,
		char *node_name, int num)
{
	int out, len, index1, index, endswap = 0;
	uchar_t *prop_valp;

	for (index1 = 0; index1 < num; index1++) {
		len = get_propval_by_name(promh, node, propstr[index1],
		    &prop_valp);
		if (len != -1) {
			if (strcmp(node_name, "root"))
				(void) printf("%s ", node_name);

			(void) printf("%s: ", propstr[index1]);

			if (print_composite_string((const char *)
			    propstr[index1], (char *)prop_valp, len)) {
				free(prop_valp);
				continue;
			}

			if (!unprintable((char *)prop_valp, len)) {
				(void) printf(" %s\n", (char *)prop_valp);
				free(prop_valp);
				continue;
			}

			(void) printf(" ");
#ifdef  __x86
			endswap = (len % 4) == 0;
#endif  /* __x86 */
			for (index = 0; index < len; index++) {
				if (index && (index % 4 == 0))
					(void) putchar('.');
				if (endswap)
					out = prop_valp[index +
					    (3 - 2 * (index % 4))] & 0xff;
				else
					out = prop_valp[index] & 0xff;
				(void) printf("%02x", out);
			}
			(void) putchar('\n');
			free(prop_valp);
		}
	}
}

static int
dump_compatible(char *name, int ilev, di_node_t node)
{
	int	ncompat;
	char	*compat_array;
	char	*p, *q;
	int	i;

	if (node == DI_PATH_NIL)
		return (0);

	ncompat = di_compatible_names(node, &compat_array);
	if (ncompat <= 0)
		return (0);	/* no 'compatible' available */

	/* verify integrety of compat_array */
	for (i = 0, p = compat_array; i < ncompat; i++, p += strlen(p) + 1) {
		if (strlen(p) == 0)
			return (0);		/* NULL string */
		for (q = p; *q; q++) {
			if (!(isascii(*q) && (isprint(*q) || isspace(*q))))
				return (0);	/* Not printable or space */
		}
	}

	/* If name is non-NULL, produce header */
	if (name) {
		indent_to_level(ilev);
		(void) printf("%s properties:\n", name);
	}
	ilev++;

	/* process like a string array property */
	indent_to_level(ilev);
	(void) printf("name='compatible' type=string items=%d\n", ncompat);
	indent_to_level(ilev);
	(void) printf("    value=");
	for (i = 0, p = compat_array; i < (ncompat - 1);
	    i++, p += strlen(p) + 1)
		(void) printf("'%s' + ", p);
	(void) printf("'%s'", p);
	(void) putchar('\n');
	return (1);
}