mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-19 10:44:14 +08:00
xtensa: implement jump_label support
Use 3-byte 'nop' and 'j' instructions that are always present. Don't let assembler mark a spot right after patchable 'j' instruction as unreachable and later put literals or padding bytes there. Add separate implementations of patch_text for SMP and UP cases, avoiding use of atomics on UP. Signed-off-by: Max Filippov <jcmvbkbc@gmail.com>
This commit is contained in:
parent
af5395c214
commit
64711f9a47
@ -29,5 +29,5 @@
|
|||||||
| um: | TODO |
|
| um: | TODO |
|
||||||
| unicore32: | TODO |
|
| unicore32: | TODO |
|
||||||
| x86: | ok |
|
| x86: | ok |
|
||||||
| xtensa: | TODO |
|
| xtensa: | ok |
|
||||||
-----------------------
|
-----------------------
|
||||||
|
@ -17,6 +17,7 @@ config XTENSA
|
|||||||
select GENERIC_PCI_IOMAP
|
select GENERIC_PCI_IOMAP
|
||||||
select GENERIC_SCHED_CLOCK
|
select GENERIC_SCHED_CLOCK
|
||||||
select GENERIC_STRNCPY_FROM_USER if KASAN
|
select GENERIC_STRNCPY_FROM_USER if KASAN
|
||||||
|
select HAVE_ARCH_JUMP_LABEL
|
||||||
select HAVE_ARCH_KASAN if MMU
|
select HAVE_ARCH_KASAN if MMU
|
||||||
select HAVE_ARCH_TRACEHOOK
|
select HAVE_ARCH_TRACEHOOK
|
||||||
select HAVE_DEBUG_KMEMLEAK
|
select HAVE_DEBUG_KMEMLEAK
|
||||||
|
65
arch/xtensa/include/asm/jump_label.h
Normal file
65
arch/xtensa/include/asm/jump_label.h
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/* Copyright (C) 2018 Cadence Design Systems Inc. */
|
||||||
|
|
||||||
|
#ifndef _ASM_XTENSA_JUMP_LABEL_H
|
||||||
|
#define _ASM_XTENSA_JUMP_LABEL_H
|
||||||
|
|
||||||
|
#ifndef __ASSEMBLY__
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
#define JUMP_LABEL_NOP_SIZE 3
|
||||||
|
|
||||||
|
static __always_inline bool arch_static_branch(struct static_key *key,
|
||||||
|
bool branch)
|
||||||
|
{
|
||||||
|
asm_volatile_goto("1:\n\t"
|
||||||
|
"_nop\n\t"
|
||||||
|
".pushsection __jump_table, \"aw\"\n\t"
|
||||||
|
".word 1b, %l[l_yes], %c0\n\t"
|
||||||
|
".popsection\n\t"
|
||||||
|
: : "i" (&((char *)key)[branch]) : : l_yes);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
l_yes:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __always_inline bool arch_static_branch_jump(struct static_key *key,
|
||||||
|
bool branch)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Xtensa assembler will mark certain points in the code
|
||||||
|
* as unreachable, so that later assembler or linker relaxation
|
||||||
|
* passes could use them. A spot right after the J instruction
|
||||||
|
* is one such point. Assembler and/or linker may insert padding
|
||||||
|
* or literals here, breaking code flow in case the J instruction
|
||||||
|
* is later replaced with NOP. Put a label right after the J to
|
||||||
|
* make it reachable and wrap both into a no-transform block
|
||||||
|
* to avoid any assembler interference with this.
|
||||||
|
*/
|
||||||
|
asm_volatile_goto("1:\n\t"
|
||||||
|
".begin no-transform\n\t"
|
||||||
|
"_j %l[l_yes]\n\t"
|
||||||
|
"2:\n\t"
|
||||||
|
".end no-transform\n\t"
|
||||||
|
".pushsection __jump_table, \"aw\"\n\t"
|
||||||
|
".word 1b, %l[l_yes], %c0\n\t"
|
||||||
|
".popsection\n\t"
|
||||||
|
: : "i" (&((char *)key)[branch]) : : l_yes);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
l_yes:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef u32 jump_label_t;
|
||||||
|
|
||||||
|
struct jump_entry {
|
||||||
|
jump_label_t code;
|
||||||
|
jump_label_t target;
|
||||||
|
jump_label_t key;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __ASSEMBLY__ */
|
||||||
|
#endif
|
@ -16,6 +16,7 @@ obj-$(CONFIG_SMP) += smp.o mxhead.o
|
|||||||
obj-$(CONFIG_XTENSA_VARIANT_HAVE_PERF_EVENTS) += perf_event.o
|
obj-$(CONFIG_XTENSA_VARIANT_HAVE_PERF_EVENTS) += perf_event.o
|
||||||
obj-$(CONFIG_HAVE_HW_BREAKPOINT) += hw_breakpoint.o
|
obj-$(CONFIG_HAVE_HW_BREAKPOINT) += hw_breakpoint.o
|
||||||
obj-$(CONFIG_S32C1I_SELFTEST) += s32c1i_selftest.o
|
obj-$(CONFIG_S32C1I_SELFTEST) += s32c1i_selftest.o
|
||||||
|
obj-$(CONFIG_JUMP_LABEL) += jump_label.o
|
||||||
|
|
||||||
# In the Xtensa architecture, assembly generates literals which must always
|
# In the Xtensa architecture, assembly generates literals which must always
|
||||||
# precede the L32R instruction with a relative offset less than 256 kB.
|
# precede the L32R instruction with a relative offset less than 256 kB.
|
||||||
|
99
arch/xtensa/kernel/jump_label.c
Normal file
99
arch/xtensa/kernel/jump_label.c
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// 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>
|
||||||
|
|
||||||
|
#ifdef HAVE_JUMP_LABEL
|
||||||
|
|
||||||
|
#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) == 1) {
|
||||||
|
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, NULL);
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* HAVE_JUMP_LABEL */
|
Loading…
Reference in New Issue
Block a user