/* $Id: process.c,v 1.23 2000/10/12 09:34:20 pkj Exp $
 * 
 *  linux/arch/etrax100/kernel/process.c
 *
 *  Copyright (C) 1995  Linus Torvalds
 *  Copyright (C) 1998, 1999  Axis Communications AB
 */

/*
 * This file handles the architecture-dependent parts of process handling..
 */

/* Stack handling on Etrax-Linux:
 * Unified stack. A single stack is used both when a program is in kernel- and user-mode.
 * current->kernel_stack_page is allocated by do_fork(). The initial task and kernel_threads
 * use only that. execve() will, in binfmt_aout_flat_cris, allocate a user stack which is
 * saved in current->mm->user_stack and switched to when the user program starts in crt0.o.
 * So as soon as a user program starts, its current->kernel_stack_page is not used, although
 * we don't free it (it will be freed in exit() anyway). Therefore, copy_thread() which needs
 * to copy the stack, should use the kernel_stack_page stack if user_stack == 0, otherwise it
 * should use user_stack. The allocated total size of the user_stack is in mm->user_stack_size,
 * although this is not optimal - maybe the user program has tried to extend its mmap'ed stack
 * because it wants more? Can that happen? Ideally, copy_mm() which is called from do_fork() 
 * should use the actual stack mmap data to duplicate the user stack mmap, just as it would
 * duplicate data segments if we had real MM.
 */

#define __KERNEL_SYSCALLS__
#include <stdarg.h>

#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/stddef.h>
#include <linux/unistd.h>
#include <linux/ptrace.h>
#include <linux/malloc.h>
#include <linux/ldt.h>
#include <linux/user.h>
#include <linux/a.out.h>
#include <linux/interrupt.h>
#include <linux/config.h>
#include <linux/unistd.h>
#include <linux/delay.h>

#include <asm/segment.h>
#include <asm/pgtable.h>
#include <asm/system.h>
#include <asm/io.h>
#include <linux/smp.h>

//#define DEBUG



static int hlt_counter=0;

/* in a system call, set_esp0 is called to remember the stack frame, therefore
   in the implementation of syscalls we can use that value to access the stack
   frame and saved registers.
*/

#define currentregs ((struct pt_regs *)current->tss.esp0)

asmlinkage void set_esp0(unsigned long ssp)
{
  current->tss.esp0 = ssp;
}

void disable_hlt(void)
{
	hlt_counter++;
}

void enable_hlt(void)
{
	hlt_counter--;
}
 
int cpu_idle(void *unused)
{
	while(1) {
		current->counter = -100;
		schedule();
	}
}

asmlinkage int sys_idle(void)
{
	if (current->pid != 0)
		return -EPERM;
	cpu_idle(NULL);
	return 0;
}

/*
 * This routine reboots the machine by asking the keyboard
 * controller to pulse the reset-line low. We try that for a while,
 * and if it doesn't work, we do some other stupid things.
 */

static int reboot_mode = 0;
static int reboot_thru_bios = 0;

void reboot_setup(char *str, int *ints)
{
	while(1) {
		switch (*str) {
		case 'w': /* "warm" reboot (no memory testing etc) */
			reboot_mode = 0x1234;
			break;
		case 'c': /* "cold" reboot (with memory testing etc) */
			reboot_mode = 0x0;
			break;
		case 'b': /* "bios" reboot by jumping through the BIOS */
			reboot_thru_bios = 1;
			break;
		case 'h': /* "hard" reboot by toggling RESET and/or crashing the CPU */
			reboot_thru_bios = 0;
			break;
		}
		if((str = strchr(str,',')) != NULL)
			str++;
		else
			break;
	}
}

/* if the watchdog is enabled, we can simply disable interrupts and go
 * into an eternal loop, and the watchdog will reset the CPU after 0.1s
 */

void hard_reset_now (void)
{
	printk("*** HARD RESET ***\n");
	cli();
	while(1) /* waiting for RETRIBUTION! */ ;
}

void show_regs(struct pt_regs * regs)
{
  printk("IRP: %08lx SRP: %08lx CCR: %08lx oR10: %08lx\n",
	 regs->irp, regs->srp, regs->dccr, regs->orig_r10);
  printk(" r0: %08lx  r1: %08lx  r2: %08lx  r3: %08lx\n",
	 regs->r0, regs->r1, regs->r2, regs->r3);
  printk(" r4: %08lx  r5: %08lx  r6: %08lx  r7: %08lx\n",
	 regs->r4, regs->r5, regs->r6, regs->r7);
  printk(" r8: %08lx  r9: %08lx r10: %08lx r11: %08lx\n",
	 regs->r8, regs->r9, regs->r10, regs->r11);
  printk("r12: %08lx r13: %08lx\n",
	 regs->r12, regs->r13);
}

void show_switch_stack(struct switch_stack *sw)
{
	printk("r0: %08lx r1: %08lx return_ip: %08lx\n",
	       sw->r0, sw->r1, sw->return_ip);
}

/* displays a short stack trace */

int show_stack()
{
	unsigned long *sp = (unsigned long *)rdusp();
	int i;

	printk("Stack dump [0x%p]:\n", sp);
	for (i = 0; i < 16; i++)
		printk("sp + %d: 0x%08lx\n", i * 4, sp[i]);
	return 0;
}

/* due to our unified stack, the kernel stack needs to be as big as the user stack, and not
   just one page.
*/

unsigned long alloc_kernel_stack() {
	unsigned long sp = (unsigned long)kmalloc(current->mm->user_stack_size ? \
						  current->mm->user_stack_size : 4096, GFP_KERNEL);
	/* this way we can detect if the stack was overrun later at exit() */
	*(unsigned long *)sp = STACK_MAGIC;
	return sp;
}

