• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * libusb event abstraction on Microsoft Windows
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 <config.h>
22 
23 #include "libusbi.h"
24 #include "windows_common.h"
25 
usbi_create_event(usbi_event_t * event)26 int usbi_create_event(usbi_event_t *event)
27 {
28 	event->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
29 	if (event->hEvent == NULL) {
30 		usbi_err(NULL, "CreateEvent failed: %s", windows_error_str(0));
31 		return LIBUSB_ERROR_OTHER;
32 	}
33 
34 	return 0;
35 }
36 
usbi_destroy_event(usbi_event_t * event)37 void usbi_destroy_event(usbi_event_t *event)
38 {
39 	if (!CloseHandle(event->hEvent))
40 		usbi_warn(NULL, "CloseHandle failed: %s", windows_error_str(0));
41 }
42 
usbi_signal_event(usbi_event_t * event)43 void usbi_signal_event(usbi_event_t *event)
44 {
45 	if (!SetEvent(event->hEvent))
46 		usbi_warn(NULL, "SetEvent failed: %s", windows_error_str(0));
47 }
48 
usbi_clear_event(usbi_event_t * event)49 void usbi_clear_event(usbi_event_t *event)
50 {
51 	if (!ResetEvent(event->hEvent))
52 		usbi_warn(NULL, "ResetEvent failed: %s", windows_error_str(0));
53 }
54 
55 #ifdef HAVE_OS_TIMER
usbi_create_timer(usbi_timer_t * timer)56 int usbi_create_timer(usbi_timer_t *timer)
57 {
58 	timer->hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
59 	if (timer->hTimer == NULL) {
60 		usbi_warn(NULL, "CreateWaitableTimer failed: %s", windows_error_str(0));
61 		return LIBUSB_ERROR_OTHER;
62 	}
63 
64 	return 0;
65 }
66 
usbi_destroy_timer(usbi_timer_t * timer)67 void usbi_destroy_timer(usbi_timer_t *timer)
68 {
69 	if (!CloseHandle(timer->hTimer))
70 		usbi_warn(NULL, "CloseHandle failed: %s", windows_error_str(0));
71 }
72 
usbi_arm_timer(usbi_timer_t * timer,const struct timespec * timeout)73 int usbi_arm_timer(usbi_timer_t *timer, const struct timespec *timeout)
74 {
75 	struct timespec systime, remaining;
76 	FILETIME filetime;
77 	LARGE_INTEGER dueTime;
78 
79 	/* Transfer timeouts are based on the monotonic clock and the waitable
80 	 * timers on the system clock. This requires a conversion between the
81 	 * two, so we calculate the remaining time relative to the monotonic
82 	 * clock and calculate an absolute system time for the timer expiration.
83 	 * Note that if the timeout has already passed, the remaining time will
84 	 * be negative and thus an absolute system time in the past will be set.
85 	 * This works just as intended because the timer becomes signalled
86 	 * immediately. */
87 	usbi_get_monotonic_time(&systime);
88 
89 	TIMESPEC_SUB(timeout, &systime, &remaining);
90 
91 	GetSystemTimeAsFileTime(&filetime);
92 	dueTime.LowPart = filetime.dwLowDateTime;
93 	dueTime.HighPart = filetime.dwHighDateTime;
94 	dueTime.QuadPart += (remaining.tv_sec * 10000000LL) + (remaining.tv_nsec / 100LL);
95 
96 	if (!SetWaitableTimer(timer->hTimer, &dueTime, 0, NULL, NULL, FALSE)) {
97 		usbi_warn(NULL, "SetWaitableTimer failed: %s", windows_error_str(0));
98 		return LIBUSB_ERROR_OTHER;
99 	}
100 
101 	return 0;
102 }
103 
usbi_disarm_timer(usbi_timer_t * timer)104 int usbi_disarm_timer(usbi_timer_t *timer)
105 {
106 	LARGE_INTEGER dueTime;
107 
108 	/* A manual-reset waitable timer will stay in the signalled state until
109 	 * another call to SetWaitableTimer() is made. It is possible that the
110 	 * timer has already expired by the time we come in to disarm it, so to
111 	 * be entirely sure the timer is disarmed and not in the signalled state,
112 	 * we will set it with an impossibly large expiration and immediately
113 	 * cancel. */
114 	dueTime.QuadPart = LLONG_MAX;
115 	if (!SetWaitableTimer(timer->hTimer, &dueTime, 0, NULL, NULL, FALSE)) {
116 		usbi_warn(NULL, "SetWaitableTimer failed: %s", windows_error_str(0));
117 		return LIBUSB_ERROR_OTHER;
118 	}
119 
120 	if (!CancelWaitableTimer(timer->hTimer)) {
121 		usbi_warn(NULL, "SetWaitableTimer failed: %s", windows_error_str(0));
122 		return LIBUSB_ERROR_OTHER;
123 	}
124 
125 	return 0;
126 }
127 #endif
128 
usbi_alloc_event_data(struct libusb_context * ctx)129 int usbi_alloc_event_data(struct libusb_context *ctx)
130 {
131 	struct usbi_event_source *ievent_source;
132 	HANDLE *handles;
133 	size_t i = 0;
134 
135 	/* Event sources are only added during usbi_io_init(). We should not
136 	 * be running this function again if the event data has already been
137 	 * allocated. */
138 	if (ctx->event_data) {
139 		usbi_warn(ctx, "program assertion failed - event data already allocated");
140 		return LIBUSB_ERROR_OTHER;
141 	}
142 
143 	ctx->event_data_cnt = 0;
144 	for_each_event_source(ctx, ievent_source)
145 		ctx->event_data_cnt++;
146 
147 	/* We only expect up to two HANDLEs to wait on, one for the internal
148 	 * signalling event and the other for the timer. */
149 	if (ctx->event_data_cnt != 1 && ctx->event_data_cnt != 2) {
150 		usbi_err(ctx, "program assertion failed - expected exactly 1 or 2 HANDLEs");
151 		return LIBUSB_ERROR_OTHER;
152 	}
153 
154 	handles = calloc(ctx->event_data_cnt, sizeof(HANDLE));
155 	if (!handles)
156 		return LIBUSB_ERROR_NO_MEM;
157 
158 	for_each_event_source(ctx, ievent_source) {
159 		handles[i] = ievent_source->data.os_handle;
160 		i++;
161 	}
162 
163 	ctx->event_data = handles;
164 	return 0;
165 }
166 
usbi_wait_for_events(struct libusb_context * ctx,struct usbi_reported_events * reported_events,int timeout_ms)167 int usbi_wait_for_events(struct libusb_context *ctx,
168 	struct usbi_reported_events *reported_events, int timeout_ms)
169 {
170 	HANDLE *handles = ctx->event_data;
171 	DWORD num_handles = (DWORD)ctx->event_data_cnt;
172 	DWORD result;
173 
174 	usbi_dbg("WaitForMultipleObjects() for %lu HANDLEs with timeout in %dms", ULONG_CAST(num_handles), timeout_ms);
175 	result = WaitForMultipleObjects(num_handles, handles, FALSE, (DWORD)timeout_ms);
176 	usbi_dbg("WaitForMultipleObjects() returned %lu", ULONG_CAST(result));
177 	if (result == WAIT_TIMEOUT) {
178 		if (usbi_using_timer(ctx))
179 			goto done;
180 		return LIBUSB_ERROR_TIMEOUT;
181 	} else if (result == WAIT_FAILED) {
182 		usbi_err(ctx, "WaitForMultipleObjects() failed: %s", windows_error_str(0));
183 		return LIBUSB_ERROR_IO;
184 	}
185 
186 	result -= WAIT_OBJECT_0;
187 
188 	/* handles[0] is always the internal signalling event */
189 	if (result == 0)
190 		reported_events->event_triggered = 1;
191 	else
192 		reported_events->event_triggered = 0;
193 
194 #ifdef HAVE_OS_TIMER
195 	/* on timer configurations, handles[1] is the timer */
196 	if (usbi_using_timer(ctx)) {
197 		/* The WaitForMultipleObjects() function reports the index of
198 		 * the first object that became signalled. If the internal
199 		 * signalling event was reported, we need to also check and
200 		 * report whether the timer is in the signalled state. */
201 		if (result == 1 || WaitForSingleObject(handles[1], 0) == WAIT_OBJECT_0)
202 			reported_events->timer_triggered = 1;
203 		else
204 			reported_events->timer_triggered = 0;
205 	} else {
206 		reported_events->timer_triggered = 0;
207 	}
208 #endif
209 
210 done:
211 	/* no events are ever reported to the backend */
212 	reported_events->num_ready = 0;
213 	return LIBUSB_SUCCESS;
214 }
215