aboutsummaryrefslogtreecommitdiffstats
path: root/arch/parisc/kernel/signal.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/parisc/kernel/signal.c')
-rw-r--r--arch/parisc/kernel/signal.c64
1 files changed, 52 insertions, 12 deletions
diff --git a/arch/parisc/kernel/signal.c b/arch/parisc/kernel/signal.c
index dc1ea796fd60..2264f68f3c2f 100644
--- a/arch/parisc/kernel/signal.c
+++ b/arch/parisc/kernel/signal.c
@@ -435,6 +435,55 @@ handle_signal(struct ksignal *ksig, struct pt_regs *regs, int in_syscall)
435 regs->gr[28]); 435 regs->gr[28]);
436} 436}
437 437
438/*
439 * Check how the syscall number gets loaded into %r20 within
440 * the delay branch in userspace and adjust as needed.
441 */
442
443static void check_syscallno_in_delay_branch(struct pt_regs *regs)
444{
445 u32 opcode, source_reg;
446 u32 __user *uaddr;
447 int err;
448
449 /* Usually we don't have to restore %r20 (the system call number)
450 * because it gets loaded in the delay slot of the branch external
451 * instruction via the ldi instruction.
452 * In some cases a register-to-register copy instruction might have
453 * been used instead, in which case we need to copy the syscall
454 * number into the source register before returning to userspace.
455 */
456
457 /* A syscall is just a branch, so all we have to do is fiddle the
458 * return pointer so that the ble instruction gets executed again.
459 */
460 regs->gr[31] -= 8; /* delayed branching */
461
462 /* Get assembler opcode of code in delay branch */
463 uaddr = (unsigned int *) ((regs->gr[31] & ~3) + 4);
464 err = get_user(opcode, uaddr);
465 if (err)
466 return;
467
468 /* Check if delay branch uses "ldi int,%r20" */
469 if ((opcode & 0xffff0000) == 0x34140000)
470 return; /* everything ok, just return */
471
472 /* Check if delay branch uses "nop" */
473 if (opcode == INSN_NOP)
474 return;
475
476 /* Check if delay branch uses "copy %rX,%r20" */
477 if ((opcode & 0xffe0ffff) == 0x08000254) {
478 source_reg = (opcode >> 16) & 31;
479 regs->gr[source_reg] = regs->gr[20];
480 return;
481 }
482
483 pr_warn("syscall restart: %s (pid %d): unexpected opcode 0x%08x\n",
484 current->comm, task_pid_nr(current), opcode);
485}
486
438static inline void 487static inline void
439syscall_restart(struct pt_regs *regs, struct k_sigaction *ka) 488syscall_restart(struct pt_regs *regs, struct k_sigaction *ka)
440{ 489{
@@ -457,10 +506,7 @@ syscall_restart(struct pt_regs *regs, struct k_sigaction *ka)
457 } 506 }
458 /* fallthrough */ 507 /* fallthrough */
459 case -ERESTARTNOINTR: 508 case -ERESTARTNOINTR:
460 /* A syscall is just a branch, so all 509 check_syscallno_in_delay_branch(regs);
461 * we have to do is fiddle the return pointer.
462 */
463 regs->gr[31] -= 8; /* delayed branching */
464 break; 510 break;
465 } 511 }
466} 512}
@@ -510,15 +556,9 @@ insert_restart_trampoline(struct pt_regs *regs)
510 } 556 }
511 case -ERESTARTNOHAND: 557 case -ERESTARTNOHAND:
512 case -ERESTARTSYS: 558 case -ERESTARTSYS:
513 case -ERESTARTNOINTR: { 559 case -ERESTARTNOINTR:
514 /* Hooray for delayed branching. We don't 560 check_syscallno_in_delay_branch(regs);
515 * have to restore %r20 (the system call
516 * number) because it gets loaded in the delay
517 * slot of the branch external instruction.
518 */
519 regs->gr[31] -= 8;
520 return; 561 return;
521 }
522 default: 562 default:
523 break; 563 break;
524 } 564 }