Gcc adapter fixes (hotplugging, claim retries, shutdown, bad yields)
This commit is contained in:
@@ -12,8 +12,10 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
||||||
#include <libusb.h>
|
#include <libusb.h>
|
||||||
@@ -137,6 +139,9 @@ static Common::Flag s_adapter_detect_thread_running;
|
|||||||
|
|
||||||
static Common::Event s_hotplug_event;
|
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
|
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
||||||
static std::function<void(void)> s_detect_callback;
|
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_running.Set(true);
|
||||||
s_write_adapter_thread = std::thread(WriteThreadFunc);
|
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
|
// Reset rumble once on initial reading
|
||||||
ResetRumble();
|
ResetRumble();
|
||||||
|
|
||||||
@@ -205,33 +215,46 @@ static void ReadThreadFunc()
|
|||||||
auto poll_rate_measurement_start_time = Clock::now();
|
auto poll_rate_measurement_start_time = Clock::now();
|
||||||
int poll_rate_measurement_count = 0;
|
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
|
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
||||||
std::array<u8, CONTROLLER_INPUT_PAYLOAD_EXPECTED_SIZE> input_buffer;
|
std::array<u8, CONTROLLER_INPUT_PAYLOAD_EXPECTED_SIZE> input_buffer;
|
||||||
|
|
||||||
int payload_size = 0;
|
int payload_size = 0;
|
||||||
int error = libusb_interrupt_transfer(s_handle, s_endpoint_in, input_buffer.data(),
|
int transfer_return_code =
|
||||||
|
libusb_interrupt_transfer(s_handle, s_endpoint_in, input_buffer.data(),
|
||||||
int(input_buffer.size()), &payload_size, USB_TIMEOUT_MS);
|
int(input_buffer.size()), &payload_size, USB_TIMEOUT_MS);
|
||||||
if (error != LIBUSB_SUCCESS)
|
if (transfer_return_code == LIBUSB_SUCCESS)
|
||||||
{
|
{
|
||||||
ERROR_LOG_FMT(CONTROLLERINTERFACE, "Read: libusb_interrupt_transfer failed: {}",
|
|
||||||
LibusbUtils::ErrorWrap(error));
|
|
||||||
}
|
|
||||||
if (error == LIBUSB_ERROR_IO)
|
|
||||||
{
|
|
||||||
// s_read_adapter_thread_running is cleared by the joiner, not the stopper.
|
|
||||||
|
|
||||||
// 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));
|
|
||||||
|
|
||||||
// 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().
|
|
||||||
}
|
|
||||||
|
|
||||||
ProcessInputPayload(input_buffer.data(), payload_size);
|
ProcessInputPayload(input_buffer.data(), payload_size);
|
||||||
|
last_successful_read_time = std::chrono::steady_clock::now();
|
||||||
|
last_read_failed = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (last_read_failed)
|
||||||
|
{
|
||||||
|
INFO_LOG_FMT(CONTROLLERINTERFACE, "Read: libusb_interrupt_transfer failed: {}",
|
||||||
|
LibusbUtils::ErrorWrap(transfer_return_code));
|
||||||
|
|
||||||
|
// 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 (std::chrono::steady_clock::now() - last_successful_read_time > 500ms)
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(CONTROLLERINTERFACE, "Read: continuously failing transfers, resetting.");
|
||||||
|
s_adapter_reads_failing.Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
||||||
const int payload_size = env->CallStaticIntMethod(s_adapter_class, input_func);
|
const int payload_size = env->CallStaticIntMethod(s_adapter_class, input_func);
|
||||||
@@ -260,8 +283,6 @@ static void ReadThreadFunc()
|
|||||||
poll_rate_measurement_start_time = now;
|
poll_rate_measurement_start_time = now;
|
||||||
poll_rate_measurement_count = 0;
|
poll_rate_measurement_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Common::YieldCPU();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terminate the write thread on leaving
|
// Terminate the write thread on leaving
|
||||||
@@ -387,6 +408,7 @@ static void ScanThreadFunc()
|
|||||||
#if LIBUSB_API_HAS_HOTPLUG
|
#if LIBUSB_API_HAS_HOTPLUG
|
||||||
#ifndef __FreeBSD__
|
#ifndef __FreeBSD__
|
||||||
s_libusb_hotplug_enabled = libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) != 0;
|
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
|
#endif
|
||||||
if (s_libusb_hotplug_enabled)
|
if (s_libusb_hotplug_enabled)
|
||||||
{
|
{
|
||||||
@@ -418,9 +440,12 @@ static void ScanThreadFunc()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (s_libusb_hotplug_enabled)
|
if (s_libusb_hotplug_enabled)
|
||||||
s_hotplug_event.Wait();
|
s_hotplug_event.WaitFor(1000ms);
|
||||||
else
|
else
|
||||||
Common::SleepCurrentThread(500);
|
Common::SleepCurrentThread(500);
|
||||||
|
|
||||||
|
if (s_read_thread_has_init.IsSet() && s_adapter_reads_failing.IsSet())
|
||||||
|
Reset();
|
||||||
}
|
}
|
||||||
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
|
||||||
JNIEnv* const env = IDCache::GetEnvForThread();
|
JNIEnv* const env = IDCache::GetEnvForThread();
|
||||||
@@ -588,8 +613,13 @@ static bool CheckDeviceAccess(libusb_device* device)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
NOTICE_LOG_FMT(CONTROLLERINTERFACE, "Found GC Adapter with Vendor: {:X} Product: {:X} Devnum: {}",
|
const u8 bus = libusb_get_bus_number(device);
|
||||||
desc.idVendor, desc.idProduct, 1);
|
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
|
// In case of failure, capture the libusb error code into the adapter status
|
||||||
Common::ScopeGuard status_guard([&ret] {
|
Common::ScopeGuard status_guard([&ret] {
|
||||||
@@ -597,20 +627,13 @@ static bool CheckDeviceAccess(libusb_device* device)
|
|||||||
s_status = AdapterStatus::Error;
|
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);
|
ret = libusb_open(device, &s_handle);
|
||||||
if (ret != LIBUSB_SUCCESS)
|
if (ret != LIBUSB_SUCCESS)
|
||||||
{
|
|
||||||
if (ret == LIBUSB_ERROR_ACCESS)
|
|
||||||
{
|
{
|
||||||
ERROR_LOG_FMT(CONTROLLERINTERFACE,
|
ERROR_LOG_FMT(CONTROLLERINTERFACE,
|
||||||
"Dolphin does not have access to this device: Bus {:03d} Device {:03d}: ID "
|
"libusb_open failed to open GC Adapter [Bus:{:03d}, Address:{:03d}, VID:"
|
||||||
"{:04X}:{:04X}.",
|
"{:04X}, PID:{:04X}] with error {}",
|
||||||
bus, port, desc.idVendor, desc.idProduct);
|
bus, port, desc.idVendor, desc.idProduct, LibusbUtils::ErrorWrap(ret));
|
||||||
}
|
|
||||||
ERROR_LOG_FMT(CONTROLLERINTERFACE, "libusb_open failed to open device: {}",
|
|
||||||
LibusbUtils::ErrorWrap(ret));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,10 +656,19 @@ static bool CheckDeviceAccess(libusb_device* device)
|
|||||||
}
|
}
|
||||||
else if (ret != 0) // 0: kernel driver is not active, but otherwise no error.
|
else if (ret != 0) // 0: kernel driver is not active, but otherwise no error.
|
||||||
{
|
{
|
||||||
// Neither 0 nor 1 means an error occurred.
|
// 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: {}",
|
ERROR_LOG_FMT(CONTROLLERINTERFACE, "libusb_kernel_driver_active failed: {}",
|
||||||
LibusbUtils::ErrorWrap(ret));
|
LibusbUtils::ErrorWrap(ret));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This call makes Nyko-brand (and perhaps other) adapters work.
|
// This call makes Nyko-brand (and perhaps other) adapters work.
|
||||||
// However it returns LIBUSB_ERROR_PIPE with Mayflash adapters.
|
// However it returns LIBUSB_ERROR_PIPE with Mayflash adapters.
|
||||||
@@ -763,6 +795,8 @@ static void Reset()
|
|||||||
s_read_adapter_thread.join();
|
s_read_adapter_thread.join();
|
||||||
// The read thread will close the write thread
|
// The read thread will close the write thread
|
||||||
|
|
||||||
|
s_read_thread_has_init.Clear();
|
||||||
|
|
||||||
s_port_states.fill({});
|
s_port_states.fill({});
|
||||||
|
|
||||||
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
|
||||||
|
|||||||
Reference in New Issue
Block a user