summaryrefslogtreecommitdiff
path: root/Source/Core/InputCommon/GCAdapter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Core/InputCommon/GCAdapter.cpp')
-rw-r--r--Source/Core/InputCommon/GCAdapter.cpp104
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