/*	$NetBSD: aml_name.c,v 1.4 2009/10/08 13:16:13 cegger Exp $	*/

/*-
 * Copyright (c) 1999 Takanori Watanabe
 * Copyright (c) 1999, 2000 Yasuo Yokoyama
 * Copyright (c) 1999, 2000 Mitsuru IWASAKI <iwasaki@FreeBSD.org>
 * All rights reserved.
 *
 * 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 AUTHOR 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 AUTHOR 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.
 *
 *	Id: aml_name.c,v 1.15 2000/08/16 18:14:53 iwasaki Exp 
 *	$FreeBSD: src/usr.sbin/acpi/amldb/aml/aml_name.c,v 1.3 2000/11/09 06:24:45 iwasaki Exp $
 */
#include <sys/cdefs.h>
__RCSID("$NetBSD: aml_name.c,v 1.4 2009/10/08 13:16:13 cegger Exp $");

#include <sys/param.h>

#include <acpi_common.h>
#include <aml/aml_amlmem.h>
#include <aml/aml_common.h>
#include <aml/aml_env.h>
#include <aml/aml_name.h>

#ifndef _KERNEL
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "debug.h"
#else /* _KERNEL */
#include <sys/systm.h>
#endif /* !_KERNEL */

static struct aml_name	*aml_find_name(struct aml_name *, const u_int8_t *);
static struct aml_name	*aml_new_name(struct aml_name *, const u_int8_t *);
static void		 aml_delete_name(struct aml_name *);

static struct	aml_name rootname = {"\\", NULL, NULL, NULL, NULL, NULL};

static struct	aml_name_group root_group = {
	AML_NAME_GROUP_ROOT,
	&rootname,
	NULL
};

struct	aml_name_group *name_group_list = &root_group;
struct	aml_local_stack *stack_top = NULL;

struct aml_name *
aml_get_rootname()
{

	return (&rootname);
}

static struct aml_name *
aml_find_name(struct aml_name *parent, const u_int8_t *name)
{
	struct	aml_name *result;

	if (!parent)
		parent = &rootname;
	for (result = parent->child; result; result = result->brother)
		if (!strncmp(result->name, (const char *)name, 4))
			break;
	return (result);
}

/*
 * Parse given namesppace expression and find a first matched object
 * under given level of the tree by depth first search.
 */

struct aml_name *
aml_find_from_namespace(struct aml_name *parent, const char *name)
{
	const char	*ptr;
	int	len;
	struct	aml_name *result;

	ptr = name;
	if (!parent)
		parent = &rootname;

	if (ptr[0] == '\\') {
		ptr++;
		parent = &rootname;
	}
	for (len = 0; ptr[len] != '.' && ptr[len] != '\0'; len++)
		;

	for (result = parent->child; result; result = result->brother) {
		if (!strncmp(result->name, ptr, len)) {
			if (ptr[len] == '\0' || ptr[len + 1] == '\0') {
				return (result);
			}
			ptr += len;
			if (ptr[0] != '.') {
				return (NULL);
			}
			ptr++;
			return (aml_find_from_namespace(result, ptr));
		}
	}

	return (NULL);
}

static void
_aml_apply_foreach_found_objects(struct aml_name *parent, char *name,
    int len, int shallow, int (*func)(struct aml_name *, va_list), va_list ap)
{
	struct	aml_name *child, *ptr;

	child = ptr = NULL;

	/* function to apply must be specified */
	if (func == NULL) {
		return;
	}

	for (child = parent->child; child; child = child->brother) {
		if (!strncmp(child->name, name, len)) {
			/* if function call was failed, stop searching */
			if (func(child, ap) != 0) {
				return;
			}
		}
	}

	if (shallow == 1) {
		return;
	}

	for (ptr = parent->child; ptr; ptr = ptr->brother) {
		/* do more searching */
		_aml_apply_foreach_found_objects(ptr, name, len, 0, func, ap);
	}
}

