[RFC PATCH 7/8] ARM: signal: perform restart_block system call restarting in the kernel
Will Deacon
will.deacon at arm.com
Fri Jun 22 11:07:05 EDT 2012
This patch moves the restart_block system call restarting into the
kernel and avoids a problematic return to userspace when restarting
system calls failing with -ERESTART_RESTARTBLOCK.
Rather than fake up an svc invocation from kernel space, this patch
jumps directly to a wrapper around sys_restart_syscall on the return
to userspace path if a restart is pending. This allows us to enable
interrupts during do_signal (required by the freezer code) and also
correctly abort a pending restart if we process further signals that
invalidate the restart requirements.
Heavily-inspired-by: Al Viro <viro at zeniv.linux.org.uk>
Reviewed-by: Catalin Marinas <catalin.marinas at arm.com>
Signed-off-by: Will Deacon <will.deacon at arm.com>
---
arch/arm/include/asm/ptrace.h | 3 +
arch/arm/include/asm/thread_info.h | 6 ++-
arch/arm/kernel/calls.S | 2 +-
arch/arm/kernel/entry-common.S | 5 ++
arch/arm/kernel/signal.c | 86 ++++++++++++++++++------------------
5 files changed, 57 insertions(+), 45 deletions(-)
diff --git a/arch/arm/include/asm/ptrace.h b/arch/arm/include/asm/ptrace.h
index 355ece5..93908d5 100644
--- a/arch/arm/include/asm/ptrace.h
+++ b/arch/arm/include/asm/ptrace.h
@@ -254,6 +254,9 @@ static inline unsigned long user_stack_pointer(struct pt_regs *regs)
return regs->ARM_sp;
}
+extern int syscall_trace_enter(struct pt_regs *regs, int scno);
+extern int syscall_trace_exit(struct pt_regs *regs, int scno);
+
#endif /* __KERNEL__ */
#endif /* __ASSEMBLY__ */
diff --git a/arch/arm/include/asm/thread_info.h b/arch/arm/include/asm/thread_info.h
index af7b0bd..d3d1689 100644
--- a/arch/arm/include/asm/thread_info.h
+++ b/arch/arm/include/asm/thread_info.h
@@ -65,6 +65,7 @@ struct thread_info {
#ifdef CONFIG_ARM_THUMBEE
unsigned long thumbee_state; /* ThumbEE Handler Base register */
#endif
+ unsigned long restart_addr;
struct restart_block restart_block;
};
@@ -136,7 +137,8 @@ extern int vfp_restore_user_hwstate(struct user_vfp __user *,
/*
* thread information flags:
* TIF_SYSCALL_TRACE - syscall trace active
- * TIF_SYSCAL_AUDIT - syscall auditing active
+ * TIF_SYSCALL_AUDIT - syscall auditing active
+ * TIF_SYSCALL_RESTART - syscall restart in progress
* TIF_SIGPENDING - signal pending
* TIF_NEED_RESCHED - rescheduling necessary
* TIF_NOTIFY_RESUME - callback before returning to user
@@ -148,6 +150,7 @@ extern int vfp_restore_user_hwstate(struct user_vfp __user *,
#define TIF_NOTIFY_RESUME 2 /* callback before returning to user */
#define TIF_SYSCALL_TRACE 8
#define TIF_SYSCALL_AUDIT 9
+#define TIF_SYSCALL_RESTART 10
#define TIF_POLLING_NRFLAG 16
#define TIF_USING_IWMMXT 17
#define TIF_MEMDIE 18 /* is terminating due to OOM killer */
@@ -160,6 +163,7 @@ extern int vfp_restore_user_hwstate(struct user_vfp __user *,
#define _TIF_NOTIFY_RESUME (1 << TIF_NOTIFY_RESUME)
#define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE)
#define _TIF_SYSCALL_AUDIT (1 << TIF_SYSCALL_AUDIT)
+#define _TIF_SYSCALL_RESTART (1 << TIF_SYSCALL_RESTART)
#define _TIF_POLLING_NRFLAG (1 << TIF_POLLING_NRFLAG)
#define _TIF_USING_IWMMXT (1 << TIF_USING_IWMMXT)
#define _TIF_SECCOMP (1 << TIF_SECCOMP)
diff --git a/arch/arm/kernel/calls.S b/arch/arm/kernel/calls.S
index 463ff4a..252a140 100644
--- a/arch/arm/kernel/calls.S
+++ b/arch/arm/kernel/calls.S
@@ -9,7 +9,7 @@
*
* This file is included thrice in entry-common.S
*/
-/* 0 */ CALL(sys_restart_syscall)
+/* 0 */ CALL(sys_ni_syscall) /* was sys_restart_syscall */
CALL(sys_exit)
CALL(sys_fork_wrapper)
CALL(sys_read)
diff --git a/arch/arm/kernel/entry-common.S b/arch/arm/kernel/entry-common.S
index 49d9f93..c2085bf 100644
--- a/arch/arm/kernel/entry-common.S
+++ b/arch/arm/kernel/entry-common.S
@@ -51,6 +51,7 @@ ret_fast_syscall:
fast_work_pending:
str r0, [sp, #S_R0+S_OFF]! @ returned r0
work_pending:
+ enable_irq
tst r1, #_TIF_NEED_RESCHED
bne work_resched
/*
@@ -79,6 +80,10 @@ ENTRY(ret_to_user_from_irq)
tst r1, #_TIF_WORK_MASK
bne work_pending
no_work_pending:
+ tst r1, #_TIF_SYSCALL_RESTART
+ adrne lr, ret_slow_syscall
+ movne why, #1
+ bne do_sys_restart_syscall
#if defined(CONFIG_IRQSOFF_TRACER)
asm_trace_hardirqs_on
#endif
diff --git a/arch/arm/kernel/signal.c b/arch/arm/kernel/signal.c
index 536c5d6..8250475 100644
--- a/arch/arm/kernel/signal.c
+++ b/arch/arm/kernel/signal.c
@@ -11,6 +11,7 @@
#include <linux/signal.h>
#include <linux/personality.h>
#include <linux/freezer.h>
+#include <linux/syscalls.h>
#include <linux/uaccess.h>
#include <linux/tracehook.h>
@@ -584,17 +585,17 @@ handle_signal(unsigned long sig, struct k_sigaction *ka,
*/
static void do_signal(struct pt_regs *regs, int syscall)
{
- unsigned int retval = 0, continue_addr = 0, restart_addr = 0;
+ unsigned int retval = 0;
struct k_sigaction ka;
siginfo_t info;
int signr;
+ struct thread_info *thread = current_thread_info();
/*
* If we were from a system call, check for system call restarting...
*/
if (syscall) {
- continue_addr = regs->ARM_pc;
- restart_addr = continue_addr - (thumb_mode(regs) ? 2 : 4);
+ thread->restart_addr = regs->ARM_pc - (thumb_mode(regs) ? 2 : 4);
retval = regs->ARM_r0;
/*
@@ -605,11 +606,9 @@ static void do_signal(struct pt_regs *regs, int syscall)
case -ERESTARTNOHAND:
case -ERESTARTSYS:
case -ERESTARTNOINTR:
- regs->ARM_r0 = regs->ARM_ORIG_r0;
- regs->ARM_pc = restart_addr;
- break;
case -ERESTART_RESTARTBLOCK:
- regs->ARM_r0 = -EINTR;
+ regs->ARM_r0 = regs->ARM_ORIG_r0;
+ regs->ARM_pc = thread->restart_addr;
break;
}
}
@@ -625,12 +624,17 @@ static void do_signal(struct pt_regs *regs, int syscall)
* decision to restart the system call. But skip this if a
* debugger has chosen to restart at a different PC.
*/
- if (regs->ARM_pc == restart_addr) {
- if (retval == -ERESTARTNOHAND
- || (retval == -ERESTARTSYS
- && !(ka.sa.sa_flags & SA_RESTART))) {
+ if (regs->ARM_pc == thread->restart_addr) {
+ switch (retval) {
+ case -ERESTARTSYS:
+ if (ka.sa.sa_flags & SA_RESTART)
+ break;
+ case -ERESTARTNOHAND:
+ case -ERESTART_RESTARTBLOCK:
regs->ARM_r0 = -EINTR;
- regs->ARM_pc = continue_addr;
+ regs->ARM_pc += thumb_mode(regs) ? 2 : 4;
+ thread->restart_addr = 0;
+ clear_thread_flag(TIF_SYSCALL_RESTART);
}
}
@@ -638,37 +642,14 @@ static void do_signal(struct pt_regs *regs, int syscall)
return;
}
- if (syscall) {
- /*
- * Handle restarting a different system call. As above,
- * if a debugger has chosen to restart at a different PC,
- * ignore the restart.
- */
- if (retval == -ERESTART_RESTARTBLOCK
- && regs->ARM_pc == continue_addr) {
- if (thumb_mode(regs)) {
- regs->ARM_r7 = __NR_restart_syscall - __NR_SYSCALL_BASE;
- regs->ARM_pc -= 2;
- } else {
-#if defined(CONFIG_AEABI) && !defined(CONFIG_OABI_COMPAT)
- regs->ARM_r7 = __NR_restart_syscall;
- regs->ARM_pc -= 4;
-#else
- u32 __user *usp;
-
- regs->ARM_sp -= 4;
- usp = (u32 __user *)regs->ARM_sp;
-
- if (put_user(regs->ARM_pc, usp) == 0) {
- regs->ARM_pc = KERN_RESTART_CODE;
- } else {
- regs->ARM_sp += 4;
- force_sigsegv(0, current);
- }
-#endif
- }
- }
- }
+ /*
+ * Handle restarting a different system call. As above,
+ * if a debugger has chosen to restart at a different PC,
+ * ignore the restart.
+ */
+ if (syscall && retval == -ERESTART_RESTARTBLOCK &&
+ regs->ARM_pc == thread->restart_addr)
+ set_thread_flag(TIF_SYSCALL_RESTART);
restore_saved_sigmask();
}
@@ -684,3 +665,22 @@ do_notify_resume(struct pt_regs *regs, unsigned int thread_flags, int syscall)
tracehook_notify_resume(regs);
}
}
+
+asmlinkage void
+do_sys_restart_syscall(void)
+{
+ struct pt_regs *regs = task_pt_regs(current);
+ regs->ARM_pc += thumb_mode(regs) ? 2 : 4;
+
+ syscall_trace_enter(regs, __NR_restart_syscall);
+
+ if (test_and_clear_thread_flag(TIF_SYSCALL_RESTART)) {
+ local_irq_enable();
+ regs->ARM_r0 = sys_restart_syscall();
+ } else {
+ pr_warning("Attempt to restart syscall without thread flag set!\n");
+ regs->ARM_r0 = -EINTR;
+ }
+
+ syscall_trace_exit(regs, __NR_restart_syscall);
+}
--
1.7.4.1
More information about the linux-arm-kernel
mailing list