/* $Id: signal.c,v 1.13 2000/01/17 18:13:40 bjornw Exp $
 *
 *  linux/arch/etrax100/kernel/signal.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 *  Copyright (C) 1998        Axis Communications AB
 */

#include <linux/config.h>

#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/kernel.h>
#include <linux/signal.h>
#include <linux/errno.h>
#include <linux/wait.h>
#include <linux/ptrace.h>
#include <linux/unistd.h>

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

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

#define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP)))

/* a syscall in EtraxLinux is a jir _system_call which is 6 bytes */
/* manipulate regs so that upon return, it will be re-executed */
#define RESTARTETRAXSYS regs->r10 = regs->orig_r10; /* restore r10 since we've trashed it */ \
		        regs->irp -= 6;             /* my god this is ugly :) */

asmlinkage int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options);
asmlinkage int do_signal(int canrestart, unsigned long oldmask, struct pt_regs * regs);

#undef DEBUG

/*
 * atomically swap in the new signal mask, and wait for a signal.
 */
asmlinkage int sys_sigsuspend(int restart, unsigned long oldmask, unsigned long set)
{
	unsigned long mask;
	struct pt_regs * regs = (struct pt_regs *) &restart;

	mask = current->blocked;
	current->blocked = set & _BLOCKABLE;

	regs->r10 = -EINTR; /* set a return value to -EINTR */

	while (1) {
		current->state = TASK_INTERRUPTIBLE;
		schedule();
		if (do_signal(0, mask,regs))
			return -EINTR;
	}
}

/* setup_frame and sys_sigreturn is the pair that sets up and destroys the signal
   context. The context needs to restore the regs so that they are like they were
   when the original syscall which caused the signal handling was run. The signal
   handler should actually only mess with r9-r13 I think.. but for now we let the 
   signal context contain a complete regs structure. 
   
   The sigcontext_struct is defined in include/asm-etrax100/sigcontext.h.

   Linux seems to want to disable signal checks when sys_sigreturn returns. We can't
   do that since we don't have a regs entry which can be used to tell ret_from_sys_call
   that we don't want the syscall check. But it will work anyway.

*/

asmlinkage int sys_sigreturn(unsigned long __unused)
{
	/* the syscall regs are pointed to by tss.esp0, and the signal context pushed
	   by setup_frame before, is just on top of that */
	struct pt_regs *regs = (struct pt_regs *)current->tss.esp0;
	struct sigcontext_struct *context = 
		(struct sigcontext_struct *)(current->tss.esp0 + sizeof(struct pt_regs));
	current->tss.esp0 = (unsigned long)context + 4;
	/* restore the signal mask */
	current->blocked = context->oldmask & _BLOCKABLE;

#ifdef DEBUG
	printk("sigreturn. ok. context irp is 0x%p, oldmask is 0x%x, r10 is 0x%x.\n",
	       context->regs.irp, context->oldmask, context->regs.r10);
#endif	
	/* Now restore the stack pointer. we rely heavily on that the compiler has NOT
	   pushed SRP or any regs onto the stack and is trying to pop them below!!!
	   We also note that entry.S does an addq 4,sp just after calling a syscall, so
	   while we should really set sp below to &context->regs, we set it to context
	   which is 4 bytes below due to the context->oldmask member. Voodoo magic man...
	*/

	wrusp(context);

	/* return to ret_from_sys_call which will return to the original user program */
	/* entry.S stores what we return here into sp+LR10 so return that value so we
	   dont trash anything for the user program */
	return context->regs.r10;
}

void do_sigreturn()
{
	/* This is reached after the user-mode signal handler is finished. the sigreturn
	   syscall will restore the sigcontext and continue operation, this call never
	   returns to do_sigreturn.
	   
	   It is VITAL that the compiler doesn't stack anything in this function before the
	   systemcall!
	*/
	__asm__ __volatile__ ("movu.w %0,r1\n\t"
			      "jir _system_call\n\t"
			      : /* no outputs */
			      : "" (__NR_sigreturn));
	/* NOT REACHED */
}


