/* $NetBSD: $ */
/*-
  * Copyright (c) 2009 UPMC/LIP6
  * All rights reserved.
  * This software is distributed under the following condiions
  * compliant with the NetBSD foundation policy.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
  *    notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
  *
  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  * POSSIBILITY OF SUCH DAMAGE.
  */

#include <sys/types.h>
#include <sys/endian.h>
#include <sys/errno.h>
#include <sys/null.h>
#include <sys/stdint.h>
#if defined(_KERNEL) || defined(_STANDALONE)
#include <sys/malloc.h>
#include <sys/systm.h>
#include <machine/boot_fdt.h>
#ifdef _STANDALONE
#include <lib/libsa/stand.h>
#include <lib/libkern/libkern.h>
#endif
#else
#include <malloc.h>
#include <stdio.h>
#include "boot_fdt.h"
#endif

struct fdt _fdt;
struct fdt *fdt = &_fdt;

static const uint32_t *
fdt_skipprop(const struct boot_fdt_prop *prop)
{
	/* skip this property, return pointer to next object */
	const uint32_t *p = (const void *)prop;

	p += (sizeof(struct boot_fdt_prop) +
	    be32toh(prop->prop_value_size) +
	    sizeof(int32_t) - 1) / sizeof(int32_t);
	return p;
}

static const char *
fdt_getstring(uint32_t offset)
{
	return &fdt->fdt_strings[offset];
}

/* update properties "address-cells" and "size-cells" for node */
static int
fdt_get_addr_size_cells(struct fdt_node *node)
{
	const struct boot_fdt_prop *myprop_addr;
	const struct boot_fdt_prop *myprop_size;

	if ((myprop_addr = fdt_find_prop(node->fdt_node_data,
	    "#address-cells")) == NULL)
		return ENOENT;
	if ((myprop_size = fdt_find_prop(node->fdt_node_data,
	    "#size-cells")) == NULL)
		return ENOENT;
	node->fdt_node_addr_cells = be32toh(myprop_addr->prop_value[0]);
	node->fdt_node_size_cells = be32toh(myprop_size->prop_value[0]);
	return 0;
}

/* parse node header pointed to by p, fill *node */
static int
fdt_parse_node(const struct fdt_node *parent,
    const uint32_t *p, struct fdt_node *node)
{
	int err;
	const char *cp = (const void *)&p[1];
	if (p[0] != htobe32(FDT_BEGIN_NODE))
		return EINVAL;
	node->fdt_node_name = cp;
	/* skip name */
	while (*cp != 0)
		cp++;
	/* align to 32bit */
	do {
		cp++;
	} while (((intptr_t)cp % sizeof(uint32_t)) != 0);

	node->fdt_node_data = (const void *)cp;
	node->fdt_node_addr_cells = parent->fdt_node_addr_cells;
	node->fdt_node_size_cells = parent->fdt_node_size_cells;
	err = fdt_get_addr_size_cells(node);
	if (err != ENOENT && err != 0) {
		printf("parse error %d for node %s\n", err,
		node->fdt_node_name);
		return err;
	}
	return 0;
}

int
fdt_walk(const struct fdt_node *node, const char *name,
    int (*callback)(const struct fdt_node *,
    const struct boot_fdt_prop *, void *), void *arg)
{
	int err;
	const struct boot_fdt_prop *myprop;
	struct fdt_node subnode;

	myprop = fdt_find_prop(node->fdt_node_data, name);
	if (myprop) {
		err = (*callback)(node, myprop, arg);
		if (err) {
			printf("callback for node %s failed: %d\n",
			    node->fdt_node_name, err);
		}
	}
	/* recurse for each subnode of this node */
	err = fdt_find_subnode(node, &subnode);
	if (err) {
		if (err != ENOENT) {
			printf("fdt_find_subnode for %s failed: %d\n",
			    node->fdt_node_name, err);
			return err;
		}
		return 0;
	}
	do {
		err = fdt_walk(&subnode, name, callback, arg);
		if (err) {
			printf("fdt_walk for subnode %s failed: %d\n",
			    subnode.fdt_node_name, err);
			return err;
		}
		err = fdt_next_node(node, subnode.fdt_node_data, &subnode);
		if (err && err != ENOENT) {
			printf("fdt_walk: fdt_next_node(%s) failed: %d\n",
			    subnode.fdt_node_name, err);
			return err;
		}
	} while (err == 0);
	return 0;
}

