1043cb41aSZong Li // SPDX-License-Identifier: GPL-2.0-only
2043cb41aSZong Li /*
3043cb41aSZong Li * Copyright (C) 2020 SiFive
4043cb41aSZong Li */
5043cb41aSZong Li
6043cb41aSZong Li #include <linux/spinlock.h>
7043cb41aSZong Li #include <linux/mm.h>
80ff7c3b3SZong Li #include <linux/memory.h>
9cad539baSPuranjay Mohan #include <linux/string.h>
10043cb41aSZong Li #include <linux/uaccess.h>
11043cb41aSZong Li #include <linux/stop_machine.h>
12043cb41aSZong Li #include <asm/kprobes.h>
13043cb41aSZong Li #include <asm/cacheflush.h>
14043cb41aSZong Li #include <asm/fixmap.h>
152a8db5ecSConor Dooley #include <asm/ftrace.h>
16*0c3beacfSMike Rapoport (Microsoft) #include <asm/text-patching.h>
17420370f3SAlexandre Ghiti #include <asm/sections.h>
18043cb41aSZong Li
19b80b3d58SZong Li struct patch_insn {
20043cb41aSZong Li void *addr;
215e57fb7bSPu Lehui u32 *insns;
2251781ce8SSamuel Holland size_t len;
23043cb41aSZong Li atomic_t cpu_count;
24043cb41aSZong Li };
25043cb41aSZong Li
262a8db5ecSConor Dooley int riscv_patch_in_stop_machine = false;
272a8db5ecSConor Dooley
28043cb41aSZong Li #ifdef CONFIG_MMU
29420370f3SAlexandre Ghiti
is_kernel_exittext(uintptr_t addr)30420370f3SAlexandre Ghiti static inline bool is_kernel_exittext(uintptr_t addr)
31420370f3SAlexandre Ghiti {
32420370f3SAlexandre Ghiti return system_state < SYSTEM_RUNNING &&
33420370f3SAlexandre Ghiti addr >= (uintptr_t)__exittext_begin &&
34420370f3SAlexandre Ghiti addr < (uintptr_t)__exittext_end;
35420370f3SAlexandre Ghiti }
36420370f3SAlexandre Ghiti
37edfcf91fSGuo Ren /*
38edfcf91fSGuo Ren * The fix_to_virt(, idx) needs a const value (not a dynamic variable of
39edfcf91fSGuo Ren * reg-a0) or BUILD_BUG_ON failed with "idx >= __end_of_fixed_addresses".
40edfcf91fSGuo Ren * So use '__always_inline' and 'const unsigned int fixmap' here.
41edfcf91fSGuo Ren */
patch_map(void * addr,const unsigned int fixmap)42edfcf91fSGuo Ren static __always_inline void *patch_map(void *addr, const unsigned int fixmap)
43043cb41aSZong Li {
44043cb41aSZong Li uintptr_t uintaddr = (uintptr_t) addr;
45043cb41aSZong Li struct page *page;
46043cb41aSZong Li
47420370f3SAlexandre Ghiti if (core_kernel_text(uintaddr) || is_kernel_exittext(uintaddr))
48043cb41aSZong Li page = phys_to_page(__pa_symbol(addr));
49043cb41aSZong Li else if (IS_ENABLED(CONFIG_STRICT_MODULE_RWX))
50043cb41aSZong Li page = vmalloc_to_page(addr);
51043cb41aSZong Li else
52043cb41aSZong Li return addr;
53043cb41aSZong Li
54043cb41aSZong Li BUG_ON(!page);
55043cb41aSZong Li
56043cb41aSZong Li return (void *)set_fixmap_offset(fixmap, page_to_phys(page) +
57eaee5487SSamuel Holland offset_in_page(addr));
58043cb41aSZong Li }
59043cb41aSZong Li
patch_unmap(int fixmap)605303df24SZong Li static void patch_unmap(int fixmap)
61043cb41aSZong Li {
62043cb41aSZong Li clear_fixmap(fixmap);
63043cb41aSZong Li }
645303df24SZong Li NOKPROBE_SYMBOL(patch_unmap);
65043cb41aSZong Li
__patch_insn_set(void * addr,u8 c,size_t len)66cad539baSPuranjay Mohan static int __patch_insn_set(void *addr, u8 c, size_t len)
67cad539baSPuranjay Mohan {
68eaee5487SSamuel Holland bool across_pages = (offset_in_page(addr) + len) > PAGE_SIZE;
69cad539baSPuranjay Mohan void *waddr = addr;
70cad539baSPuranjay Mohan
71cad539baSPuranjay Mohan /*
72cad539baSPuranjay Mohan * Only two pages can be mapped at a time for writing.
73cad539baSPuranjay Mohan */
74cad539baSPuranjay Mohan if (len + offset_in_page(addr) > 2 * PAGE_SIZE)
75cad539baSPuranjay Mohan return -EINVAL;
76cad539baSPuranjay Mohan /*
77cad539baSPuranjay Mohan * Before reaching here, it was expected to lock the text_mutex
78cad539baSPuranjay Mohan * already, so we don't need to give another lock here and could
79cad539baSPuranjay Mohan * ensure that it was safe between each cores.
80cad539baSPuranjay Mohan */
81cad539baSPuranjay Mohan lockdep_assert_held(&text_mutex);
82cad539baSPuranjay Mohan
83a370c241SAlexandre Ghiti preempt_disable();
84a370c241SAlexandre Ghiti
85cad539baSPuranjay Mohan if (across_pages)
86cad539baSPuranjay Mohan patch_map(addr + PAGE_SIZE, FIX_TEXT_POKE1);
87cad539baSPuranjay Mohan
88cad539baSPuranjay Mohan waddr = patch_map(addr, FIX_TEXT_POKE0);
89cad539baSPuranjay Mohan
90cad539baSPuranjay Mohan memset(waddr, c, len);
91cad539baSPuranjay Mohan
92edf2d546SAlexandre Ghiti /*
93edf2d546SAlexandre Ghiti * We could have just patched a function that is about to be
94edf2d546SAlexandre Ghiti * called so make sure we don't execute partially patched
95edf2d546SAlexandre Ghiti * instructions by flushing the icache as soon as possible.
96edf2d546SAlexandre Ghiti */
97edf2d546SAlexandre Ghiti local_flush_icache_range((unsigned long)waddr,
98edf2d546SAlexandre Ghiti (unsigned long)waddr + len);
99edf2d546SAlexandre Ghiti
100cad539baSPuranjay Mohan patch_unmap(FIX_TEXT_POKE0);
101cad539baSPuranjay Mohan
102cad539baSPuranjay Mohan if (across_pages)
103cad539baSPuranjay Mohan patch_unmap(FIX_TEXT_POKE1);
104cad539baSPuranjay Mohan
105a370c241SAlexandre Ghiti preempt_enable();
106a370c241SAlexandre Ghiti
107cad539baSPuranjay Mohan return 0;
108cad539baSPuranjay Mohan }
109cad539baSPuranjay Mohan NOKPROBE_SYMBOL(__patch_insn_set);
110cad539baSPuranjay Mohan
__patch_insn_write(void * addr,const void * insn,size_t len)1119721873cSPuranjay Mohan static int __patch_insn_write(void *addr, const void *insn, size_t len)
112043cb41aSZong Li {
113eaee5487SSamuel Holland bool across_pages = (offset_in_page(addr) + len) > PAGE_SIZE;
114043cb41aSZong Li void *waddr = addr;
115043cb41aSZong Li int ret;
116043cb41aSZong Li
1170ff7c3b3SZong Li /*
1189721873cSPuranjay Mohan * Only two pages can be mapped at a time for writing.
1199721873cSPuranjay Mohan */
1209721873cSPuranjay Mohan if (len + offset_in_page(addr) > 2 * PAGE_SIZE)
1219721873cSPuranjay Mohan return -EINVAL;
1229721873cSPuranjay Mohan
1239721873cSPuranjay Mohan /*
1240ff7c3b3SZong Li * Before reaching here, it was expected to lock the text_mutex
1250ff7c3b3SZong Li * already, so we don't need to give another lock here and could
1260ff7c3b3SZong Li * ensure that it was safe between each cores.
1272a8db5ecSConor Dooley *
1282a8db5ecSConor Dooley * We're currently using stop_machine() for ftrace & kprobes, and while
1292a8db5ecSConor Dooley * that ensures text_mutex is held before installing the mappings it
1302a8db5ecSConor Dooley * does not ensure text_mutex is held by the calling thread. That's
1312a8db5ecSConor Dooley * safe but triggers a lockdep failure, so just elide it for that
1322a8db5ecSConor Dooley * specific case.
1330ff7c3b3SZong Li */
1342a8db5ecSConor Dooley if (!riscv_patch_in_stop_machine)
1350ff7c3b3SZong Li lockdep_assert_held(&text_mutex);
136043cb41aSZong Li
137a370c241SAlexandre Ghiti preempt_disable();
138a370c241SAlexandre Ghiti
139043cb41aSZong Li if (across_pages)
1409721873cSPuranjay Mohan patch_map(addr + PAGE_SIZE, FIX_TEXT_POKE1);
141043cb41aSZong Li
142043cb41aSZong Li waddr = patch_map(addr, FIX_TEXT_POKE0);
143043cb41aSZong Li
144fe557319SChristoph Hellwig ret = copy_to_kernel_nofault(waddr, insn, len);
145043cb41aSZong Li
146edf2d546SAlexandre Ghiti /*
147edf2d546SAlexandre Ghiti * We could have just patched a function that is about to be
148edf2d546SAlexandre Ghiti * called so make sure we don't execute partially patched
149edf2d546SAlexandre Ghiti * instructions by flushing the icache as soon as possible.
150edf2d546SAlexandre Ghiti */
151edf2d546SAlexandre Ghiti local_flush_icache_range((unsigned long)waddr,
152edf2d546SAlexandre Ghiti (unsigned long)waddr + len);
153edf2d546SAlexandre Ghiti
154043cb41aSZong Li patch_unmap(FIX_TEXT_POKE0);
155043cb41aSZong Li
156043cb41aSZong Li if (across_pages)
157043cb41aSZong Li patch_unmap(FIX_TEXT_POKE1);
158043cb41aSZong Li
159a370c241SAlexandre Ghiti preempt_enable();
160a370c241SAlexandre Ghiti
161043cb41aSZong Li return ret;
162043cb41aSZong Li }
1639721873cSPuranjay Mohan NOKPROBE_SYMBOL(__patch_insn_write);
164043cb41aSZong Li #else
__patch_insn_set(void * addr,u8 c,size_t len)165cad539baSPuranjay Mohan static int __patch_insn_set(void *addr, u8 c, size_t len)
166cad539baSPuranjay Mohan {
167cad539baSPuranjay Mohan memset(addr, c, len);
168cad539baSPuranjay Mohan
169cad539baSPuranjay Mohan return 0;
170cad539baSPuranjay Mohan }
171cad539baSPuranjay Mohan NOKPROBE_SYMBOL(__patch_insn_set);
172cad539baSPuranjay Mohan
__patch_insn_write(void * addr,const void * insn,size_t len)1739721873cSPuranjay Mohan static int __patch_insn_write(void *addr, const void *insn, size_t len)
174043cb41aSZong Li {
175fe557319SChristoph Hellwig return copy_to_kernel_nofault(addr, insn, len);
176043cb41aSZong Li }
1779721873cSPuranjay Mohan NOKPROBE_SYMBOL(__patch_insn_write);
178043cb41aSZong Li #endif /* CONFIG_MMU */
179043cb41aSZong Li
patch_insn_set(void * addr,u8 c,size_t len)180cad539baSPuranjay Mohan static int patch_insn_set(void *addr, u8 c, size_t len)
181cad539baSPuranjay Mohan {
182cad539baSPuranjay Mohan size_t size;
1835080ca0fSSamuel Holland int ret;
184cad539baSPuranjay Mohan
185cad539baSPuranjay Mohan /*
186cad539baSPuranjay Mohan * __patch_insn_set() can only work on 2 pages at a time so call it in a
187cad539baSPuranjay Mohan * loop with len <= 2 * PAGE_SIZE.
188cad539baSPuranjay Mohan */
1895080ca0fSSamuel Holland while (len) {
1905080ca0fSSamuel Holland size = min(len, PAGE_SIZE * 2 - offset_in_page(addr));
1915080ca0fSSamuel Holland ret = __patch_insn_set(addr, c, size);
1925080ca0fSSamuel Holland if (ret)
1935080ca0fSSamuel Holland return ret;
194cad539baSPuranjay Mohan
1955080ca0fSSamuel Holland addr += size;
1965080ca0fSSamuel Holland len -= size;
197cad539baSPuranjay Mohan }
198cad539baSPuranjay Mohan
1995080ca0fSSamuel Holland return 0;
200cad539baSPuranjay Mohan }
201cad539baSPuranjay Mohan NOKPROBE_SYMBOL(patch_insn_set);
202cad539baSPuranjay Mohan
patch_text_set_nosync(void * addr,u8 c,size_t len)203cad539baSPuranjay Mohan int patch_text_set_nosync(void *addr, u8 c, size_t len)
204cad539baSPuranjay Mohan {
205cad539baSPuranjay Mohan int ret;
206cad539baSPuranjay Mohan
20747742484SSamuel Holland ret = patch_insn_set(addr, c, len);
208ee9a6839SAlexandre Ghiti if (!ret)
209ee9a6839SAlexandre Ghiti flush_icache_range((uintptr_t)addr, (uintptr_t)addr + len);
210cad539baSPuranjay Mohan
211cad539baSPuranjay Mohan return ret;
212cad539baSPuranjay Mohan }
213cad539baSPuranjay Mohan NOKPROBE_SYMBOL(patch_text_set_nosync);
214cad539baSPuranjay Mohan
patch_insn_write(void * addr,const void * insn,size_t len)215c97bf629SAlexandre Ghiti int patch_insn_write(void *addr, const void *insn, size_t len)
2169721873cSPuranjay Mohan {
2179721873cSPuranjay Mohan size_t size;
2185080ca0fSSamuel Holland int ret;
2199721873cSPuranjay Mohan
2209721873cSPuranjay Mohan /*
2219721873cSPuranjay Mohan * Copy the instructions to the destination address, two pages at a time
2229721873cSPuranjay Mohan * because __patch_insn_write() can only handle len <= 2 * PAGE_SIZE.
2239721873cSPuranjay Mohan */
2245080ca0fSSamuel Holland while (len) {
2255080ca0fSSamuel Holland size = min(len, PAGE_SIZE * 2 - offset_in_page(addr));
2265080ca0fSSamuel Holland ret = __patch_insn_write(addr, insn, size);
2275080ca0fSSamuel Holland if (ret)
2285080ca0fSSamuel Holland return ret;
2299721873cSPuranjay Mohan
2305080ca0fSSamuel Holland addr += size;
2315080ca0fSSamuel Holland insn += size;
2325080ca0fSSamuel Holland len -= size;
2339721873cSPuranjay Mohan }
2349721873cSPuranjay Mohan
2355080ca0fSSamuel Holland return 0;
2369721873cSPuranjay Mohan }
2379721873cSPuranjay Mohan NOKPROBE_SYMBOL(patch_insn_write);
2389721873cSPuranjay Mohan
patch_text_nosync(void * addr,const void * insns,size_t len)2395303df24SZong Li int patch_text_nosync(void *addr, const void *insns, size_t len)
240043cb41aSZong Li {
241043cb41aSZong Li int ret;
242043cb41aSZong Li
24347742484SSamuel Holland ret = patch_insn_write(addr, insns, len);
244ee9a6839SAlexandre Ghiti if (!ret)
245ee9a6839SAlexandre Ghiti flush_icache_range((uintptr_t)addr, (uintptr_t)addr + len);
246043cb41aSZong Li
247043cb41aSZong Li return ret;
248043cb41aSZong Li }
2495303df24SZong Li NOKPROBE_SYMBOL(patch_text_nosync);
250043cb41aSZong Li
patch_text_cb(void * data)2515303df24SZong Li static int patch_text_cb(void *data)
252043cb41aSZong Li {
253b80b3d58SZong Li struct patch_insn *patch = data;
25451781ce8SSamuel Holland int ret = 0;
255043cb41aSZong Li
2568ec14429SGuo Ren if (atomic_inc_return(&patch->cpu_count) == num_online_cpus()) {
25751781ce8SSamuel Holland ret = patch_insn_write(patch->addr, patch->insns, patch->len);
258c97bf629SAlexandre Ghiti /*
259c97bf629SAlexandre Ghiti * Make sure the patching store is effective *before* we
260c97bf629SAlexandre Ghiti * increment the counter which releases all waiting CPUs
261c97bf629SAlexandre Ghiti * by using the release variant of atomic increment. The
262c97bf629SAlexandre Ghiti * release pairs with the call to local_flush_icache_all()
263c97bf629SAlexandre Ghiti * on the waiting CPU.
264c97bf629SAlexandre Ghiti */
265c97bf629SAlexandre Ghiti atomic_inc_return_release(&patch->cpu_count);
266043cb41aSZong Li } else {
267043cb41aSZong Li while (atomic_read(&patch->cpu_count) <= num_online_cpus())
268043cb41aSZong Li cpu_relax();
269043cb41aSZong Li
270c97bf629SAlexandre Ghiti local_flush_icache_all();
271edf2d546SAlexandre Ghiti }
272c97bf629SAlexandre Ghiti
273043cb41aSZong Li return ret;
274043cb41aSZong Li }
2755303df24SZong Li NOKPROBE_SYMBOL(patch_text_cb);
276043cb41aSZong Li
patch_text(void * addr,u32 * insns,size_t len)27751781ce8SSamuel Holland int patch_text(void *addr, u32 *insns, size_t len)
278043cb41aSZong Li {
2792a8db5ecSConor Dooley int ret;
280b80b3d58SZong Li struct patch_insn patch = {
281043cb41aSZong Li .addr = addr,
2825e57fb7bSPu Lehui .insns = insns,
28351781ce8SSamuel Holland .len = len,
284043cb41aSZong Li .cpu_count = ATOMIC_INIT(0),
285043cb41aSZong Li };
286043cb41aSZong Li
2872a8db5ecSConor Dooley /*
2882a8db5ecSConor Dooley * kprobes takes text_mutex, before calling patch_text(), but as we call
2892a8db5ecSConor Dooley * calls stop_machine(), the lockdep assertion in patch_insn_write()
2902a8db5ecSConor Dooley * gets confused by the context in which the lock is taken.
2912a8db5ecSConor Dooley * Instead, ensure the lock is held before calling stop_machine(), and
2922a8db5ecSConor Dooley * set riscv_patch_in_stop_machine to skip the check in
2932a8db5ecSConor Dooley * patch_insn_write().
2942a8db5ecSConor Dooley */
2952a8db5ecSConor Dooley lockdep_assert_held(&text_mutex);
2962a8db5ecSConor Dooley riscv_patch_in_stop_machine = true;
2972a8db5ecSConor Dooley ret = stop_machine_cpuslocked(patch_text_cb, &patch, cpu_online_mask);
2982a8db5ecSConor Dooley riscv_patch_in_stop_machine = false;
2992a8db5ecSConor Dooley return ret;
300043cb41aSZong Li }
3015303df24SZong Li NOKPROBE_SYMBOL(patch_text);
302