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

#include "asm.h"
#include "constants.h"
#include "cpu_mmu_spt.h"
#include "current.h"
#include "entry.h"
#include "mm.h"
#include "panic.h"
#include <core/printf.h>
#include "seg.h"
#include "vt_internal.h"

#define REALMODE_IDTR_BASE	0
#define REALMODE_IDTR_LIMIT	0
#define REALMODE_TR_LIMIT	(65536 / 8 - 1)
#define REALMODE_TR_ACR		0x8B
#define REALMODE_TR_BASE	sym_to_phys (head)

#define CR4_PASSTHROUGH CR4_OSXSAVE_BIT

#define VMREAD(a,b) do { \
	ulong tmp; \
 \
	asm_vmread ((a), &tmp); \
	*(b) = tmp; \
} while (0)

void
vt_read_general_reg (enum general_reg reg, ulong *val)
{
	switch (reg) {
	case GENERAL_REG_RAX:
		*val = current->u.vt.vr.rax;
		break;
	case GENERAL_REG_RCX:
		*val = current->u.vt.vr.rcx;
		break;
	case GENERAL_REG_RDX:
		*val = current->u.vt.vr.rdx;
		break;
	case GENERAL_REG_RBX:
		*val = current->u.vt.vr.rbx;
		break;
	case GENERAL_REG_RSP:
		asm_vmread (VMCS_GUEST_RSP, val);
		break;
	case GENERAL_REG_RBP:
		*val = current->u.vt.vr.rbp;
		break;
	case GENERAL_REG_RSI:
		*val = current->u.vt.vr.rsi;
		break;
	case GENERAL_REG_RDI:
		*val = current->u.vt.vr.rdi;
		break;
	case GENERAL_REG_R8:
		*val = current->u.vt.vr.r8;
		break;
	case GENERAL_REG_R9:
		*val = current->u.vt.vr.r9;
		break;
	case GENERAL_REG_R10:
		*val = current->u.vt.vr.r10;
		break;
	case GENERAL_REG_R11:
		*val = current->u.vt.vr.r11;
		break;
	case GENERAL_REG_R12:
		*val = current->u.vt.vr.r12;
		break;
	case GENERAL_REG_R13:
		*val = current->u.vt.vr.r13;
		break;
	case GENERAL_REG_R14:
		*val = current->u.vt.vr.r14;
		break;
	case GENERAL_REG_R15:
		*val = current->u.vt.vr.r15;
		break;
	default:
		panic ("Unknown register.");
	}
}

void
vt_write_general_reg (enum general_reg reg, ulong val)
{
	switch (reg) {
	case GENERAL_REG_RAX:
		current->u.vt.vr.rax = val;
		break;
	case GENERAL_REG_RCX:
		current->u.vt.vr.rcx = val;
		break;
	case GENERAL_REG_RDX:
		current->u.vt.vr.rdx = val;
		break;
	case GENERAL_REG_RBX:
		current->u.vt.vr.rbx = val;
		break;
	case GENERAL_REG_RSP:
		asm_vmwrite (VMCS_GUEST_RSP, val);
		break;
	case GENERAL_REG_RBP:
		current->u.vt.vr.rbp = val;
		break;
	case GENERAL_REG_RSI:
		current->u.vt.vr.rsi = val;
		break;
	case GENERAL_REG_RDI:
		current->u.vt.vr.rdi = val;
		break;
	case GENERAL_REG_R8:
		current->u.vt.vr.r8 = val;
		break;
	case GENERAL_REG_R9:
		current->u.vt.vr.r9 = val;
		break;
	case GENERAL_REG_R10:
		current->u.vt.vr.r10 = val;
		break;
	case GENERAL_REG_R11:
		current->u.vt.vr.r11 = val;
		break;
	case GENERAL_REG_R12:
		current->u.vt.vr.r12 = val;
		break;
	case GENERAL_REG_R13:
		current->u.vt.vr.r13 = val;
		break;
	case GENERAL_REG_R14:
		current->u.vt.vr.r14 = val;
		break;
	case GENERAL_REG_R15:
		current->u.vt.vr.r15 = val;
		break;
	default:
		panic ("Unknown register.");
	}
}

