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:
Vincent Chen 2020-04-16 10:38:05 +08:00 committed by Palmer Dabbelt
parent f83b04d36e
commit fe89bd2be8
No known key found for this signature in database
GPG Key ID: 2E1319F35FBB1889
6 changed files with 325 additions and 0 deletions

View File

@ -68,6 +68,7 @@ config RISCV
select ARCH_HAS_GCOV_PROFILE_ALL
select HAVE_COPY_THREAD_TLS
select HAVE_ARCH_KASAN if MMU && 64BIT
select HAVE_ARCH_KGDB
config ARCH_MMAP_RND_BITS_MIN
default 18 if 64BIT

View File

@ -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

View File

@ -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

View File

@ -51,5 +51,6 @@ ifeq ($(CONFIG_RISCV_SBI), y)
obj-$(CONFIG_SMP) += cpu_ops_sbi.o
endif
obj-$(CONFIG_HOTPLUG_CPU) += cpu-hotplug.o
obj-$(CONFIG_KGDB) += kgdb.o
clean:

200
arch/riscv/kernel/kgdb.c Normal file
View File

@ -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

View File

@ -147,6 +147,11 @@ asmlinkage __visible void do_trap_break(struct pt_regs *regs)
{
if (user_mode(regs))
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)
regs->epc += get_break_insn_length(regs->epc);
else