/* $Id: signal.c,v 1.2 2005/06/16 13:46:21 cgoh Exp $
 *
 *  linux/arch/mips/kernel/signal.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 *  Copyright (C) 1994, 1995, 1996, 1997, 1998  Ralf Baechle
 */
#include <linux/config.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/smp.h>
#include <linux/smp_lock.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/bitops.h>
#include <asm/pgtable.h>
#include <asm/uaccess.h>
#include <asm/assert.h>

#include "context.h"

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

int sys_wait4(pid_t pid, unsigned long *stat_addr,
                         int options, unsigned long *ru);

int do_signal(sigset_t *oldset, ctxContext_t regs);



/* ------------------------------------------------------------------------- */



int sys_sigreturn()
{
	return -ENOSYS;
}

int sys_rt_sigreturn()
{
	return -ENOSYS;
}

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

int
sys_rt_sigsuspend(const sigset_t *mask)
{
    return sys_sigsuspend(mask);
}



int 
sys_sigaction(int sig, const struct sigaction *act, struct sigaction *oact)
{
	struct k_sigaction new_sa, old_sa;
	int ret;

	if (act) {
	        if (copy_from_user(&new_sa.sa, act, sizeof(new_sa.sa)) )
			return -EFAULT;
	}

	ret = do_sigaction(sig, act ? &new_sa : NULL, oact ? &old_sa : NULL);

   	if (!ret && oact) {
		if (copy_to_user(oact, &old_sa.sa, sizeof(old_sa.sa)))
			return -EFAULT;
	}

	return ret;
}



int
sys_sigaltstack(const stack_t *uss, stack_t *uoss,
                void *padd2, void *padd3, void *padd4, void *padd5, void *padd6, 
                ctxSyscallContext_t syscall_context )
{
	return do_sigaltstack(uss, uoss, syscall_context->sp);
}



int
sys_sigsuspend(const sigset_t *uset, 
               void* padd1, void *padd2, void *padd3, void *padd4, void *padd5, void *padd6, 
               ctxSyscallContext_t syscall_context )
{
        sigset_t saveset,     newset;

        if (copy_from_user(&newset, uset, sizeof(sigset_t)))
                return -EFAULT;

        sigdelsetmask(&newset, ~_BLOCKABLE);

        spin_lock_irq(&current->sigmask_lock);
        saveset = current->blocked;
        current->blocked = newset;
        recalc_sigpending(current);
        spin_unlock_irq(&current->sigmask_lock);

        syscall_context->result= -EINTR;
        
        while (1) {
                current->state = TASK_INTERRUPTIBLE;
                schedule();
                if (do_signal(&saveset, syscall_context))
                        return -EINTR;
        }
}





/* ------------------------------------------------------------------------- */



struct sigframe {
        ctxExceptionContext  context;
	sigset_t             blocked;
};



static inline void *
get_sigframe(struct k_sigaction *ka, ctxContext_t context, size_t frame_size)
{
	unsigned long sp;

        switch (context->u.header.u.s.type) {
        case ETI_SYSCALL_CONTEXT    : sp= context->u.syscall.sp;     break;
        case ETI_EXCEPTION_CONTEXT  : sp= context->u.exc.cl[0].r16;  break;
        default: ASSERT(0,("UNEXPECTED CONTEXT TYPE"));
        }    

        /* This is the X/Open sanctioned signal stack switching.  */
	if ((ka->sa.sa_flags & SA_ONSTACK) && ! on_sig_stack(sp) ) {
            sp = current->sas_ss_sp + current->sas_ss_size;
	}

	return (void *)((sp - frame_size) & ~(L1_CACHE_BYTES-1));
}