void
vt_read_control_reg (enum control_reg reg, ulong *val)
{
	switch (reg) {
	case CONTROL_REG_CR0:
		asm_vmread (VMCS_CR0_READ_SHADOW, val);
		break;
	case CONTROL_REG_CR2:
		*val = current->u.vt.vr.cr2;
		break;
	case CONTROL_REG_CR3:
		*val = current->u.vt.vr.cr3;
		break;
	case CONTROL_REG_CR4:
		asm_vmread (VMCS_CR4_READ_SHADOW, val);
		break;
	default:
		panic ("Unknown control register.");
	}
}

static void
pe_change_switch_tr (bool pe)
{
	struct vt_realmode_data *rd;

	rd = &current->u.vt.realmode;
	if (pe) {
		asm_vmwrite (VMCS_GUEST_TR_LIMIT, rd->tr_limit);
		asm_vmwrite (VMCS_GUEST_TR_ACCESS_RIGHTS, rd->tr_acr);
		asm_vmwrite (VMCS_GUEST_TR_BASE, rd->tr_base);
	} else {
		asm_vmread (VMCS_GUEST_TR_LIMIT, &rd->tr_limit);
		asm_vmread (VMCS_GUEST_TR_ACCESS_RIGHTS, &rd->tr_acr);
		asm_vmread (VMCS_GUEST_TR_BASE, &rd->tr_base);
		asm_vmwrite (VMCS_GUEST_TR_LIMIT, REALMODE_TR_LIMIT);
		asm_vmwrite (VMCS_GUEST_TR_ACCESS_RIGHTS, REALMODE_TR_ACR);
		asm_vmwrite (VMCS_GUEST_TR_BASE, REALMODE_TR_BASE);
	}
}

