/* * timer.c * * This file is part of Emu48 * * Copyright (C) 1995 Sebastien Carlier * */ #include "pch.h" #include "Emu48.h" #include "ops.h" #include "io.h" // I/O definitions #define AUTO_OFF 10 // Time in minutes for 'auto off' // Ticks for 'auto off' #define OFF_TIME ((ULONGLONG) (AUTO_OFF * 60) << 13) // memory address for clock and auto off // S(X) = 0x70052-0x70070, G(X) = 0x80058-0x80076, 49G = 0x80058-0x80076 #define RPLTIME ((cCurrentRomType=='S')?0x52:0x58) #define T1_FREQ 62 // Timer1 1/frequency in ms #define T2_FREQ 8192 // Timer2 frequency static BOOL bStarted = FALSE; static BOOL bOutRange = FALSE; // flag if timer value out of range static UINT uT1TimerId = 0; static UINT uT2TimerId = 0; static BOOL bNINT2T1 = FALSE; // state of NINT2 affected from timer1 static BOOL bNINT2T2 = FALSE; // state of NINT2 affected from timer2 static BOOL bAccurateTimer; // flag if accurate timer is used static LARGE_INTEGER lT2Ref; // counter value at timer2 start static TIMECAPS tc; // timer information static UINT uT2MaxTicks; // max. timer2 ticks handled by one timer event static DWORD dwT2Ref; // timer2 value at last timer2 access static DWORD dwT2Cyc; // cpu cycle counter at last timer2 access static void CALLBACK TimeProc(UINT uEventId, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2); static DWORD CalcT2(VOID) // calculate timer2 value { DWORD dwT2 = Chipset.t2; // get value from chipset if (bStarted) // timer2 running { LARGE_INTEGER lT2Act; DWORD dwT2Dif; // timer should run a little bit faster (10%) than maschine in authentic speed mode DWORD dwCycPerTick = (9 * dwT2Cycles) / 5; QueryPerformanceCounter(&lT2Act); // actual time // calculate realtime timer2 ticks since reference point dwT2 -= (DWORD) (((lT2Act.QuadPart - lT2Ref.QuadPart) * T2_FREQ) / lFreq.QuadPart); dwT2Dif = dwT2Ref - dwT2; // timer2 ticks since last request // checking if the MSB of dwT2Dif can be used as sign flag _ASSERT((DWORD) tc.wPeriodMax < ((1<<(sizeof(dwT2Dif)*8-1))/8192)*1000); // 2nd timer call in a 32ms time frame or elapsed time is negative (Win2k bug) if (!Chipset.Shutdn && ((dwT2Dif > 0x01 && dwT2Dif <= 0x100) || (dwT2Dif & 0x80000000) != 0)) { DWORD dwT2Ticks = ((DWORD) (Chipset.cycles & 0xFFFFFFFF) - dwT2Cyc) / dwCycPerTick; // estimated < real elapsed timer2 ticks or negative time if (dwT2Ticks < dwT2Dif || (dwT2Dif & 0x80000000) != 0) { // real time too long or got negative time elapsed dwT2 = dwT2Ref - dwT2Ticks; // estimated timer2 value from CPU cycles dwT2Cyc += dwT2Ticks * dwCycPerTick; // estimated CPU cycles for the timer2 ticks } else { // reached actual time -> new synchronizing dwT2Cyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF) - dwCycPerTick; } } else { // valid actual time -> new synchronizing dwT2Cyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF) - dwCycPerTick; } // check if timer2 interrupt is active -> no timer2 value below 0xFFFFFFFF if ( Chipset.inte && (dwT2 & 0x80000000) != 0 && (!Chipset.Shutdn || (Chipset.IORam[TIMER2_CTRL]&WKE)) && (Chipset.IORam[TIMER2_CTRL]&INTR) ) { dwT2 = 0xFFFFFFFF; dwT2Cyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF) - dwCycPerTick; } dwT2Ref = dwT2; // new reference time } return dwT2; } static VOID CheckT1(BYTE nT1) { // implementation of TSRQ bNINT2T1 = (Chipset.IORam[TIMER1_CTRL]&INTR) != 0 && (nT1&8) != 0; IOBit(SRQ1,TSRQ,bNINT2T1 || bNINT2T2); if ((nT1&8) == 0) // timer1 MSB not set { Chipset.IORam[TIMER1_CTRL] &= ~SRQ; // clear SRQ bit return; } _ASSERT((nT1&8) != 0); // timer1 MSB set // timer MSB and INT or WAKE bit is set if ((Chipset.IORam[TIMER1_CTRL]&(WKE|INTR)) != 0) Chipset.IORam[TIMER1_CTRL] |= SRQ; // set SRQ // cpu not sleeping and T1 -> Interrupt if ( (!Chipset.Shutdn || (Chipset.IORam[TIMER1_CTRL]&WKE)) && (Chipset.IORam[TIMER1_CTRL]&INTR)) { Chipset.SoftInt = TRUE; bInterrupt = TRUE; } // cpu sleeping and T1 -> Wake Up if (Chipset.Shutdn && (Chipset.IORam[TIMER1_CTRL]&WKE)) { Chipset.IORam[TIMER1_CTRL] &= ~WKE; // clear WKE bit Chipset.bShutdnWake = TRUE; // wake up from SHUTDN mode SetEvent(hEventShutdn); // wake up emulation thread } return; } static VOID CheckT2(DWORD dwT2) { // implementation of TSRQ bNINT2T2 = (Chipset.IORam[TIMER2_CTRL]&INTR) != 0 && (dwT2&0x80000000) != 0; IOBit(SRQ1,TSRQ,bNINT2T1 || bNINT2T2); if ((dwT2&0x80000000) == 0) // timer2 MSB not set { Chipset.IORam[TIMER2_CTRL] &= ~SRQ; // clear SRQ bit return; } _ASSERT((dwT2&0x80000000) != 0); // timer2 MSB set // timer MSB and INT or WAKE bit is set if ((Chipset.IORam[TIMER2_CTRL]&(WKE|INTR)) != 0) Chipset.IORam[TIMER2_CTRL] |= SRQ; // set SRQ // cpu not sleeping and T2 -> Interrupt if ( (!Chipset.Shutdn || (Chipset.IORam[TIMER2_CTRL]&WKE)) && (Chipset.IORam[TIMER2_CTRL]&INTR)) { Chipset.SoftInt = TRUE; bInterrupt = TRUE; } // cpu sleeping and T2 -> Wake Up if (Chipset.Shutdn && (Chipset.IORam[TIMER2_CTRL]&WKE)) { Chipset.IORam[TIMER2_CTRL] &= ~WKE; // clear WKE bit Chipset.bShutdnWake = TRUE; // wake up from SHUTDN mode SetEvent(hEventShutdn); // wake up emulation thread } return; } static VOID RescheduleT2(BOOL bRefPoint) { UINT uDelay; _ASSERT(uT2TimerId == 0); // timer2 must stopped if (bRefPoint) // save reference time { dwT2Ref = Chipset.t2; // timer2 value at last timer2 access dwT2Cyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF); // cpu cycle counter at last timer2 access QueryPerformanceCounter(&lT2Ref); // time of corresponding Chipset.t2 value uDelay = Chipset.t2; // timer value for delay } else // called without new refpoint, restart t2 with actual value { uDelay = CalcT2(); // actual timer value for delay } if ((bOutRange = uDelay > uT2MaxTicks)) // delay greater maximum delay uDelay = uT2MaxTicks; // wait maximum delay time uDelay = (uDelay * 125 + 1023) / 1024; // timer delay in ms (1000/8192 = 125/1024) uDelay = __max(tc.wPeriodMin,uDelay); // wait minimum delay of timer _ASSERT(uDelay <= tc.wPeriodMax); // inside maximum event delay // start timer2; schedule event, when Chipset.t2 will be zero VERIFY(uT2TimerId = timeSetEvent(uDelay,0,&TimeProc,2,TIME_ONESHOT)); return; } static VOID AbortT2(VOID) { _ASSERT(uT2TimerId); timeKillEvent(uT2TimerId); // kill event uT2TimerId = 0; // then reset var return; } static void CALLBACK TimeProc(UINT uEventId, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2) { if (uEventId == 0) return; // illegal EventId if (uEventId == uT1TimerId) // called from timer1 event (default period 16 Hz) { EnterCriticalSection(&csT1Lock); { Chipset.t1 = (Chipset.t1-1)&0xF;// decrement timer value CheckT1(Chipset.t1); // test timer1 control bits } LeaveCriticalSection(&csT1Lock); return; } if (uEventId == uT2TimerId) // called from timer2 event, Chipset.t2 should be zero { EnterCriticalSection(&csT2Lock); { uT2TimerId = 0; // single shot timer timer2 stopped if (!bOutRange) // timer event elapsed { // timer2 overrun, test timer2 control bits else restart timer2 Chipset.t2 = CalcT2(); // calculate new timer2 value CheckT2(Chipset.t2); // test timer2 control bits } RescheduleT2(!bOutRange); // restart timer2 } LeaveCriticalSection(&csT2Lock); return; } return; UNREFERENCED_PARAMETER(uMsg); UNREFERENCED_PARAMETER(dwUser); UNREFERENCED_PARAMETER(dw1); UNREFERENCED_PARAMETER(dw2); } VOID SetHP48Time(VOID) // set date and time { SYSTEMTIME ts; ULONGLONG ticks, time; DWORD dw; WORD crc, i; BYTE p[4]; _ASSERT(sizeof(ULONGLONG) == 8); // check size of datatype GetLocalTime(&ts); // local time, _ftime() cause memory/resource leaks // calculate days until 01.01.0000 (Erlang BIF localtime/0) dw = (DWORD) ts.wMonth; if (dw > 2) dw -= 3L; else { dw += 9L; --ts.wYear; } dw = (DWORD) ts.wDay + (153L * dw + 2L) / 5L; dw += (146097L * (((DWORD) ts.wYear) / 100L)) / 4L; dw += (1461L * (((DWORD) ts.wYear) % 100L)) / 4L; dw += (-719469L + 719528L); // days from year 0 ticks = (ULONGLONG) dw; // convert to 64 bit // convert into seconds and add time ticks = ticks * 24L + (ULONGLONG) ts.wHour; ticks = ticks * 60L + (ULONGLONG) ts.wMinute; ticks = ticks * 60L + (ULONGLONG) ts.wSecond; // create timerticks = (s + ms) * 8192 ticks = (ticks << 13) | (((ULONGLONG) ts.wMilliseconds << 10) / 125); ticks += Chipset.t2; // add actual timer2 value time = ticks; // save for calc. timeout time += OFF_TIME; // add 10 min for auto off dw = RPLTIME; // HP addresses for clock in port0 crc = 0x0; // reset crc value for (i = 0; i < 13; ++i, ++dw) // write date and time { *p = (BYTE) ticks & 0xf; crc = (crc >> 4) ^ (((crc ^ ((WORD) *p)) & 0xf) * 0x1081); Port0[dw] = *p; // always store in port0 ticks >>= 4; } Nunpack(p,crc,4); // write crc memcpy(Port0+dw,p,4); // always store in port0 dw += 4; // HP addresses for timeout for (i = 0; i < 13; ++i, ++dw) // write time for auto off { Port0[dw] = (BYTE) time & 0xf; // always store in port0 time >>= 4; } Port0[dw] = 0xf; // always store in port0 return; } VOID StartTimers(VOID) { if (bStarted) // timer running return; // -> quit if (Chipset.IORam[TIMER2_CTRL]&RUN) // start timer1 and timer2 ? { bStarted = TRUE; // flag timer running // initialisation of NINT2 lines bNINT2T1 = (Chipset.IORam[TIMER1_CTRL]&INTR) != 0 && (Chipset.t1 & 8) != 0; bNINT2T2 = (Chipset.IORam[TIMER2_CTRL]&INTR) != 0 && (Chipset.t2 & 0x80000000) != 0; timeGetDevCaps(&tc,sizeof(tc)); // get timer resolution // max. timer2 ticks that can be handled by one timer event uT2MaxTicks = __min((0xFFFFFFFF / 1024),tc.wPeriodMax); uT2MaxTicks = __min((0xFFFFFFFF - 1023) / 125,uT2MaxTicks * 1024 / 125); CheckT1(Chipset.t1); // check for timer1 interrupts CheckT2(Chipset.t2); // check for timer2 interrupts // set timer resolution to greatest possible one bAccurateTimer = (timeBeginPeriod(tc.wPeriodMin) == TIMERR_NOERROR); // set timer1 with given period VERIFY(uT1TimerId = timeSetEvent(T1_FREQ,0,&TimeProc,1,TIME_PERIODIC)); RescheduleT2(TRUE); // start timer2 } return; } VOID StopTimers(VOID) { if (!bStarted) // timer stopped return; // -> quit if (uT1TimerId != 0) // timer1 running { // Critical Section handler may cause a dead lock timeKillEvent(uT1TimerId); // stop timer1 uT1TimerId = 0; // set flag timer1 stopped } if (uT2TimerId != 0) // timer2 running { EnterCriticalSection(&csT2Lock); { Chipset.t2 = CalcT2(); // update chipset timer2 value } LeaveCriticalSection(&csT2Lock); AbortT2(); // stop timer2 outside critical section } bStarted = FALSE; if (bAccurateTimer) // "Accurate timer" running { timeEndPeriod(tc.wPeriodMin); // finish service } return; } DWORD ReadT2(VOID) { DWORD dwT2; EnterCriticalSection(&csT2Lock); { dwT2 = CalcT2(); // calculate timer2 value or if stopped last timer value CheckT2(dwT2); // update timer2 control bits } LeaveCriticalSection(&csT2Lock); return dwT2; } VOID SetT2(DWORD dwValue) { // calling AbortT2() inside Critical Section handler may cause a dead lock if (uT2TimerId != 0) // timer2 running AbortT2(); // stop timer2 EnterCriticalSection(&csT2Lock); { Chipset.t2 = dwValue; // set new value CheckT2(Chipset.t2); // test timer2 control bits if (bStarted) // timer running RescheduleT2(TRUE); // restart timer2 } LeaveCriticalSection(&csT2Lock); return; } BYTE ReadT1(VOID) { BYTE nT1; EnterCriticalSection(&csT1Lock); { nT1 = Chipset.t1; // read timer1 value CheckT1(nT1); // update timer1 control bits } LeaveCriticalSection(&csT1Lock); return nT1; } VOID SetT1(BYTE byValue) { BOOL bEqual; _ASSERT(byValue < 0x10); // timer1 is only a 4bit counter EnterCriticalSection(&csT1Lock); { bEqual = (Chipset.t1 == byValue); // check for same value } LeaveCriticalSection(&csT1Lock); if (bEqual) return; // same value doesn't restart timer period if (uT1TimerId != 0) // timer1 running { timeKillEvent(uT1TimerId); // stop timer1 uT1TimerId = 0; // set flag timer1 stopped } EnterCriticalSection(&csT1Lock); { Chipset.t1 = byValue; // set new timer1 value CheckT1(Chipset.t1); // test timer1 control bits } LeaveCriticalSection(&csT1Lock); if (bStarted) // timer running { // restart timer1 to get full period of frequency VERIFY(uT1TimerId = timeSetEvent(T1_FREQ,0,&TimeProc,1,TIME_PERIODIC)); } return; }