]>
Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
1da177e4 | 2 | /* |
09abbcff | 3 | * Copyright (C) 2003, 2004, 2007 Maciej W. Rozycki |
1da177e4 | 4 | */ |
c3fc5cd5 | 5 | #include <linux/context_tracking.h> |
1da177e4 LT |
6 | #include <linux/init.h> |
7 | #include <linux/kernel.h> | |
8 | #include <linux/ptrace.h> | |
9 | #include <linux/stddef.h> | |
10 | ||
11 | #include <asm/bugs.h> | |
12 | #include <asm/compiler.h> | |
13 | #include <asm/cpu.h> | |
14 | #include <asm/fpu.h> | |
15 | #include <asm/mipsregs.h> | |
b81947c6 | 16 | #include <asm/setup.h> |
1da177e4 | 17 | |
20d60d99 MR |
18 | static char bug64hit[] __initdata = |
19 | "reliable operation impossible!\n%s"; | |
20 | static char nowar[] __initdata = | |
21 | "Please report to <linux-mips@linux-mips.org>."; | |
22 | static char r4kwar[] __initdata = | |
23 | "Enable CPU_R4000_WORKAROUNDS to rectify."; | |
24 | static char daddiwar[] __initdata = | |
25 | "Enable CPU_DADDI_WORKAROUNDS to rectify."; | |
26 | ||
1da177e4 LT |
27 | static inline void align_mod(const int align, const int mod) |
28 | { | |
29 | asm volatile( | |
30 | ".set push\n\t" | |
31 | ".set noreorder\n\t" | |
32 | ".balign %0\n\t" | |
33 | ".rept %1\n\t" | |
34 | "nop\n\t" | |
35 | ".endr\n\t" | |
36 | ".set pop" | |
37 | : | |
57810ecb | 38 | : "n"(align), "n"(mod)); |
1da177e4 LT |
39 | } |
40 | ||
1221a585 MY |
41 | static __always_inline void mult_sh_align_mod(long *v1, long *v2, long *w, |
42 | const int align, const int mod) | |
1da177e4 LT |
43 | { |
44 | unsigned long flags; | |
45 | int m1, m2; | |
46 | long p, s, lv1, lv2, lw; | |
47 | ||
48 | /* | |
49 | * We want the multiply and the shift to be isolated from the | |
50 | * rest of the code to disable gcc optimizations. Hence the | |
51 | * asm statements that execute nothing, but make gcc not know | |
52 | * what the values of m1, m2 and s are and what lv2 and p are | |
53 | * used for. | |
54 | */ | |
55 | ||
56 | local_irq_save(flags); | |
57 | /* | |
58 | * The following code leads to a wrong result of the first | |
59 | * dsll32 when executed on R4000 rev. 2.2 or 3.0 (PRId | |
60 | * 00000422 or 00000430, respectively). | |
61 | * | |
62 | * See "MIPS R4000PC/SC Errata, Processor Revision 2.2 and | |
63 | * 3.0" by MIPS Technologies, Inc., errata #16 and #28 for | |
64 | * details. I got no permission to duplicate them here, | |
65 | * sigh... --macro | |
66 | */ | |
67 | asm volatile( | |
68 | "" | |
69 | : "=r" (m1), "=r" (m2), "=r" (s) | |
70 | : "0" (5), "1" (8), "2" (5)); | |
71 | align_mod(align, mod); | |
72 | /* | |
25985edc | 73 | * The trailing nop is needed to fulfill the two-instruction |
1da177e4 LT |
74 | * requirement between reading hi/lo and staring a mult/div. |
75 | * Leaving it out may cause gas insert a nop itself breaking | |
76 | * the desired alignment of the next chunk. | |
77 | */ | |
78 | asm volatile( | |
79 | ".set push\n\t" | |
80 | ".set noat\n\t" | |
81 | ".set noreorder\n\t" | |
82 | ".set nomacro\n\t" | |
83 | "mult %2, %3\n\t" | |
70342287 | 84 | "dsll32 %0, %4, %5\n\t" |
1da177e4 | 85 | "mflo $0\n\t" |
70342287 | 86 | "dsll32 %1, %4, %5\n\t" |
1da177e4 LT |
87 | "nop\n\t" |
88 | ".set pop" | |
89 | : "=&r" (lv1), "=r" (lw) | |
90 | : "r" (m1), "r" (m2), "r" (s), "I" (0) | |
57810ecb | 91 | : "hi", "lo", "$0"); |
1da177e4 LT |
92 | /* We have to use single integers for m1 and m2 and a double |
93 | * one for p to be sure the mulsidi3 gcc's RTL multiplication | |
94 | * instruction has the workaround applied. Older versions of | |
95 | * gcc have correct umulsi3 and mulsi3, but other | |
96 | * multiplication variants lack the workaround. | |
97 | */ | |
98 | asm volatile( | |
99 | "" | |
100 | : "=r" (m1), "=r" (m2), "=r" (s) | |
101 | : "0" (m1), "1" (m2), "2" (s)); | |
102 | align_mod(align, mod); | |
103 | p = m1 * m2; | |
104 | lv2 = s << 32; | |
105 | asm volatile( | |
106 | "" | |
107 | : "=r" (lv2) | |
108 | : "0" (lv2), "r" (p)); | |
109 | local_irq_restore(flags); | |
110 | ||
111 | *v1 = lv1; | |
112 | *v2 = lv2; | |
113 | *w = lw; | |
114 | } | |
115 | ||
116 | static inline void check_mult_sh(void) | |
117 | { | |
118 | long v1[8], v2[8], w[8]; | |
119 | int bug, fix, i; | |
120 | ||
121 | printk("Checking for the multiply/shift bug... "); | |
122 | ||
123 | /* | |
124 | * Testing discovered false negatives for certain code offsets | |
125 | * into cache lines. Hence we test all possible offsets for | |
126 | * the worst assumption of an R4000 I-cache line width of 32 | |
127 | * bytes. | |
128 | * | |
129 | * We can't use a loop as alignment directives need to be | |
130 | * immediates. | |
131 | */ | |
132 | mult_sh_align_mod(&v1[0], &v2[0], &w[0], 32, 0); | |
133 | mult_sh_align_mod(&v1[1], &v2[1], &w[1], 32, 1); | |
134 | mult_sh_align_mod(&v1[2], &v2[2], &w[2], 32, 2); | |
135 | mult_sh_align_mod(&v1[3], &v2[3], &w[3], 32, 3); | |
136 | mult_sh_align_mod(&v1[4], &v2[4], &w[4], 32, 4); | |
137 | mult_sh_align_mod(&v1[5], &v2[5], &w[5], 32, 5); | |
138 | mult_sh_align_mod(&v1[6], &v2[6], &w[6], 32, 6); | |
139 | mult_sh_align_mod(&v1[7], &v2[7], &w[7], 32, 7); | |
140 | ||
141 | bug = 0; | |
142 | for (i = 0; i < 8; i++) | |
143 | if (v1[i] != w[i]) | |
144 | bug = 1; | |
42a3b4f2 | 145 | |
1da177e4 | 146 | if (bug == 0) { |
35e7f788 | 147 | pr_cont("no.\n"); |
1da177e4 LT |
148 | return; |
149 | } | |
150 | ||
35e7f788 | 151 | pr_cont("yes, workaround... "); |
1da177e4 LT |
152 | |
153 | fix = 1; | |
154 | for (i = 0; i < 8; i++) | |
155 | if (v2[i] != w[i]) | |
156 | fix = 0; | |
42a3b4f2 | 157 | |
1da177e4 | 158 | if (fix == 1) { |
35e7f788 | 159 | pr_cont("yes.\n"); |
1da177e4 LT |
160 | return; |
161 | } | |
162 | ||
35e7f788 | 163 | pr_cont("no.\n"); |
20d60d99 | 164 | panic(bug64hit, !R4000_WAR ? r4kwar : nowar); |
1da177e4 LT |
165 | } |
166 | ||
078a55fc | 167 | static volatile int daddi_ov; |
1da177e4 LT |
168 | |
169 | asmlinkage void __init do_daddi_ov(struct pt_regs *regs) | |
170 | { | |
c3fc5cd5 RB |
171 | enum ctx_state prev_state; |
172 | ||
173 | prev_state = exception_enter(); | |
1da177e4 LT |
174 | daddi_ov = 1; |
175 | regs->cp0_epc += 4; | |
c3fc5cd5 | 176 | exception_exit(prev_state); |
1da177e4 LT |
177 | } |
178 | ||
179 | static inline void check_daddi(void) | |
180 | { | |
181 | extern asmlinkage void handle_daddi_ov(void); | |
182 | unsigned long flags; | |
183 | void *handler; | |
184 | long v, tmp; | |
185 | ||
186 | printk("Checking for the daddi bug... "); | |
187 | ||
188 | local_irq_save(flags); | |
1b505def | 189 | handler = set_except_vector(EXCCODE_OV, handle_daddi_ov); |
1da177e4 LT |
190 | /* |
191 | * The following code fails to trigger an overflow exception | |
192 | * when executed on R4000 rev. 2.2 or 3.0 (PRId 00000422 or | |
193 | * 00000430, respectively). | |
194 | * | |
195 | * See "MIPS R4000PC/SC Errata, Processor Revision 2.2 and | |
196 | * 3.0" by MIPS Technologies, Inc., erratum #23 for details. | |
197 | * I got no permission to duplicate it here, sigh... --macro | |
198 | */ | |
199 | asm volatile( | |
200 | ".set push\n\t" | |
201 | ".set noat\n\t" | |
202 | ".set noreorder\n\t" | |
203 | ".set nomacro\n\t" | |
204 | "addiu %1, $0, %2\n\t" | |
205 | "dsrl %1, %1, 1\n\t" | |
206 | #ifdef HAVE_AS_SET_DADDI | |
207 | ".set daddi\n\t" | |
208 | #endif | |
209 | "daddi %0, %1, %3\n\t" | |
210 | ".set pop" | |
211 | : "=r" (v), "=&r" (tmp) | |
460c0422 | 212 | : "I" (0xffffffffffffdb9aUL), "I" (0x1234)); |
1b505def | 213 | set_except_vector(EXCCODE_OV, handler); |
1da177e4 LT |
214 | local_irq_restore(flags); |
215 | ||
216 | if (daddi_ov) { | |
35e7f788 | 217 | pr_cont("no.\n"); |
1da177e4 LT |
218 | return; |
219 | } | |
220 | ||
35e7f788 | 221 | pr_cont("yes, workaround... "); |
1da177e4 LT |
222 | |
223 | local_irq_save(flags); | |
1b505def | 224 | handler = set_except_vector(EXCCODE_OV, handle_daddi_ov); |
1da177e4 LT |
225 | asm volatile( |
226 | "addiu %1, $0, %2\n\t" | |
227 | "dsrl %1, %1, 1\n\t" | |
228 | "daddi %0, %1, %3" | |
229 | : "=r" (v), "=&r" (tmp) | |
460c0422 | 230 | : "I" (0xffffffffffffdb9aUL), "I" (0x1234)); |
1b505def | 231 | set_except_vector(EXCCODE_OV, handler); |
1da177e4 LT |
232 | local_irq_restore(flags); |
233 | ||
234 | if (daddi_ov) { | |
35e7f788 | 235 | pr_cont("yes.\n"); |
1da177e4 LT |
236 | return; |
237 | } | |
238 | ||
35e7f788 | 239 | pr_cont("no.\n"); |
20d60d99 | 240 | panic(bug64hit, !DADDI_WAR ? daddiwar : nowar); |
1da177e4 LT |
241 | } |
242 | ||
97f2645f | 243 | int daddiu_bug = IS_ENABLED(CONFIG_CPU_MIPSR6) ? 0 : -1; |
20d60d99 | 244 | |
1da177e4 LT |
245 | static inline void check_daddiu(void) |
246 | { | |
247 | long v, w, tmp; | |
248 | ||
249 | printk("Checking for the daddiu bug... "); | |
250 | ||
251 | /* | |
252 | * The following code leads to a wrong result of daddiu when | |
253 | * executed on R4400 rev. 1.0 (PRId 00000440). | |
254 | * | |
255 | * See "MIPS R4400PC/SC Errata, Processor Revision 1.0" by | |
256 | * MIPS Technologies, Inc., erratum #7 for details. | |
257 | * | |
258 | * According to "MIPS R4000PC/SC Errata, Processor Revision | |
259 | * 2.2 and 3.0" by MIPS Technologies, Inc., erratum #41 this | |
260 | * problem affects R4000 rev. 2.2 and 3.0 (PRId 00000422 and | |
261 | * 00000430, respectively), too. Testing failed to trigger it | |
262 | * so far. | |
263 | * | |
264 | * I got no permission to duplicate the errata here, sigh... | |
265 | * --macro | |
266 | */ | |
267 | asm volatile( | |
268 | ".set push\n\t" | |
269 | ".set noat\n\t" | |
270 | ".set noreorder\n\t" | |
271 | ".set nomacro\n\t" | |
272 | "addiu %2, $0, %3\n\t" | |
273 | "dsrl %2, %2, 1\n\t" | |
274 | #ifdef HAVE_AS_SET_DADDI | |
275 | ".set daddi\n\t" | |
276 | #endif | |
70342287 | 277 | "daddiu %0, %2, %4\n\t" |
1da177e4 LT |
278 | "addiu %1, $0, %4\n\t" |
279 | "daddu %1, %2\n\t" | |
280 | ".set pop" | |
281 | : "=&r" (v), "=&r" (w), "=&r" (tmp) | |
460c0422 | 282 | : "I" (0xffffffffffffdb9aUL), "I" (0x1234)); |
1da177e4 | 283 | |
20d60d99 MR |
284 | daddiu_bug = v != w; |
285 | ||
286 | if (!daddiu_bug) { | |
35e7f788 | 287 | pr_cont("no.\n"); |
1da177e4 LT |
288 | return; |
289 | } | |
290 | ||
35e7f788 | 291 | pr_cont("yes, workaround... "); |
1da177e4 LT |
292 | |
293 | asm volatile( | |
294 | "addiu %2, $0, %3\n\t" | |
295 | "dsrl %2, %2, 1\n\t" | |
70342287 | 296 | "daddiu %0, %2, %4\n\t" |
1da177e4 LT |
297 | "addiu %1, $0, %4\n\t" |
298 | "daddu %1, %2" | |
299 | : "=&r" (v), "=&r" (w), "=&r" (tmp) | |
460c0422 | 300 | : "I" (0xffffffffffffdb9aUL), "I" (0x1234)); |
1da177e4 LT |
301 | |
302 | if (v == w) { | |
35e7f788 | 303 | pr_cont("yes.\n"); |
1da177e4 LT |
304 | return; |
305 | } | |
306 | ||
35e7f788 | 307 | pr_cont("no.\n"); |
20d60d99 | 308 | panic(bug64hit, !DADDI_WAR ? daddiwar : nowar); |
1da177e4 LT |
309 | } |
310 | ||
20d60d99 | 311 | void __init check_bugs64_early(void) |
1da177e4 | 312 | { |
97f2645f | 313 | if (!IS_ENABLED(CONFIG_CPU_MIPSR6)) { |
180b1e3b LY |
314 | check_mult_sh(); |
315 | check_daddiu(); | |
316 | } | |
1da177e4 | 317 | } |
20d60d99 MR |
318 | |
319 | void __init check_bugs64(void) | |
320 | { | |
97f2645f | 321 | if (!IS_ENABLED(CONFIG_CPU_MIPSR6)) |
180b1e3b | 322 | check_daddi(); |
20d60d99 | 323 | } |