static void
pe_change_enable_sw (bool pe)
{
	ulong rflags, tmp;
	struct vt_vmentry_regs *vr;

	/* save all segment selectors */
	vr = &current->u.vt.vr;
	if (!(vr->sw.enable & SW_SREG_ES_BIT))
		VMREAD (VMCS_GUEST_ES_SEL, &vr->sw.es);
	if (!(vr->sw.enable & SW_SREG_CS_BIT))
		VMREAD (VMCS_GUEST_CS_SEL, &vr->sw.cs);
	if (!(vr->sw.enable & SW_SREG_SS_BIT))
		VMREAD (VMCS_GUEST_SS_SEL, &vr->sw.ss);
	if (!(vr->sw.enable & SW_SREG_DS_BIT))
		VMREAD (VMCS_GUEST_DS_SEL, &vr->sw.ds);
	if (!(vr->sw.enable & SW_SREG_FS_BIT))
		VMREAD (VMCS_GUEST_FS_SEL, &vr->sw.fs);
	if (!(vr->sw.enable & SW_SREG_GS_BIT))
		VMREAD (VMCS_GUEST_GS_SEL, &vr->sw.gs);

	/* enable emulation */
	vr->sw.num = 0; /* number of emulated instructions */
	vr->sw.enable = 0;

	if (pe) {
		/* real address mode to protected mode */

		/* clear a virtual 8086 mode flag */
		asm_vmread (VMCS_GUEST_RFLAGS, &rflags);
		rflags &= ~RFLAGS_VM_BIT;
		asm_vmwrite (VMCS_GUEST_RFLAGS, rflags);

		/*
		 * To keep segment base and limit in real mode,
		 * we don't touch them.
		 */

		/*
		 * Set segment access rights to proper value for protect mode.
		 * The DPL of CS and SS must be zero.
		 */
		asm_vmwrite (VMCS_GUEST_ES_ACCESS_RIGHTS, 0xF3);
		asm_vmwrite (VMCS_GUEST_CS_ACCESS_RIGHTS, 0x9B);
		asm_vmwrite (VMCS_GUEST_SS_ACCESS_RIGHTS, 0x93);
		asm_vmwrite (VMCS_GUEST_DS_ACCESS_RIGHTS, 0xF3);
		asm_vmwrite (VMCS_GUEST_FS_ACCESS_RIGHTS, 0xF3);
		asm_vmwrite (VMCS_GUEST_GS_ACCESS_RIGHTS, 0xF3);

		asm_vmwrite (VMCS_GUEST_SS_SEL, 8);
		/*
		 * If a RPL of CS or SS is 0, it is usable as it is
		 * without emulation. A null selector is also acceptable.
		 * If a RPL is not 0, set segment selector to dummy value
		 * (RPL 0, TI 0, Index 1), and emulate it.
		 */
		if ((vr->sw.cs & 3) == 0) {
			asm_vmwrite (VMCS_GUEST_CS_SEL, vr->sw.cs);
		} else {
			vr->sw.enable |= SW_SREG_CS_BIT;
			asm_vmwrite (VMCS_GUEST_CS_SEL, 8);
		}

		if ((vr->sw.ss & 3) == 0) {
			asm_vmwrite (VMCS_GUEST_SS_SEL, vr->sw.ss);
		} else {
			asm_vmwrite (VMCS_GUEST_CS_SEL, 8);
			vr->sw.enable |= SW_SREG_SS_BIT;
		}

		asm_vmwrite (VMCS_GUEST_ES_SEL, vr->sw.es);
		asm_vmwrite (VMCS_GUEST_DS_SEL, vr->sw.ds);
		asm_vmwrite (VMCS_GUEST_FS_SEL, vr->sw.fs);
		asm_vmwrite (VMCS_GUEST_GS_SEL, vr->sw.gs);
	} else {
		/* protected mode to real address mode */

		/* set a virtual 8086 mode flag */
		asm_vmread (VMCS_GUEST_RFLAGS, &rflags);
		rflags |= RFLAGS_VM_BIT;
		asm_vmwrite (VMCS_GUEST_RFLAGS, rflags);

		/*
		 * If the guest is in virtual-8086 mode, base-address
		 * fields must be the selector field shifted left 4
		 * bits.
		 */
		/* FIXME: segment base must be a multiple of 16.
		   if not, it will be panic. */
		asm_vmread (VMCS_GUEST_CS_BASE, &tmp);
		asm_vmwrite (VMCS_GUEST_CS_SEL, tmp >> 4);
		if ((tmp >> 4) != vr->sw.cs)
		       vr->sw.enable |= SW_SREG_CS_BIT;

		asm_vmread (VMCS_GUEST_ES_BASE, &tmp);
		asm_vmwrite (VMCS_GUEST_ES_SEL, tmp >> 4);
		if ((tmp >> 4) != vr->sw.es)
			vr->sw.enable |= SW_SREG_ES_BIT;

		asm_vmread (VMCS_GUEST_SS_BASE, &tmp);
		asm_vmwrite (VMCS_GUEST_SS_SEL, tmp >> 4);
		if ((tmp >> 4) != vr->sw.ss)
			vr->sw.enable |= SW_SREG_SS_BIT;

		asm_vmread (VMCS_GUEST_DS_BASE, &tmp);
		asm_vmwrite (VMCS_GUEST_DS_SEL, tmp >> 4);
		if ((tmp >> 4) != vr->sw.ds)
			vr->sw.enable |= SW_SREG_DS_BIT;

		asm_vmread (VMCS_GUEST_FS_BASE, &tmp);
		asm_vmwrite (VMCS_GUEST_FS_SEL, tmp >> 4);
		if ((tmp >> 4) != vr->sw.fs)
			vr->sw.enable |= SW_SREG_FS_BIT;

		asm_vmread (VMCS_GUEST_GS_BASE, &tmp);
		asm_vmwrite (VMCS_GUEST_GS_SEL, tmp >> 4);
		if ((tmp >> 4) != vr->sw.gs)
			vr->sw.enable |= SW_SREG_GS_BIT;

		/*
		 * If the guest will be virtual-8086, limit fields
		 * must be 0000FFFFH.
		 */
		/* set segment access rights to proper value */
		asm_vmwrite (VMCS_GUEST_ES_ACCESS_RIGHTS, 0xF3);
		asm_vmwrite (VMCS_GUEST_CS_ACCESS_RIGHTS, 0xF3);
		asm_vmwrite (VMCS_GUEST_SS_ACCESS_RIGHTS, 0xF3);
		asm_vmwrite (VMCS_GUEST_DS_ACCESS_RIGHTS, 0xF3);
		asm_vmwrite (VMCS_GUEST_FS_ACCESS_RIGHTS, 0xF3);
		asm_vmwrite (VMCS_GUEST_GS_ACCESS_RIGHTS, 0xF3);
		/*
		 * If the guest will be virtual-8086, access-rights
		 * fields must be 000000F3H
		 */
		asm_vmwrite (VMCS_GUEST_ES_LIMIT, 0xFFFF);
		asm_vmwrite (VMCS_GUEST_CS_LIMIT, 0xFFFF);
		asm_vmwrite (VMCS_GUEST_SS_LIMIT, 0xFFFF);
		asm_vmwrite (VMCS_GUEST_DS_LIMIT, 0xFFFF);
		asm_vmwrite (VMCS_GUEST_FS_LIMIT, 0xFFFF);
		asm_vmwrite (VMCS_GUEST_GS_LIMIT, 0xFFFF);
	}

}

