mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v4 0/6] LoongArch: Fix perf hardware breakpoints
@ 2026-09-24 14:27 Tiezhu Yang
  2026-09-24 14:27 ` [PATCH v4 1/6] LoongArch: Fix bitmask corruption in update_bp_registers() Tiezhu Yang
                   ` (6 more replies)
  0 siblings, 7 replies; 8+ messages in thread
From: Tiezhu Yang @ 2026-09-24 14:27 UTC (permalink / raw)
  To: Huacai Chen; +Cc: loongarch, linux-kernel

v4:
  -- Do not modify thread_struct layout for hardware breakpoints

This series addresses severe functional degradations in standard
perf_event usage, including initialization failures and one-shot
triggering limitations.

Tiezhu Yang (6):
  LoongArch: Fix bitmask corruption in update_bp_registers()
  LoongArch: Remove redundant call in update_bp_registers()
  LoongArch: Fix architectural naming typo for CSR_FWPS_SKIP
  LoongArch: Only send SIGTRAP signal if necessary in do_watch()
  LoongArch: Fix perf hardware breakpoint failure via installation
  LoongArch: Fix one-shot limitation for perf hardware breakpoints

 arch/loongarch/include/asm/hw_breakpoint.h |  8 +--
 arch/loongarch/include/asm/loongarch.h     |  7 ++-
 arch/loongarch/include/asm/processor.h     |  3 +
 arch/loongarch/include/asm/switch_to.h     |  2 +-
 arch/loongarch/kernel/hw_breakpoint.c      | 66 ++++++++++++++++++----
 arch/loongarch/kernel/traps.c              | 12 ++--
 6 files changed, 74 insertions(+), 24 deletions(-)

-- 
2.42.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v4 1/6] LoongArch: Fix bitmask corruption in update_bp_registers()
  2026-09-24 14:27 [PATCH v4 0/6] LoongArch: Fix perf hardware breakpoints Tiezhu Yang
@ 2026-09-24 14:27 ` Tiezhu Yang
  2026-09-24 14:27 ` [PATCH v4 2/6] LoongArch: Remove redundant call " Tiezhu Yang
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Tiezhu Yang @ 2026-09-24 14:27 UTC (permalink / raw)
  To: Huacai Chen; +Cc: loongarch, linux-kernel

In update_bp_registers(), when disabling a LOAD or STORE watchpoint,
the code attempts to clear the bit of LoadEn or StoreEn by using the
standard bit-clearing pattern.

However, due to the omission of parentheses, the bitwise NOT operator
'~' takes higher precedence than the left shift operator '<<'. Then:
(1) ~0x1 << MWPnCFG3_LoadEn evaluates to "(~0x1) << 8 = 0xFFFFFE00",
(2) ~0x1 << MWPnCFG3_StoreEn evaluates to "(~0x1) << 9 = 0xFFFFFC00",
that incorrectly clears all configuration fields in the lower 8 bits.

These fields in the lower 8 bits contain critical configurations such
as DSOnly (bit 0), PLV0-PLV3 privilege levels (bits 1-4), and LCL (bit
7). Writing back these corrupted values severely breaks configuration
isolation and ruins the hardware watchpoint states.

Add parentheses to ensure the correct evaluation order, so that only
the targeted LoadEn/StoreEn bit is cleared while preserving the other
crucial configuration bits in the lower 8 bits.

Fixes: edffa33c7bb5 ("LoongArch: Add hardware breakpoints/watchpoints support")
Cc: stable@vger.kernel.org
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
---
 arch/loongarch/kernel/hw_breakpoint.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/arch/loongarch/kernel/hw_breakpoint.c b/arch/loongarch/kernel/hw_breakpoint.c
index c35f9bf38033..6ba7315852f3 100644
--- a/arch/loongarch/kernel/hw_breakpoint.c
+++ b/arch/loongarch/kernel/hw_breakpoint.c
@@ -469,9 +469,9 @@ static void update_bp_registers(struct pt_regs *regs, int enable, int type)
 			} else {
 				ctrl = read_wb_reg(CSR_CFG_CTRL, i, 1);
 				if (info->ctrl.type == LOONGARCH_BREAKPOINT_LOAD)
-					ctrl &= ~0x1 << MWPnCFG3_LoadEn;
+					ctrl &= ~(0x1 << MWPnCFG3_LoadEn);
 				if (info->ctrl.type == LOONGARCH_BREAKPOINT_STORE)
-					ctrl &= ~0x1 << MWPnCFG3_StoreEn;
+					ctrl &= ~(0x1 << MWPnCFG3_StoreEn);
 				write_wb_reg(CSR_CFG_CTRL, i, 1, ctrl);
 			}
 			regs->csr_prmd &= ~CSR_PRMD_PWE;
