diff --git a/libusb/Makefile.am b/libusb/Makefile.am index 640edd870..adba4d3a8 100644 --- a/libusb/Makefile.am +++ b/libusb/Makefile.am @@ -28,7 +28,8 @@ OS_SUNOS_SRC = os/sunos_usb.h os/sunos_usb.c OS_WINDOWS_SRC = libusb-1.0.def libusb-1.0.rc \ os/windows_common.h os/windows_common.c \ os/windows_usbdk.h os/windows_usbdk.c \ - os/windows_winusb.h os/windows_winusb.c + os/windows_winusb.h os/windows_winusb.c \ + os/windows_hotplug.h os/windows_hotplug.c if OS_DARWIN OS_SRC = $(OS_DARWIN_SRC) diff --git a/libusb/core.c b/libusb/core.c index ffe33b775..f712bfbfc 100644 --- a/libusb/core.c +++ b/libusb/core.c @@ -728,20 +728,24 @@ struct libusb_device *usbi_alloc_device(struct libusb_context *ctx, return dev; } -void usbi_connect_device(struct libusb_device *dev) +void usbi_attach_device(struct libusb_device *dev) { struct libusb_context *ctx = DEVICE_CTX(dev); usbi_atomic_store(&dev->attached, 1); - usbi_mutex_lock(&dev->ctx->usb_devs_lock); - list_add(&dev->list, &dev->ctx->usb_devs); - usbi_mutex_unlock(&dev->ctx->usb_devs_lock); + usbi_mutex_lock(&ctx->usb_devs_lock); + list_add(&dev->list, &ctx->usb_devs); + usbi_mutex_unlock(&ctx->usb_devs_lock); +} - usbi_hotplug_notification(ctx, dev, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED); +void usbi_connect_device(struct libusb_device *dev) +{ + usbi_attach_device(dev); + usbi_hotplug_notification(DEVICE_CTX(dev), dev, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED); } -void usbi_disconnect_device(struct libusb_device *dev) +void usbi_detach_device(struct libusb_device *dev) { struct libusb_context *ctx = DEVICE_CTX(dev); @@ -750,8 +754,12 @@ void usbi_disconnect_device(struct libusb_device *dev) usbi_mutex_lock(&ctx->usb_devs_lock); list_del(&dev->list); usbi_mutex_unlock(&ctx->usb_devs_lock); +} - usbi_hotplug_notification(ctx, dev, LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT); +void usbi_disconnect_device(struct libusb_device *dev) +{ + usbi_detach_device(dev); + usbi_hotplug_notification(DEVICE_CTX(dev), dev, LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT); } /* Perform some final sanity checks on a newly discovered device. If this diff --git a/libusb/libusbi.h b/libusb/libusbi.h index 3b0c6105c..d50cbe0b4 100644 --- a/libusb/libusbi.h +++ b/libusb/libusbi.h @@ -809,6 +809,9 @@ int usbi_handle_transfer_completion(struct usbi_transfer *itransfer, int usbi_handle_transfer_cancellation(struct usbi_transfer *itransfer); void usbi_signal_transfer_completion(struct usbi_transfer *itransfer); +void usbi_attach_device(struct libusb_device *dev); +void usbi_detach_device(struct libusb_device *dev); + void usbi_connect_device(struct libusb_device *dev); void usbi_disconnect_device(struct libusb_device *dev); diff --git a/libusb/os/windows_common.c b/libusb/os/windows_common.c index 5373db9b1..990400d11 100644 --- a/libusb/os/windows_common.c +++ b/libusb/os/windows_common.c @@ -28,6 +28,7 @@ #include "libusbi.h" #include "windows_common.h" +#include "windows_hotplug.h" #define EPOCH_TIME UINT64_C(116444736000000000) // 1970.01.01 00:00:000 in MS Filetime @@ -565,6 +566,20 @@ static int windows_init(struct libusb_context *ctx) r = LIBUSB_SUCCESS; + if (init_count == 1) { + r = windows_start_event_monitor(); + } + if (r != LIBUSB_SUCCESS) { + usbi_err(ctx, "error starting hotplug event monitor"); + + if (init_count == 1) { + windows_stop_event_monitor(); + } + goto init_exit; + } + + windows_initial_scan_devices(ctx); + init_exit: // Holds semaphore here if ((init_count == 1) && (r != LIBUSB_SUCCESS)) { // First init failed? if (usbdk_available) { @@ -596,6 +611,7 @@ static void windows_exit(struct libusb_context *ctx) // Only works if exits and inits are balanced exactly if (--init_count == 0) { // Last exit + windows_stop_event_monitor(); if (usbdk_available) { usbdk_backend.exit(ctx); usbdk_available = false; @@ -624,12 +640,6 @@ static int windows_set_option(struct libusb_context *ctx, enum libusb_option opt return LIBUSB_ERROR_NOT_SUPPORTED; } -static int windows_get_device_list(struct libusb_context *ctx, struct discovered_devs **discdevs) -{ - struct windows_context_priv *priv = usbi_get_context_priv(ctx); - return priv->backend->get_device_list(ctx, discdevs); -} - static int windows_open(struct libusb_device_handle *dev_handle) { struct windows_context_priv *priv = usbi_get_context_priv(HANDLE_CTX(dev_handle)); @@ -888,7 +898,7 @@ const struct usbi_os_backend usbi_backend = { windows_init, windows_exit, windows_set_option, - windows_get_device_list, + NULL, /* get_device_list */ NULL, /* hotplug_poll */ NULL, /* wrap_sys_device */ windows_open, diff --git a/libusb/os/windows_common.h b/libusb/os/windows_common.h index 0c0ff4b8f..c552ab802 100644 --- a/libusb/os/windows_common.h +++ b/libusb/os/windows_common.h @@ -232,12 +232,29 @@ typedef struct USB_DK_TRANSFER_REQUEST { USB_DK_TRANSFER_RESULT Result; } USB_DK_TRANSFER_REQUEST, *PUSB_DK_TRANSFER_REQUEST; +/* track devices that are removed, kept unchanged, or added when updating device list + * using system enumeration, in response to low-level hotplug related events */ + enum enumeration_status { + /* device is no longer present in system enumeration. + * A hotplug "device left" event shall be triggered and the device removed from the list */ + NO_LONGER_ENUMERATED, + + /* device is still in system enumeration as it was in previous enumeration. + * There is no need to take any action */ + STILL_ENUMERATED, + + /* device is newly added to the list, meaning it just appeared in system enumeration. + * A hotplug "device arrived" event shall be triggered */ + NEWLY_ENUMERATED +}; + struct usbdk_device_priv { USB_DK_DEVICE_ID ID; PUSB_CONFIGURATION_DESCRIPTOR *config_descriptors; HANDLE redirector_handle; HANDLE system_handle; uint8_t active_configuration; + enum enumeration_status discovery_status; /* updated while getting current device list */ }; struct winusb_device_priv { @@ -269,6 +286,7 @@ struct winusb_device_priv { struct hid_device_priv *hid; PUSB_CONFIGURATION_DESCRIPTOR *config_descriptor; // list of pointers to the cached config descriptors GUID class_guid; // checked for change during re-enumeration + enum enumeration_status discovery_status; }; struct usbdk_device_handle_priv { @@ -318,8 +336,7 @@ struct winusb_transfer_priv { struct windows_backend { int (*init)(struct libusb_context *ctx); void (*exit)(struct libusb_context *ctx); - int (*get_device_list)(struct libusb_context *ctx, - struct discovered_devs **discdevs); + int (*get_device_list)(struct libusb_context *ctx); int (*open)(struct libusb_device_handle *dev_handle); void (*close)(struct libusb_device_handle *dev_handle); int (*get_active_config_descriptor)(struct libusb_device *device, diff --git a/libusb/os/windows_hotplug.c b/libusb/os/windows_hotplug.c new file mode 100644 index 000000000..86d16cc4b --- /dev/null +++ b/libusb/os/windows_hotplug.c @@ -0,0 +1,296 @@ +#include "libusbi.h" +#include "threads_windows.h" + +#include "windows_common.h" +#include "windows_hotplug.h" + +#include +#include +#include + +static HWND windows_event_hWnd; +static HANDLE windows_event_thread_handle; +static DWORD WINAPI windows_event_thread_main(LPVOID lpParam); +static LRESULT CALLBACK windows_proc_callback(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + +#define log_error(operation) do { \ + usbi_err(NULL, "%s failed with error: %s", operation, windows_error_str(0)); \ +} while (0) + +int windows_start_event_monitor(void) +{ + windows_event_thread_handle = CreateThread( + NULL, + 0, + windows_event_thread_main, + NULL, + 0, + NULL + ); + + if(windows_event_thread_handle == NULL) + { + log_error("CreateThread"); + return LIBUSB_ERROR_OTHER; + } + + return LIBUSB_SUCCESS; +} + +int windows_stop_event_monitor(void) +{ + if(windows_event_hWnd == NULL) + { + return LIBUSB_SUCCESS; + } + + if(!SUCCEEDED(SendMessage(windows_event_hWnd, WM_CLOSE, 0, 0))) + { + log_error("SendMessage"); + } + + int ret = LIBUSB_SUCCESS; + + if(WaitForSingleObject(windows_event_thread_handle, INFINITE) != WAIT_OBJECT_0) + { + log_error("WaitForSingleObject"); + ret = LIBUSB_ERROR_OTHER; + } + + if(!CloseHandle(windows_event_thread_handle)) + { + log_error("WaitForSingleObject"); + ret = LIBUSB_ERROR_OTHER; + } + + return ret; +} + +static int windows_get_device_list(struct libusb_context *ctx) +{ + // context device list is protected by active_contexts_lock + struct libusb_device *dev; + for_each_device(ctx, dev) + { + ((struct winusb_device_priv *)usbi_get_device_priv(dev))->discovery_status = NO_LONGER_ENUMERATED; + } + + return ((struct windows_context_priv *)usbi_get_context_priv(ctx))->backend->get_device_list(ctx); +} + +int windows_initial_scan_devices(struct libusb_context *ctx) +{ + usbi_mutex_static_lock(&active_contexts_lock); + + int ret = windows_get_device_list(ctx); + + usbi_mutex_static_unlock(&active_contexts_lock); + return ret; +} + +static void windows_refresh_device_list(struct libusb_context *ctx) +{ + int ret = windows_get_device_list(ctx); + if(ret != LIBUSB_SUCCESS) + { + usbi_err(ctx, "hotplug failed to retrieve current list with error: %s", libusb_error_name(ret)); + return; + } + + struct libusb_device *dev, *next_dev; + struct winusb_device_priv *priv; + + for_each_device_safe(ctx, dev, next_dev) + { + priv = usbi_get_device_priv(dev); + if(priv->discovery_status != NO_LONGER_ENUMERATED) + { + continue; + } + + if(priv->initialized) + { + usbi_disconnect_device(dev); + } + else + { + usbi_detach_device(dev); + } + } + + for_each_device(ctx, dev) + { + priv = usbi_get_device_priv(dev); + + if(priv->discovery_status != NEWLY_ENUMERATED) + { + continue; + } + + usbi_hotplug_notification(ctx, dev, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED); + } +} + +static void windows_refresh_device_list_for_all_ctx(void) +{ + usbi_mutex_static_lock(&active_contexts_lock); + + struct libusb_context *ctx; + for_each_context(ctx) + { + windows_refresh_device_list(ctx); + } + usbi_mutex_static_unlock(&active_contexts_lock); +} + +#define WND_CLASS_NAME TEXT("libusb-1.0-windows-hotplug") + +static bool init_wnd_class(void) +{ + WNDCLASS wndClass = { 0 }; + wndClass.lpfnWndProc = windows_proc_callback; + wndClass.hInstance = GetModuleHandle(NULL); + wndClass.lpszClassName = WND_CLASS_NAME; + + if (!RegisterClass(&wndClass)) + { + log_error("RegisterClassEx"); + return false; + } + + return true; +} + +static DWORD WINAPI windows_event_thread_main(LPVOID lpParam) +{ + UNUSED(lpParam); + + usbi_dbg(NULL, "windows event thread entering"); + + if (!init_wnd_class()) + { + return (DWORD)-1; + } + + windows_event_hWnd = CreateWindow( + WND_CLASS_NAME, + TEXT(""), + 0, + 0, 0, 0, 0, + NULL, NULL, + GetModuleHandle(NULL), + NULL); + + if (windows_event_hWnd == NULL) + { + log_error("CreateWindow"); + return (DWORD)-1; + } + + MSG msg; + BOOL ret_val; + + while ((ret_val = GetMessage(&msg, windows_event_hWnd, 0, 0)) != 0) + { + if (ret_val == -1) + { + log_error("GetMessage"); + break; + } + + if(!SUCCEEDED(TranslateMessage(&msg))) + { + log_error("GetMessage"); + } + + if(!SUCCEEDED(DispatchMessage(&msg))) + { + log_error("DispatchMessage"); + } + } + + usbi_dbg(NULL, "windows event thread exiting"); + + return 0; +} + +static bool register_device_interface_to_window_handle( + IN GUID interface_class_guid, + IN HWND hWnd, + OUT HDEVNOTIFY* device_notify_handle) +{ + DEV_BROADCAST_DEVICEINTERFACE notificationFilter = { 0}; + notificationFilter.dbcc_size = sizeof(notificationFilter); + notificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + notificationFilter.dbcc_classguid = interface_class_guid; + + *device_notify_handle = RegisterDeviceNotification( + hWnd, + ¬ificationFilter, + DEVICE_NOTIFY_WINDOW_HANDLE// | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES// ?? for the ALL? + ); + + if (*device_notify_handle == NULL) + { + log_error("register_device_interface_to_window_handle"); + return false; + } + + return true; +} + +static LRESULT CALLBACK windows_proc_callback( + HWND hWnd, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + UNUSED(lParam); + + static HDEVNOTIFY device_notify_handle; + + switch (message) + { + case WM_CREATE: + if (!register_device_interface_to_window_handle( + GUID_DEVINTERFACE_USB_DEVICE, + hWnd, + &device_notify_handle)) + { + return -1; + } + return 0; + + case WM_DEVICECHANGE: + switch (wParam) + { + case DBT_DEVICEARRIVAL: + case DBT_DEVICEREMOVECOMPLETE: + if (((PDEV_BROADCAST_HDR)lParam)->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) + { + windows_refresh_device_list_for_all_ctx(); + return TRUE; + } + break; + } + return BROADCAST_QUERY_DENY; + + case WM_CLOSE: + if (!UnregisterDeviceNotification(device_notify_handle)) + { + log_error("UnregisterDeviceNotification"); + } + if(!DestroyWindow(hWnd)) + { + log_error("DestroyWindow"); + } + return 0; + + case WM_DESTROY: + PostQuitMessage(0); + return 0; + + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } +} diff --git a/libusb/os/windows_hotplug.h b/libusb/os/windows_hotplug.h new file mode 100644 index 000000000..0e046b9cf --- /dev/null +++ b/libusb/os/windows_hotplug.h @@ -0,0 +1,9 @@ +#ifndef WINDOWS_HOTPLUG_H +#define WINDOWS_HOTPLUG_H + +int windows_start_event_monitor(void); +int windows_stop_event_monitor(void); + +int windows_initial_scan_devices(struct libusb_context *ctx); + +#endif \ No newline at end of file diff --git a/libusb/os/windows_usbdk.c b/libusb/os/windows_usbdk.c index 9f52b486c..a2d7c336d 100644 --- a/libusb/os/windows_usbdk.c +++ b/libusb/os/windows_usbdk.c @@ -310,11 +310,10 @@ static void usbdk_device_init(struct libusb_device *dev, PUSB_DK_DEVICE_INFO inf } } -static int usbdk_get_device_list(struct libusb_context *ctx, struct discovered_devs **_discdevs) +static int usbdk_get_device_list(struct libusb_context *ctx) { int r = LIBUSB_SUCCESS; ULONG i; - struct discovered_devs *discdevs = NULL; ULONG dev_number; PUSB_DK_DEVICE_INFO devices; @@ -336,25 +335,19 @@ static int usbdk_get_device_list(struct libusb_context *ctx, struct discovered_d continue; } + ((struct winusb_device_priv *)usbi_get_device_priv(dev))->discovery_status = NEWLY_ENUMERATED; + usbdk_device_init(dev, &devices[i]); if (usbdk_device_priv_init(ctx, dev, &devices[i]) != LIBUSB_SUCCESS) { libusb_unref_device(dev); continue; } } - - discdevs = discovered_devs_append(*_discdevs, dev); - libusb_unref_device(dev); - if (!discdevs) { - usbi_err(ctx, "cannot append new device to list"); - r = LIBUSB_ERROR_NO_MEM; - goto func_exit; + else { + ((struct winusb_device_priv *)usbi_get_device_priv(dev))->discovery_status = STILL_ENUMERATED; } - - *_discdevs = discdevs; } -func_exit: usbdk_helper.ReleaseDevicesList(devices); return r; } diff --git a/libusb/os/windows_winusb.c b/libusb/os/windows_winusb.c index 926b9e88f..411ce3661 100644 --- a/libusb/os/windows_winusb.c +++ b/libusb/os/windows_winusb.c @@ -1585,9 +1585,8 @@ static int get_guid(struct libusb_context *ctx, char *dev_id, HDEVINFO *dev_info /* * get_device_list: libusb backend device enumeration function */ -static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_devs **_discdevs) +static int winusb_get_device_list(struct libusb_context *ctx) { - struct discovered_devs *discdevs; HDEVINFO *dev_info, dev_info_intf, dev_info_enum; SP_DEVINFO_DATA dev_info_data; DWORD _index = 0; @@ -1857,26 +1856,30 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ if (dev == NULL) LOOP_BREAK(LIBUSB_ERROR_NO_MEM); + usbi_attach_device(dev); + priv = winusb_device_priv_init(dev); + priv->discovery_status = NEWLY_ENUMERATED; priv->dev_id = _strdup(dev_id); priv->class_guid = dev_info_data.ClassGuid; if (priv->dev_id == NULL) { libusb_unref_device(dev); LOOP_BREAK(LIBUSB_ERROR_NO_MEM); } + goto dont_track_unref; } else { usbi_dbg(ctx, "found existing device for session [%lX]", session_id); priv = usbi_get_device_priv(dev); if (strcmp(priv->dev_id, dev_id) != 0) { usbi_dbg(ctx, "device instance ID for session [%lX] changed", session_id); - usbi_disconnect_device(dev); + usbi_detach_device(dev); libusb_unref_device(dev); goto alloc_device; } if (!IsEqualGUID(&priv->class_guid, &dev_info_data.ClassGuid)) { usbi_dbg(ctx, "device class GUID for session [%lX] changed", session_id); - usbi_disconnect_device(dev); + usbi_detach_device(dev); libusb_unref_device(dev); goto alloc_device; } @@ -1896,6 +1899,7 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ unref_list[unref_cur++] = dev; } + dont_track_unref: // Setup device switch (pass_type) { case HUB_PASS: @@ -1949,22 +1953,26 @@ static int winusb_get_device_list(struct libusb_context *ctx, struct discovered_ break; case GEN_PASS: port_nr = 0; - if (!get_dev_port_number(*dev_info, &dev_info_data, &port_nr)) - usbi_warn(ctx, "could not retrieve port number for device '%s': %s", dev_id, windows_error_str(0)); - r = init_device(dev, parent_dev, (uint8_t)port_nr, dev_info_data.DevInst); - if (r == LIBUSB_SUCCESS) { - // Append device to the list of discovered devices - discdevs = discovered_devs_append(*_discdevs, dev); - if (!discdevs) - LOOP_BREAK(LIBUSB_ERROR_NO_MEM); - - *_discdevs = discdevs; - } else { + if(priv->initialized) { + libusb_unref_device(parent_dev); + r = LIBUSB_SUCCESS; + priv->discovery_status = STILL_ENUMERATED; + } + else { + if (!get_dev_port_number(*dev_info, &dev_info_data, &port_nr)) + usbi_warn(ctx, "could not retrieve port number for device '%s': %s", dev_id, windows_error_str(0)); + r = init_device(dev, parent_dev, (uint8_t)port_nr, dev_info_data.DevInst); + } + if (r != LIBUSB_SUCCESS) { // Failed to initialize a single device doesn't stop us from enumerating all other devices, // but we skip it (don't add to list of discovered devices) usbi_warn(ctx, "failed to initialize device '%s'", priv->dev_id); r = LIBUSB_SUCCESS; + + usbi_detach_device(dev); + libusb_unref_device(dev); } + break; case HID_PASS: case EXT_PASS: diff --git a/msvc/libusb_dll.vcxproj b/msvc/libusb_dll.vcxproj index 6d6c73131..bd85d4d48 100644 --- a/msvc/libusb_dll.vcxproj +++ b/msvc/libusb_dll.vcxproj @@ -30,6 +30,7 @@ + @@ -38,6 +39,7 @@ + diff --git a/msvc/libusb_static.vcxproj b/msvc/libusb_static.vcxproj index ad72d8c6b..63042078a 100644 --- a/msvc/libusb_static.vcxproj +++ b/msvc/libusb_static.vcxproj @@ -24,6 +24,7 @@ + @@ -32,6 +33,7 @@ +