static void
pe_change (bool pe)
{
	ulong idtr_base, idtr_limit;

	/* IDTR is different between real mode emulation and protected mode */
	vt_read_idtr (&idtr_base, &idtr_limit);
	vt_write_idtr (REALMODE_IDTR_BASE, REALMODE_IDTR_LIMIT);
	current->u.vt.vr.pe = pe;
	vt_write_idtr (idtr_base, idtr_limit);

	/* switching TR */
	pe_change_switch_tr (pe);

	/* enable emulation */
	pe_change_enable_sw (pe);
}

void
vt_write_control_reg (enum control_reg reg, ulong val)
{
	ulong cr0_0, cr0_1;
	ulong cr4_0, cr4_1;
	ulong host_val;
	ulong cr4;

	switch (reg) {
	case CONTROL_REG_CR0:
		asm_vmwrite (VMCS_CR0_READ_SHADOW, val);
		if (!(val & CR0_PE_BIT))
			val &= ~CR0_PG_BIT;
		if (!!(val & CR0_PE_BIT) != !!current->u.vt.vr.pe) {
			pe_change (!!(val & CR0_PE_BIT));
		}
		if (!!(val & CR0_PG_BIT) != !!current->u.vt.vr.pg) {
			current->u.vt.vr.pg = !!(val & CR0_PG_BIT);
			vt_ept_pg_change(current->u.vt.vr.pg);

			/*
			 * Update actual guest cr4.
			 */
			vt_read_control_reg(CONTROL_REG_CR4, &cr4);
			vt_write_control_reg(CONTROL_REG_CR4, cr4);
		}
		vt_msr_update_lma ();
		if (!(current->u.vt.ept_enabled && current->u.vt.vr.pg)) {
			val |= CR0_WP_BIT;
		}
		asm_rdmsr (MSR_IA32_VMX_CR0_FIXED0, &cr0_0);
		asm_rdmsr (MSR_IA32_VMX_CR0_FIXED1, &cr0_1);
		val &= cr0_1;
		val |= cr0_0;
		asm_vmwrite (VMCS_GUEST_CR0, val);
		cpu_mmu_spt_updatecr3();
		break;
	case CONTROL_REG_CR2:
		current->u.vt.vr.cr2 = val;
		break;
	case CONTROL_REG_CR3:
		current->u.vt.vr.cr3 = val;
		if (current->u.vt.ept_enabled && current->u.vt.vr.pg) {
			asm_vmwrite(VMCS_GUEST_CR3, val);
		}
		cpu_mmu_spt_updatecr3();
		break;
	case CONTROL_REG_CR4:
		/*
		 *Update host cr4.
		 */
		asm_rdcr4(&host_val);
		host_val = (host_val & ~CR4_PASSTHROUGH)
			| (val & CR4_PASSTHROUGH);
		asm_wrcr4(host_val);
		asm_vmwrite(VMCS_HOST_CR4, host_val);

		/*
		 * Update guest cr4 shadow.
		 */
		asm_vmwrite (VMCS_CR4_READ_SHADOW, val);

		/*
		 * Update acutual guest cr4.
		 */
		asm_rdmsr (MSR_IA32_VMX_CR4_FIXED0, &cr4_0);
		asm_rdmsr (MSR_IA32_VMX_CR4_FIXED1, &cr4_1);
		val &= cr4_1;
		val |= cr4_0;
		if (!(current->u.vt.ept_enabled && current->u.vt.vr.pg)) {
			/*
			 * Shadow paging requires PAE for both 32 bit
			 * and 64 bit guest.
			 */
			val |= CR4_PAE_BIT;
		}
		if (!current->u.vt.vr.pg) {
			/*
			 * For non-paged protect mode, disable
			 * supervisor-mode execution prevention,
			 * because PTE_US_BIT in shadow pte is set,
			 * and guest software runs in supervisor-mode.
			 */
			val &= ~CR4_SMEP_BIT;
		}
		asm_vmwrite(VMCS_GUEST_CR4, val);

		cpu_mmu_spt_updatecr3();
		break;
	default:
		panic ("Unknown control register.");
	}
}

