#include "internal.h"
#include <sys/time.h>
#include <stdio.h>
#ifdef PLOCKSTAT
#include "plockstat.h"
#else
#define PLOCKSTAT_MUTEX_RELEASE(x, y)
#endif
__private_extern__ int _pthread_cond_init(_pthread_cond *, const pthread_condattr_t *, int);
__private_extern__ int _pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime, int isRelative, int isconforming);
extern int __gettimeofday(struct timeval *, struct timezone *);
#ifndef BUILDING_VARIANT
static void _pthread_cond_cleanup(void *arg);
static void _pthread_cond_updateval(_pthread_cond * cond, int error, uint32_t updateval);
#endif
static void
COND_GETSEQ_ADDR(_pthread_cond *cond,
volatile uint32_t **c_lseqcnt,
volatile uint32_t **c_useqcnt,
volatile uint32_t **c_sseqcnt)
{
if (cond->misalign) {
*c_lseqcnt = &cond->c_seq[1];
*c_sseqcnt = &cond->c_seq[2];
*c_useqcnt = &cond->c_seq[0];
} else {
*c_lseqcnt = &cond->c_seq[0];
*c_sseqcnt = &cond->c_seq[1];
*c_useqcnt = &cond->c_seq[2];
}
}
#ifndef BUILDING_VARIANT
int
pthread_condattr_init(pthread_condattr_t *attr)
{
attr->sig = _PTHREAD_COND_ATTR_SIG;
attr->pshared = _PTHREAD_DEFAULT_PSHARED;
return 0;
}
int
pthread_condattr_destroy(pthread_condattr_t *attr)
{
attr->sig = _PTHREAD_NO_SIG;
return 0;
}
int
pthread_condattr_getpshared(const pthread_condattr_t *attr, int *pshared)
{
int res = EINVAL;
if (attr->sig == _PTHREAD_COND_ATTR_SIG) {
*pshared = (int)attr->pshared;
res = 0;
}
return res;
}
int
pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared)
{
int res = EINVAL;
if (attr->sig == _PTHREAD_COND_ATTR_SIG) {
#if __DARWIN_UNIX03
if (pshared == PTHREAD_PROCESS_PRIVATE || pshared == PTHREAD_PROCESS_SHARED)
#else
if (pshared == PTHREAD_PROCESS_PRIVATE)
#endif
{
attr->pshared = pshared;
res = 0;
}
}
return res;
}
__private_extern__ int
_pthread_cond_init(_pthread_cond *cond, const pthread_condattr_t *attr, int conforming)
{
volatile uint32_t *c_lseqcnt, *c_useqcnt, *c_sseqcnt;
cond->busy = NULL;
cond->c_seq[0] = 0;
cond->c_seq[1] = 0;
cond->c_seq[2] = 0;
cond->unused = 0;
cond->misalign = (((uintptr_t)&cond->c_seq[0]) & 0x7) != 0;
COND_GETSEQ_ADDR(cond, &c_lseqcnt, &c_useqcnt, &c_sseqcnt);
*c_sseqcnt = PTH_RWS_CV_CBIT;
if (conforming) {
if (attr) {
cond->pshared = attr->pshared;
} else {
cond->pshared = _PTHREAD_DEFAULT_PSHARED;
}
} else {
cond->pshared = _PTHREAD_DEFAULT_PSHARED;
}
OSMemoryBarrier();
cond->sig = _PTHREAD_COND_SIG;
return 0;
}
static int
_pthread_cond_check_init(_pthread_cond *cond, bool *inited)
{
int res = 0;
if (cond->sig != _PTHREAD_COND_SIG) {
res = EINVAL;
if (cond->sig == _PTHREAD_COND_SIG_init) {
LOCK(cond->lock);
if (cond->sig == _PTHREAD_COND_SIG_init) {
res = _pthread_cond_init(cond, NULL, 0);
if (inited) {
*inited = true;
}
} else if (cond->sig == _PTHREAD_COND_SIG) {
res = 0;
}
UNLOCK(cond->lock);
}
}
return res;
}
int
pthread_cond_destroy(pthread_cond_t *ocond)
{
_pthread_cond *cond = (_pthread_cond *)ocond;
int res = EINVAL;
if (cond->sig == _PTHREAD_COND_SIG) {
LOCK(cond->lock);
uint64_t oldval64, newval64;
uint32_t lcntval, ucntval, scntval;
volatile uint32_t *c_lseqcnt, *c_useqcnt, *c_sseqcnt;
COND_GETSEQ_ADDR(cond, &c_lseqcnt, &c_useqcnt, &c_sseqcnt);
do {
lcntval = *c_lseqcnt;
ucntval = *c_useqcnt;
scntval = *c_sseqcnt;
if ((lcntval & PTHRW_COUNT_MASK) != (scntval & PTHRW_COUNT_MASK)) {
break;
}
oldval64 = (((uint64_t)scntval) << 32);
oldval64 |= lcntval;
newval64 = oldval64;
} while (OSAtomicCompareAndSwap64Barrier(oldval64, newval64, (volatile int64_t *)c_lseqcnt) != TRUE);
uint32_t flags = 0;
bool needclearpre = ((scntval & PTH_RWS_CV_PBIT) != 0);
if (needclearpre && cond->pshared == PTHREAD_PROCESS_SHARED) {
flags |= _PTHREAD_MTX_OPT_PSHARED;
}
cond->sig = _PTHREAD_NO_SIG;
res = 0;
UNLOCK(cond->lock);
if (needclearpre) {
(void)__psynch_cvclrprepost(cond, lcntval, ucntval, scntval, 0, lcntval, flags);
}
} else if (cond->sig == _PTHREAD_COND_SIG_init) {
cond->sig = _PTHREAD_NO_SIG;
res = 0;
}
return res;
}
static int
_pthread_cond_signal(pthread_cond_t *ocond, bool broadcast, mach_port_t thread)
{
int res;
_pthread_cond *cond = (_pthread_cond *)ocond;
uint32_t updateval;
uint32_t diffgen;
uint32_t ulval;
uint64_t oldval64, newval64;
uint32_t lcntval, ucntval, scntval;
volatile uint32_t *c_lseqcnt, *c_useqcnt, *c_sseqcnt;
int retry_count = 0, uretry_count = 0;
int ucountreset = 0;
bool inited = false;
res = _pthread_cond_check_init(cond, &inited);
if (res != 0 || inited == true) {
return res;
}
COND_GETSEQ_ADDR(cond, &c_lseqcnt, &c_useqcnt, &c_sseqcnt);
bool retry;
do {
retry = false;
lcntval = *c_lseqcnt;
ucntval = *c_useqcnt;
scntval = *c_sseqcnt;
if (((lcntval & PTHRW_COUNT_MASK) == (scntval & PTHRW_COUNT_MASK)) ||
(thread == MACH_PORT_NULL && ((lcntval & PTHRW_COUNT_MASK) == (ucntval & PTHRW_COUNT_MASK)))) {
oldval64 = (((uint64_t)scntval) << 32);
oldval64 |= lcntval;
newval64 = oldval64;
if (OSAtomicCompareAndSwap64Barrier(oldval64, newval64, (volatile int64_t *)c_lseqcnt) != TRUE) {
retry = true;
continue;
} else {
return 0;
}
}
if (thread) {
break;
}
if (is_seqhigher((scntval & PTHRW_COUNT_MASK), (lcntval & PTHRW_COUNT_MASK))) {
retry_count++;
if (retry_count > 8192) {
return EAGAIN;
} else {
sched_yield();
retry = true;
continue;
}
} else if (is_seqhigher((ucntval & PTHRW_COUNT_MASK), (lcntval & PTHRW_COUNT_MASK))) {
uretry_count++;
if (uretry_count > 8192) {
if (ucountreset != 0) {
return EAGAIN;
} else if (OSAtomicCompareAndSwap32Barrier(ucntval, (scntval & PTHRW_COUNT_MASK), (volatile int32_t *)c_useqcnt) == TRUE) {
ucountreset = 1;
uretry_count = 0;
}
}
sched_yield();
retry = true;
continue;
}
if (is_seqlower(ucntval & PTHRW_COUNT_MASK, scntval & PTHRW_COUNT_MASK) != 0) {
ulval = (scntval & PTHRW_COUNT_MASK);
} else {
ulval = (ucntval & PTHRW_COUNT_MASK);
}
if (broadcast) {
diffgen = diff_genseq(lcntval, ulval);
ulval = (lcntval & PTHRW_COUNT_MASK);
} else {
ulval += PTHRW_INC;
}
} while (retry || OSAtomicCompareAndSwap32Barrier(ucntval, ulval, (volatile int32_t *)c_useqcnt) != TRUE);
uint32_t flags = 0;
if (cond->pshared == PTHREAD_PROCESS_SHARED) {
flags |= _PTHREAD_MTX_OPT_PSHARED;
}
uint64_t cvlsgen = ((uint64_t)scntval << 32) | lcntval;
if (broadcast) {
uint64_t cvudgen = ((uint64_t)ucntval << 32) | diffgen;
updateval = __psynch_cvbroad(ocond, cvlsgen, cvudgen, flags, NULL, 0, 0);
} else {
updateval = __psynch_cvsignal(ocond, cvlsgen, ucntval, thread, NULL, 0, 0, flags);
}
if (updateval != (uint32_t)-1 && updateval != 0) {
_pthread_cond_updateval(cond, 0, updateval);
}
return 0;
}
int
pthread_cond_broadcast(pthread_cond_t *ocond)
{
return _pthread_cond_signal(ocond, true, MACH_PORT_NULL);
}
int
pthread_cond_signal_thread_np(pthread_cond_t *ocond, pthread_t thread)
{
mach_port_t mp = MACH_PORT_NULL;
if (thread) {
mp = pthread_mach_thread_np(thread);
}
return _pthread_cond_signal(ocond, false, mp);
}
int
pthread_cond_signal(pthread_cond_t *cond)
{
return pthread_cond_signal_thread_np(cond, NULL);
}
__private_extern__ int
_pthread_cond_wait(pthread_cond_t *ocond,
pthread_mutex_t *omutex,
const struct timespec *abstime,
int isRelative,
int isconforming)
{
int res;
_pthread_cond *cond = (_pthread_cond *)ocond;
_pthread_mutex *mutex = (_pthread_mutex *)omutex;
struct timespec then = { 0, 0 };
uint32_t mtxgen, mtxugen, flags=0, updateval;
uint32_t lcntval, ucntval, scntval;
uint32_t nlval, ulval, savebits;
volatile uint32_t *c_lseqcnt, *c_useqcnt, *c_sseqcnt;
uint64_t oldval64, newval64, mugen, cvlsgen;
uint32_t *npmtx = NULL;
extern void _pthread_testcancel(pthread_t thread, int isconforming);
res = _pthread_cond_check_init(cond, NULL);
if (res != 0) {
return res;
}
if (isconforming) {
if (mutex->sig != _PTHREAD_MUTEX_SIG && (mutex->sig & _PTHREAD_MUTEX_SIG_init_MASK) != _PTHREAD_MUTEX_SIG_CMP) {
return EINVAL;
}
if (isconforming > 0) {
_pthread_testcancel(pthread_self(), 1);
}
}
if (abstime) {
if (isRelative == 0) {
struct timespec now;
struct timeval tv;
__gettimeofday(&tv, NULL);
TIMEVAL_TO_TIMESPEC(&tv, &now);
then.tv_nsec = abstime->tv_nsec - now.tv_nsec;
then.tv_sec = abstime->tv_sec - now.tv_sec;
if (then.tv_nsec < 0) {
then.tv_nsec += NSEC_PER_SEC;
then.tv_sec--;
}
if (then.tv_sec < 0 || (then.tv_sec == 0 && then.tv_nsec == 0)) {
return ETIMEDOUT;
}
if (isconforming &&
(abstime->tv_sec < 0 ||
abstime->tv_nsec < 0 ||
abstime->tv_nsec >= NSEC_PER_SEC)) {
return EINVAL;
}
} else {
then.tv_sec = abstime->tv_sec;
then.tv_nsec = abstime->tv_nsec;
if ((then.tv_sec == 0) && (then.tv_nsec == 0)) {
return ETIMEDOUT;
}
}
if (isconforming && (then.tv_sec < 0 || then.tv_nsec < 0)) {
return EINVAL;
}
if (then.tv_nsec >= NSEC_PER_SEC) {
return EINVAL;
}
}
if (cond->busy != NULL && cond->busy != mutex) {
return EINVAL;
}
COND_GETSEQ_ADDR(cond, &c_lseqcnt, &c_useqcnt, &c_sseqcnt);
do {
lcntval = *c_lseqcnt;
ucntval = *c_useqcnt;
scntval = *c_sseqcnt;
oldval64 = (((uint64_t)scntval) << 32);
oldval64 |= lcntval;
savebits = scntval & PTH_RWS_CV_BITSALL;
ulval = (scntval & PTHRW_COUNT_MASK);
nlval = lcntval + PTHRW_INC;
newval64 = (((uint64_t)ulval) << 32);
newval64 |= nlval;
} while (OSAtomicCompareAndSwap64Barrier(oldval64, newval64, (volatile int64_t *)c_lseqcnt) != TRUE);
cond->busy = mutex;
res = __mtx_droplock(mutex, &flags, &npmtx, &mtxgen, &mtxugen);
if (res != 0) {
return EINVAL;
}
if ((flags & _PTHREAD_MTX_OPT_NOTIFY) == 0) {
npmtx = NULL;
mugen = 0;
} else {
mugen = ((uint64_t)mtxugen << 32) | mtxgen;
}
flags &= ~_PTHREAD_MTX_OPT_MUTEX;
cvlsgen = ((uint64_t)(ulval | savebits)<< 32) | nlval;
if (isconforming) {
pthread_cleanup_push(_pthread_cond_cleanup, (void *)cond);
updateval = __psynch_cvwait(ocond, cvlsgen, ucntval, (pthread_mutex_t *)npmtx, mugen, flags, (int64_t)then.tv_sec, (int32_t)then.tv_nsec);
_pthread_testcancel(pthread_self(), isconforming);
pthread_cleanup_pop(0);
} else {
updateval = __psynch_cvwait(ocond, cvlsgen, ucntval, (pthread_mutex_t *)npmtx, mugen, flags, (int64_t)then.tv_sec, (int32_t)then.tv_nsec);
}
if (updateval == (uint32_t)-1) {
int err = errno;
switch (err & 0xff) {
case ETIMEDOUT:
res = ETIMEDOUT;
break;
case EINTR:
res = 0;
break;
default:
res = EINVAL;
break;
}
_pthread_cond_updateval(cond, err, 0);
} else if (updateval != 0) {
_pthread_cond_updateval(cond, 0, updateval);
}
pthread_mutex_lock(omutex);
return res;
}
static void
_pthread_cond_cleanup(void *arg)
{
_pthread_cond *cond = (_pthread_cond *)arg;
pthread_mutex_t *mutex;
pthread_t thread = pthread_self();
int thcanceled = 0;
LOCK(thread->lock);
thcanceled = (thread->detached & _PTHREAD_WASCANCEL);
UNLOCK(thread->lock);
if (thcanceled == 0) {
return;
}
mutex = (pthread_mutex_t *)cond->busy;
_pthread_cond_updateval(cond, thread->cancel_error, 0);
if (mutex != NULL) {
(void)pthread_mutex_lock(mutex);
}
}
#define ECVCERORR 256
#define ECVPERORR 512
static void
_pthread_cond_updateval(_pthread_cond *cond, int error, uint32_t updateval)
{
int needclearpre;
uint32_t diffgen, nsval;
uint64_t oldval64, newval64;
uint32_t lcntval, ucntval, scntval;
volatile uint32_t *c_lseqcnt, *c_useqcnt, *c_sseqcnt;
if (error != 0) {
updateval = PTHRW_INC;
if ((error & ECVCERORR) != 0) {
updateval |= PTH_RWS_CV_CBIT;
}
if ((error & ECVPERORR) != 0) {
updateval |= PTH_RWS_CV_PBIT;
}
}
COND_GETSEQ_ADDR(cond, &c_lseqcnt, &c_useqcnt, &c_sseqcnt);
do {
lcntval = *c_lseqcnt;
ucntval = *c_useqcnt;
scntval = *c_sseqcnt;
diffgen = diff_genseq(lcntval, scntval);
oldval64 = (((uint64_t)scntval) << 32);
oldval64 |= lcntval;
if (diffgen <= 0) {
newval64 = oldval64;
} else {
nsval = (scntval & PTHRW_COUNT_MASK) + (updateval & PTHRW_COUNT_MASK);
nsval |= ((scntval & PTH_RWS_CV_BITSALL) | (updateval & PTH_RWS_CV_BITSALL));
if (((nsval & PTHRW_COUNT_MASK) == (lcntval & PTHRW_COUNT_MASK)) &&
((nsval & PTH_RWS_CV_BITSALL) == PTH_RWS_CV_BITSALL)) {
nsval &= PTH_RWS_CV_RESET_PBIT;
needclearpre = 1;
} else {
needclearpre = 0;
}
newval64 = (((uint64_t)nsval) << 32);
newval64 |= lcntval;
}
} while (OSAtomicCompareAndSwap64Barrier(oldval64, newval64, (volatile int64_t *)c_lseqcnt) != TRUE);
if (diffgen > 0) {
if ((nsval & PTHRW_COUNT_MASK) == (lcntval & PTHRW_COUNT_MASK)) {
cond->busy = NULL;
}
if (needclearpre != 0) {
uint32_t flags = 0;
if (cond->pshared == PTHREAD_PROCESS_SHARED) {
flags |= _PTHREAD_MTX_OPT_PSHARED;
}
(void)__psynch_cvclrprepost(cond, lcntval, ucntval, nsval, 0, lcntval, flags);
}
}
}
int
pthread_cond_timedwait_relative_np(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
{
return _pthread_cond_wait(cond, mutex, abstime, 1, 0);
}
#endif
int
pthread_cond_init(pthread_cond_t *ocond, const pthread_condattr_t *attr)
{
int conforming;
#if __DARWIN_UNIX03
conforming = 1;
#else
conforming = 0;
#endif
_pthread_cond *cond = (_pthread_cond *)ocond;
LOCK_INIT(cond->lock);
return _pthread_cond_init(cond, attr, conforming);
}