/*****************************************************************************/
/* Copyright (c) 2008 NXP B.V.                                               */
/*                                                                           */
/* This program is free software; you can redistribute it and/or modify      */
/* it under the terms of the GNU General Public License as published by      */
/* the Free Software Foundation, using version 2 of the License.             */
/*                                                                           */
/* This program is distributed in the hope that it will be useful,           */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the              */
/* GNU General Public License for more details.                              */
/*                                                                           */
/* You should have received a copy of the GNU General Public License         */
/* along with this program; if not, write to the Free Software               */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307       */
/* USA.                                                                      */
/*                                                                           */
/*****************************************************************************/

#include "common.h"
#include <linux/wait.h>		/* wait queue for exit waiters */
#include <asm/delay.h>		/* udelay() */
#include <linux/kthread.h>

/* Internal OSAL task structure */
struct osal_task {
	
	u32 handle;			/* copy of handle */
	struct task_struct *task;	/* corresponding kernel thread */

	void (*routine)(void *arg);	/* task root routine, zero if undefined */
	void *arg;			/* argument for task's root routine */

	int priority;			/* task's OSAL priority */
	
	char name[5];			/* task's name, with terminating zero */
	int resume_counter;		/* suspend-resume counter */
	volatile int started;		/* is task marked to be started? */

	spinlock_t lock;		/* lock guarding structure fields */
};


static struct handle_table task_handle_table;
#define allocate_osal_task_handle(handle) \
	ALLOCATE_HANDLE_MACRO(&task_handle_table, handle, TMOSAL_ERR_OUT_OF_MEMORY)
#define allocate_osal_task(ot) \
	ALLOCATE_ZEROED_MACRO(ot)
#define bind_osal_task_to_handle(handle, ot) \
	BIND_OBJECT_MACRO(&task_handle_table, handle, ot)
#define cancel_osal_task_handle(handle) \
	CANCEL_HANDLE_MACRO(&task_handle_table, handle)
#define get_osal_task(handle, ot) \
	GET_OBJECT_MACRO(&task_handle_table, handle, ot, TMOSAL_ERR_HANDLE)
#define put_osal_task(handle) \
	PUT_OBJECT_MACRO(&task_handle_table, handle)
#define mark_dead_osal_task(handle) \
	MARK_DEAD_OBJECT_MACRO(&task_handle_table, handle)


/* Value for suspend-resume counter that indicates no counting */
#define NO_COUNTING		-100	/* important that less than -1 */
#define NO_COUNTING_SUSPENDED	-101	/* important that less than NO_COUNTING */

/* Macro to check priority for correctness */
#define VALID_PRIORITY(prio) ((prio) >= 1 && (prio) <= 63)

/* Macro for checking stack size for not being larger than possible */
#define VALID_STACK_SIZE(size) (size <= 8*1024)


/* Wait queue for those who wait tasks exits */
static DECLARE_WAIT_QUEUE_HEAD(exit_waiters);


/* Find OSAL task structure for kernel task structure */
static int is_osal_task_for_task_struct(void *object, void *param)
{
	struct osal_task *ot = (struct osal_task *)object;
	struct task_struct *task = (struct task_struct *)param;
	return ot->task == task;
}
static inline struct osal_task *find_osal_task(struct task_struct *t)
{
	return (struct osal_task *) search_object(&task_handle_table,
			is_osal_task_for_task_struct, t);
}


/* Set priority for given OSAL task */
static void set_thread_priority(struct osal_task *ot)
{
	/* This routine does not cause actual rescheduling */

	struct sched_param param;
	int policy;

	if (ot->priority >= rr_prio_start) {
		policy = SCHED_RR;
		param.sched_priority = rr_prio_mapping + ot->priority - rr_prio_start;
		sched_setscheduler(ot->task, policy, &param);
	} else {
		policy = SCHED_NORMAL;
		param.sched_priority = 0;
		sched_setscheduler(ot->task, policy, &param);
		set_user_nice(ot->task, prio_1_mapping - (ot->priority - 1));
	}
}


