/* $Id: process.c,v 1.2 2005/06/16 13:46:21 cgoh Exp $
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 * Copyright (C) Equator Technologies
 */

#include <linux/types.h>
#include <linux/sched.h> 
#include <linux/unistd.h> 

#include <asm/current.h>
#include <asm/uaccess.h>
#include <asm/elf.h>
#include <asm/cache.h>

#include <asm/debug.h>
#include <asm/assert.h>

#include <etilib/etiLocal.h>
#include <libdev/etiProcessor.h>

#include "regspass.h"
#include "context.h"


/*
 * Do necessary setup to start up a newly executed thread.
 */
 
      static void wrapper() 
      {
        /*
         * Task wrapper: the user mode stack is already current,
         * and therefore the 'current' macro cannot be used here
         * because the usual rounding of the (kernel) stack pointer
         * to achieve the current task pointer does not work;
         * use the global task var instead:
         */
          start_fun_t  start_fun = current_var->tss.start_fun;
          u32         *args      = current_var->tss.args;
          
          u32   argc =       args[0];
          u32   argv = (u32)&args[1];
          u32   env  = (u32)&args[argc+2];

          current_var->tss.current_ds = USER_DS;

        __cle();
          sti();

         /*
          * And execute user mode function:
          */
          run_in_usermode( argc, argv, env, start_fun );
          
          /* falltrough will trap on kernel mode violation */
      }
 
void start_thread(struct pt_regs * regs, unsigned long pc, unsigned long sp)
{
    /*
     * NOTE: 'regs' is allocated as a thread_struct 
     *       on the stack in sys_execve
     */
        struct thread_struct *tss= (void*)regs;
        
	tss->regs.header.u.s.type  = ETI_THREAD_CONTEXT;
    tss->page_cluster_behavior = 0; /* Take default from cluster driver */
	tss->regs.pc               = (u32)wrapper;
	tss->regs.sp               = (u32*) etiROUNDDOWN(sp,dcacheLINE_SIZE);
    tss->regs.excSP            = (((u32)(current)) + KERNEL_STACK_SIZE );
    tss->start_fun             = (start_fun_t)pc;
    tss->args                  = (u32*)sp;
    tss->atomic_start          = 0;
    tss->atomic_end            = 0;

    /* debug state */
    tss->debug_flags           = 0;
    tss->saved_pc              = 0;
}



void exit_thread(void) {}
void flush_thread(void) {}
void release_thread(struct task_struct *dead_task) {}



int copy_thread(int                   nr, 
                unsigned long         clone_flags, 
                unsigned long         usp,
		struct task_struct   *child, 
                struct pt_regs       *regs)
{
    /*
     * NOTE: 'parent_regs' is allocated as a taskContextAndSP struct 
     *       on the stack in sys_clone and sys_fork, with the parent's
     *       return location for 'delta' immediately after the ctxThreadContext
     */
    struct task_struct *parent      = current;
    u32                 delta       = (u32)child   - (u32)current;
    u32                 parentsp    = (u32)&parentsp;
    u32                 childsp     = parentsp + delta;
    u32                 ssize       = (u32)current + KERNEL_STACK_SIZE - parentsp;
    ctxThreadContext_t  childregs   = &child->tss.regs;
    ctxContext_t        parent_regs = (ctxContext_t *)regs;
    u32                *todelta     = &((struct taskContextAndDelta*)parent_regs)->delta;
    u32                *topreturnsp = &((struct taskContextAndDelta*)parent_regs)->sp;
    u32                *topexcsp    = &((struct taskContextAndDelta*)parent_regs)->excSP;
    u32                *tocreturnsp = SHIFT_POINTER(topreturnsp, delta);
    u32                *tocexcsp    = SHIFT_POINTER(topexcsp,    delta);

   /* return delta to parent and child */
    *todelta= delta;

   /* copy registers and stack contents from parent onto child */
	memcpy((char*)childregs, (char*)parent_regs, sizeof(ctxThreadContext));
	memcpy((char*)childsp,   (char*)parentsp,    ssize);

   /* relocate the child's stack pointer from the parent's stack into its own */
    childregs->sp     += delta;

    if (user_mode(parent_regs)) {
       /* user mode cloner, set new sp */
       *tocreturnsp             = usp;
       *tocexcsp               += delta;
        child->tss.current_ds   = USER_DS;
        child->tss.atomic_start = parent->tss.atomic_start;
        child->tss.atomic_end   = parent->tss.atomic_end;

	/*
	 * If user has provided us with a new sp, clone enough of
	 * the parent stack frame to allow loading spilled
	 * registers. 64 bytes should be enough for now.
	 *
	 * Since do_fork() has the parent semaphore, we grab it here
	 * to allow the copy to user space.
	 */
/* TODO: this will only work for clone! This will not work with a new ASID */
	if (usp != *topreturnsp)
	{
	    *tocreturnsp = etiROUNDDOWN(*tocreturnsp - 64 ,dcacheLINE_SIZE);

	    up(&current->mm->mmap_sem);
	    {
	   copy_to_user((void*) *tocreturnsp, (void*) *topreturnsp, 64);
	    }
	    down(&current->mm->mmap_sem);
	}

    } else {
       /* kernel mode cloner, relocate old sp */ 
       *tocreturnsp          += delta;
        child->tss.current_ds = KERNEL_DS;
        child->tss.atomic_start = 0;
        child->tss.atomic_end = 0;
    }
       
	/* return non-zero for error */
	return 0;
}


struct task_struct *__switch_to( struct task_struct *prev, struct task_struct *next )
{
    struct task_struct *result;
    ctxContext_t from, to;

    INITIATE_SWITCH();
    
    CHECK_STACK("S");

    from= TASK_CONTEXT(prev);
    to  = TASK_CONTEXT(next);

    if (SAVE_CONTEXT(from)==0) { 
        RESTORE_CONTEXT(to);
    } 

    result= current_var;

    TERMINATE_SWITCH();

    return result;
}





pid_t kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{
 	pid_t pid;
 
 	pid = __syscall_trap(flags | CLONE_VM, NULL, NULL,NULL,NULL,NULL,NULL,__NR_clone);

 	if (pid) {
 		/* parent */
 		return pid;
 	} else {
 		/* child */
 	       _exit( fn(arg) );
 	}
}



unsigned long __eti_idle_count=0;
void idle(void) 
{
	/* endless idle loop with no priority at all */
	current->priority = 0;
	current->counter = -100;

	while (True) {
		__eti_idle_count++;

		schedule();
	}
}



int dump_fpu(struct pt_regs *regs, elf_fpregset_t *r)
{
	return 0; /* Task didn't use the fpu at all. */
}



void bsp_panic( const char* fmt, ... )
{
   char    buf[1024];
   va_list args;

   va_start( args, fmt );
   vsprintf( buf, fmt, args );
   va_end( args );
   printk( "BSP PANIC: %s\n", buf );
   procHalt(1);
}


