/************************************************************************\
 * File Version Information
 * $Header: /Altair32v2/scheduler.c 52    11/08/06 9:21p Racini $
 ************************************************************************/
/************************************************************************\
  MITS Altair Emulator
  Timeslicing scheduler

  Scheduler support for the Solace Sol-20 emulator
  Copyright (c) Jim Battle, 2000-2002

  Modifications for the Altair32 Emulator
  Copyright (c) 2001-2006 Richard A. Cini
  Copyright (c) 2004-2005 Scott M. LaBombard (Timeslicing rewrite)


  This code supplies a very simple scheduler that operates in simulated
  processor time. A fixed number of timers are supported; timers are one
  -shot, not oscillators. The timer is a 64-bit integer (__int64). A
  routine desiring later notification at some specific time calls
  TimerCreate, with a callback function 'fcn' which is called with
  parameters arg1 and arg2 after simulating 'ticks' clock cycles.
  At time-out, the event is removed from the queue.


Change Log:
  2001/12/19  RAC -- Initial changes to support time slicing in Altair32
						(included but unused in that version)
  2002/01/31  RAC -- RELEASE MARKER -- v2.2
  2002/05/17  RAC -- Added new routines from Solace v3.1
  2002/07/07  RAC -- RELEASE MARKER -- v2.3
  2002/08/23  RAC -- RELEASE MARKER -- v2.30.10
  2002/10/16  RAC -- Moved forward prototypes from scheduler.h to altair32.h
  2002/10/18  RAC -- Added IMSAI sense switch output and LED updating to
  						multiple parts of the timeslicing loop.
  2002/10/23  RAC -- Attempted to fix single-step screen update	flicker.
  2002/11/15  RAC -- RELEASE MARKER -- v2.40.2100
  2003/03/26  RAC -- Minor bug fixes (thanks again Rodger) in QueryTimebase
  						(added missing "else") and DoTimeSlicing (moved
  						"else" clause to correct "if"). Fixes LED update
  						anomaly by shifting around LED update in
  						DoTimeSlicing.
  2003/04/26  RAC -- RELEASE MARKER -- v2.50.2045
  2003/04/30  RS  -- Changes to support moving channel polling to scheduler;
  						removed timeslip catchup code ("chasing the needle").
  2004/03/09  RAC -- Diff'ed changes from FJS (Fred J. Scipione)
  2004/06/21  SML -- Overhauled DoTimeSlicing to support threaded operation,
  						significantly reduced host system resource utilization,
						and improved unregulated speed operation.
  2004/07/30  RAC -- RELEASE MARKER -- v3.00.0135
  2006/05/12  RAC -- RELEASE MARKER -- v3.10.0200
  2006/06/12  RAC -- Made changes for Dazzler joystick (RS); changes to
  						TimerTickScale to initialize scale array;
  						TimerRollover synced with Solace 3.2.
  2006/11/15  RAC -- RELEASE MARKER -- v3.20.0400
\************************************************************************/
#include <windows.h>
#include <commdlg.h>
#include <commctrl.h>
#include <string.h>
#include <stdio.h>		// C-lib - I/O
#include <limits.h>
#include <mmsystem.h>	// needed for timeGetTime() and related funcs
#include "altair32.h"	// includes "resource.h"
#include "i8080.h"		// for Out80 macro
#include "comcthlp.h"	// common control macro file
#include "windbg.h"		// breakpoint stuff


/** Defines  **************************************************/
#define NUM_TIMERS   20	// number of active timers allowed
#define MAXTICK     100


/** Typedefs  *************************************************/
static struct tagTIMERS {
    int active_timers;		// # of active timers
    int updating;		// semaphore that we are updating

    // Rather than updating all N counters every instruction,
    // instead we find the one which will expire first, and
    // transfer that into countdown and startcnt. As simulated
    // time ticks by, we decrement countdown.
    //
    // When countdown goes <=0, all timers have (startcnt-countdown)
    // ticks removed from their own counter.
    uint32 countdown;
    uint32 startcnt;

