* [PATCH v10 1/9] riscv: store percpu offset into thread_info
2026-11-12 10:41 [PATCH v10 0/9] riscv: optimize percpu access Yunhui Cui
@ 2026-11-12 10:41 ` Yunhui Cui
2026-11-12 10:41 ` [PATCH v10 2/9] riscv: add infrastructure for preemptible this_cpu operations Yunhui Cui
` (8 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Yunhui Cui @ 2026-11-12 10:41 UTC (permalink / raw)
To: pjw, palmer, aou, alex, dennis, tj, cl, oleg, ast, daniel, andrii,
eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
ihor.solodrai, bjorn, pulehui, puranjay, broonie, cuiyunhui, dave,
david.laight.linux, jszhang, tglx, conor.dooley, thuth, ajones,
ben.dooks, rkrcmar, samuel.holland, zong.li, debug, seanwascoding,
andybnac, menglong8.dong, cyrilbur, wangruikang, atishp, apatel,
arnd, nathan, nick.desaulniers+lkml, morbo, justinstitt,
qingfang.deng, linux-riscv, linux-kernel, linux-mm, bpf,
linux-arch, llvm
RISC-V currently derives the current per-CPU offset from the CPU number
and __per_cpu_offset[]. Cache it in thread_info so per-CPU accesses can
load the offset directly.
Initialize the cached offset for the boot and secondary CPUs, and
propagate it across context switches. Set it before each secondary CPU
startup, including parallel bringup and CPU hotplug.
Link: https://lists.riscv.org/g/tech-privileged/topic/risc_v_tech_arch_review/113437553?page=2
Link: https://lore.kernel.org/r/20260826012457.ig3rfmji7vywf6kc@offworld
Signed-off-by: Yunhui Cui <cuiyunhui@bytedance.com>
---
arch/riscv/include/asm/asm.h | 6 +-----
arch/riscv/include/asm/percpu.h | 14 ++++++++++++++
arch/riscv/include/asm/switch_to.h | 9 +++++++++
arch/riscv/include/asm/thread_info.h | 3 ++-
arch/riscv/kernel/asm-offsets.c | 2 +-
arch/riscv/kernel/smpboot.c | 10 +++++++++-
arch/riscv/net/bpf_jit_comp64.c | 9 +--------
7 files changed, 37 insertions(+), 16 deletions(-)
create mode 100644 arch/riscv/include/asm/percpu.h
diff --git a/arch/riscv/include/asm/asm.h b/arch/riscv/include/asm/asm.h
index b8bf842d4c136..06ac38f733b0a 100644
--- a/arch/riscv/include/asm/asm.h
+++ b/arch/riscv/include/asm/asm.h
@@ -92,11 +92,7 @@
#ifdef CONFIG_SMP
.macro asm_per_cpu dst sym tmp
- lw \tmp, TASK_TI_CPU_NUM(tp)
- slli \tmp, \tmp, RISCV_LGPTR
- la \dst, __per_cpu_offset
- add \dst, \dst, \tmp
- REG_L \tmp, 0(\dst)
+ REG_L \tmp, TASK_TI_PCPU_OFFSET(tp)
la \dst, \sym
add \dst, \dst, \tmp
.endm
diff --git a/arch/riscv/include/asm/percpu.h b/arch/riscv/include/asm/percpu.h
new file mode 100644
index 0000000000000..faf9a75ec66d7
--- /dev/null
+++ b/arch/riscv/include/asm/percpu.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __ASM_PERCPU_H
+#define __ASM_PERCPU_H
+
+#include <linux/thread_info.h>
+
+#include <asm/current.h>
+
+#define __my_cpu_offset (((struct thread_info *)current)->pcpu_offset)
+
+#include <asm-generic/percpu.h>
+
+#endif /* __ASM_PERCPU_H */
diff --git a/arch/riscv/include/asm/switch_to.h b/arch/riscv/include/asm/switch_to.h
index 123c89b694e87..b4a4d4a44f25d 100644
--- a/arch/riscv/include/asm/switch_to.h
+++ b/arch/riscv/include/asm/switch_to.h
@@ -89,6 +89,14 @@ static inline void __switch_to_envcfg(struct task_struct *next)
:: "r" (next->thread.envcfg) : "memory");
}
+static inline void __switch_to_pcpu_offset(struct task_struct *prev,
+ struct task_struct *next)
+{
+#ifdef CONFIG_SMP
+ next->thread_info.pcpu_offset = prev->thread_info.pcpu_offset;
+#endif
+}
+
extern struct task_struct *__switch_to(struct task_struct *,
struct task_struct *);
@@ -125,6 +133,7 @@ do { \
if (switch_to_should_flush_icache(__next)) \
local_flush_icache_all(); \
__switch_to_envcfg(__next); \
+ __switch_to_pcpu_offset(__prev, __next); \
((last) = __switch_to(__prev, __next)); \
} while (0)
diff --git a/arch/riscv/include/asm/thread_info.h b/arch/riscv/include/asm/thread_info.h
index 55019fdfa9eca..f10ba62b61016 100644
--- a/arch/riscv/include/asm/thread_info.h
+++ b/arch/riscv/include/asm/thread_info.h
@@ -53,6 +53,7 @@
struct thread_info {
unsigned long flags; /* low level flags */
int preempt_count; /* 0=>preemptible, <0=>BUG */
+ int cpu;
/*
* These stack pointers are overwritten on every system call or
* exception. SP is also saved to the stack it can be recovered when
@@ -60,8 +61,8 @@ struct thread_info {
*/
long kernel_sp; /* Kernel stack pointer */
long user_sp; /* User stack pointer */
- int cpu;
unsigned long syscall_work; /* SYSCALL_WORK_ flags */
+ unsigned long pcpu_offset;
#ifdef CONFIG_SHADOW_CALL_STACK
void *scs_base;
void *scs_sp;
diff --git a/arch/riscv/kernel/asm-offsets.c b/arch/riscv/kernel/asm-offsets.c
index a75f0cfea1e9f..f4f7ee976cd94 100644
--- a/arch/riscv/kernel/asm-offsets.c
+++ b/arch/riscv/kernel/asm-offsets.c
@@ -38,6 +38,7 @@ void asm_offsets(void)
OFFSET(TASK_THREAD_SUM, task_struct, thread.sum);
OFFSET(TASK_TI_CPU, task_struct, thread_info.cpu);
+ OFFSET(TASK_TI_PCPU_OFFSET, task_struct, thread_info.pcpu_offset);
OFFSET(TASK_TI_PREEMPT_COUNT, task_struct, thread_info.preempt_count);
OFFSET(TASK_TI_KERNEL_SP, task_struct, thread_info.kernel_sp);
OFFSET(TASK_TI_USER_SP, task_struct, thread_info.user_sp);
@@ -50,7 +51,6 @@ void asm_offsets(void)
OFFSET(TASK_TI_A2, task_struct, thread_info.a2);
#endif
- OFFSET(TASK_TI_CPU_NUM, task_struct, thread_info.cpu);
#ifdef CONFIG_RISCV_USER_CFI
OFFSET(TASK_TI_CFI_STATE, task_struct, thread_info.user_cfi_state);
OFFSET(TASK_TI_USER_SSP, task_struct, thread_info.user_cfi_state.user_shdw_stk);
diff --git a/arch/riscv/kernel/smpboot.c b/arch/riscv/kernel/smpboot.c
index a9de2dae804c6..6431914d21165 100644
--- a/arch/riscv/kernel/smpboot.c
+++ b/arch/riscv/kernel/smpboot.c
@@ -175,6 +175,10 @@ void __init setup_smp(void)
static int start_secondary_cpu(int cpu, struct task_struct *tidle)
{
+ /* The idle task is reused across CPU hotplug. */
+ tidle->thread_info.cpu = cpu;
+ tidle->thread_info.pcpu_offset = per_cpu_offset(cpu);
+
if (cpu_ops->cpu_start)
return cpu_ops->cpu_start(cpu, tidle);
@@ -190,7 +194,6 @@ int arch_cpuhp_kick_ap_alive(unsigned int cpu, struct task_struct *tidle)
int __cpu_up(unsigned int cpu, struct task_struct *tidle)
{
int ret;
- tidle->thread_info.cpu = cpu;
ret = start_secondary_cpu(cpu, tidle);
if (!ret) {
@@ -208,6 +211,11 @@ int __cpu_up(unsigned int cpu, struct task_struct *tidle)
}
#endif
+void __init smp_prepare_boot_cpu(void)
+{
+ __my_cpu_offset = per_cpu_offset(smp_processor_id());
+}
+
void __init smp_cpus_done(unsigned int max_cpus)
{
}
diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c
index 371405598faa7..fa6ce5613c4d6 100644
--- a/arch/riscv/net/bpf_jit_comp64.c
+++ b/arch/riscv/net/bpf_jit_comp64.c
@@ -1466,15 +1466,8 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
if (rd != rs)
emit_mv(rd, rs, ctx);
#ifdef CONFIG_SMP
- /* Load current CPU number in T1 */
- emit_lw(RV_REG_T1, offsetof(struct thread_info, cpu),
+ emit_ld(RV_REG_T1, offsetof(struct thread_info, pcpu_offset),
RV_REG_TP, ctx);
- /* Load address of __per_cpu_offset array in T2 */
- emit_addr(RV_REG_T2, (u64)&__per_cpu_offset, extra_pass, ctx);
- /* Get address of __per_cpu_offset[cpu] in T1 */
- emit_sh3add(RV_REG_T1, RV_REG_T1, RV_REG_T2, ctx);
- /* Load __per_cpu_offset[cpu] in T1 */
- emit_ld(RV_REG_T1, 0, RV_REG_T1, ctx);
/* Add the offset to Rd */
emit_add(rd, rd, RV_REG_T1, ctx);
#endif
--
2.39.5
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v10 2/9] riscv: add infrastructure for preemptible this_cpu operations
2026-11-12 10:41 [PATCH v10 0/9] riscv: optimize percpu access Yunhui Cui
2026-11-12 10:41 ` [PATCH v10 1/9] riscv: store percpu offset into thread_info Yunhui Cui
@ 2026-11-12 10:41 ` Yunhui Cui
2026-11-12 12:02 ` David Laight
2026-11-12 10:41 ` [PATCH v10 3/9] riscv: implement preemptible this_cpu read and write operations Yunhui Cui
` (7 subsequent siblings)
9 siblings, 1 reply; 15+ messages in thread
From: Yunhui Cui @ 2026-11-12 10:41 UTC (permalink / raw)
To: pjw, palmer, aou, alex, dennis, tj, cl, oleg, ast, daniel, andrii,
eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
ihor.solodrai, bjorn, pulehui, puranjay, broonie, cuiyunhui, dave,
david.laight.linux, jszhang, tglx, conor.dooley, thuth, ajones,
ben.dooks, rkrcmar, samuel.holland, zong.li, debug, seanwascoding,
andybnac, menglong8.dong, cyrilbur, wangruikang, atishp, apatel,
arnd, nathan, nick.desaulniers+lkml, morbo, justinstitt,
qingfang.deng, linux-riscv, linux-kernel, linux-mm, bpf,
linux-arch, llvm
RISC-V does not provide architecture-specific this_cpu operations, so
read-modify-write operations use generic fallbacks that disable local
interrupts. Disabling preemption around each architecture-specific
operation would avoid the interrupt-off region, but would still add
preemption-count bookkeeping and a reschedule check to the common path.
Use the recoverable-GPR scheme employed by arm64 so the operations can
remain preemptible. Record the registers used for per-CPU address
generation and repair them on exception return if the task migrated.
Exception entry saves and clears the active state so handlers can use
per-CPU operations independently. Keep this protocol in common assembly
macros so NMI-like entry paths which bypass handle_exception, such as SBI
Supervisor Software Events, can preserve an interrupted operation.
The existing exception return path clears LR/SC reservations, so an
interrupted LR/SC sequence retries after its saved address is repaired.
Suggested-by: David Laight <david.laight.linux@gmail.com>
Link: https://lore.kernel.org/r/20261108213254.67805ef0@pumpkin
Suggested-by: Jisheng Zhang <jszhang@kernel.org>
Link: https://lore.kernel.org/r/avG07pmpcLGkq5i-@xhacker
Signed-off-by: Yunhui Cui <cuiyunhui@bytedance.com>
---
arch/riscv/include/asm/asm.h | 19 ++++++++++++
arch/riscv/include/asm/percpu.h | 43 ++++++++++++++++++++++++++++
arch/riscv/include/asm/ptrace.h | 15 ++++++++++
arch/riscv/include/asm/thread_info.h | 1 +
arch/riscv/kernel/Makefile | 1 +
arch/riscv/kernel/asm-offsets.c | 2 ++
arch/riscv/kernel/entry-common.c | 32 +++++++++++++++++++++
arch/riscv/kernel/entry.S | 10 +++++++
8 files changed, 123 insertions(+)
create mode 100644 arch/riscv/kernel/entry-common.c
diff --git a/arch/riscv/include/asm/asm.h b/arch/riscv/include/asm/asm.h
index 06ac38f733b0a..57af10a936d9b 100644
--- a/arch/riscv/include/asm/asm.h
+++ b/arch/riscv/include/asm/asm.h
@@ -81,6 +81,25 @@
/* Common assembly source macros */
+/*
+ * Save and clear the active per-CPU GPR state on exception entry. Entry
+ * paths which bypass handle_exception, such as SBI Supervisor Software
+ * Events (SSE), must pair this with pcpu_gprs_exit around their handlers.
+ */
+.macro pcpu_gprs_entry tsk regs tmp
+ lhu \tmp, TASK_TI_PCPU_GPRS(\tsk)
+ REG_S \tmp, PT_PCPU_GPRS(\regs)
+ sh zero, TASK_TI_PCPU_GPRS(\tsk)
+.endm
+
+/*
+ * Restore the saved state before resuming the interrupted context. tmp must
+ * contain PT_PCPU_GPRS from the interrupted frame.
+ */
+.macro pcpu_gprs_exit tsk tmp
+ sh \tmp, TASK_TI_PCPU_GPRS(\tsk)
+.endm
+
/*
* NOP sequence
*/
diff --git a/arch/riscv/include/asm/percpu.h b/arch/riscv/include/asm/percpu.h
index faf9a75ec66d7..59f7d44f7d3fe 100644
--- a/arch/riscv/include/asm/percpu.h
+++ b/arch/riscv/include/asm/percpu.h
@@ -3,12 +3,55 @@
#ifndef __ASM_PERCPU_H
#define __ASM_PERCPU_H
+#include <linux/bits.h>
+#include <linux/stringify.h>
#include <linux/thread_info.h>
+#include <asm/asm.h>
#include <asm/current.h>
+#include <asm/gpr-num.h>
#define __my_cpu_offset (((struct thread_info *)current)->pcpu_offset)
+#define PCPU_GPR_PCP_SHIFT 0
+#define PCPU_GPR_PCP GENMASK(4, 0)
+#define PCPU_GPR_OFF_SHIFT 5
+#define PCPU_GPR_OFF GENMASK(9, 5)
+#define PCPU_GPR_ADDR_SHIFT 10
+#define PCPU_GPR_ADDR GENMASK(14, 10)
+
+#define __PCPU_GPR_NUM(gpr) ".L__gpr_num_" gpr
+
+#define __PCPU_GPRS_VALUE \
+ "(" \
+ "(" __PCPU_GPR_NUM("%[pcp]") " << " __stringify(PCPU_GPR_PCP_SHIFT) ") | " \
+ "(" __PCPU_GPR_NUM("%[off]") " << " __stringify(PCPU_GPR_OFF_SHIFT) ") | " \
+ "(" __PCPU_GPR_NUM("%[addr]") " << " __stringify(PCPU_GPR_ADDR_SHIFT) ")" \
+ ")"
+
+#define __PCPU_GPRS_ASSERT_DISTINCT \
+ ".if ((" __PCPU_GPR_NUM("%[pcp]") " == " __PCPU_GPR_NUM("%[off]") ") || " \
+ "(" __PCPU_GPR_NUM("%[pcp]") " == " __PCPU_GPR_NUM("%[addr]") ") || " \
+ "(" __PCPU_GPR_NUM("%[off]") " == " __PCPU_GPR_NUM("%[addr]") "))\n" \
+ ".error \"PCPU GPRs overlap\"\n" \
+ ".endif\n"
+
+/*
+ * Mark a region where the per-CPU offset and address registers may need to
+ * be rewritten by exception return after migration. The three registers
+ * must remain distinct for the whole region.
+ */
+#define __PCPU_GPRS_BEGIN \
+ __DEFINE_ASM_GPR_NUMS \
+ __PCPU_GPRS_ASSERT_DISTINCT \
+ "li %[off], " __PCPU_GPRS_VALUE "\n" \
+ "sh %[off], %[gprs]\n" \
+ REG_L " %[off], %[pcpu_offset]\n" \
+ "add %[addr], %[pcp], %[off]\n"
+
+#define __PCPU_GPRS_END \
+ "sh zero, %[gprs]\n"
+
#include <asm-generic/percpu.h>
#endif /* __ASM_PERCPU_H */
diff --git a/arch/riscv/include/asm/ptrace.h b/arch/riscv/include/asm/ptrace.h
index addc8188152f7..ceff8f63bc866 100644
--- a/arch/riscv/include/asm/ptrace.h
+++ b/arch/riscv/include/asm/ptrace.h
@@ -53,6 +53,8 @@ struct pt_regs {
unsigned long cause;
/* a0 value before the syscall */
unsigned long orig_a0;
+ /* Active preemptible per-CPU operation, if any. */
+ unsigned long pcpu_gprs;
};
#define PTRACE_SYSEMU 0x1f
@@ -145,6 +147,19 @@ static inline unsigned long regs_get_register(struct pt_regs *regs,
return *(unsigned long *)((unsigned long)regs + offset);
}
+static inline void regs_set_register(struct pt_regs *regs,
+ unsigned int offset, unsigned long val)
+{
+ if (unlikely(offset > MAX_REG_OFFSET))
+ return;
+
+ /* Register x0 is represented by the first, read-only slot. */
+ if (offset)
+ *(unsigned long *)((unsigned long)regs + offset) = val;
+}
+
+void noinstr riscv_pcpu_regs_fixup(struct pt_regs *regs);
+
/**
* regs_get_kernel_argument() - get Nth function argument in kernel
* @regs: pt_regs of that context
diff --git a/arch/riscv/include/asm/thread_info.h b/arch/riscv/include/asm/thread_info.h
index f10ba62b61016..51c4076763c1b 100644
--- a/arch/riscv/include/asm/thread_info.h
+++ b/arch/riscv/include/asm/thread_info.h
@@ -63,6 +63,7 @@ struct thread_info {
long user_sp; /* User stack pointer */
unsigned long syscall_work; /* SYSCALL_WORK_ flags */
unsigned long pcpu_offset;
+ unsigned short pcpu_gprs;
#ifdef CONFIG_SHADOW_CALL_STACK
void *scs_base;
void *scs_sp;
diff --git a/arch/riscv/kernel/Makefile b/arch/riscv/kernel/Makefile
index f54c336bf547c..bf786d5dd4de3 100644
--- a/arch/riscv/kernel/Makefile
+++ b/arch/riscv/kernel/Makefile
@@ -52,6 +52,7 @@ obj-y += alternative.o
obj-y += cpu.o
obj-y += cpufeature.o
obj-y += entry.o
+obj-y += entry-common.o
obj-y += irq.o
obj-y += process.o
obj-y += ptrace.o
diff --git a/arch/riscv/kernel/asm-offsets.c b/arch/riscv/kernel/asm-offsets.c
index f4f7ee976cd94..38f246a691c60 100644
--- a/arch/riscv/kernel/asm-offsets.c
+++ b/arch/riscv/kernel/asm-offsets.c
@@ -39,6 +39,7 @@ void asm_offsets(void)
OFFSET(TASK_TI_CPU, task_struct, thread_info.cpu);
OFFSET(TASK_TI_PCPU_OFFSET, task_struct, thread_info.pcpu_offset);
+ OFFSET(TASK_TI_PCPU_GPRS, task_struct, thread_info.pcpu_gprs);
OFFSET(TASK_TI_PREEMPT_COUNT, task_struct, thread_info.preempt_count);
OFFSET(TASK_TI_KERNEL_SP, task_struct, thread_info.kernel_sp);
OFFSET(TASK_TI_USER_SP, task_struct, thread_info.user_sp);
@@ -130,6 +131,7 @@ void asm_offsets(void)
OFFSET(PT_STATUS, pt_regs, status);
OFFSET(PT_BADADDR, pt_regs, badaddr);
OFFSET(PT_CAUSE, pt_regs, cause);
+ OFFSET(PT_PCPU_GPRS, pt_regs, pcpu_gprs);
OFFSET(SUSPEND_CONTEXT_REGS, suspend_context, regs);
diff --git a/arch/riscv/kernel/entry-common.c b/arch/riscv/kernel/entry-common.c
new file mode 100644
index 0000000000000..023d856b1679b
--- /dev/null
+++ b/arch/riscv/kernel/entry-common.c
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bitfield.h>
+#include <linux/compiler.h>
+#include <linux/percpu.h>
+
+#include <asm/current.h>
+#include <asm/ptrace.h>
+
+/*
+ * If the interrupted context had an active per-CPU operation, rewrite its
+ * saved offset and address registers for the CPU on which it will resume.
+ */
+void noinstr riscv_pcpu_regs_fixup(struct pt_regs *regs)
+{
+ unsigned long pcpu_offset = __my_cpu_offset;
+ unsigned long gprs = regs->pcpu_gprs;
+ unsigned long pcp;
+ unsigned int reg_pcp;
+ unsigned int reg_off;
+ unsigned int reg_addr;
+
+ reg_pcp = FIELD_GET(PCPU_GPR_PCP, gprs);
+ reg_off = FIELD_GET(PCPU_GPR_OFF, gprs);
+ reg_addr = FIELD_GET(PCPU_GPR_ADDR, gprs);
+
+ /* pt_regs offset zero represents x0, followed by x1 through x31. */
+ pcp = regs_get_register(regs, reg_pcp * sizeof(unsigned long));
+ regs_set_register(regs, reg_off * sizeof(unsigned long), pcpu_offset);
+ regs_set_register(regs, reg_addr * sizeof(unsigned long),
+ pcp + pcpu_offset);
+}
diff --git a/arch/riscv/kernel/entry.S b/arch/riscv/kernel/entry.S
index 628b85e3915a7..65298ac828959 100644
--- a/arch/riscv/kernel/entry.S
+++ b/arch/riscv/kernel/entry.S
@@ -198,6 +198,8 @@ SYM_CODE_START(handle_exception)
REG_S s4, PT_CAUSE(sp)
REG_S s5, PT_TP(sp)
+ pcpu_gprs_entry tp sp s0
+
/*
* Set the scratch register to 0, so that if a recursive exception
* occurs, the exception vector knows it came from the kernel
@@ -281,6 +283,14 @@ SYM_CODE_START_NOALIGN(ret_from_exception)
move a0, sp
call riscv_v_context_nesting_end
#endif
+ REG_L t0, PT_PCPU_GPRS(sp)
+ beqz t0, .Lpcpu_regs_restored
+ move a0, sp
+ call riscv_pcpu_regs_fixup
+ REG_L t0, PT_PCPU_GPRS(sp)
+.Lpcpu_regs_restored:
+ pcpu_gprs_exit tp t0
+
REG_L a0, PT_STATUS(sp)
restore_userssp s3, a0
/*
--
2.39.5
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v10 2/9] riscv: add infrastructure for preemptible this_cpu operations
2026-11-12 10:41 ` [PATCH v10 2/9] riscv: add infrastructure for preemptible this_cpu operations Yunhui Cui
@ 2026-11-12 12:02 ` David Laight
0 siblings, 0 replies; 15+ messages in thread
From: David Laight @ 2026-11-12 12:02 UTC (permalink / raw)
To: Yunhui Cui
Cc: pjw, palmer, aou, alex, dennis, tj, cl, oleg, ast, daniel, andrii,
eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
ihor.solodrai, bjorn, pulehui, puranjay, broonie, dave, jszhang,
tglx, conor.dooley, thuth, ajones, ben.dooks, rkrcmar,
samuel.holland, zong.li, debug, seanwascoding, andybnac,
menglong8.dong, cyrilbur, wangruikang, atishp, apatel, arnd,
nathan, nick.desaulniers+lkml, morbo, justinstitt, qingfang.deng,
linux-riscv, linux-kernel, linux-mm, bpf, linux-arch, llvm
On Thu, 12 Nov 2026 18:41:42 +0800
Yunhui Cui <cuiyunhui@bytedance.com> wrote:
> +/*
> + * Mark a region where the per-CPU offset and address registers may need to
> + * be rewritten by exception return after migration. The three registers
> + * must remain distinct for the whole region.
> + */
> +#define __PCPU_GPRS_BEGIN \
> + __DEFINE_ASM_GPR_NUMS \
> + __PCPU_GPRS_ASSERT_DISTINCT \
> + "li %[off], " __PCPU_GPRS_VALUE "\n" \
> + "sh %[off], %[gprs]\n" \
> + REG_L " %[off], %[pcpu_offset]\n" \
> + "add %[addr], %[pcp], %[off]\n"
> +
> +#define __PCPU_GPRS_END \
> + "sh zero, %[gprs]\n"
Those are quite nasty with all the hidden variable names.
It wouldn't do any harm to pass the names though.
David
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v10 3/9] riscv: implement preemptible this_cpu read and write operations
2026-11-12 10:41 [PATCH v10 0/9] riscv: optimize percpu access Yunhui Cui
2026-11-12 10:41 ` [PATCH v10 1/9] riscv: store percpu offset into thread_info Yunhui Cui
2026-11-12 10:41 ` [PATCH v10 2/9] riscv: add infrastructure for preemptible this_cpu operations Yunhui Cui
@ 2026-11-12 10:41 ` Yunhui Cui
2026-11-12 10:41 ` [PATCH v10 4/9] riscv: implement preemptible this_cpu void RMW operations Yunhui Cui
` (6 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Yunhui Cui @ 2026-11-12 10:41 UTC (permalink / raw)
To: pjw, palmer, aou, alex, dennis, tj, cl, oleg, ast, daniel, andrii,
eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
ihor.solodrai, bjorn, pulehui, puranjay, broonie, cuiyunhui, dave,
david.laight.linux, jszhang, tglx, conor.dooley, thuth, ajones,
ben.dooks, rkrcmar, samuel.holland, zong.li, debug, seanwascoding,
andybnac, menglong8.dong, cyrilbur, wangruikang, atishp, apatel,
arnd, nathan, nick.desaulniers+lkml, morbo, justinstitt,
qingfang.deng, linux-riscv, linux-kernel, linux-mm, bpf,
linux-arch, llvm
Apply the recoverable per-CPU address handling to read and write
operations.
Keep the address calculation and memory access in the same inline assembly
block, and use naturally sized loads and stores while preserving the type
semantics of the generic percpu API.
Signed-off-by: Yunhui Cui <cuiyunhui@bytedance.com>
---
arch/riscv/include/asm/percpu.h | 85 +++++++++++++++++++++++++++++++++
1 file changed, 85 insertions(+)
diff --git a/arch/riscv/include/asm/percpu.h b/arch/riscv/include/asm/percpu.h
index 59f7d44f7d3fe..ae7c123a48093 100644
--- a/arch/riscv/include/asm/percpu.h
+++ b/arch/riscv/include/asm/percpu.h
@@ -4,6 +4,7 @@
#define __ASM_PERCPU_H
#include <linux/bits.h>
+#include <linux/compiler.h>
#include <linux/stringify.h>
#include <linux/thread_info.h>
@@ -52,6 +53,90 @@
#define __PCPU_GPRS_END \
"sh zero, %[gprs]\n"
+#define __PCPU_OP_CONTEXT() \
+ struct thread_info *__ti = (struct thread_info *)current; \
+ unsigned long __off, __addr
+
+#define PERCPU_RW_OPS(load, store, sz) \
+static __always_inline unsigned long \
+__percpu_read_##sz(const void __percpu *pcp) \
+{ \
+ __PCPU_OP_CONTEXT(); \
+ unsigned long val; \
+ \
+ asm volatile (__PCPU_GPRS_BEGIN \
+ load " %[val], 0(%[addr])\n" \
+ __PCPU_GPRS_END \
+ : [gprs] "=m" (__ti->pcpu_gprs), \
+ [off] "=&r" (__off), [addr] "=&r" (__addr), \
+ [val] "=&r" (val) \
+ : [pcpu_offset] "m" (__ti->pcpu_offset), \
+ [pcp] "r" (pcp) \
+ : "memory"); \
+ \
+ return (u##sz)val; \
+} \
+ \
+static __always_inline void __percpu_write_##sz(void __percpu *pcp, \
+ unsigned long val) \
+{ \
+ __PCPU_OP_CONTEXT(); \
+ \
+ asm volatile (__PCPU_GPRS_BEGIN \
+ store " %[val], 0(%[addr])\n" \
+ __PCPU_GPRS_END \
+ : [gprs] "=m" (__ti->pcpu_gprs), \
+ [off] "=&r" (__off), [addr] "=&r" (__addr) \
+ : [pcpu_offset] "m" (__ti->pcpu_offset), \
+ [pcp] "r" (pcp), [val] "r" ((u##sz)val) \
+ : "memory"); \
+}
+
+PERCPU_RW_OPS("lbu", "sb", 8)
+PERCPU_RW_OPS("lhu", "sh", 16)
+
+#ifdef CONFIG_64BIT
+PERCPU_RW_OPS("lwu", "sw", 32)
+PERCPU_RW_OPS("ld", "sd", 64)
+#else
+PERCPU_RW_OPS("lw", "sw", 32)
+#endif
+
+#define _pcp_wrap(op, pcp, ...) \
+({ \
+ op(&(pcp), __VA_ARGS__); \
+})
+
+#define _pcp_wrap_return(op, pcp, args...) \
+({ \
+ __auto_type __pcp_ptr = &(pcp); \
+ \
+ (TYPEOF_UNQUAL(*__pcp_ptr))op(__pcp_ptr, ##args); \
+})
+
+#define this_cpu_read_1(pcp) _pcp_wrap_return(__percpu_read_8, pcp)
+#define this_cpu_read_2(pcp) _pcp_wrap_return(__percpu_read_16, pcp)
+#define this_cpu_read_4(pcp) _pcp_wrap_return(__percpu_read_32, pcp)
+
+#ifdef CONFIG_64BIT
+#define this_cpu_read_8(pcp) _pcp_wrap_return(__percpu_read_64, pcp)
+#endif
+
+#define this_cpu_write_1(pcp, val) \
+ _pcp_wrap(__percpu_write_8, pcp, (unsigned long)(val))
+#define this_cpu_write_2(pcp, val) \
+ _pcp_wrap(__percpu_write_16, pcp, (unsigned long)(val))
+#define this_cpu_write_4(pcp, val) \
+ _pcp_wrap(__percpu_write_32, pcp, (unsigned long)(val))
+
+#ifdef CONFIG_64BIT
+#define this_cpu_write_8(pcp, val) \
+ _pcp_wrap(__percpu_write_64, pcp, (unsigned long)(val))
+#endif
+
+#undef PERCPU_RW_OPS
+#undef __PCPU_OP_CONTEXT
+
#include <asm-generic/percpu.h>
#endif /* __ASM_PERCPU_H */
--
2.39.5
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v10 4/9] riscv: implement preemptible this_cpu void RMW operations
2026-11-12 10:41 [PATCH v10 0/9] riscv: optimize percpu access Yunhui Cui
` (2 preceding siblings ...)
2026-11-12 10:41 ` [PATCH v10 3/9] riscv: implement preemptible this_cpu read and write operations Yunhui Cui
@ 2026-11-12 10:41 ` Yunhui Cui
2026-11-12 10:41 ` [PATCH v10 5/9] riscv: implement preemptible this_cpu add_return operations Yunhui Cui
` (5 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Yunhui Cui @ 2026-11-12 10:41 UTC (permalink / raw)
To: pjw, palmer, aou, alex, dennis, tj, cl, oleg, ast, daniel, andrii,
eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
ihor.solodrai, bjorn, pulehui, puranjay, broonie, cuiyunhui, dave,
david.laight.linux, jszhang, tglx, conor.dooley, thuth, ajones,
ben.dooks, rkrcmar, samuel.holland, zong.li, debug, seanwascoding,
andybnac, menglong8.dong, cyrilbur, wangruikang, atishp, apatel,
arnd, nathan, nick.desaulniers+lkml, morbo, justinstitt,
qingfang.deng, linux-riscv, linux-kernel, linux-mm, bpf,
linux-arch, llvm
Apply the recoverable per-CPU address handling to void read-modify-write
operations.
Use relaxed AMOs for native-width operations, and Zabha or a masked LR/SC
sequence for subword operations. Use value-returning AMOs even when the old
value is unused so CPU-local operations remain likely to execute near the
CPU.
Link: https://lore.kernel.org/r/20260826010748.zphu3wh2hlm4t4o2@offworld
Signed-off-by: Yunhui Cui <cuiyunhui@bytedance.com>
---
arch/riscv/include/asm/percpu.h | 127 ++++++++++++++++++++++++++++++++
1 file changed, 127 insertions(+)
diff --git a/arch/riscv/include/asm/percpu.h b/arch/riscv/include/asm/percpu.h
index ae7c123a48093..46eafa1ef02a1 100644
--- a/arch/riscv/include/asm/percpu.h
+++ b/arch/riscv/include/asm/percpu.h
@@ -9,8 +9,10 @@
#include <linux/thread_info.h>
#include <asm/asm.h>
+#include <asm/cpufeature-macros.h>
#include <asm/current.h>
#include <asm/gpr-num.h>
+#include <asm/hwcap.h>
#define __my_cpu_offset (((struct thread_info *)current)->pcpu_offset)
@@ -102,6 +104,105 @@ PERCPU_RW_OPS("ld", "sd", 64)
PERCPU_RW_OPS("lw", "sw", 32)
#endif
+/*
+ * Use value-returning AMOs even when the old value is unused. Avoiding x0
+ * as rd makes an AMO more likely to execute near the CPU, which better suits
+ * CPU-local data that is commonly accessed again by the same CPU.
+ */
+#define __PERCPU_AMO_OP_CASE(sfx, name, sz, amo_insn) \
+static __always_inline void \
+__percpu_##name##_amo_case_##sz(void __percpu *pcp, unsigned long val) \
+{ \
+ __PCPU_OP_CONTEXT(); \
+ u##sz tmp; \
+ \
+ asm volatile (__PCPU_GPRS_BEGIN \
+ "amo" #amo_insn #sfx " %[tmp], %[val], 0(%[addr])\n" \
+ __PCPU_GPRS_END \
+ : [gprs] "=m" (__ti->pcpu_gprs), \
+ [off] "=&r" (__off), [addr] "=&r" (__addr), \
+ [tmp] "=&r" (tmp) \
+ : [pcpu_offset] "m" (__ti->pcpu_offset), \
+ [pcp] "r" (pcp), [val] "r" ((u##sz)val) \
+ : "memory"); \
+}
+
+__PERCPU_AMO_OP_CASE(.w, add, 32, add)
+__PERCPU_AMO_OP_CASE(.w, and, 32, and)
+__PERCPU_AMO_OP_CASE(.w, or, 32, or)
+
+#ifdef CONFIG_64BIT
+__PERCPU_AMO_OP_CASE(.d, add, 64, add)
+__PERCPU_AMO_OP_CASE(.d, and, 64, and)
+__PERCPU_AMO_OP_CASE(.d, or, 64, or)
+#endif
+
+#define PERCPU_8_16_GET_SHIFT(pcp) \
+ (((__force unsigned long)(pcp) & 0x3) * BITS_PER_BYTE)
+#define PERCPU_8_16_GET_MASK(sz) GENMASK((sz) - 1, 0)
+#define PERCPU_8_16_GET_PCP32(pcp) \
+ ((void __percpu *)((__force unsigned long)(pcp) & ~0x3UL))
+
+#define __PERCPU_8_16_OP_CASE(name, sz, sfx, amo_insn, op_insn) \
+static __always_inline void \
+__percpu_##name##_amo_case_##sz(void __percpu *pcp, unsigned long val) \
+{ \
+ __PCPU_OP_CONTEXT(); \
+ const u32 field_mask = PERCPU_8_16_GET_MASK(sz); \
+ const unsigned long shift = PERCPU_8_16_GET_SHIFT(pcp); \
+ const u32 mask = field_mask << shift; \
+ const u32 val_trunc = val & field_mask; \
+ void __percpu *pcp32 = PERCPU_8_16_GET_PCP32(pcp); \
+ u32 old, new, field; \
+ u##sz tmp; \
+ \
+ if (IS_ENABLED(CONFIG_RISCV_ISA_ZABHA) && \
+ riscv_has_extension_unlikely(RISCV_ISA_EXT_ZABHA)) { \
+ asm volatile (__PCPU_GPRS_BEGIN \
+ ".option push\n" \
+ ".option arch, +zabha\n" \
+ "amo" #amo_insn #sfx " %[tmp], %[val], 0(%[addr])\n" \
+ ".option pop\n" \
+ __PCPU_GPRS_END \
+ : [gprs] "=m" (__ti->pcpu_gprs), \
+ [off] "=&r" (__off), [addr] "=&r" (__addr), \
+ [tmp] "=&r" (tmp) \
+ : [pcpu_offset] "m" (__ti->pcpu_offset), \
+ [pcp] "r" (pcp), [val] "r" ((u##sz)val) \
+ : "memory"); \
+ } else { \
+ asm volatile (__PCPU_GPRS_BEGIN \
+ "0: lr.w %[old], 0(%[addr])\n" \
+ "and %[field], %[old], %[mask]\n" \
+ "srl %[field], %[field], %[shift]\n" \
+ op_insn " %[field], %[field], %[val]\n" \
+ "and %[field], %[field], %[field_mask]\n" \
+ "sll %[field], %[field], %[shift]\n" \
+ "and %[new], %[old], %[inv_mask]\n" \
+ "or %[new], %[new], %[field]\n" \
+ "sc.w %[new], %[new], 0(%[addr])\n" \
+ "bnez %[new], 0b\n" \
+ __PCPU_GPRS_END \
+ : [gprs] "=m" (__ti->pcpu_gprs), \
+ [off] "=&r" (__off), [addr] "=&r" (__addr), \
+ [old] "=&r" (old), [new] "=&r" (new), \
+ [field] "=&r" (field) \
+ : [pcpu_offset] "m" (__ti->pcpu_offset), \
+ [pcp] "r" (pcp32), [mask] "r" (mask), \
+ [shift] "r" (shift), [val] "r" (val_trunc), \
+ [field_mask] "r" (field_mask), \
+ [inv_mask] "r" (~mask) \
+ : "memory"); \
+ } \
+}
+
+__PERCPU_8_16_OP_CASE(add, 8, .b, add, "add")
+__PERCPU_8_16_OP_CASE(add, 16, .h, add, "add")
+__PERCPU_8_16_OP_CASE(and, 8, .b, and, "and")
+__PERCPU_8_16_OP_CASE(and, 16, .h, and, "and")
+__PERCPU_8_16_OP_CASE(or, 8, .b, or, "or")
+__PERCPU_8_16_OP_CASE(or, 16, .h, or, "or")
+
#define _pcp_wrap(op, pcp, ...) \
({ \
op(&(pcp), __VA_ARGS__); \
@@ -134,7 +235,33 @@ PERCPU_RW_OPS("lw", "sw", 32)
_pcp_wrap(__percpu_write_64, pcp, (unsigned long)(val))
#endif
+#define this_cpu_add_1(pcp, val) _pcp_wrap(__percpu_add_amo_case_8, pcp, val)
+#define this_cpu_add_2(pcp, val) _pcp_wrap(__percpu_add_amo_case_16, pcp, val)
+#define this_cpu_add_4(pcp, val) _pcp_wrap(__percpu_add_amo_case_32, pcp, val)
+#ifdef CONFIG_64BIT
+#define this_cpu_add_8(pcp, val) _pcp_wrap(__percpu_add_amo_case_64, pcp, val)
+#endif
+
+#define this_cpu_and_1(pcp, val) _pcp_wrap(__percpu_and_amo_case_8, pcp, val)
+#define this_cpu_and_2(pcp, val) _pcp_wrap(__percpu_and_amo_case_16, pcp, val)
+#define this_cpu_and_4(pcp, val) _pcp_wrap(__percpu_and_amo_case_32, pcp, val)
+#ifdef CONFIG_64BIT
+#define this_cpu_and_8(pcp, val) _pcp_wrap(__percpu_and_amo_case_64, pcp, val)
+#endif
+
+#define this_cpu_or_1(pcp, val) _pcp_wrap(__percpu_or_amo_case_8, pcp, val)
+#define this_cpu_or_2(pcp, val) _pcp_wrap(__percpu_or_amo_case_16, pcp, val)
+#define this_cpu_or_4(pcp, val) _pcp_wrap(__percpu_or_amo_case_32, pcp, val)
+#ifdef CONFIG_64BIT
+#define this_cpu_or_8(pcp, val) _pcp_wrap(__percpu_or_amo_case_64, pcp, val)
+#endif
+
#undef PERCPU_RW_OPS
+#undef __PERCPU_AMO_OP_CASE
+#undef __PERCPU_8_16_OP_CASE
+#undef PERCPU_8_16_GET_SHIFT
+#undef PERCPU_8_16_GET_MASK
+#undef PERCPU_8_16_GET_PCP32
#undef __PCPU_OP_CONTEXT
#include <asm-generic/percpu.h>
--
2.39.5
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v10 5/9] riscv: implement preemptible this_cpu add_return operations
2026-11-12 10:41 [PATCH v10 0/9] riscv: optimize percpu access Yunhui Cui
` (3 preceding siblings ...)
2026-11-12 10:41 ` [PATCH v10 4/9] riscv: implement preemptible this_cpu void RMW operations Yunhui Cui
@ 2026-11-12 10:41 ` Yunhui Cui
2026-11-12 11:30 ` bot+bpf-ci
2026-11-12 12:09 ` David Laight
2026-11-12 10:41 ` [PATCH v10 6/9] riscv: implement preemptible this_cpu xchg operations Yunhui Cui
` (4 subsequent siblings)
9 siblings, 2 replies; 15+ messages in thread
From: Yunhui Cui @ 2026-11-12 10:41 UTC (permalink / raw)
To: pjw, palmer, aou, alex, dennis, tj, cl, oleg, ast, daniel, andrii,
eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
ihor.solodrai, bjorn, pulehui, puranjay, broonie, cuiyunhui, dave,
david.laight.linux, jszhang, tglx, conor.dooley, thuth, ajones,
ben.dooks, rkrcmar, samuel.holland, zong.li, debug, seanwascoding,
andybnac, menglong8.dong, cyrilbur, wangruikang, atishp, apatel,
arnd, nathan, nick.desaulniers+lkml, morbo, justinstitt,
qingfang.deng, linux-riscv, linux-kernel, linux-mm, bpf,
linux-arch, llvm
Apply the recoverable per-CPU address handling to add-return operations.
Native-width AMOs return the old value, so derive the new value required
by the percpu API from the operand. Use Zabha for subword operations when
available, or a masked LR/SC sequence that returns the updated field.
Signed-off-by: Yunhui Cui <cuiyunhui@bytedance.com>
---
arch/riscv/include/asm/percpu.h | 134 ++++++++++++++++++++++++++++++++
1 file changed, 134 insertions(+)
diff --git a/arch/riscv/include/asm/percpu.h b/arch/riscv/include/asm/percpu.h
index 46eafa1ef02a1..9ff02f3811550 100644
--- a/arch/riscv/include/asm/percpu.h
+++ b/arch/riscv/include/asm/percpu.h
@@ -203,6 +203,129 @@ __PERCPU_8_16_OP_CASE(and, 16, .h, and, "and")
__PERCPU_8_16_OP_CASE(or, 8, .b, or, "or")
__PERCPU_8_16_OP_CASE(or, 16, .h, or, "or")
+static __always_inline u32
+__percpu_add_return_amo_case_32(void __percpu *pcp, unsigned long val)
+{
+ __PCPU_OP_CONTEXT();
+ u32 ret;
+
+ asm volatile (__PCPU_GPRS_BEGIN
+ "amoadd.w %[ret], %[val], 0(%[addr])\n"
+ __PCPU_GPRS_END
+ : [gprs] "=m" (__ti->pcpu_gprs),
+ [off] "=&r" (__off), [addr] "=&r" (__addr),
+ [ret] "=&r" (ret)
+ : [pcpu_offset] "m" (__ti->pcpu_offset),
+ [pcp] "r" (pcp), [val] "r" ((u32)val)
+ : "memory");
+
+ return ret + (u32)val;
+}
+
+#ifdef CONFIG_64BIT
+static __always_inline u64
+__percpu_add_return_amo_case_64(void __percpu *pcp, unsigned long val)
+{
+ __PCPU_OP_CONTEXT();
+ u64 ret;
+
+ asm volatile (__PCPU_GPRS_BEGIN
+ "amoadd.d %[ret], %[val], 0(%[addr])\n"
+ __PCPU_GPRS_END
+ : [gprs] "=m" (__ti->pcpu_gprs),
+ [off] "=&r" (__off), [addr] "=&r" (__addr),
+ [ret] "=&r" (ret)
+ : [pcpu_offset] "m" (__ti->pcpu_offset),
+ [pcp] "r" (pcp), [val] "r" ((u64)val)
+ : "memory");
+
+ return ret + (u64)val;
+}
+#endif
+
+static __always_inline unsigned long
+__percpu_add_return_8_16(void __percpu *pcp, unsigned long val,
+ unsigned int bits)
+{
+ __PCPU_OP_CONTEXT();
+ const u32 field_mask = GENMASK(bits - 1, 0);
+ const unsigned long shift = PERCPU_8_16_GET_SHIFT(pcp);
+ const u32 mask = field_mask << shift;
+ const u32 val_trunc = val & field_mask;
+ void __percpu *pcp32 = PERCPU_8_16_GET_PCP32(pcp);
+ u32 old, new, field;
+ unsigned long ret;
+
+ if (IS_ENABLED(CONFIG_RISCV_ISA_ZABHA) &&
+ riscv_has_extension_unlikely(RISCV_ISA_EXT_ZABHA)) {
+ if (bits == 8) {
+ asm volatile (__PCPU_GPRS_BEGIN
+ ".option push\n"
+ ".option arch, +zabha\n"
+ "amoadd.b %[ret], %[val], 0(%[addr])\n"
+ ".option pop\n"
+ __PCPU_GPRS_END
+ : [gprs] "=m" (__ti->pcpu_gprs),
+ [off] "=&r" (__off), [addr] "=&r" (__addr),
+ [ret] "=&r" (ret)
+ : [pcpu_offset] "m" (__ti->pcpu_offset),
+ [pcp] "r" (pcp), [val] "r" ((u8)val)
+ : "memory");
+ return (u8)(ret + val);
+ }
+
+ asm volatile (__PCPU_GPRS_BEGIN
+ ".option push\n"
+ ".option arch, +zabha\n"
+ "amoadd.h %[ret], %[val], 0(%[addr])\n"
+ ".option pop\n"
+ __PCPU_GPRS_END
+ : [gprs] "=m" (__ti->pcpu_gprs),
+ [off] "=&r" (__off), [addr] "=&r" (__addr),
+ [ret] "=&r" (ret)
+ : [pcpu_offset] "m" (__ti->pcpu_offset),
+ [pcp] "r" (pcp), [val] "r" ((u16)val)
+ : "memory");
+ return (u16)(ret + val);
+ }
+
+ asm volatile (__PCPU_GPRS_BEGIN
+ "0: lr.w %[old], 0(%[addr])\n"
+ "and %[field], %[old], %[mask]\n"
+ "srl %[field], %[field], %[shift]\n"
+ "add %[field], %[field], %[val]\n"
+ "and %[field], %[field], %[field_mask]\n"
+ "sll %[field], %[field], %[shift]\n"
+ "and %[new], %[old], %[inv_mask]\n"
+ "or %[new], %[new], %[field]\n"
+ "sc.w %[new], %[new], 0(%[addr])\n"
+ "bnez %[new], 0b\n"
+ __PCPU_GPRS_END
+ : [gprs] "=m" (__ti->pcpu_gprs),
+ [off] "=&r" (__off), [addr] "=&r" (__addr),
+ [old] "=&r" (old), [new] "=&r" (new),
+ [field] "=&r" (field)
+ : [pcpu_offset] "m" (__ti->pcpu_offset),
+ [pcp] "r" (pcp32), [mask] "r" (mask),
+ [shift] "r" (shift), [val] "r" (val_trunc),
+ [field_mask] "r" (field_mask), [inv_mask] "r" (~mask)
+ : "memory");
+
+ return field >> shift;
+}
+
+static __always_inline u8
+__percpu_add_return_amo_case_8(void __percpu *pcp, unsigned long val)
+{
+ return __percpu_add_return_8_16(pcp, val, 8);
+}
+
+static __always_inline u16
+__percpu_add_return_amo_case_16(void __percpu *pcp, unsigned long val)
+{
+ return __percpu_add_return_8_16(pcp, val, 16);
+}
+
#define _pcp_wrap(op, pcp, ...) \
({ \
op(&(pcp), __VA_ARGS__); \
@@ -256,6 +379,17 @@ __PERCPU_8_16_OP_CASE(or, 16, .h, or, "or")
#define this_cpu_or_8(pcp, val) _pcp_wrap(__percpu_or_amo_case_64, pcp, val)
#endif
+#define this_cpu_add_return_1(pcp, val) \
+ _pcp_wrap_return(__percpu_add_return_amo_case_8, pcp, val)
+#define this_cpu_add_return_2(pcp, val) \
+ _pcp_wrap_return(__percpu_add_return_amo_case_16, pcp, val)
+#define this_cpu_add_return_4(pcp, val) \
+ _pcp_wrap_return(__percpu_add_return_amo_case_32, pcp, val)
+#ifdef CONFIG_64BIT
+#define this_cpu_add_return_8(pcp, val) \
+ _pcp_wrap_return(__percpu_add_return_amo_case_64, pcp, val)
+#endif
+
#undef PERCPU_RW_OPS
#undef __PERCPU_AMO_OP_CASE
#undef __PERCPU_8_16_OP_CASE
--
2.39.5
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v10 5/9] riscv: implement preemptible this_cpu add_return operations
2026-11-12 10:41 ` [PATCH v10 5/9] riscv: implement preemptible this_cpu add_return operations Yunhui Cui
@ 2026-11-12 11:30 ` bot+bpf-ci
2026-11-12 12:09 ` David Laight
1 sibling, 0 replies; 15+ messages in thread
From: bot+bpf-ci @ 2026-11-12 11:30 UTC (permalink / raw)
To: cuiyunhui, pjw, palmer, aou, alex, dennis, tj, cl, oleg, ast,
daniel, andrii, eddyz87, memxor, martin.lau, song, yonghong.song,
jolsa, emil, ihor.solodrai, bjorn, pulehui, puranjay, broonie,
cuiyunhui, dave, david.laight.linux, jszhang, tglx, conor.dooley,
thuth, ajones, ben.dooks, rkrcmar, samuel.holland, zong.li, debug,
seanwascoding, andybnac, menglong8.dong, cyrilbur, wangruikang,
atishp, apatel, arnd, nathan, nick.desaulniers+lkml, morbo,
justinstitt
Cc: ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1951 bytes --]
> riscv: implement preemptible this_cpu add_return operations
TL;DR: Nit only: the riscv this_cpu add_return helpers duplicate the 32/64 and
8/16 code and the field mask; could they use the existing macro-generated
pattern like neighbouring ops? Not a bug.
> diff --git a/arch/riscv/include/asm/percpu.h b/arch/riscv/include/asm/percpu.h
> --- a/arch/riscv/include/asm/percpu.h
> +++ b/arch/riscv/include/asm/percpu.h
[ ... ]
> +static __always_inline u32
> +__percpu_add_return_amo_case_32(void __percpu *pcp, unsigned long val)
> +{
[ ... ]
> + return ret + (u32)val;
> +}
> +
> +#ifdef CONFIG_64BIT
> +static __always_inline u64
> +__percpu_add_return_amo_case_64(void __percpu *pcp, unsigned long val)
> +{
[ ... ]
> +static __always_inline unsigned long
> +__percpu_add_return_8_16(void __percpu *pcp, unsigned long val,
> + unsigned int bits)
> +{
> + __PCPU_OP_CONTEXT();
> + const u32 field_mask = GENMASK(bits - 1, 0);
This isn't a bug, but the add_return helpers repeat code that the rest of
this header generates with macros.
__percpu_add_return_amo_case_32() and __percpu_add_return_amo_case_64() are
identical apart from the width and the .w/.d suffix. Inside
__percpu_add_return_8_16(), the amoadd.b and amoadd.h asm blocks are
identical apart from the suffix and the (u8)/(u16) casts. The field mask is
rebuilt with GENMASK(bits - 1, 0) even though PERCPU_8_16_GET_MASK(sz)
already exists.
The neighbouring __PERCPU_AMO_OP_CASE and __PERCPU_8_16_OP_CASE helpers take
the width and suffix as macro parameters.
Could the add_return variants be generated the same way (taking sz/sfx, and
using PERCPU_8_16_GET_MASK), so the 32/64 and 8/16 copies don't need to be
kept in sync by hand?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/43205409812
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v10 5/9] riscv: implement preemptible this_cpu add_return operations
2026-11-12 10:41 ` [PATCH v10 5/9] riscv: implement preemptible this_cpu add_return operations Yunhui Cui
2026-11-12 11:30 ` bot+bpf-ci
@ 2026-11-12 12:09 ` David Laight
1 sibling, 0 replies; 15+ messages in thread
From: David Laight @ 2026-11-12 12:09 UTC (permalink / raw)
To: Yunhui Cui
Cc: pjw, palmer, aou, alex, dennis, tj, cl, oleg, ast, daniel, andrii,
eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
ihor.solodrai, bjorn, pulehui, puranjay, broonie, dave, jszhang,
tglx, conor.dooley, thuth, ajones, ben.dooks, rkrcmar,
samuel.holland, zong.li, debug, seanwascoding, andybnac,
menglong8.dong, cyrilbur, wangruikang, atishp, apatel, arnd,
nathan, nick.desaulniers+lkml, morbo, justinstitt, qingfang.deng,
linux-riscv, linux-kernel, linux-mm, bpf, linux-arch, llvm
On Thu, 12 Nov 2026 18:41:45 +0800
Yunhui Cui <cuiyunhui@bytedance.com> wrote:
> Apply the recoverable per-CPU address handling to add-return operations.
>
> Native-width AMOs return the old value, so derive the new value required
> by the percpu API from the operand. Use Zabha for subword operations when
> available, or a masked LR/SC sequence that returns the updated field.
I can't help feeling that some of this code is the same as that for
normal atomic accesses.
Can they me merged?
David
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v10 6/9] riscv: implement preemptible this_cpu xchg operations
2026-11-12 10:41 [PATCH v10 0/9] riscv: optimize percpu access Yunhui Cui
` (4 preceding siblings ...)
2026-11-12 10:41 ` [PATCH v10 5/9] riscv: implement preemptible this_cpu add_return operations Yunhui Cui
@ 2026-11-12 10:41 ` Yunhui Cui
2026-11-12 10:41 ` [PATCH v10 7/9] riscv: implement preemptible this_cpu cmpxchg operations Yunhui Cui
` (3 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Yunhui Cui @ 2026-11-12 10:41 UTC (permalink / raw)
To: pjw, palmer, aou, alex, dennis, tj, cl, oleg, ast, daniel, andrii,
eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
ihor.solodrai, bjorn, pulehui, puranjay, broonie, cuiyunhui, dave,
david.laight.linux, jszhang, tglx, conor.dooley, thuth, ajones,
ben.dooks, rkrcmar, samuel.holland, zong.li, debug, seanwascoding,
andybnac, menglong8.dong, cyrilbur, wangruikang, atishp, apatel,
arnd, nathan, nick.desaulniers+lkml, morbo, justinstitt,
qingfang.deng, linux-riscv, linux-kernel, linux-mm, bpf,
linux-arch, llvm
Apply the recoverable per-CPU address handling to exchange operations.
Use AMOSWAP for native-width and Zabha subword operations. Where Zabha is
unavailable, use a masked LR/SC sequence that preserves neighboring fields
in the same word.
Signed-off-by: Yunhui Cui <cuiyunhui@bytedance.com>
---
arch/riscv/include/asm/percpu.h | 92 +++++++++++++++++++++++++++++++++
1 file changed, 92 insertions(+)
diff --git a/arch/riscv/include/asm/percpu.h b/arch/riscv/include/asm/percpu.h
index 9ff02f3811550..810f4c5eaa508 100644
--- a/arch/riscv/include/asm/percpu.h
+++ b/arch/riscv/include/asm/percpu.h
@@ -326,6 +326,81 @@ __percpu_add_return_amo_case_16(void __percpu *pcp, unsigned long val)
return __percpu_add_return_8_16(pcp, val, 16);
}
+#define __PERCPU_XCHG_AMO_CASE(sfx, sz) \
+static __always_inline unsigned long \
+__percpu_xchg_case_##sz(void __percpu *pcp, unsigned long val) \
+{ \
+ __PCPU_OP_CONTEXT(); \
+ u##sz ret; \
+ \
+ asm volatile (__PCPU_GPRS_BEGIN \
+ "amoswap" #sfx " %[ret], %[val], 0(%[addr])\n" \
+ __PCPU_GPRS_END \
+ : [gprs] "=m" (__ti->pcpu_gprs), \
+ [off] "=&r" (__off), [addr] "=&r" (__addr), \
+ [ret] "=&r" (ret) \
+ : [pcpu_offset] "m" (__ti->pcpu_offset), \
+ [pcp] "r" (pcp), [val] "r" ((u##sz)val) \
+ : "memory"); \
+ \
+ return ret; \
+}
+
+__PERCPU_XCHG_AMO_CASE(.w, 32)
+#ifdef CONFIG_64BIT
+__PERCPU_XCHG_AMO_CASE(.d, 64)
+#endif
+
+#define __PERCPU_8_16_XCHG_CASE(sz, sfx) \
+static __always_inline unsigned long \
+__percpu_xchg_case_##sz(void __percpu *pcp, unsigned long val) \
+{ \
+ __PCPU_OP_CONTEXT(); \
+ const u32 field_mask = PERCPU_8_16_GET_MASK(sz); \
+ const unsigned long shift = PERCPU_8_16_GET_SHIFT(pcp); \
+ const u32 mask = field_mask << shift; \
+ const u32 new_field = (val & field_mask) << shift; \
+ void __percpu *pcp32 = PERCPU_8_16_GET_PCP32(pcp); \
+ u32 old, new, rc; \
+ u##sz ret; \
+ \
+ if (IS_ENABLED(CONFIG_RISCV_ISA_ZABHA) && \
+ riscv_has_extension_unlikely(RISCV_ISA_EXT_ZABHA)) { \
+ asm volatile (__PCPU_GPRS_BEGIN \
+ ".option push\n" \
+ ".option arch, +zabha\n" \
+ "amoswap" #sfx " %[ret], %[val], 0(%[addr])\n" \
+ ".option pop\n" \
+ __PCPU_GPRS_END \
+ : [gprs] "=m" (__ti->pcpu_gprs), \
+ [off] "=&r" (__off), [addr] "=&r" (__addr), \
+ [ret] "=&r" (ret) \
+ : [pcpu_offset] "m" (__ti->pcpu_offset), \
+ [pcp] "r" (pcp), [val] "r" ((u##sz)val) \
+ : "memory"); \
+ return ret; \
+ } \
+ \
+ asm volatile (__PCPU_GPRS_BEGIN \
+ "0: lr.w %[old], 0(%[addr])\n" \
+ "and %[new], %[old], %[inv_mask]\n" \
+ "or %[new], %[new], %[new_field]\n" \
+ "sc.w %[rc], %[new], 0(%[addr])\n" \
+ "bnez %[rc], 0b\n" \
+ __PCPU_GPRS_END \
+ : [gprs] "=m" (__ti->pcpu_gprs), \
+ [off] "=&r" (__off), [addr] "=&r" (__addr), \
+ [old] "=&r" (old), [new] "=&r" (new), [rc] "=&r" (rc) \
+ : [pcpu_offset] "m" (__ti->pcpu_offset), \
+ [pcp] "r" (pcp32), [inv_mask] "r" (~mask), \
+ [new_field] "r" (new_field) \
+ : "memory"); \
+ \
+ return (old & mask) >> shift; \
+}
+
+__PERCPU_8_16_XCHG_CASE(8, .b)
+__PERCPU_8_16_XCHG_CASE(16, .h)
#define _pcp_wrap(op, pcp, ...) \
({ \
op(&(pcp), __VA_ARGS__); \
@@ -338,6 +413,14 @@ __percpu_add_return_amo_case_16(void __percpu *pcp, unsigned long val)
(TYPEOF_UNQUAL(*__pcp_ptr))op(__pcp_ptr, ##args); \
})
+#define _pcp_wrap_xchg(op, pcp, val) \
+({ \
+ __auto_type __pcp_ptr = &(pcp); \
+ \
+ (TYPEOF_UNQUAL(*__pcp_ptr))op(__pcp_ptr, \
+ (unsigned long)(val)); \
+})
+
#define this_cpu_read_1(pcp) _pcp_wrap_return(__percpu_read_8, pcp)
#define this_cpu_read_2(pcp) _pcp_wrap_return(__percpu_read_16, pcp)
#define this_cpu_read_4(pcp) _pcp_wrap_return(__percpu_read_32, pcp)
@@ -390,9 +473,18 @@ __percpu_add_return_amo_case_16(void __percpu *pcp, unsigned long val)
_pcp_wrap_return(__percpu_add_return_amo_case_64, pcp, val)
#endif
+#define this_cpu_xchg_1(pcp, val) _pcp_wrap_xchg(__percpu_xchg_case_8, pcp, val)
+#define this_cpu_xchg_2(pcp, val) _pcp_wrap_xchg(__percpu_xchg_case_16, pcp, val)
+#define this_cpu_xchg_4(pcp, val) _pcp_wrap_xchg(__percpu_xchg_case_32, pcp, val)
+#ifdef CONFIG_64BIT
+#define this_cpu_xchg_8(pcp, val) _pcp_wrap_xchg(__percpu_xchg_case_64, pcp, val)
+#endif
+
#undef PERCPU_RW_OPS
#undef __PERCPU_AMO_OP_CASE
#undef __PERCPU_8_16_OP_CASE
+#undef __PERCPU_XCHG_AMO_CASE
+#undef __PERCPU_8_16_XCHG_CASE
#undef PERCPU_8_16_GET_SHIFT
#undef PERCPU_8_16_GET_MASK
#undef PERCPU_8_16_GET_PCP32
--
2.39.5
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v10 7/9] riscv: implement preemptible this_cpu cmpxchg operations
2026-11-12 10:41 [PATCH v10 0/9] riscv: optimize percpu access Yunhui Cui
` (5 preceding siblings ...)
2026-11-12 10:41 ` [PATCH v10 6/9] riscv: implement preemptible this_cpu xchg operations Yunhui Cui
@ 2026-11-12 10:41 ` Yunhui Cui
2026-11-12 10:41 ` [PATCH v10 8/9] riscv: implement preemptible this_cpu cmpxchg128 Yunhui Cui
` (2 subsequent siblings)
9 siblings, 0 replies; 15+ messages in thread
From: Yunhui Cui @ 2026-11-12 10:41 UTC (permalink / raw)
To: pjw, palmer, aou, alex, dennis, tj, cl, oleg, ast, daniel, andrii,
eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
ihor.solodrai, bjorn, pulehui, puranjay, broonie, cuiyunhui, dave,
david.laight.linux, jszhang, tglx, conor.dooley, thuth, ajones,
ben.dooks, rkrcmar, samuel.holland, zong.li, debug, seanwascoding,
andybnac, menglong8.dong, cyrilbur, wangruikang, atishp, apatel,
arnd, nathan, nick.desaulniers+lkml, morbo, justinstitt,
qingfang.deng, linux-riscv, linux-kernel, linux-mm, bpf,
linux-arch, llvm
Apply the recoverable per-CPU address handling to compare-and-exchange
operations. Providing this_cpu_cmpxchg() also allows asm-generic to derive
this_cpu_try_cmpxchg().
Use Zacas where available and LR/SC otherwise. Keep masked subword operands
XLEN-wide so RV64 LR.W sign extension does not affect the comparison, and
use typed helpers to separate asm-generic from the architecture cmpxchg
macro expansion.
Link: https://lore.kernel.org/r/20260826010748.zphu3wh2hlm4t4o2@offworld
Signed-off-by: Yunhui Cui <cuiyunhui@bytedance.com>
---
arch/riscv/include/asm/percpu.h | 140 ++++++++++++++++++++++++++++++++
1 file changed, 140 insertions(+)
diff --git a/arch/riscv/include/asm/percpu.h b/arch/riscv/include/asm/percpu.h
index 810f4c5eaa508..4d2fc10cbdab1 100644
--- a/arch/riscv/include/asm/percpu.h
+++ b/arch/riscv/include/asm/percpu.h
@@ -401,6 +401,123 @@ __percpu_xchg_case_##sz(void __percpu *pcp, unsigned long val) \
__PERCPU_8_16_XCHG_CASE(8, .b)
__PERCPU_8_16_XCHG_CASE(16, .h)
+
+#define __PERCPU_CMPXCHG_CASE(sz, lr_sfx, sc_sfx, cas_sfx) \
+static __always_inline unsigned long \
+__percpu_cmpxchg_case_##sz(void __percpu *pcp, unsigned long old, \
+ unsigned long new) \
+{ \
+ __PCPU_OP_CONTEXT(); \
+ const long cmp = (long)(s##sz)(u##sz)old; \
+ unsigned long rc; \
+ u##sz ret = (u##sz)old; \
+ \
+ if (IS_ENABLED(CONFIG_RISCV_ISA_ZACAS) && \
+ IS_ENABLED(CONFIG_TOOLCHAIN_HAS_ZACAS) && \
+ riscv_has_extension_unlikely(RISCV_ISA_EXT_ZACAS)) { \
+ asm volatile (__PCPU_GPRS_BEGIN \
+ ".option push\n" \
+ ".option arch, +zacas\n" \
+ "amocas" #cas_sfx " %[ret], %[new], 0(%[addr])\n" \
+ ".option pop\n" \
+ __PCPU_GPRS_END \
+ : [gprs] "=m" (__ti->pcpu_gprs), \
+ [off] "=&r" (__off), [addr] "=&r" (__addr), \
+ [ret] "+&r" (ret) \
+ : [pcpu_offset] "m" (__ti->pcpu_offset), \
+ [pcp] "r" (pcp), [new] "r" ((u##sz)new) \
+ : "memory"); \
+ } else { \
+ asm volatile (__PCPU_GPRS_BEGIN \
+ "0: lr" #lr_sfx " %[ret], 0(%[addr])\n" \
+ "bne %[ret], %[old], 1f\n" \
+ "sc" #sc_sfx " %[rc], %[new], 0(%[addr])\n" \
+ "bnez %[rc], 0b\n" \
+ "1:\n" \
+ __PCPU_GPRS_END \
+ : [gprs] "=m" (__ti->pcpu_gprs), \
+ [off] "=&r" (__off), [addr] "=&r" (__addr), \
+ [ret] "=&r" (ret), [rc] "=&r" (rc) \
+ : [pcpu_offset] "m" (__ti->pcpu_offset), \
+ [pcp] "r" (pcp), [old] "r" (cmp), \
+ [new] "r" ((u##sz)new) \
+ : "memory"); \
+ } \
+ \
+ return ret; \
+}
+
+__PERCPU_CMPXCHG_CASE(32, .w, .w, .w)
+#ifdef CONFIG_64BIT
+__PERCPU_CMPXCHG_CASE(64, .d, .d, .d)
+#endif
+
+/*
+ * Use XLEN-wide masks to clear the upper bits of an RV64 sign-extended
+ * LR.W result before comparing a high byte or halfword.
+ */
+#define __PERCPU_8_16_CMPXCHG_CASE(sz, sfx) \
+static __always_inline unsigned long \
+__percpu_cmpxchg_case_##sz(void __percpu *pcp, unsigned long old, \
+ unsigned long new) \
+{ \
+ __PCPU_OP_CONTEXT(); \
+ const unsigned long shift = PERCPU_8_16_GET_SHIFT(pcp); \
+ const unsigned long field_mask = PERCPU_8_16_GET_MASK(sz); \
+ const unsigned long mask = field_mask << shift; \
+ const unsigned long old_field = (old & field_mask) << shift; \
+ const unsigned long new_field = (new & field_mask) << shift; \
+ void __percpu *pcp32 = PERCPU_8_16_GET_PCP32(pcp); \
+ unsigned long ret32, tmp, rc; \
+ u##sz ret = (u##sz)old; \
+ \
+ if (IS_ENABLED(CONFIG_RISCV_ISA_ZABHA) && \
+ IS_ENABLED(CONFIG_RISCV_ISA_ZACAS) && \
+ IS_ENABLED(CONFIG_TOOLCHAIN_HAS_ZACAS) && \
+ riscv_has_extension_unlikely(RISCV_ISA_EXT_ZABHA) && \
+ riscv_has_extension_unlikely(RISCV_ISA_EXT_ZACAS)) { \
+ asm volatile (__PCPU_GPRS_BEGIN \
+ ".option push\n" \
+ ".option arch, +zacas, +zabha\n" \
+ "amocas" #sfx " %[ret], %[new], 0(%[addr])\n" \
+ ".option pop\n" \
+ __PCPU_GPRS_END \
+ : [gprs] "=m" (__ti->pcpu_gprs), \
+ [off] "=&r" (__off), [addr] "=&r" (__addr), \
+ [ret] "+&r" (ret) \
+ : [pcpu_offset] "m" (__ti->pcpu_offset), \
+ [pcp] "r" (pcp), [new] "r" ((u##sz)new) \
+ : "memory"); \
+ } else { \
+ asm volatile (__PCPU_GPRS_BEGIN \
+ "0: lr.w %[ret], 0(%[addr])\n" \
+ "and %[tmp], %[ret], %[mask]\n" \
+ "bne %[tmp], %[old], 1f\n" \
+ "and %[tmp], %[ret], %[inv_mask]\n" \
+ "or %[tmp], %[tmp], %[new]\n" \
+ "sc.w %[rc], %[tmp], 0(%[addr])\n" \
+ "bnez %[rc], 0b\n" \
+ "1:\n" \
+ __PCPU_GPRS_END \
+ : [gprs] "=m" (__ti->pcpu_gprs), \
+ [off] "=&r" (__off), [addr] "=&r" (__addr), \
+ [ret] "=&r" (ret32), [tmp] "=&r" (tmp), \
+ [rc] "=&r" (rc) \
+ : [pcpu_offset] "m" (__ti->pcpu_offset), \
+ [pcp] "r" (pcp32), [mask] "r" (mask), \
+ [old] "r" (old_field), \
+ [new] "r" (new_field), \
+ [inv_mask] "r" (~mask) \
+ : "memory"); \
+ \
+ ret = (ret32 & mask) >> shift; \
+ } \
+ \
+ return ret; \
+}
+
+__PERCPU_8_16_CMPXCHG_CASE(8, .b)
+__PERCPU_8_16_CMPXCHG_CASE(16, .h)
#define _pcp_wrap(op, pcp, ...) \
({ \
op(&(pcp), __VA_ARGS__); \
@@ -421,6 +538,15 @@ __PERCPU_8_16_XCHG_CASE(16, .h)
(unsigned long)(val)); \
})
+#define _pcp_wrap_cmpxchg(op, pcp, old, new) \
+({ \
+ __auto_type __pcp_ptr = &(pcp); \
+ \
+ (TYPEOF_UNQUAL(*__pcp_ptr))op(__pcp_ptr, \
+ (unsigned long)(old), \
+ (unsigned long)(new)); \
+})
+
#define this_cpu_read_1(pcp) _pcp_wrap_return(__percpu_read_8, pcp)
#define this_cpu_read_2(pcp) _pcp_wrap_return(__percpu_read_16, pcp)
#define this_cpu_read_4(pcp) _pcp_wrap_return(__percpu_read_32, pcp)
@@ -480,11 +606,25 @@ __PERCPU_8_16_XCHG_CASE(16, .h)
#define this_cpu_xchg_8(pcp, val) _pcp_wrap_xchg(__percpu_xchg_case_64, pcp, val)
#endif
+#define this_cpu_cmpxchg_1(pcp, o, n) \
+ _pcp_wrap_cmpxchg(__percpu_cmpxchg_case_8, pcp, o, n)
+#define this_cpu_cmpxchg_2(pcp, o, n) \
+ _pcp_wrap_cmpxchg(__percpu_cmpxchg_case_16, pcp, o, n)
+#define this_cpu_cmpxchg_4(pcp, o, n) \
+ _pcp_wrap_cmpxchg(__percpu_cmpxchg_case_32, pcp, o, n)
+#ifdef CONFIG_64BIT
+#define this_cpu_cmpxchg_8(pcp, o, n) \
+ _pcp_wrap_cmpxchg(__percpu_cmpxchg_case_64, pcp, o, n)
+#define this_cpu_cmpxchg64(pcp, o, n) this_cpu_cmpxchg_8(pcp, o, n)
+#endif
+
#undef PERCPU_RW_OPS
#undef __PERCPU_AMO_OP_CASE
#undef __PERCPU_8_16_OP_CASE
#undef __PERCPU_XCHG_AMO_CASE
#undef __PERCPU_8_16_XCHG_CASE
+#undef __PERCPU_CMPXCHG_CASE
+#undef __PERCPU_8_16_CMPXCHG_CASE
#undef PERCPU_8_16_GET_SHIFT
#undef PERCPU_8_16_GET_MASK
#undef PERCPU_8_16_GET_PCP32
--
2.39.5
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v10 8/9] riscv: implement preemptible this_cpu cmpxchg128
2026-11-12 10:41 [PATCH v10 0/9] riscv: optimize percpu access Yunhui Cui
` (6 preceding siblings ...)
2026-11-12 10:41 ` [PATCH v10 7/9] riscv: implement preemptible this_cpu cmpxchg operations Yunhui Cui
@ 2026-11-12 10:41 ` Yunhui Cui
2026-11-12 10:41 ` [PATCH v10 9/9] riscv: select HAVE_CMPXCHG_LOCAL Yunhui Cui
2026-11-12 12:10 ` [PATCH v10 0/9] riscv: optimize percpu access Jisheng Zhang
9 siblings, 0 replies; 15+ messages in thread
From: Yunhui Cui @ 2026-11-12 10:41 UTC (permalink / raw)
To: pjw, palmer, aou, alex, dennis, tj, cl, oleg, ast, daniel, andrii,
eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
ihor.solodrai, bjorn, pulehui, puranjay, broonie, cuiyunhui, dave,
david.laight.linux, jszhang, tglx, conor.dooley, thuth, ajones,
ben.dooks, rkrcmar, samuel.holland, zong.li, debug, seanwascoding,
andybnac, menglong8.dong, cyrilbur, wangruikang, atishp, apatel,
arnd, nathan, nick.desaulniers+lkml, morbo, justinstitt,
qingfang.deng, linux-riscv, linux-kernel, linux-mm, bpf,
linux-arch, llvm
Extend the preemptible compare-and-exchange support to 128-bit values on
systems with native AMOCAS.Q support.
AMOCAS.Q implicitly consumes register pairs, so keep both halves of the new
value in fixed registers even when either half is zero. Retain the
local-IRQ-protected fallback when native cmpxchg128 is unavailable.
Signed-off-by: Yunhui Cui <cuiyunhui@bytedance.com>
---
arch/riscv/include/asm/percpu.h | 66 +++++++++++++++++++++++++++++++++
1 file changed, 66 insertions(+)
diff --git a/arch/riscv/include/asm/percpu.h b/arch/riscv/include/asm/percpu.h
index 4d2fc10cbdab1..1d35fc89eeeb2 100644
--- a/arch/riscv/include/asm/percpu.h
+++ b/arch/riscv/include/asm/percpu.h
@@ -9,10 +9,12 @@
#include <linux/thread_info.h>
#include <asm/asm.h>
+#include <asm/cmpxchg.h>
#include <asm/cpufeature-macros.h>
#include <asm/current.h>
#include <asm/gpr-num.h>
#include <asm/hwcap.h>
+#include <asm/irqflags.h>
#define __my_cpu_offset (((struct thread_info *)current)->pcpu_offset)
@@ -518,6 +520,65 @@ __percpu_cmpxchg_case_##sz(void __percpu *pcp, unsigned long old, \
__PERCPU_8_16_CMPXCHG_CASE(8, .b)
__PERCPU_8_16_CMPXCHG_CASE(16, .h)
+
+#ifdef system_has_cmpxchg128
+static __always_inline u128
+__percpu_cmpxchg128_case(void __percpu *pcp, u128 old, u128 new)
+{
+ __PCPU_OP_CONTEXT();
+ union __u128_halves old_halves = { .full = old };
+ union __u128_halves new_halves = { .full = new };
+
+ register unsigned long new_lo asm("t1") = new_halves.low;
+ register unsigned long new_hi asm("t2") = new_halves.high;
+ register unsigned long old_lo asm("t3") = old_halves.low;
+ register unsigned long old_hi asm("t4") = old_halves.high;
+
+ asm volatile (__PCPU_GPRS_BEGIN
+ ".option push\n"
+ ".option arch, +zacas\n"
+ "amocas.q %[old_lo], %[new_lo], 0(%[addr])\n"
+ ".option pop\n"
+ __PCPU_GPRS_END
+ : [gprs] "=m" (__ti->pcpu_gprs),
+ [off] "=&r" (__off), [addr] "=&r" (__addr),
+ [old_lo] "+&r" (old_lo), [old_hi] "+&r" (old_hi)
+ : [pcpu_offset] "m" (__ti->pcpu_offset),
+ [pcp] "r" (pcp), [new_lo] "r" (new_lo),
+ [new_hi] "r" (new_hi)
+ : "memory");
+
+ return ((u128)old_hi << 64) | old_lo;
+}
+
+static __always_inline u128
+__percpu_generic_cmpxchg128(void __percpu *pcp, u128 old, u128 new)
+{
+ unsigned long flags;
+ u128 *ptr;
+ u128 ret;
+
+ flags = arch_local_irq_save();
+ ptr = (u128 *)((__force unsigned long)pcp +
+ __my_cpu_offset);
+ ret = *ptr;
+ if (ret == old)
+ *ptr = new;
+ arch_local_irq_restore(flags);
+
+ return ret;
+}
+
+static __always_inline u128
+__percpu_cmpxchg128(void __percpu *pcp, u128 old, u128 new)
+{
+ if (system_has_cmpxchg128())
+ return __percpu_cmpxchg128_case(pcp, old, new);
+
+ return __percpu_generic_cmpxchg128(pcp, old, new);
+}
+#endif
+
#define _pcp_wrap(op, pcp, ...) \
({ \
op(&(pcp), __VA_ARGS__); \
@@ -618,6 +679,11 @@ __PERCPU_8_16_CMPXCHG_CASE(16, .h)
#define this_cpu_cmpxchg64(pcp, o, n) this_cpu_cmpxchg_8(pcp, o, n)
#endif
+#ifdef system_has_cmpxchg128
+#define this_cpu_cmpxchg128(pcp, o, n) \
+ _pcp_wrap_return(__percpu_cmpxchg128, pcp, (u128)(o), (u128)(n))
+#endif
+
#undef PERCPU_RW_OPS
#undef __PERCPU_AMO_OP_CASE
#undef __PERCPU_8_16_OP_CASE
--
2.39.5
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH v10 9/9] riscv: select HAVE_CMPXCHG_LOCAL
2026-11-12 10:41 [PATCH v10 0/9] riscv: optimize percpu access Yunhui Cui
` (7 preceding siblings ...)
2026-11-12 10:41 ` [PATCH v10 8/9] riscv: implement preemptible this_cpu cmpxchg128 Yunhui Cui
@ 2026-11-12 10:41 ` Yunhui Cui
2026-11-12 11:30 ` bot+bpf-ci
2026-11-12 12:10 ` [PATCH v10 0/9] riscv: optimize percpu access Jisheng Zhang
9 siblings, 1 reply; 15+ messages in thread
From: Yunhui Cui @ 2026-11-12 10:41 UTC (permalink / raw)
To: pjw, palmer, aou, alex, dennis, tj, cl, oleg, ast, daniel, andrii,
eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
ihor.solodrai, bjorn, pulehui, puranjay, broonie, cuiyunhui, dave,
david.laight.linux, jszhang, tglx, conor.dooley, thuth, ajones,
ben.dooks, rkrcmar, samuel.holland, zong.li, debug, seanwascoding,
andybnac, menglong8.dong, cyrilbur, wangruikang, atishp, apatel,
arnd, nathan, nick.desaulniers+lkml, morbo, justinstitt,
qingfang.deng, linux-riscv, linux-kernel, linux-mm, bpf,
linux-arch, llvm
Now that RISC-V provides this_cpu_cmpxchg(), select
HAVE_CMPXCHG_LOCAL so mm/vmstat.c and lib/percpu_counter.c can use
this_cpu_try_cmpxchg() instead of serializing per-CPU counter updates
with local_irq_save() and local_irq_restore().
Suggested-by: Davidlohr Bueso <dave@stgolabs.net>
Link: https://lore.kernel.org/r/20260826010748.zphu3wh2hlm4t4o2@offworld
Signed-off-by: Yunhui Cui <cuiyunhui@bytedance.com>
---
arch/riscv/Kconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
index bde1f1885038f..458ef01e926a4 100644
--- a/arch/riscv/Kconfig
+++ b/arch/riscv/Kconfig
@@ -155,6 +155,7 @@ config RISCV
select HAVE_ARCH_VMAP_STACK if MMU && 64BIT
select HAVE_ASM_MODVERSIONS
select HAVE_BUILDTIME_MCOUNT_SORT
+ select HAVE_CMPXCHG_LOCAL
select HAVE_CONTEXT_TRACKING_USER
select HAVE_DEBUG_KMEMLEAK
select HAVE_DMA_CONTIGUOUS if MMU
--
2.39.5
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v10 9/9] riscv: select HAVE_CMPXCHG_LOCAL
2026-11-12 10:41 ` [PATCH v10 9/9] riscv: select HAVE_CMPXCHG_LOCAL Yunhui Cui
@ 2026-11-12 11:30 ` bot+bpf-ci
0 siblings, 0 replies; 15+ messages in thread
From: bot+bpf-ci @ 2026-11-12 11:30 UTC (permalink / raw)
To: cuiyunhui, pjw, palmer, aou, alex, dennis, tj, cl, oleg, ast,
daniel, andrii, eddyz87, memxor, martin.lau, song, yonghong.song,
jolsa, emil, ihor.solodrai, bjorn, pulehui, puranjay, broonie,
cuiyunhui, dave, david.laight.linux, jszhang, tglx, conor.dooley,
thuth, ajones, ben.dooks, rkrcmar, samuel.holland, zong.li, debug,
seanwascoding, andybnac, menglong8.dong, cyrilbur, wangruikang,
atishp, apatel, arnd, nathan, nick.desaulniers+lkml, morbo,
justinstitt
Cc: ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason,
ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 1491 bytes --]
> riscv: select HAVE_CMPXCHG_LOCAL
TL;DR: Nit only, no runtime problem: selecting HAVE_CMPXCHG_LOCAL on riscv may
need the cmpxchg-local arch-support.txt feature table updated from TODO to ok
in the same patch.
> diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig
> --- a/arch/riscv/Kconfig
> +++ b/arch/riscv/Kconfig
> @@ -155,6 +155,7 @@ config RISCV
> select HAVE_ARCH_VMAP_STACK if MMU && 64BIT
> select HAVE_ASM_MODVERSIONS
> select HAVE_BUILDTIME_MCOUNT_SORT
> + select HAVE_CMPXCHG_LOCAL
> select HAVE_CONTEXT_TRACKING_USER
This isn't a runtime problem, but should the feature table be updated in
this patch as well?
Documentation/features/locking/cmpxchg-local/arch-support.txt is keyed on
this Kconfig symbol and still lists riscv as TODO:
| riscv: | TODO |
Nothing later in the series changes it. Other riscv patches that select a
feature symbol update the matching arch-support.txt in the same commit, for
example d1f014012571 ("riscv: enable HAVE_IOREMAP_PROT"). LoongArch
selected the same symbol without updating the table, and it needed a
follow-up in 0c4c9abbf68b ("Documentation/features: Mark cmpxchg-local as
supported on loongarch"). Would changing the riscv row to "ok" here keep
the table accurate?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/43205409812
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v10 0/9] riscv: optimize percpu access
2026-11-12 10:41 [PATCH v10 0/9] riscv: optimize percpu access Yunhui Cui
` (8 preceding siblings ...)
2026-11-12 10:41 ` [PATCH v10 9/9] riscv: select HAVE_CMPXCHG_LOCAL Yunhui Cui
@ 2026-11-12 12:10 ` Jisheng Zhang
9 siblings, 0 replies; 15+ messages in thread
From: Jisheng Zhang @ 2026-11-12 12:10 UTC (permalink / raw)
To: Yunhui Cui
Cc: pjw, palmer, aou, alex, dennis, tj, cl, oleg, ast, daniel, andrii,
eddyz87, memxor, martin.lau, song, yonghong.song, jolsa, emil,
ihor.solodrai, bjorn, pulehui, puranjay, broonie, dave,
david.laight.linux, tglx, conor.dooley, thuth, ajones, ben.dooks,
rkrcmar, samuel.holland, zong.li, debug, seanwascoding, andybnac,
menglong8.dong, cyrilbur, wangruikang, atishp, apatel, arnd,
nathan, nick.desaulniers+lkml, morbo, justinstitt, qingfang.deng,
linux-riscv, linux-kernel, linux-mm, bpf, linux-arch, llvm
On Thu, Nov 12, 2026 at 06:41:40PM +0800, Yunhui Cui wrote:
> RISC-V has no asm/percpu.h, so this_cpu_*() read-modify-write
> operations use generic fallbacks that disable local interrupts. Per-CPU
> addressing also derives the current offset from the CPU number and
> __per_cpu_offset[].
>
> Cache the per-CPU offset in thread_info and implement preemptible this_cpu
> operations using recoverable GPR critical sections. Each operation records
> the registers used to form its per-CPU address so exception return can
> repair them if the task resumes on a different CPU. Use AMOs for
> native-width operations. Use Zabha for 8-bit and 16-bit operations when
> available, or a 32-bit LR/SC fallback otherwise. Finally, select
> HAVE_CMPXCHG_LOCAL so common per-CPU counter users can use
> this_cpu_try_cmpxchg().
>
> The recoverable-GPR scheme follows the arm64 design. Only the protocol is
> common, however: RISC-V uses a cached offset in thread_info, its own GPR
> encoding and exception entry code, and AMO/LR/SC operation sequences.
> Factoring these pieces into generic code would share little beyond the
> field layout while coupling architecture entry paths, so keep the
> implementation under arch/riscv.
>
> As with arm64's SDEI path, independent NMI-like entry paths must
> participate in the entry-state protocol directly. Keep the save, clear and
> restore operations in common RISC-V assembly macros so SBI Supervisor
> Software Events (SSE) can preserve an interrupted per-CPU operation. SBI
> SSE support is developed separately and is not a dependency of this
> series.
>
> With CONFIG_PREEMPT_LAZY=y, GCC 15, the same linux-next base and
> configuration, an in-kernel microbenchmark pinned to one CPU ran each
> operation 2,000,000 times for 15 rounds. The table reports the median
> empty-loop-adjusted time; lower is better.
>
> operation v9 (ns/op) v10 (ns/op) change
> this_cpu_read(u64) 5.668 0.333 -94.1%
> this_cpu_write(u64) 5.668 0.395 -93.0%
> this_cpu_add(u64) 8.002 8.004 +0.0%
> this_cpu_add_return(u64) 8.335 8.004 -4.0%
> this_cpu_xchg(u64) 8.278 8.004 -3.3%
> this_cpu_cmpxchg(u64) 18.713 16.133 -13.8%
Hi Yunhui,
Thanks for this series. Could you plz run some benchmark to compare the
performance between v9 and v10? We can use some benchmarks used in arm64
version:
https://lore.kernel.org/linux-arm-kernel/28978498-348e-4cfd-af72-1146a106e96b@arm.com/
Something in my mind: hackbench, kernbench, sysbench-cpu, sysbench-mutex,
sysbench-thread, perf/futex , perf/sched and perf/syscall.
I will read the code during this weekend.
Thanks
>
> With the same configuration, vmlinux .text decreased from 14,369,618 to
> 14,212,882 bytes (-156,736 bytes, -1.09%). percpu_counter_add_batch()
> decreased from 330 to 228 bytes.
>
> Changes since v9:
>
> - Rework the this_cpu operations to remain preemptible using recoverable
> GPR critical sections, as suggested by David Laight and Jisheng Zhang.
> - Put the cached per-CPU offset first, then split the exception/fixup
> infrastructure and operation classes into independently reviewable
> patches.
> - Keep GPR-state save and restore in reusable assembly macros for NMI-like
> entry paths such as SBI SSE.
> - Keep masked LR/SC cmpxchg operands XLEN-wide so RV64 high-byte and
> high-halfword comparisons are not affected by LR.W sign extension.
> - Force both halves of the AMOCAS.Q new value into fixed registers so the
> implicit register pair remains valid when either half is zero.
> - Use TYPEOF_UNQUAL() in wrappers and fully parenthesize this_cpu_write()
> values.
>
> Changes since v8:
>
> - Drop the PIO helper patch, which has already been merged.
> - Rename the first patch and update its changelog as suggested by
> Davidlohr Bueso.
> - Use value-returning AMOs instead of rd=x0 for CPU-local operations.
> - Remove unnecessary memory clobbers from the new relaxed AMO and LR/SC
> code.
> - Use .option arch,+zabha for the Zabha inline asm, matching the
> existing RISC-V cmpxchg implementation.
> - Isolate arch_cmpxchg_relaxed() behind typed always-inline helpers to
> avoid macro variable capture while preserving atomic instrumentation.
> - Make the cmpxchg128 fallback use the already resolved per-CPU pointer,
> avoiding the same macro variable capture.
> - Copy the cached per-CPU offset from prev to next during context switch,
> as suggested by Thomas Gleixner.
> - Initialize secondary idle tasks in the common startup path, covering
> parallel bringup and CPU hotplug, and remove the duplicate initialization
> from smp_callin(), as reported by Sashiko.
> - Select HAVE_CMPXCHG_LOCAL in a separate patch.
>
> Yunhui Cui (9):
> riscv: store percpu offset into thread_info
> riscv: add infrastructure for preemptible this_cpu operations
> riscv: implement preemptible this_cpu read and write operations
> riscv: implement preemptible this_cpu void RMW operations
> riscv: implement preemptible this_cpu add_return operations
> riscv: implement preemptible this_cpu xchg operations
> riscv: implement preemptible this_cpu cmpxchg operations
> riscv: implement preemptible this_cpu cmpxchg128
> riscv: select HAVE_CMPXCHG_LOCAL
>
> arch/riscv/Kconfig | 1 +
> arch/riscv/include/asm/asm.h | 25 +-
> arch/riscv/include/asm/percpu.h | 701 +++++++++++++++++++++++++++
> arch/riscv/include/asm/ptrace.h | 15 +
> arch/riscv/include/asm/switch_to.h | 9 +
> arch/riscv/include/asm/thread_info.h | 4 +-
> arch/riscv/kernel/Makefile | 1 +
> arch/riscv/kernel/asm-offsets.c | 4 +-
> arch/riscv/kernel/entry-common.c | 32 ++
> arch/riscv/kernel/entry.S | 10 +
> arch/riscv/kernel/smpboot.c | 10 +-
> arch/riscv/net/bpf_jit_comp64.c | 9 +-
> 12 files changed, 805 insertions(+), 16 deletions(-)
> create mode 100644 arch/riscv/include/asm/percpu.h
> create mode 100644 arch/riscv/kernel/entry-common.c
>
>
> base-commit: ddfe51b0a0f5dc9e415cc2c8ad8db17532dd426b
> --
> 2.39.5
>
^ permalink raw reply [flat|nested] 15+ messages in thread