/*
 * 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 <core/initfunc.h>
#include "apic_pass.h"
#include "asm.h"
#include "current.h"
#include "int.h"
#include "mm.h"
#include "panic.h"

/* #define MSR_DEBUG */
#ifdef MSR_DEBUG
#include <core/printf.h>
#define MSR_DBG(...)						\
	do {							\
		printf("MSR: " __VA_ARGS__);			\
	} while (0)
#else
#define MSR_DBG(...)
#endif

#define PAT_ENCODING_UC		0x00
#define PAT_ENCODING_WC		0x01
#define PAT_ENCODING_WT		0x04
#define PAT_ENCODING_WP		0x05
#define PAT_ENCODING_WB		0x06
#define PAT_ENCODING_WUC	0x07
#define MSR_IA32_PAT_GUEST_INIT_VAL 0x0007040600070406LL
					/* UC WUC WT WB UC WUC WT WB */

static u64
read_pat(void)
{
	return current->msr_data.pat_reg;
}

static bool
write_pat(u64 msrdata)
{
	int i;
	u8 pat_encoding;
	u32 flag;
#ifdef MSR_DEBUG
	ulong rip;
#endif
	for (i = 0; i < MSR_DATA_PAT_COUNT; i++) {
		pat_encoding = MSR_PAT_FIELD(msrdata, i);
		switch (pat_encoding) {
		case 0:
		case 1:
		case 4:
		case 5:
		case 6:
		case 7:
			break;
		default:
#ifdef MSR_DEBUG
			current->vmctl.read_ip(&rip);
			MSR_DBG("Unsupported pat encoding "
				"0x%llx %d 0x%x ip 0x%lx\n",
				msrdata, i, pat_encoding, rip);
#endif
			return true; /* genelate page fault */
		}
	}

	current->msr_data.pat_reg = msrdata;
	for (i = 0; i < MSR_DATA_PAT_COUNT; i++) {
		pat_encoding = MSR_PAT_FIELD(msrdata, i);
		switch (pat_encoding) {
		case 0:
			flag = MAPMEM_UC;
			break;
		case 1:
			flag = MAPMEM_WC;
			break;
		case 4:
			flag = MAPMEM_WT;
			break;
		case 5:
			flag = MAPMEM_WP;
			break;
		case 6:
			flag = MAPMEM_WB;
			break;
		case 7:
			flag = MAPMEM_WUC;
			break;
		default:
			panic ("Unsupported pat encoding 0x%x", pat_encoding);
		}
		current->msr_data.pat_to_cache_flag[i] = flag;
	}
	MSR_DBG("Update PAT 0x%llx of cpu%d\n", msrdata, get_cpu_id());
	return false; /* success */
}

bool
msr_read(u32 msrindex, u64 *msrdata)
{
	switch (msrindex) {
	case MSR_IA32_TIME_STAMP_COUNTER:
		asm_rdmsr64(MSR_IA32_TIME_STAMP_COUNTER, msrdata);
		*msrdata += current->tsc_offset;
		break;
	case MSR_IA32_PAT:
		*msrdata = read_pat();
		break;
	default:
		asm_rdmsr64(msrindex, msrdata);
	}
	return false;
}

u32
msr_pte_to_cache_flag(u64 pte)
{
	int pat_index;

	pat_index =
		((pte & PTE_PWT_BIT) ? 1 : 0) |
		((pte & PTE_PCD_BIT) ? 2 : 0) |
		((pte & PTE_PAT_BIT) ? 4 : 0);
	return current->msr_data.pat_to_cache_flag[pat_index];
}

bool
msr_write(u32 msrindex, u64 msrdata)
{
	u64 tmp;

	/* FIXME: Exception handling */
	switch (msrindex) {
	case MSR_IA32_BIOS_UPDT_TRIG:
		MSR_DBG("msr_pass: microcode updates cannot be loaded.\n");
		break;
	case MSR_IA32_TIME_STAMP_COUNTER:
		asm_rdmsr64(MSR_IA32_TIME_STAMP_COUNTER, &tmp);
		current->tsc_offset = msrdata - tmp;
		current->vmctl.tsc_offset_changed ();
		break;
	case MSR_IA32_APIC_BASE:
		if (msrdata & MSR_IA32_APIC_BASE_APIC_GLOBAL_ENABLE_BIT) {
			tmp = msrdata & MSR_IA32_APIC_BASE_APIC_BASE_MASK;
			apic_base_changed(tmp);
			if (phys_in_vmm(tmp))
				panic ("relocating APIC Base to VMM address!");
		}
		goto pass;
	case MSR_IA32_PAT:
		return write_pat(msrdata);
	default:
	pass:
		asm_wrmsr64(msrindex, msrdata);
	}
	return false;
}

static void
msr_init(void)
{
	current->vmctl.write_msr(MSR_IA32_PAT, MSR_IA32_PAT_GUEST_INIT_VAL);
}

INITFUNC("passcpu0", msr_init);
