summaryrefslogtreecommitdiff
path: root/include/asm-generic/rqspinlock.h
diff options
context:
space:
mode:
Diffstat (limited to 'include/asm-generic/rqspinlock.h')
-rw-r--r--include/asm-generic/rqspinlock.h60
1 files changed, 32 insertions, 28 deletions
diff --git a/include/asm-generic/rqspinlock.h b/include/asm-generic/rqspinlock.h
index 6d4244d643df..0f2dcbbfee2f 100644
--- a/include/asm-generic/rqspinlock.h
+++ b/include/asm-generic/rqspinlock.h
@@ -129,8 +129,8 @@ dec:
* <error> for lock B
* release_held_lock_entry
*
- * try_cmpxchg_acquire for lock A
* grab_held_lock_entry
+ * try_cmpxchg_acquire for lock A
*
* Lack of any ordering means reordering may occur such that dec, inc
* are done before entry is overwritten. This permits a remote lock
@@ -139,13 +139,8 @@ dec:
* 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.
+ * The case of unlock is treated differently due to NMI reentrancy, see
+ * comments in res_spin_unlock.
*
* 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
@@ -175,10 +170,22 @@ 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);
+ /*
+ * Grab the deadlock detection entry before doing the cmpxchg, so that
+ * reentrancy due to NMIs between the succeeding cmpxchg and creation of
+ * held lock entry can correctly detect an acquisition attempt in the
+ * interrupted context.
+ *
+ * cmpxchg lock A
+ * <NMI>
+ * res_spin_lock(A) --> missed AA, leads to timeout
+ * </NMI>
+ * grab_held_lock_entry(A)
+ */
+ grab_held_lock_entry(lock);
+
+ if (likely(atomic_try_cmpxchg_acquire(&lock->val, &val, _Q_LOCKED_VAL)))
return 0;
- }
return resilient_queued_spin_lock_slowpath(lock, val);
}
@@ -192,28 +199,25 @@ 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.
+ * Release barrier, ensures correct ordering. Perform release store
+ * instead of queued_spin_unlock, since we use this function for the TAS
+ * 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.
+ * Perform the smp_store_release before clearing the lock entry so that
+ * NMIs landing in the unlock path can correctly detect AA issues. The
+ * opposite order shown below may lead to missed AA checks:
*
- * 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.
+ * WRITE_ONCE(rqh->locks[rqh->cnt - 1], NULL)
+ * <NMI>
+ * res_spin_lock(A) --> missed AA, leads to timeout
+ * </NMI>
+ * smp_store_release(A->locked, 0)
*/
smp_store_release(&lock->locked, 0);
+ if (likely(rqh->cnt <= RES_NR_HELD))
+ WRITE_ONCE(rqh->locks[rqh->cnt - 1], NULL);
this_cpu_dec(rqspinlock_held_locks.cnt);
}