415d832497
These operations are documented as always ordered in include/asm-generic/bitops/instrumented-atomic.h, and producer-consumer type use cases where one side needs to ensure a flag is left pending after some shared data was updated rely on this ordering, even in the failure case. This is the case with the workqueue code, which currently suffers from a reproducible ordering violation on Apple M1 platforms (which are notoriously out-of-order) that ends up causing the TTY layer to fail to deliver data to userspace properly under the right conditions. This change fixes that bug. Change the documentation to restrict the "no order on failure" story to the _lock() variant (for which it makes sense), and remove the early-exit from the generic implementation, which is what causes the missing barrier semantics in that case. Without this, the remaining atomic op is fully ordered (including on ARM64 LSE, as of recent versions of the architecture spec). Suggested-by: Linus Torvalds <torvalds@linux-foundation.org> Cc: stable@vger.kernel.org Fixes: e986a0d6cb36 ("locking/atomics, asm-generic/bitops/atomic.h: Rewrite using atomic_*() APIs") Fixes: 61e02392d3c7 ("locking/atomic/bitops: Document and clarify ordering semantics for failed test_and_{}_bit()") Signed-off-by: Hector Martin <marcan@marcan.st> Acked-by: Will Deacon <will@kernel.org> Reviewed-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
71 lines
1.6 KiB
C
71 lines
1.6 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
#ifndef _ASM_GENERIC_BITOPS_ATOMIC_H_
|
|
#define _ASM_GENERIC_BITOPS_ATOMIC_H_
|
|
|
|
#include <linux/atomic.h>
|
|
#include <linux/compiler.h>
|
|
#include <asm/barrier.h>
|
|
|
|
/*
|
|
* Implementation of atomic bitops using atomic-fetch ops.
|
|
* See Documentation/atomic_bitops.txt for details.
|
|
*/
|
|
|
|
static __always_inline void
|
|
arch_set_bit(unsigned int nr, volatile unsigned long *p)
|
|
{
|
|
p += BIT_WORD(nr);
|
|
arch_atomic_long_or(BIT_MASK(nr), (atomic_long_t *)p);
|
|
}
|
|
|
|
static __always_inline void
|
|
arch_clear_bit(unsigned int nr, volatile unsigned long *p)
|
|
{
|
|
p += BIT_WORD(nr);
|
|
arch_atomic_long_andnot(BIT_MASK(nr), (atomic_long_t *)p);
|
|
}
|
|
|
|
static __always_inline void
|
|
arch_change_bit(unsigned int nr, volatile unsigned long *p)
|
|
{
|
|
p += BIT_WORD(nr);
|
|
arch_atomic_long_xor(BIT_MASK(nr), (atomic_long_t *)p);
|
|
}
|
|
|
|
static __always_inline int
|
|
arch_test_and_set_bit(unsigned int nr, volatile unsigned long *p)
|
|
{
|
|
long old;
|
|
unsigned long mask = BIT_MASK(nr);
|
|
|
|
p += BIT_WORD(nr);
|
|
old = arch_atomic_long_fetch_or(mask, (atomic_long_t *)p);
|
|
return !!(old & mask);
|
|
}
|
|
|
|
static __always_inline int
|
|
arch_test_and_clear_bit(unsigned int nr, volatile unsigned long *p)
|
|
{
|
|
long old;
|
|
unsigned long mask = BIT_MASK(nr);
|
|
|
|
p += BIT_WORD(nr);
|
|
old = arch_atomic_long_fetch_andnot(mask, (atomic_long_t *)p);
|
|
return !!(old & mask);
|
|
}
|
|
|
|
static __always_inline int
|
|
arch_test_and_change_bit(unsigned int nr, volatile unsigned long *p)
|
|
{
|
|
long old;
|
|
unsigned long mask = BIT_MASK(nr);
|
|
|
|
p += BIT_WORD(nr);
|
|
old = arch_atomic_long_fetch_xor(mask, (atomic_long_t *)p);
|
|
return !!(old & mask);
|
|
}
|
|
|
|
#include <asm-generic/bitops/instrumented-atomic.h>
|
|
|
|
#endif /* _ASM_GENERIC_BITOPS_ATOMIC_H */
|