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

680 lines
19 KiB
C++

/*
* Threads.cpp - 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.
*
*******************
*/
#include "TeensyThreads.h"
#include <Arduino.h>
#ifndef __IMXRT1062__
#include <IntervalTimer.h>
IntervalTimer context_timer;
#endif
Threads threads;
unsigned int time_start;
unsigned int time_end;
#define __flush_cpu() __asm__ volatile("DMB");
// These variables are used by the assembly context_switch() function.
// They are copies or pointers to data in Threads and ThreadInfo
// and put here seperately in order to simplify the code.
extern "C" {
int currentUseSystick; // using Systick vs PIT/GPT
int currentActive; // state of the system (first, start, stop)
int currentCount;
ThreadInfo *currentThread; // the thread currently running
void *currentSave;
int currentMSP; // Stack pointers to save
void *currentSP;
void loadNextThread() {
threads.getNextThread();
}
}
extern "C" void stack_overflow_default_isr() {
currentThread->flags = Threads::ENDED;
}
extern "C" void stack_overflow_isr(void) __attribute__ ((weak, alias("stack_overflow_default_isr")));
extern unsigned long _estack; // the main thread 0 stack
// static void threads_svcall_isr(void);
// static void threads_systick_isr(void);
IsrFunction Threads::save_systick_isr;
IsrFunction Threads::save_svcall_isr;
/*
* Teensy 3:
* Replace the SysTick interrupt for our context switching. Note that
* this function is "naked" meaning it does not save it's registers
* on the stack. This is so we can preserve the stack of the caller.
*
* Interrupts will save r0-r4 in the stack and since this function
* is short and simple, it should only use those registers. In the
* future, this should be coded in assembly to make sure.
*/
extern volatile uint32_t systick_millis_count;
extern "C" void systick_isr();
void __attribute((naked, noinline)) threads_systick_isr(void)
{
if (Threads::save_systick_isr) {
asm volatile("push {r0-r4,lr}");
(*Threads::save_systick_isr)();
asm volatile("pop {r0-r4,lr}");
}
// TODO: Teensyduino 1.38 calls MillisTimer::runFromTimer() from SysTick
if (currentUseSystick) {
// we branch in order to preserve LR and the stack
__asm volatile("b context_switch");
}
__asm volatile("bx lr");
}
void __attribute((naked, noinline)) threads_svcall_isr(void)
{
if (Threads::save_svcall_isr) {
asm volatile("push {r0-r4,lr}");
(*Threads::save_svcall_isr)();
asm volatile("pop {r0-r4,lr}");
}
// Get the right stack so we can extract the PC (next instruction)
// and then see the SVC calling instruction number
__asm volatile("TST lr, #4 \n"
"ITE EQ \n"
"MRSEQ r0, msp \n"
"MRSNE r0, psp \n");
register unsigned int *rsp __asm("r0");
unsigned int svc = ((uint8_t*)rsp[6])[-2];
if (svc == Threads::SVC_NUMBER) {
__asm volatile("b context_switch_direct");
}
else if (svc == Threads::SVC_NUMBER_ACTIVE) {
currentActive = Threads::STARTED;
__asm volatile("b context_switch_direct_active");
}
__asm volatile("bx lr");
}
#ifdef __IMXRT1062__
/*
*
* Teensy 4:
* Use unused GPT timers for context switching
*/
extern "C" void unused_interrupt_vector(void);
static void __attribute((naked, noinline)) gpt1_isr() {
GPT1_SR |= GPT_SR_OF1; // clear set bit
__asm volatile ("dsb"); // see github bug #20 by manitou48
__asm volatile("b context_switch");
}
static void __attribute((naked, noinline)) gpt2_isr() {
GPT2_SR |= GPT_SR_OF1; // clear set bit
__asm volatile ("dsb"); // see github bug #20 by manitou48
__asm volatile("b context_switch");
}
bool gtp1_init(unsigned int microseconds)
{
// Initialization code derived from @manitou48.
// See https://github.com/manitou48/teensy4/blob/master/gpt_isr.ino
// See https://forum.pjrc.com/threads/54265-Teensy-4-testing-mbed-NXP-MXRT1050-EVKB-(600-Mhz-M7)?p=193217&viewfull=1#post193217
// keep track of which GPT timer we are using
static int gpt_number = 0;
// not configured yet, so find an inactive GPT timer
if (gpt_number == 0) {
if (! NVIC_IS_ENABLED(IRQ_GPT1)) {
attachInterruptVector(IRQ_GPT1, &gpt1_isr);
NVIC_SET_PRIORITY(IRQ_GPT1, 255);
NVIC_ENABLE_IRQ(IRQ_GPT1);
gpt_number = 1;
}
else if (! NVIC_IS_ENABLED(IRQ_GPT2)) {
attachInterruptVector(IRQ_GPT2, &gpt2_isr);
NVIC_SET_PRIORITY(IRQ_GPT2, 255);
NVIC_ENABLE_IRQ(IRQ_GPT2);
gpt_number = 2;
}
else {
// if neither timer is free, we fail
return false;
}
}
switch (gpt_number) {
case 1:
CCM_CCGR1 |= CCM_CCGR1_GPT(CCM_CCGR_ON) ; // enable GPT1 module
GPT1_CR = 0; // disable timer
GPT1_PR = 23; // prescale: divide by 24 so 1 tick = 1 microsecond at 24MHz
GPT1_OCR1 = microseconds - 1; // compare value
GPT1_SR = 0x3F; // clear all prior status
GPT1_IR = GPT_IR_OF1IE; // use first timer
GPT1_CR = GPT_CR_EN | GPT_CR_CLKSRC(1) ; // set to peripheral clock (24MHz)
break;
case 2:
CCM_CCGR1 |= CCM_CCGR1_GPT(CCM_CCGR_ON) ; // enable GPT1 module
GPT2_CR = 0; // disable timer
GPT2_PR = 23; // prescale: divide by 24 so 1 tick = 1 microsecond at 24MHz
GPT2_OCR1 = microseconds - 1; // compare value
GPT2_SR = 0x3F; // clear all prior status
GPT2_IR = GPT_IR_OF1IE; // use first timer
GPT2_CR = GPT_CR_EN | GPT_CR_CLKSRC(1) ; // set to peripheral clock (24MHz)
break;
default:
return false;
}
return true;
}
#endif
Threads::Threads() : current_thread(0), thread_count(0), thread_error(0) {
// initilize thread slots to empty
for(int i=0; i<MAX_THREADS; i++) {
threadp[i] = NULL;
}
// fill thread 0, which is always running
threadp[0] = new ThreadInfo();
// initialize context_switch() globals from thread 0, which is MSP and always running
currentThread = threadp[0]; // thread 0 is active
currentSave = &threadp[0]->save;
currentMSP = 1;
currentSP = 0;
currentCount = Threads::DEFAULT_TICKS;
currentActive = FIRST_RUN;
threadp[0]->flags = RUNNING;
threadp[0]->ticks = DEFAULT_TICKS;
threadp[0]->stack = (uint8_t*)&_estack - DEFAULT_STACK0_SIZE;
threadp[0]->stack_size = DEFAULT_STACK0_SIZE;
#ifdef __IMXRT1062__
// commandeer SVCall & use GTP1 Interrupt
save_svcall_isr = _VectorsRam[11];
if (save_svcall_isr == unused_interrupt_vector) save_svcall_isr = 0;
_VectorsRam[11] = threads_svcall_isr;
currentUseSystick = 0; // disable Systick calls
gtp1_init(1000); // tick every millisecond
#else
currentUseSystick = 1;
// commandeer the SVCall & SysTick Exceptions
save_svcall_isr = _VectorsRam[11];
if (save_svcall_isr == unused_isr) save_svcall_isr = 0;
_VectorsRam[11] = threads_svcall_isr;
save_systick_isr = _VectorsRam[15];
if (save_systick_isr == unused_isr) save_systick_isr = 0;
_VectorsRam[15] = threads_systick_isr;
#ifdef DEBUG
#if defined(__MK20DX256__) || defined(__MK20DX128__)
ARM_DEMCR |= ARM_DEMCR_TRCENA; // Make ssure Cycle Counter active
ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
#endif
#endif
#endif
}
/*
* start() - Begin threading
*/
int Threads::start(int prev_state) {
__disable_irq();
int old_state = currentActive;
if (prev_state == -1) prev_state = STARTED;
currentActive = prev_state;
__enable_irq();
return old_state;
}
/*
* stop() - Stop threading, even if active.
*
* If threads have already started, this should be called sparingly
* because it could destabalize the system if thread 0 is stopped.
*/
int Threads::stop() {
__disable_irq();
int old_state = currentActive;
currentActive = STOPPED;
__enable_irq();
return old_state;
}
/*
* getNextThread() - Find next running thread
*
* This will also set the context_switcher() state variables
*/
void Threads::getNextThread() {
#ifdef DEBUG
// Keep track of the number of cycles expended by each thread.
// See @dfragster: https://forum.pjrc.com/threads/41504-Teensy-3-x-multithreading-library-first-release?p=213086#post213086
currentThread->cyclesAccum += ARM_DWT_CYCCNT - currentThread->cyclesStart;
#endif
// First, save the currentSP set by context_switch
currentThread->sp = currentSP;
// did we overflow the stack (don't check thread 0)?
// allow an extra 8 bytes for a call to the ISR and one additional call or variable
if (current_thread && ((uint8_t*)currentThread->sp - currentThread->stack <= 8)) {
stack_overflow_isr();
}
// Find the next running thread
while(1) {
current_thread++;
if (current_thread >= MAX_THREADS) {
current_thread = 0; // thread 0 is MSP; always active so return
break;
}
if (threadp[current_thread] && threadp[current_thread]->flags == RUNNING) break;
}
currentCount = threadp[current_thread]->ticks;
currentThread = threadp[current_thread];
currentSave = &threadp[current_thread]->save;
currentMSP = (current_thread==0?1:0);
currentSP = threadp[current_thread]->sp;
#ifdef DEBUG
currentThread->cyclesStart = ARM_DWT_CYCCNT;
#endif
}
/*
* Empty placeholder for IntervalTimer class
*/
static void context_pit_empty() {}
/*
* Store the PIT timer flag register for use in assembly
*/
volatile uint32_t *context_timer_flag;
/*
* Defined in assembly code
*/
extern "C" void context_switch_pit_isr();
/*
* Stop using the SysTick interrupt and start using
* the IntervalTimer timer. The parameter is the number of microseconds
* for each tick.
*/
int Threads::setMicroTimer(int tick_microseconds)
{
#ifdef __IMXRT1062__
gtp1_init(tick_microseconds);
#else
/*
* Implementation strategy suggested by @tni in Teensy Forums; see
* https://forum.pjrc.com/threads/41504-Teensy-3-x-multithreading-library-first-release
*/
// lowest priority so we don't interrupt other interrupts
context_timer.priority(255);
// start timer with dummy fuction
if (context_timer.begin(context_pit_empty, tick_microseconds) == 0) {
// failed to set the timer!
return 0;
}
currentUseSystick = 0; // disable Systick calls
// get the PIT number [0-3] (IntervalTimer overrides IRQ_NUMBER_t op)
int number = (IRQ_NUMBER_t)context_timer - IRQ_PIT_CH0;
// calculate number of uint32_t per PIT; should be 4.
// Not hard-coded in case this changes in future CPUs.
const int width = (PIT_TFLG1 - PIT_TFLG0) / sizeof(uint32_t);
// get the right flag to ackowledge PIT interrupt
context_timer_flag = &PIT_TFLG0 + (width * number);
attachInterruptVector(context_timer, context_switch_pit_isr);
#endif
return 1;
}
/*
* Set each time slice to be 'microseconds' long
*/
int Threads::setSliceMicros(int microseconds)
{
setMicroTimer(microseconds);
setDefaultTimeSlice(1);
return 1;
}
/*
* Set each time slice to be 'milliseconds' long
*/
int Threads::setSliceMillis(int milliseconds)
{
if (currentUseSystick) {
setDefaultTimeSlice(milliseconds);
}
else {
// if we're using the PIT, we should probably really disable it and
// re-establish the systick timer; but this is easier for now
setSliceMicros(milliseconds * 1000);
}
return 1;
}
/*
* del_process() - This is called when the task returns
*
* Turns thread off. Thread continues running until next call to
* context_switch() at which point it all stops. The while(1) statement
* just stalls until such time.
*/
void Threads::del_process(void)
{
int old_state = threads.stop();
ThreadInfo *me = threads.threadp[threads.current_thread];
// Would love to delete stack here but the thread doesn't
// end now. It continues until the next tick.
// if (me->my_stack) {
// delete[] me->stack;
// me->stack = 0;
// }
threads.thread_count--;
me->flags = ENDED; //clear the flags so thread can stop and be reused
threads.start(old_state);
while(1); // just in case, keep working until context change when execution will not return to this thread
}
/*
* Initializes a thread's stack. Called when thread is created
*/
void *Threads::loadstack(ThreadFunction p, void * arg, void *stackaddr, int stack_size)
{
interrupt_stack_t * process_frame = (interrupt_stack_t *)((uint8_t*)stackaddr + stack_size - sizeof(interrupt_stack_t) - 8);
process_frame->r0 = (uint32_t)arg;
process_frame->r1 = 0;
process_frame->r2 = 0;
process_frame->r3 = 0;
process_frame->r12 = 0;
process_frame->lr = (uint32_t)Threads::del_process;
process_frame->pc = ((uint32_t)p);
process_frame->xpsr = 0x1000000;
uint8_t *ret = (uint8_t*)process_frame;
// ret -= sizeof(software_stack_t); // uncomment this if we are saving R4-R11 to the stack
return (void*)ret;
}
/*
* Add a new thread to the queue.
* add_thread(fund, arg)
*
* fund : is a function pointer. The function prototype is:
* void *func(void *param)
* arg : is a void pointer that is passed as the first parameter
* of the function. In the example above, arg is passed
* as param.
* stack_size : the size of the buffer pointed to by stack. If
* it is 0, then "stack" must also be 0. If so, the function
* will allocate the default stack size of the heap using new().
* stack : pointer to new data stack of size stack_size. If this is 0,
* then it will allocate a stack on the heap using new() of size
* stack_size. If stack_size is 0, a default size will be used.
* return: an integer ID to be used for other calls
*/
int Threads::addThread(ThreadFunction p, void * arg, int stack_size, void *stack)
{
int old_state = stop();
if (stack_size == -1) stack_size = DEFAULT_STACK_SIZE;
for (int i=1; i < MAX_THREADS; i++) {
if (threadp[i] == NULL) { // empty thread, so fill it
threadp[i] = new ThreadInfo();
}
if (threadp[i]->flags == ENDED || threadp[i]->flags == EMPTY) { // free thread
ThreadInfo *tp = threadp[i]; // working on this thread
if (tp->stack && tp->my_stack) {
delete[] tp->stack;
}
if (stack==0) {
stack = new uint8_t[stack_size];
tp->my_stack = 1;
}
else {
tp->my_stack = 0;
}
tp->stack = (uint8_t*)stack;
tp->stack_size = stack_size;
void *psp = loadstack(p, arg, tp->stack, tp->stack_size);
tp->sp = psp;
tp->ticks = DEFAULT_TICKS;
tp->flags = RUNNING;
tp->save.lr = 0xFFFFFFF9;
#ifdef DEBUG
tp->cyclesStart = ARM_DWT_CYCCNT;
tp->cyclesAccum = 0;
#endif
currentActive = old_state;
thread_count++;
if (old_state == STARTED || old_state == FIRST_RUN) start();
return i;
}
}
if (old_state == STARTED) start();
return -1;
}
int Threads::getState(int id)
{
return threadp[id]->flags;
}
int Threads::setState(int id, int state)
{
threadp[id]->flags = state;
return state;
}
int Threads::wait(int id, unsigned int timeout_ms)
{
unsigned int start = millis();
// need to store state in temp volatile memory for optimizer.
// "while (thread[id].flags != RUNNING)" will be optimized away
volatile int state;
while (1) {
if (timeout_ms != 0 && millis() - start > timeout_ms) return -1;
state = threadp[id]->flags;
if (state != RUNNING) break;
yield();
}
return id;
}
int Threads::kill(int id)
{
threadp[id]->flags = ENDED;
return id;
}
int Threads::suspend(int id)
{
threadp[id]->flags = SUSPENDED;
return id;
}
int Threads::restart(int id)
{
threadp[id]->flags = RUNNING;
return id;
}
void Threads::setTimeSlice(int id, unsigned int ticks)
{
threadp[id]->ticks = ticks - 1;
}
void Threads::setDefaultTimeSlice(unsigned int ticks)
{
DEFAULT_TICKS = ticks - 1;
}
void Threads::setDefaultStackSize(unsigned int bytes_size)
{
DEFAULT_STACK_SIZE = bytes_size;
}
void Threads::yield() {
__asm volatile("svc %0" : : "i"(Threads::SVC_NUMBER));
}
void Threads::yield_and_start() {
__asm volatile("svc %0" : : "i"(Threads::SVC_NUMBER_ACTIVE));
}
void Threads::delay(int millisecond) {
int mx = millis();
while((int)millis() - mx < millisecond) yield();
}
int Threads::id() {
volatile int ret;
__disable_irq();
ret = current_thread;
__enable_irq();
return ret;
}
int Threads::getStackUsed(int id) {
return threadp[id]->stack + threadp[id]->stack_size - (uint8_t*)threadp[id]->sp;
}
int Threads::getStackRemaining(int id) {
return (uint8_t*)threadp[id]->sp - threadp[id]->stack;
}
#ifdef DEBUG
unsigned long Threads::getCyclesUsed(int id) {
stop();
unsigned long ret = threadp[id]->cyclesAccum;
start();
return ret;
}
#endif
/*
* On creation, stop threading and save state
*/
Threads::Suspend::Suspend() {
__disable_irq();
save_state = currentActive;
currentActive = 0;
__enable_irq();
}
/*
* On destruction, restore threading state
*/
Threads::Suspend::~Suspend() {
__disable_irq();
currentActive = save_state;
__enable_irq();
}
int Threads::Mutex::getState() {
int p = threads.stop();
int ret = state;
threads.start(p);
return ret;
}
int __attribute__ ((noinline)) Threads::Mutex::lock(unsigned int timeout_ms) {
if (try_lock()) return 1; // we're good, so avoid more checks
uint32_t start = systick_millis_count;
while (1) {
if (try_lock()) return 1;
if (timeout_ms && (systick_millis_count - start > timeout_ms)) return 0;
if (waitthread==-1) { // can hold 1 thread suspend until unlock
int p = threads.stop();
waitthread = threads.current_thread;
waitcount = currentCount;
threads.suspend(waitthread);
threads.start(p);
}
threads.yield();
}
__flush_cpu();
return 0;
}
int Threads::Mutex::try_lock() {
int p = threads.stop();
if (state == 0) {
state = 1;
threads.start(p);
return 1;
}
threads.start(p);
return 0;
}
int __attribute__ ((noinline)) Threads::Mutex::unlock() {
int p = threads.stop();
if (state==1) {
state = 0;
if (waitthread >= 0) { // reanimate a suspended thread waiting for unlock
threads.restart(waitthread);
waitthread = -1;
__flush_cpu();
threads.yield_and_start();
return 1;
}
}
__flush_cpu();
threads.start(p);
return 1;
}