-- 
2.42.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v4 2/6] LoongArch: Remove redundant call in update_bp_registers()
  2026-09-24 14:27 [PATCH v4 0/6] LoongArch: Fix perf hardware breakpoints Tiezhu Yang
  2026-09-24 14:27 ` [PATCH v4 1/6] LoongArch: Fix bitmask corruption in update_bp_registers() Tiezhu Yang
@ 2026-09-24 14:27 ` Tiezhu Yang
  2026-09-24 14:27 ` [PATCH v4 3/6] LoongArch: Fix architectural naming typo for CSR_FWPS_SKIP Tiezhu Yang
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Tiezhu Yang @ 2026-09-24 14:27 UTC (permalink / raw)
  To: Huacai Chen; +Cc: loongarch, linux-kernel

In update_bp_registers(), there is a duplicate call to write_wb_reg()
when enabling an execute breakpoint:

    write_wb_reg(CSR_CFG_CTRL, i, 0, CTRL_PLV_ENABLE);
    write_wb_reg(CSR_CFG_CTRL, i, 0, CTRL_PLV_ENABLE);

The two lines are completely identical. Remove the redundant call to
clean up the code and eliminate unnecessary register writes.

Fixes: edffa33c7bb5 ("LoongArch: Add hardware breakpoints/watchpoints support")
Cc: stable@vger.kernel.org
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
---
 arch/loongarch/kernel/hw_breakpoint.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/arch/loongarch/kernel/hw_breakpoint.c b/arch/loongarch/kernel/hw_breakpoint.c
index 6ba7315852f3..bd891b3808a6 100644
--- a/arch/loongarch/kernel/hw_breakpoint.c
+++ b/arch/loongarch/kernel/hw_breakpoint.c
@@ -453,7 +453,6 @@ static void update_bp_registers(struct pt_regs *regs, int enable, int type)
 		if (enable) {
 			if ((info->ctrl.type == LOONGARCH_BREAKPOINT_EXECUTE) && (type == 0)) {
 				write_wb_reg(CSR_CFG_CTRL, i, 0, CTRL_PLV_ENABLE);
-				write_wb_reg(CSR_CFG_CTRL, i, 0, CTRL_PLV_ENABLE);
 			} else {
 				ctrl = read_wb_reg(CSR_CFG_CTRL, i, 1);
 				if (info->ctrl.type == LOONGARCH_BREAKPOINT_LOAD)
-- 
2.42.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v4 3/6] LoongArch: Fix architectural naming typo for CSR_FWPS_SKIP
  2026-09-24 14:27 [PATCH v4 0/6] LoongArch: Fix perf hardware breakpoints Tiezhu Yang
  2026-09-24 14:27 ` [PATCH v4 1/6] LoongArch: Fix bitmask corruption in update_bp_registers() Tiezhu Yang
  2026-09-24 14:27 ` [PATCH v4 2/6] LoongArch: Remove redundant call " Tiezhu Yang
@ 2026-09-24 14:27 ` Tiezhu Yang
  2026-09-24 14:27 ` [PATCH v4 4/6] LoongArch: Only send SIGTRAP signal if necessary in do_watch() Tiezhu Yang
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Tiezhu Yang @ 2026-09-24 14:27 UTC (permalink / raw)
  To: Huacai Chen; +Cc: loongarch, linux-kernel

According to the LoongArch Reference Manual, the single-step 'Skip'
bit resides in the instruction breakpoint status register CSR.FWPS,
rather than the configuration register CSR.FWPC.

Furthermore, the kernel code comments also explicitly document it
as "CSR.FWPS.Skip", yet the actual macro was erroneously defined
as CSR_FWPC_SKIP and used as such in traps.c and hw_breakpoint.c.
This mismatch creates architectural naming confusion.

Fix this by renaming the macro from CSR_FWPC_SKIP to CSR_FWPS_SKIP
to precisely match the manual and comments. Also, update all call
sites to align with the rectified definition.

Fixes: 424421a7f34c ("LoongArch: ptrace: Add hardware single step support")
Cc: stable@vger.kernel.org
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
---
 arch/loongarch/include/asm/loongarch.h | 4 ++--
 arch/loongarch/kernel/hw_breakpoint.c  | 2 +-
 arch/loongarch/kernel/traps.c          | 4 ++--
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/arch/loongarch/include/asm/loongarch.h b/arch/loongarch/include/asm/loongarch.h
index 2a6bc99177d8..32bbff337c5c 100644
--- a/arch/loongarch/include/asm/loongarch.h
+++ b/arch/loongarch/include/asm/loongarch.h
@@ -1130,8 +1130,8 @@
 #define LOONGARCH_CSR_DERA		0x501	/* debug era */
 #define LOONGARCH_CSR_DESAVE		0x502	/* debug save */
 
-#define CSR_FWPC_SKIP_SHIFT		16
-#define CSR_FWPC_SKIP			(_ULCAST_(1) << CSR_FWPC_SKIP_SHIFT)
+#define CSR_FWPS_SKIP_SHIFT		16
+#define CSR_FWPS_SKIP			(_ULCAST_(1) << CSR_FWPS_SKIP_SHIFT)
 
 /*
  * CSR_ECFG IM
diff --git a/arch/loongarch/kernel/hw_breakpoint.c b/arch/loongarch/kernel/hw_breakpoint.c
index bd891b3808a6..7f69cf361a5e 100644
--- a/arch/loongarch/kernel/hw_breakpoint.c
+++ b/arch/loongarch/kernel/hw_breakpoint.c
@@ -550,7 +550,7 @@ void hw_breakpoint_thread_switch(struct task_struct *next)
 		addr = read_wb_reg(CSR_CFG_ADDR, 0, 0);
 		mask = read_wb_reg(CSR_CFG_MASK, 0, 0);
 		if (!((regs->csr_era ^ addr) & ~mask))
-			csr_write32(CSR_FWPC_SKIP, LOONGARCH_CSR_FWPS);
+			csr_write32(CSR_FWPS_SKIP, LOONGARCH_CSR_FWPS);
 		regs->csr_prmd |= CSR_PRMD_PWE;
 	} else {
 		/* Update breakpoints */
diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c
index 776523747ea3..c4d7e55fb3ea 100644
--- a/arch/loongarch/kernel/traps.c
+++ b/arch/loongarch/kernel/traps.c
@@ -829,7 +829,7 @@ asmlinkage void noinstr do_watch(struct pt_regs *regs)
 			 * instruction. So don't clear llbit and reset CSR.FWPS.Skip until
 			 * the llsc execution is completed.
 			 */
-			csr_write32(CSR_FWPC_SKIP, LOONGARCH_CSR_FWPS);
+			csr_write32(CSR_FWPS_SKIP, LOONGARCH_CSR_FWPS);
 			csr_write32(CSR_LLBCTL_KLO, LOONGARCH_CSR_LLBCTL);
 			goto out;
 		}
@@ -846,7 +846,7 @@ asmlinkage void noinstr do_watch(struct pt_regs *regs)
 				 * current pc, If yes, then we should not set the CSR.FWPS.SKIP
 				 * bit to break the original instruction stream.
 				 */
-				csr_write32(CSR_FWPC_SKIP, LOONGARCH_CSR_FWPS);
+				csr_write32(CSR_FWPS_SKIP, LOONGARCH_CSR_FWPS);
 				goto out;
 			}
 		}
-- 
2.42.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v4 4/6] LoongArch: Only send SIGTRAP signal if necessary in do_watch()
  2026-09-24 14:27 [PATCH v4 0/6] LoongArch: Fix perf hardware breakpoints Tiezhu Yang
                   ` (2 preceding siblings ...)
  2026-09-24 14:27 ` [PATCH v4 3/6] LoongArch: Fix architectural naming typo for CSR_FWPS_SKIP Tiezhu Yang
@ 2026-09-24 14:27 ` Tiezhu Yang
  2026-09-24 14:27 ` [PATCH v4 5/6] LoongArch: Fix perf hardware breakpoint failure via installation Tiezhu Yang
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Tiezhu Yang @ 2026-09-24 14:27 UTC (permalink / raw)
  To: Huacai Chen; +Cc: loongarch, linux-kernel

In do_watch(), the kernel unconditionally forces a SIGTRAP signal at the
end of the handler via force_sig(SIGTRAP). This is essential for ptrace
operations, but it severely disrupts standard perf_event usage.

Under normal perf usage, it is not necessary to force a SIGTRAP signal
on every hit. Forcing it unnecessarily aborts the target process if no
user-space signal handler is registered, which violates performance
monitoring behaviors and injects context switch overheads.

However, a conditional check using only 'current->ptrace' would block
legitimate user-requested signals when a perf_event is configured with
'attr.sigtrap = 1' for asynchronous user-space tracking.

Fix this by refactoring breakpoint_handler() and watchpoint_handler()
to return a boolean status indicating whether any triggered breakpoint
explicitly requires a signal notification. Update do_watch() to enforce
the SIGTRAP signal only when the process is actively being debugged via
ptrace, or when the underlying perf_event infrastructure specifically
demands it via 'attr.sigtrap'.

Fixes: 424421a7f34c ("LoongArch: ptrace: Add hardware single step support")
Cc: stable@vger.kernel.org
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
---
 arch/loongarch/include/asm/hw_breakpoint.h |  4 ++--
 arch/loongarch/kernel/hw_breakpoint.c      | 18 ++++++++++++++++--
 arch/loongarch/kernel/traps.c              |  8 +++++---
 3 files changed, 23 insertions(+), 7 deletions(-)

diff --git a/arch/loongarch/include/asm/hw_breakpoint.h b/arch/loongarch/include/asm/hw_breakpoint.h
index 5faa97a87a9e..d202052df8a1 100644
--- a/arch/loongarch/include/asm/hw_breakpoint.h
+++ b/arch/loongarch/include/asm/hw_breakpoint.h
@@ -116,8 +116,8 @@ extern void arch_uninstall_hw_breakpoint(struct perf_event *bp);
 extern int hw_breakpoint_slots(int type);
 extern void hw_breakpoint_pmu_read(struct perf_event *bp);
 
-void breakpoint_handler(struct pt_regs *regs);
-void watchpoint_handler(struct pt_regs *regs);
+bool breakpoint_handler(struct pt_regs *regs);
+bool watchpoint_handler(struct pt_regs *regs);
 
 #ifdef CONFIG_HAVE_HW_BREAKPOINT
 extern void ptrace_hw_copy_thread(struct task_struct *task);
diff --git a/arch/loongarch/kernel/hw_breakpoint.c b/arch/loongarch/kernel/hw_breakpoint.c
index 7f69cf361a5e..3683a52b2368 100644
--- a/arch/loongarch/kernel/hw_breakpoint.c
+++ b/arch/loongarch/kernel/hw_breakpoint.c
@@ -482,10 +482,11 @@ NOKPROBE_SYMBOL(update_bp_registers);
 /*
  * Debug exception handlers.
  */
-void breakpoint_handler(struct pt_regs *regs)
+bool breakpoint_handler(struct pt_regs *regs)
 {
 	int i;
 	struct perf_event *bp, **slots;
+	bool need_sigtrap = false;
 
 	slots = this_cpu_ptr(bp_on_reg);
 
@@ -494,18 +495,25 @@ void breakpoint_handler(struct pt_regs *regs)
 			bp = slots[i];
 			if (bp == NULL)
 				continue;
+
 			perf_bp_event(bp, regs);
+			if (bp->attr.sigtrap)
+				need_sigtrap = true;
+
 			csr_write32(0x1 << i, LOONGARCH_CSR_FWPS);
 			update_bp_registers(regs, 0, 0);
 		}
 	}
+
+	return need_sigtrap;
 }
 NOKPROBE_SYMBOL(breakpoint_handler);
 
-void watchpoint_handler(struct pt_regs *regs)
+bool watchpoint_handler(struct pt_regs *regs)
 {
 	int i;
 	struct perf_event *wp, **slots;
+	bool need_sigtrap = false;
 
 	slots = this_cpu_ptr(wp_on_reg);
 
@@ -514,11 +522,17 @@ void watchpoint_handler(struct pt_regs *regs)
 			wp = slots[i];
 			if (wp == NULL)
 				continue;
+
 			perf_bp_event(wp, regs);
+			if (wp->attr.sigtrap)
+				need_sigtrap = true;
+
 			csr_write32(0x1 << i, LOONGARCH_CSR_MWPS);
 			update_bp_registers(regs, 0, 1);
 		}
 	}
+
+	return need_sigtrap;
 }
 NOKPROBE_SYMBOL(watchpoint_handler);
 
diff --git a/arch/loongarch/kernel/traps.c b/arch/loongarch/kernel/traps.c
index c4d7e55fb3ea..4cbf84b148cd 100644
--- a/arch/loongarch/kernel/traps.c
+++ b/arch/loongarch/kernel/traps.c
@@ -811,6 +811,7 @@ asmlinkage void noinstr do_bp(struct pt_regs *regs)
 asmlinkage void noinstr do_watch(struct pt_regs *regs)
 {
 	irqentry_state_t state = irqentry_enter(regs);
+	bool need_sigtrap = !!current->ptrace;
 
 #ifndef CONFIG_HAVE_HW_BREAKPOINT
 	pr_warn("Hardware watch point handler not implemented!\n");
@@ -851,11 +852,12 @@ asmlinkage void noinstr do_watch(struct pt_regs *regs)
 			}
 		}
 	} else {
-		breakpoint_handler(regs);
-		watchpoint_handler(regs);
+		need_sigtrap |= breakpoint_handler(regs);
+		need_sigtrap |= watchpoint_handler(regs);
 	}
 
-	force_sig(SIGTRAP);
+	if (need_sigtrap)
+		force_sig(SIGTRAP);
 out:
 #endif
 	irqentry_exit(regs, state);
-- 
2.42.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v4 5/6] LoongArch: Fix perf hardware breakpoint failure via installation
  2026-09-24 14:27 [PATCH v4 0/6] LoongArch: Fix perf hardware breakpoints Tiezhu Yang
                   ` (3 preceding siblings ...)
  2026-09-24 14:27 ` [PATCH v4 4/6] LoongArch: Only send SIGTRAP signal if necessary in do_watch() Tiezhu Yang
@ 2026-09-24 14:27 ` Tiezhu Yang
  2026-09-24 14:27 ` [PATCH v4 6/6] LoongArch: Fix one-shot limitation for perf hardware breakpoints Tiezhu Yang
  2026-10-03  4:01 ` [PATCH v4 0/6] LoongArch: Fix " Huacai Chen
  6 siblings, 0 replies; 8+ messages in thread
