diff options
Diffstat (limited to 'arch/powerpc/lib/code-patching.c')
-rw-r--r-- | arch/powerpc/lib/code-patching.c | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/arch/powerpc/lib/code-patching.c b/arch/powerpc/lib/code-patching.c new file mode 100644 index 000000000000..0559fe086eb4 --- /dev/null +++ b/arch/powerpc/lib/code-patching.c | |||
@@ -0,0 +1,448 @@ | |||
1 | /* | ||
2 | * Copyright 2008 Michael Ellerman, IBM Corporation. | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU General Public License | ||
6 | * as published by the Free Software Foundation; either version | ||
7 | * 2 of the License, or (at your option) any later version. | ||
8 | */ | ||
9 | |||
10 | #include <linux/kernel.h> | ||
11 | #include <linux/vmalloc.h> | ||
12 | #include <linux/init.h> | ||
13 | #include <asm/page.h> | ||
14 | #include <asm/code-patching.h> | ||
15 | |||
16 | |||
17 | void patch_instruction(unsigned int *addr, unsigned int instr) | ||
18 | { | ||
19 | *addr = instr; | ||
20 | asm ("dcbst 0, %0; sync; icbi 0,%0; sync; isync" : : "r" (addr)); | ||
21 | } | ||
22 | |||
23 | void patch_branch(unsigned int *addr, unsigned long target, int flags) | ||
24 | { | ||
25 | patch_instruction(addr, create_branch(addr, target, flags)); | ||
26 | } | ||
27 | |||
28 | unsigned int create_branch(const unsigned int *addr, | ||
29 | unsigned long target, int flags) | ||
30 | { | ||
31 | unsigned int instruction; | ||
32 | long offset; | ||
33 | |||
34 | offset = target; | ||
35 | if (! (flags & BRANCH_ABSOLUTE)) | ||
36 | offset = offset - (unsigned long)addr; | ||
37 | |||
38 | /* Check we can represent the target in the instruction format */ | ||
39 | if (offset < -0x2000000 || offset > 0x1fffffc || offset & 0x3) | ||
40 | return 0; | ||
41 | |||
42 | /* Mask out the flags and target, so they don't step on each other. */ | ||
43 | instruction = 0x48000000 | (flags & 0x3) | (offset & 0x03FFFFFC); | ||
44 | |||
45 | return instruction; | ||
46 | } | ||
47 | |||
48 | unsigned int create_cond_branch(const unsigned int *addr, | ||
49 | unsigned long target, int flags) | ||
50 | { | ||
51 | unsigned int instruction; | ||
52 | long offset; | ||
53 | |||
54 | offset = target; | ||
55 | if (! (flags & BRANCH_ABSOLUTE)) | ||
56 | offset = offset - (unsigned long)addr; | ||
57 | |||
58 | /* Check we can represent the target in the instruction format */ | ||
59 | if (offset < -0x8000 || offset > 0x7FFF || offset & 0x3) | ||
60 | return 0; | ||
61 | |||
62 | /* Mask out the flags and target, so they don't step on each other. */ | ||
63 | instruction = 0x40000000 | (flags & 0x3FF0003) | (offset & 0xFFFC); | ||
64 | |||
65 | return instruction; | ||
66 | } | ||
67 | |||
68 | static unsigned int branch_opcode(unsigned int instr) | ||
69 | { | ||
70 | return (instr >> 26) & 0x3F; | ||
71 | } | ||
72 | |||
73 | static int instr_is_branch_iform(unsigned int instr) | ||
74 | { | ||
75 | return branch_opcode(instr) == 18; | ||
76 | } | ||
77 | |||
78 | static int instr_is_branch_bform(unsigned int instr) | ||
79 | { | ||
80 | return branch_opcode(instr) == 16; | ||
81 | } | ||
82 | |||
83 | int instr_is_relative_branch(unsigned int instr) | ||
84 | { | ||
85 | if (instr & BRANCH_ABSOLUTE) | ||
86 | return 0; | ||
87 | |||
88 | return instr_is_branch_iform(instr) || instr_is_branch_bform(instr); | ||
89 | } | ||
90 | |||
91 | static unsigned long branch_iform_target(const unsigned int *instr) | ||
92 | { | ||
93 | signed long imm; | ||
94 | |||
95 | imm = *instr & 0x3FFFFFC; | ||
96 | |||
97 | /* If the top bit of the immediate value is set this is negative */ | ||
98 | if (imm & 0x2000000) | ||
99 | imm -= 0x4000000; | ||
100 | |||
101 | if ((*instr & BRANCH_ABSOLUTE) == 0) | ||
102 | imm += (unsigned long)instr; | ||
103 | |||
104 | return (unsigned long)imm; | ||
105 | } | ||
106 | |||
107 | static unsigned long branch_bform_target(const unsigned int *instr) | ||
108 | { | ||
109 | signed long imm; | ||
110 | |||
111 | imm = *instr & 0xFFFC; | ||
112 | |||
113 | /* If the top bit of the immediate value is set this is negative */ | ||
114 | if (imm & 0x8000) | ||
115 | imm -= 0x10000; | ||
116 | |||
117 | if ((*instr & BRANCH_ABSOLUTE) == 0) | ||
118 | imm += (unsigned long)instr; | ||
119 | |||
120 | return (unsigned long)imm; | ||
121 | } | ||
122 | |||
123 | unsigned long branch_target(const unsigned int *instr) | ||
124 | { | ||
125 | if (instr_is_branch_iform(*instr)) | ||
126 | return branch_iform_target(instr); | ||
127 | else if (instr_is_branch_bform(*instr)) | ||
128 | return branch_bform_target(instr); | ||
129 | |||
130 | return 0; | ||
131 | } | ||
132 | |||
133 | int instr_is_branch_to_addr(const unsigned int *instr, unsigned long addr) | ||
134 | { | ||
135 | if (instr_is_branch_iform(*instr) || instr_is_branch_bform(*instr)) | ||
136 | return branch_target(instr) == addr; | ||
137 | |||
138 | return 0; | ||
139 | } | ||
140 | |||
141 | unsigned int translate_branch(const unsigned int *dest, const unsigned int *src) | ||
142 | { | ||
143 | unsigned long target; | ||
144 | |||
145 | target = branch_target(src); | ||
146 | |||
147 | if (instr_is_branch_iform(*src)) | ||
148 | return create_branch(dest, target, *src); | ||
149 | else if (instr_is_branch_bform(*src)) | ||
150 | return create_cond_branch(dest, target, *src); | ||
151 | |||
152 | return 0; | ||
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 */ | ||