blob: 5d4d4bbfc759d5e3cc6f6b64bb633f6d9a14ee62 [file] [log] [blame]
#include <uthread.h>
#include <semaphore.h>
#include <mcs.h>
#include <stdio.h>
#include <alarm.h>
#include <errno.h>
struct sem_queue_element {
TAILQ_ENTRY(sem_queue_element) next;
struct sem *sem;
pthread_t pthread;
uint64_t us_timeout;
struct alarm_waiter awaiter;
bool timedout;
};
int sem_init (sem_t *__sem, int __pshared, unsigned int __value)
{
if(__pshared == TRUE) {
printf("__pshared functionality of sem_init is not yet implemented!");
return -1;
}
__sem->count = __value;
TAILQ_INIT(&__sem->queue);
spin_pdr_init(&__sem->lock);
return 0;
}
int sem_destroy (sem_t *__sem)
{
return 0;
}
sem_t *sem_open (__const char *__name, int __oflag, ...)
{
printf("sem_open is not yet implemented!");
return NULL;
}
int sem_close (sem_t *__sem)
{
printf("sem_close is not yet implemented!");
return -1;
}
int sem_unlink (__const char *__name)
{
printf("sem_unlink is not yet implemented!");
return -1;
}
static void __sem_timeout(struct alarm_waiter *awaiter)
{
struct sem_queue_element *e = awaiter->data;
struct sem_queue_element *__e = NULL;
/* Try and yank out the thread. */
spin_pdr_lock(&e->sem->lock);
TAILQ_FOREACH(__e, &e->sem->queue, next)
if (__e == e) break;
if (__e) {
TAILQ_REMOVE(&e->sem->queue, e, next);
e->timedout = true;
}
spin_pdr_unlock(&e->sem->lock);
/* If we were able to yank it out, wake it up. */
if (__e)
uthread_runnable((struct uthread*)e->pthread);
/* Set this as the very last thing we do whether we
* successfully woke the thread blocked on the futex or not.
* Either we set this or post() sets this, not both. Spin on
* this in the bottom-half of the wait() code to ensure there
* are no more references to awaiter before freeing the
* memory for it. */
e->awaiter.data = NULL;
}
static void __sem_block(struct uthread *uthread, void *arg)
{
struct sem_queue_element *e = (struct sem_queue_element *)arg;
pthread_t pthread = (pthread_t)uthread;
__pthread_generic_yield(pthread);
pthread->state = PTH_BLK_MUTEX;
TAILQ_INSERT_TAIL(&e->sem->queue, e, next);
spin_pdr_unlock(&e->sem->lock);
}
static void __sem_timedblock(struct uthread *uthread, void *arg)
{
struct sem_queue_element *e = (struct sem_queue_element *)arg;
e->awaiter.data = e;
init_awaiter(&e->awaiter, __sem_timeout);
set_awaiter_abs(&e->awaiter, e->us_timeout);
set_alarm(&e->awaiter);
__sem_block(uthread, e->sem);
}
int sem_wait (sem_t *__sem)
{
pthread_t pthread = (pthread_t)current_uthread;
struct sem_queue_element e = {{0}, __sem, pthread, -1, {0}, false};
spin_pdr_lock(&__sem->lock);
if(__sem->count > 0) {
__sem->count--;
spin_pdr_unlock(&__sem->lock);
}
else {
/* We unlock in the body of __sem_block */
uthread_yield(TRUE, __sem_block, __sem);
}
return 0;
}
int sem_timedwait(sem_t *__sem, const struct timespec *abs_timeout)
{
int ret = 0;
uint64_t us = abs_timeout->tv_nsec/1000 + (abs_timeout->tv_sec)*1000000L;
pthread_t pthread = (pthread_t)current_uthread;
struct sem_queue_element e = {{0}, __sem, pthread, us, {0}, false};
spin_pdr_lock(&__sem->lock);
if(__sem->count > 0) {
__sem->count--;
spin_pdr_unlock(&__sem->lock);
}
else {
/* We unlock in the body of __sem_block */
uthread_yield(TRUE, __sem_timedblock, &e);
/* Spin briefly to make sure that all references to e are
* gone between the post() and the timeout() code. We use
* e.awaiter.data to do this. */
while (e.awaiter.data != NULL)
cpu_relax();
if (e.timedout) {
errno = ETIMEDOUT;
ret = -1;
}
}
return ret;
}
int sem_trywait (sem_t *__sem)
{
int ret = -1;
spin_pdr_lock(&__sem->lock);
if(__sem->count > 0) {
__sem->count--;
ret = 0;
}
spin_pdr_unlock(&__sem->lock);
return ret;
}
int sem_post (sem_t *__sem)
{
spin_pdr_lock(&__sem->lock);
struct sem_queue_element *e = TAILQ_FIRST(&__sem->queue);
if (e)
TAILQ_REMOVE(&__sem->queue, e, next);
else
__sem->count++;
spin_pdr_unlock(&__sem->lock);
if (e) {
if(e->us_timeout != (uint64_t)-1) {
/* Try and unset the alarm. If this fails, then we
* have already started running the alarm callback. If
* it succeeds, then we can set awaiter->data to NULL
* so that the bottom half of wake can proceed. Either
* we set awaiter->data to NULL or __sem_timeout
* does. The fact that we made it here though, means
* that WE are the one who removed e from the queue, so
* we are basically just deciding who should set
* awaiter->data to NULL to indicate that there are no
* more references to it. */
if(unset_alarm(&e->awaiter))
e->awaiter.data = NULL;
}
uthread_runnable((struct uthread*)e->pthread);
}
return 0;
}
int sem_getvalue (sem_t *__restrict __sem, int *__restrict __sval)
{
spin_pdr_lock(&__sem->lock);
*__sval = __sem->count;
spin_pdr_unlock(&__sem->lock);
return 0;
}