-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathTeensyThreads-asm.S
More file actions
187 lines (167 loc) · 8.11 KB
/
TeensyThreads-asm.S
File metadata and controls
187 lines (167 loc) · 8.11 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
/*
* 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