/*
 * Copyright (C) 1999-2013 Paolo Mantegazza <mantegazza@aero.polimi.it>
 * Copyright (C) 2019 Alec Ari <neotheuser@ymail.com>
 *
 * 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; either version 2 of the
 * License, or (at your option) any later version.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 *
 */


#ifndef _RTAI_SCHEDCORE_H
#define _RTAI_SCHEDCORE_H

#ifdef __KERNEL__

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/timex.h>
#include <linux/sched.h>
#include <asm/param.h>
#include <asm/io.h>
#include <linux/oom.h>
#include <rtai_lxrt.h>
#include <rtai_sched.h>
#include <rtai_malloc.h>
#include <rtai_trace.h>
#include <rtai_sem.h>
#include <rtai_rwl.h>
#include <rtai_spl.h>
#include <rtai_scb.h>
#include <rtai_mbx.h>
#include <rtai_msg.h>
#include <rtai_fifos.h>
#include <rtai_shm.h>


#ifdef OOM_DISABLE
#define RTAI_OOM_DISABLE() \
	do { current->signal->oom_score_adj = OOM_DISABLE; } while (0)
#else
#define RTAI_OOM_DISABLE()
#endif

#define NON_RTAI_TASK_SUSPEND(task) \
	do { (task->lnxtsk)->state = TASK_SOFTREALTIME; } while (0)

#define NON_RTAI_TASK_RESUME(ready_task) \
	do { pend_wake_up_srq(ready_task->lnxtsk, rtai_cpuid()); } while (0)

#define REQUEST_RESUME_SRQs_STUFF() \
do { \
	if (!(wake_up_srq[0].srq = hal_alloc_irq())) { \
		printk("*** ABORT, NO VIRQ AVAILABLE FOR THE WAKING UP SRQ. ***\n"); \
		return -1; \
	} \
	ipipe_request_irq(hal_root_domain, wake_up_srq[0].srq, (void *)wake_up_srq_handler, NULL, NULL); \
} while (0)

#define RELEASE_RESUME_SRQs_STUFF() \
do { \
	ipipe_free_irq(hal_root_domain, wake_up_srq[0].srq); \
	hal_free_irq(wake_up_srq[0].srq); \
} while (0)

extern RT_TASK rt_smp_linux_task[];

extern RT_TASK *rt_smp_current[];

extern RTIME rt_smp_time_h[];

extern volatile int rt_sched_timed;

RT_TASK *rt_get_base_linux_task(RT_TASK **base_linux_task);

RT_TASK *rt_alloc_dynamic_task(void);

void rt_enq_ready_edf_task(RT_TASK *ready_task);

void rt_enq_ready_task(RT_TASK *ready_task);

int rt_renq_ready_task(RT_TASK *ready_task,
		       int priority);

void rt_rem_ready_task(RT_TASK *task);

void rt_rem_ready_current(RT_TASK *rt_current);

void rt_enq_timed_task(RT_TASK *timed_task);

void rt_rem_timed_task(RT_TASK *task);

void rt_dequeue_blocked(RT_TASK *task);

#ifdef CONFIG_RTAI_MALLOC
#ifdef CONFIG_RTAI_MALLOC_BUILTIN
#define sched_mem_init() \
	{ if(__rtai_heap_init() != 0) { \
                return(-ENOMEM); \
        } }
#define sched_mem_end()	 __rtai_heap_exit()
#else  /* CONFIG_RTAI_MALLOC_BUILTIN */
#define sched_mem_init()
#define sched_mem_end()
#endif /* !CONFIG_RTAI_MALLOC_BUILTIN */
#define call_exit_handlers(task)	        __call_exit_handlers(task)
#define set_exit_handler(task, fun, arg1, arg2)	__set_exit_handler(task, fun, arg1, arg2)
#else  /* !CONFIG_RTAI_MALLOC */
#define sched_mem_init()
#define sched_mem_end()
#define call_exit_handlers(task)
#define set_exit_handler(task, fun, arg1, arg2)
#endif /* CONFIG_RTAI_MALLOC */

#define SEMHLF 0x0000FFFF
#define RPCHLF 0xFFFF0000
#define RPCINC 0x00010000

#define DECLARE_RT_CURRENT int cpuid; RT_TASK *rt_current
#define ASSIGN_RT_CURRENT rt_current = rt_smp_current[cpuid = rtai_cpuid()]
#define RT_CURRENT rt_smp_current[rtai_cpuid()]

#define MAX_LINUX_RTPRIO  99
#define MIN_LINUX_RTPRIO   1

