DPDK 25.11.0-rc3
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>
26#include <rte_stdatomic.h>
27
28#ifdef __cplusplus
29extern "C" {
30#endif
31
35typedef struct rte_mcslock {
36 RTE_ATOMIC(struct rte_mcslock *) next;
37 RTE_ATOMIC(int) locked; /* 1 if the queue locked, 0 otherwise */
39
51static 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
115static inline void
116rte_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
171static 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
198static 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