/* This routine is the entry point of new kernel threads */
static int thread_entry(void *arg)
{
	struct osal_task *ot = (struct osal_task *)arg;
	unsigned long flags;

	set_current_state(TASK_INTERRUPTIBLE);

	/* At this point, new task is set up, and runs with it's initial
	 * priority. So wait for it to become ready, then start it. */

	spin_lock_irqsave(&ot->lock, flags);
	while (!ot->started) {		/* while loop is for extra safety -
					   if it will wake up unexpectedly */
		spin_unlock_irqrestore(&ot->lock, flags);
		schedule();
		spin_lock_irqsave(&ot->lock, flags);
	}
	spin_unlock_irqrestore(&ot->lock, flags);

	/* If the while loop didn't even go round once then we don't want
	 * to leave current->state set incorrectly. */
	set_current_state(TASK_RUNNING);

	/* since we could be able to get task lock, and started flag was
	 * true under that lock, it's guaranteed now that routine is reasdy */
	ot->routine(ot->arg);

	/* If we get to this point, OSAL task's body returned without
	 * calling tmosalTaskExit() */
	printk(KERN_ERR "OSAL task \'%s\' terminated without calling tmosalExit()\n",
			ot->name);
	tmosalTaskExit();

	/* not reached */
	return 0;
}


/* Now interface routines ... */

tmErrorCode_t tmosalTaskCreate(
		void (*pkStartFunction)(void *arg),
		void *pkArgument,
		const tmosalTaskPriority_t kPriority,
		const UInt32 kStackSize,
		ptmosalTaskHandle_t pioTaskHandle,
		char pkName[4],
		const tmosalTaskCreateFlags_t kFlags)
{
	u32 handle;
	struct osal_task *ot;
	
	BUG_IF_INTERRUPT();
	BUG_IF_NOT_PREEMPTIBLE();

	/* check args */
	
	if (unlikely(!VALID_PRIORITY(kPriority)))
		return TMOSAL_ERR_TASK_PRIORITY;

	if (unlikely(!VALID_STACK_SIZE(kStackSize)))
		return TMOSAL_ERR_TASK_HIGH;

	if (unlikely((kFlags & tmosalTaskStarted) && !pkStartFunction))
		return TMOSAL_ERR_TASK_START;	/* is this ok? */
			
	/* allocate and initialize OSAL task structure */
	/* no locking needed here, because rest of the system is yet not aware
	 * of the new task */
	allocate_osal_task_handle(handle);
	allocate_osal_task(ot);
	if (unlikely(!ot)) {
		cancel_osal_task_handle(handle);
		return TMOSAL_ERR_OUT_OF_MEMORY;
	}

	ot->handle = handle;
	ot->lock = SPIN_LOCK_UNLOCKED;
	ot->routine = pkStartFunction;
	ot->arg = pkArgument;
	ot->priority = kPriority;
	memcpy(ot->name, pkName, 4);
	ot->name[4] = 0;
	ot->resume_counter = (kFlags & tmosalTaskCounting) ? 0 : NO_COUNTING;
	ot->started = (kFlags & tmosalTaskStarted) != 0;

	/* create kernel thread; it will be stopped until we manually
	   wake it */
	ot->task = kthread_create(thread_entry, ot, ot->name);

	if (IS_ERR(ot->task))
	{
		kfree(ot);
		cancel_osal_task_handle(handle);
		return TMOSAL_ERR_TASK_FULL;
	}

	set_thread_priority(ot);	/* this does not actually reschedule */
	
	bind_osal_task_to_handle(handle, ot);
	*pioTaskHandle = handle;	/* before task is waked */

	STAT_HANDLE_CREATE(curTaskNr, maxTaskNr);

	/* now it's ready */
	wake_up_process(ot->task);
	schedule();

	return TM_OK;
}
EXPORT_SYMBOL(tmosalTaskCreate);


tmErrorCode_t tmosalTaskSetup(
		tmosalTaskHandle_t kTaskHandle,
		void (*pkStartFunction)(void *pArg),
		void *pkArgument,
		const tmosalTaskSetupFlags_t kFlags)
{
	struct osal_task *ot;
	unsigned long flags;

	if (unlikely(!pkStartFunction))
		return TMOSAL_ERR_TASK_FUNC;

	get_osal_task(kTaskHandle, ot);
	
	/* now lock, to ensure consistent data structure operation */
	spin_lock_irqsave(&ot->lock, flags);

	if (unlikely(ot->started)) {
		spin_unlock_irqrestore(&ot->lock, flags);
		put_osal_task(kTaskHandle);
		return TMOSAL_ERR_TASK_ALREADY_SETUP;
	}

	ot->routine = pkStartFunction;
	ot->arg = pkArgument;
	
	spin_unlock_irqrestore(&ot->lock, flags);
	put_osal_task(kTaskHandle);
	return TM_OK;
}
EXPORT_SYMBOL(tmosalTaskSetup);


