diff options
Diffstat (limited to 'usr/src/uts/common/os/ddi_timer.c')
-rw-r--r-- | usr/src/uts/common/os/ddi_timer.c | 936 |
1 files changed, 936 insertions, 0 deletions
diff --git a/usr/src/uts/common/os/ddi_timer.c b/usr/src/uts/common/os/ddi_timer.c new file mode 100644 index 0000000000..0f60567d25 --- /dev/null +++ b/usr/src/uts/common/os/ddi_timer.c @@ -0,0 +1,936 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/atomic.h> +#include <sys/callb.h> +#include <sys/conf.h> +#include <sys/cmn_err.h> +#include <sys/taskq.h> +#include <sys/dditypes.h> +#include <sys/ddi_timer.h> +#include <sys/disp.h> +#include <sys/kobj.h> +#include <sys/note.h> +#include <sys/param.h> +#include <sys/sysmacros.h> +#include <sys/systm.h> +#include <sys/time.h> +#include <sys/types.h> + +/* + * global variables for timeout request + */ +static kmem_cache_t *req_cache; /* kmem cache for timeout request */ + +/* + * taskq for timer + */ +static int timer_taskq_num = 16; /* initial thread number */ +static taskq_t *tm_taskq; /* taskq thread pool */ +static kthread_t *tm_work_thread; /* work thread invoking taskq */ + +/* + * timer variables + */ +static cyc_timer_t *ddi_timer; /* ddi timer based on the cyclic */ +static volatile hrtime_t timer_hrtime; /* current tick time on the timer */ + +/* + * Variable used for the suspend/resume. + */ +static volatile boolean_t timer_suspended; + +/* + * Kernel taskq queue to ddi timer + */ +static list_t kern_queue; /* kernel thread request queue */ +static kcondvar_t kern_cv; /* condition variable for taskq queue */ + +/* + * Software interrupt queue dedicated to ddi timer + */ +static list_t intr_queue; /* software interrupt request queue */ +static uint_t intr_state; /* software interrupt state */ + +/* + * This lock is used to protect the intr_queue and kern_queue. + * It's also used to protect the intr_state which represents the software + * interrupt state for the timer. + */ +static kmutex_t disp_req_lock; + +/* + * the periodic timer interrupt priority level + */ +enum { + TM_IPL_0 = 0, /* kernel context */ + TM_IPL_1, TM_IPL_2, TM_IPL_3, /* level 1-3 */ + TM_IPL_4, TM_IPL_5, TM_IPL_6, /* level 4-6 */ + TM_IPL_7, TM_IPL_8, TM_IPL_9, /* level 7-9 */ + TM_IPL_10 /* level 10 */ +}; + +/* + * A callback handler used by CPR to stop and resume callouts. + * Since the taskq uses TASKQ_CPR_SAFE, the function just set the boolean + * flag to timer_suspended here. + */ +/*ARGSUSED*/ +static boolean_t +timer_cpr_callb(void *arg, int code) +{ + timer_suspended = (code == CB_CODE_CPR_CHKPT); + return (B_TRUE); +} + +/* + * Return a proposed timeout request id. add_req() determines whether + * or not the proposed one is used. If it's not suitable, add_req() + * recalls get_req_cnt(). To reduce the lock contention between the + * timer and i_untimeout(), the atomic instruction should be used here. + */ +static timeout_t +get_req_cnt(void) +{ + static volatile ulong_t timeout_cnt = 0; + return ((timeout_t)atomic_inc_ulong_nv(&timeout_cnt)); +} + +/* + * Get the system resolution. + * Note. currently there is a restriction about the system resolution, and + * the 10ms tick (the default clock resolution) is only supported now. + */ +static hrtime_t +i_get_res(void) +{ + return ((hrtime_t)10000000); /* 10ms tick only */ +} + +/* + * Return the value for the cog of the timing wheel. + * TICK_FACTOR is used to gain a finer cog on the clock resolution. + */ +static hrtime_t +tw_tick(hrtime_t time) +{ + return ((time << TICK_FACTOR) / ddi_timer->res); +} + +/* + * Calculate the expiration time for the timeout request. + */ +static hrtime_t +expire_tick(tm_req_t *req) +{ + return (tw_tick(req->exp_time)); +} + +/* + * Register a timeout request to the timer. This function is used + * in i_timeout(). + */ +static timeout_t +add_req(tm_req_t *req) +{ + timer_tw_t *tid, *tw; + tm_req_t *next; + timeout_t id; + +retry: + /* + * Retrieve a timeout request id. Since i_timeout() needs to return + * a non-zero value, re-try if the zero is gotten. + */ + if ((id = get_req_cnt()) == 0) + id = get_req_cnt(); + + /* + * Check if the id is not used yet. Since the framework now deals + * with the periodic timeout requests, we cannot assume the id + * allocated (long) before doesn't exist any more when it will + * be re-assigned again (especially on 32bit) but need to handle + * this case to solve the conflicts. If it's used already, retry + * another. + */ + tid = &ddi_timer->idhash[TM_HASH((uintptr_t)id)]; + mutex_enter(&tid->lock); + for (next = list_head(&tid->req); next != NULL; + next = list_next(&tid->req, next)) { + if (next->id == id) { + mutex_exit(&tid->lock); + goto retry; + } + } + /* Nobody uses this id yet */ + req->id = id; + + /* + * Register this request to the timer. + * The list operation must be list_insert_head(). + * Other operations can degrade performance. + */ + list_insert_head(&tid->req, req); + mutex_exit(&tid->lock); + + tw = &ddi_timer->exhash[TM_HASH(expire_tick(req))]; + mutex_enter(&tw->lock); + /* + * Other operations than list_insert_head() can + * degrade performance here. + */ + list_insert_head(&tw->req, req); + mutex_exit(&tw->lock); + + return (id); +} + +/* + * Periodic timeout requests cannot be removed until they are canceled + * explicitly. Until then, they need to be re-registerd after they are + * fired. transfer_req() re-registers the requests for the next fires. + * Note. transfer_req() sends the cv_signal to timeout_execute(), which + * runs in interrupt context. Make sure this function will not be blocked, + * otherwise the deadlock situation can occur. + */ +static void +transfer_req(tm_req_t *req, timer_tw_t *tw) +{ + timer_tw_t *new_tw; + hrtime_t curr_time; + ASSERT(tw && MUTEX_HELD(&tw->lock)); + + /* Calculate the next expiration time by interval */ + req->exp_time += req->interval; + curr_time = gethrtime(); + + /* + * If a long time (more than 1 clock resolution) has already + * passed for some reason (e.g. debugger or high interrupt), + * round up the next expiration to the appropriate one + * since this request is periodic and never catches with it. + */ + if (curr_time - req->exp_time >= ddi_timer->res) { + req->exp_time = roundup(curr_time + req->interval, + ddi_timer->res); + } + + /* + * Re-register this request. + * Note. since it is guaranteed that the timer is invoked on only + * one CPU at any time (by the cyclic subsystem), a deadlock + * cannot occur regardless of the lock order here. + */ + new_tw = &ddi_timer->exhash[TM_HASH(expire_tick(req))]; + + /* + * If it's on the timer cog already, there is nothing + * to do. Just return. + */ + if (new_tw == tw) + return; + + /* Remove this request from the timer */ + list_remove(&tw->req, req); + + /* Re-register this request to the timer */ + mutex_enter(&new_tw->lock); + + /* + * Other operations than list_insert_head() can + * degrade performance here. + */ + list_insert_head(&new_tw->req, req); + mutex_exit(&new_tw->lock); + + /* + * Set the TM_TRANSFER flag and notify the request is transfered + * completely. This prevents a race in the case that this request + * is serviced on another CPU already. + */ + mutex_enter(&req->lock); + req->flags |= TM_TRANSFER; + cv_signal(&req->cv); + mutex_exit(&req->lock); +} + +/* + * Execute timeout requests. + * Note. since timeout_execute() can run in interrupt context and block + * on condition variables, there are restrictions on the timer code that + * signals these condition variables (see i_untimeout(), transfer_req(), + * and condvar(9F)). Functions that signal these cvs must ensure that + * they will not be blocked (for memory allocations or any other reason) + * since condition variables don't support priority inheritance. + */ +static void +timeout_execute(void *arg) +{ + tm_req_t *req = (tm_req_t *)arg; + ASSERT(req->flags & TM_INVOKING && !(req->flags & TM_EXECUTING)); + + for (;;) { + /* + * Check if this request is canceled. If it's canceled, do not + * execute this request. + */ + mutex_enter(&req->lock); + if (!(req->flags & TM_CANCEL)) { + /* + * Set the current thread to prevent a dead lock + * situation in case that this timeout request is + * canceled in the handler being invoked now. + * (this doesn't violate the spec) Set TM_EXECUTING + * to show this handler is invoked soon. + */ + req->h_thread = curthread; + req->flags |= TM_EXECUTING; + mutex_exit(&req->lock); + + /* The handler is invoked without holding any locks */ + (*req->handler)(req->arg); + + /* + * Set TM_COMPLETE and notify the request is complete + * now. + */ + mutex_enter(&req->lock); + req->flags |= TM_COMPLETE; + if (req->flags & TM_COMPWAIT) + cv_signal(&req->cv); + } + + /* + * The handler is invoked at this point. If this request + * is not canceled, prepare for the next fire. + */ + if (req->flags & TM_CANCEL) { + timer_tw_t *tw; + /* + * Wait until the timer finishes all things for + * this request. + */ + while (!(req->flags & TM_TRANSFER)) + cv_wait(&req->cv, &req->lock); + mutex_exit(&req->lock); + ASSERT(req->flags & TM_TRANSFER); + + /* Remove this request from the timer */ + tw = &ddi_timer->exhash[TM_HASH(expire_tick(req))]; + mutex_enter(&tw->lock); + list_remove(&tw->req, req); + mutex_exit(&tw->lock); + + /* + * Wait until i_untimeout() can go ahead. + * This prevents the request from being freed before + * i_untimeout() is complete. + */ + mutex_enter(&req->lock); + while (req->flags & TM_COMPWAIT) + cv_wait(&req->cv, &req->lock); + mutex_exit(&req->lock); + ASSERT(!(req->flags & TM_COMPWAIT)); + + /* Free this request */ + kmem_cache_free(req_cache, req); + return; + } + ASSERT(req->flags & TM_EXECUTING); + + /* + * TM_EXECUTING must be set at this point. + * Unset the flag. + */ + req->flags &= ~(TM_EXECUTING | TM_TRANSFER); + + /* + * Decrease the request cnt. The reqest cnt shows + * how many times this request is executed now. + * If this counter becomes the zero, drop TM_INVOKING + * to show there is no requests to do now. + */ + req->cnt--; + if (req->cnt == 0) { + req->flags &= ~TM_INVOKING; + mutex_exit(&req->lock); + return; + } + mutex_exit(&req->lock); + } +} + +/* + * Timeout worker thread for processing task queue. + */ +static void +timeout_taskq_thread(void *arg) +{ + _NOTE(ARGUNUSED(arg)); + tm_req_t *kern_req; + callb_cpr_t cprinfo; + + CALLB_CPR_INIT(&cprinfo, &disp_req_lock, callb_generic_cpr, + "timeout_taskq_thread"); + + /* + * This thread is wakened up when a new request is added to + * the queue. Then pick up all requests and dispatch them + * via taskq_dispatch(). + */ + for (;;) { + /* + * Check the queue and pick up a request if the queue + * is not NULL. + */ + mutex_enter(&disp_req_lock); + while ((kern_req = list_head(&kern_queue)) == NULL) { + CALLB_CPR_SAFE_BEGIN(&cprinfo); + cv_wait(&kern_cv, &disp_req_lock); + CALLB_CPR_SAFE_END(&cprinfo, &disp_req_lock); + } + list_remove(&kern_queue, kern_req); + mutex_exit(&disp_req_lock); + + /* Execute the timeout request via the taskq thread */ + (void) taskq_dispatch(tm_taskq, timeout_execute, + (void *)kern_req, TQ_SLEEP); + } +} + +/* + * Dispatch the timeout request based on the level specified. + * If the level is equal to zero, notify the worker thread to + * call taskq_dispatch() in kernel context. If the level is bigger + * than zero, add a software interrupt request to the queue and raise + * the interrupt level to the specified one. + */ +static void +timeout_dispatch(tm_req_t *req) +{ + int level = req->level; + extern void sir_on(int); + + if (level == TM_IPL_0) { + /* Add a new request to the tail */ + mutex_enter(&disp_req_lock); + list_insert_tail(&kern_queue, req); + mutex_exit(&disp_req_lock); + + /* + * notify the worker thread that this request + * is newly added to the queue. + * Note. this cv_signal() can be called after the + * mutex_lock. + */ + cv_signal(&kern_cv); + } else { + /* Add a new request to the tail */ + mutex_enter(&disp_req_lock); + list_insert_tail(&intr_queue, req); + + /* Issue the software interrupt */ + if (intr_state & TM_INTR_START(level)) { + /* + * timer_softintr() is already running; no need to + * raise a siron. Due to lock protection of + * the intr_queue and intr_state, we know that + * timer_softintr() will see the new addition to + * the intr_queue. + */ + mutex_exit(&disp_req_lock); + } else { + intr_state |= TM_INTR_SET(level); + mutex_exit(&disp_req_lock); + + /* Raise an interrupt to execute timeout requests */ + sir_on(level); + } + } +} + +/* + * Check the software interrupt queue and invoke requests at the specified + * interrupt level. + * Note that the queue may change during call so that the disp_req_lock + * and the intr_state are used to protect it. + * The software interrupts supported here are up to the level 10. Higher + * than 10 interrupts cannot be supported. + */ +void +timer_softintr(int level) +{ + tm_req_t *intr_req; + ASSERT(level >= TM_IPL_1 && level <= TM_IPL_10); + + /* Check if we are asked to process the softcall list */ + mutex_enter(&disp_req_lock); + if (!(intr_state & TM_INTR_SET(level))) { + mutex_exit(&disp_req_lock); + return; + } + + /* Notify this software interrupt request will be executed soon */ + intr_state |= TM_INTR_START(level); + intr_state &= ~TM_INTR_SET(level); + + /* loop the link until there is no requests */ + for (intr_req = list_head(&intr_queue); intr_req != NULL; + /* Nothing */) { + + /* Check the interrupt level */ + if (intr_req->level != level) { + intr_req = list_next(&intr_queue, intr_req); + continue; + } + list_remove(&intr_queue, intr_req); + mutex_exit(&disp_req_lock); + + /* Execute the software interrupt request */ + timeout_execute(intr_req); + + mutex_enter(&disp_req_lock); + /* Restart the loop since new requests might be added */ + intr_req = list_head(&intr_queue); + } + + /* reset the interrupt state */ + intr_state &= ~TM_INTR_START(level); + mutex_exit(&disp_req_lock); +} + +/* + * void + * cyclic_timer(void) + * + * Overview + * cyclic_timer() is a function invoked periodically by the cyclic + * subsystem. + * + * The function calls timeout_invoke() with timeout requests whose + * expiration time is already reached. + * + * Arguments + * Nothing + * + * Return value + * Nothing + */ +void +cyclic_timer(void) +{ + tm_req_t *req; + timer_tw_t *tw; + hrtime_t curr_tick, curr; + + /* If the system is suspended, just return */ + if (timer_suspended) + return; + + /* Get the current time */ + timer_hrtime = ddi_timer->tick_time = curr = gethrtime(); + curr_tick = tw_tick(ddi_timer->tick_time); + +restart: + /* + * Check the timer cogs to see if there are timeout requests + * who reach the expiration time. Call timeout_invoke() to execute + * the requests, then. + */ + while (curr_tick >= ddi_timer->tick) { + tm_req_t *next; + tw = &ddi_timer->exhash[TM_HASH(ddi_timer->tick)]; + mutex_enter(&tw->lock); + for (req = list_head(&tw->req); req != NULL; req = next) { + next = list_next(&tw->req, req); + /* + * If this request is already obsolete, free + * it here. + */ + if (req->flags & TM_UTMCOMP) { + /* + * Remove this request from the timer, + * then free it. + */ + list_remove(&tw->req, req); + kmem_cache_free(req_cache, req); + } else if (curr >= req->exp_time) { + mutex_enter(&req->lock); + /* + * Check if this request is canceled, but not + * being executed now. + */ + if (req->flags & TM_CANCEL && + !(req->flags & TM_INVOKING)) { + mutex_exit(&req->lock); + continue; + } + /* + * Record how many times timeout_execute() + * must be invoked. + */ + req->cnt++; + /* + * Invoke timeout_execute() via taskq or + * software interrupt. + */ + if (req->flags & TM_INVOKING) { + /* + * If it's already invoked, + * There is nothing to do. + */ + mutex_exit(&req->lock); + } else { + req->flags |= TM_INVOKING; + mutex_exit(&req->lock); + /* + * Dispatch this timeout request. + * timeout_dispatch() chooses either + * a software interrupt or taskq thread + * based on the level. + */ + timeout_dispatch(req); + } + /* + * Periodic timeout requests must prepare for + * the next fire. + */ + transfer_req(req, tw); + } + } + mutex_exit(&tw->lock); + ddi_timer->tick++; + } + + /* + * Check the current time. If we spend some amount of time, + * double-check if some of the requests reaches the expiration + * time during the work. + */ + curr = gethrtime(); + curr_tick = tw_tick(curr); + if (curr_tick >= ddi_timer->tick) { + ddi_timer->tick -= 1; + goto restart; + } + /* Adjustment for the next rolling */ + ddi_timer->tick -= 1; +} + +/* + * void + * timer_init(void) + * + * Overview + * timer_init() allocates the internal data structures used by + * i_timeout(), i_untimeout() and the timer. + * + * Arguments + * Nothing + * + * Return value + * Nothing + * + * Caller's context + * timer_init() can be called in kernel context only. + */ +void +timer_init(void) +{ + int i; + + /* Create kmem_cache for timeout requests */ + req_cache = kmem_cache_create("timeout_request", sizeof (tm_req_t), + 0, NULL, NULL, NULL, NULL, NULL, 0); + + /* Initialize the timer which is invoked by the cyclic subsystem */ + ddi_timer = kmem_alloc(sizeof (cyc_timer_t), KM_SLEEP); + ddi_timer->res = nsec_per_tick; + ddi_timer->tick = tw_tick(gethrtime()); + ddi_timer->tick_time = 0; + + /* Initialize the timing wheel */ + bzero((char *)&ddi_timer->idhash[0], TM_HASH_SZ * sizeof (timer_tw_t)); + bzero((char *)&ddi_timer->exhash[0], TM_HASH_SZ * sizeof (timer_tw_t)); + + for (i = 0; i < TM_HASH_SZ; i++) { + list_create(&ddi_timer->idhash[i].req, sizeof (tm_req_t), + offsetof(tm_req_t, id_req)); + mutex_init(&ddi_timer->idhash[i].lock, NULL, MUTEX_ADAPTIVE, + NULL); + + list_create(&ddi_timer->exhash[i].req, sizeof (tm_req_t), + offsetof(tm_req_t, ex_req)); + mutex_init(&ddi_timer->exhash[i].lock, NULL, MUTEX_ADAPTIVE, + NULL); + } + + /* Create a taskq thread pool */ + tm_taskq = taskq_create_instance("timeout_taskq", 0, + timer_taskq_num, MAXCLSYSPRI, + timer_taskq_num, 2 * timer_taskq_num, + TASKQ_PREPOPULATE | TASKQ_CPR_SAFE); + + /* + * Initialize the taskq queue which is dedicated to this timeout + * interface/timer. + */ + list_create(&kern_queue, sizeof (tm_req_t), + offsetof(tm_req_t, disp_req)); + + /* Create a worker thread to dispatch the taskq thread */ + tm_work_thread = thread_create(NULL, 0, timeout_taskq_thread, NULL, + 0, &p0, TS_RUN, MAXCLSYSPRI); + + /* + * Initialize the software interrupt queue which is dedicated to + * this timeout interface/timer. + */ + list_create(&intr_queue, sizeof (tm_req_t), + offsetof(tm_req_t, disp_req)); + + /* + * Initialize the mutex lock used for both of kern_queue and + * intr_queue. + */ + mutex_init(&disp_req_lock, NULL, MUTEX_ADAPTIVE, NULL); + cv_init(&kern_cv, NULL, CV_DEFAULT, NULL); + + /* Register the callback handler for the system suspend/resume */ + (void) callb_add(timer_cpr_callb, 0, CB_CL_CPR_CALLOUT, "cyclicTimer"); +} + +/* + * timeout_t + * i_timeout(void (*func)(void *), void *arg, hrtime_t interval, + * int level, int flags) + * + * Overview + * i_timeout() is an internal function scheduling the passed function + * to be invoked in the interval in nanoseconds. The callback function + * keeps invoked until the request is explicitly canceled by i_untimeout(). + * This function is used for ddi_periodic_add(9F). + * + * Arguments + * + * func: the callback function + * the callback function will be invoked in kernel context if + * the level passed is the zero. Otherwise be invoked in interrupt + * context at the specified level by the argument "level". + * + * Note that It's guaranteed by the cyclic subsystem that the + * function is invoked on the only one CPU and is never executed + * simultaneously even on MP system. + * + * arg: the argument passed to the callback function + * + * interval: interval time in nanoseconds + * if the interval is the zero, the timer resolution is used. + * + * level : callback interrupt level + * If the value is 0 (the zero), the callback function is invoked + * in kernel context. If the value is more than 0 (the zero), but + * less than or equal to 10, the callback function is invoked in + * interrupt context at the specified interrupt level. + * This value must be in range of 0-10. + * + * Return value + * returns a non-zero opaque value (timeout_t) on success. + * + * Caller's context + * i_timeout() can be called in user, kernel or interrupt context. + * It cannot be called in high interrupt context. + * + * Note. This function is used by ddi_periodic_add(), which cannot + * be called in interrupt context. As a result, this function is called + * in user or kernel context only in practice. + * + */ +timeout_t +i_timeout(void (*func)(void *), void *arg, hrtime_t interval, int level) +{ + hrtime_t start_time = gethrtime(), res; + tm_req_t *req = NULL; + + /* Allocate and initialize the timeout request */ + req = kmem_cache_alloc(req_cache, KM_SLEEP); + req->handler = func; + req->arg = arg; + req->h_thread = NULL; + req->level = level; + req->flags = 0; + req->cnt = 0; + mutex_init(&req->lock, NULL, MUTEX_ADAPTIVE, NULL); + cv_init(&req->cv, NULL, CV_DEFAULT, NULL); + + /* + * The resolution must be finer than or equal to + * the requested interval. If it's not, set the resolution + * to the interval. + * Note. There is a restriction currently. Regardless of the + * clock resolution used here, 10ms is set as the timer resolution. + * Even on the 1ms resolution timer, the minimum interval is 10ms. + */ + if ((res = i_get_res()) > interval) { + uintptr_t pc = (uintptr_t)req->handler; + ulong_t off; + cmn_err(CE_WARN, + "The periodic timeout (handler=%s, interval=%lld) " + "requests a finer interval than the supported resolution. " + "It rounds up to %lld\n", kobj_getsymname(pc, &off), + interval, res); + interval = res; + } + + /* + * If the specified interval is already multiples of + * the resolution, use it as is. Otherwise, it rounds + * up to multiples of the timer resolution. + */ + req->interval = roundup(interval, i_get_res()); + + /* + * For the periodic timeout requests, the first expiration time will + * be adjusted to the timer tick edge to take advantage of the cyclic + * subsystem. In that case, the first fire is likely not an expected + * one, but the fires later can be more accurate due to this. + */ + req->exp_time = roundup(start_time + req->interval, i_get_res()); + + /* Add the request to the timer */ + return (add_req(req)); +} + +/* + * void + * i_untimeout(timeout_t req) + * + * Overview + * i_untimeout() is an internal function canceling the i_timeout() + * request previously issued. + * This function is used for ddi_periodic_delete(9F). + * + * Argument + * req: timeout_t opaque value i_timeout() returned previously. + * + * Return value + * Nothing. + * + * Caller's context + * i_untimeout() can be called in user, kernel or interrupt context. + * It cannot be called in high interrupt context. + * + * Note. This function is used by ddi_periodic_delete(), which cannot + * be called in interrupt context. As a result, this function is called + * in user or kernel context only in practice. Also i_untimeout() sends + * the cv_signal to timeout_execute(), which runs in interrupt context. + * Make sure this function will not be blocked, otherwise the deadlock + * situation can occur. See timeout_execute(). + */ +void +i_untimeout(timeout_t timeout_req) +{ + timer_tw_t *tid; + tm_req_t *req; + timeout_t id; + + /* Retrieve the id for this timeout request */ + id = (timeout_t)timeout_req; + tid = &ddi_timer->idhash[TM_HASH((uintptr_t)id)]; + + mutex_enter(&tid->lock); + for (req = list_head(&tid->req); req != NULL; + req = list_next(&tid->req, req)) { + if (req->id == id) + break; + } + if (req == NULL) { + /* There is no requests with this id after all */ + mutex_exit(&tid->lock); + return; + } + mutex_enter(&req->lock); + + /* Unregister this request first */ + list_remove(&tid->req, req); + + /* Notify that this request is canceled */ + req->flags |= TM_CANCEL; + + /* Check if the handler is invoked */ + if (req->flags & TM_INVOKING) { + /* + * If this request is not yet executed or is already finished + * then there is nothing to do but just return. Otherwise + * we'll have to wait for the callback execution being complete. + */ + if (!(req->flags & TM_EXECUTING) || req->flags & TM_COMPLETE) { + /* There is nothing to do any more */ + mutex_exit(&req->lock); + mutex_exit(&tid->lock); + return; + } + + /* + * If this is the recursive call, there is nothing + * to do any more. This is the case that i_untimeout() + * is called in the handler. + */ + if (req->h_thread == curthread) { + mutex_exit(&req->lock); + mutex_exit(&tid->lock); + return; + } + + /* + * Notify that i_untimeout() is waiting until this request + * is complete. + */ + req->flags |= TM_COMPWAIT; + mutex_exit(&tid->lock); + + /* + * Wait for this timeout request being complete before + * the return. + */ + while (!(req->flags & TM_COMPLETE)) + cv_wait(&req->cv, &req->lock); + req->flags &= ~TM_COMPWAIT; + cv_signal(&req->cv); + mutex_exit(&req->lock); + return; + } + mutex_exit(&req->lock); + mutex_exit(&tid->lock); + + /* + * Notify untimeout() is about to be finished, and this request + * can be freed. + */ + atomic_or_uint(&req->flags, TM_UTMCOMP); +} |