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 | } |
