riscv: Add KGDB support
The skeleton of RISC-V KGDB port. Signed-off-by: Vincent Chen <vincent.chen@sifive.com> Reviewed-by: Palmer Dabbelt <palmerdabbelt@google.com> Signed-off-by: Palmer Dabbelt <palmerdabbelt@google.com>
This commit is contained in:
parent
f83b04d36e
commit
fe89bd2be8
|
@ -68,6 +68,7 @@ config RISCV
|
||||||
select ARCH_HAS_GCOV_PROFILE_ALL
|
select ARCH_HAS_GCOV_PROFILE_ALL
|
||||||
select HAVE_COPY_THREAD_TLS
|
select HAVE_COPY_THREAD_TLS
|
||||||
select HAVE_ARCH_KASAN if MMU && 64BIT
|
select HAVE_ARCH_KASAN if MMU && 64BIT
|
||||||
|
select HAVE_ARCH_KGDB
|
||||||
|
|
||||||
config ARCH_MMAP_RND_BITS_MIN
|
config ARCH_MMAP_RND_BITS_MIN
|
||||||
default 18 if 64BIT
|
default 18 if 64BIT
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||||
|
|
||||||
|
#ifndef _ASM_ARC_KDEBUG_H
|
||||||
|
#define _ASM_ARC_KDEBUG_H
|
||||||
|
|
||||||
|
enum die_val {
|
||||||
|
DIE_UNUSED,
|
||||||
|
DIE_TRAP,
|
||||||
|
DIE_OOPS
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,106 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||||
|
|
||||||
|
#ifndef __ASM_KGDB_H_
|
||||||
|
#define __ASM_KGDB_H_
|
||||||
|
|
||||||
|
#ifdef __KERNEL__
|
||||||
|
|
||||||
|
#define GDB_SIZEOF_REG sizeof(unsigned long)
|
||||||
|
|
||||||
|
#define DBG_MAX_REG_NUM (33)
|
||||||
|
#define NUMREGBYTES ((DBG_MAX_REG_NUM) * GDB_SIZEOF_REG)
|
||||||
|
#define CACHE_FLUSH_IS_SAFE 1
|
||||||
|
#define BUFMAX 2048
|
||||||
|
#ifdef CONFIG_RISCV_ISA_C
|
||||||
|
#define BREAK_INSTR_SIZE 2
|
||||||
|
#else
|
||||||
|
#define BREAK_INSTR_SIZE 4
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef __ASSEMBLY__
|
||||||
|
|
||||||
|
extern int kgdb_has_hit_break(unsigned long addr);
|
||||||
|
extern unsigned long kgdb_compiled_break;
|
||||||
|
|
||||||
|
static inline void arch_kgdb_breakpoint(void)
|
||||||
|
{
|
||||||
|
asm(".global kgdb_compiled_break\n"
|
||||||
|
".option norvc\n"
|
||||||
|
"kgdb_compiled_break: ebreak\n"
|
||||||
|
".option rvc\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* !__ASSEMBLY__ */
|
||||||
|
|
||||||
|
#define DBG_REG_ZERO "zero"
|
||||||
|
#define DBG_REG_RA "ra"
|
||||||
|
#define DBG_REG_SP "sp"
|
||||||
|
#define DBG_REG_GP "gp"
|
||||||
|
#define DBG_REG_TP "tp"
|
||||||
|
#define DBG_REG_T0 "t0"
|
||||||
|
#define DBG_REG_T1 "t1"
|
||||||
|
#define DBG_REG_T2 "t2"
|
||||||
|
#define DBG_REG_FP "fp"
|
||||||
|
#define DBG_REG_S1 "s1"
|
||||||
|
#define DBG_REG_A0 "a0"
|
||||||
|
#define DBG_REG_A1 "a1"
|
||||||
|
#define DBG_REG_A2 "a2"
|
||||||
|
#define DBG_REG_A3 "a3"
|
||||||
|
#define DBG_REG_A4 "a4"
|
||||||
|
#define DBG_REG_A5 "a5"
|
||||||
|
#define DBG_REG_A6 "a6"
|
||||||
|
#define DBG_REG_A7 "a7"
|
||||||
|
#define DBG_REG_S2 "s2"
|
||||||
|
#define DBG_REG_S3 "s3"
|
||||||
|
#define DBG_REG_S4 "s4"
|
||||||
|
#define DBG_REG_S5 "s5"
|
||||||
|
#define DBG_REG_S6 "s6"
|
||||||
|
#define DBG_REG_S7 "s7"
|
||||||
|
#define DBG_REG_S8 "s8"
|
||||||
|
#define DBG_REG_S9 "s9"
|
||||||
|
#define DBG_REG_S10 "s10"
|
||||||
|
#define DBG_REG_S11 "s11"
|
||||||
|
#define DBG_REG_T3 "t3"
|
||||||
|
#define DBG_REG_T4 "t4"
|
||||||
|
#define DBG_REG_T5 "t5"
|
||||||
|
#define DBG_REG_T6 "t6"
|
||||||
|
#define DBG_REG_EPC "pc"
|
||||||
|
|
||||||
|
#define DBG_REG_ZERO_OFF 0
|
||||||
|
#define DBG_REG_RA_OFF 1
|
||||||
|
#define DBG_REG_SP_OFF 2
|
||||||
|
#define DBG_REG_GP_OFF 3
|
||||||
|
#define DBG_REG_TP_OFF 4
|
||||||
|
#define DBG_REG_T0_OFF 5
|
||||||
|
#define DBG_REG_T1_OFF 6
|
||||||
|
#define DBG_REG_T2_OFF 7
|
||||||
|
#define DBG_REG_FP_OFF 8
|
||||||
|
#define DBG_REG_S1_OFF 9
|
||||||
|
#define DBG_REG_A0_OFF 10
|
||||||
|
#define DBG_REG_A1_OFF 11
|
||||||
|
#define DBG_REG_A2_OFF 12
|
||||||
|
#define DBG_REG_A3_OFF 13
|
||||||
|
#define DBG_REG_A4_OFF 14
|
||||||
|
#define DBG_REG_A5_OFF 15
|
||||||
|
#define DBG_REG_A6_OFF 16
|
||||||
|
#define DBG_REG_A7_OFF 17
|
||||||
|
#define DBG_REG_S2_OFF 18
|
||||||
|
#define DBG_REG_S3_OFF 19
|
||||||
|
#define DBG_REG_S4_OFF 20
|
||||||
|
#define DBG_REG_S5_OFF 21
|
||||||
|
#define DBG_REG_S6_OFF 22
|
||||||
|
#define DBG_REG_S7_OFF 23
|
||||||
|
#define DBG_REG_S8_OFF 24
|
||||||
|
#define DBG_REG_S9_OFF 25
|
||||||
|
#define DBG_REG_S10_OFF 26
|
||||||
|
#define DBG_REG_S11_OFF 27
|
||||||
|
#define DBG_REG_T3_OFF 28
|
||||||
|
#define DBG_REG_T4_OFF 29
|
||||||
|
#define DBG_REG_T5_OFF 30
|
||||||
|
#define DBG_REG_T6_OFF 31
|
||||||
|
#define DBG_REG_EPC_OFF 32
|
||||||
|
#define DBG_REG_STATUS_OFF 33
|
||||||
|
#define DBG_REG_BADADDR_OFF 34
|
||||||
|
#define DBG_REG_CAUSE_OFF 35
|
||||||
|
#endif
|
||||||
|
#endif
|
|
@ -51,5 +51,6 @@ ifeq ($(CONFIG_RISCV_SBI), y)
|
||||||
obj-$(CONFIG_SMP) += cpu_ops_sbi.o
|
obj-$(CONFIG_SMP) += cpu_ops_sbi.o
|
||||||
endif
|
endif
|
||||||
obj-$(CONFIG_HOTPLUG_CPU) += cpu-hotplug.o
|
obj-$(CONFIG_HOTPLUG_CPU) += cpu-hotplug.o
|
||||||
|
obj-$(CONFIG_KGDB) += kgdb.o
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|
|
@ -0,0 +1,200 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 SiFive
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/ptrace.h>
|
||||||
|
#include <linux/kdebug.h>
|
||||||
|
#include <linux/bug.h>
|
||||||
|
#include <linux/kgdb.h>
|
||||||
|
#include <linux/irqflags.h>
|
||||||
|
#include <linux/string.h>
|
||||||
|
#include <asm/cacheflush.h>
|
||||||
|
|
||||||
|
enum {
|
||||||
|
NOT_KGDB_BREAK = 0,
|
||||||
|
KGDB_SW_BREAK,
|
||||||
|
KGDB_COMPILED_BREAK,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct dbg_reg_def_t dbg_reg_def[DBG_MAX_REG_NUM] = {
|
||||||
|
{DBG_REG_ZERO, GDB_SIZEOF_REG, -1},
|
||||||
|
{DBG_REG_RA, GDB_SIZEOF_REG, offsetof(struct pt_regs, ra)},
|
||||||
|
{DBG_REG_SP, GDB_SIZEOF_REG, offsetof(struct pt_regs, sp)},
|
||||||
|
{DBG_REG_GP, GDB_SIZEOF_REG, offsetof(struct pt_regs, gp)},
|
||||||
|
{DBG_REG_TP, GDB_SIZEOF_REG, offsetof(struct pt_regs, tp)},
|
||||||
|
{DBG_REG_T0, GDB_SIZEOF_REG, offsetof(struct pt_regs, t0)},
|
||||||
|
{DBG_REG_T1, GDB_SIZEOF_REG, offsetof(struct pt_regs, t1)},
|
||||||
|
{DBG_REG_T2, GDB_SIZEOF_REG, offsetof(struct pt_regs, t2)},
|
||||||
|
{DBG_REG_FP, GDB_SIZEOF_REG, offsetof(struct pt_regs, s0)},
|
||||||
|
{DBG_REG_S1, GDB_SIZEOF_REG, offsetof(struct pt_regs, a1)},
|
||||||
|
{DBG_REG_A0, GDB_SIZEOF_REG, offsetof(struct pt_regs, a0)},
|
||||||
|
{DBG_REG_A1, GDB_SIZEOF_REG, offsetof(struct pt_regs, a1)},
|
||||||
|
{DBG_REG_A2, GDB_SIZEOF_REG, offsetof(struct pt_regs, a2)},
|
||||||
|
{DBG_REG_A3, GDB_SIZEOF_REG, offsetof(struct pt_regs, a3)},
|
||||||
|
{DBG_REG_A4, GDB_SIZEOF_REG, offsetof(struct pt_regs, a4)},
|
||||||
|
{DBG_REG_A5, GDB_SIZEOF_REG, offsetof(struct pt_regs, a5)},
|
||||||
|
{DBG_REG_A6, GDB_SIZEOF_REG, offsetof(struct pt_regs, a6)},
|
||||||
|
{DBG_REG_A7, GDB_SIZEOF_REG, offsetof(struct pt_regs, a7)},
|
||||||
|
{DBG_REG_S2, GDB_SIZEOF_REG, offsetof(struct pt_regs, s2)},
|
||||||
|
{DBG_REG_S3, GDB_SIZEOF_REG, offsetof(struct pt_regs, s3)},
|
||||||
|
{DBG_REG_S4, GDB_SIZEOF_REG, offsetof(struct pt_regs, s4)},
|
||||||
|
{DBG_REG_S5, GDB_SIZEOF_REG, offsetof(struct pt_regs, s5)},
|
||||||
|
{DBG_REG_S6, GDB_SIZEOF_REG, offsetof(struct pt_regs, s6)},
|
||||||
|
{DBG_REG_S7, GDB_SIZEOF_REG, offsetof(struct pt_regs, s7)},
|
||||||
|
{DBG_REG_S8, GDB_SIZEOF_REG, offsetof(struct pt_regs, s8)},
|
||||||
|
{DBG_REG_S9, GDB_SIZEOF_REG, offsetof(struct pt_regs, s9)},
|
||||||
|
{DBG_REG_S10, GDB_SIZEOF_REG, offsetof(struct pt_regs, s10)},
|
||||||
|
{DBG_REG_S11, GDB_SIZEOF_REG, offsetof(struct pt_regs, s11)},
|
||||||
|
{DBG_REG_T3, GDB_SIZEOF_REG, offsetof(struct pt_regs, t3)},
|
||||||
|
{DBG_REG_T4, GDB_SIZEOF_REG, offsetof(struct pt_regs, t4)},
|
||||||
|
{DBG_REG_T5, GDB_SIZEOF_REG, offsetof(struct pt_regs, t5)},
|
||||||
|
{DBG_REG_T6, GDB_SIZEOF_REG, offsetof(struct pt_regs, t6)},
|
||||||
|
{DBG_REG_EPC, GDB_SIZEOF_REG, offsetof(struct pt_regs, epc)},
|
||||||
|
};
|
||||||
|
|
||||||
|
char *dbg_get_reg(int regno, void *mem, struct pt_regs *regs)
|
||||||
|
{
|
||||||
|
if (regno >= DBG_MAX_REG_NUM || regno < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (dbg_reg_def[regno].offset != -1)
|
||||||
|
memcpy(mem, (void *)regs + dbg_reg_def[regno].offset,
|
||||||
|
dbg_reg_def[regno].size);
|
||||||
|
else
|
||||||
|
memset(mem, 0, dbg_reg_def[regno].size);
|
||||||
|
return dbg_reg_def[regno].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dbg_set_reg(int regno, void *mem, struct pt_regs *regs)
|
||||||
|
{
|
||||||
|
if (regno >= DBG_MAX_REG_NUM || regno < 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
if (dbg_reg_def[regno].offset != -1)
|
||||||
|
memcpy((void *)regs + dbg_reg_def[regno].offset, mem,
|
||||||
|
dbg_reg_def[regno].size);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sleeping_thread_to_gdb_regs(unsigned long *gdb_regs, struct task_struct *task)
|
||||||
|
{
|
||||||
|
/* Initialize to zero */
|
||||||
|
memset((char *)gdb_regs, 0, NUMREGBYTES);
|
||||||
|
|
||||||
|
gdb_regs[DBG_REG_SP_OFF] = task->thread.sp;
|
||||||
|
gdb_regs[DBG_REG_FP_OFF] = task->thread.s[0];
|
||||||
|
gdb_regs[DBG_REG_S1_OFF] = task->thread.s[1];
|
||||||
|
gdb_regs[DBG_REG_S2_OFF] = task->thread.s[2];
|
||||||
|
gdb_regs[DBG_REG_S3_OFF] = task->thread.s[3];
|
||||||
|
gdb_regs[DBG_REG_S4_OFF] = task->thread.s[4];
|
||||||
|
gdb_regs[DBG_REG_S5_OFF] = task->thread.s[5];
|
||||||
|
gdb_regs[DBG_REG_S6_OFF] = task->thread.s[6];
|
||||||
|
gdb_regs[DBG_REG_S7_OFF] = task->thread.s[7];
|
||||||
|
gdb_regs[DBG_REG_S8_OFF] = task->thread.s[8];
|
||||||
|
gdb_regs[DBG_REG_S9_OFF] = task->thread.s[10];
|
||||||
|
gdb_regs[DBG_REG_S10_OFF] = task->thread.s[11];
|
||||||
|
gdb_regs[DBG_REG_EPC_OFF] = task->thread.ra;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kgdb_arch_set_pc(struct pt_regs *regs, unsigned long pc)
|
||||||
|
{
|
||||||
|
regs->epc = pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void kgdb_arch_update_addr(struct pt_regs *regs,
|
||||||
|
char *remcom_in_buffer)
|
||||||
|
{
|
||||||
|
unsigned long addr;
|
||||||
|
char *ptr;
|
||||||
|
|
||||||
|
ptr = &remcom_in_buffer[1];
|
||||||
|
if (kgdb_hex2long(&ptr, &addr))
|
||||||
|
regs->epc = addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int kgdb_arch_handle_exception(int vector, int signo, int err_code,
|
||||||
|
char *remcom_in_buffer, char *remcom_out_buffer,
|
||||||
|
struct pt_regs *regs)
|
||||||
|
{
|
||||||
|
int err = 0;
|
||||||
|
|
||||||
|
switch (remcom_in_buffer[0]) {
|
||||||
|
case 'c':
|
||||||
|
case 'D':
|
||||||
|
case 'k':
|
||||||
|
if (remcom_in_buffer[0] == 'c')
|
||||||
|
kgdb_arch_update_addr(regs, remcom_in_buffer);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
err = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
int kgdb_riscv_kgdbbreak(unsigned long addr)
|
||||||
|
{
|
||||||
|
if (atomic_read(&kgdb_setting_breakpoint))
|
||||||
|
if (addr == (unsigned long)&kgdb_compiled_break)
|
||||||
|
return KGDB_COMPILED_BREAK;
|
||||||
|
|
||||||
|
return kgdb_has_hit_break(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int kgdb_riscv_notify(struct notifier_block *self, unsigned long cmd,
|
||||||
|
void *ptr)
|
||||||
|
{
|
||||||
|
struct die_args *args = (struct die_args *)ptr;
|
||||||
|
struct pt_regs *regs = args->regs;
|
||||||
|
unsigned long flags;
|
||||||
|
int type;
|
||||||
|
|
||||||
|
if (user_mode(regs))
|
||||||
|
return NOTIFY_DONE;
|
||||||
|
|
||||||
|
type = kgdb_riscv_kgdbbreak(regs->epc);
|
||||||
|
if (type == NOT_KGDB_BREAK && cmd == DIE_TRAP)
|
||||||
|
return NOTIFY_DONE;
|
||||||
|
|
||||||
|
local_irq_save(flags);
|
||||||
|
if (kgdb_handle_exception(1, args->signr, cmd, regs))
|
||||||
|
return NOTIFY_DONE;
|
||||||
|
|
||||||
|
if (type == KGDB_COMPILED_BREAK)
|
||||||
|
regs->epc += 4;
|
||||||
|
|
||||||
|
local_irq_restore(flags);
|
||||||
|
|
||||||
|
return NOTIFY_STOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct notifier_block kgdb_notifier = {
|
||||||
|
.notifier_call = kgdb_riscv_notify,
|
||||||
|
};
|
||||||
|
|
||||||
|
int kgdb_arch_init(void)
|
||||||
|
{
|
||||||
|
register_die_notifier(&kgdb_notifier);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kgdb_arch_exit(void)
|
||||||
|
{
|
||||||
|
unregister_die_notifier(&kgdb_notifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Global data
|
||||||
|
*/
|
||||||
|
#ifdef CONFIG_RISCV_ISA_C
|
||||||
|
const struct kgdb_arch arch_kgdb_ops = {
|
||||||
|
.gdb_bpt_instr = {0x02, 0x90}, /* c.ebreak */
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
const struct kgdb_arch arch_kgdb_ops = {
|
||||||
|
.gdb_bpt_instr = {0x73, 0x00, 0x10, 0x00}, /* ebreak */
|
||||||
|
};
|
||||||
|
#endif
|
|
@ -147,6 +147,11 @@ asmlinkage __visible void do_trap_break(struct pt_regs *regs)
|
||||||
{
|
{
|
||||||
if (user_mode(regs))
|
if (user_mode(regs))
|
||||||
force_sig_fault(SIGTRAP, TRAP_BRKPT, (void __user *)regs->epc);
|
force_sig_fault(SIGTRAP, TRAP_BRKPT, (void __user *)regs->epc);
|
||||||
|
#ifdef CONFIG_KGDB
|
||||||
|
else if (notify_die(DIE_TRAP, "EBREAK", regs, 0, regs->cause, SIGTRAP)
|
||||||
|
== NOTIFY_STOP)
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
else if (report_bug(regs->epc, regs) == BUG_TRAP_TYPE_WARN)
|
else if (report_bug(regs->epc, regs) == BUG_TRAP_TYPE_WARN)
|
||||||
regs->epc += get_break_insn_length(regs->epc);
|
regs->epc += get_break_insn_length(regs->epc);
|
||||||
else
|
else
|
||||||
|
|
Loading…
Reference in New Issue