executor/src/time.c

370 lines
9.3 KiB
C

/* Copyright 1989, 1990, 1995, 1996 by Abacus Research and
* Development, Inc. All rights reserved.
*/
#if !defined (OMIT_RCSID_STRINGS)
char ROMlib_rcsid_time[] =
"$Id: time.c 63 2004-12-24 18:19:43Z ctm $";
#endif
/* Forward declarations in TimeMgr.h (DO NOT DELETE THIS LINE) */
#include "rsys/common.h"
#include "OSUtil.h"
#include "TimeMgr.h"
#include "rsys/blockinterrupts.h"
#include "rsys/pstuff.h"
#include "rsys/osutil.h"
#include "rsys/vbl.h"
#include "rsys/time.h"
#include "rsys/syncint.h"
#include "rsys/hook.h"
#include "rsys/refresh.h"
#include "rsys/soundopts.h"
#if !defined (SYN68K)
#include "m68k-stack.h"
#endif
#if defined (SYSV)
LONGINT
setitimer (which, value, ovalue) /* TODO */
LONGINT which;
CONST struct itimerval *value;
struct itimerval *ovalue;
{
return 0L;
}
#endif /* SYSV */
PUBLIC QHdr ROMlib_timehead;
/* Actual time at which Executor started running, GMT. */
PUBLIC struct timeval ROMlib_start_time;
#if defined (SYN68K)
/* Current state of virtual interrupt enabling. */
PUBLIC virtual_int_state_t _virtual_interrupts_blocked = FALSE;
#endif
/* Msecs during last interrupt. */
PRIVATE unsigned long last_interrupt_msecs;
/* Msecs during next anticipated interrupt. */
PRIVATE unsigned long next_interrupt_msecs;
#if defined (MSDOS) && !defined (USE_BIOS_TIMER)
unsigned long
msecs_elapsed (void)
{
static char beenhere = FALSE;
unsigned long e;
if (!beenhere)
{
struct timezone tz;
tz.tz_minuteswest = 0;
tz.tz_dsttime = 0;
gettimeofday (&ROMlib_start_time, &tz); /* GMT */
set_elapsed_1024 (0);
beenhere = TRUE;
}
/* Convert elapsed 1024ths of a second to elapsed milliseconds,
* and try to avoid overflow.
*/
e = fetch_elapsed_1024 ();
return ((e >> 10) * 1000) + (((e & 1023) * 1000) >> 10);
}
#else /* !defined (MSDOS) || defined (USE_BIOS_TIMER) */
#if !defined (CYGWIN32)
unsigned long
msecs_elapsed ()
{
struct timeval t;
struct timezone tz;
unsigned long m;
static unsigned long start_msecs;
tz.tz_minuteswest = 0;
tz.tz_dsttime = 0;
gettimeofday (&t, &tz); /* GMT */
m = (t.tv_sec * 1000 + t.tv_usec / 1000);
if (start_msecs == 0)
{
start_msecs = m;
ROMlib_start_time = t;
}
return m - start_msecs;
}
#endif
#endif /* !defined (MSDOS) || defined (USE_BIOS_TIMER) */
/*
* catchalarm has been written with an eye toward not having errors accumulate.
* In the UNIX world it is going to be hard to give great response time, but
* it is unexcusable for repeated calls to catchalarm to distort the timing
* of events that are far in the future.
*
* Unfortunately, unless we change the data structure that is in the linked
* list, errors are bound to accumulate. Each time we take an interrupt
* our idea of how much time has passed could be off by as much as a
* millisecond. We have to take interrupts 60 times a second so we will tend
* to drift a couple ticks each second. If this proves unacceptable, the
* datastructure will have to be expanded so we keep a timeval around that
* tells us when to expire.
*/
#define REALLONGTIME 0x7FFFFFFF
#if defined (SYN68K)
A2 (PUBLIC, syn68k_addr_t, catchalarm, syn68k_addr_t, interrupt_pc,
void *, unused)
#else
A3 (PRIVATE, LONGINT, catchalarm, LONGINT, volatile signo,
LONGINT, volatile code, struct sigcontext *, volatile scp)
#endif
{
ULONGINT diff;
TMTask *qp;
LONGINT min;
LONGINT tm_count;
M68kReg saved_regs[16];
CCRElement saved_ccnz, saved_ccn, saved_ccc, saved_ccv, saved_ccx;
unsigned long now_msecs;
/* We save the 68k registers and cc bits away. */
memcpy (saved_regs, &cpu_state.regs, sizeof saved_regs);
saved_ccnz = cpu_state.ccnz;
saved_ccn = cpu_state.ccn;
saved_ccc = cpu_state.ccc;
saved_ccv = cpu_state.ccv;
saved_ccx = cpu_state.ccx;
#if defined (SYN68K)
/* There's no reason to think we need to decrement A7 by 32;
* it's just a paranoid thing to do.
*/
EM_A7 = (EM_A7 - 32) & ~3; /* Might as well long-align it. */
#else /* !SYN68K */
/* Since we don't know which stack we were on when interrupted,
* switch stacks as appropriate. If all stacks are busy,
* there's not much we can do other than return.
*/
if (!m68k_use_interrupt_stacks ())
return 0;
#endif /* !SYN68K */
/* Loop while it's still time to do stuff sitting in the queue. */
do
{
unsigned long msecs;
msecs = msecs_elapsed ();
diff = msecs - last_interrupt_msecs;
last_interrupt_msecs = msecs;
for (qp = MR ((TMTask *) ROMlib_timehead.qHead);
qp;
qp = (TMTask *) MR (qp->qLink))
{
tm_count = CL (qp->tmCount);
if (tm_count > 0)
{
tm_count -= diff;
qp->tmCount = CL (tm_count);
if (tm_count <= 0)
{
ProcPtr tm_addr;
ROMlib_hook (time_number);
tm_addr = MR (qp->tmAddr);
if (tm_addr == (ProcPtr) P_ROMlib_wakeup)
C_ROMlib_wakeup ();
else if (tm_addr == (ProcPtr) P_ROMlib_vcatch)
C_ROMlib_vcatch ();
else if (tm_addr == (ProcPtr) P_handle_refresh)
C_handle_refresh ();
else if (tm_addr == (ProcPtr) P_sound_timer_handler)
C_sound_timer_handler ();
else if (tm_addr)
{
/* No need to save and restore regs here; we
* save and restore all of them outside this
* loop. */
EM_A0 = (LONGINT) (long) US_TO_SYN68K(tm_addr);
EM_A1 = (LONGINT) (long) US_TO_SYN68K(qp);
CALL_EMULATOR ((syn68k_addr_t) EM_A0);
}
}
}
}
/* Find the next imminent timer event in the queue. */
min = REALLONGTIME;
for (qp = MR ((TMTask *) ROMlib_timehead.qHead);
qp;
qp = (TMTask *) MR (qp->qLink))
{
tm_count = CL (qp->tmCount);
if (tm_count > 0 && tm_count < min)
min = tm_count;
}
/* Fetch the current time and move the nearest event even closer
* to compensate for time spent in this procedure.
*/
now_msecs = msecs_elapsed ();
if (min < REALLONGTIME)
min -= now_msecs - last_interrupt_msecs;
}
while (min <= 0);
if (min < REALLONGTIME)
{
/* If there's anything left in the queue, set up another
* timer interrupt to come in at the appropriate time.
*/
#if defined (SYN68K)
syncint_post (min * 1000);
#else /* !SYN68K */
struct itimerval t;
t.it_value.tv_sec = min / 1000;
t.it_value.tv_usec = (min * 1000) % 1000000;
t.it_interval.tv_sec = 0;
t.it_interval.tv_usec = 0;
setitimer (ITIMER_REAL, &t, (struct itimerval *) 0);
#endif /* !SYN68K */
next_interrupt_msecs = now_msecs + min;
}
else
{
/* Note that there's no interrupt queued up. */
next_interrupt_msecs = 0;
}
#if !defined (SYN68K)
m68k_restore_stacks ();
#endif
memcpy (&cpu_state.regs, saved_regs, sizeof saved_regs);
cpu_state.ccnz = saved_ccnz;
cpu_state.ccn = saved_ccn;
cpu_state.ccc = saved_ccc;
cpu_state.ccv = saved_ccv;
cpu_state.ccx = saved_ccx;
#if defined (SYN68K)
return MAGIC_RTE_ADDRESS;
#else
return 0;
#endif
}
A2 (PRIVATE, void, ROMlib_PrimeTime, QElemPtr, taskp, LONGINT, count)
{
static char beenhere = FALSE;
LONGINT msecs_until_next;
virtual_int_state_t block;
unsigned long now_msecs;
/*
* We introduce this fudge factor because we are nervous that our time
* calculations will mess up and result in a negative number under Executor
* when on a real Mac the number wouldn't be negative. This is paranoia,
* but small timing stuff just isn't going to work properly under Executor
* anyway.
*/
#define FUDGE_FACTOR (-30)
if (count < FUDGE_FACTOR)
count = -count / 1000; /* IM-Processes 3-20 */
if (count <= 0)
count = 1;
block = block_virtual_ints ();
now_msecs = msecs_elapsed ();
if (!beenhere)
{
last_interrupt_msecs = now_msecs; /* actually there haven't been any */
msecs_until_next = 0x7FFF0000; /* Arbitrary large value. */
next_interrupt_msecs = now_msecs + msecs_until_next;
beenhere = TRUE;
#if !defined (SYN68K)
signal (SIGALRM, (void *) catchalarm);
#endif
}
else
{
msecs_until_next = next_interrupt_msecs - now_msecs;
}
/* catchalarm works by subtracting off the msecs _since the last
* interrupt_ from each entry in the queue. Since we might be
* getting posted sometime between interrupts, we have to compensate
* by adding the time since the last interrupt to our count. That
* way the extra time subtracted off during the catchalarm will
* exactly match the extra time we added here.
*/
((TMTask *) taskp)->tmCount = CL (count + now_msecs - last_interrupt_msecs);
if (count < msecs_until_next || msecs_until_next <= 0)
{
#if defined (SYN68K)
syncint_post (count * 1000);
#else /* !SYN68K */
struct itimerval t;
t.it_value.tv_sec = count / 1000;
t.it_value.tv_usec = (count * 1000) % 1000000;
t.it_interval.tv_sec = 0;
t.it_interval.tv_usec = 0;
setitimer (ITIMER_REAL, &t, (struct itimerval *) 0);
#endif /* !SYN68K */
next_interrupt_msecs = now_msecs + count;
}
restore_virtual_ints (block);
}
A1 (PUBLIC, void, InsTime, QElemPtr, taskp)
{
((TMTask *) taskp)->tmCount = CLC (-1);
Enqueue (taskp, &ROMlib_timehead);
}
A1 (PUBLIC, void, RmvTime, QElemPtr, taskp)
{
Dequeue (taskp, &ROMlib_timehead);
}
A2 (PUBLIC, void, PrimeTime, QElemPtr, taskp, LONGINT, count)
{
ROMlib_PrimeTime (taskp, count);
}