xref: /linux-6.15/arch/riscv/kernel/patch.c (revision 0c3beacf)
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