/*
 *  linux/kernel/signal.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
 */

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

#include <linux/sched.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>

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

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

extern int core_dump(long signr,struct pt_regs * regs);
int do_signal(unsigned long oldmask, struct pt_regs * regs);

int sys_sgetmask(void)
{
	return current->blocked;
}

int sys_ssetmask(int newmask)
{
	int old=current->blocked;

	current->blocked = newmask & _BLOCKABLE;
	return old;
}

int sys_sigpending(sigset_t *set)
{
	int error;
	/* fill in "set" with signals pending but blocked. */
	error = verify_area(VERIFY_WRITE, set, 4);
	if (!error)
		put_fs_long(current->blocked & current->signal, (unsigned long *)set);
	return error;
}

/*
 * atomically swap in the new signal mask, and wait for a signal.
 */
int sys_sigsuspend(volatile int restart, volatile 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->d0 = -EINTR;
	while (1) {
		current->state = TASK_INTERRUPTIBLE;
		schedule();
		if (do_signal(mask,regs))
			return -EINTR;
	}
}

/*
 * POSIX 3.3.1.3:
 *  "Setting a signal action to SIG_IGN for a signal that is pending
 *   shall cause the pending signal to be discarded, whether or not
 *   it is blocked" (but SIGCHLD is unspecified: linux leaves it alone).
 *
 *  "Setting a signal action to SIG_DFL for a signal that is pending
 *   and whose default action is to ignore the signal (for example,
 *   SIGCHLD), shall cause the pending signal to be discarded, whether
 *   or not it is blocked"
 *
 * Note the silly behaviour of SIGCHLD: SIG_IGN means that the signal
 * isn't actually ignored, but does automatic child reaping, while
 * SIG_DFL is explicitly said by POSIX to force the signal to be ignored..
 */
static void check_pending(int signum)
{
	struct sigaction *p;

	p = signum - 1 + current->sigaction;
	if (p->sa_handler == SIG_IGN) {
		if (signum == SIGCHLD)
			return;
		current->signal &= ~_S(signum);
		return;
	}
	if (p->sa_handler == SIG_DFL) {
		if (signum != SIGCONT && signum != SIGCHLD && signum != SIGWINCH)
			return;
		current->signal &= ~_S(signum);
		return;
	}
}

int sys_signal(int signum, long handler)
{
	struct sigaction tmp;

	if (signum<1 || signum>32 || signum==SIGKILL || signum==SIGSTOP)
		return -EINVAL;
	tmp.sa_handler = (void (*)(int)) handler;
	tmp.sa_mask = 0;
	tmp.sa_flags = SA_ONESHOT | SA_NOMASK | SA_INTERRUPT;
	tmp.sa_restorer = NULL;
	handler = (long) current->sigaction[signum-1].sa_handler;
	current->sigaction[signum-1] = tmp;
	check_pending(signum);
	return handler;
}

int sys_sigaction(int signum, const struct sigaction * action,
	struct sigaction * oldaction)
{
	struct sigaction new, *p;

	if (signum<1 || signum>32 || signum==SIGKILL || signum==SIGSTOP)
		return -EINVAL;
	p = signum - 1 + current->sigaction;
	if (action) {
		memcpy_fromfs(&new, action, sizeof(struct sigaction));
		if (new.sa_flags & SA_NOMASK)
			new.sa_mask = 0;
		else {
			new.sa_mask |= _S(signum);
			new.sa_mask &= _BLOCKABLE;
		}
	}
	if (oldaction) {
		if (!verify_area(VERIFY_WRITE,oldaction, sizeof(struct sigaction)))
			memcpy_tofs(oldaction, p, sizeof(struct sigaction));
	}
	if (action) {
		*p = new;
		check_pending(signum);
	}
	return 0;
}

extern int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options);

void sys_sigreturn(int signr, unsigned long oldmask, unsigned long unused)
{
	current->blocked = oldmask & _BLOCKABLE;
}

