/* * Threads.h - Library for threading on the Teensy. * ******************* * * Copyright 2017 by Fernando Trias. * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, publish, distribute, * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ******************* * * Multithreading library for Teensy board. * See Threads.cpp for explanation of internal functions. * * A global variable "threads" of type Threads will be created * to provide all threading functions. See example below: * * #include * * volatile int count = 0; * * void thread_func(int data){ * while(1) count++; * } * * void setup() { * threads.addThread(thread_func, 0); * } * * void loop() { * Serial.print(count); * } * * Alternatively, you can use the std::threads class defined * by C++11 * * #include * * volatile int count = 0; * * void thread_func(){ * while(1) count++; * } * * void setup() { * std::thead th1(thread_func); * th1.detach(); * } * * void loop() { * Serial.print(count); * } * */ #ifndef _THREADS_H #define _THREADS_H #include /* Enabling debugging information allows access to: * getCyclesUsed() */ // #define DEBUG extern "C" { void context_switch(void); void context_switch_direct(void); void context_switch_pit_isr(void); void loadNextThread(); void stack_overflow_isr(void); void threads_svcall_isr(void); void threads_systick_isr(void); } // The stack frame saved by the interrupt typedef struct { uint32_t r0; uint32_t r1; uint32_t r2; uint32_t r3; uint32_t r12; uint32_t lr; uint32_t pc; uint32_t xpsr; } interrupt_stack_t; // The stack frame saved by the context switch typedef struct { uint32_t r4; uint32_t r5; uint32_t r6; uint32_t r7; uint32_t r8; uint32_t r9; uint32_t r10; uint32_t r11; uint32_t lr; #ifdef __ARM_PCS_VFP uint32_t s0; uint32_t s1; uint32_t s2; uint32_t s3; uint32_t s4; uint32_t s5; uint32_t s6; uint32_t s7; uint32_t s8; uint32_t s9; uint32_t s10; uint32_t s11; uint32_t s12; uint32_t s13; uint32_t s14; uint32_t s15; uint32_t s16; uint32_t s17; uint32_t s18; uint32_t s19; uint32_t s20; uint32_t s21; uint32_t s22; uint32_t s23; uint32_t s24; uint32_t s25; uint32_t s26; uint32_t s27; uint32_t s28; uint32_t s29; uint32_t s30; uint32_t s31; uint32_t fpscr; #endif } software_stack_t; // The state of each thread (including thread 0) class ThreadInfo { public: int stack_size; uint8_t *stack=0; int my_stack = 0; software_stack_t save; volatile int flags = 0; void *sp; int ticks; #ifdef DEBUG unsigned long cyclesStart; // On T_4 the CycCnt is always active - on T_3.x it currently is not - unless Audio starts it AFAIK unsigned long cyclesAccum; #endif }; extern "C" void unused_isr(void); typedef void (*ThreadFunction)(void*); typedef void (*ThreadFunctionInt)(int); typedef void (*ThreadFunctionNone)(); typedef void (*IsrFunction)(); /* * Threads handles all the threading interaction with users. It gets * instantiated in a global variable "threads". */ class Threads { public: // The maximum number of threads is hard-coded to simplify // the implementation. See notes of ThreadInfo. static const int MAX_THREADS = 8; int DEFAULT_STACK_SIZE = 1024; const int DEFAULT_STACK0_SIZE = 10240; // estimate for thread 0? int DEFAULT_TICKS = 10; static const int DEFAULT_TICK_MICROSECONDS = 100; // State of threading system static const int STARTED = 1; static const int STOPPED = 2; static const int FIRST_RUN = 3; // State of individual threads static const int EMPTY = 0; static const int RUNNING = 1; static const int ENDED = 2; static const int ENDING = 3; static const int SUSPENDED = 4; static const int SVC_NUMBER = 0x21; static const int SVC_NUMBER_ACTIVE = 0x22; protected: int current_thread; int thread_count; int thread_error; /* * The maximum number of threads is hard-coded. Alternatively, we could implement * a linked list which would mean using up less memory for a small number of * threads while allowing an unlimited number of possible threads. This would * probably not slow down thread switching too much, but it would introduce * complexity and possibly bugs. So to simplifiy for now, we use an array. * But in the future, a linked list might be more appropriate. */ ThreadInfo *threadp[MAX_THREADS]; // This used to be allocated statically, as below. Kept for reference in case of bugs. // ThreadInfo thread[MAX_THREADS]; public: // public for debugging static IsrFunction save_systick_isr; static IsrFunction save_svcall_isr; public: Threads(); // Create a new thread for function "p", passing argument "arg". If stack is 0, // stack allocated on heap. Function "p" has form "void p(void *)". int addThread(ThreadFunction p, void * arg=0, int stack_size=-1, void *stack=0); // For: void f(int) int addThread(ThreadFunctionInt p, int arg=0, int stack_size=-1, void *stack=0) { return addThread((ThreadFunction)p, (void*)arg, stack_size, stack); } // For: void f() int addThread(ThreadFunctionNone p, int arg=0, int stack_size=-1, void *stack=0) { return addThread((ThreadFunction)p, (void*)arg, stack_size, stack); } // Get the state; see class constants. Can be EMPTY, RUNNING, etc. int getState(int id); // Explicityly set a state. See getState(). Call with care. int setState(int id, int state); // Wait until thread returns up to timeout_ms milliseconds. If ms is 0, wait // indefinitely. int wait(int id, unsigned int timeout_ms = 0); // Permanently stop a running thread. Thread will end on the next thread slice tick. int kill(int id); // Suspend a thread (on the next slice tick). Can be restarted with restart(). int suspend(int id); // Restart a suspended thread. int restart(int id); // Set the slice length time in ticks for a thread (1 tick = 1 millisecond, unless using MicroTimer) void setTimeSlice(int id, unsigned int ticks); // Set the slice length time in ticks for all new threads (1 tick = 1 millisecond, unless using MicroTimer) void setDefaultTimeSlice(unsigned int ticks); // Set the stack size for new threads in bytes void setDefaultStackSize(unsigned int bytes_size); // Use the microsecond timer provided by IntervalTimer & PIT; instead of 1 tick = 1 millisecond, // 1 tick will be the number of microseconds provided (default is 100 microseconds) int setMicroTimer(int tick_microseconds = DEFAULT_TICK_MICROSECONDS); // Simple function to set each time slice to be 'milliseconds' long int setSliceMillis(int milliseconds); // Set each time slice to be 'microseconds' long int setSliceMicros(int microseconds); // Get the id of the currently running thread int id(); int getStackUsed(int id); int getStackRemaining(int id); #ifdef DEBUG unsigned long getCyclesUsed(int id); #endif // Yield current thread's remaining time slice to the next thread, causing immediate // context switch void yield(); // Wait for milliseconds using yield(), giving other slices your wait time void delay(int millisecond); // Start/restart threading system; returns previous state: STARTED, STOPPED, FIRST_RUN // can pass the previous state to restore int start(int old_state = -1); // Stop threading system; returns previous state: STARTED, STOPPED, FIRST_RUN int stop(); // Allow these static functions and classes to access our members friend void context_switch(void); friend void context_switch_direct(void); friend void context_pit_isr(void); friend void threads_systick_isr(void); friend void threads_svcall_isr(void); friend void loadNextThread(); friend class ThreadLock; protected: void getNextThread(); void *loadstack(ThreadFunction p, void * arg, void *stackaddr, int stack_size); static void force_switch_isr(); private: static void del_process(void); void yield_and_start(); public: class Mutex { private: volatile int state = 0; volatile int waitthread = -1; volatile int waitcount = 0; public: int getState(); // get the lock state; 1=locked; 0=unlocked int lock(unsigned int timeout_ms = 0); // lock, optionally waiting up to timeout_ms milliseconds int try_lock(); // if lock available, get it and return 1; otherwise return 0 int unlock(); // unlock if locked }; class Scope { private: Mutex *r; public: Scope(Mutex& m) { r = &m; r->lock(); } ~Scope() { r->unlock(); } }; class Suspend { private: int save_state; public: Suspend(); // Stop threads and save thread state ~Suspend(); // Restore saved state }; template class GrabTemp { private: Mutex *lkp; public: C *me; GrabTemp(C *obj, Mutex *lk) { me = obj; lkp=lk; lkp->lock(); } ~GrabTemp() { lkp->unlock(); } C &get() { return *me; } }; template class Grab { private: Mutex lk; T *me; public: Grab(T &t) { me = &t; } GrabTemp grab() { return GrabTemp(me, &lk); } operator T&() { return grab().get(); } T *operator->() { return grab().me; } Mutex &getLock() { return lk; } }; #define ThreadWrap(OLDOBJ, NEWOBJ) Threads::Grab NEWOBJ(OLDOBJ); #define ThreadClone(NEWOBJ) (NEWOBJ.grab().get()) }; extern Threads threads; /* * Rudimentary compliance to C++11 class * * See http://www.cplusplus.com/reference/thread/thread/ * * Example: * int x; * void thread_func() { x++; } * int main() { * std::thread(thread_func); * } * */ namespace std { class thread { private: int id; // internal thread id int destroy; // flag to kill thread on instance destruction public: // By casting all (args...) to (void*), if there are more than one args, the compiler // will fail to find a matching function. This fancy template just allows any kind of // function to match. template explicit thread(F&& f, Args&&... args) { id = threads.addThread((ThreadFunction)f, (void*)args...); destroy = 1; } // If thread has not been detached when destructor called, then thread must end ~thread() { if (destroy) threads.kill(id); } // Threads are joinable until detached per definition, but in this implementation // that's not so. We emulate expected behavior anyway. bool joinable() { return destroy==1; } // Once detach() is called, thread runs until it terminates; otherwise it terminates // when destructor called. void detach() { destroy = 0; } // In theory, the thread merges with the running thread; if we just wait until // termination, it's basically the same thing except it's slower because // there are two threads running instead of one. Close enough. void join() { threads.wait(id); } // Get the unique thread id. int get_id() { return id; } }; class mutex { private: Threads::Mutex mx; public: void lock() { mx.lock(); } bool try_lock() { return mx.try_lock(); } void unlock() { mx.unlock(); } }; template class lock_guard { private: cMutex *r; public: explicit lock_guard(cMutex& m) { r = &m; r->lock(); } ~lock_guard() { r->unlock(); } }; } #endif