tmErrorCode_t tmosalTaskStart(const tmosalTaskHandle_t kTaskHandle)
{
	struct osal_task *ot;
	unsigned long flags;

	BUG_IF_INTERRUPT();

	get_osal_task(kTaskHandle, ot);

	/* now lock, to ensure consistent data structure operation */
	spin_lock_irqsave(&ot->lock, flags);

	if (unlikely(ot->started)) {
		spin_unlock_irqrestore(&ot->lock, flags);
		put_osal_task(kTaskHandle);
		return TM_OK;			/* is this OK? */
	}

	if (unlikely(!ot->routine)) {
		spin_unlock_irqrestore(&ot->lock, flags);
		put_osal_task(kTaskHandle);
		return TMOSAL_ERR_TASK_START;	/* is this ok? */
	}

	ot->started = 1;
	spin_unlock_irqrestore(&ot->lock, flags);

	/* now really wake it */
	wake_up_process(ot->task);
	if (preemptible())
		schedule();

	put_osal_task(kTaskHandle);
	return TM_OK;
}
EXPORT_SYMBOL(tmosalTaskStart);


tmErrorCode_t tmosalTaskIdent(ptmosalTaskHandle_t pioTaskHandle)
{
	struct osal_task *ot;

	BUG_IF_INTERRUPT();
	
	if (unlikely(!pioTaskHandle))
		return TMOSAL_ERR_BAD_PARAM;

	ot = find_osal_task(current);
	if (unlikely(!ot))
		return TMOSAL_ERR_UNKNOWN;

	*pioTaskHandle = ot->handle;
	put_osal_task(ot->handle);
	return TM_OK;
}
EXPORT_SYMBOL(tmosalTaskIdent);


void tmosalTaskExit(void)
{
	struct osal_task *ot;

	/* no locking here - if someone tries to access exitting task,
	 * it is his problems not ours */

	BUG_IF_INTERRUPT();
	BUG_IF_NOT_PREEMPTIBLE();
	
	ot = find_osal_task(current);
	BUG_ON(!ot);
	
	STAT_HANDLE_DESTROY(curTaskNr);
	
	mark_dead_osal_task(ot->handle);
	/* object is marked dead now - so object_alive() will no longer
	 * return true for it. So safe to wake waiters now */
	wake_up(&exit_waiters);

	put_osal_task(ot->handle);
	complete_and_exit(0, 0);	/* do_exit() is not exported */
}
EXPORT_SYMBOL(tmosalTaskExit);


tmErrorCode_t tmosalTaskSuspend(void)
{
	struct osal_task *ot;
	unsigned long flags;

	BUG_IF_INTERRUPT();
	BUG_IF_NOT_PREEMPTIBLE();
	
	ot = find_osal_task(current);
	if (unlikely(!ot))
		return TMOSAL_ERR_UNKNOWN;

	spin_lock_irqsave(&ot->lock, flags);

	/* Suspend-resume counter */
	if (ot->resume_counter > NO_COUNTING) {
		ot->resume_counter--;
		if (ot->resume_counter >= 0) {
			spin_unlock_irqrestore(&ot->lock, flags);	/* no suspend, just count */
			put_osal_task(ot->handle);
			return TM_OK;
		}
	} else {
		ot->resume_counter = NO_COUNTING_SUSPENDED;
	}

	/* Change task state while lock still held - it's important to
	 * ensure that outside of lock-held-section task state and
	 * resume_counter are kept consistent */
	set_current_state(TASK_INTERRUPTIBLE);

	spin_unlock_irqrestore(&ot->lock, flags);
	put_osal_task(ot->handle);
	
	/* "commit" task status change */
	schedule();

	return TM_OK;
}
EXPORT_SYMBOL(tmosalTaskSuspend);


