#include "opt_ddb.h"
#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/lock.h>
#include <sys/caps.h>
#include <sys/malloc.h>
#include <sys/queue.h>
#include <sys/sleepqueue.h>
#include <sys/objcache.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <machine/atomic.h>
#ifdef DDB
#include <ddb/ddb.h>
#endif
#include <sys/signal2.h>
#include <sys/thread2.h>
#include <sys/spinlock2.h>
#include <sys/mutex2.h>
#include <vm/vm_extern.h>
#define SLEEPQ_HSIZE 1024
#define SLEEPQ_HMASK (SLEEPQ_HSIZE - 1)
#define SLEEPQ_HASH(wchan) ((((uintptr_t)(wchan) >> 10) ^ \
((uintptr_t)(wchan) & SLEEPQ_HMASK)))
#define SLEEPQ_LOOKUP(wchan) &sleepq_chains[SLEEPQ_HASH(wchan)]
#define SLEEPQ_NRQUEUES 2
#define SLEEPQ_FREEPERSLOT 4
struct sleepqueue_wchan;
struct sleepqueue_chain {
struct spinlock sc_spin;
TAILQ_HEAD(, sleepqueue_wchan) sc_wchead;
u_int sc_free_count;
};
struct sleepqueue_wchan {
TAILQ_ENTRY(sleepqueue_wchan) wc_entry;
const void *wc_wchan;
struct sleepqueue_chain *wc_sc;
u_int wc_refs;
int wc_type;
u_int wc_blocked[SLEEPQ_NRQUEUES];
};
static struct sleepqueue_chain sleepq_chains[SLEEPQ_HSIZE];
static MALLOC_DEFINE(M_SLEEPQ, "sleepq", "fbsd sleepq api");
static struct objcache *sleepq_wc_cache;
static __inline
struct sleepqueue_wchan *
sleepq_wclookup(const void *wchan)
{
struct sleepqueue_chain *sc;
struct sleepqueue_wchan *wc;
sc = SLEEPQ_LOOKUP(wchan);
KKASSERT(spin_held(&sc->sc_spin));
TAILQ_FOREACH(wc, &sc->sc_wchead, wc_entry) {
if (wc->wc_wchan == wchan)
return(wc);
}
panic("sleepq_wclookup: wchan %p not found\n", wc);
return NULL;
}
static void
init_sleepqueues(void)
{
int i;
for (i = 0; i < SLEEPQ_HSIZE; ++i) {
spin_init(&sleepq_chains[i].sc_spin, "sleepq");
TAILQ_INIT(&sleepq_chains[i].sc_wchead);
}
sleepq_wc_cache = objcache_create_simple(M_SLEEPQ,
sizeof(struct sleepqueue_wchan));
}
SYSINIT(sysinit_sleepqueues, SI_BOOT2_LWKT_INIT, SI_ORDER_SECOND,
init_sleepqueues, NULL);
void
sleepq_setup_thread(struct thread *td)
{
}
void
sleepq_teardown_thread(struct thread *td)
{
}
void
sleepq_lock(const void *wchan)
{
struct sleepqueue_chain *sc;
struct sleepqueue_wchan *wc;
sc = SLEEPQ_LOOKUP(wchan);
spin_lock(&sc->sc_spin);
for (;;) {
TAILQ_FOREACH(wc, &sc->sc_wchead, wc_entry) {
if (wc->wc_wchan == wchan) {
++wc->wc_refs;
return;
}
if (wc->wc_wchan == NULL) {
wc->wc_wchan = wchan;
++wc->wc_refs;
--sc->sc_free_count;
return;
}
}
spin_unlock(&sc->sc_spin);
wc = objcache_get(sleepq_wc_cache, M_WAITOK);
KKASSERT(wc->wc_wchan == NULL && wc->wc_refs == 0);
wc->wc_sc = sc;
spin_lock(&sc->sc_spin);
TAILQ_INSERT_TAIL(&sc->sc_wchead, wc, wc_entry);
++sc->sc_free_count;
}
}
void
sleepq_release(const void *wchan)
{
struct sleepqueue_chain *sc;
struct sleepqueue_wchan *wc;
wc = sleepq_wclookup(wchan);
sc = wc->wc_sc;
KKASSERT(wc->wc_refs > 0);
if (--wc->wc_refs == 0) {
KKASSERT(wc->wc_blocked[0] == 0);
wc->wc_wchan = NULL;
wc->wc_type = 0;
++sc->sc_free_count;
}
spin_unlock(&sc->sc_spin);
}
void
sleepq_add(const void *wchan, struct lock_object *lock, const char *wmesg,
int flags, int queue)
{
struct sleepqueue_wchan *wc;
struct thread *td;
int domain;
wc = sleepq_wclookup(wchan);
++wc->wc_refs;
++wc->wc_blocked[queue];
wc->wc_type = flags & SLEEPQ_TYPE;
td = curthread;
td->td_wmesg = wmesg;
domain = PDOMAIN_FBSD0 + queue * PDOMAIN_FBSDINC;
tsleep_interlock(wchan, domain);
td->td_sqwc = wc;
td->td_sqtimo = 0;
td->td_sqqueue = queue;
}
void
sleepq_set_timeout_sbt(const void *wchan, sbintime_t sbt, sbintime_t pr,
int flags)
{
struct sleepqueue_wchan *wc;
struct thread *td;
td = curthread;
wc = td->td_sqwc;
KKASSERT(wc && wc->wc_wchan == wchan);
td->td_sqtimo = sbticks + sbt;
}
u_int
sleepq_sleepcnt(const void *wchan, int queue)
{
struct sleepqueue_wchan *wc;
wc = sleepq_wclookup(wchan);
return (wc->wc_blocked[queue]);
}
static __inline
int
_sleepq_wait_begin(struct thread *td, struct sleepqueue_chain *sc,
struct sleepqueue_wchan *wc, sbintime_t timo, int tflags)
{
int domain;
int ret;
spin_unlock(&sc->sc_spin);
domain = PDOMAIN_FBSD0 + td->td_sqqueue * PDOMAIN_FBSDINC;
if (timo) {
timo -= sbticks;
if (timo > 0) {
ret = tsleep(td->td_wchan, tflags, td->td_wmesg, timo);
} else {
ret = EWOULDBLOCK;
}
} else {
ret = tsleep(td->td_wchan, tflags, td->td_wmesg, 0);
}
return ret;
}
static __inline
void
_sleepq_wait_complete(struct thread *td, struct sleepqueue_chain *sc,
struct sleepqueue_wchan *wc)
{
struct sleepqueue_wchan *wcn;
td->td_sqwc = NULL;
spin_lock(&sc->sc_spin);
--wc->wc_blocked[td->td_sqqueue];
if (--wc->wc_refs == 0) {
wc->wc_wchan = NULL;
wc->wc_type = 0;
++sc->sc_free_count;
if (sc->sc_free_count <= SLEEPQ_FREEPERSLOT) {
wcn = TAILQ_NEXT(wc, wc_entry);
if (wcn && wcn->wc_wchan) {
TAILQ_REMOVE(&sc->sc_wchead, wc, wc_entry);
TAILQ_INSERT_TAIL(&sc->sc_wchead, wc, wc_entry);
}
spin_unlock(&sc->sc_spin);
} else {
--sc->sc_free_count;
TAILQ_REMOVE(&sc->sc_wchead, wc, wc_entry);
spin_unlock(&sc->sc_spin);
objcache_put(sleepq_wc_cache, wc);
}
} else {
spin_unlock(&sc->sc_spin);
}
}
void
sleepq_wait(const void *wchan, int pri)
{
struct sleepqueue_chain *sc;
struct sleepqueue_wchan *wc;
struct thread *td;
td = curthread;
wc = td->td_sqwc;
KKASSERT(wc != NULL && wc->wc_wchan == wchan);
sc = wc->wc_sc;
(void)_sleepq_wait_begin(td, sc, wc, 0, PINTERLOCKED);
_sleepq_wait_complete(td, sc, wc);
}
int
sleepq_wait_sig(const void *wchan, int pri)
{
struct sleepqueue_chain *sc;
struct sleepqueue_wchan *wc;
struct thread *td;
int ret;
td = curthread;
wc = td->td_sqwc;
KKASSERT(wc != NULL && wc->wc_wchan == wchan);
sc = wc->wc_sc;
ret = _sleepq_wait_begin(td, sc, wc, 0, PINTERLOCKED | PCATCH);
_sleepq_wait_complete(td, sc, wc);
return ret;
}
int
sleepq_timedwait(const void *wchan, int pri)
{
struct sleepqueue_chain *sc;
struct sleepqueue_wchan *wc;
struct thread *td;
int ret;
td = curthread;
wc = td->td_sqwc;
KKASSERT(wc != NULL && wc->wc_wchan == wchan);
sc = wc->wc_sc;
ret = _sleepq_wait_begin(td, sc, wc, td->td_sqtimo, PINTERLOCKED);
_sleepq_wait_complete(td, sc, wc);
return ret;
}
int
sleepq_timedwait_sig(const void *wchan, int pri)
{
struct sleepqueue_chain *sc;
struct sleepqueue_wchan *wc;
struct thread *td;
int ret;
td = curthread;
wc = td->td_sqwc;
KKASSERT(wc != NULL && wc->wc_wchan == wchan);
sc = wc->wc_sc;
ret = _sleepq_wait_begin(td, sc, wc, td->td_sqtimo,
PINTERLOCKED | PCATCH);
_sleepq_wait_complete(td, sc, wc);
return ret;
}
int
sleepq_type(const void *wchan)
{
struct sleepqueue_wchan *wc;
int type;
wc = sleepq_wclookup(wchan);
type = wc->wc_type;
return type;
}
int
sleepq_signal(const void *wchan, int flags, int pri, int queue)
{
int domain;
domain = PDOMAIN_FBSD0 + queue * PDOMAIN_FBSDINC;
wakeup_domain_one(wchan, domain);
return 0;
}
int
sleepq_broadcast(const void *wchan, int flags, int pri, int queue)
{
int domain;
domain = PDOMAIN_FBSD0 + queue * PDOMAIN_FBSDINC;
wakeup_domain(wchan, domain);
return 0;
}