diff options
| author | Julien Bernard <artessbm@gmail.com> | 2025-12-28 20:16:25 +0100 |
|---|---|---|
| committer | Julien Bernard <artessbm@gmail.com> | 2026-02-25 22:55:48 +0100 |
| commit | afc2920a5fb6eaed34b091508b73e3ae5e8c7257 (patch) | |
| tree | 13201d0ba340df8d5ab04ee39589c4b5e14f227a /Source/Core/InputCommon/GCAdapter.cpp | |
| parent | 2a3078b833fe3d7b199b833fb4dfc08111d44dd7 (diff) | |
Gcc adapter fixes (hotplugging, claim retries, shutdown, bad yields)
Diffstat (limited to 'Source/Core/InputCommon/GCAdapter.cpp')
| -rw-r--r-- | Source/Core/InputCommon/GCAdapter.cpp | 104 |
1 files 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 <array> +#include <chrono> #include <mutex> #include <optional> +using namespace std::chrono_literals; #if GCADAPTER_USE_LIBUSB_IMPLEMENTATION #include <libusb.h> @@ -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<void(void)> 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<u8, CONTROLLER_INPUT_PAYLOAD_EXPECTED_SIZE> 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 |
