[BACK]Return to kern_timeout.c CVS log [TXT][DIR] Up to [local] / src / sys / kern

File: [local] / src / sys / kern / kern_timeout.c (download)

Revision 1.8, Wed Mar 28 07:33:51 2001 UTC (23 years, 2 months ago) by art
Branch: MAIN
CVS Tags: OPENBSD_2_9_BASE, OPENBSD_2_9
Changes since 1.7: +31 -7 lines

Add some DIAGNOSTIC checks that panic on some of the common mistakes.

/*	$OpenBSD: kern_timeout.c,v 1.8 2001/03/28 07:33:51 art Exp $	*/
/*
 * Copyright (c) 2000 Artur Grabowski <art@openbsd.org>
 * All rights reserved. 
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions 
 * are met: 
 *
 * 1. Redistributions of source code must retain the above copyright 
 *    notice, this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright 
 *    notice, this list of conditions and the following disclaimer in the 
 *    documentation and/or other materials provided with the distribution. 
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/lock.h>
#include <sys/timeout.h>

#ifdef DDB
#include <machine/db_machdep.h>
#include <ddb/db_interface.h>
#include <ddb/db_access.h>
#include <ddb/db_sym.h>
#include <ddb/db_output.h>
#endif

/*
 * Timeouts are kept on a queue. The to_time is the value of the global
 * variable "ticks" when the timeout should be called.
 *
 * In the future we might want to build a timer wheel to improve the speed
 * of timeout_add (right now it's linear). See "Redesigning the BSD Callout 
 * and Timer Facilities" by Adam M. Costello and Geroge Varghese.
 */

TAILQ_HEAD(,timeout) timeout_todo;	/* Queue of timeouts. */
TAILQ_HEAD(,timeout) timeout_static;	/* Static pool of timeouts. */

/*
 * All lists are locked with the same lock (which must also block out all
 * interrupts).
 */
struct simplelock _timeout_lock;

#define timeout_list_lock(s) \
	do { *(s) = splhigh(); simple_lock(&_timeout_lock); } while (0)
#define timeout_list_unlock(s) \
	do { simple_unlock(&_timeout_lock); splx(s); } while (0)

/*
 * Some of the "math" in here is a bit tricky.
 *
 * We have to beware of wrapping ints.
 * We use the fact that any element added to the list must be added with a
 * positive time. That means that any element `to' on the list cannot be
 * scheduled to timeout further in time than INT_MAX, but to->to_time can
 * be positive or negative so comparing it with anything is dangerous.
 * The only way we can use the to->to_time value in any predictable way
 * is when we caluculate how far in the future `to' will timeout - 
 *"to->to_time - ticks". The result will always be positive for future
 * timeouts and 0 or negative for due timeouts.
 */
extern int ticks;		/* XXX - move to sys/X.h */

void
timeout_init()
{
	int i;

	TAILQ_INIT(&timeout_todo);
	TAILQ_INIT(&timeout_static);
	simple_lock_init(&_timeout_lock);

	for (i = 0; i < ntimeout; i++)
		TAILQ_INSERT_HEAD(&timeout_static, &timeouts[i], to_list);
}

void
timeout_set(new, fn, arg)
	struct timeout *new;
	void (*fn)(void *);
	void *arg;
{

#ifdef DIAGNOSTIC
	struct timeout *to;
	int s;

	/*
	 * Be careful. We could be called with random non-zero memory, but
	 * on the other hand we could be called with a timeout that's
	 * already queued.
	 * XXX - this is expensive.
	 */
	timeout_list_lock(&s);
	if (new->to_flags & TIMEOUT_ONQUEUE) {
		TAILQ_FOREACH(to, &timeout_todo, to_list)
			if (to == new)
				panic("timeout_set: on queue");
	}
	timeout_list_unlock(s);
#endif

	new->to_func = fn;
	new->to_arg = arg;
	new->to_flags = TIMEOUT_INITIALIZED;
}

void
timeout_add(new, to_ticks)
	struct timeout *new;
	int to_ticks;
{
	struct timeout *to;
	int s;

	/*
	 * You are supposed to understand this function before you fiddle.
	 */

#ifdef DIAGNOSTIC
	if (!(new->to_flags & TIMEOUT_INITIALIZED))
		panic("timeout_add: not initialized");
	if (to_ticks < 0)
		panic("timeout_add: to_ticks < 0");
#endif

	timeout_list_lock(&s);

	/*
	 * First we prepare the new timeout so that we can return right
	 * after the insertion in the queue (makes the code simpler).
	 */

	/* If this timeout was already on a queue we remove it. */
	if (new->to_flags & TIMEOUT_ONQUEUE)
		TAILQ_REMOVE(&timeout_todo, new, to_list);
	else
		new->to_flags |= TIMEOUT_ONQUEUE;
	/* Initialize the time here, it won't change. */
	new->to_time = to_ticks + ticks;
	new->to_flags &= ~TIMEOUT_TRIGGERED;

	/*
	 * Walk the list of pending timeouts and find an entry which
	 * will timeout after we do, insert the new timeout there.
	 */
	TAILQ_FOREACH(to, &timeout_todo, to_list) {
		if (to->to_time - ticks > to_ticks) {
			TAILQ_INSERT_BEFORE(to, new, to_list);
			goto out;
		}
	}

	/* We can only get here if we're the last (or only) entry */
	TAILQ_INSERT_TAIL(&timeout_todo, new, to_list);
out:
	timeout_list_unlock(s);
}