    // Any timer that has its own counter go <=0 will then
    // cause a callback to the supplied function using the
    // supplied parameters.
    struct {
		uint32    ctr;			// counter until expiration
		timercb_t callback;	// callback function
		uint32    arg1;		// arbitrary value
		uint32    arg2;		// arbitrary value
    } timer[NUM_TIMERS];
} timers;


/**  Module Globals - Private *********************************/
static int  tickscale[MAXTICK];
static int  tscale;
// this macro doesn't work if 't' isn't assignable
#define NORMALIZE_TICKS(t) \
	(((t) < MAXTICK) ? tickscale[(t)] : ((t)*tscale))

// create an absolute tick timer.
// we update a uint32 version for speed, then roll that into a uint64 version
// either when rollover out of the uint32 accumulator is a danger, or when
// somebody want to get an absolute timer value.
timer_t		timer64;
uint32		timer32;


/**  Forward Prototypes  **************************************/
static __int64 HOST_QueryTimebase(void);
static uint32  HOST_CurTick(void);
static uint32  HOST_ElapsedTick(uint32 begin, uint32 end);


/**************************************************************\
|** TIMER API  ************************************************|
\**************************************************************/
/*--  SCHED_TimerInit  ----------------------------------------
	Initializes timer data structures.

	Params:		None

	Uses:		simstate

	Returns:	Nothing

	Comments:	Called at emulator startup; public
-------------------------------------------------------------*/
void SCHED_TimerInit(void)
{
	int i;
	timers.active_timers = 0;
	timers.countdown     = 0;
	timers.updating      = 0;

	for(i=0; i<NUM_TIMERS; i++) {
		timers.timer[i].ctr = 0;	// inactive timer
	}
	timer64 = 0;
	timer32 = 0;
	// to make sure our 32b counters don't ever wrap,
	// this callback makes sure the 32b things propagate
	// into 64b things once in a while.
	(void)TimerCreate((1<<30), TimerRollover, 0, 0);

}


/*--  TimerAbs  -----------------------------------------------
	Return the absolute 14.3 MHz tick count.

	Params:		None

	Uses:

	Returns:	timer_t

	Comments:
-------------------------------------------------------------*/
timer_t TimeAbs(void)
{
    return (timer64 + timer32);
}


/*--  TimeDiff  -----------------------------------------------
	Computes time difference from earlier time to now.

	Params:		timer_t

	Uses:		simstate

	Returns:	timer_t

	Comments:
-------------------------------------------------------------*/
timer_t TimeDiff(timer_t oldtime)
{
    return (timer64 + timer32 - oldtime);
}


/*--  TimeDiffSat  --------------------------------------------
	Computes time difference, saturated to int32 range

	Params:		timer_t

	Uses:

	Returns:	uint32

	Comments:
-------------------------------------------------------------*/
uint32 TimeDiffSat(timer_t oldtime)
{
    timer_t diff = (timer64 + timer32 - oldtime);

    if (diff <= INT_MAX)
		return (int)diff;
    else
		return INT_MAX;
}


/*--  TimerRollover  ------------------------------------------
	Roll over the 32-bit tick counter into the 64-bit counter.

	Params:

	Uses:

	Returns:	Nothing

	Comments:
-------------------------------------------------------------*/
void TimerRollover(uint32 arg1, uint32 arg2)
{
    timer64 += timer32;
    timer32 = 0;

// ADDED 6/13/06
    arg1 = arg1;
    arg2 = arg2;	// keep lint happy
// END
    // kick it off again
    (void)TimerCreate((1<<30), TimerRollover, 0, 0);
}


