Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

riscv/spinlock: Strengthen implementations with fences

Current implementations map locking operations using .rl and .aq
annotations. However, this mapping is unsound w.r.t. the kernel
memory consistency model (LKMM) [1]:

Referring to the "unlock-lock-read-ordering" test reported below,
Daniel wrote:

"I think an RCpc interpretation of .aq and .rl would in fact
allow the two normal loads in P1 to be reordered [...]

The intuition would be that the amoswap.w.aq can forward from
the amoswap.w.rl while that's still in the store buffer, and
then the lw x3,0(x4) can also perform while the amoswap.w.rl
is still in the store buffer, all before the l1 x1,0(x2)
executes. That's not forbidden unless the amoswaps are RCsc,
unless I'm missing something.

Likewise even if the unlock()/lock() is between two stores.
A control dependency might originate from the load part of
the amoswap.w.aq, but there still would have to be something
to ensure that this load part in fact performs after the store
part of the amoswap.w.rl performs globally, and that's not
automatic under RCpc."

Simulation of the RISC-V memory consistency model confirmed this
expectation.

In order to "synchronize" LKMM and RISC-V's implementation, this
commit strengthens the implementations of the locking operations
by replacing .rl and .aq with the use of ("lightweigth") fences,
resp., "fence rw, w" and "fence r , rw".

C unlock-lock-read-ordering

{}
/* s initially owned by P1 */

P0(int *x, int *y)
{
WRITE_ONCE(*x, 1);
smp_wmb();
WRITE_ONCE(*y, 1);
}

P1(int *x, int *y, spinlock_t *s)
{
int r0;
int r1;

r0 = READ_ONCE(*y);
spin_unlock(s);
spin_lock(s);
r1 = READ_ONCE(*x);
}

exists (1:r0=1 /\ 1:r1=0)

[1] https://marc.info/?l=linux-kernel&m=151930201102853&w=2
https://groups.google.com/a/groups.riscv.org/forum/#!topic/isa-dev/hKywNHBkAXM
https://marc.info/?l=linux-kernel&m=151633436614259&w=2

Signed-off-by: Andrea Parri <parri.andrea@gmail.com>
Cc: Palmer Dabbelt <palmer@sifive.com>
Cc: Albert Ou <albert@sifive.com>
Cc: Daniel Lustig <dlustig@nvidia.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: Will Deacon <will.deacon@arm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Boqun Feng <boqun.feng@gmail.com>
Cc: Nicholas Piggin <npiggin@gmail.com>
Cc: David Howells <dhowells@redhat.com>
Cc: Jade Alglave <j.alglave@ucl.ac.uk>
Cc: Luc Maranget <luc.maranget@inria.fr>
Cc: "Paul E. McKenney" <paulmck@linux.vnet.ibm.com>
Cc: Akira Yokosawa <akiyks@gmail.com>
Cc: Ingo Molnar <mingo@kernel.org>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: linux-riscv@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Palmer Dabbelt <palmer@sifive.com>

authored by

Andrea Parri and committed by
Palmer Dabbelt
0123f4d7 8d235b17

+27 -14
+12
arch/riscv/include/asm/fence.h
··· 1 + #ifndef _ASM_RISCV_FENCE_H 2 + #define _ASM_RISCV_FENCE_H 3 + 4 + #ifdef CONFIG_SMP 5 + #define RISCV_ACQUIRE_BARRIER "\tfence r , rw\n" 6 + #define RISCV_RELEASE_BARRIER "\tfence rw, w\n" 7 + #else 8 + #define RISCV_ACQUIRE_BARRIER 9 + #define RISCV_RELEASE_BARRIER 10 + #endif 11 + 12 + #endif /* _ASM_RISCV_FENCE_H */
+15 -14
arch/riscv/include/asm/spinlock.h
··· 17 17 18 18 #include <linux/kernel.h> 19 19 #include <asm/current.h> 20 + #include <asm/fence.h> 20 21 21 22 /* 22 23 * Simple spin lock operations. These provide no fairness guarantees. ··· 29 28 30 29 static inline void arch_spin_unlock(arch_spinlock_t *lock) 31 30 { 32 - __asm__ __volatile__ ( 33 - "amoswap.w.rl x0, x0, %0" 34 - : "=A" (lock->lock) 35 - :: "memory"); 31 + smp_store_release(&lock->lock, 0); 36 32 } 37 33 38 34 static inline int arch_spin_trylock(arch_spinlock_t *lock) ··· 37 39 int tmp = 1, busy; 38 40 39 41 __asm__ __volatile__ ( 40 - "amoswap.w.aq %0, %2, %1" 42 + " amoswap.w %0, %2, %1\n" 43 + RISCV_ACQUIRE_BARRIER 41 44 : "=r" (busy), "+A" (lock->lock) 42 45 : "r" (tmp) 43 46 : "memory"); ··· 67 68 "1: lr.w %1, %0\n" 68 69 " bltz %1, 1b\n" 69 70 " addi %1, %1, 1\n" 70 - " sc.w.aq %1, %1, %0\n" 71 + " sc.w %1, %1, %0\n" 71 72 " bnez %1, 1b\n" 73 + RISCV_ACQUIRE_BARRIER 72 74 : "+A" (lock->lock), "=&r" (tmp) 73 75 :: "memory"); 74 76 } ··· 82 82 "1: lr.w %1, %0\n" 83 83 " bnez %1, 1b\n" 84 84 " li %1, -1\n" 85 - " sc.w.aq %1, %1, %0\n" 85 + " sc.w %1, %1, %0\n" 86 86 " bnez %1, 1b\n" 87 + RISCV_ACQUIRE_BARRIER 87 88 : "+A" (lock->lock), "=&r" (tmp) 88 89 :: "memory"); 89 90 } ··· 97 96 "1: lr.w %1, %0\n" 98 97 " bltz %1, 1f\n" 99 98 " addi %1, %1, 1\n" 100 - " sc.w.aq %1, %1, %0\n" 99 + " sc.w %1, %1, %0\n" 101 100 " bnez %1, 1b\n" 101 + RISCV_ACQUIRE_BARRIER 102 102 "1:\n" 103 103 : "+A" (lock->lock), "=&r" (busy) 104 104 :: "memory"); ··· 115 113 "1: lr.w %1, %0\n" 116 114 " bnez %1, 1f\n" 117 115 " li %1, -1\n" 118 - " sc.w.aq %1, %1, %0\n" 116 + " sc.w %1, %1, %0\n" 119 117 " bnez %1, 1b\n" 118 + RISCV_ACQUIRE_BARRIER 120 119 "1:\n" 121 120 : "+A" (lock->lock), "=&r" (busy) 122 121 :: "memory"); ··· 128 125 static inline void arch_read_unlock(arch_rwlock_t *lock) 129 126 { 130 127 __asm__ __volatile__( 131 - "amoadd.w.rl x0, %1, %0" 128 + RISCV_RELEASE_BARRIER 129 + " amoadd.w x0, %1, %0\n" 132 130 : "+A" (lock->lock) 133 131 : "r" (-1) 134 132 : "memory"); ··· 137 133 138 134 static inline void arch_write_unlock(arch_rwlock_t *lock) 139 135 { 140 - __asm__ __volatile__ ( 141 - "amoswap.w.rl x0, x0, %0" 142 - : "=A" (lock->lock) 143 - :: "memory"); 136 + smp_store_release(&lock->lock, 0); 144 137 } 145 138 146 139 #endif /* _ASM_RISCV_SPINLOCK_H */