|
@@ -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;
|
|
|
}
|
|
|
|
|
@@ -181,24 +174,18 @@ static int setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
|
|
|
int err = 0;
|
|
|
|
|
|
frame = get_sigframe(ka, regs, sizeof(*frame));
|
|
|
-
|
|
|
if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame)))
|
|
|
goto give_sigsegv;
|
|
|
|
|
|
- err |= __put_user(&frame->info, &frame->pinfo);
|
|
|
- err |= __put_user(&frame->uc, &frame->puc);
|
|
|
-
|
|
|
+ /* Create siginfo. */
|
|
|
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));
|
|
|
|
|
@@ -207,9 +194,12 @@ static int setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
|
|
|
|
|
|
/* 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));
|
|
|
|
|
@@ -262,82 +252,106 @@ 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]) {
|
|
|
+ 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;
|
|
|
+ /*
|
|
|
+ * Get the signal to deliver. When running under ptrace, at this
|
|
|
+ * point the debugger may change all our registers ...
|
|
|
+ */
|
|
|
+ signr = get_signal_to_deliver(&info, &ka, regs, NULL);
|
|
|
+ /*
|
|
|
+ * Depending on the signal settings we may need to revert the
|
|
|
+ * decision to restart the system call. But skip this if a
|
|
|
+ * debugger has chosen to restart at a different PC.
|
|
|
+ */
|
|
|
+ if (signr > 0) {
|
|
|
+ if (unlikely(restart) && regs->pc == restart_addr) {
|
|
|
+ if (retval == -ERESTARTNOHAND ||
|
|
|
+ retval == -ERESTART_RESTARTBLOCK
|
|
|
+ || (retval == -ERESTARTSYS
|
|
|
+ && !(ka.sa.sa_flags & SA_RESTART))) {
|
|
|
+ /* No automatic restart */
|
|
|
+ regs->gpr[11] = -EINTR;
|
|
|
+ regs->pc = continue_addr;
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- if (signr <= 0) {
|
|
|
- /* no signal to deliver so we just put the saved sigmask
|
|
|
- * back */
|
|
|
- restore_saved_sigmask();
|
|
|
- } else { /* signr > 0 */
|
|
|
- /* Whee! Actually deliver the signal. */
|
|
|
handle_signal(signr, &info, &ka, regs);
|
|
|
+ } else {
|
|
|
+ /* no handler */
|
|
|
+ restore_saved_sigmask();
|
|
|
+ /*
|
|
|
+ * 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;
|
|
|
}
|