/*--  TimerCreate  --------------------------------------------
	Creates a timer.

	Params:		ticks, function, arg1, arg2

	Uses:		timers

	Returns:	(int) timer handle

	Comments:
-------------------------------------------------------------*/
int TimerCreate(uint32 ticks, timercb_t fcn, uint32 arg1, uint32 arg2)
{

	int tmr;

    // scan through all slots looking for an empty one
    int found = 0;
    for(tmr=0; tmr<NUM_TIMERS; tmr++) {
		if (timers.timer[tmr].ctr == 0) {
	    	timers.timer[tmr].ctr      = ticks;
			timers.timer[tmr].callback = fcn;
			timers.timer[tmr].arg1     = arg1;
			timers.timer[tmr].arg2     = arg2;
			timers.active_timers++;
			found = 1;
			break;
		}
    }

    if (!found) {
		HOST_ThrowErrMsg("Scheduler.c: Out of simulated timers!");
		exit(-1);
    }

    if (timers.updating) {
		// an expiring timer scheduled a new timer, so at the
		// end of the TimerCredit, the startcnt and countdown
		// get recomputed, and we don't need to tweak them here.
	;
    } else if (timers.active_timers == 1) {
		// this is the only timer
		timers.startcnt = timers.countdown = ticks;
    } else if (ticks < timers.countdown) {
		// the new timer expires before current target
		uint32 diff = (timers.countdown - ticks);
		timers.startcnt  -= diff;
		timers.countdown -= diff;	// == ticks
    } else {
		// the timer state might be that we already have credits lined up
		// for other timers.  rather than doling out the elapsed time to
		// each timer (which may cause some timer to expire, which may cause
		// a callback to try and create another timer, causing reentrancy
		// issues), instead we just add the current credit to the new timer.
		// that is, if N cycles have gone by since the last time we
		// evaluated who to trigger next, we pretend that the new event
		// which is to trigger in M cycles was actually scheduled for M+N
		// ticks N cycles ago.
		int elapsed = (timers.startcnt - timers.countdown);
		timers.timer[tmr].ctr += elapsed;
    }

    return tmr;
}


/*--  TimerRemaining  -----------------------------------------
	Returns number of base ticks left before the timer expires.

	Params:		timer#

	Uses:		timers

	Returns:	ticks or 0 if timer already expired

	Comments:
-------------------------------------------------------------*/
int TimerRemaining(int n)
{
    int uncredited = (timers.startcnt - timers.countdown);
    int remains;

    if (timers.timer[n].ctr == 0)
		return 0;	// dead timer

    ASSERT(uncredited >= 0);

    remains = timers.timer[n].ctr - uncredited;
    if (remains < 0)
	remains = 0;

    return remains;
}


/*--  TimerKill  ----------------------------------------------
	Kills a timer.

	Params:		Timer handle

	Uses:		timers

	Returns:	Nothing

	Comments:	The timer number passed to this function is the
				one returned by the TimerCreate function. If we
				happen to be killing the timer nearest to completion,
				we don't bother messing with updating
				timers.countdown. Instead, we just let that
				countdown expire, nothing will be triggered,
				and a new countdown will be established then.
-------------------------------------------------------------*/
void TimerKill(int n)
{
    if (timers.timer[n].ctr == 0) {
//		HOST_ThrowErrMsg("Scheduler.c: Killing non-existent simulated timer!");
		exit(-1);
    }

    timers.timer[n].ctr = 0;
    timers.active_timers--;
}


/*--  TimerCredit  --------------------------------------------
	Transfers accumulated timer deficit to each active timer.

	Params:		Deficit

	Uses:		timers

	Returns:	Nothing

	Comments:	n is added to the already elapsed time. This
				shouldn't need to be called frequently.
-------------------------------------------------------------*/
static void TimerCredit(uint32 n)
{
	uint32 elapsed;
    int i, flag;

    if ((timers.active_timers == 0) || (n == 0))
		return;	// no timers or no time

    elapsed = n + (timers.startcnt - timers.countdown);
    timers.updating = 1;

    // scan each old timer
    for(i=0; i<NUM_TIMERS; i++) {
		if (timers.timer[i].ctr != 0) {
		    int overshoot = (elapsed - timers.timer[i].ctr);
		    if (overshoot >= 0) {
				// a timer has expired
				timercb_t fcn = timers.timer[i].callback;
				uint32 arg1   = timers.timer[i].arg1;
				uint32 arg2   = timers.timer[i].arg2;
				// destroy timer
				timers.timer[i].ctr = 0;
				timers.active_timers--;
				// call the callback
				fcn(arg1,arg2);
				// ideally, we'd also pass the number of base ticks
				// of overshoot so that a periodic timer could make
				// sure the new period for the next cycle accounted
				// for this small (?) difference.
	    	} else {
				// a timer hasn't expired
				timers.timer[i].ctr -= elapsed;
			}
		}
	}

    // determine the new target time
    flag = 0;
    timers.countdown = 0;
    for(i=0; i<NUM_TIMERS; i++) {
		if (timers.timer[i].ctr != 0) {
			if (!flag || (timers.timer[i].ctr < timers.countdown)) {
				timers.countdown = timers.timer[i].ctr;
				flag = 1;
	    	}
		}
    }
    timers.startcnt = timers.countdown;
    timers.updating = 0;	// done updating
}


