DPDK 22.11.11-rc1
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
23extern "C" {
24#endif
25
26#include <rte_lcore.h>
27#include <rte_common.h>
28#include <rte_pause.h>
30
34typedef struct rte_mcslock {
35 struct rte_mcslock *next;
36 int locked; /* 1 if the queue locked, 0 otherwise */
38
50static 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
114static 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
170static 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
197static 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_ */
#define likely(x)
static int rte_mcslock_trylock(rte_mcslock_t **msl, rte_mcslock_t *me)
Definition: rte_mcslock.h:171
static int rte_mcslock_is_locked(rte_mcslock_t *msl)
Definition: rte_mcslock.h:198
struct rte_mcslock rte_mcslock_t
static void rte_mcslock_unlock(rte_mcslock_t **msl, rte_mcslock_t *me)
Definition: rte_mcslock.h:115
static void rte_mcslock_lock(rte_mcslock_t **msl, rte_mcslock_t *me)
Definition: rte_mcslock.h:51
static __rte_always_inline void rte_wait_until_equal_32(volatile uint32_t *addr, uint32_t expected, int memorder)
Definition: rte_pause.h:95