xref: /qemu/util/event.c (revision 96215036f47403438c7c7869b7cd419bd7a11f82)
1 /* SPDX-License-Identifier: GPL-2.0-or-later */
2 
3 #include "qemu/osdep.h"
4 #include "qemu/thread.h"
5 
6 /*
7  * Valid transitions:
8  * - FREE -> SET (qemu_event_set)
9  * - BUSY -> SET (qemu_event_set)
10  * - SET -> FREE (qemu_event_reset)
11  * - FREE -> BUSY (qemu_event_wait)
12  *
13  * With futex, the waking and blocking operations follow
14  * BUSY -> SET and FREE -> BUSY, respectively.
15  *
16  * Without futex, BUSY -> SET and FREE -> BUSY never happen. Instead, the waking
17  * operation follows FREE -> SET and the blocking operation will happen in
18  * qemu_event_wait() if the event is not SET.
19  *
20  * SET->BUSY does not happen (it can be observed from the outside but
21  * it really is SET->FREE->BUSY).
22  *
23  * busy->free provably cannot happen; to enforce it, the set->free transition
24  * is done with an OR, which becomes a no-op if the event has concurrently
25  * transitioned to free or busy.
26  */
27 
28 #define EV_SET         0
29 #define EV_FREE        1
30 #define EV_BUSY       -1
31 
qemu_event_init(QemuEvent * ev,bool init)32 void qemu_event_init(QemuEvent *ev, bool init)
33 {
34 #ifndef HAVE_FUTEX
35     pthread_mutex_init(&ev->lock, NULL);
36     pthread_cond_init(&ev->cond, NULL);
37 #endif
38 
39     ev->value = (init ? EV_SET : EV_FREE);
40     ev->initialized = true;
41 }
42 
qemu_event_destroy(QemuEvent * ev)43 void qemu_event_destroy(QemuEvent *ev)
44 {
45     assert(ev->initialized);
46     ev->initialized = false;
47 #ifndef HAVE_FUTEX
48     pthread_mutex_destroy(&ev->lock);
49     pthread_cond_destroy(&ev->cond);
50 #endif
51 }
52 
qemu_event_set(QemuEvent * ev)53 void qemu_event_set(QemuEvent *ev)
54 {
55     assert(ev->initialized);
56 
57 #ifdef HAVE_FUTEX
58     /*
59      * Pairs with both qemu_event_reset() and qemu_event_wait().
60      *
61      * qemu_event_set has release semantics, but because it *loads*
62      * ev->value we need a full memory barrier here.
63      */
64     smp_mb();
65     if (qatomic_read(&ev->value) != EV_SET) {
66         int old = qatomic_xchg(&ev->value, EV_SET);
67 
68         /* Pairs with memory barrier in kernel futex_wait system call.  */
69         smp_mb__after_rmw();
70         if (old == EV_BUSY) {
71             /* There were waiters, wake them up.  */
72             qemu_futex_wake_all(ev);
73         }
74     }
75 #else
76     pthread_mutex_lock(&ev->lock);
77     /* Pairs with qemu_event_reset()'s load acquire.  */
78     qatomic_store_release(&ev->value, EV_SET);
79     pthread_cond_broadcast(&ev->cond);
80     pthread_mutex_unlock(&ev->lock);
81 #endif
82 }
83 
qemu_event_reset(QemuEvent * ev)84 void qemu_event_reset(QemuEvent *ev)
85 {
86     assert(ev->initialized);
87 
88 #ifdef HAVE_FUTEX
89     /*
90      * If there was a concurrent reset (or even reset+wait),
91      * do nothing.  Otherwise change EV_SET->EV_FREE.
92      */
93     qatomic_or(&ev->value, EV_FREE);
94 
95     /*
96      * Order reset before checking the condition in the caller.
97      * Pairs with the first memory barrier in qemu_event_set().
98      */
99     smp_mb__after_rmw();
100 #else
101     /*
102      * If futexes are not available, there are no EV_FREE->EV_BUSY
103      * transitions because wakeups are done entirely through the
104      * condition variable.  Since qatomic_set() only writes EV_FREE,
105      * the load seems useless but in reality, the acquire synchronizes
106      * with qemu_event_set()'s store release: if qemu_event_reset()
107      * sees EV_SET here, then the caller will certainly see a
108      * successful condition and skip qemu_event_wait():
109      *
110      * done = 1;                 if (done == 0)
111      * qemu_event_set() {          qemu_event_reset() {
112      *   lock();
113      *   ev->value = EV_SET ----->     load ev->value
114      *                                 ev->value = old value | EV_FREE
115      *   cond_broadcast()
116      *   unlock();                 }
117      * }                           if (done == 0)
118      *                               // qemu_event_wait() not called
119      */
120     qatomic_set(&ev->value, qatomic_load_acquire(&ev->value) | EV_FREE);
121 #endif
122 }
123 
qemu_event_wait(QemuEvent * ev)124 void qemu_event_wait(QemuEvent *ev)
125 {
126     assert(ev->initialized);
127 
128 #ifdef HAVE_FUTEX
129     while (true) {
130         /*
131          * qemu_event_wait must synchronize with qemu_event_set even if it does
132          * not go down the slow path, so this load-acquire is needed that
133          * synchronizes with the first memory barrier in qemu_event_set().
134          */
135         unsigned value = qatomic_load_acquire(&ev->value);
136         if (value == EV_SET) {
137             break;
138         }
139 
140         if (value == EV_FREE) {
141             /*
142              * Leave the event reset and tell qemu_event_set that there are
143              * waiters.  No need to retry, because there cannot be a concurrent
144              * busy->free transition.  After the CAS, the event will be either
145              * set or busy.
146              *
147              * This cmpxchg doesn't have particular ordering requirements if it
148              * succeeds (moving the store earlier can only cause
149              * qemu_event_set() to issue _more_ wakeups), the failing case needs
150              * acquire semantics like the load above.
151              */
152             if (qatomic_cmpxchg(&ev->value, EV_FREE, EV_BUSY) == EV_SET) {
153                 break;
154             }
155         }
156 
157         /*
158          * This is the final check for a concurrent set, so it does need
159          * a smp_mb() pairing with the second barrier of qemu_event_set().
160          * The barrier is inside the FUTEX_WAIT system call.
161          */
162         qemu_futex_wait(ev, EV_BUSY);
163     }
164 #else
165     pthread_mutex_lock(&ev->lock);
166     while (qatomic_read(&ev->value) != EV_SET) {
167         pthread_cond_wait(&ev->cond, &ev->lock);
168     }
169     pthread_mutex_unlock(&ev->lock);
170 #endif
171 }
172