/*
 * Copyright (c) 2007 Jan Hudecek
 * Copyright (c) 2006 Jakub Jermar
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - 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.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 */

/** @addtogroup genericddi
 * @{
 */
/** @file tasklet.c
 *  @brief Tasklet implementation
 */

#include <arch.h>
#include <config.h>
#include <arch/types.h>
#include <arch/asm.h>
#include <proc/tasklet.h>
#include <synch/spinlock.h>
#include <time/delay.h>
#include <proc/thread.h>
#include <panic.h>
#include <print.h>
#include <synch/waitq.h>
#include <cpu.h>
#include <proc/scheduler.h>

/** Timeout length in the tasklet thread */
#define TASKLET_THREAD_SLEEPDELAY 10000
///** Priority of the tasklet thread */
#define TASKLET_THREAD_PRIORITY 1


/** Tasklet state enum */
typedef enum  {
TASKLET_STATE_NOTACTIVE = 0,
TASKLET_STATE_SCHEDULED = 1,
TASKLET_STATE_RUNNING = 2,
TASKLET_STATE_DISABLED = 4
} tasklet_state_enum;

/** Spinlock protecting list of tasklets */
SPINLOCK_INITIALIZE(tasklet_lock);

/** array of tasklet lists for every CPU */
tasklet_descriptor_t** tasklet_list;

/** Initializes tasklets - except for the tasklet thread */
void tasklet_init(void)
{
	int i;
	tasklet_list=malloc(sizeof(tasklet_descriptor_t*)*config.cpu_count,0);
	if (tasklet_list==0)
		panic_printf("tasklet list mem not allocated\n");
	for(i=0;i<config.cpu_count;i++)	{
		tasklet_list[i]=0;
	}
	spinlock_initialize(&tasklet_lock, "tasklet_lock");

}

/** 
* Creates and runs the tasklet thread 
*
*  @param kernel_task Pointer to the kernel task - for create_thread 
*/
void tasklet_run_tasklet_thread(task_t * kernel_task)
{
	//create the tasklet_thread, it's wired to the current cpu, we'll migrate it ourselves
	thread_t* t= thread_create(&tasklet_thread, NULL, kernel_task, THREAD_FLAG_WIRED, "tasklet_thread", false);
	if (t==NULL) {
		panic_printf("tasklet thread not created\n");
	} else {
		spinlock_lock(&t->lock);	
		//we'll default on the first CPU
		t->cpu = &cpus[0];
		t->priority = TASKLET_THREAD_PRIORITY;
		spinlock_unlock(&t->lock);
		thread_ready(t);
	}
}

/** Thread which keeps executing scheduled enabled tasklets 
* @param data not used
*/
void tasklet_thread(void* data)
{
#ifdef CONFIG_SMP
	int current_cpu, new_cpu ;
	cpu_t *cpu;
#endif
	waitq_t  wq;
	waitq_initialize(&wq);
	
	//the infinite loop
	while (true) {
		//execute any scheduled tasklets
		tasklet_do();
#ifdef CONFIG_SMP
		//check whether other CPUs have tasklets to execute
		if (config.cpu_active>1) {
			current_cpu = CPU->id;
			//find the first cpu with nonempty tasklet_list 
			for (new_cpu = (current_cpu + 1) % config.cpu_count; new_cpu!=current_cpu && tasklet_list[new_cpu]==0 ; 
					new_cpu=(new_cpu + 1)% config.cpu_count);

			//if we found a CPU with unsatisfied tasklet schedule to run there. It must be active!
			if (new_cpu!=current_cpu && cpus[new_cpu].active) {
				//we need to migrate this thread to CPU with id new_cpu
				cpu = &cpus[new_cpu];

				spinlock_lock(&THREAD->lock);
				//move tasklet_thread on the new_cpu 
				THREAD->cpu = cpu;
				spinlock_unlock(&THREAD->lock);
			}
		} 
#endif
		waitq_sleep_timeout(&wq, (uint32_t)TASKLET_THREAD_SLEEPDELAY, 0);


	}
}