void
timeout_del(to)
	struct timeout *to;
{
	int s;

	timeout_list_lock(&s);
	if (to->to_flags & TIMEOUT_ONQUEUE) {
		TAILQ_REMOVE(&timeout_todo, to, to_list);
		to->to_flags &= ~TIMEOUT_ONQUEUE;
	}
	to->to_flags &= ~TIMEOUT_TRIGGERED;
	timeout_list_unlock(s);
}

/*
 * This is called from hardclock() once every tick.
 * We return !0 if we need to schedule a softclock.
 *
 * We don't need locking in here.
 */
int
timeout_hardclock_update()
{
	struct timeout *to;

	to = TAILQ_FIRST(&timeout_todo);

	if (to == NULL)
		return 0;

	return (to->to_time - ticks <= 0);
}

void
softclock()
{
	int s;
	struct timeout *to;
	void (*fn) __P((void *));
	void *arg;

	timeout_list_lock(&s);
	while ((to = TAILQ_FIRST(&timeout_todo)) != NULL &&
	       to->to_time - ticks <= 0) {
#ifdef DIAGNOSTIC
		if (!(to->to_flags & TIMEOUT_ONQUEUE))
			panic("softclock: not onqueue");
#endif
		TAILQ_REMOVE(&timeout_todo, to, to_list);
		to->to_flags &= ~TIMEOUT_ONQUEUE;
		to->to_flags |= TIMEOUT_TRIGGERED;

		fn = to->to_func;
		arg = to->to_arg;

		if (to->to_flags & TIMEOUT_STATIC)
			TAILQ_INSERT_HEAD(&timeout_static, to, to_list);
		timeout_list_unlock(s);
		fn(arg);
		timeout_list_lock(&s);
	}
	timeout_list_unlock(s);
}

/*
 * Legacy interfaces. timeout() and untimeout()
 *
 * Kill those when everything is converted. They are slow and use the
 * static pool (which causes (potential and real) problems).
 */

void
timeout(fn, arg, to_ticks)
	void (*fn) __P((void *));
	void *arg;
	int to_ticks;
{
	struct timeout *to;
	int s;

	if (to_ticks <= 0)
		to_ticks = 1;

	/*
	 * Get a timeout struct from the static list.
	 */
	timeout_list_lock(&s);

	to = TAILQ_FIRST(&timeout_static);
	if (to == NULL)
		panic("timeout table full");
	TAILQ_REMOVE(&timeout_static, to, to_list);

	timeout_list_unlock(s);

	timeout_set(to, fn, arg);
	to->to_flags |= TIMEOUT_STATIC;
	timeout_add(to, to_ticks);
}

void
untimeout(fn, arg)
	void (*fn) __P((void *));
	void *arg;
{
	int s;
	struct timeout *to;

	timeout_list_lock(&s);
	TAILQ_FOREACH(to, &timeout_todo, to_list) {
		if (to->to_func == fn && to->to_arg == arg) {
#ifdef DIAGNOSTIC
			if ((to->to_flags & TIMEOUT_ONQUEUE) == 0)
				panic("untimeout: not TIMEOUT_ONQUEUE");
			if ((to->to_flags & TIMEOUT_STATIC) == 0)
				panic("untimeout: not static");
#endif
			TAILQ_REMOVE(&timeout_todo, to, to_list);
			to->to_flags &= ~TIMEOUT_ONQUEUE;
			/* return it to the static pool */
			TAILQ_INSERT_HEAD(&timeout_static, to, to_list);
			break;
		}
	}
	timeout_list_unlock(s);
}

#ifdef DDB
void
db_show_callout(addr, haddr, count, modif)
	db_expr_t addr; 
	int haddr; 
	db_expr_t count;
	char *modif;
{
	struct timeout *to;
	int s;
	db_expr_t offset;
	char *name;

	db_printf("ticks now: %d\n", ticks);
	db_printf("    ticks      arg  func\n");

	timeout_list_lock(&s);

	TAILQ_FOREACH(to, &timeout_todo, to_list) {
		db_find_sym_and_offset((db_addr_t)to->to_func, &name, &offset);

		name = name ? name : "?";

		db_printf("%9d %8x  %s\n", to->to_time, to->to_arg, name);
	}

	timeout_list_unlock(s);
		
}
#endif