-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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>
- Loading branch information
Max Filippov
committed
Dec 20, 2018
1 parent
af5395c
commit 64711f9
Showing
5 changed files
with
167 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,5 +29,5 @@ | |
| um: | TODO | | ||
| unicore32: | TODO | | ||
| x86: | ok | | ||
| xtensa: | TODO | | ||
| xtensa: | ok | | ||
----------------------- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 */ |