Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 49 additions & 9 deletions kernel/futex.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@
#define FUTEX_WAIT_ 0
#define FUTEX_WAKE_ 1
#define FUTEX_REQUEUE_ 3
#define FUTEX_WAIT_BITSET_ 9
#define FUTEX_WAKE_BITSET_ 10
#define FUTEX_PRIVATE_FLAG_ 128
#define FUTEX_CMD_MASK_ ~(FUTEX_PRIVATE_FLAG_)
#define FUTEX_CLOCK_REALTIME_ 256
// FUTEX_CLOCK_REALTIME selects the clock for FUTEX_WAIT_BITSET's absolute
// timeout; like FUTEX_PRIVATE_FLAG it is a modifier, not part of the command,
// so it has to come off before the switch.
#define FUTEX_CMD_MASK_ ~(FUTEX_PRIVATE_FLAG_ | FUTEX_CLOCK_REALTIME_)
#define FUTEX_BITSET_MATCH_ANY_ 0xffffffff

struct futex {
atomic_uint refcount;
Expand All @@ -18,6 +25,7 @@ struct futex_wait {
cond_t cond;
struct futex *futex; // will be changed by a requeue
struct list queue;
dword_t bitset; // FUTEX_BITSET_MATCH_ANY_ for a plain FUTEX_WAIT
};

#define FUTEX_HASH_BITS 12
Expand Down Expand Up @@ -90,7 +98,7 @@ static int futex_load(struct futex *futex, dword_t *out) {
return 0;
}

static int futex_wait(addr_t uaddr, dword_t val, struct timespec *timeout) {
static int futex_wait(addr_t uaddr, dword_t val, struct timespec *timeout, dword_t bitset) {
struct futex *futex = futex_get(uaddr);
int err = 0;
dword_t tmp;
Expand All @@ -102,6 +110,7 @@ static int futex_wait(addr_t uaddr, dword_t val, struct timespec *timeout) {
struct futex_wait wait;
wait.cond = COND_INITIALIZER;
wait.futex = futex;
wait.bitset = bitset;
list_add_tail(&futex->queue, &wait.queue);
err = wait_for(&wait.cond, &futex_lock, timeout);
futex = wait.futex;
Expand All @@ -112,14 +121,19 @@ static int futex_wait(addr_t uaddr, dword_t val, struct timespec *timeout) {
return err;
}

static int futex_wakelike(int op, addr_t uaddr, dword_t wake_max, dword_t requeue_max, addr_t requeue_addr) {
static int futex_wakelike(int op, addr_t uaddr, dword_t wake_max, dword_t requeue_max, addr_t requeue_addr, dword_t bitset) {
struct futex *futex = futex_get(uaddr);

struct futex_wait *wait, *tmp;
unsigned woken = 0;
list_for_each_entry_safe(&futex->queue, wait, tmp, queue) {
if (woken >= wake_max)
break;
// A waiter only wakes if its mask overlaps the waker's. Plain
// FUTEX_WAIT/FUTEX_WAKE both use FUTEX_BITSET_MATCH_ANY_, so they are
// unaffected.
if ((wait->bitset & bitset) == 0)
continue;
notify(&wait->cond);
list_remove(&wait->queue);
woken++;
Expand Down Expand Up @@ -149,31 +163,57 @@ static int futex_wakelike(int op, addr_t uaddr, dword_t wake_max, dword_t requeu
}

int futex_wake(addr_t uaddr, dword_t wake_max) {
return futex_wakelike(FUTEX_WAKE_, uaddr, wake_max, 0, 0);
return futex_wakelike(FUTEX_WAKE_, uaddr, wake_max, 0, 0, FUTEX_BITSET_MATCH_ANY_);
}

dword_t sys_futex(addr_t uaddr, dword_t op, dword_t val, addr_t timeout_or_val2, addr_t uaddr2, dword_t val3) {
if (!(op & FUTEX_PRIVATE_FLAG_)) {
STRACE("!FUTEX_PRIVATE ");
}
struct timespec timeout = {0};
if ((op & FUTEX_CMD_MASK_) == FUTEX_WAIT_ && timeout_or_val2) {
int cmd = op & FUTEX_CMD_MASK_;
if ((cmd == FUTEX_WAIT_ || cmd == FUTEX_WAIT_BITSET_) && timeout_or_val2) {
struct timespec_ timeout_;
if (user_get(timeout_or_val2, timeout_))
return _EFAULT;
timeout.tv_sec = timeout_.sec;
timeout.tv_nsec = timeout_.nsec;
if (cmd == FUTEX_WAIT_BITSET_) {
// FUTEX_WAIT takes a relative timeout, FUTEX_WAIT_BITSET an
// absolute one -- against CLOCK_REALTIME when FUTEX_CLOCK_REALTIME
// is set, CLOCK_MONOTONIC otherwise. wait_for wants it relative.
struct timespec now;
clock_gettime(op & FUTEX_CLOCK_REALTIME_ ? CLOCK_REALTIME : CLOCK_MONOTONIC, &now);
timeout.tv_sec -= now.tv_sec;
timeout.tv_nsec -= now.tv_nsec;
if (timeout.tv_nsec < 0) {
timeout.tv_nsec += 1000000000;
timeout.tv_sec--;
}
if (timeout.tv_sec < 0)
timeout.tv_sec = timeout.tv_nsec = 0; // already expired
}
}
switch (op & FUTEX_CMD_MASK_) {
switch (cmd) {
case FUTEX_WAIT_:
STRACE("futex(FUTEX_WAIT, %#x, %d, 0x%x {%ds %dns}) = ...\n", uaddr, val, timeout_or_val2, timeout.tv_sec, timeout.tv_nsec);
return futex_wait(uaddr, val, timeout_or_val2 ? &timeout : NULL);
return futex_wait(uaddr, val, timeout_or_val2 ? &timeout : NULL, FUTEX_BITSET_MATCH_ANY_);
case FUTEX_WAIT_BITSET_:
STRACE("futex(FUTEX_WAIT_BITSET, %#x, %d, 0x%x {%ds %dns}, %#x) = ...\n", uaddr, val, timeout_or_val2, timeout.tv_sec, timeout.tv_nsec, val3);
if (val3 == 0)
return _EINVAL;
return futex_wait(uaddr, val, timeout_or_val2 ? &timeout : NULL, val3);
case FUTEX_WAKE_:
STRACE("futex(FUTEX_WAKE, %#x, %d)", uaddr, val);
return futex_wakelike(op & FUTEX_CMD_MASK_, uaddr, val, 0, 0);
return futex_wakelike(cmd, uaddr, val, 0, 0, FUTEX_BITSET_MATCH_ANY_);
case FUTEX_WAKE_BITSET_:
STRACE("futex(FUTEX_WAKE_BITSET, %#x, %d, %#x)", uaddr, val, val3);
if (val3 == 0)
return _EINVAL;
return futex_wakelike(FUTEX_WAKE_, uaddr, val, 0, 0, val3);
case FUTEX_REQUEUE_:
STRACE("futex(FUTEX_REQUEUE, %#x, %d, %#x)", uaddr, val, uaddr2);
return futex_wakelike(op & FUTEX_CMD_MASK_, uaddr, val, timeout_or_val2, uaddr2);
return futex_wakelike(cmd, uaddr, val, timeout_or_val2, uaddr2, FUTEX_BITSET_MATCH_ANY_);
}
STRACE("futex(%#x, %d, %d, timeout=%#x, %#x, %d) ", uaddr, op, val, timeout_or_val2, uaddr2, val3);
FIXME("unsupported futex operation %d", op);
Expand Down
Loading