/*
 * 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-2013 Yuichi Watanabe
 */

/*
 * Guest Memory Manager whose guest-address is translated into
 * host-address via a mapping table.
 */

#include <core/assert.h>
#include <core/initfunc.h>
#include <core/mm.h>
#include <core/panic.h>
#include <core/param.h>
#include <core/printf.h>
#include <core/rm.h>
#include <core/string.h>
#include <core/vmmerr.h>
#include <io/pci.h>
#include "current.h"
#include "mm.h"
#include "gmm.h"
#include "gmm_trans.h"

#define MAXNUM_OF_TRANS_MEM_MAP 64

static phys_t
gmm_trans_gp2hp (phys_t gphys)
{
	struct gmm_trans_mem_map* mem_map;
	int low, mid, high;

	low = 0;
	mem_map = current->vm->gmm_trans.mem_map;
	high = current->vm->gmm_trans.mem_map_count - 1;
	
	while (low <= high) {
		mid = (low + high) / 2;
		if (gphys < mem_map[mid].gphys_start) {
			high = mid - 1;
		} else if (gphys > mem_map[mid].gphys_end) {
			low = mid + 1;
		} else {
			return mem_map[mid].hphys.start
				+ (gphys - mem_map[mid].gphys_start);
		}
	}
	if (pci_assigned_mmio_addr(gphys, NULL)) {
		return gphys;
	}
	return GMM_NO_MAPPING;
}

static int
gmm_trans_get_mem_map(int index, phys_t *base, phys_t *len, u32 *type,
		      bool *restrict_access)
{
	struct gmm_trans *gmm_trans;
	struct gmm_trans_mem_map *mem_map;

	gmm_trans = &current->vm->gmm_trans;

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

	ASSERT(gmm_trans->mem_map);
	mem_map = gmm_trans->mem_map + index;

	*base = mem_map->gphys_start;
	*len = mem_map->gphys_end - mem_map->gphys_start + 1;
	*type = mem_map->hphys.data;

	if (restrict_access) {
		/*
		 * This resource is not used by VMM or other VMs.
		 */
		*restrict_access = false;
	}
	return 1;
}

static int
gmm_trans_get_mem_map_count(void)
{
	return current->vm->gmm_trans.mem_map_count;
}

static void
gmm_trans_clear_mem(void)
{
	struct gmm_trans *gmm_trans;
	struct gmm_trans_mem_map *mem_map;
	phys_t hphys;
	void *vaddr;
	int i;

	gmm_trans = &current->vm->gmm_trans;

	ASSERT(gmm_trans->mem_map);
	for (i = 0; i < gmm_trans->mem_map_count; i++) {
		mem_map = gmm_trans->mem_map + i;
#if 0
		printf("clear hphys 0x%llx 0x%llx gphys 0x%llx 0x%llx\n",
		       mem_map->hphys.start,
		       mem_map->hphys.end,
		       mem_map->gphys_start,
		       mem_map->gphys_end);
#endif
		for (hphys = ROUND_DOWN(mem_map->hphys.start, PAGESIZE);
		     hphys < ROUND_UP(mem_map->hphys.end, PAGESIZE);
		     hphys += PAGESIZE) {
			vaddr = mapmem_hphys(hphys, PAGESIZE, MAPMEM_WRITE);
			memset(vaddr, 0, PAGESIZE);
			unmapmem(vaddr, PAGESIZE);
		}
	}
}

static struct gmm_func gmm_trans_func = {
	.gp2hp			= gmm_trans_gp2hp,
	.get_mem_map		= gmm_trans_get_mem_map,
	.get_mem_map_count	= gmm_trans_get_mem_map_count,
	.clear_mem		= gmm_trans_clear_mem
};

int
gmm_trans_alloc_avail(int count, struct resource *parent, phys_t start, phys_t end)
{
	struct gmm_trans_mem_map *new;
	char *name = current->vm->name;
	struct gmm_trans* gmm_trans;
	phys_t low_size;

	gmm_trans = &current->vm->gmm_trans;

	while(count < MAXNUM_OF_TRANS_MEM_MAP) {
		new = gmm_trans->mem_map + count;
		if (rm_alloc_avail_resource(parent, &new->hphys,
					    start, end, PAGESIZE,
					    RESOURCE_TYPE_MEM,
					    MEM_TYPE_AVAILABLE,
					    name) != VMMERR_SUCCESS) {
			break;
		}
		if (count > 0) {
			if ((new - 1)->gphys_end
			    == mm_top_of_low_avail_mem()) {
				new->gphys_start = 0x100000000;
			} else {
				new->gphys_start = (new - 1)->gphys_end + 1;
			}
		} else {
			new->gphys_start = 0;
		}
		new->gphys_end = new->gphys_start
			+ (new->hphys.end - new->hphys.start);

		if (new->gphys_start < mm_top_of_low_avail_mem()
		    && new->gphys_end > mm_top_of_low_avail_mem()) {
			new->gphys_end = mm_top_of_low_avail_mem();
			low_size = new->gphys_end - new->gphys_start + 1;
			new->hphys.end = new->hphys.start + low_size;
		}
		count++;
	}
	return count;
}

static void
gmm_trans_init_mem_map(void)
{
	int index = 0;
	int count = 0;
	struct gmm_trans* gmm_trans;
	struct resource *parent;
	phys_t start, end;
	char param_name[VM_NAME_BUF_SIZE + 4];
	vmmerr_t err;

	gmm_trans = &current->vm->gmm_trans;
	gmm_trans->mem_map
		= alloc(sizeof(struct resource) * MAXNUM_OF_TRANS_MEM_MAP);
	if (gmm_trans->mem_map == NULL) {
		panic("gmm_trans_init_mem_map: Failed to alloc mem_map");
	}

	snprintf(param_name, VM_NAME_BUF_SIZE + 4, "%s.mem", current->vm->name);
	while ((err = param_get_u64_range(param_name, index++, &start, &end))
		!= VMMERR_NOTEXIST) {
		if (err != VMMERR_SUCCESS) {
			printf("Failed to get %s(%d) paramater. err 0x%x\n",
			       param_name, index - 1,  err);
			continue;
		}
		parent = NULL;
		while ((parent = mm_next_mem_map(parent)) != NULL) {
			if (parent->data != MEM_TYPE_AVAILABLE) {
				continue;
			}
			count = gmm_trans_alloc_avail(count, parent, start, end);
			if (count >= MAXNUM_OF_TRANS_MEM_MAP) {
				goto out;
			}
		}
	}
 out:
	gmm_trans->mem_map_count = count;
}

static void
gmm_trans_init (void)
{
	if (vm_get_id() == 0) {
		return;
	}
	memcpy((void *)&current->vm->gmm, (void *)&gmm_trans_func,
	       sizeof(gmm_trans_func));
	gmm_trans_init_mem_map();
}

INITFUNC ("setupvm0", gmm_trans_init);
