root/usr/src/lib/libprtdiag/common/pdevinfo_funcs.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <stdarg.h>
#include <errno.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <sys/openpromio.h>
#include <libintl.h>
#include "pdevinfo.h"
#include "display.h"
#include "pdevinfo_sun4u.h"

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


static int prom_fd;
extern char *progname;
extern char *promdev;
extern void getppdata();
extern void printppdata();

/*
 * Define DPRINT for run-time debugging printf's...
 * #define DPRINT       1
 */

#ifdef  DPRINT
static  char    vdebug_flag = 1;
#define dprintf if (vdebug_flag) printf
static void dprint_dev_info(caddr_t, dev_info_t *);
#endif  /* DPRINT */

#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN     "SYS_TEST"
#endif

/*VARARGS1*/
int
_error(char *fmt, ...)
{
        int saved_errno;
        va_list ap;
        extern int errno;
        saved_errno = errno;

        if (progname)
                (void) fprintf(stderr, "%s: ", progname);

        va_start(ap, fmt);

        (void) vfprintf(stderr, fmt, ap);

        va_end(ap);

        (void) fprintf(stderr, ": ");
        errno = saved_errno;
        perror("");

        return (2);
}

int
is_openprom(void)
{
        Oppbuf  oppbuf;
        register struct openpromio *opp = &(oppbuf.opp);
        register 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);
}

/*
 * Read all properties and values from nodes.
 * Copy the properties read into the prom_node passsed in.
 */
void
dump_node(Prom_node *node)
{
        Oppbuf oppbuf;
        register struct openpromio *opp = &oppbuf.opp;
        Prop *prop = NULL;      /* tail of properties list */
        StaticProp *temp;

        /* clear out pointers in pnode */
        node->props = NULL;

        /* get first prop by asking for null string */
        (void) memset((void *) oppbuf.buf, 0, BUFSIZE);

        /* allocate space for the property */
        if ((temp = malloc(sizeof (StaticProp))) == NULL) {
                perror("malloc");
                exit(1);
        }

        opp->oprom_size = MAXPROPSIZE;
        while (opp->oprom_size != 0) {
                Prop *new;
                int i;
                char *tempp, *newp;

                /*
                 * get property
                 */
                opp->oprom_size = MAXPROPSIZE;

                if (ioctl(prom_fd, OPROMNXTPROP, opp) < 0)
                        exit(_error("OPROMNXTPROP"));

                if (opp->oprom_size != 0) {
                        temp->name.opp.oprom_size = opp->oprom_size;
                        (void) strcpy(temp->name.opp.oprom_array,
                                opp->oprom_array);

                        (void) strcpy(temp->value.opp.oprom_array,
                                temp->name.opp.oprom_array);
                        getpropval(&temp->value.opp);
                        temp->size = temp->value.opp.oprom_size;

                        /* Now copy over temp's data to new. */
                        if ((new = malloc(sizeof (Prop))) == NULL) {
                                perror("malloc");
                                exit(1);
                        }

                        /*
                         * First copy over temp->name's data. The
                         * temp->name.opp.opio_u union always contains char[]
                         * (as opposed to an int or int []).
                         */
                        new->name.opp.oprom_size = temp->name.opp.oprom_size;

                        if ((new->name.opp.oprom_array =
                            malloc(new->name.opp.oprom_size)) == NULL) {
                                perror("malloc");
                                exit(1);
                        }
                        (void) strcpy(new->name.opp.oprom_array,
                            temp->name.opp.oprom_array);

                        new->name.opp.holds_array = 1;

                        /*
                         * Then copy over temp->value's data.
                         * temp->value.opp.opio_u could contain char[], int or
                         * int []. If *(temp->value.opp.oprom_array) is '\0',
                         * this indicates int or int []. int is the norm, but
                         * to be safe we assume int [] and copy over
                         * OPROM_NODE_SIZE int elements.
                         */
                        new->value.opp.oprom_size = temp->value.opp.oprom_size;

                        if (*(temp->value.opp.oprom_array) == '\0') {
                                for (i = 0; i < OPROM_NODE_SIZE; i++)
                                        new->value.opp.oprom_node[i] =
                                            *(&temp->value.opp.oprom_node+i);

                                new->value.opp.holds_array = 0;
                        } else {
                                if ((new->value.opp.oprom_array =
                                    malloc(new->value.opp.oprom_size))
                                    == NULL) {
                                        perror("malloc");
                                        exit(1);
                                }

                                /*
                                 * temp->value.opp.oprom_array can contain one
                                 * or more embedded NULLs. These trip-up the
                                 * standard string copying functions, so we do
                                 * the copy by hand. temp->value.opp.oprom_array
                                 * will be NULL-terminated. oprom_size includes
                                 * this terminating NULL.
                                 */
                                newp = new->value.opp.oprom_array;
                                tempp = temp->value.opp.oprom_array;
                                for (i = new->value.opp.oprom_size; i > 0; i--)
                                        *newp++ = *tempp++;

                                new->value.opp.holds_array = 1;
                        }

                        new->size = temp->size;

                        /* everything worked so link the property list */
                        if (node->props == NULL)
                                node->props = new;
                        else if (prop != NULL)
                                prop->next = new;
                        prop = new;
                        prop->next = NULL;
                }
        }
        free(temp);
}