/*
 * Set up a signal frame. Up to now, the stack contains user stack data, a pt_regs structure
 * pushed by the kernel entry, and kernel stack data. The strategy here is to insert a 
 * sigcontext_struct structure between the user stack data and the pt_regs, containing among
 * others the old signal mask and a copy of the pt_regs struct (analogous to an IRQ entry).
 * Then the pt_regs IRP is redirected into the users signal handler, and SRP is set to code
 * which performs sigreturn. When ret_from_syscall exits now, the pt_regs structure will be
 * popped and the thread jumps to the signal handler, with the sigcontext as the topmost thing
 * on the stack. After the signal handler is done, it jumps to the SRP contents which should
 * invoke the sigreturn syscall, which would add another pt_regs structure in kernel entry
 * and subsequently the sys_sigreturn function above has to restore the signal mask and change
 * the stackpointer so that upon kernel exit, the pt_regs inside the sigcontext struct is
 * popped, and everything is restored to the way it was before the signal was detected.
 */

static inline void setup_frame(struct sigaction * sa,
	struct pt_regs * regs, int signr,
	unsigned long oldmask)
{
	/* this is _really_ volatile man! after this code, pointers to stack variables 
	   are corrupt and must not be used. but maybe the compiler generates some!

	   there is some additional magic here. since the sigcontext_struct contains a
	   pt_regs struct, and the pt_regs struct we want to copy into the sigcontext already
	   is on the stack where we will make room for the sigcontext, we obviously design the
	   sigcontext layout so it fits and we dont have to copy it an extra time.. 
	*/

	register struct pt_regs *newregs;

	__asm__ __volatile__ ("move.d sp,r0\n\t" /* r0 = oldsp */
			      "move.d %1,r1\n\t"
			      "sub.d r0,r1\n\t"  /* r1 = regs - oldsp */
			      "addu.w %2,r1\n\t" /* splen(r1) = (regs + 1) - oldsp */
			      "move.d r0,%0\n\t"
			      "subu.w %3,%0\n\t" /* newsp(%0) = oldsp - sizeof(sigcontext) */
			      "move.d %0,sp\n\t" /* load the new stack pointer */
			      "lsrq 1,r1\n"      /* copy words, so divide count by 2 */ 
			      "1:\n\t"           /* copy from oldsp to newsp, backwards */
			      "move.w [r0+],r3\n\t"
			      "subq 1,r1\n\t"
			      "bne 1b\n\t"
			      "move.w r3,[%0+]\n\t"
			      /* %0 now points to the sigcontext_struct */
			      "move.d %4,[%0]\n\t" /* insert oldmask in the sigcontext */
			      "subu.w %2,%0\n\t"   /* %0 now points to the regs new location */
			      : "=r" (newregs)
			      : "r" (regs),
			        "" (sizeof(struct pt_regs)), 
			        "" (sizeof(struct sigcontext_struct)),
			        "r" (oldmask)
			      : "r0", "r1", "r2", "r3", "memory");

	/* Set up registers for signal handler */
	newregs->irp = (unsigned long) sa->sa_handler; /* signal handlers entry point */
	newregs->srp = (unsigned long) do_sigreturn;   /* what we want to do after the handler */
	newregs->r10 = signr;                          /* 1 argument to the handler - the signal nbr */
}

/*
 * OK, we're invoking a handler
 */	
static void handle_signal(int canrestart, unsigned long signr, struct sigaction *sa,
	unsigned long oldmask, struct pt_regs * regs)
{
#ifdef DEBUG
	printk("handle_signal, canrestart %d, signr %d.\n", canrestart, signr);
#endif
	/* are we from a system call, that is, can we restart? */
	if (canrestart) {
		/* If so, check system call restarting.. the syscall has returned
		   an internal error code, which describes how it wants restart to
		   work */
		switch (regs->r10) {
			case -ERESTARTNOHAND:
				/* ERESTARTNOHAND means that the syscall should only be
				   restarted if there was no handler for the signal, and since
				   we only get here if there is a handler, we dont restart */
				regs->r10 = -EINTR;
				break;

			case -ERESTARTSYS:
				/* ERESTARTSYS means to restart the syscall if there is no
				   handler or the handler was registered with SA_RESTART */
				if (!(sa->sa_flags & SA_RESTART)) {
					regs->r10 = -EINTR;
					break;
				}
				/* fallthrough */
			case -ERESTARTNOINTR:
				/* ERESTARTNOINTR means that the syscall should be called again
				   after the signal handler returns. */
				RESTARTETRAXSYS;
		}
	}

	/* set up the stack frame */
	setup_frame(sa, regs, signr, oldmask);

	if (sa->sa_flags & SA_ONESHOT)
		sa->sa_handler = NULL;
	if (!(sa->sa_flags & SA_NOMASK))
		current->blocked |= (sa->sa_mask | _S(signr)) & _BLOCKABLE;
}