static void retry_syscall( ctxSyscallContext_t ctx)
{
	if ( ctx->result == -ERESTARTSYS
	  || ctx->result == -ERESTARTNOINTR
           ) {
            ctx->result= syscall_handler( ctx->a0, ctx->a1, ctx->a2, ctx->a3, 
                                           ctx->a4, ctx->a5, ctx->a6, ctx->function_code, ctx );
        }
}
    
            
void do_sigreturn(ctxExceptionContext_t  ctx)
{
	struct sigframe    *frame;
	sigset_t blocked;

	frame = (struct sigframe *)ctx->cl[0].r16;
        
	if (!access_ok(VERIFY_READ, frame, sizeof(struct sigframe)))     goto badframe;
	if ( (unsigned long)frame & (L1_CACHE_BYTES - 1))                goto badframe;
                
	if (copy_from_user(&blocked, &frame->blocked, sizeof(blocked)))  goto badframe;
	if (copy_from_user(ctx,      &frame->context, sizeof(*ctx)))     goto badframe;

        if ((ctx->header.u.s.psw & 0xa0000)  !=   0xa0000)               goto badframe;

	sigdelsetmask(&blocked, ~_BLOCKABLE);
	spin_lock_irq(&current->sigmask_lock);
	current->blocked = blocked;
	recalc_sigpending(current);
	spin_unlock_irq(&current->sigmask_lock);

	if (ctx->header.u.s.type == ETI_SYSCALL_CONTEXT) {
            retry_syscall(ctx);
	}

        __cle();
    	ctxRestoreContext(ctx);
        
badframe:
	lock_kernel();
	do_exit(SIGSEGV);
	unlock_kernel();
}



static void 
setup_frame(struct k_sigaction * ka, ctxContext_t regs,
            int signr, sigset_t *oldmask)
{
        ctxInitialContext_t  initial = regs;
	struct sigframe      *frame    = get_sigframe(ka,regs,sizeof(struct sigframe));
        Register              excSP;                

        switch (regs->u.header.u.s.type) {
        case ETI_SYSCALL_CONTEXT    : excSP= ((ctxSyscallContext_t)regs)->excSP; break;
        case ETI_EXCEPTION_CONTEXT  : excSP= ((ctxExceptionContext_t)regs)->excSP; break;
        default: ASSERT(0,("UNKNOWN CONTEXT TYPE"));
        }    

	if (verify_area(VERIFY_WRITE, frame, sizeof (*frame)))            goto segv_and_exit;
	if (copy_to_user(&frame->blocked, oldmask, sizeof(*oldmask)))     goto segv_and_exit;
	if (copy_to_user(&frame->context, regs,    ctxContextSize(regs))) goto segv_and_exit;


        initial->header.u.s.type= ETI_INITIAL_CONTEXT;
        initial->header.u.s.psw = hmpv_read_psw() | bspregPL;/* user mode         */
        initial->a0    = signr;		              /* arguments         */
        initial->a1    = 0;                                  /* should be cause   */
        initial->a2    = (unsigned long) frame;              /* ptr to sigcontext */
        initial->sp    = (unsigned long) frame;              /* Stack pointer     */
        initial->pc    = (unsigned long) ka->sa.sa_handler;  /* handler address   */
        initial->excSP = excSP;
	if (current->tss.signal_return != NULL) {
	    initial->rp= current->tss.signal_return;         /* Return address    */
	} else {
	    initial->rp= BSP_SIGNAL_HANDLER_RETURN;
	}

        return;

segv_and_exit:
	lock_kernel();
	do_exit(SIGSEGV);
	unlock_kernel();
}


static void 
setup_rt_frame(struct k_sigaction * ka, ctxExceptionContext_t regs,
            int signr, sigset_t *oldmask, siginfo_t *info)
{
    setup_frame(ka,regs,signr,oldmask);
}

/* ------------------------------------------------------------------------- */


static inline void handle_signal(unsigned long sig, struct k_sigaction *ka,
	   siginfo_t *info, sigset_t *oldset, ctxExceptionContext_t regs)
{
    	if (ka->sa.sa_flags & SA_SIGINFO)
		setup_rt_frame(ka, regs, sig, oldset, info);
	else
		setup_frame(ka, regs, sig, oldset);

	if (ka->sa.sa_flags & SA_ONESHOT)
		ka->sa.sa_handler = SIG_DFL;
        
	if (!(ka->sa.sa_flags & SA_NODEFER)) {
		spin_lock_irq(&current->sigmask_lock);
		sigorsets(&current->blocked,&current->blocked,&ka->sa.sa_mask);
		sigaddset(&current->blocked,sig);
		recalc_sigpending(current);
		spin_unlock_irq(&current->sigmask_lock);
	}
}



