1 /*
2 * libusb event abstraction on POSIX platforms
3 *
4 * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.com>
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 #include <unistd.h>
32
33 #ifdef HAVE_EVENTFD
34 #define EVENT_READ_FD(e) ((e)->eventfd)
35 #define EVENT_WRITE_FD(e) ((e)->eventfd)
36 #else
37 #define EVENT_READ_FD(e) ((e)->pipefd[0])
38 #define EVENT_WRITE_FD(e) ((e)->pipefd[1])
39 #endif
40
41 #ifdef HAVE_NFDS_T
42 typedef nfds_t usbi_nfds_t;
43 #else
44 typedef unsigned int usbi_nfds_t;
45 #endif
46
usbi_create_event(usbi_event_t * event)47 int usbi_create_event(usbi_event_t *event)
48 {
49 #ifdef HAVE_EVENTFD
50 event->eventfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
51 if (event->eventfd == -1) {
52 usbi_err(NULL, "failed to create eventfd, errno=%d", errno);
53 return LIBUSB_ERROR_OTHER;
54 }
55
56 return 0;
57 #else
58 #if defined(HAVE_PIPE2)
59 int ret = pipe2(event->pipefd, O_CLOEXEC);
60 #else
61 int ret = pipe(event->pipefd);
62 #endif
63
64 if (ret != 0) {
65 usbi_err(NULL, "failed to create pipe, errno=%d", errno);
66 return LIBUSB_ERROR_OTHER;
67 }
68
69 #if !defined(HAVE_PIPE2) && defined(FD_CLOEXEC)
70 ret = fcntl(event->pipefd[0], F_GETFD);
71 if (ret == -1) {
72 usbi_err(NULL, "failed to get pipe fd flags, errno=%d", errno);
73 goto err_close_pipe;
74 }
75 ret = fcntl(event->pipefd[0], F_SETFD, ret | FD_CLOEXEC);
76 if (ret == -1) {
77 usbi_err(NULL, "failed to set pipe fd flags, errno=%d", errno);
78 goto err_close_pipe;
79 }
80
81 ret = fcntl(event->pipefd[1], F_GETFD);
82 if (ret == -1) {
83 usbi_err(NULL, "failed to get pipe fd flags, errno=%d", errno);
84 goto err_close_pipe;
85 }
86 ret = fcntl(event->pipefd[1], F_SETFD, ret | FD_CLOEXEC);
87 if (ret == -1) {
88 usbi_err(NULL, "failed to set pipe fd flags, errno=%d", errno);
89 goto err_close_pipe;
90 }
91 #endif
92
93 ret = fcntl(event->pipefd[1], F_GETFL);
94 if (ret == -1) {
95 usbi_err(NULL, "failed to get pipe fd status flags, errno=%d", errno);
96 goto err_close_pipe;
97 }
98 ret = fcntl(event->pipefd[1], F_SETFL, ret | O_NONBLOCK);
99 if (ret == -1) {
100 usbi_err(NULL, "failed to set pipe fd status flags, errno=%d", errno);
101 goto err_close_pipe;
102 }
103
104 return 0;
105
106 err_close_pipe:
107 close(event->pipefd[1]);
108 close(event->pipefd[0]);
109 return LIBUSB_ERROR_OTHER;
110 #endif
111 }
112
usbi_destroy_event(usbi_event_t * event)113 void usbi_destroy_event(usbi_event_t *event)
114 {
115 #ifdef HAVE_EVENTFD
116 if (close(event->eventfd) == -1)
117 usbi_warn(NULL, "failed to close eventfd, errno=%d", errno);
118 #else
119 if (close(event->pipefd[1]) == -1)
120 usbi_warn(NULL, "failed to close pipe write end, errno=%d", errno);
121 if (close(event->pipefd[0]) == -1)
122 usbi_warn(NULL, "failed to close pipe read end, errno=%d", errno);
123 #endif
124 }
125
usbi_signal_event(usbi_event_t * event)126 void usbi_signal_event(usbi_event_t *event)
127 {
128 uint64_t dummy = 1;
129 ssize_t r;
130
131 r = write(EVENT_WRITE_FD(event), &dummy, sizeof(dummy));
132 if (r != sizeof(dummy))
133 usbi_warn(NULL, "event write failed");
134 }
135
usbi_clear_event(usbi_event_t * event)136 void usbi_clear_event(usbi_event_t *event)
137 {
138 uint64_t dummy;
139 ssize_t r;
140
141 r = read(EVENT_READ_FD(event), &dummy, sizeof(dummy));
142 if (r != sizeof(dummy))
143 usbi_warn(NULL, "event read failed");
144 }
145
146 #ifdef HAVE_TIMERFD
usbi_create_timer(usbi_timer_t * timer)147 int usbi_create_timer(usbi_timer_t *timer)
148 {
149 timer->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
150 if (timer->timerfd == -1) {
151 usbi_warn(NULL, "failed to create timerfd, errno=%d", errno);
152 return LIBUSB_ERROR_OTHER;
153 }
154
155 return 0;
156 }
157
usbi_destroy_timer(usbi_timer_t * timer)158 void usbi_destroy_timer(usbi_timer_t *timer)
159 {
160 if (close(timer->timerfd) == -1)
161 usbi_warn(NULL, "failed to close timerfd, errno=%d", errno);
162 }
163
usbi_arm_timer(usbi_timer_t * timer,const struct timespec * timeout)164 int usbi_arm_timer(usbi_timer_t *timer, const struct timespec *timeout)
165 {
166 const struct itimerspec it = { { 0, 0 }, { timeout->tv_sec, timeout->tv_nsec } };
167
168 if (timerfd_settime(timer->timerfd, TFD_TIMER_ABSTIME, &it, NULL) == -1) {
169 usbi_warn(NULL, "failed to arm timerfd, errno=%d", errno);
170 return LIBUSB_ERROR_OTHER;
171 }
172
173 return 0;
174 }
175
usbi_disarm_timer(usbi_timer_t * timer)176 int usbi_disarm_timer(usbi_timer_t *timer)
177 {
178 const struct itimerspec it = { { 0, 0 }, { 0, 0 } };
179
180 if (timerfd_settime(timer->timerfd, 0, &it, NULL) == -1) {
181 usbi_warn(NULL, "failed to disarm timerfd, errno=%d", errno);
182 return LIBUSB_ERROR_OTHER;
183 }
184
185 return 0;
186 }
187 #endif
188
usbi_alloc_event_data(struct libusb_context * ctx)189 int usbi_alloc_event_data(struct libusb_context *ctx)
190 {
191 struct usbi_event_source *ievent_source;
192 struct pollfd *fds;
193 size_t i = 0;
194
195 if (ctx->event_data) {
196 free(ctx->event_data);
197 ctx->event_data = NULL;
198 }
199
200 ctx->event_data_cnt = 0;
201 for_each_event_source(ctx, ievent_source)
202 ctx->event_data_cnt++;
203
204 fds = calloc(ctx->event_data_cnt, sizeof(*fds));
205 if (!fds)
206 return LIBUSB_ERROR_NO_MEM;
207
208 for_each_event_source(ctx, ievent_source) {
209 fds[i].fd = ievent_source->data.os_handle;
210 fds[i].events = ievent_source->data.poll_events;
211 i++;
212 }
213
214 ctx->event_data = fds;
215 return 0;
216 }
217
usbi_wait_for_events(struct libusb_context * ctx,struct usbi_reported_events * reported_events,int timeout_ms)218 int usbi_wait_for_events(struct libusb_context *ctx,
219 struct usbi_reported_events *reported_events, int timeout_ms)
220 {
221 struct pollfd *fds = ctx->event_data;
222 usbi_nfds_t nfds = (usbi_nfds_t)ctx->event_data_cnt;
223 int internal_fds, num_ready;
224
225 usbi_dbg("poll() %u fds with timeout in %dms", (unsigned int)nfds, timeout_ms);
226 num_ready = poll(fds, nfds, timeout_ms);
227 usbi_dbg("poll() returned %d", num_ready);
228 if (num_ready == 0) {
229 if (usbi_using_timer(ctx))
230 goto done;
231 return LIBUSB_ERROR_TIMEOUT;
232 } else if (num_ready == -1) {
233 if (errno == EINTR)
234 return LIBUSB_ERROR_INTERRUPTED;
235 usbi_err(ctx, "poll() failed, errno=%d", errno);
236 return LIBUSB_ERROR_IO;
237 }
238
239 /* fds[0] is always the internal signalling event */
240 if (fds[0].revents) {
241 reported_events->event_triggered = 1;
242 num_ready--;
243 } else {
244 reported_events->event_triggered = 0;
245 }
246
247 #ifdef HAVE_OS_TIMER
248 /* on timer configurations, fds[1] is the timer */
249 if (usbi_using_timer(ctx) && fds[1].revents) {
250 reported_events->timer_triggered = 1;
251 num_ready--;
252 } else {
253 reported_events->timer_triggered = 0;
254 }
255 #endif
256
257 if (!num_ready)
258 goto done;
259
260 /* the backend will never need to attempt to handle events on the
261 * library's internal file descriptors, so we determine how many are
262 * in use internally for this context and skip these when passing any
263 * remaining pollfds to the backend. */
264 internal_fds = usbi_using_timer(ctx) ? 2 : 1;
265 fds += internal_fds;
266 nfds -= internal_fds;
267
268 usbi_mutex_lock(&ctx->event_data_lock);
269 if (ctx->event_flags & USBI_EVENT_EVENT_SOURCES_MODIFIED) {
270 struct usbi_event_source *ievent_source;
271
272 for_each_removed_event_source(ctx, ievent_source) {
273 usbi_nfds_t n;
274
275 for (n = 0; n < nfds; n++) {
276 if (ievent_source->data.os_handle != fds[n].fd)
277 continue;
278 if (!fds[n].revents)
279 continue;
280 /* pollfd was removed between the creation of the fds array and
281 * here. remove triggered revent as it is no longer relevant. */
282 usbi_dbg("fd %d was removed, ignoring raised events", fds[n].fd);
283 fds[n].revents = 0;
284 num_ready--;
285 break;
286 }
287 }
288 }
289 usbi_mutex_unlock(&ctx->event_data_lock);
290
291 if (num_ready) {
292 assert(num_ready > 0);
293 reported_events->event_data = fds;
294 reported_events->event_data_count = (unsigned int)nfds;
295 }
296
297 done:
298 reported_events->num_ready = num_ready;
299 return LIBUSB_SUCCESS;
300 }
301