#include "kernel/calls.h" struct futex { atomic_uint refcount; struct mem *mem; addr_t addr; lock_t lock; pthread_cond_t cond; struct list chain; // locked by futex_hash_lock }; #define FUTEX_HASH_BITS 12 #define FUTEX_HASH_SIZE (1 << FUTEX_HASH_BITS) static lock_t futex_hash_lock = LOCK_INITIALIZER; static struct list futex_hash[FUTEX_HASH_SIZE]; static void __attribute__((constructor)) init_futex_hash() { for (int i = 0; i < FUTEX_HASH_SIZE; i++) list_init(&futex_hash[i]); } // returns the futex for the current process at the given addr, and locks it static struct futex *futex_get(addr_t addr) { int hash = (addr ^ (unsigned long) current->mem) % FUTEX_HASH_SIZE; lock(&futex_hash_lock); struct list *bucket = &futex_hash[hash]; struct futex *futex; list_for_each_entry(bucket, futex, chain) { if (futex->addr == addr) { futex->refcount++; goto have_futex; } } futex = malloc(sizeof(struct futex)); if (futex == NULL) { unlock(&futex_hash_lock); return NULL; } futex->refcount = 0; futex->mem = current->mem; futex->addr = addr; lock_init(&futex->lock); pthread_cond_init(&futex->cond, NULL); list_add(bucket, &futex->chain); have_futex: lock(&futex->lock); unlock(&futex_hash_lock); return futex; } // must be called on the result of futex_get when you're done with it static void futex_put(struct futex *futex) { unlock(&futex->lock); if (--futex->refcount == 0) { lock(&futex_hash_lock); list_remove(&futex->chain); unlock(&futex_hash_lock); } } static int futex_load(struct futex *futex, dword_t *out) { dword_t *ptr = mem_ptr(futex->mem, futex->addr, MEM_READ); if (ptr == NULL) return 1; *out = *ptr; return 0; } int futex_wait(addr_t uaddr, dword_t val) { struct futex *futex = futex_get(uaddr); int err = 0; dword_t tmp; if (futex_load(futex, &tmp)) err = _EFAULT; else if (tmp != val) err = _EAGAIN; else pthread_cond_wait(&futex->cond, &futex->lock); futex_put(futex); return err; } int futex_wake(addr_t uaddr, dword_t val) { struct futex *futex = futex_get(uaddr); if (val == 1) pthread_cond_signal(&futex->cond); else if (val == 0x7fffffff) pthread_cond_broadcast(&futex->cond); else TODO("invalid number of futex wakes %d", val); futex_put(futex); return val; // FIXME wrong if val is INT_MAX } #define FUTEX_WAIT_ 0 #define FUTEX_WAKE_ 1 #define FUTEX_PRIVATE_FLAG_ 128 #define FUTEX_CMD_MASK_ ~(FUTEX_PRIVATE_FLAG_) dword_t sys_futex(addr_t uaddr, dword_t op, dword_t val) { if (!(op & FUTEX_PRIVATE_FLAG_)) { FIXME("no support for shared futexes"); } switch (op & FUTEX_CMD_MASK_) { case FUTEX_WAIT_: STRACE("futex_wait(0x%x, %d)", uaddr, val); return futex_wait(uaddr, val); case FUTEX_WAKE_: STRACE("futex_wake(0x%x, %d)", uaddr, val); return futex_wake(uaddr, val); } FIXME("unsupported futex operation %d", op); return _ENOSYS; }