void
vt_read_sreg_sel (enum sreg s, u16 *val)
{
	ulong tmp;

	switch (s) {
	case SREG_ES:
		if (!(current->u.vt.vr.sw.enable & SW_SREG_ES_BIT))
			asm_vmread (VMCS_GUEST_ES_SEL, &tmp);
		else
			tmp = current->u.vt.vr.sw.es;
		*val = tmp;
		break;
	case SREG_CS:
		if (!(current->u.vt.vr.sw.enable & SW_SREG_CS_BIT))
			asm_vmread (VMCS_GUEST_CS_SEL, &tmp);
		else
			tmp = current->u.vt.vr.sw.cs;
		*val = tmp;
		break;
	case SREG_SS:
		if (!(current->u.vt.vr.sw.enable & SW_SREG_SS_BIT))
			asm_vmread (VMCS_GUEST_SS_SEL, &tmp);
		else
			tmp = current->u.vt.vr.sw.ss;
		*val = tmp;
		break;
	case SREG_DS:
		if (!(current->u.vt.vr.sw.enable & SW_SREG_DS_BIT))
			asm_vmread (VMCS_GUEST_DS_SEL, &tmp);
		else
			tmp = current->u.vt.vr.sw.ds;
		*val = tmp;
		break;
	case SREG_FS:
		if (!(current->u.vt.vr.sw.enable & SW_SREG_FS_BIT))
			asm_vmread (VMCS_GUEST_FS_SEL, &tmp);
		else
			tmp = current->u.vt.vr.sw.fs;
		*val = tmp;
		break;
	case SREG_GS:
		if (!(current->u.vt.vr.sw.enable & SW_SREG_GS_BIT))
			asm_vmread (VMCS_GUEST_GS_SEL, &tmp);
		else
			tmp = current->u.vt.vr.sw.gs;
		*val = tmp;
		break;
	default:
		panic ("Unknown sreg.");
	}
}