/** Initializes tasklet structure 
* @param func tasklet callback function to be called 
* @param data pointer to be passed to the tasklet function
*/
tasklet_descriptor_t* tasklet_register(void (*func)(void* data), void* data)
{
	tasklet_descriptor_t* tasklet=malloc(sizeof(tasklet_descriptor_t),0);
	tasklet->data = data;
	tasklet->func = func;
	tasklet->state = TASKLET_STATE_NOTACTIVE;
	tasklet->next = 0;
	return tasklet;
}

/** Schedules the tasklet for execution on current CPU 
* @param t tasklet to be scheduled
*/
void tasklet_schedule(tasklet_descriptor_t* t)
{
	tasklet_schedule_SMP(t, CPU->id);
}

/** Schedules the tasklet for execution on id CPU 
* @param t tasklet to be scheduled
* @param id CPU id on which the tasklet will be scheduled
*/
void tasklet_schedule_SMP(tasklet_descriptor_t* t, uint32_t id)
{
	spinlock_lock(&tasklet_lock);
	//clear notactive, running and scheduled flags
	t->state &= TASKLET_STATE_DISABLED;
	//set the scheduled flag
	t->state |= TASKLET_STATE_SCHEDULED;
	t->next=tasklet_list[id];
	tasklet_list[id]=t;
	spinlock_unlock(&tasklet_lock);
}

/** Tasklet will not be run, even if scheduled 
* @param t tasklet to be disabled
*/
void tasklet_disable(tasklet_descriptor_t* t)
{
	spinlock_lock(&tasklet_lock);
	//set the disabled flag
	t->state |= TASKLET_STATE_DISABLED;
	spinlock_unlock(&tasklet_lock);
}

/** Tasklet will be run if scheduled 
* @param t tasklet to be enabled
*/
void tasklet_enable(tasklet_descriptor_t* t)
{
	spinlock_lock(&tasklet_lock);
	//clear the disabled flag
	t->state &= ~TASKLET_STATE_DISABLED;
	spinlock_unlock(&tasklet_lock);
}




/** Executes scheduled enabled tasklets on current CPU 
* this function could be called from other parts of kernel */
void tasklet_do(void)
{
	spinlock_lock(&tasklet_lock);
	tasklet_descriptor_t* t = tasklet_list[CPU->id];
	if (t) {
		//empty the tasklet_list
		tasklet_list[CPU->id]=0;
		spinlock_unlock(&tasklet_lock);	
		do {
			if (!(t->state & TASKLET_STATE_DISABLED)) {
				if (t->func) {
					t->state = TASKLET_STATE_RUNNING;
					t->func(t->data);
					//clear running flag, set not active 
					t->state &= ~TASKLET_STATE_RUNNING;
					t->state |= TASKLET_STATE_NOTACTIVE;
				} else
					panic_printf("tasklet func NULL\n");
			} else {   
				//return it back to the queue of scheduled tasklets
				spinlock_lock(&tasklet_lock);	
				t->next = tasklet_list[CPU->id];
				tasklet_list[CPU->id] = t;
				spinlock_unlock(&tasklet_lock);	
			}
			t=t->next;
		}
		while (t);
	}
	else
		spinlock_unlock(&tasklet_lock);	
}

/** Frees the tasklet structure when no longer needed. The function doesn't provide 
*   any synchronization, the caller must be sure, that the tasklet is not scheduled.
*
* @param tasklet to be freed
*/
void tasklet_free(tasklet_descriptor_t* t)
{
	spinlock_lock(&tasklet_lock);
	if (t->state == TASKLET_STATE_NOTACTIVE)
		free(t);
	else
		panic_printf("attempting to free an active tasklet");
	spinlock_unlock(&tasklet_lock);	
}


