2023-02-25 15:52:57 +08:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright ( C ) 2022 - 2023 Loongson Technology Corporation Limited
*/
# define pr_fmt(fmt) "hw-breakpoint: " fmt
# include <linux/hw_breakpoint.h>
# include <linux/kprobes.h>
# include <linux/perf_event.h>
# include <asm/hw_breakpoint.h>
/* Breakpoint currently in use for each BRP. */
static DEFINE_PER_CPU ( struct perf_event * , bp_on_reg [ LOONGARCH_MAX_BRP ] ) ;
/* Watchpoint currently in use for each WRP. */
static DEFINE_PER_CPU ( struct perf_event * , wp_on_reg [ LOONGARCH_MAX_WRP ] ) ;
int hw_breakpoint_slots ( int type )
{
/*
* We can be called early , so don ' t rely on
* our static variables being initialised .
*/
switch ( type ) {
case TYPE_INST :
return get_num_brps ( ) ;
case TYPE_DATA :
return get_num_wrps ( ) ;
default :
pr_warn ( " unknown slot type: %d \n " , type ) ;
return 0 ;
}
}
# define READ_WB_REG_CASE(OFF, N, REG, T, VAL) \
case ( OFF + N ) : \
LOONGARCH_CSR_WATCH_READ ( N , REG , T , VAL ) ; \
break
# define WRITE_WB_REG_CASE(OFF, N, REG, T, VAL) \
case ( OFF + N ) : \
LOONGARCH_CSR_WATCH_WRITE ( N , REG , T , VAL ) ; \
break
# define GEN_READ_WB_REG_CASES(OFF, REG, T, VAL) \
READ_WB_REG_CASE ( OFF , 0 , REG , T , VAL ) ; \
READ_WB_REG_CASE ( OFF , 1 , REG , T , VAL ) ; \
READ_WB_REG_CASE ( OFF , 2 , REG , T , VAL ) ; \
READ_WB_REG_CASE ( OFF , 3 , REG , T , VAL ) ; \
READ_WB_REG_CASE ( OFF , 4 , REG , T , VAL ) ; \
READ_WB_REG_CASE ( OFF , 5 , REG , T , VAL ) ; \
READ_WB_REG_CASE ( OFF , 6 , REG , T , VAL ) ; \
READ_WB_REG_CASE ( OFF , 7 , REG , T , VAL ) ;
# define GEN_WRITE_WB_REG_CASES(OFF, REG, T, VAL) \
WRITE_WB_REG_CASE ( OFF , 0 , REG , T , VAL ) ; \
WRITE_WB_REG_CASE ( OFF , 1 , REG , T , VAL ) ; \
WRITE_WB_REG_CASE ( OFF , 2 , REG , T , VAL ) ; \
WRITE_WB_REG_CASE ( OFF , 3 , REG , T , VAL ) ; \
WRITE_WB_REG_CASE ( OFF , 4 , REG , T , VAL ) ; \
WRITE_WB_REG_CASE ( OFF , 5 , REG , T , VAL ) ; \
WRITE_WB_REG_CASE ( OFF , 6 , REG , T , VAL ) ; \
WRITE_WB_REG_CASE ( OFF , 7 , REG , T , VAL ) ;
static u64 read_wb_reg ( int reg , int n , int t )
{
u64 val = 0 ;
switch ( reg + n ) {
GEN_READ_WB_REG_CASES ( CSR_CFG_ADDR , ADDR , t , val ) ;
GEN_READ_WB_REG_CASES ( CSR_CFG_MASK , MASK , t , val ) ;
GEN_READ_WB_REG_CASES ( CSR_CFG_CTRL , CTRL , t , val ) ;
GEN_READ_WB_REG_CASES ( CSR_CFG_ASID , ASID , t , val ) ;
default :
pr_warn ( " Attempt to read from unknown breakpoint register %d \n " , n ) ;
}
return val ;
}
NOKPROBE_SYMBOL ( read_wb_reg ) ;
static void write_wb_reg ( int reg , int n , int t , u64 val )
{
switch ( reg + n ) {
GEN_WRITE_WB_REG_CASES ( CSR_CFG_ADDR , ADDR , t , val ) ;
GEN_WRITE_WB_REG_CASES ( CSR_CFG_MASK , MASK , t , val ) ;
GEN_WRITE_WB_REG_CASES ( CSR_CFG_CTRL , CTRL , t , val ) ;
GEN_WRITE_WB_REG_CASES ( CSR_CFG_ASID , ASID , t , val ) ;
default :
pr_warn ( " Attempt to write to unknown breakpoint register %d \n " , n ) ;
}
}
NOKPROBE_SYMBOL ( write_wb_reg ) ;
enum hw_breakpoint_ops {
HW_BREAKPOINT_INSTALL ,
HW_BREAKPOINT_UNINSTALL ,
} ;
/*
* hw_breakpoint_slot_setup - Find and setup a perf slot according to operations
*
* @ slots : pointer to array of slots
* @ max_slots : max number of slots
* @ bp : perf_event to setup
* @ ops : operation to be carried out on the slot
*
* Return :
* slot index on success
* - ENOSPC if no slot is available / matches
* - EINVAL on wrong operations parameter
*/
static int hw_breakpoint_slot_setup ( struct perf_event * * slots , int max_slots ,
struct perf_event * bp , enum hw_breakpoint_ops ops )
{
int i ;
struct perf_event * * slot ;
for ( i = 0 ; i < max_slots ; + + i ) {
slot = & slots [ i ] ;
switch ( ops ) {
case HW_BREAKPOINT_INSTALL :
if ( ! * slot ) {
* slot = bp ;
return i ;
}
break ;
case HW_BREAKPOINT_UNINSTALL :
if ( * slot = = bp ) {
* slot = NULL ;
return i ;
}
break ;
default :
pr_warn_once ( " Unhandled hw breakpoint ops %d \n " , ops ) ;
return - EINVAL ;
}
}
return - ENOSPC ;
}
void ptrace_hw_copy_thread ( struct task_struct * tsk )
{
memset ( tsk - > thread . hbp_break , 0 , sizeof ( tsk - > thread . hbp_break ) ) ;
memset ( tsk - > thread . hbp_watch , 0 , sizeof ( tsk - > thread . hbp_watch ) ) ;
}
/*
* Unregister breakpoints from this task and reset the pointers in the thread_struct .
*/
void flush_ptrace_hw_breakpoint ( struct task_struct * tsk )
{
2023-02-25 15:52:57 +08:00
int i ;
struct thread_struct * t = & tsk - > thread ;
for ( i = 0 ; i < LOONGARCH_MAX_BRP ; i + + ) {
if ( t - > hbp_break [ i ] ) {
unregister_hw_breakpoint ( t - > hbp_break [ i ] ) ;
t - > hbp_break [ i ] = NULL ;
}
}
for ( i = 0 ; i < LOONGARCH_MAX_WRP ; i + + ) {
if ( t - > hbp_watch [ i ] ) {
unregister_hw_breakpoint ( t - > hbp_watch [ i ] ) ;
t - > hbp_watch [ i ] = NULL ;
}
}
2023-02-25 15:52:57 +08:00
}
static int hw_breakpoint_control ( struct perf_event * bp ,
enum hw_breakpoint_ops ops )
{
u32 ctrl ;
int i , max_slots , enable ;
struct perf_event * * slots ;
struct arch_hw_breakpoint * info = counter_arch_bp ( bp ) ;
if ( info - > ctrl . type = = LOONGARCH_BREAKPOINT_EXECUTE ) {
/* Breakpoint */
slots = this_cpu_ptr ( bp_on_reg ) ;
max_slots = boot_cpu_data . watch_ireg_count ;
} else {
/* Watchpoint */
slots = this_cpu_ptr ( wp_on_reg ) ;
max_slots = boot_cpu_data . watch_dreg_count ;
}
i = hw_breakpoint_slot_setup ( slots , max_slots , bp , ops ) ;
if ( WARN_ONCE ( i < 0 , " Can't find any breakpoint slot " ) )
return i ;
switch ( ops ) {
case HW_BREAKPOINT_INSTALL :
/* Set the FWPnCFG/MWPnCFG 1~4 register. */
write_wb_reg ( CSR_CFG_ADDR , i , 0 , info - > address ) ;
write_wb_reg ( CSR_CFG_ADDR , i , 1 , info - > address ) ;
write_wb_reg ( CSR_CFG_MASK , i , 0 , info - > mask ) ;
write_wb_reg ( CSR_CFG_MASK , i , 1 , info - > mask ) ;
write_wb_reg ( CSR_CFG_ASID , i , 0 , 0 ) ;
write_wb_reg ( CSR_CFG_ASID , i , 1 , 0 ) ;
if ( info - > ctrl . type = = LOONGARCH_BREAKPOINT_EXECUTE ) {
write_wb_reg ( CSR_CFG_CTRL , i , 0 , CTRL_PLV_ENABLE ) ;
} else {
ctrl = encode_ctrl_reg ( info - > ctrl ) ;
write_wb_reg ( CSR_CFG_CTRL , i , 1 , ctrl | CTRL_PLV_ENABLE |
1 < < MWPnCFG3_LoadEn | 1 < < MWPnCFG3_StoreEn ) ;
}
enable = csr_read64 ( LOONGARCH_CSR_CRMD ) ;
csr_write64 ( CSR_CRMD_WE | enable , LOONGARCH_CSR_CRMD ) ;
break ;
case HW_BREAKPOINT_UNINSTALL :
/* Reset the FWPnCFG/MWPnCFG 1~4 register. */
write_wb_reg ( CSR_CFG_ADDR , i , 0 , 0 ) ;
write_wb_reg ( CSR_CFG_ADDR , i , 1 , 0 ) ;
write_wb_reg ( CSR_CFG_MASK , i , 0 , 0 ) ;
write_wb_reg ( CSR_CFG_MASK , i , 1 , 0 ) ;
write_wb_reg ( CSR_CFG_CTRL , i , 0 , 0 ) ;
write_wb_reg ( CSR_CFG_CTRL , i , 1 , 0 ) ;
write_wb_reg ( CSR_CFG_ASID , i , 0 , 0 ) ;
write_wb_reg ( CSR_CFG_ASID , i , 1 , 0 ) ;
break ;
}
return 0 ;
}
/*
* Install a perf counter breakpoint .
*/
int arch_install_hw_breakpoint ( struct perf_event * bp )
{
return hw_breakpoint_control ( bp , HW_BREAKPOINT_INSTALL ) ;
}
void arch_uninstall_hw_breakpoint ( struct perf_event * bp )
{
hw_breakpoint_control ( bp , HW_BREAKPOINT_UNINSTALL ) ;
}
static int get_hbp_len ( u8 hbp_len )
{
unsigned int len_in_bytes = 0 ;
switch ( hbp_len ) {
case LOONGARCH_BREAKPOINT_LEN_1 :
len_in_bytes = 1 ;
break ;
case LOONGARCH_BREAKPOINT_LEN_2 :
len_in_bytes = 2 ;
break ;
case LOONGARCH_BREAKPOINT_LEN_4 :
len_in_bytes = 4 ;
break ;
case LOONGARCH_BREAKPOINT_LEN_8 :
len_in_bytes = 8 ;
break ;
}
return len_in_bytes ;
}
/*
* Check whether bp virtual address is in kernel space .
*/
int arch_check_bp_in_kernelspace ( struct arch_hw_breakpoint * hw )
{
unsigned int len ;
unsigned long va ;
va = hw - > address ;
len = get_hbp_len ( hw - > ctrl . len ) ;
return ( va > = TASK_SIZE ) & & ( ( va + len - 1 ) > = TASK_SIZE ) ;
}
/*
* Extract generic type and length encodings from an arch_hw_breakpoint_ctrl .
* Hopefully this will disappear when ptrace can bypass the conversion
* to generic breakpoint descriptions .
*/
int arch_bp_generic_fields ( struct arch_hw_breakpoint_ctrl ctrl ,
int * gen_len , int * gen_type , int * offset )
{
/* Type */
switch ( ctrl . type ) {
case LOONGARCH_BREAKPOINT_EXECUTE :
* gen_type = HW_BREAKPOINT_X ;
break ;
case LOONGARCH_BREAKPOINT_LOAD :
* gen_type = HW_BREAKPOINT_R ;
break ;
case LOONGARCH_BREAKPOINT_STORE :
* gen_type = HW_BREAKPOINT_W ;
break ;
case LOONGARCH_BREAKPOINT_LOAD | LOONGARCH_BREAKPOINT_STORE :
* gen_type = HW_BREAKPOINT_RW ;
break ;
default :
return - EINVAL ;
}
if ( ! ctrl . len )
return - EINVAL ;
* offset = __ffs ( ctrl . len ) ;
/* Len */
switch ( ctrl . len ) {
case LOONGARCH_BREAKPOINT_LEN_1 :
* gen_len = HW_BREAKPOINT_LEN_1 ;
break ;
case LOONGARCH_BREAKPOINT_LEN_2 :
* gen_len = HW_BREAKPOINT_LEN_2 ;
break ;
case LOONGARCH_BREAKPOINT_LEN_4 :
* gen_len = HW_BREAKPOINT_LEN_4 ;
break ;
case LOONGARCH_BREAKPOINT_LEN_8 :
* gen_len = HW_BREAKPOINT_LEN_8 ;
break ;
default :
return - EINVAL ;
}
return 0 ;
}
/*
* Construct an arch_hw_breakpoint from a perf_event .
*/
static int arch_build_bp_info ( struct perf_event * bp ,
const struct perf_event_attr * attr ,
struct arch_hw_breakpoint * hw )
{
/* Type */
switch ( attr - > bp_type ) {
case HW_BREAKPOINT_X :
hw - > ctrl . type = LOONGARCH_BREAKPOINT_EXECUTE ;
break ;
case HW_BREAKPOINT_R :
hw - > ctrl . type = LOONGARCH_BREAKPOINT_LOAD ;
break ;
case HW_BREAKPOINT_W :
hw - > ctrl . type = LOONGARCH_BREAKPOINT_STORE ;
break ;
case HW_BREAKPOINT_RW :
hw - > ctrl . type = LOONGARCH_BREAKPOINT_LOAD | LOONGARCH_BREAKPOINT_STORE ;
break ;
default :
return - EINVAL ;
}
/* Len */
switch ( attr - > bp_len ) {
case HW_BREAKPOINT_LEN_1 :
hw - > ctrl . len = LOONGARCH_BREAKPOINT_LEN_1 ;
break ;
case HW_BREAKPOINT_LEN_2 :
hw - > ctrl . len = LOONGARCH_BREAKPOINT_LEN_2 ;
break ;
case HW_BREAKPOINT_LEN_4 :
hw - > ctrl . len = LOONGARCH_BREAKPOINT_LEN_4 ;
break ;
case HW_BREAKPOINT_LEN_8 :
hw - > ctrl . len = LOONGARCH_BREAKPOINT_LEN_8 ;
break ;
default :
return - EINVAL ;
}
/* Address */
hw - > address = attr - > bp_addr ;
return 0 ;
}
/*
* Validate the arch - specific HW Breakpoint register settings .
*/
int hw_breakpoint_arch_parse ( struct perf_event * bp ,
const struct perf_event_attr * attr ,
struct arch_hw_breakpoint * hw )
{
int ret ;
u64 alignment_mask , offset ;
/* Build the arch_hw_breakpoint. */
ret = arch_build_bp_info ( bp , attr , hw ) ;
if ( ret )
return ret ;
if ( hw - > ctrl . type ! = LOONGARCH_BREAKPOINT_EXECUTE )
alignment_mask = 0x7 ;
offset = hw - > address & alignment_mask ;
hw - > address & = ~ alignment_mask ;
hw - > ctrl . len < < = offset ;
return 0 ;
}
static void update_bp_registers ( struct pt_regs * regs , int enable , int type )
{
u32 ctrl ;
int i , max_slots ;
struct perf_event * * slots ;
struct arch_hw_breakpoint * info ;
switch ( type ) {
case 0 :
slots = this_cpu_ptr ( bp_on_reg ) ;
max_slots = boot_cpu_data . watch_ireg_count ;
break ;
case 1 :
slots = this_cpu_ptr ( wp_on_reg ) ;
max_slots = boot_cpu_data . watch_dreg_count ;
break ;
default :
return ;
}
for ( i = 0 ; i < max_slots ; + + i ) {
if ( ! slots [ i ] )
continue ;
info = counter_arch_bp ( slots [ i ] ) ;
if ( enable ) {
if ( ( info - > ctrl . type = = LOONGARCH_BREAKPOINT_EXECUTE ) & & ( type = = 0 ) ) {
write_wb_reg ( CSR_CFG_CTRL , i , 0 , CTRL_PLV_ENABLE ) ;
write_wb_reg ( CSR_CFG_CTRL , i , 0 , CTRL_PLV_ENABLE ) ;
} else {
ctrl = read_wb_reg ( CSR_CFG_CTRL , i , 1 ) ;
if ( info - > ctrl . type = = LOONGARCH_BREAKPOINT_LOAD )
ctrl | = 0x1 < < MWPnCFG3_LoadEn ;
if ( info - > ctrl . type = = LOONGARCH_BREAKPOINT_STORE )
ctrl | = 0x1 < < MWPnCFG3_StoreEn ;
write_wb_reg ( CSR_CFG_CTRL , i , 1 , ctrl ) ;
}
regs - > csr_prmd | = CSR_PRMD_PWE ;
} else {
if ( ( info - > ctrl . type = = LOONGARCH_BREAKPOINT_EXECUTE ) & & ( type = = 0 ) ) {
write_wb_reg ( CSR_CFG_CTRL , i , 0 , 0 ) ;
} else {
ctrl = read_wb_reg ( CSR_CFG_CTRL , i , 1 ) ;
if ( info - > ctrl . type = = LOONGARCH_BREAKPOINT_LOAD )
ctrl & = ~ 0x1 < < MWPnCFG3_LoadEn ;
if ( info - > ctrl . type = = LOONGARCH_BREAKPOINT_STORE )
ctrl & = ~ 0x1 < < MWPnCFG3_StoreEn ;
write_wb_reg ( CSR_CFG_CTRL , i , 1 , ctrl ) ;
}
regs - > csr_prmd & = ~ CSR_PRMD_PWE ;
}
}
}
NOKPROBE_SYMBOL ( update_bp_registers ) ;
/*
* Debug exception handlers .
*/
void breakpoint_handler ( struct pt_regs * regs )
{
int i ;
struct perf_event * bp , * * slots ;
slots = this_cpu_ptr ( bp_on_reg ) ;
for ( i = 0 ; i < boot_cpu_data . watch_ireg_count ; + + i ) {
bp = slots [ i ] ;
if ( bp = = NULL )
continue ;
perf_bp_event ( bp , regs ) ;
}
update_bp_registers ( regs , 0 , 0 ) ;
}
NOKPROBE_SYMBOL ( breakpoint_handler ) ;
void watchpoint_handler ( struct pt_regs * regs )
{
int i ;
struct perf_event * wp , * * slots ;
slots = this_cpu_ptr ( wp_on_reg ) ;
for ( i = 0 ; i < boot_cpu_data . watch_dreg_count ; + + i ) {
wp = slots [ i ] ;
if ( wp = = NULL )
continue ;
perf_bp_event ( wp , regs ) ;
}
update_bp_registers ( regs , 0 , 1 ) ;
}
NOKPROBE_SYMBOL ( watchpoint_handler ) ;
static int __init arch_hw_breakpoint_init ( void )
{
int cpu ;
boot_cpu_data . watch_ireg_count = get_num_brps ( ) ;
boot_cpu_data . watch_dreg_count = get_num_wrps ( ) ;
pr_info ( " Found %d breakpoint and %d watchpoint registers. \n " ,
boot_cpu_data . watch_ireg_count , boot_cpu_data . watch_dreg_count ) ;
for ( cpu = 1 ; cpu < NR_CPUS ; cpu + + ) {
cpu_data [ cpu ] . watch_ireg_count = boot_cpu_data . watch_ireg_count ;
cpu_data [ cpu ] . watch_dreg_count = boot_cpu_data . watch_dreg_count ;
}
return 0 ;
}
arch_initcall ( arch_hw_breakpoint_init ) ;
void hw_breakpoint_thread_switch ( struct task_struct * next )
{
2023-02-25 15:52:57 +08:00
u64 addr , mask ;
2023-02-25 15:52:57 +08:00
struct pt_regs * regs = task_pt_regs ( next ) ;
2023-02-25 15:52:57 +08:00
if ( test_tsk_thread_flag ( next , TIF_SINGLESTEP ) ) {
addr = read_wb_reg ( CSR_CFG_ADDR , 0 , 0 ) ;
mask = read_wb_reg ( CSR_CFG_MASK , 0 , 0 ) ;
if ( ! ( ( regs - > csr_era ^ addr ) & ~ mask ) )
csr_write32 ( CSR_FWPC_SKIP , LOONGARCH_CSR_FWPS ) ;
regs - > csr_prmd | = CSR_PRMD_PWE ;
} else {
/* Update breakpoints */
update_bp_registers ( regs , 1 , 0 ) ;
/* Update watchpoints */
update_bp_registers ( regs , 1 , 1 ) ;
}
2023-02-25 15:52:57 +08:00
}
void hw_breakpoint_pmu_read ( struct perf_event * bp )
{
}
/*
* Dummy function to register with die_notifier .
*/
int hw_breakpoint_exceptions_notify ( struct notifier_block * unused ,
unsigned long val , void * data )
{
return NOTIFY_DONE ;
}