void
vt_read_sreg_acr (enum sreg s, ulong *val)
{
	switch (s) {
	case SREG_ES:
		asm_vmread (VMCS_GUEST_ES_ACCESS_RIGHTS, val);
		break;
	case SREG_CS:
		asm_vmread (VMCS_GUEST_CS_ACCESS_RIGHTS, val);
		break;
	case SREG_SS:
		asm_vmread (VMCS_GUEST_SS_ACCESS_RIGHTS, val);
		break;
	case SREG_DS:
		asm_vmread (VMCS_GUEST_DS_ACCESS_RIGHTS, val);
		break;
	case SREG_FS:
		asm_vmread (VMCS_GUEST_FS_ACCESS_RIGHTS, val);
		break;
	case SREG_GS:
		asm_vmread (VMCS_GUEST_GS_ACCESS_RIGHTS, val);
		break;
	default:
		panic ("Unknown sreg.");
	}
}

void
vt_read_sreg_base (enum sreg s, ulong *val)
{
	ulong tmp;

	switch (s) {
	case SREG_ES:
	case SREG_CS:
	case SREG_SS:
	case SREG_DS:
		if (current->u.vt.lma) {
			vt_read_sreg_acr (SREG_CS, &tmp);
			if (tmp & ACCESS_RIGHTS_L_BIT) {
				*val = 0;
				return;
			}
		}
		break;
	default:
		break;
	}
	switch (s) {
	case SREG_ES:
		asm_vmread (VMCS_GUEST_ES_BASE, val);
		break;
	case SREG_CS:
		asm_vmread (VMCS_GUEST_CS_BASE, val);
		break;
	case SREG_SS:
		asm_vmread (VMCS_GUEST_SS_BASE, val);
		break;
	case SREG_DS:
		asm_vmread (VMCS_GUEST_DS_BASE, val);
		break;
	case SREG_FS:
		asm_vmread (VMCS_GUEST_FS_BASE, val);
		break;
	case SREG_GS:
		asm_vmread (VMCS_GUEST_GS_BASE, val);
		break;
	default:
		panic ("Unknown sreg.");
	}
}

void
vt_read_sreg_limit (enum sreg s, ulong *val)
{
	ulong tmp;

	if (current->u.vt.lma) {
		vt_read_sreg_acr (SREG_CS, &tmp);
		if (tmp & ACCESS_RIGHTS_L_BIT) {
			*val = ~0UL;
			return;
		}
	}
	switch (s) {
	case SREG_ES:
		asm_vmread (VMCS_GUEST_ES_LIMIT, val);
		break;
	case SREG_CS:
		asm_vmread (VMCS_GUEST_CS_LIMIT, val);
		break;
	case SREG_SS:
		asm_vmread (VMCS_GUEST_SS_LIMIT, val);
		break;
	case SREG_DS:
		asm_vmread (VMCS_GUEST_DS_LIMIT, val);
		break;
	case SREG_FS:
		asm_vmread (VMCS_GUEST_FS_LIMIT, val);
		break;
	case SREG_GS:
		asm_vmread (VMCS_GUEST_GS_LIMIT, val);
		break;
	default:
		panic ("Unknown sreg.");
	}
}

void
vt_spt_setcr3 (ulong cr3)
{
	if (current->u.vt.ept_enabled && current->u.vt.vr.pg) {
		return;
	}
	asm_vmwrite (VMCS_GUEST_CR3, cr3);
}

void
vt_read_ip (ulong *val)
{
	asm_vmread (VMCS_GUEST_RIP, val);
}

void
vt_write_ip (ulong val)
{
	asm_vmwrite (VMCS_GUEST_RIP, val);
	current->updateip = true;
}

void
vt_read_flags (ulong *val)
{
	asm_vmread (VMCS_GUEST_RFLAGS, val);
	if (!current->u.vt.vr.pe)
		*val &= ~RFLAGS_VM_BIT;
}

void
vt_write_flags (ulong val)
{
	if (!current->u.vt.vr.pe)
		val |= RFLAGS_VM_BIT;
	asm_vmwrite (VMCS_GUEST_RFLAGS, val);
}

