Commit | Line | Data |
---|---|---|
b21d55e9 | 1 | #include <linux/kernel.h> |
ab0615e2 | 2 | #include <linux/spinlock.h> |
b21d55e9 | 3 | #include <linux/kprobes.h> |
ab0615e2 | 4 | #include <linux/mm.h> |
b21d55e9 RV |
5 | #include <linux/stop_machine.h> |
6 | ||
7 | #include <asm/cacheflush.h> | |
ab0615e2 | 8 | #include <asm/fixmap.h> |
b21d55e9 RV |
9 | #include <asm/smp_plat.h> |
10 | #include <asm/opcodes.h> | |
11 | ||
12 | #include "patch.h" | |
13 | ||
14 | struct patch { | |
15 | void *addr; | |
16 | unsigned int insn; | |
17 | }; | |
18 | ||
ab0615e2 RV |
19 | static DEFINE_SPINLOCK(patch_lock); |
20 | ||
21 | static void __kprobes *patch_map(void *addr, int fixmap, unsigned long *flags) | |
22 | __acquires(&patch_lock) | |
23 | { | |
24 | unsigned int uintaddr = (uintptr_t) addr; | |
25 | bool module = !core_kernel_text(uintaddr); | |
26 | struct page *page; | |
27 | ||
28 | if (module && IS_ENABLED(CONFIG_DEBUG_SET_MODULE_RONX)) | |
29 | page = vmalloc_to_page(addr); | |
30 | else if (!module && IS_ENABLED(CONFIG_DEBUG_RODATA)) | |
31 | page = virt_to_page(addr); | |
32 | else | |
33 | return addr; | |
34 | ||
35 | if (flags) | |
36 | spin_lock_irqsave(&patch_lock, *flags); | |
37 | else | |
38 | __acquire(&patch_lock); | |
39 | ||
40 | set_fixmap(fixmap, page_to_phys(page)); | |
41 | ||
42 | return (void *) (__fix_to_virt(fixmap) + (uintaddr & ~PAGE_MASK)); | |
43 | } | |
44 | ||
45 | static void __kprobes patch_unmap(int fixmap, unsigned long *flags) | |
46 | __releases(&patch_lock) | |
47 | { | |
48 | clear_fixmap(fixmap); | |
49 | ||
50 | if (flags) | |
51 | spin_unlock_irqrestore(&patch_lock, *flags); | |
52 | else | |
53 | __release(&patch_lock); | |
54 | } | |
55 | ||
56 | void __kprobes __patch_text_real(void *addr, unsigned int insn, bool remap) | |
b21d55e9 RV |
57 | { |
58 | bool thumb2 = IS_ENABLED(CONFIG_THUMB2_KERNEL); | |
ab0615e2 RV |
59 | unsigned int uintaddr = (uintptr_t) addr; |
60 | bool twopage = false; | |
61 | unsigned long flags; | |
62 | void *waddr = addr; | |
b21d55e9 RV |
63 | int size; |
64 | ||
ab0615e2 RV |
65 | if (remap) |
66 | waddr = patch_map(addr, FIX_TEXT_POKE0, &flags); | |
67 | else | |
68 | __acquire(&patch_lock); | |
69 | ||
b21d55e9 | 70 | if (thumb2 && __opcode_is_thumb16(insn)) { |
ab0615e2 | 71 | *(u16 *)waddr = __opcode_to_mem_thumb16(insn); |
b21d55e9 | 72 | size = sizeof(u16); |
ab0615e2 | 73 | } else if (thumb2 && (uintaddr & 2)) { |
b21d55e9 RV |
74 | u16 first = __opcode_thumb32_first(insn); |
75 | u16 second = __opcode_thumb32_second(insn); | |
ab0615e2 RV |
76 | u16 *addrh0 = waddr; |
77 | u16 *addrh1 = waddr + 2; | |
78 | ||
79 | twopage = (uintaddr & ~PAGE_MASK) == PAGE_SIZE - 2; | |
80 | if (twopage && remap) | |
81 | addrh1 = patch_map(addr + 2, FIX_TEXT_POKE1, NULL); | |
82 | ||
83 | *addrh0 = __opcode_to_mem_thumb16(first); | |
84 | *addrh1 = __opcode_to_mem_thumb16(second); | |
b21d55e9 | 85 | |
ab0615e2 RV |
86 | if (twopage && addrh1 != addr + 2) { |
87 | flush_kernel_vmap_range(addrh1, 2); | |
88 | patch_unmap(FIX_TEXT_POKE1, NULL); | |
89 | } | |
b21d55e9 RV |
90 | |
91 | size = sizeof(u32); | |
92 | } else { | |
93 | if (thumb2) | |
94 | insn = __opcode_to_mem_thumb32(insn); | |
95 | else | |
96 | insn = __opcode_to_mem_arm(insn); | |
97 | ||
ab0615e2 | 98 | *(u32 *)waddr = insn; |
b21d55e9 RV |
99 | size = sizeof(u32); |
100 | } | |
101 | ||
ab0615e2 RV |
102 | if (waddr != addr) { |
103 | flush_kernel_vmap_range(waddr, twopage ? size / 2 : size); | |
104 | patch_unmap(FIX_TEXT_POKE0, &flags); | |
105 | } else | |
106 | __release(&patch_lock); | |
107 | ||
b21d55e9 RV |
108 | flush_icache_range((uintptr_t)(addr), |
109 | (uintptr_t)(addr) + size); | |
110 | } | |
111 | ||
112 | static int __kprobes patch_text_stop_machine(void *data) | |
113 | { | |
114 | struct patch *patch = data; | |
115 | ||
116 | __patch_text(patch->addr, patch->insn); | |
117 | ||
118 | return 0; | |
119 | } | |
120 | ||
121 | void __kprobes patch_text(void *addr, unsigned int insn) | |
122 | { | |
123 | struct patch patch = { | |
124 | .addr = addr, | |
125 | .insn = insn, | |
126 | }; | |
127 | ||
ab0615e2 | 128 | stop_machine(patch_text_stop_machine, &patch, NULL); |
b21d55e9 | 129 | } |