From 5209677f2fd661949778c071287f5005b57bc8fe Mon Sep 17 00:00:00 2001 From: Joshua de Reeper Date: Tue, 2 Jul 2024 02:32:37 +0100 Subject: [PATCH] nsyshid: Add SetProtocol and SetReport support for libusb backend (#1243) --- src/Cafe/OS/libs/nsyshid/Backend.h | 2 +- src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp | 161 +++++++++++++----- src/Cafe/OS/libs/nsyshid/BackendLibusb.h | 15 +- .../OS/libs/nsyshid/BackendWindowsHID.cpp | 2 +- src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h | 2 +- src/Cafe/OS/libs/nsyshid/Skylander.cpp | 2 +- src/Cafe/OS/libs/nsyshid/Skylander.h | 2 +- src/Cafe/OS/libs/nsyshid/nsyshid.cpp | 4 +- 8 files changed, 137 insertions(+), 53 deletions(-) diff --git a/src/Cafe/OS/libs/nsyshid/Backend.h b/src/Cafe/OS/libs/nsyshid/Backend.h index 03232736..12362773 100644 --- a/src/Cafe/OS/libs/nsyshid/Backend.h +++ b/src/Cafe/OS/libs/nsyshid/Backend.h @@ -135,7 +135,7 @@ namespace nsyshid uint8* output, uint32 outputMaxLength) = 0; - virtual bool SetProtocol(uint32 ifIndef, uint32 protocol) = 0; + virtual bool SetProtocol(uint8 ifIndex, uint8 protocol) = 0; virtual bool SetReport(ReportMessage* message) = 0; }; diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp index 6701d780..7548c998 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp @@ -230,6 +230,17 @@ namespace nsyshid::backend::libusb return nullptr; } + std::pair MakeConfigDescriptor(libusb_device* device, uint8 config_num) + { + libusb_config_descriptor* descriptor = nullptr; + const int ret = libusb_get_config_descriptor(device, config_num, &descriptor); + if (ret == LIBUSB_SUCCESS) + return {ret, ConfigDescriptor{descriptor, libusb_free_config_descriptor}}; + + return {ret, ConfigDescriptor{nullptr, [](auto) { + }}}; + } + std::shared_ptr BackendLibusb::CheckAndCreateDevice(libusb_device* dev) { struct libusb_device_descriptor desc; @@ -241,6 +252,25 @@ namespace nsyshid::backend::libusb ret); return nullptr; } + std::vector config_descriptors{}; + for (uint8 i = 0; i < desc.bNumConfigurations; ++i) + { + auto [ret, config_descriptor] = MakeConfigDescriptor(dev, i); + if (ret != LIBUSB_SUCCESS || !config_descriptor) + { + cemuLog_log(LogType::Force, "Failed to make config descriptor {} for {:04x}:{:04x}: {}", + i, desc.idVendor, desc.idProduct, libusb_error_name(ret)); + } + else + { + config_descriptors.emplace_back(std::move(config_descriptor)); + } + } + if (desc.idVendor == 0x0e6f && desc.idProduct == 0x0241) + { + cemuLog_logDebug(LogType::Force, + "nsyshid::BackendLibusb::CheckAndCreateDevice(): lego dimensions portal detected"); + } auto device = std::make_shared(m_ctx, desc.idVendor, desc.idProduct, @@ -248,7 +278,8 @@ namespace nsyshid::backend::libusb 2, 0, libusb_get_bus_number(dev), - libusb_get_device_address(dev)); + libusb_get_device_address(dev), + std::move(config_descriptors)); // figure out device endpoints if (!FindDefaultDeviceEndpoints(dev, device->m_libusbHasEndpointIn, @@ -330,7 +361,8 @@ namespace nsyshid::backend::libusb uint8 interfaceSubClass, uint8 protocol, uint8 libusbBusNumber, - uint8 libusbDeviceAddress) + uint8 libusbDeviceAddress, + std::vector configs) : Device(vendorId, productId, interfaceIndex, @@ -346,6 +378,7 @@ namespace nsyshid::backend::libusb m_libusbHasEndpointOut(false), m_libusbEndpointOut(0) { + m_config_descriptors = std::move(configs); } DeviceLibusb::~DeviceLibusb() @@ -413,20 +446,8 @@ namespace nsyshid::backend::libusb } this->m_handleInUseCounter = 0; } - if (libusb_kernel_driver_active(this->m_libusbHandle, 0) == 1) { - cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): kernel driver active"); - if (libusb_detach_kernel_driver(this->m_libusbHandle, 0) == 0) - { - cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): kernel driver detached"); - } - else - { - cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): failed to detach kernel driver"); - } - } - { - int ret = libusb_claim_interface(this->m_libusbHandle, 0); + int ret = ClaimAllInterfaces(0); if (ret != 0) { cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): cannot claim interface"); @@ -680,7 +701,65 @@ namespace nsyshid::backend::libusb return false; } - bool DeviceLibusb::SetProtocol(uint32 ifIndex, uint32 protocol) + template + static int DoForEachInterface(const Configs& configs, uint8 config_num, Function action) + { + int ret = LIBUSB_ERROR_NOT_FOUND; + if (configs.size() <= config_num || !configs[config_num]) + return ret; + for (uint8 i = 0; i < configs[config_num]->bNumInterfaces; ++i) + { + ret = action(i); + if (ret < LIBUSB_SUCCESS) + break; + } + return ret; + } + + int DeviceLibusb::ClaimAllInterfaces(uint8 config_num) + { + const int ret = DoForEachInterface(m_config_descriptors, config_num, [this](uint8 i) { + if (libusb_kernel_driver_active(this->m_libusbHandle, i)) + { + const int ret2 = libusb_detach_kernel_driver(this->m_libusbHandle, i); + if (ret2 < LIBUSB_SUCCESS && ret2 != LIBUSB_ERROR_NOT_FOUND && + ret2 != LIBUSB_ERROR_NOT_SUPPORTED) + { + cemuLog_log(LogType::Force, "Failed to detach kernel driver {}", libusb_error_name(ret2)); + return ret2; + } + } + return libusb_claim_interface(this->m_libusbHandle, i); + }); + if (ret < LIBUSB_SUCCESS) + { + cemuLog_log(LogType::Force, "Failed to release all interfaces for config {}", config_num); + } + return ret; + } + + int DeviceLibusb::ReleaseAllInterfaces(uint8 config_num) + { + const int ret = DoForEachInterface(m_config_descriptors, config_num, [this](uint8 i) { + return libusb_release_interface(AquireHandleLock()->GetHandle(), i); + }); + if (ret < LIBUSB_SUCCESS && ret != LIBUSB_ERROR_NO_DEVICE && ret != LIBUSB_ERROR_NOT_FOUND) + { + cemuLog_log(LogType::Force, "Failed to release all interfaces for config {}", config_num); + } + return ret; + } + + int DeviceLibusb::ReleaseAllInterfacesForCurrentConfig() + { + int config_num; + const int get_config_ret = libusb_get_configuration(AquireHandleLock()->GetHandle(), &config_num); + if (get_config_ret < LIBUSB_SUCCESS) + return get_config_ret; + return ReleaseAllInterfaces(config_num); + } + + bool DeviceLibusb::SetProtocol(uint8 ifIndex, uint8 protocol) { auto handleLock = AquireHandleLock(); if (!handleLock->IsValid()) @@ -688,24 +767,18 @@ namespace nsyshid::backend::libusb cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): device is not opened"); return false; } + if (m_interfaceIndex != ifIndex) + m_interfaceIndex = ifIndex; - // ToDo: implement this -#if 0 - // is this correct? Discarding "ifIndex" seems like a bad idea - int ret = libusb_set_configuration(handleLock->getHandle(), protocol); - if (ret == 0) { - cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::setProtocol(): success"); + ReleaseAllInterfacesForCurrentConfig(); + int ret = libusb_set_configuration(AquireHandleLock()->GetHandle(), protocol); + if (ret == LIBUSB_SUCCESS) + ret = ClaimAllInterfaces(protocol); + + if (ret == LIBUSB_SUCCESS) return true; - } - cemuLog_logDebug(LogType::Force, - "nsyshid::DeviceLibusb::setProtocol(): failed with error code: {}", - ret); - return false; -#endif - // pretend that everything is fine - return true; + return false; } bool DeviceLibusb::SetReport(ReportMessage* message) @@ -717,20 +790,20 @@ namespace nsyshid::backend::libusb return false; } - // ToDo: implement this -#if 0 - // not sure if libusb_control_transfer() is the right candidate for this - int ret = libusb_control_transfer(handleLock->getHandle(), - bmRequestType, - bRequest, - wValue, - wIndex, - message->reportData, - message->length, - timeout); -#endif + int ret = libusb_control_transfer(handleLock->GetHandle(), + LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, + LIBUSB_REQUEST_SET_CONFIGURATION, + 512, + 0, + message->originalData, + message->originalLength, + 0); - // pretend that everything is fine + if (ret != message->originalLength) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetReport(): Control Transfer Failed: {}", libusb_error_name(ret)); + return false; + } return true; } diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.h b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h index a8122aff..a7b23769 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendLibusb.h +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h @@ -44,6 +44,11 @@ namespace nsyshid::backend::libusb bool& endpointOutFound, uint8& endpointOut, uint16& endpointOutMaxPacketSize); }; + template + using UniquePtr = std::unique_ptr; + + using ConfigDescriptor = UniquePtr; + class DeviceLibusb : public nsyshid::Device { public: DeviceLibusb(libusb_context* ctx, @@ -53,7 +58,8 @@ namespace nsyshid::backend::libusb uint8 interfaceSubClass, uint8 protocol, uint8 libusbBusNumber, - uint8 libusbDeviceAddress); + uint8 libusbDeviceAddress, + std::vector configs); ~DeviceLibusb() override; @@ -73,7 +79,11 @@ namespace nsyshid::backend::libusb uint8* output, uint32 outputMaxLength) override; - bool SetProtocol(uint32 ifIndex, uint32 protocol) override; + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; + + int ClaimAllInterfaces(uint8 config_num); + int ReleaseAllInterfaces(uint8 config_num); + int ReleaseAllInterfacesForCurrentConfig(); bool SetReport(ReportMessage* message) override; @@ -92,6 +102,7 @@ namespace nsyshid::backend::libusb std::atomic m_handleInUseCounter; std::condition_variable m_handleInUseCounterDecremented; libusb_device_handle* m_libusbHandle; + std::vector m_config_descriptors; class HandleLock { public: diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp index 3cfba26a..44e01399 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp @@ -400,7 +400,7 @@ namespace nsyshid::backend::windows return false; } - bool DeviceWindowsHID::SetProtocol(uint32 ifIndef, uint32 protocol) + bool DeviceWindowsHID::SetProtocol(uint8 ifIndex, uint8 protocol) { // ToDo: implement this // pretend that everything is fine diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h index 84fe7bda..9a8a78e9 100644 --- a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h @@ -47,7 +47,7 @@ namespace nsyshid::backend::windows bool GetDescriptor(uint8 descType, uint8 descIndex, uint8 lang, uint8* output, uint32 outputMaxLength) override; - bool SetProtocol(uint32 ifIndef, uint32 protocol) override; + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; bool SetReport(ReportMessage* message) override; diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.cpp b/src/Cafe/OS/libs/nsyshid/Skylander.cpp index 241e1969..7f17f8a3 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.cpp +++ b/src/Cafe/OS/libs/nsyshid/Skylander.cpp @@ -627,7 +627,7 @@ namespace nsyshid return true; } - bool SkylanderPortalDevice::SetProtocol(uint32 ifIndex, uint32 protocol) + bool SkylanderPortalDevice::SetProtocol(uint8 ifIndex, uint8 protocol) { return true; } diff --git a/src/Cafe/OS/libs/nsyshid/Skylander.h b/src/Cafe/OS/libs/nsyshid/Skylander.h index a1ca7f8f..ae8b5d92 100644 --- a/src/Cafe/OS/libs/nsyshid/Skylander.h +++ b/src/Cafe/OS/libs/nsyshid/Skylander.h @@ -30,7 +30,7 @@ namespace nsyshid uint8* output, uint32 outputMaxLength) override; - bool SetProtocol(uint32 ifIndex, uint32 protocol) override; + bool SetProtocol(uint8 ifIndex, uint8 protocol) override; bool SetReport(ReportMessage* message) override; diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp index c674b844..99a736d9 100644 --- a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp @@ -379,8 +379,8 @@ namespace nsyshid void export_HIDSetProtocol(PPCInterpreter_t* hCPU) { ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamU32(ifIndex, 1); // r4 - ppcDefineParamU32(protocol, 2); // r5 + ppcDefineParamU8(ifIndex, 1); // r4 + ppcDefineParamU8(protocol, 2); // r5 ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(...)");