/*
 * Find named objects as many as possible under given level of
 * namespace, and apply given callback function for each
 * named objects found.  If the callback function returns non-zero
 * value, then the search terminates immediately.
 * Note that object name expression is used as forward substring match,
 * not exact match.  The name expression "_L" will match for objects
 * which have name starting with "_L" such as "\_SB_.LID_._LID" and
 * "\_GPE._L00" and so on. The name expression can include parent object
 * name in it like "\_GPE._L".  In this case, GPE X level wake handlers
 * will be found under "\_GPE" in shallow level.
 */

void
aml_apply_foreach_found_objects(struct aml_name *start, char *name,
    int (*func)(struct aml_name *, va_list), ...)
{
	int	i, len, has_dot, last_is_dot, shallow;
	struct	aml_name *child, *parent;
	va_list	ap;

	shallow = 0;
	if (start == NULL) {
		parent = &rootname;
	} else {
		parent = start;
	}
	if (name[0] == '\\') {
		name++;
		parent = &rootname;
		shallow = 1;
	}

	len = strlen(name);
	last_is_dot = 0;
	/* the last dot should be ignored */
	if (len > 0 && name[len - 1] == '.') {
		len--;
		last_is_dot = 1;
	}

	has_dot = 0;
	for (i = 0; i < len - 1; i++) {
		if (name[i] == '.') {
			has_dot = 1;
			break;
		}
	}

	/* try to parse expression and find any matched object. */
	if (has_dot == 1) {
		child = aml_find_from_namespace(parent, name);
		if (child == NULL) {
			return;
		}

		/*
		 * we have at least one object matched, search all objects
		 * under upper level of the found object.
		 */
		parent = child->parent;

		/* find the last `.' */
		for (name = name + len - 1; *name != '.'; name--)
			;
		name++;
		len = strlen(name) - last_is_dot;
		shallow = 1;
	}

	if (len > 4) {
		return;
	}

	va_start(ap, func);
	_aml_apply_foreach_found_objects(parent, name, len, shallow, func, ap);
	va_end(ap);
}

struct aml_name_group *
aml_new_name_group(void *id)
{
	struct	aml_name_group *result;

	result = memman_alloc(aml_memman, memid_aml_name_group);
	result->id = id;
	result->head = NULL;
	result->next = name_group_list;
	name_group_list = result;
	return (result);
}

void
aml_delete_name_group(struct aml_name_group *target)
{
	struct	aml_name_group *previous;

	previous = name_group_list;
	if (previous == target)
		name_group_list = target->next;
	else {
		while (previous && previous->next != target)
			previous = previous->next;
		if (previous)
			previous->next = target->next;
	}
	target->next = NULL;
	if (target->head)
		aml_delete_name(target->head);
	memman_free(aml_memman, memid_aml_name_group, target);
}

static struct aml_name *
aml_new_name(struct aml_name *parent, const u_int8_t *name)
{
	struct	aml_name *newname;

	if ((newname = aml_find_name(parent, name)) != NULL)
		return (newname);

	newname = memman_alloc(aml_memman, memid_aml_name);
	strncpy(newname->name, (const char *)name, 4);
	newname->parent = parent;
	newname->child = NULL;
	newname->property = NULL;
	if (parent && parent->child)
		newname->brother = parent->child;
	else
		newname->brother = NULL;
	if (parent)
		parent->child = newname;

	newname->chain = name_group_list->head;
	name_group_list->head = newname;

	return (newname);
}

/*
 * NOTE:
 * aml_delete_name() doesn't maintain aml_name_group::{head,tail}.
 */
static void
aml_delete_name(struct aml_name *target)
{
	struct	aml_name *next;
	struct	aml_name *ptr;

	for (; target; target = next) {
		next = target->chain;
		if (target->child) {
			target->chain = NULL;
			continue;
		}
		if (target->brother) {
			if (target->parent) {
				if (target->parent->child == target) {
					target->parent->child = target->brother;
				} else {
					ptr = target->parent->child;
					while (ptr && ptr->brother != target)
						ptr = ptr->brother;
					if (ptr)
						ptr->brother = target->brother;
				}
				target->brother = NULL;
			}
		} else if (target->parent) {
			target->parent->child = NULL;
		}
		aml_free_object(&target->property);
		memman_free(aml_memman, memid_aml_name, target);
	}
}

