/* Copyright 1990,1991,1992 Eric R. Smith. Copyright 1992,1993,1994 Atari Corporation. All rights reserved. */ #include "mint.h" /* * We initialize proc_clock to a very large value so that we don't have * to worry about unexpected process switches while starting up */ short proc_clock = 0x7fff; /* used by filesystems for time/date stamps; updated once per second */ short timestamp, datestamp; extern short in_kernel; /* in main.c */ static void unnapme P_((PROC *)); static TIMEOUT *newtimeout P_((short)); static void disposetimeout P_((TIMEOUT *)); static void inserttimeout P_ ((TIMEOUT *, long)); #define TIMEOUTS 20 /* # of static timeout structs */ #define TIMEOUT_USED 0x01 /* timeout struct is in use */ #define TIMEOUT_STATIC 0x02 /* this is a static timeout */ /* This gets implizitly initialized to zero, thus the flags are * set up correctly. */ static TIMEOUT timeouts[TIMEOUTS] = { { 0, }, }; TIMEOUT *tlist = NULL; TIMEOUT *expire_list = NULL; /* Number of ticks after that an expired timeout is considered to be old and disposed automatically. */ #define TIMEOUT_EXPIRE_LIMIT 400 /* 2 secs */ static TIMEOUT * newtimeout(fromlist) short fromlist; { TIMEOUT *t; short i, sr; if (!fromlist) { t = kmalloc(SIZEOF(TIMEOUT)); if (t) { t->flags = 0; t->arg = 0; return t; } } sr = spl7(); for (i = 0; i < TIMEOUTS; ++i) { if (!(timeouts[i].flags & TIMEOUT_USED)) { timeouts[i].flags |= (TIMEOUT_STATIC|TIMEOUT_USED); spl(sr); timeouts[i].arg = 0; return &timeouts[i]; } } spl(sr); return 0; } static void disposetimeout(t) TIMEOUT *t; { if (t->flags & TIMEOUT_STATIC) t->flags &= ~TIMEOUT_USED; else kfree(t); } static void dispose_old_timeouts () { TIMEOUT *t, **prev, *old; long now = *(long *) 0x4ba; short sr = spl7 (); for (prev = &expire_list, t = *prev; t; prev = &t->next, t = *prev) { if (t->when < now) { /* This and the following timeouts are too old. Throw them away. */ *prev = 0; spl (sr); while (t) { old = t; t = t->next; disposetimeout (old); } return; } } spl (sr); } static void inserttimeout(t, delta) TIMEOUT *t; long delta; { TIMEOUT **prev, *cur; short sr = spl7(); cur = tlist; prev = &tlist; while (cur) { if (cur->when >= delta) { cur->when -= delta; t->next = cur; t->when = delta; *prev = t; spl(sr); return; } delta -= cur->when; prev = &cur->next; cur = cur->next; } assert(delta >= 0); t->when = delta; t->next = cur; *prev = t; spl(sr); } /* * addtimeout(long delta, void (*func)()): schedule a timeout for the current * process, to take place in "delta" milliseconds. "func" specifies a * function to be called at that time; the function is passed as a parameter * the process for which the timeout was specified (i.e. the value of * curproc at the time addtimeout() was called; note that this is probably * *not* the current process when the timeout occurs). * * NOTE: if kernel memory is low, newtimeout() will try to get a statically * allocated timeout struct (fallback method). */ TIMEOUT * ARGS_ON_STACK addtimeout(delta, func) long delta; void (*func) P_((PROC *)); { TIMEOUT *t; TIMEOUT **prev; short sr; /* Try to reuse an already expired timeout that had the same function attached */ sr = spl7(); prev = &expire_list; for (t = *prev; t != NULL; prev = &t->next, t = *prev) if (t->proc == curproc && t->func == func) { *prev = t->next; break; } spl(sr); if (t == NULL) t = newtimeout(0); /* BUG: we should have some fallback mechanism for timeouts when the kernel memory is exhausted */ assert(t); t->proc = curproc; t->func = func; inserttimeout(t, delta); return t; } /* * addroottimeout(long delta, void (*)(PROC *), short flags); * Same as addtimeout(), except that the timeout is attached to Pid 0 (MiNT). * This means the timeout won't be cancelled if the process which was * running at the time addroottimeout() was called exits. * * Currently only bit 0 of `flags' is used. Meaning: * Bit 0 set: Call from interrupt (cannot use kmalloc, use statically * allocated `struct timeout' instead). * Bit 0 clear: Not called from interrupt, can use kmalloc. * * Thus addroottimeout() can be called from interrupts (bit 0 of flags set), * which makes it *extremly* useful for device drivers. * A serial device driver would make an addroottimeout(0, check_keys, 1) * if some bytes have arrived. * check_keys() is then called at the next context switch, can use all * the kernel functions and can do time cosuming jobs. */ TIMEOUT * ARGS_ON_STACK addroottimeout(delta, func, flags) long delta; void (*func) P_((PROC *)); short flags; { TIMEOUT *t; TIMEOUT **prev; short sr; /* Try to reuse an already expired timeout that had the same function attached */ sr = spl7(); prev = &expire_list; for (t = *prev; t != NULL; t = *prev) { if (t->proc == rootproc && t->func == func) { *prev = t->next; break; } prev = &t->next; } spl(sr); if (!t) t = newtimeout(flags & 1); if (!t) return NULL; t->proc = rootproc; t->func = func; inserttimeout(t, delta); return t; } /* * cancelalltimeouts(): cancels all pending timeouts for the current * process */ void ARGS_ON_STACK cancelalltimeouts() { TIMEOUT *cur, **prev, *old; long delta; short sr = spl7 (); cur = tlist; prev = &tlist; while (cur) { if (cur->proc == curproc) { delta = cur->when; old = cur; *prev = cur = cur->next; if (cur) cur->when += delta; spl(sr); disposetimeout(old); sr = spl7(); /* ++kay: just in case an interrupt handler installed a * timeout right after `prev' and before `cur' */ cur = *prev; } else { prev = &cur->next; cur = cur->next; } } prev = &expire_list; for (cur = *prev; cur; cur = *prev) { if (cur->proc == curproc) { *prev = cur->next; spl (sr); disposetimeout (cur); sr = spl7 (); } else prev = &cur->next; } spl (sr); } /* * Cancel a specific timeout. If the timeout isn't on the list, or isn't * for this process, we do nothing; otherwise, we cancel the time out * and then free the memory it used. *NOTE*: it's very possible (indeed * likely) that "this" was already removed from the list and disposed of * by the timeout processing routines, so it's important that we check * for it's presence in the list and do absolutely nothing if we don't * find it there! */ void ARGS_ON_STACK canceltimeout(this) TIMEOUT *this; { TIMEOUT *cur, **prev; short sr = spl7(); /* First look at the list of expired timeouts */ prev = &expire_list; for (cur = *prev; cur; cur = *prev) { if (cur == this && cur->proc == curproc) { *prev = cur->next; spl (sr); disposetimeout (this); return; } prev = &cur->next; } prev = &tlist; for (cur = tlist; cur; cur = cur->next) { if (cur == this && cur->proc == curproc) { *prev = cur->next; if (cur->next) { cur->next->when += this->when; } spl (sr); disposetimeout(this); return; } prev = &cur->next; } spl(sr); } void ARGS_ON_STACK cancelroottimeout(this) TIMEOUT *this; { TIMEOUT *cur, **prev; short sr = spl7(); /* First look at the list of expired timeouts */ prev = &expire_list; for (cur = *prev; cur; cur = *prev) { if (cur == this && cur->proc == rootproc) { *prev = cur->next; spl (sr); disposetimeout (this); return; } prev = &cur->next; } prev = &tlist; for (cur = tlist; cur; cur = cur->next) { if (cur == this && (cur->proc == rootproc)) { *prev = cur->next; if (cur->next) { cur->next->when += this->when; } spl (sr); disposetimeout(this); return; } prev = &cur->next; } spl(sr); } /* * timeout: called every 20 ms or so by GEMDOS, this routine * is responsible for maintaining process times and such. * it should also decrement the "proc_clock" variable, but * should *not* take any action when it reaches 0 (the state of the * stack is too uncertain, and time is too critical). Instead, * a vbl routine checks periodically and if "proc_clock" is 0 * suspends the current process */ volatile int our_clock = 1000; /* variables for monitoring the keyboard */ extern IOREC_T *keyrec; /* keyboard i/o record pointer */ extern short kintr; /* keyboard interrupt pending (see intr.s) */ void ARGS_ON_STACK timeout() { int ms; /* time between ticks */ kintr = keyrec->head != keyrec->tail; ms = *((short *)0x442L); if (proc_clock > 0) proc_clock--; our_clock -= ms; if (tlist) { tlist->when -= ms; } } /* * sleep() calls this routine to check on alarms and other sorts * of time-outs on every context switch. */ void checkalarms() { extern long searchtime; /* in dosdir.c */ PROC *p; long delta; void (*evnt) P_((PROC *, long arg)); TIMEOUT *old; short sr; long arg; /* do the once per second things */ while (our_clock < 0) { our_clock += 1000; timestamp = Tgettime(); datestamp = Tgetdate(); searchtime++; reset_priorities(); } sr = spl7(); /* see if there are outstanding timeout requests to do */ while (tlist && ((delta = tlist->when) <= 0)) { p = tlist->proc; /* hack: pass an extra long as arg, those intrested in it will need * a cast and have to place it in t->arg themselves but that way * everything else still works without change -nox */ arg = tlist->arg; evnt = (void (*)P_((PROC *, long)))tlist->func; old = tlist; tlist = tlist->next; /* if delta < 0, it's possible that the time has come for the next timeout * to occur. * ++kay: moved this before the timeout fuction is called, in case the * timeout function installes a new timeout. */ if (tlist) tlist->when += delta; old->next = expire_list; old->when = *(long *) 0x4ba + TIMEOUT_EXPIRE_LIMIT; expire_list = old; spl(sr); /* ++kay: debug output at spl7 hangs the system, so moved it here */ TRACE(("doing timeout code for pid %d", p->pid)); /* call the timeout function */ (*evnt)(p, arg); sr = spl7(); } spl(sr); /* Now look at the expired timeouts if some are getting old */ dispose_old_timeouts (); } /* * nap(n): nap for n milliseconds. Used in loops where we're waiting for * an event. If we expect the event *very* soon, we should use yield * instead. * NOTE: we may not sleep for exactly n milliseconds; signals can wake * us earlier, and the vagaries of process scheduling may cause us to * oversleep... */ static void unnapme(p) PROC *p; { if (p->wait_q == SELECT_Q && p->wait_cond == (long)nap) { short sr = spl7(); rm_q(SELECT_Q, p); add_q(READY_Q, p); spl(sr); p->wait_cond = 0; } } void ARGS_ON_STACK nap(n) unsigned n; { TIMEOUT *t; t = addtimeout((long)n, unnapme); sleep(SELECT_Q, (long)nap); canceltimeout(t); }