mirror of
https://github.com/torvalds/linux.git
synced 2024-11-05 03:21:32 +00:00
cd689985cf
To allow the implementation of optimized rw-locks in user space, glibc needs a possibility to select waiters for wakeup depending on a bitset mask. This requires two new futex OPs: FUTEX_WAIT_BITS and FUTEX_WAKE_BITS These OPs are basically the same as FUTEX_WAIT and FUTEX_WAKE plus an additional argument - a bitset. Further the FUTEX_WAIT_BITS OP is expecting an absolute timeout value instead of the relative one, which is used for the FUTEX_WAIT OP. FUTEX_WAIT_BITS calls into the kernel with a bitset. The bitset is stored in the futex_q structure, which is used to enqueue the waiter into the hashed futex waitqueue. FUTEX_WAKE_BITS also calls into the kernel with a bitset. The wakeup function logically ANDs the bitset with the bitset stored in each waiters futex_q structure. If the result is zero (i.e. none of the set bits in the bitsets is matching), then the waiter is not woken up. If the result is not zero (i.e. one of the set bits in the bitsets is matching), then the waiter is woken. The bitset provided by the caller must be non zero. In case the provided bitset is zero the kernel returns EINVAL. Internaly the new OPs are only extensions to the existing FUTEX_WAIT and FUTEX_WAKE functions. The existing OPs hand a bitset with all bits set into the futex_wait() and futex_wake() functions. Signed-off-by: Thomas Gleixner <tgxl@linutronix.de> Signed-off-by: Ingo Molnar <mingo@elte.hu>
187 lines
4.2 KiB
C
187 lines
4.2 KiB
C
/*
|
|
* linux/kernel/futex_compat.c
|
|
*
|
|
* Futex compatibililty routines.
|
|
*
|
|
* Copyright 2006, Red Hat, Inc., Ingo Molnar
|
|
*/
|
|
|
|
#include <linux/linkage.h>
|
|
#include <linux/compat.h>
|
|
#include <linux/nsproxy.h>
|
|
#include <linux/futex.h>
|
|
|
|
#include <asm/uaccess.h>
|
|
|
|
|
|
/*
|
|
* Fetch a robust-list pointer. Bit 0 signals PI futexes:
|
|
*/
|
|
static inline int
|
|
fetch_robust_entry(compat_uptr_t *uentry, struct robust_list __user **entry,
|
|
compat_uptr_t __user *head, int *pi)
|
|
{
|
|
if (get_user(*uentry, head))
|
|
return -EFAULT;
|
|
|
|
*entry = compat_ptr((*uentry) & ~1);
|
|
*pi = (unsigned int)(*uentry) & 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __user *futex_uaddr(struct robust_list *entry,
|
|
compat_long_t futex_offset)
|
|
{
|
|
compat_uptr_t base = ptr_to_compat(entry);
|
|
void __user *uaddr = compat_ptr(base + futex_offset);
|
|
|
|
return uaddr;
|
|
}
|
|
|
|
/*
|
|
* Walk curr->robust_list (very carefully, it's a userspace list!)
|
|
* and mark any locks found there dead, and notify any waiters.
|
|
*
|
|
* We silently return on any sign of list-walking problem.
|
|
*/
|
|
void compat_exit_robust_list(struct task_struct *curr)
|
|
{
|
|
struct compat_robust_list_head __user *head = curr->compat_robust_list;
|
|
struct robust_list __user *entry, *next_entry, *pending;
|
|
unsigned int limit = ROBUST_LIST_LIMIT, pi, next_pi, pip;
|
|
compat_uptr_t uentry, next_uentry, upending;
|
|
compat_long_t futex_offset;
|
|
int rc;
|
|
|
|
/*
|
|
* Fetch the list head (which was registered earlier, via
|
|
* sys_set_robust_list()):
|
|
*/
|
|
if (fetch_robust_entry(&uentry, &entry, &head->list.next, &pi))
|
|
return;
|
|
/*
|
|
* Fetch the relative futex offset:
|
|
*/
|
|
if (get_user(futex_offset, &head->futex_offset))
|
|
return;
|
|
/*
|
|
* Fetch any possibly pending lock-add first, and handle it
|
|
* if it exists:
|
|
*/
|
|
if (fetch_robust_entry(&upending, &pending,
|
|
&head->list_op_pending, &pip))
|
|
return;
|
|
|
|
next_entry = NULL; /* avoid warning with gcc */
|
|
while (entry != (struct robust_list __user *) &head->list) {
|
|
/*
|
|
* Fetch the next entry in the list before calling
|
|
* handle_futex_death:
|
|
*/
|
|
rc = fetch_robust_entry(&next_uentry, &next_entry,
|
|
(compat_uptr_t __user *)&entry->next, &next_pi);
|
|
/*
|
|
* A pending lock might already be on the list, so
|
|
* dont process it twice:
|
|
*/
|
|
if (entry != pending) {
|
|
void __user *uaddr = futex_uaddr(entry, futex_offset);
|
|
|
|
if (handle_futex_death(uaddr, curr, pi))
|
|
return;
|
|
}
|
|
if (rc)
|
|
return;
|
|
uentry = next_uentry;
|
|
entry = next_entry;
|
|
pi = next_pi;
|
|
/*
|
|
* Avoid excessively long or circular lists:
|
|
*/
|
|
if (!--limit)
|
|
break;
|
|
|
|
cond_resched();
|
|
}
|
|
if (pending) {
|
|
void __user *uaddr = futex_uaddr(pending, futex_offset);
|
|
|
|
handle_futex_death(uaddr, curr, pip);
|
|
}
|
|
}
|
|
|
|
asmlinkage long
|
|
compat_sys_set_robust_list(struct compat_robust_list_head __user *head,
|
|
compat_size_t len)
|
|
{
|
|
if (unlikely(len != sizeof(*head)))
|
|
return -EINVAL;
|
|
|
|
current->compat_robust_list = head;
|
|
|
|
return 0;
|
|
}
|
|
|
|
asmlinkage long
|
|
compat_sys_get_robust_list(int pid, compat_uptr_t __user *head_ptr,
|
|
compat_size_t __user *len_ptr)
|
|
{
|
|
struct compat_robust_list_head __user *head;
|
|
unsigned long ret;
|
|
|
|
if (!pid)
|
|
head = current->compat_robust_list;
|
|
else {
|
|
struct task_struct *p;
|
|
|
|
ret = -ESRCH;
|
|
read_lock(&tasklist_lock);
|
|
p = find_task_by_vpid(pid);
|
|
if (!p)
|
|
goto err_unlock;
|
|
ret = -EPERM;
|
|
if ((current->euid != p->euid) && (current->euid != p->uid) &&
|
|
!capable(CAP_SYS_PTRACE))
|
|
goto err_unlock;
|
|
head = p->compat_robust_list;
|
|
read_unlock(&tasklist_lock);
|
|
}
|
|
|
|
if (put_user(sizeof(*head), len_ptr))
|
|
return -EFAULT;
|
|
return put_user(ptr_to_compat(head), head_ptr);
|
|
|
|
err_unlock:
|
|
read_unlock(&tasklist_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
asmlinkage long compat_sys_futex(u32 __user *uaddr, int op, u32 val,
|
|
struct compat_timespec __user *utime, u32 __user *uaddr2,
|
|
u32 val3)
|
|
{
|
|
struct timespec ts;
|
|
ktime_t t, *tp = NULL;
|
|
int val2 = 0;
|
|
int cmd = op & FUTEX_CMD_MASK;
|
|
|
|
if (utime && (cmd == FUTEX_WAIT || cmd == FUTEX_LOCK_PI ||
|
|
cmd == FUTEX_WAIT_BITSET)) {
|
|
if (get_compat_timespec(&ts, utime))
|
|
return -EFAULT;
|
|
if (!timespec_valid(&ts))
|
|
return -EINVAL;
|
|
|
|
t = timespec_to_ktime(ts);
|
|
if (cmd == FUTEX_WAIT)
|
|
t = ktime_add(ktime_get(), t);
|
|
tp = &t;
|
|
}
|
|
if (cmd == FUTEX_REQUEUE || cmd == FUTEX_CMP_REQUEUE)
|
|
val2 = (int) (unsigned long) utime;
|
|
|
|
return do_futex(uaddr, op, val, tp, uaddr2, val2, val3);
|
|
}
|