root/usr/src/cmd/prtconf/pdevinfo.c
/*
 * 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.
 */
/*
 * Copyright (c) 2012, Joyent, Inc. All rights reserved.
 */
/*
 * Copyright (c) 2019 Peter Tribble.
 * Copyright 2022 Oxide Computer Company
 */

/*
 * 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 <err.h>
#include <upanic.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 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_private_set(di_node_t);
static int node_display_set(di_node_t, void *);
static void dump_pciid(char *, int, 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;

        dbgprintf("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)
                        err(-1, "di_prom_init() failed.");
        }

        if (opts.o_forcecache) {
                if (dbg.d_forceload) {
                        warnx("option combination not supported");
                }
                if (strcmp(rootpath, "/") != 0) {
                        errx(-1, "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)
                        err(-1, "di_devlink_init() failed.");
        } else
                root_node = di_init(rootpath, flag);

        if (root_node == DI_NODE_NIL) {
                warnx("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_private_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)) != DI_NODE_NIL)
                                node_display_private_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)) != DI_NODE_NIL)
                                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, 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)
                        err(-1, "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 */
                const char *msg = "internal error";
                upanic(msg, strlen(msg));
        }
        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_private_set(di_node_t node)
{
        long data = (long)di_node_private_get(node);
        data |= NODE_DISPLAY;
        di_node_private_set(node, (void *)data);
}

static int
node_display_set(di_node_t node, void *arg __unused)
{
        node_display_private_set(node);
        return (0);
}

#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 = DDI_DEV_T_NONE;
        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);
        }
}

static const char *
devinfo_is_pci(di_node_t node)
{
        char *t = NULL;
        di_node_t pnode = di_parent_node(node);

        if (di_prop_lookup_strings(DDI_DEV_T_ANY, pnode,
            "device_type", &t) <= 0)
                return (NULL);

        if (t == NULL || (strcmp(t, "pci") != 0 &&
            strcmp(t, "pciex") != 0))
                return (NULL);

        return (t);
}

/*
 * 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);
                dbgprintf("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) {
                int *vid, *did;
                const char *dtype = devinfo_is_pci(node);

                if (dtype != NULL &&
                    di_prop_lookup_ints(DDI_DEV_T_ANY, node, "vendor-id",
                    &vid) > 0 && vid[0] <= UINT16_MAX &&
                    di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-id",
                    &did) > 0 && did[0] <= UINT16_MAX) {
                        print_pciid(dtype, (uint16_t)vid[0], (uint16_t)did[0],
                            opts.o_pcidb);
                }
        }

        /*
         * 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)
                        printed |= dump_compatible(printed ? NULL : "Hardware",
                            ilev + 1, node);

                /* Ensure that pci id information is printed under Hardware */
                dump_pciid(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);
}

