[RFC PATCH v1] riscv: support for hardware breakpoints/watchpoints
Andrew Bresticker
abrestic at rivosinc.com
Tue Nov 1 14:23:41 PDT 2022
On Mon, Oct 31, 2022 at 5:39 PM Sergey Matyukevich <geomatsi at gmail.com> wrote:
>
> From: Sergey Matyukevich <sergey.matyukevich at syntacore.com>
>
> RISC-V Debug specification includes Sdtrig ISA extension. This extension
> describes Trigger Module. Triggers can cause a breakpoint exception,
> entry into Debug Mode, or a trace action without having to execute a
> special instruction. For native debugging triggers can be used to
> implement hardware breakpoints and watchpoints.
>
> Software support for RISC-V hardware triggers consists of the following
> major components:
> - U-mode: gdb support for setting hw breakpoints/watchpoints
> - S/VS-mode: hardware breakpoints framework in Linux kernel
> - M-mode: SBI firmware code to handle hardware triggers
>
> SBI Debug Trigger extension proposal has been posted by Anup Patel
> to lists.riscv.org tech-debug mailing list, see:
> https://lists.riscv.org/g/tech-debug/topic/92375492
>
> This patch provides initial Linux support for RISC-V hardware breakpoints
> and watchpoints based on the proposed SBI Debug Trigger extension. The
> accompanying OpenSBI changes implementing new extension are also posted
> for review, see:
>
> http://lists.infradead.org/pipermail/opensbi/2022-October/003531.html
>
> Initial version has the following limitations:
> - userspace debug is not yet enabled: work on ptrace/gdb is in progress
> - only mcontrol6 trigger type is supported
> - no support for chained triggers
> - no support for virtualization
>
> Despite missing userspace debug, initial implementation can be tested
> on QEMU using kernel breakpoints, e.g. see samples/hw_breakpoint and
> register_wide_hw_breakpoint. Hardware breakpoints work on upstream QEMU.
We should also be able to enable the use of HW breakpoints (and
watchpoints, modulo the issue mentioned below) in kdb, right?
> However this is not the case for watchpoints since there is no way to
> figure out which watchpoint is triggered. IIUC there are two possible
> options for doing this: using 'hit' bit in tdata1 or reading faulting
> virtual address from STVAL. QEMU implements neither of them. Current
> implementation opts for STVAL. So the following experimental QEMU patch
> is required to make watchpoints work:
>
> : diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c
> : index 278d163803..8858be7411 100644
> : --- a/target/riscv/cpu_helper.c
> : +++ b/target/riscv/cpu_helper.c
> : @@ -1639,6 +1639,10 @@ void riscv_cpu_do_interrupt(CPUState *cs)
> : case RISCV_EXCP_VIRT_INSTRUCTION_FAULT:
> : tval = env->bins;
> : break;
> : + case RISCV_EXCP_BREAKPOINT:
> : + tval = env->badaddr;
> : + env->badaddr = 0x0;
> : + break;
> : default:
> : break;
> : }
> : diff --git a/target/riscv/debug.c b/target/riscv/debug.c
> : index 26ea764407..b4d1d566ab 100644
> : --- a/target/riscv/debug.c
> : +++ b/target/riscv/debug.c
> : @@ -560,6 +560,7 @@ void riscv_cpu_debug_excp_handler(CPUState *cs)
> :
> : if (cs->watchpoint_hit) {
> : if (cs->watchpoint_hit->flags & BP_CPU) {
> : + env->badaddr = cs->watchpoint_hit->hitaddr;
> : cs->watchpoint_hit = NULL;
> : do_trigger_action(env, DBG_ACTION_BP);
> : }
>
> Signed-off-by: Sergey Matyukevich <sergey.matyukevich at syntacore.com>
> ---
> arch/riscv/Kconfig | 2 +
> arch/riscv/include/asm/hw_breakpoint.h | 131 ++++++++
> arch/riscv/include/asm/kdebug.h | 3 +-
> arch/riscv/include/asm/processor.h | 5 +
> arch/riscv/include/asm/sbi.h | 24 ++
> arch/riscv/kernel/Makefile | 1 +
> arch/riscv/kernel/hw_breakpoint.c | 416 +++++++++++++++++++++++++
> arch/riscv/kernel/process.c | 1 +
> arch/riscv/kernel/traps.c | 5 +
> 9 files changed, 587 insertions(+), 1 deletion(-)
> create mode 100644 arch/riscv/include/asm/hw_breakpoint.h
> create mode 100644 arch/riscv/kernel/hw_breakpoint.c
>
> diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
> index fa78595a6089..245ed0628211 100644
> --- a/arch/riscv/Kconfig
> +++ b/arch/riscv/Kconfig
> @@ -95,10 +95,12 @@ config RISCV
> select HAVE_FUNCTION_ERROR_INJECTION
> select HAVE_GCC_PLUGINS
> select HAVE_GENERIC_VDSO if MMU && 64BIT
> + select HAVE_HW_BREAKPOINT if PERF_EVENTS
> select HAVE_IRQ_TIME_ACCOUNTING
> select HAVE_KPROBES if !XIP_KERNEL
> select HAVE_KPROBES_ON_FTRACE if !XIP_KERNEL
> select HAVE_KRETPROBES if !XIP_KERNEL
> + select HAVE_MIXED_BREAKPOINTS_REGS
> select HAVE_MOVE_PMD
> select HAVE_MOVE_PUD
> select HAVE_PCI
> diff --git a/arch/riscv/include/asm/hw_breakpoint.h b/arch/riscv/include/asm/hw_breakpoint.h
> new file mode 100644
> index 000000000000..7610b6be669d
> --- /dev/null
> +++ b/arch/riscv/include/asm/hw_breakpoint.h
> @@ -0,0 +1,131 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +
> +#ifndef __RISCV_HW_BREAKPOINT_H
> +#define __RISCV_HW_BREAKPOINT_H
> +
> +struct task_struct;
> +
> +#ifdef CONFIG_HAVE_HW_BREAKPOINT
> +
> +#include <uapi/linux/hw_breakpoint.h>
> +
> +enum {
> + RISCV_DBTR_BREAKPOINT = 0,
> + RISCV_DBTR_WATCHPOINT = 1,
> +};
> +
> +enum {
> + RISCV_DBTR_TRIG_NONE = 0,
> + RISCV_DBTR_TRIG_LEGACY,
> + RISCV_DBTR_TRIG_MCONTROL,
> + RISCV_DBTR_TRIG_ICOUNT,
> + RISCV_DBTR_TRIG_ITRIGGER,
> + RISCV_DBTR_TRIG_ETRIGGER,
> + RISCV_DBTR_TRIG_MCONTROL6,
> +};
> +
> +union riscv_dbtr_tdata1 {
> + unsigned long value;
> + struct {
> +#if __riscv_xlen == 64
> + unsigned long data:59;
> +#elif __riscv_xlen == 32
> + unsigned long data:27;
> +#else
> +#error "Unexpected __riscv_xlen"
> +#endif
> + unsigned long dmode:1;
> + unsigned long type:4;
> + };
> +};
> +
> +union riscv_dbtr_tdata1_mcontrol6 {
> + unsigned long value;
> + struct {
> + unsigned long load:1;
> + unsigned long store:1;
> + unsigned long execute:1;
> + unsigned long u:1;
> + unsigned long s:1;
> + unsigned long _res2:1;
> + unsigned long m:1;
> + unsigned long match:4;
> + unsigned long chain:1;
> + unsigned long action:4;
> + unsigned long size:4;
> + unsigned long timing:1;
> + unsigned long select:1;
> + unsigned long hit:1;
> + unsigned long vu:1;
> + unsigned long vs:1;
> +#if __riscv_xlen == 64
> + unsigned long _res1:34;
> +#elif __riscv_xlen == 32
> + unsigned long _res1:2;
> +#else
> +#error "Unexpected __riscv_xlen"
> +#endif
> + unsigned long dmode:1;
> + unsigned long type:4;
> + };
> +};
> +
> +struct arch_hw_breakpoint {
> + unsigned long address;
> + unsigned long len;
> + unsigned int type;
> +
> + union riscv_dbtr_tdata1_mcontrol6 trig_data1;
> + unsigned long trig_data2;
> + unsigned long trig_data3;
> +};
> +
> +/* Max supported HW breakpoints */
> +#define HBP_NUM_MAX 32
> +
> +struct perf_event_attr;
> +struct notifier_block;
> +struct perf_event;
> +struct pt_regs;
> +
> +int hw_breakpoint_slots(int type);
> +int arch_check_bp_in_kernelspace(struct arch_hw_breakpoint *hw);
> +int hw_breakpoint_arch_parse(struct perf_event *bp,
> + const struct perf_event_attr *attr,
> + struct arch_hw_breakpoint *hw);
> +int hw_breakpoint_exceptions_notify(struct notifier_block *unused,
> + unsigned long val, void *data);
> +
> +void arch_enable_hw_breakpoint(struct perf_event *bp);
> +void arch_update_hw_breakpoint(struct perf_event *bp);
> +void arch_disable_hw_breakpoint(struct perf_event *bp);
> +int arch_install_hw_breakpoint(struct perf_event *bp);
> +void arch_uninstall_hw_breakpoint(struct perf_event *bp);
> +void hw_breakpoint_pmu_read(struct perf_event *bp);
> +void clear_ptrace_hw_breakpoint(struct task_struct *tsk);
> +
> +#else
> +
> +int hw_breakpoint_slots(int type)
> +{
> + return 0;
> +}
> +
> +static inline void clear_ptrace_hw_breakpoint(struct task_struct *tsk)
> +{
> +}
> +
> +void arch_enable_hw_breakpoint(struct perf_event *bp)
> +{
> +}
> +
> +void arch_update_hw_breakpoint(struct perf_event *bp)
> +{
> +}
> +
> +void arch_disable_hw_breakpoint(struct perf_event *bp)
> +{
> +}
> +
> +#endif /* CONFIG_HAVE_HW_BREAKPOINT */
> +#endif /* __RISCV_HW_BREAKPOINT_H */
> diff --git a/arch/riscv/include/asm/kdebug.h b/arch/riscv/include/asm/kdebug.h
> index 85ac00411f6e..53e989781aa1 100644
> --- a/arch/riscv/include/asm/kdebug.h
> +++ b/arch/riscv/include/asm/kdebug.h
> @@ -6,7 +6,8 @@
> enum die_val {
> DIE_UNUSED,
> DIE_TRAP,
> - DIE_OOPS
> + DIE_OOPS,
> + DIE_DEBUG
> };
>
> #endif
> diff --git a/arch/riscv/include/asm/processor.h b/arch/riscv/include/asm/processor.h
> index 94a0590c6971..10c87fba2548 100644
> --- a/arch/riscv/include/asm/processor.h
> +++ b/arch/riscv/include/asm/processor.h
> @@ -11,6 +11,7 @@
> #include <vdso/processor.h>
>
> #include <asm/ptrace.h>
> +#include <asm/hw_breakpoint.h>
>
> /*
> * This decides where the kernel will search for a free chunk of vm
> @@ -29,6 +30,7 @@
> #ifndef __ASSEMBLY__
>
> struct task_struct;
> +struct perf_event;
> struct pt_regs;
>
> /* CPU-specific state of a task */
> @@ -39,6 +41,9 @@ struct thread_struct {
> unsigned long s[12]; /* s[0]: frame pointer */
> struct __riscv_d_ext_state fstate;
> unsigned long bad_cause;
> +#ifdef CONFIG_HAVE_HW_BREAKPOINT
> + struct perf_event *ptrace_bps[HBP_NUM_MAX];
> +#endif
> };
>
> /* Whitelist the fstate from the task_struct for hardened usercopy */
> diff --git a/arch/riscv/include/asm/sbi.h b/arch/riscv/include/asm/sbi.h
> index 2a0ef738695e..f7f5ef51c350 100644
> --- a/arch/riscv/include/asm/sbi.h
> +++ b/arch/riscv/include/asm/sbi.h
> @@ -31,6 +31,9 @@ enum sbi_ext_id {
> SBI_EXT_SRST = 0x53525354,
> SBI_EXT_PMU = 0x504D55,
>
> + /* Experimental: Debug Trigger Extension */
> + SBI_EXT_DBTR = 0x44425452,
> +
> /* Experimentals extensions must lie within this range */
> SBI_EXT_EXPERIMENTAL_START = 0x08000000,
> SBI_EXT_EXPERIMENTAL_END = 0x08FFFFFF,
> @@ -113,6 +116,27 @@ enum sbi_srst_reset_reason {
> SBI_SRST_RESET_REASON_SYS_FAILURE,
> };
>
> +enum sbi_ext_dbtr_fid {
> + SBI_EXT_DBTR_NUM_TRIGGERS = 0,
> + SBI_EXT_DBTR_TRIGGER_READ,
> + SBI_EXT_DBTR_TRIGGER_INSTALL,
> + SBI_EXT_DBTR_TRIGGER_UNINSTALL,
> + SBI_EXT_DBTR_TRIGGER_ENABLE,
> + SBI_EXT_DBTR_TRIGGER_UPDATE,
> + SBI_EXT_DBTR_TRIGGER_DISABLE,
> +};
> +
> +struct sbi_dbtr_data_msg {
> + unsigned long tstate;
> + unsigned long tdata1;
> + unsigned long tdata2;
> + unsigned long tdata3;
> +} __packed;
> +
> +struct sbi_dbtr_id_msg {
> + unsigned long idx;
> +} __packed;
> +
> enum sbi_ext_pmu_fid {
> SBI_EXT_PMU_NUM_COUNTERS = 0,
> SBI_EXT_PMU_COUNTER_GET_INFO,
> diff --git a/arch/riscv/kernel/Makefile b/arch/riscv/kernel/Makefile
> index db6e4b1294ba..116697d0ca1d 100644
> --- a/arch/riscv/kernel/Makefile
> +++ b/arch/riscv/kernel/Makefile
> @@ -72,6 +72,7 @@ obj-$(CONFIG_TRACE_IRQFLAGS) += trace_irq.o
>
> obj-$(CONFIG_PERF_EVENTS) += perf_callchain.o
> obj-$(CONFIG_HAVE_PERF_REGS) += perf_regs.o
> +obj-$(CONFIG_HAVE_HW_BREAKPOINT) += hw_breakpoint.o
> obj-$(CONFIG_RISCV_SBI) += sbi.o
> ifeq ($(CONFIG_RISCV_SBI), y)
> obj-$(CONFIG_SMP) += cpu_ops_sbi.o
> diff --git a/arch/riscv/kernel/hw_breakpoint.c b/arch/riscv/kernel/hw_breakpoint.c
> new file mode 100644
> index 000000000000..32b7ca9ca694
> --- /dev/null
> +++ b/arch/riscv/kernel/hw_breakpoint.c
> @@ -0,0 +1,416 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/hw_breakpoint.h>
> +#include <linux/perf_event.h>
> +#include <linux/percpu.h>
> +#include <linux/kdebug.h>
> +
> +#include <asm/sbi.h>
> +
> +#define SBI_MSG_SZ_ALIGN(x) __roundup_pow_of_two(max_t(size_t, (x), SZ_16))
> +
> +/* bps/wps currently set on each debug trigger for each cpu */
> +static DEFINE_PER_CPU(struct perf_event *, bp_per_reg[HBP_NUM_MAX]);
> +
> +/* number of debug triggers on this cpu . */
> +static int dbtr_total_num __ro_after_init;
> +
> +int hw_breakpoint_slots(int type)
> +{
> + union riscv_dbtr_tdata1 tdata1;
> + struct sbiret ret;
> +
> + /*
> + * We can be called early, so don't rely on
> + * our static variables being initialised.
> + */
> +
> + tdata1.value = 0;
> + tdata1.type = RISCV_DBTR_TRIG_MCONTROL6;
> +
> + ret = sbi_ecall(SBI_EXT_DBTR, SBI_EXT_DBTR_NUM_TRIGGERS,
> + tdata1.value, 0, 0, 0, 0, 0);
> + if (ret.error) {
> + pr_warn("%s: failed to get hbp slots\n", __func__);
> + return 0;
> + }
> +
> + return ret.value;
> +}
> +
> +int arch_check_bp_in_kernelspace(struct arch_hw_breakpoint *hw)
> +{
> + unsigned int len;
> + unsigned long va;
> +
> + va = hw->address;
> + len = hw->len;
> +
> + return (va >= TASK_SIZE) && ((va + len - 1) >= TASK_SIZE);
> +}
> +
> +int hw_breakpoint_arch_parse(struct perf_event *bp,
> + const struct perf_event_attr *attr,
> + struct arch_hw_breakpoint *hw)
> +{
> + /* address */
> + hw->address = attr->bp_addr;
> + hw->trig_data2 = attr->bp_addr;
> + hw->trig_data3 = 0x0;
> +
> + /* type */
> + switch (attr->bp_type) {
> + case HW_BREAKPOINT_X:
> + hw->type = RISCV_DBTR_BREAKPOINT;
> + hw->trig_data1.execute = 1;
> + break;
> + case HW_BREAKPOINT_R:
> + hw->type = RISCV_DBTR_WATCHPOINT;
> + hw->trig_data1.load = 1;
> + break;
> + case HW_BREAKPOINT_W:
> + hw->type = RISCV_DBTR_WATCHPOINT;
> + hw->trig_data1.store = 1;
> + break;
> + case HW_BREAKPOINT_RW:
> + hw->type = RISCV_DBTR_WATCHPOINT;
> + hw->trig_data1.store = 1;
> + hw->trig_data1.load = 1;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + /* length */
> + switch (attr->bp_len) {
> + case HW_BREAKPOINT_LEN_1:
> + hw->len = 1;
> + hw->trig_data1.size = 1;
> + break;
> + case HW_BREAKPOINT_LEN_2:
> + hw->len = 2;
> + hw->trig_data1.size = 2;
> + break;
> + case HW_BREAKPOINT_LEN_4:
> + hw->len = 4;
> + hw->trig_data1.size = 3;
> + break;
> + case HW_BREAKPOINT_LEN_8:
> + hw->len = 8;
> + hw->trig_data1.size = 5;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + hw->trig_data1.type = RISCV_DBTR_TRIG_MCONTROL6;
> + hw->trig_data1.dmode = 0;
> + hw->trig_data1.select = 0;
> + hw->trig_data1.action = 0;
> + hw->trig_data1.chain = 0;
> + hw->trig_data1.match = 0;
> +
> + hw->trig_data1.m = 0;
> + hw->trig_data1.s = 1;
> + hw->trig_data1.u = 1;
> + hw->trig_data1.vs = 0;
> + hw->trig_data1.vu = 0;
> +
> + return 0;
> +}
> +
> +/*
> + * Handle debug exception notifications.
> + */
> +static int hw_breakpoint_handler(struct die_args *args)
> +{
> + int ret = NOTIFY_DONE;
> + struct arch_hw_breakpoint *info;
> + struct perf_event *bp;
> + int i;
> +
> + for (i = 0; i < dbtr_total_num; ++i) {
> + bp = this_cpu_read(bp_per_reg[i]);
> + if (!bp)
> + continue;
> +
> + info = counter_arch_bp(bp);
> + switch (info->type) {
> + case RISCV_DBTR_BREAKPOINT:
> + if (info->address == args->regs->epc) {
> + pr_debug("%s: breakpoint fired: pc[0x%lx]\n",
> + __func__, args->regs->epc);
> + perf_bp_event(bp, args->regs);
> + ret = NOTIFY_STOP;
> + }
> +
> + break;
> + case RISCV_DBTR_WATCHPOINT:
> + if (info->address == csr_read(CSR_STVAL)) {
> + pr_debug("%s: watchpoint fired: addr[0x%lx]\n",
> + __func__, info->address);
> + perf_bp_event(bp, args->regs);
> + ret = NOTIFY_STOP;
> + }
> +
> + break;
> + default:
> + pr_warn("%s: unexpected breakpoint type: %u\n",
> + __func__, info->type);
> + break;
> + }
> + }
> +
> + return ret;
> +}
> +
> +int hw_breakpoint_exceptions_notify(struct notifier_block *unused,
> + unsigned long val, void *data)
> +{
> + if (val != DIE_DEBUG)
> + return NOTIFY_DONE;
> +
> + return hw_breakpoint_handler(data);
> +}
> +
> +int arch_install_hw_breakpoint(struct perf_event *bp)
> +{
> + struct arch_hw_breakpoint *info = counter_arch_bp(bp);
> + struct sbi_dbtr_data_msg *xmit;
> + struct sbi_dbtr_id_msg *recv;
> + struct perf_event **slot;
> + struct sbiret ret;
> + int err = 0;
> +
> + xmit = kzalloc(SBI_MSG_SZ_ALIGN(sizeof(*xmit)), GFP_ATOMIC);
> + if (!xmit) {
> + err = -ENOMEM;
> + goto out;
> + }
> +
> + recv = kzalloc(SBI_MSG_SZ_ALIGN(sizeof(*recv)), GFP_ATOMIC);
> + if (!recv) {
> + err = -ENOMEM;
> + goto out;
> + }
Do these really need to be dynamically allocated?
> +
> + xmit->tdata1 = info->trig_data1.value;
> + xmit->tdata2 = info->trig_data2;
> + xmit->tdata3 = info->trig_data3;
> +
> + ret = sbi_ecall(SBI_EXT_DBTR, SBI_EXT_DBTR_TRIGGER_INSTALL,
> + 1, __pa(xmit) >> 4, __pa(recv) >> 4,
> + 0, 0, 0);
> + if (ret.error) {
> + pr_warn("%s: failed to install trigger\n", __func__);
> + err = -EIO;
> + goto out;
> + }
> +
> + if (recv->idx >= dbtr_total_num) {
> + pr_warn("%s: invalid trigger index %lu\n", __func__, recv->idx);
> + err = -EINVAL;
> + goto out;
> + }
> +
> + slot = this_cpu_ptr(&bp_per_reg[recv->idx]);
> + if (*slot) {
> + pr_warn("%s: slot %lu is in use\n", __func__, recv->idx);
> + err = -EBUSY;
> + goto out;
> + }
> +
> + *slot = bp;
> +
> +out:
> + kfree(xmit);
> + kfree(recv);
> +
> + return err;
> +}
> +
> +void arch_uninstall_hw_breakpoint(struct perf_event *bp)
> +{
> + struct sbiret ret;
> + int i;
> +
> + for (i = 0; i < dbtr_total_num; i++) {
> + struct perf_event **slot = this_cpu_ptr(&bp_per_reg[i]);
> +
> + if (*slot == bp) {
> + *slot = NULL;
> + break;
> + }
> + }
> +
> + if (i == dbtr_total_num) {
> + pr_warn("%s: unknown breakpoint\n", __func__);
> + return;
> + }
> +
> + ret = sbi_ecall(SBI_EXT_DBTR, SBI_EXT_DBTR_TRIGGER_UNINSTALL,
> + i, 1, 0, 0, 0, 0);
> + if (ret.error)
> + pr_warn("%s: failed to uninstall trigger %d\n", __func__, i);
> +}
> +
> +void arch_enable_hw_breakpoint(struct perf_event *bp)
> +{
> + struct sbiret ret;
> + int i;
> +
> + for (i = 0; i < dbtr_total_num; i++) {
> + struct perf_event **slot = this_cpu_ptr(&bp_per_reg[i]);
> +
> + if (*slot == bp)
> + break;
> + }
> +
> + if (i == dbtr_total_num) {
> + pr_warn("%s: unknown breakpoint\n", __func__);
> + return;
> + }
> +
> + ret = sbi_ecall(SBI_EXT_DBTR, SBI_EXT_DBTR_TRIGGER_ENABLE,
> + i, 1, 0, 0, 0, 0);
> + if (ret.error) {
> + pr_warn("%s: failed to install trigger %d\n", __func__, i);
> + return;
> + }
> +}
> +EXPORT_SYMBOL_GPL(arch_enable_hw_breakpoint);
> +
> +void arch_update_hw_breakpoint(struct perf_event *bp)
> +{
> + struct arch_hw_breakpoint *info = counter_arch_bp(bp);
> + struct sbi_dbtr_data_msg *xmit;
> + struct perf_event **slot;
> + struct sbiret ret;
> + int i;
> +
> + xmit = kzalloc(SBI_MSG_SZ_ALIGN(sizeof(*xmit)), GFP_ATOMIC);
> + if (!xmit)
> + return;
> +
> + for (i = 0; i < dbtr_total_num; i++) {
> + slot = this_cpu_ptr(&bp_per_reg[i]);
> +
> + if (*slot == bp)
> + break;
> + }
> +
> + if (i == dbtr_total_num) {
> + pr_warn("%s: unknown breakpoint\n", __func__);
> + goto out;
> + }
> +
> + xmit->tdata1 = info->trig_data1.value;
> + xmit->tdata2 = info->trig_data2;
> + xmit->tdata3 = info->trig_data3;
> +
> + ret = sbi_ecall(SBI_EXT_DBTR, SBI_EXT_DBTR_TRIGGER_UPDATE,
> + i, 1, __pa(xmit) >> 4,
> + 0, 0, 0);
> + if (ret.error) {
> + pr_warn("%s: failed to update trigger %d\n", __func__, i);
> + goto out;
> + }
> +
> +out:
> + kfree(xmit);
> +}
> +EXPORT_SYMBOL_GPL(arch_update_hw_breakpoint);
> +
> +void arch_disable_hw_breakpoint(struct perf_event *bp)
> +{
> + struct sbiret ret;
> + int i;
> +
> + for (i = 0; i < dbtr_total_num; i++) {
> + struct perf_event **slot = this_cpu_ptr(&bp_per_reg[i]);
> +
> + if (*slot == bp)
> + break;
> + }
> +
> + if (i == dbtr_total_num) {
> + pr_warn("%s: unknown breakpoint\n", __func__);
> + return;
> + }
> +
> + ret = sbi_ecall(SBI_EXT_DBTR, SBI_EXT_DBTR_TRIGGER_DISABLE,
> + i, 1, 0, 0, 0, 0);
> + if (ret.error) {
> + pr_warn("%s: failed to uninstall trigger %d\n", __func__, i);
> + return;
> + }
> +}
> +EXPORT_SYMBOL_GPL(arch_disable_hw_breakpoint);
> +
> +void hw_breakpoint_pmu_read(struct perf_event *bp)
> +{
> +}
> +
> +/*
> + * Set ptrace breakpoint pointers to zero for this task.
> + * This is required in order to prevent child processes from unregistering
> + * breakpoints held by their parent.
> + */
> +void clear_ptrace_hw_breakpoint(struct task_struct *tsk)
> +{
> + memset(tsk->thread.ptrace_bps, 0, sizeof(tsk->thread.ptrace_bps));
> +}
> +
> +/*
> + * Unregister breakpoints from this task and reset the pointers in
> + * the thread_struct.
> + */
> +void flush_ptrace_hw_breakpoint(struct task_struct *tsk)
> +{
> + int i;
> + struct thread_struct *t = &tsk->thread;
> +
> + for (i = 0; i < dbtr_total_num; i++) {
> + unregister_hw_breakpoint(t->ptrace_bps[i]);
> + t->ptrace_bps[i] = NULL;
> + }
> +}
> +
> +static int __init arch_hw_breakpoint_init(void)
> +{
> + union riscv_dbtr_tdata1 tdata1;
> + struct sbiret ret;
> +
> + if (sbi_probe_extension(SBI_EXT_DBTR) <= 0) {
> + pr_info("%s: SBI_EXT_DBTR is not supported\n", __func__);
> + return 0;
> + }
> +
> + ret = sbi_ecall(SBI_EXT_DBTR, SBI_EXT_DBTR_NUM_TRIGGERS,
> + 0, 0, 0, 0, 0, 0);
> + if (ret.error) {
> + pr_warn("%s: failed to detect triggers\n", __func__);
> + return 0;
> + }
> +
> + pr_info("%s: total number of triggers: %lu\n", __func__, ret.value);
> +
> + tdata1.value = 0;
> + tdata1.type = RISCV_DBTR_TRIG_MCONTROL6;
> +
> + ret = sbi_ecall(SBI_EXT_DBTR, SBI_EXT_DBTR_NUM_TRIGGERS,
> + tdata1.value, 0, 0, 0, 0, 0);
> + if (ret.error) {
> + pr_warn("%s: failed to detect triggers\n", __func__);
> + dbtr_total_num = 0;
> + return 0;
> + }
nit: This is basically identical to hw_breakpoint_slots() -- just call
it here, or perhaps pull the DBTR_NUM_TRIGGERS ECALL into its own
function to reduce the duplication, e.g. 'dbtr_num_triggers(unsigned
long type)'?
> +
> + pr_info("%s: total number of type %d triggers: %lu\n",
> + __func__, tdata1.type, ret.value);
> +
> + dbtr_total_num = ret.value;
> +
> + return 0;
> +}
> +arch_initcall(arch_hw_breakpoint_init);
> diff --git a/arch/riscv/kernel/process.c b/arch/riscv/kernel/process.c
> index b0c63e8e867e..da379f6af3f3 100644
> --- a/arch/riscv/kernel/process.c
> +++ b/arch/riscv/kernel/process.c
> @@ -185,5 +185,6 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
> p->thread.ra = (unsigned long)ret_from_fork;
> }
> p->thread.sp = (unsigned long)childregs; /* kernel sp */
> + clear_ptrace_hw_breakpoint(p);
> return 0;
> }
> diff --git a/arch/riscv/kernel/traps.c b/arch/riscv/kernel/traps.c
> index f3e96d60a2ff..97712c52348e 100644
> --- a/arch/riscv/kernel/traps.c
> +++ b/arch/riscv/kernel/traps.c
> @@ -174,6 +174,11 @@ asmlinkage __visible __trap_section void do_trap_break(struct pt_regs *regs)
>
> if (uprobe_breakpoint_handler(regs))
> return;
> +#endif
> +#ifdef CONFIG_HAVE_HW_BREAKPOINT
> + if (notify_die(DIE_DEBUG, "EBREAK", regs, 0, regs->cause, SIGTRAP)
> + == NOTIFY_STOP)
> + return;
> #endif
> current->thread.bad_cause = regs->cause;
>
> --
> 2.38.1
>
>
> _______________________________________________
> linux-riscv mailing list
> linux-riscv at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-riscv
More information about the linux-riscv
mailing list