* [PATCH v9 1/3] riscv: implement this_cpu operations
2026-11-08 11:04 [PATCH v9 0/3] riscv: optimize percpu access Yunhui Cui
@ 2026-11-08 11:04 ` Yunhui Cui
2026-11-08 11:58 ` bot+bpf-ci
2026-11-08 21:32 ` David Laight
2026-11-08 11:04 ` [PATCH v9 2/3] riscv: store percpu offset into thread_info Yunhui Cui
2026-11-08 11:04 ` [PATCH v9 3/3] riscv: select HAVE_CMPXCHG_LOCAL Yunhui Cui
2 siblings, 2 replies; 6+ messages in thread
From: Yunhui Cui @ 2026-11-08 11:04 UTC (permalink / raw)
To: pjw, palmer, aou, alex, dennis, tj, cl, ast, daniel, andrii,
martin.lau, eddyz87, memxor, song, yonghong.song, jolsa, bjorn,
pulehui, puranjay, thuth, ajones, ben.dooks, rkrcmar,
samuel.holland, zong.li, conor.dooley, tglx, debug,
seanwascoding, andybnac, menglong8.dong, cyrilbur, wangruikang,
atishp, apatel, linux-riscv, linux-kernel, linux-mm, bpf, arnd,
nathan, nick.desaulniers+lkml, morbo, justinstitt, qingfang.deng,
linux-arch, llvm, dave, emil, ihor.solodrai, broonie, cuiyunhui
RISC-V has no asm/percpu.h, so this_cpu_*() read-modify-write
operations use generic implementations that disable local interrupts.
Implement RISC-V-specific this_cpu operations with preemption disabled
to keep accesses on the current CPU. Native-width read-modify-write
operations use relaxed AMOs, while 8-bit and 16-bit read-modify-write
operations use Zabha when available or a local 32-bit LR/SC sequence
otherwise.
Link: https://lore.kernel.org/r/20260826010748.zphu3wh2hlm4t4o2@offworld
Tested-by: Davidlohr Bueso <dave@stgolabs.net>
Signed-off-by: Yunhui Cui <cuiyunhui@bytedance.com>
---
arch/riscv/include/asm/percpu.h | 569 ++++++++++++++++++++++++++++++++
1 file changed, 569 insertions(+)
create mode 100644 arch/riscv/include/asm/percpu.h
diff --git a/arch/riscv/include/asm/percpu.h b/arch/riscv/include/asm/percpu.h
new file mode 100644
index 0000000000000..280b122b9fe69
--- /dev/null
+++ b/arch/riscv/include/asm/percpu.h
@@ -0,0 +1,569 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef __ASM_PERCPU_H
+#define __ASM_PERCPU_H
+
+#include <linux/bits.h>
+#include <linux/preempt.h>
+
+#include <asm/alternative-macros.h>
+#include <asm/cmpxchg.h>
+#include <asm/cpufeature-macros.h>
+#include <asm/hwcap.h>
+#include <asm/irqflags.h>
+
+#define PERCPU_RW_OPS(sz) \
+static inline unsigned long __percpu_read_##sz(void *ptr) \
+{ \
+ return READ_ONCE(*(u##sz *)ptr); \
+} \
+ \
+static inline void __percpu_write_##sz(void *ptr, unsigned long val) \
+{ \
+ WRITE_ONCE(*(u##sz *)ptr, (u##sz)val); \
+}
+
+PERCPU_RW_OPS(8)
+PERCPU_RW_OPS(16)
+PERCPU_RW_OPS(32)
+
+#ifdef CONFIG_64BIT
+PERCPU_RW_OPS(64)
+#endif
+
+/*
+ * Use a real destination register instead of x0 even when the result is
+ * unused. Implementations may execute rd=x0 AMOs at a memory controller
+ * without returning the old value to the CPU. Value-returning AMOs are
+ * more likely to execute near the CPU, for example in the L1 cache. This
+ * better suits per-CPU data, which is typically accessed repeatedly by
+ * the same CPU.
+ *
+ * https://lore.kernel.org/r/e7d539ed-ced0-4b96-8ecd-048a5b803b85@paulmck-laptop
+ */
+#define __PERCPU_AMO_OP_CASE(sfx, name, sz, amo_insn) \
+static inline void \
+__percpu_##name##_amo_case_##sz(void *ptr, unsigned long val) \
+{ \
+ u##sz tmp; \
+ \
+ asm volatile ( \
+ "amo" #amo_insn #sfx " %[tmp], %[val], %[ptr]" \
+ : [ptr] "+A" (*(u##sz *)ptr), [tmp] "=r" (tmp) \
+ : [val] "r" ((u##sz)(val))); \
+}
+
+__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
+
+static inline u32 __percpu_add_return_amo_case_32(void *ptr, unsigned long val)
+{
+ register u32 ret;
+
+ asm volatile ("amoadd.w %[ret], %[val], %[ptr]"
+ : [ptr] "+A" (*(u32 *)ptr), [ret] "=r" (ret)
+ : [val] "r" ((u32)(val)));
+
+ return ret + val;
+}
+
+#ifdef CONFIG_64BIT
+static inline u64 __percpu_add_return_amo_case_64(void *ptr, unsigned long val)
+{
+ register u64 ret;
+
+ asm volatile ("amoadd.d %[ret], %[val], %[ptr]"
+ : [ptr] "+A" (*(u64 *)ptr), [ret] "=r" (ret)
+ : [val] "r" ((u64)(val)));
+
+ return ret + val;
+}
+#endif
+
+#define PERCPU_8_16_GET_SHIFT(ptr) (((unsigned long)(ptr) & 0x3) * BITS_PER_BYTE)
+#define PERCPU_8_16_GET_MASK(sz) GENMASK((sz) - 1, 0)
+#define PERCPU_8_16_GET_PTR32(ptr) ((u32 *)((unsigned long)(ptr) & ~0x3))
+
+static inline void __percpu_add_amo_case_8(void *ptr, unsigned long val)
+{
+ u8 tmp;
+
+ if (IS_ENABLED(CONFIG_RISCV_ISA_ZABHA) &&
+ riscv_has_extension_unlikely(RISCV_ISA_EXT_ZABHA)) {
+ asm volatile (".option push\n"
+ ".option arch, +zabha\n"
+ "amoadd.b %[tmp], %[val], %[ptr]\n"
+ ".option pop"
+ : [ptr] "+A" (*(u8 *)ptr), [tmp] "=r" (tmp)
+ : [val] "r" ((u8)(val & PERCPU_8_16_GET_MASK(8))));
+ } else {
+ u32 *ptr32 = PERCPU_8_16_GET_PTR32(ptr);
+ const unsigned long shift = PERCPU_8_16_GET_SHIFT(ptr);
+ const u32 mask = PERCPU_8_16_GET_MASK(8) << shift;
+ const u8 val_trunc = val & PERCPU_8_16_GET_MASK(8);
+ u32 old, new;
+ u8 field;
+
+ asm volatile ("0: lr.w %0, %2\n"
+ "and %3, %0, %4\n"
+ "srl %3, %3, %5\n"
+ "add %3, %3, %6\n"
+ "and %3, %3, %8\n"
+ "sll %3, %3, %5\n"
+ "and %1, %0, %7\n"
+ "or %1, %1, %3\n"
+ "sc.w %1, %1, %2\n"
+ "bnez %1, 0b\n"
+ : "=&r"(old), "=&r"(new), "+A"(*ptr32), "=&r"(field)
+ : "r"(mask), "r"(shift), "r"(val_trunc), "r"(~mask),
+ "r"(PERCPU_8_16_GET_MASK(8)));
+ }
+}
+
+static inline void __percpu_add_amo_case_16(void *ptr, unsigned long val)
+{
+ u16 tmp;
+
+ if (IS_ENABLED(CONFIG_RISCV_ISA_ZABHA) &&
+ riscv_has_extension_unlikely(RISCV_ISA_EXT_ZABHA)) {
+ asm volatile (".option push\n"
+ ".option arch, +zabha\n"
+ "amoadd.h %[tmp], %[val], %[ptr]\n"
+ ".option pop"
+ : [ptr] "+A" (*(u16 *)ptr), [tmp] "=r" (tmp)
+ : [val] "r" ((u16)(val & PERCPU_8_16_GET_MASK(16))));
+ } else {
+ u32 *ptr32 = PERCPU_8_16_GET_PTR32(ptr);
+ const unsigned long shift = PERCPU_8_16_GET_SHIFT(ptr);
+ const u32 mask = PERCPU_8_16_GET_MASK(16) << shift;
+ const u16 val_trunc = val & PERCPU_8_16_GET_MASK(16);
+ u32 old, new;
+ u16 field;
+
+ asm volatile ("0: lr.w %0, %2\n"
+ "and %3, %0, %4\n"
+ "srl %3, %3, %5\n"
+ "add %3, %3, %6\n"
+ "and %3, %3, %8\n"
+ "sll %3, %3, %5\n"
+ "and %1, %0, %7\n"
+ "or %1, %1, %3\n"
+ "sc.w %1, %1, %2\n"
+ "bnez %1, 0b\n"
+ : "=&r"(old), "=&r"(new), "+A"(*ptr32), "=&r"(field)
+ : "r"(mask), "r"(shift), "r"(val_trunc), "r"(~mask),
+ "r"(PERCPU_8_16_GET_MASK(16)));
+ }
+}
+
+static inline void __percpu_and_amo_case_8(void *ptr, unsigned long val)
+{
+ u8 tmp;
+
+ if (IS_ENABLED(CONFIG_RISCV_ISA_ZABHA) &&
+ riscv_has_extension_unlikely(RISCV_ISA_EXT_ZABHA)) {
+ asm volatile (".option push\n"
+ ".option arch, +zabha\n"
+ "amoand.b %[tmp], %[val], %[ptr]\n"
+ ".option pop"
+ : [ptr] "+A" (*(u8 *)ptr), [tmp] "=r" (tmp)
+ : [val] "r" ((u8)(val & PERCPU_8_16_GET_MASK(8))));
+ } else {
+ u32 *ptr32 = PERCPU_8_16_GET_PTR32(ptr);
+ const unsigned long shift = PERCPU_8_16_GET_SHIFT(ptr);
+ const u32 mask = PERCPU_8_16_GET_MASK(8) << shift;
+ const u8 val_trunc = val & PERCPU_8_16_GET_MASK(8);
+ u32 old, new;
+ u8 field;
+
+ asm volatile ("0: lr.w %0, %2\n"
+ "and %3, %0, %4\n"
+ "srl %3, %3, %5\n"
+ "and %3, %3, %6\n"
+ "and %3, %3, %8\n"
+ "sll %3, %3, %5\n"
+ "and %1, %0, %7\n"
+ "or %1, %1, %3\n"
+ "sc.w %1, %1, %2\n"
+ "bnez %1, 0b\n"
+ : "=&r"(old), "=&r"(new), "+A"(*ptr32), "=&r"(field)
+ : "r"(mask), "r"(shift), "r"(val_trunc), "r"(~mask),
+ "r"(PERCPU_8_16_GET_MASK(8)));
+ }
+}
+
+static inline void __percpu_and_amo_case_16(void *ptr, unsigned long val)
+{
+ u16 tmp;
+
+ if (IS_ENABLED(CONFIG_RISCV_ISA_ZABHA) &&
+ riscv_has_extension_unlikely(RISCV_ISA_EXT_ZABHA)) {
+ asm volatile (".option push\n"
+ ".option arch, +zabha\n"
+ "amoand.h %[tmp], %[val], %[ptr]\n"
+ ".option pop"
+ : [ptr] "+A" (*(u16 *)ptr), [tmp] "=r" (tmp)
+ : [val] "r" ((u16)(val & PERCPU_8_16_GET_MASK(16))));
+ } else {
+ u32 *ptr32 = PERCPU_8_16_GET_PTR32(ptr);
+ const unsigned long shift = PERCPU_8_16_GET_SHIFT(ptr);
+ const u32 mask = PERCPU_8_16_GET_MASK(16) << shift;
+ const u16 val_trunc = val & PERCPU_8_16_GET_MASK(16);
+ u32 old, new;
+ u16 field;
+
+ asm volatile ("0: lr.w %0, %2\n"
+ "and %3, %0, %4\n"
+ "srl %3, %3, %5\n"
+ "and %3, %3, %6\n"
+ "and %3, %3, %8\n"
+ "sll %3, %3, %5\n"
+ "and %1, %0, %7\n"
+ "or %1, %1, %3\n"
+ "sc.w %1, %1, %2\n"
+ "bnez %1, 0b\n"
+ : "=&r"(old), "=&r"(new), "+A"(*ptr32), "=&r"(field)
+ : "r"(mask), "r"(shift), "r"(val_trunc), "r"(~mask),
+ "r"(PERCPU_8_16_GET_MASK(16)));
+ }
+}
+
+static inline void __percpu_or_amo_case_8(void *ptr, unsigned long val)
+{
+ u8 tmp;
+
+ if (IS_ENABLED(CONFIG_RISCV_ISA_ZABHA) &&
+ riscv_has_extension_unlikely(RISCV_ISA_EXT_ZABHA)) {
+ asm volatile (".option push\n"
+ ".option arch, +zabha\n"
+ "amoor.b %[tmp], %[val], %[ptr]\n"
+ ".option pop"
+ : [ptr] "+A" (*(u8 *)ptr), [tmp] "=r" (tmp)
+ : [val] "r" ((u8)(val & PERCPU_8_16_GET_MASK(8))));
+ } else {
+ u32 *ptr32 = PERCPU_8_16_GET_PTR32(ptr);
+ const unsigned long shift = PERCPU_8_16_GET_SHIFT(ptr);
+ const u32 mask = PERCPU_8_16_GET_MASK(8) << shift;
+ const u8 val_trunc = val & PERCPU_8_16_GET_MASK(8);
+ u32 old, new;
+ u8 field;
+
+ asm volatile ("0: lr.w %0, %2\n"
+ "and %3, %0, %4\n"
+ "srl %3, %3, %5\n"
+ "or %3, %3, %6\n"
+ "and %3, %3, %8\n"
+ "sll %3, %3, %5\n"
+ "and %1, %0, %7\n"
+ "or %1, %1, %3\n"
+ "sc.w %1, %1, %2\n"
+ "bnez %1, 0b\n"
+ : "=&r"(old), "=&r"(new), "+A"(*ptr32), "=&r"(field)
+ : "r"(mask), "r"(shift), "r"(val_trunc), "r"(~mask),
+ "r"(PERCPU_8_16_GET_MASK(8)));
+ }
+}
+
+static inline void __percpu_or_amo_case_16(void *ptr, unsigned long val)
+{
+ u16 tmp;
+
+ if (IS_ENABLED(CONFIG_RISCV_ISA_ZABHA) &&
+ riscv_has_extension_unlikely(RISCV_ISA_EXT_ZABHA)) {
+ asm volatile (".option push\n"
+ ".option arch, +zabha\n"
+ "amoor.h %[tmp], %[val], %[ptr]\n"
+ ".option pop"
+ : [ptr] "+A" (*(u16 *)ptr), [tmp] "=r" (tmp)
+ : [val] "r" ((u16)(val & PERCPU_8_16_GET_MASK(16))));
+ } else {
+ u32 *ptr32 = PERCPU_8_16_GET_PTR32(ptr);
+ const unsigned long shift = PERCPU_8_16_GET_SHIFT(ptr);
+ const u32 mask = PERCPU_8_16_GET_MASK(16) << shift;
+ const u16 val_trunc = val & PERCPU_8_16_GET_MASK(16);
+ u32 old, new;
+ u16 field;
+
+ asm volatile ("0: lr.w %0, %2\n"
+ "and %3, %0, %4\n"
+ "srl %3, %3, %5\n"
+ "or %3, %3, %6\n"
+ "and %3, %3, %8\n"
+ "sll %3, %3, %5\n"
+ "and %1, %0, %7\n"
+ "or %1, %1, %3\n"
+ "sc.w %1, %1, %2\n"
+ "bnez %1, 0b\n"
+ : "=&r"(old), "=&r"(new), "+A"(*ptr32), "=&r"(field)
+ : "r"(mask), "r"(shift), "r"(val_trunc), "r"(~mask),
+ "r"(PERCPU_8_16_GET_MASK(16)));
+ }
+}
+
+static inline u8 __percpu_add_return_amo_case_8(void *ptr, unsigned long val)
+{
+ if (IS_ENABLED(CONFIG_RISCV_ISA_ZABHA) &&
+ riscv_has_extension_unlikely(RISCV_ISA_EXT_ZABHA)) {
+ register u8 ret;
+
+ asm volatile (".option push\n"
+ ".option arch, +zabha\n"
+ "amoadd.b %[ret], %[val], %[ptr]\n"
+ ".option pop"
+ : [ptr] "+A"(*(u8 *)ptr), [ret] "=r"(ret)
+ : [val] "r"((u8)(val & PERCPU_8_16_GET_MASK(8))));
+ return ret + (u8)(val & PERCPU_8_16_GET_MASK(8));
+ }
+
+ {
+ u32 *ptr32 = PERCPU_8_16_GET_PTR32(ptr);
+ const unsigned long shift = PERCPU_8_16_GET_SHIFT(ptr);
+ const u32 mask = PERCPU_8_16_GET_MASK(8) << shift;
+ const u32 inv_mask = ~mask;
+ const u8 val_trunc = val & PERCPU_8_16_GET_MASK(8);
+ u32 old, new, field;
+
+ asm volatile ("0: lr.w %0, %3\n"
+ "and %1, %0, %4\n"
+ "srl %1, %1, %5\n"
+ "add %1, %1, %6\n"
+ "and %1, %1, %7\n"
+ "sll %1, %1, %5\n"
+ "and %2, %0, %8\n"
+ "or %2, %2, %1\n"
+ "sc.w %2, %2, %3\n"
+ "bnez %2, 0b\n"
+ : "=&r"(old), "=&r"(field), "=&r"(new), "+A"(*ptr32)
+ : "r"(mask), "r"(shift), "r"(val_trunc),
+ "r"(PERCPU_8_16_GET_MASK(8)), "r"(inv_mask));
+ return field >> shift;
+ }
+}
+
+static inline u16 __percpu_add_return_amo_case_16(void *ptr, unsigned long val)
+{
+ if (IS_ENABLED(CONFIG_RISCV_ISA_ZABHA) &&
+ riscv_has_extension_unlikely(RISCV_ISA_EXT_ZABHA)) {
+ register u16 ret;
+
+ asm volatile (".option push\n"
+ ".option arch, +zabha\n"
+ "amoadd.h %[ret], %[val], %[ptr]\n"
+ ".option pop"
+ : [ptr] "+A"(*(u16 *)ptr), [ret] "=r"(ret)
+ : [val] "r"((u16)(val & PERCPU_8_16_GET_MASK(16))));
+ return ret + (u16)(val & PERCPU_8_16_GET_MASK(16));
+ }
+
+ {
+ u32 *ptr32 = PERCPU_8_16_GET_PTR32(ptr);
+ const unsigned long shift = PERCPU_8_16_GET_SHIFT(ptr);
+ const u32 mask = PERCPU_8_16_GET_MASK(16) << shift;
+ const u32 inv_mask = ~mask;
+ const u16 val_trunc = val & PERCPU_8_16_GET_MASK(16);
+ u32 old, new, field;
+
+ asm volatile ("0: lr.w %0, %3\n"
+ "and %1, %0, %4\n"
+ "srl %1, %1, %5\n"
+ "add %1, %1, %6\n"
+ "and %1, %1, %7\n"
+ "sll %1, %1, %5\n"
+ "and %2, %0, %8\n"
+ "or %2, %2, %1\n"
+ "sc.w %2, %2, %3\n"
+ "bnez %2, 0b\n"
+ : "=&r"(old), "=&r"(field), "=&r"(new), "+A"(*ptr32)
+ : "r"(mask), "r"(shift), "r"(val_trunc),
+ "r"(PERCPU_8_16_GET_MASK(16)), "r"(inv_mask));
+ return field >> shift;
+ }
+}
+
+/*
+ * Put arch_cmpxchg_relaxed() behind a function boundary to isolate its macro
+ * locals from those at the call site. asm-generic implements
+ * this_cpu_try_cmpxchg_N() using this_cpu_cmpxchg_N() and a local named __old.
+ * RISC-V's _arch_cmpxchg() also declares __old, so expanding it in the caller's
+ * scope would turn its initializer into "__old = (__old)" and read the
+ * uninitialized inner variable.
+ *
+ * __always_inline avoids an out-of-line call.
+ * _pcp_protect_cmpxchg_return() provides the instrument_atomic_read_write()
+ * hook that cmpxchg_relaxed() would otherwise provide.
+ */
+#define PERCPU_CMPXCHG_OP(sz) \
+static __always_inline unsigned long \
+__percpu_cmpxchg_case_##sz(void *ptr, unsigned long old, \
+ unsigned long new) \
+{ \
+ return arch_cmpxchg_relaxed((u##sz *)ptr, (u##sz)old, \
+ (u##sz)new); \
+}
+
+PERCPU_CMPXCHG_OP(8)
+PERCPU_CMPXCHG_OP(16)
+PERCPU_CMPXCHG_OP(32)
+
+#ifdef CONFIG_64BIT
+PERCPU_CMPXCHG_OP(64)
+#endif
+
+#define _pcp_protect(op, pcp, ...) \
+({ \
+ preempt_disable_notrace(); \
+ op(raw_cpu_ptr(&(pcp)), __VA_ARGS__); \
+ preempt_enable_notrace(); \
+})
+
+#define _pcp_protect_return(op, pcp, args...) \
+({ \
+ typeof(pcp) __retval; \
+ preempt_disable_notrace(); \
+ __retval = (typeof(pcp))op(raw_cpu_ptr(&(pcp)), ##args); \
+ preempt_enable_notrace(); \
+ __retval; \
+})
+
+#define _pcp_protect_cmpxchg_return(op, pcp, old, new) \
+({ \
+ typeof(pcp) __retval; \
+ typeof(pcp) *__pcp_ptr; \
+ \
+ preempt_disable_notrace(); \
+ __pcp_ptr = raw_cpu_ptr(&(pcp)); \
+ instrument_atomic_read_write(__pcp_ptr, sizeof(*__pcp_ptr)); \
+ __retval = (typeof(pcp))op(__pcp_ptr, (unsigned long)(old), \
+ (unsigned long)(new)); \
+ preempt_enable_notrace(); \
+ __retval; \
+})
+
+#define this_cpu_read_1(pcp) _pcp_protect_return(__percpu_read_8, pcp)
+#define this_cpu_read_2(pcp) _pcp_protect_return(__percpu_read_16, pcp)
+#define this_cpu_read_4(pcp) _pcp_protect_return(__percpu_read_32, pcp)
+
+#ifdef CONFIG_64BIT
+#define this_cpu_read_8(pcp) _pcp_protect_return(__percpu_read_64, pcp)
+#endif
+
+#define this_cpu_write_1(pcp, val) _pcp_protect(__percpu_write_8, pcp, (unsigned long)val)
+#define this_cpu_write_2(pcp, val) _pcp_protect(__percpu_write_16, pcp, (unsigned long)val)
+#define this_cpu_write_4(pcp, val) _pcp_protect(__percpu_write_32, pcp, (unsigned long)val)
+
+#ifdef CONFIG_64BIT
+#define this_cpu_write_8(pcp, val) _pcp_protect(__percpu_write_64, pcp, (unsigned long)val)
+#endif
+
+#define this_cpu_add_1(pcp, val) _pcp_protect(__percpu_add_amo_case_8, pcp, val)
+#define this_cpu_add_2(pcp, val) _pcp_protect(__percpu_add_amo_case_16, pcp, val)
+#define this_cpu_add_4(pcp, val) _pcp_protect(__percpu_add_amo_case_32, pcp, val)
+
+#ifdef CONFIG_64BIT
+#define this_cpu_add_8(pcp, val) _pcp_protect(__percpu_add_amo_case_64, pcp, val)
+#endif
+
+#define this_cpu_add_return_1(pcp, val) \
+_pcp_protect_return(__percpu_add_return_amo_case_8, pcp, val)
+
+#define this_cpu_add_return_2(pcp, val) \
+_pcp_protect_return(__percpu_add_return_amo_case_16, pcp, val)
+
+#define this_cpu_add_return_4(pcp, val) \
+_pcp_protect_return(__percpu_add_return_amo_case_32, pcp, val)
+
+#ifdef CONFIG_64BIT
+#define this_cpu_add_return_8(pcp, val) \
+_pcp_protect_return(__percpu_add_return_amo_case_64, pcp, val)
+#endif
+
+#define this_cpu_and_1(pcp, val) _pcp_protect(__percpu_and_amo_case_8, pcp, val)
+#define this_cpu_and_2(pcp, val) _pcp_protect(__percpu_and_amo_case_16, pcp, val)
+#define this_cpu_and_4(pcp, val) _pcp_protect(__percpu_and_amo_case_32, pcp, val)
+
+#ifdef CONFIG_64BIT
+#define this_cpu_and_8(pcp, val) _pcp_protect(__percpu_and_amo_case_64, pcp, val)
+#endif
+
+#define this_cpu_or_1(pcp, val) _pcp_protect(__percpu_or_amo_case_8, pcp, val)
+#define this_cpu_or_2(pcp, val) _pcp_protect(__percpu_or_amo_case_16, pcp, val)
+#define this_cpu_or_4(pcp, val) _pcp_protect(__percpu_or_amo_case_32, pcp, val)
+
+#ifdef CONFIG_64BIT
+#define this_cpu_or_8(pcp, val) _pcp_protect(__percpu_or_amo_case_64, pcp, val)
+#endif
+
+#define this_cpu_xchg_1(pcp, val) _pcp_protect_return(xchg_relaxed, pcp, val)
+#define this_cpu_xchg_2(pcp, val) _pcp_protect_return(xchg_relaxed, pcp, val)
+#define this_cpu_xchg_4(pcp, val) _pcp_protect_return(xchg_relaxed, pcp, val)
+
+#ifdef CONFIG_64BIT
+#define this_cpu_xchg_8(pcp, val) _pcp_protect_return(xchg_relaxed, pcp, val)
+#endif
+
+#define this_cpu_cmpxchg_1(pcp, o, n) \
+ _pcp_protect_cmpxchg_return(__percpu_cmpxchg_case_8, pcp, o, n)
+
+#define this_cpu_cmpxchg_2(pcp, o, n) \
+ _pcp_protect_cmpxchg_return(__percpu_cmpxchg_case_16, pcp, o, n)
+
+#define this_cpu_cmpxchg_4(pcp, o, n) \
+ _pcp_protect_cmpxchg_return(__percpu_cmpxchg_case_32, pcp, o, n)
+
+#ifdef CONFIG_64BIT
+#define this_cpu_cmpxchg_8(pcp, o, n) \
+ _pcp_protect_cmpxchg_return(__percpu_cmpxchg_case_64, pcp, o, n)
+
+#define this_cpu_cmpxchg64(pcp, o, n) this_cpu_cmpxchg_8(pcp, o, n)
+#endif
+
+#ifdef system_has_cmpxchg128
+static inline u128 __percpu_generic_cmpxchg128(void *ptr, u128 old_val,
+ u128 new_val)
+{
+ unsigned long flags;
+ u128 *p = ptr;
+ u128 ret;
+
+ flags = arch_local_irq_save();
+ ret = *p;
+ if (ret == old_val)
+ *p = new_val;
+ arch_local_irq_restore(flags);
+
+ return ret;
+}
+
+#define this_cpu_cmpxchg128(pcp, o, n) \
+({ \
+ u128 old__ = (o), new__ = (n), ret__; \
+ typeof(pcp) *ptr__; \
+ \
+ preempt_disable_notrace(); \
+ ptr__ = raw_cpu_ptr(&(pcp)); \
+ if (system_has_cmpxchg128()) \
+ ret__ = cmpxchg128_local(ptr__, old__, new__); \
+ else \
+ ret__ = __percpu_generic_cmpxchg128(ptr__, old__, new__); \
+ preempt_enable_notrace(); \
+ ret__; \
+})
+#endif
+
+#undef PERCPU_RW_OPS
+#undef __PERCPU_AMO_OP_CASE
+#undef PERCPU_8_16_GET_SHIFT
+#undef PERCPU_8_16_GET_MASK
+#undef PERCPU_8_16_GET_PTR32
+#undef PERCPU_CMPXCHG_OP
+
+#include <asm-generic/percpu.h>
+
+#endif /* __ASM_PERCPU_H */
base-commit: ddfe51b0a0f5dc9e415cc2c8ad8db17532dd426b
--
2.39.5
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH v9 2/3] riscv: store percpu offset into thread_info
2026-11-08 11:04 [PATCH v9 0/3] riscv: optimize percpu access Yunhui Cui
2026-11-08 11:04 ` [PATCH v9 1/3] riscv: implement this_cpu operations Yunhui Cui
@ 2026-11-08 11:04 ` Yunhui Cui
2026-11-08 11:04 ` [PATCH v9 3/3] riscv: select HAVE_CMPXCHG_LOCAL Yunhui Cui
2 siblings, 0 replies; 6+ messages in thread
From: Yunhui Cui @ 2026-11-08 11:04 UTC (permalink / raw)
To: pjw, palmer, aou, alex, dennis, tj, cl, ast, daniel, andrii,
martin.lau, eddyz87, memxor, song, yonghong.song, jolsa, bjorn,
pulehui, puranjay, thuth, ajones, ben.dooks, rkrcmar,
samuel.holland, zong.li, conor.dooley, tglx, debug,
seanwascoding, andybnac, menglong8.dong, cyrilbur, wangruikang,
atishp, apatel, linux-riscv, linux-kernel, linux-mm, bpf, arnd,
nathan, nick.desaulniers+lkml, morbo, justinstitt, qingfang.deng,
linux-arch, llvm, dave, emil, ihor.solodrai, broonie, cuiyunhui
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 | 5 +++++
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, 28 insertions(+), 16 deletions(-)
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
index 280b122b9fe69..2a3ddc3538869 100644
--- a/arch/riscv/include/asm/percpu.h
+++ b/arch/riscv/include/asm/percpu.h
@@ -9,9 +9,12 @@
#include <asm/alternative-macros.h>
#include <asm/cmpxchg.h>
#include <asm/cpufeature-macros.h>
+#include <asm/current.h>
#include <asm/hwcap.h>
#include <asm/irqflags.h>
+#include <linux/thread_info.h>
+
#define PERCPU_RW_OPS(sz) \
static inline unsigned long __percpu_read_##sz(void *ptr) \
{ \
@@ -557,6 +560,8 @@ static inline u128 __percpu_generic_cmpxchg128(void *ptr, u128 old_val,
})
#endif
+#define __my_cpu_offset (((struct thread_info *)current)->pcpu_offset)
+
#undef PERCPU_RW_OPS
#undef __PERCPU_AMO_OP_CASE
#undef PERCPU_8_16_GET_SHIFT
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 [flat|nested] 6+ messages in thread