xref: /aosp_15_r20/external/libusb/libusb/os/events_posix.c (revision 86b64dcb59b3a0b37502ecd56e119234366a6f7e)
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