diff options
author | Ley Foon Tan <lftan@altera.com> | 2014-11-06 10:19:59 +0300 |
---|---|---|
committer | Ley Foon Tan <lftan@altera.com> | 2014-12-08 07:55:59 +0300 |
commit | b53e906d255d7bc3539c2729afb8a18c309cd41e (patch) | |
tree | 2cd954b17aad5bdf540e23023e82d6ca2871c361 /arch/nios2/kernel | |
parent | 1000197d801329804d30094aef5dba0265204d17 (diff) | |
download | linux-b53e906d255d7bc3539c2729afb8a18c309cd41e.tar.xz |
nios2: Signal handling support
This patch adds support for signal handling.
Signed-off-by: Ley Foon Tan <lftan@altera.com>
Diffstat (limited to 'arch/nios2/kernel')
-rw-r--r-- | arch/nios2/kernel/signal.c | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/arch/nios2/kernel/signal.c b/arch/nios2/kernel/signal.c new file mode 100644 index 000000000000..f9d27883a714 --- /dev/null +++ b/arch/nios2/kernel/signal.c @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2013-2014 Altera Corporation + * Copyright (C) 2011-2012 Tobias Klauser <tklauser@distanz.ch> + * Copyright (C) 2004 Microtronix Datacom Ltd + * Copyright (C) 1991, 1992 Linus Torvalds + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + */ + +#include <linux/signal.h> +#include <linux/errno.h> +#include <linux/ptrace.h> +#include <linux/uaccess.h> +#include <linux/unistd.h> +#include <linux/personality.h> +#include <linux/tracehook.h> + +#include <asm/ucontext.h> +#include <asm/cacheflush.h> + +#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP))) + +/* + * Do a signal return; undo the signal stack. + * + * Keep the return code on the stack quadword aligned! + * That makes the cache flush below easier. + */ + +struct rt_sigframe { + struct siginfo info; + struct ucontext uc; +}; + +static inline int rt_restore_ucontext(struct pt_regs *regs, + struct switch_stack *sw, + struct ucontext *uc, int *pr2) +{ + int temp; + greg_t *gregs = uc->uc_mcontext.gregs; + int err; + + /* Always make any pending restarted system calls return -EINTR */ + current_thread_info()->restart_block.fn = do_no_restart_syscall; + + err = __get_user(temp, &uc->uc_mcontext.version); + if (temp != MCONTEXT_VERSION) + goto badframe; + /* restore passed registers */ + err |= __get_user(regs->r1, &gregs[0]); + err |= __get_user(regs->r2, &gregs[1]); + err |= __get_user(regs->r3, &gregs[2]); + err |= __get_user(regs->r4, &gregs[3]); + err |= __get_user(regs->r5, &gregs[4]); + err |= __get_user(regs->r6, &gregs[5]); + err |= __get_user(regs->r7, &gregs[6]); + err |= __get_user(regs->r8, &gregs[7]); + err |= __get_user(regs->r9, &gregs[8]); + err |= __get_user(regs->r10, &gregs[9]); + err |= __get_user(regs->r11, &gregs[10]); + err |= __get_user(regs->r12, &gregs[11]); + err |= __get_user(regs->r13, &gregs[12]); + err |= __get_user(regs->r14, &gregs[13]); + err |= __get_user(regs->r15, &gregs[14]); + err |= __get_user(sw->r16, &gregs[15]); + err |= __get_user(sw->r17, &gregs[16]); + err |= __get_user(sw->r18, &gregs[17]); + err |= __get_user(sw->r19, &gregs[18]); + err |= __get_user(sw->r20, &gregs[19]); + err |= __get_user(sw->r21, &gregs[20]); + err |= __get_user(sw->r22, &gregs[21]); + err |= __get_user(sw->r23, &gregs[22]); + /* gregs[23] is handled below */ + err |= __get_user(sw->fp, &gregs[24]); /* Verify, should this be + settable */ + err |= __get_user(sw->gp, &gregs[25]); /* Verify, should this be + settable */ + + err |= __get_user(temp, &gregs[26]); /* Not really necessary no user + settable bits */ + err |= __get_user(regs->ea, &gregs[27]); + + err |= __get_user(regs->ra, &gregs[23]); + err |= __get_user(regs->sp, &gregs[28]); + + regs->orig_r2 = -1; /* disable syscall checks */ + + err |= restore_altstack(&uc->uc_stack); + if (err) + goto badframe; + + *pr2 = regs->r2; + return err; + +badframe: + return 1; +} + +asmlinkage int do_rt_sigreturn(struct switch_stack *sw) +{ + struct pt_regs *regs = (struct pt_regs *)(sw + 1); + /* Verify, can we follow the stack back */ + struct rt_sigframe *frame = (struct rt_sigframe *) regs->sp; + sigset_t set; + int rval; + + if (!access_ok(VERIFY_READ, frame, sizeof(*frame))) + goto badframe; + + if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) + goto badframe; + + set_current_blocked(&set); + + if (rt_restore_ucontext(regs, sw, &frame->uc, &rval)) + goto badframe; + + return rval; + +badframe: + force_sig(SIGSEGV, current); + return 0; +} + +static inline int rt_setup_ucontext(struct ucontext *uc, struct pt_regs *regs) +{ + struct switch_stack *sw = (struct switch_stack *)regs - 1; + greg_t *gregs = uc->uc_mcontext.gregs; + int err = 0; + + err |= __put_user(MCONTEXT_VERSION, &uc->uc_mcontext.version); + err |= __put_user(regs->r1, &gregs[0]); + err |= __put_user(regs->r2, &gregs[1]); + err |= __put_user(regs->r3, &gregs[2]); + err |= __put_user(regs->r4, &gregs[3]); + err |= __put_user(regs->r5, &gregs[4]); + err |= __put_user(regs->r6, &gregs[5]); + err |= __put_user(regs->r7, &gregs[6]); + err |= __put_user(regs->r8, &gregs[7]); + err |= __put_user(regs->r9, &gregs[8]); + err |= __put_user(regs->r10, &gregs[9]); + err |= __put_user(regs->r11, &gregs[10]); + err |= __put_user(regs->r12, &gregs[11]); + err |= __put_user(regs->r13, &gregs[12]); + err |= __put_user(regs->r14, &gregs[13]); + err |= __put_user(regs->r15, &gregs[14]); + err |= __put_user(sw->r16, &gregs[15]); + err |= __put_user(sw->r17, &gregs[16]); + err |= __put_user(sw->r18, &gregs[17]); + err |= __put_user(sw->r19, &gregs[18]); + err |= __put_user(sw->r20, &gregs[19]); + err |= __put_user(sw->r21, &gregs[20]); + err |= __put_user(sw->r22, &gregs[21]); + err |= __put_user(sw->r23, &gregs[22]); + err |= __put_user(regs->ra, &gregs[23]); + err |= __put_user(sw->fp, &gregs[24]); + err |= __put_user(sw->gp, &gregs[25]); + err |= __put_user(regs->ea, &gregs[27]); + err |= __put_user(regs->sp, &gregs[28]); + return err; +} + +static inline void *get_sigframe(struct ksignal *ksig, struct pt_regs *regs, + size_t frame_size) +{ + unsigned long usp; + + /* Default to using normal stack. */ + usp = regs->sp; + + /* This is the X/Open sanctioned signal stack switching. */ + usp = sigsp(usp, ksig); + + /* Verify, is it 32 or 64 bit aligned */ + return (void *)((usp - frame_size) & -8UL); +} + +static int setup_rt_frame(struct ksignal *ksig, sigset_t *set, + struct pt_regs *regs) +{ + struct rt_sigframe *frame; + int err = 0; + + frame = get_sigframe(ksig, regs, sizeof(*frame)); + + if (ksig->ka.sa.sa_flags & SA_SIGINFO) + err |= copy_siginfo_to_user(&frame->info, &ksig->info); + + /* Create the ucontext. */ + err |= __put_user(0, &frame->uc.uc_flags); + err |= __put_user(0, &frame->uc.uc_link); + err |= __save_altstack(&frame->uc.uc_stack, regs->sp); + err |= rt_setup_ucontext(&frame->uc, regs); + err |= copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set)); + + if (err) + goto give_sigsegv; + + /* Set up to return from userspace; jump to fixed address sigreturn + trampoline on kuser page. */ + regs->ra = (unsigned long) (0x1040); + + /* Set up registers for signal handler */ + regs->sp = (unsigned long) frame; + regs->r4 = (unsigned long) ksig->sig; + regs->r5 = (unsigned long) &frame->info; + regs->r6 = (unsigned long) &frame->uc; + regs->ea = (unsigned long) ksig->ka.sa.sa_handler; + return 0; + +give_sigsegv: + force_sigsegv(ksig->sig, current); + return -EFAULT; +} + +/* + * OK, we're invoking a handler + */ +static void handle_signal(struct ksignal *ksig, struct pt_regs *regs) +{ + int ret; + sigset_t *oldset = sigmask_to_save(); + + /* set up the stack frame */ + ret = setup_rt_frame(ksig, oldset, regs); + + signal_setup_done(ret, ksig, 0); +} + +static int do_signal(struct pt_regs *regs) +{ + unsigned int retval = 0, continue_addr = 0, restart_addr = 0; + int restart = 0; + struct ksignal ksig; + + current->thread.kregs = regs; + + /* + * If we were from a system call, check for system call restarting... + */ + if (regs->orig_r2 >= 0) { + continue_addr = regs->ea; + restart_addr = continue_addr - 4; + retval = regs->r2; + + /* + * Prepare for system call restart. We do this here so that a + * debugger will see the already changed PC. + */ + switch (retval) { + case ERESTART_RESTARTBLOCK: + restart = -2; + case ERESTARTNOHAND: + case ERESTARTSYS: + case ERESTARTNOINTR: + restart++; + regs->r2 = regs->orig_r2; + regs->r7 = regs->orig_r7; + regs->ea = restart_addr; + break; + } + } + + if (get_signal(&ksig)) { + /* handler */ + if (unlikely(restart && regs->ea == restart_addr)) { + if (retval == ERESTARTNOHAND || + retval == ERESTART_RESTARTBLOCK || + (retval == ERESTARTSYS + && !(ksig.ka.sa.sa_flags & SA_RESTART))) { + regs->r2 = EINTR; + regs->r7 = 1; + regs->ea = continue_addr; + } + } + handle_signal(&ksig, regs); + return 0; + } + + /* + * No handler present + */ + if (unlikely(restart) && regs->ea == restart_addr) { + regs->ea = continue_addr; + regs->r2 = __NR_restart_syscall; + } + + /* + * If there's no signal to deliver, we just put the saved sigmask back. + */ + restore_saved_sigmask(); + + return restart; +} + +asmlinkage int do_notify_resume(struct pt_regs *regs) +{ + /* + * 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 0; + + if (test_thread_flag(TIF_SIGPENDING)) { + int restart = do_signal(regs); + + if (unlikely(restart)) { + /* + * Restart without handlers. + * Deal with it without leaving + * the kernel space. + */ + return restart; + } + } else if (test_and_clear_thread_flag(TIF_NOTIFY_RESUME)) + tracehook_notify_resume(regs); + + return 0; +} |