|
- /*
- * 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;
- }
|