From: Tiezhu Yang @ 2026-09-24 14:27 UTC (permalink / raw)
  To: Huacai Chen; +Cc: loongarch, linux-kernel

In hw_breakpoint_control(), the logic to enable CSR_PRMD_PWE (global
watchpoint enable) incorrectly relies on TIF_LOAD_WATCH. This thread
flag is only set during ptrace operations, meaning that the standard
hardware breakpoints created via perf_event_open() will never have
this flag set.

As a result, the CSR_PRMD_PWE switch is skipped, leaving the hardware
breakpoint completely inactive in perf usage.

Fix this by decoupling the CSR_PRMD_PWE from the ptrace specific flag
TIF_LOAD_WATCH, which ensures that CSR_PRMD_PWE is enabled whenever a
hardware breakpoint is installed.

Here is a user-space reproducer to demonstrate the issue:

(1) Test program (test_perf_install.c):

  #include <stdio.h>
  #include <unistd.h>
  #include <sys/syscall.h>
  #include <sys/ioctl.h>
  #include <linux/perf_event.h>
  #include <linux/hw_breakpoint.h>

  static int var = 0;

  int main(void)
  {
  	size_t count = 0;
  	struct perf_event_attr attr = {
  		.type = PERF_TYPE_BREAKPOINT,
  		.size = sizeof(attr),
  		.bp_type = HW_BREAKPOINT_W,
  		.bp_addr = (unsigned long)&var,
  		.bp_len = HW_BREAKPOINT_LEN_1,
  		.exclude_kernel = 1,
  	};

  	int fd = syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0);
  	ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);

  	asm volatile("st.b %1, %0" : "=m"(var) : "r"(11) : "memory");

  	ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
  	read(fd, &count, sizeof(size_t));

  	printf("Watchpoint counts: expected = 1, actual = %zu\n", count);

  	close(fd);
  	return 0;
  }

