#ident "$Id: timer.c,v 1.1 1995/03/16 16:07:04 garyp Exp $"
/*****************************************************************************
 * Copyright (c) 1988-95 Digi International Inc., All Rights Reserved
 * 
 * This  software  contains  proprietary  and  confidential   information   of
 * Digi International  Inc.   By  accepting  transfer  of this copy, Recipient
 * agrees to retain this software in  confidence,  to  prevent  disclosure  to
 * others,  and  to make no  use of this software other than that for which it
 * was delivered.  This is an unpublished copyright work  Digi   International
 * Inc.    Except   as   permitted   by  federal  law,  17 USC 117, copying is
 * strictly prohibited.
 * 
 * Digi  International  Inc.  CONFIDENTIAL  -   (Digi    International    Inc.
 * Confidential  when   combined   with   the   aggregated  modules  for  this
 * product) OBJECT CODE ONLY SOURCE MATERIALS
 * 
 * (C) COPYRIGHT Digi International Inc. 1995
 * 
 * All Rights Reserved
 * 
 * US   Government   Users   Restricted   Rights   -   Use,   duplication   or
 * disclosure  restricted by GSA ADP Schedule Contract with Digi International
 * Inc.
 * 
 *****************************************************************************/

/******************************************************************
 *
 *  Timing Module
 *
 *  Copyright 1990  Spider Systems Limited
 *
 *  TIMER.C
 *
 *    Fast multiple time-out module.
 *
 ******************************************************************/

/*
 *	 /projects/common/PBRAIN/SCCS/pbrainF/dev/embed/common/71/s.timer.c
 *	@(#)timer.c	1.1
 *
 *	Last delta created	12:49:01 6/25/92
 *	This file extracted	12:55:13 10/29/92
 *
 *	Modifications:
 *
 *	GSA	18 Sep 91	New file created
 *
 */




#define STATIC
#include "types.h"
#include "timer.h"

#ifdef DEVMT
#define NTICKS 1	/* Test Bed */
int expired;
#else
#define NTICKS 5	/* Barretta or ENP */
#endif


/* Length of tick ring */
#define TCYCLE  100

STATIC timer_t *tring[TCYCLE];
STATIC int curtinx, ticking;

/*
 * 'Init_timers' initialises the set of associated timers.
 * The timers concerned must either never have been used, or
 *    be guaranteed not to be attached to the main timing ring.
 * Function 'expfunc(exparg)' will be called whenever any timer
 *    expires and is moved on to the 'expired' list.
 */
void init_timers(theadp, tmtab, n, expfunc, exparg)
thead_t *theadp;
timer_t  tmtab[];
int      n;
void   (*expfunc)();
caddr_t  exparg;
{
    int i, step_time(caddr_t);

    /* Set up timer header */
    theadp->th_expired = (timer_t *)0;
    theadp->th_expfunc = expfunc;
    theadp->th_exparg = exparg;

    /* Set up individual timers */
    for (i = 0; i<n; i++)
    {
	register timer_t *tp = &tmtab[i];
	int offset = (int)((char *)tp - (char *)theadp);

	if (offset>255)
	    panic("Net Timer offset > 255\n");

	tp->tm_id = (unchar) i ;
	tp->tm_offset = (unchar) offset ;
    }

    if (!ticking)
    {   /* Start clock ticking on first entry */
	timeout(step_time, (caddr_t)0, NTICKS);
	ticking = 1;
    }
}

/*
 * 'Stop_timers' stops the entire set of associated timers.
 */
void stop_timers(theadp, tmtab, n)
thead_t *theadp;
timer_t  tmtab[];
int      n;
{
    int i;

    /* Stop individual timers */
    for (i = 0; i<n; i++)
	set_timer(&tmtab[i], 0);
}

/*
 * Set_timer stops the specified timer (time == 0) or (re)activates it
 *	to expire at the given time (time > 0, in ticks).
 *
 * If the timer was already running, or its expiry has not yet processed,
 *	it will be stopped or re-started.
 */
