[BACK]Return to timer.c CVS log [TXT][DIR] Up to [local] / prex-old / sys / kern

File: [local] / prex-old / sys / kern / timer.c (download)

Revision 1.1, Tue Jun 3 09:38:46 2008 UTC (15 years, 10 months ago) by nbrk
Branch point for: MAIN

Initial revision

/*-
 * Copyright (c) 2005-2007, Kohsuke Ohtani
 * 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. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
 */

/*
 * timer.c - kernel timer services.
 */

#include <kernel.h>
#include <task.h>
#include <event.h>
#include <timer.h>
#include <irq.h>
#include <sched.h>
#include <thread.h>
#include <kmem.h>
#include <exception.h>

static volatile u_long lbolt;		/* ticks elapsed since bootup */
static struct event timer_event;	/* event to wakeup timer thread */
static struct event delay_event;	/* event for the delay thread */
static struct list timer_list;		/* list of active timers */
static struct list expire_list;		/* list of expired timers */
static void (*tick_hook)(int);		/* hook routine for timer tick */

/*
 * Macro to get a timer element for the next timer expiration.
 */
#define timer_next() \
	(list_entry(list_first(&timer_list), struct timer, link))

/*
 * Helper routine to get remaining ticks for the expiration time.
 * Return 0 if time already passed.
 */
static u_long
time_remain(u_long expire)
{

	if (time_before(lbolt, expire))
		return expire - lbolt;
	return 0;
}

/*
 * Add a timer element to the timer list, in the proper place.
 * Requires interrupts to be disabled by the caller.
 */
static void
timer_add(struct timer *tmr, u_long ticks)
{
	list_t head, n;
	struct timer *t;

	tmr->expire = lbolt + ticks;

	/*
	 * We sort the timer list by time. So, we can quickly
	 * get the next expiration time from the head element
	 * of the timer list.
	 */
	head = &timer_list;
	for (n = list_first(head); n != head; n = list_next(n)) {
		t = list_entry(n, struct timer, link);
		if (time_before(tmr->expire, t->expire))
			break;
	}
	list_insert(list_prev(n), &tmr->link);
}

/*
 * Execute a function after a specified length of time.
 * A device driver can call timer_callout() or timer_stop()
 * from ISR at interrupt level.
 */
void
timer_callout(struct timer *tmr, void (*func)(void *),
	      void *arg, u_long msec)
{
	u_long ticks;

	ASSERT(tmr);

	ticks = msec_to_tick(msec);
	if (ticks == 0)
		ticks = 1;

	irq_lock();

	/*
	 * Stop timer if running
	 */
	if (tmr->active)
		list_remove(&tmr->link);
	/*
	 * Program timer
	 */
	tmr->func = func;
	tmr->arg = arg;
	tmr->active = 1;
	tmr->interval = 0;
	timer_add(tmr, ticks);

	irq_unlock();
}

void
timer_stop(struct timer *tmr)
{
	ASSERT(tmr);

	irq_lock();
	if (tmr->active) {
		list_remove(&tmr->link);
		tmr->active = 0;
	}
	irq_unlock();
}

/*
 * timer_delay - delay thread execution.
 * The caller thread is blocked for the specified time.
 * Returns 0 on success, or the remaining time (msec) on failure.
 * This service is not available at interrupt level.
 */
u_long
timer_delay(u_long msec)
{
	struct timer *tmr;
	u_long remain = 0;
	int rc;

	ASSERT(irq_level == 0);

	rc = sched_tsleep(&delay_event, msec);
	if (rc != SLP_TIMEOUT) {
		tmr = &cur_thread->timeout;
		remain = tick_to_msec(time_remain(tmr->expire));
	}
	return remain;
}

/*
 * timer_sleep - sleep system call.
 * @delay:  delay time in milli-second
 * @remain: remaining time returned if the sleep is interrupted.
 *
 * Stop execution of current thread for the indicated amount of time.
 * Returns EINTR if sleep is canceled by some reasons.
 */
