// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2018 Cadence Design Systems Inc. #include <linux/cpu.h> #include <linux/jump_label.h> #include <linux/kernel.h> #include <linux/memory.h> #include <linux/stop_machine.h> #include <linux/types.h> #include <asm/cacheflush.h> #define J_OFFSET_MASK 0x0003ffff #define J_SIGN_MASK (~(J_OFFSET_MASK >> 1)) #if defined(__XTENSA_EL__) #define J_INSN 0x6 #define NOP_INSN 0x0020f0 #elif defined(__XTENSA_EB__) #define J_INSN 0x60000000 #define NOP_INSN 0x0f020000 #else #error Unsupported endianness. #endif struct patch { atomic_t cpu_count; unsigned long addr; size_t sz; const void *data; }; static void local_patch_text(unsigned long addr, const void *data, size_t sz) { memcpy((void *)addr, data, sz); local_flush_icache_range(addr, addr + sz); } static int patch_text_stop_machine(void *data) { struct patch *patch = data; if (atomic_inc_return(&patch->cpu_count) == num_online_cpus()) { local_patch_text(patch->addr, patch->data, patch->sz); atomic_inc(&patch->cpu_count); } else { while (atomic_read(&patch->cpu_count) <= num_online_cpus()) cpu_relax(); __invalidate_icache_range(patch->addr, patch->sz); } return 0; } static void patch_text(unsigned long addr, const void *data, size_t sz) { if (IS_ENABLED(CONFIG_SMP)) { struct patch patch = { .cpu_count = ATOMIC_INIT(0), .addr = addr, .sz = sz, .data = data, }; stop_machine_cpuslocked(patch_text_stop_machine, &patch, cpu_online_mask); } else { unsigned long flags; local_irq_save(flags); local_patch_text(addr, data, sz); local_irq_restore(flags); } } void arch_jump_label_transform(struct jump_entry *e, enum jump_label_type type) { u32 d = (jump_entry_target(e) - (jump_entry_code(e) + 4)); u32 insn; /* Jump only works within 128K of the J instruction. */ BUG_ON(!((d & J_SIGN_MASK) == 0 || (d & J_SIGN_MASK) == J_SIGN_MASK)); if (type == JUMP_LABEL_JMP) { #if defined(__XTENSA_EL__) insn = ((d & J_OFFSET_MASK) << 6) | J_INSN; #elif defined(__XTENSA_EB__) insn = ((d & J_OFFSET_MASK) << 8) | J_INSN; #endif } else { insn = NOP_INSN; } patch_text(jump_entry_code(e), &insn, JUMP_LABEL_NOP_SIZE); }