root/usr/src/uts/sun4v/promif/promif_stree.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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

#include <sys/promif_impl.h>
#include <sys/kmem.h>
#include <sys/machsystm.h>

/*
 * A property attached to a node in the kernel's
 * shadow copy of the PROM device tree.
 */
typedef struct prom_prop {
        struct prom_prop *pp_next;
        char             *pp_name;
        int              pp_len;
        void             *pp_val;
} prom_prop_t;

/*
 * A node in the kernel's shadow copy of the PROM
 * device tree.
 */
typedef struct prom_node {
        pnode_t                 pn_nodeid;
        struct prom_prop        *pn_propp;
        struct prom_node        *pn_parent;
        struct prom_node        *pn_child;
        struct prom_node        *pn_sibling;
} prom_node_t;

static prom_node_t *promif_root;

static prom_node_t *find_node(pnode_t nodeid);
static prom_node_t *find_node_work(prom_node_t *np, pnode_t node);
static int getproplen(prom_node_t *pnp, char *name);
static void *getprop(prom_node_t *pnp, char *name);
static char *nextprop(prom_node_t *pnp, char *name);

#ifndef _KMDB
static void create_prop(prom_node_t *pnp, char *name, void *val, int len);
static prom_node_t *create_node(prom_node_t *parent, pnode_t node);
static void create_peers(prom_node_t *pnp, pnode_t node);
static void create_children(prom_node_t *pnp, pnode_t parent);
#endif

/*
 * Hooks for kmdb for accessing the PROM shadow tree. The driver portion
 * of kmdb will retrieve the root of the tree and pass it down to the
 * debugger portion of kmdb. As the kmdb debugger is standalone, it has
 * its own promif_root pointer that it will be set to the value passed by
 * the driver so that kmdb points to the shadow tree maintained by the kernel.
 * So the "get" function is in the kernel while the "set" function is in kmdb.
 */
#ifdef _KMDB
void
promif_stree_setroot(void *root)
{
        promif_root = (prom_node_t *)root;
}
#else
void *
promif_stree_getroot(void)
{
        return (promif_root);
}
#endif

/*
 * Interfaces used internally by promif functions.
 * These hide all accesses to the shadow tree.
 */

pnode_t
promif_stree_parentnode(pnode_t nodeid)
{
        prom_node_t *pnp;

        pnp = find_node(nodeid);
        if (pnp && pnp->pn_parent) {
                return (pnp->pn_parent->pn_nodeid);
        }

        return (OBP_NONODE);
}

pnode_t
promif_stree_childnode(pnode_t nodeid)
{
        prom_node_t *pnp;

        pnp = find_node(nodeid);
        if (pnp && pnp->pn_child)
                return (pnp->pn_child->pn_nodeid);

        return (OBP_NONODE);
}

pnode_t
promif_stree_nextnode(pnode_t nodeid)
{
        prom_node_t *pnp;

        /*
         * Note: next(0) returns the root node
         */
        pnp = find_node(nodeid);
        if (pnp && (nodeid == OBP_NONODE))
                return (pnp->pn_nodeid);
        if (pnp && pnp->pn_sibling)
                return (pnp->pn_sibling->pn_nodeid);

        return (OBP_NONODE);
}

int
promif_stree_getproplen(pnode_t nodeid, char *name)
{
        prom_node_t *pnp;

        pnp = find_node(nodeid);
        if (pnp == NULL)
                return (-1);

        return (getproplen(pnp, name));
}

int
promif_stree_getprop(pnode_t nodeid, char *name, void *value)
{
        prom_node_t     *pnp;
        void            *prop;
        int             len;

        pnp = find_node(nodeid);
        if (pnp == NULL) {
                prom_printf("find_node: no node?\n");
                return (-1);
        }

        len = getproplen(pnp, name);
        if (len > 0) {
                prop = getprop(pnp, name);
                bcopy(prop, value, len);
        } else {
                prom_printf("find_node: getproplen: %d\n", len);
        }

        return (len);
}