int
fdt_parse_header(const uint32_t *p, struct fdt *fdtp)
{
	fdtp->fdt_header = (const void *)p;
	int err;

	if (fdtp->fdt_header->fdt_magic != htobe32(0xd00dfeed))
		return EINVAL;
	fdtp->fdt_struct =
	    &p[be32toh(fdtp->fdt_header->fdt_struct_off) / sizeof(uint32_t)];
	fdtp->fdt_strings = (const void *)
	    &p[be32toh(fdtp->fdt_header->fdt_strings_off) / sizeof(uint32_t)];

	fdtp->fdt_refs_cache_size = 0;
	fdtp->fdt_numnodes = 0;
	fdtp->fdt_refs_cache = NULL;
	memset(&fdtp->fdt_root_node, 0, sizeof(struct fdt_node));
	/* parse and cache the root node */
	err = fdt_parse_node(&fdtp->fdt_root_node,
	    fdtp->fdt_struct, &fdtp->fdt_root_node);
	if (err) {
		printf("fdt_parse_header node %p fdt_parse_node failed: %d\n",
		    fdtp->fdt_struct, err);
		return err;
	}
	return 0;
}

#ifndef _STANDALONE
/* callback for fdt_walk, filling the node references cache */
static int
fdt_fill_cache(const struct fdt_node *node,
    const struct boot_fdt_prop *prop, void *v)
{
	void *new;
	if (fdt->fdt_refs_cache_size == fdt->fdt_numnodes) {
#ifdef _KERNEL
		new = realloc(fdt->fdt_refs_cache,
		    sizeof(struct fdt_refs) *
		    (fdt->fdt_refs_cache_size + 128), M_TEMP, M_NOWAIT);
#else
		new = realloc(fdt->fdt_refs_cache,
		    sizeof(struct fdt_refs) *
		    (fdt->fdt_refs_cache_size + 128));
#endif
		if (new == NULL) {
			printf("fdt_fill_cache: ENOMEM\n");
			return ENOMEM;
		}
		fdt->fdt_refs_cache = new;
		fdt->fdt_refs_cache_size += 128;
	}
	fdt->fdt_refs_cache[fdt->fdt_numnodes].fdtref_cookie =
	    prop->prop_value[0];
	fdt->fdt_refs_cache[fdt->fdt_numnodes].fdtref_node = *node;
	fdt->fdt_numnodes++;
	printf("fdt_fill_cache %s entry %d = 0x%x %d %d\n",
	    node->fdt_node_name, fdt->fdt_numnodes - 1, prop->prop_value[0],
	    node->fdt_node_addr_cells, node->fdt_node_size_cells);
	return 0;
}

int
fdt_parse_tree(void)
{
	/* walk the tree, filling in the fdt_refs_cache */
	return fdt_walk(&fdt->fdt_root_node, "linux,phandle",
	    fdt_fill_cache, NULL);
}
#endif /* _STANDALONE */

const struct boot_fdt_prop *
fdt_find_prop(const uint32_t *p, const char *name)
{
	const struct boot_fdt_prop *prop = (const void *)p;
	const char *thisname;
	while(prop->prop_start == htobe32(FDT_PROP)) {
		thisname = fdt_getstring(be32toh(prop->prop_name_off));
		if (strcmp(thisname, name) == 0)
			return prop;
		prop = (const void *)fdt_skipprop(prop);
	}
	return NULL;
}

/* helper for fdt_next_node */
static const uint32_t *
fdt_next_node_recurse(const struct fdt_node *parent, const uint32_t *p)
{
	const uint32_t *myp = p;
	struct fdt_node mynode;

	while (1)
	{
		switch(*myp) {
		case htobe32(FDT_BEGIN_NODE):
			fdt_parse_node(parent, myp, &mynode);
			myp = fdt_next_node_recurse(&mynode,
			    mynode.fdt_node_data);
			break;
		case htobe32(FDT_END_NODE):
			myp++;
			return myp;
		case htobe32(FDT_PROP):
			myp = fdt_skipprop((const void *)myp);
			break;
		default:
			printf("fdt_next_node: parse error %d %p\n",
			    htobe32(*myp), myp);
			return NULL;
		}
	}
}

