timer.c

来自「开放源码的编译器open watcom 1.6.0版的源代码」· C语言 代码 · 共 323 行

C
323
字号
/*
 *  Module for handling timers.
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <dos.h>
#include <sys/wtime.h>

#include "wattcp.h"
#include "ioport.h"
#include "strings.h"
#include "gettod.h"
#include "misc.h"

int has_8254  = 0; /* Do we have a working 8254 timer chip? */
int has_rdtsc = 0; /* Do we have a CPU with RDTSC instruction? */

/*
 * System clock at BIOS location 40:6C (dword). Counts upwards.
 */
#define BIOS_CLK 0x46C

static DWORD date    = 0;
static DWORD date_ms = 0;

/*
 * init_timers() - setup timer handling.
 * Called from init_misc()
 */
void init_timers (void)
{
#if (DOSX)         /* problems using 64-bit types in small/large models */
  has_8254 = 1;    /* !! to-do: check if 8254 PIT is really working */
#endif

  chk_timeout (0UL); /* init 'date' variables */
}

/*
 * When using Watt-32 together with programs that reprogram the PIT
 * (8254 timer chip), the millisec_clock() function may return wrong
 * values. In such cases it's best to use old-style 55ms timers.
 * Using Watt-32 with Allegro graphics library is one such case where
 * application program should call `hires_timer(0)'.
 *
 * !!to-do: A viable solution is to use the RDTSC instruction
 *          on Pentiums when that is detected (has_rdtsc=1).
 *          64-bit timestamps will wrap after ~830 years on a
 *          700MHz PC !!
 */
void hires_timer (int on)
{
  has_8254 = on;
  chk_timeout (0UL);
}

/*
 * Compare two timers with expirary at 't1' and 't2'.
 *    returns -1 if t1 expires before t2
 *             0 if t1 == t2
 *            +1 if t1 expires after t2
 *
 * FIX-ME: This logic fails when timers approaches ULONG_MAX
 *         after approx. 50 days.
 */
int cmp_timers (DWORD t1, DWORD t2)
{
  if (t1 == t2)
     return (0);
  return (t1 < t2 ? -1 : +1);
}

/*
 * The following 2 routines are modified versions from KA9Q NOS
 * by Phil Karn.
 *
 * clockbits() - Read low order bits of timer 0 (the TOD clock)
 * This works only for the 8254 chips used in ATs and 386s.
 *
 * The timer 0 runs in mode 3 (square wave mode), counting down
 * by twos, twice for each cycle. So it is necessary to read back the
 * OUTPUT pin to see which half of the cycle we're in. I.e., the OUTPUT
 * pin forms the most significant bit of the count. Unfortunately,
 * the 8253 in the PC/XT lacks a command to read the OUTPUT pin...
 */
static DWORD clockbits (void)
{
  VOLATILE DWORD stat, count;

  do
  {
    _outportb (0x43, 0xC2);        /* Read Back Command (timer 0, 8254 only) */
    stat   = _inportb (0x40);      /* get status of timer 0 */
    count  = _inportb (0x40);      /* LSB of count */
    count |= _inportb (0x40) << 8; /* MSB of count */
  }
  while (stat & 0x40);             /* reread if NULL COUNT bit set */

  stat = (stat & 0x80) << 8;       /* Shift OUTPUT to msb of 16-bit word */
  count >>= 1;                     /* count /= 2 */
  if (count == 0)
     return (stat ^ 0x8000UL);     /* return complement of OUTPUT bit */
  return (count | stat);           /* Combine OUTPUT with counter */
}

/*
 * Return hardware time-of-day in milliseconds. Resolution is improved
 * beyond 55 ms (the clock tick interval) by reading back the instantaneous
 * 8254 counter value and combining it with the clock tick counter.
 *
 * Reading the 8254 is a bit tricky since a tick could occur asynchronously
 * between the two reads in clockbits(). The tick counter is examined before
 * and after the hardware counter is read. If the tick counter changes, try
 * again.
 * Note: the hardware counter (lo) counts down from 65536 at a rate of
 *       1.19318 MHz (4.77/4). System tick count (hi) counts up.
 *       Fraction 11/13125 gives us a milli-sec rate count.
 */
DWORD millisec_clock (void)
{
  DWORD hi, lo;
  do
  {
    hi = peekl (0, BIOS_CLK);
    lo = clockbits();
  }
  while (hi != peekl(0, BIOS_CLK));   /* tick count changed, try again */

#ifdef HAVE_UINT64
  {
    uint64 x = ((uint64)hi << 16) - lo;
    return (x * 11UL / 13125UL);
  }
#else   /* Emulating this would be slow. We'd better have a math-processor */
  {
    double x = ldexp ((double)hi, 16) - (double)lo;  /* x = hi*2^16 - lo */
    return (DWORD) (x * 4.0 / 4770.0);
  }
#endif
}

