diff options
Diffstat (limited to 'arch/xtensa/kernel/jump_label.c')
-rw-r--r-- | arch/xtensa/kernel/jump_label.c | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/arch/xtensa/kernel/jump_label.c b/arch/xtensa/kernel/jump_label.c new file mode 100644 index 000000000..ad1841cec --- /dev/null +++ b/arch/xtensa/kernel/jump_label.c @@ -0,0 +1,95 @@ +// 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); +} |