Skip to content

Commit df327c8

Browse files
Merge remote-tracking branch 'libusb/master'
2 parents 52eccd1 + ac57f47 commit df327c8

23 files changed

+1039
-1339
lines changed

.github/workflows/android.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Android
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
build:
7+
runs-on: ubuntu-latest
8+
strategy:
9+
fail-fast: false
10+
matrix:
11+
abi: [arm64-v8a, armeabi-v7a, x86_64]
12+
api: [21]
13+
env:
14+
ANDROID_ABI: ${{ matrix.abi }}
15+
ANDROID_API: ${{ matrix.api }}
16+
NDK_OUT_DIR: ${{ github.workspace }}/out/${{ matrix.abi }}
17+
NDK_LIBS_DIR: ${{ github.workspace }}/libs/${{ matrix.abi }}
18+
19+
steps:
20+
- name: Checkout
21+
uses: actions/checkout@v4
22+
23+
- name: Show ANDROID_NDK_ROOT
24+
run: |
25+
echo "ANDROID_NDK_ROOT=${ANDROID_NDK_ROOT}"
26+
test -d "${ANDROID_NDK_ROOT}" || (echo "ANDROID_NDK_ROOT not set or invalid" && exit 1)
27+
28+
- name: Install dependencies
29+
run: |
30+
sudo apt update
31+
sudo apt install -y file
32+
33+
- name: Build with ndk-build
34+
working-directory: android/jni
35+
run: |
36+
mkdir -p "${NDK_OUT_DIR}" "${NDK_LIBS_DIR}"
37+
"${ANDROID_NDK_ROOT}/ndk-build" -j \
38+
APP_ABI="${ANDROID_ABI}" \
39+
APP_PLATFORM="android-${ANDROID_API}" \
40+
NDK_OUT="${NDK_OUT_DIR}" \
41+
NDK_LIBS_OUT="${NDK_LIBS_DIR}"
42+
43+
- name: Show outputs
44+
run: |
45+
set -euxo pipefail
46+
find "${NDK_LIBS_DIR}" -maxdepth 2 -type f -print0 | xargs -0 ls -lh || true
47+
file "${NDK_LIBS_DIR}/"* || true
48+
49+
- name: Upload artifacts
50+
uses: actions/upload-artifact@v4
51+
with:
52+
name: libusb-android-${{ matrix.abi }}-api${{ matrix.api }}
53+
path: |
54+
libs/${{ matrix.abi }}/
55+
if-no-files-found: error

.github/workflows/windows.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,19 @@ jobs:
3131

3232
- name: Setup MSBuild
3333
uses: microsoft/setup-msbuild@v1.3
34-
34+
3535
- name: Build solution
3636
run: |
3737
msbuild msvc/libusb.sln /m -property:Configuration=${{ matrix.configuration }} -property:Platform=${{ matrix.architecture }}
38+
39+
- name: Upload artifacts
40+
uses: actions/upload-artifact@v4
41+
with:
42+
name: build-${{ matrix.os }}-${{ matrix.configuration }}-${{ matrix.architecture }}
43+
path: |
44+
build/**/*.exe
45+
build/**/dll/libusb-1.0.dll
46+
build/**/dll/libusb-1.0.lib
47+
build/**/dll/libusb-1.0.exp
48+
build/**/dll/libusb-1.0.pdb
49+
build/**/lib/libusb-1.0.lib