/*--  TimerTickScale  -----------------------------------------
	Scale emulated CPU speed

	Params:		scale factor

	Uses:

	Returns:	Nothing

	Comments:	Each CPU clock corresponds to n base clocks.
				Because all scheduling is done in base ticks,
				we need to scale CPU ticks by some factor to
				get base ticks.
-------------------------------------------------------------*/
void TimerTickScale(int n)
{
	int i;
    tscale = n;

    
    for(i=0; i<MAXTICK; i++)
		tickscale[i] = tscale * i;
}


/*--  TimerTick  ----------------------------------------------
	Let n cycles of simulated time go past.

	Params:		n cycles

	Uses:		timers

	Returns:	Nothing

	Comments:
-------------------------------------------------------------*/
void TimerTick(uint32 n)
{
    uint32 base_n = NORMALIZE_TICKS(n);


    timer32 += base_n;

    if (timers.countdown > base_n) {

	// this should be the most common case
	timers.countdown -= base_n;

    } else if (timers.countdown > 0) {

	// FIXME: timers.countdown is unsigned, so this is really only true
	//        if contdown!=0. What if countdown gets stuck between
	//        0 and base_n? Do we keep calling TimerCredit() pointlessly?
	//        The comment where countdown is defined indicates at one time
	//        it was signed.

	// it is likely that one or more timers has expired.
	// it isn't guaranteed since it is possible that the timer
	// which was nearest to completion was recently TimerKill'd.
	TimerCredit(base_n);

    } // else no active timers
}


/*--  UpdateTimerInfo  ----------------------------------------
	Computes timeslice info after something has changed.

	Params:		None

	Uses:		simstate

	Returns:	Nothing

	Comments:	Actual simulated CPU clock is set by
				SYS_Set8080Speed.
-------------------------------------------------------------*/
void UpdateTimerInfo(void)
{

    switch (simstate.up_speed) {

	case MHZ_unregulated:
		/*	In this mode, we will use the default ticks per milliseconds
		 *	to determine how many machine cycles to execute in a single
		 *	pass. Unlike the regulated modes however, we will execute as
		 *	many passes as we can instead of a single iteration during
		 *	the timeslice allocation.
		 */
		 // Fall through ...
	case MHZ_2_04:
	default:
	    simstate.sol_ticks_per_ms    = 2048;
   		TimerTickScale(7); 
	    break;

	case MHZ_1_02:
	    simstate.sol_ticks_per_ms    = 1024;
	    TimerTickScale(14);
	    break;

	case MHZ_4_09:
	    simstate.sol_ticks_per_ms    = 4096;
	    TimerTickScale(3);
		break;

	case MHZ_0_50:	// slow-stepping at 500KHz (0.5 MHz)==511.3636KHz
	    simstate.sol_ticks_per_ms    = 512;
	    TimerTickScale(28); 
	    break;
    }
}