/*
 * 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 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)
                err(-1, "OPROMGETCONS");

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

int
do_prominfo(void)
{
        uint_t arg = 0;
        uint_t *ptr;
        uchar_t *prom_snapshot;

        if (promopen(O_RDONLY))  {
                err(-1, "openeepr device open failed");
        }

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

        /*
         * If we're eiher in verbose mode or asked to get device information,
         * then we need to actually ask for verbose information from the prom by
         * setting a non-zero value.
         */
        if (opts.o_verbose != 0 || opts.o_pciid != 0) {
                arg = 1;
        }

        /* OPROMSNAPSHOT returns size in arg */
        if (ioctl(prom_fd, OPROMSNAPSHOT, &arg) < 0)
                err(-1, "OPROMSNAPSHOT");

        /*
         * ioctl OPROMCOPYOUT is expecting buffer with at least
         * sizeof (uint_t).
         */
        if (arg < sizeof (uint_t))
                return (1);

        if ((ptr = malloc(arg)) == NULL)
                err(-1, "failed to allocate memory");

        /* copy out the snapshot for printing */
        *ptr = arg;
        prom_snapshot = (uchar_t *)ptr;
        if (ioctl(prom_fd, OPROMCOPYOUT, prom_snapshot) < 0)
                err(-1, "OPROMCOPYOUT");

        promclose();

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

        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))
                err(-1, "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))
                err(-1, "error processing snapshot");

        while ((child = nvlist_next_nvpair(cnvl, child)) != NULL) {
                char *name = nvpair_name(child);
                data_type_t type = nvpair_type(child);
                uchar_t *nodebuf;
                uint_t nodesize;
                if (strcmp("node", name) != 0) {
                        dbgprintf("unexpected nvpair name %s != name\n", name);
                        continue;
                }
                if (type != DATA_TYPE_BYTE_ARRAY) {
                        dbgprintf("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);
}

/*
 * The encoding of the name property depends on whether we got verbose prom
 * information or not. If we didn't, it'll just be a string in the nvlist_t.
 * However, otherwise it'll end up being byte data that the kernel guarantees
 * for 'name' is actually a null terminated string.
 */
static const char *
prom_node_name(nvlist_t *nvl)
{
        char *str;
        uchar_t *bval;
        uint_t len;

        if (nvlist_lookup_string(nvl, "name", &str) == 0) {
                return (str);
        }

        if (nvlist_lookup_byte_array(nvl, "name", &bval, &len) == 0) {
                if (bval[len - 1] == '\0')
                        return ((char *)bval);
        }

        return ("data not available");
}

/*
 * Given a node at a given level, try to determine if this is a PCI device. We
 * do this through a two step process mostly due to the fact that we don't have
 * easy linkage to the parent here and not all nodes have everything we expect.
 * This test is more similar to the pcieadm test than what we use for the normal
 * devinfo part.
 *
 * 1. Check the node name to see if it starts with pci with another character
 *   (to avoid the synthetic pci instances).
 * 2. Look at the compatible property for the class strings.
 */
static boolean_t
prom_is_pci(nvlist_t *nvl, const char *name)
{
        uchar_t *value;
        uint_t len;

        if (strncmp("pci", name, 3) == 0 && name[3] != '\0') {
                return (B_TRUE);
        }

        /*
         * This is a composite string. Unlike with devinfo, we just have the
         * array of strings here and we have to manually make sure we don't
         * exceed the size as we don't have the total number of entries.
         */
        if (nvlist_lookup_byte_array(nvl, "compatible", &value, &len) == 0) {
                const char *str;

                /*
                 * Adjust by one to account or the extra NUL that the driver
                 * inserts.
                 */
                len--;
                for (str = (char *)value; str < ((char *)value + len);
                    str += strlen(str) + 1) {
                        if (strncmp("pciclass,", str,
                            sizeof ("pciclass,") - 1) == 0 ||
                            strncmp("pciexclass,", str,
                            sizeof ("pciexclass,") - 1) == 0) {
                                return (B_TRUE);
                        }
                }
        }

        return (B_FALSE);
}

static boolean_t
prom_extract_u16(nvlist_t *nvl, const char *name, uint16_t *valp)
{
        uchar_t *value;
        uint_t len;
        uint32_t u32;

        if (nvlist_lookup_byte_array(nvl, name, &value, &len) != 0) {
                return (B_FALSE);
        }

        /*
         * A uint32_t will be encoded as a 4-byte value followed by a NUL
         * regardless.
         */
        if (len != 5 || value[4] != '\0') {
                return (B_FALSE);
        }

        /*
         * The current PROM code puts values in the native-endianness for x86
         * and SPARC as opposed to always translating into what 1275 wants of
         * big endian. It is unclear what'll happen for subsequent platforms.
         */
#if !defined(__x86)
#error "determine endianness of the platform's openprom interface"
#endif
        (void) memcpy(&u32, value, sizeof (u32));
        if (u32 > UINT16_MAX) {
                return (B_FALSE);
        }

        *valp = (uint16_t)u32;
        return (B_TRUE);
}

/*
 * Similar to the above, synthesize the device type as either pci or pciex based
 * on the compatible array. A PCI Express device will have their first entry
 * start with 'pciexXXXX,XXXX'. A device without that will just start with pci.
 */
static const char *
prom_pci_device_type(nvlist_t *nvl)
{
        uchar_t *value;
        uint_t len;

        if (nvlist_lookup_byte_array(nvl, "compatible", &value, &len) != 0) {
                return (NULL);
        }

        if (strncmp("pciex", (char *)value, 5) == 0 && value[5] != '\0') {
                return ("pciex");
        }

        if (strncmp("pci", (char *)value, 3) == 0 && value[3] != '\0') {
                return ("pci");
        }

        return (NULL);
}

static void
dump_pcidb(int level, uint16_t vid, uint16_t did, boolean_t do_sub,
    uint16_t svid, uint16_t sdid)
{
        const char *vstr = "unknown vendor";
        const char *dstr = "unknown device";
        const char *sstr = "unknown subsystem";
        pcidb_vendor_t *pciv = NULL;
        pcidb_device_t *pcid = NULL;
        pcidb_subvd_t *pcis = NULL;

        if (opts.o_pcidb == NULL)
                return;

        pciv = pcidb_lookup_vendor(opts.o_pcidb, vid);
        if (pciv != NULL) {
                vstr = pcidb_vendor_name(pciv);
                pcid = pcidb_lookup_device_by_vendor(pciv, did);
                if (pcid != NULL) {
                        dstr = pcidb_device_name(pcid);
                }
        }

        indent_to_level(level);
        (void) printf("vendor-name:  '%s'\n", vstr);
        indent_to_level(level);
        (void) printf("device-name:  '%s'\n", dstr);

        if (!do_sub)
                return;

        if (pciv != NULL && pcid != NULL) {
                pcis = pcidb_lookup_subvd_by_device(pcid, svid, sdid);
                if (pcis != NULL) {
                        sstr = pcidb_subvd_name(pcis);
                }
        }

        indent_to_level(level);
        (void) printf("subsystem-name:  '%s'\n", sstr);
}

/*
 * Print all properties and values. When o_verbose is specified then rather than
 * printing the name of the node (and potentially the PCI ID and DB info), we
 * print the node name and instead include all that information in properties.
 * This mimics the behavior of the non-prom path.
 */
static void
dump_node(nvlist_t *nvl, int level)
{
        int id = 0;
        const char *name;
        nvpair_t *nvp = NULL;

        indent_to_level(level);
        name = prom_node_name(nvl);
        (void) printf("Node");
        if (!opts.o_verbose) {
                (void) printf(" '%s'", name);

                if (opts.o_pciid && prom_is_pci(nvl, name)) {
                        const char *dtype = prom_pci_device_type(nvl);
                        uint16_t vid, did;

                        if (prom_extract_u16(nvl, "vendor-id", &vid) &&
                            prom_extract_u16(nvl, "device-id", &did) &&
                            dtype != NULL) {
                                print_pciid(dtype, vid, did, opts.o_pcidb);
                        }

                }
        } else {
                (void) nvlist_lookup_int32(nvl, "@nodeid", &id);
                (void) printf(" %#08x\n", id);
        }

        if (!opts.o_verbose) {
                (void) putchar('\n');
                return;
        }

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

                print_one(nvp, level + 1);
        }

        /*
         * Go through and create synthetic properties for PCI devices like the
         * normal device tree path.
         */
        if (prom_is_pci(nvl, name)) {
                uint16_t vid = UINT16_MAX, did = UINT16_MAX;
                uint16_t svid = UINT16_MAX, sdid = UINT16_MAX;
                boolean_t valid_sub = B_FALSE;

                if (prom_extract_u16(nvl, "subsystem-vendor-id", &svid) &&
                    prom_extract_u16(nvl, "subsystem-id", &sdid)) {
                        valid_sub = B_TRUE;
                }

                if (prom_extract_u16(nvl, "vendor-id", &vid) &&
                    prom_extract_u16(nvl, "device-id", &did)) {
                        dump_pcidb(level + 1, vid, did, valid_sub, svid, sdid);
                }
        }

        (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");
                case DI_PATH_STATE_UNKNOWN:
                default:
                        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)
                err(-1, "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)) != DI_MINOR_NIL) {
                        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)
                err(-1, "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_LINK_NIL) {
                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_LINK_NIL) {
                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)) != DI_LNODE_NIL)
                lnode_displayed_clear(lnode);
        minor = DI_MINOR_NIL;
        while ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) {
                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)) !=
            DI_LINK_NIL) {
                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_LINK_NIL) {
                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) {
                                err(-1, "cannot open %s", opts.o_promdev);
                        }
                        /* not an error if this isn't the global zone */
                        warnx("openprom facility not available");
                        exit(0);
                } else
                        return (0);
        }
}

