/*
 *  linux/kernel/sched.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file README.legal in the main directory of this archive
 * for more details.
 */

/*
 * 680x0 support by Hamish Macdonald
 */

/*
 * 'sched.c' is the main kernel file. It contains scheduling primitives
 * (sleep_on, wakeup, schedule etc) as well as a number of simple system
 * call functions (type getpid(), which just extracts a field from
 * current-task
 */

#include <asm/system.h>
#include <asm/segment.h>

#include <linux/config.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/kernel.h>
#include <linux/traps.h>
#include <linux/sys.h>
#include <linux/fdreg.h>
#include <linux/errno.h>
#include <linux/time.h>
#include <linux/ptrace.h>

#include <machine/ktypes.h>
#include <machine/sys_info.h>
#include <machine/chipregs.h>
#include <machine/interrupt.h>

int need_resched = 0;

unsigned long * prof_buffer = NULL;
unsigned long prof_len = 0;

#define _S(nr) (1<<((nr)-1))

extern void mem_use(void);

extern int timer_interrupt(void);
extern int system_call(void);

static struct task_struct init_task = INIT_TASK;

unsigned long volatile jiffies=0;
unsigned long startup_time=0;
int jiffies_offset = 0; 	/* # clock ticks to add to get "true
				   time".  Should always be less than
				   1 second's worth.  For time fanatics
				   who like to syncronize their machines
				   to WWV :-) */

struct task_struct *current = &init_task;
struct task_struct *last_task_used_math = NULL;

struct task_struct * task[NR_TASKS] = {&init_task, };

/*
 *  'math_state_restore()' saves the current math information in the
 * old math state array, and gets the new ones from the current task
 */
void math_state_restore(void)
{
	if (last_task_used_math == current)
		return;
#if 0
	timer_table[COPRO_TIMER].expires = jiffies+50;
	timer_active |= 1<<COPRO_TIMER;
	if (last_task_used_math) {
		__asm__("fnsave %0"::"m" (last_task_used_math->i387));
	}
	last_task_used_math=current;
	if (current->used_math) {
		__asm__("frstor %0"::"m" (current->i387));
	} else {
		__asm__("fninit"::);
		current->used_math=1;
	}
	timer_active &= ~(1<<COPRO_TIMER);
#endif
}

int setjmp(label_t *buf)
{
    __asm__ __volatile__ ("movel sp@,d1\n\t"
			  "moveml d1-d7/a2-sp,%0@\n\t"
			  :: "a" (buf->regs) : "d1");

    return 0;
}

volatile void longjmp(label_t *buf,int val)
{
    __asm__ __volatile__ ("movel  %1,d0\n\t"
			  "moveml %0@,d1-d7/a2-sp\n\t"
			  "movel  d1,sp@\n\t"  /* new return address */
			  "rts"
			  :: "a" (buf->regs), "m" (val) : "d0", "d1");
/* fake out volatile */
fake_volatile:
    goto fake_volatile;
}

void resume(struct task_struct *tsk);

static void ____dummy_proc(void)
{
    __asm__ __volatile__ (".globl _resume\n"
			  "_resume:");

    __asm__ __volatile__ ("movel sp@,d1\n\t"
			  "moveml d1-d7/a2-sp,%0@\n\t"
			  :: "a" (current->swtch.regs) : "d1");

    /* switch current task */
    __asm__ __volatile__ ("movel sp@(4),%0" : "=m" (current));

    /* switch address space */
    switch_pmap (&current->pmap);

    /* flush caches */
    flush_cache();

    /* restore context */
    __asm__ __volatile__ ("moveml %0@,d1-d7/a2-sp\n\t"
			  "movel d1,sp@\n\t"  /* new return address */
			  :: "a" (current->swtch.regs) : "d1");

    /* longjmp if necessary */
    __asm__ __volatile__ ("movel %0,d0" :: "m" (current->pjmp) : "d0");
    __asm__ __volatile__ ("bne 1f");
    __asm__ __volatile__ ("rts");   /* normal context switch */
    __asm__ __volatile__ ("1: clrl %0" : "=m" (current->pjmp));
    __asm__ __volatile__ ("pea 1:w");
    __asm__ __volatile__ ("movel d0,sp@-");
    __asm__ __volatile__ ("jsr _longjmp");

/* NOTREACHED */
    ____dummy_proc();
}