char *
promif_stree_nextprop(pnode_t nodeid, char *name, char *next)
{
        prom_node_t     *pnp;
        char            *propname;

        next[0] = '\0';

        pnp = find_node(nodeid);
        if (pnp == NULL)
                return (NULL);

        propname = nextprop(pnp, name);
        if (propname == NULL)
                return (next);

        (void) prom_strcpy(next, propname);

        return (next);
}

static prom_node_t *
find_node_work(prom_node_t *np, pnode_t node)
{
        prom_node_t *nnp;
        prom_node_t *snp;

        for (snp = np; snp != NULL; snp = snp->pn_sibling) {
                if (snp->pn_nodeid == node)
                        return (snp);

                if (snp->pn_child)
                        if ((nnp = find_node_work(snp->pn_child, node)) != NULL)
                                return (nnp);
        }

        return (NULL);
}

static prom_node_t *
find_node(pnode_t nodeid)
{

        if (nodeid == OBP_NONODE)
                return (promif_root);

        if (promif_root == NULL)
                return (NULL);

        return (find_node_work(promif_root, nodeid));
}

static int
getproplen(prom_node_t *pnp, char *name)
{
        struct prom_prop *propp;

        for (propp = pnp->pn_propp; propp != NULL; propp = propp->pp_next)
                if (prom_strcmp(propp->pp_name, name) == 0)
                        return (propp->pp_len);

        return (-1);
}

static void *
getprop(prom_node_t *np, char *name)
{
        struct prom_prop *propp;

        for (propp = np->pn_propp; propp != NULL; propp = propp->pp_next)
                if (prom_strcmp(propp->pp_name, name) == 0)
                        return (propp->pp_val);

        return (NULL);
}

static char *
nextprop(prom_node_t *pnp, char *name)
{
        struct prom_prop *propp;

        /*
         * getting next of NULL or a null string returns the first prop name
         */
        if (name == NULL || *name == '\0')
                if (pnp->pn_propp)
                        return (pnp->pn_propp->pp_name);

        for (propp = pnp->pn_propp; propp != NULL; propp = propp->pp_next)
                if (prom_strcmp(propp->pp_name, name) == 0)
                        if (propp->pp_next)
                                return (propp->pp_next->pp_name);

        return (NULL);
}

#ifndef _KMDB

int
promif_stree_setprop(pnode_t nodeid, char *name, void *value, int len)
{
        prom_node_t             *pnp;
        struct prom_prop        *prop;

        pnp = find_node(nodeid);
        if (pnp == NULL) {
                prom_printf("find_node: no node?\n");
                return (-1);
        }

        /*
         * If a property with this name exists, replace the existing
         * value.
         */
        for (prop = pnp->pn_propp; prop; prop = prop->pp_next)
                if (prom_strcmp(prop->pp_name, name) == 0) {
                        /*
                         * Make sure we don't get dispatched onto a
                         * different cpu if we happen to sleep.  See
                         * kern_postprom().
                         */
                        thread_affinity_set(curthread, CPU->cpu_id);
                        kmem_free(prop->pp_val, prop->pp_len);

                        prop->pp_val = NULL;
                        if (len > 0) {
                                prop->pp_val = kmem_zalloc(len, KM_SLEEP);
                                bcopy(value, prop->pp_val, len);
                        }
                        thread_affinity_clear(curthread);
                        prop->pp_len = len;
                        return (len);
                }

        return (-1);
}

/*
 * Create a promif private copy of boot's device tree.
 */
void
promif_stree_init(void)
{
        pnode_t         node;
        prom_node_t     *pnp;

        node = prom_rootnode();
        promif_root = pnp = create_node(OBP_NONODE, node);

        create_peers(pnp, node);
        create_children(pnp, node);
}

