2022-05-31 18:04:11 +08:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright ( C ) 2020 - 2022 Loongson Technology Corporation Limited
*/
2022-12-10 22:40:15 +08:00
# include <linux/sizes.h>
# include <linux/uaccess.h>
# include <asm/cacheflush.h>
2022-05-31 18:04:11 +08:00
# include <asm/inst.h>
2022-12-10 22:40:15 +08:00
static DEFINE_RAW_SPINLOCK ( patch_lock ) ;
2023-02-25 15:52:57 +08:00
void simu_pc ( struct pt_regs * regs , union loongarch_instruction insn )
{
unsigned long pc = regs - > csr_era ;
unsigned int rd = insn . reg1i20_format . rd ;
unsigned int imm = insn . reg1i20_format . immediate ;
if ( pc & 3 ) {
pr_warn ( " %s: invalid pc 0x%lx \n " , __func__ , pc ) ;
return ;
}
switch ( insn . reg1i20_format . opcode ) {
case pcaddi_op :
regs - > regs [ rd ] = pc + sign_extend64 ( imm < < 2 , 21 ) ;
break ;
case pcaddu12i_op :
regs - > regs [ rd ] = pc + sign_extend64 ( imm < < 12 , 31 ) ;
break ;
case pcaddu18i_op :
regs - > regs [ rd ] = pc + sign_extend64 ( imm < < 18 , 37 ) ;
break ;
case pcalau12i_op :
regs - > regs [ rd ] = pc + sign_extend64 ( imm < < 12 , 31 ) ;
regs - > regs [ rd ] & = ~ ( ( 1 < < 12 ) - 1 ) ;
break ;
default :
pr_info ( " %s: unknown opcode \n " , __func__ ) ;
return ;
}
regs - > csr_era + = LOONGARCH_INSN_SIZE ;
}
void simu_branch ( struct pt_regs * regs , union loongarch_instruction insn )
{
unsigned int imm , imm_l , imm_h , rd , rj ;
unsigned long pc = regs - > csr_era ;
if ( pc & 3 ) {
pr_warn ( " %s: invalid pc 0x%lx \n " , __func__ , pc ) ;
return ;
}
imm_l = insn . reg0i26_format . immediate_l ;
imm_h = insn . reg0i26_format . immediate_h ;
switch ( insn . reg0i26_format . opcode ) {
case b_op :
regs - > csr_era = pc + sign_extend64 ( ( imm_h < < 16 | imm_l ) < < 2 , 27 ) ;
return ;
case bl_op :
regs - > csr_era = pc + sign_extend64 ( ( imm_h < < 16 | imm_l ) < < 2 , 27 ) ;
regs - > regs [ 1 ] = pc + LOONGARCH_INSN_SIZE ;
return ;
}
imm_l = insn . reg1i21_format . immediate_l ;
imm_h = insn . reg1i21_format . immediate_h ;
rj = insn . reg1i21_format . rj ;
switch ( insn . reg1i21_format . opcode ) {
case beqz_op :
if ( regs - > regs [ rj ] = = 0 )
regs - > csr_era = pc + sign_extend64 ( ( imm_h < < 16 | imm_l ) < < 2 , 22 ) ;
else
regs - > csr_era = pc + LOONGARCH_INSN_SIZE ;
return ;
case bnez_op :
if ( regs - > regs [ rj ] ! = 0 )
regs - > csr_era = pc + sign_extend64 ( ( imm_h < < 16 | imm_l ) < < 2 , 22 ) ;
else
regs - > csr_era = pc + LOONGARCH_INSN_SIZE ;
return ;
}
imm = insn . reg2i16_format . immediate ;
rj = insn . reg2i16_format . rj ;
rd = insn . reg2i16_format . rd ;
switch ( insn . reg2i16_format . opcode ) {
case beq_op :
if ( regs - > regs [ rj ] = = regs - > regs [ rd ] )
regs - > csr_era = pc + sign_extend64 ( imm < < 2 , 17 ) ;
else
regs - > csr_era = pc + LOONGARCH_INSN_SIZE ;
break ;
case bne_op :
if ( regs - > regs [ rj ] ! = regs - > regs [ rd ] )
regs - > csr_era = pc + sign_extend64 ( imm < < 2 , 17 ) ;
else
regs - > csr_era = pc + LOONGARCH_INSN_SIZE ;
break ;
case blt_op :
if ( ( long ) regs - > regs [ rj ] < ( long ) regs - > regs [ rd ] )
regs - > csr_era = pc + sign_extend64 ( imm < < 2 , 17 ) ;
else
regs - > csr_era = pc + LOONGARCH_INSN_SIZE ;
break ;
case bge_op :
if ( ( long ) regs - > regs [ rj ] > = ( long ) regs - > regs [ rd ] )
regs - > csr_era = pc + sign_extend64 ( imm < < 2 , 17 ) ;
else
regs - > csr_era = pc + LOONGARCH_INSN_SIZE ;
break ;
case bltu_op :
if ( regs - > regs [ rj ] < regs - > regs [ rd ] )
regs - > csr_era = pc + sign_extend64 ( imm < < 2 , 17 ) ;
else
regs - > csr_era = pc + LOONGARCH_INSN_SIZE ;
break ;
case bgeu_op :
if ( regs - > regs [ rj ] > = regs - > regs [ rd ] )
regs - > csr_era = pc + sign_extend64 ( imm < < 2 , 17 ) ;
else
regs - > csr_era = pc + LOONGARCH_INSN_SIZE ;
break ;
case jirl_op :
regs - > csr_era = regs - > regs [ rj ] + sign_extend64 ( imm < < 2 , 17 ) ;
regs - > regs [ rd ] = pc + LOONGARCH_INSN_SIZE ;
break ;
default :
pr_info ( " %s: unknown opcode \n " , __func__ ) ;
return ;
}
}
2022-12-10 22:40:15 +08:00
int larch_insn_read ( void * addr , u32 * insnp )
{
int ret ;
u32 val ;
ret = copy_from_kernel_nofault ( & val , addr , LOONGARCH_INSN_SIZE ) ;
if ( ! ret )
* insnp = val ;
return ret ;
}
int larch_insn_write ( void * addr , u32 insn )
{
int ret ;
unsigned long flags = 0 ;
raw_spin_lock_irqsave ( & patch_lock , flags ) ;
ret = copy_to_kernel_nofault ( addr , & insn , LOONGARCH_INSN_SIZE ) ;
raw_spin_unlock_irqrestore ( & patch_lock , flags ) ;
return ret ;
}
int larch_insn_patch_text ( void * addr , u32 insn )
{
int ret ;
u32 * tp = addr ;
if ( ( unsigned long ) tp & 3 )
return - EINVAL ;
ret = larch_insn_write ( tp , insn ) ;
if ( ! ret )
flush_icache_range ( ( unsigned long ) tp ,
( unsigned long ) tp + LOONGARCH_INSN_SIZE ) ;
return ret ;
}
u32 larch_insn_gen_nop ( void )
{
return INSN_NOP ;
}
2022-12-10 22:40:15 +08:00
u32 larch_insn_gen_b ( unsigned long pc , unsigned long dest )
{
long offset = dest - pc ;
union loongarch_instruction insn ;
if ( ( offset & 3 ) | | offset < - SZ_128M | | offset > = SZ_128M ) {
pr_warn ( " The generated b instruction is out of range. \n " ) ;
return INSN_BREAK ;
}
2023-01-17 11:42:16 +08:00
emit_b ( & insn , offset > > 2 ) ;
2022-12-10 22:40:15 +08:00
return insn . word ;
}
2022-12-10 22:40:15 +08:00
u32 larch_insn_gen_bl ( unsigned long pc , unsigned long dest )
{
long offset = dest - pc ;
union loongarch_instruction insn ;
if ( ( offset & 3 ) | | offset < - SZ_128M | | offset > = SZ_128M ) {
pr_warn ( " The generated bl instruction is out of range. \n " ) ;
return INSN_BREAK ;
}
2023-01-17 11:42:16 +08:00
emit_bl ( & insn , offset > > 2 ) ;
2022-12-10 22:40:15 +08:00
return insn . word ;
}
u32 larch_insn_gen_or ( enum loongarch_gpr rd , enum loongarch_gpr rj , enum loongarch_gpr rk )
{
union loongarch_instruction insn ;
2023-01-17 11:42:16 +08:00
emit_or ( & insn , rd , rj , rk ) ;
2022-12-10 22:40:15 +08:00
return insn . word ;
}
u32 larch_insn_gen_move ( enum loongarch_gpr rd , enum loongarch_gpr rj )
{
return larch_insn_gen_or ( rd , rj , 0 ) ;
}
2022-12-10 22:40:21 +08:00
u32 larch_insn_gen_lu12iw ( enum loongarch_gpr rd , int imm )
{
union loongarch_instruction insn ;
2023-01-17 11:42:16 +08:00
emit_lu12iw ( & insn , rd , imm ) ;
2022-12-10 22:40:21 +08:00
return insn . word ;
}
2022-05-31 18:04:11 +08:00
u32 larch_insn_gen_lu32id ( enum loongarch_gpr rd , int imm )
{
union loongarch_instruction insn ;
2023-01-17 11:42:16 +08:00
emit_lu32id ( & insn , rd , imm ) ;
2022-05-31 18:04:11 +08:00
return insn . word ;
}
u32 larch_insn_gen_lu52id ( enum loongarch_gpr rd , enum loongarch_gpr rj , int imm )
{
union loongarch_instruction insn ;
2023-01-17 11:42:16 +08:00
emit_lu52id ( & insn , rd , rj , imm ) ;
2022-05-31 18:04:11 +08:00
return insn . word ;
}
u32 larch_insn_gen_jirl ( enum loongarch_gpr rd , enum loongarch_gpr rj , unsigned long pc , unsigned long dest )
{
union loongarch_instruction insn ;
2023-01-17 11:42:16 +08:00
emit_jirl ( & insn , rj , rd , ( dest - pc ) > > 2 ) ;
2022-05-31 18:04:11 +08:00
return insn . word ;
}