#ifdef CONFIG_RTAI_SCHED_ISR_LOCK
void rtai_handle_isched_lock(int nesting);
#endif /* CONFIG_RTAI_SCHED_ISR_LOCK */

#ifdef CONFIG_SMP
#define rt_time_h (rt_smp_time_h[cpuid])
#define rt_linux_task (rt_smp_linux_task[cpuid])
#else
#define rt_time_h (rt_smp_time_h[0])
#define rt_linux_task (rt_smp_linux_task[0])
#endif

/*
 * WATCH OUT for the max expected number of arguments of rtai funs and 
 * their scattered around different calling ways.
 */

#define RTAI_MAX_FUN_ARGS  9
struct fun_args { unsigned long a[RTAI_MAX_FUN_ARGS]; RTAI_SYSCALL_MODE long long (*fun)(unsigned long, ...); };
//used in sys.c
#define RTAI_FUN_ARGS  arg[0],arg[1],arg[2],arg[3],arg[4],arg[5],arg[6],arg[7],arg[RTAI_MAX_FUN_ARGS - 1]
//used in sched.c (generalised calls from soft threads)
#define RTAI_FUNARGS   funarg->a[0],funarg->a[1],funarg->a[2],funarg->a[3],funarg->a[4],funarg->a[5],funarg->a[6],funarg->a[7],funarg->a[RTAI_MAX_FUN_ARGS - 1]

#ifdef CONFIG_SMP

static inline void send_sched_ipi(unsigned long dest)
{
	_send_sched_ipi(dest);
}

#define RT_SCHEDULE_MAP(schedmap) \
	do { if (schedmap) send_sched_ipi(schedmap); } while (0)

#define RT_SCHEDULE_MAP_BOTH(schedmap) \
	do { if (schedmap) send_sched_ipi(schedmap); rt_schedule(); } while (0)

#define RT_SCHEDULE(task, cpuid) \
	do { \
		if ((task)->runnable_on_cpus != (cpuid)) { \
			send_sched_ipi(1 << (task)->runnable_on_cpus); \
		} else { \
			rt_schedule(); \
		} \
	} while (0)

#define RT_SCHEDULE_BOTH(task, cpuid) \
	{ \
		if ((task)->runnable_on_cpus != (cpuid)) { \
			send_sched_ipi(1 << (task)->runnable_on_cpus); \
		} \
		rt_schedule(); \
	}

#else /* !CONFIG_SMP */

#define send_sched_ipi(dest)

#define RT_SCHEDULE_MAP_BOTH(schedmap)  rt_schedule()

#define RT_SCHEDULE_MAP(schedmap)       rt_schedule()

#define RT_SCHEDULE(task, cpuid)        rt_schedule()

#define RT_SCHEDULE_BOTH(task, cpuid)   rt_schedule()

#endif /* CONFIG_SMP */

#define BASE_SOFT_PRIORITY 1000000000

#ifndef TASK_NOWAKEUP
#define TASK_NOWAKEUP  TASK_UNINTERRUPTIBLE
#endif

#define TASK_HARDREALTIME  (TASK_INTERRUPTIBLE) // | TASK_NOWAKEUP)
#define TASK_RTAISRVSLEEP  (TASK_INTERRUPTIBLE) // | TASK_NOWAKEUP)
#define TASK_SOFTREALTIME  TASK_INTERRUPTIBLE

static inline void enq_ready_edf_task(RT_TASK *ready_task)
{
	RT_TASK *task;
#ifdef CONFIG_SMP
	task = rt_smp_linux_task[ready_task->runnable_on_cpus].rnext;
#else
	task = rt_smp_linux_task[0].rnext;
#endif
	while (task->policy < 0 && ready_task->period >= task->period) {
		task = task->rnext;
	}
	task->rprev = (ready_task->rprev = task->rprev)->rnext = ready_task;
	ready_task->rnext = task;
}

struct epoch_struct { spinlock_t lock; volatile int touse; volatile RTIME time[2][2]; };

#ifdef CONFIG_RTAI_CLOCK_REALTIME
#define REALTIME2COUNT(rtime) \
	if (rtime > boot_epoch.time[boot_epoch.touse][0]) { \
		rtime -= boot_epoch.time[boot_epoch.touse][0]; \
	}
#else 
#define REALTIME2COUNT(rtime)
#endif

#define MAX_WAKEUP_SRQ (1 << 6)

struct klist_t { int srq; volatile unsigned long in, out; void *task[MAX_WAKEUP_SRQ]; };
extern struct klist_t wake_up_srq[];

