/* * Threads-asm.S - 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. * ******************* * * context_switch() changes the context to a new thread. It follows this strategy: * * 1. Abort if called from within an interrupt (unless using PIT) * 2. Save registers r4-r11 to the current thread state (s0-s31 for FPU) * 3. If not running on MSP, save PSP to the current thread state * 4. Get the next running thread state * 5. Restore r4-r11 from thread state (s0-s31 for FPU) * 6. Set MSP or PSP depending on state * 7. Switch MSP/PSP on return * * Notes: * - Cortex-M has two stack pointers, MSP and PSP, which we alternate. See the * reference manual under the Exception Model section. * - I tried coding this in asm embedded in Threads.cpp but the compiler * optimizations kept changing my code and removing lines so I have to use * a separate assembly file. But if you try it, make sure to declare the * function "naked" so the stack pointer SP is not modified when called. * This means you can't use local variables, which are stored in stack. * Try to turn optimizations off using optimize("O0") (which doesn't really * turn off all optimizations). * - Function can be called from systick_isr() or from the PIT timer (implemented * by IntervalTimer) * - If using systick, we override the default systick_isr() in order * to preserve the stack and LR. If using PIT, we override the pitX_isr() for * the same reason. * - Since Systick can be called from within another interrupt, for simplicity, we * check for this and abort. * - Teensy uses MSP for it's main thread; we preserve that. Alternatively, we * could have used PSP for all threads, including main, and reserve MSP for * interrupts only. This would simplify the code slightly, but could introduce * incompatabilities. * - If this interrupt is nested within another interrupt, all kinds of bad * things can happen. This is especially true if usb_isr() is active. In theory * we should be able to do a switch even within an interrupt, but in my * tests, it would not work reliably. * - If using the PIT interrupt, it's priority is set to 255 (the lowest) so it * cannot interrupt an interrupt. */ .syntax unified .align 2 .thumb .global context_switch_direct .thumb_func context_switch_direct: CPSID I // Call here to force a context switch, so we skip checking the tick counter. B call_direct .global context_switch_direct_active .thumb_func context_switch_direct_active: CPSID I // Call here to force a context switch, so we skip checking the tick counter. B call_direct_active .global context_switch_pit_isr .thumb_func context_switch_pit_isr: CPSID I LDR r0, =context_timer_flag // acknowledge the interrupt by LDR r0, [r0] // getting the pointer to the pointer MOVS r1, #1 // STR r1, [r0] // and setting to 1 B context_switch_check // now go do the context switch .global context_switch .thumb_func context_switch: // Disable all interrupts; if we get interrupted during a context switch this // could corrupt the system. CPSID I // Did we interrupt another interrupt? If so, don't switch. Switching would // wreck the system. In theory, we could reschedule the switch until the // other interrupt is done. Or we could do a more sophisticated switch, but the // easiest thing is to just ignore this condition. CMP lr, #0xFFFFFFF1 // this means we interrupted an interrupt BEQ to_exit // so don't do anything until next time CMP lr, #0xFFFFFFE1 // this means we interrupted an interrupt with FPU BEQ to_exit // so don't do anything until next time context_switch_check: // Count down number of ticks we should stay in thread LDR r0, =currentCount // get the tick count (address to variable) LDR r1, [r0] // get the value from the address CMP r1, #0 // is it 0? BEQ call_direct // if so, thread is done, so switch SUB r1, #1 // otherwise, subtract 1 tick STR r1, [r0] // and put it back B to_exit // and quit until next context_switch call_direct: // Just do the context-switch (even if it's not time) LDR r0, =currentActive // If the thread isn't active, skip it LDR r0, [r0] CMP r0, #1 BNE to_exit call_direct_active: // Save the r4-r11 registers; (r0-r3,r12 are saved by the interrupt handler). // Most thread libraries save this to the thread stack. I don't for simplicity // and to make debugging easier. Since the Teensy doesn't have a debugging port, // it's hard to examine the stack so this is easier. LDR r0, =currentSave // get the address of the pointer LDR r0, [r0] // get the pointer itself STMIA r0!, {r4-r11,lr} // save r4-r11 to buffer #ifdef __ARM_PCS_VFP // compile if using FPU VSTMIA r0!, {s0-s31} // save all FPU registers VMRS r1, FPSCR // and FPU app status register STMIA r0!, {r1} #endif // Are we running on thread 0, which is MSP? // It so, there is no need to save the stack pointer because MSP is never changed. // If not, save the stack pointer. LDR r0, =currentMSP // get the address of the variable LDR r0, [r0] // get value from address CMP r0, #0 // it is 0? This means it's PSP BNE current_is_msp // not 0, so MSP, we can skip saving SP MRS r0, psp // get the PSP value LDR r1, =currentSP // get the address of our save variable STR r0, [r1] // and store the PSP value there current_is_msp: BL loadNextThread; // set the state to next running thread // Restore the r4-r11 registers from the saved thread LDR r0, =currentSave // get address of pointer save buffer LDR r0, [r0] // get the actual pointer LDMIA r0!, {r4-r11,lr} // and restore r4-r11 & lr from save buffer #ifdef __ARM_PCS_VFP // compile if using FPU VLDMIA r0!, {s0-s31} // restore all FPU registers LDMIA r0!, {r1} // and the FP app status register VMSR FPSCR, r1 #endif // Setting LR causes the handler to switch MSP/PSP when returning. // Switching to MSP? no need to restore MSP. AND lr, lr, #0x10 // return stack with FP bit? ORR lr, lr, #0xFFFFFFE9 // add basic LR bits LDR r0, =currentMSP // get address of the variable LDR r0, [r0] // get the actual value CMP r0, #0 // is it 0? Then it's PSP BNE to_exit // it's not 0, so it's MSP, all done // if it's PSP, we need to switch PSP LDR r0, =currentSP // get address of stack pointer LDR r0, [r0] // get the actual value MSR psp, r0 // save it to PSP ORR lr, lr, #0b100 // set the PSP context switch to_exit: // Re-enable interrupts CPSIE I // Return. The CPU will change MSP/PSP as needed based on LR BX lr