/*
 * This routine sets up the return stack for the first signal found
 * (== last delivered). It makes room for the registers we need to save,
 * but the actual saving is left until the very last moment when we
 * know whether we can restart system calls etc.
 */
static unsigned long * setup_first(struct pt_regs * regs,
	int signr, unsigned long sa_handler, unsigned long oldmask)
{
	unsigned long * tmp_usp;

	regs->usp -= 26*sizeof(long);
	tmp_usp = (unsigned long *) regs->usp;
	verify_area(VERIFY_WRITE,tmp_usp,26*sizeof(long));

	/* set up the "normal" stack seen by the signal handler */

	/* return address from handler (restore code on the stack) */
	put_fs_long(regs->usp+20*sizeof(long)+sizeof(short),tmp_usp);
	/* parameter to handler and sigreturn */
	put_fs_long(signr,tmp_usp+1);
	/* second .. */
	put_fs_long(oldmask,tmp_usp+2);
	/* third parameter to sigreturn */
	put_fs_long(0,tmp_usp+3);
	/* save this frame so that we later can fill in the saved registers */
	return tmp_usp+4;
}

/*
 * This sets up the stack for any stacked signals other than the
 * first one: no need to restore registers etc, as that is done
 * by the very last signal handler return code..
 */
static void setup_other(unsigned long pc, struct pt_regs * regs, int signr,
	unsigned long sa_handler, unsigned long oldmask)
{
	unsigned long * tmp_usp;

	regs->usp -= 9*sizeof(long);
	tmp_usp = (unsigned long *) regs->usp;
	verify_area(VERIFY_WRITE,tmp_usp,9*sizeof(long));

	/*
	 * set up the "normal" stack seen by the signal handler
	 */
	/* return address from handler (restore code on the stack) */
	put_fs_long(regs->usp+5*sizeof(long),tmp_usp);
	/* parameter to handler and sigreturn */
	put_fs_long(signr,tmp_usp+1);
	/* second .. */
	put_fs_long(oldmask,tmp_usp+2);
	/* third parameter to sigreturn */
	put_fs_long(0,tmp_usp+3);

	/* return address */
	put_fs_long(pc,tmp_usp+4);

	/* set up the return code... */
	put_fs_long(0x4cd7000e,tmp_usp+5);    /* moveml sp@,d1-d3 */
	put_fs_long(0x508f588f,tmp_usp+6);    /* addql #8,sp; addql #4,sp */
	put_fs_long(0x70774e40,tmp_usp+7);    /* moveq #119,d0; trap #0 */
	put_fs_long(0x4e740010,tmp_usp+8);    /* rtd #16 */
}

/*
 * 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.
 */
