#include #include "Iop_Timrman.h" #include "Iop_Intc.h" #include "Log.h" #include "IopBios.h" #include "Iop_RootCounters.h" #include "zip/ZipArchiveWriter.h" #include "zip/ZipArchiveReader.h" #include "states/RegisterStateFile.h" #define LOG_NAME ("iop_timrman") #define FUNCTION_ALLOCHARDTIMER "AllocHardTimer" #define FUNCTION_REFERHARDTIMER "ReferHardTimer" #define FUNCTION_FREEHARDTIMER "FreeHardTimer" #define FUNCTION_SETTIMERMODE "SetTimerMode" #define FUNCTION_GETTIMERSTATUS "GetTimerStatus" #define FUNCTION_GETTIMERCOUNTER "GetTimerCounter" #define FUNCTION_SETTIMERCOMPARE "SetTimerCompare" #define FUNCTION_GETHARDTIMERINTRCODE "GetHardTimerIntrCode" #define FUNCTION_SETTIMERCALLBACK "SetTimerCallback" #define FUNCTION_SETOVERFLOWCALLBACK "SetOverflowCallback" #define FUNCTION_SETUPHARDTIMER "SetupHardTimer" #define FUNCTION_STARTHARDTIMER "StartHardTimer" #define FUNCTION_STOPHARDTIMER "StopHardTimer" #define STATE_FILENAME ("iop_timrman/state.xml") #define STATE_HARDTIMERALLOC ("HardTimerAlloc") using namespace Iop; CTimrman::CTimrman(CIopBios& bios) : m_bios(bios) { static_assert((sizeof(m_hardTimerAlloc) * 8) >= Iop::CRootCounters::MAX_COUNTERS); } std::string CTimrman::GetId() const { return "timrman"; } std::string CTimrman::GetFunctionName(unsigned int functionId) const { switch(functionId) { case 4: return FUNCTION_ALLOCHARDTIMER; break; case 5: return FUNCTION_REFERHARDTIMER; break; case 6: return FUNCTION_FREEHARDTIMER; break; case 7: return FUNCTION_SETTIMERMODE; break; case 8: return FUNCTION_GETTIMERSTATUS; break; case 10: return FUNCTION_GETTIMERCOUNTER; break; case 11: return FUNCTION_SETTIMERCOMPARE; break; case 16: return FUNCTION_GETHARDTIMERINTRCODE; break; case 20: return FUNCTION_SETTIMERCALLBACK; break; case 21: return FUNCTION_SETOVERFLOWCALLBACK; break; case 22: return FUNCTION_SETUPHARDTIMER; break; case 23: return FUNCTION_STARTHARDTIMER; break; case 24: return FUNCTION_STOPHARDTIMER; break; default: return "unknown"; break; } } void CTimrman::Invoke(CMIPS& context, unsigned int functionId) { switch(functionId) { case 4: context.m_State.nGPR[CMIPS::V0].nD0 = AllocHardTimer( context.m_State.nGPR[CMIPS::A0].nV0, context.m_State.nGPR[CMIPS::A1].nV0, context.m_State.nGPR[CMIPS::A2].nV0); break; case 5: context.m_State.nGPR[CMIPS::V0].nD0 = ReferHardTimer( context.m_State.nGPR[CMIPS::A0].nV0, context.m_State.nGPR[CMIPS::A1].nV0, context.m_State.nGPR[CMIPS::A2].nV0, context.m_State.nGPR[CMIPS::A3].nV0); break; case 6: context.m_State.nGPR[CMIPS::V0].nD0 = FreeHardTimer( context.m_State.nGPR[CMIPS::A0].nV0); break; case 7: SetTimerMode( context, context.m_State.nGPR[CMIPS::A0].nV0, context.m_State.nGPR[CMIPS::A1].nV0); break; case 8: context.m_State.nGPR[CMIPS::V0].nD0 = GetTimerStatus( context, context.m_State.nGPR[CMIPS::A0].nV0); break; case 10: context.m_State.nGPR[CMIPS::V0].nD0 = GetTimerCounter( context, context.m_State.nGPR[CMIPS::A0].nV0); break; case 11: SetTimerCompare( context, context.m_State.nGPR[CMIPS::A0].nV0, context.m_State.nGPR[CMIPS::A1].nV0); break; case 16: context.m_State.nGPR[CMIPS::V0].nD0 = GetHardTimerIntrCode( context.m_State.nGPR[CMIPS::A0].nV0); break; case 20: context.m_State.nGPR[CMIPS::V0].nD0 = SetTimerCallback( context, context.m_State.nGPR[CMIPS::A0].nV0, context.m_State.nGPR[CMIPS::A1].nV0, context.m_State.nGPR[CMIPS::A2].nV0, context.m_State.nGPR[CMIPS::A3].nV0); break; case 21: context.m_State.nGPR[CMIPS::V0].nD0 = SetOverflowCallback( context, context.m_State.nGPR[CMIPS::A0].nV0, context.m_State.nGPR[CMIPS::A1].nV0, context.m_State.nGPR[CMIPS::A2].nV0); break; case 22: context.m_State.nGPR[CMIPS::V0].nD0 = SetupHardTimer( context, context.m_State.nGPR[CMIPS::A0].nV0, context.m_State.nGPR[CMIPS::A1].nV0, context.m_State.nGPR[CMIPS::A2].nV0, context.m_State.nGPR[CMIPS::A3].nV0); break; case 23: context.m_State.nGPR[CMIPS::V0].nD0 = StartHardTimer( context, context.m_State.nGPR[CMIPS::A0].nV0); break; case 24: context.m_State.nGPR[CMIPS::V0].nD0 = StopHardTimer( context, context.m_State.nGPR[CMIPS::A0].nV0); break; default: CLog::GetInstance().Warn(LOG_NAME, "(%08X): Unknown function (%d) called.\r\n", context.m_State.nPC, functionId); break; } } void CTimrman::SaveState(Framework::CZipArchiveWriter& archive) const { auto registerFile = std::make_unique(STATE_FILENAME); registerFile->SetRegister32(STATE_HARDTIMERALLOC, m_hardTimerAlloc); archive.InsertFile(std::move(registerFile)); } void CTimrman::LoadState(Framework::CZipArchiveReader& archive) { auto registerFile = CRegisterStateFile(*archive.BeginReadFile(STATE_FILENAME)); m_hardTimerAlloc = registerFile.GetRegister32(STATE_HARDTIMERALLOC); } int32 CTimrman::AllocHardTimer(uint32 source, uint32 size, uint32 prescale) { #ifdef _DEBUG CLog::GetInstance().Print(LOG_NAME, FUNCTION_ALLOCHARDTIMER "(source = %d, size = %d, prescale = %d);\r\n", source, size, prescale); #endif assert( (source == CRootCounters::COUNTER_SOURCE_SYSCLOCK) || (source == CRootCounters::COUNTER_SOURCE_PIXEL) || (source == CRootCounters::COUNTER_SOURCE_HLINE)); //Look for a match in reverse order because counters with //multiple sources appear at the beginning of the list for(int i = (CRootCounters::MAX_COUNTERS - 1); i >= 0; i--) { if( (CRootCounters::g_counterSizes[i] == size) && ((CRootCounters::g_counterSources[i] & source) != 0) && (CRootCounters::g_counterMaxScales[i] >= prescale) && ((m_hardTimerAlloc & (1 << i)) == 0)) { m_hardTimerAlloc |= (1 << i); return (i + 1); } } CLog::GetInstance().Warn(LOG_NAME, "Couldn't allocate a timer.\r\n"); return CIopBios::KERNEL_RESULT_ERROR_NO_TIMER; } int CTimrman::ReferHardTimer(uint32 source, uint32 size, uint32 mode, uint32 modeMask) { #ifdef _DEBUG CLog::GetInstance().Print(LOG_NAME, FUNCTION_REFERHARDTIMER "(source = %d, size = %d, mode = 0x%08X, mask = 0x%08X);\r\n", source, size, mode, modeMask); #endif return 0; } int32 CTimrman::FreeHardTimer(uint32 timerId) { #ifdef _DEBUG CLog::GetInstance().Print(LOG_NAME, FUNCTION_FREEHARDTIMER "(timerId = %d);\r\n", timerId); #endif uint32 hardTimerIndex = timerId - 1; if(hardTimerIndex >= CRootCounters::MAX_COUNTERS) { CLog::GetInstance().Warn(LOG_NAME, "Trying to free an invalid timer id (%d).\r\n", timerId); return CIopBios::KERNEL_RESULT_ERROR_ILLEGAL_TIMERID; } if((m_hardTimerAlloc & (1 << hardTimerIndex)) == 0) { CLog::GetInstance().Warn(LOG_NAME, "Trying to free a free timer (%d).\r\n", timerId); return CIopBios::KERNEL_RESULT_ERROR_ILLEGAL_TIMERID; } m_hardTimerAlloc &= ~(1 << hardTimerIndex); return 0; } void CTimrman::SetTimerMode(CMIPS& context, uint32 timerId, uint32 mode) { #ifdef _DEBUG CLog::GetInstance().Print(LOG_NAME, FUNCTION_SETTIMERMODE "(timerId = %d, mode = 0x%08X);\r\n", timerId, mode); #endif if(timerId == 0) return; timerId--; context.m_pMemoryMap->SetWord(CRootCounters::g_counterBaseAddresses[timerId] + CRootCounters::CNT_MODE, mode); } int CTimrman::GetTimerStatus(CMIPS& context, uint32 timerId) { #ifdef _DEBUG CLog::GetInstance().Print(LOG_NAME, FUNCTION_GETTIMERSTATUS "(timerId = %d).\r\n", timerId); #endif if(timerId == 0) return 0; timerId--; return context.m_pMemoryMap->GetWord(CRootCounters::g_counterBaseAddresses[timerId] + CRootCounters::CNT_MODE) | 0x800; } int CTimrman::GetTimerCounter(CMIPS& context, uint32 timerId) { #ifdef _DEBUG CLog::GetInstance().Print(LOG_NAME, FUNCTION_GETTIMERCOUNTER "(timerId = %d).\r\n", timerId); #endif if(timerId == 0) return 0; timerId--; return context.m_pMemoryMap->GetWord(CRootCounters::g_counterBaseAddresses[timerId] + CRootCounters::CNT_COUNT); } void CTimrman::SetTimerCompare(CMIPS& context, uint32 timerId, uint32 compare) { #ifdef _DEBUG CLog::GetInstance().Print(LOG_NAME, FUNCTION_SETTIMERCOMPARE "(timerId = %d, compare = 0x%08X);\r\n", timerId, compare); #endif if(timerId == 0) return; timerId--; context.m_pMemoryMap->SetWord(CRootCounters::g_counterBaseAddresses[timerId] + CRootCounters::CNT_COUNT, 0); context.m_pMemoryMap->SetWord(CRootCounters::g_counterBaseAddresses[timerId] + CRootCounters::CNT_TARGET, compare); } int CTimrman::GetHardTimerIntrCode(uint32 timerId) { #ifdef _DEBUG CLog::GetInstance().Print(LOG_NAME, FUNCTION_GETHARDTIMERINTRCODE "(timerId = %d).\r\n", timerId); #endif if(timerId == 0) return CIntc::LINE_RTC0; timerId--; return CRootCounters::g_counterInterruptLines[timerId]; } int32 CTimrman::SetTimerCallback(CMIPS& context, uint32 timerId, uint32 target, uint32 handler, uint32 arg) { #ifdef _DEBUG CLog::GetInstance().Print(LOG_NAME, FUNCTION_SETTIMERCALLBACK "(timerId = %d, target = %d, handler = 0x%08X, arg = 0x%08X);\r\n", timerId, target, handler, arg); #endif uint32 hardTimerIndex = timerId - 1; if(hardTimerIndex >= CRootCounters::MAX_COUNTERS) { CLog::GetInstance().Warn(LOG_NAME, "Setting callback on an invalid timer id (%d).\r\n", timerId); return CIopBios::KERNEL_RESULT_ERROR_ILLEGAL_TIMERID; } if((m_hardTimerAlloc & (1 << hardTimerIndex)) == 0) { CLog::GetInstance().Warn(LOG_NAME, "Setting callback on a free timer (%d).\r\n", timerId); return CIopBios::KERNEL_RESULT_ERROR_ILLEGAL_TIMERID; } context.m_pMemoryMap->SetWord(CRootCounters::g_counterBaseAddresses[hardTimerIndex] + CRootCounters::CNT_TARGET, target); uint32 timerInterruptLine = CRootCounters::g_counterInterruptLines[hardTimerIndex]; m_bios.ReleaseIntrHandler(timerInterruptLine); m_bios.RegisterIntrHandler(timerInterruptLine, 0, handler, arg); return 0; } int32 CTimrman::SetOverflowCallback(CMIPS& context, uint32 timerId, uint32 handler, uint32 arg) { uint32 hardTimerIndex = timerId - 1; //Our timer hardware implementation doesn't really differentiate overflow & target interrupts. //Just use the maximum value of the timer as a target to simulate overflow handling. //This would break if there were handlers set for both target and overflow on the same timer. uint32 overflowValue = (CRootCounters::g_counterSizes[hardTimerIndex] == 16) ? 0xFFFF : 0xFFFFFFFF; return SetTimerCallback(context, timerId, overflowValue, handler, arg); } int32 CTimrman::SetupHardTimer(CMIPS& context, uint32 timerId, uint32 source, uint32 gateMode, uint32 prescale) { #ifdef _DEBUG CLog::GetInstance().Print(LOG_NAME, FUNCTION_SETUPHARDTIMER "(timerId = %d, source = %d, mode = %d, prescale = %d);\r\n", timerId, source, gateMode, prescale); #endif uint32 hardTimerIndex = timerId - 1; if(hardTimerIndex >= CRootCounters::MAX_COUNTERS) { CLog::GetInstance().Warn(LOG_NAME, "Trying to setup an invalid timer (%d).\r\n", timerId); return CIopBios::KERNEL_RESULT_ERROR_ILLEGAL_TIMERID; } if((m_hardTimerAlloc & (1 << hardTimerIndex)) == 0) { CLog::GetInstance().Warn(LOG_NAME, "Trying to setup a free timer (%d).\r\n", timerId); return CIopBios::KERNEL_RESULT_ERROR_ILLEGAL_TIMERID; } assert(gateMode == 0); assert((CRootCounters::g_counterSources[hardTimerIndex] & source) != 0); assert(CRootCounters::g_counterMaxScales[hardTimerIndex] >= prescale); //Set proper clock divider auto modeAddr = CRootCounters::g_counterBaseAddresses[hardTimerIndex] + CRootCounters::CNT_MODE; auto mode = make_convertible(context.m_pMemoryMap->GetWord(modeAddr)); mode.clc = (source != CRootCounters::COUNTER_SOURCE_SYSCLOCK) ? 1 : 0; if(prescale == 1) mode.div = CRootCounters::COUNTER_SCALE_1; else if(prescale == 8) mode.div = CRootCounters::COUNTER_SCALE_8; else if(prescale == 16) mode.div = CRootCounters::COUNTER_SCALE_16; else if(prescale == 256) mode.div = CRootCounters::COUNTER_SCALE_256; else assert(false); context.m_pMemoryMap->SetWord(modeAddr, mode); return 0; } int32 CTimrman::StartHardTimer(CMIPS& context, uint32 timerId) { #ifdef _DEBUG CLog::GetInstance().Print(LOG_NAME, FUNCTION_STARTHARDTIMER "(timerId = %d);\r\n", timerId); #endif uint32 hardTimerIndex = timerId - 1; if(hardTimerIndex >= CRootCounters::MAX_COUNTERS) { CLog::GetInstance().Warn(LOG_NAME, "Trying to start an invalid timer (%d).\r\n", timerId); return CIopBios::KERNEL_RESULT_ERROR_ILLEGAL_TIMERID; } if((m_hardTimerAlloc & (1 << hardTimerIndex)) == 0) { CLog::GetInstance().Warn(LOG_NAME, "Trying to start a free timer (%d).\r\n", timerId); return CIopBios::KERNEL_RESULT_ERROR_ILLEGAL_TIMERID; } //Enable timer { auto modeAddr = CRootCounters::g_counterBaseAddresses[hardTimerIndex] + CRootCounters::CNT_MODE; auto mode = make_convertible(context.m_pMemoryMap->GetWord(modeAddr)); mode.tar = 1; mode.iq1 = 1; mode.iq2 = 1; context.m_pMemoryMap->SetWord(CRootCounters::g_counterBaseAddresses[hardTimerIndex] + CRootCounters::CNT_COUNT, 0x0000); context.m_pMemoryMap->SetWord(CRootCounters::g_counterBaseAddresses[hardTimerIndex] + CRootCounters::CNT_MODE, mode); } //Enable interrupts { uint32 timerInterruptLine = CRootCounters::g_counterInterruptLines[hardTimerIndex]; if(m_bios.FindIntrHandler(timerInterruptLine) != -1) { uint32 mask = context.m_pMemoryMap->GetWord(CIntc::MASK0); mask |= (1 << timerInterruptLine); context.m_pMemoryMap->SetWord(CIntc::MASK0, mask); } } return 0; } int32 CTimrman::StopHardTimer(CMIPS& context, uint32 timerId) { #ifdef _DEBUG CLog::GetInstance().Print(LOG_NAME, FUNCTION_STOPHARDTIMER "(timerId = %d);\r\n", timerId); #endif uint32 hardTimerIndex = timerId - 1; if(hardTimerIndex >= CRootCounters::MAX_COUNTERS) { CLog::GetInstance().Warn(LOG_NAME, "Trying to stop an invalid timer (%d).\r\n", timerId); return CIopBios::KERNEL_RESULT_ERROR_ILLEGAL_TIMERID; } if((m_hardTimerAlloc & (1 << hardTimerIndex)) == 0) { CLog::GetInstance().Warn(LOG_NAME, "Trying to stop a free timer (%d).\r\n", timerId); return CIopBios::KERNEL_RESULT_ERROR_ILLEGAL_TIMERID; } //Stop timer { auto modeAddr = CRootCounters::g_counterBaseAddresses[hardTimerIndex] + CRootCounters::CNT_MODE; auto mode = make_convertible(context.m_pMemoryMap->GetWord(modeAddr)); mode.tar = 0; mode.iq1 = 0; mode.iq2 = 0; context.m_pMemoryMap->SetWord(CRootCounters::g_counterBaseAddresses[hardTimerIndex] + CRootCounters::CNT_MODE, mode); } //Disable interrupts { uint32 timerInterruptLine = CRootCounters::g_counterInterruptLines[hardTimerIndex]; uint32 mask = context.m_pMemoryMap->GetWord(CIntc::MASK0); mask &= ~(1 << timerInterruptLine); context.m_pMemoryMap->SetWord(CIntc::MASK0, mask); } return 0; }