/*
 * Copyright (c) 2010-2013 Yuichi Watanabe
 * 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 Yuichi Watanabe 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.
 */

#include <common/common.h>
#include <core/assert.h>
#include <core/cpu.h>
#include <core/initfunc.h>
#include <core/mm.h>
#include <core/param.h>
#include <core/printf.h>
#include <core/sleep.h>
#include <core/spinlock.h>
#include <io/pci.h>
#include "apic.h"
#include "apic_pass.h"
#include "current.h"
#include "gbiosloader.h"
#include "int.h"
#include "pcpu.h"
#include "setup_guestboot.h"
#include "vm.h"

static size_t driver_data_size = 0;
static int new_vm_called = 0;

static LIST2_DEFINE_HEAD_INIT(struct vm, vm_list);
static spinlock_t vm_list_lock = SPINLOCK_INITIALIZER;

drvdata_hdl_t
vm_alloc_driver_data(size_t size)
{
	off_t offset;

	ASSERT(new_vm_called == 0);
	ASSERT(cpu_is_bsp());

	offset = driver_data_size;
	driver_data_size += ROUND_UP(size, sizeof(u64));

	return offset;
}

void *
vm_get_driver_data(drvdata_hdl_t handle)
{
	ASSERT(handle < driver_data_size);
	return (void *)((ulong)(current->vm->driver_data) + handle);
}

unsigned int
vm_get_vcpu_count()
{
	struct vm *vm;
	unsigned int vcpu_count;

	vm = current->vm;
	spinlock_lock(&vm->vcpu_list_lock);
	vcpu_count = vm->vcpu_count;
	spinlock_unlock(&vm->vcpu_list_lock);
	return vcpu_count;
}

char *
vm_get_name()
{
	return current->vm->name;
}

static u32
vm_create_id(char *name)
{
	char buf[VM_NAME_BUF_SIZE];
	u32 id = 0;
	int i = 0;
	vmmerr_t err;

	if (strcmp(name, VM0_NAME) == 0) {
		return 0;
	}

	while ((err = param_get_str("vm", i++, buf, VM_NAME_BUF_SIZE))
	       != VMMERR_NOTEXIST) {
		if (err != VMMERR_SUCCESS) {
			continue;
		}
		if (strcmp(buf, VM0_NAME) != 0) {
			id++;
		}
		if (strcmp(buf, name) == 0) {
			return id;
		}
	}
	return (u32)-1;
}

u32
vm_get_id(void)
{
	return current->vm->id;
}

apic_id_t
vm_vbsp_apic_id(void)
{
	return current->vm->vbsp_apic_id;
}

static struct vm *
vm_find_by_name(char *name)
{
	struct vm *vm;

	spinlock_lock(&vm_list_lock);
	LIST2_FOREACH(vm_list, vm_list, vm) {
		if (strcmp(vm->name, name) == 0) {
			break;
		}
	}
	spinlock_unlock(&vm_list_lock);
	return vm;
}

static vmmerr_t
vm_get_name_from_param(char *buf, size_t size)
{
	char name[VM_NAME_BUF_SIZE];
	char param_name[VM_NAME_BUF_SIZE + 4];
	u32 apic_id;
	int i = 0;
	int j;
	vmmerr_t err;

	ASSERT(sizeof(u32) == sizeof(apic_id_t));

	if (cpu_is_bsp()) {
		/*
		 * CPU0 is always VM0.
		 */
		snprintf(buf, size, "%s", VM0_NAME);
		return VMMERR_SUCCESS;
	}

	while ((err = param_get_str("vm", i++, name, VM_NAME_BUF_SIZE))
	       != VMMERR_NOTEXIST) {
		if (err != VMMERR_SUCCESS) {
			printf("Failed to get vm(%d) paramater. err 0x%x\n",
			       i - 1, err);
			continue;
		}
		snprintf(param_name, VM_NAME_BUF_SIZE + 4, "%s.cpu", name);
		j = 0;
		while ((err = param_get_u32(param_name, j++, &apic_id))
		       != VMMERR_NOTEXIST) {
			if (err != VMMERR_SUCCESS) {
				printf("Failed to get %s(%d) paramater. "
				       "err 0x%x\n",
				       param_name, j - 1, err);
				continue;
			}
			if (apic_id == get_apic_id()) {
				snprintf(buf, size, "%s", name);
				return VMMERR_SUCCESS;
			}
		}
	}

	/*
	 * CPUs not specified by <vmname>.cpu parameter are VM0.
	 */
	snprintf(buf, size, "%s", VM0_NAME);
	return VMMERR_SUCCESS;
}

