#include <sys/callo.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/cpuvar.h>
#include <sys/thread.h>
#include <sys/kmem.h>
#include <sys/kmem_impl.h>
#include <sys/cmn_err.h>
#include <sys/callb.h>
#include <sys/debug.h>
#include <sys/vtrace.h>
#include <sys/sysmacros.h>
#include <sys/sdt.h>
int callout_init_done;
static int callout_threads;
static hrtime_t callout_debug_hrtime;
static int callout_chunk;
static int callout_min_reap;
static int callout_tolerance;
static callout_table_t *callout_boot_ct;
static clock_t callout_max_ticks;
static hrtime_t callout_longterm;
static ulong_t callout_counter_low;
static ulong_t callout_table_bits;
static ulong_t callout_table_mask;
static callout_cache_t *callout_caches;
#pragma align 64(callout_table)
static callout_table_t *callout_table;
static volatile int callout_realtime_level = CY_LOW_LEVEL;
static volatile int callout_normal_level = CY_LOCK_LEVEL;
static char *callout_kstat_names[] = {
"callout_timeouts",
"callout_timeouts_pending",
"callout_untimeouts_unexpired",
"callout_untimeouts_executing",
"callout_untimeouts_expired",
"callout_expirations",
"callout_allocations",
"callout_cleanups",
};
static hrtime_t callout_heap_process(callout_table_t *, hrtime_t, int);
#define CALLOUT_HASH_INSERT(hash, cp, cnext, cprev) \
{ \
callout_hash_t *hashp = &(hash); \
\
cp->cprev = NULL; \
cp->cnext = hashp->ch_head; \
if (hashp->ch_head == NULL) \
hashp->ch_tail = cp; \
else \
cp->cnext->cprev = cp; \
hashp->ch_head = cp; \
}
#define CALLOUT_HASH_APPEND(hash, cp, cnext, cprev) \
{ \
callout_hash_t *hashp = &(hash); \
\
cp->cnext = NULL; \
cp->cprev = hashp->ch_tail; \
if (hashp->ch_tail == NULL) \
hashp->ch_head = cp; \
else \
cp->cprev->cnext = cp; \
hashp->ch_tail = cp; \
}
#define CALLOUT_HASH_DELETE(hash, cp, cnext, cprev) \
{ \
callout_hash_t *hashp = &(hash); \
\
if (cp->cnext == NULL) \
hashp->ch_tail = cp->cprev; \
else \
cp->cnext->cprev = cp->cprev; \
if (cp->cprev == NULL) \
hashp->ch_head = cp->cnext; \
else \
cp->cprev->cnext = cp->cnext; \
}
#define CALLOUT_APPEND(ct, cp) \
CALLOUT_HASH_APPEND(ct->ct_idhash[CALLOUT_IDHASH(cp->c_xid)], \
cp, c_idnext, c_idprev); \
CALLOUT_HASH_APPEND(cp->c_list->cl_callouts, cp, c_clnext, c_clprev)
#define CALLOUT_DELETE(ct, cp) \
CALLOUT_HASH_DELETE(ct->ct_idhash[CALLOUT_IDHASH(cp->c_xid)], \
cp, c_idnext, c_idprev); \
CALLOUT_HASH_DELETE(cp->c_list->cl_callouts, cp, c_clnext, c_clprev)
#define CALLOUT_LIST_INSERT(hash, cl) \
CALLOUT_HASH_INSERT(hash, cl, cl_next, cl_prev)
#define CALLOUT_LIST_APPEND(hash, cl) \
CALLOUT_HASH_APPEND(hash, cl, cl_next, cl_prev)
#define CALLOUT_LIST_DELETE(hash, cl) \
CALLOUT_HASH_DELETE(hash, cl, cl_next, cl_prev)
#define CALLOUT_LIST_BEFORE(cl, nextcl) \
{ \
(cl)->cl_prev = (nextcl)->cl_prev; \
(cl)->cl_next = (nextcl); \
(nextcl)->cl_prev = (cl); \
if (cl->cl_prev != NULL) \
cl->cl_prev->cl_next = cl; \
}
#define CALLOUT_THRESHOLD 100000000
#define CALLOUT_EXEC_COMPUTE(ct, nextexp, exec) \
{ \
callout_list_t *cl; \
\
cl = ct->ct_expired.ch_head; \
if (cl == NULL) { \
\
exec = 0; \
} else if ((cl->cl_next == NULL) && \
(cl->cl_callouts.ch_head == cl->cl_callouts.ch_tail)) { \
\
exec = 1; \
} else if ((nextexp) > (gethrtime() + CALLOUT_THRESHOLD)) { \
\
exec = 2; \
} else { \
\
exec = 1; \
} \
}
#define CALLOUT_SWAP(h1, h2) \
{ \
callout_heap_t tmp; \
\
tmp = *h1; \
*h1 = *h2; \
*h2 = tmp; \
}
#define CALLOUT_LIST_FREE(ct, cl) \
{ \
cl->cl_next = ct->ct_lfree; \
ct->ct_lfree = cl; \
cl->cl_flags |= CALLOUT_LIST_FLAG_FREE; \
}
#define CALLOUT_FREE(ct, cl) \
{ \
cp->c_idnext = ct->ct_free; \
ct->ct_free = cp; \
cp->c_xid |= CALLOUT_ID_FREE; \
}
static callout_t *
callout_alloc(callout_table_t *ct)
{
size_t size;
callout_t *cp;
ASSERT(MUTEX_HELD(&ct->ct_mutex));
mutex_exit(&ct->ct_mutex);
cp = kmem_cache_alloc(ct->ct_cache, KM_NOSLEEP);
if (cp == NULL) {
size = sizeof (callout_t);
cp = kmem_alloc_tryhard(size, &size, KM_NOSLEEP | KM_PANIC);
}
cp->c_xid = 0;
cp->c_executor = NULL;
cv_init(&cp->c_done, NULL, CV_DEFAULT, NULL);
cp->c_waiting = 0;
mutex_enter(&ct->ct_mutex);
ct->ct_allocations++;
return (cp);
}
static void
callout_list_alloc(callout_table_t *ct)
{
size_t size;
callout_list_t *cl;
ASSERT(MUTEX_HELD(&ct->ct_mutex));
mutex_exit(&ct->ct_mutex);
cl = kmem_cache_alloc(ct->ct_lcache, KM_NOSLEEP);
if (cl == NULL) {
size = sizeof (callout_list_t);
cl = kmem_alloc_tryhard(size, &size, KM_NOSLEEP | KM_PANIC);
}
bzero(cl, sizeof (callout_list_t));
mutex_enter(&ct->ct_mutex);
CALLOUT_LIST_FREE(ct, cl);
}
static callout_list_t *
callout_list_get(callout_table_t *ct, hrtime_t expiration, int flags, int hash)
{
callout_list_t *cl;
int clflags;
ASSERT(MUTEX_HELD(&ct->ct_mutex));
if (flags & CALLOUT_LIST_FLAG_NANO) {
return (NULL);
}
clflags = (CALLOUT_LIST_FLAG_ABSOLUTE | CALLOUT_LIST_FLAG_HRESTIME);
for (cl = ct->ct_clhash[hash].ch_head; (cl != NULL); cl = cl->cl_next) {
if (cl->cl_flags & CALLOUT_LIST_FLAG_NANO)
return (NULL);
if ((cl->cl_expiration == expiration) &&
((cl->cl_flags & clflags) == (flags & clflags)))
return (cl);
}
return (NULL);
}
static int
callout_queue_add(callout_table_t *ct, callout_list_t *cl)
{
callout_list_t *nextcl;
hrtime_t expiration;
expiration = cl->cl_expiration;
nextcl = ct->ct_queue.ch_head;
if ((nextcl == NULL) || (expiration < nextcl->cl_expiration)) {
CALLOUT_LIST_INSERT(ct->ct_queue, cl);
return (1);
}
while (nextcl != NULL) {
if (expiration < nextcl->cl_expiration) {
CALLOUT_LIST_BEFORE(cl, nextcl);
return (0);
}
nextcl = nextcl->cl_next;
}
CALLOUT_LIST_APPEND(ct->ct_queue, cl);
return (0);
}
static void
callout_queue_insert(callout_table_t *ct, callout_list_t *cl)
{
cl->cl_flags |= CALLOUT_LIST_FLAG_QUEUED;
if (callout_queue_add(ct, cl) && (ct->ct_suspend == 0))
(void) cyclic_reprogram(ct->ct_qcyclic, cl->cl_expiration);
}
static hrtime_t
callout_queue_delete(callout_table_t *ct)
{
callout_list_t *cl;
hrtime_t now;
ASSERT(MUTEX_HELD(&ct->ct_mutex));
now = gethrtime();
while ((cl = ct->ct_queue.ch_head) != NULL) {
if (cl->cl_expiration > now)
break;
cl->cl_flags &= ~CALLOUT_LIST_FLAG_QUEUED;
CALLOUT_LIST_DELETE(ct->ct_queue, cl);
CALLOUT_LIST_APPEND(ct->ct_expired, cl);
}
if ((cl == NULL) || (ct->ct_suspend > 0))
return (CY_INFINITY);
(void) cyclic_reprogram(ct->ct_qcyclic, cl->cl_expiration);
return (cl->cl_expiration);
}
static hrtime_t
callout_queue_process(callout_table_t *ct, hrtime_t delta, int timechange)
{
callout_list_t *firstcl, *cl;
hrtime_t expiration, now;
int clflags;
callout_hash_t temp;
ASSERT(MUTEX_HELD(&ct->ct_mutex));
firstcl = ct->ct_queue.ch_head;
if (firstcl == NULL)
return (CY_INFINITY);
temp = ct->ct_queue;
ct->ct_queue.ch_head = NULL;
ct->ct_queue.ch_tail = NULL;
clflags = (CALLOUT_LIST_FLAG_HRESTIME | CALLOUT_LIST_FLAG_ABSOLUTE);
now = gethrtime();
while ((cl = temp.ch_head) != NULL) {
CALLOUT_LIST_DELETE(temp, cl);
if ((cl->cl_expiration <= now) ||
(timechange && ((cl->cl_flags & clflags) == clflags))) {
cl->cl_flags &= ~CALLOUT_LIST_FLAG_QUEUED;
CALLOUT_LIST_APPEND(ct->ct_expired, cl);
continue;
}
if (delta && !(cl->cl_flags & CALLOUT_LIST_FLAG_ABSOLUTE)) {
expiration = cl->cl_expiration + delta;
if (expiration <= 0)
expiration = CY_INFINITY;
cl->cl_expiration = expiration;
}
(void) callout_queue_add(ct, cl);
}
if (ct->ct_expired.ch_head != NULL)
return (gethrtime());
cl = ct->ct_queue.ch_head;
if (cl == NULL)
return (CY_INFINITY);
return (cl->cl_expiration);
}
static void
callout_heap_init(callout_table_t *ct)
{
size_t size;
ASSERT(MUTEX_HELD(&ct->ct_mutex));
ASSERT(ct->ct_heap == NULL);
ct->ct_heap_num = 0;
ct->ct_heap_max = callout_chunk;
size = sizeof (callout_heap_t) * callout_chunk;
ct->ct_heap = kmem_alloc(size, KM_SLEEP);
}
static int
callout_heap_expand(callout_table_t *ct)
{
size_t max, size, osize;
callout_heap_t *heap;
ASSERT(MUTEX_HELD(&ct->ct_mutex));
ASSERT(ct->ct_heap_num <= ct->ct_heap_max);
while (ct->ct_heap_num == ct->ct_heap_max) {
max = ct->ct_heap_max;
mutex_exit(&ct->ct_mutex);
osize = sizeof (callout_heap_t) * max;
size = sizeof (callout_heap_t) * (max + callout_chunk);
heap = kmem_alloc(size, KM_NOSLEEP);
mutex_enter(&ct->ct_mutex);
if (heap == NULL) {
if (ct->ct_nreap > 0)
(void) callout_heap_process(ct, 0, 0);
if (ct->ct_heap_num == ct->ct_heap_max)
return (0);
return (1);
}
if (max < ct->ct_heap_max) {
kmem_free(heap, size);
continue;
}
bcopy(ct->ct_heap, heap, osize);
kmem_free(ct->ct_heap, osize);
ct->ct_heap = heap;
ct->ct_heap_max = size / sizeof (callout_heap_t);
}
return (1);
}
static int
callout_upheap(callout_table_t *ct)
{
int current, parent;
callout_heap_t *heap, *hcurrent, *hparent;
ASSERT(MUTEX_HELD(&ct->ct_mutex));
ASSERT(ct->ct_heap_num >= 1);
if (ct->ct_heap_num == 1) {
return (1);
}
heap = ct->ct_heap;
current = ct->ct_heap_num - 1;
for (;;) {
parent = CALLOUT_HEAP_PARENT(current);
hparent = &heap[parent];
hcurrent = &heap[current];
if (hcurrent->ch_expiration >= hparent->ch_expiration) {
return (0);
}
CALLOUT_SWAP(hparent, hcurrent);
if (parent == 0) {
return (1);
}
current = parent;
}
}
static void
callout_heap_insert(callout_table_t *ct, callout_list_t *cl)
{
ASSERT(MUTEX_HELD(&ct->ct_mutex));
ASSERT(ct->ct_heap_num < ct->ct_heap_max);
cl->cl_flags |= CALLOUT_LIST_FLAG_HEAPED;
ct->ct_heap[ct->ct_heap_num].ch_expiration = cl->cl_expiration;
ct->ct_heap[ct->ct_heap_num].ch_list = cl;
ct->ct_heap_num++;
if (callout_upheap(ct) && (ct->ct_suspend == 0))
(void) cyclic_reprogram(ct->ct_cyclic, cl->cl_expiration);
}
static void
callout_downheap(callout_table_t *ct)
{
int current, left, right, nelems;
callout_heap_t *heap, *hleft, *hright, *hcurrent;
ASSERT(MUTEX_HELD(&ct->ct_mutex));
ASSERT(ct->ct_heap_num >= 1);
heap = ct->ct_heap;
current = 0;
nelems = ct->ct_heap_num;
for (;;) {
if ((left = CALLOUT_HEAP_LEFT(current)) >= nelems)
return;
hleft = &heap[left];
hcurrent = &heap[current];
right = CALLOUT_HEAP_RIGHT(current);
if (right >= nelems)
goto comp_left;
hright = &heap[right];
if (hright->ch_expiration < hleft->ch_expiration) {
if (hcurrent->ch_expiration <= hright->ch_expiration)
return;
CALLOUT_SWAP(hright, hcurrent);
current = right;
continue;
}
comp_left:
if (hcurrent->ch_expiration <= hleft->ch_expiration)
return;
CALLOUT_SWAP(hleft, hcurrent);
current = left;
}
}
static hrtime_t
callout_heap_delete(callout_table_t *ct)
{
hrtime_t now, expiration, next;
callout_list_t *cl;
callout_heap_t *heap;
int hash;
ASSERT(MUTEX_HELD(&ct->ct_mutex));
if (CALLOUT_CLEANUP(ct)) {
(void) callout_heap_process(ct, 0, 0);
}
now = gethrtime();
heap = ct->ct_heap;
while (ct->ct_heap_num > 0) {
expiration = heap->ch_expiration;
hash = CALLOUT_CLHASH(expiration);
cl = heap->ch_list;
ASSERT(expiration == cl->cl_expiration);
if (cl->cl_callouts.ch_head == NULL) {
CALLOUT_LIST_DELETE(ct->ct_clhash[hash], cl);
CALLOUT_LIST_FREE(ct, cl);
ct->ct_nreap--;
} else {
if (expiration > now)
break;
cl->cl_flags &= ~CALLOUT_LIST_FLAG_HEAPED;
CALLOUT_LIST_DELETE(ct->ct_clhash[hash], cl);
CALLOUT_LIST_APPEND(ct->ct_expired, cl);
}
ct->ct_heap_num--;
if (ct->ct_heap_num > 0) {
heap[0] = heap[ct->ct_heap_num];
callout_downheap(ct);
}
}
if ((ct->ct_heap_num == 0) || (ct->ct_suspend > 0))
return (CY_INFINITY);
if (ct->ct_heap_num > 2) {
next = expiration + callout_tolerance;
if ((heap[1].ch_expiration < next) ||
(heap[2].ch_expiration < next))
expiration = next;
}
(void) cyclic_reprogram(ct->ct_cyclic, expiration);
return (expiration);
}
static hrtime_t
callout_heap_process(callout_table_t *ct, hrtime_t delta, int timechange)
{
callout_heap_t *heap;
callout_list_t *cl;
hrtime_t expiration, now;
int i, hash, clflags;
ulong_t num;
ASSERT(MUTEX_HELD(&ct->ct_mutex));
if (ct->ct_heap_num == 0)
return (CY_INFINITY);
if (ct->ct_nreap > 0)
ct->ct_cleanups++;
heap = ct->ct_heap;
num = ct->ct_heap_num;
ct->ct_heap_num = 0;
clflags = (CALLOUT_LIST_FLAG_HRESTIME | CALLOUT_LIST_FLAG_ABSOLUTE);
now = gethrtime();
for (i = 0; i < num; i++) {
cl = heap[i].ch_list;
if (cl->cl_callouts.ch_head == NULL) {
hash = CALLOUT_CLHASH(cl->cl_expiration);
CALLOUT_LIST_DELETE(ct->ct_clhash[hash], cl);
CALLOUT_LIST_FREE(ct, cl);
continue;
}
if ((cl->cl_expiration <= now) ||
(timechange && ((cl->cl_flags & clflags) == clflags))) {
hash = CALLOUT_CLHASH(cl->cl_expiration);
cl->cl_flags &= ~CALLOUT_LIST_FLAG_HEAPED;
CALLOUT_LIST_DELETE(ct->ct_clhash[hash], cl);
CALLOUT_LIST_APPEND(ct->ct_expired, cl);
continue;
}
if (delta && !(cl->cl_flags & CALLOUT_LIST_FLAG_ABSOLUTE)) {
hash = CALLOUT_CLHASH(cl->cl_expiration);
CALLOUT_LIST_DELETE(ct->ct_clhash[hash], cl);
expiration = cl->cl_expiration + delta;
if (expiration <= 0)
expiration = CY_INFINITY;
heap[i].ch_expiration = expiration;
cl->cl_expiration = expiration;
hash = CALLOUT_CLHASH(cl->cl_expiration);
if (cl->cl_flags & CALLOUT_LIST_FLAG_NANO) {
CALLOUT_LIST_APPEND(ct->ct_clhash[hash], cl);
} else {
CALLOUT_LIST_INSERT(ct->ct_clhash[hash], cl);
}
}
heap[ct->ct_heap_num] = heap[i];
ct->ct_heap_num++;
(void) callout_upheap(ct);
}
ct->ct_nreap = 0;
if (ct->ct_expired.ch_head != NULL)
return (gethrtime());
if (ct->ct_heap_num == 0)
return (CY_INFINITY);
return (heap->ch_expiration);
}
callout_id_t
timeout_generic(int type, void (*func)(void *), void *arg,
hrtime_t expiration, hrtime_t resolution, int flags)
{
callout_table_t *ct;
callout_t *cp;
callout_id_t id;
callout_list_t *cl;
hrtime_t now, interval;
int hash, clflags;
ASSERT(resolution > 0);
ASSERT(func != NULL);
now = gethrtime();
kpreempt_disable();
ct = &callout_table[CALLOUT_TABLE(type, CPU->cpu_seqid)];
mutex_enter(&ct->ct_mutex);
if (ct->ct_cyclic == CYCLIC_NONE) {
mutex_exit(&ct->ct_mutex);
ct = &callout_boot_ct[type];
mutex_enter(&ct->ct_mutex);
}
if (CALLOUT_CLEANUP(ct)) {
(void) callout_heap_process(ct, 0, 0);
}
if ((cp = ct->ct_free) == NULL)
cp = callout_alloc(ct);
else
ct->ct_free = cp->c_idnext;
cp->c_func = func;
cp->c_arg = arg;
if (flags & CALLOUT_FLAG_ABSOLUTE) {
interval = expiration - now;
} else {
interval = expiration;
expiration += now;
}
if (resolution > 1) {
if (flags & CALLOUT_FLAG_ROUNDUP)
expiration += resolution - 1;
expiration = (expiration / resolution) * resolution;
}
if (expiration <= 0) {
expiration = CY_INFINITY;
}
if (flags & CALLOUT_FLAG_32BIT) {
if (interval > callout_longterm) {
id = (ct->ct_long_id - callout_counter_low);
id |= CALLOUT_COUNTER_HIGH;
ct->ct_long_id = id;
} else {
id = (ct->ct_short_id - callout_counter_low);
id |= CALLOUT_COUNTER_HIGH;
ct->ct_short_id = id;
}
} else {
id = (ct->ct_gen_id - callout_counter_low);
if ((id & CALLOUT_COUNTER_HIGH) == 0) {
id |= CALLOUT_COUNTER_HIGH;
id += CALLOUT_GENERATION_LOW;
}
ct->ct_gen_id = id;
}
cp->c_xid = id;
clflags = 0;
if (flags & CALLOUT_FLAG_ABSOLUTE)
clflags |= CALLOUT_LIST_FLAG_ABSOLUTE;
if (flags & CALLOUT_FLAG_HRESTIME)
clflags |= CALLOUT_LIST_FLAG_HRESTIME;
if (resolution == 1)
clflags |= CALLOUT_LIST_FLAG_NANO;
hash = CALLOUT_CLHASH(expiration);
again:
cl = callout_list_get(ct, expiration, clflags, hash);
if (cl == NULL) {
if ((cl = ct->ct_lfree) == NULL) {
callout_list_alloc(ct);
goto again;
}
ct->ct_lfree = cl->cl_next;
cl->cl_expiration = expiration;
cl->cl_flags = clflags;
if (ct->ct_heap_num == ct->ct_heap_max) {
if (callout_heap_expand(ct) == 0) {
callout_queue_insert(ct, cl);
goto out;
}
}
if (clflags & CALLOUT_LIST_FLAG_NANO) {
CALLOUT_LIST_APPEND(ct->ct_clhash[hash], cl);
} else {
CALLOUT_LIST_INSERT(ct->ct_clhash[hash], cl);
}
callout_heap_insert(ct, cl);
} else {
if (cl->cl_callouts.ch_head == NULL)
ct->ct_nreap--;
}
out:
cp->c_list = cl;
CALLOUT_APPEND(ct, cp);
ct->ct_timeouts++;
ct->ct_timeouts_pending++;
mutex_exit(&ct->ct_mutex);
kpreempt_enable();
TRACE_4(TR_FAC_CALLOUT, TR_TIMEOUT,
"timeout:%K(%p) in %llx expiration, cp %p", func, arg, expiration,
cp);
return (id);
}
timeout_id_t
timeout(void (*func)(void *), void *arg, clock_t delta)
{
ulong_t id;
if (delta <= 0)
delta = 1;
else if (delta > callout_max_ticks)
delta = callout_max_ticks;
id = (ulong_t)timeout_generic(CALLOUT_NORMAL, func, arg,
TICK_TO_NSEC(delta), nsec_per_tick, CALLOUT_LEGACY);
return ((timeout_id_t)id);
}
callout_id_t
timeout_default(void (*func)(void *), void *arg, clock_t delta)
{
callout_id_t id;
if (delta <= 0)
delta = 1;
else if (delta > callout_max_ticks)
delta = callout_max_ticks;
id = timeout_generic(CALLOUT_NORMAL, func, arg, TICK_TO_NSEC(delta),
nsec_per_tick, 0);
return (id);
}
timeout_id_t
realtime_timeout(void (*func)(void *), void *arg, clock_t delta)
{
ulong_t id;
if (delta <= 0)
delta = 1;
else if (delta > callout_max_ticks)
delta = callout_max_ticks;
id = (ulong_t)timeout_generic(CALLOUT_REALTIME, func, arg,
TICK_TO_NSEC(delta), nsec_per_tick, CALLOUT_LEGACY);
return ((timeout_id_t)id);
}
callout_id_t
realtime_timeout_default(void (*func)(void *), void *arg, clock_t delta)
{
callout_id_t id;
if (delta <= 0)
delta = 1;
else if (delta > callout_max_ticks)
delta = callout_max_ticks;
id = timeout_generic(CALLOUT_REALTIME, func, arg, TICK_TO_NSEC(delta),
nsec_per_tick, 0);
return (id);
}
hrtime_t
untimeout_generic(callout_id_t id, int nowait)
{
callout_table_t *ct;
callout_t *cp;
callout_id_t xid;
callout_list_t *cl;
int hash, flags;
callout_id_t bogus;
ct = &callout_table[CALLOUT_ID_TO_TABLE(id)];
hash = CALLOUT_IDHASH(id);
mutex_enter(&ct->ct_mutex);
for (cp = ct->ct_idhash[hash].ch_head; cp; cp = cp->c_idnext) {
xid = cp->c_xid;
if ((xid & CALLOUT_ID_MASK) != id)
continue;
if ((xid & CALLOUT_EXECUTING) == 0) {
hrtime_t expiration;
cl = cp->c_list;
expiration = cl->cl_expiration;
CALLOUT_DELETE(ct, cp);
CALLOUT_FREE(ct, cp);
ct->ct_untimeouts_unexpired++;
ct->ct_timeouts_pending--;
if (cl->cl_callouts.ch_head == NULL) {
flags = cl->cl_flags;
if (flags & CALLOUT_LIST_FLAG_HEAPED) {
ct->ct_nreap++;
} else if (flags & CALLOUT_LIST_FLAG_QUEUED) {
CALLOUT_LIST_DELETE(ct->ct_queue, cl);
CALLOUT_LIST_FREE(ct, cl);
} else {
CALLOUT_LIST_DELETE(ct->ct_expired, cl);
CALLOUT_LIST_FREE(ct, cl);
}
}
mutex_exit(&ct->ct_mutex);
expiration -= gethrtime();
TRACE_2(TR_FAC_CALLOUT, TR_UNTIMEOUT,
"untimeout:ID %lx hrtime left %llx", id,
expiration);
return (expiration < 0 ? 0 : expiration);
}
ct->ct_untimeouts_executing++;
if (cp->c_executor == curthread) {
mutex_exit(&ct->ct_mutex);
TRACE_1(TR_FAC_CALLOUT, TR_UNTIMEOUT_SELF,
"untimeout_self:ID %x", id);
return (-1);
}
if (nowait == 0) {
while (cp->c_xid == xid) {
cp->c_waiting = 1;
cv_wait(&cp->c_done, &ct->ct_mutex);
}
}
mutex_exit(&ct->ct_mutex);
TRACE_1(TR_FAC_CALLOUT, TR_UNTIMEOUT_EXECUTING,
"untimeout_executing:ID %lx", id);
return (-1);
}
ct->ct_untimeouts_expired++;
mutex_exit(&ct->ct_mutex);
TRACE_1(TR_FAC_CALLOUT, TR_UNTIMEOUT_BOGUS_ID,
"untimeout_bogus_id:ID %lx", id);
bogus = (CALLOUT_ID_FLAGS | CALLOUT_COUNTER_HIGH);
if (((id & bogus) != CALLOUT_COUNTER_HIGH) && (id != 0))
panic("untimeout: impossible timeout id %llx",
(unsigned long long)id);
return (-1);
}
clock_t
untimeout(timeout_id_t id_arg)
{
hrtime_t hleft;
clock_t tleft;
callout_id_t id;
id = (ulong_t)id_arg;
hleft = untimeout_generic(id, 0);
if (hleft < 0)
tleft = -1;
else if (hleft == 0)
tleft = 0;
else
tleft = NSEC_TO_TICK(hleft);
return (tleft);
}
clock_t
untimeout_default(callout_id_t id, int nowait)
{
hrtime_t hleft;
clock_t tleft;
hleft = untimeout_generic(id, nowait);
if (hleft < 0)
tleft = -1;
else if (hleft == 0)
tleft = 0;
else
tleft = NSEC_TO_TICK(hleft);
return (tleft);
}
static void
callout_list_expire(callout_table_t *ct, callout_list_t *cl)
{
callout_t *cp, *cnext;
ASSERT(MUTEX_HELD(&ct->ct_mutex));
ASSERT(cl != NULL);
for (cp = cl->cl_callouts.ch_head; cp != NULL; cp = cnext) {
if (cp->c_xid & CALLOUT_EXECUTING) {
cnext = cp->c_clnext;
continue;
}
cp->c_xid |= CALLOUT_EXECUTING;
cp->c_executor = curthread;
mutex_exit(&ct->ct_mutex);
DTRACE_PROBE1(callout__start, callout_t *, cp);
(*cp->c_func)(cp->c_arg);
DTRACE_PROBE1(callout__end, callout_t *, cp);
mutex_enter(&ct->ct_mutex);
ct->ct_expirations++;
ct->ct_timeouts_pending--;
cp->c_xid &= ~CALLOUT_EXECUTING;
cp->c_executor = NULL;
cnext = cp->c_clnext;
CALLOUT_DELETE(ct, cp);
CALLOUT_FREE(ct, cp);
if (cp->c_waiting) {
cp->c_waiting = 0;
cv_broadcast(&cp->c_done);
}
}
}
static void
callout_expire(callout_table_t *ct)
{
callout_list_t *cl, *clnext;
ASSERT(MUTEX_HELD(&ct->ct_mutex));
for (cl = ct->ct_expired.ch_head; (cl != NULL); cl = clnext) {
callout_list_expire(ct, cl);
clnext = cl->cl_next;
if (cl->cl_callouts.ch_head == NULL) {
CALLOUT_LIST_DELETE(ct->ct_expired, cl);
CALLOUT_LIST_FREE(ct, cl);
}
}
}
void
callout_realtime(callout_table_t *ct)
{
mutex_enter(&ct->ct_mutex);
(void) callout_heap_delete(ct);
callout_expire(ct);
mutex_exit(&ct->ct_mutex);
}
void
callout_queue_realtime(callout_table_t *ct)
{
mutex_enter(&ct->ct_mutex);
(void) callout_queue_delete(ct);
callout_expire(ct);
mutex_exit(&ct->ct_mutex);
}
void
callout_execute(callout_table_t *ct)
{
mutex_enter(&ct->ct_mutex);
callout_expire(ct);
mutex_exit(&ct->ct_mutex);
}
void
callout_normal(callout_table_t *ct)
{
int i, exec;
hrtime_t exp;
mutex_enter(&ct->ct_mutex);
exp = callout_heap_delete(ct);
CALLOUT_EXEC_COMPUTE(ct, exp, exec);
mutex_exit(&ct->ct_mutex);
for (i = 0; i < exec; i++) {
ASSERT(ct->ct_taskq != NULL);
(void) taskq_dispatch(ct->ct_taskq,
(task_func_t *)callout_execute, ct, TQ_NOSLEEP);
}
}
void
callout_queue_normal(callout_table_t *ct)
{
int i, exec;
hrtime_t exp;
mutex_enter(&ct->ct_mutex);
exp = callout_queue_delete(ct);
CALLOUT_EXEC_COMPUTE(ct, exp, exec);
mutex_exit(&ct->ct_mutex);
for (i = 0; i < exec; i++) {
ASSERT(ct->ct_taskq != NULL);
(void) taskq_dispatch(ct->ct_taskq,
(task_func_t *)callout_execute, ct, TQ_NOSLEEP);
}
}
static void
callout_suspend(void)
{
int t, f;
callout_table_t *ct;
for (f = 0; f < max_ncpus; f++) {
for (t = 0; t < CALLOUT_NTYPES; t++) {
ct = &callout_table[CALLOUT_TABLE(t, f)];
mutex_enter(&ct->ct_mutex);
ct->ct_suspend++;
if (ct->ct_cyclic == CYCLIC_NONE) {
mutex_exit(&ct->ct_mutex);
continue;
}
if (ct->ct_suspend == 1) {
(void) cyclic_reprogram(ct->ct_cyclic,
CY_INFINITY);
(void) cyclic_reprogram(ct->ct_qcyclic,
CY_INFINITY);
}
mutex_exit(&ct->ct_mutex);
}
}
}
static void
callout_resume(hrtime_t delta, int timechange)
{
hrtime_t hexp, qexp;
int t, f;
callout_table_t *ct;
for (f = 0; f < max_ncpus; f++) {
for (t = 0; t < CALLOUT_NTYPES; t++) {
ct = &callout_table[CALLOUT_TABLE(t, f)];
mutex_enter(&ct->ct_mutex);
if (ct->ct_cyclic == CYCLIC_NONE) {
ct->ct_suspend--;
mutex_exit(&ct->ct_mutex);
continue;
}
hexp = callout_heap_process(ct, delta, timechange);
qexp = callout_queue_process(ct, delta, timechange);
ct->ct_suspend--;
if (ct->ct_suspend == 0) {
(void) cyclic_reprogram(ct->ct_cyclic, hexp);
(void) cyclic_reprogram(ct->ct_qcyclic, qexp);
}
mutex_exit(&ct->ct_mutex);
}
}
}
static boolean_t
callout_cpr_callb(void *arg, int code)
{
if (code == CB_CODE_CPR_CHKPT)
callout_suspend();
else
callout_resume(0, 1);
return (B_TRUE);
}
static boolean_t
callout_debug_callb(void *arg, int code)
{
hrtime_t delta;
if (code == 0) {
callout_suspend();
callout_debug_hrtime = gethrtime();
} else {
delta = gethrtime() - callout_debug_hrtime;
callout_resume(delta, 0);
}
return (B_TRUE);
}
static void
callout_hrestime_one(callout_table_t *ct)
{
hrtime_t hexp, qexp;
mutex_enter(&ct->ct_mutex);
if (ct->ct_cyclic == CYCLIC_NONE) {
mutex_exit(&ct->ct_mutex);
return;
}
hexp = callout_heap_process(ct, 0, 1);
qexp = callout_queue_process(ct, 0, 1);
if (ct->ct_suspend == 0) {
(void) cyclic_reprogram(ct->ct_cyclic, hexp);
(void) cyclic_reprogram(ct->ct_qcyclic, qexp);
}
mutex_exit(&ct->ct_mutex);
}
void
callout_hrestime(void)
{
int t, f;
callout_table_t *ct;
for (f = 0; f < max_ncpus; f++) {
for (t = 0; t < CALLOUT_NTYPES; t++) {
ct = &callout_table[CALLOUT_TABLE(t, f)];
callout_hrestime_one(ct);
}
}
}
static void
callout_hash_init(callout_table_t *ct)
{
size_t size;
ASSERT(MUTEX_HELD(&ct->ct_mutex));
ASSERT((ct->ct_idhash == NULL) && (ct->ct_clhash == NULL));
size = sizeof (callout_hash_t) * CALLOUT_BUCKETS;
ct->ct_idhash = kmem_zalloc(size, KM_SLEEP);
ct->ct_clhash = kmem_zalloc(size, KM_SLEEP);
}
static void
callout_kstat_init(callout_table_t *ct)
{
callout_stat_type_t stat;
kstat_t *ct_kstats;
int ndx;
ASSERT(MUTEX_HELD(&ct->ct_mutex));
ASSERT(ct->ct_kstats == NULL);
ndx = ct - callout_table;
ct_kstats = kstat_create("unix", ndx, "callout",
"misc", KSTAT_TYPE_NAMED, CALLOUT_NUM_STATS, KSTAT_FLAG_VIRTUAL);
if (ct_kstats == NULL) {
cmn_err(CE_WARN, "kstat_create for callout table %p failed",
(void *)ct);
} else {
ct_kstats->ks_data = ct->ct_kstat_data;
for (stat = 0; stat < CALLOUT_NUM_STATS; stat++)
kstat_named_init(&ct->ct_kstat_data[stat],
callout_kstat_names[stat], KSTAT_DATA_INT64);
ct->ct_kstats = ct_kstats;
kstat_install(ct_kstats);
}
}
static void
callout_cyclic_init(callout_table_t *ct)
{
cyc_handler_t hdlr;
cyc_time_t when;
processorid_t seqid;
int t;
cyclic_id_t cyclic, qcyclic;
ASSERT(MUTEX_HELD(&ct->ct_mutex));
t = ct->ct_type;
seqid = CALLOUT_TABLE_SEQID(ct);
if (t == CALLOUT_NORMAL) {
ASSERT(ct->ct_taskq == NULL);
ct->ct_taskq =
taskq_create_instance("callout_taskq", seqid,
callout_threads, maxclsyspri,
2 * callout_threads, 2 * callout_threads,
TASKQ_PREPOPULATE | TASKQ_CPR_SAFE);
}
ASSERT(ct->ct_heap_num == 0);
mutex_exit(&ct->ct_mutex);
ASSERT(ct->ct_cyclic == CYCLIC_NONE);
if (t == CALLOUT_REALTIME) {
hdlr.cyh_level = callout_realtime_level;
hdlr.cyh_func = (cyc_func_t)callout_realtime;
} else {
hdlr.cyh_level = callout_normal_level;
hdlr.cyh_func = (cyc_func_t)callout_normal;
}
hdlr.cyh_arg = ct;
when.cyt_when = CY_INFINITY;
when.cyt_interval = CY_INFINITY;
cyclic = cyclic_add(&hdlr, &when);
if (t == CALLOUT_REALTIME)
hdlr.cyh_func = (cyc_func_t)callout_queue_realtime;
else
hdlr.cyh_func = (cyc_func_t)callout_queue_normal;
qcyclic = cyclic_add(&hdlr, &when);
mutex_enter(&ct->ct_mutex);
ct->ct_cyclic = cyclic;
ct->ct_qcyclic = qcyclic;
}
void
callout_cpu_online(cpu_t *cp)
{
lgrp_handle_t hand;
callout_cache_t *cache;
char s[KMEM_CACHE_NAMELEN];
callout_table_t *ct;
processorid_t seqid;
int t;
ASSERT(MUTEX_HELD(&cpu_lock));
hand = lgrp_plat_cpu_to_hand(cp->cpu_id);
for (cache = callout_caches; cache != NULL; cache = cache->cc_next) {
if (cache->cc_hand == hand)
break;
}
if (cache == NULL) {
cache = kmem_alloc(sizeof (callout_cache_t), KM_SLEEP);
cache->cc_hand = hand;
(void) snprintf(s, KMEM_CACHE_NAMELEN, "callout_cache%lx",
(long)hand);
cache->cc_cache = kmem_cache_create(s, sizeof (callout_t),
CALLOUT_ALIGN, NULL, NULL, NULL, NULL, NULL, 0);
(void) snprintf(s, KMEM_CACHE_NAMELEN, "callout_lcache%lx",
(long)hand);
cache->cc_lcache = kmem_cache_create(s, sizeof (callout_list_t),
CALLOUT_ALIGN, NULL, NULL, NULL, NULL, NULL, 0);
cache->cc_next = callout_caches;
callout_caches = cache;
}
seqid = cp->cpu_seqid;
for (t = 0; t < CALLOUT_NTYPES; t++) {
ct = &callout_table[CALLOUT_TABLE(t, seqid)];
mutex_enter(&ct->ct_mutex);
ct->ct_cache = cache->cc_cache;
ct->ct_lcache = cache->cc_lcache;
if (ct->ct_heap == NULL) {
callout_heap_init(ct);
callout_hash_init(ct);
callout_kstat_init(ct);
callout_cyclic_init(ct);
}
mutex_exit(&ct->ct_mutex);
cyclic_bind(ct->ct_cyclic, cp, NULL);
cyclic_bind(ct->ct_qcyclic, cp, NULL);
}
}
void
callout_cpu_offline(cpu_t *cp)
{
callout_table_t *ct;
processorid_t seqid;
int t;
ASSERT(MUTEX_HELD(&cpu_lock));
seqid = cp->cpu_seqid;
for (t = 0; t < CALLOUT_NTYPES; t++) {
ct = &callout_table[CALLOUT_TABLE(t, seqid)];
cyclic_bind(ct->ct_cyclic, NULL, NULL);
cyclic_bind(ct->ct_qcyclic, NULL, NULL);
}
}
void
callout_mp_init(void)
{
cpu_t *cp;
size_t min, max;
if (callout_chunk == CALLOUT_CHUNK) {
min = CALLOUT_MIN_HEAP_SIZE;
max = ptob(physmem / CALLOUT_MEM_FRACTION);
if (min > max)
min = max;
callout_chunk = min / sizeof (callout_heap_t);
callout_chunk /= ncpus_online;
callout_chunk = P2ROUNDUP(callout_chunk, CALLOUT_CHUNK);
}
mutex_enter(&cpu_lock);
cp = cpu_active;
do {
callout_cpu_online(cp);
} while ((cp = cp->cpu_next_onln) != cpu_active);
mutex_exit(&cpu_lock);
}
void
callout_init(void)
{
int f, t;
size_t size;
int table_id;
callout_table_t *ct;
long bits, fanout;
uintptr_t buf;
bits = 0;
for (fanout = 1; (fanout < max_ncpus); fanout <<= 1)
bits++;
callout_table_bits = CALLOUT_TYPE_BITS + bits;
callout_table_mask = (1 << callout_table_bits) - 1;
callout_counter_low = 1 << CALLOUT_COUNTER_SHIFT;
callout_longterm = TICK_TO_NSEC(CALLOUT_LONGTERM_TICKS);
callout_max_ticks = CALLOUT_MAX_TICKS;
if (callout_min_reap == 0)
callout_min_reap = CALLOUT_MIN_REAP;
if (callout_tolerance <= 0)
callout_tolerance = CALLOUT_TOLERANCE;
if (callout_threads <= 0)
callout_threads = CALLOUT_THREADS;
if (callout_chunk <= 0)
callout_chunk = CALLOUT_CHUNK;
else
callout_chunk = P2ROUNDUP(callout_chunk, CALLOUT_CHUNK);
size = sizeof (callout_table_t) * CALLOUT_NTYPES * max_ncpus;
size += CALLOUT_ALIGN;
buf = (uintptr_t)kmem_zalloc(size, KM_SLEEP);
callout_table = (callout_table_t *)P2ROUNDUP(buf, CALLOUT_ALIGN);
size = sizeof (kstat_named_t) * CALLOUT_NUM_STATS;
for (f = 0; f < max_ncpus; f++) {
for (t = 0; t < CALLOUT_NTYPES; t++) {
table_id = CALLOUT_TABLE(t, f);
ct = &callout_table[table_id];
ct->ct_type = t;
mutex_init(&ct->ct_mutex, NULL, MUTEX_DEFAULT, NULL);
ct->ct_short_id = CALLOUT_SHORT_ID(table_id);
ct->ct_long_id = CALLOUT_LONG_ID(table_id);
ct->ct_gen_id = CALLOUT_SHORT_ID(table_id);
ct->ct_cyclic = CYCLIC_NONE;
ct->ct_qcyclic = CYCLIC_NONE;
ct->ct_kstat_data = kmem_zalloc(size, KM_SLEEP);
}
}
(void) callb_add(callout_cpr_callb, 0, CB_CL_CPR_CALLOUT,
"callout_cpr");
(void) callb_add(callout_debug_callb, 0, CB_CL_ENTER_DEBUGGER,
"callout_debug");
mutex_enter(&cpu_lock);
callout_boot_ct = &callout_table[CALLOUT_TABLE(0, CPU->cpu_seqid)];
callout_cpu_online(CPU);
mutex_exit(&cpu_lock);
callout_init_done = 1;
}