int
timer_sleep(u_long delay, u_long *remain)
{
	u_long msec;
	int err = 0;

	msec = timer_delay(delay);

	if (remain != NULL)
		err = umem_copyout(&msec, remain, sizeof(u_long));
	if (err == 0 && msec > 0)
		err = EINTR;
	return err;
}

/*
 * Alarm timer expired:
 * Send an alarm exception to the target task.
 */
static void
alarm_expire(void *task)
{

	exception_post((task_t)task, SIGALRM);
}

/*
 * timer_alarm - schedule an alarm exception.
 * @delay:  delay time in milli-second. If delay is 0, stop timer.
 * @remain: remaining time of the previous alarm request.
 *
 * SIGALRM is sent to the caller task when specified delay time
 * is passed.
 */
int
timer_alarm(u_long delay, u_long *remain)
{
	struct timer *tmr;
	u_long msec = 0;
	int err = 0;

	irq_lock();
	tmr = &cur_task()->alarm;
	if (tmr->active) {
		/*
		 * Save the remaining time before we update
		 * the timer value.
		 */
		msec = tick_to_msec(time_remain(tmr->expire));
	}
	if (delay == 0) {
		timer_stop(tmr);
	} else {
		timer_callout(tmr, alarm_expire, cur_task(), delay);
	}
	irq_unlock();

	if (remain != NULL)
		err = umem_copyout(&msec, remain, sizeof(u_long));
	return err;
}

/*
 * timer_periodic - set periodic timer for the specified thread.
 * @th:     thread to set timer.
 * @start:  first time to wakeup. set 0 to stop timer.
 * @period: time interval to wakeup. This must be non-zero.
 *          (The unit of start/period is milli-seconds.)
 *
 * The periodic thread will wait the timer period by calling
 * timer_waitperiod().
 */
int
timer_periodic(thread_t th, u_long start, u_long period)
{
	struct timer *tmr;
	int err = 0;

	ASSERT(irq_level == 0);

	if (start != 0 && period == 0)
		return EINVAL;

	sched_lock();
	if (!thread_valid(th)) {
		sched_unlock();
		return ESRCH;
	}
	if (th->task != cur_task()) {
		sched_unlock();
		return EPERM;
	}
	tmr = th->periodic;
	if (start == 0) {
		if (tmr != NULL && tmr->active)
			timer_stop(tmr);
		else
			err = EINVAL;
	} else {
		if (tmr == NULL) {
			/*
			 * Allocate a timer element at first call. We
			 * don't put this data in the thread structure
			 * because only a few threads will use the
			 * periodic timer function.
			 */
			tmr = kmem_alloc(sizeof(struct timer));
			if (tmr == NULL) {
				sched_unlock();
				return ENOMEM;
			}
			event_init(&tmr->event, "periodic");
			tmr->active = 1;
			th->periodic = tmr;
		}
		/*
		 * Program an interval timer.
		 */
		irq_lock();
		tmr->interval = msec_to_tick(period);
		if (tmr->interval == 0)
			tmr->interval = 1;
		timer_add(tmr, msec_to_tick(start));
		irq_unlock();
	}
	sched_unlock();
	return err;
}


/*
 * timer_waitperiod - wait next period of the periodic timer.
 *
 * Since this routine can exit by any exceptions, the control may
 * return at non-period time. So, the caller must retry immediately
 * if the error status is EINTR. This will be automatically done
 * by the library stub routine.
 */
int
timer_waitperiod(void)
{
	struct timer *tmr;
	int rc, err = 0;

	ASSERT(irq_level == 0);

	if ((tmr = cur_thread->periodic) == NULL)
		return EINVAL;

	if (time_before(lbolt, tmr->expire)) {
		/*
		 * Sleep until timer_tick() routine wakes us up.
		 */
		rc = sched_sleep(&tmr->event);
		if (rc != SLP_SUCCESS)
			err = EINTR;
	}
	return err;
}

