| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Resilient Queued Spin Lock | |
| * | |
| * (C) Copyright 2013-2015 Hewlett-Packard Development Company, L.P. | |
| * (C) Copyright 2013-2014,2018 Red Hat, Inc. | |
| * (C) Copyright 2015 Intel Corp. | |
| * (C) Copyright 2015 Hewlett-Packard Enterprise Development LP | |
| * (C) Copyright 2024-2025 Meta Platforms, Inc. and affiliates. | |
| * | |
| * Authors: Waiman Long <longman@redhat.com> | |
| * Peter Zijlstra <peterz@infradead.org> | |
| * Kumar Kartikeya Dwivedi <memxor@gmail.com> | |
| */ | |
| #include <linux/smp.h> | |
| #include <linux/bug.h> | |
| #include <linux/bpf.h> | |
| #include <linux/err.h> | |
| #include <linux/cpumask.h> | |
| #include <linux/percpu.h> | |
| #include <linux/hardirq.h> | |
| #include <linux/mutex.h> | |
| #include <linux/prefetch.h> | |
| #include <asm/byteorder.h> | |
| #ifdef CONFIG_QUEUED_SPINLOCKS | |
| #include <asm/qspinlock.h> | |
| #endif | |
| #include <trace/events/lock.h> | |
| #include <asm/rqspinlock.h> | |
| #include <linux/timekeeping.h> | |
| /* | |
| * Include queued spinlock definitions and statistics code | |
| */ | |
| #ifdef CONFIG_QUEUED_SPINLOCKS | |
| #include "../locking/qspinlock.h" | |
| #include "../locking/lock_events.h" | |
| #include "rqspinlock.h" | |
| #include "../locking/mcs_spinlock.h" | |
| #endif | |
| /* | |
| * The basic principle of a queue-based spinlock can best be understood | |
| * by studying a classic queue-based spinlock implementation called the | |
| * MCS lock. A copy of the original MCS lock paper ("Algorithms for Scalable | |
| * Synchronization on Shared-Memory Multiprocessors by Mellor-Crummey and | |
| * Scott") is available at | |
| * | |
| * https://bugzilla.kernel.org/show_bug.cgi?id=206115 | |
| * | |
| * This queued spinlock implementation is based on the MCS lock, however to | |
| * make it fit the 4 bytes we assume spinlock_t to be, and preserve its | |
| * existing API, we must modify it somehow. | |
| * | |
| * In particular; where the traditional MCS lock consists of a tail pointer | |
| * (8 bytes) and needs the next pointer (another 8 bytes) of its own node to | |
| * unlock the next pending (next->locked), we compress both these: {tail, | |
| * next->locked} into a single u32 value. | |
| * | |
| * Since a spinlock disables recursion of its own context and there is a limit | |
| * to the contexts that can nest; namely: task, softirq, hardirq, nmi. As there | |
| * are at most 4 nesting levels, it can be encoded by a 2-bit number. Now | |
| * we can encode the tail by combining the 2-bit nesting level with the cpu | |
| * number. With one byte for the lock value and 3 bytes for the tail, only a | |
| * 32-bit word is now needed. Even though we only need 1 bit for the lock, | |
| * we extend it to a full byte to achieve better performance for architectures | |
| * that support atomic byte write. | |
| * | |
| * We also change the first spinner to spin on the lock bit instead of its | |
| * node; whereby avoiding the need to carry a node from lock to unlock, and | |
| * preserving existing lock API. This also makes the unlock code simpler and | |
| * faster. | |
| * | |
| * N.B. The current implementation only supports architectures that allow | |
| * atomic operations on smaller 8-bit and 16-bit data types. | |
| * | |
| */ | |
| struct rqspinlock_timeout { | |
| u64 timeout_end; | |
| u64 duration; | |
| u64 cur; | |
| u16 spin; | |
| }; | |
| #define RES_TIMEOUT_VAL 2 | |
| DEFINE_PER_CPU_ALIGNED(struct rqspinlock_held, rqspinlock_held_locks); | |
| EXPORT_SYMBOL_GPL(rqspinlock_held_locks); | |
| static bool is_lock_released(rqspinlock_t *lock, u32 mask) | |
| { | |
| if (!(atomic_read_acquire(&lock->val) & (mask))) | |
| return true; | |
| return false; | |
| } | |
| static noinline int check_deadlock_AA(rqspinlock_t *lock) | |
| { | |
| struct rqspinlock_held *rqh = this_cpu_ptr(&rqspinlock_held_locks); | |
| int cnt = min(RES_NR_HELD, rqh->cnt); | |
| /* | |
| * Return an error if we hold the lock we are attempting to acquire. | |
| * We'll iterate over max 32 locks; no need to do is_lock_released. | |
| */ | |
| for (int i = 0; i < cnt - 1; i++) { | |
| if (rqh->locks[i] == lock) | |
| return -EDEADLK; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * This focuses on the most common case of ABBA deadlocks (or ABBA involving | |
| * more locks, which reduce to ABBA). This is not exhaustive, and we rely on | |
| * timeouts as the final line of defense. | |
| */ | |
| static noinline int check_deadlock_ABBA(rqspinlock_t *lock, u32 mask) | |
| { | |
| struct rqspinlock_held *rqh = this_cpu_ptr(&rqspinlock_held_locks); | |
| int rqh_cnt = min(RES_NR_HELD, rqh->cnt); | |
| void *remote_lock; | |
| int cpu; | |
| /* | |
| * Find the CPU holding the lock that we want to acquire. If there is a | |
| * deadlock scenario, we will read a stable set on the remote CPU and | |
| * find the target. This would be a constant time operation instead of | |
| * O(NR_CPUS) if we could determine the owning CPU from a lock value, but | |
| * that requires increasing the size of the lock word. | |
| */ | |
| for_each_possible_cpu(cpu) { | |
| struct rqspinlock_held *rqh_cpu = per_cpu_ptr(&rqspinlock_held_locks, cpu); | |
| int real_cnt = READ_ONCE(rqh_cpu->cnt); | |
| int cnt = min(RES_NR_HELD, real_cnt); | |
| /* | |
| * Let's ensure to break out of this loop if the lock is available for | |
| * us to potentially acquire. | |
| */ | |
| if (is_lock_released(lock, mask)) | |
| return 0; | |
| /* | |
| * Skip ourselves, and CPUs whose count is less than 2, as they need at | |
| * least one held lock and one acquisition attempt (reflected as top | |
| * most entry) to participate in an ABBA deadlock. | |
| * | |
| * If cnt is more than RES_NR_HELD, it means the current lock being | |
| * acquired won't appear in the table, and other locks in the table are | |
| * already held, so we can't determine ABBA. | |
| */ | |
| if (cpu == smp_processor_id() || real_cnt < 2 || real_cnt > RES_NR_HELD) | |
| continue; | |
| /* | |
| * Obtain the entry at the top, this corresponds to the lock the | |
| * remote CPU is attempting to acquire in a deadlock situation, | |
| * and would be one of the locks we hold on the current CPU. | |
| */ | |
| remote_lock = READ_ONCE(rqh_cpu->locks[cnt - 1]); | |
| /* | |
| * If it is NULL, we've raced and cannot determine a deadlock | |
| * conclusively, skip this CPU. | |
| */ | |
| if (!remote_lock) | |
| continue; | |
| /* | |
| * Find if the lock we're attempting to acquire is held by this CPU. | |
| * Don't consider the topmost entry, as that must be the latest lock | |
| * being held or acquired. For a deadlock, the target CPU must also | |
| * attempt to acquire a lock we hold, so for this search only 'cnt - 1' | |
| * entries are important. | |
| */ | |
| for (int i = 0; i < cnt - 1; i++) { | |
| if (READ_ONCE(rqh_cpu->locks[i]) != lock) | |
| continue; | |
| /* | |
| * We found our lock as held on the remote CPU. Is the | |
| * acquisition attempt on the remote CPU for a lock held | |
| * by us? If so, we have a deadlock situation, and need | |
| * to recover. | |
| */ | |
| for (int i = 0; i < rqh_cnt - 1; i++) { | |
| if (rqh->locks[i] == remote_lock) | |
| return -EDEADLK; | |
| } | |
| /* | |
| * Inconclusive; retry again later. | |
| */ | |
| return 0; | |
| } | |
| } | |
| return 0; | |
| } | |
| static noinline int check_timeout(rqspinlock_t *lock, u32 mask, | |
| struct rqspinlock_timeout *ts) | |
| { | |
| u64 prev = ts->cur; | |
| u64 time; | |
| if (!ts->timeout_end) { | |
| if (check_deadlock_AA(lock)) | |
| return -EDEADLK; | |
| ts->cur = ktime_get_mono_fast_ns(); | |
| ts->timeout_end = ts->cur + ts->duration; | |
| return 0; | |
| } | |
| time = ktime_get_mono_fast_ns(); | |
| if (time > ts->timeout_end) | |
| return -ETIMEDOUT; | |
| /* | |
| * A millisecond interval passed from last time? Trigger deadlock | |
| * checks. | |
| */ | |
| if (prev + NSEC_PER_MSEC < time) { | |
| ts->cur = time; | |
| return check_deadlock_ABBA(lock, mask); | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Do not amortize with spins when res_smp_cond_load_acquire is defined, | |
| * as the macro does internal amortization for us. | |
| */ | |
| #ifndef res_smp_cond_load_acquire | |
| #define RES_CHECK_TIMEOUT(ts, ret, mask) \ | |
| ({ \ | |
| if (!(ts).spin++) \ | |
| (ret) = check_timeout((lock), (mask), &(ts)); \ | |
| (ret); \ | |
| }) | |
| #else | |
| #define RES_CHECK_TIMEOUT(ts, ret, mask) \ | |
| ({ (ret) = check_timeout((lock), (mask), &(ts)); }) | |
| #endif | |
| /* | |
| * Initialize the 'spin' member. | |
| * Set spin member to 0 to trigger AA/ABBA checks immediately. | |
| */ | |
| #define RES_INIT_TIMEOUT(ts) ({ (ts).spin = 0; }) | |
| /* | |
| * We only need to reset 'timeout_end', 'spin' will just wrap around as necessary. | |
| * Duration is defined for each spin attempt, so set it here. | |
| */ | |
| #define RES_RESET_TIMEOUT(ts, _duration) ({ (ts).timeout_end = 0; (ts).duration = _duration; }) | |
| /* | |
| * Provide a test-and-set fallback for cases when queued spin lock support is | |
| * absent from the architecture. | |
| */ | |
| int __lockfunc resilient_tas_spin_lock(rqspinlock_t *lock) | |
| { | |
| struct rqspinlock_timeout ts; | |
| int val, ret = 0; | |
| RES_INIT_TIMEOUT(ts); | |
| /* | |
| * We are either called directly from res_spin_lock after grabbing the | |
| * deadlock detection entry when queued spinlocks are disabled, or from | |
| * resilient_queued_spin_lock_slowpath after grabbing the deadlock | |
| * detection entry. No need to obtain it here. | |
| */ | |
| /* | |
| * Since the waiting loop's time is dependent on the amount of | |
| * contention, a short timeout unlike rqspinlock waiting loops | |
| * isn't enough. Choose a second as the timeout value. | |
| */ | |
| RES_RESET_TIMEOUT(ts, NSEC_PER_SEC); | |
| retry: | |
| val = atomic_read(&lock->val); | |
| if (val || !atomic_try_cmpxchg(&lock->val, &val, 1)) { | |
| if (RES_CHECK_TIMEOUT(ts, ret, ~0u)) | |
| goto out; | |
| cpu_relax(); | |
| goto retry; | |
| } | |
| return 0; | |
| out: | |
| release_held_lock_entry(); | |
| return ret; | |
| } | |
| EXPORT_SYMBOL_GPL(resilient_tas_spin_lock); | |
| #ifdef CONFIG_QUEUED_SPINLOCKS | |
| /* | |
| * Per-CPU queue node structures; we can never have more than 4 nested | |
| * contexts: task, softirq, hardirq, nmi. | |
| * | |
| * Exactly fits one 64-byte cacheline on a 64-bit architecture. | |
| */ | |
| static DEFINE_PER_CPU_ALIGNED(struct qnode, rqnodes[_Q_MAX_NODES]); | |
| #ifndef res_smp_cond_load_acquire | |
| #define res_smp_cond_load_acquire(v, c) smp_cond_load_acquire(v, c) | |
| #endif | |
| #define res_atomic_cond_read_acquire(v, c) res_smp_cond_load_acquire(&(v)->counter, (c)) | |
| /** | |
| * resilient_queued_spin_lock_slowpath - acquire the queued spinlock | |
| * @lock: Pointer to queued spinlock structure | |
| * @val: Current value of the queued spinlock 32-bit word | |
| * | |
| * Return: | |
| * * 0 - Lock was acquired successfully. | |
| * * -EDEADLK - Lock acquisition failed because of AA/ABBA deadlock. | |
| * * -ETIMEDOUT - Lock acquisition failed because of timeout. | |
| * | |
| * (queue tail, pending bit, lock value) | |
| * | |
| * fast : slow : unlock | |
| * : : | |
| * uncontended (0,0,0) -:--> (0,0,1) ------------------------------:--> (*,*,0) | |
| * : | ^--------.------. / : | |
| * : v \ \ | : | |
| * pending : (0,1,1) +--> (0,1,0) \ | : | |
| * : | ^--' | | : | |
| * : v | | : | |
| * uncontended : (n,x,y) +--> (n,0,0) --' | : | |
| * queue : | ^--' | : | |
| * : v | : | |
| * contended : (*,x,y) +--> (*,0,0) ---> (*,0,1) -' : | |
| * queue : ^--' : | |
| */ | |
| int __lockfunc resilient_queued_spin_lock_slowpath(rqspinlock_t *lock, u32 val) | |
| { | |
| struct mcs_spinlock *prev, *next, *node; | |
| struct rqspinlock_timeout ts; | |
| int idx, ret = 0; | |
| u32 old, tail; | |
| BUILD_BUG_ON(CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS)); | |
| if (resilient_virt_spin_lock_enabled()) | |
| return resilient_virt_spin_lock(lock); | |
| RES_INIT_TIMEOUT(ts); | |
| /* | |
| * Wait for in-progress pending->locked hand-overs with a bounded | |
| * number of spins so that we guarantee forward progress. | |
| * | |
| * 0,1,0 -> 0,0,1 | |
| */ | |
| if (val == _Q_PENDING_VAL) { | |
| int cnt = _Q_PENDING_LOOPS; | |
| val = atomic_cond_read_relaxed(&lock->val, | |
| (VAL != _Q_PENDING_VAL) || !cnt--); | |
| } | |
| /* | |
| * If we observe any contention; queue. | |
| */ | |
| if (val & ~_Q_LOCKED_MASK) | |
| goto queue; | |
| /* | |
| * trylock || pending | |
| * | |
| * 0,0,* -> 0,1,* -> 0,0,1 pending, trylock | |
| */ | |
| val = queued_fetch_set_pending_acquire(lock); | |
| /* | |
| * If we observe contention, there is a concurrent locker. | |
| * | |
| * Undo and queue; our setting of PENDING might have made the | |
| * n,0,0 -> 0,0,0 transition fail and it will now be waiting | |
| * on @next to become !NULL. | |
| */ | |
| if (unlikely(val & ~_Q_LOCKED_MASK)) { | |
| /* Undo PENDING if we set it. */ | |
| if (!(val & _Q_PENDING_MASK)) | |
| clear_pending(lock); | |
| goto queue; | |
| } | |
| /* Deadlock detection entry already held after failing fast path. */ | |
| /* | |
| * We're pending, wait for the owner to go away. | |
| * | |
| * 0,1,1 -> *,1,0 | |
| * | |
| * this wait loop must be a load-acquire such that we match the | |
| * store-release that clears the locked bit and create lock | |
| * sequentiality; this is because not all | |
| * clear_pending_set_locked() implementations imply full | |
| * barriers. | |
| */ | |
| if (val & _Q_LOCKED_MASK) { | |
| RES_RESET_TIMEOUT(ts, RES_DEF_TIMEOUT); | |
| res_smp_cond_load_acquire(&lock->locked, !VAL || RES_CHECK_TIMEOUT(ts, ret, _Q_LOCKED_MASK)); | |
| } | |
| if (ret) { | |
| /* | |
| * We waited for the locked bit to go back to 0, as the pending | |
| * waiter, but timed out. We need to clear the pending bit since | |
| * we own it. Once a stuck owner has been recovered, the lock | |
| * must be restored to a valid state, hence removing the pending | |
| * bit is necessary. | |
| * | |
| * *,1,* -> *,0,* | |
| */ | |
| clear_pending(lock); | |
| lockevent_inc(rqspinlock_lock_timeout); | |
| goto err_release_entry; | |
| } | |
| /* | |
| * take ownership and clear the pending bit. | |
| * | |
| * 0,1,0 -> 0,0,1 | |
| */ | |
| clear_pending_set_locked(lock); | |
| lockevent_inc(lock_pending); | |
| return 0; | |
| /* | |
| * End of pending bit optimistic spinning and beginning of MCS | |
| * queuing. | |
| */ | |
| queue: | |
| /* | |
| * Do not queue if we're a waiter and someone is attempting this lock on | |
| * the same CPU. In case of NMIs, this prevents long timeouts where we | |
| * interrupt the pending waiter, and the owner, that will eventually | |
| * signal the head of our queue, both of which are logically but not | |
| * physically part of the queue, hence outside the scope of the idx > 0 | |
| * check above for the trylock fallback. | |
| */ | |
| if (check_deadlock_AA(lock)) { | |
| ret = -EDEADLK; | |
| goto err_release_entry; | |
| } | |
| lockevent_inc(lock_slowpath); | |
| /* Deadlock detection entry already held after failing fast path. */ | |
| node = this_cpu_ptr(&rqnodes[0].mcs); | |
| idx = node->count++; | |
| tail = encode_tail(smp_processor_id(), idx); | |
| trace_contention_begin(lock, LCB_F_SPIN); | |
| /* | |
| * 4 nodes are allocated based on the assumption that there will | |
| * not be nested NMIs taking spinlocks. That may not be true in | |
| * some architectures even though the chance of needing more than | |
| * 4 nodes will still be extremely unlikely. When that happens, | |
| * we fall back to attempting a trylock operation without using | |
| * any MCS node. Unlike qspinlock which cannot fail, we have the | |
| * option of failing the slow path, and under contention, such a | |
| * trylock spinning will likely be treated unfairly due to lack of | |
| * queueing, hence do not spin. | |
| */ | |
| if (unlikely(idx >= _Q_MAX_NODES || (in_nmi() && idx > 0))) { | |
| lockevent_inc(lock_no_node); | |
| if (!queued_spin_trylock(lock)) { | |
| ret = -EDEADLK; | |
| goto err_release_node; | |
| } | |
| goto release; | |
| } | |
| node = grab_mcs_node(node, idx); | |
| /* | |
| * Keep counts of non-zero index values: | |
| */ | |
| lockevent_cond_inc(lock_use_node2 + idx - 1, idx); | |
| /* | |
| * Ensure that we increment the head node->count before initialising | |
| * the actual node. If the compiler is kind enough to reorder these | |
| * stores, then an IRQ could overwrite our assignments. | |
| */ | |
| barrier(); | |
| node->locked = 0; | |
| node->next = NULL; | |
| /* | |
| * We touched a (possibly) cold cacheline in the per-cpu queue node; | |
| * attempt the trylock once more in the hope someone let go while we | |
| * weren't watching. | |
| */ | |
| if (queued_spin_trylock(lock)) | |
| goto release; | |
| /* | |
| * Ensure that the initialisation of @node is complete before we | |
| * publish the updated tail via xchg_tail() and potentially link | |
| * @node into the waitqueue via WRITE_ONCE(prev->next, node) below. | |
| */ | |
| smp_wmb(); | |
| /* | |
| * Publish the updated tail. | |
| * We have already touched the queueing cacheline; don't bother with | |
| * pending stuff. | |
| * | |
| * p,*,* -> n,*,* | |
| */ | |
| old = xchg_tail(lock, tail); | |
| next = NULL; | |
| /* | |
| * if there was a previous node; link it and wait until reaching the | |
| * head of the waitqueue. | |
| */ | |
| if (old & _Q_TAIL_MASK) { | |
| int val; | |
| prev = decode_tail(old, rqnodes); | |
| /* Link @node into the waitqueue. */ | |
| WRITE_ONCE(prev->next, node); | |
| val = arch_mcs_spin_lock_contended(&node->locked); | |
| if (val == RES_TIMEOUT_VAL) { | |
| ret = -ETIMEDOUT; | |
| goto waitq_timeout; | |
| } | |
| /* | |
| * While waiting for the MCS lock, the next pointer may have | |
| * been set by another lock waiter. We optimistically load | |
| * the next pointer & prefetch the cacheline for writing | |
| * to reduce latency in the upcoming MCS unlock operation. | |
| */ | |
| next = READ_ONCE(node->next); | |
| if (next) | |
| prefetchw(next); | |
| } | |
| /* | |
| * we're at the head of the waitqueue, wait for the owner & pending to | |
| * go away. | |
| * | |
| * *,x,y -> *,0,0 | |
| * | |
| * this wait loop must use a load-acquire such that we match the | |
| * store-release that clears the locked bit and create lock | |
| * sequentiality; this is because the set_locked() function below | |
| * does not imply a full barrier. | |
| * | |
| * We use RES_DEF_TIMEOUT * 2 as the duration, as RES_DEF_TIMEOUT is | |
| * meant to span maximum allowed time per critical section, and we may | |
| * have both the owner of the lock and the pending bit waiter ahead of | |
| * us. | |
| */ | |
| RES_RESET_TIMEOUT(ts, RES_DEF_TIMEOUT * 2); | |
| val = res_atomic_cond_read_acquire(&lock->val, !(VAL & _Q_LOCKED_PENDING_MASK) || | |
| RES_CHECK_TIMEOUT(ts, ret, _Q_LOCKED_PENDING_MASK)); | |
| /* Disable queue destruction when we detect deadlocks. */ | |
| if (ret == -EDEADLK) { | |
| if (!try_cmpxchg_tail(lock, tail, 0)) { | |
| next = smp_cond_load_relaxed(&node->next, (VAL)); | |
| arch_mcs_spin_unlock_contended(&next->locked); | |
| } | |
| goto err_release_node; | |
| } | |
| waitq_timeout: | |
| if (ret) { | |
| /* | |
| * If the tail is still pointing to us, then we are the final waiter, | |
| * and are responsible for resetting the tail back to 0. Otherwise, if | |
| * the cmpxchg operation fails, we signal the next waiter to take exit | |
| * and try the same. For a waiter with tail node 'n': | |
| * | |
| * n,*,* -> 0,*,* | |
| * | |
| * When performing cmpxchg for the whole word (NR_CPUS > 16k), it is | |
| * possible locked/pending bits keep changing and we see failures even | |
| * when we remain the head of wait queue. However, eventually, | |
| * pending bit owner will unset the pending bit, and new waiters | |
| * will queue behind us. This will leave the lock owner in | |
| * charge, and it will eventually either set locked bit to 0, or | |
| * leave it as 1, allowing us to make progress. | |
| * | |
| * We terminate the whole wait queue for two reasons. Firstly, | |
| * we eschew per-waiter timeouts with one applied at the head of | |
| * the wait queue. This allows everyone to break out faster | |
| * once we've seen the owner / pending waiter not responding for | |
| * the timeout duration from the head. Secondly, it avoids | |
| * complicated synchronization, because when not leaving in FIFO | |
| * order, prev's next pointer needs to be fixed up etc. | |
| */ | |
| if (!try_cmpxchg_tail(lock, tail, 0)) { | |
| next = smp_cond_load_relaxed(&node->next, VAL); | |
| WRITE_ONCE(next->locked, RES_TIMEOUT_VAL); | |
| } | |
| lockevent_inc(rqspinlock_lock_timeout); | |
| goto err_release_node; | |
| } | |
| /* | |
| * claim the lock: | |
| * | |
| * n,0,0 -> 0,0,1 : lock, uncontended | |
| * *,*,0 -> *,*,1 : lock, contended | |
| * | |
| * If the queue head is the only one in the queue (lock value == tail) | |
| * and nobody is pending, clear the tail code and grab the lock. | |
| * Otherwise, we only need to grab the lock. | |
| */ | |
| /* | |
| * Note: at this point: (val & _Q_PENDING_MASK) == 0, because of the | |
| * above wait condition, therefore any concurrent setting of | |
| * PENDING will make the uncontended transition fail. | |
| */ | |
| if ((val & _Q_TAIL_MASK) == tail) { | |
| if (atomic_try_cmpxchg_relaxed(&lock->val, &val, _Q_LOCKED_VAL)) | |
| goto release; /* No contention */ | |
| } | |
| /* | |
| * Either somebody is queued behind us or _Q_PENDING_VAL got set | |
| * which will then detect the remaining tail and queue behind us | |
| * ensuring we'll see a @next. | |
| */ | |
| set_locked(lock); | |
| /* | |
| * contended path; wait for next if not observed yet, release. | |
| */ | |
| if (!next) | |
| next = smp_cond_load_relaxed(&node->next, (VAL)); | |
| arch_mcs_spin_unlock_contended(&next->locked); | |
| release: | |
| trace_contention_end(lock, 0); | |
| /* | |
| * release the node | |
| */ | |
| __this_cpu_dec(rqnodes[0].mcs.count); | |
| return ret; | |
| err_release_node: | |
| trace_contention_end(lock, ret); | |
| __this_cpu_dec(rqnodes[0].mcs.count); | |
| err_release_entry: | |
| release_held_lock_entry(); | |
| return ret; | |
| } | |
| EXPORT_SYMBOL_GPL(resilient_queued_spin_lock_slowpath); | |
| #endif /* CONFIG_QUEUED_SPINLOCKS */ | |
| __bpf_kfunc_start_defs(); | |
| static void bpf_prog_report_rqspinlock_violation(const char *str, void *lock, bool irqsave) | |
| { | |
| struct rqspinlock_held *rqh = this_cpu_ptr(&rqspinlock_held_locks); | |
| struct bpf_stream_stage ss; | |
| struct bpf_prog *prog; | |
| prog = bpf_prog_find_from_stack(); | |
| if (!prog) | |
| return; | |
| bpf_stream_stage(ss, prog, BPF_STDERR, ({ | |
| bpf_stream_printk(ss, "ERROR: %s for bpf_res_spin_lock%s\n", str, irqsave ? "_irqsave" : ""); | |
| bpf_stream_printk(ss, "Attempted lock = 0x%px\n", lock); | |
| bpf_stream_printk(ss, "Total held locks = %d\n", rqh->cnt); | |
| for (int i = 0; i < min(RES_NR_HELD, rqh->cnt); i++) | |
| bpf_stream_printk(ss, "Held lock[%2d] = 0x%px\n", i, rqh->locks[i]); | |
| bpf_stream_dump_stack(ss); | |
| })); | |
| } | |
| #define REPORT_STR(ret) ({ (ret) == -ETIMEDOUT ? "Timeout detected" : "AA or ABBA deadlock detected"; }) | |
| __bpf_kfunc int bpf_res_spin_lock(struct bpf_res_spin_lock *lock) | |
| { | |
| int ret; | |
| BUILD_BUG_ON(sizeof(rqspinlock_t) != sizeof(struct bpf_res_spin_lock)); | |
| preempt_disable(); | |
| ret = res_spin_lock((rqspinlock_t *)lock); | |
| if (unlikely(ret)) { | |
| bpf_prog_report_rqspinlock_violation(REPORT_STR(ret), lock, false); | |
| preempt_enable(); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| __bpf_kfunc void bpf_res_spin_unlock(struct bpf_res_spin_lock *lock) | |
| { | |
| res_spin_unlock((rqspinlock_t *)lock); | |
| preempt_enable(); | |
| } | |
| __bpf_kfunc int bpf_res_spin_lock_irqsave(struct bpf_res_spin_lock *lock, unsigned long *flags__irq_flag) | |
| { | |
| u64 *ptr = (u64 *)flags__irq_flag; | |
| unsigned long flags; | |
| int ret; | |
| preempt_disable(); | |
| local_irq_save(flags); | |
| ret = res_spin_lock((rqspinlock_t *)lock); | |
| if (unlikely(ret)) { | |
| bpf_prog_report_rqspinlock_violation(REPORT_STR(ret), lock, true); | |
| local_irq_restore(flags); | |
| preempt_enable(); | |
| return ret; | |
| } | |
| *ptr = flags; | |
| return 0; | |
| } | |
| __bpf_kfunc void bpf_res_spin_unlock_irqrestore(struct bpf_res_spin_lock *lock, unsigned long *flags__irq_flag) | |
| { | |
| u64 *ptr = (u64 *)flags__irq_flag; | |
| unsigned long flags = *ptr; | |
| res_spin_unlock((rqspinlock_t *)lock); | |
| local_irq_restore(flags); | |
| preempt_enable(); | |
| } | |
| __bpf_kfunc_end_defs(); | |
| BTF_KFUNCS_START(rqspinlock_kfunc_ids) | |
| BTF_ID_FLAGS(func, bpf_res_spin_lock, KF_RET_NULL | KF_SPINLOCK_SAFE) | |
| BTF_ID_FLAGS(func, bpf_res_spin_unlock, KF_SPINLOCK_SAFE) | |
| BTF_ID_FLAGS(func, bpf_res_spin_lock_irqsave, KF_RET_NULL | KF_SPINLOCK_SAFE) | |
| BTF_ID_FLAGS(func, bpf_res_spin_unlock_irqrestore, KF_SPINLOCK_SAFE) | |
| BTF_KFUNCS_END(rqspinlock_kfunc_ids) | |
| static const struct btf_kfunc_id_set rqspinlock_kfunc_set = { | |
| .owner = THIS_MODULE, | |
| .set = &rqspinlock_kfunc_ids, | |
| }; | |
| static __init int rqspinlock_register_kfuncs(void) | |
| { | |
| return register_btf_kfunc_id_set(BPF_PROG_TYPE_UNSPEC, &rqspinlock_kfunc_set); | |
| } | |
| late_initcall(rqspinlock_register_kfuncs); |