/*
 * event.c
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1996,1993,1991,1990  Arizona Board of Regents
 *
 * $Revision: 1.6 $
 * $Date: 1998/10/22 18:23:12 $
 */

#include <thread.h>
#include <signal.h>
#include <errno.h>
#include <time.h>

#include "xk_debug.h"
#include "upi.h"
#include "platform.h"
#include "x_util.h"
#include "event.h"
#include "event_i.h"
#include "xk_assert.h"
#include "x_libc.h"

typedef int (*intFunc)();

#define THIS_IS_THE_HEADER ((EvFunc)-42)

extern int insque(void *, void *);
extern int remque(void *);
/* extern int sigwait(sigset_t *set); */

#if (__STDC__ == 1)
/* extern int thr_sigsetmask(int, sigset_t *, sigset_t *); */
#endif

/*
 * I actually define this in solaris/include/config.h, since it appears
 * in process.c as well, and I want to ba able to change it while doing
 * some tests on the effect of binding threads, etc. [Gordon]
 */
#define THR_CREATION_FLAGS (THR_DETACHED | THR_BOUND | THR_NEW_LWP | THR_SUSPENDED)

/*
 * BIG_N should be a function of the estimated number of scheduled events,
 * such that queues don't get too long, but with as little overhead as
 * possible.  128 is probably good for up to some 500-1000 events. [mats]
 */

#define BIG_N 128

static struct Event evHead[BIG_N];
static Event        evSelf(void);

/*
 * These are appropriate for in-kernel threads for the Decstations.
 * ... what's an appropriate limit for cthread stacks?  For other platforms?
 */
int eventStackWarnLimit = 2000;
int eventStackErrorLimit = 13750;

#define THREAD_T       thread_t
#define THREAD_SELF()  thr_self()
#define EVENT_LOCK()   mutex_lock(&event_mutex)
#define EVENT_UNLOCK() mutex_unlock(&event_mutex)

mutex_t    event_mutex;
static int tickmod;
static int event_interval;

#ifdef XK_THREAD_TRACE

Map        localEventMap;
static Map threadEventMap;

#endif

void evClock(void *unused);		/* Forward */

/*
 * evInit -- initialize the timer queues and start the clock thread
 */
void
evInit(int interval)
{
    int i;
    thread_t child;
    sigset_t set;

    /*
     * this must be called from the main thread, so that
     * all other threads will also block SIGALRM
     */
    sigemptyset(&set);
    sigaddset(&set, SIGALRM);
    thr_sigsetmask(SIG_BLOCK, &set, NULL);

    event_interval = interval;
    xTrace0(event, TR_GROSS_EVENTS, "evInit enter");

#ifdef XK_THREAD_TRACE
    localEventMap = mapCreate(BIG_N, sizeof(Event));
    threadEventMap = mapCreate(BIG_N, sizeof(THREAD_T));
#endif
    for (i = 0; i < BIG_N; i++) {
        evHead[i].next = &evHead[i];
        evHead[i].prev = &evHead[i];
        evHead[i].func = THIS_IS_THE_HEADER;
    }
    tickmod = 0;

    /* initialize timers and semaphores */

    mutex_init(&event_mutex, USYNC_THREAD, 0);

    /*
     * Since evClock does not use any xkernel routines, this should be
     * allowed to remain a cthread, in spite of the sledgehammer concurrency
     * control otherwise enforced in the xkernel.
     */

    xTrace0(event, TR_EVENTS, "evInit starting evClock thread");
    thr_create(0, 0, (void *(*)(void *))&evClock, 0,
	       (THR_CREATION_FLAGS) & (~THR_SUSPENDED), &child);

    xTrace0(event, TR_EVENTS, "evInit exit");
}

/*
 * insert an event into an event bucket, ordered by increasing timeout value
 */
