/*
- * Acess 2
+ * Acess2 Kernel
* - By John Hodge (thePowersGang)
*
* Timer Code
*/
+#define SANITY 1 // Enable ASSERTs
+#define DEBUG 0
#include <acess.h>
#include <timers.h>
+#include <timers_int.h>
#include <events.h>
#include <hal_proc.h> // Proc_GetCurThread
-
-// === CONSTANTS ===
-#define NUM_TIMERS 8
-
-// === TYPEDEFS ===
-struct sTimer {
- tTimer *Next;
- Sint64 FiresAfter;
- void (*Callback)(void*);
- void *Argument;
-};
+#include <workqueue.h>
+#include <threads_int.h> // Used to get thread timer
// === PROTOTYPES ===
+void Timer_CallbackThread(void *Unused);
void Timer_CallTimers(void);
+#if 0
+tTimer *Time_CreateTimer(int Delta, tTimerCallback *Callback, void *Argument);
+void Time_ScheduleTimer(tTimer *Timer, int Delta);
+void Time_RemoveTimer(tTimer *Timer);
+tTimer *Time_AllocateTimer(tTimerCallback *Callback, void *Argument);
+#endif
+void Time_InitTimer(tTimer *Timer, tTimerCallback *Callback, void *Argument);
+#if 0
+void Time_FreeTimer(tTimer *Timer);
+void Time_Delay(int Time);
+#endif
// === GLOBALS ===
volatile Uint64 giTicks = 0;
volatile Sint64 giTimestamp = 0;
volatile Uint64 giPartMiliseconds = 0;
-tTimer *gTimers; // TODO: Replace by a ring-list timer
+tTimer *gTimers;
+tWorkqueue gTimers_CallbackQueue;
+tShortSpinlock gTimers_ListLock;
// === CODE ===
+void Timer_CallbackThread(void *Unused)
+{
+ Threads_SetName("Timer Callback Thread");
+ Workqueue_Init(&gTimers_CallbackQueue, "Timer Callbacks", offsetof(tTimer, Next));
+
+ for(;;)
+ {
+ tTimer *timer = Workqueue_GetWork(&gTimers_CallbackQueue);
+
+ if( !timer->Callback ) {
+ LOG("Timer %p doesn't have a callback", timer);
+ ASSERT( timer->Callback );
+ }
+
+ // Save callback and argument (because once the mutex is released
+ // the timer may no longer be valid)
+ tTimerCallback *cb = timer->Callback;
+ void *arg = timer->Argument;
+
+ LOG("Callback fire %p", timer);
+
+ // Allow Time_RemoveTimer to be called safely
+ timer->bActive = 0;
+
+ // Fire callback
+ cb(arg);
+
+ // Mark timer as no longer needed
+ }
+}
+
/**
* \fn void Timer_CallTimers()
*/
void Timer_CallTimers()
{
+ // Tick the random number generator every time timers are checked
+ rand();
+
+ SHORTLOCK(&gTimers_ListLock);
+ LOG("gTimers = %p (%lli ms)", gTimers, (gTimers ? gTimers->FiresAfter : 0));
while( gTimers && gTimers->FiresAfter < now() )
{
- tTimer *next;
+ ASSERT( gTimers != gTimers->Next );
+ // Get timer from list
+ tTimer *timer = gTimers;
+ gTimers = gTimers->Next;
- if( gTimers->Callback )
- gTimers->Callback(gTimers->Argument);
- else
- Threads_PostEvent(gTimers->Argument, THREAD_EVENT_TIMER);
-
- next = gTimers->Next;
- free(gTimers);
- gTimers = next;
+ // Perform event
+ if( timer->Callback ) {
+ LOG("Callback schedule %p", timer);
+ Workqueue_AddWork(&gTimers_CallbackQueue, timer);
+ }
+ else {
+ LOG("Event fire %p", timer);
+ ASSERT( timer->Argument );
+ Threads_PostEvent(timer->Argument, THREAD_EVENT_TIMER);
+ timer->bActive = 0;
+ }
}
+ SHORTREL(&gTimers_ListLock);
}
/**
- * \brief Schedule an action
+ * \brief Schedule an action (Legacy)
*/
tTimer *Time_CreateTimer(int Delta, tTimerCallback *Callback, void *Argument)
{
- tTimer *ret;
- tTimer *t, *p;
-
- if(Callback == NULL)
- Argument = Proc_GetCurThread();
+ tTimer *ret = malloc(sizeof(tTimer));
+ if( !ret ) return NULL;
+ Time_InitTimer(ret, Callback, Argument);
+ Time_ScheduleTimer(ret, Delta);
+ return ret;
+}
- // TODO: Use a pool instead?
- ret = malloc(sizeof(tTimer));
+/**
+ * \brief Schedule a timer to fire
+ */
+void Time_ScheduleTimer(tTimer *Timer, int Delta)
+{
+ tTimer *t;
+ tTimer **prev_next;
+
+ // Sanity checks
+ if( !Timer ) return ;
+
+ if( Timer->bActive ) return;
+
+ // Set time
+ Timer->FiresAfter = now() + Delta;
- ret->Callback = Callback;
- ret->FiresAfter = now() + Delta;
- ret->Argument = Argument;
+ // Debug
+ LOG("%p added timer %p - %i ms (ts=%lli)",
+ __builtin_return_address(0), Timer, Delta, Timer->FiresAfter);
// Add into list (sorted)
- for( p = (tTimer*)&gTimers, t = gTimers; t; p = t, t = t->Next )
+ SHORTLOCK(&gTimers_ListLock);
+// Mutex_Release( &Timer->Lock ); // Prevent deadlocks
+ for( prev_next = &gTimers, t = gTimers; t; prev_next = &t->Next, t = t->Next )
{
- if( t->FiresAfter > ret->FiresAfter ) break;
+ ASSERT( prev_next != &t->Next );
+ ASSERT( CheckMem(t, sizeof(tTimer)) );
+ if( t == Timer )
+ {
+ LOG("Double schedule - increasing delta");
+ SHORTREL(&gTimers_ListLock);
+ return ;
+ }
+ LOG(" t = %p ts:%lli", t, t->FiresAfter);
+ if( t->FiresAfter > Timer->FiresAfter ) break;
}
- ret->Next = t;
- p->Next = ret;
-
- return ret;
+ Timer->Next = t;
+ *prev_next = Timer;
+ Timer->bActive = 1;
+ LOG(" prev_next=%p Timer=%p next=%p", prev_next, Timer, t);
+ SHORTREL(&gTimers_ListLock);
}
/**
- * \brief Delete a timer
+ * \brief Delete a timer from the running list
*/
void Time_RemoveTimer(tTimer *Timer)
{
- tTimer *t, *p;
- for( p = (tTimer*)&gTimers, t = gTimers; t; p = t, t = t->Next )
+ tTimer *t;
+ tTimer **prev_next;
+
+ if( !Timer ) return ;
+
+ SHORTLOCK(&gTimers_ListLock);
+ for( prev_next = &gTimers, t = gTimers; t; prev_next = &t->Next, t = t->Next )
{
+ ASSERT( prev_next != &t->Next );
+ ASSERT( CheckMem(t, sizeof(tTimer)) );
if( t == Timer )
{
- p->Next = t->Next;
- free(Timer);
- return ;
+ *prev_next = t->Next;
+ break ;
}
}
+ SHORTREL(&gTimers_ListLock);
+
+ if( t ) {
+ Timer->bActive = 0;
+ LOG("%p removed %p", __builtin_return_address(0), Timer);
+ }
+ else
+ LOG("%p tried to remove %p (already complete)", __builtin_return_address(0), Timer);
+}
+
+/**
+ * \brief Allocate a timer object, but don't schedule it
+ */
+tTimer *Time_AllocateTimer(tTimerCallback *Callback, void *Argument)
+{
+ tTimer *ret = malloc(sizeof(tTimer));
+ if( !ret ) return NULL;
+ Time_InitTimer(ret, Callback, Argument);
+ return ret;
+}
+
+/**
+ * \brief Initialise a timer
+ * \note Mostly an internal function
+ */
+void Time_InitTimer(tTimer *Timer, tTimerCallback *Callback, void *Argument)
+{
+ if(Callback == NULL)
+ Argument = Proc_GetCurThread();
+ Timer->FiresAfter = 0;
+ Timer->Callback = Callback;
+ Timer->Argument = Argument;
+// memset( &Timer->Lock, 0, sizeof(Timer->Lock) );
+ Timer->bActive = 0;
+ LOG("Initialised timer %p (cb=%p,arg=%p)", Timer, Callback, Argument);
+}
+
+/**
+ * \brief Free an allocated timer
+ */
+void Time_FreeTimer(tTimer *Timer)
+{
+ if( !Timer ) return ;
+ Time_RemoveTimer( Timer ); // Just in case
+
+ // Ensures that we don't free until the timer callback has started
+ while( Timer->bActive ) Threads_Yield();
+ // Release won't be needed, as nothing should be waiting on it
+// Mutex_Acquire( &Timer->Lock );
+
+ // Free timer
+ free(Timer);
+ LOG("%p deallocated %p", __builtin_return_address(0), Timer);
+}
+
+void Time_ScheduleEvent(int Delay)
+{
+ tTimer *t = &Proc_GetCurThread()->ThreadTimer;
+ Time_RemoveTimer(t);
+ Time_InitTimer(t, NULL, NULL);
+ Time_ScheduleTimer(t, Delay);
}
/**
*/
void Time_Delay(int Delay)
{
-// tTime dest = now() + Delay;
-// while(dest > now()) Threads_Yield();
- Time_CreateTimer(Delay, NULL, NULL);
+ LOG("(%i)", Delay);
+ Threads_ClearEvent(THREAD_EVENT_TIMER);
+ Time_ScheduleEvent(Delay);
Threads_WaitEvents(THREAD_EVENT_TIMER);
}
// === EXPORTS ===
-EXPORT(Time_CreateTimer);
+//EXPORT(Time_CreateTimer);
+EXPORT(Time_ScheduleTimer);
EXPORT(Time_RemoveTimer);
+EXPORT(Time_AllocateTimer);
+//EXPORT(Time_InitTimer);
+EXPORT(Time_FreeTimer);
EXPORT(Time_Delay);