From afc2920a5fb6eaed34b091508b73e3ae5e8c7257 Mon Sep 17 00:00:00 2001 From: Julien Bernard Date: Sun, 28 Dec 2025 20:16:25 +0100 Subject: [PATCH 1/2] Gcc adapter fixes (hotplugging, claim retries, shutdown, bad yields) --- Source/Core/InputCommon/GCAdapter.cpp | 104 +++++++++++++++++--------- 1 file changed, 69 insertions(+), 35 deletions(-) diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp index 5126384939..ab17b00e79 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,7 @@ static void ScanThreadFunc() #if LIBUSB_API_HAS_HOTPLUG #ifndef __FreeBSD__ s_libusb_hotplug_enabled = libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) != 0; + // As of 28/12/2025 libusb has hotplug event capability on Linux & MacOS, but not on Windows #endif if (s_libusb_hotplug_enabled) { @@ -418,9 +440,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 +613,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 +627,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 +656,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 +795,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 From 0e8db4fa225ea6852e1683cdd870e6806b0440a9 Mon Sep 17 00:00:00 2001 From: Julien Bernard Date: Wed, 25 Feb 2026 22:50:06 +0100 Subject: [PATCH 2/2] Send neutral stick positions when disconnected rather than down-left --- Source/Core/InputCommon/GCAdapter.cpp | 3 ++- Source/Core/InputCommon/GCPadStatus.h | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp index ab17b00e79..986480227d 100644 --- a/Source/Core/InputCommon/GCAdapter.cpp +++ b/Source/Core/InputCommon/GCAdapter.cpp @@ -408,7 +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 28/12/2025 libusb has hotplug event capability on Linux & MacOS, but not on Windows + // 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) { 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; };