#include "insignia.h" #include "host_def.h" /* * SoftPC version 2.0 * * Title : Time Handler * * Description : Emulate the 8253 3-channel timer; invoke 'BIOS * sytem timer interrupt code', cursor flash, repeat * key processing etc. * * Author : Jerry Kramskoy * * Notes : There is only one real time timer per process, this * module counts clock ticks and distributes calls * to the appropriate functions as required. * * This module is host independent - see xxxx_timer.c * where xxxx is a machine type for host dependent stuff. * * Mods: (r3.2) : (SCR 257). Code has been added to time_tick() to spot * that video has been disabled for a period. If this is * so, clear the screen. Refresh when video is enabled * again. * * (r3.4) : Make use of the host time structures host_timeval, * host_timezone, and host_tm, which are equivalent * to the Unix BSD4.2 structures. * Also convert references to gettimeofday() to * host_getIdealTime(). */ #ifdef SCCSID static char SccsID[]="@(#)timer.c 1.41 05/31/95 Copyright Insignia Solutions Ltd."; #endif #ifdef SEGMENTATION /* * The following #include specifies the code segment into which this * module will by placed by the MPW C compiler on the Mac II running * MultiFinder. */ #include "SOFTPC_SUPPORT.seg" #endif /* * O/S include files. */ #include #include TypesH #include TimeH /* * SoftPC include files */ #include "xt.h" #include CpuH #include "sas.h" #include "ios.h" #include "ica.h" #include "trace.h" #include "bios.h" #include "host.h" #include "timer.h" #include "timeval.h" #include "idetect.h" #include "debug.h" #include "quick_ev.h" #ifndef PROD #include #endif #ifdef HUNTER #include "hunter.h" #endif #ifdef NTVDM #include "fla.h" #include "nt_eoi.h" #include "nt_reset.h" #include "nt_pif.h" #undef LOCAL #define LOCAL #endif /* Imports */ /* * ============================================================================ * Local static data and defines * ============================================================================ */ /* 'idealtime' gets initialised to be the host system's current * time value (timer_init()), and thereafter gets incremented by * the value 'idealInterval' every time that time_tick() gets called * which gives the illusion of a 100% accurate signal delivery */ static struct host_timeval idealtime; static unsigned long idealInterval; #ifdef NTVDM ULONG timer_delay_size= HOST_IDEAL_ALARM >> 1; // usecs ULONG EoiPending=0; ULONG EoiIntsPending=0; ULONG EoiDelayInUse=0; int ticks_blocked = 0; #else /* NTVDM */ static unsigned long ticksPerIdealInterval; static int ticks_blocked = 0; #endif /* NTVDM */ #ifndef PROD static char buf[80]; /* Used for tracing messages */ #endif #ifdef HUNTER /* Only needed for HUNTER */ word timer_batch_count; /* Batch update when PC tick occurs */ #endif int timer_int_enabled; /* Whether Bios timer ints are required */ /* control word format */ /* Values in D54 of control word - number of bytes to read/load into counter */ #define LATCH 0 #define RL_LSB 1 #define RL_MSB 2 #define RL_LMSB 3 /* Values in D321 of control word - the counter mode. */ #define INT_ON_TERMINALCOUNT 0 #define PROG_ONESHOT 1 #define RATE_GEN 2 #define SQUAREWAVE_GEN 3 #define SW_TRIG_STROBE 4 #define HW_TRIG_STROBE 5 /* NB. 6 = RATE_GEN, 7 = SQUAREWAVE_GEN */ /* Values in D0 of control word - whether prog wants to read/write binary or BCD to counter */ #define BINARY 0 #define BCD 1 #define INDEFINITE (ULONG)-1 #define STARTLO 0 #define STARTHI 1 #define NOREPEAT 0 #define REPEAT ~NOREPEAT #define WRITE_SIGNAL 1 #define GATE_SIGNAL 2 #define UNLOCKED 0 #define LOCKED ~UNLOCKED #define STATE_FUNCTION void #define UNBLOCK_FUNCTION void #define GATENABLED_FUNCTION void #define UPDATECOUNTER_FUNCTION void /* * Timer read state */ #define UNREAD 0 /* Timer is in normal state */ #define READMSB 1 /* First byte of LMSB mode read, but not second yet */ #define READSTATUS 2 /* Status latched, will read it first */ /* * These two figures give a timer frequency of 1.193 MHz (which is * how fast the 8235 is clocked. This means that the timer will wrap round * every 1/18.2th of a second... the same amount of time as the PC tick * rate. This is not surprizing, as the PC tick rate is controlled by * timer 0. Every time timer 0 wraps, the PC is interrupted by the timer. */ #define TIMER_CLOCK_NUMER 1000 #define TIMER_CLOCK_DENOM 1193 typedef half_word TSIGNAL; /* the following structure defines an output waveform from a * timer channel. The waveform consists of 'n' clocks at one * logic level, and 'm' ticks at the other logic level. * Which level starts the waveform is given by 'startLogicLevel'. * e.g; the following waveform ... */ typedef struct { long clocksAtLoLogicLevel; long clocksAtHiLogicLevel; long period; long startLogicLevel; long repeatWaveForm; } WAVEFORM; /* __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ * | | | | | * |__ __ __| |__ __ __| | * * would be described by * clocksAtLoLogicLevel = 3 * clocksAtHiLogicLevel = 6 * startLogicLevel = STARTHI * repeatWaveForm = TRUE; * * * The overall state of a counter is represented by the following * structure. Its contents are described below. */ typedef enum trigCond_ {LEVEL, EDGE} trigCond; typedef enum countload_ {AVAILABLE, USED} countload; typedef struct { int m; int bcd; int rl; STATE_FUNCTION (*state) IPT2(int, signal, half_word, value); STATE_FUNCTION (*statePriorWt) IPT2(int, signal, half_word, value); STATE_FUNCTION (*stateOnGate) IPT2(int, signal, half_word, value); UNBLOCK_FUNCTION (*actionOnWtComplete) IPT0(); UNBLOCK_FUNCTION (*actionOnGateEnabled) IPT0(); void (*getTime) IPT1(struct host_timeval *, t); unsigned char outblsb; unsigned char outbmsb; unsigned long initialCount; int readState; int countlatched; unsigned char latchvaluelsb; unsigned char latchvaluemsb; unsigned char latchstatus; word Count; countload newCount; word tickadjust; struct host_timeval activationTime; int tc; int freezeCounter; #ifndef NTVDM unsigned long lastTicks; long microtick; long timeFrig; word saveCount; int guessesPerHostTick; /* How often per host tick are we forced to guess? */ int guessesSoFar; /* How many times have we guessed so far? */ #endif unsigned int delay; trigCond trigger; TSIGNAL gate; TSIGNAL clk; WAVEFORM out; } COUNTER_UNIT; /* * When the counter is programmed, the 8253 receives a control word. * (see pg 6-266 of manual). The counter being programmed is specified * within this word. Provided 'rl' is non-zero, then this counter is * being reprogrammed, and we remember the values of m (mode), rl * (control of which bytes are involved in a read or load sequence) * and bcd (whether the counter is in binary or bcd mode). * Based on rl, the counter then must receive one or two bytes via * outb's. Two states are used to accept 'outblsb' or * 'outbmsb', or both. When the full byte complement has been received, * 'initialCount' gets set to the value specified by 'outblsb' and 'outbmsb', * taking account of BCD etc., along with 'Count'. * 'Count' gets adjusted by the value of 'timeadjust'. * 'timeadjust' is initialised to zero every time a new mode * word (non-zero) is received. For certain modes of the counter, * if they are sent a new count, without receiving a new mode, then * this will cause the counter to start counting from this new count * at some stage (based on mode and gate values). SInce we are not * maintaining the counters continually (rather we prod them as a * result of io or gate activity) then there is a good chance we * will be late at resetting a counter for counting again. Hence * 'timeadjust' is calculated for this lateness, and used as a * correction factor. * If a 'latch counter' command ('rl'=0 in command word) is issued, then * the current counter value is latched into 'latchvaluelsb' and * 'latchvaluemsb', and the flag 'countlatched' is set non-zero. * If this flag is non-zero during a counter read, then these latched * bytes are returned, and upon completion of the read sequence, the * flag is cleared. If this flag is zero, then the current counter value * is read, and returned. 'donate' is used to point at the appropriate * byte to be delivered to the 'inb'. * when a counter activates (i.e; count begins or continues after * a gate signal change) a time stamp is taken, to enable a time delta * to be calculated whenever the counter is read .. this is stored in * 'activationTime'. * A state function (state), representing the current state * of the counter, gets called whenever inb,outb accesses occur, or when * the ppi's signal TIM2GATESPK changes. reading/writing of the counters * (as opposed to the control word register) always 'blocks' the current * state,and puts the counter into a temporary state which handles reading or * loading the counter. The blocked state is remembered in 'statePriorWt'. * Once the counter has been loaded or read (as specified by its 'rl' * parameter) then 'actionOnWtComplete' gets called. Typically this in turn * reverts the counter back to the state it was in before it became * blocked. * If a counter is read, then the function 'updateCounter' gets called to * determine what the current counter value is. * If the counter's gate signal is disabling counting, and the counter * has been fully programmed (and hence able to count), then the counter * will be in the state 'awaitingGate'. When the appropriate gate signal * appears (via a ppi call), the counter activates by calling the * function 'actionOnGateEnabled'. This will take some sort of action, and then * place the counter into the state 'stateOnGate'. * * sending a new count to a counter in modes 2 or 3 will not take * effect until the end of the current period ... hence 'delay' * is used as an indicator (for sound logic emulation only) of * this. If the counter has say 10 clocks left to count down to * the end of the period when it receives new waveform parameters, * this information is passed onto the sound logic, with a 'delay' * of 10. Otherwise 'delay' is not used. * * On some operating systems, the real time clock may well have to * coarse a granularity. If the 8253 is read to quickly, there is a * very good chance that the OS clock will still be reading the same. * To cater for this, a frig factor 'microsecs' has been introduced. * This gets incremented every time the above condition is detected, * and used as part of the counter update calculations. Whenever * the OS actually says something sensible, it gets cleared again. */ static COUNTER_UNIT timers[3], *pcu; LOCAL STATE_FUNCTION uninit IPT2(int, signal, half_word, value); LOCAL STATE_FUNCTION awaitingGate IPT2(int, signal, half_word, value); LOCAL STATE_FUNCTION waitingFor1stWrite IPT2(int, signal, half_word, value); LOCAL STATE_FUNCTION waitingFor2ndWrite IPT2(int, signal, half_word, value); LOCAL STATE_FUNCTION Counting0 IPT2(int, signal, half_word, value); LOCAL STATE_FUNCTION Counting_4_5 IPT2(int, signal, half_word, value); LOCAL STATE_FUNCTION Counting1 IPT2(int, signal, half_word, value); LOCAL STATE_FUNCTION Counting_2_3 IPT2(int, signal, half_word, value); LOCAL UNBLOCK_FUNCTION resumeCounting_1_5 IPT0(); LOCAL GATENABLED_FUNCTION resumeCounting_2_3_4 IPT0(); LOCAL void resumeAwaitGate IPT0(); LOCAL UNBLOCK_FUNCTION CounterBufferLoaded IPT0(); LOCAL UNBLOCK_FUNCTION timererror IPT0(); LOCAL GATENABLED_FUNCTION runCount IPT0(); LOCAL void controlWordReg IPT1(half_word, cwd); LOCAL void latchStatusValue IPT0(); LOCAL void readCounter IPT0(); LOCAL void timestamp IPT0(); LOCAL void outputWaveForm IPT5(unsigned int, delay, unsigned long, lowclocks, unsigned long, hiclocks, int, lohi, int, repeat); LOCAL void outputHigh IPT0(); LOCAL void outputLow IPT0(); LOCAL void setOutputAfterMode IPT0(); LOCAL void loadCounter IPT0(); LOCAL void updateCounter IPT0(); LOCAL void startCounting IPT0(); #ifdef NTVDM unsigned long updateCount(void); #else LOCAL void updateCount IPT3(unsigned long, ticks, unsigned long *, wrap, struct host_timeval *, now); #endif LOCAL unsigned short bin_to_bcd IPT1(unsigned long, val); LOCAL word bcd_to_bin IPT1(word, val); LOCAL void emu_8253 IPT3(io_addr, port, int, signal, half_word, value); LOCAL void Timer_init IPT0(); LOCAL unsigned long timer_conv IPT1(word, count); LOCAL void issueIREQ0 IPT1(unsigned int, n); #ifndef NTVDM LOCAL unsigned long guess IPT0(); LOCAL void throwaway IPT0(); #endif #ifdef SYNCH_TIMERS GLOBAL void IdealTimeInit IPT0(); #else LOCAL void IdealTimeInit IPT0(); #endif LOCAL void updateIdealTime IPT0(); LOCAL void getIdealTime IPT1(struct host_timeval *, t); #ifndef NTVDM LOCAL void getHostSysTime IPT1(struct host_timeval *, t); LOCAL void checktimelock IPT0(); #endif LOCAL void setTriggerCond IPT0(); LOCAL void WtComplete IPT0(); LOCAL void counter_init IPT1(COUNTER_UNIT *, p); #ifndef NTVDM LOCAL void setLastWrap IPT2(unsigned int, nclocks, struct host_timeval *, now); LOCAL void timer_generate_int IPT1(long, n); LOCAL void timer_multiple_ints IPT1(long, n); #define MAX_BACK_SECS 15 LOCAL IU32 max_backlog = 0; /* max # of ints allowed to queue up */ IBOOL active_int_event = FALSE; /* current quick_event for timer queue */ IU32 more_timer_mult = 0; /* additions to timer int queue */ IU32 timer_multiple_delay = 0; /* us delay to next timer queue elem */ #endif #if defined(NTVDM) void timer_generate_int(void); unsigned long clocksSinceCounterUpdate(struct host_timeval *pCuurTime, struct host_timeval *pLastTime, word *pCounter); void ReinitIdealTime IPT1(struct host_timeval *, t); void host_GetSysTime(struct host_timeval *time); void InitPerfCounter(void); /* holds real time values for counter zero */ struct host_timeval LastTimeCounterZero; word RealTimeCountCounterZero; #ifndef PROD ULONG NtTicTesting = 0; /* tracing specific to NT port */ ULONG TicsGenerated; ULONG TicsReceived; #endif /* for optimizing timer hardware interrupt generation */ word TimerInt08Seg = TIMER_INT_SEGMENT; word TimerInt08Off = TIMER_INT_OFFSET; word TimerInt1CSeg; word TimerInt1COff; #else static int timelock; /* locks out time_tick if set */ static int needtick; /* causes time_tick() to be called if set */ /* * Data for the hack to make sure that windows in standard mode doesn't get two timer * ticks too close together. */ LOCAL BOOL hack_active=FALSE; /* This boolean indicates that we are spacing timer interrupts out by discarding timer ticks. It is set when we see a protected mode tick */ LOCAL BOOL too_soon_after_previous=FALSE; /* This boolean is set on when an interrupt is generated... a quick event is requested to clear it again after a "fixed" number of instructions */ LOCAL BOOL ticks_lost_this_time=FALSE; /* This boolean is set if any interrupts were required to be generated while too_soon_after_previous was TRUE - if it's TRUE when too_soon_after_previous is being set to FALSE, we generate an immediate interrupt to get the best responsiveness */ LOCAL ULONG real_mode_ticks_in_a_row = 0; /* A count of the number of real mode ticks in a row... this is used to disable the hack again when we have left protected mode for a good while */ LOCAL ULONG instrs_per_tick = 37000; /* Nominal (as timed on the reference machine - a SPARC 1+) */ LOCAL ULONG adj_instrs_per_tick = 0; /* The estimated number of Intel instructions being emulated each 20th of a second */ LOCAL ULONG n_rm_instrs_before_full_speed = 3000000;/* Nominal number of instructions to be emulated in real mode before we're convinced that we're staying back in real mode */ LOCAL ULONG adj_n_real_mode_ticks_before_full_speed = 0;/* The value which real_mode_ticks_in_a_row must reach before the hack is disabled */ #ifndef PROD LOCAL ULONG ticks_ignored = 0; /* For information purposes only. */ #endif #endif /* NTVDM */ #ifndef PROD /* Specific Timer change tracing that isn't timer_verbose */ GLOBAL int DoTimerChangeTracing = 0; #endif #if !defined(NTVDM) #if defined(IRET_HOOKS) && defined(GISP_CPU) extern IBOOL HostDelayTimerInt IPT1(int, numberInts); extern IBOOL HostPendingTimerInt IPT0(); extern void HostResetTimerInts IPT0(); #endif /* IRET_HOOKS && GISP_CPU */ #endif /* * ============================================================================ * External variables * ============================================================================ */ boolean timer_video_enabled; /* * Table of function pointers to access TIMER routines */ void (*timer_inb_func) IPT2(io_addr, port, half_word *, value); void (*timer_outb_func) IPT2(io_addr, port, half_word, value); void (*timer_gate_func) IPT2(io_addr, port, half_word, value); void (*timer_tick_func) IPT0(); /* * ============================================================================ * External functions * ============================================================================ */ void SWTMR_init_funcptrs IFN0() { /* * initialize access functions for SW [emulated] TIMER */ timer_inb_func = SWTMR_inb; timer_outb_func = SWTMR_outb; timer_gate_func = SWTMR_gate; timer_tick_func = SWTMR_time_tick; #ifndef NTVDM /* * initialise stuff for PM timer hack */ too_soon_after_previous = FALSE; if (adj_n_real_mode_ticks_before_full_speed == 0){ HOST_TIMER_DELAY_SIZE=25000; adj_instrs_per_tick = host_speed (instrs_per_tick); adj_n_real_mode_ticks_before_full_speed = n_rm_instrs_before_full_speed / adj_instrs_per_tick; } #endif } void SWTMR_time_tick IFN0() { /* * Give the idle detector a chance to see if we are really idle. */ #ifndef NTVDM /* this is done in Nt's host heartbeat */ IDLE_tick(); #endif /* * Interrupt routine - called from time_strobe event/signal handler. * That is called from host_timer_event */ #ifndef NTVDM /* protect ourselves from shafting a timer-channel in midst-program * ... the lock is controlled by timer_inb(),_outb() and _gate() */ if (timelock == LOCKED) { needtick = ~0; return; } #endif /* ideal time goes up by the number of timer clocks * equivalent to the interval between calls to 'time_tick' * (we assume these calls appear at perfectly spaced intervals ... * thus ignoring fluctuations on the host system) * The idea is to give an illusion of constant ticks here */ updateIdealTime(); pcu = &timers[0]; updateCounter(); /* * Counter 2 is only used for sound, or for timing very short * times. For timing short times (~100ms) we need accurate time * all the time. */ pcu = &timers[2]; updateCounter(); } /* * The timer low I/O functions that support the Intel 8253 Counter chip. * * The Counters are used by the PC in the following ways: * * Counter 0 - Bios time of day function * Counter 1 - Handler memory refresh * Counter 2 - Drive Speaker Interface (plus input on PPI) * */ /* * These control the timer chip. they are the interface between the * CPU and the timer chip. */ void SWTMR_gate IFN2(io_addr, port, half_word, value) { #ifdef NTVDM host_ica_lock(); #else timelock = LOCKED; #endif emu_8253 (port, GATE_SIGNAL, value); #ifndef PROD if (io_verbose & TIMER_VERBOSE) { sprintf(buf, "timer_gate() - sending %d to port 0x%x", value, port); trace(buf, DUMP_REG); } #endif #ifdef NTVDM host_ica_unlock(); #else checktimelock(); #endif } void SWTMR_inb IFN2(io_addr, port, half_word *, value) { #ifdef NTVDM host_ica_lock(); #else timelock = LOCKED; #endif pcu = &timers[port & 3]; if (!pcu->countlatched) readCounter(); switch (pcu->readState) { case UNREAD: switch (pcu->rl) { case RL_LSB: *value = pcu->latchvaluelsb; pcu->countlatched = 0; /* Unlatch the value read by inb() */ break; case RL_LMSB: *value = pcu->latchvaluelsb; pcu->readState = READMSB; /* Read LSB, next in read MSB. */ break; case RL_MSB: *value = pcu->latchvaluemsb; pcu->countlatched = 0; /* Unlatch the value read by inb() */ break; } break; case READMSB: *value = pcu->latchvaluemsb; pcu->countlatched = 0; /* Unlatch the value read by inb() */ pcu->readState = UNREAD; /* Read MSB, back to unread state. */ break; case READSTATUS: *value = pcu->latchstatus; pcu->readState = UNREAD; break; } #ifndef PROD if (io_verbose & TIMER_VERBOSE) { sprintf(buf, "timer_inb() - Returning %d(0x%x) for port 0x%x", *value, *value, port); trace(buf, DUMP_REG); } #endif #ifdef NTVDM host_ica_unlock(); #else checktimelock(); #endif } void SWTMR_outb IFN2(io_addr, port, half_word, value) { #if defined(NTVDM) || defined(GISP_SVGA) if (port == 0x4f) /* dead port used by PS/2 XGA bios for DAC delays */ return; #ifdef NTVDM host_ica_lock(); #endif #else timelock = LOCKED; #endif port = port & TIMER_BIT_MASK; if(port == 0x43) controlWordReg(value); else emu_8253 (port, WRITE_SIGNAL, value); #ifndef PROD if (io_verbose & TIMER_VERBOSE) { sprintf(buf, "timer_outb() - Value %d to port 0x%x", value, port); trace(buf, DUMP_REG); } #endif #ifdef NTVDM host_ica_unlock(); #else checktimelock(); #endif } /* --------------------------------------------------------------------------- */ /* */ /* Return the current DOS tick value based on Timer 0 */ /* */ /* --------------------------------------------------------------------------- */ #ifndef NTVDM GLOBAL ULONG get_DOS_ticks IFN0() { return( timers[0].Count ); } #endif /* NTVDM */ #ifdef NTVDM /* * called by host to update the current ideal time * after a block and resume. */ void ReinitIdealTime IFN1(struct host_timeval *, t) { /* * Currently these are all time stamped by the same thing! */ LastTimeCounterZero = timers[2].activationTime = timers[1].activationTime = timers[0].activationTime = idealtime = *t; /* * Clear out extra pending interrupts */ if (EoiPending) { EoiPending = 1; } EoiIntsPending = 0; } #endif /* **************************************************************************** */ /* **************************************************************************** */ /* **************************************************************************** */ #ifndef NTVDM /* check whether a time lock (set while application io is being serviced) * has blocked out an alarm signal ... call time_tick() to catch up if so * ... (time_tick() sets timelock = ~0, and just returns, if it sees the time * lock set */ LOCAL void checktimelock IFN0() { timelock = UNLOCKED; if (needtick) { needtick = 0; time_tick(); } } #endif /* * emulate the 8253 chip. * * emu_8253(port,signal,value) * * port - port address being accessed * (port & 3) gives A0,A1 lines * * signal - WRITE_SIGNAL (outb) or * GATE_SIGNAL (ppi TIM2GATESPK change) * * value - * for WRITE_SIGNAL, value = byte being written to chip * for GATE_SIGNAL, value = GATE_SIGNAL_LOW or * GATE_SIGNAL_RISE */ LOCAL void emu_8253 IFN3(io_addr, port, int, signal, half_word, value) { int A0A1; /* get address lines A0 and A1 */ A0A1 = port & 3; /* handle the access */ pcu = &timers[A0A1]; (pcu->state)(signal,value); } /* if a timer channel is unitialised, this is its associated * state function ... simply ignore all accesses in this state * A state transition from this state is only possible via * the procedure controlReg() */ /* handle write access to the control word register. * The documentation does not specify what happens if a mode * setting control word is issued while a counter is active. * In this model, we assume it resets the operation back to * the start of a new counter programming sequence */ /*ADE*/ #define SELECT_0 0x00 #define SELECT_1 0x01 #define SELECT_2 0x02 #define READ_BACK 0x03 LOCAL void controlWordReg IFN1(half_word, cwd) { int rl,m,channel; half_word select_bits; /* decode control word */ channel = (cwd & 0xc0) >> 6; if(channel == READ_BACK) { /* decode read back command */ select_bits = (cwd & 0xe) >> 1; /* first look for counters to latch */ if (!(cwd & 0x20)) { /* count bit low so latch selected counters */ if (select_bits & 0x01) { /* counter 0 */ pcu = &timers[0]; readCounter(); pcu->countlatched = 1; } if (select_bits & 0x02) { /* counter 1 */ pcu = &timers[1]; readCounter(); pcu->countlatched = 1; } if (select_bits & 0x04) { /* counter 2 */ pcu = &timers[2]; readCounter(); pcu->countlatched = 1; } } /* now look for the status latch */ if (!(cwd & 0x10)) { /* status bit low - status to be latched */ if (select_bits & 0x01) { /* counter 0 */ pcu = &timers[0]; latchStatusValue(); } if (select_bits & 0x02) { /* counter 1 */ pcu = &timers[1]; latchStatusValue(); } if (select_bits & 0x04) { /* counter 2 */ pcu = &timers[2]; latchStatusValue(); } } } else { pcu = &timers[channel]; rl = (cwd & 0x30) >> 4; /* are we simply latching the present count value, or are * programming up a new mode ?? */ if (rl == LATCH) { /* latching present count value */ readCounter(); pcu->countlatched = 1; return; } else { /* new mode */ if (pcu == &timers[0]) timer_int_enabled = FALSE; pcu->countlatched = 0; pcu->tc = 0; pcu->tickadjust = 0; #ifndef NTVDM pcu->microtick = 0; pcu->saveCount = 0; #endif m = (cwd & 0xe) >> 1; if(m > 5)m -= 4; /* Modes 6 and 7 don't exist - they are intepreted as modes 2 and 3 */ pcu->m = m; setTriggerCond(); setOutputAfterMode(); pcu->bcd = cwd & 1; pcu->rl = rl; pcu->actionOnWtComplete = CounterBufferLoaded; pcu->actionOnGateEnabled = CounterBufferLoaded; pcu->statePriorWt = pcu->state; pcu->state = waitingFor1stWrite; } } } /* latch status ready for reading */ LOCAL void latchStatusValue IFN0() { /* * Status byte is of format : * * |OUT|Null Count|RW1|RW0|M2|M1|M0|BCD| * */ /* NULL COUNT still only approximated. Who cares? */ pcu->latchstatus = (pcu->out.startLogicLevel<<7) | (pcu->newCount == AVAILABLE ? (1<<6) : 0) | (pcu->rl<<4) | (pcu->m<<1) | (pcu->bcd); pcu->readState = READSTATUS; } /* set up flag establishing type of trigger condition for * counter bassed on its mode */ LOCAL void setTriggerCond IFN0() { switch (pcu->m) { case RATE_GEN: case SQUAREWAVE_GEN: case SW_TRIG_STROBE: case INT_ON_TERMINALCOUNT: pcu->trigger = LEVEL; return; case PROG_ONESHOT: case HW_TRIG_STROBE: pcu->trigger = EDGE; return; } } /* transfer count buffer into counter */ LOCAL void loadCounter IFN0() { unsigned long modulo; #ifndef NTVDM IU32 maxback; #endif /* set counter */ /* get correct modulo to use for counter calculations */ modulo = (pcu->outbmsb << 8) | pcu->outblsb; if (pcu->bcd == BCD) { if(modulo) modulo = bcd_to_bin((word)modulo); else modulo = 10000L; } else if(!modulo)modulo = 0x10000L; /* Beware - Count and initialCount are different sizes, so don't merge the next two lines!! */ pcu->initialCount = modulo; pcu->Count = (word)modulo; /* * not at terminal count anymore, so reflect this fact by resetting * tc (which I think means "reached terminal count" */ pcu->tc = 0; pcu->newCount = USED; if(pcu == &timers[0]) { /* Get rid of pending interrupts - these may no longer me appropriate - eg. in Sailing */ ica_hw_interrupt_cancel(ICA_MASTER,CPU_TIMER_INT); #ifdef NTVDM RealTimeCountCounterZero = pcu->Count; #endif #ifndef NTVDM /* how many interrupts in MAX_BACK_SECS seconds? */ maxback = (1193180 * MAX_BACK_SECS) / modulo; if (maxback > max_backlog) { #ifndef PROD fprintf(trace_file, "setting max backlog to %d\n", maxback); #endif max_backlog = maxback; } #endif } #if defined(NTVDM) && !defined(PROD) if (NtTicTesting) { printf("Timer %d modulo=%lu %dHz\n", pcu-timers, modulo, 1193180/modulo); } #endif /*NTVDM & !PROD*/ } /* read counter into latch, ready for next read */ LOCAL void readCounter IFN0() { int countread; updateCounter(); #ifdef NTVDM /* * Timer Zero is a special case, as it is maintaned * by IdealInterval, and not RealTime. We must give * real time granularity */ countread = pcu == &timers[0] ? RealTimeCountCounterZero : pcu->Count; if (pcu->bcd == BCD) countread = bin_to_bcd(countread); #else if(pcu->bcd == BCD) countread = bin_to_bcd(pcu->Count); else countread = pcu->Count; #endif pcu->latchvaluemsb = countread >> 8; pcu->latchvaluelsb = countread & 0xff; sure_note_trace1(TIMER_VERBOSE,"reading count %d",pcu->Count); } /* active counter (mode 0) lost its gate ... gate has now * reappeared. Either continue 'active' counting (pre terminal count) * or continue decrementing, but with OUT signal at high indefinitely. */ LOCAL GATENABLED_FUNCTION resumeCounting0onGate IFN0() { if (pcu->freezeCounter) { pcu->freezeCounter = 0; timestamp(); } if (pcu->newCount == AVAILABLE) loadCounter(); if (!pcu->tc) { timestamp(); pcu->stateOnGate = Counting0; runCount(); } else pcu->state = Counting0; } LOCAL GATENABLED_FUNCTION resumeCounting0 IFN0() { int doadjust = 0; if (pcu->freezeCounter) { pcu->freezeCounter = 0; timestamp(); } if (pcu->newCount == AVAILABLE) { doadjust = 1; loadCounter(); } if (!pcu->tc) { pcu->stateOnGate = Counting0; runCount(); } else { pcu->state = Counting0; if (doadjust) pcu->Count -= pcu->tickadjust; } } LOCAL GATENABLED_FUNCTION resumeCounting_2_3_4_onGate IFN0() { /* for modes 2 and 3, ought to wait until counter * completes its current period, but we cant be as accurate * as that */ if (pcu->newCount == AVAILABLE) loadCounter(); if (pcu->m == RATE_GEN || pcu->m == SQUAREWAVE_GEN) pcu->stateOnGate = Counting_2_3; else pcu->stateOnGate = Counting_4_5; timestamp(); runCount(); } LOCAL GATENABLED_FUNCTION resumeCounting_2_3_4 IFN0() { /* for modes 2 and 3, ought to wait until counter * completes its current period, but we cant be as accurate * as that */ if (pcu->newCount == AVAILABLE) { pcu->delay = pcu->Count; loadCounter(); } if (pcu->m == RATE_GEN || pcu->m == SQUAREWAVE_GEN) pcu->stateOnGate = Counting_2_3; else pcu->stateOnGate = Counting_4_5; runCount(); } LOCAL GATENABLED_FUNCTION runCount IFN0() { unsigned long lowticks, hiticks; unsigned long adjustedCount; /* For count = 0 and BCD */ adjustedCount = timer_conv(pcu->Count); pcu->state = pcu->stateOnGate; switch (pcu->m) { case INT_ON_TERMINALCOUNT: outputWaveForm(pcu->delay,adjustedCount, INDEFINITE,STARTLO,NOREPEAT); return; case PROG_ONESHOT: loadCounter(); outputWaveForm(pcu->delay,adjustedCount, INDEFINITE,STARTLO,NOREPEAT); pcu->Count -= pcu->tickadjust; return; case RATE_GEN: loadCounter(); outputWaveForm(pcu->delay,1, adjustedCount-1,STARTHI,REPEAT); pcu->Count -= pcu->tickadjust; return; case SQUAREWAVE_GEN: loadCounter(); if (!(pcu->Count & 1)) lowticks = hiticks = adjustedCount >> 1; else { lowticks = (adjustedCount - 1) >> 1; hiticks = (adjustedCount + 1) >> 1; } outputWaveForm(pcu->delay,lowticks, hiticks,STARTHI,REPEAT); pcu->Count -= pcu->tickadjust; return; case SW_TRIG_STROBE: outputWaveForm(pcu->delay,1, adjustedCount,STARTHI,NOREPEAT); return; case HW_TRIG_STROBE: loadCounter(); outputWaveForm(pcu->delay,1, adjustedCount,STARTHI,NOREPEAT); return; } } /* return to state waiting for gate signal */ LOCAL void resumeAwaitGate IFN0() { pcu->actionOnWtComplete = timererror; pcu->state = awaitingGate; awaitingGate(GATE_SIGNAL,pcu->gate); } /* ========================= OUTPUT SIGNAL UTILITIES ====================== */ /* ========================= OUTPUT SIGNAL UTILITIES ====================== */ /* ========================= OUTPUT SIGNAL UTILITIES ====================== */ /* set state of output signal after a mode command has * been programmed (see pages 6-266 - 6-268 of Intel manual 231306-001) */ LOCAL void setOutputAfterMode IFN0() { switch (pcu->m) { case INT_ON_TERMINALCOUNT: outputLow( /*INDEFINITE*/ ); return; case PROG_ONESHOT: case RATE_GEN: case SQUAREWAVE_GEN: case SW_TRIG_STROBE: case HW_TRIG_STROBE: outputHigh( /*INDEFINITE*/ ); return; } } /* set output state low ... inform sound chip emulation if * channel 2 */ LOCAL void outputLow IFN0() { outputWaveForm(0,INDEFINITE,0,STARTLO,NOREPEAT); } /* set output state high ... inform sound chip emulation if * channel 2 */ LOCAL void outputHigh IFN0() { outputWaveForm(0,0,INDEFINITE,STARTHI,NOREPEAT); } /* when the wave form is deterministic, tell the sound emulation about it. * delay - if <>0, don't start this waveform for * this number of counter clocks. * lowclocks - the #.counter clocks to stay low for * hiclocks - the #.counter clocks to stay high for * (either parameter may be INDEFINITE duration) * lohi - 0 ==> start at low logic level * - <>0 ==> start at high logic level * repeat - 0 ==> don't * <>0 ==> repeat. * * (n.b; 1 counter clock period = 1.19318 usecs) */ LOCAL void outputWaveForm IFN5(unsigned int, delay, unsigned long, lowclocks, unsigned long, hiclocks, int, lohi, int, repeat) { #ifdef DOCUMENTATION int ch; #endif /* DOCUMENTATION */ pcu->out.startLogicLevel = lohi; pcu->out.repeatWaveForm = repeat; pcu->out.clocksAtLoLogicLevel = lowclocks; pcu->out.clocksAtHiLogicLevel = hiclocks; if (repeat == REPEAT) pcu->out.period = lowclocks + hiclocks; if (pcu == &timers[2]) { host_timer2_waveform(delay,lowclocks,hiclocks,lohi,repeat); } pcu->delay = 0; #ifdef DOCUMENTATION if (pcu==&timers[0]) ch = 0; if (pcu==&timers[1]) ch = 1; if (pcu==&timers[2]) ch = 2; sprintf(buf,"ch.%d waveform:delay %d lo %d hi %d lohi %d repeat %d\n", ch,delay,lowclocks,hiclocks,lohi,repeat); trace(buf,0); #endif /* DOCUMENTATION */ } /* time stamp the counter unit ... it is counting from this time */ LOCAL void timestamp IFN0() { #ifdef NTVDM /* update counter zero time stamp */ if (pcu == &timers[0]) { host_GetSysTime(&LastTimeCounterZero); } #else /* Initialise lastTicks before referencing it in updateCount() */ /* Makes Norton SYSINFO version 5.0 work on fast (HP) machines */ pcu->lastTicks = 0 ; #endif /* Go and get the time since it was activated */ (*pcu->getTime)(&pcu->activationTime); } LOCAL UNBLOCK_FUNCTION timererror IFN0() { always_trace0("time error!!!!"); } /* *************** COUNTER UPDATING FUNCTIONS FOR NON_IDLE COUNTERS **********/ /* *************** COUNTER UPDATING FUNCTIONS FOR NON_IDLE COUNTERS **********/ /* *************** COUNTER UPDATING FUNCTIONS FOR NON_IDLE COUNTERS **********/ /* ************************ STATE FUNCTIONS ********************************* */ /* ************************ STATE FUNCTIONS ********************************* */ /* ************************ STATE FUNCTIONS ********************************* */ /* * STATE_FUNCTION uninit(); * STATE_FUNCTION awaitingGate(); * STATE_FUNCTION waitingFor1stWrite(); * STATE_FUNCTION waitingFor2ndWrite(); * STATE_FUNCTION Counting0(); * STATE_FUNCTION Counting_4_5(); * STATE_FUNCTION Counting1(); * STATE_FUNCTION Counting_2_3(); */ LOCAL STATE_FUNCTION uninit IFN2(int, signal, half_word, value) { if (signal == GATE_SIGNAL) pcu->gate = value; } LOCAL STATE_FUNCTION awaitingGate IFN2(int, signal, half_word, value) { switch (signal) { case GATE_SIGNAL: pcu->gate = value; if (value == GATE_SIGNAL_LOW) return; /* this is pathological ... should never have to * wait for gate for channel 0 */ if (pcu == &timers[0]) timer_int_enabled = TRUE; (pcu->actionOnGateEnabled)(); return; case WRITE_SIGNAL: pcu->actionOnWtComplete = resumeAwaitGate; pcu->statePriorWt = pcu->state; waitingFor1stWrite(signal,value); return; } } /* * Perform first of (probably) 2 writes. * This is either called directly when some other state is * written to, or set up as the current state when the timer mode is changed. * If the timer is in 'read/write 2 bytes' mode, set the timer state * to 'waiting for second byte'. */ LOCAL STATE_FUNCTION waitingFor1stWrite IFN2(int, signal, half_word, value) { switch (signal) { case GATE_SIGNAL: /* remember gate signal state */ pcu->gate = value; return; case WRITE_SIGNAL: switch (pcu->rl) { case RL_LSB: pcu->outblsb = value; /* Zero the most signifcant byte. */ pcu->outbmsb = 0; pcu->newCount = AVAILABLE; WtComplete(); return; case RL_LMSB: pcu->outblsb = value; pcu->state = waitingFor2ndWrite; return; case RL_MSB: pcu->outbmsb = value; /* Zero the least signifcant byte. */ pcu->outblsb = 0; pcu->newCount = AVAILABLE; WtComplete(); return; } } } /* * Write second byte to timer and unblock it. */ LOCAL STATE_FUNCTION waitingFor2ndWrite IFN2(int, signal, half_word, value) { switch (signal) { case GATE_SIGNAL: /* remember gate signal state */ pcu->gate = value; return; case WRITE_SIGNAL: pcu->newCount = AVAILABLE; pcu->outbmsb = value; WtComplete(); return; } } /* * the full complement of bytes has been read/loaded. During this * phase, the gate signal might have been removed ... if so, * change state to wait for an enabling gate signal. Otherwise * take appropriate action to get back to previous state */ LOCAL void WtComplete IFN0() { if (pcu->gate == GATE_SIGNAL_LOW && pcu->trigger == LEVEL) { pcu->state = awaitingGate; awaitingGate(GATE_SIGNAL, pcu->gate); } else (pcu->actionOnWtComplete)(); } /* active counter (Interrupt on Terminal Count) * if the gate is lost, then * set the output indefinitely high if at terminal count, or * indefinitely low if still counting (i.e; extend current low * level signal duration). * if count reprogrammed during this time, this new count will be * used on next trigger (gate). * else * if new count programmed, stop counter on receiving 1st byte. * start new count on second byte. (done by resumeCounting0()) */ LOCAL STATE_FUNCTION Counting0 IFN2(int, signal, half_word, value) { pcu->actionOnGateEnabled = resumeCounting0onGate; pcu->actionOnWtComplete = resumeCounting0; switch (signal) { case GATE_SIGNAL: if (value == GATE_SIGNAL_HIGH) return; /* we're about to freeze timer channel ... * get an up to date count. This might change the * state of the counter. */ updateCounter(); pcu->gate = value; if (pcu->tc) outputHigh(); else outputLow(); pcu->state = awaitingGate; return; case WRITE_SIGNAL: pcu->freezeCounter = 1; updateCounter(); if (pcu->tc) outputHigh(); else outputLow(); pcu->statePriorWt = pcu->state; waitingFor1stWrite(signal,value); return; } } /* active counter (programmable One-Shot) * if cvounter loses its gate, then simply wait for retrigger * to start off count again. */ LOCAL STATE_FUNCTION Counting1 IFN2(int, signal, half_word, value) { pcu->actionOnGateEnabled = startCounting; pcu->actionOnWtComplete = resumeCounting_1_5; switch (signal) { case GATE_SIGNAL: /* ignore transition to low on trigger. * any rising edge retriggers the counter. */ if (value == GATE_SIGNAL_LOW) return; pcu->gate = GATE_SIGNAL_HIGH; pcu->stateOnGate = Counting1; timestamp(); runCount(); return; case WRITE_SIGNAL: pcu->statePriorWt = pcu->state; waitingFor1stWrite(signal,value); return; } } LOCAL UNBLOCK_FUNCTION resumeCounting_1_5 IFN0() { /* if terminal count has been reached, wait for the next * trigger ... any new count value programmed will be used * then. * Otherwise, even if new count is available, it still won't * be used until next trigger */ if (pcu->gate == GATE_SIGNAL_RISE) { pcu->state = Counting1; if (pcu->m == HW_TRIG_STROBE) pcu->state = Counting_4_5; return; } if (pcu->tc) pcu->state = awaitingGate; else { pcu->state = Counting1; if (pcu->m == HW_TRIG_STROBE) pcu->state = Counting_4_5; } } LOCAL STATE_FUNCTION Counting_2_3 IFN2(int, signal, half_word, value) { pcu->actionOnGateEnabled = resumeCounting_2_3_4_onGate; pcu->actionOnWtComplete = resumeCounting_2_3_4; switch (signal) { case GATE_SIGNAL: if (value == GATE_SIGNAL_HIGH) return; /* we're about to freeze timer channel ... * get an up to date count. This might change the * state of the counter. */ updateCounter(); pcu->gate = value; outputHigh(); pcu->state = awaitingGate; return; case WRITE_SIGNAL: pcu->statePriorWt = pcu->state; waitingFor1stWrite(signal,value); return; } } LOCAL STATE_FUNCTION Counting_4_5 IFN2(int, signal, half_word, value) { pcu->actionOnGateEnabled = resumeCounting_2_3_4_onGate; pcu->actionOnWtComplete = resumeCounting_2_3_4; if (pcu->m == HW_TRIG_STROBE) { pcu->actionOnGateEnabled = resumeCounting_1_5; pcu->actionOnWtComplete = resumeCounting_1_5; } switch (signal) { case GATE_SIGNAL: if (value == GATE_SIGNAL_HIGH) return; /* we're about to freeze timer channel ... * get an up to date count. This might change the * state of the counter. */ updateCounter(); pcu->gate = value; outputHigh(); pcu->state = awaitingGate; return; case WRITE_SIGNAL: pcu->statePriorWt = pcu->state; waitingFor1stWrite(signal,value); return; } } /* ****************** UNBLOCK FUNCTIONS ************************************* */ /* ****************** UNBLOCK FUNCTIONS ************************************* */ /* ****************** UNBLOCK FUNCTIONS ************************************* */ /* upon reaching this state, the timer's count register can be * loaded (as per 'rl') ... and it can potentially start counting * depending upon the state of its gate signal. * If it can begin counting, then setup the output waveform that will * appear at the timer channel's OUT signal. * (If this channel is for sound, the waveform is exactly known) */ LOCAL UNBLOCK_FUNCTION CounterBufferLoaded IFN0() { unsigned long lowticks, hiticks, adjustedCount; pcu->actionOnWtComplete = timererror; loadCounter(); #ifdef DOCUMENTATION /* * Output state of timer if tracing. * Currently dumpCounter has no effect, so just leave this in * case anyone wants to implement it properly. */ if (io_verbose & TIMER_VERBOSE) { dumpCounter(); } #endif /* DOCUMENTATION */ if (pcu->gate != GATE_SIGNAL_LOW) { if (pcu == &timers[0]) timer_int_enabled = TRUE; timestamp(); adjustedCount = timer_conv(pcu->Count); switch (pcu->m) { case INT_ON_TERMINALCOUNT: outputWaveForm(pcu->delay,adjustedCount, INDEFINITE,STARTLO,NOREPEAT); pcu->Count -= pcu->tickadjust; pcu->state = Counting0; return; case PROG_ONESHOT: outputWaveForm(pcu->delay,adjustedCount, INDEFINITE,STARTLO,NOREPEAT); pcu->Count -= pcu->tickadjust; pcu->state = Counting1; return; case RATE_GEN: outputWaveForm(pcu->delay,1, adjustedCount-1,STARTHI,REPEAT); pcu->Count -= pcu->tickadjust; pcu->state = Counting_2_3; return; case SQUAREWAVE_GEN: if (!(pcu->Count & 1)) lowticks = hiticks = adjustedCount >> 1; else { lowticks = (adjustedCount - 1) >> 1; hiticks = (adjustedCount + 1) >> 1; } outputWaveForm(pcu->delay,lowticks, hiticks,STARTHI,REPEAT); pcu->Count -= pcu->tickadjust; pcu->state = Counting_2_3; return; case SW_TRIG_STROBE: case HW_TRIG_STROBE: outputWaveForm(pcu->delay,1, adjustedCount,STARTHI,NOREPEAT); pcu->Count -= pcu->tickadjust; pcu->state = Counting_4_5; return; } } else if (pcu == &timers[0]) timer_int_enabled = FALSE; pcu->state = awaitingGate; pcu->actionOnGateEnabled = startCounting; switch (pcu->m) { case INT_ON_TERMINALCOUNT: pcu->stateOnGate = Counting0; return; case PROG_ONESHOT: pcu->stateOnGate = Counting1; return; case RATE_GEN: case SQUAREWAVE_GEN: pcu->stateOnGate = Counting_2_3; return; case SW_TRIG_STROBE: case HW_TRIG_STROBE: pcu->stateOnGate = Counting_4_5; return; } } LOCAL void startCounting IFN0() { timestamp(); runCount(); } #ifndef NTVDM /* calculate the number of 8253 clocks elapsed since counter was last * activated */ LOCAL unsigned long clocksSinceCounterActivated IFN1(struct host_timeval *, now) { struct host_timeval *first; register unsigned long usec_val, nclocks; register unsigned int secs; first = &pcu->activationTime; (*pcu->getTime)(now); /* calculate #.usecs elapsed */ secs = (int)(now->tv_sec - first->tv_sec); switch (secs) { case 0: usec_val = now->tv_usec - first->tv_usec; #ifndef PROD if (io_verbose & TIMER_VERBOSE) if ( usec_val == 0 ) trace("clocksSinceCounterActivated() == 0 !", 0); #endif nclocks = (usec_val * TIMER_CLOCK_DENOM) / TIMER_CLOCK_NUMER; break; case 1: usec_val = 1000000L + now->tv_usec - first->tv_usec; nclocks = (usec_val * TIMER_CLOCK_DENOM) / TIMER_CLOCK_NUMER; break; default: nclocks = ((now->tv_usec - first->tv_usec) * TIMER_CLOCK_DENOM) / TIMER_CLOCK_NUMER; nclocks += secs * (1000000L * TIMER_CLOCK_DENOM / TIMER_CLOCK_NUMER); #ifndef PROD if (io_verbose & TIMER_VERBOSE) { sprintf(buf, "timer[%d]: %d seconds have passed!", pcu-timers, secs); trace(buf, DUMP_NONE); } #endif break; } return nclocks; } #endif LOCAL void updateCounter IFN0() { #ifndef NTVDM unsigned long nticks; struct host_timeval now; #endif /* NTVDM */ unsigned long wrap; switch (pcu->m) { case INT_ON_TERMINALCOUNT: case RATE_GEN: case SQUAREWAVE_GEN: if (pcu->gate == GATE_SIGNAL_LOW) return; #ifdef NTVDM wrap = updateCount(); #else nticks = clocksSinceCounterActivated(&now); updateCount(nticks, &wrap,&now); #endif if (wrap) pcu->tickadjust = pcu->Count; if (pcu->m == INT_ON_TERMINALCOUNT && wrap) pcu->tc = 1; if (pcu == &timers[0] && wrap){ if (pcu->m != INT_ON_TERMINALCOUNT) issueIREQ0((unsigned int)wrap); else issueIREQ0(1); #ifdef HUNTER timer_batch_count = wrap; #endif } return; case PROG_ONESHOT: case SW_TRIG_STROBE: case HW_TRIG_STROBE: if (pcu->tc) return; #ifdef NTVDM wrap = updateCount(); #else nticks = clocksSinceCounterActivated(&now); updateCount(nticks, &wrap,&now); #endif if (wrap) { pcu->Count = 0; pcu->tc = 1; #ifdef NTVDM RealTimeCountCounterZero = 0; #endif } #ifdef HUNTER if (pcu == &timers[0]){ timer_batch_count = wrap; } #endif return; } } #ifndef NTVDM #ifndef DELAYED_INTS /* * timer_no_longer_too_soon() - this is the function invoked by the quick event manager * "HOST_TIMER_TOOLONG_DELAY" instructions after a hardware interrupt is generated. It * clears the variable "too_soon_after_previous" to allow more interrupts to be * generated and kicks off an immediate one if any have been suppressed this time. */ LOCAL void timer_no_longer_too_soon IFN1(long, dummy) { UNUSED(dummy); too_soon_after_previous = FALSE; if (ticks_lost_this_time){ /* At least one tick was suppressed... so send another one immediately */ timer_generate_int (1); } } /* * timer_generate_int() -The routine to generate a single timer hardware interrupt (and to * schedule a quick event timer call on itself to have the remaining pending interrupts * generated at a later time). * */ LOCAL void timer_generate_int IFN1(long, n) { #if !(defined(GISP_CPU) || defined(CPU_40_STYLE)) if (getPE()){ /* Prot mode tick... */ #ifndef PROD if (!hack_active){ SAVED BOOL first=TRUE; always_trace0 ("PM timer Hack activated."); if (first){ always_trace1 ("Min # instrs between interrupts = %d", HOST_TIMER_TOOLONG_DELAY); always_trace1 (" Nominal instrs_per_tick = %d", instrs_per_tick); always_trace1 (" adjusted instrs_per_tick = %d", adj_instrs_per_tick); always_trace1 (" # rm instrs before full speed = %d", n_rm_instrs_before_full_speed); always_trace1 (" rm ticks before full speed = %d", adj_n_real_mode_ticks_before_full_speed); first = FALSE; } } #endif hack_active = TRUE; real_mode_ticks_in_a_row = 0; }else{ /* Real Mode Tick... */ if (hack_active){ real_mode_ticks_in_a_row++; if (real_mode_ticks_in_a_row >= adj_n_real_mode_ticks_before_full_speed){ hack_active = FALSE; always_trace0 ("PM timer Hack deactivated."); } } } #endif /* ! (GISP_CPU||CPU_40_STYLE) */ if (hack_active){ if (!too_soon_after_previous){ ica_hw_interrupt(ICA_MASTER,CPU_TIMER_INT, 1); too_soon_after_previous = TRUE; ticks_lost_this_time = FALSE; add_q_event_i(timer_no_longer_too_soon,HOST_TIMER_TOOLONG_DELAY,0); }else{ ticks_lost_this_time = TRUE; #ifndef PROD ticks_ignored++; if (!(ticks_ignored & 0xFF)){ always_trace0 ("another 256 ticks lost!"); } #endif } }else{ #ifndef GISP_CPU ica_hw_interrupt(ICA_MASTER,CPU_TIMER_INT, 1); } } #else /* GISP_CPU */ #if defined(IRET_HOOKS) if (!HostDelayTimerInt(n)) { /* no host need to delay this timer int, so generate one now. */ ica_hw_interrupt(ICA_MASTER,CPU_TIMER_INT, 1); } #else /* !IRET_HOOKS */ /* GISP_CPU doesn't use quick events so use ica_hw_interrupt(,,n). */ ica_hw_interrupt(ICA_MASTER,CPU_TIMER_INT, n); #endif /* IRET_HOOKS */ } } #endif /* GISP_CPU */ #endif /* DELAYED_INTS */ #else /* NTVDM */ /* * TimerGenerateMultipleInterrupts * */ void TimerGenerateMultipleInterrupts(long n) { if (!EoiPending) { EoiPending += n; ica_hw_interrupt(ICA_MASTER,CPU_TIMER_INT, n); } else { if (n > 5 && (dwWNTPifFlags & COMPAT_TIMERTIC)) { n = 5; } if (EoiIntsPending/n < 19) { // less than a second behind ? EoiIntsPending += n; } else { EoiIntsPending++; } } } #ifndef MONITOR /* On RISC ports, it is dangerous to call getIF from a non-CPU thread, so we replace the call with a peek at the global 'EFLAGS' variable until the CPU emulator getIF is made safe. */ #define getIF() (GLOBAL_EFLAGS & 0x200) #endif /* !MONITOR */ /* timer_generate_int NTVDM * */ void timer_generate_int (void) { word lo, hi, wrap; /* * For Nt port see if we really need to generate * an int, checking if an app has hooked real-mode * or protect-mode vectors. * * If we don't need to do it, then update the bios * Data tic count directly. * * WARNING according to sfrost it is not safe to * use sas, because of multithreading. */ hi = * (word *)(Start_of_M_area+0x1c*4+2); lo = * (word *)(Start_of_M_area+0x1c*4); wrap = (word) *(half_word *)(Start_of_M_area + ((ULONG)hi << 4) + lo); if (!getIF() || ((hi != TimerInt1CSeg || lo != TimerInt1COff) && wrap != 0xcf) || *(word *)(Start_of_M_area+0x08*4+2) != TimerInt08Seg || *(word *)(Start_of_M_area+0x08*4) != TimerInt08Off ) { TimerGenerateMultipleInterrupts(1); } else { /* update Bios Data Area directly */ ++(*(double_word *)(Start_of_M_area + TIMER_LOW)); /* Wrap at 24 hours ? */ if (*(double_word *)(Start_of_M_area + TIMER_LOW) == 0x1800b0) { *(word *)(Start_of_M_area + TIMER_LOW) = 0; *(word *)(Start_of_M_area + TIMER_HIGH) = 0; *(half_word *)(Start_of_M_area+TIMER_OVFL)=1; } /* decr motor count */ --(*(half_word *)(Start_of_M_area + MOTOR_COUNT)); /* if motor count went to zero turn off the motor */ if (!*(half_word *)(Start_of_M_area + MOTOR_COUNT)) { *(half_word *)(Start_of_M_area + MOTOR_STATUS) &= 0xF0; fla_outb(DISKETTE_DOR_REG, 0x0C); } if (EoiDelayInUse && !(--EoiDelayInUse)) { host_DelayHwInterrupt(CPU_TIMER_INT, 0, 0xFFFFFFFF); } } } /* TimerEoiHook * * EoiHook for the timer interrupt used to regulate the flow of * timer interrupts to ensure that ints are not generated too * close together. This routine is invoked by the ica EoiHook * callbacks. * */ void TimerEoiHook(int IrqLine, int CallCount) { if (EoiPending) --EoiPending; if (CallCount < 0) { // interrupts were canceled EoiIntsPending = 0; EoiPending = 0; } else if (CallCount) { EoiDelayInUse = 100; host_DelayHwInterrupt(CPU_TIMER_INT, 0, timer_delay_size ); } else if (EoiIntsPending) { EoiDelayInUse = 100; if (host_DelayHwInterrupt(CPU_TIMER_INT, EoiIntsPending, timer_delay_size )) { EoiPending = EoiIntsPending; } EoiIntsPending = 0; } else { if (EoiDelayInUse && !(--EoiDelayInUse)) { host_DelayHwInterrupt(CPU_TIMER_INT, 0, 0xFFFFFFFF); } } } #endif /* NTVDM */ #ifndef NTVDM /* * Handle a cranked up timer where multiple interrupts are * required per tick. Schedule 'n' ints over a tick. Period * stored in quick event argument rather than in global. */ LOCAL void timer_multiple_ints IFN1(long, num_ints) { /* generate timer int */ timer_generate_int(1); /* one less to do */ num_ints --; /* any more arrived whilst we were q_ expiring? */ num_ints += more_timer_mult; more_timer_mult = 0; /* throw away ints that are going to take more than MAX_BACK_SECS * to clear up. (!!!!) */ if (num_ints > max_backlog) { num_ints = max_backlog; } /* schedule next int (if required) */ if (num_ints == 0) { active_int_event = FALSE; /* 1.193180 usecs per clock */ max_backlog = (1193180 * MAX_BACK_SECS) / timers[0].initialCount; } else /* more work to do */ { /* set new quick_ev off - delay determined by timer wrap rate */ add_q_event_t(timer_multiple_ints, timer_multiple_delay, num_ints); } } #endif LOCAL void issueIREQ0 IFN1(unsigned int, n) { IU16 int_delay; /* delay before handling wrapped int */ #ifndef PROD static pig_factor = 0; static time_factor = 0; #endif #ifdef PIG extern IBOOL ccpu_pig_enabled; #endif #ifndef PROD if ( time_factor == 0 ) { char *env; env = host_getenv("TIMER_FACTOR"); if ( env ) time_factor = atoi(env); if ( time_factor == 0 ) time_factor = 1; #ifdef PIG if ( pig_factor == 0 ) { env = host_getenv("PIG_TIMER_FACTOR"); if ( env ) pig_factor = atoi(env); if ( pig_factor == 0 ) pig_factor = 10; } #else pig_factor = 1; #endif } #endif if (ticks_blocked == 0) { #ifndef PROD #ifdef PIG if ( ccpu_pig_enabled ) { ticks_blocked = pig_factor-1; } else #endif ticks_blocked = time_factor-1; #endif if (timer_int_enabled) { #ifdef DELAYED_INTS ica_hw_interrupt_delay(ICA_MASTER,CPU_TIMER_INT, n, HOST_TIMER_INT_DELAY); #else /* !DELAYED_INTS */ #ifdef NTVDM if (n > 0) timer_delay_size= HOST_IDEAL_ALARM/(n+1); if (n == 1) { timer_generate_int(); } else if (n > 1){ TimerGenerateMultipleInterrupts(n); } #else /* !NTVDM */ /* if we've got a quick event running, add to its workload */ if (active_int_event) { /* spread interrupts through system tick */ int_delay = SYSTEM_TICK_INTV / (n + 1); if (int_delay < timer_multiple_delay) timer_multiple_delay = int_delay; more_timer_mult += n; } else { /* ensure multiple delay restarts at sensible speed */ timer_multiple_delay = SYSTEM_TICK_INTV >> 1; if (n == 1) { timer_generate_int(1); } else { /* spread interrupts through system tick */ timer_generate_int(1); timer_multiple_delay = SYSTEM_TICK_INTV / n; active_int_event = TRUE; add_q_event_t(timer_multiple_ints, timer_multiple_delay, n-1); } } #endif /* !NTVDM */ #endif /* !DELAYED_INTS */ } } else if (ticks_blocked > 0) { ticks_blocked--; } } #ifdef NTVDM unsigned long clocksSinceCounterUpdate(struct host_timeval *pCurrTime, struct host_timeval *pLastTime, word *pCounter ) { unsigned long clocks, wrap, usecs; /* Calculate usecs elapsed and clocks elapsed since last update * * For NT port timer zero's IdealInterval is exact to a modulo * of 65536, for efficiency and accuracy we stick with the exact * number of clocks between IdealIntervals. */ if (pCounter == &timers[0].Count) { /* update quick way for IdealTime */ if (pCurrTime->tv_sec != pLastTime->tv_sec || pCurrTime->tv_usec != pLastTime->tv_usec ) { *pLastTime = *pCurrTime; return 65536/pcu->initialCount; } else { usecs = clocks = 0; } } else { /* calc diff in usecs and clocks elapsed */ usecs = (unsigned long)(pCurrTime->tv_sec - pLastTime->tv_sec); if (!usecs) { usecs = pCurrTime->tv_usec - pLastTime->tv_usec; } else if (usecs == 1) { usecs = 1000000L - pLastTime->tv_usec + pCurrTime->tv_usec; } else { usecs = pCurrTime->tv_usec - pLastTime->tv_usec + (pCurrTime->tv_sec - pLastTime->tv_sec) * 1000000L; } /* ... clocks elapsed 1.193180 usecs per clock * * However, app time is not real time so round down * a teency bit by truncating the "180" * * clocks = (usecs * 1193)/1000 + (usecs * 180)/1000000; */ clocks = (usecs * 1193)/1000; } /* how many times did counter wrap ? */ wrap = clocks/pcu->initialCount; /* calc ticks from elapsed clocks */ clocks = clocks && pcu->initialCount ? clocks % pcu->initialCount : 0; *pCounter = (word) (pcu->initialCount - clocks); /* if the count wrapped reset Last Update time stamp */ if (wrap) { *pLastTime = *pCurrTime; if ((ULONG)pLastTime->tv_usec < usecs) { pLastTime->tv_sec--; pLastTime->tv_usec = 1000000L + pLastTime->tv_usec - usecs; } else { pLastTime->tv_usec -= usecs; } } return wrap; } unsigned long updateCount(void) { unsigned long wrap; struct host_timeval curr; /* * For timer zero, update real time count, time stamp */ if (pcu == &timers[0]) { host_GetSysTime(&curr); clocksSinceCounterUpdate(&curr, &LastTimeCounterZero, &RealTimeCountCounterZero); } /* * Update the pcu count, time stamps */ (*pcu->getTime)(&curr); wrap = clocksSinceCounterUpdate(&curr, &pcu->activationTime, &pcu->Count); return wrap; } #else LOCAL void updateCount IFN3(unsigned long, ticks, unsigned long *, wrap, struct host_timeval *, now) { unsigned long modulo = pcu->initialCount; /* * PCLABS version 4.2 uses counter 2 (the sound channel) to * time around 45 ms on a 8MHz 286. On SoftPC we cannot * guarantee to go that fast, and so we must wind the tick * rate down to ensure that the counter does not wrap. How much * we wind down the tick rate is host dependent. The object is * to get the test finishing in less than * host_timer_2_frig_factor/18 secs. * * host_timer_2_frig_factor is now redundant. 28/4/93 niall. */ if (pcu == &timers[2]) { /* * PMINFO uses ~counter, so one tick becomes 0. * 2 ticks is just as good. Avoid guessing (frig_factor!). */ if ((ticks - pcu->lastTicks) == 0) ticks = 2; } /* if the counter has been read too quickly after its last * access, then the host may not show any visible change in * host time ... in which case we just guess at a suitable * number of elapsed ticks. */ if ((long)(ticks - pcu->lastTicks) <= 0){ ticks = guess(); }else{ throwaway(); pcu->lastTicks = ticks; } /* the counter holds some count down value ... * if the number of 8253 clocks elapsed exceeds this amount * then the counter must have wrapped */ if ( ticks < modulo ) { *wrap = 0; } else { *wrap = 1; ticks -= modulo; if ( pcu->m == INT_ON_TERMINALCOUNT ) modulo = 0x10000L; if ( ticks >= modulo ) { *wrap += ticks/modulo; ticks %= modulo; #ifndef PROD if (io_verbose & TIMER_VERBOSE) if ( pcu->m == INT_ON_TERMINALCOUNT ) { sprintf(buf, "%lx wraps for timer[%d]", *wrap, pcu-timers); trace(buf, DUMP_NONE); } #endif } } /* calculate new counter value */ pcu->Count = (word)(modulo-ticks); /* calculate time at which last wrap point occurred, and * use this to stamp the counter */ if (*wrap) setLastWrap((unsigned int)(modulo-pcu->Count),now); } /* calculate time for last wrap around of counter, and use this * to mark counter activation time. */ LOCAL void setLastWrap IFN2(unsigned int, nclocks, struct host_timeval *, now) { struct host_timeval *stamp; unsigned long usecs; stamp = &pcu->activationTime; *stamp = *now; usecs = ((unsigned long)nclocks * TIMER_CLOCK_NUMER) / TIMER_CLOCK_DENOM; if (stamp->tv_usec < usecs) { stamp->tv_sec--; stamp->tv_usec += 1000000L; } stamp->tv_usec -= usecs; pcu->lastTicks = nclocks; } #endif /* NTVDM*/ #ifndef NTVDM /* * If the host timer gives the same result as last time it was called, * we must give the illusion that time has passed. * The algorithm uesd is to keep track of how often we have to guess * between host timer ticks, and to assume that guesses should be evenly * distributed between host ticks, ie. the time between two guesses should be: * Guess ticks = hosttickinterval/nguesses * If we find that the total guessed time is getting dangerously near to being the time * between two host ticks, we start reducing the guess tick interval so as to avoid * guessing a total time that is too large. * * From observation, applications use the timer in both coarse and fine 'modes'. * The coarse mode is the hardware interrupt handler and then in between * interrupts, the timer is polled to detect time passing (hence guess below). * The fine mode polling does not commence until some portion of the tick time * has elapsed - probably as the coarse int handler will consume some of the * time. If guess() bases its timefrig over the whole tick, then given the * above behaviour, a polling counter can reach tick end time before the int * is delivered. This can fool applications (e.g. Win 3.1 VTD) into having * time pass at the wrong rate (approx double for Win 3.1). By calculating * the timefrig on most (7/8) of the tick period, this problem is avoided. */ LOCAL unsigned long guess IFN0() { if (!pcu->microtick) { pcu->saveCount = pcu->Count; pcu->timeFrig = ((ticksPerIdealInterval * 7) >> 3) / pcu->guessesPerHostTick; /* guesses over 7/8 of tick */ #ifndef PROD if (io_verbose & TIMER_VERBOSE) { sprintf(buf, "guess init timer[%d]: timeFrig = %lx", pcu-timers, pcu->timeFrig); trace(buf, DUMP_NONE); } #endif } if(pcu->guessesSoFar++ > pcu->guessesPerHostTick) { /* * PC Program is reading the timer more often than in the last timer tick, so need to * decay timeFrig to avoid too much 'time' passing between host ticks */ pcu->timeFrig = (pcu->timeFrig >> 1) + 1; #ifndef PROD if (io_verbose & TIMER_VERBOSE) { sprintf(buf, "guess decay: timeFrig = %lx", pcu->timeFrig); trace(buf, DUMP_NONE); } #endif } pcu->microtick += pcu->timeFrig; return (pcu->microtick + pcu->lastTicks); } /* * After a few (maybe none) guesses, the host timer has finally ticked. * Try and work out a good frig factor for the next few guesses, based * on the number of guesses we had to make. */ LOCAL void throwaway IFN0() { pcu->guessesPerHostTick = (pcu->guessesPerHostTick + pcu->guessesSoFar)>>1; pcu->guessesSoFar = 2; /* Handy to count from 2! */ if (!pcu->microtick) return; #ifndef PROD if (io_verbose & TIMER_VERBOSE) { sprintf(buf, "throwaway: guessesPerHostTick = %d", (int)pcu->guessesPerHostTick); trace(buf, DUMP_NONE); } #endif pcu->Count = pcu->saveCount; pcu->microtick = 0; } #endif /* ndef NTVDM */ LOCAL unsigned short bin_to_bcd IFN1(unsigned long, val) { register unsigned short m, bcd, i; m = (short)(val % 10000L); bcd = 0; for (i=0; i<4; i++) { bcd = bcd | ((m % 10) << (i << 2)); m /= 10; } return(bcd); } /* * convert 4 decade bcd value to binary */ LOCAL word bcd_to_bin IFN1(word, val) { register word bin, i, mul; bin = 0; mul = 1; for (i=0; i<4; i++) { bin += (val & 0xf) * mul; mul *= 10; val = val >> 4; } return (bin); } /* * this routine returns the number of timer clocks equivalent * to the input count, allowing for timer mode and down count. */ LOCAL unsigned long timer_conv IFN1(word, count) { if (!count) { if (pcu->bcd == BCD) return 10000L; else return 0x10000L; } else return (unsigned long)count; } /* this routine returns the current ideal time value ... * this is a very coarse resolution time ... it only changes * per call to time_tick(). It does however represent what the * system time would be given 100% accurate time signal * ... this routine gets used for time-stamping if time_tick() * is active, otherwise time-stamping is done using * getHostSysTime() ... see below. */ LOCAL void getIdealTime IFN1(struct host_timeval *, t) { t->tv_sec = idealtime.tv_sec; t->tv_usec = idealtime.tv_usec; } /* update our ideal time by the period (in usecs) between timer signals * from the host as though these were delivered 100% accurately */ LOCAL void updateIdealTime IFN0() { idealtime.tv_usec += idealInterval; if (idealtime.tv_usec > 1000000L) { idealtime.tv_usec -=1000000L; idealtime.tv_sec++; } } #ifndef NTVDM /* get current host system time ... used for time-stamping and * querying during io from Intel application */ LOCAL void getHostSysTime IFN1(struct host_timeval *, t) { struct host_timezone dummy; host_gettimeofday(t, &dummy); /* * check that we haven't gone back in time. */ if (t->tv_sec < idealtime.tv_sec || (t->tv_usec < idealtime.tv_usec && t->tv_sec == idealtime.tv_sec)) { /* * The real time has fallen behind the ideal time. * This should never happen... If it does we must * stay at the ideal time. */ #ifndef PROD #ifndef PIG sprintf(buf,"TIME WARP!!"); trace(buf,0); #endif #endif *t = idealtime; } } #endif /* !NTVDM */ /* * Used to temporarily stop the timer interrupts. PCLABS bench29 * causes this to be called iff a 80287 is being used. */ void axe_ticks IFN1(int, ticks) { #ifndef PROD /* * No need to axe ticks if timers are disabled by toff2 (if * ticks_blocked is negative) */ if (ticks_blocked >=0) #endif /* PROD */ ticks_blocked = ticks; } /* * Initialization code */ #ifdef SEGMENTATION /* * The following #include specifies the code segment into which this * module will by placed by the MPW C compiler on the Mac II running * MultiFinder. */ #include "SOFTPC_INIT.seg" #endif #ifdef SYNCH_TIMERS GLOBAL void IdealTimeInit IFN0() { extern IU32 Q_timer_restart_val; idealInterval = Q_timer_restart_val; #else LOCAL void IdealTimeInit IFN0() { idealInterval = HOST_IDEAL_ALARM; #endif #ifndef NTVDM getHostSysTime(&idealtime); ticksPerIdealInterval = (idealInterval * TIMER_CLOCK_DENOM) / TIMER_CLOCK_NUMER; #endif } LOCAL void Timer_init IFN0() { int i; for (i=0; i<3; i++) counter_init(&timers[i]); #ifdef NTVDM timers[0].getTime = getIdealTime; /* Use the 'ideal' time for timer 0, ie. calls to time_tick */ timers[1].getTime = host_GetSysTime; /* We don't really expect anyone to use timer 1 */ timers[2].getTime = host_GetSysTime; /* Use real host time for timer 2 */ #else timers[0].getTime = getIdealTime; /* Use the 'ideal' time for timer 0, ie. calls to time_tick */ timers[1].getTime = getHostSysTime; /* We don't really expect anyone to use timer 1 */ timers[2].getTime = getHostSysTime; /* Use real host time for timer 2 */ #endif } LOCAL void counter_init IFN1(COUNTER_UNIT *, p) { p->state = uninit; p->initialCount = 0x10000L; /* Avoid dividing by zero in updateCount()! */ #ifndef NTVDM p->guessesPerHostTick = p->guessesSoFar = 2; /* Handy to count from 2! */ p->timeFrig = ticksPerIdealInterval / p->guessesPerHostTick; #endif } #if !defined (NTVDM) #if defined(IRET_HOOKS) && defined(GISP_CPU) /*( *======================= TimerHookAgain() ============================ * TimerHookAgain * * Purpose * This is the function that we tell the ica to call when a timer * interrupt service routine IRETs. * * Input * adapter_id The adapter id for the line. (Note the caller doesn't * know what this is, he's just returning something * we gave him earlier). * * Outputs * return TRUE if there are more interrupts to service, FALSE otherwise. * * Description * Check if we have a delayed interrupt, if so then generate the timer int * and return TRUE, else return FALSE )*/ GLOBAL IBOOL TimerHookAgain IFN1(IUM32, adapter) { char scancode; if (HostPendingTimerInt()) { /* We have a host delayed interrupt, so generate a timer int. */ sure_note_trace0(TIMER_VERBOSE,"callback with delayed timer int."); ica_hw_interrupt(ICA_MASTER,CPU_TIMER_INT, 1); return(TRUE); /* more to do */ } else { return(FALSE); } } #endif /* IRET_HOOKS && GISP_CPU */ #endif /* !NTVDM */ void timer_init IFN0() { io_addr i; /* * Set up the IO chip select logic for this adaptor */ io_define_inb(TIMER_ADAPTOR, timer_inb_func); io_define_outb(TIMER_ADAPTOR, timer_outb_func); for(i = TIMER_PORT_START; i < TIMER_PORT_END; i++) { if( (i & 3) == 3 ) io_connect_port(i, TIMER_ADAPTOR, IO_WRITE); /* Control port - write only */ else io_connect_port(i, TIMER_ADAPTOR, IO_READ_WRITE); /* Timer port - read/write */ } IdealTimeInit(); Timer_init(); #ifndef NTVDM timelock = UNLOCKED; needtick = 0; #else RegisterEOIHook(CPU_TIMER_INT, TimerEoiHook); #endif /* * Start up the host alarm system */ host_timer_init(); #if !defined(NTVDM) #if defined(IRET_HOOKS) && defined(GISP_CPU) /* * Remove any existing hook call-back, and re-instate it afresh. * TimerHookAgain is what gets called on a timer int iret. */ Ica_enable_hooking(CPU_TIMER_INT, NULL, ICA_MASTER); Ica_enable_hooking(CPU_TIMER_INT, TimerHookAgain, ICA_MASTER); /* Host routine to reset any internal data for IRET_HOOK delayed ints. */ HostResetTimerInts(); #endif /* IRET_HOOKS && GISP_CPU */ active_int_event = FALSE; /* clear any cranked timer state */ more_timer_mult = 0; #if defined(CPU_40_STYLE) ica_iret_hook_control(ICA_MASTER, CPU_TIMER_INT, TRUE); #endif #endif /* !NTVDM */ } void timer_post IFN0() { /* enable gates on all timer channels */ timer_gate(TIMER0_REG,GATE_SIGNAL_RISE); /* start timer 1 going... */ timer_gate(TIMER1_REG,GATE_SIGNAL_RISE); timer_gate(TIMER2_REG,GATE_SIGNAL_RISE); timer_outb(TIMER_MODE_REG,0x36); timer_outb(TIMER0_REG,0); timer_outb(TIMER0_REG,0); timer_outb(TIMER_MODE_REG,0x54); timer_outb(TIMER1_REG,17); timer_outb(TIMER_MODE_REG,0xb6); timer_outb(TIMER2_REG,0x68); timer_outb(TIMER2_REG,0x04); } #ifdef DOCUMENTATION #ifndef PROD /* * Debugging code.... * This code has no effect. It is left here in case anyone wants to * expand it in future. */ dumpCounter IFN0() { static char *modes[] = { "int on tc", "prog one shot", "rate gen", "squarewave gen", "sw trig strobe", "hw trig strobe" }; static char *as[] = { "binary", "bcd" }; char *p, *q; p = modes[pcu->m]; q = as[pcu->bcd]; } #endif /* nPROD */ #endif /* DOCUMENTATION */