/*
****************************************************************************
*        Copyright IBM Corporation 1988, 1989 - All Rights Reserved        *
*                                                                          *
* Permission to use, copy, modify, and distribute this software and its    *
* documentation for any purpose and without fee is hereby granted,         *
* provided that the above copyright notice appear in all copies and        *
* that both that copyright notice and this permission notice appear in     *
* supporting documentation, and that the name of IBM not be used in        *
* advertising or publicity pertaining to distribution of the software      *
* without specific, written prior permission.                              *
*                                                                          *
* IBM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL *
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL IBM *
* BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY      *
* DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER  *
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING   *
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.    *
****************************************************************************
*/

#include "rx_locl.h"

RCSID("$Id: rx_event.c,v 1.3 1998/02/22 19:43:59 joda Exp $");

/*
 * All event processing is relative to the apparent current time
 * given by clock_GetTime
 */

/* This should be static, but event_test wants to look at the free list... */
struct rx_queue rxevent_free;	       /* It's somewhat bogus to use a
				        * doubly-linked queue for the free
				        * list */
static struct rx_queue rxevent_queue;  /* The list of waiting events */
static int rxevent_allocUnit = 10;     /* Allocation unit (number of event
				        * records to allocate at one time) */
int rxevent_nFree;		       /* Number of free event records */
int rxevent_nPosted;		       /* Current number of posted events */
static void (*rxevent_ScheduledEarlierEvent) ();/* Proc to call when an event
						 * is scheduled that is
						 * earlier than all other
						 * events */
static struct xfreelist {
    struct xfreelist *next;
} *xfreemallocs = 0, *xsp = 0;

#ifdef RXDEBUG
FILE *rxevent_debugFile;	       /* Set to an stdio descriptor for
				        * event logging to that file */
#endif

/* Pass in the number of events to allocate at a time */
static int initialized = 0;

void
rxevent_Init(int nEvents, void (*scheduler) ())
{
    if (initialized)
	return;
    clock_Init();
    if (nEvents)
	rxevent_allocUnit = nEvents;
    queue_Init(&rxevent_free);
    queue_Init(&rxevent_queue);
    rxevent_nFree = rxevent_nPosted = 0;
    rxevent_ScheduledEarlierEvent = scheduler;
    initialized = 1;
}

/* Add the indicated event (function, arg) at the specified clock time */
struct rxevent *
rxevent_Post(struct clock * when, void (*func)(), void *arg, void *arg1)
/* when - When event should happen, in clock (clock.h) units */
{
    register struct rxevent *ev, *qe, *qpr;

#ifdef RXDEBUG
    if (Log) {
	struct clock now;

	clock_GetTime(&now);
	fprintf(Log, "%ld.%ld: rxevent_Post(%ld.%ld, %p, %p)\n",
		now.sec, now.usec, when->sec, when->usec, func, arg);
    }
#endif
#if defined(AFS_SGIMP_ENV)
    ASSERT(osi_rxislocked());
#endif

    /*
     * If we're short on free event entries, create a block of new ones and
     * add them to the free queue
     */
    if (queue_IsEmpty(&rxevent_free)) {
	register int i;

#if	defined(AFS_AIX32_ENV) && defined(KERNEL)
	ev = (struct rxevent *) rxi_Alloc(sizeof(struct rxevent));
	queue_Append(&rxevent_free, &ev[0]), rxevent_nFree++;
#else
	ev = (struct rxevent *) osi_Alloc(sizeof(struct rxevent) *
					  rxevent_allocUnit);
	xsp = xfreemallocs;
	xfreemallocs = (struct xfreelist *) ev;
	xfreemallocs->next = xsp;
	for (i = 0; i < rxevent_allocUnit; i++)
	    queue_Append(&rxevent_free, &ev[i]), rxevent_nFree++;
#endif
    }
    /* Grab and initialize a new rxevent structure */
    ev = queue_First(&rxevent_free, rxevent);
    queue_Remove(ev);
    rxevent_nFree--;

    /* Record user defined event state */
    ev->eventTime = *when;
    ev->func = func;
    ev->arg = arg;
    ev->arg1 = arg1;
    rxevent_nPosted += 1;	       /* Rather than ++, to shut high-C up
				        * regarding never-set variables */

    /*
     * Locate a slot for the new entry.  The queue is ordered by time, and we
     * assume that a new entry is likely to be greater than a majority of the
     * entries already on the queue (unless there's very few entries on the
     * queue), so we scan it backwards
     */
    for (queue_ScanBackwards(&rxevent_queue, qe, qpr, rxevent)) {
	if (clock_Ge(when, &qe->eventTime)) {
	    queue_InsertAfter(qe, ev);
	    return ev;
	}
    }
    /* The event is to expire earlier than any existing events */
    queue_Prepend(&rxevent_queue, ev);
    if (rxevent_ScheduledEarlierEvent)
	(*rxevent_ScheduledEarlierEvent) ();	/* Notify our external
						 * scheduler */
    return ev;
}

/*
 * Cancel an event by moving it from the event queue to the free list.
 * Warning, the event must be on the event queue! If not, this should core
 * dump (reference through 0).  This routine should be called using the macro
 * event_Cancel, which checks for a null event and also nulls the caller's
 * event pointer after cancelling the event.
 */
void
rxevent_Cancel_1(register struct rxevent * ev)
{
#ifdef RXDEBUG
    if (Log) {
	struct clock now;

	clock_GetTime(&now);
	fprintf(Log, "%ld.%ld: rxevent_Cancel_1(%ld.%ld, %p, %p)\n",
		now.sec, now.usec, ev->eventTime.sec, ev->eventTime.usec,
		ev->func, ev->arg);
    }
#endif
    /*
     * Append it to the free list (rather than prepending) to keep
     * the free list hot so nothing pages out
     */
#if defined(AFS_SGIMP_ENV)
    ASSERT(osi_rxislocked());
#endif
    queue_MoveAppend(&rxevent_free, ev);
    rxevent_nPosted--;
    rxevent_nFree++;
}

/*
 * Process all events that have expired relative to the current clock time
 * (which is not re-evaluated unless clock_NewTime has been called).
 * The relative time to the next event is returned in the output parameter
 * next and the function returns 1.  If there are is no next event,
 * the function returns 0.
 */
int
rxevent_RaiseEvents(struct clock * next)
{
    register struct rxevent *qe;
    struct clock now;

#ifdef RXDEBUG
    if (Log)
	fprintf(Log, "rxevent_RaiseEvents(%ld.%ld)\n", now.sec, now.usec);
#endif

    /*
     * Events are sorted by time, so only scan until an event is found that
     * has not yet timed out
     */
    while (queue_IsNotEmpty(&rxevent_queue)) {
	clock_GetTime(&now);
	qe = queue_First(&rxevent_queue, rxevent);
	if (clock_Lt(&now, &qe->eventTime)) {
	    *next = qe->eventTime;
	    clock_Sub(next, &now);
	    return 1;
	}
	queue_Remove(qe);
	rxevent_nPosted--;
	qe->func(qe, qe->arg, qe->arg1);
	queue_Append(&rxevent_free, qe);
	rxevent_nFree++;
    }
    return 0;
}

void
shutdown_rxevent()
{
    struct xfreelist *xp, *nxp;

    initialized = 0;
#if	defined(AFS_AIX32_ENV) && defined(KERNEL)
    /* Everything is freed in afs_osinet.c */
#else
    while ((xp = xfreemallocs) != NULL) {
	nxp = xp->next;
	osi_Free((char *) xp, sizeof(struct rxevent) * rxevent_allocUnit);
    }
#endif
}
