[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