void pstack(unsigned long *addr)
{
    int i = 0;

    while (i < 10 && (unsigned long)addr < KSTACK_ADDR+PAGE_SIZE)
    {
	if (i % 5 == 0)
	    printk ("%#010x: ", addr);
	printk ("%#010x  ", *addr);
	if (i % 5 == 4)
	    printk("\n");
	addr++;
	i++;
    }

    if (i % 5 != 0)
	printk ("\n");
}

/*
 *  'schedule()' is the scheduler function. It's a very simple and nice
 * scheduler: it's not perfect, but certainly works for most things.
 * The one thing you might take a look at is the signal-handler code here.
 *
 *   NOTE!!  Task 0 is the 'idle' task, which gets called when no other
 * tasks can run. It can not be killed, and it cannot sleep. The 'state'
 * information in task[0] is never used.
 */

void schedule(void)
{
    int i,next,c;
    struct task_struct ** p;
#if DEBUG
    unsigned long l;
#endif

    /* check alarm, wake up any interruptible tasks that have got a signal */

    need_resched = 0;
    for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) {
	if (!*p || ((*p)->state != TASK_INTERRUPTIBLE))
	    continue;
	if ((*p)->timeout && (*p)->timeout < jiffies) {
	    (*p)->timeout = 0;
	    (*p)->state = TASK_RUNNING;
	} else if ((*p)->signal & ~(*p)->blocked)
	    (*p)->state = TASK_RUNNING;
    }

    /* this is the scheduler proper: */

    while (1) {
	c = -1;
	next = 0;
	i = NR_TASKS;
	p = &task[NR_TASKS];
	while (--i) {
	    if (!*--p)
		continue;
	    if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
		c = (*p)->counter, next = i;
	}
	if (c)
	    break;
	for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
	    if (*p)
		(*p)->counter = ((*p)->counter >> 1) +
		    (*p)->priority;
    }

#if DEBUG
    printk ("schedule: switching to task #%d (pc=%#x)\n", next,
	    task[next]->swtch.regs[0]);

    __asm__ __volatile__ ("movel sp,%0" : "=d" (l));
    printk( "Switching from sp %#x\n", l);
#endif
    i = 0;
    __asm__ __volatile__ ("movew sr,%0" :"=d" (i));
    if (!(i & 0x1000))
	panic ("switching from interrupt stack!");

    switch_to(next);

#if DEBUG
    __asm__ __volatile__ ("movel sp,%0" : "=d" (l));
    printk( "Switched to sp %#x\n", l);
#endif
}

int sys_pause(void)
{
	current->state = TASK_INTERRUPTIBLE;
	schedule();
	return -ERESTARTNOHAND;
}

/*
 * wake_up doesn't wake up stopped processes - they have to be awakened
 * with signals or similar.
 *
 * Note that this doesn't need cli-sti pairs: interrupts may not change
 * the wait-queue structures directly, but only call wake_up() to wake
 * a process. The process itself must remove the queue once it has woken.
 */
void wake_up(struct wait_queue **q)
{
    struct wait_queue *tmp;
    struct task_struct * p;

    if (!q || !(tmp = *q))
	return;
    do {
	if ((p = tmp->task) != NULL) {
	    if ((p->state == TASK_UNINTERRUPTIBLE) ||
		(p->state == TASK_INTERRUPTIBLE)) {
		p->state = TASK_RUNNING;
		if (p->counter > current->counter)
		    need_resched = 1;
	    }
	}
	if (!tmp->next) {
	    printk("wait_queue is bad (eip = %08x)\n",((unsigned long *) q)[-1]);
	    printk("        q = %08x\n",q);
	    printk("       *q = %08x\n",*q);
	    printk("      tmp = %08x\n",tmp);
	    break;
	}
	tmp = tmp->next;
    } while (tmp != *q);
}

