summaryrefslogtreecommitdiff
path: root/arch/arm/kernel
diff options
context:
space:
mode:
authorAl Viro <viro@zeniv.linux.org.uk>2012-07-19 17:48:21 +0100
committerRussell King <rmk+kernel@arm.linux.org.uk>2012-07-28 11:11:52 +0100
commit81783786d5cf4aa0d3e15bb0fac856aa8ebf1a76 (patch)
treeea1266df42b055ef03df4f3937f6c6b4326bafc7 /arch/arm/kernel
parent0a267fa6a15d41c4061358bf2e67c633fdbffc90 (diff)
ARM: 7473/1: deal with handlerless restarts without leaving the kernel
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk> Signed-off-by: Will Deacon <will.deacon@arm.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'arch/arm/kernel')
-rw-r--r--arch/arm/kernel/entry-common.S7
-rw-r--r--arch/arm/kernel/signal.c50
2 files changed, 35 insertions, 22 deletions
diff --git a/arch/arm/kernel/entry-common.S b/arch/arm/kernel/entry-common.S
index 1873f653641c..8ae58c47dce6 100644
--- a/arch/arm/kernel/entry-common.S
+++ b/arch/arm/kernel/entry-common.S
@@ -54,7 +54,11 @@ work_pending:
mov r0, sp @ 'regs'
mov r2, why @ 'syscall'
bl do_work_pending
- b no_work_pending
+ tst r0, #1
+ beq no_work_pending
+ ldmia sp, {r0 - r6} @ have to reload r0 - r6
+ b local_restart @ ... and off we go
+
/*
* "slow" syscall return path. "why" tells us if this was a real syscall.
*/
@@ -396,6 +400,7 @@ ENTRY(vector_swi)
eor scno, scno, #__NR_SYSCALL_BASE @ check OS number
#endif
+local_restart:
ldr r10, [tsk, #TI_FLAGS] @ check for syscall tracing
stmdb sp!, {r4, r5} @ push fifth and sixth args
diff --git a/arch/arm/kernel/signal.c b/arch/arm/kernel/signal.c
index 8756e5dcd377..99851cb9591d 100644
--- a/arch/arm/kernel/signal.c
+++ b/arch/arm/kernel/signal.c
@@ -569,12 +569,13 @@ handle_signal(unsigned long sig, struct k_sigaction *ka,
* the kernel can handle, and then we build all the user-level signal handling
* stack-frames in one go after that.
*/
-static void do_signal(struct pt_regs *regs, int syscall)
+static int do_signal(struct pt_regs *regs, int syscall)
{
unsigned int retval = 0, continue_addr = 0, restart_addr = 0;
struct k_sigaction ka;
siginfo_t info;
int signr;
+ int restart = 0;
/*
* If we were from a system call, check for system call restarting...
@@ -589,10 +590,12 @@ static void do_signal(struct pt_regs *regs, int syscall)
* debugger will see the already changed PSW.
*/
switch (retval) {
+ case -ERESTART_RESTARTBLOCK:
+ restart++;
case -ERESTARTNOHAND:
case -ERESTARTSYS:
case -ERESTARTNOINTR:
- case -ERESTART_RESTARTBLOCK:
+ restart++;
regs->ARM_r0 = regs->ARM_ORIG_r0;
regs->ARM_pc = restart_addr;
break;
@@ -604,13 +607,15 @@ static void do_signal(struct pt_regs *regs, int syscall)
* 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 (regs->ARM_pc != restart_addr)
+ restart = 0;
if (signr > 0) {
- /*
- * 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 (regs->ARM_pc == restart_addr) {
+ if (unlikely(restart)) {
if (retval == -ERESTARTNOHAND ||
retval == -ERESTART_RESTARTBLOCK
|| (retval == -ERESTARTSYS
@@ -618,28 +623,23 @@ static void do_signal(struct pt_regs *regs, int syscall)
regs->ARM_r0 = -EINTR;
regs->ARM_pc = continue_addr;
}
- clear_thread_flag(TIF_SYSCALL_RESTARTSYS);
}
handle_signal(signr, &ka, &info, regs);
- return;
+ return 0;
}
- 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 == restart_addr)
+ if (unlikely(restart)) {
+ if (restart > 1)
set_thread_flag(TIF_SYSCALL_RESTARTSYS);
+ regs->ARM_pc = continue_addr;
}
restore_saved_sigmask();
+ return restart;
}
-asmlinkage void
+asmlinkage int
do_work_pending(struct pt_regs *regs, unsigned int thread_flags, int syscall)
{
do {
@@ -647,10 +647,17 @@ do_work_pending(struct pt_regs *regs, unsigned int thread_flags, int syscall)
schedule();
} else {
if (unlikely(!user_mode(regs)))
- return;
+ return 0;
local_irq_enable();
if (thread_flags & _TIF_SIGPENDING) {
- do_signal(regs, syscall);
+ if (unlikely(do_signal(regs, syscall))) {
+ /*
+ * Restart without handlers.
+ * Deal with it without leaving
+ * the kernel space.
+ */
+ return 1;
+ }
syscall = 0;
} else {
clear_thread_flag(TIF_NOTIFY_RESUME);
@@ -660,4 +667,5 @@ do_work_pending(struct pt_regs *regs, unsigned int thread_flags, int syscall)
local_irq_disable();
thread_flags = current_thread_info()->flags;
} while (thread_flags & _TIF_WORK_MASK);
+ return 0;
}