void
vt_read_gdtr (ulong *base, ulong *limit)
{
	asm_vmread (VMCS_GUEST_GDTR_BASE, base);
	asm_vmread (VMCS_GUEST_GDTR_LIMIT, limit);
}

void
vt_write_gdtr (ulong base, ulong limit)
{
	asm_vmwrite (VMCS_GUEST_GDTR_BASE, base);
	asm_vmwrite (VMCS_GUEST_GDTR_LIMIT, limit);
}

void
vt_read_idtr (ulong *base, ulong *limit)
{
	if (current->u.vt.vr.pe) {
		asm_vmread (VMCS_GUEST_IDTR_BASE, base);
		asm_vmread (VMCS_GUEST_IDTR_LIMIT, limit);
	} else {
		*base = current->u.vt.realmode.idtr.base;
		*limit = current->u.vt.realmode.idtr.limit;
	}
}

void
vt_write_idtr (ulong base, ulong limit)
{
	if (current->u.vt.vr.pe) {
		asm_vmwrite (VMCS_GUEST_IDTR_BASE, base);
		asm_vmwrite (VMCS_GUEST_IDTR_LIMIT, limit);
	} else {
		current->u.vt.realmode.idtr.base = base;
		current->u.vt.realmode.idtr.limit = limit;
	}
}

void
vt_get_current_regs_in_vmcs (struct regs_in_vmcs *p)
{
	asm_rdes (&p->es.sel);
	asm_rdcs (&p->cs.sel);
	asm_rdss (&p->ss.sel);
	asm_rdds (&p->ds.sel);
	asm_rdfs (&p->fs.sel);
	asm_rdgs (&p->gs.sel);
	asm_rdldtr (&p->ldtr.sel);
	asm_rdtr (&p->tr.sel);
	asm_rdcr0 (&p->cr0);
	asm_rdcr3 (&p->cr3);
	asm_rdcr4 (&p->cr4);
	asm_lsl (p->es.sel, &p->es.limit);
	asm_lsl (p->cs.sel, &p->cs.limit);
	asm_lsl (p->ss.sel, &p->ss.limit);
	asm_lsl (p->ds.sel, &p->ds.limit);
	asm_lsl (p->fs.sel, &p->fs.limit);
	asm_lsl (p->gs.sel, &p->gs.limit);
	asm_lsl (p->ldtr.sel, &p->ldtr.limit);
	asm_lsl (p->tr.sel, &p->tr.limit);
	p->es.acr = get_seg_access_rights (p->es.sel);
	p->cs.acr = get_seg_access_rights (p->cs.sel);
	p->ss.acr = get_seg_access_rights (p->ss.sel);
	p->ds.acr = get_seg_access_rights (p->ds.sel);
	p->fs.acr = get_seg_access_rights (p->fs.sel);
	p->gs.acr = get_seg_access_rights (p->gs.sel);
	p->ldtr.acr = get_seg_access_rights (p->ldtr.sel);
	p->tr.acr = get_seg_access_rights (p->tr.sel);
	asm_rdgdtr (&p->gdtr.base, &p->gdtr.limit);
	asm_rdidtr (&p->idtr.base, &p->idtr.limit);
	get_seg_base (p->gdtr.base, p->ldtr.sel, p->es.sel, &p->es.base);
	get_seg_base (p->gdtr.base, p->ldtr.sel, p->cs.sel, &p->cs.base);
	get_seg_base (p->gdtr.base, p->ldtr.sel, p->ss.sel, &p->ss.base);
	get_seg_base (p->gdtr.base, p->ldtr.sel, p->ds.sel, &p->ds.base);
	get_seg_base (p->gdtr.base, p->ldtr.sel, p->fs.sel, &p->fs.base);
	get_seg_base (p->gdtr.base, p->ldtr.sel, p->gs.sel, &p->gs.base);
	get_seg_base (p->gdtr.base, p->ldtr.sel, p->ldtr.sel, &p->ldtr.base);
	get_seg_base (p->gdtr.base, p->ldtr.sel, p->tr.sel, &p->tr.base);
	asm_rddr7 (&p->dr7);
	asm_rdrflags (&p->rflags);
}