/*
 * Note that 'init' is a special process: it doesn't get signals it doesn't
 * want to handle. Thus you cannot kill init even with a SIGKILL even by
 * mistake.
 *
 * Note that we go through the signals twice: once to check the signals that
 * the kernel can handle, and then we build all the user-level signal handling
 * stack-frames in one go after that.
 */

/* The story so far:
 * A system call or interrupt has occured, thereby pushing a pt_regs compatible
 * structure onto the stack. The syscall or interrupt has been handled, and
 * ret_from_sys_call is jumped to. current->signal was checked and we've figured
 * out that we need to handle signals, so we've called do_signal. sp is pointing
 * at the pt_regs structure, because ret_from_sys_call was just about to pop in
 * Rexit when it decided to call here. Beware that current->tss.esp0 will not
 * point correctly if entry was through an interrupt (its only set by a syscall).
 *
 * -So what will happen next?
 * When do_signal returns, ret_from_sys_call will pop the pt_regs structure and
 * return to the user program. We want it to return into the signal handler instead,
 * so we need to modify regs->irp. When the signal handler is done, it should return
 * into our function do_sigreturn which performs the sys_sigreturn syscall to clean
 * up the stack. To achieve this, we set regs->srp before going into the signal handler.
 *
 * The linux kernel version this is built on, doesn't stack multiple signal handlers if
 * more than one signal was active.
 *
 */


asmlinkage int do_signal(int canrestart, unsigned long oldmask, struct pt_regs *regs)
{
	unsigned long mask = ~current->blocked;
	unsigned long signr;
	struct sigaction * sa;

	if(canrestart != 0 && canrestart != 1)
		printk("do_signal: bogus canrestart == %d.\n", canrestart);

#ifdef DEBUG
	printk("do_signal(%d, 0x%x, ...)\n", canrestart, oldmask);
#endif
	while ((signr = current->signal & mask)) {
		/*
		 *	This stops gcc flipping out. Otherwise the assembler
		 *	including volatiles for the inline function to get
		 *	current combined with this gets it confused.
		 */
		struct task_struct *t=current;
		signr = find_first_one(t->signal);  /* get first signal to process */
		t->signal &= ~(1U << signr);            /* clear the signal we're processing */
		sa = current->sig->action + signr;
		signr++;
		if ((current->flags & PF_PTRACED) && signr != SIGKILL) {
			current->exit_code = signr;
			current->state = TASK_STOPPED;
			notify_parent(current, SIGCHLD);
			schedule();
			if (!(signr = current->exit_code))
				continue;
			current->exit_code = 0;
			if (signr == SIGSTOP)
				continue;
			if (_S(signr) & current->blocked) {
				current->signal |= _S(signr);
				continue;
			}
			sa = current->sig->action + signr - 1;
		}
		if (sa->sa_handler == SIG_IGN) {
			if (signr != SIGCHLD)
				continue;
			/* check for SIGCHLD: it's special */
			while (sys_waitpid(-1,NULL,WNOHANG) > 0)
				/* nothing */;
			continue;
		}
		if (sa->sa_handler == SIG_DFL) {
			if (current->pid == 1)
				continue;
			switch (signr) {
				case SIGCONT: case SIGCHLD: case SIGWINCH:
					continue;

				case SIGTSTP: case SIGTTIN: case SIGTTOU:
					if (is_orphaned_pgrp(current->pgrp))
						continue;
				case SIGSTOP:
					if (current->flags & PF_PTRACED)
						continue;
					current->state = TASK_STOPPED;
					current->exit_code = signr;
					if (!(current->p_pptr->sig->action[SIGCHLD-1].sa_flags & 
					      SA_NOCLDSTOP))
						notify_parent(current, SIGCHLD);
					schedule();
					continue;

				case SIGQUIT: case SIGILL: case SIGTRAP:
				case SIGABRT: case SIGFPE: case SIGSEGV:
					if (current->binfmt && current->binfmt->core_dump) {
						if (current->binfmt->core_dump(signr, regs))
							signr |= 0x80;
					}
				/* fall through */
				default:
					current->signal |= _S(signr & 0x7f);
					current->flags |= PF_SIGNALED;
					do_exit(signr);
			}
		}
		handle_signal(canrestart, signr, sa, oldmask, regs);
		return 1;
	}

	/* Did we come from a system call? */
	if (canrestart) {
		/* Restart the system call - no handlers present */
		if (regs->r10 == -ERESTARTNOHAND ||
		    regs->r10 == -ERESTARTSYS ||
		    regs->r10 == -ERESTARTNOINTR) {
			RESTARTETRAXSYS;
		}
	}
	return 0;
}