void wake_up_interruptible(struct wait_queue **q)
{
    struct wait_queue *tmp;
    struct task_struct * p;

    if (!q || !(tmp = *q))
	return;
    do {
	if ((p = tmp->task) != NULL) {
	    if (p->state == TASK_INTERRUPTIBLE) {
		p->state = TASK_RUNNING;
		if (p->counter > current->counter)
		    need_resched = 1;
	    }
	}
	if (!tmp->next) {
	    printk("wait_queue is bad (eip = %08x)\n",
		   ((unsigned long *) q)[-1]);
	    printk("        q = %08x\n",q);
	    printk("       *q = %08x\n",*q);
	    printk("      tmp = %08x\n",tmp);
	    break;
	}
	tmp = tmp->next;
    } while (tmp != *q);
}

static inline void __sleep_on(struct wait_queue **p, int state)
{
    unsigned long flags;

    if (!p)
	return;
    if (current == task[0])
	panic("task[0] trying to sleep");
    current->wait.task = current;
    current->wait.next = NULL;
    current->state = state;
    add_wait_queue(p, &current->wait);
    save_flags(flags);
    sti();
    schedule();
    remove_wait_queue(p, &current->wait);
    restore_flags(flags);
}

void interruptible_sleep_on(struct wait_queue **p)
{
    __sleep_on(p,TASK_INTERRUPTIBLE);
}

void sleep_on(struct wait_queue **p)
{
    __sleep_on(p,TASK_UNINTERRUPTIBLE);
}

/*
 * OK, here are some floppy things that shouldn't be in the kernel
 * proper. They are here because the floppy needs a timer, and this
 * was the easiest way of doing it.
 */
static struct wait_queue * wait_motor[4] = {NULL,NULL,NULL,NULL};
static int  mon_timer[4]={0,0,0,0};
static int moff_timer[4]={0,0,0,0};
unsigned char current_DOR = 0x0C;

#if 0
int ticks_to_floppy_on(unsigned int nr)
{
	extern unsigned char selected;
	unsigned char mask = 0x10 << nr;

	if (nr>3)
		panic("floppy_on: nr>3");
	moff_timer[nr]=10000;		/* 100 s = very big :-) */
	cli();                          /* use floppy_off to turn it off */
	mask |= current_DOR;
	if (!selected) {
		mask &= 0xFC;
		mask |= nr;
	}
	if (mask != current_DOR) {
		outb(mask,FD_DOR);
		if ((mask ^ current_DOR) & 0xf0)
			mon_timer[nr] = HZ/2;
		else if (mon_timer[nr] < 2)
			mon_timer[nr] = 2;
		current_DOR = mask;
	}
	sti();
	return mon_timer[nr];
}

void floppy_off(unsigned int nr)
{
	moff_timer[nr]=3*HZ;
}
#endif

void do_floppy_timer(void)
{
#if 0
	int i;
	unsigned char mask = 0x10;

	for (i=0 ; i<4 ; i++,mask <<= 1) {
		if (!(mask & current_DOR))
			continue;
		if (mon_timer[i]) {
			if (!--mon_timer[i])
				wake_up(i+wait_motor);
		} else if (!moff_timer[i]) {
			current_DOR &= ~mask;
			outb(current_DOR,FD_DOR);
		} else
			moff_timer[i]--;
	}
#endif
}

#define TIME_REQUESTS 64

static struct timer_list {
	long jiffies;
	void (*fn)(void);
	struct timer_list * next;
} timer_list[TIME_REQUESTS] = { { 0, NULL, NULL }, };

static struct timer_list * next_timer = NULL;

