From: Will Deacon <hidden> Date: 2018-02-15 17:56:06
Hi all,
Whilst investigating a livelock in fs/dcache.c [1], I noticed that the
arm64 test_and_set operation always writes back to memory even if the
value is already set. This led me to start hacking on improved versions
of our bitops, including an acquire version of test_and_set_bit_lock.
Since there was nothing arm64-specific about the resulting code, I figured
I'd replace what's sitting in asm-generic/bitops/atomic.h and simply include
that instead. I had to rejig a couple of #includes so that I can call into
atomic_* ops from bitops.h, but that was actually pretty straightforward.
Feedback welcome,
Will
[1] https://lkml.org/lkml/2018/2/13/414
--->8
Will Deacon (5):
arm64: fpsimd: include <linux/init.h> in fpsimd.h
asm-generic: Avoid including linux/kernel.h in asm-generic/bug.h
asm-generic/bitops/atomic.h: Rewrite using atomic_fetch_*
arm64: Replace our atomic bitops implementation with asm-generic
arm64: bitops: Include <asm-generic/bitops/ext2-atomic-setbit.h>
arch/arm64/include/asm/bitops.h | 20 +---
arch/arm64/include/asm/fpsimd.h | 1 +
arch/arm64/lib/Makefile | 2 +-
arch/arm64/lib/bitops.S | 76 -------------
include/asm-generic/bitops/atomic.h | 219 ++++++++++++------------------------
include/asm-generic/bug.h | 2 +-
lib/errseq.c | 1 +
7 files changed, 77 insertions(+), 244 deletions(-)
delete mode 100644 arch/arm64/lib/bitops.S
--
2.1.4
From: Will Deacon <hidden> Date: 2018-02-15 17:56:01
The asm-generic/bitops/atomic.h implementation is built around the
atomic-fetch ops, which we implement efficiently for both LSE and LL/SC
systems. Use that instead of our hand-rolled, out-of-line bitops.S.
Signed-off-by: Will Deacon <redacted>
---
arch/arm64/include/asm/bitops.h | 13 +------
arch/arm64/lib/Makefile | 2 +-
arch/arm64/lib/bitops.S | 76 -----------------------------------------
3 files changed, 2 insertions(+), 89 deletions(-)
delete mode 100644 arch/arm64/lib/bitops.S
@@ -17,22 +17,11 @@#define __ASM_BITOPS_H#include<linux/compiler.h>-#include<asm/barrier.h>#ifndef _LINUX_BITOPS_H#error only <linux/bitops.h> can be included directly#endif-/*-*Littleendianassemblyatomicbitops.-*/-externvoidset_bit(intnr,volatileunsignedlong*p);-externvoidclear_bit(intnr,volatileunsignedlong*p);-externvoidchange_bit(intnr,volatileunsignedlong*p);-externinttest_and_set_bit(intnr,volatileunsignedlong*p);-externinttest_and_clear_bit(intnr,volatileunsignedlong*p);-externinttest_and_change_bit(intnr,volatileunsignedlong*p);-#include<asm-generic/bitops/builtin-__ffs.h>#include<asm-generic/bitops/builtin-ffs.h>#include<asm-generic/bitops/builtin-__fls.h>
@@ -44,8 +33,8 @@ extern int test_and_change_bit(int nr, volatile unsigned long *p);#include<asm-generic/bitops/sched.h>#include<asm-generic/bitops/hweight.h>-#include<asm-generic/bitops/lock.h>+#include<asm-generic/bitops/atomic.h>#include<asm-generic/bitops/non-atomic.h>#include<asm-generic/bitops/le.h>
@@ -1,76 +0,0 @@-/*- * Based on arch/arm/lib/bitops.h- *- * Copyright (C) 2013 ARM Ltd.- *- * This program is free software; you can redistribute it and/or modify- * it under the terms of the GNU General Public License version 2 as- * published by the Free Software Foundation.- *- * This program is distributed in the hope that it will be useful,- * but WITHOUT ANY WARRANTY; without even the implied warranty of- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- * GNU General Public License for more details.- *- * You should have received a copy of the GNU General Public License- * along with this program. If not, see <http://www.gnu.org/licenses/>.- */--#include <linux/linkage.h>-#include <asm/assembler.h>-#include <asm/lse.h>--/*- * x0: bits 5:0 bit offset- * bits 31:6 word offset- * x1: address- */- .macro bitop, name, llsc, lse-ENTRY( \name )- and w3, w0, #63 // Get bit offset- eor w0, w0, w3 // Clear low bits- mov x2, #1- add x1, x1, x0, lsr #3 // Get word offset-alt_lse " prfm pstl1strm, [x1]", "nop"- lsl x3, x2, x3 // Create mask--alt_lse "1: ldxr x2, [x1]", "\lse x3, [x1]"-alt_lse " \llsc x2, x2, x3", "nop"-alt_lse " stxr w0, x2, [x1]", "nop"-alt_lse " cbnz w0, 1b", "nop"-- ret-ENDPROC(\name )- .endm-- .macro testop, name, llsc, lse-ENTRY( \name )- and w3, w0, #63 // Get bit offset- eor w0, w0, w3 // Clear low bits- mov x2, #1- add x1, x1, x0, lsr #3 // Get word offset-alt_lse " prfm pstl1strm, [x1]", "nop"- lsl x4, x2, x3 // Create mask--alt_lse "1: ldxr x2, [x1]", "\lse x4, x2, [x1]"- lsr x0, x2, x3-alt_lse " \llsc x2, x2, x4", "nop"-alt_lse " stlxr w5, x2, [x1]", "nop"-alt_lse " cbnz w5, 1b", "nop"-alt_lse " dmb ish", "nop"-- and x0, x0, #1- ret-ENDPROC(\name )- .endm--/*- * Atomic bit operations.- */- bitop change_bit, eor, steor- bitop clear_bit, bic, stclr- bitop set_bit, orr, stset-- testop test_and_change_bit, eor, ldeoral- testop test_and_clear_bit, bic, ldclral- testop test_and_set_bit, orr, ldsetal
From: Will Deacon <hidden> Date: 2018-02-15 17:56:02
The atomic bitops can actually be implemented pretty efficiently using
the atomic_fetch_* ops, rather than explicit use of spinlocks.
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Ingo Molnar <mingo@kernel.org>
Signed-off-by: Will Deacon <redacted>
---
include/asm-generic/bitops/atomic.h | 219 ++++++++++++------------------------
1 file changed, 71 insertions(+), 148 deletions(-)
@@ -2,189 +2,112 @@#ifndef _ASM_GENERIC_BITOPS_ATOMIC_H_#define _ASM_GENERIC_BITOPS_ATOMIC_H_-#include<asm/types.h>-#include<linux/irqflags.h>+#include<linux/atomic.h>+#include<linux/compiler.h>+#include<asm/barrier.h>-#ifdef CONFIG_SMP-#include<asm/spinlock.h>-#include<asm/cache.h> /* we use L1_CACHE_BYTES */--/* Use an array of spinlocks for our atomic_ts.-*HashfunctiontoindexintoadifferentSPINLOCK.-*Since"a"isusuallyanaddress,useonespinlockpercacheline.+/*+*Implementationofatomicbitopsusingatomic-fetchops.+*SeeDocumentation/atomic_bitops.txtfordetails.*/-# define ATOMIC_HASH_SIZE 4-# define ATOMIC_HASH(a) (&(__atomic_hash[ (((unsigned long) a)/L1_CACHE_BYTES) & (ATOMIC_HASH_SIZE-1) ]))--externarch_spinlock_t__atomic_hash[ATOMIC_HASH_SIZE]__lock_aligned;--/* Can't use raw_spin_lock_irq because of #include problems, so-*thisisthesubstitute*/-#define _atomic_spin_lock_irqsave(l,f) do { \-arch_spinlock_t*s=ATOMIC_HASH(l);\-local_irq_save(f);\-arch_spin_lock(s);\-}while(0)--#define _atomic_spin_unlock_irqrestore(l,f) do { \-arch_spinlock_t*s=ATOMIC_HASH(l);\-arch_spin_unlock(s);\-local_irq_restore(f);\-}while(0)+staticinlinevoidset_bit(unsignedintnr,volatileunsignedlong*p)+{+p+=BIT_WORD(nr);+atomic_long_fetch_or_relaxed(BIT_MASK(nr),(atomic_long_t*)p);+}-#else-# define _atomic_spin_lock_irqsave(l,f) do { local_irq_save(f); } while (0)-# define _atomic_spin_unlock_irqrestore(l,f) do { local_irq_restore(f); } while (0)-#endif+staticinlinevoidclear_bit(unsignedintnr,volatileunsignedlong*p)+{+p+=BIT_WORD(nr);+atomic_long_fetch_andnot_relaxed(BIT_MASK(nr),(atomic_long_t*)p);+}-/*-*NMIeventscanoccuratanytime,includingwheninterruptshavebeen-*disabledby*_irqsave().SoyoucangetNMIeventsoccurringwhilea-**_bitfunctionisholdingaspinlock.IftheNMIhandleralsowants-*todobitmanipulation(andtheydo)thenyoucangetadeadlock-*betweentheoriginalcallerof*_bit()andtheNMIhandler.-*-*byKeithOwens-*/+staticinlinevoidchange_bit(unsignedintnr,volatileunsignedlong*p)+{+p+=BIT_WORD(nr);+atomic_long_fetch_xor_relaxed(BIT_MASK(nr),(atomic_long_t*)p);+}-/**-*set_bit-Atomicallysetabitinmemory-*@nr:thebittoset-*@addr:theaddresstostartcountingfrom-*-*Thisfunctionisatomicandmaynotbereordered.See__set_bit()-*ifyoudonotrequiretheatomicguarantees.-*-*Note:therearenoguaranteesthatthisfunctionwillnotbereordered-*onnonx86architectures,soifyouarewritingportablecode,-*makesurenottorelyonitsreorderingguarantees.-*-*Notethat@nrmaybealmostarbitrarilylarge;thisfunctionisnot-*restrictedtoactingonasingle-wordquantity.-*/-staticinlinevoidset_bit(intnr,volatileunsignedlong*addr)+staticinlineinttest_and_set_bit(unsignedintnr,volatileunsignedlong*p){+longold;unsignedlongmask=BIT_MASK(nr);-unsignedlong*p=((unsignedlong*)addr)+BIT_WORD(nr);-unsignedlongflags;-_atomic_spin_lock_irqsave(p,flags);-*p|=mask;-_atomic_spin_unlock_irqrestore(p,flags);+p+=BIT_WORD(nr);+if(READ_ONCE(*p)&mask)+return1;++old=atomic_long_fetch_or(mask,(atomic_long_t*)p);+return!!(old&mask);}-/**-*clear_bit-Clearsabitinmemory-*@nr:Bittoclear-*@addr:Addresstostartcountingfrom-*-*clear_bit()isatomicandmaynotbereordered.However,itdoes-*notcontainamemorybarrier,soifitisusedforlockingpurposes,-*youshouldcallsmp_mb__before_atomic()and/orsmp_mb__after_atomic()-*inordertoensurechangesarevisibleonotherprocessors.-*/-staticinlinevoidclear_bit(intnr,volatileunsignedlong*addr)+staticinlineinttest_and_clear_bit(unsignedintnr,volatileunsignedlong*p){+longold;unsignedlongmask=BIT_MASK(nr);-unsignedlong*p=((unsignedlong*)addr)+BIT_WORD(nr);-unsignedlongflags;-_atomic_spin_lock_irqsave(p,flags);-*p&=~mask;-_atomic_spin_unlock_irqrestore(p,flags);+p+=BIT_WORD(nr);+if(!(READ_ONCE(*p)&mask))+return0;++old=atomic_long_fetch_andnot(mask,(atomic_long_t*)p);+return!!(old&mask);}-/**-*change_bit-Toggleabitinmemory-*@nr:Bittochange-*@addr:Addresstostartcountingfrom-*-*change_bit()isatomicandmaynotbereordered.Itmaybe-*reorderedonotherarchitecturesthanx86.-*Notethat@nrmaybealmostarbitrarilylarge;thisfunctionisnot-*restrictedtoactingonasingle-wordquantity.-*/-staticinlinevoidchange_bit(intnr,volatileunsignedlong*addr)+staticinlineinttest_and_change_bit(unsignedintnr,volatileunsignedlong*p){+longold;unsignedlongmask=BIT_MASK(nr);-unsignedlong*p=((unsignedlong*)addr)+BIT_WORD(nr);-unsignedlongflags;-_atomic_spin_lock_irqsave(p,flags);-*p^=mask;-_atomic_spin_unlock_irqrestore(p,flags);+p+=BIT_WORD(nr);+old=atomic_long_fetch_xor(mask,(atomic_long_t*)p);+return!!(old&mask);}-/**-*test_and_set_bit-Setabitandreturnitsoldvalue-*@nr:Bittoset-*@addr:Addresstocountfrom-*-*Thisoperationisatomicandcannotbereordered.-*Itmaybereorderedonotherarchitecturesthanx86.-*Italsoimpliesamemorybarrier.-*/-staticinlineinttest_and_set_bit(intnr,volatileunsignedlong*addr)+staticinlineinttest_and_set_bit_lock(unsignedintnr,+volatileunsignedlong*p){+longold;unsignedlongmask=BIT_MASK(nr);-unsignedlong*p=((unsignedlong*)addr)+BIT_WORD(nr);-unsignedlongold;-unsignedlongflags;-_atomic_spin_lock_irqsave(p,flags);-old=*p;-*p=old|mask;-_atomic_spin_unlock_irqrestore(p,flags);+p+=BIT_WORD(nr);+if(READ_ONCE(*p)&mask)+return1;-return(old&mask)!=0;+old=atomic_long_fetch_or_acquire(mask,(atomic_long_t*)p);+return!!(old&mask);}-/**-*test_and_clear_bit-Clearabitandreturnitsoldvalue-*@nr:Bittoclear-*@addr:Addresstocountfrom-*-*Thisoperationisatomicandcannotbereordered.-*Itcanbereorderderedonotherarchitecturesotherthanx86.-*Italsoimpliesamemorybarrier.-*/-staticinlineinttest_and_clear_bit(intnr,volatileunsignedlong*addr)+staticinlinevoidclear_bit_unlock(unsignedintnr,volatileunsignedlong*p){-unsignedlongmask=BIT_MASK(nr);-unsignedlong*p=((unsignedlong*)addr)+BIT_WORD(nr);-unsignedlongold;-unsignedlongflags;+p+=BIT_WORD(nr);+atomic_long_fetch_andnot_release(BIT_MASK(nr),(atomic_long_t*)p);+}-_atomic_spin_lock_irqsave(p,flags);-old=*p;-*p=old&~mask;-_atomic_spin_unlock_irqrestore(p,flags);+staticinlinevoid__clear_bit_unlock(unsignedintnr,+volatileunsignedlong*p)+{+unsignedlongold;-return(old&mask)!=0;+p+=BIT_WORD(nr);+old=READ_ONCE(*p);+old&=~BIT_MASK(nr);+smp_store_release(p,old);}-/**-*test_and_change_bit-Changeabitandreturnitsoldvalue-*@nr:Bittochange-*@addr:Addresstocountfrom-*-*Thisoperationisatomicandcannotbereordered.-*Italsoimpliesamemorybarrier.-*/-staticinlineinttest_and_change_bit(intnr,volatileunsignedlong*addr)+#ifndef clear_bit_unlock_is_negative_byte+staticinlineboolclear_bit_unlock_is_negative_byte(unsignedintnr,+volatileunsignedlong*p){+longold;unsignedlongmask=BIT_MASK(nr);-unsignedlong*p=((unsignedlong*)addr)+BIT_WORD(nr);-unsignedlongold;-unsignedlongflags;--_atomic_spin_lock_irqsave(p,flags);-old=*p;-*p=old^mask;-_atomic_spin_unlock_irqrestore(p,flags);-return(old&mask)!=0;+p+=BIT_WORD(nr);+old=atomic_long_fetch_andnot_release(mask,(atomic_long_t*)p);+return!!(old&BIT(7));}+#define clear_bit_unlock_is_negative_byte clear_bit_unlock_is_negative_byte+#endif#endif /* _ASM_GENERIC_BITOPS_ATOMIC_H */
From: Peter Zijlstra <peterz@infradead.org> Date: 2018-02-15 17:08:57
On Thu, Feb 15, 2018 at 03:29:33PM +0000, Will Deacon wrote:
+static inline void set_bit(unsigned int nr, volatile unsigned long *p)
+{
+ p += BIT_WORD(nr);
+ atomic_long_fetch_or_relaxed(BIT_MASK(nr), (atomic_long_t *)p);
+}
+static inline void clear_bit(unsigned int nr, volatile unsigned long *p)
+{
+ p += BIT_WORD(nr);
+ atomic_long_fetch_andnot_relaxed(BIT_MASK(nr), (atomic_long_t *)p);
+}
+static inline void change_bit(unsigned int nr, volatile unsigned long *p)
+{
+ p += BIT_WORD(nr);
+ atomic_long_fetch_xor_relaxed(BIT_MASK(nr), (atomic_long_t *)p);
+}
+static inline int test_and_set_bit(unsigned int nr, volatile unsigned long *p)
{
+ long old;
unsigned long mask = BIT_MASK(nr);
+ p += BIT_WORD(nr);
+ if (READ_ONCE(*p) & mask)
+ return 1;
+
+ old = atomic_long_fetch_or(mask, (atomic_long_t *)p);
+ return !!(old & mask);
}
+static inline int test_and_clear_bit(unsigned int nr, volatile unsigned long *p)
{
+ long old;
unsigned long mask = BIT_MASK(nr);
+ p += BIT_WORD(nr);
+ if (!(READ_ONCE(*p) & mask))
+ return 0;
+
+ old = atomic_long_fetch_andnot(mask, (atomic_long_t *)p);
+ return !!(old & mask);
}
+static inline int test_and_change_bit(unsigned int nr, volatile unsigned long *p)
{
+ long old;
unsigned long mask = BIT_MASK(nr);
+ p += BIT_WORD(nr);
+ old = atomic_long_fetch_xor(mask, (atomic_long_t *)p);
+ return !!(old & mask);
}
+static inline int test_and_set_bit_lock(unsigned int nr,
+ volatile unsigned long *p)
{
+ long old;
unsigned long mask = BIT_MASK(nr);
+ p += BIT_WORD(nr);
+ if (READ_ONCE(*p) & mask)
+ return 1;
+ old = atomic_long_fetch_or_acquire(mask, (atomic_long_t *)p);
+ return !!(old & mask);
}
+static inline void clear_bit_unlock(unsigned int nr, volatile unsigned long *p)
{
+ p += BIT_WORD(nr);
+ atomic_long_fetch_andnot_release(BIT_MASK(nr), (atomic_long_t *)p);
+}
+static inline void __clear_bit_unlock(unsigned int nr,
+ volatile unsigned long *p)
+{
+ unsigned long old;
+ p += BIT_WORD(nr);
+ old = READ_ONCE(*p);
+ old &= ~BIT_MASK(nr);
+ smp_store_release(p, old);
This should be atomic_set_release() I think, for the special case where
atomics are implemented with spinlocks, see the 'fun' case in
Documentation/atomic_t.txt.
}
The only other comment is that I think it would be better if you use
atomic_t instead of atomic_long_t. It would just mean changing
BIT_WORD() and BIT_MASK().
The reason is that we generate a pretty sane set of atomic_t primitives
as long as the architecture supplies cmpxchg, but atomic64 defaults to
utter crap, even on 64bit platforms.
Otherwise this looks pretty neat.
From: Will Deacon <hidden> Date: 2018-02-15 18:20:44
Hi Peter,
Thanks for having a look.
On Thu, Feb 15, 2018 at 06:08:47PM +0100, Peter Zijlstra wrote:
On Thu, Feb 15, 2018 at 03:29:33PM +0000, Will Deacon wrote:
quoted
+static inline void __clear_bit_unlock(unsigned int nr,
+ volatile unsigned long *p)
+{
+ unsigned long old;
+ p += BIT_WORD(nr);
+ old = READ_ONCE(*p);
+ old &= ~BIT_MASK(nr);
+ smp_store_release(p, old);
This should be atomic_set_release() I think, for the special case where
atomics are implemented with spinlocks, see the 'fun' case in
Documentation/atomic_t.txt.
My understanding of __clear_bit_unlock is that there is guaranteed to be
no concurrent accesses to the same word, so why would it matter whether
locks are used to implement atomics?
The only other comment is that I think it would be better if you use
atomic_t instead of atomic_long_t. It would just mean changing
BIT_WORD() and BIT_MASK().
It would make it pretty messy for big-endian architectures, I think...
The reason is that we generate a pretty sane set of atomic_t primitives
as long as the architecture supplies cmpxchg, but atomic64 defaults to
utter crap, even on 64bit platforms.
I think all the architectures using this today are 32-bit:
blackfin
c6x
cris
metag
openrisc
sh
xtensa
and I don't know how much we should care about optimising the generic atomic
bitops for 64-bit architectures that rely on spinlocks for 64-bit atomics!
Will
From: Peter Zijlstra <peterz@infradead.org> Date: 2018-02-16 10:21:06
On Thu, Feb 15, 2018 at 06:20:49PM +0000, Will Deacon wrote:
On Thu, Feb 15, 2018 at 06:08:47PM +0100, Peter Zijlstra wrote:
quoted
On Thu, Feb 15, 2018 at 03:29:33PM +0000, Will Deacon wrote:
quoted
+static inline void __clear_bit_unlock(unsigned int nr,
+ volatile unsigned long *p)
+{
+ unsigned long old;
+ p += BIT_WORD(nr);
+ old = READ_ONCE(*p);
+ old &= ~BIT_MASK(nr);
+ smp_store_release(p, old);
This should be atomic_set_release() I think, for the special case where
atomics are implemented with spinlocks, see the 'fun' case in
Documentation/atomic_t.txt.
My understanding of __clear_bit_unlock is that there is guaranteed to be
no concurrent accesses to the same word, so why would it matter whether
locks are used to implement atomics?
commit f75d48644c56a31731d17fa693c8175328957e1d
Author: Peter Zijlstra [off-list ref]
Date: Wed Mar 9 12:40:54 2016 +0100
bitops: Do not default to __clear_bit() for __clear_bit_unlock()
__clear_bit_unlock() is a special little snowflake. While it carries the
non-atomic '__' prefix, it is specifically documented to pair with
test_and_set_bit() and therefore should be 'somewhat' atomic.
Therefore the generic implementation of __clear_bit_unlock() cannot use
the fully non-atomic __clear_bit() as a default.
If an arch is able to do better; is must provide an implementation of
__clear_bit_unlock() itself.
Specifically, this came up as a result of hackbench livelock'ing in
slab_lock() on ARC with SMP + SLUB + !LLSC.
The issue was incorrect pairing of atomic ops.
slab_lock() -> bit_spin_lock() -> test_and_set_bit()
slab_unlock() -> __bit_spin_unlock() -> __clear_bit()
The non serializing __clear_bit() was getting "lost"
80543b8e: ld_s r2,[r13,0] <--- (A) Finds PG_locked is set
80543b90: or r3,r2,1 <--- (B) other core unlocks right here
80543b94: st_s r3,[r13,0] <--- (C) sets PG_locked (overwrites unlock)
Fixes ARC STAR 9000817404 (and probably more).
Reported-by: Vineet Gupta [off-list ref]
Tested-by: Vineet Gupta [off-list ref]
Signed-off-by: Peter Zijlstra (Intel) [off-list ref]
Cc: Andrew Morton [off-list ref]
Cc: Christoph Lameter [off-list ref]
Cc: David Rientjes [off-list ref]
Cc: Helge Deller [off-list ref]
Cc: James E.J. Bottomley [off-list ref]
Cc: Joonsoo Kim [off-list ref]
Cc: Linus Torvalds [off-list ref]
Cc: Noam Camus [off-list ref]
Cc: Paul E. McKenney [off-list ref]
Cc: Pekka Enberg [off-list ref]
Cc: Peter Zijlstra [off-list ref]
Cc: Thomas Gleixner [off-list ref]
Cc: stable at vger.kernel.org
Link: http://lkml.kernel.org/r/20160309114054.GJ6356 at twins.programming.kicks-ass.net
Signed-off-by: Ingo Molnar [off-list ref]
From: Will Deacon <hidden> Date: 2018-02-19 14:01:38
Hi Peter,
On Fri, Feb 16, 2018 at 11:21:00AM +0100, Peter Zijlstra wrote:
On Thu, Feb 15, 2018 at 06:20:49PM +0000, Will Deacon wrote:
quoted
On Thu, Feb 15, 2018 at 06:08:47PM +0100, Peter Zijlstra wrote:
quoted
On Thu, Feb 15, 2018 at 03:29:33PM +0000, Will Deacon wrote:
quoted
+static inline void __clear_bit_unlock(unsigned int nr,
+ volatile unsigned long *p)
+{
+ unsigned long old;
+ p += BIT_WORD(nr);
+ old = READ_ONCE(*p);
+ old &= ~BIT_MASK(nr);
+ smp_store_release(p, old);
This should be atomic_set_release() I think, for the special case where
atomics are implemented with spinlocks, see the 'fun' case in
Documentation/atomic_t.txt.
My understanding of __clear_bit_unlock is that there is guaranteed to be
no concurrent accesses to the same word, so why would it matter whether
locks are used to implement atomics?
commit f75d48644c56a31731d17fa693c8175328957e1d
Author: Peter Zijlstra [off-list ref]
Date: Wed Mar 9 12:40:54 2016 +0100
bitops: Do not default to __clear_bit() for __clear_bit_unlock()
__clear_bit_unlock() is a special little snowflake. While it carries the
non-atomic '__' prefix, it is specifically documented to pair with
test_and_set_bit() and therefore should be 'somewhat' atomic.
Therefore the generic implementation of __clear_bit_unlock() cannot use
the fully non-atomic __clear_bit() as a default.
If an arch is able to do better; is must provide an implementation of
__clear_bit_unlock() itself.
Specifically, this came up as a result of hackbench livelock'ing in
slab_lock() on ARC with SMP + SLUB + !LLSC.
The issue was incorrect pairing of atomic ops.
slab_lock() -> bit_spin_lock() -> test_and_set_bit()
slab_unlock() -> __bit_spin_unlock() -> __clear_bit()
The non serializing __clear_bit() was getting "lost"
80543b8e: ld_s r2,[r13,0] <--- (A) Finds PG_locked is set
80543b90: or r3,r2,1 <--- (B) other core unlocks right here
80543b94: st_s r3,[r13,0] <--- (C) sets PG_locked (overwrites unlock)
Ah, so it's problematic for the case where atomics are built using locks.
Got it. I'll err on the side of caution here and have the asm-generic header
(which should be bitops/lock.h not bitops/atomic.h) conditionally define
__clear_bit_unlock as clear_bit_lock unless the architecture has provided
its own implementation.
Thanks,
Will
From: Peter Zijlstra <peterz@infradead.org> Date: 2018-02-20 13:05:37
On Mon, Feb 19, 2018 at 02:01:43PM +0000, Will Deacon wrote:
quoted
The non serializing __clear_bit() was getting "lost"
80543b8e: ld_s r2,[r13,0] <--- (A) Finds PG_locked is set
80543b90: or r3,r2,1 <--- (B) other core unlocks right here
80543b94: st_s r3,[r13,0] <--- (C) sets PG_locked (overwrites unlock)
Ah, so it's problematic for the case where atomics are built using locks.
Got it. I'll err on the side of caution here and have the asm-generic header
(which should be bitops/lock.h not bitops/atomic.h) conditionally define
__clear_bit_unlock as clear_bit_lock unless the architecture has provided
its own implementation.
So I think we get it all right if we use atomic_set_release(). If the
atomics are implemented using locks, atomic_set*() should be implemented
like atomic_xchg() and avoid the above problem.
From: Peter Zijlstra <peterz@infradead.org> Date: 2018-02-16 10:35:26
On Thu, Feb 15, 2018 at 06:20:49PM +0000, Will Deacon wrote:
quoted
The only other comment is that I think it would be better if you use
atomic_t instead of atomic_long_t. It would just mean changing
BIT_WORD() and BIT_MASK().
It would make it pretty messy for big-endian architectures, I think...
Urgh, the big.little indians strike again.. Bah I always forget about
that.
#define BIT_U32_MASK(nr) (1UL << ((nr) % 32))
#define BIT_U32_WORD(nr) (((nr) / 32) ^ (4 * __BIG_ENDIAN__))
Or something like that might work, but I always get these things wrong.
quoted
The reason is that we generate a pretty sane set of atomic_t primitives
as long as the architecture supplies cmpxchg, but atomic64 defaults to
utter crap, even on 64bit platforms.
I think all the architectures using this today are 32-bit:
blackfin
c6x
cris
metag
openrisc
sh
xtensa
and I don't know how much we should care about optimising the generic atomic
bitops for 64-bit architectures that rely on spinlocks for 64-bit atomics!
You're probably right, but it just bugs me that we default to such
horrible crap. Arguably we should do a better default for atomic64_t on
64bit archs. But that's for another time.
From: Will Deacon <hidden> Date: 2018-02-19 14:01:46
On Fri, Feb 16, 2018 at 11:35:20AM +0100, Peter Zijlstra wrote:
On Thu, Feb 15, 2018 at 06:20:49PM +0000, Will Deacon wrote:
quoted
quoted
The only other comment is that I think it would be better if you use
atomic_t instead of atomic_long_t. It would just mean changing
BIT_WORD() and BIT_MASK().
It would make it pretty messy for big-endian architectures, I think...
Urgh, the big.little indians strike again.. Bah I always forget about
that.
#define BIT_U32_MASK(nr) (1UL << ((nr) % 32))
#define BIT_U32_WORD(nr) (((nr) / 32) ^ (4 * __BIG_ENDIAN__))
Or something like that might work, but I always get these things wrong.
quoted
quoted
The reason is that we generate a pretty sane set of atomic_t primitives
as long as the architecture supplies cmpxchg, but atomic64 defaults to
utter crap, even on 64bit platforms.
I think all the architectures using this today are 32-bit:
blackfin
c6x
cris
metag
openrisc
sh
xtensa
and I don't know how much we should care about optimising the generic atomic
bitops for 64-bit architectures that rely on spinlocks for 64-bit atomics!
You're probably right, but it just bugs me that we default to such
horrible crap. Arguably we should do a better default for atomic64_t on
64bit archs. But that's for another time.
If it's defined, then we could consider using cmpxchg64 to build atomic64
instead of the locks. But even then, I'm not sure we're really helping
anybody out in practice.
Will
From: Peter Zijlstra <peterz@infradead.org> Date: 2018-02-20 13:02:11
On Mon, Feb 19, 2018 at 02:01:51PM +0000, Will Deacon wrote:
If it's defined, then we could consider using cmpxchg64 to build atomic64
instead of the locks. But even then, I'm not sure we're really helping
anybody out in practice.
yeah, most 64bit archs have more atomics or ll/sc and would not use it
anyway I suppose.
From: Will Deacon <hidden> Date: 2018-02-15 17:56:05
asm-generic/bug.h unnecessarily includes linux/kernel.h whereas it can
get away with linux/types.h instead. lib/errseq.c relies on this transitive
include, so update it to include linux/kernel.h instead.
Signed-off-by: Will Deacon <redacted>
---
include/asm-generic/bug.h | 2 +-
lib/errseq.c | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)