| // 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); | |
| } |