xref: /openbmc/linux/kernel/locking/mcs_spinlock.h (revision 920c720aa5aa3900a7f1689228fdfc2580a91e7e)
1c9122da1SPeter Zijlstra /*
2c9122da1SPeter Zijlstra  * MCS lock defines
3c9122da1SPeter Zijlstra  *
4c9122da1SPeter Zijlstra  * This file contains the main data structure and API definitions of MCS lock.
5c9122da1SPeter Zijlstra  *
6c9122da1SPeter Zijlstra  * The MCS lock (proposed by Mellor-Crummey and Scott) is a simple spin-lock
7c9122da1SPeter Zijlstra  * with the desirable properties of being fair, and with each cpu trying
8c9122da1SPeter Zijlstra  * to acquire the lock spinning on a local variable.
9c9122da1SPeter Zijlstra  * It avoids expensive cache bouncings that common test-and-set spin-lock
10c9122da1SPeter Zijlstra  * implementations incur.
11c9122da1SPeter Zijlstra  */
12c9122da1SPeter Zijlstra #ifndef __LINUX_MCS_SPINLOCK_H
13c9122da1SPeter Zijlstra #define __LINUX_MCS_SPINLOCK_H
14c9122da1SPeter Zijlstra 
15c9122da1SPeter Zijlstra #include <asm/mcs_spinlock.h>
16c9122da1SPeter Zijlstra 
17c9122da1SPeter Zijlstra struct mcs_spinlock {
18c9122da1SPeter Zijlstra 	struct mcs_spinlock *next;
19c9122da1SPeter Zijlstra 	int locked; /* 1 if lock acquired */
20a33fda35SWaiman Long 	int count;  /* nesting count, see qspinlock.c */
21c9122da1SPeter Zijlstra };
22c9122da1SPeter Zijlstra 
23c9122da1SPeter Zijlstra #ifndef arch_mcs_spin_lock_contended
24c9122da1SPeter Zijlstra /*
25c9122da1SPeter Zijlstra  * Using smp_load_acquire() provides a memory barrier that ensures
26c9122da1SPeter Zijlstra  * subsequent operations happen after the lock is acquired.
27c9122da1SPeter Zijlstra  */
28c9122da1SPeter Zijlstra #define arch_mcs_spin_lock_contended(l)					\
29c9122da1SPeter Zijlstra do {									\
30c9122da1SPeter Zijlstra 	while (!(smp_load_acquire(l)))					\
313a6bfbc9SDavidlohr Bueso 		cpu_relax_lowlatency();					\
32c9122da1SPeter Zijlstra } while (0)
33c9122da1SPeter Zijlstra #endif
34c9122da1SPeter Zijlstra 
35c9122da1SPeter Zijlstra #ifndef arch_mcs_spin_unlock_contended
36c9122da1SPeter Zijlstra /*
37c9122da1SPeter Zijlstra  * smp_store_release() provides a memory barrier to ensure all
38c9122da1SPeter Zijlstra  * operations in the critical section has been completed before
39c9122da1SPeter Zijlstra  * unlocking.
40c9122da1SPeter Zijlstra  */
41c9122da1SPeter Zijlstra #define arch_mcs_spin_unlock_contended(l)				\
42c9122da1SPeter Zijlstra 	smp_store_release((l), 1)
43c9122da1SPeter Zijlstra #endif
44c9122da1SPeter Zijlstra 
45c9122da1SPeter Zijlstra /*
46c9122da1SPeter Zijlstra  * Note: the smp_load_acquire/smp_store_release pair is not
47c9122da1SPeter Zijlstra  * sufficient to form a full memory barrier across
48c9122da1SPeter Zijlstra  * cpus for many architectures (except x86) for mcs_unlock and mcs_lock.
49c9122da1SPeter Zijlstra  * For applications that need a full barrier across multiple cpus
50c9122da1SPeter Zijlstra  * with mcs_unlock and mcs_lock pair, smp_mb__after_unlock_lock() should be
51c9122da1SPeter Zijlstra  * used after mcs_lock.
52c9122da1SPeter Zijlstra  */
53c9122da1SPeter Zijlstra 
54c9122da1SPeter Zijlstra /*
55c9122da1SPeter Zijlstra  * In order to acquire the lock, the caller should declare a local node and
56c9122da1SPeter Zijlstra  * pass a reference of the node to this function in addition to the lock.
57c9122da1SPeter Zijlstra  * If the lock has already been acquired, then this will proceed to spin
58c9122da1SPeter Zijlstra  * on this node->locked until the previous lock holder sets the node->locked
59c9122da1SPeter Zijlstra  * in mcs_spin_unlock().
60c9122da1SPeter Zijlstra  */
61c9122da1SPeter Zijlstra static inline
62c9122da1SPeter Zijlstra void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node)
63c9122da1SPeter Zijlstra {
64c9122da1SPeter Zijlstra 	struct mcs_spinlock *prev;
65c9122da1SPeter Zijlstra 
66c9122da1SPeter Zijlstra 	/* Init node */
67c9122da1SPeter Zijlstra 	node->locked = 0;
68c9122da1SPeter Zijlstra 	node->next   = NULL;
69c9122da1SPeter Zijlstra 
70*920c720aSPeter Zijlstra 	/*
71*920c720aSPeter Zijlstra 	 * We rely on the full barrier with global transitivity implied by the
72*920c720aSPeter Zijlstra 	 * below xchg() to order the initialization stores above against any
73*920c720aSPeter Zijlstra 	 * observation of @node. And to provide the ACQUIRE ordering associated
74*920c720aSPeter Zijlstra 	 * with a LOCK primitive.
75*920c720aSPeter Zijlstra 	 */
76*920c720aSPeter Zijlstra 	prev = xchg(lock, node);
77c9122da1SPeter Zijlstra 	if (likely(prev == NULL)) {
78c9122da1SPeter Zijlstra 		/*
79c9122da1SPeter Zijlstra 		 * Lock acquired, don't need to set node->locked to 1. Threads
80c9122da1SPeter Zijlstra 		 * only spin on its own node->locked value for lock acquisition.
81c9122da1SPeter Zijlstra 		 * However, since this thread can immediately acquire the lock
82c9122da1SPeter Zijlstra 		 * and does not proceed to spin on its own node->locked, this
83c9122da1SPeter Zijlstra 		 * value won't be used. If a debug mode is needed to
84c9122da1SPeter Zijlstra 		 * audit lock status, then set node->locked value here.
85c9122da1SPeter Zijlstra 		 */
86c9122da1SPeter Zijlstra 		return;
87c9122da1SPeter Zijlstra 	}
884d3199e4SDavidlohr Bueso 	WRITE_ONCE(prev->next, node);
89c9122da1SPeter Zijlstra 
90c9122da1SPeter Zijlstra 	/* Wait until the lock holder passes the lock down. */
91c9122da1SPeter Zijlstra 	arch_mcs_spin_lock_contended(&node->locked);
92c9122da1SPeter Zijlstra }
93c9122da1SPeter Zijlstra 
94c9122da1SPeter Zijlstra /*
95c9122da1SPeter Zijlstra  * Releases the lock. The caller should pass in the corresponding node that
96c9122da1SPeter Zijlstra  * was used to acquire the lock.
97c9122da1SPeter Zijlstra  */
98c9122da1SPeter Zijlstra static inline
99c9122da1SPeter Zijlstra void mcs_spin_unlock(struct mcs_spinlock **lock, struct mcs_spinlock *node)
100c9122da1SPeter Zijlstra {
1014d3199e4SDavidlohr Bueso 	struct mcs_spinlock *next = READ_ONCE(node->next);
102c9122da1SPeter Zijlstra 
103c9122da1SPeter Zijlstra 	if (likely(!next)) {
104c9122da1SPeter Zijlstra 		/*
105c9122da1SPeter Zijlstra 		 * Release the lock by setting it to NULL
106c9122da1SPeter Zijlstra 		 */
1073552a07aSDavidlohr Bueso 		if (likely(cmpxchg_release(lock, node, NULL) == node))
108c9122da1SPeter Zijlstra 			return;
109c9122da1SPeter Zijlstra 		/* Wait until the next pointer is set */
1104d3199e4SDavidlohr Bueso 		while (!(next = READ_ONCE(node->next)))
1113a6bfbc9SDavidlohr Bueso 			cpu_relax_lowlatency();
112c9122da1SPeter Zijlstra 	}
113c9122da1SPeter Zijlstra 
114c9122da1SPeter Zijlstra 	/* Pass lock to next waiter. */
115c9122da1SPeter Zijlstra 	arch_mcs_spin_unlock_contended(&next->locked);
116c9122da1SPeter Zijlstra }
117c9122da1SPeter Zijlstra 
118c9122da1SPeter Zijlstra #endif /* __LINUX_MCS_SPINLOCK_H */
119