(2) Test steps:

  $ gcc test_perf_install.c -o test_perf_install
  $ ./test_perf_install

(3) Test results:

Without this patch:

  Watchpoint counts: expected = 1, actual = 0

With this patch:

  Watchpoint counts: expected = 1, actual = 1

Fixes: 3892b11eac5a ("LoongArch: Check TIF_LOAD_WATCH to enable user space watchpoint")
Cc: stable@vger.kernel.org
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
---
 arch/loongarch/kernel/hw_breakpoint.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/loongarch/kernel/hw_breakpoint.c b/arch/loongarch/kernel/hw_breakpoint.c
index 3683a52b2368..9dcb122218c2 100644
--- a/arch/loongarch/kernel/hw_breakpoint.c
+++ b/arch/loongarch/kernel/hw_breakpoint.c
@@ -233,7 +233,7 @@ static int hw_breakpoint_control(struct perf_event *bp,
 		}
 		enable = csr_read64(LOONGARCH_CSR_CRMD);
 		csr_write64(CSR_CRMD_WE | enable, LOONGARCH_CSR_CRMD);
-		if (bp->hw.target && test_tsk_thread_flag(bp->hw.target, TIF_LOAD_WATCH))
+		if (bp->hw.target)
 			regs->csr_prmd |= CSR_PRMD_PWE;
 		break;
 	case HW_BREAKPOINT_UNINSTALL:
