PlatformIO package of the Teensy core framework compatible with GCC 10 & C++20
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TeensyThreads-asm.S 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. /*
  2. * Threads-asm.S - Library for threading on the Teensy.
  3. *
  4. *******************
  5. *
  6. * Copyright 2017 by Fernando Trias.
  7. *
  8. * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
  9. * and associated documentation files (the "Software"), to deal in the Software without restriction,
  10. * including without limitation the rights to use, copy, modify, merge, publish, distribute,
  11. * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
  12. * furnished to do so, subject to the following conditions:
  13. *
  14. * The above copyright notice and this permission notice shall be included in all copies or
  15. * substantial portions of the Software.
  16. *
  17. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
  18. * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  20. * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. *
  23. *******************
  24. *
  25. * context_switch() changes the context to a new thread. It follows this strategy:
  26. *
  27. * 1. Abort if called from within an interrupt (unless using PIT)
  28. * 2. Save registers r4-r11 to the current thread state (s0-s31 for FPU)
  29. * 3. If not running on MSP, save PSP to the current thread state
  30. * 4. Get the next running thread state
  31. * 5. Restore r4-r11 from thread state (s0-s31 for FPU)
  32. * 6. Set MSP or PSP depending on state
  33. * 7. Switch MSP/PSP on return
  34. *
  35. * Notes:
  36. * - Cortex-M has two stack pointers, MSP and PSP, which we alternate. See the
  37. * reference manual under the Exception Model section.
  38. * - I tried coding this in asm embedded in Threads.cpp but the compiler
  39. * optimizations kept changing my code and removing lines so I have to use
  40. * a separate assembly file. But if you try it, make sure to declare the
  41. * function "naked" so the stack pointer SP is not modified when called.
  42. * This means you can't use local variables, which are stored in stack.
  43. * Try to turn optimizations off using optimize("O0") (which doesn't really
  44. * turn off all optimizations).
  45. * - Function can be called from systick_isr() or from the PIT timer (implemented
  46. * by IntervalTimer)
  47. * - If using systick, we override the default systick_isr() in order
  48. * to preserve the stack and LR. If using PIT, we override the pitX_isr() for
  49. * the same reason.
  50. * - Since Systick can be called from within another interrupt, for simplicity, we
  51. * check for this and abort.
  52. * - Teensy uses MSP for it's main thread; we preserve that. Alternatively, we
  53. * could have used PSP for all threads, including main, and reserve MSP for
  54. * interrupts only. This would simplify the code slightly, but could introduce
  55. * incompatabilities.
  56. * - If this interrupt is nested within another interrupt, all kinds of bad
  57. * things can happen. This is especially true if usb_isr() is active. In theory
  58. * we should be able to do a switch even within an interrupt, but in my
  59. * tests, it would not work reliably.
  60. * - If using the PIT interrupt, it's priority is set to 255 (the lowest) so it
  61. * cannot interrupt an interrupt.
  62. */
  63. .syntax unified
  64. .align 2
  65. .thumb
  66. .global context_switch_direct
  67. .thumb_func
  68. context_switch_direct:
  69. CPSID I
  70. // Call here to force a context switch, so we skip checking the tick counter.
  71. B call_direct
  72. .global context_switch_direct_active
  73. .thumb_func
  74. context_switch_direct_active:
  75. CPSID I
  76. // Call here to force a context switch, so we skip checking the tick counter.
  77. B call_direct_active
  78. .global context_switch_pit_isr
  79. .thumb_func
  80. context_switch_pit_isr:
  81. CPSID I
  82. LDR r0, =context_timer_flag // acknowledge the interrupt by
  83. LDR r0, [r0] // getting the pointer to the pointer
  84. MOVS r1, #1 //
  85. STR r1, [r0] // and setting to 1
  86. B context_switch_check // now go do the context switch
  87. .global context_switch
  88. .thumb_func
  89. context_switch:
  90. // Disable all interrupts; if we get interrupted during a context switch this
  91. // could corrupt the system.
  92. CPSID I
  93. // Did we interrupt another interrupt? If so, don't switch. Switching would
  94. // wreck the system. In theory, we could reschedule the switch until the
  95. // other interrupt is done. Or we could do a more sophisticated switch, but the
  96. // easiest thing is to just ignore this condition.
  97. CMP lr, #0xFFFFFFF1 // this means we interrupted an interrupt
  98. BEQ to_exit // so don't do anything until next time
  99. CMP lr, #0xFFFFFFE1 // this means we interrupted an interrupt with FPU
  100. BEQ to_exit // so don't do anything until next time
  101. context_switch_check:
  102. // Count down number of ticks we should stay in thread
  103. LDR r0, =currentCount // get the tick count (address to variable)
  104. LDR r1, [r0] // get the value from the address
  105. CMP r1, #0 // is it 0?
  106. BEQ call_direct // if so, thread is done, so switch
  107. SUB r1, #1 // otherwise, subtract 1 tick
  108. STR r1, [r0] // and put it back
  109. B to_exit // and quit until next context_switch
  110. call_direct:
  111. // Just do the context-switch (even if it's not time)
  112. LDR r0, =currentActive // If the thread isn't active, skip it
  113. LDR r0, [r0]
  114. CMP r0, #1
  115. BNE to_exit
  116. call_direct_active:
  117. // Save the r4-r11 registers; (r0-r3,r12 are saved by the interrupt handler).
  118. // Most thread libraries save this to the thread stack. I don't for simplicity
  119. // and to make debugging easier. Since the Teensy doesn't have a debugging port,
  120. // it's hard to examine the stack so this is easier.
  121. LDR r0, =currentSave // get the address of the pointer
  122. LDR r0, [r0] // get the pointer itself
  123. STMIA r0!, {r4-r11,lr} // save r4-r11 to buffer
  124. #ifdef __ARM_PCS_VFP // compile if using FPU
  125. VSTMIA r0!, {s0-s31} // save all FPU registers
  126. VMRS r1, FPSCR // and FPU app status register
  127. STMIA r0!, {r1}
  128. #endif
  129. // Are we running on thread 0, which is MSP?
  130. // It so, there is no need to save the stack pointer because MSP is never changed.
  131. // If not, save the stack pointer.
  132. LDR r0, =currentMSP // get the address of the variable
  133. LDR r0, [r0] // get value from address
  134. CMP r0, #0 // it is 0? This means it's PSP
  135. BNE current_is_msp // not 0, so MSP, we can skip saving SP
  136. MRS r0, psp // get the PSP value
  137. LDR r1, =currentSP // get the address of our save variable
  138. STR r0, [r1] // and store the PSP value there
  139. current_is_msp:
  140. BL loadNextThread; // set the state to next running thread
  141. // Restore the r4-r11 registers from the saved thread
  142. LDR r0, =currentSave // get address of pointer save buffer
  143. LDR r0, [r0] // get the actual pointer
  144. LDMIA r0!, {r4-r11,lr} // and restore r4-r11 & lr from save buffer
  145. #ifdef __ARM_PCS_VFP // compile if using FPU
  146. VLDMIA r0!, {s0-s31} // restore all FPU registers
  147. LDMIA r0!, {r1} // and the FP app status register
  148. VMSR FPSCR, r1
  149. #endif
  150. // Setting LR causes the handler to switch MSP/PSP when returning.
  151. // Switching to MSP? no need to restore MSP.
  152. AND lr, lr, #0x10 // return stack with FP bit?
  153. ORR lr, lr, #0xFFFFFFE9 // add basic LR bits
  154. LDR r0, =currentMSP // get address of the variable
  155. LDR r0, [r0] // get the actual value
  156. CMP r0, #0 // is it 0? Then it's PSP
  157. BNE to_exit // it's not 0, so it's MSP, all done
  158. // if it's PSP, we need to switch PSP
  159. LDR r0, =currentSP // get address of stack pointer
  160. LDR r0, [r0] // get the actual value
  161. MSR psp, r0 // save it to PSP
  162. ORR lr, lr, #0b100 // set the PSP context switch
  163. to_exit:
  164. // Re-enable interrupts
  165. CPSIE I
  166. // Return. The CPU will change MSP/PSP as needed based on LR
  167. BX lr