static void
e_insque(int q, Event e)
{
    Event a;

    xTrace0(event, TR_FULL_TRACE, "e_insque enter");
    EVENT_LOCK();
    for (a = evHead[q].next; a != &evHead[q]; a = a->next) {
        if (a->deltat < e->deltat) {
            /* E goes after a */
            e->deltat -= a->deltat;
            continue;
        }
	else {
            /* E goes just before a */
            a->deltat -= e->deltat;
            insque(e, a->prev);
            EVENT_UNLOCK();
            return;
        }
    }
    /* E goes at the end */
    insque(e, evHead[q].prev);
    EVENT_UNLOCK();
}

/*
 * remove an event from an event bucket
 */
static void
e_remque(Event e)
{
    xTrace0(event, TR_FULL_TRACE, "e_remque enter");
    xAssert(e);
    if (e->next->func != THIS_IS_THE_HEADER)
        e->next->deltat += e->deltat;
    remque(e);
}

#ifdef XK_THREAD_TRACE

/*
 * It is safe to call unbindEvent multiple times with the same event.
 * Only the first call will have any effect.
 */
#define unbindEvent(_e)                               \
do {                                                  \
    xAssert(_e);                                      \
    if ((_e)->bind) {                                 \
        mapRemoveBinding(threadEventMap, (_e)->bind); \
        (_e)->bind = 0;                               \
    }                                                 \
} while(0)

#define freeEvent(_e)                   \
do {                                    \
    xAssert(_e);                        \
    unbindEvent(_e);                    \
    mapRemoveKey(localEventMap, &(_e)); \
    xFree((char *)(_e));                \
} while (0)

#else	!  XK_THREAD_TRACE

#define unbindEvent(_e) do { } while (0)
#define freeEvent(_e)   xFree((char *)(_e))

#endif XK_THREAD_TRACE

static Event
evSelf(void)
{
#ifdef XK_THREAD_TRACE
    Event    ev;
    THREAD_T thSelf;

    thSelf = THREAD_SELF();
    if (mapResolve(threadEventMap, &thSelf, (void **)&ev) == XK_SUCCESS)
        return ev;
    else {
        xTrace0(event, TR_EVENTS, "evSelf ... couldn't find event structure");
        return 0;
    }
#else
    return 0;
#endif
}

void
evMarkBlocked(Semaphore *s)
{
#ifdef XK_THREAD_TRACE
    Event ev;

    ev = evSelf();
    if (ev) {
        ev->state = E_BLOCKED;
        xGetTime(&ev->stopTime);
    }
#endif
}

void
evMarkRunning(void)
{
#ifdef XK_THREAD_TRACE
    Event ev;

    ev = evSelf();
    if (ev) {
        ev->state = E_RUNNING;
    }
#endif
}

/*
 * all scheduled events start here
 *   CreateProcess will invoke stub, and stub will apply the function
 *   pointer to the event object and the argument
 *
 */
static void
stub(Event e)
{
    xTrace0(event, TR_FULL_TRACE, "event stub enter");
    xAssert(e->state == E_SCHEDULED);
#ifdef XK_THREAD_TRACE
    xAssert(mapResolve(localEventMap, &e, 0) == XK_SUCCESS);
#endif

    if (e->flags & E_CANCELLED_F) {
        xTrace1(event, TR_MORE_EVENTS,
	        "event stub not starting cancelled event %x", e);
        freeEvent(e);
        return;
    }
#ifdef XK_THREAD_TRACE
    {
        THREAD_T self;

        self = THREAD_SELF();
        e->bind = mapBind(threadEventMap, &self, e);
        e->earlyStack = &self;
        xTrace1(event, TR_MORE_EVENTS, "initial event stack == %x",
                (int)e->earlyStack);
        xAssert(e->bind != ERR_BIND);
        xGetTime(&e->startTime);
    }
#endif
    e->state = E_RUNNING;
    xTrace1(event, TR_FULL_TRACE, "starting event at addr %x", e->func);
    e->func(e, e->arg);
    e->state = E_FINISHED;
    EVENT_LOCK();
    if (e->flags & E_DETACHED_F)
        freeEvent(e);
    else {
        unbindEvent(e);
#ifdef XK_THREAD_TRACE
        xGetTime(&e->stopTime);
#endif
    }
    EVENT_UNLOCK();
}

