diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp index 5126384939..986480227d 100644 --- a/Source/Core/InputCommon/GCAdapter.cpp +++ b/Source/Core/InputCommon/GCAdapter.cpp @@ -12,8 +12,10 @@ #endif #include +#include #include #include +using namespace std::chrono_literals; #if GCADAPTER_USE_LIBUSB_IMPLEMENTATION #include @@ -137,6 +139,9 @@ static Common::Flag s_adapter_detect_thread_running; static Common::Event s_hotplug_event; +static Common::Flag s_read_thread_has_init; +static Common::Flag s_adapter_reads_failing; + #if GCADAPTER_USE_LIBUSB_IMPLEMENTATION static std::function s_detect_callback; @@ -197,6 +202,11 @@ static void ReadThreadFunc() s_write_adapter_thread_running.Set(true); s_write_adapter_thread = std::thread(WriteThreadFunc); + std::chrono::steady_clock::time_point last_successful_read_time = + std::chrono::steady_clock::now(); + s_adapter_reads_failing.Clear(); + s_read_thread_has_init.Set(); + // Reset rumble once on initial reading ResetRumble(); @@ -205,34 +215,47 @@ static void ReadThreadFunc() auto poll_rate_measurement_start_time = Clock::now(); int poll_rate_measurement_count = 0; - while (s_read_adapter_thread_running.IsSet()) + bool last_read_failed = false; + + while (s_read_adapter_thread_running.IsSet() && !s_adapter_reads_failing.IsSet()) { #if GCADAPTER_USE_LIBUSB_IMPLEMENTATION std::array input_buffer; int payload_size = 0; - int error = libusb_interrupt_transfer(s_handle, s_endpoint_in, input_buffer.data(), - int(input_buffer.size()), &payload_size, USB_TIMEOUT_MS); - if (error != LIBUSB_SUCCESS) + int transfer_return_code = + libusb_interrupt_transfer(s_handle, s_endpoint_in, input_buffer.data(), + int(input_buffer.size()), &payload_size, USB_TIMEOUT_MS); + if (transfer_return_code == LIBUSB_SUCCESS) { - ERROR_LOG_FMT(CONTROLLERINTERFACE, "Read: libusb_interrupt_transfer failed: {}", - LibusbUtils::ErrorWrap(error)); + ProcessInputPayload(input_buffer.data(), payload_size); + last_successful_read_time = std::chrono::steady_clock::now(); + last_read_failed = false; } - if (error == LIBUSB_ERROR_IO) + else { - // s_read_adapter_thread_running is cleared by the joiner, not the stopper. + if (last_read_failed) + { + INFO_LOG_FMT(CONTROLLERINTERFACE, "Read: libusb_interrupt_transfer failed: {}", + LibusbUtils::ErrorWrap(transfer_return_code)); - // Reset the device, which may trigger a replug. - error = libusb_reset_device(s_handle); - ERROR_LOG_FMT(CONTROLLERINTERFACE, "Read: libusb_reset_device: {}", - LibusbUtils::ErrorWrap(error)); + // Prevents busy-looping when transfers return instantly e.g. on disconnect + Common::SleepCurrentThread(1); + } + else + { + ERROR_LOG_FMT(CONTROLLERINTERFACE, "Read: libusb_interrupt_transfer newly failed: {}", + LibusbUtils::ErrorWrap(transfer_return_code)); + } + last_read_failed = true; - // If error is nonzero, try fixing it next loop iteration. We can't easily return - // and cleanup program state without getting another thread to call Reset(). + if (std::chrono::steady_clock::now() - last_successful_read_time > 500ms) + { + ERROR_LOG_FMT(CONTROLLERINTERFACE, "Read: continuously failing transfers, resetting."); + s_adapter_reads_failing.Set(); + } } - ProcessInputPayload(input_buffer.data(), payload_size); - #elif GCADAPTER_USE_ANDROID_IMPLEMENTATION const int payload_size = env->CallStaticIntMethod(s_adapter_class, input_func); jbyte* const java_data = env->GetByteArrayElements(*java_controller_payload, nullptr); @@ -260,8 +283,6 @@ static void ReadThreadFunc() poll_rate_measurement_start_time = now; poll_rate_measurement_count = 0; } - - Common::YieldCPU(); } // Terminate the write thread on leaving @@ -387,6 +408,8 @@ static void ScanThreadFunc() #if LIBUSB_API_HAS_HOTPLUG #ifndef __FreeBSD__ s_libusb_hotplug_enabled = libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) != 0; + // As of v1.0.23-rc1 (2026/02) libusb has hotplug event capability on Linux & MacOS, but not on + // Windows #endif if (s_libusb_hotplug_enabled) { @@ -418,9 +441,12 @@ static void ScanThreadFunc() } if (s_libusb_hotplug_enabled) - s_hotplug_event.Wait(); + s_hotplug_event.WaitFor(1000ms); else Common::SleepCurrentThread(500); + + if (s_read_thread_has_init.IsSet() && s_adapter_reads_failing.IsSet()) + Reset(); } #elif GCADAPTER_USE_ANDROID_IMPLEMENTATION JNIEnv* const env = IDCache::GetEnvForThread(); @@ -588,8 +614,13 @@ static bool CheckDeviceAccess(libusb_device* device) return false; } - NOTICE_LOG_FMT(CONTROLLERINTERFACE, "Found GC Adapter with Vendor: {:X} Product: {:X} Devnum: {}", - desc.idVendor, desc.idProduct, 1); + const u8 bus = libusb_get_bus_number(device); + const u8 port = libusb_get_device_address(device); + + NOTICE_LOG_FMT(CONTROLLERINTERFACE, + "Found GC Adapter [Bus:{:03d}, Address:{:03d}, VID:" + "{:04X}, PID:{:04X}]", + bus, port, desc.idVendor, desc.idProduct); // In case of failure, capture the libusb error code into the adapter status Common::ScopeGuard status_guard([&ret] { @@ -597,20 +628,13 @@ static bool CheckDeviceAccess(libusb_device* device) s_status = AdapterStatus::Error; }); - const u8 bus = libusb_get_bus_number(device); - const u8 port = libusb_get_device_address(device); ret = libusb_open(device, &s_handle); if (ret != LIBUSB_SUCCESS) { - if (ret == LIBUSB_ERROR_ACCESS) - { - ERROR_LOG_FMT(CONTROLLERINTERFACE, - "Dolphin does not have access to this device: Bus {:03d} Device {:03d}: ID " - "{:04X}:{:04X}.", - bus, port, desc.idVendor, desc.idProduct); - } - ERROR_LOG_FMT(CONTROLLERINTERFACE, "libusb_open failed to open device: {}", - LibusbUtils::ErrorWrap(ret)); + ERROR_LOG_FMT(CONTROLLERINTERFACE, + "libusb_open failed to open GC Adapter [Bus:{:03d}, Address:{:03d}, VID:" + "{:04X}, PID:{:04X}] with error {}", + bus, port, desc.idVendor, desc.idProduct, LibusbUtils::ErrorWrap(ret)); return false; } @@ -633,9 +657,18 @@ static bool CheckDeviceAccess(libusb_device* device) } else if (ret != 0) // 0: kernel driver is not active, but otherwise no error. { - // Neither 0 nor 1 means an error occurred. - ERROR_LOG_FMT(CONTROLLERINTERFACE, "libusb_kernel_driver_active failed: {}", - LibusbUtils::ErrorWrap(ret)); + // Neither 0 nor 1 means an error occured. + if (ret == LIBUSB_ERROR_NOT_SUPPORTED) + { + // Expected outside Linux + INFO_LOG_FMT(CONTROLLERINTERFACE, "libusb_kernel_driver_active failed: {}", + LibusbUtils::ErrorWrap(ret)); + } + else + { + ERROR_LOG_FMT(CONTROLLERINTERFACE, "libusb_kernel_driver_active failed: {}", + LibusbUtils::ErrorWrap(ret)); + } } // This call makes Nyko-brand (and perhaps other) adapters work. @@ -763,6 +796,8 @@ static void Reset() s_read_adapter_thread.join(); // The read thread will close the write thread + s_read_thread_has_init.Clear(); + s_port_states.fill({}); #if GCADAPTER_USE_LIBUSB_IMPLEMENTATION diff --git a/Source/Core/InputCommon/GCPadStatus.h b/Source/Core/InputCommon/GCPadStatus.h index b6bbf804b4..e126654b31 100644 --- a/Source/Core/InputCommon/GCPadStatus.h +++ b/Source/Core/InputCommon/GCPadStatus.h @@ -37,23 +37,23 @@ enum SwitchButton struct GCPadStatus { - u16 button = 0; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits - u8 stickX = 0; // 0 <= stickX <= 255 - u8 stickY = 0; // 0 <= stickY <= 255 - u8 substickX = 0; // 0 <= substickX <= 255 - u8 substickY = 0; // 0 <= substickY <= 255 - u8 triggerLeft = 0; // 0 <= triggerLeft <= 255 - u8 triggerRight = 0; // 0 <= triggerRight <= 255 - u8 analogA = 0; // 0 <= analogA <= 255 - u8 analogB = 0; // 0 <= analogB <= 255 - // Triforce - u8 switches = 0; - bool isConnected = true; - static const u8 MAIN_STICK_CENTER_X = 0x80; static const u8 MAIN_STICK_CENTER_Y = 0x80; static const u8 MAIN_STICK_RADIUS = 0x7f; static const u8 C_STICK_CENTER_X = 0x80; static const u8 C_STICK_CENTER_Y = 0x80; static const u8 C_STICK_RADIUS = 0x7f; + + u16 button = 0; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits + u8 stickX = MAIN_STICK_CENTER_X; // 0 <= stickX <= 255 + u8 stickY = MAIN_STICK_CENTER_Y; // 0 <= stickY <= 255 + u8 substickX = C_STICK_CENTER_X; // 0 <= substickX <= 255 + u8 substickY = C_STICK_CENTER_Y; // 0 <= substickY <= 255 + u8 triggerLeft = 0; // 0 <= triggerLeft <= 255 + u8 triggerRight = 0; // 0 <= triggerRight <= 255 + u8 analogA = 0; // 0 <= analogA <= 255 + u8 analogB = 0; // 0 <= analogB <= 255 + // Triforce + u8 switches = 0; + bool isConnected = true; };