2018-12-20 06:48:37 +03:00
// 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 ) = = 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 ,
2022-03-16 12:04:17 +03:00
& patch , cpu_online_mask ) ;
2018-12-20 06:48:37 +03:00
} 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 ) ;
}