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