[PATCH] riscv: Avoid smp_mb__after_spinlock() fences on non-SMP

From: Davidlohr Bueso

Date: Sun Oct 18 2026 - 21:54:12 EST


smp_mb__after_spinlock() is defined in asm/barrier.h, which
linux/spinlock.h includes unconditionally, so non-SMP kernels get
RISCV_FENCE(iorw, iorw) as well and emit a full fence.

The other architectures that override it, arm64, csky, powerpc and
xtensa, define it in asm/spinlock.h, which linux/spinlock.h only
includes for CONFIG_SMP, so their non-SMP builds get the generic
kcsan_mb() fallback. Move the riscv definition, the SMP fence is
unchanged.

Signed-off-by: Davidlohr Bueso <dave@xxxxxxxxxxxx>
---
arch/riscv/include/asm/barrier.h | 27 ---------------------------
arch/riscv/include/asm/spinlock.h | 27 +++++++++++++++++++++++++++
2 files changed, 27 insertions(+), 27 deletions(-)

diff --git a/arch/riscv/include/asm/barrier.h b/arch/riscv/include/asm/barrier.h
index 700ba3f922cb..2284473844d8 100644
--- a/arch/riscv/include/asm/barrier.h
+++ b/arch/riscv/include/asm/barrier.h
@@ -24,33 +24,6 @@
#define __smp_rmb() RISCV_FENCE(r, r)
#define __smp_wmb() RISCV_FENCE(w, w)

-/*
- * This is a very specific barrier: it's currently only used in two places in
- * the kernel, both in the scheduler. See include/linux/spinlock.h for the two
- * orderings it guarantees, but the "critical section is RCsc" guarantee
- * mandates a barrier on RISC-V. The sequence looks like:
- *
- * lr.aq lock
- * sc lock <= LOCKED
- * smp_mb__after_spinlock()
- * // critical section
- * lr lock
- * sc.rl lock <= UNLOCKED
- *
- * The AQ/RL pair provides a RCpc critical section, but there's not really any
- * way we can take advantage of that here because the ordering is only enforced
- * on that one lock. Thus, we're just doing a full fence.
- *
- * Since we allow writeX to be called from preemptive regions we need at least
- * an "o" in the predecessor set to ensure device writes are visible before the
- * task is marked as available for scheduling on a new hart. While I don't see
- * any concrete reason we need a full IO fence, it seems safer to just upgrade
- * this in order to avoid any IO crossing a scheduling boundary. In both
- * instances the scheduler pairs this with an mb(), so nothing is necessary on
- * the new hart.
- */
-#define smp_mb__after_spinlock() RISCV_FENCE(iorw, iorw)
-
#define __smp_store_release(p, v) \
do { \
compiletime_assert_atomic_type(*p); \
diff --git a/arch/riscv/include/asm/spinlock.h b/arch/riscv/include/asm/spinlock.h
index 52f11bfd0079..54bd8db8eb4f 100644
--- a/arch/riscv/include/asm/spinlock.h
+++ b/arch/riscv/include/asm/spinlock.h
@@ -3,6 +3,33 @@
#ifndef __ASM_RISCV_SPINLOCK_H
#define __ASM_RISCV_SPINLOCK_H

+#include <asm/fence.h>
+
+/*
+ * This is a very specific barrier. See include/linux/spinlock.h for the two
+ * orderings it guarantees, but the "critical section is RCsc" guarantee
+ * mandates a barrier on RISC-V. The sequence looks like:
+ *
+ * lr.aq lock
+ * sc lock <= LOCKED
+ * smp_mb__after_spinlock()
+ * // critical section
+ * lr lock
+ * sc.rl lock <= UNLOCKED
+ *
+ * The AQ/RL pair provides a RCpc critical section, but there's not really any
+ * way we can take advantage of that here because the ordering is only enforced
+ * on that one lock. Thus, we're just doing a full fence.
+ *
+ * Since we allow writeX to be called from preemptive regions we need at least
+ * an "o" in the predecessor set to ensure device writes are visible before the
+ * task is marked as available for scheduling on a new hart. While I don't see
+ * any concrete reason we need a full IO fence, it seems safer to just upgrade
+ * this in order to avoid any IO crossing a scheduling boundary. The
+ * scheduler pairs this with an mb(), so nothing is necessary on the new hart.
+ */
+#define smp_mb__after_spinlock() RISCV_FENCE(iorw, iorw)
+
#ifdef CONFIG_QUEUED_SPINLOCKS
#define _Q_PENDING_LOOPS (1 << 9)
#endif
--
2.39.5