void free_kernel_stack(unsigned long page) 
{
	if (*(unsigned long *) page != STACK_MAGIC)
		panic("Kernel stack overwritten @ 0x%08lx: 0x%08lx != 0x%08x",
		      page, *(unsigned long *) page, STACK_MAGIC);
	kfree((void *)page);
}

/*
 * Free current thread data structures etc..
 */

void exit_thread(void)
{
}

void flush_thread(void)
{
  /* these are from uClinux.. dunno if they are appropriate */
  set_fs(USER_DS);
  current->tss.fs = USER_DS;
}

void release_thread(struct task_struct *dead_task)
{
}

asmlinkage void ret_from_sys_call(void);

/* the usp stack that enters here MUST contain the pt_regs stacked on it */ 

void copy_thread(int nr, unsigned long clone_flags, unsigned long usp,
	struct task_struct *p, struct pt_regs *regs)
{
  struct pt_regs * childregs;
  struct switch_stack *swstack;
  unsigned long stacksize = current->mm->user_stack_size ? current->mm->user_stack_size : 4096;
  unsigned long usedstack;

  /* ok this is the situation: do_fork has allocated a new stack for the clone,
     in p->kernel_stack_page with size stacksize. normally, copy_thread can just
     start out fresh with that stack. but since we have a unified stack for
     kernel/user-mode, and we want the child to be able to return back into
     user mode, we need to duplicate not only the kernel part of the stack
     (that is, the register stack frame) but also the rest of the stack.
  */
	
  usedstack = (unsigned long)current->kernel_stack_page + stacksize - 
    (unsigned long)usp;   /* get size of used stack */
  if(usedstack > stacksize)
	  printk("Achtung Achtung process %d used more stack (%lu) that its size!\n",
		 current->pid, usedstack);
  p->tss.usp = (unsigned long)p->kernel_stack_page + stacksize - usedstack;

#ifdef DEBUG
  printk("kern_stack_page 0x%x, used stack %d, tss.usp 0x%x, usp 0x%x\n", 
	 current->kernel_stack_page, usedstack, p->tss.usp, usp);
#endif
  memcpy((void *)p->tss.usp, (void *)usp, usedstack);        /* copy the stack */
  
  /* return 0 in the child, since this is due to a clone/fork - parent will return PID */

  childregs = (struct pt_regs *)p->tss.usp;
  childregs->r10 = 0; 

  p->tss.esp0 = p->tss.usp;

#ifdef DEBUG
  printk("[copy_thread] child regs, at %x:\n", p->tss.usp);
  show_regs((struct pt_regs *)p->tss.usp);
#endif

  /* _resume which will start this thread, will load tss.usp and pop some stuff 
     including a return adress. we need to push that stuff now, and make
     sure that the new thread starts by jumping to ret_from_sys_call which will pop
     the pt_regs struct and start the thread where the parent were after fork().
  */

  swstack = ((struct switch_stack *)p->tss.usp) - 1;
  swstack->r9 = 0; /* parameter to ret_from_sys_call, 0 == dont restart the syscall */
  swstack->return_ip = (unsigned long)ret_from_sys_call;
  
  p->tss.usp = (unsigned long)swstack;

#ifdef DEBUG
  printk("switchstack is at %x\n", swstack);
  show_switch_stack(swstack);
#endif

  /*
   * Must save the current SFC/DFC value, NOT the value when
   * the parent was last descheduled - RGH  10-08-96
   */
  p->tss.fs = get_fs();
}

/*
 * fill in the fpu structure for a core dump..
 */
int dump_fpu (struct pt_regs * regs, struct user_i387_struct* fpu)
{
  return 0;
}

/*
 * fill in the user structure for a core dump..
 */
void dump_thread(struct pt_regs * regs, struct user * dump)
{
#if 0
	int i;
/* changed the size calculations - should hopefully work better. lbt */
	dump->magic = CMAGIC;
	dump->start_code = 0;
	dump->start_stack = regs->esp & ~(PAGE_SIZE - 1);
	dump->u_tsize = ((unsigned long) current->mm->end_code) >> PAGE_SHIFT;
	dump->u_dsize = ((unsigned long) (current->mm->brk + (PAGE_SIZE-1))) >> PAGE_SHIFT;
	dump->u_dsize -= dump->u_tsize;
	dump->u_ssize = 0;
	for (i = 0; i < 8; i++)
		dump->u_debugreg[i] = current->debugreg[i];  

	if (dump->start_stack < TASK_SIZE)
		dump->u_ssize = ((unsigned long) (TASK_SIZE - dump->start_stack)) >> PAGE_SHIFT;

	dump->regs = *regs;

	dump->u_fpvalid = dump_fpu (regs, &dump->i387);
#endif 
}

/* default sp is the sp after stacking the syscall frame which is saved in tss.esp0 */

asmlinkage int sys_fork()
{
  return do_fork(SIGCHLD, current->tss.esp0, currentregs);
}

asmlinkage int sys_clone(unsigned long newsp, unsigned long flags)
{
  if (!newsp)
    newsp = current->tss.esp0;
  return do_fork(flags, newsp, currentregs);
}

/*
 * sys_execve() executes a new program.
 */
asmlinkage int sys_execve(const char *fname, char **argv, char **envp)
{
  int error;
  char *filename;
  error = getname(fname, &filename);
  if (error)
    return error;
  error = do_execve(filename, argv, envp, currentregs);
  putname(filename);
  return error;
}