/*
 * prepare an event for later scheduling
 */
Event
evSchedule(EvFunc func, VOID *arg, unsigned etime)  /* time in usec */
{
    Event e;
    u_int delt;
    bool ret;

    xTrace0(event, TR_FULL_TRACE, "evSchedule enter");
    xTrace3(event, TR_GROSS_EVENTS, "evSchedule: f = %x, arg = %x, etime = %d",
	    func, arg, etime);
    e = (Event)xMalloc(sizeof(struct Event));
    xAssert(e);
    e->func = func;
    e->arg = arg;

#ifdef XK_THREAD_TRACE
    {
        XTime   offset;
        Binding b;

        offset.sec = etime / 1E6;
        offset.usec = etime % (unsigned)1E6;

        xAssert(localEventMap);
        b = mapBind(localEventMap, &e, e);
        xAssert(b != ERR_BIND);
        e->bind = 0;  /* filled in in 'stub' once a native thread is assigned */
        xGetTime(&e->startTime);
        xTrace2(event, TR_DETAILED, "event start time: %d %d",
                e->startTime.sec, e->startTime.usec);
        xAddTime(&e->startTime, e->startTime, offset);
    }
#endif

    /* event_interval, time between scans of the event list, is also in usec. */
    delt = (etime + event_interval / 2) / event_interval;
    if (delt == 0 && etime != 0)
        delt = 1;
    xTrace1(event, TR_EVENTS, "event requires %d clock ticks", delt);
    e->deltat = delt / BIG_N + 1;
    e->flags = 0;

    if (delt == 0) {
        e->state = E_SCHEDULED;

        xTrace0(event, TR_EVENTS, "evSchedule starting event");
        ret = CreateProcess((void *)&stub, STD_PRIO, 1, e);
    }
    else {
        xTrace1(event, TR_FUNCTIONAL_TRACE,
		"Placing event in queue %d", (tickmod+delt)%BIG_N);
        /* e_insque will take care of locking */
        e->state = E_PENDING;
        e_insque((tickmod + delt) % BIG_N, e);
    }
    xTrace1(event, TR_MAJOR_EVENTS, "evSchedule returns event %x", e);
    return e;
}

/*
 * free the storage used by event, or mark it as detachable after the
 * associate thread finishes
 */
void
evDetach(Event e)
{
    xTrace1(event, TR_GROSS_EVENTS, "evDetach: event = %x", e);
    EVENT_LOCK();
    if (e->state == E_FINISHED)
	freeEvent(e);
    else
	e->flags |= E_DETACHED_F;
    EVENT_UNLOCK();
}

/*
 * See description in event.h
 */
EvCancelReturn
evCancel(Event e)
{
    int ans = 0;

    xTrace1(event, TR_GROSS_EVENTS, "evCancel: event = %x", e);
    xAssert(e);
    EVENT_LOCK();
#ifdef XK_DEBUG
    {
        XkReturn xkr;

        xkr = mapResolve(localEventMap, &e, 0);
        if (xkr != XK_SUCCESS)
            Kabort("Attempt to cancel nonexistent event");
    }
#endif

    e->flags |= (E_DETACHED_F | E_CANCELLED_F);

    switch (e->state) {
        case E_SCHEDULED:
            /*
             * The stub routine will catch this event before it gets a chance
	     * to run
             */
            xTrace2(event, TR_MORE_EVENTS,
		    "cancelled event %x in SCHEDULED state, flags %x",
		    e, e->flags);
            ans = EVENT_CANCELLED;
            break;
        case E_BLOCKED:
        case E_RUNNING:
            ans = EVENT_RUNNING;
            break;
        case E_FINISHED:
            ans = EVENT_FINISHED;
            freeEvent(e);
            break;
        case E_PENDING:
            e_remque(e);
            ans = EVENT_CANCELLED;
            freeEvent(e);
            break;
        default:
            xError("broken event state in evCancel");
    }
    EVENT_UNLOCK();
    return ans;
}