#define AML_SEARCH_NAME 0
#define AML_CREATE_NAME 1
static struct aml_name	*aml_nameman(struct aml_environ *, const u_int8_t *, int);

struct aml_name *
aml_search_name(struct aml_environ *env, const u_int8_t *dp)
{

	return (aml_nameman(env, dp, AML_SEARCH_NAME));
}

struct aml_name *
aml_create_name(struct aml_environ *env, const u_int8_t *dp)
{

	return (aml_nameman(env, dp, AML_CREATE_NAME));
}

static struct aml_name *
aml_nameman(struct aml_environ *env, const u_int8_t *dp, int flag)
{
	int	segcount;
	int	i;
	struct	aml_name *newname, *curname;
	struct	aml_name *(*searchfunc) (struct aml_name *, const u_int8_t *);

#define CREATECHECK() do {						\
	if (newname == NULL) {						\
		AML_DEBUGPRINT("ERROR CANNOT FIND NAME\n");		\
		env->stat = aml_stat_panic;				\
		return (NULL);						\
	}								\
} while(0)

	searchfunc = (flag == AML_CREATE_NAME) ? aml_new_name : aml_find_name;
	newname = env->curname;
	if (dp[0] == '\\') {
		newname = &rootname;
		dp++;
	} else if (dp[0] == '^') {
		while (dp[0] == '^') {
			newname = newname->parent;
			CREATECHECK();
			dp++;
		}
	}
	if (dp[0] == 0x00) {	/* NullName */
		dp++;
	} else if (dp[0] == 0x2e) {	/* DualNamePrefix */
		newname = (*searchfunc) (newname, dp + 1);
		CREATECHECK();
		newname = (*searchfunc) (newname, dp + 5);
		CREATECHECK();
	} else if (dp[0] == 0x2f) {	/* MultiNamePrefix */
		segcount = dp[1];
		for (i = 0, dp += 2; i < segcount; i++, dp += 4) {
			newname = (*searchfunc) (newname, dp);
			CREATECHECK();
		}
	} else if (flag == AML_CREATE_NAME) {	/* NameSeg */
		newname = aml_new_name(newname, dp);
		CREATECHECK();
	} else {
		curname = newname;
		for (;;) {
			newname = aml_find_name(curname, dp);
			if (newname != NULL)
				break;
			if (curname == &rootname || curname == NULL)
				break;
			curname = curname->parent;
		}
	}
	return (newname);
}

#undef CREATECHECK

struct aml_local_stack *
aml_local_stack_create()
{
	struct aml_local_stack *result;

	result = memman_alloc(aml_memman, memid_aml_local_stack);
	memset(result, 0, sizeof(struct aml_local_stack));
	return (result);
}

void 
aml_local_stack_push(struct aml_local_stack *stack)
{

	stack->next = stack_top;
	stack_top = stack;
}

struct aml_local_stack *
aml_local_stack_pop()
{
	struct aml_local_stack *result;

	result = stack_top;
	stack_top = result->next;
	result->next = NULL;
	return (result);
}

void
aml_local_stack_delete(struct aml_local_stack *stack)
{
	int	i;

	for (i = 0; i < 8; i++)
		aml_free_object(&stack->localvalue[i].property);
	for (i = 0; i < 7; i++)
		aml_free_object(&stack->argumentvalue[i].property);
	aml_delete_name(stack->temporary);
	memman_free(aml_memman, memid_aml_local_stack, stack);
}

struct aml_name *
aml_local_stack_getLocalX(int idx)
{

	if (stack_top == NULL)
		return (NULL);
	return (&stack_top->localvalue[idx]);
}

struct aml_name *
aml_local_stack_getArgX(struct aml_local_stack *stack, int idx)
{

	if (!stack)
		stack = stack_top;
	if (stack == NULL)
		return (NULL);
	return (&stack->argumentvalue[idx]);
}

struct aml_name *
aml_create_local_object()
{
	struct aml_name *result;

	result = memman_alloc(aml_memman, memid_aml_name);
	result->child = result->brother = result->parent = NULL;
	result->property = NULL;
	result->chain = stack_top->temporary;
	stack_top->temporary = result;
	return (result);
}
