/* 
 * Copyright 2010 Stefan Lankes, Chair for Operating Systems,
 *                               RWTH Aachen University
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * This file is part of MetalSVM. 
 */

#include <metalsvm/stddef.h>
#include <metalsvm/stdio.h>
#include <metalsvm/time.h>
#include <metalsvm/processor.h>
#ifdef CONFIG_ROCKCREEK
#include <asm/RCCE_lib.h>
#endif

cpu_info_t cpu_info = { 0, 0 };
static uint32_t cpu_freq = 0;

int cpu_detection(void)
{
	uint32_t a, b, cr4;

	cpuid(1, &a, &b, &cpu_info.feature2, &cpu_info.feature1);

	cr4 = read_cr4();
	if (has_fxsr())
		cr4 |= 0x200; // set the OSFXSR bit
	if (has_xmm())
                cr4 |= 0x400; // set the OSXMMEXCPT bit
	write_cr4(cr4);

	if (has_avx())
		kprintf("The CPU owns the Advanced Vector Extensions (AVX). However, MetalSVM doesn't support AVX!\n");

	if (has_fpu())
		asm volatile ("fninit");

	return 0;
}

uint32_t detect_cpu_frequency(void)
{
#ifdef CONFIG_ROCKCREEK
	if (cpu_freq > 0)
		return cpu_freq;

	cpu_freq = RC_REFCLOCKMHZ;

	return cpu_freq;
#else
	uint64_t start, end, diff;
	uint64_t ticks, old;

	if (BUILTIN_EXPECT(cpu_freq > 0, 0))
		return cpu_freq;

	old = get_clock_tick();

	/* wait for the next time slice */
	while((ticks = get_clock_tick()) - old == 0)
		HALT;

	mb();
	start = rdtsc();
	/* wait a second to determine the frequency */
	while(get_clock_tick() - ticks < TIMER_FREQ)
		HALT;
	mb();
	end = rdtsc();

	diff = end > start ? end - start : start - end;
	cpu_freq = (uint32_t) (diff / (uint64_t) 1000000);

	return cpu_freq;
#endif
}

uint32_t get_cpu_frequency(void)
{	
	if (cpu_freq > 0)
		return cpu_freq;

	return detect_cpu_frequency();
}

void udelay(uint32_t usecs)
{
	uint64_t diff, end, start = rdtsc();
	uint64_t deadline = get_cpu_frequency() * usecs;

	do {
		mb();
		end = rdtsc();
		diff = end > start ? end - start : start - end;
	} while(diff < deadline);
}