#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: sys_futex.c,v 1.26 2025/03/05 14:01:55 riastradh Exp $");
#include <sys/param.h>
#include <sys/types.h>
#include <sys/atomic.h>
#include <sys/condvar.h>
#include <sys/futex.h>
#include <sys/mutex.h>
#include <sys/rbtree.h>
#include <sys/queue.h>
#include <sys/syscall.h>
#include <sys/syscallargs.h>
#include <sys/syscallvar.h>
#include <uvm/uvm_extern.h>
union futex_key {
struct {
struct vmspace *vmspace;
vaddr_t va;
} fk_private;
struct uvm_voaddr fk_shared;
};
struct futex {
union futex_key fx_key;
unsigned long fx_refcnt;
bool fx_shared;
bool fx_on_tree;
struct rb_node fx_node;
kmutex_t fx_qlock;
TAILQ_HEAD(, futex_wait) fx_queue;
kmutex_t fx_abortlock;
LIST_HEAD(, futex_wait) fx_abortlist;
kcondvar_t fx_abortcv;
};
struct futex_wait {
kmutex_t fw_lock;
kcondvar_t fw_cv;
struct futex *fw_futex;
TAILQ_ENTRY(futex_wait) fw_entry;
LIST_ENTRY(futex_wait) fw_abort;
int fw_bitset;
bool fw_aborting;
};
static struct {
kmutex_t lock;
struct rb_tree va;
struct rb_tree oa;
} futex_tab __cacheline_aligned;
static int
compare_futex_key(void *cookie, const void *n, const void *k)
{
const struct futex *fa = n;
const union futex_key *fka = &fa->fx_key;
const union futex_key *fkb = k;
if ((uintptr_t)fka->fk_private.vmspace <
(uintptr_t)fkb->fk_private.vmspace)
return -1;
if ((uintptr_t)fka->fk_private.vmspace >
(uintptr_t)fkb->fk_private.vmspace)
return +1;
if (fka->fk_private.va < fkb->fk_private.va)
return -1;
if (fka->fk_private.va > fkb->fk_private.va)
return +1;
return 0;
}
static int
compare_futex(void *cookie, const void *na, const void *nb)
{
const struct futex *fa = na;
const struct futex *fb = nb;
return compare_futex_key(cookie, fa, &fb->fx_key);
}
static const rb_tree_ops_t futex_rb_ops = {
.rbto_compare_nodes = compare_futex,
.rbto_compare_key = compare_futex_key,
.rbto_node_offset = offsetof(struct futex, fx_node),
};
static int
compare_futex_shared_key(void *cookie, const void *n, const void *k)
{
const struct futex *fa = n;
const union futex_key *fka = &fa->fx_key;
const union futex_key *fkb = k;
return uvm_voaddr_compare(&fka->fk_shared, &fkb->fk_shared);
}
static int
compare_futex_shared(void *cookie, const void *na, const void *nb)
{
const struct futex *fa = na;
const struct futex *fb = nb;
return compare_futex_shared_key(cookie, fa, &fb->fx_key);
}
static const rb_tree_ops_t futex_shared_rb_ops = {
.rbto_compare_nodes = compare_futex_shared,
.rbto_compare_key = compare_futex_shared_key,
.rbto_node_offset = offsetof(struct futex, fx_node),
};
static void futex_wait_dequeue(struct futex_wait *, struct futex *);
static inline int
futex_load(int *uaddr, int *kaddr)
{
return ufetch_int((u_int *)uaddr, (u_int *)kaddr);
}
static bool
futex_test(int *uaddr, int expected)
{
int val;
int error;
error = futex_load(uaddr, &val);
if (error)
return false;
return val == expected;
}
void
futex_sys_init(void)
{
mutex_init(&futex_tab.lock, MUTEX_DEFAULT, IPL_NONE);
rb_tree_init(&futex_tab.va, &futex_rb_ops);
rb_tree_init(&futex_tab.oa, &futex_shared_rb_ops);
}
void
futex_sys_fini(void)
{
KASSERT(RB_TREE_MIN(&futex_tab.oa) == NULL);
KASSERT(RB_TREE_MIN(&futex_tab.va) == NULL);
mutex_destroy(&futex_tab.lock);
}
static void
futex_queue_init(struct futex *f)
{
mutex_init(&f->fx_qlock, MUTEX_DEFAULT, IPL_NONE);
mutex_init(&f->fx_abortlock, MUTEX_DEFAULT, IPL_NONE);
cv_init(&f->fx_abortcv, "fqabort");
LIST_INIT(&f->fx_abortlist);
TAILQ_INIT(&f->fx_queue);
}
static void
futex_queue_drain(struct futex *f)
{
struct futex_wait *fw, *fw_next;
mutex_enter(&f->fx_abortlock);
while (!LIST_EMPTY(&f->fx_abortlist))
cv_wait(&f->fx_abortcv, &f->fx_abortlock);
mutex_exit(&f->fx_abortlock);
mutex_enter(&f->fx_qlock);
TAILQ_FOREACH_SAFE(fw, &f->fx_queue, fw_entry, fw_next) {
mutex_enter(&fw->fw_lock);
futex_wait_dequeue(fw, f);
cv_broadcast(&fw->fw_cv);
mutex_exit(&fw->fw_lock);
}
mutex_exit(&f->fx_qlock);
}
static void
futex_queue_fini(struct futex *f)
{
KASSERT(TAILQ_EMPTY(&f->fx_queue));
KASSERT(LIST_EMPTY(&f->fx_abortlist));
mutex_destroy(&f->fx_qlock);
mutex_destroy(&f->fx_abortlock);
cv_destroy(&f->fx_abortcv);
}
static int
futex_key_init(union futex_key *fk, struct vmspace *vm, vaddr_t va, bool shared)
{
int error = 0;
if (__predict_false(shared)) {
if (!uvm_voaddr_acquire(&vm->vm_map, va, &fk->fk_shared))
error = EFAULT;
} else {
fk->fk_private.vmspace = vm;
fk->fk_private.va = va;
}
return error;
}
static void
futex_key_fini(union futex_key *fk, bool shared)
{
if (__predict_false(shared))
uvm_voaddr_release(&fk->fk_shared);
memset(fk, 0, sizeof(*fk));
}
static struct futex *
futex_create(union futex_key *fk, bool shared)
{
struct futex *f;
f = kmem_alloc(sizeof(*f), KM_NOSLEEP);
if (f == NULL) {
futex_key_fini(fk, shared);
return NULL;
}
f->fx_key = *fk;
f->fx_refcnt = 1;
f->fx_shared = shared;
f->fx_on_tree = false;
futex_queue_init(f);
return f;
}
static void
futex_destroy(struct futex *f)
{
ASSERT_SLEEPABLE();
KASSERT(atomic_load_relaxed(&f->fx_refcnt) == 0);
KASSERT(!f->fx_on_tree);
futex_queue_drain(f);
futex_queue_fini(f);
futex_key_fini(&f->fx_key, f->fx_shared);
kmem_free(f, sizeof(*f));
}
static int
futex_hold(struct futex *f)
{
unsigned long refcnt;
do {
refcnt = atomic_load_relaxed(&f->fx_refcnt);
if (refcnt == ULONG_MAX)
return ENFILE;
} while (atomic_cas_ulong(&f->fx_refcnt, refcnt, refcnt + 1) != refcnt);
return 0;
}
static void
futex_rele(struct futex *f)
{
unsigned long refcnt;
ASSERT_SLEEPABLE();
do {
refcnt = atomic_load_relaxed(&f->fx_refcnt);
if (refcnt == 1)
goto trylast;
membar_release();
} while (atomic_cas_ulong(&f->fx_refcnt, refcnt, refcnt - 1) != refcnt);
return;
trylast:
mutex_enter(&futex_tab.lock);
if (atomic_dec_ulong_nv(&f->fx_refcnt) == 0) {
membar_acquire();
if (f->fx_on_tree) {
if (__predict_false(f->fx_shared))
rb_tree_remove_node(&futex_tab.oa, f);
else
rb_tree_remove_node(&futex_tab.va, f);
f->fx_on_tree = false;
}
} else {
f = NULL;
}
mutex_exit(&futex_tab.lock);
if (f != NULL)
futex_destroy(f);
}
static void
futex_rele_not_last(struct futex *f)
{
unsigned long refcnt;
do {
refcnt = atomic_load_relaxed(&f->fx_refcnt);
KASSERT(refcnt > 1);
} while (atomic_cas_ulong(&f->fx_refcnt, refcnt, refcnt - 1) != refcnt);
}
static int
futex_lookup_by_key(union futex_key *fk, bool shared, struct futex **fp)
{
struct futex *f;
int error = 0;
mutex_enter(&futex_tab.lock);
if (__predict_false(shared)) {
f = rb_tree_find_node(&futex_tab.oa, fk);
} else {
f = rb_tree_find_node(&futex_tab.va, fk);
}
if (f) {
error = futex_hold(f);
if (error)
f = NULL;
}
*fp = f;
mutex_exit(&futex_tab.lock);
return error;
}
static int
futex_insert(struct futex *f, struct futex **fp)
{
struct futex *f0;
int error;
KASSERT(atomic_load_relaxed(&f->fx_refcnt) != 0);
KASSERT(!f->fx_on_tree);
mutex_enter(&futex_tab.lock);
if (__predict_false(f->fx_shared))
f0 = rb_tree_insert_node(&futex_tab.oa, f);
else
f0 = rb_tree_insert_node(&futex_tab.va, f);
if (f0 == f) {
f->fx_on_tree = true;
error = 0;
} else {
KASSERT(atomic_load_relaxed(&f0->fx_refcnt) != 0);
KASSERT(f0->fx_on_tree);
error = futex_hold(f0);
if (error)
goto out;
}
*fp = f0;
out: mutex_exit(&futex_tab.lock);
return error;
}
static int
futex_lookup(int *uaddr, bool shared, struct futex **fp)
{
union futex_key fk;
struct vmspace *vm = curproc->p_vmspace;
vaddr_t va = (vaddr_t)uaddr;
int error;
if ((va & 3) != 0)
return EINVAL;
error = futex_key_init(&fk, vm, va, shared);
if (error)
return error;
error = futex_lookup_by_key(&fk, shared, fp);
futex_key_fini(&fk, shared);
if (error)
return error;
KASSERT(*fp == NULL || (*fp)->fx_shared == shared);
KASSERT(*fp == NULL || atomic_load_relaxed(&(*fp)->fx_refcnt) != 0);
KASSERT(error == 0);
return error;
}
static int
futex_lookup_create(int *uaddr, bool shared, struct futex **fp)
{
union futex_key fk;
struct vmspace *vm = curproc->p_vmspace;
struct futex *f = NULL;
vaddr_t va = (vaddr_t)uaddr;
int error;
if ((va & 3) != 0)
return EINVAL;
error = futex_key_init(&fk, vm, va, shared);
if (error)
return error;
error = futex_lookup_by_key(&fk, shared, fp);
if (error || *fp != NULL) {
futex_key_fini(&fk, shared);
goto out;
}
f = futex_create(&fk, shared);
if (f == NULL) {
error = ENOMEM;
goto out;
}
error = futex_insert(f, fp);
if (error)
goto out;
if (*fp == f)
f = NULL;
KASSERT(error == 0);
out: if (f != NULL)
futex_rele(f);
KASSERT(error || *fp != NULL);
KASSERT(error || atomic_load_relaxed(&(*fp)->fx_refcnt) != 0);
return error;
}
static void
futex_wait_init(struct futex_wait *fw, int bitset)
{
KASSERT(bitset);
mutex_init(&fw->fw_lock, MUTEX_DEFAULT, IPL_NONE);
cv_init(&fw->fw_cv, "futex");
fw->fw_futex = NULL;
fw->fw_bitset = bitset;
fw->fw_aborting = false;
}
static void
futex_wait_fini(struct futex_wait *fw)
{
KASSERT(fw->fw_futex == NULL);
cv_destroy(&fw->fw_cv);
mutex_destroy(&fw->fw_lock);
}
static void
futex_wait_enqueue(struct futex_wait *fw, struct futex *f)
{
KASSERT(mutex_owned(&f->fx_qlock));
KASSERT(mutex_owned(&fw->fw_lock));
KASSERT(fw->fw_futex == NULL);
KASSERT(!fw->fw_aborting);
fw->fw_futex = f;
TAILQ_INSERT_TAIL(&f->fx_queue, fw, fw_entry);
}
static void
futex_wait_dequeue(struct futex_wait *fw, struct futex *f)
{
KASSERT(mutex_owned(&f->fx_qlock));
KASSERT(mutex_owned(&fw->fw_lock));
KASSERT(fw->fw_futex == f);
TAILQ_REMOVE(&f->fx_queue, fw, fw_entry);
fw->fw_futex = NULL;
}
static void
futex_wait_abort(struct futex_wait *fw)
{
struct futex *f;
KASSERT(mutex_owned(&fw->fw_lock));
f = fw->fw_futex;
mutex_enter(&f->fx_abortlock);
LIST_INSERT_HEAD(&f->fx_abortlist, fw, fw_abort);
mutex_exit(&f->fx_abortlock);
fw->fw_aborting = true;
mutex_exit(&fw->fw_lock);
mutex_enter(&f->fx_qlock);
mutex_enter(&fw->fw_lock);
futex_wait_dequeue(fw, f);
mutex_exit(&fw->fw_lock);
mutex_exit(&f->fx_qlock);
mutex_enter(&f->fx_abortlock);
LIST_REMOVE(fw, fw_abort);
if (LIST_EMPTY(&f->fx_abortlist))
cv_broadcast(&f->fx_abortcv);
mutex_exit(&f->fx_abortlock);
futex_rele(f);
mutex_enter(&fw->fw_lock);
KASSERT(fw->fw_aborting);
KASSERT(fw->fw_futex == NULL);
}
static int
futex_wait(struct futex_wait *fw, const struct timespec *deadline,
clockid_t clkid)
{
int error = 0;
mutex_enter(&fw->fw_lock);
for (;;) {
if (fw->fw_bitset == 0 || fw->fw_futex == NULL) {
error = 0;
break;
}
if (error)
break;
if (deadline) {
struct timespec ts;
error = clock_gettime1(clkid, &ts);
if (error)
break;
if (timespeccmp(deadline, &ts, <=)) {
error = ETIMEDOUT;
break;
}
timespecsub(deadline, &ts, &ts);
error = cv_timedwait_sig(&fw->fw_cv, &fw->fw_lock,
MAX(1, tstohz(&ts)));
if (error == EWOULDBLOCK)
error = 0;
} else {
error = cv_wait_sig(&fw->fw_cv, &fw->fw_lock);
}
}
if (error)
futex_wait_abort(fw);
mutex_exit(&fw->fw_lock);
return error;
}
static unsigned
futex_wake(struct futex *f, unsigned nwake, struct futex *f2,
unsigned nrequeue, int bitset)
{
struct futex_wait *fw, *fw_next;
unsigned nwoken_or_requeued = 0;
int hold_error __diagused;
KASSERT(mutex_owned(&f->fx_qlock));
KASSERT(f2 == NULL || mutex_owned(&f2->fx_qlock));
TAILQ_FOREACH_SAFE(fw, &f->fx_queue, fw_entry, fw_next) {
if ((fw->fw_bitset & bitset) == 0)
continue;
if (nwake > 0) {
mutex_enter(&fw->fw_lock);
if (__predict_false(fw->fw_aborting)) {
mutex_exit(&fw->fw_lock);
continue;
}
futex_wait_dequeue(fw, f);
fw->fw_bitset = 0;
cv_broadcast(&fw->fw_cv);
mutex_exit(&fw->fw_lock);
nwake--;
nwoken_or_requeued++;
futex_rele_not_last(f);
} else {
break;
}
}
if (f2) {
TAILQ_FOREACH_SAFE(fw, &f->fx_queue, fw_entry, fw_next) {
if ((fw->fw_bitset & bitset) == 0)
continue;
if (nrequeue > 0) {
mutex_enter(&fw->fw_lock);
if (__predict_false(fw->fw_aborting)) {
mutex_exit(&fw->fw_lock);
continue;
}
futex_wait_dequeue(fw, f);
futex_wait_enqueue(fw, f2);
mutex_exit(&fw->fw_lock);
nrequeue--;
KASSERT(nwoken_or_requeued <
MIN(PID_MAX*MAXMAXLWP, FUTEX_TID_MASK));
__CTASSERT(UINT_MAX >=
MIN(PID_MAX*MAXMAXLWP, FUTEX_TID_MASK));
if (++nwoken_or_requeued == 0)
nwoken_or_requeued = UINT_MAX;
futex_rele_not_last(f);
hold_error = futex_hold(f2);
KASSERT(hold_error == 0);
} else {
break;
}
}
} else {
KASSERT(nrequeue == 0);
}
return nwoken_or_requeued;
}
static void
futex_queue_lock(struct futex *f)
{
mutex_enter(&f->fx_qlock);
}
static void
futex_queue_unlock(struct futex *f)
{
mutex_exit(&f->fx_qlock);
}
static void
futex_queue_lock2(struct futex *f, struct futex *f2)
{
if (f == NULL && f2 == NULL) {
return;
} else if (f == NULL) {
mutex_enter(&f2->fx_qlock);
return;
} else if (f2 == NULL) {
mutex_enter(&f->fx_qlock);
return;
}
if (f == f2) {
mutex_enter(&f->fx_qlock);
return;
}
if ((uintptr_t)f < (uintptr_t)f2) {
mutex_enter(&f->fx_qlock);
mutex_enter(&f2->fx_qlock);
} else {
mutex_enter(&f2->fx_qlock);
mutex_enter(&f->fx_qlock);
}
}
static void
futex_queue_unlock2(struct futex *f, struct futex *f2)
{
if (f == NULL && f2 == NULL) {
return;
} else if (f == NULL) {
mutex_exit(&f2->fx_qlock);
return;
} else if (f2 == NULL) {
mutex_exit(&f->fx_qlock);
return;
}
if (f == f2) {
mutex_exit(&f->fx_qlock);
return;
}
if ((uintptr_t)f < (uintptr_t)f2) {
mutex_exit(&f2->fx_qlock);
mutex_exit(&f->fx_qlock);
} else {
mutex_exit(&f->fx_qlock);
mutex_exit(&f2->fx_qlock);
}
}
static int
futex_func_wait(bool shared, int *uaddr, int cmpval, int bitset,
const struct timespec *timeout, clockid_t clkid, int clkflags,
register_t *retval)
{
struct futex *f;
struct futex_wait wait, *fw = &wait;
struct timespec ts;
const struct timespec *deadline;
int error;
if (bitset == 0)
return EINVAL;
if (!futex_test(uaddr, cmpval))
return EAGAIN;
if (timeout == NULL || (clkflags & TIMER_ABSTIME) == TIMER_ABSTIME) {
deadline = timeout;
} else {
error = clock_gettime1(clkid, &ts);
if (error)
return error;
timespecadd(&ts, timeout, &ts);
deadline = &ts;
}
error = futex_lookup_create(uaddr, shared, &f);
if (error)
return error;
KASSERT(f);
futex_wait_init(fw, bitset);
futex_queue_lock(f);
if (!futex_test(uaddr, cmpval)) {
futex_queue_unlock(f);
error = EAGAIN;
goto out;
}
mutex_enter(&fw->fw_lock);
futex_wait_enqueue(fw, f);
mutex_exit(&fw->fw_lock);
futex_queue_unlock(f);
f = NULL;
error = futex_wait(fw, deadline, clkid);
if (error)
goto out;
*retval = 0;
out: if (f != NULL)
futex_rele(f);
futex_wait_fini(fw);
return error;
}
static int
futex_func_wake(bool shared, int *uaddr, int nwake, int bitset,
register_t *retval)
{
struct futex *f;
unsigned int nwoken = 0;
int error = 0;
if (nwake < 0) {
error = EINVAL;
goto out;
}
error = futex_lookup(uaddr, shared, &f);
if (error)
goto out;
if (f == NULL)
goto out;
futex_queue_lock(f);
nwoken = futex_wake(f, nwake, NULL, 0, bitset);
futex_queue_unlock(f);
futex_rele(f);
out:
*retval = nwoken;
return error;
}
static int
futex_func_requeue(bool shared, int op, int *uaddr, int nwake, int *uaddr2,
int nrequeue, int cmpval, register_t *retval)
{
struct futex *f = NULL, *f2 = NULL;
unsigned nwoken_or_requeued = 0;
int error;
if (nwake < 0 || nrequeue < 0) {
error = EINVAL;
goto out;
}
error = (op == FUTEX_CMP_REQUEUE
? futex_lookup_create(uaddr, shared, &f)
: futex_lookup(uaddr, shared, &f));
if (error)
goto out;
if (f == NULL) {
KASSERT(op != FUTEX_CMP_REQUEUE);
goto out;
}
error = futex_lookup_create(uaddr2, shared, &f2);
if (error)
goto out;
futex_queue_lock2(f, f2);
if (op == FUTEX_CMP_REQUEUE && !futex_test(uaddr, cmpval)) {
error = EAGAIN;
} else {
error = 0;
nwoken_or_requeued = futex_wake(f, nwake, f2, nrequeue,
FUTEX_BITSET_MATCH_ANY);
}
futex_queue_unlock2(f, f2);
out:
*retval = nwoken_or_requeued;
if (f2)
futex_rele(f2);
if (f)
futex_rele(f);
return error;
}
static int
futex_opcmp_arg(int arg)
{
KASSERT(arg == (arg & __BITS(11,0)));
return arg - 0x1000*__SHIFTOUT(arg, __BIT(11));
}
static int
futex_validate_op_cmp(int opcmp)
{
int op = __SHIFTOUT(opcmp, FUTEX_OP_OP_MASK);
int cmp = __SHIFTOUT(opcmp, FUTEX_OP_CMP_MASK);
if (op & FUTEX_OP_OPARG_SHIFT) {
int oparg =
futex_opcmp_arg(__SHIFTOUT(opcmp, FUTEX_OP_OPARG_MASK));
if (oparg < 0)
return EINVAL;
if (oparg >= 32)
return EINVAL;
op &= ~FUTEX_OP_OPARG_SHIFT;
}
switch (op) {
case FUTEX_OP_SET:
case FUTEX_OP_ADD:
case FUTEX_OP_OR:
case FUTEX_OP_ANDN:
case FUTEX_OP_XOR:
break;
default:
return EINVAL;
}
switch (cmp) {
case FUTEX_OP_CMP_EQ:
case FUTEX_OP_CMP_NE:
case FUTEX_OP_CMP_LT:
case FUTEX_OP_CMP_LE:
case FUTEX_OP_CMP_GT:
case FUTEX_OP_CMP_GE:
break;
default:
return EINVAL;
}
return 0;
}
static int
futex_compute_op(int oldval, int opcmp)
{
int op = __SHIFTOUT(opcmp, FUTEX_OP_OP_MASK);
int oparg = futex_opcmp_arg(__SHIFTOUT(opcmp, FUTEX_OP_OPARG_MASK));
if (op & FUTEX_OP_OPARG_SHIFT) {
KASSERT(oparg >= 0);
KASSERT(oparg < 32);
oparg = 1u << oparg;
op &= ~FUTEX_OP_OPARG_SHIFT;
}
switch (op) {
case FUTEX_OP_SET:
return oparg;
case FUTEX_OP_ADD:
return (int)((unsigned)oldval + (unsigned)oparg);
case FUTEX_OP_OR:
return oldval | oparg;
case FUTEX_OP_ANDN:
return oldval & ~oparg;
case FUTEX_OP_XOR:
return oldval ^ oparg;
default:
panic("invalid futex op");
}
}
static bool
futex_compute_cmp(int oldval, int opcmp)
{
int cmp = __SHIFTOUT(opcmp, FUTEX_OP_CMP_MASK);
int cmparg = futex_opcmp_arg(__SHIFTOUT(opcmp, FUTEX_OP_CMPARG_MASK));
switch (cmp) {
case FUTEX_OP_CMP_EQ:
return (oldval == cmparg);
case FUTEX_OP_CMP_NE:
return (oldval != cmparg);
case FUTEX_OP_CMP_LT:
return (oldval < cmparg);
case FUTEX_OP_CMP_LE:
return (oldval <= cmparg);
case FUTEX_OP_CMP_GT:
return (oldval > cmparg);
case FUTEX_OP_CMP_GE:
return (oldval >= cmparg);
default:
panic("invalid futex cmp operation");
}
}
static int
futex_func_wake_op(bool shared, int *uaddr, int nwake, int *uaddr2, int nwake2,
int opcmp, register_t *retval)
{
struct futex *f = NULL, *f2 = NULL;
int oldval, newval, actual;
unsigned nwoken = 0;
int error;
if (nwake < 0 || nwake2 < 0) {
error = EINVAL;
goto out;
}
if ((error = futex_validate_op_cmp(opcmp)) != 0)
goto out;
error = futex_lookup(uaddr, shared, &f);
if (error)
goto out;
error = futex_lookup(uaddr2, shared, &f2);
if (error)
goto out;
futex_queue_lock2(f, f2);
do {
error = futex_load(uaddr2, &oldval);
if (error)
goto out_unlock;
newval = futex_compute_op(oldval, opcmp);
error = ucas_int(uaddr2, oldval, newval, &actual);
if (error)
goto out_unlock;
} while (actual != oldval);
if (f == NULL) {
nwoken = 0;
} else {
nwoken = futex_wake(f, nwake, NULL, 0,
FUTEX_BITSET_MATCH_ANY);
}
if (f2 && futex_compute_cmp(oldval, opcmp)) {
nwoken += futex_wake(f2, nwake2, NULL, 0,
FUTEX_BITSET_MATCH_ANY);
}
error = 0;
out_unlock:
futex_queue_unlock2(f, f2);
out:
*retval = nwoken;
if (f2)
futex_rele(f2);
if (f)
futex_rele(f);
return error;
}
int
do_futex(int *uaddr, int op, int val, const struct timespec *timeout,
int *uaddr2, int val2, int val3, register_t *retval)
{
const bool shared = (op & FUTEX_PRIVATE_FLAG) ? false : true;
const clockid_t clkid = (op & FUTEX_CLOCK_REALTIME) ? CLOCK_REALTIME
: CLOCK_MONOTONIC;
op &= FUTEX_CMD_MASK;
switch (op) {
case FUTEX_WAIT: {
const int cmpval = val;
const int bitset = FUTEX_BITSET_MATCH_ANY;
return futex_func_wait(shared, uaddr, cmpval, bitset, timeout,
clkid, TIMER_RELTIME, retval);
}
case FUTEX_WAKE: {
const int nwake = val;
const int bitset = FUTEX_BITSET_MATCH_ANY;
return futex_func_wake(shared, uaddr, nwake, bitset, retval);
}
case FUTEX_WAKE_BITSET: {
const int nwake = val;
const int bitset = val3;
return futex_func_wake(shared, uaddr, nwake, bitset, retval);
}
case FUTEX_REQUEUE:
case FUTEX_CMP_REQUEUE: {
const int nwake = val;
const int nrequeue = val2;
const int cmpval = val3;
return futex_func_requeue(shared, op, uaddr, nwake, uaddr2,
nrequeue, cmpval, retval);
}
case FUTEX_WAIT_BITSET: {
const int cmpval = val;
const int bitset = val3;
return futex_func_wait(shared, uaddr, cmpval, bitset, timeout,
clkid, TIMER_ABSTIME, retval);
}
case FUTEX_WAKE_OP: {
const int nwake = val;
const int nwake2 = val2;
const int opcmp = val3;
return futex_func_wake_op(shared, uaddr, nwake, uaddr2, nwake2,
opcmp, retval);
}
case FUTEX_FD:
default:
return ENOSYS;
}
}
int
sys___futex(struct lwp *l, const struct sys___futex_args *uap,
register_t *retval)
{
struct timespec ts, *tsp;
int error;
if (SCARG(uap, timeout)) {
error = copyin(SCARG(uap, timeout), &ts, sizeof(ts));
if (error)
return error;
tsp = &ts;
} else {
tsp = NULL;
}
return do_futex(SCARG(uap, uaddr), SCARG(uap, op), SCARG(uap, val),
tsp, SCARG(uap, uaddr2), SCARG(uap, val2), SCARG(uap, val3),
retval);
}
int
sys___futex_set_robust_list(struct lwp *l,
const struct sys___futex_set_robust_list_args *uap, register_t *retval)
{
void *head = SCARG(uap, head);
if (SCARG(uap, len) != _FUTEX_ROBUST_HEAD_SIZE)
return EINVAL;
if ((uintptr_t)head % sizeof(u_long))
return EINVAL;
l->l_robust_head = (uintptr_t)head;
return 0;
}
int
sys___futex_get_robust_list(struct lwp *l,
const struct sys___futex_get_robust_list_args *uap, register_t *retval)
{
void *head;
const size_t len = _FUTEX_ROBUST_HEAD_SIZE;
int error;
error = futex_robust_head_lookup(l, SCARG(uap, lwpid), &head);
if (error)
return error;
error = copyout(&head, SCARG(uap, headp), sizeof(head));
if (__predict_true(error == 0)) {
error = copyout(&len, SCARG(uap, lenp), sizeof(len));
}
return error;
}
static void
release_futex(uintptr_t const uptr, lwpid_t const tid, bool const is_pi,
bool const is_pending)
{
int *uaddr;
struct futex *f;
int oldval, newval, actual;
int error;
if (__predict_false(uptr & 3))
return;
uaddr = (int *)uptr;
error = futex_load(uaddr, &oldval);
if (__predict_false(error))
return;
if (__predict_false(is_pending && (oldval & ~FUTEX_WAITERS) == 0)) {
register_t retval;
(void) futex_func_wake(true, uaddr, 1,
FUTEX_BITSET_MATCH_ANY, &retval);
return;
}
if ((oldval & FUTEX_TID_MASK) != tid)
return;
if ((oldval & FUTEX_WAITERS) == 0) {
do {
error = futex_load(uaddr, &oldval);
if (error)
return;
if ((oldval & FUTEX_TID_MASK) != tid)
return;
newval = oldval | FUTEX_OWNER_DIED;
error = ucas_int(uaddr, oldval, newval, &actual);
if (error)
return;
} while (actual != oldval);
if ((oldval & FUTEX_WAITERS) == 0)
return;
}
error = futex_lookup(uaddr, true, &f);
if (error)
return;
if (f == NULL)
return;
futex_queue_lock(f);
do {
error = futex_load(uaddr, &oldval);
if (error)
goto out;
if ((oldval & FUTEX_TID_MASK) != tid)
goto out;
newval = oldval | FUTEX_OWNER_DIED;
error = ucas_int(uaddr, oldval, newval, &actual);
if (error)
goto out;
} while (actual != oldval);
if (oldval & FUTEX_WAITERS) {
(void)futex_wake(f, 1, NULL, 0,
FUTEX_BITSET_MATCH_ANY);
}
out: futex_queue_unlock(f);
futex_rele(f);
}
int
futex_robust_head_lookup(struct lwp *l, lwpid_t lwpid, void **headp)
{
struct proc *p = l->l_proc;
if (lwpid) {
mutex_enter(p->p_lock);
l = lwp_find(p, lwpid);
if (l == NULL) {
mutex_exit(p->p_lock);
return ESRCH;
}
*headp = (void *)l->l_robust_head;
mutex_exit(p->p_lock);
} else {
*headp = (void *)l->l_robust_head;
}
return 0;
}
static int
futex_fetch_robust_head(uintptr_t uaddr, u_long *rhead)
{
#ifdef _LP64
if (curproc->p_flag & PK_32) {
uint32_t rhead32[_FUTEX_ROBUST_HEAD_NWORDS];
int error;
error = copyin((void *)uaddr, rhead32, sizeof(rhead32));
if (__predict_true(error == 0)) {
for (int i = 0; i < _FUTEX_ROBUST_HEAD_NWORDS; i++) {
if (i == _FUTEX_ROBUST_HEAD_OFFSET) {
rhead[i] = (int32_t)rhead32[i];
} else {
rhead[i] = rhead32[i];
}
}
}
return error;
}
#endif
return copyin((void *)uaddr, rhead,
sizeof(*rhead) * _FUTEX_ROBUST_HEAD_NWORDS);
}
static inline void
futex_decode_robust_word(uintptr_t const word, uintptr_t * const entry,
bool * const is_pi)
{
*is_pi = (word & _FUTEX_ROBUST_ENTRY_PI) ? true : false;
*entry = word & ~_FUTEX_ROBUST_ENTRY_PI;
}
static int
futex_fetch_robust_entry(uintptr_t const uaddr, uintptr_t * const valp,
bool * const is_pi)
{
uintptr_t val = 0;
int error = 0;
#ifdef _LP64
if (curproc->p_flag & PK_32) {
uint32_t val32;
error = ufetch_32((uint32_t *)uaddr, &val32);
if (__predict_true(error == 0))
val = val32;
} else
#endif
error = ufetch_long((u_long *)uaddr, (u_long *)&val);
if (__predict_false(error))
return error;
futex_decode_robust_word(val, valp, is_pi);
return 0;
}
void
futex_release_all_lwp(struct lwp * const l)
{
u_long rhead[_FUTEX_ROBUST_HEAD_NWORDS];
int limit = 1000000;
int error;
if (l->l_robust_head == 0)
return;
KASSERT((l->l_lid & FUTEX_TID_MASK) == l->l_lid);
error = futex_fetch_robust_head(l->l_robust_head, rhead);
if (error) {
printf("WARNING: pid %jd (%s) lwp %jd:"
" unmapped robust futex list head\n",
(uintmax_t)l->l_proc->p_pid, l->l_proc->p_comm,
(uintmax_t)l->l_lid);
return;
}
const long offset = (long)rhead[_FUTEX_ROBUST_HEAD_OFFSET];
uintptr_t next, pending;
bool is_pi, pending_is_pi;
futex_decode_robust_word(rhead[_FUTEX_ROBUST_HEAD_LIST],
&next, &is_pi);
futex_decode_robust_word(rhead[_FUTEX_ROBUST_HEAD_PENDING],
&pending, &pending_is_pi);
while (next != l->l_robust_head && limit-- > 0) {
if (next != pending)
release_futex(next + offset, l->l_lid, is_pi, false);
error = futex_fetch_robust_entry(next, &next, &is_pi);
if (error)
break;
preempt_point();
}
if (limit <= 0) {
printf("WARNING: pid %jd (%s) lwp %jd:"
" exhausted robust futex limit\n",
(uintmax_t)l->l_proc->p_pid, l->l_proc->p_comm,
(uintmax_t)l->l_lid);
}
if (pending != 0) {
release_futex(pending + offset, l->l_lid, pending_is_pi, true);
}
}