-- 
2.42.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH v4 6/6] LoongArch: Fix one-shot limitation for perf hardware breakpoints
  2026-09-24 14:27 [PATCH v4 0/6] LoongArch: Fix perf hardware breakpoints Tiezhu Yang
                   ` (4 preceding siblings ...)
  2026-09-24 14:27 ` [PATCH v4 5/6] LoongArch: Fix perf hardware breakpoint failure via installation Tiezhu Yang
@ 2026-09-24 14:27 ` Tiezhu Yang
  2026-10-03  4:01 ` [PATCH v4 0/6] LoongArch: Fix " Huacai Chen
  6 siblings, 0 replies; 8+ messages in thread
From: Tiezhu Yang @ 2026-09-24 14:27 UTC (permalink / raw)
  To: Huacai Chen; +Cc: loongarch, linux-kernel

In breakpoint_handler() and watchpoint_handler(), the code disables the
hardware slot by executing update_bp_registers(regs, 0, ...). This design
forces the active hardware breakpoint configuration to be wiped out upon
its first hit, turning standard perf hardware breakpoints into "one-shot"
events.

Furthermore, while the ptrace single-step path in do_watch() executes a
hardware single-step skip mechanism to advance the PC, the standard perf
path lacks any mechanism to bypass the original triggering instruction.