#if defined(HAVE_UINT64)
/*
 * Return hardware time-of-day in microseconds.
 */
uint64 microsec_clock (void)
{
  DWORD  hi, lo;
  uint64 x;
  do
  {
    hi = peekl (0, BIOS_CLK);
    lo = clockbits();
  }
  while (hi != peekl(0, BIOS_CLK));   /* tick count changed, try again */

  x = ((uint64)hi << 16) - lo;
  return (x * 4000000 / 4770000);
}
#endif


/*
 * Return time for when given timeout (msec) expires
 */
DWORD set_timeout (DWORD msec)
{
#if defined(USE_NEW_TIMERS) && defined(HAVE_UINT64)
  struct timeval now;
  DWORD  usec = microsec_clock() % 1000;

  gettimeofday (&now, NULL);   /* 55msec resolution */
  return (msec + 1000 * now.tv_sec + (now.tv_usec + usec) / 1000);

#else
  if (!has_8254)
  {
    DWORD ticks;

    if (msec > 0 && msec < 55)
         ticks = 1;
    else ticks = msec / 55UL;
    return (ticks + date + (DWORD)peekl (0,BIOS_CLK));
  }
  return (msec + date_ms + millisec_clock());
#endif
}

/*
 * Check if milli-sec value has expired:
 *   returns 1 timer expired
 *           0 not expired (or value not set)
 */
int chk_timeout (DWORD value)
{
#if defined(USE_NEW_TIMERS) && defined(HAVE_UINT64)
  if (value == 0UL)
     return (0);
  return (set_timeout(0) >= value);
#else
  static char  oldHour = -1;
  static DWORD now_ms;
  static DWORD now;
  char   hour;

  now  = (DWORD) peekl (0, BIOS_CLK);
  hour = (char)(now >> 16U);

  if (hour != oldHour)
  {
    if (hour < oldHour)            /* midnight passed */
    {
      #define CORR 290UL           /* experimental correction */
      date    += 1572750UL + CORR; /* Update date (0x1800B0UL) */
      date_ms += 3600*24*1000UL;   /* ~2^26 */
    }
    oldHour = hour;
  }

  if (value == 0L)        /* timer not initialised */
     return (0);          /* (or stopped) */

  if (has_8254)
  {
    now_ms = millisec_clock();
    return (now_ms + date_ms >= value);
  }
  now += date;            /* date extend current time */
  return (now >= value);  /* return true if expired */
#endif
}

/*
 * Must be called by user right before or after a time change occurs.
 */
int set_timediff (long msec)
{
  date_ms -= msec;
  date    -= msec/55;
  return (0);
}

/*
 * Return time difference between timers 'now' and 't'.
 * Check for day rollover. Max timer distance is 24 hours.
 * This function should be called immediately after chk_timeout()
 * is called.
 */
DWORD get_timediff (DWORD now, DWORD tim)
{
#ifdef USE_NEW_TIMERS
  return (now - tim);
#else
  DWORD dt;

  if (now < tim)           /* passed midnight */
       dt = tim - now;
  else dt = now - tim;

  if (has_8254)
       dt = dt % (3600*24*1000UL);
  else dt = dt % (1572750UL + CORR);
  return (dt);
#endif
}

/*
 * Return difference (in micro-sec) between timevals `*a' and `*b'
 */
double timeval_diff (const struct timeval *a, const struct timeval *b)
{
  long d_sec, d_usec;

  d_sec  = (long)(a->tv_sec - b->tv_sec);
  d_usec = a->tv_usec - b->tv_usec;
  return (1E6 * (double)d_sec + (double)d_usec);
}


#if defined(USE_DEBUG)
/*
 * time_str() - return string "x.xx" for timeout value.
 */
const char *time_str (DWORD val)
{
  static char buf[30];

  if (has_8254 || has_rdtsc)
       sprintf (buf, "%.3f", (double)val/1000.0);
  else sprintf (buf, "%.2f", (double)val/18.2);
  return (buf);
}
#endif /* USE_DEBUG */

/*
 * The following was contributed by
 * "Alain" <alainm@pobox.com>
 *
 */
#ifdef NOT_USED
#define TICKS_DAY 1573067UL   /* Number of ticks in one day */

DWORD ms_clock (void)
{
  static DWORD lastTick, tickOffset;
  static char firstTime = 1;
  DWORD  tick = peekl (0, 0x46C);

  if (firstTime)
  {
    firstTime = 0;
    lastTick = tickOffset = tick;
    return (0);
  }
  if (lastTick > tick)        /* day changed */
     tickOffset -= TICKS_DAY;
  lastTick = tick;
  return (tick - tickOffset);
}
#endif

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?