int
promopen(int oflag)
{
        /*CONSTCOND*/
        while (1)  {
                if ((prom_fd = open(promdev, oflag)) < 0)  {
                        if (errno == EAGAIN)   {
                                (void) sleep(5);
                                continue;
                        }
                        if (errno == ENXIO)
                                return (-1);
                        exit(_error(dgettext(TEXT_DOMAIN, "cannot open %s"),
                                promdev));
                } else
                        return (0);
        }
        /*NOTREACHED*/
}

void
promclose(void)
{
        if (close(prom_fd) < 0)
                exit(_error(dgettext(TEXT_DOMAIN, "close error on %s"),
                        promdev));
}

/*
 * Read the value of the property from the PROM device tree
 */
void
getpropval(struct openpromio *opp)
{
        opp->oprom_size = MAXVALSIZE;

        if (ioctl(prom_fd, OPROMGETPROP, opp) < 0)
                exit(_error("OPROMGETPROP"));
}

int
next(int id)
{
        Oppbuf  oppbuf;
        register struct openpromio *opp = &(oppbuf.opp);
        /* LINTED */
        int *ip = (int *)(opp->oprom_array);

        (void) memset((void *) oppbuf.buf, 0, BUFSIZE);

        opp->oprom_size = MAXVALSIZE;
        *ip = id;
        if (ioctl(prom_fd, OPROMNEXT, opp) < 0)
                return (_error("OPROMNEXT"));
        /* LINTED */
        return (*(int *)opp->oprom_array);
}

int
child(int id)
{
        Oppbuf  oppbuf;
        register struct openpromio *opp = &(oppbuf.opp);
        /* LINTED */
        int *ip = (int *)(opp->oprom_array);

        (void) memset((void *) oppbuf.buf, 0, BUFSIZE);
        opp->oprom_size = MAXVALSIZE;
        *ip = id;
        if (ioctl(prom_fd, OPROMCHILD, opp) < 0)
                return (_error("OPROMCHILD"));
        /* LINTED */
        return (*(int *)opp->oprom_array);
}

/*
 * Check if the Prom node passed in contains a property called
 * "board#".
 */
int
has_board_num(Prom_node *node)
{
        Prop *prop = node->props;

        /*
         * walk thru all properties in this PROM node and look for
         * board# prop
         */
        while (prop != NULL) {
                if (strcmp(prop->name.opp.oprom_array, "board#") == 0)
                    return (1);

                prop = prop->next;
        }

        return (0);
}       /* end of has_board_num() */

/*
 * Retrieve the value of the board number property from this Prom
 * node. It has the type of int.
 */