void add_timer(long jiffies, void (*fn)(void))
{
	struct timer_list * p;
	unsigned long flags;

	if (!fn)
		return;
	save_flags(flags);
	cli();
	if (jiffies <= 0)
		(fn)();
	else {
		for (p = timer_list ; p < timer_list + TIME_REQUESTS ; p++)
			if (!p->fn)
				break;
		if (p >= timer_list + TIME_REQUESTS)
			panic("No more time requests free");
		p->fn = fn;
		p->jiffies = jiffies;
		p->next = next_timer;
		next_timer = p;
		while (p->next && p->next->jiffies < p->jiffies) {
			p->jiffies -= p->next->jiffies;
			fn = p->fn;
			p->fn = p->next->fn;
			p->next->fn = fn;
			jiffies = p->jiffies;
			p->jiffies = p->next->jiffies;
			p->next->jiffies = jiffies;
			p = p->next;
		}
		if (p->next)
			p->next->jiffies -= p->jiffies;
	}
	restore_flags(flags);
}

unsigned long timer_active = 0;
struct timer_struct timer_table[32];

/*
 * Hmm.. Changed this, as the GNU make sources (load.c) seems to
 * imply that avenrun[] is the standard name for this kind of thing.
 * Nothing else seems to be standardized: the fractional size etc
 * all seem to differ on different machines.
 */
unsigned long avenrun[3] = { 0,0,0 };

/*
 * Nr of active tasks - counted in fixed-point numbers
 */
static unsigned long count_active_tasks(void)
{
	struct task_struct **p;
	unsigned long nr = 0;

	for(p = &LAST_TASK; p > &FIRST_TASK; --p)
		if (*p && (*p)->state == TASK_RUNNING)
			nr += FIXED_1;
	return nr;
}

static inline void calc_load(void)
{
	unsigned long active_tasks; /* fixed-point */
	static int count = LOAD_FREQ;

	if (count-- > 0)
		return;
	count = LOAD_FREQ;
	active_tasks = count_active_tasks();
	CALC_LOAD(avenrun[0], EXP_1, active_tasks);
	CALC_LOAD(avenrun[1], EXP_5, active_tasks);
	CALC_LOAD(avenrun[2], EXP_15, active_tasks);
}

/*
 * This routine is called when the (100Hz) timer interrupt goes off.
 */
static int do_timer(struct ISR_mask *imp, ushort sr)
{
    unsigned long mask;
    struct timer_struct *tp = timer_table+0;
    struct task_struct ** task_p;

    jiffies++;
    calc_load();
    if (sr & PS_S) {
	current->stime++;
#ifdef CONFIG_PROFILE
	if (prof_buffer && current != task[0]) {
	    unsigned long eip = regs->eip;
	    eip >>= 2;
	    if (eip < prof_len)
		prof_buffer[eip]++;
	}
    } else {
	current->utime++;
	/* Update ITIMER_VIRT for current task if not in a system call */
	if (current->it_virt_value && !(--current->it_virt_value)) {
	    current->it_virt_value = current->it_virt_incr;
	    send_sig(SIGVTALRM,current,1);
	}
#endif
    }
    if (current == task[0] || (--current->counter)<=0) {
	current->counter=0;
	need_resched = 1;
    }
    /* Update ITIMER_REAL for every task */
    for (task_p = &LAST_TASK; task_p >= &FIRST_TASK; task_p--)
	if (*task_p && (*task_p)->it_real_value
	    && !(--(*task_p)->it_real_value)) {
	    send_sig(SIGALRM,*task_p,1);
	    (*task_p)->it_real_value = (*task_p)->it_real_incr;
	    need_resched = 1;
	}
    /* Update ITIMER_PROF for the current task */
    if (current->it_prof_value && !(--current->it_prof_value)) {
	current->it_prof_value = current->it_prof_incr;
	send_sig(SIGPROF,current,1);
    }
    for (mask = 1 ; mask ; tp++,mask += mask) {
	if (mask > timer_active)
	    break;
	if (!(mask & timer_active))
	    continue;
	if (tp->expires > jiffies)
	    continue;
	timer_active &= ~mask;
	tp->fn();
	sti();
    }
    if (next_timer) {
	next_timer->jiffies--;
	while (next_timer && next_timer->jiffies <= 0) {
	    void (*fn)(void);

	    fn = next_timer->fn;
	    next_timer->fn = NULL;
	    next_timer = next_timer->next;
	    (fn)();
	}
    }
    if (current_DOR & 0xf0)
	do_floppy_timer();

    /* allow other interrupt processing */
    return 1;
}