struct vm *
vm_find(void)
{
	char name[VM_NAME_BUF_SIZE];
	vmmerr_t err;

	err = vm_get_name_from_param(name, VM_NAME_BUF_SIZE);
	if (err != VMMERR_SUCCESS) {
		return NULL;
	}
	return vm_find_by_name(name);
}

bool
vm_current_is_vbsp(void)
{
	char name[VM_NAME_BUF_SIZE];
	char param_name[VM_NAME_BUF_SIZE + 4];
	u32 apic_id;
	vmmerr_t err;
	int i;

	if (cpu_is_bsp()) {
		/*
		 * physical CPU0 is always vBSP of VM0.
		 */
		return true;
	}

	err = vm_get_name_from_param(name, VM_NAME_BUF_SIZE);
	if (err != VMMERR_SUCCESS) {
		return false;
	}

	if (strcmp(name, VM0_NAME) == 0) {
		return false;
	}

	snprintf(param_name, VM_NAME_BUF_SIZE + 4, "%s.cpu", name);

	i = 0;
	while ((err = param_get_u32(param_name, i++, &apic_id))
	       != VMMERR_NOTEXIST) {
		if (err != VMMERR_SUCCESS) {
			if (err != VMMERR_NOTEXIST) {
				printf("Failed to get %s(%d) paramater. "
				       "err 0x%x\n", param_name, i - 1, err);
				continue;
			}
		}
		if (apic_id != get_bsp_apic_id()) {
			break;
		}
	}
	if (err == VMMERR_NOTEXIST) {
		panic("Can't found the self apic_id.\n");
	}
	if (apic_id == get_apic_id()) {
		return true;
	} else {
		return false;
	}
}

struct vm *
vm_new(void)
{
	struct vm *vm;
	vmmerr_t err;

	new_vm_called = 1;

	vm = (struct vm *)alloc(sizeof(struct vm) + driver_data_size);
	if (vm == NULL) {
		return NULL;
	}
	memset(vm, 0, sizeof(struct vm) + driver_data_size);

	err = vm_get_name_from_param(vm->name, VM_NAME_BUF_SIZE);
	if (err != VMMERR_SUCCESS) {
		free(vm);
		return NULL;
	}

	vm->id = vm_create_id(vm->name);

	spinlock_init(&vm->vcpu_list_lock);
	LIST2_HEAD_INIT(vm->vcpu_list);

	spinlock_lock(&vm_list_lock);
	LIST2_ADD(vm_list, vm_list, vm);
	spinlock_unlock(&vm_list_lock);

	vm->vbsp_apic_id = get_apic_id();

	wait_init(&vm->wait);
	return vm;
}

void
vm_dump(void)
{
	struct vm *vm;
	struct vcpu *vcpu;
	int index = 0;
	phys_t gphys;
	phys_t len;
	u32 type;
	static spinlock_t vm_dump_lock = SPINLOCK_INITIALIZER;

	spinlock_lock(&vm_dump_lock);
	vm = current->vm;
	printf("%s:", vm->name);
	vcpu = NULL;
	while ((vcpu = vcpu_next(vm, vcpu)) != NULL) {
		printf(" 0x%x", vcpu->apic_id);
	}
	printf("\n");
	while (vm->gmm.get_mem_map(index++, &gphys, &len, &type, NULL)) {
		printf("    %016llx-%016llx 0x%x\n",
		       gphys, gphys + len - 1, type);
	}
	pci_dump_assignment();
	spinlock_unlock(&vm_dump_lock);
	return;
}

void
vm_iommu_enabled(void)
{
	current->vm->iommu_enabled = true;
}

void
vm_wait_other_cpus(void)
{
	wait(&current->vm->wait, vm_get_vcpu_count());
}

bool
vm_resetting(void)
{
	return current->vm->resetting;
}

vmmerr_t
vm_reset(void)
{
	struct vm *vm = current->vm;

	if (!current->vbsp) {
		return VMMERR_NOSUP;
	}

	printf("Reset %s\n", vm_get_name());

	vm->resetting = true;

	/*
	 * Initialize other cpus.
	 */
	apic_send_init_others();

	/*
	 * Reset vcpu and load guest BIOS.
	 */
	printf("Reset cpu 0x%x\n", get_cpu_id());
	vcpu_reset();
	apic_maskall();

	vm_wait_other_cpus();

	if (vm->gmm.clear_mem) {
		vm->gmm.clear_mem();
	}

	if (vm_get_id() == 0) {
		setup_guestboot();
	} else {
		gbios_load();
	}

	call_initfunc("resetvm");

	vm_wait_other_cpus();
	extint_reset_apic();
	vm_wait_other_cpus();

	vm->resetting = false;

	return VMMERR_SUCCESS;
}
