diff --git a/libusb/Makefile.am b/libusb/Makefile.am index 640edd870..ada300361 100644 --- a/libusb/Makefile.am +++ b/libusb/Makefile.am @@ -27,6 +27,7 @@ OS_OPENBSD_SRC = os/openbsd_usb.c 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_hotplug.h os/windows_hotplug.c \ os/windows_usbdk.h os/windows_usbdk.c \ os/windows_winusb.h os/windows_winusb.c diff --git a/libusb/core.c b/libusb/core.c index 010201c5f..f70dd7700 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 6d19a930c..ae8a620a1 100644 --- a/libusb/libusbi.h +++ b/libusb/libusbi.h @@ -821,6 +821,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..4aabd2a3f 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,16 @@ static int windows_init(struct libusb_context *ctx) r = LIBUSB_SUCCESS; + if (init_count == 1) { + r = windows_start_event_monitor(); // Start up hotplug event handler + if (r != LIBUSB_SUCCESS) { + usbi_err(ctx, "error starting hotplug 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 +607,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 +636,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 +894,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..cf297c3b1 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, added when updating device list + * using system enumeration in response to low-level hotplug events */ + enum hotplug_status { + /* device is still in system enumeration as it was in previous enumeration. + * There is no need to take any action */ + UNCHANGED, + + /* device is newly added to the list, meaning it just appeared in system enumeration. + * A hotplug "device arrived" event shall be triggered */ + ARRIVED, + + /* device is no longer present in system enumeration. + * A hotplug "device left" event shall be triggered and the device removed from the list */ + LEFT +}; + 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 hotplug_status hotplug_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 hotplug_status hotplug_status; /* updated while getting current device list */ }; 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); // Refresh ctx device list with devices currently connected to the system and update the `hotplug_status` field of all enumerated devices. 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..c147dc65f --- /dev/null +++ b/libusb/os/windows_hotplug.c @@ -0,0 +1,309 @@ +#include "libusbi.h" +#include "threads_windows.h" + +#include "windows_common.h" +#include "windows_hotplug.h" + +#include +#include +#include + +/* The Windows Hotplug system is a three steps process. + * 1. We create a monitor on GUID_DEVINTERFACE_USB_DEVICE via a hidden window. + * 2. Upon notification of an event, we run the current windows backend to get the list of devices. + * This updates the hotplug status of each device to one of three values {UNCHANGED, ARRIVED, LEFT}. + * 3. According to the value, we generate events to libusb client via hotplug callbacks. */ + +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, // Default security descriptor + 0, // Default stack size + windows_event_thread_main, + NULL, // No parameters to pass to the thread + 0, // Start immediately + NULL // No need to keep track of thread ID + ); + + 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"); + return LIBUSB_ERROR_OTHER; + } + + if (WaitForSingleObject(windows_event_thread_handle, INFINITE) != WAIT_OBJECT_0) + { + log_error("WaitForSingleObject"); + return LIBUSB_ERROR_OTHER; + } + + if (!CloseHandle(windows_event_thread_handle)) + { + log_error("CloseHandle"); + return LIBUSB_ERROR_OTHER; + } + + return LIBUSB_SUCCESS; +} + +/* We leverage the backend's get_device_list method to discover new/gone devices. + * 1. Mark all devices as if they LEFT + * 2. Run get_device_list which will set devices still connected to UNCHANGED and new devices to ARRIVED. + * Since the marker is not modified for devices which had disconnected and are thus no longer enumerated, + * all devices have appropriate connection status and the end of the process */ +static int windows_get_device_list(struct libusb_context *ctx) +{ + // note: 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))->hotplug_status = LEFT; + } + + return ((struct windows_context_priv *)usbi_get_context_priv(ctx))->backend->get_device_list(ctx); +} + +void windows_initial_scan_devices(struct libusb_context *ctx) +{ + usbi_mutex_static_lock(&active_contexts_lock); + + const int ret = windows_get_device_list(ctx); + if (ret != LIBUSB_SUCCESS) + { + usbi_err(ctx, "hotplug failed to retrieve initial list with error: %s", libusb_error_name(ret)); + } + usbi_mutex_static_unlock(&active_contexts_lock); +} + +static void windows_refresh_device_list(struct libusb_context *ctx) +{ + const 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->hotplug_status != LEFT) + { + 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->hotplug_status != ARRIVED) + { + 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("event thread: RegisterClass"); + 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("event thread: 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("event thread: GetMessage"); + break; + } + + if (!SUCCEEDED(TranslateMessage(&msg))) + { + log_error("event thread: TranslateMessage"); + } + + if (!SUCCEEDED(DispatchMessage(&msg))) + { + log_error("event thread: 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 + ); + + 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..1be1e1a65 --- /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); + +void 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..f85fe532d 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))->hotplug_status = ARRIVED; + 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))->hotplug_status = UNCHANGED; } - - *_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 65d288fce..b69f4e8e3 100644 --- a/libusb/os/windows_winusb.c +++ b/libusb/os/windows_winusb.c @@ -1584,9 +1584,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; @@ -1858,26 +1857,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->hotplug_status = ARRIVED; 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; } @@ -1897,6 +1900,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: @@ -1950,22 +1954,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->hotplug_status = UNCHANGED; + } + 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 @@ +