-
Notifications
You must be signed in to change notification settings - Fork 0
/
libusb.patch
256 lines (244 loc) · 9.38 KB
/
libusb.patch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
diff --git a/libusb/os/emscripten_webusb.cpp b/libusb/os/emscripten_webusb.cpp
index 325a3a1f..129da3c2 100644
--- a/libusb/os/emscripten_webusb.cpp
+++ b/libusb/os/emscripten_webusb.cpp
@@ -20,6 +20,8 @@
*/
#include <emscripten.h>
+#include <emscripten/proxying.h>
+#include <emscripten/threading.h>
#include <emscripten/val.h>
#include "libusbi.h"
@@ -227,9 +229,11 @@ int em_open(libusb_device_handle *handle) {
}
void em_close(libusb_device_handle *handle) {
- // LibUSB API doesn't allow us to handle an error here, so ignore the Promise
- // altogether.
- return get_web_usb_device(handle->dev).call<void>("close");
+ // LibUSB API doesn't allow us to handle an error here, but we still need to
+ // await for the promise to avoid issues with multiple synchronous calls like
+ // closing and immediately re-opening the device
+ auto web_usb_device = get_web_usb_device(handle->dev);
+ promise_result::await(web_usb_device.call<val>("close"));
}
int em_get_config_descriptor_impl(val &&web_usb_config, void *buf, size_t len) {
@@ -387,6 +391,13 @@ extern "C" void em_signal_transfer_completion(usbi_transfer *itransfer,
val::take_ownership(result_handle));
}
+EM_JS(void, em_cancel_transfer_promise, (usbi_transfer *transfer), {
+ if (Module.libusbTransferPromises[transfer]) {
+ Module.libusbTransferPromises[transfer].cancel();
+ delete Module.libusbTransferPromises[transfer];
+ }
+});
+
// clang-format off
EM_JS(void, em_start_transfer_impl, (usbi_transfer *transfer, EM_VAL handle), {
// Right now the handle value should be a `Promise<{value, error}>`.
@@ -394,7 +405,29 @@ EM_JS(void, em_start_transfer_impl, (usbi_transfer *transfer, EM_VAL handle), {
// and signal transfer completion.
// Catch the error to transform promise of `value` into promise of `{value,
// error}`.
- Emval.toValue(handle).then(result => {
+ // wrap with cancellable promise
+ let prom = Emval.toValue(handle);
+ const promWrap = new Promise((resolve, reject) => {
+ prom.then((...args) => {
+ if (prom) {
+ resolve(...args);
+ }
+ }).catch((...args) => {
+ if (prom) {
+ reject(...args);
+ }
+ }).finally(() => {
+ delete Module.libusbTransferPromises[transfer];
+ });
+ });
+ promWrap.cancel = () => {
+ prom = null; // mark as cancelled
+ };
+ // save promise
+ Module.libusbTransferPromises = Module.libusbTransferPromises || {};
+ Module.libusbTransferPromises[transfer] = promWrap;
+ // connect promise
+ promWrap.then(result => {
_em_signal_transfer_completion(transfer, Emval.toHandle(result));
});
});
@@ -520,7 +553,14 @@ void em_clear_transfer_priv(usbi_transfer *itransfer) {
WebUsbTransferPtr(itransfer).take();
}
-int em_cancel_transfer(usbi_transfer *itransfer) { return LIBUSB_SUCCESS; }
+int em_cancel_transfer(usbi_transfer *itransfer) {
+ // WebUSB doesn't support canceling transfers, but we act as it succeeds and
+ // signal transfer completion
+ em_signal_transfer_completion_impl(itransfer, val::null());
+ // cancel original promise to avoid it resolving later on device close
+ em_cancel_transfer_promise(itransfer);
+ return LIBUSB_SUCCESS;
+}
int em_handle_transfer_completion(usbi_transfer *itransfer) {
auto transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
@@ -596,6 +636,65 @@ int em_handle_transfer_completion(usbi_transfer *itransfer) {
return usbi_handle_transfer_completion(itransfer, status);
}
+
+// XXX: rudimentary support for multi-threading, this is very basic,
+// using a new queue causes deadlock because during a main thread wait
+// (i.e. futex_wait_main_browser_thread()) _emscripten_yield() only clears
+// system queue calls
+// em_proxying_queue* proxy_queue = em_proxying_queue_create();
+em_proxying_queue* proxy_queue = emscripten_proxy_get_system_queue();
+
+template <typename TRet, typename... TArgs>
+struct fn_task {
+ std::function<void()> fn;
+ TRet res;
+ fn_task(TRet(*func)(TArgs...), TArgs... args) {
+ fn = [&](){
+ res = (func)(args...);
+ };
+ }
+};
+
+void fn_task_call(void *tsk) {
+ (*((std::function<void()> *) tsk))();
+}
+
+int em_clear_halt_safe(libusb_device_handle *handle, unsigned char endpoint) {
+ if (emscripten_main_runtime_thread_id() != pthread_self()) {
+ fn_task<int, libusb_device_handle *, unsigned char> tsk(em_clear_halt, handle, endpoint);
+ emscripten_proxy_sync(proxy_queue, emscripten_main_runtime_thread_id(), fn_task_call, &tsk.fn);
+ return tsk.res;
+ }
+ return em_clear_halt(handle, endpoint);
+}
+
+int em_submit_transfer_safe(usbi_transfer *itransfer) {
+ if (emscripten_main_runtime_thread_id() != pthread_self()) {
+ fn_task<int, usbi_transfer *> tsk(em_submit_transfer, itransfer);
+ emscripten_proxy_sync(proxy_queue, emscripten_main_runtime_thread_id(), fn_task_call, &tsk.fn);
+ return tsk.res;
+ }
+ return em_submit_transfer(itransfer);
+}
+
+int em_cancel_transfer_safe(usbi_transfer *itransfer) {
+ if (emscripten_main_runtime_thread_id() != pthread_self()) {
+ fn_task<int, usbi_transfer *> tsk(em_cancel_transfer, itransfer);
+ emscripten_proxy_sync(proxy_queue, emscripten_main_runtime_thread_id(), fn_task_call, &tsk.fn);
+ return tsk.res;
+ }
+ return em_cancel_transfer(itransfer);
+}
+
+int em_handle_transfer_completion_safe(usbi_transfer *itransfer) {
+ if (emscripten_main_runtime_thread_id() != pthread_self()) {
+ fn_task<int, usbi_transfer *> tsk(em_handle_transfer_completion, itransfer);
+ emscripten_proxy_sync(proxy_queue, emscripten_main_runtime_thread_id(), fn_task_call, &tsk.fn);
+ return tsk.res;
+ }
+ return em_handle_transfer_completion(itransfer);
+}
+
} // namespace
extern "C" {
@@ -612,13 +711,13 @@ const usbi_os_backend usbi_backend = {
.claim_interface = em_claim_interface,
.release_interface = em_release_interface,
.set_interface_altsetting = em_set_interface_altsetting,
- .clear_halt = em_clear_halt,
+ .clear_halt = em_clear_halt_safe,
.reset_device = em_reset_device,
.destroy_device = em_destroy_device,
- .submit_transfer = em_submit_transfer,
- .cancel_transfer = em_cancel_transfer,
+ .submit_transfer = em_submit_transfer_safe,
+ .cancel_transfer = em_cancel_transfer_safe,
.clear_transfer_priv = em_clear_transfer_priv,
- .handle_transfer_completion = em_handle_transfer_completion,
+ .handle_transfer_completion = em_handle_transfer_completion_safe,
.device_priv_size = sizeof(val),
.transfer_priv_size = sizeof(val),
};
diff --git a/libusb/os/events_posix.c b/libusb/os/events_posix.c
index 2ba01033..7fd39637 100644
--- a/libusb/os/events_posix.c
+++ b/libusb/os/events_posix.c
@@ -37,23 +37,60 @@
*
* Therefore use a custom event system based on browser event emitters. */
#include <emscripten.h>
+#include <emscripten/threading.h>
+
+// XXX: changes to this file are also to support multi-threading
+// overall, usb events with emscripten should be redesigned
+
+// during a pthread_join on the main thread (bad) browser events are not
+// processed, to avoid a deadlock when waiting for a usb transaction promise
+// we queue emscripten_sleep on the system queue, tasks on the system queue
+// are still processed even when busy waiting, so this unwinds the stack using
+// asyncify and allows the event loop to do work
+void em_force_yield_main_thread(void);
+void EMSCRIPTEN_KEEPALIVE em_force_yield_main_thread(void) {
+ if (emscripten_main_runtime_thread_id() != pthread_self()) {
+ emscripten_async_run_in_main_runtime_thread(EM_FUNC_SIG_VI, emscripten_sleep, 0);
+ }
+}
EM_JS(void, em_libusb_notify, (void), {
dispatchEvent(new Event("em-libusb"));
+ // also message all workers (threads)
+ Object.values(PThread.pthreads).forEach(w => {
+ // XXX: (target: "setimmediate"): VERY hacky way to prevent errors
+ // with the default emscripten message listener, that target
+ // is a "no-op" on that listener
+ w.postMessage({
+ target: "setimmediate", // XXX: grrr
+ cmd: "em-libusb"
+ });
+ });
});
EM_ASYNC_JS(int, em_libusb_wait, (int timeout), {
- let onEvent, timeoutId;
+ let onEvent, onEventWorker, timeoutId, intervalId;
try {
+ intervalId = setInterval(_em_force_yield_main_thread, 50);
return await new Promise(resolve => {
onEvent = () => resolve(0);
addEventListener('em-libusb', onEvent);
-
+ // on worker also register message listener
+ if (typeof WorkerGlobalScope !== 'undefined') {
+ onEventWorker = e => {
+ if (e.data.cmd == 'em-libusb') {
+ resolve(0);
+ }
+ };
+ addEventListener('message', onEventWorker);
+ }
timeoutId = setTimeout(resolve, timeout, -1);
});
} finally {
removeEventListener('em-libusb', onEvent);
+ removeEventListener('message', onEventWorker);
+ clearInterval(intervalId);
clearTimeout(timeoutId);
}
});
@@ -268,6 +305,15 @@ int usbi_wait_for_events(struct libusb_context *ctx,
int timeout = until_time - emscripten_get_now();
if (timeout <= 0) break;
int result = em_libusb_wait(timeout);
+ // emscripten_sleep required after EM_ASYNC_JS call to fix issue with
+ // unwind and live runtimes, see:
+ // https://github.com/emscripten-core/emscripten/issues/12814
+ // https://github.com/emscripten-core/emscripten/issues/18442
+ // maybe related to:
+ // https://github.com/emscripten-core/emscripten/issues/13193
+ // https://github.com/emscripten-core/emscripten/pull/13335
+ // XXX: is this fixed?
+ emscripten_sleep(0);
if (result != 0) break;
}
#else