blob: 7a27c47dad5b04687aabb5366db459edf70af3bf [file] [log] [blame]
Rabin Vincentb21d55e2012-02-18 17:50:51 +01001#include <linux/kernel.h>
2#include <linux/kprobes.h>
3#include <linux/stop_machine.h>
4
5#include <asm/cacheflush.h>
6#include <asm/smp_plat.h>
7#include <asm/opcodes.h>
Steve Mucklef132c6c2012-06-06 18:30:57 -07008#include <asm/mmu_writeable.h>
Rabin Vincentb21d55e2012-02-18 17:50:51 +01009
10#include "patch.h"
11
12struct patch {
13 void *addr;
14 unsigned int insn;
15};
16
17void __kprobes __patch_text(void *addr, unsigned int insn)
18{
19 bool thumb2 = IS_ENABLED(CONFIG_THUMB2_KERNEL);
20 int size;
Steve Mucklef132c6c2012-06-06 18:30:57 -070021 unsigned long flags;
22
23 mem_text_writeable_spinlock(&flags);
24 mem_text_address_writeable((unsigned long)addr);
Rabin Vincentb21d55e2012-02-18 17:50:51 +010025
26 if (thumb2 && __opcode_is_thumb16(insn)) {
27 *(u16 *)addr = __opcode_to_mem_thumb16(insn);
28 size = sizeof(u16);
29 } else if (thumb2 && ((uintptr_t)addr & 2)) {
30 u16 first = __opcode_thumb32_first(insn);
31 u16 second = __opcode_thumb32_second(insn);
32 u16 *addrh = addr;
33
34 addrh[0] = __opcode_to_mem_thumb16(first);
35 addrh[1] = __opcode_to_mem_thumb16(second);
36
37 size = sizeof(u32);
38 } else {
39 if (thumb2)
40 insn = __opcode_to_mem_thumb32(insn);
41 else
42 insn = __opcode_to_mem_arm(insn);
43
44 *(u32 *)addr = insn;
45 size = sizeof(u32);
46 }
47
48 flush_icache_range((uintptr_t)(addr),
49 (uintptr_t)(addr) + size);
Steve Mucklef132c6c2012-06-06 18:30:57 -070050
51 mem_text_address_restore();
52 mem_text_writeable_spinunlock(&flags);
Rabin Vincentb21d55e2012-02-18 17:50:51 +010053}
54
55static int __kprobes patch_text_stop_machine(void *data)
56{
57 struct patch *patch = data;
58
59 __patch_text(patch->addr, patch->insn);
60
61 return 0;
62}
63
64void __kprobes patch_text(void *addr, unsigned int insn)
65{
66 struct patch patch = {
67 .addr = addr,
68 .insn = insn,
69 };
70
71 if (cache_ops_need_broadcast()) {
72 stop_machine(patch_text_stop_machine, &patch, cpu_online_mask);
73 } else {
74 bool straddles_word = IS_ENABLED(CONFIG_THUMB2_KERNEL)
75 && __opcode_is_thumb32(insn)
76 && ((uintptr_t)addr & 2);
77
78 if (straddles_word)
79 stop_machine(patch_text_stop_machine, &patch, NULL);
80 else
81 __patch_text(addr, insn);
82 }
83}