/*
 * 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 (framework)
 */

#include <core/initfunc.h>
#include <core/panic.h>
#include <core/printf.h>
#include <core/string.h>
#include "callrealmode.h"
#include "constants.h"
#include "convert.h"
#include "cpu_seg.h"
#include "current.h"
#include "io_io.h"
#include "misc.h"
#include "mm.h"

/*
 * Modify int15 entry in IVT to execute iow to INT15_IOPORT.
 * INT15_IOPORT need to be less than 0x100.
 * 0xe0 seems not to be used, referencing the datasheet of P55 PCH.
 */
#define INT15_IOPORT 0xe0

#define INT15_VECTOR_PHYS (0x15 * 4)

static void
emulate_int15(void)
{
	ulong rax, rbx, rcx, rdx, rdi, rflags;
	u8 ah, al;
	u32 type;
	phys_t base, len;

	current->vmctl.read_general_reg (GENERAL_REG_RAX, &rax);
	conv16to8 ((u16)rax, &al, &ah);
	switch(ah) {
	case 0xE8:
		if (al == 0x20)
			goto emulate_e820;
		if (al == 0x01)
			goto err_status_ret;
		if (al == 0x81)
			goto errret;
		break;
	case 0x88:
		goto err_status_ret;
	case 0x8a:
	case 0xC7:
		goto errret;
	case 0xDA:
		if (al == 0x88)
			goto errret;
		break;
	}
	/* continue, jump to the original interrupt handler (BIOS) */
	return;
errret:
/* 	printf ("DEBUG: emulate_int15: return a error. ah 0x%x al 0x%x\n", ah, al); */
	/* on error set CF */
	current->vmctl.write_ip (0x25C);
	current->vmctl.read_flags (&rflags);
	current->vmctl.write_flags (rflags | RFLAGS_IF_BIT | RFLAGS_CF_BIT);
	return;
err_status_ret:
/* 	printf ("DEBUG: emulate_int15: return a error. ah 0x%x al 0x%x\n", ah, al); */
	/* on error, ah=0x86 and set CF */
	rax = (rax & ~0xFF00UL) | 0x8600;
	current->vmctl.write_general_reg (GENERAL_REG_RAX, rax);
	current->vmctl.write_ip (0x25C);
	current->vmctl.read_flags (&rflags);
	current->vmctl.write_flags (rflags | RFLAGS_IF_BIT | RFLAGS_CF_BIT);
	return;
/* emulate_88: */
/* 	printf("DEBUG: int 15 ah=0x88\n"); */
/* 	rax = modify_ulong_u16(rax, 15 * 1024); */
/* 	current->vmctl.write_general_reg (GENERAL_REG_RAX, rax); */
/* 	current->vmctl.write_ip (0x25C); */
/* 	current->vmctl.read_flags (&rflags); */
/* 	current->vmctl.write_flags ((rflags | RFLAGS_IF_BIT) & ~RFLAGS_CF_BIT); */
/* 	return; */
emulate_e820:
	/* E820 */
	current->vmctl.read_general_reg (GENERAL_REG_RBX, &rbx);
	current->vmctl.read_general_reg (GENERAL_REG_RCX, &rcx);
	current->vmctl.read_general_reg (GENERAL_REG_RDX, &rdx);
	current->vmctl.read_general_reg (GENERAL_REG_RDI, &rdi);
	rdi = (u16)rdi;
	if ((u32)rdx != 0x534D4150)
		goto errret;
	if ((u32)rcx < 0x14)
		goto errret;

	if (current->vm->gmm.get_mem_map((int)rbx, &base, &len, &type, NULL)) {
/* 		printf("DEBUG: E820 0x%llx 0x%llx %d\n", base, len, type); */
		if (rbx < current->vm->gmm.get_mem_map_count() - 1) {
			rbx = rbx + 1;
		} else {
			rbx = 0;
		}
	} else {
		rbx = 0;
		base = 0;
		len = 0;
		type = 0;
	}

	/* FIXME: cpu_seg_write fails if ES:[DI] page is not present */
	/* nor writable (virtual 8086 mode only) */
	rax = rdx;
	rcx = 0x14;
	if (cpu_seg_write_q (SREG_ES, rdi + 0x0, base))
		panic ("emulate_int15: write base failed");
	if (cpu_seg_write_q (SREG_ES, rdi + 0x8, len))
		panic ("emulate_int15: write len failed");
	if (cpu_seg_write_l (SREG_ES, rdi + 0x10, type))
		panic ("emulate_int15: write type failed");
	current->vmctl.write_general_reg (GENERAL_REG_RAX, rax);
	current->vmctl.write_general_reg (GENERAL_REG_RBX, rbx);
	current->vmctl.write_general_reg (GENERAL_REG_RCX, rcx);
	current->vmctl.write_ip (0x25C);
	current->vmctl.read_flags (&rflags);
	current->vmctl.write_flags ((rflags | RFLAGS_IF_BIT) & ~RFLAGS_CF_BIT);
	return;
}