To maintain the long-term persistence of hardware breakpoints for perf
usage, eliminate the disruptive calls to update_bp_registers() within
the handler loops to keep the breakpoint configuration enabled.

Concurrently, execute a single, unified register write outside the loop
to atomize the state transition and explicitly enforce the hardware skip
mechanism once per exception return, ensuring the processor safely steps
forward without lockups.

Meanwhile, in order to prevent status contamination during single-core
context switches or cross-core thread migrations, the hardware register
skip state of the previous thread is saved first. Then, the skip state
is restored and its software skip flag is also cleared only if the next
thread has the skip flag; otherwise, 0 is written to clear the FWPS and
MWPS registers, which explicitly clears the skip bit according to the
architectural specification.

Additionally, explicitly clear hbp_break_skip and hbp_watch_skip inside
ptrace_hw_copy_thread() which is called by copy_thread() during task
creation. This guarantees that a newly cloned child task starts with a
clean slate.

Here is a user-space reproducer to demonstrate the issue.

(1) Test program (test_perf_continuous.c):

  #include <stdio.h>
  #include <unistd.h>
  #include <sys/syscall.h>
  #include <sys/ioctl.h>
  #include <linux/perf_event.h>
  #include <linux/hw_breakpoint.h>

  static int var = 0;

  int main(void)
  {
  	size_t count = 0;
  	struct perf_event_attr attr = {
  		.type = PERF_TYPE_BREAKPOINT,
  		.size = sizeof(attr),
  		.bp_type = HW_BREAKPOINT_W,
  		.bp_addr = (unsigned long)&var,
  		.bp_len = HW_BREAKPOINT_LEN_1,
  		.exclude_kernel = 1,
  	};

  	int fd = syscall(__NR_perf_event_open, &attr, 0, -1, -1, 0);
  	ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);

  	asm volatile("st.b %1, %0" : "=m"(var) : "r"(11) : "memory");
  	asm volatile("st.b %1, %0" : "=m"(var) : "r"(22) : "memory");
  	asm volatile("st.b %1, %0" : "=m"(var) : "r"(33) : "memory");

  	ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
  	read(fd, &count, sizeof(size_t));

  	printf("Watchpoint counts: expected = 3, actual = %zu\n", count);

  	close(fd);
  	return 0;
  }

(2) Test steps:

  $ gcc test_perf_continuous.c -o test_perf_continuous
  $ ./test_perf_continuous

(3) Test results:

Without this patch:

  Watchpoint counts: expected = 3, actual = 1

With this patch:

  Watchpoint counts: expected = 3, actual = 3

Fixes: 3eb2a8b23598 ("LoongArch: Fix multiple hardware watchpoint issues")
Cc: stable@vger.kernel.org
Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn>
---
 arch/loongarch/include/asm/hw_breakpoint.h |  4 +--
 arch/loongarch/include/asm/loongarch.h     |  3 ++
 arch/loongarch/include/asm/processor.h     |  3 ++
 arch/loongarch/include/asm/switch_to.h     |  2 +-
 arch/loongarch/kernel/hw_breakpoint.c      | 39 +++++++++++++++++++---
 5 files changed, 43 insertions(+), 8 deletions(-)

diff --git a/arch/loongarch/include/asm/hw_breakpoint.h b/arch/loongarch/include/asm/hw_breakpoint.h
index d202052df8a1..f4478936787d 100644
--- a/arch/loongarch/include/asm/hw_breakpoint.h
+++ b/arch/loongarch/include/asm/hw_breakpoint.h
@@ -121,12 +121,12 @@ bool watchpoint_handler(struct pt_regs *regs);
 
 #ifdef CONFIG_HAVE_HW_BREAKPOINT
 extern void ptrace_hw_copy_thread(struct task_struct *task);
-extern void hw_breakpoint_thread_switch(struct task_struct *next);
+extern void hw_breakpoint_thread_switch(struct task_struct *prev, struct task_struct *next);
 #else
 static inline void ptrace_hw_copy_thread(struct task_struct *task)
 {
 }
-static inline void hw_breakpoint_thread_switch(struct task_struct *next)
+static inline void hw_breakpoint_thread_switch(struct task_struct *prev, struct task_struct *next)
 {
 }
 #endif
diff --git a/arch/loongarch/include/asm/loongarch.h b/arch/loongarch/include/asm/loongarch.h
index 32bbff337c5c..28eff8b49d1b 100644
--- a/arch/loongarch/include/asm/loongarch.h
+++ b/arch/loongarch/include/asm/loongarch.h
@@ -1133,6 +1133,9 @@
 #define CSR_FWPS_SKIP_SHIFT		16
 #define CSR_FWPS_SKIP			(_ULCAST_(1) << CSR_FWPS_SKIP_SHIFT)
 