int do_signal(unsigned long oldmask, struct pt_regs * regs)
{
    unsigned short *frame = NULL;
    unsigned long pc = 0;
    unsigned long signr;
    unsigned long sa_handler;
    struct sigaction * sa;

    while ((signr = current->signal & ~current->blocked)) {
	__asm__("bfffo  %2,#0,#0,%1\n\t"
		"bfclr  %0,%1,#1\n\t"
		"negl   %1\n\t"
		"addl   #31,%1"
		:"=m" (current->signal),"=r" (signr)
		:"1" (signr));

	sa = current->sigaction + signr;
	signr++;
	sa_handler = (unsigned long) sa->sa_handler;
	if (sa_handler==1) {
	    /* check for SIGCHLD: it's special */
	    if (signr == SIGCHLD)
		while (sys_waitpid(-1,NULL,WNOHANG) > 0)
		    /* nothing */;
	    continue;
	}
	if (!sa_handler) {
	    if (current->pid == 1)
		continue;
	    switch (signr) {
	      case SIGCONT: case SIGCHLD: case SIGWINCH:
		continue;

	      case SIGSTOP: case SIGTSTP: case SIGTTIN: case SIGTTOU:
		current->state = TASK_STOPPED;
		current->exit_code = signr;
		if (!(current->p_pptr->sigaction[SIGCHLD-1].sa_flags &
		      SA_NOCLDSTOP))
		    send_sig(SIGCHLD, current->p_pptr, 1);
		schedule();
		continue;

	      case SIGQUIT: case SIGILL: case SIGTRAP:
	      case SIGIOT: case SIGFPE: case SIGSEGV:
		if (core_dump(signr,regs))
		    signr |= 0x80;
		/* fall through */
	      default:
		current->signal |= _S(signr & 0x7f);
		do_exit(signr);
	    }
	}
	/*
	 * OK, we're invoking a handler
	 */
	if (regs->orig_d0 >= 0) {
	    if (regs->d0 == -ERESTARTNOHAND ||
		(regs->d0 == -ERESTARTSYS && (sa->sa_flags & SA_INTERRUPT)))
		regs->d0 = -EINTR;
	}
	if (sa->sa_flags & SA_ONESHOT)
	    sa->sa_handler = NULL;
  /* force a supervisor-mode page-in of the signal handler to reduce races */
	__asm__ __volatile__("movesb %0,d0"::"m" (*(char *)sa_handler):"d0");
	if (!frame) {
	    frame = (unsigned short *)setup_first(regs,signr,
						  sa_handler,oldmask);
	} else
	    setup_other(pc,regs,signr,sa_handler,oldmask);
	pc = sa_handler;
	current->blocked |= sa->sa_mask;
	oldmask |= sa->sa_mask;
    }
    if (regs->orig_d0 >= 0 &&
	(regs->d0 == -ERESTARTNOHAND ||
	 regs->d0 == -ERESTARTSYS ||
	 regs->d0 == -ERESTARTNOINTR)) {
	regs->d0 = regs->orig_d0;
	regs->pc -= 2;
    }

    if (!frame)                         /* no handlers installed - return 0 */
	return 0;

    /* save registers if one or more handlers are called.. */
    put_fs_long(regs->d0,(ulong *)frame);
    put_fs_long(regs->d1,(ulong *)(frame+2));
    put_fs_long(regs->d2,(ulong *)(frame+4));
    put_fs_long(regs->d3,(ulong *)(frame+6));
    put_fs_long(regs->d4,(ulong *)(frame+8));
    put_fs_long(regs->d5,(ulong *)(frame+10));
    put_fs_long(regs->d6,(ulong *)(frame+12));
    put_fs_long(regs->d7,(ulong *)(frame+14));
    put_fs_long(regs->a0,(ulong *)(frame+16));
    put_fs_long(regs->a1,(ulong *)(frame+18));
    put_fs_long(regs->a2,(ulong *)(frame+20));
    put_fs_long(regs->a3,(ulong *)(frame+22));
    put_fs_long(regs->a4,(ulong *)(frame+24));
    put_fs_long(regs->a5,(ulong *)(frame+26));
    put_fs_long(regs->a6,(ulong *)(frame+28));
    put_fs_word(regs->sr,frame+30);
    /* original return address */
    put_fs_long(regs->pc,(ulong *)(frame+31));

    /* set up the return code... */
    /* moveml sp@,d1-d3 */
    put_fs_long(0x4cd7000e,(ulong *)(frame+33));
    /* addql  #8,sp; addql #4,sp */
    put_fs_long(0x508f588f,(ulong *)(frame+35));
    /* moveq  #119,d0; trap #0 */
    put_fs_long(0x70774e40,(ulong *)(frame+37));
    /* moveml sp@+,d0-a6 */
    put_fs_long(0x4cdf7fff,(ulong *)(frame+39));
    /* movew  sp@+,ccr */
    put_fs_word(0x44df,frame+41);
    /* rtd #22 */
    put_fs_long(0x4e740016,(ulong *)(frame+42));
    /* "return" to the first handler */
    regs->pc = pc;

    return 1;
}
