diff options
Diffstat (limited to 'kernel/futex.c')
| -rw-r--r-- | kernel/futex.c | 137 |
1 files changed, 125 insertions, 12 deletions
diff --git a/kernel/futex.c b/kernel/futex.c index c7130f86106c..ca05fe6a70b2 100644 --- a/kernel/futex.c +++ b/kernel/futex.c | |||
| @@ -40,6 +40,7 @@ | |||
| 40 | #include <linux/pagemap.h> | 40 | #include <linux/pagemap.h> |
| 41 | #include <linux/syscalls.h> | 41 | #include <linux/syscalls.h> |
| 42 | #include <linux/signal.h> | 42 | #include <linux/signal.h> |
| 43 | #include <asm/futex.h> | ||
| 43 | 44 | ||
| 44 | #define FUTEX_HASHBITS (CONFIG_BASE_SMALL ? 4 : 8) | 45 | #define FUTEX_HASHBITS (CONFIG_BASE_SMALL ? 4 : 8) |
| 45 | 46 | ||
| @@ -327,6 +328,118 @@ out: | |||
| 327 | } | 328 | } |
| 328 | 329 | ||
| 329 | /* | 330 | /* |
| 331 | * Wake up all waiters hashed on the physical page that is mapped | ||
| 332 | * to this virtual address: | ||
| 333 | */ | ||
| 334 | static int futex_wake_op(unsigned long uaddr1, unsigned long uaddr2, int nr_wake, int nr_wake2, int op) | ||
| 335 | { | ||
| 336 | union futex_key key1, key2; | ||
| 337 | struct futex_hash_bucket *bh1, *bh2; | ||
| 338 | struct list_head *head; | ||
| 339 | struct futex_q *this, *next; | ||
| 340 | int ret, op_ret, attempt = 0; | ||
| 341 | |||
| 342 | retryfull: | ||
| 343 | down_read(¤t->mm->mmap_sem); | ||
| 344 | |||
| 345 | ret = get_futex_key(uaddr1, &key1); | ||
| 346 | if (unlikely(ret != 0)) | ||
| 347 | goto out; | ||
| 348 | ret = get_futex_key(uaddr2, &key2); | ||
| 349 | if (unlikely(ret != 0)) | ||
| 350 | goto out; | ||
| 351 | |||
| 352 | bh1 = hash_futex(&key1); | ||
| 353 | bh2 = hash_futex(&key2); | ||
| 354 | |||
| 355 | retry: | ||
| 356 | if (bh1 < bh2) | ||
| 357 | spin_lock(&bh1->lock); | ||
| 358 | spin_lock(&bh2->lock); | ||
| 359 | if (bh1 > bh2) | ||
| 360 | spin_lock(&bh1->lock); | ||
| 361 | |||
| 362 | op_ret = futex_atomic_op_inuser(op, (int __user *)uaddr2); | ||
| 363 | if (unlikely(op_ret < 0)) { | ||
| 364 | int dummy; | ||
| 365 | |||
| 366 | spin_unlock(&bh1->lock); | ||
| 367 | if (bh1 != bh2) | ||
| 368 | spin_unlock(&bh2->lock); | ||
| 369 | |||
| 370 | /* futex_atomic_op_inuser needs to both read and write | ||
| 371 | * *(int __user *)uaddr2, but we can't modify it | ||
| 372 | * non-atomically. Therefore, if get_user below is not | ||
| 373 | * enough, we need to handle the fault ourselves, while | ||
| 374 | * still holding the mmap_sem. */ | ||
| 375 | if (attempt++) { | ||
| 376 | struct vm_area_struct * vma; | ||
| 377 | struct mm_struct *mm = current->mm; | ||
| 378 | |||
| 379 | ret = -EFAULT; | ||
| 380 | if (attempt >= 2 || | ||
| 381 | !(vma = find_vma(mm, uaddr2)) || | ||
| 382 | vma->vm_start > uaddr2 || | ||
| 383 | !(vma->vm_flags & VM_WRITE)) | ||
| 384 | goto out; | ||
| 385 | |||
| 386 | switch (handle_mm_fault(mm, vma, uaddr2, 1)) { | ||
| 387 | case VM_FAULT_MINOR: | ||
| 388 | current->min_flt++; | ||
| 389 | break; | ||
| 390 | case VM_FAULT_MAJOR: | ||
| 391 | current->maj_flt++; | ||
| 392 | break; | ||
| 393 | default: | ||
| 394 | goto out; | ||
| 395 | } | ||
| 396 | goto retry; | ||
| 397 | } | ||
| 398 | |||
| 399 | /* If we would have faulted, release mmap_sem, | ||
| 400 | * fault it in and start all over again. */ | ||
| 401 | up_read(¤t->mm->mmap_sem); | ||
| 402 | |||
| 403 | ret = get_user(dummy, (int __user *)uaddr2); | ||
| 404 | if (ret) | ||
| 405 | return ret; | ||
| 406 | |||
| 407 | goto retryfull; | ||
| 408 | } | ||
| 409 | |||
| 410 | head = &bh1->chain; | ||
| 411 | |||
| 412 | list_for_each_entry_safe(this, next, head, list) { | ||
| 413 | if (match_futex (&this->key, &key1)) { | ||
| 414 | wake_futex(this); | ||
| 415 | if (++ret >= nr_wake) | ||
| 416 | break; | ||
| 417 | } | ||
| 418 | } | ||
| 419 | |||
| 420 | if (op_ret > 0) { | ||
| 421 | head = &bh2->chain; | ||
| 422 | |||
| 423 | op_ret = 0; | ||
| 424 | list_for_each_entry_safe(this, next, head, list) { | ||
| 425 | if (match_futex (&this->key, &key2)) { | ||
| 426 | wake_futex(this); | ||
| 427 | if (++op_ret >= nr_wake2) | ||
| 428 | break; | ||
| 429 | } | ||
| 430 | } | ||
| 431 | ret += op_ret; | ||
| 432 | } | ||
| 433 | |||
| 434 | spin_unlock(&bh1->lock); | ||
| 435 | if (bh1 != bh2) | ||
| 436 | spin_unlock(&bh2->lock); | ||
| 437 | out: | ||
| 438 | up_read(¤t->mm->mmap_sem); | ||
| 439 | return ret; | ||
| 440 | } | ||
| 441 | |||
| 442 | /* | ||
| 330 | * Requeue all waiters hashed on one physical page to another | 443 | * Requeue all waiters hashed on one physical page to another |
| 331 | * physical page. | 444 | * physical page. |
| 332 | */ | 445 | */ |
| @@ -673,23 +786,17 @@ static int futex_fd(unsigned long uaddr, int signal) | |||
| 673 | filp->f_mapping = filp->f_dentry->d_inode->i_mapping; | 786 | filp->f_mapping = filp->f_dentry->d_inode->i_mapping; |
| 674 | 787 | ||
| 675 | if (signal) { | 788 | if (signal) { |
| 676 | int err; | ||
| 677 | err = f_setown(filp, current->pid, 1); | 789 | err = f_setown(filp, current->pid, 1); |
| 678 | if (err < 0) { | 790 | if (err < 0) { |
| 679 | put_unused_fd(ret); | 791 | goto error; |
| 680 | put_filp(filp); | ||
| 681 | ret = err; | ||
| 682 | goto out; | ||
| 683 | } | 792 | } |
| 684 | filp->f_owner.signum = signal; | 793 | filp->f_owner.signum = signal; |
| 685 | } | 794 | } |
| 686 | 795 | ||
| 687 | q = kmalloc(sizeof(*q), GFP_KERNEL); | 796 | q = kmalloc(sizeof(*q), GFP_KERNEL); |
| 688 | if (!q) { | 797 | if (!q) { |
| 689 | put_unused_fd(ret); | 798 | err = -ENOMEM; |
| 690 | put_filp(filp); | 799 | goto error; |
| 691 | ret = -ENOMEM; | ||
| 692 | goto out; | ||
| 693 | } | 800 | } |
| 694 | 801 | ||
| 695 | down_read(¤t->mm->mmap_sem); | 802 | down_read(¤t->mm->mmap_sem); |
| @@ -697,10 +804,8 @@ static int futex_fd(unsigned long uaddr, int signal) | |||
| 697 | 804 | ||
| 698 | if (unlikely(err != 0)) { | 805 | if (unlikely(err != 0)) { |
| 699 | up_read(¤t->mm->mmap_sem); | 806 | up_read(¤t->mm->mmap_sem); |
| 700 | put_unused_fd(ret); | ||
| 701 | put_filp(filp); | ||
| 702 | kfree(q); | 807 | kfree(q); |
| 703 | return err; | 808 | goto error; |
| 704 | } | 809 | } |
| 705 | 810 | ||
| 706 | /* | 811 | /* |
| @@ -716,6 +821,11 @@ static int futex_fd(unsigned long uaddr, int signal) | |||
| 716 | fd_install(ret, filp); | 821 | fd_install(ret, filp); |
| 717 | out: | 822 | out: |
| 718 | return ret; | 823 | return ret; |
| 824 | error: | ||
| 825 | put_unused_fd(ret); | ||
| 826 | put_filp(filp); | ||
| 827 | ret = err; | ||
| 828 | goto out; | ||
| 719 | } | 829 | } |
| 720 | 830 | ||
| 721 | long do_futex(unsigned long uaddr, int op, int val, unsigned long timeout, | 831 | long do_futex(unsigned long uaddr, int op, int val, unsigned long timeout, |
| @@ -740,6 +850,9 @@ long do_futex(unsigned long uaddr, int op, int val, unsigned long timeout, | |||
| 740 | case FUTEX_CMP_REQUEUE: | 850 | case FUTEX_CMP_REQUEUE: |
| 741 | ret = futex_requeue(uaddr, uaddr2, val, val2, &val3); | 851 | ret = futex_requeue(uaddr, uaddr2, val, val2, &val3); |
| 742 | break; | 852 | break; |
| 853 | case FUTEX_WAKE_OP: | ||
| 854 | ret = futex_wake_op(uaddr, uaddr2, val, val2, val3); | ||
| 855 | break; | ||
| 743 | default: | 856 | default: |
| 744 | ret = -ENOSYS; | 857 | ret = -ENOSYS; |
| 745 | } | 858 | } |
