[PATCH bpf-next v3 3/6] locking/local_lock: Introduce local_trylock_irqsave()

alexei.starovoitov
2024-12-18T03:07:43Z

From: Alexei Starovoitov <ast@kernel.org>

This is inspired by 'struct local_tryirq_lock' in:
https://lore.kernel.org/all/20241112-slub-percpu-caches-v1-5-ddc0bdc27e05@suse.cz/

Similar to local_lock_irqsave() introduce local_trylock_irqsave().
It uses spin_trylock in PREEMPT_RT when not in hard IRQ and not in NMI
and instantly fails otherwise.
In !RT it uses simple active flag that prevents IRQs or NMIs reentering
locked region.

Note there is no need to use local_inc for active flag.
If IRQ handler grabs the same local_lock after READ_ONCE(lock->active)
already completed it has to unlock it before returning.
Similar with NMI handler. So there is a strict nesting of scopes.
It's a per cpu lock, so multiple cpus do not access it in parallel.

Signed-off-by: Alexei Starovoitov <ast@kernel.org>
---
include/linux/local_lock.h | 9 ++++
include/linux/local_lock_internal.h | 76 ++++++++++++++++++++++++++---
2 files changed, 78 insertions(+), 7 deletions(-)

diff --git a/include/linux/local_lock.h b/include/linux/local_lock.h
index 091dc0b6bdfb..84ee560c4f51 100644
--- a/include/linux/local_lock.h
+++ b/include/linux/local_lock.h
@@ -30,6 +30,15 @@
#define local_lock_irqsave(lock, flags) \
__local_lock_irqsave(lock, flags)

+/**
+ * local_trylock_irqsave - Try to acquire a per CPU local lock, save and disable
+ * interrupts. Always fails in RT when in_hardirq or NMI.
+ * @lock: The lock variable
+ * @flags: Storage for interrupt flags
+ */
+#define local_trylock_irqsave(lock, flags) \
+ __local_trylock_irqsave(lock, flags)
+
/**
* local_unlock - Release a per CPU local lock
* @lock: The lock variable
diff --git a/include/linux/local_lock_internal.h b/include/linux/local_lock_internal.h
index 8dd71fbbb6d2..93672127c73d 100644
--- a/include/linux/local_lock_internal.h
+++ b/include/linux/local_lock_internal.h
@@ -9,6 +9,7 @@
#ifndef CONFIG_PREEMPT_RT

typedef struct {
+ int active;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
struct task_struct *owner;
@@ -22,7 +23,7 @@ typedef struct {
.wait_type_inner = LD_WAIT_CONFIG, \
.lock_type = LD_LOCK_PERCPU, \
}, \
- .owner = NULL,
+ .owner = NULL, .active = 0

static inline void local_lock_acquire(local_lock_t *l)
{
@@ -31,6 +32,13 @@ static inline void local_lock_acquire(local_lock_t *l)
l->owner = current;
}

+static inline void local_trylock_acquire(local_lock_t *l)
+{
+ lock_map_acquire_try(&l->dep_map);
+ DEBUG_LOCKS_WARN_ON(l->owner);
+ l->owner = current;
+}
+
static inline void local_lock_release(local_lock_t *l)
{
DEBUG_LOCKS_WARN_ON(l->owner != current);
@@ -45,6 +53,7 @@ static inline void local_lock_debug_init(local_lock_t *l)
#else /* CONFIG_DEBUG_LOCK_ALLOC */
# define LOCAL_LOCK_DEBUG_INIT(lockname)
static inline void local_lock_acquire(local_lock_t *l) { }
+static inline void local_trylock_acquire(local_lock_t *l) { }
static inline void local_lock_release(local_lock_t *l) { }
static inline void local_lock_debug_init(local_lock_t *l) { }
#endif /* !CONFIG_DEBUG_LOCK_ALLOC */
@@ -60,6 +69,7 @@ do { \
0, LD_WAIT_CONFIG, LD_WAIT_INV, \
LD_LOCK_PERCPU); \
local_lock_debug_init(lock); \
+ (lock)->active = 0; \
} while (0)

#define __spinlock_nested_bh_init(lock) \
@@ -75,37 +85,73 @@ do { \

#define __local_lock(lock) \
do { \
+ local_lock_t *l; \
preempt_disable(); \
- local_lock_acquire(this_cpu_ptr(lock)); \
+ l = this_cpu_ptr(lock); \
+ lockdep_assert(l->active == 0); \
+ WRITE_ONCE(l->active, 1); \
+ local_lock_acquire(l); \
} while (0)

#define __local_lock_irq(lock) \
do { \
+ local_lock_t *l; \
local_irq_disable(); \
- local_lock_acquire(this_cpu_ptr(lock)); \
+ l = this_cpu_ptr(lock); \
+ lockdep_assert(l->active == 0); \
+ WRITE_ONCE(l->active, 1); \
+ local_lock_acquire(l); \
} while (0)

#define __local_lock_irqsave(lock, flags) \
do { \
+ local_lock_t *l; \
local_irq_save(flags); \
- local_lock_acquire(this_cpu_ptr(lock)); \
+ l = this_cpu_ptr(lock); \
+ lockdep_assert(l->active == 0); \
+ WRITE_ONCE(l->active, 1); \
+ local_lock_acquire(l); \
} while (0)

+#define __local_trylock_irqsave(lock, flags) \
+ ({ \
+ local_lock_t *l; \
+ local_irq_save(flags); \
+ l = this_cpu_ptr(lock); \
+ if (READ_ONCE(l->active) == 1) { \
+ local_irq_restore(flags); \
+ l = NULL; \
+ } else { \
+ WRITE_ONCE(l->active, 1); \
+ local_trylock_acquire(l); \
+ } \
+ !!l; \
+ })
+
#define __local_unlock(lock) \
do { \
- local_lock_release(this_cpu_ptr(lock)); \
+ local_lock_t *l = this_cpu_ptr(lock); \
+ lockdep_assert(l->active == 1); \
+ WRITE_ONCE(l->active, 0); \
+ local_lock_release(l); \
preempt_enable(); \
} while (0)

#define __local_unlock_irq(lock) \
do { \
- local_lock_release(this_cpu_ptr(lock)); \
+ local_lock_t *l = this_cpu_ptr(lock); \
+ lockdep_assert(l->active == 1); \
+ WRITE_ONCE(l->active, 0); \
+ local_lock_release(l); \
local_irq_enable(); \
} while (0)

#define __local_unlock_irqrestore(lock, flags) \
do { \
- local_lock_release(this_cpu_ptr(lock)); \
+ local_lock_t *l = this_cpu_ptr(lock); \
+ lockdep_assert(l->active == 1); \
+ WRITE_ONCE(l->active, 0); \
+ local_lock_release(l); \
local_irq_restore(flags); \
} while (0)

@@ -148,6 +194,22 @@ typedef spinlock_t local_lock_t;
__local_lock(lock); \
} while (0)

+#define __local_trylock_irqsave(lock, flags) \
+ ({ \
+ __label__ out; \
+ int ret = 0; \
+ typecheck(unsigned long, flags); \
+ flags = 0; \
+ if (in_nmi() || in_hardirq()) \
+ goto out; \
+ migrate_disable(); \
+ ret = spin_trylock(this_cpu_ptr((lock))); \
+ if (!ret) \
+ migrate_enable(); \
+ out: \
+ ret; \
+ })
+
#define __local_unlock(__lock) \
do { \
spin_unlock(this_cpu_ptr((__lock))); \
--
2.43.5