/*
 * 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.
 */

#include <sys/types.h>
#include <sys/esunddi.h>
#include <sys/promif_impl.h>

#ifdef _KMDB
static pnode_t chosennode;
static pnode_t optionsnode;
#else
static char *gettoken(char *tp, char *token);
static pnode_t finddevice(char *path);
#endif

/*
 * Routines for walking the PROMs devinfo tree
 */

#ifdef _KMDB

void
promif_set_nodes(pnode_t chosen, pnode_t options)
{
	chosennode = chosen;
	optionsnode = options;
}

int
promif_finddevice(void *p)
{
	cell_t	*ci = (cell_t *)p;
	char *path;

	ASSERT(ci[1] == 1);

	path = p1275_cell2ptr(ci[3]);

	if (strcmp("/chosen", path) == 0) {
		ci[4] = p1275_dnode2cell(chosennode);
	} else if (strcmp("/options", path) == 0) {
		ci[4] = p1275_dnode2cell(optionsnode);
	} else {
		/* only supports known nodes */
		ASSERT(0);
	}

	return (0);
}

#else

int
promif_finddevice(void *p)
{
	cell_t	*ci = (cell_t *)p;
	pnode_t	node;

	ASSERT(ci[1] == 1);

	/*
	 * We are passing the cpu pointer (CPU->cpu_id) explicitly to
	 * thread_affinity_set() so that we don't attempt to grab the
	 * cpu_lock internally in thread_affinity_set() and may sleep
	 * as a result.
	 * It is safe to pass CPU->cpu_id and it will always be valid.
	 */
	thread_affinity_set(curthread, CPU->cpu_id);
	node = finddevice(p1275_cell2ptr(ci[3]));

	ci[4] = p1275_dnode2cell(node);
	thread_affinity_clear(curthread);

	return (0);
}

#endif

int
promif_nextnode(void *p)
{
	cell_t	*ci = (cell_t *)p;
	pnode_t	next;

	ASSERT(ci[1] == 1);

	next = promif_stree_nextnode(p1275_cell2dnode(ci[3]));

	ci[4] = p1275_dnode2cell(next);

	return (0);
}

int
promif_childnode(void *p)
{
	cell_t	*ci = (cell_t *)p;
	pnode_t	child;

	ASSERT(ci[1] == 1);

	child = promif_stree_childnode(p1275_cell2dnode(ci[3]));

	ci[4] = p1275_dnode2cell(child);

	return (0);
}

int
promif_parentnode(void *p)
{
	cell_t	*ci = (cell_t *)p;
	pnode_t	parent;

	ASSERT(ci[1] == 1);

	parent = promif_stree_parentnode(p1275_cell2dnode(ci[3]));

	ci[4] = p1275_dnode2cell(parent);

	return (0);
}

#ifndef _KMDB

/*
 * Get a token from a prom pathname, collecting everything
 * until a non-comma, non-colon separator is found. Any
 * options, including the ':' option separator, on the end
 * of the token are removed.
 */
static char *
gettoken(char *tp, char *token)
{
	char *result = token;

	for (;;) {
		tp = prom_path_gettoken(tp, token);
		token += prom_strlen(token);
		if ((*tp == ',') || (*tp == ':')) {
			*token++ = *tp++;
			*token = '\0';
			continue;
		}
		break;
	}

	/* strip off any options from the token */
	prom_strip_options(result, result);

	return (tp);
}

/*
 * Retrieve the unit address for a node by looking it up
 * in the corresponding dip. -1 is returned if no unit
 * address can be determined.
 */
static int
get_unit_addr(pnode_t np, char *paddr)
{
	dev_info_t	*dip;
	char		*addr;

	if ((dip = e_ddi_nodeid_to_dip(np)) == NULL) {
		return (-1);
	}

	if ((addr = ddi_get_name_addr(dip)) == NULL) {
		ddi_release_devi(dip);
		return (-1);
	}

	(void) prom_strcpy(paddr, addr);

	ddi_release_devi(dip);

	return (0);
}

/*
 * Get node id of node in prom tree that path identifies
 */
static pnode_t
finddevice(char *path)
{
	char	name[OBP_MAXPROPNAME];
	char	addr[OBP_MAXPROPNAME];
	char	pname[OBP_MAXPROPNAME];
	char	paddr[OBP_MAXPROPNAME];
	char	*tp;
	pnode_t	np;
	pnode_t	device;

	CIF_DBG_NODE("finddevice: %s\n", path);

	tp = path;
	np = prom_rootnode();
	device = OBP_BADNODE;

	/* must be a fully specified path */
	if (*tp++ != '/')
		goto done;

	for (;;) {
		/* get the name from the path */
		tp = gettoken(tp, name);
		if (*name == '\0')
			break;

		/* get the address from the path */
		if (*tp == '@') {
			tp++;
			tp = gettoken(tp, addr);
		} else {
			addr[0] = '\0';
		}

		CIF_DBG_NODE("looking for: %s%s%s\n", name,
		    (*addr != '\0') ? "@" : "", addr);

		if ((np = prom_childnode(np)) == OBP_NONODE)
			break;

		while (np != OBP_NONODE) {

			/* get the name from the current node */
			if (prom_getprop(np, OBP_NAME, pname) < 0)
				goto done;

			/* get the address from the current node */
			if (get_unit_addr(np, paddr) < 0)
				paddr[0] = '\0';

			/* compare the names and addresses */
			if ((prom_strcmp(name, pname) == 0) &&
			    (prom_strcmp(addr, paddr) == 0)) {
				CIF_DBG_NODE("found dev: %s%s%s (0x%x)\n",
				    pname, (*paddr != '\0') ? "@" : "",
				    paddr, np);
				break;
			} else {
				CIF_DBG_NODE("  no match: %s%s%s vs %s%s%s\n",
				    name, (*addr != '\0') ? "@" : "", addr,
				    pname, (*paddr != '\0') ? "@" : "", paddr);
			}
			np = prom_nextnode(np);
		}

		/* path does not map to a node */
		if (np == OBP_NONODE)
			break;

		if (*tp == '\0') {
			/* found a matching node */
			device = np;
			break;
		}

		/*
		 * Continue the loop with the
		 * next component of the path.
		 */
		tp++;
	}
done:

	if (device == OBP_BADNODE) {
		CIF_DBG_NODE("device not found\n\n");
	} else {
		CIF_DBG_NODE("returning 0x%x\n\n", device);
	}

	return (device);
}

#endif