root/arch/xtensa/kernel/jump_label.c
// 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);
}