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