#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/sysctl.h>
#include <sys/indefinite.h>
#include <sys/thread.h>
#include <machine/cpufunc.h>
#include <sys/thread2.h>
#include <sys/mutex2.h>
#include <sys/indefinite2.h>
static int mtx_chain_link_ex(mtx_t *mtx, u_int olock);
static int mtx_chain_link_sh(mtx_t *mtx, u_int olock);
static void mtx_delete_link(mtx_t *mtx, mtx_link_t *link);
static __inline int
__mtx_lock_ex(mtx_t *mtx, mtx_link_t *link, int flags, int to)
{
thread_t td;
u_int lock;
u_int nlock;
int error;
int isasync;
for (;;) {
lock = mtx->mtx_lock;
cpu_ccfence();
if (lock == 0) {
nlock = MTX_EXCLUSIVE | 1;
if (atomic_cmpset_int(&mtx->mtx_lock, 0, nlock)) {
mtx->mtx_owner = curthread;
cpu_sfence();
link->state = MTX_LINK_ACQUIRED;
error = 0;
break;
}
continue;
}
if ((lock & MTX_EXCLUSIVE) && mtx->mtx_owner == curthread) {
KKASSERT((lock & MTX_MASK) != MTX_MASK);
nlock = lock + 1;
if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock)) {
cpu_sfence();
link->state = MTX_LINK_ACQUIRED;
error = 0;
break;
}
continue;
}
if (lock & MTX_LINKSPIN) {
cpu_pause();
continue;
}
td = curthread;
nlock = lock | MTX_EXWANTED | MTX_LINKSPIN;
crit_enter_quick(td);
if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock) == 0) {
crit_exit_quick(td);
continue;
}
if (link->state == MTX_LINK_ABORTED) {
if (mtx->mtx_exlink == NULL) {
atomic_clear_int(&mtx->mtx_lock,
MTX_LINKSPIN |
MTX_EXWANTED);
} else {
atomic_clear_int(&mtx->mtx_lock,
MTX_LINKSPIN);
}
crit_exit_quick(td);
link->state = MTX_LINK_IDLE;
error = ENOLCK;
break;
}
link->owner = td;
link->state = MTX_LINK_LINKED_EX;
if (mtx->mtx_exlink) {
link->next = mtx->mtx_exlink;
link->prev = link->next->prev;
link->next->prev = link;
link->prev->next = link;
} else {
link->next = link;
link->prev = link;
mtx->mtx_exlink = link;
}
isasync = (link->callback != NULL);
atomic_clear_int(&mtx->mtx_lock, MTX_LINKSPIN);
crit_exit_quick(td);
if (isasync) {
error = EINPROGRESS;
break;
}
error = mtx_wait_link(mtx, link, flags, to);
break;
}
return (error);
}
int
_mtx_lock_ex_link(mtx_t *mtx, mtx_link_t *link, int flags, int to)
{
return(__mtx_lock_ex(mtx, link, flags, to));
}
int
_mtx_lock_ex(mtx_t *mtx, int flags, int to)
{
mtx_link_t link;
mtx_link_init(&link);
return(__mtx_lock_ex(mtx, &link, flags, to));
}
int
_mtx_lock_ex_quick(mtx_t *mtx)
{
mtx_link_t link;
mtx_link_init(&link);
return(__mtx_lock_ex(mtx, &link, 0, 0));
}
static __inline int
__mtx_lock_sh(mtx_t *mtx, mtx_link_t *link, int flags, int to)
{
thread_t td;
u_int lock;
u_int nlock;
int error;
int isasync;
for (;;) {
lock = mtx->mtx_lock;
cpu_ccfence();
if (lock == 0) {
nlock = 1;
if (atomic_cmpset_int(&mtx->mtx_lock, 0, nlock)) {
error = 0;
cpu_sfence();
link->state = MTX_LINK_ACQUIRED;
break;
}
continue;
}
if ((lock & (MTX_EXCLUSIVE | MTX_EXWANTED)) == 0) {
KKASSERT((lock & MTX_MASK) != MTX_MASK);
nlock = lock + 1;
if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock)) {
error = 0;
cpu_sfence();
link->state = MTX_LINK_ACQUIRED;
break;
}
continue;
}
if (lock & MTX_LINKSPIN) {
cpu_pause();
continue;
}
td = curthread;
nlock = lock | MTX_SHWANTED | MTX_LINKSPIN;
crit_enter_quick(td);
if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock) == 0) {
crit_exit_quick(td);
continue;
}
if (link->state == MTX_LINK_ABORTED) {
if (mtx->mtx_shlink) {
atomic_clear_int(&mtx->mtx_lock,
MTX_LINKSPIN);
} else {
atomic_clear_int(&mtx->mtx_lock,
MTX_LINKSPIN |
MTX_SHWANTED);
}
crit_exit_quick(td);
link->state = MTX_LINK_IDLE;
error = ENOLCK;
break;
}
link->owner = td;
link->state = MTX_LINK_LINKED_SH;
if (mtx->mtx_shlink) {
link->next = mtx->mtx_shlink;
link->prev = link->next->prev;
link->next->prev = link;
link->prev->next = link;
} else {
link->next = link;
link->prev = link;
mtx->mtx_shlink = link;
}
isasync = (link->callback != NULL);
atomic_clear_int(&mtx->mtx_lock, MTX_LINKSPIN);
crit_exit_quick(td);
if (isasync) {
error = EINPROGRESS;
break;
}
error = mtx_wait_link(mtx, link, flags, to);
break;
}
return (error);
}
int
_mtx_lock_sh_link(mtx_t *mtx, mtx_link_t *link, int flags, int to)
{
return(__mtx_lock_sh(mtx, link, flags, to));
}
int
_mtx_lock_sh(mtx_t *mtx, int flags, int to)
{
mtx_link_t link;
mtx_link_init(&link);
return(__mtx_lock_sh(mtx, &link, flags, to));
}
int
_mtx_lock_sh_quick(mtx_t *mtx)
{
mtx_link_t link;
mtx_link_init(&link);
return(__mtx_lock_sh(mtx, &link, 0, 0));
}
void
_mtx_spinlock(mtx_t *mtx)
{
u_int lock;
u_int nlock;
int bb = 1;
int bo;
for (;;) {
lock = mtx->mtx_lock;
if (lock == 0) {
nlock = MTX_EXCLUSIVE | 1;
if (atomic_cmpset_int(&mtx->mtx_lock, 0, nlock)) {
mtx->mtx_owner = curthread;
break;
}
} else if ((lock & MTX_EXCLUSIVE) &&
mtx->mtx_owner == curthread) {
KKASSERT((lock & MTX_MASK) != MTX_MASK);
nlock = lock + 1;
if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
break;
} else {
if (bb < 1000)
++bb;
cpu_pause();
for (bo = 0; bo < bb; ++bo)
;
}
cpu_pause();
}
}
int
_mtx_spinlock_try(mtx_t *mtx)
{
globaldata_t gd = mycpu;
u_int lock;
u_int nlock;
int res = 0;
for (;;) {
lock = mtx->mtx_lock;
if (lock == 0) {
nlock = MTX_EXCLUSIVE | 1;
if (atomic_cmpset_int(&mtx->mtx_lock, 0, nlock)) {
mtx->mtx_owner = gd->gd_curthread;
break;
}
} else if ((lock & MTX_EXCLUSIVE) &&
mtx->mtx_owner == gd->gd_curthread) {
KKASSERT((lock & MTX_MASK) != MTX_MASK);
nlock = lock + 1;
if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
break;
} else {
--gd->gd_spinlocks;
cpu_ccfence();
crit_exit_quick(gd->gd_curthread);
res = EAGAIN;
break;
}
cpu_pause();
}
return res;
}
#if 0
void
_mtx_spinlock_sh(mtx_t *mtx)
{
u_int lock;
u_int nlock;
int bb = 1;
int bo;
for (;;) {
lock = mtx->mtx_lock;
if ((lock & MTX_EXCLUSIVE) == 0) {
KKASSERT((lock & MTX_MASK) != MTX_MASK);
nlock = lock + 1;
if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
break;
} else {
if (bb < 1000)
++bb;
cpu_pause();
for (bo = 0; bo < bb; ++bo)
;
}
cpu_pause();
}
}
#endif
int
_mtx_lock_ex_try(mtx_t *mtx)
{
u_int lock;
u_int nlock;
int error;
for (;;) {
lock = mtx->mtx_lock;
if (lock == 0) {
nlock = MTX_EXCLUSIVE | 1;
if (atomic_cmpset_int(&mtx->mtx_lock, 0, nlock)) {
mtx->mtx_owner = curthread;
error = 0;
break;
}
} else if ((lock & MTX_EXCLUSIVE) &&
mtx->mtx_owner == curthread) {
KKASSERT((lock & MTX_MASK) != MTX_MASK);
nlock = lock + 1;
if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock)) {
error = 0;
break;
}
} else {
error = EAGAIN;
break;
}
cpu_pause();
}
return (error);
}
int
_mtx_lock_sh_try(mtx_t *mtx)
{
u_int lock;
u_int nlock;
int error = 0;
for (;;) {
lock = mtx->mtx_lock;
if ((lock & MTX_EXCLUSIVE) == 0) {
KKASSERT((lock & MTX_MASK) != MTX_MASK);
nlock = lock + 1;
if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
break;
} else {
error = EAGAIN;
break;
}
cpu_pause();
}
return (error);
}
void
_mtx_downgrade(mtx_t *mtx)
{
u_int lock;
u_int nlock;
for (;;) {
lock = mtx->mtx_lock;
cpu_ccfence();
if ((lock & MTX_EXCLUSIVE) == 0) {
KKASSERT((lock & MTX_MASK) > 0);
break;
}
if (lock & MTX_SHWANTED) {
if (mtx_chain_link_sh(mtx, lock))
break;
} else {
nlock = lock & ~MTX_EXCLUSIVE;
if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
break;
}
cpu_pause();
}
}
int
_mtx_upgrade_try(mtx_t *mtx)
{
u_int lock;
u_int nlock;
int error = 0;
for (;;) {
lock = mtx->mtx_lock;
cpu_ccfence();
if ((lock & ~MTX_EXWANTED) == 1) {
nlock = lock | MTX_EXCLUSIVE;
if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock)) {
mtx->mtx_owner = curthread;
break;
}
} else if (lock & MTX_EXCLUSIVE) {
KKASSERT(mtx->mtx_owner == curthread);
break;
} else {
error = EDEADLK;
break;
}
cpu_pause();
}
return (error);
}
void
_mtx_unlock(mtx_t *mtx)
{
thread_t td __debugvar = curthread;
u_int lock;
u_int nlock;
for (;;) {
lock = mtx->mtx_lock;
cpu_ccfence();
switch(lock) {
case MTX_EXCLUSIVE | 1:
KKASSERT(mtx->mtx_owner == td ||
mtx->mtx_owner == NULL);
mtx->mtx_owner = NULL;
nlock = 0;
if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
goto done;
break;
case MTX_EXCLUSIVE | MTX_EXWANTED | 1:
case MTX_EXCLUSIVE | MTX_EXWANTED | MTX_SHWANTED | 1:
KKASSERT(mtx->mtx_owner == td ||
mtx->mtx_owner == NULL);
mtx->mtx_owner = NULL;
if (mtx_chain_link_ex(mtx, lock))
goto done;
break;
case MTX_EXCLUSIVE | MTX_SHWANTED | 1:
KKASSERT(mtx->mtx_owner == td ||
mtx->mtx_owner == NULL);
mtx->mtx_owner = NULL;
if (mtx_chain_link_sh(mtx, lock))
goto done;
break;
case 1:
nlock = 0;
if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
goto done;
break;
case MTX_EXWANTED | 1:
case MTX_EXWANTED | MTX_SHWANTED | 1:
if (mtx_chain_link_ex(mtx, lock))
goto done;
break;
case MTX_SHWANTED | 1:
if (mtx_chain_link_sh(mtx, lock))
goto done;
break;
default:
if ((lock & MTX_MASK) == 1) {
KKASSERT(lock & MTX_LINKSPIN);
break;
}
nlock = lock - 1;
KKASSERT((nlock & MTX_MASK) != MTX_MASK);
if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
goto done;
break;
}
cpu_pause();
}
done:
;
}
static int
mtx_chain_link_ex(mtx_t *mtx, u_int olock)
{
thread_t td = curthread;
mtx_link_t *link;
u_int nlock;
olock &= ~MTX_LINKSPIN;
nlock = olock | MTX_LINKSPIN | MTX_EXCLUSIVE;
crit_enter_quick(td);
if (atomic_cmpset_int(&mtx->mtx_lock, olock, nlock)) {
link = mtx->mtx_exlink;
KKASSERT(link != NULL);
if (link->next == link) {
mtx->mtx_exlink = NULL;
nlock = MTX_LINKSPIN | MTX_EXWANTED;
} else {
mtx->mtx_exlink = link->next;
link->next->prev = link->prev;
link->prev->next = link->next;
nlock = MTX_LINKSPIN;
}
KKASSERT(link->state == MTX_LINK_LINKED_EX);
mtx->mtx_owner = link->owner;
cpu_sfence();
if (link->callback) {
link->state = MTX_LINK_CALLEDBACK;
link->callback(link, link->arg, 0);
} else {
link->state = MTX_LINK_ACQUIRED;
wakeup(link);
}
atomic_clear_int(&mtx->mtx_lock, nlock);
crit_exit_quick(td);
return 1;
}
crit_exit_quick(td);
return 0;
}
static int
mtx_chain_link_sh(mtx_t *mtx, u_int olock)
{
thread_t td = curthread;
mtx_link_t *link;
u_int addcount;
u_int nlock;
olock &= ~MTX_LINKSPIN;
nlock = olock | MTX_LINKSPIN;
nlock &= ~MTX_EXCLUSIVE;
crit_enter_quick(td);
if (atomic_cmpset_int(&mtx->mtx_lock, olock, nlock)) {
KKASSERT(mtx->mtx_shlink != NULL);
addcount = 0;
for (link = mtx->mtx_shlink->next; link != mtx->mtx_shlink;
link = link->next) {
++addcount;
}
if (addcount > 0)
atomic_add_int(&mtx->mtx_lock, addcount);
while ((link = mtx->mtx_shlink) != NULL) {
KKASSERT(link->state == MTX_LINK_LINKED_SH);
if (link->next == link) {
mtx->mtx_shlink = NULL;
} else {
mtx->mtx_shlink = link->next;
link->next->prev = link->prev;
link->prev->next = link->next;
}
link->next = NULL;
link->prev = NULL;
cpu_sfence();
if (link->callback) {
link->state = MTX_LINK_CALLEDBACK;
link->callback(link, link->arg, 0);
} else {
cpu_sfence();
link->state = MTX_LINK_ACQUIRED;
wakeup(link);
}
}
atomic_clear_int(&mtx->mtx_lock, MTX_LINKSPIN |
MTX_SHWANTED);
crit_exit_quick(td);
return 1;
}
crit_exit_quick(td);
return 0;
}
static
void
mtx_delete_link(mtx_t *mtx, mtx_link_t *link)
{
thread_t td = curthread;
u_int lock;
u_int nlock;
crit_enter_quick(td);
for (;;) {
lock = mtx->mtx_lock;
if (lock & MTX_LINKSPIN) {
cpu_pause();
continue;
}
nlock = lock | MTX_LINKSPIN;
if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
break;
cpu_pause();
}
nlock = MTX_LINKSPIN;
switch(link->state) {
case MTX_LINK_LINKED_EX:
if (link->next == link) {
mtx->mtx_exlink = NULL;
nlock |= MTX_EXWANTED;
} else {
mtx->mtx_exlink = link->next;
link->next->prev = link->prev;
link->prev->next = link->next;
}
break;
case MTX_LINK_LINKED_SH:
if (link->next == link) {
mtx->mtx_shlink = NULL;
nlock |= MTX_SHWANTED;
} else {
mtx->mtx_shlink = link->next;
link->next->prev = link->prev;
link->prev->next = link->next;
}
break;
default:
break;
}
atomic_clear_int(&mtx->mtx_lock, nlock);
crit_exit_quick(td);
}
int
mtx_wait_link(mtx_t *mtx, mtx_link_t *link, int flags, int to)
{
indefinite_info_t info;
int error;
indefinite_init(&info, mtx, mtx->mtx_ident, 1,
((link->state & MTX_LINK_LINKED_SH) ? 'm' : 'M'));
error = 0;
while (link->state & MTX_LINK_LINKED) {
tsleep_interlock(link, 0);
cpu_lfence();
if (link->state & MTX_LINK_LINKED) {
error = tsleep(link, flags | PINTERLOCKED,
mtx->mtx_ident, to);
if (error)
break;
}
if ((mtx->mtx_flags & MTXF_NOCOLLSTATS) == 0)
indefinite_check(&info);
}
cpu_mfence();
switch(link->state) {
case MTX_LINK_ACQUIRED:
case MTX_LINK_CALLEDBACK:
error = 0;
break;
case MTX_LINK_ABORTED:
error = ENOLCK;
break;
case MTX_LINK_LINKED_EX:
case MTX_LINK_LINKED_SH:
mtx_delete_link(mtx, link);
default:
if (error == 0)
error = EWOULDBLOCK;
break;
}
link->state = MTX_LINK_IDLE;
if ((mtx->mtx_flags & MTXF_NOCOLLSTATS) == 0)
indefinite_done(&info);
return error;
}
void
mtx_abort_link(mtx_t *mtx, mtx_link_t *link)
{
thread_t td = curthread;
u_int lock;
u_int nlock;
crit_enter_quick(td);
for (;;) {
lock = mtx->mtx_lock;
if (lock & MTX_LINKSPIN) {
cpu_pause();
continue;
}
nlock = lock | MTX_LINKSPIN;
if (atomic_cmpset_int(&mtx->mtx_lock, lock, nlock))
break;
cpu_pause();
}
nlock = MTX_LINKSPIN;
switch(link->state) {
case MTX_LINK_IDLE:
link->state = MTX_LINK_ABORTED;
break;
case MTX_LINK_LINKED_EX:
if (link->next == link) {
if (mtx->mtx_exlink == link) {
mtx->mtx_exlink = NULL;
nlock |= MTX_EXWANTED;
}
} else {
if (mtx->mtx_exlink == link)
mtx->mtx_exlink = link->next;
link->next->prev = link->prev;
link->prev->next = link->next;
}
if (link->callback) {
link->state = MTX_LINK_CALLEDBACK;
link->callback(link, link->arg, ENOLCK);
} else {
link->state = MTX_LINK_ABORTED;
wakeup(link);
}
break;
case MTX_LINK_LINKED_SH:
if (link->next == link) {
if (mtx->mtx_shlink == link) {
mtx->mtx_shlink = NULL;
nlock |= MTX_SHWANTED;
}
} else {
if (mtx->mtx_shlink == link)
mtx->mtx_shlink = link->next;
link->next->prev = link->prev;
link->prev->next = link->next;
}
if (link->callback) {
link->state = MTX_LINK_CALLEDBACK;
link->callback(link, link->arg, ENOLCK);
} else {
link->state = MTX_LINK_ABORTED;
wakeup(link);
}
break;
case MTX_LINK_ACQUIRED:
case MTX_LINK_CALLEDBACK:
break;
default:
break;
}
atomic_clear_int(&mtx->mtx_lock, nlock);
crit_exit_quick(td);
}