tmErrorCode_t tmosalTaskResume(const tmosalTaskHandle_t kTaskHandle)
{
	struct osal_task *ot;
	unsigned long flags;

	/* this call is allowed in interrupt context */

	get_osal_task(kTaskHandle, ot);

	spin_lock_irqsave(&ot->lock, flags);

	/* Suspend-resume counter */
	if (ot->resume_counter > NO_COUNTING) {
		ot->resume_counter++;
		if (ot->resume_counter > 0) {
			spin_unlock_irqrestore(&ot->lock, flags);	/* no resume needed */
			put_osal_task(kTaskHandle);
			return TM_OK;
		}
	} else {
		if (ot->resume_counter != NO_COUNTING_SUSPENDED) {
			spin_unlock_irqrestore(&ot->lock, flags);	/* don't try to wake it if it is not suspended - it may just sleep */
			put_osal_task(kTaskHandle);
			return TM_OK;
		}
		ot->resume_counter = NO_COUNTING;
	}
	
	/* We call wake_up_process() whith lock still held - it's
	 * important to ensure that outside of lock-held-section
	 * task state and resume_counter are kept consistent. */
	wake_up_process(ot->task);
	
	spin_unlock_irqrestore(&ot->lock, flags);
	put_osal_task(kTaskHandle);

	/* "commit" task status change */
	if (preemptible() && !in_interrupt())
		schedule();
	
	return TM_OK;
}
EXPORT_SYMBOL(tmosalTaskResume);


tmErrorCode_t tmosalTaskGetPriority(
		const tmosalTaskHandle_t kTaskHandle,
		ptmosalTaskPriority_t pioPriority)
{
	struct osal_task *ot;

	/* no reason to disallow this in interrupt handler */

	if (unlikely(!pioPriority))
		return TMOSAL_ERR_BAD_PARAM;
	
	get_osal_task(kTaskHandle, ot);

	/* don't need any locking to read a single value */
	*pioPriority = ot->priority;

	put_osal_task(kTaskHandle);
	return TM_OK;
}
EXPORT_SYMBOL(tmosalTaskGetPriority);


tmErrorCode_t tmosalTaskSetPriority(
		const tmosalTaskHandle_t kTaskHandle,
		const tmosalTaskPriority_t kPriority)
{
	struct osal_task *ot;
	unsigned long flags;

	if (unlikely(!VALID_PRIORITY(kPriority)))
		return TMOSAL_ERR_TASK_PRIORITY;
	
	get_osal_task(kTaskHandle, ot);

	/* need locking here, to make actual priority consistent with
	 * what is set in OSAL task structure */
	spin_lock_irqsave(&ot->lock, flags);

	if (unlikely(ot->priority == kPriority)) {
		spin_unlock_irqrestore(&ot->lock, flags);
		put_osal_task(kTaskHandle);
		return TM_OK;
	}

	ot->priority = kPriority;
	set_thread_priority(ot);

	spin_unlock_irqrestore(&ot->lock, flags);
	put_osal_task(kTaskHandle);

	/* "commit" priority change */
	if (!in_interrupt() && preemptible())
		schedule();

	return TM_OK;
}
EXPORT_SYMBOL(tmosalTaskSetPriority);


void tmosalTaskSleep(const Int32 kTime)
{
	BUG_IF_INTERRUPT();
	BUG_IF_NOT_PREEMPTIBLE();
	
	if (kTime <= 0)
		yield();
	else {
		set_current_state(TASK_INTERRUPTIBLE);
		schedule_timeout(msecs_to_jiffies(kTime));
	}
}
EXPORT_SYMBOL(tmosalTaskSleep);


void tmosalTaskMicroSleep(const Int32 kTime)
{
	if (kTime < 1000)
		udelay(kTime);
	else
		tmosalTaskSleep((kTime / 1000) + 1);
}
EXPORT_SYMBOL(tmosalTaskMicroSleep);


void tmosalTaskWaitForExit(const tmosalTaskHandle_t kTaskHandle)
{
	wait_event(exit_waiters, !object_alive(&task_handle_table, kTaskHandle));
}
EXPORT_SYMBOL(tmosalTaskWaitForExit);


int osal_task_submodule_init(void)
{
	return init_handle_table(&task_handle_table, TASK_HANDLE_TABLE_INITIAL_SIZE, "task");
}

void osal_task_submodule_uninit(void)
{
	uninit_handle_table(&task_handle_table);
}