+#define CSR_MWPS_SKIP_SHIFT		16
+#define CSR_MWPS_SKIP			(_ULCAST_(1) << CSR_MWPS_SKIP_SHIFT)
+
 /*
  * CSR_ECFG IM
  */
diff --git a/arch/loongarch/include/asm/processor.h b/arch/loongarch/include/asm/processor.h
index ce8b953f8c79..d70d77b25872 100644
--- a/arch/loongarch/include/asm/processor.h
+++ b/arch/loongarch/include/asm/processor.h
@@ -140,6 +140,9 @@ struct thread_struct {
 	/* Hardware breakpoints pinned to this task. */
 	struct perf_event *hbp_break[LOONGARCH_MAX_BRP];
 	struct perf_event *hbp_watch[LOONGARCH_MAX_WRP];
+
+	bool hbp_break_skip;
+	bool hbp_watch_skip;
 };
 
 #define thread_saved_ra(tsk)	(tsk->thread.sched_ra)
diff --git a/arch/loongarch/include/asm/switch_to.h b/arch/loongarch/include/asm/switch_to.h
index 27acbf913774..ab9e0292c7f3 100644
--- a/arch/loongarch/include/asm/switch_to.h
+++ b/arch/loongarch/include/asm/switch_to.h
@@ -34,7 +34,7 @@ extern asmlinkage struct task_struct *__switch_to(struct task_struct *prev,
 do {										\
 	lose_fpu_inatomic(1, prev);						\
 	lose_lbt_inatomic(1, prev);						\
-	hw_breakpoint_thread_switch(next);					\
+	hw_breakpoint_thread_switch(prev, next);				\
 	set_current(next);							\
 	(last) = __switch_to(prev, next,					\
 		 __builtin_return_address(0), __builtin_frame_address(0));	\
diff --git a/arch/loongarch/kernel/hw_breakpoint.c b/arch/loongarch/kernel/hw_breakpoint.c
index 9dcb122218c2..fc0bf9f402bc 100644
--- a/arch/loongarch/kernel/hw_breakpoint.c
+++ b/arch/loongarch/kernel/hw_breakpoint.c
@@ -156,6 +156,9 @@ static int hw_breakpoint_slot_setup(struct perf_event **slots, int max_slots,
 
 void ptrace_hw_copy_thread(struct task_struct *tsk)
 {
+	tsk->thread.hbp_break_skip = 0;
+	tsk->thread.hbp_watch_skip = 0;
+
 	memset(tsk->thread.hbp_break, 0, sizeof(tsk->thread.hbp_break));
 	memset(tsk->thread.hbp_watch, 0, sizeof(tsk->thread.hbp_watch));
 }
@@ -487,6 +490,7 @@ bool breakpoint_handler(struct pt_regs *regs)
 	int i;
 	struct perf_event *bp, **slots;
 	bool need_sigtrap = false;
+	unsigned int clear_mask = 0;
 
 	slots = this_cpu_ptr(bp_on_reg);
 
@@ -500,11 +504,13 @@ bool breakpoint_handler(struct pt_regs *regs)
 			if (bp->attr.sigtrap)
 				need_sigtrap = true;
 
-			csr_write32(0x1 << i, LOONGARCH_CSR_FWPS);
-			update_bp_registers(regs, 0, 0);
+			clear_mask |= (0x1 << i);
 		}
 	}
 
+	if (clear_mask)
+		csr_write32(clear_mask | CSR_FWPS_SKIP, LOONGARCH_CSR_FWPS);
+
 	return need_sigtrap;
 }
 NOKPROBE_SYMBOL(breakpoint_handler);
@@ -514,6 +520,7 @@ bool watchpoint_handler(struct pt_regs *regs)
 	int i;
 	struct perf_event *wp, **slots;
 	bool need_sigtrap = false;
+	unsigned int clear_mask = 0;
 
 	slots = this_cpu_ptr(wp_on_reg);
 
@@ -527,11 +534,13 @@ bool watchpoint_handler(struct pt_regs *regs)
 			if (wp->attr.sigtrap)
 				need_sigtrap = true;
 
-			csr_write32(0x1 << i, LOONGARCH_CSR_MWPS);
-			update_bp_registers(regs, 0, 1);
+			clear_mask |= (0x1 << i);
 		}
 	}
 
+	if (clear_mask)
+		csr_write32(clear_mask | CSR_MWPS_SKIP, LOONGARCH_CSR_MWPS);
+
 	return need_sigtrap;
 }
 NOKPROBE_SYMBOL(watchpoint_handler);
@@ -555,7 +564,7 @@ static int __init arch_hw_breakpoint_init(void)
 }
 arch_initcall(arch_hw_breakpoint_init);
 
