/*
 * 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 <common/list.h>
#include <core/assert.h>
#include <core/initfunc.h>
#include <core/time.h>
#include <core/timer.h>
#include <core/panic.h>
#include <core/spinlock.h>
#include "comphappy.h"
#include "constants.h"
#include "mm.h"
#include "pcpu.h"
#include "thread.h"
#include <core/types.h>

/* #include <core/printf.h> */

#define MAX_TIMER_PCPU 8


void *
timer_new (void (*callback) (void *handle, void *data), void *data)
{
	struct timer_data *p;

	spinlock_lock (&currentcpu->timer_lock);
	p = LIST1_POP (currentcpu->timer_free_list);
	if (p == NULL) {
		spinlock_unlock (&currentcpu->timer_lock);
		return NULL;
	}
	p->enable = false;
	p->callback = callback;
	p->data = data;
	LIST1_ADD (currentcpu->timer_off_list, p);
	spinlock_unlock (&currentcpu->timer_lock);
	return p;
}

void
timer_set (void *handle, u64 interval_usec)
{
	struct timer_data *p, *d;
	u64 time;
	u64 expire;

	spinlock_lock (&currentcpu->timer_lock);
	time = get_cpu_time ();
	p = handle;
	if (p->enable) {
		LIST1_DEL (currentcpu->timer_on_list, p);
	} else {
		LIST1_DEL (currentcpu->timer_off_list, p);
		p->enable = true;
	}
	p->settime = time;
	p->interval = interval_usec;
	expire = p->settime + p->interval;

	LIST1_FOREACH (currentcpu->timer_on_list, d) {
		if ((d->settime + d->interval) > expire) {
			break;
		}
	}
	if (d)
		LIST1_INSERT (currentcpu->timer_on_list, d, p);
	else
		LIST1_ADD(currentcpu->timer_on_list, p);

	spinlock_unlock (&currentcpu->timer_lock);
}

void
timer_free (void *handle)
{
	struct timer_data *p;

	spinlock_lock (&currentcpu->timer_lock);
	p = handle;
	if (p->enable)
		LIST1_DEL (currentcpu->timer_on_list, p);
	else
		LIST1_DEL (currentcpu->timer_off_list, p);
	p->enable = false;
	LIST1_ADD (currentcpu->timer_free_list, p);
	spinlock_unlock (&currentcpu->timer_lock);
}

static void
timer_thread (void *thread_data)
{
	struct timer_data *p;
	u64 time;
	bool call;
	void (*callback) (void *handle, void *data);
	void *data;

	VAR_IS_INITIALIZED (callback);
	VAR_IS_INITIALIZED (data);
	for (;;) {
		call = false;
		spinlock_lock (&currentcpu->timer_lock);
		time = get_cpu_time ();
		p = LIST1_POP (currentcpu->timer_on_list);
		if (p) {
			ASSERT(p->enable);
			if ((time - p->settime) >= p->interval) {
				p->enable = false;
				call = true;
				callback = p->callback;
				data = p->data;
				LIST1_ADD (currentcpu->timer_off_list, p);
			} else {
				LIST1_PUSH (currentcpu->timer_on_list, p);
			}
		}
		spinlock_unlock (&currentcpu->timer_lock);
		if (call)
			callback (p, data);
		else
			schedule ();
	}
}

static void
timer_init_pcpu (void)
{
	struct timer_data *p;
	int i;

	p = alloc (MAX_TIMER_PCPU * sizeof (struct timer_data));
	if (p == NULL) {
		panic("Failed to allocate memory for struct timer_data.");
	}
	for (i = 0; i < MAX_TIMER_PCPU; i++)
		LIST1_PUSH (currentcpu->timer_free_list, &p[i]);
	spinlock_init (&currentcpu->timer_lock);
	thread_new_bind(timer_thread, NULL, VMM_STACKSIZE, get_cpu_id());
}

INITFUNC ("pcpu4", timer_init_pcpu);
