/* * This file is subject to the terms of the GFX License. If a copy of * the license was not distributed with this file, you can obtain one at: * * http://ugfx.org/license.html */ #include "../../gfx.h" #if GOS_NEED_X_THREADS /********************************************************* * Semaphores and critical region functions *********************************************************/ #if !defined(INTERRUPTS_OFF) || !defined(INTERRUPTS_ON) #define INTERRUPTS_OFF() #define INTERRUPTS_ON() #endif void gfxSystemLock(void) { INTERRUPTS_OFF(); } void gfxSystemUnlock(void) { INTERRUPTS_ON(); } void gfxMutexInit(gfxMutex *pmutex) { pmutex[0] = 0; } void gfxMutexEnter(gfxMutex *pmutex) { INTERRUPTS_OFF(); while (pmutex[0]) { INTERRUPTS_ON(); gfxYield(); INTERRUPTS_OFF(); } pmutex[0] = 1; INTERRUPTS_ON(); } void gfxMutexExit(gfxMutex *pmutex) { pmutex[0] = 0; } void gfxSemInit(gfxSem *psem, semcount_t val, semcount_t limit) { psem->cnt = val; psem->limit = limit; } bool_t gfxSemWait(gfxSem *psem, delaytime_t ms) { systemticks_t starttm, delay; // Convert our delay to ticks starttm = 0; switch (ms) { case TIME_IMMEDIATE: delay = TIME_IMMEDIATE; break; case TIME_INFINITE: delay = TIME_INFINITE; break; default: delay = gfxMillisecondsToTicks(ms); if (!delay) delay = 1; starttm = gfxSystemTicks(); } INTERRUPTS_OFF(); while (psem->cnt <= 0) { INTERRUPTS_ON(); // Check if we have exceeded the defined delay switch (delay) { case TIME_IMMEDIATE: return FALSE; case TIME_INFINITE: break; default: if (gfxSystemTicks() - starttm >= delay) return FALSE; break; } gfxYield(); INTERRUPTS_OFF(); } psem->cnt--; INTERRUPTS_ON(); return TRUE; } bool_t gfxSemWaitI(gfxSem *psem) { if (psem->cnt <= 0) return FALSE; psem->cnt--; return TRUE; } void gfxSemSignal(gfxSem *psem) { INTERRUPTS_OFF(); gfxSemSignalI(psem); INTERRUPTS_ON(); } void gfxSemSignalI(gfxSem *psem) { if (psem->cnt < psem->limit) psem->cnt++; } /********************************************************* * Sleep functions *********************************************************/ void gfxSleepMilliseconds(delaytime_t ms) { systemticks_t starttm, delay; // Safety first switch (ms) { case TIME_IMMEDIATE: return; case TIME_INFINITE: while(1) gfxYield(); } // Convert our delay to ticks delay = gfxMillisecondsToTicks(ms); starttm = gfxSystemTicks(); do { gfxYield(); } while (gfxSystemTicks() - starttm < delay); } void gfxSleepMicroseconds(delaytime_t ms) { systemticks_t starttm, delay; // Safety first switch (ms) { case TIME_IMMEDIATE: return; case TIME_INFINITE: while(1) gfxYield(); } // Convert our delay to ticks delay = gfxMillisecondsToTicks(ms/1000); starttm = gfxSystemTicks(); do { gfxYield(); } while (gfxSystemTicks() - starttm < delay); } /********************************************************* * Threading functions *********************************************************/ /** For each scheduler the following need to be defined... * * void _gfxThreadsInit(void); - Initialise the scheduler * void _gfxStartThread(thread *oldt, thread *newt); - Start a new thread * void _gfxTaskSwitch(thread *oldt, thread *newt); - Switch to a different thread * */ typedef struct thread { struct thread * next; // Next thread int flags; // Flags #define FLG_THD_ALLOC 0x0001 #define FLG_THD_MAIN 0x0002 #define FLG_THD_DEAD 0x0004 #define FLG_THD_WAIT 0x0008 size_t size; // Size of the thread stack (including this structure) threadreturn_t (*fn)(void *param); // Thread function void * param; // Parameter for the thread function void * cxt; // The current thread context. } thread; typedef struct threadQ { thread *head; thread *tail; } threadQ; static threadQ readyQ; // The list of ready threads static threadQ deadQ; // Where we put threads waiting to be deallocated thread * _gfxCurrentThread; // The current running thread - unfortunately this has to be non-static for the keil compiler static thread mainthread; // The main thread context #undef GFX_THREADS_DONE #if GFX_CPU == GFX_CPU_CORTEX_M0 || GFX_CPU == GFX_CPU_CORTEX_M1 #include "gos_x_threads_cortexm01.h" #elif GFX_CPU == GFX_CPU_CORTEX_M3 || GFX_CPU == GFX_CPU_CORTEX_M4 || GFX_CPU == GFX_CPU_CORTEX_M7 #include "gos_x_threads_cortexm347.h" #elif GFX_CPU == GFX_CPU_CORTEX_M4_FP || GFX_CPU == GFX_CPU_CORTEX_M7_FP #include "gos_x_threads_cortexm47fp.h" #endif #ifndef GFX_THREADS_DONE #define GFX_THREADS_DONE #include // Prototype for memcpy() #include /** * Some compilers define a _setjmp() and a setjmp(). * The difference between them is that setjmp() saves the signal masks. * That is of no use to us so we prefer to use the _setjmp() methods. * If they don't exist compile them to be the standard setjmp() function. * Similarly for longjmp(). */ #if (!defined(setjmp) && !defined(_setjmp)) || GFX_COMPILER == GFX_COMPILER_KEIL || GFX_COMPILER == GFX_COMPILER_MINGW32 || GFX_COMPILER == GFX_COMPILER_MINGW64 #define CXT_SAVE setjmp #else #define CXT_SAVE _setjmp #endif #if (!defined(longjmp) && !defined(_longjmp)) || GFX_COMPILER == GFX_COMPILER_KEIL || GFX_COMPILER == GFX_COMPILER_MINGW32 || GFX_COMPILER == GFX_COMPILER_MINGW64 #define CXT_RESTORE longjmp #else #define CXT_RESTORE _longjmp #endif // A place to store the main thread context. // All other threads will store the context directly after the thread structure (as part of the stack space). static jmp_buf maincxt; /** * There are some compilers we know how they store the jmpbuf. For those * we can use the constant macro definitions. For others we have to "auto-detect". * Auto-detection is hairy and there is no guarantee it will work on all architectures. * For those it doesn't - read the compiler manuals and the library source code to * work out the correct macro values. * You can use the debugger to work out the values for your compiler and put them here. * Defining these macros as constant values makes the system behaviour guaranteed but also * makes your code compiler and cpu architecture dependent. It also saves a heap of code * and a few bytes of RAM. * * MACROS: * * AUTO_DETECT_STACKFRAME TRUE/FALSE - TRUE to auto-detect stack frame structure * STACK_DIR_UP Macro/bool_t - TRUE if the stack grows up instead of down * MASK1 Macro/uint32_t - The 1st mask of jmp_buf elements that need relocation * MASK2 Macro/uint32_t - The 2nd mask of jmp_buf elements that need relocation * STACK_BASE Macro/size_t - The base of the stack frame relative to the local variables * _gfxThreadsInit() Macro/Function - Initialise the scheduler * */ #if GFX_COMPILER == GFX_COMPILER_MINGW32 #define AUTO_DETECT_STACKFRAME FALSE #define STACK_DIR_UP FALSE #define MASK1 0x00000011 #define MASK2 0x00000000 #define STACK_BASE 12 #define _gfxThreadsInit() mainthread.cxt = maincxt #else // Use auto-detection of the stack frame format // Assumes all the relevant stuff to be relocated is in the first 256 bytes of the jmpbuf. #define AUTO_DETECT_STACKFRAME TRUE #define STACK_DIR_UP stackdirup // TRUE if the stack grow up instead of down #define MASK1 jmpmask1 // The 1st mask of jmp_buf elements that need relocation #define MASK2 jmpmask2 // The 2nd mask of jmp_buf elements that need relocation #define STACK_BASE stackbase // The base of the stack frame relative to the local variables // The structure for the saved stack frame information typedef struct saveloc { char * localptr; jmp_buf cxt; } saveloc; static bool_t stackdirup; static uint32_t jmpmask1; static uint32_t jmpmask2; static size_t stackbase; static saveloc *pframeinfo; // These two functions are not static to prevent the compiler removing them as functions void _gfxGetStackState(void) { char *c; pframeinfo->localptr = (char *)&c; CXT_SAVE(pframeinfo->cxt); } void _gfxGetStackStateInFn(void) { pframeinfo++; _gfxGetStackState(); pframeinfo--; } static void _gfxThreadsInit(void) { uint32_t i; char ** pout; char ** pin; size_t diff; char * framebase; saveloc tmpsaveloc[2]; // Create the main thread context mainthread.cxt = maincxt; // Allocate a buffer to store our test data pframeinfo = tmpsaveloc; // Get details of the stack frame from within a function _gfxGetStackStateInFn(); // Get details of the stack frame outside the function _gfxGetStackState(); /* Work out the frame entries to relocate by treating the jump buffer as an array of pointers */ stackdirup = pframeinfo[1].localptr > pframeinfo[0].localptr; pout = (char **)pframeinfo[0].cxt; pin = (char **)pframeinfo[1].cxt; diff = pframeinfo[0].localptr - pframeinfo[1].localptr; framebase = pframeinfo[0].localptr; jmpmask1 = jmpmask2 = 0; for (i = 0; i < sizeof(jmp_buf)/sizeof(char *); i++, pout++, pin++) { if ((size_t)(*pout - *pin) == diff) { if (i < 32) jmpmask1 |= 1 << i; else jmpmask2 |= 1 << (i-32); if (stackdirup) { if (framebase > *pout) framebase = *pout; } else { if (framebase < *pout) framebase = *pout; } } } stackbase = stackdirup ? (pframeinfo[0].localptr - framebase) : (framebase - pframeinfo[0].localptr); } #endif // Move the stack frame and relocate the context data static void _gfxAdjustCxt(thread *t) { char ** s; char * nf; int diff; uint32_t i; // Copy the stack frame s = 0; #if AUTO_DETECT_STACKFRAME if (STACK_DIR_UP) { // Stack grows up nf = (char *)(t) + sizeof(thread) + sizeof(jmp_buf) + STACK_BASE; memcpy(t+1, (char *)&s - STACK_BASE, STACK_BASE+sizeof(char *)); } else { // Stack grows down nf = (char *)(t) + t->size - (STACK_BASE + sizeof(char *)); memcpy(nf, &s, STACK_BASE+sizeof(char *)); } #elif STACK_DIR_UP // Stack grows up nf = (char *)(t) + sizeof(thread) + sizeof(jmp_buf) + STACK_BASE; memcpy(t+1, (char *)&s - STACK_BASE, STACK_BASE+sizeof(char *)); #else // Stack grows down nf = (char *)(t) + t->size - (STACK_BASE + sizeof(char *)); memcpy(nf, &s, STACK_BASE+sizeof(char *)); #endif // Relocate the context data s = (char **)(t->cxt); diff = nf - (char *)&s; // Relocate the elements we know need to be relocated for (i = MASK1; i ; i >>= 1, s++) { if ((i & 1)) *s += diff; } #ifdef MASK2 s = (char **)(t->cxt)+32; for (i = MASK2; i ; i >>= 1, s++) { if ((i & 1)) *s += diff; } #endif } static void _gfxXSwitch(thread *oldt, thread *newt, bool_t doBuildFrame) { // Save the old context if (CXT_SAVE(oldt->cxt)) return; // Do we need to build a new context? if (doBuildFrame) { // Save our existing context as a starting point for the new context newt->cxt = newt+1; if (CXT_SAVE(newt->cxt)) { // We are now running the new thread // We can't use any of the above function parameters here // as we are on a different stack. // Run the users function. gfxThreadExit(_gfxCurrentThread->fn(_gfxCurrentThread->param)); // We never get here as gfxThreadExit() never returns } // Adjust the new context so the stack references are correct _gfxAdjustCxt(newt); } // Start the new context CXT_RESTORE(newt->cxt, 1); } #define _gfxTaskSwitch(oldt, newt) _gfxXSwitch(oldt, newt, FALSE) #define _gfxStartThread(oldt, newt) _gfxXSwitch(oldt, newt, TRUE) #endif #undef GFX_THREADS_DONE static void Qinit(threadQ * q) { q->head = q->tail = 0; } static void Qadd(threadQ * q, thread *t) { t->next = 0; if (q->head) { q->tail->next = t; q->tail = t; } else q->head = q->tail = t; } static thread *Qpop(threadQ * q) { struct thread * t; if (!q->head) return 0; t = q->head; q->head = t->next; return t; } void _gosThreadsInit(void) { Qinit(&readyQ); mainthread.next = 0; mainthread.size = sizeof(thread); mainthread.flags = FLG_THD_MAIN; mainthread.fn = 0; mainthread.param = 0; _gfxThreadsInit(); _gfxCurrentThread = &mainthread; } gfxThreadHandle gfxThreadMe(void) { return (gfxThreadHandle)_gfxCurrentThread; } // Check if there are dead processes to deallocate static void cleanUpDeadThreads(void) { thread *p; while ((p = Qpop(&deadQ))) gfxFree(p); } void gfxYield(void) { thread *me; // Clean up zombies cleanUpDeadThreads(); // Is there another thread to run? if (!readyQ.head) return; Qadd(&readyQ, me = _gfxCurrentThread); _gfxCurrentThread = Qpop(&readyQ); _gfxTaskSwitch(me, _gfxCurrentThread); } // This routine is not currently public - but it could be. void gfxThreadExit(threadreturn_t ret) { thread *me; // Save the results in case someone is waiting me = _gfxCurrentThread; me->param = (void *)ret; me->flags |= FLG_THD_DEAD; // Add us to the dead list if we need deallocation as we can't free ourselves. // If someone is waiting on the thread they will do the cleanup. if ((me->flags & (FLG_THD_ALLOC|FLG_THD_WAIT)) == FLG_THD_ALLOC) Qadd(&deadQ, me); // Set the next thread. Exit if it was the last thread if (!(_gfxCurrentThread = Qpop(&readyQ))) gfxExit(); // Switch to the new thread _gfxTaskSwitch(me, _gfxCurrentThread); // We never get back here as we didn't re-queue ourselves } gfxThreadHandle gfxThreadCreate(void *stackarea, size_t stacksz, threadpriority_t prio, DECLARE_THREAD_FUNCTION((*fn),p), void *param) { thread * t; thread * me; (void) prio; // Ensure we have a minimum stack size if (stacksz < sizeof(thread)+64) { stacksz = sizeof(thread)+64; stackarea = 0; } if (stackarea) { t = (thread *)stackarea; t->flags = 0; } else { t = (thread *)gfxAlloc(stacksz); if (!t) return 0; t->flags = FLG_THD_ALLOC; } t->size = stacksz; t->fn = fn; t->param = param; // Add the current thread to the queue because we are starting a new thread. me = _gfxCurrentThread; Qadd(&readyQ, me); _gfxCurrentThread = t; _gfxStartThread(me, t); // Return the new thread handle return t; } threadreturn_t gfxThreadWait(gfxThreadHandle th) { thread * t; t = th; if (t == _gfxCurrentThread) return -1; // Mark that we are waiting t->flags |= FLG_THD_WAIT; // Wait for the thread to die while(!(t->flags & FLG_THD_DEAD)) gfxYield(); // Unmark t->flags &= ~FLG_THD_WAIT; // Clean up resources if needed if (t->flags & FLG_THD_ALLOC) gfxFree(t); // Return the status left by the dead process return (threadreturn_t)t->param; } #endif /* GFX_USE_OS_RAW32 */