int
get_board_num(Prom_node *node)
{
        Prop *prop = node->props;

        /*
         * walk thru all properties in this PROM node and look for
         * board# prop
         */
        while (prop != NULL) {
                if (strcmp(prop->name.opp.oprom_array, "board#") == 0)
                        return (prop->value.opp.oprom_node[0]);

                prop = prop->next;
        }

        return (-1);
}       /* end of get_board_num() */

/*
 * Find the requested board struct in the system device tree.
 */
Board_node *
find_board(Sys_tree *root, int board)
{
        Board_node *bnode = root->bd_list;

        while ((bnode != NULL) && (board != bnode->board_num))
                bnode = bnode->next;

        return (bnode);
}       /* end of find_board() */

/*
 * Add a board to the system list in order. Initialize all pointer
 * fields to NULL.
 */
Board_node *
insert_board(Sys_tree *root, int board)
{
        Board_node *bnode;
        Board_node *temp = root->bd_list;

        if ((bnode = (Board_node *) malloc(sizeof (Board_node))) == NULL) {
                perror("malloc");
                exit(1);
        }
        bnode->nodes = NULL;
        bnode->next = NULL;
        bnode->board_num = board;

        if (temp == NULL)
                root->bd_list = bnode;
        else if (temp->board_num > board) {
                bnode->next = temp;
                root->bd_list = bnode;
        } else {
                while ((temp->next != NULL) && (board > temp->next->board_num))
                        temp = temp->next;
                bnode->next = temp->next;
                temp->next = bnode;
        }
        root->board_cnt++;

        return (bnode);
}       /* end of insert_board() */

/*
 * This function searches through the properties of the node passed in
 * and returns a pointer to the value of the name property.
 */
char *
get_node_name(Prom_node *pnode)
{
        Prop *prop;

        if (pnode == NULL) {
                return (NULL);
        }

        prop = pnode->props;
        while (prop != NULL) {
                if (strcmp("name", prop->name.opp.oprom_array) == 0)
                        return (prop->value.opp.oprom_array);
                prop = prop->next;
        }
        return (NULL);
}       /* end of get_node_name() */

/*
 * This function searches through the properties of the node passed in
 * and returns a pointer to the value of the name property.
 */
char *
get_node_type(Prom_node *pnode)
{
        Prop *prop;

        if (pnode == NULL) {
                return (NULL);
        }

        prop = pnode->props;
        while (prop != NULL) {
                if (strcmp("device_type", prop->name.opp.oprom_array) == 0)
                        return (prop->value.opp.oprom_array);
                prop = prop->next;
        }
        return (NULL);
}       /* end of get_node_type() */

/*
 * Do a depth-first walk of a device tree and
 * return the first node with the name matching.
 */

Prom_node *
dev_find_node(Prom_node *root, char *name)
{
        Prom_node *node;

        node = dev_find_node_by_type(root, "name", name);

        return (node);
}

Prom_node *
dev_next_node(Prom_node *root, char *name)
{
        Prom_node *node;

        node = dev_next_node_by_type(root, "name", name);

        return (node);
}

/*
 * Search for and return a node of the required type. If no node is found,
 * then return NULL.
 */
Prom_node *
dev_find_type(Prom_node *root, char *type)
{
        Prom_node *node;

        node = dev_find_node_by_type(root, "device_type", type);

        return (node);  /* not found */
}

/*
 * Start from the current node and return the next node besides the
 * current one which has the requested type property.
 */
Prom_node *
dev_next_type(Prom_node *root, char *type)
{
        Prom_node *node;

        node = dev_next_node_by_type(root, "device_type", type);

        return (node);  /* not found */
}

/*
 * Search a device tree and return the first failed node that is found.
 * (has a 'status' property)
 */
