/*
 * 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
 */

#include <core/assert.h>
#include <core/cpu.h>
#include <core/initfunc.h>
#include <core/linkage.h>
#include <core/param.h>
#include <core/printf.h>
#include <core/string.h>
#include <io/pci.h>
#include "callrealmode.h"
#include "convert.h"
#include "current.h"
#include "initfunc.h"
#include "guest_boot.h"
#include "mm.h"
#include "multiboot.h"
#include "gbiosloader.h"
#include "panic.h"
#include "param.h"
#include "pcpu.h"
#include "process.h"
#include "regs.h"
#include "setup_guestboot.h"
#include "smp.h"
#include "svm.h"
#include "svm_init.h"
#include "tty.h"
#include "vcpu.h"
#include "vm.h"
#include "vmmcall.h"
#include "vramwrite.h"
#include "vt.h"

static struct multiboot_info multiboot_info;

static void
print_boot_msg (void)
{
#ifdef __x86_64__
	printf ("Starting VMM (x86_64)...\n");
#else
	printf ("Starting VMM (x86_32)...\n");
#endif
	printf ("Copyright (c) 2007, 2008 University of Tsukuba\n");
	printf ("Copyright (c) 2010-2014 Yuichi Watanabe\n");
	printf ("All rights reserved.\n");
}

static void
print_startvm_msg (void)
{
	printf ("Starting a virtual machine.\n");
}

static void
load_drivers(void)
{
	printf("Loading drivers.\n");
	call_initfunc("driver");
}

static u8
detect_bios_boot_device (struct multiboot_info *mi)
{
	if (mi->flags.boot_device) {
		return mi->boot_device.drive;
	} else {
		printf ("BIOS boot device detection failed. Using 0x80.\n");
		return 0x80;
	}
}

static void
copy_cmdline (void)
{
	void *vaddr;
	if (multiboot_info.flags.cmdline) {
		vaddr = mapmem (MAPMEM_HPHYS, multiboot_info.cmdline, CMDLINE_SIZE);
		if (vaddr == NULL) {
			panic("Failed to map multiboot_info.cmdline.");
		}
		param_save(vaddr);
		unmapmem(vaddr, CMDLINE_SIZE);
	}
}

static void
copy_gbios (void)
{
	struct multiboot_modules *mod;

	if (multiboot_info.flags.mods && multiboot_info.mods_count) {
		mod = mapmem(MAPMEM_HPHYS, multiboot_info.mods_addr,
			      sizeof(mod));
		if (mod == NULL) {
			panic("Failed to map multiboot_info.mods_addr.");
		}
		printf("Guest BIOS found. size 0x%x\n", mod->mod_end - mod->mod_start);
		gbios_save(mod->mod_start, mod->mod_end - mod->mod_start);
		unmapmem(mod, sizeof(mod));;
	} else {
		printf("Guest BIOS not found.\n");
	}
}

/* make CPU's virtualization extension usable */
static void
virtualization_init_pcpu (void)
{
	currentcpu->fullvirtualize = FULLVIRTUALIZE_NONE;
	if (vt_available ()) {
		vt_init ();
		currentcpu->fullvirtualize = FULLVIRTUALIZE_VT;
	}
	if (svm_available ()) {
		svm_init ();
		currentcpu->fullvirtualize = FULLVIRTUALIZE_SVM;
	}
}

/* set current vcpu for full virtualization */
static void
vmctl_init (void)
{
	switch (currentcpu->fullvirtualize) {
	case FULLVIRTUALIZE_NONE:
		panic ("This processor does not support"
		       " Intel VT or AMD Virtualization");
		break;
	case FULLVIRTUALIZE_VT:
		vt_vmctl_init ();
		break;
	case FULLVIRTUALIZE_SVM:
		svm_vmctl_init ();
		break;
	}
}

/* static void */
/* sync_cursor_pos (void) */
/* { */
/* 	unsigned int row, col; */
/* 	u16 dx; */

/* 	vramwrite_get_cursor_pos (&col, &row); */
/* 	conv8to16 (col, row, &dx); */
/* 	current->vmctl.write_general_reg (GENERAL_REG_RBX, 0); */
/* 	current->vmctl.write_general_reg (GENERAL_REG_RDX, dx); */
/* } */