void set_timer(tp, time)
register timer_t *tp;
unsigned short time;
{

    if (tp->tm_back)
    {   /* Timer is attached to a ring slot or expired list: detach it */
	register timer_t *nextp;

	if (nextp = tp->tm_next)
	    nextp->tm_back = tp->tm_back;
	*tp->tm_back = nextp;
    }

    if (time)
	activate_timer(tp, time);
    else
    {   /* Leave timer tidily detached */
	tp->tm_next = (timer_t *)0;
	tp->tm_back = (timer_t **)0;
    }

}

/*
 * 'Run_timer' starts the specified timer to expire at the given time
 *  (time > 0, in ticks).
 * If the timer was already running, or its expiry has not yet processed,
 *  its status will not be changed.
 */
void run_timer(tp, time)
register timer_t *tp;
unsigned short time;
{

    if (time && !tp->tm_back)
	activate_timer(tp, time);

}

/*
 * 'Step_time' is called at regular intervals to record the passage of time.
 *
 * On each call the current time variable is stepped, and any
 *  expired timers moved on to their own 'expired' lists.
 *
 * The function 'expfunc' specified to 'init_timers' is called to
 *  schedule processing for any expired timers.
 *
 * 'Step_time' is normally called indirectly from the clock ISR
 *  by way of the 'timeout' function.
 *
 */
int step_time(caddr_t Dummy)
{   /* Obeyed within the clock ISR: steps time by one tick */
    register timer_t *tp;

    /* Step curtinx round ring */
    if (++curtinx >= TCYCLE)
	curtinx = 0;

    if (tp = tring[curtinx])
    {   /* There are timers to be dealt with */
	timer_t *nextp;

	/* We clear the ring entry now as it may be used below */
	tring[curtinx] = (timer_t *)0;

	do
	{   /* Deal with the chain of timers in order */
	    nextp = tp->tm_next;
	    tp->tm_back = (timer_t **)0;

	    if (tp->tm_left)
	    {   /* Not really expired, so restart for time left */
		set_timer(tp, tp->tm_left);
	    }
	    else
	    {   /* Timer has really expired */
		register thead_t *theadp =
		    (thead_t *)((char *)tp - tp->tm_offset);

		/* Push expired timer on to its own 'expired' chain */
		attach_timer(tp, &theadp->th_expired);

		/* Tell owner about expiry */
		(*theadp->th_expfunc)(theadp->th_exparg);

#ifdef DEVMT
		/* Count expiries */
		expired++;
#endif
	    }

	} while (tp = nextp);
    }

    /* Request next step */
    timeout(step_time, (caddr_t)0, NTICKS);

	return 0 ;
}

/*
 * 'Attach_timer' is an inner function which is used to attach a timer cell
 * either to one of the slots on the main timing ring, or to its expired
 * list.
 */
void attach_timer(tp, tlistp)
register timer_t *tp;
register timer_t **tlistp;
{
    if (*tlistp)
	(*tlistp)->tm_back = &tp->tm_next;
    tp->tm_next = *tlistp;
    tp->tm_back = tlistp;
    *tlistp = tp;
}


/*
 * 'Activate_timer' is an inner procedure which chooses the
 *  correct time ring entry for expiry of the given timer
 *  (which must be unattached) at the specified time,
 *  then attaches the cell to this entry.
 * If the time to expiry is more than the length of the
 *  time ring, the cell is attached to the "time=0" entry
 *  which implies one complete cycle of the ring; the 'time_left'
 *  field is then set to the remaining time.
 * After the ring cycle is complete, such a cell will be processed
 *  at the same time as those cells which have expired, but will
 *  be restarted for the remaining time instead of being
 *  transferred to the expired list.
 * This "full-cycle" action will be repeated until the time
 *  left is within normal range of the time ring, at which point
 *  the cell will be processed for the remaining time as a normal
 *  expiring cell.
 */
void activate_timer(tp, time)
register timer_t *tp;
register ushort time;
{
    if (time<TCYCLE)
    {   /* In range of time ring */
	tp->tm_left = 0;
    }
    else
    {   /* Not in range: set up full cycle */
	tp->tm_left = time - TCYCLE;
	time = 0;
    }

    if ((time += (ushort) curtinx) >= TCYCLE)
	time -= TCYCLE;

    attach_timer(tp, &tring[time]);
}
