diff options
Diffstat (limited to 'arch/openrisc/kernel/signal.c')
| -rw-r--r-- | arch/openrisc/kernel/signal.c | 233 | 
1 files changed, 116 insertions, 117 deletions
diff --git a/arch/openrisc/kernel/signal.c b/arch/openrisc/kernel/signal.c index ae167f7e081..66775bc07a8 100644 --- a/arch/openrisc/kernel/signal.c +++ b/arch/openrisc/kernel/signal.c @@ -28,24 +28,24 @@  #include <linux/tracehook.h>  #include <asm/processor.h> +#include <asm/syscall.h>  #include <asm/ucontext.h>  #include <asm/uaccess.h>  #define DEBUG_SIG 0  struct rt_sigframe { -	struct siginfo *pinfo; -	void *puc;  	struct siginfo info;  	struct ucontext uc;  	unsigned char retcode[16];	/* trampoline code */  }; -static int restore_sigcontext(struct pt_regs *regs, struct sigcontext *sc) +static int restore_sigcontext(struct pt_regs *regs, +			      struct sigcontext __user *sc)  { -	unsigned int err = 0; +	int err = 0; -	/* Alwys make any pending restarted system call return -EINTR */ +	/* Always make any pending restarted system calls return -EINTR */  	current_thread_info()->restart_block.fn = do_no_restart_syscall;  	/* @@ -53,25 +53,21 @@ static int restore_sigcontext(struct pt_regs *regs, struct sigcontext *sc)  	 * (sc is already checked for VERIFY_READ since the sigframe was  	 *  checked in sys_sigreturn previously)  	 */ -	if (__copy_from_user(regs, sc->regs.gpr, 32 * sizeof(unsigned long))) -		goto badframe; -	if (__copy_from_user(®s->pc, &sc->regs.pc, sizeof(unsigned long))) -		goto badframe; -	if (__copy_from_user(®s->sr, &sc->regs.sr, sizeof(unsigned long))) -		goto badframe; +	err |= __copy_from_user(regs, sc->regs.gpr, 32 * sizeof(unsigned long)); +	err |= __copy_from_user(®s->pc, &sc->regs.pc, sizeof(unsigned long)); +	err |= __copy_from_user(®s->sr, &sc->regs.sr, sizeof(unsigned long));  	/* make sure the SM-bit is cleared so user-mode cannot fool us */  	regs->sr &= ~SPR_SR_SM; +	regs->orig_gpr11 = -1;	/* Avoid syscall restart checks */ +  	/* TODO: the other ports use regs->orig_XX to disable syscall checks  	 * after this completes, but we don't use that mechanism. maybe we can  	 * use it now ?  	 */  	return err; - -badframe: -	return 1;  }  asmlinkage long _sys_rt_sigreturn(struct pt_regs *regs) @@ -111,21 +107,18 @@ badframe:   * Set up a signal frame.   */ -static int setup_sigcontext(struct sigcontext *sc, struct pt_regs *regs, -			    unsigned long mask) +static int setup_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc)  {  	int err = 0;  	/* copy the regs */ - +	/* There should be no need to save callee-saved registers here... +	 * ...but we save them anyway.  Revisit this +	 */  	err |= __copy_to_user(sc->regs.gpr, regs, 32 * sizeof(unsigned long));  	err |= __copy_to_user(&sc->regs.pc, ®s->pc, sizeof(unsigned long));  	err |= __copy_to_user(&sc->regs.sr, ®s->sr, sizeof(unsigned long)); -	/* then some other stuff */ - -	err |= __put_user(mask, &sc->oldmask); -  	return err;  } @@ -173,55 +166,53 @@ static inline void __user *get_sigframe(struct k_sigaction *ka,   * trampoline which performs the syscall sigreturn, or a provided   * user-mode trampoline.   */ -static int setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info, -			  sigset_t *set, struct pt_regs *regs) +static int setup_rt_frame(struct ksignal *ksig, sigset_t *set, +			  struct pt_regs *regs)  {  	struct rt_sigframe *frame;  	unsigned long return_ip;  	int err = 0; -	frame = get_sigframe(ka, regs, sizeof(*frame)); +	frame = get_sigframe(&ksig->ka, regs, sizeof(*frame));  	if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) -		goto give_sigsegv; +		return -EFAULT; -	err |= __put_user(&frame->info, &frame->pinfo); -	err |= __put_user(&frame->uc, &frame->puc); +	/* Create siginfo.  */ +	if (ksig->ka.sa.sa_flags & SA_SIGINFO) +		err |= copy_siginfo_to_user(&frame->info, &ksig->info); -	if (ka->sa.sa_flags & SA_SIGINFO) -		err |= copy_siginfo_to_user(&frame->info, info); -	if (err) -		goto give_sigsegv; - -	/* Clear all the bits of the ucontext we don't use.  */ -	err |= __clear_user(&frame->uc, offsetof(struct ucontext, uc_mcontext)); +	/* Create the ucontext.  */  	err |= __put_user(0, &frame->uc.uc_flags);  	err |= __put_user(NULL, &frame->uc.uc_link);  	err |= __save_altstack(&frame->uc.uc_stack, regs->sp); -	err |= setup_sigcontext(&frame->uc.uc_mcontext, regs, set->sig[0]); +	err |= setup_sigcontext(regs, &frame->uc.uc_mcontext);  	err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set));  	if (err) -		goto give_sigsegv; +		return -EFAULT;  	/* trampoline - the desired return ip is the retcode itself */  	return_ip = (unsigned long)&frame->retcode; -	/* This is l.ori r11,r0,__NR_sigreturn, l.sys 1 */ -	err |= __put_user(0xa960, (short *)(frame->retcode + 0)); -	err |= __put_user(__NR_rt_sigreturn, (short *)(frame->retcode + 2)); +	/* This is: +		l.ori r11,r0,__NR_sigreturn +		l.sys 1 +	 */ +	err |= __put_user(0xa960,             (short *)(frame->retcode + 0)); +	err |= __put_user(__NR_rt_sigreturn,  (short *)(frame->retcode + 2));  	err |= __put_user(0x20000001, (unsigned long *)(frame->retcode + 4));  	err |= __put_user(0x15000000, (unsigned long *)(frame->retcode + 8));  	if (err) -		goto give_sigsegv; +		return -EFAULT;  	/* TODO what is the current->exec_domain stuff and invmap ? */  	/* Set up registers for signal handler */ -	regs->pc = (unsigned long)ka->sa.sa_handler; /* what we enter NOW */ +	regs->pc = (unsigned long)ksig->ka.sa.sa_handler; /* what we enter NOW */  	regs->gpr[9] = (unsigned long)return_ip;     /* what we enter LATER */ -	regs->gpr[3] = (unsigned long)sig;           /* arg 1: signo */ +	regs->gpr[3] = (unsigned long)ksig->sig;           /* arg 1: signo */  	regs->gpr[4] = (unsigned long)&frame->info;  /* arg 2: (siginfo_t*) */  	regs->gpr[5] = (unsigned long)&frame->uc;    /* arg 3: ucontext */ @@ -229,25 +220,16 @@ static int setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,  	regs->sp = (unsigned long)frame;  	return 0; - -give_sigsegv: -	force_sigsegv(sig, current); -	return -EFAULT;  }  static inline void -handle_signal(unsigned long sig, -	      siginfo_t *info, struct k_sigaction *ka, -	      struct pt_regs *regs) +handle_signal(struct ksignal *ksig, struct pt_regs *regs)  {  	int ret; -	ret = setup_rt_frame(sig, ka, info, sigmask_to_save(), regs); -	if (ret) -		return; +	ret = setup_rt_frame(ksig, sigmask_to_save(), regs); -	signal_delivered(sig, info, ka, regs, -				 test_thread_flag(TIF_SINGLESTEP)); +	signal_setup_done(ret, ksig, test_thread_flag(TIF_SINGLESTEP));  }  /* @@ -262,82 +244,99 @@ handle_signal(unsigned long sig,   * mode below.   */ -void do_signal(struct pt_regs *regs) +int do_signal(struct pt_regs *regs, int syscall)  { -	siginfo_t info; -	int signr; -	struct k_sigaction ka; - -	/* -	 * We want the common case to go fast, which -	 * is why we may in certain cases get here from -	 * kernel mode. Just return without doing anything -	 * if so. -	 */ -	if (!user_mode(regs)) -		return; - -	signr = get_signal_to_deliver(&info, &ka, regs, NULL); - -	/* If we are coming out of a syscall then we need -	 * to check if the syscall was interrupted and wants to be -	 * restarted after handling the signal.  If so, the original -	 * syscall number is put back into r11 and the PC rewound to -	 * point at the l.sys instruction that resulted in the -	 * original syscall.  Syscall results other than the four -	 * below mean that the syscall executed to completion and no -	 * restart is necessary. -	 */ -	if (regs->orig_gpr11) { -		int restart = 0; - -		switch (regs->gpr[11]) { +	struct ksignal ksig; +	unsigned long continue_addr = 0; +	unsigned long restart_addr = 0; +	unsigned long retval = 0; +	int restart = 0; + +	if (syscall) { +		continue_addr = regs->pc; +		restart_addr = continue_addr - 4; +		retval = regs->gpr[11]; + +		/* +		 * Setup syscall restart here so that a debugger will +		 * see the already changed PC. +		 */ +		switch (retval) {  		case -ERESTART_RESTARTBLOCK: +			restart = -2; +			/* Fall through */  		case -ERESTARTNOHAND: -			/* Restart if there is no signal handler */ -			restart = (signr <= 0); -			break;  		case -ERESTARTSYS: -			/* Restart if there no signal handler or -			 * SA_RESTART flag is set */ -			restart = (signr <= 0 || (ka.sa.sa_flags & SA_RESTART)); -			break;  		case -ERESTARTNOINTR: -			/* Always restart */ -			restart = 1; +			restart++; +			regs->gpr[11] = regs->orig_gpr11; +			regs->pc = restart_addr;  			break;  		} - -		if (restart) { -			if (regs->gpr[11] == -ERESTART_RESTARTBLOCK) -				regs->gpr[11] = __NR_restart_syscall; -			else -				regs->gpr[11] = regs->orig_gpr11; -			regs->pc -= 4; -		} else { -			regs->gpr[11] = -EINTR; -		}  	} -	if (signr <= 0) { -		/* no signal to deliver so we just put the saved sigmask -		 * back */ +	/* +	 * Get the signal to deliver.  During the call to get_signal the +	 * debugger may change all our registers so we may need to revert +	 * the decision to restart the syscall; specifically, if the PC is +	 * changed, don't restart the syscall. +	 */ +	if (get_signal(&ksig)) { +		if (unlikely(restart) && regs->pc == restart_addr) { +			if (retval == -ERESTARTNOHAND || +			    retval == -ERESTART_RESTARTBLOCK +			    || (retval == -ERESTARTSYS +			        && !(ksig.ka.sa.sa_flags & SA_RESTART))) { +				/* No automatic restart */ +				regs->gpr[11] = -EINTR; +				regs->pc = continue_addr; +			} +		} +		handle_signal(&ksig, regs); +	} else { +		/* no handler */  		restore_saved_sigmask(); -	} else {		/* signr > 0 */ -		/* Whee!  Actually deliver the signal.  */ -		handle_signal(signr, &info, &ka, regs); +		/* +		 * Restore pt_regs PC as syscall restart will be handled by +		 * kernel without return to userspace +		 */ +		if (unlikely(restart) && regs->pc == restart_addr) { +			regs->pc = continue_addr; +			return restart; +		}  	} -	return; +	return 0;  } -asmlinkage void do_notify_resume(struct pt_regs *regs) +asmlinkage int +do_work_pending(struct pt_regs *regs, unsigned int thread_flags, int syscall)  { -	if (current_thread_info()->flags & _TIF_SIGPENDING) -		do_signal(regs); - -	if (current_thread_info()->flags & _TIF_NOTIFY_RESUME) { -		clear_thread_flag(TIF_NOTIFY_RESUME); -		tracehook_notify_resume(regs); -	} +	do { +		if (likely(thread_flags & _TIF_NEED_RESCHED)) { +			schedule(); +		} else { +			if (unlikely(!user_mode(regs))) +				return 0; +			local_irq_enable(); +			if (thread_flags & _TIF_SIGPENDING) { +				int restart = do_signal(regs, syscall); +				if (unlikely(restart)) { +					/* +					 * Restart without handlers. +					 * Deal with it without leaving +					 * the kernel space. +					 */ +					return restart; +				} +				syscall = 0; +			} else { +				clear_thread_flag(TIF_NOTIFY_RESUME); +				tracehook_notify_resume(regs); +			} +		} +		local_irq_disable(); +		thread_flags = current_thread_info()->flags; +	} while (thread_flags & _TIF_WORK_MASK); +	return 0;  }  | 
