diff options
Diffstat (limited to 'arch/parisc/kernel/signal.c')
-rw-r--r-- | arch/parisc/kernel/signal.c | 64 |
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 | |||
443 | static 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 | |||
438 | static inline void | 487 | static inline void |
439 | syscall_restart(struct pt_regs *regs, struct k_sigaction *ka) | 488 | syscall_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 | } |