static void
create_children(prom_node_t *pnp, pnode_t parent)
{
        prom_node_t     *cnp;
        pnode_t         child;

        _NOTE(CONSTCOND)
        while (1) {
                child = prom_childnode(parent);
                if (child == 0)
                        break;
                if (prom_getproplen(child, "name") <= 0) {
                        parent = child;
                        continue;
                }
                cnp = create_node(pnp, child);
                pnp->pn_child = cnp;
                create_peers(cnp, child);
                pnp = cnp;
                parent = child;
        }
}

static void
create_peers(prom_node_t *np, pnode_t node)
{
        prom_node_t     *pnp;
        pnode_t         peer;

        _NOTE(CONSTCOND)
        while (1) {
                peer = prom_nextnode(node);
                if (peer == 0)
                        break;
                if (prom_getproplen(peer, "name") <= 0) {
                        node = peer;
                        continue;
                }
                pnp = create_node(np->pn_parent, peer);
                np->pn_sibling = pnp;
                create_children(pnp, peer);
                np = pnp;
                node = peer;
        }
}

static prom_node_t *
create_node(prom_node_t *parent, pnode_t node)
{
        prom_node_t     *pnp;
        char            prvname[OBP_MAXPROPNAME];
        char            propname[OBP_MAXPROPNAME];
        int             proplen;
        void            *propval;

        /*
         * Make sure we don't get dispatched onto a different
         * cpu if we happen to sleep.  See kern_postprom().
         */
        thread_affinity_set(curthread, CPU->cpu_id);

        pnp = kmem_zalloc(sizeof (prom_node_t), KM_SLEEP);
        pnp->pn_nodeid = node;
        pnp->pn_parent = parent;

        prvname[0] = '\0';

        _NOTE(CONSTCOND)
        while (1) {
                (void) prom_nextprop(node, prvname, propname);
                if (prom_strlen(propname) == 0)
                        break;
                if ((proplen = prom_getproplen(node, propname)) == -1)
                        continue;
                propval = NULL;
                if (proplen != 0) {
                        propval = kmem_zalloc(proplen, KM_SLEEP);
                        (void) prom_getprop(node, propname, propval);
                }
                create_prop(pnp, propname, propval, proplen);

                (void) prom_strcpy(prvname, propname);
        }

        thread_affinity_clear(curthread);

        return (pnp);
}

static void
create_prop(prom_node_t *pnp, char *name, void *val, int len)
{
        struct prom_prop        *prop;
        struct prom_prop        *newprop;

        /*
         * Make sure we don't get dispatched onto a different
         * cpu if we happen to sleep.  See kern_postprom().
         */
        thread_affinity_set(curthread, CPU->cpu_id);
        newprop = kmem_zalloc(sizeof (*newprop), KM_SLEEP);
        newprop->pp_name = kmem_zalloc(prom_strlen(name) + 1, KM_SLEEP);
        thread_affinity_clear(curthread);

        (void) prom_strcpy(newprop->pp_name, name);
        newprop->pp_val = val;
        newprop->pp_len = len;

        if (pnp->pn_propp == NULL) {
                pnp->pn_propp = newprop;
                return;
        }

        /* move to the end of the prop list */
        for (prop = pnp->pn_propp; prop->pp_next != NULL; prop = prop->pp_next)
                /* empty */;

        /* append the new prop */
        prop->pp_next = newprop;
}

static void
promif_dump_tree(prom_node_t *pnp)
{
        int             i;
        static int      level = 0;

        if (pnp == NULL)
                return;

        for (i = 0; i < level; i++) {
                prom_printf("    ");
        }

        prom_printf("Node 0x%x (parent=0x%x, sibling=0x%x)\n", pnp->pn_nodeid,
            (pnp->pn_parent) ? pnp->pn_parent->pn_nodeid : 0,
            (pnp->pn_sibling) ? pnp->pn_sibling->pn_nodeid : 0);

        if (pnp->pn_child != NULL) {
                level++;
                promif_dump_tree(pnp->pn_child);
                level--;
        }

        if (pnp->pn_sibling != NULL)
                promif_dump_tree(pnp->pn_sibling);
}

#endif