425 lines
12 KiB
C++
425 lines
12 KiB
C++
/*
|
|
* 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 <Threads.h>
|
|
*
|
|
* 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 <Threads.h>
|
|
*
|
|
* 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 <stdint.h>
|
|
|
|
/* 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 C> 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 T> class Grab {
|
|
private:
|
|
Mutex lk;
|
|
T *me;
|
|
public:
|
|
Grab(T &t) { me = &t; }
|
|
GrabTemp<T> grab() { return GrabTemp<T>(me, &lk); }
|
|
operator T&() { return grab().get(); }
|
|
T *operator->() { return grab().me; }
|
|
Mutex &getLock() { return lk; }
|
|
};
|
|
|
|
#define ThreadWrap(OLDOBJ, NEWOBJ) Threads::Grab<decltype(OLDOBJ)> 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 <class F, class ...Args> 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 cMutex> class lock_guard {
|
|
private:
|
|
cMutex *r;
|
|
public:
|
|
explicit lock_guard(cMutex& m) { r = &m; r->lock(); }
|
|
~lock_guard() { r->unlock(); }
|
|
};
|
|
}
|
|
|
|
#endif
|