mirror of
https://github.com/torvalds/linux.git
synced 2025-04-09 14:45:27 +00:00

raw_spin_locks can be traced by lockdep or tracing itself. Atomic64 operations can be used in the tracing infrastructure. When an architecture does not have true atomic64 operations it can use the generic version that disables interrupts and uses spin_locks. The tracing ring buffer code uses atomic64 operations for the time keeping. But because some architectures use the default operations, the locking inside the atomic operations can cause an infinite recursion. As atomic64 implementation is architecture specific, it should not be using raw_spin_locks() but instead arch_spin_locks as that is the purpose of arch_spin_locks. To be used in architecture specific implementations of generic infrastructure like atomic64 operations. Note, by switching from raw_spin_locks to arch_spin_locks, the locks taken to emulate the atomic64 operations will not have lockdep, mmio, or any kind of checks done on them. They will not even disable preemption, although the code will disable interrupts preventing the tasks that hold the locks from being preempted. As the locks held are done so for very short periods of time, and the logic is only done to emulate atomic64, not having them be instrumented should not be an issue. Cc: stable@vger.kernel.org Cc: Mark Rutland <mark.rutland@arm.com> Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Andreas Larsson <andreas@gaisler.com> Link: https://lore.kernel.org/20250122144311.64392baf@gandalf.local.home Fixes: c84897c0ff592 ("ring-buffer: Remove 32bit timestamp logic") Closes: https://lore.kernel.org/all/86fb4f86-a0e4-45a2-a2df-3154acc4f086@gaisler.com/ Reported-by: Ludwig Rydberg <ludwig.rydberg@gaisler.com> Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
208 lines
4.8 KiB
C
208 lines
4.8 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Generic implementation of 64-bit atomics using spinlocks,
|
|
* useful on processors that don't have 64-bit atomic instructions.
|
|
*
|
|
* Copyright © 2009 Paul Mackerras, IBM Corp. <paulus@au1.ibm.com>
|
|
*/
|
|
#include <linux/types.h>
|
|
#include <linux/cache.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/init.h>
|
|
#include <linux/export.h>
|
|
#include <linux/atomic.h>
|
|
|
|
/*
|
|
* We use a hashed array of spinlocks to provide exclusive access
|
|
* to each atomic64_t variable. Since this is expected to used on
|
|
* systems with small numbers of CPUs (<= 4 or so), we use a
|
|
* relatively small array of 16 spinlocks to avoid wasting too much
|
|
* memory on the spinlock array.
|
|
*/
|
|
#define NR_LOCKS 16
|
|
|
|
/*
|
|
* Ensure each lock is in a separate cacheline.
|
|
*/
|
|
static union {
|
|
arch_spinlock_t lock;
|
|
char pad[L1_CACHE_BYTES];
|
|
} atomic64_lock[NR_LOCKS] __cacheline_aligned_in_smp = {
|
|
[0 ... (NR_LOCKS - 1)] = {
|
|
.lock = __ARCH_SPIN_LOCK_UNLOCKED,
|
|
},
|
|
};
|
|
|
|
static inline arch_spinlock_t *lock_addr(const atomic64_t *v)
|
|
{
|
|
unsigned long addr = (unsigned long) v;
|
|
|
|
addr >>= L1_CACHE_SHIFT;
|
|
addr ^= (addr >> 8) ^ (addr >> 16);
|
|
return &atomic64_lock[addr & (NR_LOCKS - 1)].lock;
|
|
}
|
|
|
|
s64 generic_atomic64_read(const atomic64_t *v)
|
|
{
|
|
unsigned long flags;
|
|
arch_spinlock_t *lock = lock_addr(v);
|
|
s64 val;
|
|
|
|
local_irq_save(flags);
|
|
arch_spin_lock(lock);
|
|
val = v->counter;
|
|
arch_spin_unlock(lock);
|
|
local_irq_restore(flags);
|
|
return val;
|
|
}
|
|
EXPORT_SYMBOL(generic_atomic64_read);
|
|
|
|
void generic_atomic64_set(atomic64_t *v, s64 i)
|
|
{
|
|
unsigned long flags;
|
|
arch_spinlock_t *lock = lock_addr(v);
|
|
|
|
local_irq_save(flags);
|
|
arch_spin_lock(lock);
|
|
v->counter = i;
|
|
arch_spin_unlock(lock);
|
|
local_irq_restore(flags);
|
|
}
|
|
EXPORT_SYMBOL(generic_atomic64_set);
|
|
|
|
#define ATOMIC64_OP(op, c_op) \
|
|
void generic_atomic64_##op(s64 a, atomic64_t *v) \
|
|
{ \
|
|
unsigned long flags; \
|
|
arch_spinlock_t *lock = lock_addr(v); \
|
|
\
|
|
local_irq_save(flags); \
|
|
arch_spin_lock(lock); \
|
|
v->counter c_op a; \
|
|
arch_spin_unlock(lock); \
|
|
local_irq_restore(flags); \
|
|
} \
|
|
EXPORT_SYMBOL(generic_atomic64_##op);
|
|
|
|
#define ATOMIC64_OP_RETURN(op, c_op) \
|
|
s64 generic_atomic64_##op##_return(s64 a, atomic64_t *v) \
|
|
{ \
|
|
unsigned long flags; \
|
|
arch_spinlock_t *lock = lock_addr(v); \
|
|
s64 val; \
|
|
\
|
|
local_irq_save(flags); \
|
|
arch_spin_lock(lock); \
|
|
val = (v->counter c_op a); \
|
|
arch_spin_unlock(lock); \
|
|
local_irq_restore(flags); \
|
|
return val; \
|
|
} \
|
|
EXPORT_SYMBOL(generic_atomic64_##op##_return);
|
|
|
|
#define ATOMIC64_FETCH_OP(op, c_op) \
|
|
s64 generic_atomic64_fetch_##op(s64 a, atomic64_t *v) \
|
|
{ \
|
|
unsigned long flags; \
|
|
arch_spinlock_t *lock = lock_addr(v); \
|
|
s64 val; \
|
|
\
|
|
local_irq_save(flags); \
|
|
arch_spin_lock(lock); \
|
|
val = v->counter; \
|
|
v->counter c_op a; \
|
|
arch_spin_unlock(lock); \
|
|
local_irq_restore(flags); \
|
|
return val; \
|
|
} \
|
|
EXPORT_SYMBOL(generic_atomic64_fetch_##op);
|
|
|
|
#define ATOMIC64_OPS(op, c_op) \
|
|
ATOMIC64_OP(op, c_op) \
|
|
ATOMIC64_OP_RETURN(op, c_op) \
|
|
ATOMIC64_FETCH_OP(op, c_op)
|
|
|
|
ATOMIC64_OPS(add, +=)
|
|
ATOMIC64_OPS(sub, -=)
|
|
|
|
#undef ATOMIC64_OPS
|
|
#define ATOMIC64_OPS(op, c_op) \
|
|
ATOMIC64_OP(op, c_op) \
|
|
ATOMIC64_FETCH_OP(op, c_op)
|
|
|
|
ATOMIC64_OPS(and, &=)
|
|
ATOMIC64_OPS(or, |=)
|
|
ATOMIC64_OPS(xor, ^=)
|
|
|
|
#undef ATOMIC64_OPS
|
|
#undef ATOMIC64_FETCH_OP
|
|
#undef ATOMIC64_OP
|
|
|
|
s64 generic_atomic64_dec_if_positive(atomic64_t *v)
|
|
{
|
|
unsigned long flags;
|
|
arch_spinlock_t *lock = lock_addr(v);
|
|
s64 val;
|
|
|
|
local_irq_save(flags);
|
|
arch_spin_lock(lock);
|
|
val = v->counter - 1;
|
|
if (val >= 0)
|
|
v->counter = val;
|
|
arch_spin_unlock(lock);
|
|
local_irq_restore(flags);
|
|
return val;
|
|
}
|
|
EXPORT_SYMBOL(generic_atomic64_dec_if_positive);
|
|
|
|
s64 generic_atomic64_cmpxchg(atomic64_t *v, s64 o, s64 n)
|
|
{
|
|
unsigned long flags;
|
|
arch_spinlock_t *lock = lock_addr(v);
|
|
s64 val;
|
|
|
|
local_irq_save(flags);
|
|
arch_spin_lock(lock);
|
|
val = v->counter;
|
|
if (val == o)
|
|
v->counter = n;
|
|
arch_spin_unlock(lock);
|
|
local_irq_restore(flags);
|
|
return val;
|
|
}
|
|
EXPORT_SYMBOL(generic_atomic64_cmpxchg);
|
|
|
|
s64 generic_atomic64_xchg(atomic64_t *v, s64 new)
|
|
{
|
|
unsigned long flags;
|
|
arch_spinlock_t *lock = lock_addr(v);
|
|
s64 val;
|
|
|
|
local_irq_save(flags);
|
|
arch_spin_lock(lock);
|
|
val = v->counter;
|
|
v->counter = new;
|
|
arch_spin_unlock(lock);
|
|
local_irq_restore(flags);
|
|
return val;
|
|
}
|
|
EXPORT_SYMBOL(generic_atomic64_xchg);
|
|
|
|
s64 generic_atomic64_fetch_add_unless(atomic64_t *v, s64 a, s64 u)
|
|
{
|
|
unsigned long flags;
|
|
arch_spinlock_t *lock = lock_addr(v);
|
|
s64 val;
|
|
|
|
local_irq_save(flags);
|
|
arch_spin_lock(lock);
|
|
val = v->counter;
|
|
if (val != u)
|
|
v->counter += a;
|
|
arch_spin_unlock(lock);
|
|
local_irq_restore(flags);
|
|
|
|
return val;
|
|
}
|
|
EXPORT_SYMBOL(generic_atomic64_fetch_add_unless);
|