1 | /* SPDX-License-Identifier: GPL-2.0-only */ |
2 | /* |
3 | * linux/arch/arm/kernel/iwmmxt.S |
4 | * |
5 | * XScale iWMMXt (Concan) context switching and handling |
6 | * |
7 | * Initial code: |
8 | * Copyright (c) 2003, Intel Corporation |
9 | * |
10 | * Full lazy switching support, optimizations and more, by Nicolas Pitre |
11 | * Copyright (c) 2003-2004, MontaVista Software, Inc. |
12 | */ |
13 | |
14 | #include <linux/linkage.h> |
15 | #include <asm/ptrace.h> |
16 | #include <asm/thread_info.h> |
17 | #include <asm/asm-offsets.h> |
18 | #include <asm/assembler.h> |
19 | #include "iwmmxt.h" |
20 | |
21 | #define MMX_WR0 (0x00) |
22 | #define MMX_WR1 (0x08) |
23 | #define MMX_WR2 (0x10) |
24 | #define MMX_WR3 (0x18) |
25 | #define MMX_WR4 (0x20) |
26 | #define MMX_WR5 (0x28) |
27 | #define MMX_WR6 (0x30) |
28 | #define MMX_WR7 (0x38) |
29 | #define MMX_WR8 (0x40) |
30 | #define MMX_WR9 (0x48) |
31 | #define MMX_WR10 (0x50) |
32 | #define MMX_WR11 (0x58) |
33 | #define MMX_WR12 (0x60) |
34 | #define MMX_WR13 (0x68) |
35 | #define MMX_WR14 (0x70) |
36 | #define MMX_WR15 (0x78) |
37 | #define MMX_WCSSF (0x80) |
38 | #define MMX_WCASF (0x84) |
39 | #define MMX_WCGR0 (0x88) |
40 | #define MMX_WCGR1 (0x8C) |
41 | #define MMX_WCGR2 (0x90) |
42 | #define MMX_WCGR3 (0x94) |
43 | |
44 | #define MMX_SIZE (0x98) |
45 | |
46 | .text |
47 | .arm |
48 | |
49 | ENTRY(iwmmxt_undef_handler) |
50 | push {r9, r10, lr} |
51 | get_thread_info r10 |
52 | mov r9, pc |
53 | b iwmmxt_task_enable |
54 | mov r0, #0 |
55 | pop {r9, r10, pc} |
56 | ENDPROC(iwmmxt_undef_handler) |
57 | |
58 | /* |
59 | * Lazy switching of Concan coprocessor context |
60 | * |
61 | * r0 = struct pt_regs pointer |
62 | * r10 = struct thread_info pointer |
63 | * r9 = ret_from_exception |
64 | * lr = undefined instr exit |
65 | * |
66 | * called from prefetch exception handler with interrupts enabled |
67 | */ |
68 | |
69 | ENTRY(iwmmxt_task_enable) |
70 | inc_preempt_count r10, r3 |
71 | |
72 | mrc p15, 0, r2, c15, c1, 0 |
73 | @ CP0 and CP1 accessible? |
74 | tst r2, #0x3 |
75 | bne 4f @ if so no business here |
76 | @ enable access to CP0 and CP1 |
77 | orr r2, r2, #0x3 |
78 | mcr p15, 0, r2, c15, c1, 0 |
79 | |
80 | ldr r3, =concan_owner |
81 | ldr r2, [r0, #S_PC] @ current task pc value |
82 | ldr r1, [r3] @ get current Concan owner |
83 | sub r2, r2, #4 @ adjust pc back |
84 | str r2, [r0, #S_PC] |
85 | add r0, r10, #TI_IWMMXT_STATE @ get task Concan save area |
86 | str r0, [r3] @ this task now owns Concan regs |
87 | |
88 | mrc p15, 0, r2, c2, c0, 0 |
89 | mov r2, r2 @ cpwait |
90 | bl concan_save |
91 | |
92 | #ifdef CONFIG_PREEMPT_COUNT |
93 | get_thread_info r10 |
94 | #endif |
95 | 4: dec_preempt_count r10, r3 |
96 | ret r9 @ normal exit from exception |
97 | |
98 | concan_save: |
99 | |
100 | teq r1, #0 @ test for last ownership |
101 | beq concan_load @ no owner, skip save |
102 | |
103 | tmrc r2, wCon |
104 | |
105 | @ CUP? wCx |
106 | tst r2, #0x1 |
107 | beq 1f |
108 | |
109 | concan_dump: |
110 | |
111 | wstrw wCSSF, r1, MMX_WCSSF |
112 | wstrw wCASF, r1, MMX_WCASF |
113 | wstrw wCGR0, r1, MMX_WCGR0 |
114 | wstrw wCGR1, r1, MMX_WCGR1 |
115 | wstrw wCGR2, r1, MMX_WCGR2 |
116 | wstrw wCGR3, r1, MMX_WCGR3 |
117 | |
118 | 1: @ MUP? wRn |
119 | tst r2, #0x2 |
120 | beq 2f |
121 | |
122 | wstrd wR0, r1, MMX_WR0 |
123 | wstrd wR1, r1, MMX_WR1 |
124 | wstrd wR2, r1, MMX_WR2 |
125 | wstrd wR3, r1, MMX_WR3 |
126 | wstrd wR4, r1, MMX_WR4 |
127 | wstrd wR5, r1, MMX_WR5 |
128 | wstrd wR6, r1, MMX_WR6 |
129 | wstrd wR7, r1, MMX_WR7 |
130 | wstrd wR8, r1, MMX_WR8 |
131 | wstrd wR9, r1, MMX_WR9 |
132 | wstrd wR10, r1, MMX_WR10 |
133 | wstrd wR11, r1, MMX_WR11 |
134 | wstrd wR12, r1, MMX_WR12 |
135 | wstrd wR13, r1, MMX_WR13 |
136 | wstrd wR14, r1, MMX_WR14 |
137 | wstrd wR15, r1, MMX_WR15 |
138 | |
139 | 2: teq r0, #0 @ anything to load? |
140 | reteq lr @ if not, return |
141 | |
142 | concan_load: |
143 | |
144 | @ Load wRn |
145 | wldrd wR0, r0, MMX_WR0 |
146 | wldrd wR1, r0, MMX_WR1 |
147 | wldrd wR2, r0, MMX_WR2 |
148 | wldrd wR3, r0, MMX_WR3 |
149 | wldrd wR4, r0, MMX_WR4 |
150 | wldrd wR5, r0, MMX_WR5 |
151 | wldrd wR6, r0, MMX_WR6 |
152 | wldrd wR7, r0, MMX_WR7 |
153 | wldrd wR8, r0, MMX_WR8 |
154 | wldrd wR9, r0, MMX_WR9 |
155 | wldrd wR10, r0, MMX_WR10 |
156 | wldrd wR11, r0, MMX_WR11 |
157 | wldrd wR12, r0, MMX_WR12 |
158 | wldrd wR13, r0, MMX_WR13 |
159 | wldrd wR14, r0, MMX_WR14 |
160 | wldrd wR15, r0, MMX_WR15 |
161 | |
162 | @ Load wCx |
163 | wldrw wCSSF, r0, MMX_WCSSF |
164 | wldrw wCASF, r0, MMX_WCASF |
165 | wldrw wCGR0, r0, MMX_WCGR0 |
166 | wldrw wCGR1, r0, MMX_WCGR1 |
167 | wldrw wCGR2, r0, MMX_WCGR2 |
168 | wldrw wCGR3, r0, MMX_WCGR3 |
169 | |
170 | @ clear CUP/MUP (only if r1 != 0) |
171 | teq r1, #0 |
172 | mov r2, #0 |
173 | reteq lr |
174 | |
175 | tmcr wCon, r2 |
176 | ret lr |
177 | |
178 | ENDPROC(iwmmxt_task_enable) |
179 | |
180 | /* |
181 | * Back up Concan regs to save area and disable access to them |
182 | * (mainly for gdb or sleep mode usage) |
183 | * |
184 | * r0 = struct thread_info pointer of target task or NULL for any |
185 | */ |
186 | |
187 | ENTRY(iwmmxt_task_disable) |
188 | |
189 | stmfd sp!, {r4, lr} |
190 | |
191 | mrs ip, cpsr |
192 | orr r2, ip, #PSR_I_BIT @ disable interrupts |
193 | msr cpsr_c, r2 |
194 | |
195 | ldr r3, =concan_owner |
196 | add r2, r0, #TI_IWMMXT_STATE @ get task Concan save area |
197 | ldr r1, [r3] @ get current Concan owner |
198 | teq r1, #0 @ any current owner? |
199 | beq 1f @ no: quit |
200 | teq r0, #0 @ any owner? |
201 | teqne r1, r2 @ or specified one? |
202 | bne 1f @ no: quit |
203 | |
204 | @ enable access to CP0 and CP1 |
205 | mrc p15, 0, r4, c15, c1, 0 |
206 | orr r4, r4, #0x3 |
207 | mcr p15, 0, r4, c15, c1, 0 |
208 | |
209 | mov r0, #0 @ nothing to load |
210 | str r0, [r3] @ no more current owner |
211 | mrc p15, 0, r2, c2, c0, 0 |
212 | mov r2, r2 @ cpwait |
213 | bl concan_save |
214 | |
215 | @ disable access to CP0 and CP1 |
216 | bic r4, r4, #0x3 |
217 | mcr p15, 0, r4, c15, c1, 0 |
218 | |
219 | mrc p15, 0, r2, c2, c0, 0 |
220 | mov r2, r2 @ cpwait |
221 | |
222 | 1: msr cpsr_c, ip @ restore interrupt mode |
223 | ldmfd sp!, {r4, pc} |
224 | |
225 | ENDPROC(iwmmxt_task_disable) |
226 | |
227 | /* |
228 | * Copy Concan state to given memory address |
229 | * |
230 | * r0 = struct thread_info pointer of target task |
231 | * r1 = memory address where to store Concan state |
232 | * |
233 | * this is called mainly in the creation of signal stack frames |
234 | */ |
235 | |
236 | ENTRY(iwmmxt_task_copy) |
237 | |
238 | mrs ip, cpsr |
239 | orr r2, ip, #PSR_I_BIT @ disable interrupts |
240 | msr cpsr_c, r2 |
241 | |
242 | ldr r3, =concan_owner |
243 | add r2, r0, #TI_IWMMXT_STATE @ get task Concan save area |
244 | ldr r3, [r3] @ get current Concan owner |
245 | teq r2, r3 @ does this task own it... |
246 | beq 1f |
247 | |
248 | @ current Concan values are in the task save area |
249 | msr cpsr_c, ip @ restore interrupt mode |
250 | mov r0, r1 |
251 | mov r1, r2 |
252 | mov r2, #MMX_SIZE |
253 | b memcpy |
254 | |
255 | 1: @ this task owns Concan regs -- grab a copy from there |
256 | mov r0, #0 @ nothing to load |
257 | mov r2, #3 @ save all regs |
258 | mov r3, lr @ preserve return address |
259 | bl concan_dump |
260 | msr cpsr_c, ip @ restore interrupt mode |
261 | ret r3 |
262 | |
263 | ENDPROC(iwmmxt_task_copy) |
264 | |
265 | /* |
266 | * Restore Concan state from given memory address |
267 | * |
268 | * r0 = struct thread_info pointer of target task |
269 | * r1 = memory address where to get Concan state from |
270 | * |
271 | * this is used to restore Concan state when unwinding a signal stack frame |
272 | */ |
273 | |
274 | ENTRY(iwmmxt_task_restore) |
275 | |
276 | mrs ip, cpsr |
277 | orr r2, ip, #PSR_I_BIT @ disable interrupts |
278 | msr cpsr_c, r2 |
279 | |
280 | ldr r3, =concan_owner |
281 | add r2, r0, #TI_IWMMXT_STATE @ get task Concan save area |
282 | ldr r3, [r3] @ get current Concan owner |
283 | bic r2, r2, #0x7 @ 64-bit alignment |
284 | teq r2, r3 @ does this task own it... |
285 | beq 1f |
286 | |
287 | @ this task doesn't own Concan regs -- use its save area |
288 | msr cpsr_c, ip @ restore interrupt mode |
289 | mov r0, r2 |
290 | mov r2, #MMX_SIZE |
291 | b memcpy |
292 | |
293 | 1: @ this task owns Concan regs -- load them directly |
294 | mov r0, r1 |
295 | mov r1, #0 @ don't clear CUP/MUP |
296 | mov r3, lr @ preserve return address |
297 | bl concan_load |
298 | msr cpsr_c, ip @ restore interrupt mode |
299 | ret r3 |
300 | |
301 | ENDPROC(iwmmxt_task_restore) |
302 | |
303 | /* |
304 | * Concan handling on task switch |
305 | * |
306 | * r0 = next thread_info pointer |
307 | * |
308 | * Called only from the iwmmxt notifier with task preemption disabled. |
309 | */ |
310 | ENTRY(iwmmxt_task_switch) |
311 | |
312 | mrc p15, 0, r1, c15, c1, 0 |
313 | @ CP0 and CP1 accessible? |
314 | tst r1, #0x3 |
315 | bne 1f @ yes: block them for next task |
316 | |
317 | ldr r2, =concan_owner |
318 | add r3, r0, #TI_IWMMXT_STATE @ get next task Concan save area |
319 | ldr r2, [r2] @ get current Concan owner |
320 | teq r2, r3 @ next task owns it? |
321 | retne lr @ no: leave Concan disabled |
322 | |
323 | 1: @ flip Concan access |
324 | eor r1, r1, #0x3 |
325 | mcr p15, 0, r1, c15, c1, 0 |
326 | |
327 | mrc p15, 0, r1, c2, c0, 0 |
328 | sub pc, lr, r1, lsr #32 @ cpwait and return |
329 | |
330 | ENDPROC(iwmmxt_task_switch) |
331 | |
332 | /* |
333 | * Remove Concan ownership of given task |
334 | * |
335 | * r0 = struct thread_info pointer |
336 | */ |
337 | ENTRY(iwmmxt_task_release) |
338 | |
339 | mrs r2, cpsr |
340 | orr ip, r2, #PSR_I_BIT @ disable interrupts |
341 | msr cpsr_c, ip |
342 | ldr r3, =concan_owner |
343 | add r0, r0, #TI_IWMMXT_STATE @ get task Concan save area |
344 | ldr r1, [r3] @ get current Concan owner |
345 | eors r0, r0, r1 @ if equal... |
346 | streq r0, [r3] @ then clear ownership |
347 | msr cpsr_c, r2 @ restore interrupts |
348 | ret lr |
349 | |
350 | ENDPROC(iwmmxt_task_release) |
351 | |
352 | .data |
353 | .align 2 |
354 | concan_owner: |
355 | .word 0 |
356 | |
357 | |