#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/mount.h>
#include <sys/syscallargs.h>
#include <sys/pool.h>
#include <sys/time.h>
#include <sys/rwlock.h>
#include <sys/percpu.h>
#include <sys/futex.h>
#ifdef KTRACE
#include <sys/ktrace.h>
#endif
#include <uvm/uvm.h>
struct futex_slpque;
struct futex {
struct futex_slpque * volatile
ft_fsq;
TAILQ_ENTRY(futex) ft_entry;
struct process *ft_ps;
struct uvm_object *ft_obj;
struct vm_amap *ft_amap;
volatile voff_t ft_off;
struct proc * volatile ft_proc;
};
static int
futex_is_eq(const struct futex *a, const struct futex *b)
{
return (a->ft_off == b->ft_off &&
a->ft_ps == b->ft_ps &&
a->ft_obj == b->ft_obj &&
a->ft_amap == b->ft_amap);
}
TAILQ_HEAD(futex_list, futex);
struct futex_slpque {
struct futex_list fsq_list;
struct rwlock fsq_lock;
uint32_t fsq_id;
} __aligned(CACHELINESIZE);
static int futex_wait(struct proc *, uint32_t *, uint32_t,
const struct timespec *, int);
static int futex_wake(struct proc *, uint32_t *, uint32_t, int,
register_t *);
static int futex_requeue(struct proc *, uint32_t *, uint32_t,
uint32_t *, uint32_t, int, register_t *);
#define FT_PRIVATE FUTEX_PRIVATE_FLAG
#define FUTEX_SLPQUES_BITS 6
#define FUTEX_SLPQUES_SIZE (1U << FUTEX_SLPQUES_BITS)
#define FUTEX_SLPQUES_MASK (FUTEX_SLPQUES_SIZE - 1)
static struct futex_slpque futex_slpques[FUTEX_SLPQUES_SIZE];
void
futex_init(void)
{
struct futex_slpque *fsq;
unsigned int i;
for (i = 0; i < nitems(futex_slpques); i++) {
fsq = &futex_slpques[i];
TAILQ_INIT(&fsq->fsq_list);
rw_init(&fsq->fsq_lock, "futexlk");
fsq->fsq_id = arc4random();
fsq->fsq_id &= ~FUTEX_SLPQUES_MASK;
fsq->fsq_id |= i;
}
}
int
sys_futex(struct proc *p, void *v, register_t *retval)
{
struct sys_futex_args
*uap = v;
uint32_t *uaddr = SCARG(uap, f);
int op = SCARG(uap, op);
uint32_t val = SCARG(uap, val);
const struct timespec *timeout = SCARG(uap, timeout);
void *g = SCARG(uap, g);
int flags = op & FUTEX_FLAG_MASK;
int error = 0;
switch (op & FUTEX_OP_MASK) {
case FUTEX_WAIT:
error = futex_wait(p, uaddr, val, timeout, flags);
break;
case FUTEX_WAKE:
error = futex_wake(p, uaddr, val, flags, retval);
break;
case FUTEX_REQUEUE:
error = futex_requeue(p, uaddr, val, g,
(u_long)timeout, flags, retval);
break;
default:
error = ENOSYS;
break;
}
return error;
}
static void
futex_addrs(struct proc *p, struct futex *f, uint32_t *uaddr, int flags)
{
vm_map_t map = &p->p_vmspace->vm_map;
vm_map_entry_t entry;
struct uvm_object *obj = NULL;
struct vm_amap *amap = NULL;
voff_t off = (vaddr_t)uaddr;
struct process *ps;
if (ISSET(flags, FT_PRIVATE))
ps = p->p_p;
else {
ps = NULL;
vm_map_lock_read(map);
if (uvm_map_lookup_entry(map, (vaddr_t)uaddr, &entry) &&
entry->inheritance == MAP_INHERIT_SHARE) {
if (UVM_ET_ISOBJ(entry)) {
obj = entry->object.uvm_obj;
off = entry->offset +
((vaddr_t)uaddr - entry->start);
} else if (entry->aref.ar_amap) {
amap = entry->aref.ar_amap;
off = ptoa(entry->aref.ar_pageoff) +
((vaddr_t)uaddr - entry->start);
}
}
vm_map_unlock_read(map);
}
f->ft_ps = ps;
f->ft_obj = obj;
f->ft_amap = amap;
f->ft_off = off;
}
static inline struct futex_slpque *
futex_get_slpque(struct futex *f)
{
uint32_t key = f->ft_off >> 3;
key ^= key >> FUTEX_SLPQUES_BITS;
return (&futex_slpques[key & FUTEX_SLPQUES_MASK]);
}
static int
futex_unwait(struct futex_slpque *ofsq, struct futex *f)
{
struct futex_slpque *fsq;
int rv;
for (;;) {
rw_enter_write(&ofsq->fsq_lock);
fsq = f->ft_fsq;
if (ofsq == fsq)
break;
rw_exit_write(&ofsq->fsq_lock);
ofsq = fsq;
}
rv = f->ft_proc != NULL;
if (rv)
TAILQ_REMOVE(&fsq->fsq_list, f, ft_entry);
rw_exit_write(&fsq->fsq_lock);
return (rv);
}
static int
futex_wait(struct proc *p, uint32_t *uaddr, uint32_t val,
const struct timespec *timeout, int flags)
{
struct futex f;
struct futex_slpque *fsq;
uint64_t nsecs = INFSLP;
uint32_t cval;
int error;
if (timeout != NULL) {
struct timespec ts;
if ((error = copyin(timeout, &ts, sizeof(ts))))
return error;
#ifdef KTRACE
if (KTRPOINT(p, KTR_STRUCT))
ktrreltimespec(p, &ts);
#endif
if (ts.tv_sec < 0 || !timespecisvalid(&ts))
return EINVAL;
nsecs = MIN(TIMESPEC_TO_NSEC(&ts), MAXTSLP);
if (nsecs == 0)
return ETIMEDOUT;
}
futex_addrs(p, &f, uaddr, flags);
fsq = futex_get_slpque(&f);
f.ft_fsq = fsq;
f.ft_proc = p;
rw_enter_write(&fsq->fsq_lock);
TAILQ_INSERT_TAIL(&fsq->fsq_list, &f, ft_entry);
rw_exit_write(&fsq->fsq_lock);
if ((error = copyin32(uaddr, &cval)) != 0)
goto exit;
if (cval != val) {
error = EAGAIN;
goto exit;
}
sleep_setup(&f, PWAIT|PCATCH, "fsleep");
error = sleep_finish(nsecs, f.ft_proc != NULL);
if (error != 0 || f.ft_proc != NULL) {
if (futex_unwait(fsq, &f) == 0)
error = 0;
switch (error) {
case ERESTART:
error = ECANCELED;
break;
case EWOULDBLOCK:
error = ETIMEDOUT;
break;
default:
break;
}
}
return error;
exit:
if (f.ft_proc != NULL)
futex_unwait(fsq, &f);
return error;
}
static void
futex_list_wakeup(struct futex_list *fl)
{
struct futex *f, *nf;
struct proc *p;
SCHED_LOCK();
TAILQ_FOREACH_SAFE(f, fl, ft_entry, nf) {
p = f->ft_proc;
f->ft_proc = NULL;
wakeup_proc(p);
}
SCHED_UNLOCK();
}
static int
futex_requeue(struct proc *p, uint32_t *uaddr, uint32_t n,
uint32_t *uaddr2, uint32_t m, int flags, register_t *retval)
{
struct futex_list fl = TAILQ_HEAD_INITIALIZER(fl);
struct futex okey, nkey;
struct futex *f, *nf, *mf = NULL;
struct futex_slpque *ofsq, *nfsq;
uint32_t count = 0;
if (m == 0)
return futex_wake(p, uaddr, n, flags, retval);
futex_addrs(p, &okey, uaddr, flags);
ofsq = futex_get_slpque(&okey);
futex_addrs(p, &nkey, uaddr2, flags);
nfsq = futex_get_slpque(&nkey);
if (ofsq->fsq_id < nfsq->fsq_id) {
rw_enter_write(&ofsq->fsq_lock);
rw_enter_write(&nfsq->fsq_lock);
} else if (ofsq->fsq_id > nfsq->fsq_id) {
rw_enter_write(&nfsq->fsq_lock);
rw_enter_write(&ofsq->fsq_lock);
} else
rw_enter_write(&ofsq->fsq_lock);
TAILQ_FOREACH_SAFE(f, &ofsq->fsq_list, ft_entry, nf) {
KASSERT(f->ft_proc != NULL);
if (!futex_is_eq(f, &okey))
continue;
TAILQ_REMOVE(&ofsq->fsq_list, f, ft_entry);
TAILQ_INSERT_TAIL(&fl, f, ft_entry);
if (++count == n) {
mf = nf;
break;
}
}
if (!TAILQ_EMPTY(&fl))
futex_list_wakeup(&fl);
if (mf != NULL) {
nf = TAILQ_LAST(&ofsq->fsq_list, futex_list);
do {
f = mf;
mf = TAILQ_NEXT(f, ft_entry);
KASSERT(f->ft_proc != NULL);
if (!futex_is_eq(f, &okey))
continue;
TAILQ_REMOVE(&ofsq->fsq_list, f, ft_entry);
f->ft_fsq = nfsq;
f->ft_ps = nkey.ft_ps;
f->ft_obj = nkey.ft_obj;
f->ft_amap = nkey.ft_amap;
f->ft_off = nkey.ft_off;
TAILQ_INSERT_TAIL(&nfsq->fsq_list, f, ft_entry);
if (--m == 0)
break;
} while (f != nf);
}
if (ofsq->fsq_id != nfsq->fsq_id)
rw_exit_write(&nfsq->fsq_lock);
rw_exit_write(&ofsq->fsq_lock);
*retval = count;
return 0;
}
static int
futex_wake(struct proc *p, uint32_t *uaddr, uint32_t n, int flags,
register_t *retval)
{
struct futex_list fl = TAILQ_HEAD_INITIALIZER(fl);
struct futex key;
struct futex *f, *nf;
struct futex_slpque *fsq;
int count = 0;
if (n == 0) {
*retval = 0;
return 0;
}
futex_addrs(p, &key, uaddr, flags);
fsq = futex_get_slpque(&key);
rw_enter_write(&fsq->fsq_lock);
TAILQ_FOREACH_SAFE(f, &fsq->fsq_list, ft_entry, nf) {
KASSERT(f->ft_proc != NULL);
if (!futex_is_eq(f, &key))
continue;
TAILQ_REMOVE(&fsq->fsq_list, f, ft_entry);
TAILQ_INSERT_TAIL(&fl, f, ft_entry);
if (++count == n)
break;
}
if (!TAILQ_EMPTY(&fl))
futex_list_wakeup(&fl);
rw_exit_write(&fsq->fsq_lock);
*retval = count;
return 0;
}