int sys_alarm(long seconds)
{
	extern int _setitimer(int, struct itimerval *, struct itimerval *);
	struct itimerval new, old;

	new.it_interval.tv_sec = new.it_interval.tv_usec = 0;
	new.it_value.tv_sec = seconds;
	new.it_value.tv_usec = 0;
	_setitimer(ITIMER_REAL, &new, &old);
	return(old.it_value.tv_sec + (old.it_value.tv_usec / 1000000));
}

int sys_getpid(void)
{
	return current->pid;
}

int sys_getppid(void)
{
	return current->p_pptr->pid;
}

int sys_getuid(void)
{
	return current->uid;
}

int sys_geteuid(void)
{
	return current->euid;
}

int sys_getgid(void)
{
	return current->gid;
}

int sys_getegid(void)
{
	return current->egid;
}

int sys_nice(long increment)
{
	if (increment < 0 && !suser())
		return -EPERM;
	if (increment >= current->priority)
		increment = current->priority-1;
	current->priority -= increment;
	return 0;
}

static void show_task(int nr,struct task_struct * p)
{
	int i, j;
	unsigned char * stack;

	printk("%d: pid=%d, state=%d, father=%d, child=%d, ",(p == current)?-nr:nr,p->pid,
		p->state, p->p_pptr->pid, p->p_cptr ? p->p_cptr->pid : -1);
	i = 0;
	j = 4096;
	stack = (char *) p->kernel_stack_page;
	while (i<j && !*(stack++))
		i++;
	printk("%d/%d chars free in kstack\n",i,j);

	/* excuse me? */
	printk("   PC=%08X.", *(1019 + (unsigned long *) p));

	if (p->p_ysptr || p->p_osptr)
		printk("   Younger sib=%d, older sib=%d\n",
			p->p_ysptr ? p->p_ysptr->pid : -1,
			p->p_osptr ? p->p_osptr->pid : -1);
	else
		printk("\n");
}

void show_state(void)
{
	int i;

	printk("Task-info:\n");
	for (i=0 ; i<NR_TASKS ; i++)
		if (task[i])
			show_task(i,task[i]);
}

void sched_init(void)
{
    struct ISR_mask mask;

    if (sizeof(struct sigaction) != 16)
	panic("Struct sigaction MUST be 16 bytes");

    if (sizeof(struct task_struct) > PAGE_SIZE)
	panic("struct task_struct cannot be larger than the page size");

    *ciab_cra &= 0xC0;	 /* turn off timer A, continous mode, from Eclk */
    if (sys_info.si_amiga.eclock == NTSC_ECLOCK) {
	*ciab_talo = NTSC_JIFFY_TICKS % 256;
	*ciab_tahi = NTSC_JIFFY_TICKS / 256;
    } else {
	*ciab_talo = PAL_JIFFY_TICKS % 256;
	*ciab_tahi = PAL_JIFFY_TICKS / 256;
    } /* NTSC_ECLOCK */

    /* install interrupt service routine for CIAB Timer A */
    mask.intreqr_mask = IF_EXTER;
    mask.ciaa_mask    = 0;
    mask.ciab_mask    = 0x01;
    mask.always       = 0;
    install_ISR (6, do_timer, 127, &mask);
    /* start timer */
    *ciab_cra |= 0x01;
}
