/*
 * Copyright (c) 2007, 2008 University of Tsukuba
 * 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.
 * 3. Neither the name of the University of Tsukuba nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
 */
/*
 * Copyright (c) 2010-2012 Yuichi Watanabe
 */

/*
 * Guest Memory Manager whose guest-address is identical with host-address. 
 */

#include <core/assert.h>
#include <core/initfunc.h>
#include <core/mm.h>
#include <core/panic.h>
#include <core/printf.h>
#include <core/rm.h>
#include <core/vmmerr.h>
#include "current.h"
#include "mm.h"
#include "gmm.h"
#include "gmm_ident.h"

#define MAXNUM_OF_IDENT_MEM_MAP 64

static phys_t
gmm_ident_gp2hp (phys_t gp)
{
	phys_t r;

	if (phys_in_vmm (gp)) {
		r = GMM_NO_MAPPING;
	} else {
		r = gp;
	}
	return r;
}

static int
gmm_ident_get_mem_map(int index, phys_t *base, phys_t *len, u32 *type,
		      bool *restrict_access)
{
	struct gmm_ident *gmm_ident;
	struct resource *resource;

	gmm_ident = &current->vm->gmm_ident;

	if (index < 0 || index >= gmm_ident->mem_map_count) {
		return 0;
	}

	ASSERT(gmm_ident->mem_map);
	resource = gmm_ident->mem_map + index;

	*base = resource->start;
	*len = resource->end - resource->start + 1;
	*type = resource->data;

	if (restrict_access) {
		if (resource->data == MEM_TYPE_RESERVED &&
		    resource->parent == NULL) {
			/*
			 * VMM or other VMs use this resource.
			 */
			*restrict_access = true;
		} else {
			*restrict_access = false;
		}
	}
	return 1;
}

static int
gmm_ident_get_mem_map_count(void)
{
	return current->vm->gmm_ident.mem_map_count;
}

static struct gmm_func gmm_ident_func = {
	.gp2hp = gmm_ident_gp2hp,
	.get_mem_map = gmm_ident_get_mem_map,
	.get_mem_map_count = gmm_ident_get_mem_map_count
};

static void
gmm_ident_init_mem_map(void)
{
	int index = 0;
	struct gmm_ident* gmm_ident;
	phys_t avail_start, avail_end;
	struct resource *parent = NULL, *new, *next, *prev;
	char *name = current->vm->name;

	gmm_ident = &current->vm->gmm_ident;
	gmm_ident->mem_map
		= alloc(sizeof(struct resource) * MAXNUM_OF_IDENT_MEM_MAP);
	memset(gmm_ident->mem_map, 0,
	       sizeof(struct resource) * MAXNUM_OF_IDENT_MEM_MAP);
	if (gmm_ident->mem_map == NULL) {
		panic("gmm_ident_init_mem_map: Failed to alloc mem_map");
	}

	while ((parent = mm_next_mem_map(parent)) != NULL) {
		if (parent->data != MEM_TYPE_AVAILABLE) {
			new = gmm_ident->mem_map + index++;
			rm_init_resource(new, parent->start,
					 parent->end, parent->type,
					 parent->data, name);
			rm_insert_resource(parent, new);
			if (index >= MAXNUM_OF_IDENT_MEM_MAP) {
				goto out;
			}
			continue;
		}
		next = NULL;
		prev = NULL;
		for (;;) {
			next = RM_RESOURCE_NEXT(parent, next);
			avail_start = prev ? prev->end + 1 : parent->start;
			avail_end = next ? next->start - 1 : parent->end;
			if (avail_start <= avail_end) {
				new = gmm_ident->mem_map + index++;
				rm_init_resource(new, avail_start,
						 avail_end, parent->type,
						 parent->data, name);
				rm_insert_resource(parent, new);
			}
			if (next == NULL) {
				break;
			}
			new = gmm_ident->mem_map + index++;
			rm_init_resource(new, next->start,
					 next->end, parent->type,
					 MEM_TYPE_RESERVED, name);
			if (index >= MAXNUM_OF_IDENT_MEM_MAP) {
				goto out;
			}
			prev = next;
		}
	}
 out:
	gmm_ident->mem_map_count = index;
}

static void
gmm_ident_init (void)
{
	if (vm_get_id() != 0) {
		return;
	}
	memcpy((void *)&current->vm->gmm, (void *)&gmm_ident_func, sizeof(gmm_ident_func));
	gmm_ident_init_mem_map();
}

INITFUNC ("setupvm0", gmm_ident_init);
