xref: /linux/arch/xtensa/kernel/jump_label.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
164711f9aSMax Filippov // SPDX-License-Identifier: GPL-2.0
264711f9aSMax Filippov // Copyright (C) 2018 Cadence Design Systems Inc.
364711f9aSMax Filippov 
464711f9aSMax Filippov #include <linux/cpu.h>
564711f9aSMax Filippov #include <linux/jump_label.h>
664711f9aSMax Filippov #include <linux/kernel.h>
764711f9aSMax Filippov #include <linux/memory.h>
864711f9aSMax Filippov #include <linux/stop_machine.h>
964711f9aSMax Filippov #include <linux/types.h>
1064711f9aSMax Filippov 
1164711f9aSMax Filippov #include <asm/cacheflush.h>
1264711f9aSMax Filippov 
1364711f9aSMax Filippov #define J_OFFSET_MASK 0x0003ffff
1464711f9aSMax Filippov #define J_SIGN_MASK (~(J_OFFSET_MASK >> 1))
1564711f9aSMax Filippov 
1664711f9aSMax Filippov #if defined(__XTENSA_EL__)
1764711f9aSMax Filippov #define J_INSN 0x6
1864711f9aSMax Filippov #define NOP_INSN 0x0020f0
1964711f9aSMax Filippov #elif defined(__XTENSA_EB__)
2064711f9aSMax Filippov #define J_INSN 0x60000000
2164711f9aSMax Filippov #define NOP_INSN 0x0f020000
2264711f9aSMax Filippov #else
2364711f9aSMax Filippov #error Unsupported endianness.
2464711f9aSMax Filippov #endif
2564711f9aSMax Filippov 
2664711f9aSMax Filippov struct patch {
2764711f9aSMax Filippov 	atomic_t cpu_count;
2864711f9aSMax Filippov 	unsigned long addr;
2964711f9aSMax Filippov 	size_t sz;
3064711f9aSMax Filippov 	const void *data;
3164711f9aSMax Filippov };
3264711f9aSMax Filippov 
local_patch_text(unsigned long addr,const void * data,size_t sz)3364711f9aSMax Filippov static void local_patch_text(unsigned long addr, const void *data, size_t sz)
3464711f9aSMax Filippov {
3564711f9aSMax Filippov 	memcpy((void *)addr, data, sz);
3664711f9aSMax Filippov 	local_flush_icache_range(addr, addr + sz);
3764711f9aSMax Filippov }
3864711f9aSMax Filippov 
patch_text_stop_machine(void * data)3964711f9aSMax Filippov static int patch_text_stop_machine(void *data)
4064711f9aSMax Filippov {
4164711f9aSMax Filippov 	struct patch *patch = data;
4264711f9aSMax Filippov 
43*ee69d4beSGuo Ren 	if (atomic_inc_return(&patch->cpu_count) == num_online_cpus()) {
4464711f9aSMax Filippov 		local_patch_text(patch->addr, patch->data, patch->sz);
4564711f9aSMax Filippov 		atomic_inc(&patch->cpu_count);
4664711f9aSMax Filippov 	} else {
4764711f9aSMax Filippov 		while (atomic_read(&patch->cpu_count) <= num_online_cpus())
4864711f9aSMax Filippov 			cpu_relax();
4964711f9aSMax Filippov 		__invalidate_icache_range(patch->addr, patch->sz);
5064711f9aSMax Filippov 	}
5164711f9aSMax Filippov 	return 0;
5264711f9aSMax Filippov }
5364711f9aSMax Filippov 
patch_text(unsigned long addr,const void * data,size_t sz)5464711f9aSMax Filippov static void patch_text(unsigned long addr, const void *data, size_t sz)
5564711f9aSMax Filippov {
5664711f9aSMax Filippov 	if (IS_ENABLED(CONFIG_SMP)) {
5764711f9aSMax Filippov 		struct patch patch = {
5864711f9aSMax Filippov 			.cpu_count = ATOMIC_INIT(0),
5964711f9aSMax Filippov 			.addr = addr,
6064711f9aSMax Filippov 			.sz = sz,
6164711f9aSMax Filippov 			.data = data,
6264711f9aSMax Filippov 		};
6364711f9aSMax Filippov 		stop_machine_cpuslocked(patch_text_stop_machine,
64f406f2d0SMax Filippov 					&patch, cpu_online_mask);
6564711f9aSMax Filippov 	} else {
6664711f9aSMax Filippov 		unsigned long flags;
6764711f9aSMax Filippov 
6864711f9aSMax Filippov 		local_irq_save(flags);
6964711f9aSMax Filippov 		local_patch_text(addr, data, sz);
7064711f9aSMax Filippov 		local_irq_restore(flags);
7164711f9aSMax Filippov 	}
7264711f9aSMax Filippov }
7364711f9aSMax Filippov 
arch_jump_label_transform(struct jump_entry * e,enum jump_label_type type)7464711f9aSMax Filippov void arch_jump_label_transform(struct jump_entry *e,
7564711f9aSMax Filippov 			       enum jump_label_type type)
7664711f9aSMax Filippov {
7764711f9aSMax Filippov 	u32 d = (jump_entry_target(e) - (jump_entry_code(e) + 4));
7864711f9aSMax Filippov 	u32 insn;
7964711f9aSMax Filippov 
8064711f9aSMax Filippov 	/* Jump only works within 128K of the J instruction. */
8164711f9aSMax Filippov 	BUG_ON(!((d & J_SIGN_MASK) == 0 ||
8264711f9aSMax Filippov 		 (d & J_SIGN_MASK) == J_SIGN_MASK));
8364711f9aSMax Filippov 
8464711f9aSMax Filippov 	if (type == JUMP_LABEL_JMP) {
8564711f9aSMax Filippov #if defined(__XTENSA_EL__)
8664711f9aSMax Filippov 		insn = ((d & J_OFFSET_MASK) << 6) | J_INSN;
8764711f9aSMax Filippov #elif defined(__XTENSA_EB__)
8864711f9aSMax Filippov 		insn = ((d & J_OFFSET_MASK) << 8) | J_INSN;
8964711f9aSMax Filippov #endif
9064711f9aSMax Filippov 	} else {
9164711f9aSMax Filippov 		insn = NOP_INSN;
9264711f9aSMax Filippov 	}
9364711f9aSMax Filippov 
9464711f9aSMax Filippov 	patch_text(jump_entry_code(e), &insn, JUMP_LABEL_NOP_SIZE);
9564711f9aSMax Filippov }
96