eal: simplify meson build of common directory
[dpdk.git] / lib / librte_eal / common / include / generic / rte_mcslock.h
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright(c) 2019 Arm Limited
3  */
4
5 #ifndef _RTE_MCSLOCK_H_
6 #define _RTE_MCSLOCK_H_
7
8 /**
9  * @file
10  *
11  * RTE MCS lock
12  *
13  * This file defines the main data structure and APIs for MCS queued lock.
14  *
15  * The MCS lock (proposed by John M. Mellor-Crummey and Michael L. Scott)
16  * provides scalability by spinning on a CPU/thread local variable which
17  * avoids expensive cache bouncings. It provides fairness by maintaining
18  * a list of acquirers and passing the lock to each CPU/thread in the order
19  * they acquired the lock.
20  */
21
22 #include <rte_lcore.h>
23 #include <rte_common.h>
24 #include <rte_pause.h>
25
26 /**
27  * The rte_mcslock_t type.
28  */
29 typedef struct rte_mcslock {
30         struct rte_mcslock *next;
31         int locked; /* 1 if the queue locked, 0 otherwise */
32 } rte_mcslock_t;
33
34 /**
35  * @warning
36  * @b EXPERIMENTAL: This API may change without prior notice
37  *
38  * Take the MCS lock.
39  *
40  * @param msl
41  *   A pointer to the pointer of a MCS lock.
42  *   When the lock is initialized or declared, the msl pointer should be
43  *   set to NULL.
44  * @param me
45  *   A pointer to a new node of MCS lock. Each CPU/thread acquiring the
46  *   lock should use its 'own node'.
47  */
48 __rte_experimental
49 static inline void
50 rte_mcslock_lock(rte_mcslock_t **msl, rte_mcslock_t *me)
51 {
52         rte_mcslock_t *prev;
53
54         /* Init me node */
55         __atomic_store_n(&me->locked, 1, __ATOMIC_RELAXED);
56         __atomic_store_n(&me->next, NULL, __ATOMIC_RELAXED);
57
58         /* If the queue is empty, the exchange operation is enough to acquire
59          * the lock. Hence, the exchange operation requires acquire semantics.
60          * The store to me->next above should complete before the node is
61          * visible to other CPUs/threads. Hence, the exchange operation requires
62          * release semantics as well.
63          */
64         prev = __atomic_exchange_n(msl, me, __ATOMIC_ACQ_REL);
65         if (likely(prev == NULL)) {
66                 /* Queue was empty, no further action required,
67                  * proceed with lock taken.
68                  */
69                 return;
70         }
71         __atomic_store_n(&prev->next, me, __ATOMIC_RELAXED);
72
73         /* The while-load of me->locked should not move above the previous
74          * store to prev->next. Otherwise it will cause a deadlock. Need a
75          * store-load barrier.
76          */
77         __atomic_thread_fence(__ATOMIC_ACQ_REL);
78         /* If the lock has already been acquired, it first atomically
79          * places the node at the end of the queue and then proceeds
80          * to spin on me->locked until the previous lock holder resets
81          * the me->locked using mcslock_unlock().
82          */
83         while (__atomic_load_n(&me->locked, __ATOMIC_ACQUIRE))
84                 rte_pause();
85 }
86
87 /**
88  * @warning
89  * @b EXPERIMENTAL: This API may change without prior notice
90  *
91  * Release the MCS lock.
92  *
93  * @param msl
94  *   A pointer to the pointer of a MCS lock.
95  * @param me
96  *   A pointer to the node of MCS lock passed in rte_mcslock_lock.
97  */
98 __rte_experimental
99 static inline void
100 rte_mcslock_unlock(rte_mcslock_t **msl, rte_mcslock_t *me)
101 {
102         /* Check if there are more nodes in the queue. */
103         if (likely(__atomic_load_n(&me->next, __ATOMIC_RELAXED) == NULL)) {
104                 /* No, last member in the queue. */
105                 rte_mcslock_t *save_me = __atomic_load_n(&me, __ATOMIC_RELAXED);
106
107                 /* Release the lock by setting it to NULL */
108                 if (likely(__atomic_compare_exchange_n(msl, &save_me, NULL, 0,
109                                 __ATOMIC_RELEASE, __ATOMIC_RELAXED)))
110                         return;
111
112                 /* Speculative execution would be allowed to read in the
113                  * while-loop first. This has the potential to cause a
114                  * deadlock. Need a load barrier.
115                  */
116                 __atomic_thread_fence(__ATOMIC_ACQUIRE);
117                 /* More nodes added to the queue by other CPUs.
118                  * Wait until the next pointer is set.
119                  */
120                 while (__atomic_load_n(&me->next, __ATOMIC_RELAXED) == NULL)
121                         rte_pause();
122         }
123
124         /* Pass lock to next waiter. */
125         __atomic_store_n(&me->next->locked, 0, __ATOMIC_RELEASE);
126 }
127
128 /**
129  * @warning
130  * @b EXPERIMENTAL: This API may change without prior notice
131  *
132  * Try to take the lock.
133  *
134  * @param msl
135  *   A pointer to the pointer of a MCS lock.
136  * @param me
137  *   A pointer to a new node of MCS lock.
138  * @return
139  *   1 if the lock is successfully taken; 0 otherwise.
140  */
141 __rte_experimental
142 static inline int
143 rte_mcslock_trylock(rte_mcslock_t **msl, rte_mcslock_t *me)
144 {
145         /* Init me node */
146         __atomic_store_n(&me->next, NULL, __ATOMIC_RELAXED);
147
148         /* Try to lock */
149         rte_mcslock_t *expected = NULL;
150
151         /* The lock can be taken only when the queue is empty. Hence,
152          * the compare-exchange operation requires acquire semantics.
153          * The store to me->next above should complete before the node
154          * is visible to other CPUs/threads. Hence, the compare-exchange
155          * operation requires release semantics as well.
156          */
157         return __atomic_compare_exchange_n(msl, &expected, me, 0,
158                         __ATOMIC_ACQ_REL, __ATOMIC_RELAXED);
159 }
160
161 /**
162  * @warning
163  * @b EXPERIMENTAL: This API may change without prior notice
164  *
165  * Test if the lock is taken.
166  *
167  * @param msl
168  *   A pointer to a MCS lock node.
169  * @return
170  *   1 if the lock is currently taken; 0 otherwise.
171  */
172 __rte_experimental
173 static inline int
174 rte_mcslock_is_locked(rte_mcslock_t *msl)
175 {
176         return (__atomic_load_n(&msl, __ATOMIC_RELAXED) != NULL);
177 }
178
179 #endif /* _RTE_MCSLOCK_H_ */