Files
zSoft/teensy3/TeensyThreads.h
2020-05-29 14:04:09 +01:00

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