/*
 * Clean up our resource for the thread termination.
 */
void
timer_cleanup(thread_t th)
{
	if (th->periodic != NULL) {
		timer_stop(th->periodic);
		kmem_free(th->periodic);
	}
}

int
timer_hook(void (*func)(int))
{

	if (tick_hook != NULL)
		return -1;
	irq_lock();
	tick_hook = func;
	irq_unlock();
	return 0;
}

/*
 * Timer thread.
 *
 * Handle all expired timers. Each callout routine is called
 * with scheduler locked and interrupts enabled.
 */
static void
timer_thread(u_long unused)
{
	struct timer *tmr;

	for (;;) {

		/* Wait until next timer expiration. */
		sched_sleep(&timer_event);

		while (!list_empty(&expire_list)) {
			/*
			 * Callout
			 */
			tmr = list_entry(list_first(&expire_list),
					 struct timer, link);
			list_remove(&tmr->link);
			tmr->active = 0;
			sched_lock();
			interrupt_enable();
			(*tmr->func)(tmr->arg);

			/*
			 * Unlock scheduler here in order to give
			 * chance to higher priority threads to run.
			 */
			sched_unlock();
			interrupt_disable();
		}
	}
	/* NOTREACHED */
}

/*
 * Timer tick handler
 *
 * timer_tick() is called straight from the real time clock interrupt.
 * All interrupts are still disabled at the entry of this routine.
 */
void
timer_tick(void)
{
	struct timer *tmr;
	u_long ticks;
	int idle, wakeup = 0;

	/* Bump time in ticks. */
	lbolt++;

	/*
	 * Handle all of the timer elements that have expired.
	 */
	while (!list_empty(&timer_list) &&
	       time_after_eq(lbolt, timer_next()->expire)) {
		/*
		 * Remove an expired timer from the list and wakup
		 * the appropriate thread. If it is periodic timer,
		 * reprogram the next expiration time. Otherwize,
		 * it is moved to the expired list.
		 */
		tmr = timer_next();
		list_remove(&tmr->link);
		if (tmr->interval != 0) {
			/*
			 * Periodic timer
			 */
			ticks = time_remain(tmr->expire + tmr->interval);
			if (ticks == 0)
				ticks = 1;
			timer_add(tmr, ticks);
			sched_wakeup(&tmr->event);
		} else {
			/*
			 * One-shot timer
			 */
			list_insert(&expire_list, &tmr->link);
			wakeup = 1;
		}
	}
	if (wakeup)
		sched_wakeup(&timer_event);

	sched_tick();

	/*
	 * Call a hook routine for power management or profiling work.
	 */
	if (tick_hook != NULL) {
		idle = (cur_thread->prio == PRIO_IDLE) ? 1 : 0;
		tick_hook(idle);
	}
}

u_long
timer_count(void)
{

	return lbolt;
}

void
timer_info(struct info_timer *info)
{

	info->hz = HZ;
}

#if defined(DEBUG) && defined(CONFIG_KDUMP)
void
timer_dump(void)
{
	struct timer *tmr;
	list_t head, n;

	printk("Timer dump:\n");
	printk("lbolt=%d\n", lbolt);

	head = &timer_list;
	for (n = list_first(head); n != head; n = list_next(n)) {
		tmr = list_entry(n, struct timer, link);
		printk("timer=%x func=%x arg=%x expire=%d\n", (int)tmr,
		       (int)tmr->func, (int)tmr->arg, (int)tmr->expire);
	}
}
#endif

/*
 * Initialize the timer facility, called at system startup time.
 */
void
timer_init(void)
{

	list_init(&timer_list);
	list_init(&expire_list);
	event_init(&timer_event, "timer");
	event_init(&delay_event, "delay");

	/*
	 * Start timer thread
	 */
	if (kernel_thread(PRIO_TIMER, timer_thread, 0) == NULL)
		panic("timer_init");
}