/* IBMTIMER - stopwatch and timer functions for the IBMPC. This stuff is so system specific that there's no point in calling it directly from an application. */ #include /* IBMPC_STOPWATCH This function implements a simple and very accurate stopwatch. NOTE that this function must be called at least once per half hour in order for it to remain accurate. In fact, it will spin in an almost infinite loop if called less than once per half hour. Also, for long duration events (greater than a minute,) it is much better to compute elapsed time using the system clock (as apposed to the DOS time function, which is based on the system tick counter and is not accurate at all. The information required by the stopwatch for each event being timed is kept in an IBM_STOPWATCH struct, the structure for which is declared in "ibmtimer.h" The first parameter is a reset flag. The second is a pointer to the stopwatch struct for the event being timed. If the reset flag is true, the stopwatch struct is reset to zero, and timing begins. If the reset flag is zero, then the data in the stopwatch struct is updated, and the elapsed time from the reset operation to the current operation is returned. The return value is always the number of COUNTER increments since the last reset operation. ie: 1 second = 1193180 counter increments. Operation is as follows: The 8253 Timer channel 0 is configured as a down counter which counts 65536 transitions on its clock line, then generates an interrupt. The interrupt is handled by a routine called TIMER_INT (see page A-79 of the Tech. Ref. PC-XT.) This routine increments an int var called TIMER_LOW. Since the input clock to timer channel is running at 1193180 Hz, TIMER_LOW is incremented at the rate of 1193180 / 2^16 = 18.20648193 times per second. At that rate, it rolls over once every 2^16 / 18.2 = 3599.597124 seconds, or approx. once per hour. The error is 111.91us per second on a system with a perfectly adjusted crystal (very rare.) This stopwatch works by computing the elapsed time since the reset of the stopwatch struct using the TIMER_LOW var and the 8253 channel 0 counter. Note that due to the problem of reading the DOS time & the timer channel count while same are being updated, it is possible that a read error of several microseconds may occur. To prevent such invalid values from being returned by this routine, a check is made for samples that are more than 30 minutes apart (that's what the errors look like.) In the event that such a sample is detected, another sample is taken. As such, this routine could go into a dead spin for a half hour if it is not called more than once every half hour. */ unsigned long ibmpc_stopwatch( resflg, p ) struct IBMPC_STOPWATCH *p; int resflg; /* reset flag */ { static unsigned long read_ibmpc_time(); unsigned long c; rderr: c = read_ibmpc_time(); if ( resflg ) { p->last_count = c; p->elapsed_time = 0l; }; if ( (c - p->last_count) & 0x80000000l ) goto rderr; p->elapsed_time += (c - p->last_count); p->last_count = c; return p->elapsed_time; } /* READ_IBMPC_TIME Returns the current value of TIMER_LOW as the high order 16 bits of its result, and the current count in the 8253 timer channel 0 as the low order 16 bits. */ unsigned long read_ibmpc_time() { #ifndef _lint #asm timer equ 040h ; ;routine to return TIMER_LOW:Channel Count ; push ds ;save current segment mov ax, 040h ;use segment at 40h mov ds, ax mov bx, 06ch ;get offset to TIMER_LOW mov al, 0 ;ready to read timer out timer+3,al ;this latches the current count cli ;disable interrupts mov dx, [bx] ;get TIMER_LOW in al, timer+0 ;read the current count mov ah, al in al, timer+0 sti ;re-enable interrupts xchg al, ah mov cx, ax ;save it xor ax, ax ;clear ax sub ax, cx ;convert to positive count pop ds ;restore DS #endasm #else unsigned long happy_lint; happy_lint = 123l; return happy_lint; #endif } /* READ_IBMPC_TIMER This function simply reads the current value in the 8253 channel 0. */ unsigned int read_ibmpc_timer() { #ifndef _lint #asm mov al, 0h ;set up to read low/high count out timer+3,al in al, timer+0 ;read the current count mov ah, al in al, timer+0 xchg al, ah mov cx, ax ;save it xor ax, ax ;clear ax sub ax, cx ;convert to positive count #endasm #else unsigned int happy_lint; happy_lint = 123; return happy_lint; #endif } /* CVT_IBMPC_TIME This function converts any of the time values above into Minutes, Seconds & Thousandths of seconds. It returns as its value the time passed to it. */ unsigned long cvt_ibmpc_time( time, pm, ps, phs ) unsigned long time; unsigned int *pm, *ps, *phs; { unsigned long t, m, s, hs; static unsigned long timerf = IBMPC_TIMER0_FREQ; s = time / timerf; /* total number of seconds */ t = time - (s * timerf); m = s / 60l; /* number of minutes */ s -= m * 60l; /* remainder is s */ hs = (t * 1000l) / timerf; *pm = (unsigned int) m; *ps = (unsigned int) s; *phs= (unsigned int) hs; return time; }