Print this page
OS-2366 ddi_periodic_add(9F) is entirely rubbish (MORE)

*** 1,36 **** /* ! * 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 2009 Sun Microsystems, Inc. All rights reserved. - * Use is subject to license terms. - */ - /* * Copyright (c) 2013, Joyent, Inc. All rights reserved. */ #include <sys/cmn_err.h> ! #include <sys/ddi_timer.h> #include <sys/id_space.h> #include <sys/kobj.h> #include <sys/sysmacros.h> #include <sys/systm.h> #include <sys/taskq.h> --- 1,21 ---- /* ! * This file and its contents are supplied under the terms of the ! * Common Development and Distribution License ("CDDL"), version 1.0. ! * You may only use this file in accordance with the terms of version ! * 1.0 of the CDDL. * ! * A full copy of the text of the CDDL should have accompanied this ! * source. A copy of the CDDL is also available via the Internet at ! * http://www.illumos.org/license/CDDL. */ /* * Copyright (c) 2013, Joyent, Inc. All rights reserved. */ #include <sys/cmn_err.h> ! #include <sys/ddi_periodic.h> #include <sys/id_space.h> #include <sys/kobj.h> #include <sys/sysmacros.h> #include <sys/systm.h> #include <sys/taskq.h>
*** 126,138 **** PERI_IPL_5, PERI_IPL_6, PERI_IPL_7, PERI_IPL_8, PERI_IPL_9, ! PERI_IPL_10, } periodic_ipl_t; /* * This function may be called either from a soft interrupt handler * (ddi_periodic_softintr), or as a taskq worker function. */ static void --- 111,131 ---- PERI_IPL_5, PERI_IPL_6, PERI_IPL_7, PERI_IPL_8, PERI_IPL_9, ! PERI_IPL_10 } periodic_ipl_t; + static char * + periodic_handler_symbol(ddi_periodic_impl_t *dpr) + { + ulong_t off; + + return (kobj_getsymname((uintptr_t)dpr->dpr_handler, &off)); + } + /* * This function may be called either from a soft interrupt handler * (ddi_periodic_softintr), or as a taskq worker function. */ static void
*** 144,162 **** --- 137,157 ---- /* * We must be DISPATCHED, but not yet EXECUTING: */ VERIFY((dpr->dpr_flags & (DPF_DISPATCHED | DPF_EXECUTING)) == DPF_DISPATCHED); + VERIFY(dpr->dpr_thread == NULL); if (!(dpr->dpr_flags & DPF_CANCELLED)) { int level = dpr->dpr_level; uint64_t count = dpr->dpr_fire_count; /* * If we have not yet been cancelled, then * mark us executing: */ dpr->dpr_flags |= DPF_EXECUTING; + dpr->dpr_thread = curthread; mutex_exit(&dpr->dpr_lock); /* * Execute the handler, without holding locks: */
*** 165,174 **** --- 160,170 ---- (*dpr->dpr_handler)(dpr->dpr_arg); DTRACE_PROBE4(ddi__periodic__done, void *, dpr->dpr_handler, void *, dpr->dpr_arg, int, level, uint64_t, count); mutex_enter(&dpr->dpr_lock); + dpr->dpr_thread = NULL; dpr->dpr_fire_count++; } /* * We're done with this periodic for now, so release it and
*** 188,199 **** mutex_enter(&periodics_lock); /* * Pull the first scheduled periodic off the queue for this priority * level: */ ! while ((dpr = list_remove_head(&periodic_softint_queue[level - 1])) ! != NULL) { mutex_exit(&periodics_lock); /* * And execute it: */ periodic_execute(dpr); --- 184,195 ---- mutex_enter(&periodics_lock); /* * Pull the first scheduled periodic off the queue for this priority * level: */ ! while ((dpr = list_remove_head(&periodic_softint_queue[level - 1])) != ! NULL) { mutex_exit(&periodics_lock); /* * And execute it: */ periodic_execute(dpr);
*** 244,253 **** --- 240,274 ---- * queues. */ mutex_init(&periodics_lock, NULL, MUTEX_ADAPTIVE, NULL); } + void + ddi_periodic_fini(void) + { + ddi_periodic_impl_t *dpr; + /* + * Find all periodics that have not yet been unregistered and, + * on DEBUG bits, print a warning about this resource leak. + */ + mutex_enter(&periodics_lock); + while ((dpr = list_head(&periodics)) != NULL) { + #ifdef DEBUG + printf("DDI periodic handler not deleted (id=%p, handler=%s)\n", + dpr->dpr_id, periodic_handler_symbol(dpr)); + #endif + + mutex_exit(&periodics_lock); + /* + * Delete the periodic ourselves: + */ + i_untimeout(dpr->dpr_id); + mutex_enter(&periodics_lock); + } + mutex_exit(&periodics_lock); + } + static void periodic_cyclic_handler(void *arg) { extern void sir_on(int); ddi_periodic_impl_t *dpr = arg;
*** 302,313 **** --- 323,337 ---- /* * By now, we should have a periodic that is not busy, and has been * cancelled: */ VERIFY(dpr->dpr_flags == DPF_CANCELLED); + VERIFY(dpr->dpr_thread == NULL); id_free(periodic_id_space, dpr->dpr_id); + cv_destroy(dpr->dpr_cv); + mutex_destroy(dpr->dpr_lock); kmem_cache_free(periodic_cache, dpr); } static ddi_periodic_impl_t * periodic_create(void)
*** 321,330 **** --- 345,361 ---- cv_init(&dpr->dpr_cv, NULL, CV_DEFAULT, NULL); return (dpr); } + /* + * This function provides the implementation for the ddi_periodic_add(9F) + * interface. It registers a periodic handler and returns an opaque identifier + * that can be unregistered via ddi_periodic_delete(9F)/i_untimeout(). + * + * It may be called in user or kernel context, provided cpu_lock is not held. + */ timeout_t i_timeout(void (*func)(void *), void *arg, hrtime_t interval, int level) { cyc_handler_t cyh; cyc_time_t cyt;
*** 340,371 **** dpr->dpr_level = level; dpr->dpr_handler = func; dpr->dpr_arg = arg; /* ! * 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 (ddi_periodic_resolution > interval) { - uintptr_t pc = (uintptr_t)dpr->dpr_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, ddi_periodic_resolution); interval = ddi_periodic_resolution; } /* ! * If the specified interval is already multiples of ! * the resolution, use it as is. Otherwise, it rounds ! * up to multiples of the timer resolution. */ dpr->dpr_interval = roundup(interval, ddi_periodic_resolution); /* * Create the underlying cyclic: --- 371,396 ---- dpr->dpr_level = level; dpr->dpr_handler = func; dpr->dpr_arg = arg; /* ! * The minimum supported interval between firings of the periodic ! * handler is 10ms; see ddi_periodic_add(9F) for more details. If a ! * shorter interval is requested, round up. */ if (ddi_periodic_resolution > interval) { 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", periodic_handler_symbol(dpr), interval, ddi_periodic_resolution); interval = ddi_periodic_resolution; } /* ! * Ensure that the interval is an even multiple of the base resolution ! * that is at least as long as the requested interval. */ dpr->dpr_interval = roundup(interval, ddi_periodic_resolution); /* * Create the underlying cyclic:
*** 372,383 **** */ cyh.cyh_func = periodic_cyclic_handler; cyh.cyh_arg = dpr; cyh.cyh_level = CY_LOCK_LEVEL; ! cyt.cyt_when = roundup(gethrtime() + dpr->dpr_interval, ! ddi_periodic_resolution); cyt.cyt_interval = dpr->dpr_interval; mutex_enter(&cpu_lock); dpr->dpr_cyclic_id = cyclic_add(&cyh, &cyt); mutex_exit(&cpu_lock); --- 397,407 ---- */ cyh.cyh_func = periodic_cyclic_handler; cyh.cyh_arg = dpr; cyh.cyh_level = CY_LOCK_LEVEL; ! cyt.cyt_when = 0; cyt.cyt_interval = dpr->dpr_interval; mutex_enter(&cpu_lock); dpr->dpr_cyclic_id = cyclic_add(&cyh, &cyt); mutex_exit(&cpu_lock);
*** 392,422 **** return ((timeout_t)(uintptr_t)dpr->dpr_id); } /* ! * 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. */ void i_untimeout(timeout_t id) { ddi_periodic_impl_t *dpr; --- 416,431 ---- return ((timeout_t)(uintptr_t)dpr->dpr_id); } /* ! * This function provides the implementation for the ddi_periodic_delete(9F) ! * interface. It cancels a periodic handler previously registered through ! * ddi_periodic_add(9F)/i_timeout(). * ! * It may be called in user or kernel context, provided cpu_lock is not held. ! * It may NOT be called from within a periodic handler. */ void i_untimeout(timeout_t id) { ddi_periodic_impl_t *dpr;
*** 446,455 **** --- 455,472 ---- /* * We should be the only one trying to cancel this periodic: */ VERIFY(!(dpr->dpr_flags & DPF_CANCELLED)); /* + * Removing a periodic from within its own handler function will + * cause a deadlock, so panic explicitly. + */ + if (dpr->dpr_thread == curthread) { + panic("ddi_periodic_delete(%p) called from its own handler\n", + dpr->dpr_id); + } + /* * Mark the periodic as cancelled: */ dpr->dpr_flags |= DPF_CANCELLED; mutex_exit(&dpr->dpr_lock);