1 /*
2 * libusb event abstraction on POSIX platforms
3 *
4 * Copyright © 2020 Chris Dickens <[email protected]>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but 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 this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include "libusbi.h"
22
23 #include <errno.h>
24 #include <fcntl.h>
25 #ifdef HAVE_EVENTFD
26 #include <sys/eventfd.h>
27 #endif
28 #ifdef HAVE_TIMERFD
29 #include <sys/timerfd.h>
30 #endif
31
32 #ifdef __EMSCRIPTEN__
33 /* On Emscripten `pipe` does not conform to the spec and does not block
34 * until events are available, which makes it unusable for event system
35 * and often results in deadlocks when `pipe` is in a loop like it is
36 * in libusb.
37 *
38 * Therefore use a custom event system based on browser event emitters. */
39 #include <emscripten.h>
40 #include <emscripten/atomic.h>
41 #include <emscripten/threading.h>
42
43 EM_ASYNC_JS(void, em_libusb_wait_async, (const _Atomic int* ptr, int expected_value, int timeout), {
44 await Atomics.waitAsync(HEAP32, ptr >> 2, expected_value, timeout).value;
45 });
46
em_libusb_wait(const _Atomic int * ptr,int expected_value,int timeout)47 static void em_libusb_wait(const _Atomic int *ptr, int expected_value, int timeout)
48 {
49 if (emscripten_is_main_runtime_thread()) {
50 em_libusb_wait_async(ptr, expected_value, timeout);
51 } else {
52 emscripten_atomic_wait_u32((int*)ptr, expected_value, 1000000LL * timeout);
53 }
54 }
55 #endif
56 #include <unistd.h>
57
58 #ifdef HAVE_EVENTFD
59 #define EVENT_READ_FD(e) ((e)->eventfd)
60 #define EVENT_WRITE_FD(e) ((e)->eventfd)
61 #else
62 #define EVENT_READ_FD(e) ((e)->pipefd[0])
63 #define EVENT_WRITE_FD(e) ((e)->pipefd[1])
64 #endif
65
66 #ifdef HAVE_NFDS_T
67 typedef nfds_t usbi_nfds_t;
68 #else
69 typedef unsigned int usbi_nfds_t;
70 #endif
71
usbi_create_event(usbi_event_t * event)72 int usbi_create_event(usbi_event_t *event)
73 {
74 #ifdef HAVE_EVENTFD
75 event->eventfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
76 if (event->eventfd == -1) {
77 usbi_err(NULL, "failed to create eventfd, errno=%d", errno);
78 return LIBUSB_ERROR_OTHER;
79 }
80
81 return 0;
82 #else
83 #if defined(HAVE_PIPE2)
84 int ret = pipe2(event->pipefd, O_CLOEXEC);
85 #else
86 int ret = pipe(event->pipefd);
87 #endif
88
89 if (ret != 0) {
90 usbi_err(NULL, "failed to create pipe, errno=%d", errno);
91 return LIBUSB_ERROR_OTHER;
92 }
93
94 #if !defined(HAVE_PIPE2) && defined(FD_CLOEXEC)
95 ret = fcntl(event->pipefd[0], F_GETFD);
96 if (ret == -1) {
97 usbi_err(NULL, "failed to get pipe fd flags, errno=%d", errno);
98 goto err_close_pipe;
99 }
100 ret = fcntl(event->pipefd[0], F_SETFD, ret | FD_CLOEXEC);
101 if (ret == -1) {
102 usbi_err(NULL, "failed to set pipe fd flags, errno=%d", errno);
103 goto err_close_pipe;
104 }
105
106 ret = fcntl(event->pipefd[1], F_GETFD);
107 if (ret == -1) {
108 usbi_err(NULL, "failed to get pipe fd flags, errno=%d", errno);
109 goto err_close_pipe;
110 }
111 ret = fcntl(event->pipefd[1], F_SETFD, ret | FD_CLOEXEC);
112 if (ret == -1) {
113 usbi_err(NULL, "failed to set pipe fd flags, errno=%d", errno);
114 goto err_close_pipe;
115 }
116 #endif
117
118 ret = fcntl(event->pipefd[1], F_GETFL);
119 if (ret == -1) {
120 usbi_err(NULL, "failed to get pipe fd status flags, errno=%d", errno);
121 goto err_close_pipe;
122 }
123 ret = fcntl(event->pipefd[1], F_SETFL, ret | O_NONBLOCK);
124 if (ret == -1) {
125 usbi_err(NULL, "failed to set pipe fd status flags, errno=%d", errno);
126 goto err_close_pipe;
127 }
128
129 return 0;
130
131 err_close_pipe:
132 close(event->pipefd[1]);
133 close(event->pipefd[0]);
134 return LIBUSB_ERROR_OTHER;
135 #endif
136 }
137
usbi_destroy_event(usbi_event_t * event)138 void usbi_destroy_event(usbi_event_t *event)
139 {
140 #ifdef HAVE_EVENTFD
141 if (close(event->eventfd) == -1)
142 usbi_warn(NULL, "failed to close eventfd, errno=%d", errno);
143 #else
144 if (close(event->pipefd[1]) == -1)
145 usbi_warn(NULL, "failed to close pipe write end, errno=%d", errno);
146 if (close(event->pipefd[0]) == -1)
147 usbi_warn(NULL, "failed to close pipe read end, errno=%d", errno);
148 #endif
149 }
150
usbi_signal_event(usbi_event_t * event)151 void usbi_signal_event(usbi_event_t *event)
152 {
153 uint64_t dummy = 1;
154 ssize_t r;
155
156 r = write(EVENT_WRITE_FD(event), &dummy, sizeof(dummy));
157 if (r != sizeof(dummy))
158 usbi_warn(NULL, "event write failed");
159 #ifdef __EMSCRIPTEN__
160 event->has_event = 1;
161 emscripten_atomic_notify(&event->has_event, EMSCRIPTEN_NOTIFY_ALL_WAITERS);
162 #endif
163 }
164
usbi_clear_event(usbi_event_t * event)165 void usbi_clear_event(usbi_event_t *event)
166 {
167 uint64_t dummy;
168 ssize_t r;
169
170 r = read(EVENT_READ_FD(event), &dummy, sizeof(dummy));
171 if (r != sizeof(dummy))
172 usbi_warn(NULL, "event read failed");
173 #ifdef __EMSCRIPTEN__
174 event->has_event = 0;
175 #endif
176 }
177
178 #ifdef HAVE_TIMERFD
usbi_create_timer(usbi_timer_t * timer)179 int usbi_create_timer(usbi_timer_t *timer)
180 {
181 timer->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
182 if (timer->timerfd == -1) {
183 usbi_warn(NULL, "failed to create timerfd, errno=%d", errno);
184 return LIBUSB_ERROR_OTHER;
185 }
186
187 return 0;
188 }
189
usbi_destroy_timer(usbi_timer_t * timer)190 void usbi_destroy_timer(usbi_timer_t *timer)
191 {
192 if (close(timer->timerfd) == -1)
193 usbi_warn(NULL, "failed to close timerfd, errno=%d", errno);
194 }
195
usbi_arm_timer(usbi_timer_t * timer,const struct timespec * timeout)196 int usbi_arm_timer(usbi_timer_t *timer, const struct timespec *timeout)
197 {
198 const struct itimerspec it = { { 0, 0 }, { timeout->tv_sec, timeout->tv_nsec } };
199
200 if (timerfd_settime(timer->timerfd, TFD_TIMER_ABSTIME, &it, NULL) == -1) {
201 usbi_warn(NULL, "failed to arm timerfd, errno=%d", errno);
202 return LIBUSB_ERROR_OTHER;
203 }
204
205 return 0;
206 }
207
usbi_disarm_timer(usbi_timer_t * timer)208 int usbi_disarm_timer(usbi_timer_t *timer)
209 {
210 const struct itimerspec it = { { 0, 0 }, { 0, 0 } };
211
212 if (timerfd_settime(timer->timerfd, 0, &it, NULL) == -1) {
213 usbi_warn(NULL, "failed to disarm timerfd, errno=%d", errno);
214 return LIBUSB_ERROR_OTHER;
215 }
216
217 return 0;
218 }
219 #endif
220
usbi_alloc_event_data(struct libusb_context * ctx)221 int usbi_alloc_event_data(struct libusb_context *ctx)
222 {
223 struct usbi_event_source *ievent_source;
224 struct pollfd *fds;
225 size_t i = 0;
226
227 if (ctx->event_data) {
228 free(ctx->event_data);
229 ctx->event_data = NULL;
230 }
231
232 ctx->event_data_cnt = 0;
233 for_each_event_source(ctx, ievent_source)
234 ctx->event_data_cnt++;
235
236 fds = calloc(ctx->event_data_cnt, sizeof(*fds));
237 if (!fds)
238 return LIBUSB_ERROR_NO_MEM;
239
240 for_each_event_source(ctx, ievent_source) {
241 fds[i].fd = ievent_source->data.os_handle;
242 fds[i].events = ievent_source->data.poll_events;
243 i++;
244 }
245
246 ctx->event_data = fds;
247 return 0;
248 }
249
usbi_wait_for_events(struct libusb_context * ctx,struct usbi_reported_events * reported_events,int timeout_ms)250 int usbi_wait_for_events(struct libusb_context *ctx,
251 struct usbi_reported_events *reported_events, int timeout_ms)
252 {
253 struct pollfd *fds = ctx->event_data;
254 usbi_nfds_t nfds = (usbi_nfds_t)ctx->event_data_cnt;
255 int internal_fds, num_ready;
256
257 usbi_dbg(ctx, "poll() %u fds with timeout in %dms", (unsigned int)nfds, timeout_ms);
258 #ifdef __EMSCRIPTEN__
259 /* Emscripten's poll doesn't actually block, so we need to use an
260 * out-of-band waiting signal. */
261 em_libusb_wait(&ctx->event.has_event, 0, timeout_ms);
262 /* Emscripten ignores timeout_ms, but set it to 0 for future-proofing
263 * in case they ever implement real poll. */
264 timeout_ms = 0;
265 #endif
266 num_ready = poll(fds, nfds, timeout_ms);
267 usbi_dbg(ctx, "poll() returned %d", num_ready);
268 if (num_ready == 0) {
269 if (usbi_using_timer(ctx))
270 goto done;
271 return LIBUSB_ERROR_TIMEOUT;
272 } else if (num_ready == -1) {
273 if (errno == EINTR)
274 return LIBUSB_ERROR_INTERRUPTED;
275 usbi_err(ctx, "poll() failed, errno=%d", errno);
276 return LIBUSB_ERROR_IO;
277 }
278
279 /* fds[0] is always the internal signalling event */
280 if (fds[0].revents) {
281 reported_events->event_triggered = 1;
282 num_ready--;
283 } else {
284 reported_events->event_triggered = 0;
285 }
286
287 #ifdef HAVE_OS_TIMER
288 /* on timer configurations, fds[1] is the timer */
289 if (usbi_using_timer(ctx) && fds[1].revents) {
290 reported_events->timer_triggered = 1;
291 num_ready--;
292 } else {
293 reported_events->timer_triggered = 0;
294 }
295 #endif
296
297 if (!num_ready)
298 goto done;
299
300 /* the backend will never need to attempt to handle events on the
301 * library's internal file descriptors, so we determine how many are
302 * in use internally for this context and skip these when passing any
303 * remaining pollfds to the backend. */
304 internal_fds = usbi_using_timer(ctx) ? 2 : 1;
305 fds += internal_fds;
306 nfds -= internal_fds;
307
308 usbi_mutex_lock(&ctx->event_data_lock);
309 if (ctx->event_flags & USBI_EVENT_EVENT_SOURCES_MODIFIED) {
310 struct usbi_event_source *ievent_source;
311
312 for_each_removed_event_source(ctx, ievent_source) {
313 usbi_nfds_t n;
314
315 for (n = 0; n < nfds; n++) {
316 if (ievent_source->data.os_handle != fds[n].fd)
317 continue;
318 if (!fds[n].revents)
319 continue;
320 /* pollfd was removed between the creation of the fds array and
321 * here. remove triggered revent as it is no longer relevant. */
322 usbi_dbg(ctx, "fd %d was removed, ignoring raised events", fds[n].fd);
323 fds[n].revents = 0;
324 num_ready--;
325 break;
326 }
327 }
328 }
329 usbi_mutex_unlock(&ctx->event_data_lock);
330
331 if (num_ready) {
332 assert(num_ready > 0);
333 reported_events->event_data = fds;
334 reported_events->event_data_count = (unsigned int)nfds;
335 }
336
337 done:
338 reported_events->num_ready = num_ready;
339 return LIBUSB_SUCCESS;
340 }
341