1/***
2  This file is part of PulseAudio.
3
4  Copyright 2006 Lennart Poettering
5
6  PulseAudio is free software; you can redistribute it and/or modify
7  it under the terms of the GNU Lesser General Public License as
8  published by the Free Software Foundation; either version 2.1 of the
9  License, or (at your option) any later version.
10
11  PulseAudio is distributed in the hope that it will be useful, but
12  WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  Lesser General Public License for more details.
15
16  You should have received a copy of the GNU Lesser General Public
17  License along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
18***/
19
20#ifdef HAVE_CONFIG_H
21#include <config.h>
22#endif
23
24#ifdef HAVE_SYS_SYSCALL_H
25#include <sys/syscall.h>
26#endif
27
28#include <unistd.h>
29#include <errno.h>
30
31#include <pulsecore/atomic.h>
32#include <pulsecore/log.h>
33#include <pulsecore/macro.h>
34#include <pulsecore/core-util.h>
35#include <pulsecore/core-error.h>
36#include <pulse/xmalloc.h>
37
38#ifndef HAVE_PIPE
39#include <pulsecore/pipe.h>
40#endif
41
42#ifdef HAVE_SYS_EVENTFD_H
43#include <sys/eventfd.h>
44#endif
45
46#include "fdsem.h"
47
48struct pa_fdsem {
49    int fds[2];
50#ifdef HAVE_SYS_EVENTFD_H
51    int efd;
52#endif
53    int write_type;
54    pa_fdsem_data *data;
55};
56
57pa_fdsem *pa_fdsem_new(void) {
58    pa_fdsem *f;
59
60    f = pa_xmalloc0(PA_ALIGN(sizeof(pa_fdsem)) + PA_ALIGN(sizeof(pa_fdsem_data)));
61
62#ifdef HAVE_SYS_EVENTFD_H
63    if ((f->efd = eventfd(0, EFD_CLOEXEC)) >= 0)
64        f->fds[0] = f->fds[1] = -1;
65    else
66#endif
67    {
68        if (pa_pipe_cloexec(f->fds) < 0) {
69            pa_xfree(f);
70            return NULL;
71        }
72    }
73
74    f->data = (pa_fdsem_data*) ((uint8_t*) f + PA_ALIGN(sizeof(pa_fdsem)));
75
76    pa_atomic_store(&f->data->waiting, 0);
77    pa_atomic_store(&f->data->signalled, 0);
78    pa_atomic_store(&f->data->in_pipe, 0);
79
80    return f;
81}
82
83pa_fdsem *pa_fdsem_open_shm(pa_fdsem_data *data, int event_fd) {
84    pa_fdsem *f = NULL;
85
86    pa_assert(data);
87    pa_assert(event_fd >= 0);
88
89#ifdef HAVE_SYS_EVENTFD_H
90    f = pa_xnew0(pa_fdsem, 1);
91
92    f->efd = event_fd;
93    pa_make_fd_cloexec(f->efd);
94    f->fds[0] = f->fds[1] = -1;
95    f->data = data;
96#endif
97
98    return f;
99}
100
101pa_fdsem *pa_fdsem_new_shm(pa_fdsem_data *data) {
102    pa_fdsem *f = NULL;
103
104    pa_assert(data);
105
106#ifdef HAVE_SYS_EVENTFD_H
107
108    f = pa_xnew0(pa_fdsem, 1);
109
110    if ((f->efd = eventfd(0, EFD_CLOEXEC)) < 0) {
111        pa_xfree(f);
112        return NULL;
113    }
114
115    f->fds[0] = f->fds[1] = -1;
116    f->data = data;
117
118    pa_atomic_store(&f->data->waiting, 0);
119    pa_atomic_store(&f->data->signalled, 0);
120    pa_atomic_store(&f->data->in_pipe, 0);
121
122#endif
123
124    return f;
125}
126
127void pa_fdsem_free(pa_fdsem *f) {
128    pa_assert(f);
129
130#ifdef HAVE_SYS_EVENTFD_H
131    if (f->efd >= 0)
132        pa_close(f->efd);
133#endif
134    pa_close_pipe(f->fds);
135
136    pa_xfree(f);
137}
138
139static void flush(pa_fdsem *f) {
140    ssize_t r;
141    pa_assert(f);
142
143    if (pa_atomic_load(&f->data->in_pipe) <= 0)
144        return;
145
146    do {
147        char x[10];
148
149#ifdef HAVE_SYS_EVENTFD_H
150        if (f->efd >= 0) {
151            uint64_t u;
152
153            if ((r = pa_read(f->efd, &u, sizeof(u), NULL)) != sizeof(u)) {
154                pa_log_error("Invalid read from eventfd: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
155                pa_assert_not_reached();
156            }
157            r = (ssize_t) u;
158        } else
159#endif
160
161        if ((r = pa_read(f->fds[0], &x, sizeof(x), NULL)) <= 0) {
162            pa_log_error("Invalid read from pipe: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
163            pa_assert_not_reached();
164        }
165
166    } while (pa_atomic_sub(&f->data->in_pipe, (int) r) > (int) r);
167}
168
169void pa_fdsem_post(pa_fdsem *f) {
170    pa_assert(f);
171
172    if (pa_atomic_cmpxchg(&f->data->signalled, 0, 1)) {
173
174        if (pa_atomic_load(&f->data->waiting)) {
175            ssize_t r;
176            char x = 'x';
177
178            pa_atomic_inc(&f->data->in_pipe);
179
180            for (;;) {
181
182#ifdef HAVE_SYS_EVENTFD_H
183                if (f->efd >= 0) {
184                    uint64_t u = 1;
185
186                    if ((r = pa_write(f->efd, &u, sizeof(u), &f->write_type)) != sizeof(u)) {
187                        pa_log_error("Invalid write to eventfd: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
188                        pa_assert_not_reached();
189                    }
190                } else
191#endif
192
193                if ((r = pa_write(f->fds[1], &x, 1, &f->write_type)) != 1) {
194                    pa_log_error("Invalid write to pipe: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
195                    pa_assert_not_reached();
196                }
197
198                break;
199            }
200        }
201    }
202}
203
204void pa_fdsem_wait(pa_fdsem *f) {
205    pa_assert(f);
206
207    flush(f);
208
209    if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0))
210        return;
211
212    pa_atomic_inc(&f->data->waiting);
213
214    while (!pa_atomic_cmpxchg(&f->data->signalled, 1, 0)) {
215        char x[10];
216        ssize_t r;
217
218#ifdef HAVE_SYS_EVENTFD_H
219        if (f->efd >= 0) {
220            uint64_t u;
221
222            if ((r = pa_read(f->efd, &u, sizeof(u), NULL)) != sizeof(u)) {
223                pa_log_error("Invalid read from eventfd: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
224                pa_assert_not_reached();
225            }
226
227            r = (ssize_t) u;
228        } else
229#endif
230
231        if ((r = pa_read(f->fds[0], &x, sizeof(x), NULL)) <= 0) {
232            pa_log_error("Invalid read from pipe: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
233            pa_assert_not_reached();
234        }
235
236        pa_atomic_sub(&f->data->in_pipe, (int) r);
237    }
238
239    pa_assert_se(pa_atomic_dec(&f->data->waiting) >= 1);
240}
241
242int pa_fdsem_try(pa_fdsem *f) {
243    pa_assert(f);
244
245    flush(f);
246
247    if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0))
248        return 1;
249
250    return 0;
251}
252
253int pa_fdsem_get(pa_fdsem *f) {
254    pa_assert(f);
255
256#ifdef HAVE_SYS_EVENTFD_H
257    if (f->efd >= 0)
258        return f->efd;
259#endif
260
261    return f->fds[0];
262}
263
264int pa_fdsem_before_poll(pa_fdsem *f) {
265    pa_assert(f);
266
267    flush(f);
268
269    if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0))
270        return -1;
271
272    pa_atomic_inc(&f->data->waiting);
273
274    if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0)) {
275        pa_assert_se(pa_atomic_dec(&f->data->waiting) >= 1);
276        return -1;
277    }
278    return 0;
279}
280
281int pa_fdsem_after_poll(pa_fdsem *f) {
282    pa_assert(f);
283
284    pa_assert_se(pa_atomic_dec(&f->data->waiting) >= 1);
285
286    flush(f);
287
288    if (pa_atomic_cmpxchg(&f->data->signalled, 1, 0))
289        return 1;
290
291    return 0;
292}
293