static void syscall_restart(int *result, struct k_sigaction *ka)
{
        switch(*result) {
        case -ERESTARTNOHAND:
                *result = -EINTR;
                break;
        case -ERESTARTSYS:
                if (!(ka->sa.sa_flags & SA_RESTART)) {
                        *result = -EINTR;
                        break;
                }
                /* fallthrough */
        case -ERESTARTNOINTR:
        }
}




int do_signal(sigset_t *oldset, ctxContext_t regs)
{
	struct k_sigaction *ka;
	siginfo_t info;

	if (!oldset)
		oldset = &current->blocked;

    	for (;;) {
		unsigned long signr;

		spin_lock_irq(&current->sigmask_lock);
		signr = dequeue_signal(&current->blocked, &info);
		spin_unlock_irq(&current->sigmask_lock);

		if (!signr)
			break;

		if ((current->ptrace & PT_PTRACED) && signr != SIGKILL) {
			/* Let the debugger run.  */
			current->exit_code = signr;
			current->state = TASK_STOPPED;
			notify_parent(current, SIGCHLD);
			schedule();

			/* We're back.  Did the debugger cancel the sig?  */
			if (!(signr = current->exit_code))
				continue;
			current->exit_code = 0;

			/* The debugger continued.  Ignore SIGSTOP.  */
			if (signr == SIGSTOP)
				continue;

			/* Update the siginfo structure.  Is this good?  */
			if (signr != info.si_signo) {
				info.si_signo = signr;
				info.si_errno = 0;
				info.si_code = SI_USER;
				info.si_pid = current->p_pptr->pid;
				info.si_uid = current->p_pptr->uid;
			}

			/* If the (new) signal is now blocked, requeue it.  */
			if (sigismember(&current->blocked, signr)) {
				send_sig_info(signr, &info, current);
				continue;
			}
		}

		ka = &current->sig->action[signr-1];
		if (ka->sa.sa_handler == SIG_IGN) {
			if (signr != SIGCHLD)
				continue;
			/* Check for SIGCHLD: it's special.  */
			while (sys_wait4(-1, NULL, WNOHANG, NULL) > 0)
				/* nothing */;
			continue;
		}

		if (ka->sa.sa_handler == SIG_DFL) {
			int exit_code = signr;

			/* Init gets no signals it doesn't want.  */
			if (current->pid == 1) {
                                ASSERT(0,("INIT PROCESS CRASHED"));
				continue;
                        }
                        
			switch (signr) {
			case SIGCONT: case SIGCHLD: case SIGWINCH:
				continue;

			case SIGTSTP: case SIGTTIN: case SIGTTOU:
				if (is_orphaned_pgrp(current->pgrp))
					continue;
				/* FALLTHRU */

			case SIGSTOP:
				current->state = TASK_STOPPED;
				current->exit_code = signr;
				if (!(current->p_pptr->sig->action[SIGCHLD-1].sa.sa_flags & SA_NOCLDSTOP))
					notify_parent(current, SIGCHLD);
				schedule();
				continue;

			case SIGQUIT: case SIGILL: case SIGTRAP:
			case SIGABRT: case SIGFPE: case SIGSEGV:
			case SIGBUS:
				if (do_coredump(signr, regs))
					exit_code |= 0x80;
				/* FALLTHRU */

			default:
    				lock_kernel();
				sigaddset(&current->signal, signr);
				recalc_sigpending(current);
				current->flags |= PF_SIGNALED;
				do_exit(exit_code);
				/* NOTREACHED */
			}
		}

    		if (regs->u.header.u.s.type == ETI_SYSCALL_CONTEXT) {
                        syscall_restart(&regs->u.syscall.result, ka);
                }
                    
		/* Whee!  Actually deliver the signal.  */
		handle_signal(signr, ka, &info, oldset, regs);
                
		return 1;
	}

        if (regs->u.header.u.s.type == ETI_SYSCALL_CONTEXT) {
            retry_syscall(regs);
        }

	return 0;
}