int
evIsCancelled(Event e)
{
    int res;

    xAssert(e);
    EVENT_LOCK();
    res = e->flags & E_CANCELLED_F;
    EVENT_UNLOCK();
    return res;
}

void
evCheckStack(char *name)
{
#ifdef XK_THREAD_TRACE
    Event ev;
    int   diff;

    ev = evSelf();
    if (ev) {
        diff = (char *)&ev - (char *)ev->earlyStack;
        if (diff < 0) diff = -diff;
        if (diff > eventStackWarnLimit) {
            if (traceevent >= TR_SOFT_ERRORS || diff > eventStackErrorLimit) {
                xError("x-kernel stack warning");
                xTrace2(event, TR_ALWAYS,
                        "x-kernel stack warning [%s], event %x", name, ev);
                xTrace3(event, TR_ALWAYS,
                        "cur = %x, beg = %x, size = %d",
                        (int)&ev, (int)ev->earlyStack, diff);
            }
        }
    }
#endif
}

/*
 * Update the event queues periodically
 *
 * This routine is not under the master lock.  It decrements the tick count
 * for each event bucket; events whose time has expired are scheduled for
 * execution via the master lock.  The events are stored in the bucket in
 * order of increasing time to expiration.  Each event's "deltat" field
 * indicates the time by which it exceeds the previous event's timeout value.
 *
 * The evClock under Solaris works by setting a timer and waiting for SIGALRM.
 * At each SIGALRM the timer is reset, the event queue is handled as described
 * above, and the signal is waited for again.
 */
void evClock(void *unused)
{
    timer_t timer;
    int timer_status;
    struct itimerspec interval;
    sigset_t set;
    Event e;
    bool  ret;

    /* set higher priority for clock thread */
    thr_setprio(thr_self(), STD_PRIO + 1);

    /*
     * dont set _value_ to zero like the man page says or it won't work
     * [Gordon]
     */
    interval.it_value.tv_sec = event_interval/1e6;
    interval.it_value.tv_nsec = (event_interval%1000000)*1e3;
    interval.it_interval.tv_sec = event_interval/1e6;
    interval.it_interval.tv_nsec = (event_interval%1000000)*1e3;

    /* ensure SIGALRM unblocked for this thread */
    sigemptyset(&set);
    sigaddset(&set, SIGALRM);

    /* create interval timer */
    timer_status = timer_create(CLOCK_REALTIME, NULL, &timer);
    if (timer_status) {
        xTrace1(event, TR_ERRORS, "evClock: Error in creating timer; code=%d",
	        timer_status);
    }

    xTrace0(event, TR_FULL_TRACE, "evClock enter");
    if (timer_settime(timer, TIMER_RELTIME, &interval, NULL) != 0)
        xTrace0(event, TR_ERRORS, "evClock: Error in setting timer");

    while (1) {
        int sig;

        /* wait here for the next tick */
        sigwait(&set, &sig);

        /* do some work */
        xTrace0(event, TR_EVENTS, "evClock tick");

        EVENT_LOCK();
        evHead[tickmod].next->deltat--;

        for (e = evHead[tickmod].next; e != &evHead[tickmod] &&
	     e->deltat == 0; e = e->next) {
            remque(e);
            e->state = E_SCHEDULED;

#ifdef XK_THREAD_TRACE
            xGetTime(&e->startTime);
#endif

            xTrace0(event, TR_EVENTS, "evClock starting event");
            ret = CreateProcess((void *)&stub, STD_PRIO, 1, e);
        }
        tickmod = (tickmod+1)%BIG_N;
        EVENT_UNLOCK();
    }
}