#define pend_wake_up_srq(lnxtsk, cpuid) \
do { \
	wake_up_srq[cpuid].task[wake_up_srq[cpuid].in++ & (MAX_WAKEUP_SRQ - 1)] = lnxtsk; \
	hal_pend_uncond(wake_up_srq[0].srq, cpuid); \
} while (0)

static inline void enq_ready_task(RT_TASK *ready_task)
{
	RT_TASK *task;
	if (ready_task->is_hard) {
#ifdef CONFIG_SMP
		task = rt_smp_linux_task[ready_task->runnable_on_cpus].rnext;
#else
		task = rt_smp_linux_task[0].rnext;
#endif
		while (ready_task->priority >= task->priority) {
			if ((task = task->rnext)->priority < 0) break;
		}
		task->rprev = (ready_task->rprev = task->rprev)->rnext = ready_task;
		ready_task->rnext = task;
	} else {
		ready_task->state |= RT_SCHED_SFTRDY;
		NON_RTAI_TASK_RESUME(ready_task);
	}
}

static inline int renq_ready_task(RT_TASK *ready_task, int priority)
{
	int retval;
	if ((retval = ready_task->priority != priority)) {
		ready_task->priority = priority;
		if (ready_task->state == RT_SCHED_READY) {
			(ready_task->rprev)->rnext = ready_task->rnext;
			(ready_task->rnext)->rprev = ready_task->rprev;
			enq_ready_task(ready_task);
		}
	}
	return retval;
}

static inline void rem_ready_task(RT_TASK *task)
{
	if (task->state == RT_SCHED_READY) {
		if (!task->is_hard) {
			NON_RTAI_TASK_SUSPEND(task);
		}
//		task->unblocked = 0;
		(task->rprev)->rnext = task->rnext;
		(task->rnext)->rprev = task->rprev;
	}
}

static inline void rem_ready_current(RT_TASK *rt_current)
{
	if (!rt_current->is_hard) {
		NON_RTAI_TASK_SUSPEND(rt_current);
	}
//	rt_current->unblocked = 0;
	(rt_current->rprev)->rnext = rt_current->rnext;
	(rt_current->rnext)->rprev = rt_current->rprev;
}

#ifdef CONFIG_RTAI_LONG_TIMED_LIST

/* BINARY TREE */
static inline void enq_timed_task(RT_TASK *timed_task)
{
	RT_TASK *taskh, *tsknxt, *task;
	rb_node_t **rbtn, *rbtpn = NULL;
#ifdef CONFIG_SMP
	task = taskh = &rt_smp_linux_task[timed_task->runnable_on_cpus];
#else
	task = taskh = &rt_smp_linux_task[0];
#endif
	rbtn = &taskh->rbr.rb_node;

	while (*rbtn) {
		rbtpn = *rbtn;
		tsknxt = rb_entry(rbtpn, RT_TASK, rbn);
		if (timed_task->resume_time > tsknxt->resume_time) {
			rbtn = &(rbtpn)->rb_right;
		} else {
			rbtn = &(rbtpn)->rb_left;
			task = tsknxt;
		}
	}
	rb_link_node(&timed_task->rbn, rbtpn, rbtn);
	rb_insert_color(&timed_task->rbn, &taskh->rbr);
	task->tprev = (timed_task->tprev = task->tprev)->tnext = timed_task;
	timed_task->tnext = task;
}

#define	rb_erase_task(task, cpuid) \
	rb_erase(&(task)->rbn, &rt_smp_linux_task[cpuid].rbr);

#else /* !CONFIG_RTAI_LONG_TIMED_LIST */

/* LINEAR */
static inline void enq_timed_task(RT_TASK *timed_task)
{
	RT_TASK *task;
#ifdef CONFIG_SMP
	task = rt_smp_linux_task[timed_task->runnable_on_cpus].tnext;
#else
	task = rt_smp_linux_task[0].tnext;
#endif
	while (timed_task->resume_time > task->resume_time) {
		task = task->tnext;
	}
	task->tprev = (timed_task->tprev = task->tprev)->tnext = timed_task;
	timed_task->tnext = task;
}

#define	rb_erase_task(task, cpuid)

#endif /* !CONFIG_RTAI_LONG_TIMED_LIST */

static inline void rem_timed_task(RT_TASK *task)
{
	if ((task->state & RT_SCHED_DELAYED)) {
                (task->tprev)->tnext = task->tnext;
                (task->tnext)->tprev = task->tprev;
#ifdef CONFIG_SMP
		rb_erase_task(task, task->runnable_on_cpus);
#else
		rb_erase_task(task, 0);
#endif
	}
}

