mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
Merge tag 'bpf_res_spin_lock' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next
Pull bpf relisient spinlock support from Alexei Starovoitov:
"This patch set introduces Resilient Queued Spin Lock (or rqspinlock
with res_spin_lock() and res_spin_unlock() APIs).
This is a qspinlock variant which recovers the kernel from a stalled
state when the lock acquisition path cannot make forward progress.
This can occur when a lock acquisition attempt enters a deadlock
situation (e.g. AA, or ABBA), or more generally, when the owner of the
lock (which we’re trying to acquire) isn’t making forward progress.
Deadlock detection is the main mechanism used to provide instant
recovery, with the timeout mechanism acting as a final line of
defense. Detection is triggered immediately when beginning the waiting
loop of a lock slow path.
Additionally, BPF programs attached to different parts of the kernel
can introduce new control flow into the kernel, which increases the
likelihood of deadlocks in code not written to handle reentrancy.
There have been multiple syzbot reports surfacing deadlocks in
internal kernel code due to the diverse ways in which BPF programs can
be attached to different parts of the kernel. By switching the BPF
subsystem’s lock usage to rqspinlock, all of these issues are
mitigated at runtime.
This spin lock implementation allows BPF maps to become safer and
remove mechanisms that have fallen short in assuring safety when
nesting programs in arbitrary ways in the same context or across
different contexts.
We run benchmarks that stress locking scalability and perform
comparison against the baseline (qspinlock). For the rqspinlock case,
we replace the default qspinlock with it in the kernel, such that all
spin locks in the kernel use the rqspinlock slow path. As such,
benchmarks that stress kernel spin locks end up exercising rqspinlock.
More details in the cover letter in commit 6ffb9017e9 ("Merge branch
'resilient-queued-spin-lock'")"
* tag 'bpf_res_spin_lock' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next: (24 commits)
selftests/bpf: Add tests for rqspinlock
bpf: Maintain FIFO property for rqspinlock unlock
bpf: Implement verifier support for rqspinlock
bpf: Introduce rqspinlock kfuncs
bpf: Convert lpm_trie.c to rqspinlock
bpf: Convert percpu_freelist.c to rqspinlock
bpf: Convert hashtab.c to rqspinlock
rqspinlock: Add locktorture support
rqspinlock: Add entry to Makefile, MAINTAINERS
rqspinlock: Add macros for rqspinlock usage
rqspinlock: Add basic support for CONFIG_PARAVIRT
rqspinlock: Add a test-and-set fallback
rqspinlock: Add deadlock detection and recovery
rqspinlock: Protect waiters in trylock fallback from stalls
rqspinlock: Protect waiters in queue from stalls
rqspinlock: Protect pending bit owners from stalls
rqspinlock: Hardcode cond_acquire loops for arm64
rqspinlock: Add support for timeouts
rqspinlock: Drop PV and virtualization support
rqspinlock: Add rqspinlock.h header
...
This commit is contained in:
@@ -4361,6 +4361,8 @@ F: include/uapi/linux/filter.h
|
||||
F: kernel/bpf/
|
||||
F: kernel/trace/bpf_trace.c
|
||||
F: lib/buildid.c
|
||||
F: arch/*/include/asm/rqspinlock.h
|
||||
F: include/asm-generic/rqspinlock.h
|
||||
F: lib/test_bpf.c
|
||||
F: net/bpf/
|
||||
F: net/core/filter.c
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _ASM_RQSPINLOCK_H
|
||||
#define _ASM_RQSPINLOCK_H
|
||||
|
||||
#include <asm/barrier.h>
|
||||
|
||||
/*
|
||||
* Hardcode res_smp_cond_load_acquire implementations for arm64 to a custom
|
||||
* version based on [0]. In rqspinlock code, our conditional expression involves
|
||||
* checking the value _and_ additionally a timeout. However, on arm64, the
|
||||
* WFE-based implementation may never spin again if no stores occur to the
|
||||
* locked byte in the lock word. As such, we may be stuck forever if
|
||||
* event-stream based unblocking is not available on the platform for WFE spin
|
||||
* loops (arch_timer_evtstrm_available).
|
||||
*
|
||||
* Once support for smp_cond_load_acquire_timewait [0] lands, we can drop this
|
||||
* copy-paste.
|
||||
*
|
||||
* While we rely on the implementation to amortize the cost of sampling
|
||||
* cond_expr for us, it will not happen when event stream support is
|
||||
* unavailable, time_expr check is amortized. This is not the common case, and
|
||||
* it would be difficult to fit our logic in the time_expr_ns >= time_limit_ns
|
||||
* comparison, hence just let it be. In case of event-stream, the loop is woken
|
||||
* up at microsecond granularity.
|
||||
*
|
||||
* [0]: https://lore.kernel.org/lkml/20250203214911.898276-1-ankur.a.arora@oracle.com
|
||||
*/
|
||||
|
||||
#ifndef smp_cond_load_acquire_timewait
|
||||
|
||||
#define smp_cond_time_check_count 200
|
||||
|
||||
#define __smp_cond_load_relaxed_spinwait(ptr, cond_expr, time_expr_ns, \
|
||||
time_limit_ns) ({ \
|
||||
typeof(ptr) __PTR = (ptr); \
|
||||
__unqual_scalar_typeof(*ptr) VAL; \
|
||||
unsigned int __count = 0; \
|
||||
for (;;) { \
|
||||
VAL = READ_ONCE(*__PTR); \
|
||||
if (cond_expr) \
|
||||
break; \
|
||||
cpu_relax(); \
|
||||
if (__count++ < smp_cond_time_check_count) \
|
||||
continue; \
|
||||
if ((time_expr_ns) >= (time_limit_ns)) \
|
||||
break; \
|
||||
__count = 0; \
|
||||
} \
|
||||
(typeof(*ptr))VAL; \
|
||||
})
|
||||
|
||||
#define __smp_cond_load_acquire_timewait(ptr, cond_expr, \
|
||||
time_expr_ns, time_limit_ns) \
|
||||
({ \
|
||||
typeof(ptr) __PTR = (ptr); \
|
||||
__unqual_scalar_typeof(*ptr) VAL; \
|
||||
for (;;) { \
|
||||
VAL = smp_load_acquire(__PTR); \
|
||||
if (cond_expr) \
|
||||
break; \
|
||||
__cmpwait_relaxed(__PTR, VAL); \
|
||||
if ((time_expr_ns) >= (time_limit_ns)) \
|
||||
break; \
|
||||
} \
|
||||
(typeof(*ptr))VAL; \
|
||||
})
|
||||
|
||||
#define smp_cond_load_acquire_timewait(ptr, cond_expr, \
|
||||
time_expr_ns, time_limit_ns) \
|
||||
({ \
|
||||
__unqual_scalar_typeof(*ptr) _val; \
|
||||
int __wfe = arch_timer_evtstrm_available(); \
|
||||
\
|
||||
if (likely(__wfe)) { \
|
||||
_val = __smp_cond_load_acquire_timewait(ptr, cond_expr, \
|
||||
time_expr_ns, \
|
||||
time_limit_ns); \
|
||||
} else { \
|
||||
_val = __smp_cond_load_relaxed_spinwait(ptr, cond_expr, \
|
||||
time_expr_ns, \
|
||||
time_limit_ns); \
|
||||
smp_acquire__after_ctrl_dep(); \
|
||||
} \
|
||||
(typeof(*ptr))_val; \
|
||||
})
|
||||
|
||||
#endif
|
||||
|
||||
#define res_smp_cond_load_acquire_timewait(v, c) smp_cond_load_acquire_timewait(v, c, 0, 1)
|
||||
|
||||
#include <asm-generic/rqspinlock.h>
|
||||
|
||||
#endif /* _ASM_RQSPINLOCK_H */
|
||||
@@ -0,0 +1,33 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _ASM_X86_RQSPINLOCK_H
|
||||
#define _ASM_X86_RQSPINLOCK_H
|
||||
|
||||
#include <asm/paravirt.h>
|
||||
|
||||
#ifdef CONFIG_PARAVIRT
|
||||
DECLARE_STATIC_KEY_FALSE(virt_spin_lock_key);
|
||||
|
||||
#define resilient_virt_spin_lock_enabled resilient_virt_spin_lock_enabled
|
||||
static __always_inline bool resilient_virt_spin_lock_enabled(void)
|
||||
{
|
||||
return static_branch_likely(&virt_spin_lock_key);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_QUEUED_SPINLOCKS
|
||||
typedef struct qspinlock rqspinlock_t;
|
||||
#else
|
||||
typedef struct rqspinlock rqspinlock_t;
|
||||
#endif
|
||||
extern int resilient_tas_spin_lock(rqspinlock_t *lock);
|
||||
|
||||
#define resilient_virt_spin_lock resilient_virt_spin_lock
|
||||
static inline int resilient_virt_spin_lock(rqspinlock_t *lock)
|
||||
{
|
||||
return resilient_tas_spin_lock(lock);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PARAVIRT */
|
||||
|
||||
#include <asm-generic/rqspinlock.h>
|
||||
|
||||
#endif /* _ASM_X86_RQSPINLOCK_H */
|
||||
@@ -45,6 +45,7 @@ mandatory-y += pci.h
|
||||
mandatory-y += percpu.h
|
||||
mandatory-y += pgalloc.h
|
||||
mandatory-y += preempt.h
|
||||
mandatory-y += rqspinlock.h
|
||||
mandatory-y += runtime-const.h
|
||||
mandatory-y += rwonce.h
|
||||
mandatory-y += sections.h
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
#ifndef __ASM_MCS_SPINLOCK_H
|
||||
#define __ASM_MCS_SPINLOCK_H
|
||||
|
||||
struct mcs_spinlock {
|
||||
struct mcs_spinlock *next;
|
||||
int locked; /* 1 if lock acquired */
|
||||
int count; /* nesting count, see qspinlock.c */
|
||||
};
|
||||
|
||||
/*
|
||||
* Architectures can define their own:
|
||||
*
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* Resilient Queued Spin Lock
|
||||
*
|
||||
* (C) Copyright 2024-2025 Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Authors: Kumar Kartikeya Dwivedi <memxor@gmail.com>
|
||||
*/
|
||||
#ifndef __ASM_GENERIC_RQSPINLOCK_H
|
||||
#define __ASM_GENERIC_RQSPINLOCK_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <vdso/time64.h>
|
||||
#include <linux/percpu.h>
|
||||
#ifdef CONFIG_QUEUED_SPINLOCKS
|
||||
#include <asm/qspinlock.h>
|
||||
#endif
|
||||
|
||||
struct rqspinlock {
|
||||
union {
|
||||
atomic_t val;
|
||||
u32 locked;
|
||||
};
|
||||
};
|
||||
|
||||
/* Even though this is same as struct rqspinlock, we need to emit a distinct
|
||||
* type in BTF for BPF programs.
|
||||
*/
|
||||
struct bpf_res_spin_lock {
|
||||
u32 val;
|
||||
};
|
||||
|
||||
struct qspinlock;
|
||||
#ifdef CONFIG_QUEUED_SPINLOCKS
|
||||
typedef struct qspinlock rqspinlock_t;
|
||||
#else
|
||||
typedef struct rqspinlock rqspinlock_t;
|
||||
#endif
|
||||
|
||||
extern int resilient_tas_spin_lock(rqspinlock_t *lock);
|
||||
#ifdef CONFIG_QUEUED_SPINLOCKS
|
||||
extern int resilient_queued_spin_lock_slowpath(rqspinlock_t *lock, u32 val);
|
||||
#endif
|
||||
|
||||
#ifndef resilient_virt_spin_lock_enabled
|
||||
static __always_inline bool resilient_virt_spin_lock_enabled(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef resilient_virt_spin_lock
|
||||
static __always_inline int resilient_virt_spin_lock(rqspinlock_t *lock)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Default timeout for waiting loops is 0.25 seconds
|
||||
*/
|
||||
#define RES_DEF_TIMEOUT (NSEC_PER_SEC / 4)
|
||||
|
||||
/*
|
||||
* Choose 31 as it makes rqspinlock_held cacheline-aligned.
|
||||
*/
|
||||
#define RES_NR_HELD 31
|
||||
|
||||
struct rqspinlock_held {
|
||||
int cnt;
|
||||
void *locks[RES_NR_HELD];
|
||||
};
|
||||
|
||||
DECLARE_PER_CPU_ALIGNED(struct rqspinlock_held, rqspinlock_held_locks);
|
||||
|
||||
static __always_inline void grab_held_lock_entry(void *lock)
|
||||
{
|
||||
int cnt = this_cpu_inc_return(rqspinlock_held_locks.cnt);
|
||||
|
||||
if (unlikely(cnt > RES_NR_HELD)) {
|
||||
/* Still keep the inc so we decrement later. */
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Implied compiler barrier in per-CPU operations; otherwise we can have
|
||||
* the compiler reorder inc with write to table, allowing interrupts to
|
||||
* overwrite and erase our write to the table (as on interrupt exit it
|
||||
* will be reset to NULL).
|
||||
*
|
||||
* It is fine for cnt inc to be reordered wrt remote readers though,
|
||||
* they won't observe our entry until the cnt update is visible, that's
|
||||
* all.
|
||||
*/
|
||||
this_cpu_write(rqspinlock_held_locks.locks[cnt - 1], lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* We simply don't support out-of-order unlocks, and keep the logic simple here.
|
||||
* The verifier prevents BPF programs from unlocking out-of-order, and the same
|
||||
* holds for in-kernel users.
|
||||
*
|
||||
* It is possible to run into misdetection scenarios of AA deadlocks on the same
|
||||
* CPU, and missed ABBA deadlocks on remote CPUs if this function pops entries
|
||||
* out of order (due to lock A, lock B, unlock A, unlock B) pattern. The correct
|
||||
* logic to preserve right entries in the table would be to walk the array of
|
||||
* held locks and swap and clear out-of-order entries, but that's too
|
||||
* complicated and we don't have a compelling use case for out of order unlocking.
|
||||
*/
|
||||
static __always_inline void release_held_lock_entry(void)
|
||||
{
|
||||
struct rqspinlock_held *rqh = this_cpu_ptr(&rqspinlock_held_locks);
|
||||
|
||||
if (unlikely(rqh->cnt > RES_NR_HELD))
|
||||
goto dec;
|
||||
WRITE_ONCE(rqh->locks[rqh->cnt - 1], NULL);
|
||||
dec:
|
||||
/*
|
||||
* Reordering of clearing above with inc and its write in
|
||||
* grab_held_lock_entry that came before us (in same acquisition
|
||||
* attempt) is ok, we either see a valid entry or NULL when it's
|
||||
* visible.
|
||||
*
|
||||
* But this helper is invoked when we unwind upon failing to acquire the
|
||||
* lock. Unlike the unlock path which constitutes a release store after
|
||||
* we clear the entry, we need to emit a write barrier here. Otherwise,
|
||||
* we may have a situation as follows:
|
||||
*
|
||||
* <error> for lock B
|
||||
* release_held_lock_entry
|
||||
*
|
||||
* try_cmpxchg_acquire for lock A
|
||||
* grab_held_lock_entry
|
||||
*
|
||||
* Lack of any ordering means reordering may occur such that dec, inc
|
||||
* are done before entry is overwritten. This permits a remote lock
|
||||
* holder of lock B (which this CPU failed to acquire) to now observe it
|
||||
* as being attempted on this CPU, and may lead to misdetection (if this
|
||||
* CPU holds a lock it is attempting to acquire, leading to false ABBA
|
||||
* diagnosis).
|
||||
*
|
||||
* In case of unlock, we will always do a release on the lock word after
|
||||
* releasing the entry, ensuring that other CPUs cannot hold the lock
|
||||
* (and make conclusions about deadlocks) until the entry has been
|
||||
* cleared on the local CPU, preventing any anomalies. Reordering is
|
||||
* still possible there, but a remote CPU cannot observe a lock in our
|
||||
* table which it is already holding, since visibility entails our
|
||||
* release store for the said lock has not retired.
|
||||
*
|
||||
* In theory we don't have a problem if the dec and WRITE_ONCE above get
|
||||
* reordered with each other, we either notice an empty NULL entry on
|
||||
* top (if dec succeeds WRITE_ONCE), or a potentially stale entry which
|
||||
* cannot be observed (if dec precedes WRITE_ONCE).
|
||||
*
|
||||
* Emit the write barrier _before_ the dec, this permits dec-inc
|
||||
* reordering but that is harmless as we'd have new entry set to NULL
|
||||
* already, i.e. they cannot precede the NULL store above.
|
||||
*/
|
||||
smp_wmb();
|
||||
this_cpu_dec(rqspinlock_held_locks.cnt);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_QUEUED_SPINLOCKS
|
||||
|
||||
/**
|
||||
* res_spin_lock - acquire a queued spinlock
|
||||
* @lock: Pointer to queued spinlock structure
|
||||
*
|
||||
* Return:
|
||||
* * 0 - Lock was acquired successfully.
|
||||
* * -EDEADLK - Lock acquisition failed because of AA/ABBA deadlock.
|
||||
* * -ETIMEDOUT - Lock acquisition failed because of timeout.
|
||||
*/
|
||||
static __always_inline int res_spin_lock(rqspinlock_t *lock)
|
||||
{
|
||||
int val = 0;
|
||||
|
||||
if (likely(atomic_try_cmpxchg_acquire(&lock->val, &val, _Q_LOCKED_VAL))) {
|
||||
grab_held_lock_entry(lock);
|
||||
return 0;
|
||||
}
|
||||
return resilient_queued_spin_lock_slowpath(lock, val);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define res_spin_lock(lock) resilient_tas_spin_lock(lock)
|
||||
|
||||
#endif /* CONFIG_QUEUED_SPINLOCKS */
|
||||
|
||||
static __always_inline void res_spin_unlock(rqspinlock_t *lock)
|
||||
{
|
||||
struct rqspinlock_held *rqh = this_cpu_ptr(&rqspinlock_held_locks);
|
||||
|
||||
if (unlikely(rqh->cnt > RES_NR_HELD))
|
||||
goto unlock;
|
||||
WRITE_ONCE(rqh->locks[rqh->cnt - 1], NULL);
|
||||
unlock:
|
||||
/*
|
||||
* Release barrier, ensures correct ordering. See release_held_lock_entry
|
||||
* for details. Perform release store instead of queued_spin_unlock,
|
||||
* since we use this function for test-and-set fallback as well. When we
|
||||
* have CONFIG_QUEUED_SPINLOCKS=n, we clear the full 4-byte lockword.
|
||||
*
|
||||
* Like release_held_lock_entry, we can do the release before the dec.
|
||||
* We simply care about not seeing the 'lock' in our table from a remote
|
||||
* CPU once the lock has been released, which doesn't rely on the dec.
|
||||
*
|
||||
* Unlike smp_wmb(), release is not a two way fence, hence it is
|
||||
* possible for a inc to move up and reorder with our clearing of the
|
||||
* entry. This isn't a problem however, as for a misdiagnosis of ABBA,
|
||||
* the remote CPU needs to hold this lock, which won't be released until
|
||||
* the store below is done, which would ensure the entry is overwritten
|
||||
* to NULL, etc.
|
||||
*/
|
||||
smp_store_release(&lock->locked, 0);
|
||||
this_cpu_dec(rqspinlock_held_locks.cnt);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_QUEUED_SPINLOCKS
|
||||
#define raw_res_spin_lock_init(lock) ({ *(lock) = (rqspinlock_t)__ARCH_SPIN_LOCK_UNLOCKED; })
|
||||
#else
|
||||
#define raw_res_spin_lock_init(lock) ({ *(lock) = (rqspinlock_t){0}; })
|
||||
#endif
|
||||
|
||||
#define raw_res_spin_lock(lock) \
|
||||
({ \
|
||||
int __ret; \
|
||||
preempt_disable(); \
|
||||
__ret = res_spin_lock(lock); \
|
||||
if (__ret) \
|
||||
preempt_enable(); \
|
||||
__ret; \
|
||||
})
|
||||
|
||||
#define raw_res_spin_unlock(lock) ({ res_spin_unlock(lock); preempt_enable(); })
|
||||
|
||||
#define raw_res_spin_lock_irqsave(lock, flags) \
|
||||
({ \
|
||||
int __ret; \
|
||||
local_irq_save(flags); \
|
||||
__ret = raw_res_spin_lock(lock); \
|
||||
if (__ret) \
|
||||
local_irq_restore(flags); \
|
||||
__ret; \
|
||||
})
|
||||
|
||||
#define raw_res_spin_unlock_irqrestore(lock, flags) ({ raw_res_spin_unlock(lock); local_irq_restore(flags); })
|
||||
|
||||
#endif /* __ASM_GENERIC_RQSPINLOCK_H */
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <linux/static_call.h>
|
||||
#include <linux/memcontrol.h>
|
||||
#include <linux/cfi.h>
|
||||
#include <asm/rqspinlock.h>
|
||||
|
||||
struct bpf_verifier_env;
|
||||
struct bpf_verifier_log;
|
||||
@@ -204,6 +205,7 @@ enum btf_field_type {
|
||||
BPF_REFCOUNT = (1 << 9),
|
||||
BPF_WORKQUEUE = (1 << 10),
|
||||
BPF_UPTR = (1 << 11),
|
||||
BPF_RES_SPIN_LOCK = (1 << 12),
|
||||
};
|
||||
|
||||
typedef void (*btf_dtor_kfunc_t)(void *);
|
||||
@@ -239,6 +241,7 @@ struct btf_record {
|
||||
u32 cnt;
|
||||
u32 field_mask;
|
||||
int spin_lock_off;
|
||||
int res_spin_lock_off;
|
||||
int timer_off;
|
||||
int wq_off;
|
||||
int refcount_off;
|
||||
@@ -314,6 +317,8 @@ static inline const char *btf_field_type_name(enum btf_field_type type)
|
||||
switch (type) {
|
||||
case BPF_SPIN_LOCK:
|
||||
return "bpf_spin_lock";
|
||||
case BPF_RES_SPIN_LOCK:
|
||||
return "bpf_res_spin_lock";
|
||||
case BPF_TIMER:
|
||||
return "bpf_timer";
|
||||
case BPF_WORKQUEUE:
|
||||
@@ -346,6 +351,8 @@ static inline u32 btf_field_type_size(enum btf_field_type type)
|
||||
switch (type) {
|
||||
case BPF_SPIN_LOCK:
|
||||
return sizeof(struct bpf_spin_lock);
|
||||
case BPF_RES_SPIN_LOCK:
|
||||
return sizeof(struct bpf_res_spin_lock);
|
||||
case BPF_TIMER:
|
||||
return sizeof(struct bpf_timer);
|
||||
case BPF_WORKQUEUE:
|
||||
@@ -376,6 +383,8 @@ static inline u32 btf_field_type_align(enum btf_field_type type)
|
||||
switch (type) {
|
||||
case BPF_SPIN_LOCK:
|
||||
return __alignof__(struct bpf_spin_lock);
|
||||
case BPF_RES_SPIN_LOCK:
|
||||
return __alignof__(struct bpf_res_spin_lock);
|
||||
case BPF_TIMER:
|
||||
return __alignof__(struct bpf_timer);
|
||||
case BPF_WORKQUEUE:
|
||||
@@ -419,6 +428,7 @@ static inline void bpf_obj_init_field(const struct btf_field *field, void *addr)
|
||||
case BPF_RB_ROOT:
|
||||
/* RB_ROOT_CACHED 0-inits, no need to do anything after memset */
|
||||
case BPF_SPIN_LOCK:
|
||||
case BPF_RES_SPIN_LOCK:
|
||||
case BPF_TIMER:
|
||||
case BPF_WORKQUEUE:
|
||||
case BPF_KPTR_UNREF:
|
||||
|
||||
@@ -115,6 +115,14 @@ struct bpf_reg_state {
|
||||
int depth:30;
|
||||
} iter;
|
||||
|
||||
/* For irq stack slots */
|
||||
struct {
|
||||
enum {
|
||||
IRQ_NATIVE_KFUNC,
|
||||
IRQ_LOCK_KFUNC,
|
||||
} kfunc_class;
|
||||
} irq;
|
||||
|
||||
/* Max size from any of the above. */
|
||||
struct {
|
||||
unsigned long raw1;
|
||||
@@ -255,9 +263,12 @@ struct bpf_reference_state {
|
||||
* default to pointer reference on zero initialization of a state.
|
||||
*/
|
||||
enum ref_state_type {
|
||||
REF_TYPE_PTR = 1,
|
||||
REF_TYPE_IRQ = 2,
|
||||
REF_TYPE_LOCK = 3,
|
||||
REF_TYPE_PTR = (1 << 1),
|
||||
REF_TYPE_IRQ = (1 << 2),
|
||||
REF_TYPE_LOCK = (1 << 3),
|
||||
REF_TYPE_RES_LOCK = (1 << 4),
|
||||
REF_TYPE_RES_LOCK_IRQ = (1 << 5),
|
||||
REF_TYPE_LOCK_MASK = REF_TYPE_LOCK | REF_TYPE_RES_LOCK | REF_TYPE_RES_LOCK_IRQ,
|
||||
} type;
|
||||
/* Track each reference created with a unique id, even if the same
|
||||
* instruction creates the reference multiple times (eg, via CALL).
|
||||
@@ -424,6 +435,8 @@ struct bpf_verifier_state {
|
||||
u32 active_locks;
|
||||
u32 active_preempt_locks;
|
||||
u32 active_irq_id;
|
||||
u32 active_lock_id;
|
||||
void *active_lock_ptr;
|
||||
bool active_rcu_lock;
|
||||
|
||||
bool speculative;
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ obj-$(CONFIG_BPF_SYSCALL) += bpf_local_storage.o bpf_task_storage.o
|
||||
obj-${CONFIG_BPF_LSM} += bpf_inode_storage.o
|
||||
obj-$(CONFIG_BPF_SYSCALL) += disasm.o mprog.o
|
||||
obj-$(CONFIG_BPF_JIT) += trampoline.o
|
||||
obj-$(CONFIG_BPF_SYSCALL) += btf.o memalloc.o
|
||||
obj-$(CONFIG_BPF_SYSCALL) += btf.o memalloc.o rqspinlock.o
|
||||
ifeq ($(CONFIG_MMU)$(CONFIG_64BIT),yy)
|
||||
obj-$(CONFIG_BPF_SYSCALL) += arena.o range_tree.o
|
||||
endif
|
||||
|
||||
+24
-2
@@ -3481,6 +3481,15 @@ static int btf_get_field_type(const struct btf *btf, const struct btf_type *var_
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (field_mask & BPF_RES_SPIN_LOCK) {
|
||||
if (!strcmp(name, "bpf_res_spin_lock")) {
|
||||
if (*seen_mask & BPF_RES_SPIN_LOCK)
|
||||
return -E2BIG;
|
||||
*seen_mask |= BPF_RES_SPIN_LOCK;
|
||||
type = BPF_RES_SPIN_LOCK;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (field_mask & BPF_TIMER) {
|
||||
if (!strcmp(name, "bpf_timer")) {
|
||||
if (*seen_mask & BPF_TIMER)
|
||||
@@ -3659,6 +3668,7 @@ static int btf_find_field_one(const struct btf *btf,
|
||||
|
||||
switch (field_type) {
|
||||
case BPF_SPIN_LOCK:
|
||||
case BPF_RES_SPIN_LOCK:
|
||||
case BPF_TIMER:
|
||||
case BPF_WORKQUEUE:
|
||||
case BPF_LIST_NODE:
|
||||
@@ -3952,6 +3962,7 @@ struct btf_record *btf_parse_fields(const struct btf *btf, const struct btf_type
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
rec->spin_lock_off = -EINVAL;
|
||||
rec->res_spin_lock_off = -EINVAL;
|
||||
rec->timer_off = -EINVAL;
|
||||
rec->wq_off = -EINVAL;
|
||||
rec->refcount_off = -EINVAL;
|
||||
@@ -3979,6 +3990,11 @@ struct btf_record *btf_parse_fields(const struct btf *btf, const struct btf_type
|
||||
/* Cache offset for faster lookup at runtime */
|
||||
rec->spin_lock_off = rec->fields[i].offset;
|
||||
break;
|
||||
case BPF_RES_SPIN_LOCK:
|
||||
WARN_ON_ONCE(rec->spin_lock_off >= 0);
|
||||
/* Cache offset for faster lookup at runtime */
|
||||
rec->res_spin_lock_off = rec->fields[i].offset;
|
||||
break;
|
||||
case BPF_TIMER:
|
||||
WARN_ON_ONCE(rec->timer_off >= 0);
|
||||
/* Cache offset for faster lookup at runtime */
|
||||
@@ -4022,9 +4038,15 @@ struct btf_record *btf_parse_fields(const struct btf *btf, const struct btf_type
|
||||
rec->cnt++;
|
||||
}
|
||||
|
||||
if (rec->spin_lock_off >= 0 && rec->res_spin_lock_off >= 0) {
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* bpf_{list_head, rb_node} require bpf_spin_lock */
|
||||
if ((btf_record_has_field(rec, BPF_LIST_HEAD) ||
|
||||
btf_record_has_field(rec, BPF_RB_ROOT)) && rec->spin_lock_off < 0) {
|
||||
btf_record_has_field(rec, BPF_RB_ROOT)) &&
|
||||
(rec->spin_lock_off < 0 && rec->res_spin_lock_off < 0)) {
|
||||
ret = -EINVAL;
|
||||
goto end;
|
||||
}
|
||||
@@ -5637,7 +5659,7 @@ btf_parse_struct_metas(struct bpf_verifier_log *log, struct btf *btf)
|
||||
|
||||
type = &tab->types[tab->cnt];
|
||||
type->btf_id = i;
|
||||
record = btf_parse_fields(btf, t, BPF_SPIN_LOCK | BPF_LIST_HEAD | BPF_LIST_NODE |
|
||||
record = btf_parse_fields(btf, t, BPF_SPIN_LOCK | BPF_RES_SPIN_LOCK | BPF_LIST_HEAD | BPF_LIST_NODE |
|
||||
BPF_RB_ROOT | BPF_RB_NODE | BPF_REFCOUNT |
|
||||
BPF_KPTR, t->size);
|
||||
/* The record cannot be unset, treat it as an error if so */
|
||||
|
||||
+32
-70
@@ -16,6 +16,7 @@
|
||||
#include "bpf_lru_list.h"
|
||||
#include "map_in_map.h"
|
||||
#include <linux/bpf_mem_alloc.h>
|
||||
#include <asm/rqspinlock.h>
|
||||
|
||||
#define HTAB_CREATE_FLAG_MASK \
|
||||
(BPF_F_NO_PREALLOC | BPF_F_NO_COMMON_LRU | BPF_F_NUMA_NODE | \
|
||||
@@ -78,7 +79,7 @@
|
||||
*/
|
||||
struct bucket {
|
||||
struct hlist_nulls_head head;
|
||||
raw_spinlock_t raw_lock;
|
||||
rqspinlock_t raw_lock;
|
||||
};
|
||||
|
||||
#define HASHTAB_MAP_LOCK_COUNT 8
|
||||
@@ -104,8 +105,6 @@ struct bpf_htab {
|
||||
u32 n_buckets; /* number of hash buckets */
|
||||
u32 elem_size; /* size of each element in bytes */
|
||||
u32 hashrnd;
|
||||
struct lock_class_key lockdep_key;
|
||||
int __percpu *map_locked[HASHTAB_MAP_LOCK_COUNT];
|
||||
};
|
||||
|
||||
/* each htab element is struct htab_elem + key + value */
|
||||
@@ -140,45 +139,26 @@ static void htab_init_buckets(struct bpf_htab *htab)
|
||||
|
||||
for (i = 0; i < htab->n_buckets; i++) {
|
||||
INIT_HLIST_NULLS_HEAD(&htab->buckets[i].head, i);
|
||||
raw_spin_lock_init(&htab->buckets[i].raw_lock);
|
||||
lockdep_set_class(&htab->buckets[i].raw_lock,
|
||||
&htab->lockdep_key);
|
||||
raw_res_spin_lock_init(&htab->buckets[i].raw_lock);
|
||||
cond_resched();
|
||||
}
|
||||
}
|
||||
|
||||
static inline int htab_lock_bucket(const struct bpf_htab *htab,
|
||||
struct bucket *b, u32 hash,
|
||||
unsigned long *pflags)
|
||||
static inline int htab_lock_bucket(struct bucket *b, unsigned long *pflags)
|
||||
{
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
hash = hash & min_t(u32, HASHTAB_MAP_LOCK_MASK, htab->n_buckets - 1);
|
||||
|
||||
preempt_disable();
|
||||
local_irq_save(flags);
|
||||
if (unlikely(__this_cpu_inc_return(*(htab->map_locked[hash])) != 1)) {
|
||||
__this_cpu_dec(*(htab->map_locked[hash]));
|
||||
local_irq_restore(flags);
|
||||
preempt_enable();
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
raw_spin_lock(&b->raw_lock);
|
||||
ret = raw_res_spin_lock_irqsave(&b->raw_lock, flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
*pflags = flags;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void htab_unlock_bucket(const struct bpf_htab *htab,
|
||||
struct bucket *b, u32 hash,
|
||||
unsigned long flags)
|
||||
static inline void htab_unlock_bucket(struct bucket *b, unsigned long flags)
|
||||
{
|
||||
hash = hash & min_t(u32, HASHTAB_MAP_LOCK_MASK, htab->n_buckets - 1);
|
||||
raw_spin_unlock(&b->raw_lock);
|
||||
__this_cpu_dec(*(htab->map_locked[hash]));
|
||||
local_irq_restore(flags);
|
||||
preempt_enable();
|
||||
raw_res_spin_unlock_irqrestore(&b->raw_lock, flags);
|
||||
}
|
||||
|
||||
static bool htab_lru_map_delete_node(void *arg, struct bpf_lru_node *node);
|
||||
@@ -483,14 +463,12 @@ static struct bpf_map *htab_map_alloc(union bpf_attr *attr)
|
||||
bool percpu_lru = (attr->map_flags & BPF_F_NO_COMMON_LRU);
|
||||
bool prealloc = !(attr->map_flags & BPF_F_NO_PREALLOC);
|
||||
struct bpf_htab *htab;
|
||||
int err, i;
|
||||
int err;
|
||||
|
||||
htab = bpf_map_area_alloc(sizeof(*htab), NUMA_NO_NODE);
|
||||
if (!htab)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
lockdep_register_key(&htab->lockdep_key);
|
||||
|
||||
bpf_map_init_from_attr(&htab->map, attr);
|
||||
|
||||
if (percpu_lru) {
|
||||
@@ -536,15 +514,6 @@ static struct bpf_map *htab_map_alloc(union bpf_attr *attr)
|
||||
if (!htab->buckets)
|
||||
goto free_elem_count;
|
||||
|
||||
for (i = 0; i < HASHTAB_MAP_LOCK_COUNT; i++) {
|
||||
htab->map_locked[i] = bpf_map_alloc_percpu(&htab->map,
|
||||
sizeof(int),
|
||||
sizeof(int),
|
||||
GFP_USER);
|
||||
if (!htab->map_locked[i])
|
||||
goto free_map_locked;
|
||||
}
|
||||
|
||||
if (htab->map.map_flags & BPF_F_ZERO_SEED)
|
||||
htab->hashrnd = 0;
|
||||
else
|
||||
@@ -607,15 +576,12 @@ free_prealloc:
|
||||
free_map_locked:
|
||||
if (htab->use_percpu_counter)
|
||||
percpu_counter_destroy(&htab->pcount);
|
||||
for (i = 0; i < HASHTAB_MAP_LOCK_COUNT; i++)
|
||||
free_percpu(htab->map_locked[i]);
|
||||
bpf_map_area_free(htab->buckets);
|
||||
bpf_mem_alloc_destroy(&htab->pcpu_ma);
|
||||
bpf_mem_alloc_destroy(&htab->ma);
|
||||
free_elem_count:
|
||||
bpf_map_free_elem_count(&htab->map);
|
||||
free_htab:
|
||||
lockdep_unregister_key(&htab->lockdep_key);
|
||||
bpf_map_area_free(htab);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
@@ -820,7 +786,7 @@ static bool htab_lru_map_delete_node(void *arg, struct bpf_lru_node *node)
|
||||
b = __select_bucket(htab, tgt_l->hash);
|
||||
head = &b->head;
|
||||
|
||||
ret = htab_lock_bucket(htab, b, tgt_l->hash, &flags);
|
||||
ret = htab_lock_bucket(b, &flags);
|
||||
if (ret)
|
||||
return false;
|
||||
|
||||
@@ -831,7 +797,7 @@ static bool htab_lru_map_delete_node(void *arg, struct bpf_lru_node *node)
|
||||
break;
|
||||
}
|
||||
|
||||
htab_unlock_bucket(htab, b, tgt_l->hash, flags);
|
||||
htab_unlock_bucket(b, flags);
|
||||
|
||||
if (l == tgt_l)
|
||||
check_and_free_fields(htab, l);
|
||||
@@ -1150,7 +1116,7 @@ static long htab_map_update_elem(struct bpf_map *map, void *key, void *value,
|
||||
*/
|
||||
}
|
||||
|
||||
ret = htab_lock_bucket(htab, b, hash, &flags);
|
||||
ret = htab_lock_bucket(b, &flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -1201,7 +1167,7 @@ static long htab_map_update_elem(struct bpf_map *map, void *key, void *value,
|
||||
check_and_free_fields(htab, l_old);
|
||||
}
|
||||
}
|
||||
htab_unlock_bucket(htab, b, hash, flags);
|
||||
htab_unlock_bucket(b, flags);
|
||||
if (l_old) {
|
||||
if (old_map_ptr)
|
||||
map->ops->map_fd_put_ptr(map, old_map_ptr, true);
|
||||
@@ -1210,7 +1176,7 @@ static long htab_map_update_elem(struct bpf_map *map, void *key, void *value,
|
||||
}
|
||||
return 0;
|
||||
err:
|
||||
htab_unlock_bucket(htab, b, hash, flags);
|
||||
htab_unlock_bucket(b, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1257,7 +1223,7 @@ static long htab_lru_map_update_elem(struct bpf_map *map, void *key, void *value
|
||||
copy_map_value(&htab->map,
|
||||
l_new->key + round_up(map->key_size, 8), value);
|
||||
|
||||
ret = htab_lock_bucket(htab, b, hash, &flags);
|
||||
ret = htab_lock_bucket(b, &flags);
|
||||
if (ret)
|
||||
goto err_lock_bucket;
|
||||
|
||||
@@ -1278,7 +1244,7 @@ static long htab_lru_map_update_elem(struct bpf_map *map, void *key, void *value
|
||||
ret = 0;
|
||||
|
||||
err:
|
||||
htab_unlock_bucket(htab, b, hash, flags);
|
||||
htab_unlock_bucket(b, flags);
|
||||
|
||||
err_lock_bucket:
|
||||
if (ret)
|
||||
@@ -1315,7 +1281,7 @@ static long __htab_percpu_map_update_elem(struct bpf_map *map, void *key,
|
||||
b = __select_bucket(htab, hash);
|
||||
head = &b->head;
|
||||
|
||||
ret = htab_lock_bucket(htab, b, hash, &flags);
|
||||
ret = htab_lock_bucket(b, &flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -1340,7 +1306,7 @@ static long __htab_percpu_map_update_elem(struct bpf_map *map, void *key,
|
||||
}
|
||||
ret = 0;
|
||||
err:
|
||||
htab_unlock_bucket(htab, b, hash, flags);
|
||||
htab_unlock_bucket(b, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1381,7 +1347,7 @@ static long __htab_lru_percpu_map_update_elem(struct bpf_map *map, void *key,
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ret = htab_lock_bucket(htab, b, hash, &flags);
|
||||
ret = htab_lock_bucket(b, &flags);
|
||||
if (ret)
|
||||
goto err_lock_bucket;
|
||||
|
||||
@@ -1405,7 +1371,7 @@ static long __htab_lru_percpu_map_update_elem(struct bpf_map *map, void *key,
|
||||
}
|
||||
ret = 0;
|
||||
err:
|
||||
htab_unlock_bucket(htab, b, hash, flags);
|
||||
htab_unlock_bucket(b, flags);
|
||||
err_lock_bucket:
|
||||
if (l_new) {
|
||||
bpf_map_dec_elem_count(&htab->map);
|
||||
@@ -1447,7 +1413,7 @@ static long htab_map_delete_elem(struct bpf_map *map, void *key)
|
||||
b = __select_bucket(htab, hash);
|
||||
head = &b->head;
|
||||
|
||||
ret = htab_lock_bucket(htab, b, hash, &flags);
|
||||
ret = htab_lock_bucket(b, &flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -1457,7 +1423,7 @@ static long htab_map_delete_elem(struct bpf_map *map, void *key)
|
||||
else
|
||||
ret = -ENOENT;
|
||||
|
||||
htab_unlock_bucket(htab, b, hash, flags);
|
||||
htab_unlock_bucket(b, flags);
|
||||
|
||||
if (l)
|
||||
free_htab_elem(htab, l);
|
||||
@@ -1483,7 +1449,7 @@ static long htab_lru_map_delete_elem(struct bpf_map *map, void *key)
|
||||
b = __select_bucket(htab, hash);
|
||||
head = &b->head;
|
||||
|
||||
ret = htab_lock_bucket(htab, b, hash, &flags);
|
||||
ret = htab_lock_bucket(b, &flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -1494,7 +1460,7 @@ static long htab_lru_map_delete_elem(struct bpf_map *map, void *key)
|
||||
else
|
||||
ret = -ENOENT;
|
||||
|
||||
htab_unlock_bucket(htab, b, hash, flags);
|
||||
htab_unlock_bucket(b, flags);
|
||||
if (l)
|
||||
htab_lru_push_free(htab, l);
|
||||
return ret;
|
||||
@@ -1561,7 +1527,6 @@ static void htab_map_free_timers_and_wq(struct bpf_map *map)
|
||||
static void htab_map_free(struct bpf_map *map)
|
||||
{
|
||||
struct bpf_htab *htab = container_of(map, struct bpf_htab, map);
|
||||
int i;
|
||||
|
||||
/* bpf_free_used_maps() or close(map_fd) will trigger this map_free callback.
|
||||
* bpf_free_used_maps() is called after bpf prog is no longer executing.
|
||||
@@ -1586,9 +1551,6 @@ static void htab_map_free(struct bpf_map *map)
|
||||
bpf_mem_alloc_destroy(&htab->ma);
|
||||
if (htab->use_percpu_counter)
|
||||
percpu_counter_destroy(&htab->pcount);
|
||||
for (i = 0; i < HASHTAB_MAP_LOCK_COUNT; i++)
|
||||
free_percpu(htab->map_locked[i]);
|
||||
lockdep_unregister_key(&htab->lockdep_key);
|
||||
bpf_map_area_free(htab);
|
||||
}
|
||||
|
||||
@@ -1631,7 +1593,7 @@ static int __htab_map_lookup_and_delete_elem(struct bpf_map *map, void *key,
|
||||
b = __select_bucket(htab, hash);
|
||||
head = &b->head;
|
||||
|
||||
ret = htab_lock_bucket(htab, b, hash, &bflags);
|
||||
ret = htab_lock_bucket(b, &bflags);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -1668,7 +1630,7 @@ static int __htab_map_lookup_and_delete_elem(struct bpf_map *map, void *key,
|
||||
hlist_nulls_del_rcu(&l->hash_node);
|
||||
|
||||
out_unlock:
|
||||
htab_unlock_bucket(htab, b, hash, bflags);
|
||||
htab_unlock_bucket(b, bflags);
|
||||
|
||||
if (l) {
|
||||
if (is_lru_map)
|
||||
@@ -1790,7 +1752,7 @@ again_nocopy:
|
||||
head = &b->head;
|
||||
/* do not grab the lock unless need it (bucket_cnt > 0). */
|
||||
if (locked) {
|
||||
ret = htab_lock_bucket(htab, b, batch, &flags);
|
||||
ret = htab_lock_bucket(b, &flags);
|
||||
if (ret) {
|
||||
rcu_read_unlock();
|
||||
bpf_enable_instrumentation();
|
||||
@@ -1813,7 +1775,7 @@ again_nocopy:
|
||||
/* Note that since bucket_cnt > 0 here, it is implicit
|
||||
* that the locked was grabbed, so release it.
|
||||
*/
|
||||
htab_unlock_bucket(htab, b, batch, flags);
|
||||
htab_unlock_bucket(b, flags);
|
||||
rcu_read_unlock();
|
||||
bpf_enable_instrumentation();
|
||||
goto after_loop;
|
||||
@@ -1824,7 +1786,7 @@ again_nocopy:
|
||||
/* Note that since bucket_cnt > 0 here, it is implicit
|
||||
* that the locked was grabbed, so release it.
|
||||
*/
|
||||
htab_unlock_bucket(htab, b, batch, flags);
|
||||
htab_unlock_bucket(b, flags);
|
||||
rcu_read_unlock();
|
||||
bpf_enable_instrumentation();
|
||||
kvfree(keys);
|
||||
@@ -1887,7 +1849,7 @@ again_nocopy:
|
||||
dst_val += value_size;
|
||||
}
|
||||
|
||||
htab_unlock_bucket(htab, b, batch, flags);
|
||||
htab_unlock_bucket(b, flags);
|
||||
locked = false;
|
||||
|
||||
while (node_to_free) {
|
||||
|
||||
+14
-11
@@ -15,6 +15,7 @@
|
||||
#include <net/ipv6.h>
|
||||
#include <uapi/linux/btf.h>
|
||||
#include <linux/btf_ids.h>
|
||||
#include <asm/rqspinlock.h>
|
||||
#include <linux/bpf_mem_alloc.h>
|
||||
|
||||
/* Intermediate node */
|
||||
@@ -36,7 +37,7 @@ struct lpm_trie {
|
||||
size_t n_entries;
|
||||
size_t max_prefixlen;
|
||||
size_t data_size;
|
||||
raw_spinlock_t lock;
|
||||
rqspinlock_t lock;
|
||||
};
|
||||
|
||||
/* This trie implements a longest prefix match algorithm that can be used to
|
||||
@@ -342,7 +343,9 @@ static long trie_update_elem(struct bpf_map *map,
|
||||
if (!new_node)
|
||||
return -ENOMEM;
|
||||
|
||||
raw_spin_lock_irqsave(&trie->lock, irq_flags);
|
||||
ret = raw_res_spin_lock_irqsave(&trie->lock, irq_flags);
|
||||
if (ret)
|
||||
goto out_free;
|
||||
|
||||
new_node->prefixlen = key->prefixlen;
|
||||
RCU_INIT_POINTER(new_node->child[0], NULL);
|
||||
@@ -356,8 +359,7 @@ static long trie_update_elem(struct bpf_map *map,
|
||||
*/
|
||||
slot = &trie->root;
|
||||
|
||||
while ((node = rcu_dereference_protected(*slot,
|
||||
lockdep_is_held(&trie->lock)))) {
|
||||
while ((node = rcu_dereference(*slot))) {
|
||||
matchlen = longest_prefix_match(trie, node, key);
|
||||
|
||||
if (node->prefixlen != matchlen ||
|
||||
@@ -442,8 +444,8 @@ static long trie_update_elem(struct bpf_map *map,
|
||||
rcu_assign_pointer(*slot, im_node);
|
||||
|
||||
out:
|
||||
raw_spin_unlock_irqrestore(&trie->lock, irq_flags);
|
||||
|
||||
raw_res_spin_unlock_irqrestore(&trie->lock, irq_flags);
|
||||
out_free:
|
||||
if (ret)
|
||||
bpf_mem_cache_free(&trie->ma, new_node);
|
||||
bpf_mem_cache_free_rcu(&trie->ma, free_node);
|
||||
@@ -467,7 +469,9 @@ static long trie_delete_elem(struct bpf_map *map, void *_key)
|
||||
if (key->prefixlen > trie->max_prefixlen)
|
||||
return -EINVAL;
|
||||
|
||||
raw_spin_lock_irqsave(&trie->lock, irq_flags);
|
||||
ret = raw_res_spin_lock_irqsave(&trie->lock, irq_flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Walk the tree looking for an exact key/length match and keeping
|
||||
* track of the path we traverse. We will need to know the node
|
||||
@@ -478,8 +482,7 @@ static long trie_delete_elem(struct bpf_map *map, void *_key)
|
||||
trim = &trie->root;
|
||||
trim2 = trim;
|
||||
parent = NULL;
|
||||
while ((node = rcu_dereference_protected(
|
||||
*trim, lockdep_is_held(&trie->lock)))) {
|
||||
while ((node = rcu_dereference(*trim))) {
|
||||
matchlen = longest_prefix_match(trie, node, key);
|
||||
|
||||
if (node->prefixlen != matchlen ||
|
||||
@@ -543,7 +546,7 @@ static long trie_delete_elem(struct bpf_map *map, void *_key)
|
||||
free_node = node;
|
||||
|
||||
out:
|
||||
raw_spin_unlock_irqrestore(&trie->lock, irq_flags);
|
||||
raw_res_spin_unlock_irqrestore(&trie->lock, irq_flags);
|
||||
|
||||
bpf_mem_cache_free_rcu(&trie->ma, free_parent);
|
||||
bpf_mem_cache_free_rcu(&trie->ma, free_node);
|
||||
@@ -592,7 +595,7 @@ static struct bpf_map *trie_alloc(union bpf_attr *attr)
|
||||
offsetof(struct bpf_lpm_trie_key_u8, data);
|
||||
trie->max_prefixlen = trie->data_size * 8;
|
||||
|
||||
raw_spin_lock_init(&trie->lock);
|
||||
raw_res_spin_lock_init(&trie->lock);
|
||||
|
||||
/* Allocate intermediate and leaf nodes from the same allocator */
|
||||
leaf_size = sizeof(struct lpm_trie_node) + trie->data_size +
|
||||
|
||||
@@ -14,11 +14,9 @@ int pcpu_freelist_init(struct pcpu_freelist *s)
|
||||
for_each_possible_cpu(cpu) {
|
||||
struct pcpu_freelist_head *head = per_cpu_ptr(s->freelist, cpu);
|
||||
|
||||
raw_spin_lock_init(&head->lock);
|
||||
raw_res_spin_lock_init(&head->lock);
|
||||
head->first = NULL;
|
||||
}
|
||||
raw_spin_lock_init(&s->extralist.lock);
|
||||
s->extralist.first = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -34,56 +32,37 @@ static inline void pcpu_freelist_push_node(struct pcpu_freelist_head *head,
|
||||
WRITE_ONCE(head->first, node);
|
||||
}
|
||||
|
||||
static inline void ___pcpu_freelist_push(struct pcpu_freelist_head *head,
|
||||
static inline bool ___pcpu_freelist_push(struct pcpu_freelist_head *head,
|
||||
struct pcpu_freelist_node *node)
|
||||
{
|
||||
raw_spin_lock(&head->lock);
|
||||
pcpu_freelist_push_node(head, node);
|
||||
raw_spin_unlock(&head->lock);
|
||||
}
|
||||
|
||||
static inline bool pcpu_freelist_try_push_extra(struct pcpu_freelist *s,
|
||||
struct pcpu_freelist_node *node)
|
||||
{
|
||||
if (!raw_spin_trylock(&s->extralist.lock))
|
||||
if (raw_res_spin_lock(&head->lock))
|
||||
return false;
|
||||
|
||||
pcpu_freelist_push_node(&s->extralist, node);
|
||||
raw_spin_unlock(&s->extralist.lock);
|
||||
pcpu_freelist_push_node(head, node);
|
||||
raw_res_spin_unlock(&head->lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void ___pcpu_freelist_push_nmi(struct pcpu_freelist *s,
|
||||
struct pcpu_freelist_node *node)
|
||||
{
|
||||
int cpu, orig_cpu;
|
||||
|
||||
orig_cpu = raw_smp_processor_id();
|
||||
while (1) {
|
||||
for_each_cpu_wrap(cpu, cpu_possible_mask, orig_cpu) {
|
||||
struct pcpu_freelist_head *head;
|
||||
|
||||
head = per_cpu_ptr(s->freelist, cpu);
|
||||
if (raw_spin_trylock(&head->lock)) {
|
||||
pcpu_freelist_push_node(head, node);
|
||||
raw_spin_unlock(&head->lock);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* cannot lock any per cpu lock, try extralist */
|
||||
if (pcpu_freelist_try_push_extra(s, node))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void __pcpu_freelist_push(struct pcpu_freelist *s,
|
||||
struct pcpu_freelist_node *node)
|
||||
{
|
||||
if (in_nmi())
|
||||
___pcpu_freelist_push_nmi(s, node);
|
||||
else
|
||||
___pcpu_freelist_push(this_cpu_ptr(s->freelist), node);
|
||||
struct pcpu_freelist_head *head;
|
||||
int cpu;
|
||||
|
||||
if (___pcpu_freelist_push(this_cpu_ptr(s->freelist), node))
|
||||
return;
|
||||
|
||||
while (true) {
|
||||
for_each_cpu_wrap(cpu, cpu_possible_mask, raw_smp_processor_id()) {
|
||||
if (cpu == raw_smp_processor_id())
|
||||
continue;
|
||||
head = per_cpu_ptr(s->freelist, cpu);
|
||||
if (raw_res_spin_lock(&head->lock))
|
||||
continue;
|
||||
pcpu_freelist_push_node(head, node);
|
||||
raw_res_spin_unlock(&head->lock);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pcpu_freelist_push(struct pcpu_freelist *s,
|
||||
@@ -120,71 +99,29 @@ void pcpu_freelist_populate(struct pcpu_freelist *s, void *buf, u32 elem_size,
|
||||
|
||||
static struct pcpu_freelist_node *___pcpu_freelist_pop(struct pcpu_freelist *s)
|
||||
{
|
||||
struct pcpu_freelist_node *node = NULL;
|
||||
struct pcpu_freelist_head *head;
|
||||
struct pcpu_freelist_node *node;
|
||||
int cpu;
|
||||
|
||||
for_each_cpu_wrap(cpu, cpu_possible_mask, raw_smp_processor_id()) {
|
||||
head = per_cpu_ptr(s->freelist, cpu);
|
||||
if (!READ_ONCE(head->first))
|
||||
continue;
|
||||
raw_spin_lock(&head->lock);
|
||||
if (raw_res_spin_lock(&head->lock))
|
||||
continue;
|
||||
node = head->first;
|
||||
if (node) {
|
||||
WRITE_ONCE(head->first, node->next);
|
||||
raw_spin_unlock(&head->lock);
|
||||
raw_res_spin_unlock(&head->lock);
|
||||
return node;
|
||||
}
|
||||
raw_spin_unlock(&head->lock);
|
||||
raw_res_spin_unlock(&head->lock);
|
||||
}
|
||||
|
||||
/* per cpu lists are all empty, try extralist */
|
||||
if (!READ_ONCE(s->extralist.first))
|
||||
return NULL;
|
||||
raw_spin_lock(&s->extralist.lock);
|
||||
node = s->extralist.first;
|
||||
if (node)
|
||||
WRITE_ONCE(s->extralist.first, node->next);
|
||||
raw_spin_unlock(&s->extralist.lock);
|
||||
return node;
|
||||
}
|
||||
|
||||
static struct pcpu_freelist_node *
|
||||
___pcpu_freelist_pop_nmi(struct pcpu_freelist *s)
|
||||
{
|
||||
struct pcpu_freelist_head *head;
|
||||
struct pcpu_freelist_node *node;
|
||||
int cpu;
|
||||
|
||||
for_each_cpu_wrap(cpu, cpu_possible_mask, raw_smp_processor_id()) {
|
||||
head = per_cpu_ptr(s->freelist, cpu);
|
||||
if (!READ_ONCE(head->first))
|
||||
continue;
|
||||
if (raw_spin_trylock(&head->lock)) {
|
||||
node = head->first;
|
||||
if (node) {
|
||||
WRITE_ONCE(head->first, node->next);
|
||||
raw_spin_unlock(&head->lock);
|
||||
return node;
|
||||
}
|
||||
raw_spin_unlock(&head->lock);
|
||||
}
|
||||
}
|
||||
|
||||
/* cannot pop from per cpu lists, try extralist */
|
||||
if (!READ_ONCE(s->extralist.first) || !raw_spin_trylock(&s->extralist.lock))
|
||||
return NULL;
|
||||
node = s->extralist.first;
|
||||
if (node)
|
||||
WRITE_ONCE(s->extralist.first, node->next);
|
||||
raw_spin_unlock(&s->extralist.lock);
|
||||
return node;
|
||||
}
|
||||
|
||||
struct pcpu_freelist_node *__pcpu_freelist_pop(struct pcpu_freelist *s)
|
||||
{
|
||||
if (in_nmi())
|
||||
return ___pcpu_freelist_pop_nmi(s);
|
||||
return ___pcpu_freelist_pop(s);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
#define __PERCPU_FREELIST_H__
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/percpu.h>
|
||||
#include <asm/rqspinlock.h>
|
||||
|
||||
struct pcpu_freelist_head {
|
||||
struct pcpu_freelist_node *first;
|
||||
raw_spinlock_t lock;
|
||||
rqspinlock_t lock;
|
||||
};
|
||||
|
||||
struct pcpu_freelist {
|
||||
struct pcpu_freelist_head __percpu *freelist;
|
||||
struct pcpu_freelist_head extralist;
|
||||
};
|
||||
|
||||
struct pcpu_freelist_node {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
/*
|
||||
* Resilient Queued Spin Lock defines
|
||||
*
|
||||
* (C) Copyright 2024-2025 Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Authors: Kumar Kartikeya Dwivedi <memxor@gmail.com>
|
||||
*/
|
||||
#ifndef __LINUX_RQSPINLOCK_H
|
||||
#define __LINUX_RQSPINLOCK_H
|
||||
|
||||
#include "../locking/qspinlock.h"
|
||||
|
||||
/*
|
||||
* try_cmpxchg_tail - Return result of cmpxchg of tail word with a new value
|
||||
* @lock: Pointer to queued spinlock structure
|
||||
* @tail: The tail to compare against
|
||||
* @new_tail: The new queue tail code word
|
||||
* Return: Bool to indicate whether the cmpxchg operation succeeded
|
||||
*
|
||||
* This is used by the head of the wait queue to clean up the queue.
|
||||
* Provides relaxed ordering, since observers only rely on initialized
|
||||
* state of the node which was made visible through the xchg_tail operation,
|
||||
* i.e. through the smp_wmb preceding xchg_tail.
|
||||
*
|
||||
* We avoid using 16-bit cmpxchg, which is not available on all architectures.
|
||||
*/
|
||||
static __always_inline bool try_cmpxchg_tail(struct qspinlock *lock, u32 tail, u32 new_tail)
|
||||
{
|
||||
u32 old, new;
|
||||
|
||||
old = atomic_read(&lock->val);
|
||||
do {
|
||||
/*
|
||||
* Is the tail part we compare to already stale? Fail.
|
||||
*/
|
||||
if ((old & _Q_TAIL_MASK) != tail)
|
||||
return false;
|
||||
/*
|
||||
* Encode latest locked/pending state for new tail.
|
||||
*/
|
||||
new = (old & _Q_LOCKED_PENDING_MASK) | new_tail;
|
||||
} while (!atomic_try_cmpxchg_relaxed(&lock->val, &old, new));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif /* __LINUX_RQSPINLOCK_H */
|
||||
@@ -648,6 +648,7 @@ void btf_record_free(struct btf_record *rec)
|
||||
case BPF_RB_ROOT:
|
||||
case BPF_RB_NODE:
|
||||
case BPF_SPIN_LOCK:
|
||||
case BPF_RES_SPIN_LOCK:
|
||||
case BPF_TIMER:
|
||||
case BPF_REFCOUNT:
|
||||
case BPF_WORKQUEUE:
|
||||
@@ -700,6 +701,7 @@ struct btf_record *btf_record_dup(const struct btf_record *rec)
|
||||
case BPF_RB_ROOT:
|
||||
case BPF_RB_NODE:
|
||||
case BPF_SPIN_LOCK:
|
||||
case BPF_RES_SPIN_LOCK:
|
||||
case BPF_TIMER:
|
||||
case BPF_REFCOUNT:
|
||||
case BPF_WORKQUEUE:
|
||||
@@ -777,6 +779,7 @@ void bpf_obj_free_fields(const struct btf_record *rec, void *obj)
|
||||
|
||||
switch (fields[i].type) {
|
||||
case BPF_SPIN_LOCK:
|
||||
case BPF_RES_SPIN_LOCK:
|
||||
break;
|
||||
case BPF_TIMER:
|
||||
bpf_timer_cancel_and_free(field_ptr);
|
||||
@@ -1212,7 +1215,7 @@ static int map_check_btf(struct bpf_map *map, struct bpf_token *token,
|
||||
return -EINVAL;
|
||||
|
||||
map->record = btf_parse_fields(btf, value_type,
|
||||
BPF_SPIN_LOCK | BPF_TIMER | BPF_KPTR | BPF_LIST_HEAD |
|
||||
BPF_SPIN_LOCK | BPF_RES_SPIN_LOCK | BPF_TIMER | BPF_KPTR | BPF_LIST_HEAD |
|
||||
BPF_RB_ROOT | BPF_REFCOUNT | BPF_WORKQUEUE | BPF_UPTR,
|
||||
map->value_size);
|
||||
if (!IS_ERR_OR_NULL(map->record)) {
|
||||
@@ -1231,6 +1234,7 @@ static int map_check_btf(struct bpf_map *map, struct bpf_token *token,
|
||||
case 0:
|
||||
continue;
|
||||
case BPF_SPIN_LOCK:
|
||||
case BPF_RES_SPIN_LOCK:
|
||||
if (map->map_type != BPF_MAP_TYPE_HASH &&
|
||||
map->map_type != BPF_MAP_TYPE_ARRAY &&
|
||||
map->map_type != BPF_MAP_TYPE_CGROUP_STORAGE &&
|
||||
|
||||
+206
-42
File diff suppressed because it is too large
Load Diff
@@ -49,6 +49,11 @@ LOCK_EVENT(lock_use_node4) /* # of locking ops that use 4th percpu node */
|
||||
LOCK_EVENT(lock_no_node) /* # of locking ops w/o using percpu node */
|
||||
#endif /* CONFIG_QUEUED_SPINLOCKS */
|
||||
|
||||
/*
|
||||
* Locking events for Resilient Queued Spin Lock
|
||||
*/
|
||||
LOCK_EVENT(rqspinlock_lock_timeout) /* # of locking ops that timeout */
|
||||
|
||||
/*
|
||||
* Locking events for rwsem
|
||||
*/
|
||||
|
||||
@@ -362,6 +362,60 @@ static struct lock_torture_ops raw_spin_lock_irq_ops = {
|
||||
.name = "raw_spin_lock_irq"
|
||||
};
|
||||
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
|
||||
#include <asm/rqspinlock.h>
|
||||
static rqspinlock_t rqspinlock;
|
||||
|
||||
static int torture_raw_res_spin_write_lock(int tid __maybe_unused)
|
||||
{
|
||||
raw_res_spin_lock(&rqspinlock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void torture_raw_res_spin_write_unlock(int tid __maybe_unused)
|
||||
{
|
||||
raw_res_spin_unlock(&rqspinlock);
|
||||
}
|
||||
|
||||
static struct lock_torture_ops raw_res_spin_lock_ops = {
|
||||
.writelock = torture_raw_res_spin_write_lock,
|
||||
.write_delay = torture_spin_lock_write_delay,
|
||||
.task_boost = torture_rt_boost,
|
||||
.writeunlock = torture_raw_res_spin_write_unlock,
|
||||
.readlock = NULL,
|
||||
.read_delay = NULL,
|
||||
.readunlock = NULL,
|
||||
.name = "raw_res_spin_lock"
|
||||
};
|
||||
|
||||
static int torture_raw_res_spin_write_lock_irq(int tid __maybe_unused)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
raw_res_spin_lock_irqsave(&rqspinlock, flags);
|
||||
cxt.cur_ops->flags = flags;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void torture_raw_res_spin_write_unlock_irq(int tid __maybe_unused)
|
||||
{
|
||||
raw_res_spin_unlock_irqrestore(&rqspinlock, cxt.cur_ops->flags);
|
||||
}
|
||||
|
||||
static struct lock_torture_ops raw_res_spin_lock_irq_ops = {
|
||||
.writelock = torture_raw_res_spin_write_lock_irq,
|
||||
.write_delay = torture_spin_lock_write_delay,
|
||||
.task_boost = torture_rt_boost,
|
||||
.writeunlock = torture_raw_res_spin_write_unlock_irq,
|
||||
.readlock = NULL,
|
||||
.read_delay = NULL,
|
||||
.readunlock = NULL,
|
||||
.name = "raw_res_spin_lock_irq"
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
static DEFINE_RWLOCK(torture_rwlock);
|
||||
|
||||
static int torture_rwlock_write_lock(int tid __maybe_unused)
|
||||
@@ -1168,6 +1222,9 @@ static int __init lock_torture_init(void)
|
||||
&lock_busted_ops,
|
||||
&spin_lock_ops, &spin_lock_irq_ops,
|
||||
&raw_spin_lock_ops, &raw_spin_lock_irq_ops,
|
||||
#ifdef CONFIG_BPF_SYSCALL
|
||||
&raw_res_spin_lock_ops, &raw_res_spin_lock_irq_ops,
|
||||
#endif
|
||||
&rw_lock_ops, &rw_lock_irq_ops,
|
||||
&mutex_lock_ops,
|
||||
&ww_mutex_lock_ops,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user