libusb/os/haiku_usb_raw.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ const struct usbi_os_backend usbi_backend = {
186186
/*.exit =*/ haiku_exit,
187187
/*.set_option =*/ NULL,
188188
/*.get_device_list =*/ NULL,
189+
/*.get_device_string =*/ NULL,
189190
/*.hotplug_poll =*/ NULL,
190191
/*.wrap_sys_device =*/ NULL,
191192
/*.open =*/ haiku_open,
@@ -214,6 +215,9 @@ const struct usbi_os_backend usbi_backend = {
214215
/*.kernel_driver_active =*/ NULL,
215216
/*.detach_kernel_driver =*/ NULL,
216217
/*.attach_kernel_driver =*/ NULL,
218+
/*.endpoint_supports_raw_io =*/ NULL,
219+
/*.endpoint_set_raw_io =*/ NULL,
220+
/*.get_max_raw_io_transfer_size =*/ NULL,
217221

218222
/*.destroy_device =*/ NULL,
219223

libusb/os/windows_common.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -862,8 +862,17 @@ static int windows_handle_transfer_completion(struct usbi_transfer *itransfer)
862862
struct windows_transfer_priv *transfer_priv = usbi_get_transfer_priv(itransfer);
863863
enum libusb_transfer_status status, istatus;
864864
DWORD result, bytes_transferred;
865+
HANDLE transfer_handle;
865866

866-
if (GetOverlappedResult(transfer_priv->handle, &transfer_priv->overlapped, &bytes_transferred, FALSE))
867+
/*
868+
* The submit path runs with itransfer->lock held. Grab the handle under
869+
* the same lock so we do not race with submission/completion bookkeeping.
870+
*/
871+
usbi_mutex_lock(&itransfer->lock);
872+
transfer_handle = transfer_priv->handle;
873+
usbi_mutex_unlock(&itransfer->lock);
874+
875+
if (GetOverlappedResult(transfer_handle, &transfer_priv->overlapped, &bytes_transferred, FALSE))
867876
result = NO_ERROR;
868877
else
869878
result = GetLastError();
@@ -905,7 +914,9 @@ static int windows_handle_transfer_completion(struct usbi_transfer *itransfer)
905914
break;
906915
}
907916

917+
usbi_mutex_lock(&itransfer->lock);
908918
transfer_priv->handle = NULL;
919+
usbi_mutex_unlock(&itransfer->lock);
909920

910921
// Backend-specific cleanup
911922
backend->clear_transfer_priv(itransfer);

libusb/os/windows_common.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ struct usbdk_device_priv {
242242

243243
struct winusb_device_priv {
244244
bool initialized;
245+
#if defined(LIBUSB_WINDOWS_HOTPLUG)
246+
bool seen_during_scan; // set true for each device encountered during windows_get_device_list
247+
bool seen_before_scan; // set true for each device encountered before windows_get_device_list
248+
#endif
245249
bool root_hub;
246250
uint8_t active_config;
247251
uint8_t depth; // distance to HCD
@@ -275,7 +279,7 @@ struct usbdk_device_handle_priv {
275279
// Not currently used
276280
char dummy;
277281
};
278-
282+
279283
enum WINUSB_ZLP {
280284
WINUSB_ZLP_UNSET = 0,
281285
WINUSB_ZLP_OFF = 1,

libusb/os/windows_hotplug.c

Lines changed: 78 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,25 @@
2525

2626
#include <stdio.h>
2727
#include <dbt.h>
28-
#include <usbiodef.h>
29-
30-
/* The Windows Hotplug system is a three steps process.
31-
* 1. We create a monitor on GUID_DEVINTERFACE_USB_DEVICE via a hidden window.
32-
* 2. Upon notification of an event, we run the current windows backend to get
33-
* the list of all devices.
34-
* 3. We use PDEV_BROADCAST_DEVICEINTERFACE->dbcc_name for finding device object
35-
* in this list and generate LEFT or ARRIVED events libusb client via hotplug callbacks
28+
29+
/* The Windows Hotplug system is a two steps process.
30+
* 1. We create a hidden window and listen for DBT_DEVNODES_CHANGED, which Windows
31+
* broadcasts to all top-level windows whenever the device tree changes (no
32+
* registration required). Multiple rapid events (e.g. a hub with many children)
33+
* are coalesced via a short debounce timer so we only scan once the burst settles.
34+
* A maximum delay ceiling guarantees a scan fires within a bounded time even
35+
* during sustained bursts, preventing unbounded latency.
36+
* 2. Upon timer expiry, we snapshot the current device list, run a full re-enumeration
37+
* via the Windows backend, then diff the result: newly found devices that have been
38+
* successfully initialized generate DEVICE_ARRIVED events; devices that were not
39+
* encountered by the re-enumeration (physically removed) generate DEVICE_LEFT events.
3640
*/
3741

42+
#define HOTPLUG_DEBOUNCE_TIMER_ID 1
43+
#define HOTPLUG_DEBOUNCE_MS 10
44+
#define HOTPLUG_DEBOUNCE_MAX_DELAY_MS 100
45+
46+
static ULONGLONG first_debounce_tick;
3847
static HWND windows_event_hwnd;
3948
static HANDLE windows_event_thread_handle;
4049
static DWORD WINAPI windows_event_thread_main(LPVOID lpParam);
@@ -110,52 +119,65 @@ void windows_initial_scan_devices(struct libusb_context *ctx)
110119
usbi_mutex_static_unlock(&active_contexts_lock);
111120
}
112121

113-
static void windows_refresh_device_list(struct libusb_context *ctx, const bool device_arrived, const char* device_name)
122+
static void windows_refresh_device_list(struct libusb_context *ctx)
114123
{
124+
struct libusb_device *dev, *next_dev;
125+
126+
// Step 1: clear seen_during_scan so the scan can mark which devices are still
127+
// physically present, and set seen_before_scan so we can distinguish newly
128+
// created devices (which start with seen_before_scan=false via calloc).
129+
for_each_device_safe(ctx, dev, next_dev)
130+
{
131+
struct winusb_device_priv *priv = (struct winusb_device_priv *)usbi_get_device_priv(dev);
132+
priv->seen_during_scan = false;
133+
priv->seen_before_scan = true;
134+
}
135+
136+
// Step 2: re-enumerate — winusb_get_device_list attaches newly-arrived devices
137+
// and sets seen_during_scan=true for every device it physically encounters.
138+
// seen_before_scan is untouched and will be left in default state (false) for newly created devices, allowing us to identify them in the next step.
115139
const int ret = windows_get_device_list(ctx);
116140
if (ret != LIBUSB_SUCCESS)
117141
{
118142
usbi_err(ctx, "hotplug failed to retrieve current list with error: %s", libusb_error_name(ret));
119143
return;
120144
}
121145

122-
struct libusb_device *dev, *next_dev;
123-
146+
// Step 3: diff old vs new.
124147
for_each_device_safe(ctx, dev, next_dev)
125148
{
126-
struct winusb_device_priv* priv = usbi_get_device_priv(dev);
127-
128-
if(priv->path == NULL || _stricmp(priv->path, device_name) != 0)
129-
{
130-
continue;
131-
}
149+
struct winusb_device_priv *priv = (struct winusb_device_priv *)usbi_get_device_priv(dev);
132150

133-
if(device_arrived)
134-
{
135-
usbi_hotplug_notification(ctx, dev, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED);
136-
}
137-
else
151+
if (!priv->seen_during_scan)
138152
{
153+
// Not encountered by the scan: device was physically removed.
139154
if (priv->initialized)
140155
{
141-
usbi_disconnect_device(dev);
156+
usbi_disconnect_device(dev); // fires DEVICE_LEFT
142157
}
143158
else
144159
{
145160
usbi_detach_device(dev);
146161
}
147162
}
163+
else if (!priv->seen_before_scan)
164+
{
165+
if (priv->initialized)
166+
{
167+
usbi_hotplug_notification(ctx, dev, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED);
168+
}
169+
}
148170
}
149171
}
150172

151-
static void windows_refresh_device_list_for_all_ctx(const bool device_arrived, const char* device_name)
173+
static void windows_refresh_device_list_for_all_ctx(void)
152174
{
153175
usbi_mutex_static_lock(&active_contexts_lock);
154176

155177
struct libusb_context *ctx;
156178
for_each_context(ctx)
157179
{
158-
windows_refresh_device_list(ctx, device_arrived, device_name);
180+
windows_refresh_device_list(ctx);
159181
}
160182

161183
usbi_mutex_static_unlock(&active_contexts_lock);
@@ -232,107 +254,53 @@ static DWORD WINAPI windows_event_thread_main(LPVOID lpParam)
232254
return 0;
233255
}
234256

235-
static bool register_device_interface_to_window_handle(
236-
IN GUID interface_class_guid,
237-
IN HWND hwnd,
238-
OUT HDEVNOTIFY* device_notify_handle)
239-
{
240-
DEV_BROADCAST_DEVICEINTERFACE notificationFilter = { 0 };
241-
notificationFilter.dbcc_size = sizeof(notificationFilter);
242-
notificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
243-
notificationFilter.dbcc_classguid = interface_class_guid;
244-
245-
*device_notify_handle = RegisterDeviceNotification(
246-
hwnd,
247-
&notificationFilter,
248-
DEVICE_NOTIFY_WINDOW_HANDLE
249-
);
250-
251-
if (*device_notify_handle == NULL)
252-
{
253-
log_error("register_device_interface_to_window_handle");
254-
return false;
255-
}
256-
257-
return true;
258-
}
259-
260257
static LRESULT CALLBACK windows_proc_callback(
261258
HWND hwnd,
262259
UINT message,
263260
WPARAM wParam,
264261
LPARAM lParam)
265262
{
266-
UNUSED(lParam);
267-
268-
static HDEVNOTIFY device_notify_handle;
269-
270263
switch (message)
271264
{
272-
case WM_CREATE:
273-
if (!register_device_interface_to_window_handle(
274-
GUID_DEVINTERFACE_USB_DEVICE,
275-
hwnd,
276-
&device_notify_handle))
277-
{
278-
return -1;
279-
}
280-
return 0;
281-
282265
case WM_DEVICECHANGE:
283-
switch (wParam)
266+
if (wParam == DBT_DEVNODES_CHANGED)
284267
{
285-
case DBT_DEVICEARRIVAL:
286-
case DBT_DEVICEREMOVECOMPLETE:
287-
if (((PDEV_BROADCAST_HDR)lParam)->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
268+
if (first_debounce_tick == 0)
288269
{
289-
#ifdef UNICODE
290-
char* device_name = NULL;
291-
const WCHAR* w_dbcc_name = ((PDEV_BROADCAST_DEVICEINTERFACE)lParam)->dbcc_name;
292-
293-
const int len = WideCharToMultiByte(CP_UTF8, 0, w_dbcc_name, -1, NULL, 0, NULL, NULL);
294-
if (len == 0)
295-
{
296-
log_error("Conversion length calculation failed for ((PDEV_BROADCAST_DEVICEINTERFACE)lParam)->dbcc_name conversion from wchar to char");
297-
}
298-
else
299-
{
300-
device_name = (char *)malloc(len);
301-
if (device_name == NULL)
302-
{
303-
log_error("Memory allocation failed for ((PDEV_BROADCAST_DEVICEINTERFACE)lParam)->dbcc_name conversion from wchar to char");
304-
}
305-
else
306-
{
307-
const int result = WideCharToMultiByte(CP_UTF8, 0, w_dbcc_name, -1, device_name, len, NULL, NULL);
308-
if (result == 0)
309-
{
310-
log_error("Conversion failed for ((PDEV_BROADCAST_DEVICEINTERFACE)lParam)->dbcc_name conversion from wchar to char");
311-
free(device_name);
312-
device_name = NULL;
313-
}
314-
}
315-
}
316-
#else
317-
const char* device_name = ((PDEV_BROADCAST_DEVICEINTERFACE)lParam)->dbcc_name;
318-
#endif
319-
320-
windows_refresh_device_list_for_all_ctx(wParam == DBT_DEVICEARRIVAL ? true : false, device_name);
321-
322-
#ifdef UNICODE
323-
free(device_name);
324-
#endif
325-
return TRUE;
270+
// First event in a new burst — record the time and start the debounce timer.
271+
first_debounce_tick = GetTickCount64();
272+
SetTimer(hwnd, HOTPLUG_DEBOUNCE_TIMER_ID, HOTPLUG_DEBOUNCE_MS, NULL);
326273
}
327-
break;
274+
else if (GetTickCount64() - first_debounce_tick >= HOTPLUG_DEBOUNCE_MAX_DELAY_MS)
275+
{
276+
// Maximum delay reached — force an immediate scan so devices
277+
// are not invisible for the entire duration of a sustained burst.
278+
KillTimer(hwnd, HOTPLUG_DEBOUNCE_TIMER_ID);
279+
first_debounce_tick = 0;
280+
windows_refresh_device_list_for_all_ctx();
281+
}
282+
else
283+
{
284+
// Still within the max-delay window — reset the debounce timer.
285+
SetTimer(hwnd, HOTPLUG_DEBOUNCE_TIMER_ID, HOTPLUG_DEBOUNCE_MS, NULL);
286+
}
287+
return TRUE;
328288
}
329-
return BROADCAST_QUERY_DENY;
289+
return DefWindowProc(hwnd, message, wParam, lParam);
330290

331-
case WM_CLOSE:
332-
if (!UnregisterDeviceNotification(device_notify_handle))
291+
case WM_TIMER:
292+
if (wParam == HOTPLUG_DEBOUNCE_TIMER_ID)
333293
{
334-
log_error("UnregisterDeviceNotification");
294+
KillTimer(hwnd, HOTPLUG_DEBOUNCE_TIMER_ID);
295+
first_debounce_tick = 0;
296+
windows_refresh_device_list_for_all_ctx();
297+
return 0;
335298
}
299+
return DefWindowProc(hwnd, message, wParam, lParam);
300+
301+
case WM_CLOSE:
302+
KillTimer(hwnd, HOTPLUG_DEBOUNCE_TIMER_ID);
303+
first_debounce_tick = 0;
336304
if (!DestroyWindow(hwnd))
337305
{
338306
log_error("DestroyWindow");

0 commit comments

Comments
 (0)