static inline void wake_up_timed_tasks(int cpuid)
{
	RT_TASK *taskh, *task;
#ifdef CONFIG_SMP
	task = (taskh = &rt_smp_linux_task[cpuid])->tnext;
#else
	task = (taskh = &rt_smp_linux_task[0])->tnext;
#endif
	if (task->resume_time - task->schedlat <= rt_time_h) {
		do {
			if ((task->state & RT_SCHED_SUSPENDED) && task->suspdepth > 0) {
				task->suspdepth = 0;
			}
        	        if ((task->state &= ~(RT_SCHED_DELAYED | RT_SCHED_SUSPENDED | RT_SCHED_SEMAPHORE | RT_SCHED_RECEIVE | RT_SCHED_SEND | RT_SCHED_RPC | RT_SCHED_RETURN | RT_SCHED_MBXSUSP | RT_SCHED_POLL)) == RT_SCHED_READY) {
                	        if (task->policy < 0) {
                        	        enq_ready_edf_task(task);
	                        } else {
        	                        enq_ready_task(task);
                	        }
#if ((CONFIG_RTAI_USER_BUSY_ALIGN_RET_DELAY > 0 || CONFIG_RTAI_KERN_BUSY_ALIGN_RET_DELAY > 0))
				task->busy_time_align = 1;
#endif
        	        }
			rb_erase_task(task, cpuid);
			task = task->tnext;
		} while (task->resume_time <= rt_time_h);
#ifdef CONFIG_SMP
		rt_smp_linux_task[cpuid].tnext = task;
		task->tprev = &rt_smp_linux_task[cpuid];
#else
		rt_smp_linux_task[0].tnext = task;
		task->tprev = &rt_smp_linux_task[0];
#endif
	}
}

#define get_time() rt_get_time()

static inline void enqueue_blocked(RT_TASK *task, QUEUE *queue, int qtype)
{
        QUEUE *q;
        task->blocked_on = (q = queue);
        if (!qtype) {
                while ((q = q->next) != queue && (q->task)->priority <= task->priority);
        }
        q->prev = (task->queue.prev = q->prev)->next  = &(task->queue);
        task->queue.next = q;
}


static inline void dequeue_blocked(RT_TASK *task)
{
        task->prio_passed_to     = NULL;
        (task->queue.prev)->next = task->queue.next;
        (task->queue.next)->prev = task->queue.prev;
        task->blocked_on         = NULL;
}

static inline unsigned long pass_prio(RT_TASK *to, RT_TASK *from)
{
        QUEUE *q, *blocked_on;
#ifdef CONFIG_SMP
	RT_TASK *rhead;
        unsigned long schedmap;
        schedmap = 0;
#endif
//	from->prio_passed_to = to;
        while (to && to->priority > from->priority) {
                to->priority = from->priority;
		if (to->state == RT_SCHED_READY) {
			if ((to->rprev)->priority > to->priority || (to->rnext)->priority < to->priority) {
#ifdef CONFIG_SMP
				rhead = rt_smp_linux_task[to->runnable_on_cpus].rnext;
#endif
				(to->rprev)->rnext = to->rnext;
				(to->rnext)->rprev = to->rprev;
				enq_ready_task(to);
#ifdef CONFIG_SMP
				if (rhead != rt_smp_linux_task[to->runnable_on_cpus].rnext)  {
					__set_bit(to->runnable_on_cpus & 0x1F, &schedmap);
				}
#endif
			}
			break;
//		} else if ((void *)(q = to->blocked_on) > RTE_HIGERR && !((to->state & RT_SCHED_SEMAPHORE) && ((SEM *)q)->qtype)) {
		} else if ((unsigned long)(blocked_on = to->blocked_on) > RTE_HIGERR && (((to->state & RT_SCHED_SEMAPHORE) && ((SEM *)blocked_on)->type > 0) || (to->state & (RT_SCHED_SEND | RT_SCHED_RPC | RT_SCHED_RETURN)))) {
			if (to->queue.prev != blocked_on) {
				q = blocked_on;
				(to->queue.prev)->next = to->queue.next;
				(to->queue.next)->prev = to->queue.prev;
				while ((q = q->next) != blocked_on && (q->task)->priority <= to->priority);
				q->prev = (to->queue.prev = q->prev)->next  = &(to->queue);
				to->queue.next = q;
				if (to->queue.prev != blocked_on) {
					break;
				}
			}
			to = (to->state & RT_SCHED_SEMAPHORE) ? ((SEM *)blocked_on)->owndby : blocked_on->task;
                }
//		to = to->prio_passed_to;
	}
#ifdef CONFIG_SMP
	return schedmap;
#else
	return 0;
#endif
}