int
fdt_next_node(const struct fdt_node *parent,
    const uint32_t *p, struct fdt_node *node)
{
	const uint32_t *myp = fdt_next_node_recurse(parent, p);
	if (myp == NULL)
		return EINVAL;

	switch (*myp) {
		case htobe32(FDT_BEGIN_NODE):
			fdt_parse_node(parent, myp, node);
			return 0;
		default:
			return ENOENT;
	}
}

int 
fdt_find_subnode(const struct fdt_node *parent, struct fdt_node *node)
{
	const uint32_t *myp = parent->fdt_node_data;

	while (1) {
		switch(*myp) {
		case htobe32(FDT_BEGIN_NODE):
			/* found a node, return it */
			return fdt_parse_node(parent, myp, node);
			break;
		case htobe32(FDT_END_NODE):
			/* didn't find node in the current node */
			return ENOENT;
		case htobe32(FDT_PROP):
			/* found a propery; skip to next */
			myp = fdt_skipprop((const void *)myp);
			break;
		default:
			printf("fdt_find_subnode: parse error %d %p\n",
				htobe32(*myp), myp);
			return EINVAL;
		}
	}
}

int
fdt_find_node(const struct fdt_node *parent,
    const char *name, struct fdt_node *node)
{
	int err;
	struct fdt_node mynode;

	/* find first subnode */
	err = fdt_find_subnode(parent, &mynode);
	if (err)
		return err;

	/* now check name of each node of this level */
	while (err == 0) {
		/*
		 * found a node. Check name, skip to next node
		 * if it's not what we're looking for
		 */
		if (strcmp(mynode.fdt_node_name, name) == 0) {
			*node = mynode;
			return 0;
		}
		/* not what's we're looking for, skip this node */
		err = fdt_next_node(parent, mynode.fdt_node_data, &mynode);
	}
	return ENOENT;
}

struct fdt_node *
fdt_find_node_from_ref(uint32_t cookie)
{
	int i;

	for (i = 0; i < fdt->fdt_numnodes; i++) {
		if (fdt->fdt_refs_cache[i].fdtref_cookie == cookie) {
			return &fdt->fdt_refs_cache[i].fdtref_node;
		}
	}
	return NULL;
}

int
fdt_read_node_reg(const struct fdt_node *node, struct fdt_reg *reg, int regsize)
{
	const struct boot_fdt_prop *prop;
	int validx;
	int regidx;
	int nprops;

	memset(reg, 0, sizeof(struct fdt_reg) * regsize);
	prop = fdt_find_prop(node->fdt_node_data, "reg");
	if (prop == NULL)
		return ENOENT;
	nprops = be32toh(prop->prop_value_size) / sizeof(int32_t);
	for (validx = regidx = 0;
	    regidx < regsize && validx < nprops;
	    regidx++) {
		switch (node->fdt_node_addr_cells) {
		case 0:
			break;
		case 1:
			reg[regidx].reg_addr =
			    be32toh(prop->prop_value[validx]);
			break;
		case 2:
			reg[regidx].reg_addr =
			    ((uint64_t)be32toh(prop->prop_value[validx]) << 32)
			   | (uint64_t)be32toh(prop->prop_value[validx + 1]);
			break;
		default:
			return EINVAL;
		}
		validx += node->fdt_node_addr_cells;
		switch (node->fdt_node_size_cells) {
		case 0:
			break;
		case 1:
			reg[regidx].reg_size =
			    be32toh(prop->prop_value[validx]);
			break;
		case 2:
			reg[regidx].reg_size =
			    ((uint64_t)be32toh(prop->prop_value[validx]) << 32)
			   | (uint64_t)be32toh(prop->prop_value[validx + 1]);
			break;
		default:
			return EINVAL;
		}
		validx += node->fdt_node_size_cells;
	}
	return 0;
}
