DPDK  25.11.0
rte_mcslock.h
Go to the documentation of this file.
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 
22 #include <rte_lcore.h>
23 #include <rte_common.h>
24 #include <rte_pause.h>
25 #include <rte_branch_prediction.h>
26 #include <rte_stdatomic.h>
27 
28 #ifdef __cplusplus
29 extern "C" {
30 #endif
31 
35 typedef struct rte_mcslock {
36  RTE_ATOMIC(struct rte_mcslock *) next;
37  RTE_ATOMIC(int) locked; /* 1 if the queue locked, 0 otherwise */
39 
51 static inline void
53 {
54  rte_mcslock_t *prev;
55 
56  /* Init me node */
57  rte_atomic_store_explicit(&me->locked, 1, rte_memory_order_relaxed);
58  rte_atomic_store_explicit(&me->next, NULL, rte_memory_order_relaxed);
59 
60  /*
61  * A0/R0: Queue might be empty, perform the exchange (RMW) with both acquire and
62  * release semantics:
63  * A0: Acquire — synchronizes with both R0 and R2.
64  * Must synchronize with R0 to ensure that this thread observes predecessor's
65  * initialization of its lock object or risk them overwriting this thread's
66  * update to the next of the same object via store to prev->next.
67  *
68  * Must synchronize with R2 the releasing CAS in unlock(), this will ensure
69  * that all prior critical-section writes become visible to this thread.
70  *
71  * R0: Release — ensures the successor observes our initialization of me->next;
72  * without it, me->next could be overwritten to NULL after the successor
73  * sets its own address, causing deadlock. This release synchronizes with
74  * A0 above.
75  */
76  prev = rte_atomic_exchange_explicit(msl, me, rte_memory_order_acq_rel);
77  if (likely(prev == NULL)) {
78  /* Queue was empty, no further action required,
79  * proceed with lock taken.
80  */
81  return;
82  }
83 
84  /*
85  * R1: With the relaxed memory model of C/C++, it's essential that after
86  * we link ourselves by storing prev->next = me, the owner of prev must
87  * observe our prior initialization of me->locked. Otherwise it could
88  * clear me->locked before we set it to 1, which may deadlock.
89  * Perform a releasing store so the predecessor's acquire loads A2 and A3
90  * observes our initialization, establishing a happens-before from those
91  * writes.
92  */
93  rte_atomic_store_explicit(&prev->next, me, rte_memory_order_release);
94 
95  /*
96  * A1: If the lock has already been acquired, it first atomically
97  * places the node at the end of the queue and then proceeds
98  * to spin on me->locked until the previous lock holder resets
99  * the me->locked in rte_mcslock_unlock().
100  * This load must synchronize with store-release R3 to ensure that
101  * all updates to critical section by previous lock holder is visible
102  * to this thread after acquiring the lock.
103  */
104  rte_wait_until_equal_32((uint32_t *)(uintptr_t)&me->locked, 0, rte_memory_order_acquire);
105 }
106 
115 static inline void
116 rte_mcslock_unlock(RTE_ATOMIC(rte_mcslock_t *) *msl, RTE_ATOMIC(rte_mcslock_t *) me)
117 {
118  /*
119  * A2: Check whether a successor is already queued.
120  * Load me->next with acquire semantics so it can synchronize with the
121  * successor’s release store R1. This guarantees that the successor’s
122  * initialization of its lock object (me) is completed before we observe
123  * it here, preventing a race between this thread’s store-release to
124  * me->next->locked and the successor’s store to me->locked.
125  */
126  if (likely(rte_atomic_load_explicit(&me->next, rte_memory_order_acquire) == NULL)) {
127  /* No, last member in the queue. */
128  rte_mcslock_t *save_me = me;
129 
130  /*
131  * R2: Try to release the lock by swinging *msl from save_me to NULL.
132  * Use release semantics so all critical section writes become
133  * visible to the next lock acquirer.
134  */
135  if (likely(rte_atomic_compare_exchange_strong_explicit(msl, &save_me, NULL,
136  rte_memory_order_release, rte_memory_order_relaxed)))
137  return;
138 
139  /*
140  * A3: Another thread was enqueued concurrently, so the CAS and the lock
141  * release failed. Wait until the successor sets our 'next' pointer.
142  * This load must synchronize with the successor’s release store (R1) to
143  * ensure that the successor’s initialization completes before we observe
144  * it here. This ordering prevents a race between this thread’s later
145  * store-release to me->next->locked and the successor’s store to me->locked.
146  */
147  RTE_ATOMIC(uintptr_t) *next;
148  next = (__rte_atomic uintptr_t *)&me->next;
149  RTE_WAIT_UNTIL_MASKED(next, UINTPTR_MAX, !=, 0, rte_memory_order_acquire);
150  }
151 
152  /*
153  * R3: Pass the lock to the successor.
154  * Use a release store to synchronize with A1 when clearing me->next->locked
155  * so the successor observes our critical section writes after it sees locked
156  * become 0.
157  */
158  rte_atomic_store_explicit(&me->next->locked, 0, rte_memory_order_release);
159 }
160 
171 static inline int
173 {
174  /* Init me node */
175  rte_atomic_store_explicit(&me->next, NULL, rte_memory_order_relaxed);
176 
177  /* Try to lock */
178  rte_mcslock_t *expected = NULL;
179 
180  /*
181  * A4/R4: The lock can be acquired only when the queue is empty.
182  * The compare-and-exchange operation must use acquire and release
183  * semantics for the same reasons described in the rte_mcslock_lock()
184  * function’s empty-queue case (see A0/R0 for details).
185  */
186  return rte_atomic_compare_exchange_strong_explicit(msl, &expected, me,
187  rte_memory_order_acq_rel, rte_memory_order_relaxed);
188 }
189 
198 static inline int
200 {
201  return (rte_atomic_load_explicit(&msl, rte_memory_order_relaxed) != NULL);
202 }
203 
204 #ifdef __cplusplus
205 }
206 #endif
207 
208 #endif /* _RTE_MCSLOCK_H_ */
#define likely(x)
static void rte_mcslock_unlock(1(rte_mcslock_t *) *msl, 1(rte_mcslock_t *) me)
Definition: rte_mcslock.h:116
static void rte_mcslock_lock(1(rte_mcslock_t *) *msl, rte_mcslock_t *me)
Definition: rte_mcslock.h:52
static int rte_mcslock_is_locked(1(rte_mcslock_t *) msl)
Definition: rte_mcslock.h:199
static int rte_mcslock_trylock(1(rte_mcslock_t *) *msl, rte_mcslock_t *me)
Definition: rte_mcslock.h:172
struct rte_mcslock rte_mcslock_t
static __rte_always_inline void rte_wait_until_equal_32(volatile uint32_t *addr, uint32_t expected, rte_memory_order memorder)
Definition: rte_pause.h:95