static void
setup_vm (void)
{
	bool vbsp;
	struct vm *vm = NULL;

	sync_all_processors ();

	vbsp = vm_current_is_vbsp();
	if (vbsp) {
		vm = vm_new();
		if (vm == NULL) {
			panic("new_vm failed");
		}
	}

	sync_all_processors ();

	if (!vbsp) {
		vm = vm_find();
		if (vm == NULL) {
			panic("Can't get vm");
		}
	}
	if (new_vcpu(vm, vbsp) == NULL) {
		panic ("new_vcpu failed");
	}

	vmctl_init ();
	sync_all_processors ();
	current->vmctl.vminit ();

	/*
	 * Initialize general functions of vm and vcpu.
	 */
	sync_all_processors ();
	if (vbsp) {
		call_initfunc("gvm");
	}
	sync_all_processors ();
	call_initfunc ("vcpu");

	/*
	 * Initialize functions of pass-vm and its vcpu.
	 */
	sync_all_processors ();
	if (!cpu_is_bsp() && vbsp) {
		call_initfunc("setupvm");
	}
	sync_all_processors ();
	if (cpu_is_bsp()) {
		call_initfunc("setupvm");
	}
	sync_all_processors ();

	call_initfunc ("passcpu");

	sync_all_processors ();
	vcpu_reset();
	if (cpu_is_bsp()) {
		setup_guestboot();
	}
	current->initialized = true;

	sync_all_processors ();
	if (vbsp) {
		vm_dump();
	}
	sync_all_processors ();
}

static void
run_vm (void)
{
	if (!current->initialized) {
		panic("VCPU on CPU%d is not initialized.",
		      get_cpu_id());
	}
	current->vmctl.run_vm ();
}

static void
ap_main (void)
{
	apic_id_t shell_apic_id;

	call_initfunc ("ap");
	call_initfunc ("pcpu");

	if (param_get_u32("shell", 0, &shell_apic_id) == VMMERR_SUCCESS &&
	    !vm_current_is_vbsp()) {
		if (get_apic_id() == shell_apic_id) {
			int i;

			for (i = 0; i < 14; i++) {
				sync_all_processors();
			}
			for (;;) {
				shell();
			}
		}
	}

	setup_vm();
	sync_all_processors(); /* 12 */

	if (current->vbsp) {
		if (!current->vm->iommu_enabled) {
			panic("IOMMU is not enabled. VMM can't start more than one VMs");
		}
	}

	sync_all_processors(); /* 13 */
	sync_all_processors(); /* 14 */

	if (current->vbsp && 
	    param_get_u32("shell", 0, &shell_apic_id) == VMMERR_SUCCESS) {
		if (get_apic_id() == shell_apic_id) {
			for (;;) {
				shell();
			}
		}
	}

	run_vm();
	panic ("VM stopped.");
}

static void
bsp_main (void)
{
	apic_id_t shell_apic_id;

	call_initfunc ("bsp");
	call_initfunc ("pcpu");

	set_bios_boot_drive(detect_bios_boot_device(&multiboot_info));
	load_drivers();
	setup_vm();
#ifdef DUMP_MEM_AND_PCI
	mm_dump_mem_address_space();
	pci_dump_resources();
#endif
	call_initfunc ("start");

	sync_all_processors(); /* 12 */
	sync_all_processors(); /* 13 */

	print_startvm_msg();
	vramwrite_exit_global();

	sync_all_processors(); /* 14 */

	if (param_get_u32("shell", 0, &shell_apic_id) == VMMERR_SUCCESS) {
		if (get_apic_id() == shell_apic_id) {
			for (;;) {
				shell();
			}
		}
	}

	run_vm();
	panic ("VM stopped.");
}

asmlinkage void
vmm_start (struct multiboot_info *mi_arg)
{
	tty_init_global();
	print_boot_msg();
	memcpy (&multiboot_info, mi_arg, sizeof (struct multiboot_info));
	initfunc_init ();
	call_initfunc ("global");
	call_initfunc ("vmmcal");
	call_initfunc ("msg");
	start_all_processors (bsp_main, ap_main);
}

INITFUNC ("pcpu3", virtualization_init_pcpu);
INITFUNC ("global3", copy_cmdline);
INITFUNC ("global3", copy_gbios);