void
vt_write_realmode_seg (enum sreg s, u16 val)
{
	switch (s) {
	case SREG_ES:
		asm_vmwrite (VMCS_GUEST_ES_SEL, val);
		asm_vmwrite (VMCS_GUEST_ES_BASE, val << 4);
		asm_vmwrite (VMCS_GUEST_ES_ACCESS_RIGHTS, 0xF3);
		asm_vmwrite (VMCS_GUEST_ES_LIMIT, 0xFFFF);
		break;
	case SREG_CS:
		asm_vmwrite (VMCS_GUEST_CS_SEL, val);
		asm_vmwrite (VMCS_GUEST_CS_BASE, val << 4);
		/*
		 * In virtual 8086 mode, access-right field for CS
		 *  segment must be 000000F3H
		 */
		asm_vmwrite (VMCS_GUEST_CS_ACCESS_RIGHTS, 0xF3);
		asm_vmwrite (VMCS_GUEST_CS_LIMIT, 0xFFFF);
		break;
	case SREG_SS:
		asm_vmwrite (VMCS_GUEST_SS_SEL, val);
		asm_vmwrite (VMCS_GUEST_SS_BASE, val << 4);
		asm_vmwrite (VMCS_GUEST_SS_ACCESS_RIGHTS, 0xF3);
		asm_vmwrite (VMCS_GUEST_SS_LIMIT, 0xFFFF);
		break;
	case SREG_DS:
		asm_vmwrite (VMCS_GUEST_DS_SEL, val);
		asm_vmwrite (VMCS_GUEST_DS_BASE, val << 4);
		asm_vmwrite (VMCS_GUEST_DS_ACCESS_RIGHTS, 0xF3);
		asm_vmwrite (VMCS_GUEST_DS_LIMIT, 0xFFFF);
		break;
	case SREG_FS:
		asm_vmwrite (VMCS_GUEST_FS_SEL, val);
		asm_vmwrite (VMCS_GUEST_FS_BASE, val << 4);
		asm_vmwrite (VMCS_GUEST_FS_ACCESS_RIGHTS, 0xF3);
		asm_vmwrite (VMCS_GUEST_FS_LIMIT, 0xFFFF);
		break;
	case SREG_GS:
		asm_vmwrite (VMCS_GUEST_GS_SEL, val);
		asm_vmwrite (VMCS_GUEST_GS_BASE, val << 4);
		asm_vmwrite (VMCS_GUEST_GS_ACCESS_RIGHTS, 0xF3);
		asm_vmwrite (VMCS_GUEST_GS_LIMIT, 0xFFFF);
		break;
	default:
		panic ("Unknown sreg.");
	}
}

vmmerr_t
vt_writing_sreg (enum sreg s)
{
	switch (s) {
	case SREG_ES:
		current->u.vt.vr.sw.enable &= ~SW_SREG_ES_BIT;
		break;
	case SREG_CS:
		current->u.vt.vr.sw.enable &= ~SW_SREG_CS_BIT;
		break;
	case SREG_SS:
		current->u.vt.vr.sw.enable &= ~SW_SREG_SS_BIT;
		break;
	case SREG_DS:
		current->u.vt.vr.sw.enable &= ~SW_SREG_DS_BIT;
		break;
	case SREG_FS:
		current->u.vt.vr.sw.enable &= ~SW_SREG_FS_BIT;
		break;
	case SREG_GS:
		current->u.vt.vr.sw.enable &= ~SW_SREG_GS_BIT;
		break;
	case SREG_DEFAULT:
		return VMMERR_AVOID_COMPILER_WARNING;
	}
	return VMMERR_SW;
}