Prom_node *
find_failed_node(Prom_node * root)
{
        Prom_node *pnode;

        if (root == NULL)
                return (NULL);

        if (node_failed(root)) {
                return (root);
        }

        /* search the child */
        if ((pnode = find_failed_node(root->child)) != NULL)
                return (pnode);

        /* search the siblings */
        if ((pnode = find_failed_node(root->sibling)) != NULL)
                return (pnode);

        return (NULL);
}       /* end of find_failed_node() */

/*
 * Start from the current node and return the next node besides
 * the current one which is failed. (has a 'status' property)
 */
Prom_node *
next_failed_node(Prom_node * root)
{
        Prom_node *pnode;
        Prom_node *parent;

        if (root == NULL)
                return (NULL);

        /* search the child */
        if ((pnode = find_failed_node(root->child)) != NULL) {
                return (pnode);
        }

        /* search the siblings */
        if ((pnode = find_failed_node(root->sibling)) != NULL) {
                return (pnode);
        }

        /* backtracking the search up through parents' siblings */
        parent = root->parent;
        while (parent != NULL) {
                if ((pnode = find_failed_node(parent->sibling)) != NULL)
                        return (pnode);
                else
                        parent = parent->parent;
        }

        return (NULL);
}       /* end of find_failed_node() */

/*
 * node_failed
 *
 * This function determines if the current Prom node is failed. This
 * is defined by having a status property containing the token 'fail'.
 */
int
node_failed(Prom_node *node)
{
        return (node_status(node, "fail"));
}

int
node_status(Prom_node *node, char *status)
{
        void *value;

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

        /* search the local node */
        if ((value = get_prop_val(find_prop(node, "status"))) != NULL) {
                if ((value != NULL) && strstr((char *)value, status))
                        return (1);
        }
        return (0);
}

/*
 * Get a property's value. Must be void * since the property can
 * be any data type. Caller must know the *PROPER* way to use this
 * data.
 */
void *
get_prop_val(Prop *prop)
{
        if (prop == NULL)
                return (NULL);

        if (prop->value.opp.holds_array)
                return ((void *)(prop->value.opp.oprom_array));
        else
                return ((void *)(&prop->value.opp.oprom_node[0]));
}       /* end of get_prop_val() */

/*
 * Search a Prom node and retrieve the property with the correct
 * name.
 */
Prop *
find_prop(Prom_node *pnode, char *name)
{
        Prop *prop;

        if (pnode  == NULL) {
                return (NULL);
        }

        if (pnode->props == NULL) {
                (void) printf("%s", dgettext(TEXT_DOMAIN, "Prom node has "
                        "no properties\n"));
                return (NULL);
        }

        prop = pnode->props;
        while ((prop != NULL) && (strcmp(prop->name.opp.oprom_array, name)))
                prop = prop->next;

        return (prop);
}

/*
 * This function adds a board node to the board structure where that
 * that node's physical component lives.
 */
void
add_node(Sys_tree *root, Prom_node *pnode)
{
        int board;
        Board_node *bnode;
        Prom_node *p;

        /* add this node to the Board list of the appropriate board */
        if ((board = get_board_num(pnode)) == -1) {
                /* board is 0 if not on Sunfire */
                board = 0;
        }

        /* find the node with the same board number */
        if ((bnode = find_board(root, board)) == NULL) {
                bnode = insert_board(root, board);
                bnode->board_type = UNKNOWN_BOARD;
        }

        /* now attach this prom node to the board list */
        /* Insert this node at the end of the list */
        pnode->sibling = NULL;
        if (bnode->nodes == NULL)
                bnode->nodes = pnode;
        else {
                p = bnode->nodes;
                while (p->sibling != NULL)
                        p = p->sibling;
                p->sibling = pnode;
        }

}

/*
 * Find the device on the current board with the requested device ID
 * and name. If this rountine is passed a NULL pointer, it simply returns
 * NULL.
 */
Prom_node *
find_device(Board_node *board, int id, char *name)
{
        Prom_node *pnode;
        int mask;

        /* find the first cpu node */
        pnode = dev_find_node(board->nodes, name);

        mask = 0x1F;
        while (pnode != NULL) {
                if ((get_id(pnode) & mask) == id)
                        return (pnode);

                pnode = dev_next_node(pnode, name);
        }
        return (NULL);
}

