/*
 * 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
 */

#include <core/arith.h>
#include <core/assert.h>
#include <core/cpu.h>
#include <core/initfunc.h>
#include <core/msg.h>
#include <core/spinlock.h>
#include <core/printf.h>
#include <core/time.h>
#include "asm.h"
#include "constants.h"
#include "convert.h"
#include "panic.h"
#include "pcpu.h"
#include <core/sleep.h>
#include "smp.h"
#include "vmmcall_status.h"

/* #define TIME_DEBUG */
#ifdef TIME_DEBUG
#define TIME_DBG(...)						\
	do {							\
		printf(__VA_ARGS__);				\
	} while (0)
#else
#define TIME_DBG(...)
#endif

static bool initialized = false;

static u64
tsc_to_time (u64 tsc, u64 hz, u64 *surplus)
{
	u64 tmp[2];
	u64 time;

	while (hz > 0xFFFFFFFFULL) {
		tsc >>= 1;
		hz >>= 1;
	}
	mpumul_64_64 (tsc, 1000000ULL, tmp); /* tmp = tsc * 1000000 */
	mpudiv_128_32 (tmp, (u32)hz, tmp); /* tmp = tmp / hz */
	time = tmp[0];

	mpumul_64_64 (time, hz, tmp); /* tmp = time * hz */
	mpudiv_128_32 (tmp, 1000000ULL, tmp); /* tmp = tmp / 1000000 */
	*surplus = tsc - tmp[0];
	return time;
}

/* get per-cpu time by usec */
u64
get_cpu_time (void)
{
	u32 tsc_l, tsc_h;
	u64 tsc, surplus;

	ASSERT (initialized);
	asm_rdtsc (&tsc_l, &tsc_h);
	conv32to64 (tsc_l, tsc_h, &tsc);
	currentcpu->last_time += tsc_to_time (tsc - currentcpu->last_tsc,
					      currentcpu->hz, &surplus);
	currentcpu->last_tsc = tsc - surplus;
	return currentcpu->last_time;
}

bool
time_initialized (void)
{
	return initialized;
}

static void
time_init_pcpu (void)
{
	u32 a, b, c, d;
	u32 tsc1_l, tsc1_h;
	u32 tsc2_l, tsc2_h;
	u64 tsc1, tsc2, count;

	asm_cpuid (1, 0, &a, &b, &c, &d);
	if (!(d & CPUID_1_EDX_TSC_BIT))
		panic ("CPU%d does not support TSC",
		       get_cpu_id());
	sync_all_processors ();
	asm_rdtsc (&tsc1_l, &tsc1_h);
	if (cpu_is_bsp())
		usleep (1000000);
	sync_all_processors ();
	asm_rdtsc (&tsc2_l, &tsc2_h);
	conv32to64 (tsc1_l, tsc1_h, &tsc1);
	conv32to64 (tsc2_l, tsc2_h, &tsc2);
	count = tsc2 - tsc1;
	TIME_DBG("CPU%d %llu Hz\n", get_cpu_id(), count);
	currentcpu->hz = count;
	initialized = true;
}

static char *
time_status (void)
{
	static char buf[1024];

	snprintf (buf, 1024,
		  "time:\n"
		  " cpu: %d\n"
		  " hz: %llu\n"
		  , get_cpu_id()
		  , currentcpu->hz);
	return buf;
}

static void
time_init_global_status (void)
{
	register_status_callback (time_status);
}

INITFUNC ("global4", time_init_global_status);
INITFUNC ("pcpu0", time_init_pcpu);