-void hw_breakpoint_thread_switch(struct task_struct *next)
+void hw_breakpoint_thread_switch(struct task_struct *prev, struct task_struct *next)
 {
 	u64 addr, mask;
 	struct pt_regs *regs = task_pt_regs(next);
@@ -567,6 +576,26 @@ void hw_breakpoint_thread_switch(struct task_struct *next)
 			csr_write32(CSR_FWPS_SKIP, LOONGARCH_CSR_FWPS);
 		regs->csr_prmd |= CSR_PRMD_PWE;
 	} else {
+		unsigned int fwps = csr_read32(LOONGARCH_CSR_FWPS);
+		unsigned int mwps = csr_read32(LOONGARCH_CSR_MWPS);
+
+		prev->thread.hbp_break_skip = !!(fwps & CSR_FWPS_SKIP);
+		prev->thread.hbp_watch_skip = !!(mwps & CSR_MWPS_SKIP);
+
+		if (next->thread.hbp_break_skip) {
+			csr_write32(CSR_FWPS_SKIP, LOONGARCH_CSR_FWPS);
+			next->thread.hbp_break_skip = 0;
+		} else {
+			csr_write32(0, LOONGARCH_CSR_FWPS);
+		}
+
+		if (next->thread.hbp_watch_skip) {
+			csr_write32(CSR_MWPS_SKIP, LOONGARCH_CSR_MWPS);
+			next->thread.hbp_watch_skip = 0;
+		} else {
+			csr_write32(0, LOONGARCH_CSR_MWPS);
+		}
+
 		/* Update breakpoints */
 		update_bp_registers(regs, 1, 0);
 		/* Update watchpoints */
-- 
2.42.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH v4 0/6] LoongArch: Fix perf hardware breakpoints
  2026-09-24 14:27 [PATCH v4 0/6] LoongArch: Fix perf hardware breakpoints Tiezhu Yang
                   ` (5 preceding siblings ...)
  2026-09-24 14:27 ` [PATCH v4 6/6] LoongArch: Fix one-shot limitation for perf hardware breakpoints Tiezhu Yang
@ 2026-10-03  4:01 ` Huacai Chen
  6 siblings, 0 replies; 8+ messages in thread
From: Huacai Chen @ 2026-10-03  4:01 UTC (permalink / raw)
  To: Tiezhu Yang; +Cc: loongarch, linux-kernel

Applied, thanks.


Huacai

On Thu, Sep 24, 2026 at 10:27 PM Tiezhu Yang <yangtiezhu@loongson.cn> wrote:
>
> v4:
>   -- Do not modify thread_struct layout for hardware breakpoints
>
> This series addresses severe functional degradations in standard
> perf_event usage, including initialization failures and one-shot
> triggering limitations.
>
> Tiezhu Yang (6):
>   LoongArch: Fix bitmask corruption in update_bp_registers()
>   LoongArch: Remove redundant call in update_bp_registers()
>   LoongArch: Fix architectural naming typo for CSR_FWPS_SKIP
>   LoongArch: Only send SIGTRAP signal if necessary in do_watch()
>   LoongArch: Fix perf hardware breakpoint failure via installation
>   LoongArch: Fix one-shot limitation for perf hardware breakpoints
>
>  arch/loongarch/include/asm/hw_breakpoint.h |  8 +--
>  arch/loongarch/include/asm/loongarch.h     |  7 ++-
>  arch/loongarch/include/asm/processor.h     |  3 +
>  arch/loongarch/include/asm/switch_to.h     |  2 +-
>  arch/loongarch/kernel/hw_breakpoint.c      | 66 ++++++++++++++++++----
>  arch/loongarch/kernel/traps.c              | 12 ++--
>  6 files changed, 74 insertions(+), 24 deletions(-)
>
> --
> 2.42.0
>
>

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-10-03  4:01 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-24 14:27 [PATCH v4 0/6] LoongArch: Fix perf hardware breakpoints Tiezhu Yang
2026-09-24 14:27 ` [PATCH v4 1/6] LoongArch: Fix bitmask corruption in update_bp_registers() Tiezhu Yang
2026-09-24 14:27 ` [PATCH v4 2/6] LoongArch: Remove redundant call " Tiezhu Yang
2026-09-24 14:27 ` [PATCH v4 3/6] LoongArch: Fix architectural naming typo for CSR_FWPS_SKIP Tiezhu Yang
2026-09-24 14:27 ` [PATCH v4 4/6] LoongArch: Only send SIGTRAP signal if necessary in do_watch() Tiezhu Yang
2026-09-24 14:27 ` [PATCH v4 5/6] LoongArch: Fix perf hardware breakpoint failure via installation Tiezhu Yang
2026-09-24 14:27 ` [PATCH v4 6/6] LoongArch: Fix one-shot limitation for perf hardware breakpoints Tiezhu Yang
2026-10-03  4:01 ` [PATCH v4 0/6] LoongArch: Fix " Huacai Chen

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®