/*
 * Copyright (c) 2012 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 the 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 <core/cpu.h>
#include <core/initfunc.h>
#include <core/printf.h>
#include <core/types.h>
#include "asm.h"
#include "constants.h"
#include "mtrr.h"

static u8
mtrr_get_fixed_mem_type(phys_t addr)
{
	u64	msr_val;
	ulong	msr_num;
	phys_t	width;
	phys_t	index;
	u8	type;

	if (addr >= 0xc0000) {
		width = 0x1000;
		msr_num = MSR_IA32_MTRR_FIX4K_C000 + (addr - 0xc0000)
			/ (0x1000 * MSR_IA32_MTRR_FIX_TYPE_NUM);
	} else if (addr >= 0x80000) {
		width = 0x4000;
		msr_num = MSR_IA32_MTRR_FIX16K_800 + (addr - 0x80000)
			/ (0x4000 * MSR_IA32_MTRR_FIX_TYPE_NUM);
	} else {
		width = 0x10000;
		msr_num = MSR_IA32_MTRR_FIX64K_000;
	}

	index = ((addr / width) % MSR_IA32_MTRR_FIX_TYPE_NUM);
	asm_rdmsr64(msr_num, &msr_val);
	type = (msr_val >> index * MSR_IA32_MTRR_FIX_TYPE_SHIFT)
		& MSR_IA32_MTRR_FIX_TYPE_MASK;

	return type;
}

u8
mtrr_get_mem_type(phys_t addr)
{
	u64	msr_val;
	u64	mtrrcap;
	u8	type;
	u8	vcnt;
	phys_t	base;
	phys_t	mask;
	int	i;

	asm_rdmsr64(MSR_IA32_MTRRCAP, &mtrrcap);

	asm_rdmsr64(MSR_IA32_MTRR_DEF_TYPE, &msr_val);
	if ((msr_val & MSR_IA32_MTRR_DEF_TYPE_E_BIT) == 0) {
		return MTRR_ENCODING_UC;
	}
	type = msr_val & MSR_IA32_MTRR_DEF_TYPE_TYPE_MASK;

	if ((mtrrcap & MSR_IA32_MTRRCAP_FIX_BIT) &&
	    (msr_val & MSR_IA32_MTRR_DEF_TYPE_FE_BIT) &&
	    addr < 0x100000) {
		return mtrr_get_fixed_mem_type(addr);
	}

	vcnt = mtrrcap & MSR_IA32_MTRRCAP_VCNT_MASK;

	for (i = vcnt - 1; i >= 0; i--) {
		asm_rdmsr64(MSR_IA32_MTRR_PHYSMASK0 + i * 2, &msr_val);
		if ((msr_val & MSR_IA32_MTRR_PHYSMASK_V_BIT) == 0) {
			continue;
		}
		mask = msr_val & MSR_IA32_MTRR_PHYSMASK_MASK_MASK;

		asm_rdmsr64(MSR_IA32_MTRR_PHYSBASE0 + i * 2, &msr_val);
		base = msr_val & MSR_IA32_MTRR_PHYSBASE_BASE_MASK;
		if ((addr & mask) == (base & mask)) {
			type = msr_val & MSR_IA32_MTRR_PHYSBASE_TYPE_MASK;
			break;
		}

	}

	return type;
}

#ifdef DUMP_MTRR
static void
mtrr_init(void)
{
	phys_t		start = 0;
	phys_t		cur;
	u8		type, new_type;

	if (get_cpu_id() == 0) {
		type = mtrr_get_mem_type(0);
		for (cur = 0;; cur += PAGESIZE) {
			new_type = mtrr_get_mem_type(cur);
			if (new_type != type) {
				if (cur > 0xffffffff &&
				    new_type != MTRR_ENCODING_WB) {
					break;
				}
				printf("MTRR 0x%llx-0x%llx 0x%x\n",
				       start, cur - 1, type);
				start = cur;
				type = new_type;
			}
		}
		printf("MTRR 0x%llx-0x%llx 0x%x\n",
		       start, cur - 1, type);
	}
}

INITFUNC("pcpu5", mtrr_init);
#endif