/*--  HOST_QueryTimebase  -------------------------------------
	Determines what type of timing capability the host machine
	supports.

	Params:		None

	Uses:		winstate

	Returns:	frequency of the time reference

	Comments:	We save the timer capabilities so CurTick() knows
				which facility to use.
-------------------------------------------------------------*/
static __int64 HOST_QueryTimebase(void)
{

    LARGE_INTEGER HPTimerFreq;
    __int64 freq;
    

    winstate.useHPTimer = QueryPerformanceFrequency(&HPTimerFreq);
    winstate.useLPTimer = FALSE;

    if (winstate.useHPTimer) {
		//printf("This machine has a high-performance timer of %I64d Hz\n", HPTimerFreq);
		freq = HPTimerFreq.QuadPart/1000;  // counts per millisecond
    } else {

		MMRESULT mm;
		//printf("Using 1ms timer function\n");
		mm = timeBeginPeriod(1);	// 1 ms timer resolution
		winstate.useLPTimer = (mm == TIMERR_NOERROR);
		mm = timeEndPeriod(1);	// must be paired with timeBeginPeriod()
		if (!winstate.useLPTimer) {
			//printf("Hey, mm timer can't support 1ms timer resolution\n");
			freq = 0;			// no useful timer
		} else
			freq = 1;		// counts per millisecond
    }

    winstate.tick_freq = freq;	// useful for debugging
    return freq;
}


