From: Himanshu Chauhan <himanshu.chauhan@oss.qualcomm.com>
To: Paul Walmsley <pjw@kernel.org>,
Palmer Dabbelt <palmer@dabbelt.com>,
Albert Ou <aou@eecs.berkeley.edu>,
linux-riscv@lists.infradead.org
Cc: Alexandre Ghiti <alex@ghiti.fr>,
Jesse Taube <jtaubepe@redhat.com>,
Oleg Nesterov <oleg@redhat.com>, Kees Cook <kees@kernel.org>,
Peter Zijlstra <peterz@infradead.org>,
Ingo Molnar <mingo@redhat.com>,
Arnaldo Carvalho de Melo <acme@kernel.org>,
Namhyung Kim <namhyung@kernel.org>,
Mark Rutland <mark.rutland@arm.com>,
Alexander Shishkin <alexander.shishkin@linux.intel.com>,
Jiri Olsa <jolsa@kernel.org>, Ian Rogers <irogers@google.com>,
Adrian Hunter <adrian.hunter@intel.com>,
James Clark <james.clark@linaro.org>,
Shuah Khan <shuah@kernel.org>,
linux-kernel@vger.kernel.org, linux-mm@kvack.org,
linux-perf-users@vger.kernel.org,
linux-kselftest@vger.kernel.org,
Himanshu Chauhan <himanshu.chauhan@oss.qualcomm.com>
Subject: [PATCH v8 4/8] riscv: add regset access for hardware breakpoints
Date: Sat, 7 Nov 2026 08:09:13 +0530 [thread overview]
Message-ID: <20261107023917.40606-5-himanshu.chauhan@oss.qualcomm.com> (raw)
In-Reply-To: <20261107023917.40606-1-himanshu.chauhan@oss.qualcomm.com>
Add NT_RISCV_HW_BREAK and NT_RISCV_HW_WATCH. A tracer can use
PTRACE_GETREGSET and PTRACE_SETREGSET to read or program the RISC-V
hardware breakpoint and watchpoint slots.
The regset layout uses fixed-width address and control fields. This
keeps the native and compat layouts the same, because the UAPI fields do
not depend on the task word size. Reuse the ptrace-owned perf event
backend from the previous patch.
Signed-off-by: Himanshu Chauhan <himanshu.chauhan@oss.qualcomm.com>
---
arch/riscv/include/uapi/asm/ptrace.h | 41 ++++
arch/riscv/kernel/ptrace.c | 340 +++++++++++++++++++++++++++
include/uapi/linux/elf.h | 4 +
tools/include/uapi/linux/elf.h | 2 +
4 files changed, 387 insertions(+)
diff --git a/arch/riscv/include/uapi/asm/ptrace.h b/arch/riscv/include/uapi/asm/ptrace.h
index 373a1c14f207..c6beb7b287fe 100644
--- a/arch/riscv/include/uapi/asm/ptrace.h
+++ b/arch/riscv/include/uapi/asm/ptrace.h
@@ -10,6 +10,7 @@
#include <linux/types.h>
#include <linux/const.h>
+#include <linux/bits.h>
#define PTRACE_GETFDPIC 33
@@ -166,6 +167,46 @@ struct user_cfi_state {
__u64 shstk_ptr;
};
+/*
+ * bit[3:0] Match
+ * bit[8:4] Size
+ * bit[11:9] When
+ * bit[12] Select
+ * bit[13] Chain
+ * bit[16:14] Type
+ * bit[XLEN-1:17] Reserved
+ */
+#define HWDEBUG_MATCH_MASK __GENMASK(3, 0)
+#define HWDEBUG_SIZE_MASK __GENMASK(8, 4)
+#define HWDEBUG_WHEN_MASK __GENMASK(11, 9)
+#define HWDEBUG_SELECT_MASK _BITUL(12)
+#define HWDEBUG_CHAIN_MASK _BITUL(13)
+#define HWDEBUG_TYPE_MASK __GENMASK(16, 14)
+
+#define HWDEBUG_MATCH(_ctrl) (((_ctrl) & HWDEBUG_MATCH_MASK) >> 0)
+#define HWDEBUG_SIZE(_ctrl) (((_ctrl) & HWDEBUG_SIZE_MASK) >> 4)
+#define HWDEBUG_WHEN(_ctrl) (((_ctrl) & HWDEBUG_WHEN_MASK) >> 9)
+#define HWDEBUG_SELECT(_ctrl) (((_ctrl) & HWDEBUG_SELECT_MASK) >> 12)
+#define HWDEBUG_CHAIN(_ctrl) (((_ctrl) & HWDEBUG_CHAIN_MASK) >> 13)
+#define HWDEBUG_TYPE(_ctrl) (((_ctrl) & HWDEBUG_TYPE_MASK) >> 14)
+
+#define HWDEBUG_MK_MATCH(_match) (((_match) << 0) & HWDEBUG_MATCH_MASK)
+#define HWDEBUG_MK_SIZE(_sz) (((_sz) << 4) & HWDEBUG_SIZE_MASK)
+#define HWDEBUG_MK_WHEN(_when) (((_when) << 9) & HWDEBUG_WHEN_MASK)
+#define HWDEBUG_MK_SELECT(_sel) (((_sel) << 12) & HWDEBUG_SELECT_MASK)
+#define HWDEBUG_MK_CHAIN(_chain) (((_chain) << 13) & HWDEBUG_CHAIN_MASK)
+#define HWDEBUG_MK_TYPE(_type) (((_type) << 14) & HWDEBUG_TYPE_MASK)
+
+struct user_hwdebug_state {
+ __u32 info;
+ __u32 pad;
+ struct {
+ __u64 addr;
+ __u32 control;
+ __u32 pad;
+ } dbg_regs[16];
+};
+
struct __riscv_hwdebug_state {
__u64 addr;
__u64 type;
diff --git a/arch/riscv/kernel/ptrace.c b/arch/riscv/kernel/ptrace.c
index be076865d02d..9959a8e4a307 100644
--- a/arch/riscv/kernel/ptrace.c
+++ b/arch/riscv/kernel/ptrace.c
@@ -37,6 +37,10 @@ enum riscv_regset {
#ifdef CONFIG_RISCV_USER_CFI
REGSET_CFI,
#endif
+#ifdef CONFIG_HAVE_HW_BREAKPOINT
+ REGSET_RISCV_HW_BREAK,
+ REGSET_RISCV_HW_WATCH,
+#endif
};
static int riscv_gpr_get(struct task_struct *target,
@@ -526,6 +530,302 @@ static struct perf_event *ptrace_hbp_create(enum riscv_hbp_type hbp_type,
return bp;
}
+static int ptrace_hbp_note_to_type(unsigned int note_type,
+ enum riscv_hbp_type *hbp_type)
+{
+ switch (note_type) {
+ case NT_RISCV_HW_BREAK:
+ *hbp_type = RISCV_HBP_BREAK;
+ return 0;
+ case NT_RISCV_HW_WATCH:
+ *hbp_type = RISCV_HBP_WATCH;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ptrace_hbp_fill_attr_ctrl(enum riscv_hbp_type hbp_type,
+ struct arch_hw_breakpoint *bpctrl,
+ struct perf_event_attr *attr)
+{
+ int len, type;
+
+ attr->disabled = 0;
+ type = bpctrl->type;
+ len = bpctrl->len;
+
+ switch (hbp_type) {
+ case RISCV_HBP_BREAK:
+ if ((type & HW_BREAKPOINT_X) != type)
+ return -EINVAL;
+ break;
+ case RISCV_HBP_WATCH:
+ if ((type & HW_BREAKPOINT_RW) != type)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ attr->bp_len = len;
+ attr->bp_type = type;
+ attr->bp_addr = bpctrl->address;
+
+ return 0;
+}
+
+static int ptrace_hbp_get_resource_info(enum riscv_hbp_type hbp_type, u32 *info)
+{
+ u8 num;
+
+ /*
+ * The NT_RISCV_HW_BREAK/WATCH regset ABI exposes 16 register pairs.
+ * Cap the reported count so GETREGSET/SETREGSET never walk past the
+ * fixed UAPI array even if the platform has more triggers.
+ */
+ switch (hbp_type) {
+ case RISCV_HBP_BREAK:
+ num = min_t(unsigned int, hw_breakpoint_slots(TYPE_INST),
+ RISCV_MAX_BP);
+ break;
+ case RISCV_HBP_WATCH:
+ num = min_t(unsigned int, hw_breakpoint_slots(TYPE_DATA),
+ RISCV_MAX_BP);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *info = num;
+
+ return 0;
+}
+
+static u32 encode_ctrl_reg(struct perf_event *bp)
+{
+ struct arch_hw_breakpoint *bpctrl = counter_arch_bp(bp);
+ u32 ctrl = 0;
+
+ /* Expose the generic UAPI bp_type values in ptrace control bits. */
+ ctrl |= HWDEBUG_MK_TYPE(bp->attr.bp_type);
+ ctrl |= HWDEBUG_MK_MATCH(bpctrl->match);
+ ctrl |= HWDEBUG_MK_SELECT(bpctrl->select);
+ ctrl |= HWDEBUG_MK_WHEN(bpctrl->time);
+ ctrl |= HWDEBUG_MK_SIZE(bp->attr.bp_len);
+ ctrl |= HWDEBUG_MK_CHAIN(bpctrl->chain);
+
+ return ctrl;
+}
+
+static int ptrace_hbp_get_ctrl(enum riscv_hbp_type hbp_type,
+ struct task_struct *tsk,
+ unsigned long idx,
+ u32 *ctrl)
+{
+ struct perf_event *bp = ptrace_hbp_get_event(hbp_type, tsk, idx);
+
+ if (IS_ERR(bp))
+ return PTR_ERR(bp);
+
+ *ctrl = bp ? encode_ctrl_reg(bp) : 0;
+ return 0;
+}
+
+static int ptrace_hbp_get_addr(enum riscv_hbp_type hbp_type,
+ struct task_struct *tsk,
+ unsigned long idx,
+ u64 *addr)
+{
+ struct perf_event *bp = ptrace_hbp_get_event(hbp_type, tsk, idx);
+
+ if (IS_ERR(bp))
+ return PTR_ERR(bp);
+
+ *addr = bp ? counter_arch_bp(bp)->address : 0;
+ return 0;
+}
+
+static struct perf_event *ptrace_hbp_get_initialised_bp(enum riscv_hbp_type hbp_type,
+ struct task_struct *tsk,
+ unsigned long idx)
+{
+ struct perf_event *bp = ptrace_hbp_get_event(hbp_type, tsk, idx);
+
+ if (!bp)
+ bp = ptrace_hbp_create(hbp_type, tsk, idx);
+
+ return bp;
+}
+
+static void decode_ctrl_reg(u32 uctrl, struct arch_hw_breakpoint *bpctrl)
+{
+ bpctrl->type = HWDEBUG_TYPE(uctrl);
+ bpctrl->match = HWDEBUG_MATCH(uctrl);
+ bpctrl->select = HWDEBUG_SELECT(uctrl);
+ bpctrl->time = HWDEBUG_WHEN(uctrl);
+ bpctrl->len = HWDEBUG_SIZE(uctrl);
+ bpctrl->chain = HWDEBUG_CHAIN(uctrl);
+}
+
+static int ptrace_hbp_set_ctrl(enum riscv_hbp_type hbp_type,
+ struct task_struct *tsk,
+ unsigned long idx,
+ u32 uctrl)
+{
+ int err;
+ struct perf_event *bp;
+ struct perf_event_attr attr;
+ struct arch_hw_breakpoint bpctrl;
+
+ bp = ptrace_hbp_get_initialised_bp(hbp_type, tsk, idx);
+ if (IS_ERR(bp)) {
+ err = PTR_ERR(bp);
+ return err;
+ }
+
+ attr = bp->attr;
+ decode_ctrl_reg(uctrl, &bpctrl);
+ bpctrl.address = attr.bp_addr;
+ err = ptrace_hbp_fill_attr_ctrl(hbp_type, &bpctrl, &attr);
+ if (err)
+ return err;
+
+ return modify_user_hw_breakpoint(bp, &attr);
+}
+
+static int ptrace_hbp_set_addr(enum riscv_hbp_type hbp_type,
+ struct task_struct *tsk,
+ unsigned long idx,
+ u64 addr)
+{
+ int err;
+ struct perf_event *bp;
+ struct perf_event_attr attr;
+
+ bp = ptrace_hbp_get_initialised_bp(hbp_type, tsk, idx);
+ if (IS_ERR(bp)) {
+ err = PTR_ERR(bp);
+ return err;
+ }
+
+ attr = bp->attr;
+ attr.bp_addr = addr;
+ err = modify_user_hw_breakpoint(bp, &attr);
+ return err;
+}
+
+#define PTRACE_HBP_ADDR_SZ sizeof(u64)
+#define PTRACE_HBP_CTRL_SZ sizeof(u32)
+#define PTRACE_HBP_PAD_SZ sizeof(u32)
+
+static int riscv_hw_break_get(struct task_struct *target,
+ const struct user_regset *regset,
+ struct membuf to)
+{
+ unsigned int note_type = regset->core_note_type;
+ enum riscv_hbp_type hbp_type;
+ int ret, idx, num_slots;
+ u32 info, ctrl;
+ u64 addr;
+
+ ret = ptrace_hbp_note_to_type(note_type, &hbp_type);
+ if (ret)
+ return ret;
+
+ /* Resource info: number of available slots */
+ ret = ptrace_hbp_get_resource_info(hbp_type, &info);
+ if (ret)
+ return ret;
+
+ membuf_write(&to, &info, sizeof(info));
+ membuf_zero(&to, sizeof(u32));
+
+ /* Emit one (address, ctrl, pad) entry per available slot */
+ num_slots = (int)info;
+ for (idx = 0; idx < num_slots; idx++) {
+ ret = ptrace_hbp_get_addr(hbp_type, target, idx, &addr);
+ if (ret)
+ return ret;
+ ret = ptrace_hbp_get_ctrl(hbp_type, target, idx, &ctrl);
+ if (ret)
+ return ret;
+ membuf_store(&to, addr);
+ membuf_store(&to, ctrl);
+ membuf_zero(&to, sizeof(u32));
+ }
+ return 0;
+}
+
+static int riscv_hw_break_set(struct task_struct *target,
+ const struct user_regset *regset,
+ unsigned int pos, unsigned int count,
+ const void *kbuf, const void __user *ubuf)
+{
+ unsigned int note_type = regset->core_note_type;
+ enum riscv_hbp_type hbp_type;
+ int ret, idx = 0, offset, limit;
+ u32 ctrl;
+ u64 addr;
+
+ ret = ptrace_hbp_note_to_type(note_type, &hbp_type);
+ if (ret)
+ return ret;
+
+ /* Resource info and pad */
+ offset = offsetof(struct user_hwdebug_state, dbg_regs);
+ user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, 0, offset);
+
+ /* (address, ctrl) registers */
+ limit = regset->n * regset->size;
+ while (count && offset < limit) {
+ if (count < PTRACE_HBP_ADDR_SZ)
+ return -EINVAL;
+
+ /* user_regset_copyin() can do partial copies for odd offsets. */
+ addr = 0;
+ ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &addr,
+ offset, offset + PTRACE_HBP_ADDR_SZ);
+ if (ret)
+ return ret;
+
+ ret = ptrace_hbp_set_addr(hbp_type, target, idx, addr);
+ if (ret)
+ return ret;
+
+ offset += PTRACE_HBP_ADDR_SZ;
+
+ if (!count)
+ break;
+
+ /*
+ * Reject undersized control writes, and keep ctrl initialized in
+ * case user_regset_copyin() still performs a partial copy.
+ */
+ if (count < PTRACE_HBP_CTRL_SZ)
+ return -EINVAL;
+
+ ctrl = 0;
+ ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &ctrl,
+ offset, offset + PTRACE_HBP_CTRL_SZ);
+ if (ret)
+ return ret;
+
+ ret = ptrace_hbp_set_ctrl(hbp_type, target, idx, ctrl);
+ if (ret)
+ return ret;
+
+ offset += PTRACE_HBP_CTRL_SZ;
+
+ user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf,
+ offset, offset + PTRACE_HBP_PAD_SZ);
+ offset += PTRACE_HBP_PAD_SZ;
+ idx++;
+ }
+
+ return 0;
+}
+
#endif /* CONFIG_HAVE_HW_BREAKPOINT */
static struct user_regset riscv_user_regset[] __ro_after_init = {
@@ -577,6 +877,24 @@ static struct user_regset riscv_user_regset[] __ro_after_init = {
.set = riscv_cfi_set,
},
#endif
+#ifdef CONFIG_HAVE_HW_BREAKPOINT
+ [REGSET_RISCV_HW_BREAK] = {
+ USER_REGSET_NOTE_TYPE(RISCV_HW_BREAK),
+ .n = sizeof(struct user_hwdebug_state) / sizeof(u32),
+ .size = sizeof(u32),
+ .align = sizeof(u32),
+ .regset_get = riscv_hw_break_get,
+ .set = riscv_hw_break_set,
+ },
+ [REGSET_RISCV_HW_WATCH] = {
+ USER_REGSET_NOTE_TYPE(RISCV_HW_WATCH),
+ .n = sizeof(struct user_hwdebug_state) / sizeof(u32),
+ .size = sizeof(u32),
+ .align = sizeof(u32),
+ .regset_get = riscv_hw_break_get,
+ .set = riscv_hw_break_set,
+ },
+#endif
};
static const struct user_regset_view riscv_user_native_view = {
@@ -919,6 +1237,28 @@ static const struct user_regset compat_riscv_user_regset[] = {
.set = riscv_fpr_set,
},
#endif
+#ifdef CONFIG_HAVE_HW_BREAKPOINT
+ /*
+ * The hardware-debug regset uses fixed-width UAPI fields, so the
+ * native layout is also the compat layout.
+ */
+ [REGSET_RISCV_HW_BREAK] = {
+ USER_REGSET_NOTE_TYPE(RISCV_HW_BREAK),
+ .n = sizeof(struct user_hwdebug_state) / sizeof(u32),
+ .size = sizeof(u32),
+ .align = sizeof(u32),
+ .regset_get = riscv_hw_break_get,
+ .set = riscv_hw_break_set,
+ },
+ [REGSET_RISCV_HW_WATCH] = {
+ USER_REGSET_NOTE_TYPE(RISCV_HW_WATCH),
+ .n = sizeof(struct user_hwdebug_state) / sizeof(u32),
+ .size = sizeof(u32),
+ .align = sizeof(u32),
+ .regset_get = riscv_hw_break_get,
+ .set = riscv_hw_break_set,
+ },
+#endif
};
static const struct user_regset_view compat_riscv_user_native_view = {
diff --git a/include/uapi/linux/elf.h b/include/uapi/linux/elf.h
index ee30dcd80901..1315ac35157c 100644
--- a/include/uapi/linux/elf.h
+++ b/include/uapi/linux/elf.h
@@ -547,6 +547,10 @@ typedef struct elf64_shdr {
#define NT_RISCV_TAGGED_ADDR_CTRL 0x902 /* RISC-V tagged address control (prctl()) */
#define NN_RISCV_USER_CFI "LINUX"
#define NT_RISCV_USER_CFI 0x903 /* RISC-V shadow stack state */
+#define NN_RISCV_HW_BREAK "LINUX"
+#define NT_RISCV_HW_BREAK 0x904 /* RISC-V hardware breakpoint registers */
+#define NN_RISCV_HW_WATCH "LINUX"
+#define NT_RISCV_HW_WATCH 0x905 /* RISCV-V hardware watchpoint registers */
#define NN_LOONGARCH_CPUCFG "LINUX"
#define NT_LOONGARCH_CPUCFG 0xa00 /* LoongArch CPU config registers */
#define NN_LOONGARCH_CSR "LINUX"
diff --git a/tools/include/uapi/linux/elf.h b/tools/include/uapi/linux/elf.h
index 5834b83d7f9a..21f225502051 100644
--- a/tools/include/uapi/linux/elf.h
+++ b/tools/include/uapi/linux/elf.h
@@ -460,6 +460,8 @@ typedef struct elf64_shdr {
#define NT_RISCV_CSR 0x900 /* RISC-V Control and Status Registers */
#define NT_RISCV_VECTOR 0x901 /* RISC-V vector registers */
#define NT_RISCV_TAGGED_ADDR_CTRL 0x902 /* RISC-V tagged address control (prctl()) */
+#define NT_RISCV_HW_BREAK 0x904
+#define NT_RISCV_HW_WATCH 0x905
#define NT_LOONGARCH_CPUCFG 0xa00 /* LoongArch CPU config registers */
#define NT_LOONGARCH_CSR 0xa01 /* LoongArch control and status registers */
#define NT_LOONGARCH_LSX 0xa02 /* LoongArch Loongson SIMD Extension registers */
--
2.43.0
next prev parent reply other threads:[~2026-11-07 2:40 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-07 2:39 [PATCH v8 0/8] riscv: Introduce support for hardware break/watchpoints Himanshu Chauhan
2026-11-07 2:39 ` [PATCH v8 1/8] " Himanshu Chauhan
2026-11-07 2:39 ` [PATCH v8 2/8] riscv: Add breakpoint and watchpoint test for riscv Himanshu Chauhan
2026-11-07 2:39 ` [PATCH v8 3/8] riscv: add ptrace requests for hardware breakpoints Himanshu Chauhan
2026-11-07 7:04 ` kernel test robot
2026-11-07 2:39 ` Himanshu Chauhan [this message]
2026-11-07 2:39 ` [PATCH v8 5/8] selftests/breakpoints: extend riscv test for ptrace hw break/watchpoints Himanshu Chauhan
2026-11-07 2:39 ` [PATCH v8 6/8] riscv: Add shared instruction fetch and decode helpers Himanshu Chauhan
2026-11-07 2:39 ` [PATCH v8 7/8] riscv: Add software supported single stepping with mc/mc6 triggers Himanshu Chauhan
2026-11-07 2:39 ` [PATCH v8 8/8] perf tests: keep bp_signal memory accesses visible Himanshu Chauhan
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261107023917.40606-5-himanshu.chauhan@oss.qualcomm.com \
--to=himanshu.chauhan@oss.qualcomm.com \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=alex@ghiti.fr \
--cc=alexander.shishkin@linux.intel.com \
--cc=aou@eecs.berkeley.edu \
--cc=irogers@google.com \
--cc=james.clark@linaro.org \
--cc=jolsa@kernel.org \
--cc=jtaubepe@redhat.com \
--cc=kees@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=linux-riscv@lists.infradead.org \
--cc=mark.rutland@arm.com \
--cc=mingo@redhat.com \
--cc=namhyung@kernel.org \
--cc=oleg@redhat.com \
--cc=palmer@dabbelt.com \
--cc=peterz@infradead.org \
--cc=pjw@kernel.org \
--cc=shuah@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®