diff options
author | Michael Ellerman <michael@ellerman.id.au> | 2008-06-23 21:32:32 -0400 |
---|---|---|
committer | Paul Mackerras <paulus@samba.org> | 2008-06-30 21:28:22 -0400 |
commit | ae0dc73625f9b0e636ccd130e394c9b654a062fb (patch) | |
tree | 1471cae752e3db5c2541771ec34ae8578e6984f5 /arch | |
parent | 411781a290b0d0a31fd73826b3ee110f1e3cc3b6 (diff) |
powerpc: Add tests of the code patching routines
Add tests of the existing code patching routines, as well as the new
routines added in the last commit. The self-tests are run late in boot
when CONFIG_CODE_PATCHING_SELFTEST=y, which depends on DEBUG_KERNEL=y.
Signed-off-by: Michael Ellerman <michael@ellerman.id.au>
Acked-by: Kumar Gala <galak@kernel.crashing.org>
Signed-off-by: Paul Mackerras <paulus@samba.org>
Diffstat (limited to 'arch')
-rw-r--r-- | arch/powerpc/Kconfig.debug | 5 | ||||
-rw-r--r-- | arch/powerpc/lib/code-patching.c | 298 |
2 files changed, 303 insertions, 0 deletions
diff --git a/arch/powerpc/Kconfig.debug b/arch/powerpc/Kconfig.debug index a7d24e692bab..dc5893903363 100644 --- a/arch/powerpc/Kconfig.debug +++ b/arch/powerpc/Kconfig.debug | |||
@@ -57,6 +57,11 @@ config KGDB | |||
57 | debugger. See <http://kgdb.sourceforge.net/> for more information. | 57 | debugger. See <http://kgdb.sourceforge.net/> for more information. |
58 | Unless you are intending to debug the kernel, say N here. | 58 | Unless you are intending to debug the kernel, say N here. |
59 | 59 | ||
60 | config CODE_PATCHING_SELFTEST | ||
61 | bool "Run self-tests of the code-patching code." | ||
62 | depends on DEBUG_KERNEL | ||
63 | default n | ||
64 | |||
60 | choice | 65 | choice |
61 | prompt "Serial Port" | 66 | prompt "Serial Port" |
62 | depends on KGDB | 67 | depends on KGDB |
diff --git a/arch/powerpc/lib/code-patching.c b/arch/powerpc/lib/code-patching.c index 27957c4ea9e0..0559fe086eb4 100644 --- a/arch/powerpc/lib/code-patching.c +++ b/arch/powerpc/lib/code-patching.c | |||
@@ -8,6 +8,9 @@ | |||
8 | */ | 8 | */ |
9 | 9 | ||
10 | #include <linux/kernel.h> | 10 | #include <linux/kernel.h> |
11 | #include <linux/vmalloc.h> | ||
12 | #include <linux/init.h> | ||
13 | #include <asm/page.h> | ||
11 | #include <asm/code-patching.h> | 14 | #include <asm/code-patching.h> |
12 | 15 | ||
13 | 16 | ||
@@ -148,3 +151,298 @@ unsigned int translate_branch(const unsigned int *dest, const unsigned int *src) | |||
148 | 151 | ||
149 | return 0; | 152 | return 0; |
150 | } | 153 | } |
154 | |||
155 | |||
156 | #ifdef CONFIG_CODE_PATCHING_SELFTEST | ||
157 | |||
158 | static void __init test_trampoline(void) | ||
159 | { | ||
160 | asm ("nop;\n"); | ||
161 | } | ||
162 | |||
163 | #define check(x) \ | ||
164 | if (!(x)) printk("code-patching: test failed at line %d\n", __LINE__); | ||
165 | |||
166 | static void __init test_branch_iform(void) | ||
167 | { | ||
168 | unsigned int instr; | ||
169 | unsigned long addr; | ||
170 | |||
171 | addr = (unsigned long)&instr; | ||
172 | |||
173 | /* The simplest case, branch to self, no flags */ | ||
174 | check(instr_is_branch_iform(0x48000000)); | ||
175 | /* All bits of target set, and flags */ | ||
176 | check(instr_is_branch_iform(0x4bffffff)); | ||
177 | /* High bit of opcode set, which is wrong */ | ||
178 | check(!instr_is_branch_iform(0xcbffffff)); | ||
179 | /* Middle bits of opcode set, which is wrong */ | ||
180 | check(!instr_is_branch_iform(0x7bffffff)); | ||
181 | |||
182 | /* Simplest case, branch to self with link */ | ||
183 | check(instr_is_branch_iform(0x48000001)); | ||
184 | /* All bits of targets set */ | ||
185 | check(instr_is_branch_iform(0x4bfffffd)); | ||
186 | /* Some bits of targets set */ | ||
187 | check(instr_is_branch_iform(0x4bff00fd)); | ||
188 | /* Must be a valid branch to start with */ | ||
189 | check(!instr_is_branch_iform(0x7bfffffd)); | ||
190 | |||
191 | /* Absolute branch to 0x100 */ | ||
192 | instr = 0x48000103; | ||
193 | check(instr_is_branch_to_addr(&instr, 0x100)); | ||
194 | /* Absolute branch to 0x420fc */ | ||
195 | instr = 0x480420ff; | ||
196 | check(instr_is_branch_to_addr(&instr, 0x420fc)); | ||
197 | /* Maximum positive relative branch, + 20MB - 4B */ | ||
198 | instr = 0x49fffffc; | ||
199 | check(instr_is_branch_to_addr(&instr, addr + 0x1FFFFFC)); | ||
200 | /* Smallest negative relative branch, - 4B */ | ||
201 | instr = 0x4bfffffc; | ||
202 | check(instr_is_branch_to_addr(&instr, addr - 4)); | ||
203 | /* Largest negative relative branch, - 32 MB */ | ||
204 | instr = 0x4a000000; | ||
205 | check(instr_is_branch_to_addr(&instr, addr - 0x2000000)); | ||
206 | |||
207 | /* Branch to self, with link */ | ||
208 | instr = create_branch(&instr, addr, BRANCH_SET_LINK); | ||
209 | check(instr_is_branch_to_addr(&instr, addr)); | ||
210 | |||
211 | /* Branch to self - 0x100, with link */ | ||
212 | instr = create_branch(&instr, addr - 0x100, BRANCH_SET_LINK); | ||
213 | check(instr_is_branch_to_addr(&instr, addr - 0x100)); | ||
214 | |||
215 | /* Branch to self + 0x100, no link */ | ||
216 | instr = create_branch(&instr, addr + 0x100, 0); | ||
217 | check(instr_is_branch_to_addr(&instr, addr + 0x100)); | ||
218 | |||
219 | /* Maximum relative negative offset, - 32 MB */ | ||
220 | instr = create_branch(&instr, addr - 0x2000000, BRANCH_SET_LINK); | ||
221 | check(instr_is_branch_to_addr(&instr, addr - 0x2000000)); | ||
222 | |||
223 | /* Out of range relative negative offset, - 32 MB + 4*/ | ||
224 | instr = create_branch(&instr, addr - 0x2000004, BRANCH_SET_LINK); | ||
225 | check(instr == 0); | ||
226 | |||
227 | /* Out of range relative positive offset, + 32 MB */ | ||
228 | instr = create_branch(&instr, addr + 0x2000000, BRANCH_SET_LINK); | ||
229 | check(instr == 0); | ||
230 | |||
231 | /* Unaligned target */ | ||
232 | instr = create_branch(&instr, addr + 3, BRANCH_SET_LINK); | ||
233 | check(instr == 0); | ||
234 | |||
235 | /* Check flags are masked correctly */ | ||
236 | instr = create_branch(&instr, addr, 0xFFFFFFFC); | ||
237 | check(instr_is_branch_to_addr(&instr, addr)); | ||
238 | check(instr == 0x48000000); | ||
239 | } | ||
240 | |||
241 | static void __init test_create_function_call(void) | ||
242 | { | ||
243 | unsigned int *iptr; | ||
244 | unsigned long dest; | ||
245 | |||
246 | /* Check we can create a function call */ | ||
247 | iptr = (unsigned int *)ppc_function_entry(test_trampoline); | ||
248 | dest = ppc_function_entry(test_create_function_call); | ||
249 | patch_instruction(iptr, create_branch(iptr, dest, BRANCH_SET_LINK)); | ||
250 | check(instr_is_branch_to_addr(iptr, dest)); | ||
251 | } | ||
252 | |||
253 | static void __init test_branch_bform(void) | ||
254 | { | ||
255 | unsigned long addr; | ||
256 | unsigned int *iptr, instr, flags; | ||
257 | |||
258 | iptr = &instr; | ||
259 | addr = (unsigned long)iptr; | ||
260 | |||
261 | /* The simplest case, branch to self, no flags */ | ||
262 | check(instr_is_branch_bform(0x40000000)); | ||
263 | /* All bits of target set, and flags */ | ||
264 | check(instr_is_branch_bform(0x43ffffff)); | ||
265 | /* High bit of opcode set, which is wrong */ | ||
266 | check(!instr_is_branch_bform(0xc3ffffff)); | ||
267 | /* Middle bits of opcode set, which is wrong */ | ||
268 | check(!instr_is_branch_bform(0x7bffffff)); | ||
269 | |||
270 | /* Absolute conditional branch to 0x100 */ | ||
271 | instr = 0x43ff0103; | ||
272 | check(instr_is_branch_to_addr(&instr, 0x100)); | ||
273 | /* Absolute conditional branch to 0x20fc */ | ||
274 | instr = 0x43ff20ff; | ||
275 | check(instr_is_branch_to_addr(&instr, 0x20fc)); | ||
276 | /* Maximum positive relative conditional branch, + 32 KB - 4B */ | ||
277 | instr = 0x43ff7ffc; | ||
278 | check(instr_is_branch_to_addr(&instr, addr + 0x7FFC)); | ||
279 | /* Smallest negative relative conditional branch, - 4B */ | ||
280 | instr = 0x43fffffc; | ||
281 | check(instr_is_branch_to_addr(&instr, addr - 4)); | ||
282 | /* Largest negative relative conditional branch, - 32 KB */ | ||
283 | instr = 0x43ff8000; | ||
284 | check(instr_is_branch_to_addr(&instr, addr - 0x8000)); | ||
285 | |||
286 | /* All condition code bits set & link */ | ||
287 | flags = 0x3ff000 | BRANCH_SET_LINK; | ||
288 | |||
289 | /* Branch to self */ | ||
290 | instr = create_cond_branch(iptr, addr, flags); | ||
291 | check(instr_is_branch_to_addr(&instr, addr)); | ||
292 | |||
293 | /* Branch to self - 0x100 */ | ||
294 | instr = create_cond_branch(iptr, addr - 0x100, flags); | ||
295 | check(instr_is_branch_to_addr(&instr, addr - 0x100)); | ||
296 | |||
297 | /* Branch to self + 0x100 */ | ||
298 | instr = create_cond_branch(iptr, addr + 0x100, flags); | ||
299 | check(instr_is_branch_to_addr(&instr, addr + 0x100)); | ||
300 | |||
301 | /* Maximum relative negative offset, - 32 KB */ | ||
302 | instr = create_cond_branch(iptr, addr - 0x8000, flags); | ||
303 | check(instr_is_branch_to_addr(&instr, addr - 0x8000)); | ||
304 | |||
305 | /* Out of range relative negative offset, - 32 KB + 4*/ | ||
306 | instr = create_cond_branch(iptr, addr - 0x8004, flags); | ||
307 | check(instr == 0); | ||
308 | |||
309 | /* Out of range relative positive offset, + 32 KB */ | ||
310 | instr = create_cond_branch(iptr, addr + 0x8000, flags); | ||
311 | check(instr == 0); | ||
312 | |||
313 | /* Unaligned target */ | ||
314 | instr = create_cond_branch(iptr, addr + 3, flags); | ||
315 | check(instr == 0); | ||
316 | |||
317 | /* Check flags are masked correctly */ | ||
318 | instr = create_cond_branch(iptr, addr, 0xFFFFFFFC); | ||
319 | check(instr_is_branch_to_addr(&instr, addr)); | ||
320 | check(instr == 0x43FF0000); | ||
321 | } | ||
322 | |||
323 | static void __init test_translate_branch(void) | ||
324 | { | ||
325 | unsigned long addr; | ||
326 | unsigned int *p, *q; | ||
327 | void *buf; | ||
328 | |||
329 | buf = vmalloc(PAGE_ALIGN(0x2000000 + 1)); | ||
330 | check(buf); | ||
331 | if (!buf) | ||
332 | return; | ||
333 | |||
334 | /* Simple case, branch to self moved a little */ | ||
335 | p = buf; | ||
336 | addr = (unsigned long)p; | ||
337 | patch_branch(p, addr, 0); | ||
338 | check(instr_is_branch_to_addr(p, addr)); | ||
339 | q = p + 1; | ||
340 | patch_instruction(q, translate_branch(q, p)); | ||
341 | check(instr_is_branch_to_addr(q, addr)); | ||
342 | |||
343 | /* Maximum negative case, move b . to addr + 32 MB */ | ||
344 | p = buf; | ||
345 | addr = (unsigned long)p; | ||
346 | patch_branch(p, addr, 0); | ||
347 | q = buf + 0x2000000; | ||
348 | patch_instruction(q, translate_branch(q, p)); | ||
349 | check(instr_is_branch_to_addr(p, addr)); | ||
350 | check(instr_is_branch_to_addr(q, addr)); | ||
351 | check(*q == 0x4a000000); | ||
352 | |||
353 | /* Maximum positive case, move x to x - 32 MB + 4 */ | ||
354 | p = buf + 0x2000000; | ||
355 | addr = (unsigned long)p; | ||
356 | patch_branch(p, addr, 0); | ||
357 | q = buf + 4; | ||
358 | patch_instruction(q, translate_branch(q, p)); | ||
359 | check(instr_is_branch_to_addr(p, addr)); | ||
360 | check(instr_is_branch_to_addr(q, addr)); | ||
361 | check(*q == 0x49fffffc); | ||
362 | |||
363 | /* Jump to x + 16 MB moved to x + 20 MB */ | ||
364 | p = buf; | ||
365 | addr = 0x1000000 + (unsigned long)buf; | ||
366 | patch_branch(p, addr, BRANCH_SET_LINK); | ||
367 | q = buf + 0x1400000; | ||
368 | patch_instruction(q, translate_branch(q, p)); | ||
369 | check(instr_is_branch_to_addr(p, addr)); | ||
370 | check(instr_is_branch_to_addr(q, addr)); | ||
371 | |||
372 | /* Jump to x + 16 MB moved to x - 16 MB + 4 */ | ||
373 | p = buf + 0x1000000; | ||
374 | addr = 0x2000000 + (unsigned long)buf; | ||
375 | patch_branch(p, addr, 0); | ||
376 | q = buf + 4; | ||
377 | patch_instruction(q, translate_branch(q, p)); | ||
378 | check(instr_is_branch_to_addr(p, addr)); | ||
379 | check(instr_is_branch_to_addr(q, addr)); | ||
380 | |||
381 | |||
382 | /* Conditional branch tests */ | ||
383 | |||
384 | /* Simple case, branch to self moved a little */ | ||
385 | p = buf; | ||
386 | addr = (unsigned long)p; | ||
387 | patch_instruction(p, create_cond_branch(p, addr, 0)); | ||
388 | check(instr_is_branch_to_addr(p, addr)); | ||
389 | q = p + 1; | ||
390 | patch_instruction(q, translate_branch(q, p)); | ||
391 | check(instr_is_branch_to_addr(q, addr)); | ||
392 | |||
393 | /* Maximum negative case, move b . to addr + 32 KB */ | ||
394 | p = buf; | ||
395 | addr = (unsigned long)p; | ||
396 | patch_instruction(p, create_cond_branch(p, addr, 0xFFFFFFFC)); | ||
397 | q = buf + 0x8000; | ||
398 | patch_instruction(q, translate_branch(q, p)); | ||
399 | check(instr_is_branch_to_addr(p, addr)); | ||
400 | check(instr_is_branch_to_addr(q, addr)); | ||
401 | check(*q == 0x43ff8000); | ||
402 | |||
403 | /* Maximum positive case, move x to x - 32 KB + 4 */ | ||
404 | p = buf + 0x8000; | ||
405 | addr = (unsigned long)p; | ||
406 | patch_instruction(p, create_cond_branch(p, addr, 0xFFFFFFFC)); | ||
407 | q = buf + 4; | ||
408 | patch_instruction(q, translate_branch(q, p)); | ||
409 | check(instr_is_branch_to_addr(p, addr)); | ||
410 | check(instr_is_branch_to_addr(q, addr)); | ||
411 | check(*q == 0x43ff7ffc); | ||
412 | |||
413 | /* Jump to x + 12 KB moved to x + 20 KB */ | ||
414 | p = buf; | ||
415 | addr = 0x3000 + (unsigned long)buf; | ||
416 | patch_instruction(p, create_cond_branch(p, addr, BRANCH_SET_LINK)); | ||
417 | q = buf + 0x5000; | ||
418 | patch_instruction(q, translate_branch(q, p)); | ||
419 | check(instr_is_branch_to_addr(p, addr)); | ||
420 | check(instr_is_branch_to_addr(q, addr)); | ||
421 | |||
422 | /* Jump to x + 8 KB moved to x - 8 KB + 4 */ | ||
423 | p = buf + 0x2000; | ||
424 | addr = 0x4000 + (unsigned long)buf; | ||
425 | patch_instruction(p, create_cond_branch(p, addr, 0)); | ||
426 | q = buf + 4; | ||
427 | patch_instruction(q, translate_branch(q, p)); | ||
428 | check(instr_is_branch_to_addr(p, addr)); | ||
429 | check(instr_is_branch_to_addr(q, addr)); | ||
430 | |||
431 | /* Free the buffer we were using */ | ||
432 | vfree(buf); | ||
433 | } | ||
434 | |||
435 | static int __init test_code_patching(void) | ||
436 | { | ||
437 | printk(KERN_DEBUG "Running code patching self-tests ...\n"); | ||
438 | |||
439 | test_branch_iform(); | ||
440 | test_branch_bform(); | ||
441 | test_create_function_call(); | ||
442 | test_translate_branch(); | ||
443 | |||
444 | return 0; | ||
445 | } | ||
446 | late_initcall(test_code_patching); | ||
447 | |||
448 | #endif /* CONFIG_CODE_PATCHING_SELFTEST */ | ||