/*--  SCHED_DoTimeslicing  ------------------------------------
	Main timeslicing code.

	Params:		Thread argument

	Uses:		simstate global

	Returns:	Nothing

	Comments:	This continually runs a timeslice's worth of 8080
				cycles, then it resynchronizes to the real-world
				timeslice duration. It is during during this
				resynchronization that windows messages get handled.
-------------------------------------------------------------*/
unsigned __stdcall SCHED_DoTimeSlicing(void* pArguments)
{

    int	passes = 0;
    int	fpass = 1 /* YES */;	// mark first pass main loop
#define	win_resync 2			// # of ms to resync with Windows FJS
    LARGE_INTEGER tStart, /* tEnd, */ realTime, eTime;
    uint32 unregCount = 0;
    HANDLE hEvent = (HANDLE)pArguments;
    char buf[80];				// used for speed message in statbar

    simstate.tick_freq = HOST_QueryTimebase();	// get system timer frequency
    simstate.timeslice_ms = 10;				// timeslice in milliseconds

    if (simstate.tick_freq == 0) {
		HOST_ThrowErrMsg("This machine doesn't support a high-precision timer.\n" \
		     "Running fixed instruction count.\n");
		simstate.up_speed = MHZ_unregulated;
    }

    UpdateTimerInfo();
    startrun();

    while ( TRUE ) {
	int i;

		if ((winstate.useLPTimer) || (winstate.useHPTimer))
			timeBeginPeriod(1);		// set timer rez

		if (fpass) {
			Sleep(25 /* msec */); // let the event processor know we are up
			SetEvent(hEvent);
			fpass = 0 /* NO */;
		} else 
			Sleep(win_resync);		// synch (let Windows catch up!)

		if ((winstate.useLPTimer) || (winstate.useHPTimer))
			timeEndPeriod(1);

		if (simstate.bPpower == 0)
			UpdateTimerInfo();  		// did user select another speed ?

		// If we're in the RESET or halted modes, let Windows do it's thing
		if ((simstate.runstate == RESET) || (simstate.runstate == HALTED))
			continue;

		if (simstate.iConsoleType == 0	// if TCP, check connection
				|| simstate.Siostati_func /* != NULL FJS */) {
			TELNET_TcpGetData();		// get any chars and buffer
			TELNET_TcpPutData(0);		// send any buffered chars
		}

		// If we've hit a breakpoint, stop everything and open debugger
		if (I80Regs.brkpt) {
			I80Regs.brkpt = 0;			// don't trip on it again
			brkpt_notify();
			breakpoint_temp_kill();	// in case it came from temp breakpoint
			UI_DbgWin(DW_Activate, 0);
			simstate.runstate = HALTED;
			simstate.bPrun = RUN_OFF;
			SetLEDs( SETLED_UPD );
			report_curstate();
			// continue;
		}

		// Single-step 
		if (simstate.runstate == STEP) {
			simstate.firststep = 1;
			simstep();
			if (I80Regs.brkpt)
				break;

			simstate.runstate = HALTED;
			simstate.bPrun = RUN_OFF;
			SetLEDs( SETLED_UPD );
			continue;
		}

		// Step into n instructions then HALT
		if (simstate.runstate == STEPI) {
			int i;

			simstate.firststep = 1;
			for (i=0; i<simstate.stepcount; i++) {
				simstep();
				SetLEDs( SETLED_UPD );
				if (I80Regs.brkpt) break;
		    }

			UI_DbgWin(DW_ActivateStep, 0);
			simstate.runstate = HALTED;
			simstate.bPrun = RUN_OFF;
			SetLEDs( SETLED_UPD );
			report_curstate();
 			continue;
		}

		// simulate for one timeslice's worth of CPU clock cycles
		if ((simstate.tick_freq != 0) && (simstate.bPpower != 0)) {

			I80Regs.ICount = simstate.sol_ticks_per_ms*(simstate.timeslice_ms + win_resync); // do timeslice+sync machine cycles
			unregCount = I80Regs.ICount;	// cycles about to be executed if running unregulated

			passes++;

			if (winstate.useHPTimer)
				QueryPerformanceCounter( &tStart );
			else {
				timeBeginPeriod(1);	// set rez
				tStart.QuadPart = timeGetTime();
				timeEndPeriod(1);
			}
			// timeslice allocation real end time
			// tEnd.QuadPart = (tStart.QuadPart + (simstate.tick_freq * simstate.timeslice_ms)); 

			// run for R->ICount CPU cycles
			if (simstate.runstate == RUNNING)
				simrun();
			else						// STEPRUN
				simstepover();

			if (simstate.bPpower)
				SetLEDs( SETLED_UPD );

			// take up any slack for this time slice
			while ( TRUE ) {

				if (simstate.up_speed == MHZ_unregulated) {	// simulate as many cycles as we can
					I80Regs.ICount = simstate.sol_ticks_per_ms*(simstate.timeslice_ms + win_resync);
					unregCount += I80Regs.ICount;
					if (simstate.runstate == RUNNING)
						simrun();
					else					// STEPRUN
						simstepover();
				}

				if (winstate.useHPTimer) {
					QueryPerformanceCounter( &realTime );
					eTime.QuadPart = (( realTime.QuadPart-tStart.QuadPart ) / simstate.tick_freq);
				} else {
					timeBeginPeriod(1);	// set rez
					realTime.QuadPart = timeGetTime();
					timeEndPeriod(1);
					eTime.QuadPart = (realTime.QuadPart-tStart.QuadPart);
				}

				// hack to fix performance indicator while in *SLOW* console mode
				// if (realTime.QuadPart > tEnd.QuadPart)
				//	 eTime.QuadPart = simstate.timeslice_ms;

				if (eTime.QuadPart >= simstate.timeslice_ms) break;

				if (simstate.bPpower)
					SetLEDs( SETLED_UPD );

			} // while ( TRUE )

			// Every so often update the status bar to print out some
			// emulation speed stats.
			if (passes > 48 /* 99 */ ) { // about 0.6 sec. between refreshes
				if (simstate.up_speed != MHZ_unregulated)
					sprintf(buf, "Simulated speed: %3.2f MHz",
					   (((simstate.sol_ticks_per_ms*simstate.timeslice_ms)/eTime.QuadPart) / 1000.0f));
				else
					sprintf(buf, "Simulated speed: %3.2f MHz", ((unregCount/eTime.QuadPart) / 1000.0f));

				Status_SetText(hwndStatusBar, STATBAR_READY, 0, buf);
				passes = 0;
			}

		} else if ((simstate.tick_freq == 0) && (simstate.bPpower != 0)) {

			// we don't have an accurate timer to work with
			// -- simulate 1500 instructions ASAP
			for(i=0; i<1500 && (!I80Regs.brkpt); i++)
				simstep();
			if (simstate.bPpower)
				SetLEDs( SETLED_UPD );

		} //if (tickfreq!=0) ...
	} // while( TRUE )
	
	/* Should never get here */
	return 0;
}

/*  end of file: scheduler.c  */
