diff --git a/Source/Core/Core/IOS/USB/USBV5.cpp b/Source/Core/Core/IOS/USB/USBV5.cpp index f0356d4bb6..d4712c9488 100644 --- a/Source/Core/Core/IOS/USB/USBV5.cpp +++ b/Source/Core/Core/IOS/USB/USBV5.cpp @@ -64,7 +64,7 @@ namespace #pragma pack(push, 1) struct DeviceID { - u8 ipc_address_shifted; + u8 reserved; u8 index; u16 number; }; @@ -263,10 +263,17 @@ void USBV5ResourceManager::TriggerDeviceChangeReply() continue; DeviceEntry entry; - // The actual value is static_cast(hook_internal_ipc_request >> 8). - // Since we don't actually emulate the IOS kernel and internal IPC, - // just pretend the value is 0xe7 (most common value according to hwtests). - entry.id.ipc_address_shifted = 0xe7; + if (HasInterfaceNumberInIDs()) + { + entry.id.reserved = usbv5_device.interface_number; + } + else + { + // The actual value is static_cast(hook_internal_ipc_request >> 8). + // Since we don't actually emulate the IOS kernel and internal IPC, + // just pretend the value is 0xe7 (most common value according to hwtests). + entry.id.reserved = 0xe7; + } entry.id.index = static_cast(std::distance(m_usbv5_devices.cbegin(), it.base()) - 1); entry.id.number = Common::swap16(usbv5_device.number); entry.vid = Common::swap16(device->GetVid()); diff --git a/Source/Core/Core/IOS/USB/USBV5.h b/Source/Core/Core/IOS/USB/USBV5.h index 47db9da9dc..fbfde46fb3 100644 --- a/Source/Core/Core/IOS/USB/USBV5.h +++ b/Source/Core/Core/IOS/USB/USBV5.h @@ -94,6 +94,7 @@ protected: void OnDeviceChange(ChangeEvent event, std::shared_ptr device) override; void OnDeviceChangeEnd() override; void TriggerDeviceChangeReply(); + virtual bool HasInterfaceNumberInIDs() const = 0; bool m_devicechange_first_call = true; std::mutex m_devicechange_hook_address_mutex; diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp index d51d648c66..dfc12f6b6a 100644 --- a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp @@ -4,12 +4,15 @@ #include "Core/IOS/USB/USB_HID/HIDv5.h" -#include +#include +#include +#include +#include +#include "Common/CommonTypes.h" +#include "Common/Logging/Log.h" #include "Core/HW/Memmap.h" -#include "Core/IOS/Device.h" #include "Core/IOS/USB/Common.h" -#include "Core/IOS/USB/USBV5.h" namespace IOS { @@ -17,14 +20,9 @@ namespace HLE { namespace Device { -USB_HIDv5::USB_HIDv5(Kernel& ios, const std::string& device_name) : USBHost(ios, device_name) -{ -} +constexpr u32 USBV5_VERSION = 0x50001; -USB_HIDv5::~USB_HIDv5() -{ - StopThreads(); -} +USB_HIDv5::~USB_HIDv5() = default; IPCCommandResult USB_HIDv5::IOCtl(const IOCtlRequest& request) { @@ -32,32 +30,120 @@ IPCCommandResult USB_HIDv5::IOCtl(const IOCtlRequest& request) switch (request.request) { case USB::IOCTL_USBV5_GETVERSION: - Memory::Write_U32(VERSION, request.buffer_out); - return GetDefaultReply(IPC_SUCCESS); - case USB::IOCTL_USBV5_SHUTDOWN: - if (m_hanging_request) - { - IOCtlRequest hanging_request{m_hanging_request}; - m_ios.EnqueueIPCReply(hanging_request, IPC_SUCCESS); - } + Memory::Write_U32(USBV5_VERSION, request.buffer_out); return GetDefaultReply(IPC_SUCCESS); case USB::IOCTL_USBV5_GETDEVICECHANGE: - if (m_devicechange_replied) - { - m_hanging_request = request.address; - return GetNoReply(); - } - else - { - m_devicechange_replied = true; - return GetDefaultReply(IPC_SUCCESS); - } + return GetDeviceChange(request); + case USB::IOCTL_USBV5_SHUTDOWN: + return Shutdown(request); + case USB::IOCTL_USBV5_GETDEVPARAMS: + return HandleDeviceIOCtl(request, [&](auto& device) { return GetDeviceInfo(device, request); }); + case USB::IOCTL_USBV5_ATTACHFINISH: + return GetDefaultReply(IPC_SUCCESS); + case USB::IOCTL_USBV5_SUSPEND_RESUME: + return HandleDeviceIOCtl(request, [&](auto& device) { return SuspendResume(device, request); }); + case USB::IOCTL_USBV5_CANCELENDPOINT: + return HandleDeviceIOCtl(request, + [&](auto& device) { return CancelEndpoint(device, request); }); default: - request.DumpUnknown(GetDeviceName(), LogTypes::IOS_USB); + request.DumpUnknown(GetDeviceName(), LogTypes::IOS_USB, LogTypes::LERROR); return GetDefaultReply(IPC_SUCCESS); } } +IPCCommandResult USB_HIDv5::IOCtlV(const IOCtlVRequest& request) +{ + request.DumpUnknown(GetDeviceName(), LogTypes::IOS_USB); + switch (request.request) + { + // TODO: HIDv5 seems to be able to queue transfers depending on the transfer length (unlike VEN). + case USB::IOCTLV_USBV5_CTRLMSG: + case USB::IOCTLV_USBV5_INTRMSG: + { + // IOS does not check the number of vectors, but let's do that to avoid out-of-bounds reads. + if (request.in_vectors.size() + request.io_vectors.size() != 2) + return GetDefaultReply(IPC_EINVAL); + + std::lock_guard lock{m_usbv5_devices_mutex}; + USBV5Device* device = GetUSBV5Device(request.in_vectors[0].address); + if (!device) + return GetDefaultReply(IPC_EINVAL); + auto host_device = GetDeviceById(device->host_id); + host_device->Attach(device->interface_number); + return HandleTransfer(host_device, request.request, + [&, this]() { return SubmitTransfer(*host_device, request); }); + } + default: + return GetDefaultReply(IPC_EINVAL); + } +} + +IPCCommandResult USB_HIDv5::CancelEndpoint(USBV5Device& device, const IOCtlRequest& request) +{ + // FIXME: Unlike VEN, there are 3 valid values for the endpoint, + // which determine the endpoint address that gets passed to the backend. + // Valid values: 0 (control, endpoint 0), 1 (interrupt IN) and 2 (interrupt OUT) + // This ioctl also cancels all queued transfers with return code -7022. + request.DumpUnknown(GetDeviceName(), LogTypes::IOS_USB); + const u8 endpoint = static_cast(Memory::Read_U32(request.buffer_in + 8)); + GetDeviceById(device.host_id)->CancelTransfer(endpoint); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult USB_HIDv5::GetDeviceInfo(USBV5Device& device, const IOCtlRequest& request) +{ + if (request.buffer_out == 0 || request.buffer_out_size != 0x60) + return GetDefaultReply(IPC_EINVAL); + + const std::shared_ptr host_device = GetDeviceById(device.host_id); + const u8 alt_setting = Memory::Read_U8(request.buffer_in + 8); + + Memory::Memset(request.buffer_out, 0, request.buffer_out_size); + Memory::Write_U32(Memory::Read_U32(request.buffer_in), request.buffer_out); + Memory::Write_U32(1, request.buffer_out + 4); + + USB::DeviceDescriptor device_descriptor = host_device->GetDeviceDescriptor(); + device_descriptor.Swap(); + Memory::CopyToEmu(request.buffer_out + 36, &device_descriptor, sizeof(device_descriptor)); + + // Just like VEN, HIDv5 only cares about the first configuration. + USB::ConfigDescriptor config_descriptor = host_device->GetConfigurations()[0]; + config_descriptor.Swap(); + Memory::CopyToEmu(request.buffer_out + 56, &config_descriptor, sizeof(config_descriptor)); + + std::vector interfaces = host_device->GetInterfaces(0); + auto it = std::find_if(interfaces.begin(), interfaces.end(), [&](const auto& interface) { + return interface.bInterfaceNumber == device.interface_number && + interface.bAlternateSetting == alt_setting; + }); + if (it == interfaces.end()) + return GetDefaultReply(IPC_EINVAL); + it->Swap(); + Memory::CopyToEmu(request.buffer_out + 68, &*it, sizeof(*it)); + + auto endpoints = host_device->GetEndpoints(0, it->bInterfaceNumber, it->bAlternateSetting); + for (auto& endpoint : endpoints) + { + constexpr u8 ENDPOINT_INTERRUPT = 0b11; + constexpr u8 ENDPOINT_IN = 0x80; + if (endpoint.bmAttributes == ENDPOINT_INTERRUPT) + { + const u32 offset = (endpoint.bEndpointAddress & ENDPOINT_IN) != 0 ? 80 : 88; + endpoint.Swap(); + Memory::CopyToEmu(request.buffer_out + offset, &endpoint, sizeof(endpoint)); + } + } + + return GetDefaultReply(IPC_SUCCESS); +} + +bool USB_HIDv5::ShouldAddDevice(const USB::Device& device) const +{ + // XXX: HIDv5 opens /dev/usb/usb with mode 3 (which is likely HID_CLASS), + // unlike VEN (which opens it with mode 0xff). But is this really correct? + constexpr u8 HID_CLASS = 0x03; + return device.HasClass(HID_CLASS); +} } // namespace Device } // namespace HLE } // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.h b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.h index 069651ff6e..625a810f54 100644 --- a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.h +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.h @@ -4,11 +4,10 @@ #pragma once -#include - -#include "Common/CommonTypes.h" +#include "Core/IOS/Device.h" #include "Core/IOS/IOS.h" #include "Core/IOS/USB/Host.h" +#include "Core/IOS/USB/USBV5.h" namespace IOS { @@ -16,22 +15,22 @@ namespace HLE { namespace Device { -// Stub implementation that only gets DQX to boot. -class USB_HIDv5 : public USBHost +class USB_HIDv5 final : public USBV5ResourceManager { public: - USB_HIDv5(Kernel& ios, const std::string& device_name); + using USBV5ResourceManager::USBV5ResourceManager; ~USB_HIDv5() override; IPCCommandResult IOCtl(const IOCtlRequest& request) override; + IPCCommandResult IOCtlV(const IOCtlVRequest& request) override; private: - static constexpr u32 VERSION = 0x50001; + IPCCommandResult CancelEndpoint(USBV5Device& device, const IOCtlRequest& request); + IPCCommandResult GetDeviceInfo(USBV5Device& device, const IOCtlRequest& request); - u32 m_hanging_request = 0; - bool m_devicechange_replied = false; + bool ShouldAddDevice(const USB::Device& device) const override; + bool HasInterfaceNumberInIDs() const override { return true; } }; - } // namespace Device } // namespace HLE } // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USB_VEN/VEN.h b/Source/Core/Core/IOS/USB/USB_VEN/VEN.h index a246152bb9..58c4af767e 100644 --- a/Source/Core/Core/IOS/USB/USB_VEN/VEN.h +++ b/Source/Core/Core/IOS/USB/USB_VEN/VEN.h @@ -28,6 +28,8 @@ public: private: IPCCommandResult CancelEndpoint(USBV5Device& device, const IOCtlRequest& request); IPCCommandResult GetDeviceInfo(USBV5Device& device, const IOCtlRequest& request); + + bool HasInterfaceNumberInIDs() const override { return false; } }; } // namespace Device } // namespace HLE