diff options
Diffstat (limited to 'arch/arm/kernel/traps.c')
| -rw-r--r-- | arch/arm/kernel/traps.c | 49 |
1 files changed, 49 insertions, 0 deletions
diff --git a/arch/arm/kernel/traps.c b/arch/arm/kernel/traps.c index 14df16b983f4..45d2a032d890 100644 --- a/arch/arm/kernel/traps.c +++ b/arch/arm/kernel/traps.c | |||
| @@ -464,6 +464,55 @@ asmlinkage int arm_syscall(int no, struct pt_regs *regs) | |||
| 464 | #endif | 464 | #endif |
| 465 | return 0; | 465 | return 0; |
| 466 | 466 | ||
| 467 | #ifdef CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG | ||
| 468 | /* | ||
| 469 | * Atomically store r1 in *r2 if *r2 is equal to r0 for user space. | ||
| 470 | * Return zero in r0 if *MEM was changed or non-zero if no exchange | ||
| 471 | * happened. Also set the user C flag accordingly. | ||
| 472 | * If access permissions have to be fixed up then non-zero is | ||
| 473 | * returned and the operation has to be re-attempted. | ||
| 474 | * | ||
| 475 | * *NOTE*: This is a ghost syscall private to the kernel. Only the | ||
| 476 | * __kuser_cmpxchg code in entry-armv.S should be aware of its | ||
| 477 | * existence. Don't ever use this from user code. | ||
| 478 | */ | ||
| 479 | case 0xfff0: | ||
| 480 | { | ||
| 481 | extern void do_DataAbort(unsigned long addr, unsigned int fsr, | ||
| 482 | struct pt_regs *regs); | ||
| 483 | unsigned long val; | ||
| 484 | unsigned long addr = regs->ARM_r2; | ||
| 485 | struct mm_struct *mm = current->mm; | ||
| 486 | pgd_t *pgd; pmd_t *pmd; pte_t *pte; | ||
| 487 | |||
| 488 | regs->ARM_cpsr &= ~PSR_C_BIT; | ||
| 489 | spin_lock(&mm->page_table_lock); | ||
| 490 | pgd = pgd_offset(mm, addr); | ||
| 491 | if (!pgd_present(*pgd)) | ||
| 492 | goto bad_access; | ||
| 493 | pmd = pmd_offset(pgd, addr); | ||
| 494 | if (!pmd_present(*pmd)) | ||
| 495 | goto bad_access; | ||
| 496 | pte = pte_offset_map(pmd, addr); | ||
| 497 | if (!pte_present(*pte) || !pte_write(*pte)) | ||
| 498 | goto bad_access; | ||
| 499 | val = *(unsigned long *)addr; | ||
| 500 | val -= regs->ARM_r0; | ||
| 501 | if (val == 0) { | ||
| 502 | *(unsigned long *)addr = regs->ARM_r1; | ||
| 503 | regs->ARM_cpsr |= PSR_C_BIT; | ||
| 504 | } | ||
| 505 | spin_unlock(&mm->page_table_lock); | ||
| 506 | return val; | ||
| 507 | |||
| 508 | bad_access: | ||
| 509 | spin_unlock(&mm->page_table_lock); | ||
| 510 | /* simulate a read access fault */ | ||
| 511 | do_DataAbort(addr, 15 + (1 << 11), regs); | ||
| 512 | return -1; | ||
| 513 | } | ||
| 514 | #endif | ||
| 515 | |||
| 467 | default: | 516 | default: |
| 468 | /* Calls 9f00xx..9f07ff are defined to return -ENOSYS | 517 | /* Calls 9f00xx..9f07ff are defined to return -ENOSYS |
| 469 | if not implemented, rather than raising SIGILL. This | 518 | if not implemented, rather than raising SIGILL. This |