Prom_node *
dev_find_node_by_type(Prom_node *root, char *type, char *property)
{
        Prom_node *node;
        char *type_prop;

        if (root == NULL || property == NULL)
                return (NULL);

        type_prop = (char *)get_prop_val(find_prop(root, type));

        if (type_prop != NULL) {
                if (strcmp(type_prop, property) == 0) {
                        return (root);
                }
        }

        /* look at your children first */
        if ((node = dev_find_node_by_type(root->child, type,
            property)) != NULL)
                return (node);

        /* now look at your siblings */
        if ((node = dev_find_node_by_type(root->sibling, type,
            property)) != NULL)
                return (node);

        return (NULL);  /* not found */
}

Prom_node *
dev_next_node_by_type(Prom_node *root, char *type, char *property)
{
        Prom_node *node;

        if (root == NULL || property == NULL)
                return (NULL);

        /* look at your children first */
        if ((node = dev_find_node_by_type(root->child, type,
            property)) != NULL)
                return (node);

        /* now look at your siblings */
        if ((node = dev_find_node_by_type(root->sibling, type,
            property)) != NULL)
                return (node);

        /* now look at papa's siblings */
        if ((node = dev_find_node_by_type(root->parent->sibling,
            type, property)) != NULL)
                return (node);

        return (NULL);  /* not found */
}

/*
 * Do a depth-first walk of a device tree and
 * return the first node with the matching compatible.
 */
Prom_node *
dev_find_node_by_compatible(Prom_node *root, char *compatible)
{
        Prom_node *node;
        Prop    *prop;
        char    *compatible_array;
        int     size, nbytes;

        if (root == NULL || compatible == NULL)
                return (NULL);

        if ((prop = find_prop(root, "compatible")) != NULL &&
            (compatible_array = (char *)get_prop_val(prop)) != NULL) {
                /*
                 * The Prop structure returned by find_prop() is supposed
                 * to contain an indication of how big the value of the
                 * compatible property is.  Since it is an array of strings
                 * this is our only means of determining just how many
                 * strings might be in this property.  However, this size
                 * is often left as zero even though there is at least one
                 * string present.  When this is the case, all we can do
                 * is examine the first string in the compatible property.
                 */

                for (size = prop->size; size >= 0; size -= nbytes) {
                        if (strcmp(compatible_array, compatible) == 0)
                                return (root);          /* found a match */

                        nbytes = strlen(compatible_array) + 1;
                        compatible_array += nbytes;
                }
        }

        node = dev_find_node_by_compatible(root->child, compatible);
        if (node != NULL)
                return (node);

        /*
         * Note the very deliberate use of tail recursion here.  A good
         * compiler (such as Sun's) will recognize this and generate code
         * that does not allocate another stack frame.  Instead, it will
         * overlay the existing stack frame with the new one, the only change
         * having been to replace the original root with its sibling.
         * This has the potential to create some confusion for anyone
         * trying to debug this code from a core dump, since the stack
         * trace will not reveal recursion on siblings, only on children.
         */

        return (dev_find_node_by_compatible(root->sibling, compatible));
}

/*
 * Start from the current node and return the next node besides
 * the current one which has the requested compatible property.
 */
Prom_node *
dev_next_node_by_compatible(Prom_node *root, char *compatible)
{
        Prom_node *node;

        if (root == NULL || compatible == NULL)
                return (NULL);

        node = dev_find_node_by_compatible(root->child, compatible);
        if (node != NULL)
                return (node);

        /*
         * More tail recursion.  Even though it is a different function,
         * this will overlay the current stack frame.  Caveat exterminator.
         */

        node = dev_find_node_by_compatible(root->sibling, compatible);
        if (node != NULL)
                return (node);

        return (dev_find_node_by_compatible(root->parent->sibling, compatible));
}