static int
int15_ioport(iotype_t type, ioport_t port, void *data)
{
	ulong cr0, rflags, rip;
	bool ok = false;

	/* first check: port, type and cpu mode */
	if (port == INT15_IOPORT && type == IOTYPE_OUTW) {
		current->vmctl.read_control_reg (CONTROL_REG_CR0, &cr0);
		if (cr0 & CR0_PE_BIT) {
			current->vmctl.read_flags (&rflags);
			if (rflags & RFLAGS_VM_BIT)
				ok = true; /* virtual 8086 mode */
		} else {
			ok = true; /* real mode */
		}
	}
	/* second check: ip is correct */
	if (ok) {
		current->vmctl.read_ip (&rip);
		if (rip != 0x255)
			ok = false;
	}
	if (ok) {
		emulate_int15();
		return 1; /* no pass */
	} else {
		printf ("int15_ioport: I/O port=0x%x type=%d\n", port, type);
		return 0; /* pass */
	}
}

static void
install_int15_hook (void)
{
	u32 old;

	if (vm_get_id() != 0) {
		return;
	}

	/* 9 bytes hook program */
	/* 0000:0255 E7 1F out   %ax, $0x1F */
	/* 0000:0257 EA    ljmp             */
	/* 0000:0258 XX YY xx yy xxyy:XXYY  */
	/* 0000:025C CA 02 lret  $2         */
	/* save old interrupt vector */
	read_hphys_l (INT15_VECTOR_PHYS, &old, 0);
	write_hphys_l (0x254, (INT15_IOPORT << 16) | 0xEA00E700, 0);
	write_hphys_l (0x258, old, 0);
	write_hphys_w (0x25C, 0x02CA, 0);
	/* set interrupt vector to 0x0000:0x0255 */
	write_hphys_l (INT15_VECTOR_PHYS, 0x00000255, 0);
	set_iofunc (INT15_IOPORT, int15_ioport);
}

int
gmm_get_mem_map(int index, phys_t *base, phys_t *len, u32 *type,
		bool *restrict_access)
{
	return current->vm->gmm.get_mem_map(index, base, len, type,
					    restrict_access);
}

int
gmm_get_mem_map_count(void)
{
	return current->vm->gmm.get_mem_map_count();
}

phys_t
gmm_gp2hp(phys_t gphys)
{
	return current->vm->gmm.gp2hp(gphys);
}

phys32_t
gmm_top_of_low_avail_mem(void)
{
	phys_t top_of_low = 0;
	phys_t gphys, len, end;
	u32 type;
	int index = 0;

	while (gmm_get_mem_map(index++, &gphys, &len, &type, NULL)) {
		if (type == MEM_TYPE_AVAILABLE) {
			end = gphys + len -1;
			if (end <= 0xffffffff &&
			    end > top_of_low) {
				top_of_low = end;
			}
		}

	}
	return top_of_low;
}

phys_t
gmm_top_of_high_avail_mem(void)
{
	phys_t top_of_high = 0;
	phys_t gphys, len, end;
	u32 type;
	int index = 0;

	while (gmm_get_mem_map(index++, &gphys, &len, &type, NULL)) {
		if (type == MEM_TYPE_AVAILABLE) {
			end = gphys + len -1;
			if (end >= 0x100000000LL &&
			    end > top_of_high) {
				top_of_high = end;
			}
		}

	}
	return top_of_high;
}

INITFUNC ("setupvm1", install_int15_hook);