static void
promclose(void)
{
        if (close(prom_fd) < 0)
                err(-1, "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)
                err(-1, "OPROMGETVERSION");

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

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

static void
dump_pciid(char *name, int ilev, di_node_t node)
{
        int *vid, *did, *svid, *sdid;
        const char *vname, *dname, *sname;
        pcidb_vendor_t *pciv;
        pcidb_device_t *pcid;
        pcidb_subvd_t *pcis;

        const char *unov = "unknown vendor";
        const char *unod = "unknown device";
        const char *unos = "unknown subsystem";

        if (opts.o_pcidb == NULL)
                return;

        vname = unov;
        dname = unod;
        sname = unos;

        if (devinfo_is_pci(node) == NULL) {
                return;
        }

        /*
         * All devices should have a vendor and device id, if we fail to find
         * one, then we're going to return right here and not print anything.
         *
         * We're going to also check for the subsystem-vendor-id and
         * subsystem-id. If we don't find one of them, we're going to assume
         * that this device does not have one. In that case, we will never
         * attempt to try and print anything related to that. If it does have
         * both, then we are going to look them up and print the appropriate
         * string if we find it or not.
         */
        if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "vendor-id", &vid) <= 0)
                return;

        if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-id", &did) <= 0)
                return;

        if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "subsystem-vendor-id",
            &svid) <= 0 || di_prop_lookup_ints(DDI_DEV_T_ANY, node,
            "subsystem-id", &sdid) <= 0) {
                svid = NULL;
                sdid = NULL;
                sname = NULL;
        }

        pciv = pcidb_lookup_vendor(opts.o_pcidb, vid[0]);
        if (pciv == NULL)
                goto print;
        vname = pcidb_vendor_name(pciv);

        pcid = pcidb_lookup_device_by_vendor(pciv, did[0]);
        if (pcid == NULL)
                goto print;
        dname = pcidb_device_name(pcid);

        if (svid != NULL) {
                pcis = pcidb_lookup_subvd_by_device(pcid, svid[0], sdid[0]);
                if (pcis == NULL)
                        goto print;
                sname = pcidb_subvd_name(pcis);
        }

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

        /* These are all going to be single string properties */
        indent_to_level(ilev);
        (void) printf("name='vendor-name' type=string items=1\n");
        indent_to_level(ilev);
        (void) printf("    value='%s'\n", vname);

        indent_to_level(ilev);
        (void) printf("name='device-name' type=string items=1\n");
        indent_to_level(ilev);
        (void) printf("    value='%s'\n", dname);

        if (sname != NULL) {
                indent_to_level(ilev);
                (void) printf("name='subsystem-name' type=string items=1\n");
                indent_to_level(ilev);
                (void) printf("    value='%s'\n", sname);
        }
}