forked from ftrias/TeensyThreads
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTeensyThreads.h
More file actions
445 lines (403 loc) · 13 KB
/
TeensyThreads.h
File metadata and controls
445 lines (403 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
/*
* 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>
#include <stddef.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;
volatile int sleep_time_till_end_tick; // Per-task sleep time
#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 int (*ThreadFunctionSleep)(int);
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.
int DEFAULT_TICKS = 10;
int DEFAULT_STACK_SIZE = 1024;
static const int MAX_THREADS = 50;
static const int DEFAULT_STACK0_SIZE = 10240; // estimate for thread 0?
static const int DEFAULT_TICK_MICROSECONDS = 100;
static const int UTIL_STATE_NAME_DESCRIPTION_LENGTH = 24;
static const int UTIL_TRHEADS_BUFFER_LENGTH = 1024;
// 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];
ThreadFunctionSleep enter_sleep_callback = NULL;
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);
// If using sleep, please run this in infinite loop
void idle();
// Suspend execution of current thread for ms milliseconds
void sleep(int ms);
// 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);
// Set sleep callback function
void setSleepCallback(ThreadFunctionSleep callback);
// Get the id of the currently running thread
int id();
int getStackUsed(int id);
int getStackRemaining(int id);
char* threadsInfo(void);
#ifdef DEBUG
unsigned long getCyclesUsed(int id);
#endif
// Yield current thread's remaining time slice to the next thread, causing immediate
// context switch
static void yield();
// Wait for milliseconds using yield(), giving other slices your wait time
void delay(int millisecond);
// Wait for microseconds using yield(), giving other slices your wait time
void delay_us(int microsecond);
// 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();
// Test all stack markers; if ok return 0; problems, return -1 and set *threadid to id
int testStackMarkers(int *threadid = NULL);
// 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();
void setStackMarker(void *stack);
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