/* * $Id: smartlock.c 200 2007-11-03 11:53:08Z Hans de Ruiter $ * * $Date: 2007-11-03 12:26:08 $ * $Revision: 000 $ * * (C) 2007 by Hans de Ruiter * All rights reserved * * This file is part of the MiniGL library project * See the file Licence.txt for more details * */ #include "smartlock.h" #include "sysinc.h" #include #include #include #include #include #include #include #include #include "util.h" // Ultra low latency replacement for ReadEClockVal static inline void ReadTBC(struct EClockVal* mark) { register uint32 tbcUpper0, tbcUpper1, tbcLower; do { __asm volatile ( #ifdef MGL_TABOR "mfspr %0,269\n\t" "mfspr %1,268\n\t" "mfspr %2,269" #else "mftbu %0\n\t" "mftb %1\n\t" "mftbu %2" #endif : "=r"(tbcUpper0), "=r"(tbcLower), "=r"(tbcUpper1) ); } while (tbcUpper0 != tbcUpper1); mark->ev_hi = tbcUpper0; mark->ev_lo = tbcLower; } #if 1 #define READECLOCK(mark) ReadTBC((mark)) #else #define READECLOCK(mark) ITimer->ReadEClock((mark)) #endif //#define DEBUG_SMARTLOCK #define WATCHDOG_STACKSIZE (16 * 1024) // ----- Function Prototypes ----- /** Creates the watchdog thread. */ GLboolean smartlock_createThread(SmartLock *smartLock); /** Terminates all threads. */ void smartlock_terminateThread(SmartLock *smartLock); /** The watchdog thread. A pointer to the smartlock object that created the thread should be placed in * task->tc_UserData */ void* smartlock_watchdogThread(); /** Timer based locking stuff. TMA_Start starts the time measuring for the lock time. The 68k uses the ReadEClock function due to its low overhead. PPC will use GetSysTimePPC for the same reasons. The EClock version reads the eclock and stores the current values in the handle. It then calculates a maximum lock time based on the assumption that the lock should be unlocked after 0.05 seconds (i.e. twenty times per second). TMA_Check checks if the specified time has expired. If it returns GL_FALSE, the lock may be kept alive. On return of GL_TRUE, the lock must be released. Note that this routine handles the case where the ev_hi values has changed, i.e. the eV_lo value had an overrun. This code also assumes, however, that the difference between the current and former ev_hi is no more than 1. This is, however, a very safe assumption; It takes approx. 100 minutes for the ev_hi field to increment, and it is extremely unlikely that the ev_hi field overruns - this will happen after approx. 820,000 years uptime (of course, a reliable system should be prepared for this) */ static void TMA_Start(LockTimeHandle *handle); static GLboolean TMA_Check(LockTimeHandle *handle, GLuint numTicks); /** Creates a timer, including the message port required to use it * * @param unitNum the timer unit to open * * @return timerequest* the timer (timer IO request) */ struct TimeRequest* timer_create(uint32 unitNum); /** Deletes a timer, and its message-port. * * @param timerIO the timerrequest object to close and deallocate */ void timer_delete(struct TimeRequest *timerIO); static uint32 eFreq = 0; // ----- Functions ----- SmartLock* smartlock_new(uint32 maxLockTime, GLcontext context) { SmartLock *smartLock; smartLock = (SmartLock*)AllocVecInternal(sizeof(SmartLock),MEMF_SHARED|MEMF_CLEAR); if (smartLock) { smartLock->maxLockTime = maxLockTime; smartLock->context = context; struct EClockVal eClkVal; /*uint32*/ eFreq = ITimer->ReadEClock(&eClkVal); smartLock->maxTicks = eFreq * ((double)smartLock->maxLockTime / 1000000.0); smartLock->warningTicks = smartLock->maxTicks * 0.85; smartLock->drawingLock = IExec->AllocSysObjectTags(ASOT_MUTEX, ASOMUTEX_Recursive, TRUE, TAG_END); smartLock->drawing = 0; if (!smartLock->drawingLock) { smartlock_free(smartLock); return NULL; } if (smartlock_createThread(smartLock) == GL_FALSE) { smartlock_free(smartLock); return NULL; } } return smartLock; } void smartlock_free(SmartLock *smartLock) { smartlock_terminateThread(smartLock); if (smartLock->drawingLock) { IExec->FreeSysObject(ASOT_MUTEX, smartLock->drawingLock); smartLock->drawingLock = NULL; } FreeVecInternal(smartLock); } GLboolean smartlock_beginDraw(SmartLock *smartLock) { BOOL releaseMutex; IExec->MutexObtain(smartLock->drawingLock); releaseMutex = (smartLock->drawing > 0); #ifdef DEBUG_SMARTLOCK IExec->DebugPrintF("smartlock: beginDraw\n"); #endif if ( smartLock->context->w3dLocked == GL_TRUE && TMA_Check(&(smartLock->context->LockTime), smartLock->warningTicks) == GL_TRUE ) { // Over half the allowed locking time has gone, unlock and then relock in order to // reduce the posibility of going over-time and hence preventing other tasks // (audio tasks in particular) from having sufficient access #ifdef DEBUG_SMARTLOCK IExec->DebugPrintF("smartlock: Unlocking\n"); #endif IWarp3D->W3D_UnLockHardware(smartLock->context->w3dContext); smartLock->context->w3dLocked = GL_FALSE; } if (smartLock->context->w3dLocked == GL_FALSE) { #ifdef DEBUG_SMARTLOCK IExec->DebugPrintF("smartlock: Locking\n"); #endif if (W3D_SUCCESS != IWarp3D->W3D_LockHardware(smartLock->context->w3dContext)) { IExec->MutexRelease(smartLock->drawingLock); smartLock->drawing = 0; return GL_FALSE; } smartLock->context->w3dLocked = GL_TRUE; TMA_Start(&(smartLock->context->LockTime)); // Signal the watchdog IExec->Signal((struct Task*)smartLock->watchdogThread, smartLock->watchdogThreadSigMask); } // Increase the nested drawing count ++smartLock->drawing; if (releaseMutex) { // Lock was already held. Need to unlock mutex because smartlock_endDraw() will only unlock once IExec->MutexRelease(smartLock->drawingLock); } return GL_TRUE; } GLboolean smartlock_endDraw(SmartLock *smartLock) { GLboolean didUnlock; #ifdef DEBUG_SMARTLOCK IExec->DebugPrintF("smartlock: endDraw\n"); #endif IExec->MutexObtain(smartLock->drawingLock); if (smartLock->context->w3dLocked == GL_TRUE) { if (TMA_Check(&(smartLock->context->LockTime), smartLock->warningTicks) == GL_TRUE) { // Time to unlock #ifdef DEBUG_SMARTLOCK IExec->DebugPrintF("smartlock endDraw: Unlock\n"); #endif IWarp3D->W3D_UnLockHardware(smartLock->context->w3dContext); smartLock->context->w3dLocked = GL_FALSE; didUnlock = GL_TRUE; } else { didUnlock = GL_FALSE; } } else { didUnlock = GL_FALSE; } // Unlock to cover the lock left by smartlock_beginDraw(); if (smartLock->drawing == 1) { IExec->MutexRelease(smartLock->drawingLock); } if (smartLock->drawing > 0) { --smartLock->drawing; } IExec->MutexRelease(smartLock->drawingLock); return didUnlock; } GLboolean smartlock_forceUnlock(SmartLock *smartLock) { GLboolean didUnlock; #ifdef DEBUG_SMARTLOCK IExec->DebugPrintF("smartlock: forceUnlock\n"); #endif IExec->MutexObtain(smartLock->drawingLock); if (smartLock->context->w3dLocked == GL_TRUE) { #ifdef DEBUG_SMARTLOCK IExec->DebugPrintF("smartlock forceUnlock: Unlock\n"); #endif IWarp3D->W3D_UnLockHardware(smartLock->context->w3dContext); smartLock->context->w3dLocked = GL_FALSE; didUnlock = GL_TRUE; } else { didUnlock = GL_FALSE; } if (smartLock->drawing > 0) { // Need to release the drawing mutex obtained by smartlock_beginDraw() IExec->MutexRelease(smartLock->drawingLock); } smartLock->drawing = 0; IExec->MutexRelease(smartLock->drawingLock); return didUnlock; } GLboolean smartlock_createThread(SmartLock *smartLock) { smartLock->mainThread = IExec->FindTask(NULL); smartLock->watchdogThread = IDOS->CreateNewProcTags( NP_Entry, (uint32)smartlock_watchdogThread, NP_Name, "MiniGL watch-dog", NP_Priority, 50, // want to guarantee that it responds quickly NP_Child, TRUE, NP_StackSize, WATCHDOG_STACKSIZE, NP_NotifyOnDeathSigTask, (uint32)NULL, NP_UserData, (uint32)smartLock, TAG_END ); if (!smartLock->watchdogThread) { return GL_FALSE; } // Wait for the thread to startup while (smartLock->watchdogThreadSigMask == 0) { IExec->Wait(SIGF_CHILD); } // Check if the thread failed to initialize if (smartLock->watchdogThreadSigMask == ~0UL) { smartlock_terminateThread(smartLock); return GL_FALSE; } return GL_TRUE; } void smartlock_terminateThread(SmartLock *smartLock) { if (smartLock->watchdogThread) { struct Task *watchdogTask = &(smartLock->watchdogThread->pr_Task); // No accessor method for this? IExec->Signal(watchdogTask, SIGBREAKF_CTRL_C); smartLock->watchdogThread = NULL; } // Necessary because some programs (ScreenBlankerPrefs) delete the object in a different thread smartLock->mainThread = IExec->FindTask(NULL); // Wait until the watchdog task has ended while (smartLock->watchdogThreadSigMask != 0) { IExec->Wait(SIGF_CHILD); } } void* smartlock_watchdogThread() { struct Task *thisTask = IExec->FindTask(NULL); SmartLock *thisSmartLock = (SmartLock*)thisTask->tc_UserData; // Setup struct TimeRequest *timer = timer_create(UNIT_ECLOCK); if (!timer) { thisSmartLock->watchdogThreadSigMask = ~0UL; return NULL; } struct TimeRequest *timer2 = (struct TimeRequest*)IExec->AllocSysObjectTags(ASOT_IOREQUEST, ASOIOR_Duplicate, timer, TAG_DONE); if (!timer2) { timer_delete(timer); thisSmartLock->watchdogThreadSigMask = ~0UL; return NULL; } struct TimeRequest *currTimer = timer2; struct TimeRequest *prevTimer = timer; struct EClockVal eClkVal; uint32 eFreq = ITimer->ReadEClock(&eClkVal); timer->Request.io_Command = TR_ADDREQUEST; timer->Time.Seconds = 0; uint32 delayVal = eFreq * ((double)thisSmartLock->maxLockTime / 1000000.0); timer->Time.Microseconds = delayVal; timer2->Request.io_Command = TR_ADDREQUEST; timer2->Time.Seconds = 0; timer2->Time.Microseconds = delayVal; struct MsgPort *timerPort = timer->Request.io_Message.mn_ReplyPort; uint32 timerSignal = (1 << timerPort->mp_SigBit); int8 eventSigBit = IExec->AllocSignal(-1); if (eventSigBit == -1) { IExec->FreeSysObject(ASOT_IOREQUEST, timer2); timer_delete(timer); thisSmartLock->watchdogThreadSigMask = ~0UL; return NULL; } uint32 eventSignal = (1 << eventSigBit); thisSmartLock->watchdogThreadSigMask = eventSignal; // All done, and ready to go. Signal the main thread that we're ready IExec->Signal(thisSmartLock->mainThread, SIGF_CHILD); // Main loop uint32 signalMask = SIGBREAKF_CTRL_C | eventSignal | timerSignal; while (1) { uint32 signal = IExec->Wait(signalMask); if ((signal & timerSignal) && IExec->GetMsg(timerPort)) { // The hardware has been locked as long as is allowed; unlock it now. IExec->MutexObtain(thisSmartLock->drawingLock); if (thisSmartLock->context->w3dLocked == GL_TRUE) { #ifdef DEBUG_SMARTLOCK IExec->DebugPrintF("smartlock: watchdog unlock\n"); #endif IWarp3D->W3D_UnLockHardware(thisSmartLock->context->w3dContext); thisSmartLock->context->w3dLocked = GL_FALSE; } IExec->MutexRelease(thisSmartLock->drawingLock); } if (signal & eventSignal) { // The hardware was just locked, start the timer struct TimeRequest *temp = currTimer; currTimer = prevTimer; prevTimer = temp; currTimer->Time.Seconds = 0; currTimer->Time.Microseconds = delayVal; IExec->SendIO((struct IORequest*)currTimer); // Ensure that the previous IO request is cancelled before it is reused if (IExec->CheckIO((struct IORequest*)prevTimer) == NULL) { IExec->AbortIO((struct IORequest*)prevTimer); IExec->WaitIO((struct IORequest*)prevTimer); } } if (signal & SIGBREAKF_CTRL_C) { // Time to quit break; } } // Cleanup IExec->FreeSignal(eventSigBit); thisSmartLock->watchdogThreadSigMask = 0; if (IExec->CheckIO((struct IORequest*)timer2) == NULL) { IExec->AbortIO((struct IORequest*)timer2); IExec->WaitIO((struct IORequest*)timer2); } IExec->FreeSysObject(ASOT_IOREQUEST, timer2); timer_delete(timer); // Necessary because some programs (ScreenBlankerPrefs) delete the object in a different thread IExec->Signal(thisSmartLock->mainThread, SIGF_CHILD); return NULL; } void TMA_Start(LockTimeHandle *handle) { struct EClockVal eval; handle->e_freq = eFreq; READECLOCK(&eval); //handle->e_freq = ITimer->ReadEClock(&eval); handle->s_hi = eval.ev_hi; handle->s_lo = eval.ev_lo; } GLboolean TMA_Check(LockTimeHandle *handle, GLuint maxTicks) { struct EClockVal eval; uint32 ticks; //ITimer->ReadEClock(&eval); READECLOCK(&eval); if (eval.ev_hi == handle->s_hi) { ticks = eval.ev_lo - handle->s_lo; } else { ticks = (~0)-handle->s_lo + eval.ev_lo; } if (ticks > maxTicks) { return GL_TRUE; } return GL_FALSE; } struct TimeRequest* timer_create(uint32 unitNum) { struct MsgPort *timerPort = CreateMsgPortInternal(); if (timerPort == NULL) { return FALSE; } struct TimeRequest* timerIO = (struct TimeRequest *)IExec->AllocSysObjectTags( ASOT_IOREQUEST, ASOIOR_Size, sizeof(struct TimeRequest), ASOIOR_ReplyPort, timerPort, TAG_DONE ); if (timerIO == NULL) { DeleteMsgPortInternal(timerPort); return NULL; } int8 error = IExec->OpenDevice(TIMERNAME, unitNum,(struct IORequest *) timerIO, 0L); if (error != 0 ) { timer_delete(timerIO); return NULL; } return timerIO; } void timer_delete(struct TimeRequest *timerIO) { if (timerIO) { if (timerIO->Request.io_Command != NT_MESSAGE && IExec->CheckIO((struct IORequest*)timerIO) == NULL) { IExec->AbortIO((struct IORequest*)timerIO); IExec->WaitIO((struct IORequest*)timerIO); } struct MsgPort *timerPort = timerIO->Request.io_Message.mn_ReplyPort; IExec->CloseDevice((struct IORequest *) timerIO); IExec->FreeSysObject(ASOT_IOREQUEST, timerIO); if (timerPort) { DeleteMsgPortInternal(timerPort); } } }