static inline RT_TASK *_rt_whoami(void)
{
#ifdef CONFIG_SMP
        RT_TASK *rt_current;
        unsigned long flags;
        flags = rt_global_save_flags_and_cli();
        rt_current = RT_CURRENT;
        rt_global_restore_flags(flags);
        return rt_current;
#else
        return rt_smp_current[0];
#endif
}

static inline void __call_exit_handlers(RT_TASK *task)
{
	XHDL *pt, *tmp;

	pt = task->ExitHook; // Initialise ExitHook in rt_task_init()
	while ( pt ) {
		(*pt->fun) (pt->arg1, pt->arg2);
		tmp = pt;
		pt  = pt->nxt;
		rt_free(tmp);
	}
	task->ExitHook = 0;
}

static inline XHDL *__set_exit_handler(RT_TASK *task, void (*fun) (void *, int), void *arg1, int arg2)
{
	XHDL *p;

	// exit handler functions are automatically executed at terminattion time by rt_task_delete()
	// in the reverse order they were created (like C++ destructors behave).
	if (task->magic != RT_TASK_MAGIC) return 0;
	if (!(p = (XHDL *) rt_malloc (sizeof(XHDL)))) return 0;
	p->fun  = fun;
	p->arg1 = arg1;
	p->arg2 = arg2;
	p->nxt  = task->ExitHook;
	return (task->ExitHook = p);
}

static inline int rtai_init_features (void)

{
#ifdef CONFIG_RTAI_SEM_BUILTIN
    __rtai_sem_init();
#endif /* CONFIG_RTAI_SEM_BUILTIN */
#ifdef CONFIG_RTAI_MSG_BUILTIN
    __rtai_msg_init();
#endif /* CONFIG_RTAI_MSG_BUILTIN */
#ifdef CONFIG_RTAI_MBX_BUILTIN
    __rtai_mbx_init();
#endif /* CONFIG_RTAI_MBX_BUILTIN */
#ifdef CONFIG_RTAI_FIFOS_BUILTIN
    __rtai_fifos_init();
#endif /* CONFIG_RTAI_FIFOS_BUILTIN */
#ifdef CONFIG_RTAI_SHM_BUILTIN
    __rtai_shm_init();
#endif /* CONFIG_RTAI_SHM_BUILTIN */
#ifdef CONFIG_RTAI_MATH_BUILTIN
    __rtai_math_init();
#endif /* CONFIG_RTAI_MATH_BUILTIN */

	return 0;
}

static inline void rtai_cleanup_features (void) {

#ifdef CONFIG_RTAI_MATH_BUILTIN
    __rtai_math_exit();
#endif /* CONFIG_RTAI_MATH_BUILTIN */
#ifdef CONFIG_RTAI_SHM_BUILTIN
    __rtai_shm_exit();
#endif /* CONFIG_RTAI_SHM_BUILTIN */
#ifdef CONFIG_RTAI_FIFOS_BUILTIN
    __rtai_fifos_exit();
#endif /* CONFIG_RTAI_FIFOS_BUILTIN */
#ifdef CONFIG_RTAI_MBX_BUILTIN
    __rtai_mbx_exit();
#endif /* CONFIG_RTAI_MBX_BUILTIN */
#ifdef CONFIG_RTAI_MSG_BUILTIN
    __rtai_msg_exit();
#endif /* CONFIG_RTAI_MSG_BUILTIN */
#ifdef CONFIG_RTAI_SEM_BUILTIN
    __rtai_sem_exit();
#endif /* CONFIG_RTAI_SEM_BUILTIN */
}

int rt_check_current_stack(void);

int rt_kthread_init_old(RT_TASK *task,
		    void (*rt_thread)(long),
		    long data,
		    int stack_size,
		    int priority,
		    int uses_fpu,
		    void(*signal)(void));

int rt_kthread_init_cpuid(RT_TASK *task,
			  void (*rt_thread)(long),
			  long data,
			  int stack_size,
			  int priority,
			  int uses_fpu,
			  void(*signal)(void),
			  unsigned int cpuid);

#else /* !__KERNEL__ */

#endif /* __KERNEL__ */

#endif /* !_RTAI_SCHEDCORE_H */
