From ef8b3cb96054df790cdb6b5703b5517a67f2953f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Thu, 2 Nov 2017 19:11:06 +0100 Subject: [PATCH 1/9] USB_VEN: Construct device IDs properly Fix the device ID struct to reflect the actual structure used by IOS. It turns out that offset 2 is the internal device index. The reason that field seemed to be "0x1e - interface_number" is that IOS only keeps track of 32 devices and always looks for free entries from the end of the internal array. With each USB interface being exposed as a separate USBv5 device, "0x1e - interface_number" was mostly correct... but wrong! We also made the assumption that the interface number can be identified from just a USBV5 device ID, which is definitely not true. VEN (and HID) keep track of the interface number in the internal struct instead of "reconstructing" it from the device ID (which is normally not possible if we were generating IDs correctly) This commit fixes all of these inaccuracies. --- Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp | 179 ++++++++++++----------- Source/Core/Core/IOS/USB/USB_VEN/VEN.h | 55 +++---- 2 files changed, 116 insertions(+), 118 deletions(-) diff --git a/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp b/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp index 955710f176..a61fcc5f84 100644 --- a/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp +++ b/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp @@ -4,6 +4,7 @@ #include "Core/IOS/USB/USB_VEN/VEN.h" +#include #include #include #include @@ -26,6 +27,28 @@ namespace HLE { namespace Device { +namespace +{ +#pragma pack(push, 1) +struct DeviceID +{ + u8 ipc_address_shifted; + u8 index; + u16 number; +}; + +struct DeviceEntry +{ + DeviceID id; + u16 vid; + u16 pid; + u16 number; + u8 interface_number; + u8 num_altsettings; +}; +#pragma pack(pop) +} + USB_VEN::USB_VEN(Kernel& ios, const std::string& device_name) : USBHost(ios, device_name) { } @@ -90,12 +113,14 @@ IPCCommandResult USB_VEN::IOCtlV(const IOCtlVRequest& request) if (request.in_vectors.size() + request.io_vectors.size() != s_num_vectors.at(request.request)) return GetDefaultReply(IPC_EINVAL); - const s32 device_id = Memory::Read_U32(request.in_vectors[0].address); - auto device = GetDeviceByIOSID(device_id); - if (!device || !device->Attach(GetInterfaceNumber(device_id))) - return GetDefaultReply(IPC_ENOENT); - return HandleTransfer(device, request.request, - [&, this]() { return SubmitTransfer(*device, request); }); + 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); @@ -112,38 +137,37 @@ void USB_VEN::DoState(PointerWrap& p) else m_devicechange_hook_request.reset(); - p.Do(m_device_number); - p.Do(m_ios_ids); - p.Do(m_device_ids); + p.Do(m_usbv5_devices); USBHost::DoState(p); } -std::shared_ptr USB_VEN::GetDeviceByIOSID(const s32 ios_id) const +USB_VEN::USBV5Device* USB_VEN::GetUSBV5Device(u32 in_buffer) { - std::lock_guard lk{m_id_map_mutex}; - const auto iter = m_ios_ids.find(ios_id); - if (iter == m_ios_ids.cend()) + const u8 index = Memory::Read_U8(in_buffer + offsetof(DeviceID, index)); + const u16 number = Memory::Read_U16(in_buffer + offsetof(DeviceID, number)); + + if (index >= m_usbv5_devices.size()) return nullptr; - return GetDeviceById(iter->second); + + USBV5Device* usbv5_device = &m_usbv5_devices[index]; + if (!usbv5_device->in_use || usbv5_device->number != number) + return nullptr; + + return usbv5_device; } -u8 USB_VEN::GetInterfaceNumber(const s32 ios_id) const +IPCCommandResult USB_VEN::CancelEndpoint(USBV5Device& device, const IOCtlRequest& request) { - const s32 id = Common::swap32(ios_id); - DeviceID device_id; - std::memcpy(&device_id, &id, sizeof(id)); - return device_id.interface_plus_1e - 0x1e; -} - -IPCCommandResult USB_VEN::CancelEndpoint(USB::Device& device, const IOCtlRequest& request) -{ - const u8 endpoint = static_cast(Memory::Read_U32(request.buffer_in + 2 * sizeof(s32))); - device.CancelTransfer(endpoint); + const u8 endpoint = static_cast(Memory::Read_U32(request.buffer_in + 8)); + GetDeviceById(device.host_id)->CancelTransfer(endpoint); return GetDefaultReply(IPC_SUCCESS); } IPCCommandResult USB_VEN::GetDeviceChange(const IOCtlRequest& request) { + if (request.buffer_out_size != 0x180 || m_devicechange_hook_request) + return GetDefaultReply(IPC_EINVAL); + std::lock_guard lk{m_devicechange_hook_address_mutex}; m_devicechange_hook_request = std::make_unique(request.address); // On the first call, the reply is sent immediately (instead of on device insertion/removal) @@ -155,14 +179,14 @@ IPCCommandResult USB_VEN::GetDeviceChange(const IOCtlRequest& request) return GetNoReply(); } -IPCCommandResult USB_VEN::GetDeviceInfo(USB::Device& device, const IOCtlRequest& request) +IPCCommandResult USB_VEN::GetDeviceInfo(USBV5Device& device, const IOCtlRequest& request) { - const s32 device_id = Memory::Read_U32(request.buffer_in); if (request.buffer_out == 0 || request.buffer_out_size != 0xc0) return GetDefaultReply(IPC_EINVAL); + const auto host_device = GetDeviceById(device.host_id); const u8 alt_setting = Memory::Read_U8(request.buffer_in + 8); - auto descriptors = device.GetDescriptorsUSBV5(GetInterfaceNumber(device_id), alt_setting); + auto descriptors = host_device->GetDescriptorsUSBV5(device.interface_number, alt_setting); if (descriptors.empty()) return GetDefaultReply(IPC_ENOENT); @@ -171,22 +195,22 @@ IPCCommandResult USB_VEN::GetDeviceInfo(USB::Device& device, const IOCtlRequest& WARN_LOG(IOS_USB, "Buffer is too large. Only the first 172 bytes will be copied."); Memory::Memset(request.buffer_out, 0, request.buffer_out_size); - Memory::Write_U32(device_id, request.buffer_out); + Memory::Write_U32(Memory::Read_U32(request.buffer_in), request.buffer_out); Memory::Write_U32(1, request.buffer_out + 4); Memory::CopyToEmu(request.buffer_out + 20, descriptors.data(), descriptors.size()); return GetDefaultReply(IPC_SUCCESS); } -IPCCommandResult USB_VEN::SetAlternateSetting(USB::Device& device, const IOCtlRequest& request) +IPCCommandResult USB_VEN::SetAlternateSetting(USBV5Device& device, const IOCtlRequest& request) { - const s32 device_id = Memory::Read_U32(request.buffer_in); - if (!device.Attach(GetInterfaceNumber(device_id))) + const auto host_device = GetDeviceById(device.host_id); + if (!host_device->Attach(device.interface_number)) return GetDefaultReply(-1); const u8 alt_setting = Memory::Read_U8(request.buffer_in + 2 * sizeof(s32)); - const bool success = device.SetAltSetting(alt_setting) == 0; + const bool success = host_device->SetAltSetting(alt_setting) == 0; return GetDefaultReply(success ? IPC_SUCCESS : IPC_EINVAL); } @@ -207,15 +231,15 @@ IPCCommandResult USB_VEN::Shutdown(const IOCtlRequest& request) return GetDefaultReply(IPC_SUCCESS); } -IPCCommandResult USB_VEN::SuspendResume(USB::Device& device, const IOCtlRequest& request) +IPCCommandResult USB_VEN::SuspendResume(USBV5Device& device, const IOCtlRequest& request) { - const s32 device_id = Memory::Read_U32(request.buffer_in); - const s32 resumed = Memory::Read_U32(request.buffer_in + 2 * sizeof(s32)); + const auto host_device = GetDeviceById(device.host_id); + const s32 resumed = Memory::Read_U32(request.buffer_in + 8); // Note: this is unimplemented because there's no easy way to do this in a // platform-independant way (libusb does not support power management). - INFO_LOG(IOS_USB, "[%04x:%04x %d] Received %s command", device.GetVid(), device.GetPid(), - GetInterfaceNumber(device_id), resumed == 0 ? "suspend" : "resume"); + INFO_LOG(IOS_USB, "[%04x:%04x %d] Received %s command", host_device->GetVid(), + host_device->GetPid(), device.interface_number, resumed == 0 ? "suspend" : "resume"); return GetDefaultReply(IPC_SUCCESS); } @@ -241,16 +265,17 @@ IPCCommandResult USB_VEN::HandleDeviceIOCtl(const IOCtlRequest& request, Handler if (request.buffer_in == 0 || request.buffer_in_size != 0x20) return GetDefaultReply(IPC_EINVAL); - const s32 device_id = Memory::Read_U32(request.buffer_in); - const auto device = GetDeviceByIOSID(device_id); + std::lock_guard lock{m_usbv5_devices_mutex}; + USBV5Device* device = GetUSBV5Device(request.buffer_in); if (!device) - return GetDefaultReply(IPC_ENOENT); + return GetDefaultReply(IPC_EINVAL); return handler(this, *device, request); } void USB_VEN::OnDeviceChange(const ChangeEvent event, std::shared_ptr device) { - std::lock_guard id_map_lock{m_id_map_mutex}; + std::lock_guard lock{m_usbv5_devices_mutex}; + const u64 host_device_id = device->GetId(); if (event == ChangeEvent::Inserted) { for (const auto& interface : device->GetInterfaces(0)) @@ -258,24 +283,24 @@ void USB_VEN::OnDeviceChange(const ChangeEvent event, std::shared_ptrGetId(); - m_device_ids[device->GetId()].insert(ios_device_id); + it->in_use = true; + it->interface_number = interface.bInterfaceNumber; + it->number = m_current_device_number; + it->host_id = host_device_id; } } else if (event == ChangeEvent::Removed) { - for (const s32 ios_id : m_device_ids[device->GetId()]) - m_ios_ids.erase(ios_id); - m_device_ids.erase(device->GetId()); + for (USBV5Device& entry : m_usbv5_devices) + { + if (entry.host_id == host_device_id) + entry.in_use = false; + } } } @@ -283,7 +308,7 @@ void USB_VEN::OnDeviceChangeEnd() { std::lock_guard lk{m_devicechange_hook_address_mutex}; TriggerDeviceChangeReply(); - ++m_device_number; + ++m_current_device_number; } void USB_VEN::TriggerDeviceChangeReply() @@ -291,40 +316,30 @@ void USB_VEN::TriggerDeviceChangeReply() if (!m_devicechange_hook_request) return; - std::lock_guard id_map_lock{m_id_map_mutex}; + std::lock_guard lock{m_usbv5_devices_mutex}; u8 num_devices = 0; - const size_t max_num = m_devicechange_hook_request->buffer_out_size / sizeof(DeviceEntry); - for (const auto& ios_device : m_ios_ids) + for (auto it = m_usbv5_devices.crbegin(); it != m_usbv5_devices.crend(); ++it) { - if (num_devices >= max_num) - { - WARN_LOG(IOS_USB, "Too many devices (%d ≥ %zu), skipping", num_devices, max_num); - break; - } + const USBV5Device& usbv5_device = *it; + if (!usbv5_device.in_use) + continue; - const s32 ios_device_id = ios_device.first; - const auto device = GetDeviceById(m_ios_ids.at(ios_device_id)); + const auto device = GetDeviceById(usbv5_device.host_id); if (!device) continue; - const u8 interface_number = GetInterfaceNumber(ios_device_id); - - // IOS's device list contains entries of the form: - // e7 XX 00 YY VV VV PP PP 00 YY DD AA - // ^^^^^^^^^^^ ^^^^^ ^^^^^ ^^ ^^^^^ ^^ - // Device ID VID PID ?? See ID Number of alt settings - // - // XX is 1e (for a device plugged in to the left port) + DD (interface number). - // YY is a counter that starts at 21 and is incremented on every device change. - // DD is the interface number (since VEN exposes each interface as a separate device). DeviceEntry entry; - entry.device_id = Common::swap32(ios_device_id); + // 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; + 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()); entry.pid = Common::swap16(device->GetPid()); - entry.unknown = 0x00; - entry.device_number = ios_device_id & 0xff; - entry.interface_number = interface_number; - entry.num_altsettings = device->GetNumberOfAltSettings(interface_number); + entry.number = Common::swap16(usbv5_device.number); + entry.interface_number = usbv5_device.interface_number; + entry.num_altsettings = device->GetNumberOfAltSettings(entry.interface_number); Memory::CopyToEmu(m_devicechange_hook_request->buffer_out + sizeof(entry) * num_devices++, &entry, sizeof(entry)); @@ -332,7 +347,7 @@ void USB_VEN::TriggerDeviceChangeReply() m_ios.EnqueueIPCReply(*m_devicechange_hook_request, num_devices, 0, CoreTiming::FromThread::ANY); m_devicechange_hook_request.reset(); - INFO_LOG(IOS_USB, "%d device(s), including interfaces", num_devices); + INFO_LOG(IOS_USB, "%d USBv5 device(s), including interfaces", num_devices); } } // namespace Device } // namespace HLE diff --git a/Source/Core/Core/IOS/USB/USB_VEN/VEN.h b/Source/Core/Core/IOS/USB/USB_VEN/VEN.h index ed8d71a8d7..1fe96a2b90 100644 --- a/Source/Core/Core/IOS/USB/USB_VEN/VEN.h +++ b/Source/Core/Core/IOS/USB/USB_VEN/VEN.h @@ -5,10 +5,8 @@ #pragma once #include -#include #include #include -#include #include #include @@ -38,39 +36,18 @@ public: void DoState(PointerWrap& p) override; private: -#pragma pack(push, 1) - struct DeviceID - { - u8 unknown; - u8 interface_plus_1e; - u8 zero; - u8 counter; - }; + struct USBV5Device; + USBV5Device* GetUSBV5Device(u32 in_buffer); - struct DeviceEntry - { - s32 device_id; - u16 vid; - u16 pid; - u8 unknown; - u8 device_number; - u8 interface_number; - u8 num_altsettings; - }; -#pragma pack(pop) - - std::shared_ptr GetDeviceByIOSID(s32 ios_id) const; - u8 GetInterfaceNumber(s32 ios_id) const; - - IPCCommandResult CancelEndpoint(USB::Device& device, const IOCtlRequest& request); + IPCCommandResult CancelEndpoint(USBV5Device& device, const IOCtlRequest& request); IPCCommandResult GetDeviceChange(const IOCtlRequest& request); - IPCCommandResult GetDeviceInfo(USB::Device& device, const IOCtlRequest& request); - IPCCommandResult SetAlternateSetting(USB::Device& device, const IOCtlRequest& request); + IPCCommandResult GetDeviceInfo(USBV5Device& device, const IOCtlRequest& request); + IPCCommandResult SetAlternateSetting(USBV5Device& device, const IOCtlRequest& request); IPCCommandResult Shutdown(const IOCtlRequest& request); - IPCCommandResult SuspendResume(USB::Device& device, const IOCtlRequest& request); + IPCCommandResult SuspendResume(USBV5Device& device, const IOCtlRequest& request); s32 SubmitTransfer(USB::Device& device, const IOCtlVRequest& request); - using Handler = std::function; + using Handler = std::function; IPCCommandResult HandleDeviceIOCtl(const IOCtlRequest& request, Handler handler); void OnDeviceChange(ChangeEvent, std::shared_ptr) override; @@ -83,12 +60,18 @@ private: std::mutex m_devicechange_hook_address_mutex; std::unique_ptr m_devicechange_hook_request; - mutable std::mutex m_id_map_mutex; - u8 m_device_number = 0x21; - // IOS device IDs => USB device IDs (one to one) - std::map m_ios_ids; - // USB device IDs => IOS device IDs (one to many, because VEN exposes one device per interface) - std::map> m_device_ids; + // Each interface of a USB device is internally considered as a unique device. + // USBv5 resource managers can handle up to 32 devices/interfaces. + struct USBV5Device + { + bool in_use = false; + u8 interface_number; + u16 number; + u64 host_id; + }; + std::array m_usbv5_devices; + mutable std::mutex m_usbv5_devices_mutex; + u16 m_current_device_number = 0x21; }; } // namespace Device } // namespace HLE From c6038155cc71256ef277746abc47624466cdb7f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Thu, 2 Nov 2017 22:01:38 +0100 Subject: [PATCH 2/9] USB_VEN: Move internal constant to .cpp --- Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp | 4 +++- Source/Core/Core/IOS/USB/USB_VEN/VEN.h | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp b/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp index a61fcc5f84..552d278863 100644 --- a/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp +++ b/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp @@ -49,6 +49,8 @@ struct DeviceEntry #pragma pack(pop) } +constexpr u32 USBV5_VERSION = 0x50001; + USB_VEN::USB_VEN(Kernel& ios, const std::string& device_name) : USBHost(ios, device_name) { } @@ -72,7 +74,7 @@ IPCCommandResult USB_VEN::IOCtl(const IOCtlRequest& request) switch (request.request) { case USB::IOCTL_USBV5_GETVERSION: - Memory::Write_U32(VERSION, request.buffer_out); + Memory::Write_U32(USBV5_VERSION, request.buffer_out); return GetDefaultReply(IPC_SUCCESS); case USB::IOCTL_USBV5_GETDEVICECHANGE: return GetDeviceChange(request); diff --git a/Source/Core/Core/IOS/USB/USB_VEN/VEN.h b/Source/Core/Core/IOS/USB/USB_VEN/VEN.h index 1fe96a2b90..3ec4d62445 100644 --- a/Source/Core/Core/IOS/USB/USB_VEN/VEN.h +++ b/Source/Core/Core/IOS/USB/USB_VEN/VEN.h @@ -54,8 +54,6 @@ private: void OnDeviceChangeEnd() override; void TriggerDeviceChangeReply(); - static constexpr u32 VERSION = 0x50001; - bool m_devicechange_first_call = true; std::mutex m_devicechange_hook_address_mutex; std::unique_ptr m_devicechange_hook_request; From 6bb03d900c3bb61e4fb8b855f06de8dd3d9eebc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Thu, 2 Nov 2017 21:56:16 +0100 Subject: [PATCH 3/9] USB: Separate the descriptor copy logic The descriptor copy code is not actually the same in HIDv4 and VEN, so it did not make a lot of sense to put it in USB/Common.cpp. Separate and move it to HIDv4 and VEN. This cleanup is important because there are even more differences between HIDv4 and HIDv5. --- Source/Core/Core/IOS/USB/Common.cpp | 77 +++------------------- Source/Core/Core/IOS/USB/Common.h | 7 +- Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp | 36 +++++++++- Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp | 37 ++++++++--- 4 files changed, 77 insertions(+), 80 deletions(-) diff --git a/Source/Core/Core/IOS/USB/Common.cpp b/Source/Core/Core/IOS/USB/Common.cpp index a9627b0974..a91ac518b1 100644 --- a/Source/Core/Core/IOS/USB/Common.cpp +++ b/Source/Core/Core/IOS/USB/Common.cpp @@ -6,7 +6,6 @@ #include -#include "Common/Align.h" #include "Common/Assert.h" #include "Common/CommonTypes.h" #include "Common/StringUtil.h" @@ -70,82 +69,26 @@ bool Device::HasClass(const u8 device_class) const }); } -static void CopyToBufferAligned(std::vector* buffer, const void* data, const size_t size) +void DeviceDescriptor::Swap() { - buffer->insert(buffer->end(), static_cast(data), static_cast(data) + size); - const size_t number_of_padding_bytes = Common::AlignUp(size, 4) - size; - buffer->insert(buffer->end(), number_of_padding_bytes, 0); + bcdUSB = Common::swap16(bcdUSB); + idVendor = Common::swap16(idVendor); + idProduct = Common::swap16(idProduct); + bcdDevice = Common::swap16(bcdDevice); } -static void CopyDescriptorToBuffer(std::vector* buffer, DeviceDescriptor descriptor) +void ConfigDescriptor::Swap() { - descriptor.bcdUSB = Common::swap16(descriptor.bcdUSB); - descriptor.idVendor = Common::swap16(descriptor.idVendor); - descriptor.idProduct = Common::swap16(descriptor.idProduct); - descriptor.bcdDevice = Common::swap16(descriptor.bcdDevice); - CopyToBufferAligned(buffer, &descriptor, descriptor.bLength); + wTotalLength = Common::swap16(wTotalLength); } -static void CopyDescriptorToBuffer(std::vector* buffer, ConfigDescriptor descriptor) +void InterfaceDescriptor::Swap() { - descriptor.wTotalLength = Common::swap16(descriptor.wTotalLength); - CopyToBufferAligned(buffer, &descriptor, descriptor.bLength); } -static void CopyDescriptorToBuffer(std::vector* buffer, InterfaceDescriptor descriptor) +void EndpointDescriptor::Swap() { - CopyToBufferAligned(buffer, &descriptor, descriptor.bLength); -} - -static void CopyDescriptorToBuffer(std::vector* buffer, EndpointDescriptor descriptor) -{ - descriptor.wMaxPacketSize = Common::swap16(descriptor.wMaxPacketSize); - // IOS only copies 8 bytes from the endpoint descriptor, regardless of the actual length - CopyToBufferAligned(buffer, &descriptor, sizeof(descriptor)); -} - -std::vector Device::GetDescriptorsUSBV4() const -{ - return GetDescriptors([](const auto& descriptor) { return true; }); -} - -std::vector Device::GetDescriptorsUSBV5(const u8 interface, const u8 alt_setting) const -{ - return GetDescriptors([interface, alt_setting](const auto& descriptor) { - // The USBV5 interfaces present each interface as a different device, - // and the descriptors are filtered by alternate setting. - return descriptor.bInterfaceNumber == interface && descriptor.bAlternateSetting == alt_setting; - }); -} - -std::vector -Device::GetDescriptors(std::function predicate) const -{ - std::vector buffer; - - const auto device_descriptor = GetDeviceDescriptor(); - CopyDescriptorToBuffer(&buffer, device_descriptor); - - const auto configurations = GetConfigurations(); - for (size_t c = 0; c < configurations.size(); ++c) - { - const auto& config_descriptor = configurations[c]; - CopyDescriptorToBuffer(&buffer, config_descriptor); - - const auto interfaces = GetInterfaces(static_cast(c)); - for (size_t i = interfaces.size(); i-- > 0;) - { - const auto& descriptor = interfaces[i]; - if (!predicate(descriptor)) - continue; - - CopyDescriptorToBuffer(&buffer, descriptor); - for (const auto& endpoint_descriptor : GetEndpoints( - static_cast(c), descriptor.bInterfaceNumber, descriptor.bAlternateSetting)) - CopyDescriptorToBuffer(&buffer, endpoint_descriptor); - } - } - return buffer; + wMaxPacketSize = Common::swap16(wMaxPacketSize); } std::string Device::GetErrorName(const int error_code) const diff --git a/Source/Core/Core/IOS/USB/Common.h b/Source/Core/Core/IOS/USB/Common.h index 9efb6630a1..2013716a07 100644 --- a/Source/Core/Core/IOS/USB/Common.h +++ b/Source/Core/Core/IOS/USB/Common.h @@ -44,6 +44,7 @@ constexpr u16 USBHDR(u8 dir, u8 type, u8 recipient, u8 request) struct DeviceDescriptor { + void Swap(); u8 bLength; u8 bDescriptorType; u16 bcdUSB; @@ -62,6 +63,7 @@ struct DeviceDescriptor struct ConfigDescriptor { + void Swap(); u8 bLength; u8 bDescriptorType; u16 wTotalLength; @@ -74,6 +76,7 @@ struct ConfigDescriptor struct InterfaceDescriptor { + void Swap(); u8 bLength; u8 bDescriptorType; u8 bInterfaceNumber; @@ -87,6 +90,7 @@ struct InterfaceDescriptor struct EndpointDescriptor { + void Swap(); u8 bLength; u8 bDescriptorType; u8 bEndpointAddress; @@ -158,8 +162,6 @@ public: u16 GetVid() const; u16 GetPid() const; bool HasClass(u8 device_class) const; - std::vector GetDescriptorsUSBV4() const; - std::vector GetDescriptorsUSBV5(u8 interface, u8 alt_setting) const; virtual DeviceDescriptor GetDeviceDescriptor() const = 0; virtual std::vector GetConfigurations() const = 0; @@ -178,7 +180,6 @@ public: virtual int SubmitTransfer(std::unique_ptr message) = 0; protected: - std::vector GetDescriptors(std::function predicate) const; u64 m_id = 0xFFFFFFFFFFFFFFFF; }; } // namespace USB diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp b/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp index edcf8b18c3..b0a1606e6c 100644 --- a/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp @@ -210,6 +210,40 @@ void USB_HIDv4::TriggerDeviceChangeReply() m_devicechange_hook_request.reset(); } +template +static void CopyDescriptorToBuffer(std::vector* buffer, T descriptor) +{ + const size_t size = sizeof(descriptor); + descriptor.Swap(); + buffer->insert(buffer->end(), reinterpret_cast(&descriptor), + reinterpret_cast(&descriptor) + size); + const size_t number_of_padding_bytes = Common::AlignUp(size, 4) - size; + buffer->insert(buffer->end(), number_of_padding_bytes, 0); +} + +static std::vector GetDescriptors(const USB::Device& device) +{ + std::vector buffer; + + CopyDescriptorToBuffer(&buffer, device.GetDeviceDescriptor()); + const auto configurations = device.GetConfigurations(); + for (size_t c = 0; c < configurations.size(); ++c) + { + CopyDescriptorToBuffer(&buffer, configurations[c]); + const auto interfaces = device.GetInterfaces(static_cast(c)); + for (size_t i = interfaces.size(); i-- > 0;) + { + CopyDescriptorToBuffer(&buffer, interfaces[i]); + for (const auto& endpoint_descriptor : device.GetEndpoints( + static_cast(c), interfaces[i].bInterfaceNumber, interfaces[i].bAlternateSetting)) + { + CopyDescriptorToBuffer(&buffer, endpoint_descriptor); + } + } + } + return buffer; +} + std::vector USB_HIDv4::GetDeviceEntry(const USB::Device& device) const { std::lock_guard id_map_lock{m_id_map_mutex}; @@ -219,7 +253,7 @@ std::vector USB_HIDv4::GetDeviceEntry(const USB::Device& device) const // 4-8 bytes: device ID // the rest of the buffer is device descriptors data std::vector entry(8); - const std::vector descriptors = device.GetDescriptorsUSBV4(); + const std::vector descriptors = GetDescriptors(device); const u32 entry_size = Common::swap32(static_cast(entry.size() + descriptors.size())); const u32 ios_device_id = Common::swap32(m_device_ids.at(device.GetId())); std::memcpy(entry.data(), &entry_size, sizeof(entry_size)); diff --git a/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp b/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp index 552d278863..27fb03087d 100644 --- a/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp +++ b/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp @@ -186,20 +186,39 @@ IPCCommandResult USB_VEN::GetDeviceInfo(USBV5Device& device, const IOCtlRequest& if (request.buffer_out == 0 || request.buffer_out_size != 0xc0) return GetDefaultReply(IPC_EINVAL); - const auto host_device = GetDeviceById(device.host_id); + const std::shared_ptr host_device = GetDeviceById(device.host_id); const u8 alt_setting = Memory::Read_U8(request.buffer_in + 8); - auto descriptors = host_device->GetDescriptorsUSBV5(device.interface_number, alt_setting); - if (descriptors.empty()) - return GetDefaultReply(IPC_ENOENT); - - descriptors.resize(request.buffer_out_size - 20); - if (descriptors.size() > request.buffer_out_size - 20) - WARN_LOG(IOS_USB, "Buffer is too large. Only the first 172 bytes will be copied."); 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); - Memory::CopyToEmu(request.buffer_out + 20, descriptors.data(), descriptors.size()); + + USB::DeviceDescriptor device_descriptor = host_device->GetDeviceDescriptor(); + device_descriptor.Swap(); + Memory::CopyToEmu(request.buffer_out + 20, &device_descriptor, sizeof(device_descriptor)); + + // VEN only cares about the first configuration. + USB::ConfigDescriptor config_descriptor = host_device->GetConfigurations()[0]; + config_descriptor.Swap(); + Memory::CopyToEmu(request.buffer_out + 40, &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 + 52, &*it, sizeof(*it)); + + auto endpoints = host_device->GetEndpoints(0, it->bInterfaceNumber, it->bAlternateSetting); + for (size_t i = 0; i < endpoints.size(); ++i) + { + endpoints[i].Swap(); + Memory::CopyToEmu(request.buffer_out + 64 + 8 * static_cast(i), &endpoints[i], + sizeof(endpoints[i])); + } return GetDefaultReply(IPC_SUCCESS); } From 180ad8076cbc1d64077966f56a2381d83988f5c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Thu, 2 Nov 2017 22:39:36 +0100 Subject: [PATCH 4/9] USB: Move common USBv5 IOS code to prepare for HIDv5 USBv5 IOS resource managers share most of their code. Some ioctls are even completely the same! So let's separate the common code from the VEN specific stuff to make HIDv5 easier to implement. --- Source/Core/Core/IOS/USB/USBV5.cpp | 238 +++++++++++++++++++++- Source/Core/Core/IOS/USB/USBV5.h | 59 ++++++ Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp | 243 +---------------------- Source/Core/Core/IOS/USB/USB_VEN/VEN.h | 48 +---- 4 files changed, 305 insertions(+), 283 deletions(-) diff --git a/Source/Core/Core/IOS/USB/USBV5.cpp b/Source/Core/Core/IOS/USB/USBV5.cpp index df81c11014..f0356d4bb6 100644 --- a/Source/Core/Core/IOS/USB/USBV5.cpp +++ b/Source/Core/Core/IOS/USB/USBV5.cpp @@ -4,13 +4,16 @@ #include "Core/IOS/USB/USBV5.h" +#include #include +#include #include -#include -#include "Common/CommonTypes.h" +#include "Common/ChunkFile.h" +#include "Common/Logging/Log.h" +#include "Common/Swap.h" +#include "Core/CoreTiming.h" #include "Core/HW/Memmap.h" -#include "Core/IOS/Device.h" namespace IOS { @@ -53,5 +56,234 @@ V5IsoMessage::V5IsoMessage(Kernel& ios, const IOCtlVRequest& ioctlv) length = std::accumulate(packet_sizes.begin(), packet_sizes.end(), 0); } } // namespace USB + +namespace Device +{ +namespace +{ +#pragma pack(push, 1) +struct DeviceID +{ + u8 ipc_address_shifted; + u8 index; + u16 number; +}; + +struct DeviceEntry +{ + DeviceID id; + u16 vid; + u16 pid; + u16 number; + u8 interface_number; + u8 num_altsettings; +}; +#pragma pack(pop) +} + +USBV5ResourceManager::~USBV5ResourceManager() +{ + StopThreads(); +} + +void USBV5ResourceManager::DoState(PointerWrap& p) +{ + p.Do(m_devicechange_first_call); + u32 hook_address = m_devicechange_hook_request ? m_devicechange_hook_request->address : 0; + p.Do(hook_address); + if (hook_address != 0) + m_devicechange_hook_request = std::make_unique(hook_address); + else + m_devicechange_hook_request.reset(); + + p.Do(m_usbv5_devices); + USBHost::DoState(p); +} + +USBV5ResourceManager::USBV5Device* USBV5ResourceManager::GetUSBV5Device(u32 in_buffer) +{ + const u8 index = Memory::Read_U8(in_buffer + offsetof(DeviceID, index)); + const u16 number = Memory::Read_U16(in_buffer + offsetof(DeviceID, number)); + + if (index >= m_usbv5_devices.size()) + return nullptr; + + USBV5Device* usbv5_device = &m_usbv5_devices[index]; + if (!usbv5_device->in_use || usbv5_device->number != number) + return nullptr; + + return usbv5_device; +} + +IPCCommandResult USBV5ResourceManager::GetDeviceChange(const IOCtlRequest& request) +{ + if (request.buffer_out_size != 0x180 || m_devicechange_hook_request) + return GetDefaultReply(IPC_EINVAL); + + std::lock_guard lk{m_devicechange_hook_address_mutex}; + m_devicechange_hook_request = std::make_unique(request.address); + // On the first call, the reply is sent immediately (instead of on device insertion/removal) + if (m_devicechange_first_call) + { + TriggerDeviceChangeReply(); + m_devicechange_first_call = false; + } + return GetNoReply(); +} + +IPCCommandResult USBV5ResourceManager::SetAlternateSetting(USBV5Device& device, + const IOCtlRequest& request) +{ + const auto host_device = GetDeviceById(device.host_id); + if (!host_device->Attach(device.interface_number)) + return GetDefaultReply(-1); + + const u8 alt_setting = Memory::Read_U8(request.buffer_in + 2 * sizeof(s32)); + + const bool success = host_device->SetAltSetting(alt_setting) == 0; + return GetDefaultReply(success ? IPC_SUCCESS : IPC_EINVAL); +} + +IPCCommandResult USBV5ResourceManager::Shutdown(const IOCtlRequest& request) +{ + if (request.buffer_in != 0 || request.buffer_in_size != 0 || request.buffer_out != 0 || + request.buffer_out_size != 0) + { + return GetDefaultReply(IPC_EINVAL); + } + + std::lock_guard lk{m_devicechange_hook_address_mutex}; + if (m_devicechange_hook_request) + { + m_ios.EnqueueIPCReply(*m_devicechange_hook_request, IPC_SUCCESS); + m_devicechange_hook_request.reset(); + } + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult USBV5ResourceManager::SuspendResume(USBV5Device& device, + const IOCtlRequest& request) +{ + const auto host_device = GetDeviceById(device.host_id); + const s32 resumed = Memory::Read_U32(request.buffer_in + 8); + + // Note: this is unimplemented because there's no easy way to do this in a + // platform-independant way (libusb does not support power management). + INFO_LOG(IOS_USB, "[%04x:%04x %d] Received %s command", host_device->GetVid(), + host_device->GetPid(), device.interface_number, resumed == 0 ? "suspend" : "resume"); + return GetDefaultReply(IPC_SUCCESS); +} + +s32 USBV5ResourceManager::SubmitTransfer(USB::Device& device, const IOCtlVRequest& ioctlv) +{ + switch (ioctlv.request) + { + case USB::IOCTLV_USBV5_CTRLMSG: + return device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); + case USB::IOCTLV_USBV5_INTRMSG: + return device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); + case USB::IOCTLV_USBV5_BULKMSG: + return device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); + case USB::IOCTLV_USBV5_ISOMSG: + return device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); + default: + return IPC_EINVAL; + } +} + +IPCCommandResult USBV5ResourceManager::HandleDeviceIOCtl(const IOCtlRequest& request, + Handler handler) +{ + if (request.buffer_in == 0 || request.buffer_in_size != 0x20) + return GetDefaultReply(IPC_EINVAL); + + std::lock_guard lock{m_usbv5_devices_mutex}; + USBV5Device* device = GetUSBV5Device(request.buffer_in); + if (!device) + return GetDefaultReply(IPC_EINVAL); + return handler(*device); +} + +void USBV5ResourceManager::OnDeviceChange(const ChangeEvent event, + std::shared_ptr device) +{ + std::lock_guard lock{m_usbv5_devices_mutex}; + const u64 host_device_id = device->GetId(); + if (event == ChangeEvent::Inserted) + { + for (const auto& interface : device->GetInterfaces(0)) + { + if (interface.bAlternateSetting != 0) + continue; + + auto it = std::find_if(m_usbv5_devices.rbegin(), m_usbv5_devices.rend(), + [](const USBV5Device& entry) { return !entry.in_use; }); + if (it == m_usbv5_devices.rend()) + return; + + it->in_use = true; + it->interface_number = interface.bInterfaceNumber; + it->number = m_current_device_number; + it->host_id = host_device_id; + } + } + else if (event == ChangeEvent::Removed) + { + for (USBV5Device& entry : m_usbv5_devices) + { + if (entry.host_id == host_device_id) + entry.in_use = false; + } + } +} + +void USBV5ResourceManager::OnDeviceChangeEnd() +{ + std::lock_guard lk{m_devicechange_hook_address_mutex}; + TriggerDeviceChangeReply(); + ++m_current_device_number; +} + +// Must be called with m_devicechange_hook_address_mutex locked +void USBV5ResourceManager::TriggerDeviceChangeReply() +{ + if (!m_devicechange_hook_request) + return; + + std::lock_guard lock{m_usbv5_devices_mutex}; + u8 num_devices = 0; + for (auto it = m_usbv5_devices.crbegin(); it != m_usbv5_devices.crend(); ++it) + { + const USBV5Device& usbv5_device = *it; + if (!usbv5_device.in_use) + continue; + + const auto device = GetDeviceById(usbv5_device.host_id); + if (!device) + 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; + 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()); + entry.pid = Common::swap16(device->GetPid()); + entry.number = Common::swap16(usbv5_device.number); + entry.interface_number = usbv5_device.interface_number; + entry.num_altsettings = device->GetNumberOfAltSettings(entry.interface_number); + + Memory::CopyToEmu(m_devicechange_hook_request->buffer_out + sizeof(entry) * num_devices, &entry, + sizeof(entry)); + ++num_devices; + } + + m_ios.EnqueueIPCReply(*m_devicechange_hook_request, num_devices, 0, CoreTiming::FromThread::ANY); + m_devicechange_hook_request.reset(); + INFO_LOG(IOS_USB, "%d USBv5 device(s), including interfaces", num_devices); +} +} // namespace Device } // namespace HLE } // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USBV5.h b/Source/Core/Core/IOS/USB/USBV5.h index 2db6c22110..47db9da9dc 100644 --- a/Source/Core/Core/IOS/USB/USBV5.h +++ b/Source/Core/Core/IOS/USB/USBV5.h @@ -4,8 +4,18 @@ #pragma once +#include +#include +#include +#include + #include "Common/CommonTypes.h" +#include "Core/IOS/Device.h" +#include "Core/IOS/IOS.h" #include "Core/IOS/USB/Common.h" +#include "Core/IOS/USB/Host.h" + +class PointerWrap; // Used by late USB interfaces for /dev/usb/ven and /dev/usb/hid (since IOS57 which // reorganised the USB modules in IOS). @@ -54,5 +64,54 @@ struct V5IsoMessage final : IsoMessage V5IsoMessage(Kernel& ios, const IOCtlVRequest& cmd_buffer); }; } // namespace USB + +namespace Device +{ +class USBV5ResourceManager : public USBHost +{ +public: + using USBHost::USBHost; + ~USBV5ResourceManager() override; + + IPCCommandResult IOCtl(const IOCtlRequest& request) override = 0; + IPCCommandResult IOCtlV(const IOCtlVRequest& request) override = 0; + + void DoState(PointerWrap& p) override; + +protected: + struct USBV5Device; + USBV5Device* GetUSBV5Device(u32 in_buffer); + + IPCCommandResult GetDeviceChange(const IOCtlRequest& request); + IPCCommandResult SetAlternateSetting(USBV5Device& device, const IOCtlRequest& request); + IPCCommandResult Shutdown(const IOCtlRequest& request); + IPCCommandResult SuspendResume(USBV5Device& device, const IOCtlRequest& request); + s32 SubmitTransfer(USB::Device& device, const IOCtlVRequest& request); + + using Handler = std::function; + IPCCommandResult HandleDeviceIOCtl(const IOCtlRequest& request, Handler handler); + + void OnDeviceChange(ChangeEvent event, std::shared_ptr device) override; + void OnDeviceChangeEnd() override; + void TriggerDeviceChangeReply(); + + bool m_devicechange_first_call = true; + std::mutex m_devicechange_hook_address_mutex; + std::unique_ptr m_devicechange_hook_request; + + // Each interface of a USB device is internally considered as a unique device. + // USBv5 resource managers can handle up to 32 devices/interfaces. + struct USBV5Device + { + bool in_use = false; + u8 interface_number = 0; + u16 number = 0; + u64 host_id = 0; + }; + std::array m_usbv5_devices{}; + mutable std::mutex m_usbv5_devices_mutex; + u16 m_current_device_number = 0x21; +}; +} // namespace Device } // namespace HLE } // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp b/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp index 27fb03087d..fcc7e3792b 100644 --- a/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp +++ b/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp @@ -6,20 +6,13 @@ #include #include -#include #include #include -#include -#include "Common/ChunkFile.h" +#include "Common/CommonTypes.h" #include "Common/Logging/Log.h" -#include "Common/Swap.h" - -#include "Core/CoreTiming.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 { @@ -27,38 +20,9 @@ namespace HLE { namespace Device { -namespace -{ -#pragma pack(push, 1) -struct DeviceID -{ - u8 ipc_address_shifted; - u8 index; - u16 number; -}; - -struct DeviceEntry -{ - DeviceID id; - u16 vid; - u16 pid; - u16 number; - u8 interface_number; - u8 num_altsettings; -}; -#pragma pack(pop) -} - constexpr u32 USBV5_VERSION = 0x50001; -USB_VEN::USB_VEN(Kernel& ios, const std::string& device_name) : USBHost(ios, device_name) -{ -} - -USB_VEN::~USB_VEN() -{ - StopThreads(); -} +USB_VEN::~USB_VEN() = default; ReturnCode USB_VEN::Open(const OpenRequest& request) { @@ -81,15 +45,17 @@ IPCCommandResult USB_VEN::IOCtl(const IOCtlRequest& request) case USB::IOCTL_USBV5_SHUTDOWN: return Shutdown(request); case USB::IOCTL_USBV5_GETDEVPARAMS: - return HandleDeviceIOCtl(request, &USB_VEN::GetDeviceInfo); + return HandleDeviceIOCtl(request, [&](auto& device) { return GetDeviceInfo(device, request); }); case USB::IOCTL_USBV5_ATTACHFINISH: return GetDefaultReply(IPC_SUCCESS); case USB::IOCTL_USBV5_SETALTERNATE: - return HandleDeviceIOCtl(request, &USB_VEN::SetAlternateSetting); + return HandleDeviceIOCtl(request, + [&](auto& device) { return SetAlternateSetting(device, request); }); case USB::IOCTL_USBV5_SUSPEND_RESUME: - return HandleDeviceIOCtl(request, &USB_VEN::SuspendResume); + return HandleDeviceIOCtl(request, [&](auto& device) { return SuspendResume(device, request); }); case USB::IOCTL_USBV5_CANCELENDPOINT: - return HandleDeviceIOCtl(request, &USB_VEN::CancelEndpoint); + return HandleDeviceIOCtl(request, + [&](auto& device) { return CancelEndpoint(device, request); }); default: request.DumpUnknown(GetDeviceName(), LogTypes::IOS_USB, LogTypes::LERROR); return GetDefaultReply(IPC_SUCCESS); @@ -129,35 +95,6 @@ IPCCommandResult USB_VEN::IOCtlV(const IOCtlVRequest& request) } } -void USB_VEN::DoState(PointerWrap& p) -{ - p.Do(m_devicechange_first_call); - u32 hook_address = m_devicechange_hook_request ? m_devicechange_hook_request->address : 0; - p.Do(hook_address); - if (hook_address != 0) - m_devicechange_hook_request = std::make_unique(hook_address); - else - m_devicechange_hook_request.reset(); - - p.Do(m_usbv5_devices); - USBHost::DoState(p); -} - -USB_VEN::USBV5Device* USB_VEN::GetUSBV5Device(u32 in_buffer) -{ - const u8 index = Memory::Read_U8(in_buffer + offsetof(DeviceID, index)); - const u16 number = Memory::Read_U16(in_buffer + offsetof(DeviceID, number)); - - if (index >= m_usbv5_devices.size()) - return nullptr; - - USBV5Device* usbv5_device = &m_usbv5_devices[index]; - if (!usbv5_device->in_use || usbv5_device->number != number) - return nullptr; - - return usbv5_device; -} - IPCCommandResult USB_VEN::CancelEndpoint(USBV5Device& device, const IOCtlRequest& request) { const u8 endpoint = static_cast(Memory::Read_U32(request.buffer_in + 8)); @@ -165,22 +102,6 @@ IPCCommandResult USB_VEN::CancelEndpoint(USBV5Device& device, const IOCtlRequest return GetDefaultReply(IPC_SUCCESS); } -IPCCommandResult USB_VEN::GetDeviceChange(const IOCtlRequest& request) -{ - if (request.buffer_out_size != 0x180 || m_devicechange_hook_request) - return GetDefaultReply(IPC_EINVAL); - - std::lock_guard lk{m_devicechange_hook_address_mutex}; - m_devicechange_hook_request = std::make_unique(request.address); - // On the first call, the reply is sent immediately (instead of on device insertion/removal) - if (m_devicechange_first_call) - { - TriggerDeviceChangeReply(); - m_devicechange_first_call = false; - } - return GetNoReply(); -} - IPCCommandResult USB_VEN::GetDeviceInfo(USBV5Device& device, const IOCtlRequest& request) { if (request.buffer_out == 0 || request.buffer_out_size != 0xc0) @@ -222,154 +143,6 @@ IPCCommandResult USB_VEN::GetDeviceInfo(USBV5Device& device, const IOCtlRequest& return GetDefaultReply(IPC_SUCCESS); } - -IPCCommandResult USB_VEN::SetAlternateSetting(USBV5Device& device, const IOCtlRequest& request) -{ - const auto host_device = GetDeviceById(device.host_id); - if (!host_device->Attach(device.interface_number)) - return GetDefaultReply(-1); - - const u8 alt_setting = Memory::Read_U8(request.buffer_in + 2 * sizeof(s32)); - - const bool success = host_device->SetAltSetting(alt_setting) == 0; - return GetDefaultReply(success ? IPC_SUCCESS : IPC_EINVAL); -} - -IPCCommandResult USB_VEN::Shutdown(const IOCtlRequest& request) -{ - if (request.buffer_in != 0 || request.buffer_in_size != 0 || request.buffer_out != 0 || - request.buffer_out_size != 0) - { - return GetDefaultReply(IPC_EINVAL); - } - - std::lock_guard lk{m_devicechange_hook_address_mutex}; - if (m_devicechange_hook_request) - { - m_ios.EnqueueIPCReply(*m_devicechange_hook_request, IPC_SUCCESS); - m_devicechange_hook_request.reset(); - } - return GetDefaultReply(IPC_SUCCESS); -} - -IPCCommandResult USB_VEN::SuspendResume(USBV5Device& device, const IOCtlRequest& request) -{ - const auto host_device = GetDeviceById(device.host_id); - const s32 resumed = Memory::Read_U32(request.buffer_in + 8); - - // Note: this is unimplemented because there's no easy way to do this in a - // platform-independant way (libusb does not support power management). - INFO_LOG(IOS_USB, "[%04x:%04x %d] Received %s command", host_device->GetVid(), - host_device->GetPid(), device.interface_number, resumed == 0 ? "suspend" : "resume"); - return GetDefaultReply(IPC_SUCCESS); -} - -s32 USB_VEN::SubmitTransfer(USB::Device& device, const IOCtlVRequest& ioctlv) -{ - switch (ioctlv.request) - { - case USB::IOCTLV_USBV5_CTRLMSG: - return device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); - case USB::IOCTLV_USBV5_INTRMSG: - return device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); - case USB::IOCTLV_USBV5_BULKMSG: - return device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); - case USB::IOCTLV_USBV5_ISOMSG: - return device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); - default: - return IPC_EINVAL; - } -} - -IPCCommandResult USB_VEN::HandleDeviceIOCtl(const IOCtlRequest& request, Handler handler) -{ - if (request.buffer_in == 0 || request.buffer_in_size != 0x20) - return GetDefaultReply(IPC_EINVAL); - - std::lock_guard lock{m_usbv5_devices_mutex}; - USBV5Device* device = GetUSBV5Device(request.buffer_in); - if (!device) - return GetDefaultReply(IPC_EINVAL); - return handler(this, *device, request); -} - -void USB_VEN::OnDeviceChange(const ChangeEvent event, std::shared_ptr device) -{ - std::lock_guard lock{m_usbv5_devices_mutex}; - const u64 host_device_id = device->GetId(); - if (event == ChangeEvent::Inserted) - { - for (const auto& interface : device->GetInterfaces(0)) - { - if (interface.bAlternateSetting != 0) - continue; - - auto it = std::find_if(m_usbv5_devices.rbegin(), m_usbv5_devices.rend(), - [](const USBV5Device& entry) { return !entry.in_use; }); - if (it == m_usbv5_devices.rend()) - return; - - it->in_use = true; - it->interface_number = interface.bInterfaceNumber; - it->number = m_current_device_number; - it->host_id = host_device_id; - } - } - else if (event == ChangeEvent::Removed) - { - for (USBV5Device& entry : m_usbv5_devices) - { - if (entry.host_id == host_device_id) - entry.in_use = false; - } - } -} - -void USB_VEN::OnDeviceChangeEnd() -{ - std::lock_guard lk{m_devicechange_hook_address_mutex}; - TriggerDeviceChangeReply(); - ++m_current_device_number; -} - -void USB_VEN::TriggerDeviceChangeReply() -{ - if (!m_devicechange_hook_request) - return; - - std::lock_guard lock{m_usbv5_devices_mutex}; - u8 num_devices = 0; - for (auto it = m_usbv5_devices.crbegin(); it != m_usbv5_devices.crend(); ++it) - { - const USBV5Device& usbv5_device = *it; - if (!usbv5_device.in_use) - continue; - - const auto device = GetDeviceById(usbv5_device.host_id); - if (!device) - 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; - 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()); - entry.pid = Common::swap16(device->GetPid()); - entry.number = Common::swap16(usbv5_device.number); - entry.interface_number = usbv5_device.interface_number; - entry.num_altsettings = device->GetNumberOfAltSettings(entry.interface_number); - - Memory::CopyToEmu(m_devicechange_hook_request->buffer_out + sizeof(entry) * num_devices++, - &entry, sizeof(entry)); - } - - m_ios.EnqueueIPCReply(*m_devicechange_hook_request, num_devices, 0, CoreTiming::FromThread::ANY); - m_devicechange_hook_request.reset(); - INFO_LOG(IOS_USB, "%d USBv5 device(s), including interfaces", num_devices); -} } // 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 3ec4d62445..a246152bb9 100644 --- a/Source/Core/Core/IOS/USB/USB_VEN/VEN.h +++ b/Source/Core/Core/IOS/USB/USB_VEN/VEN.h @@ -4,18 +4,10 @@ #pragma once -#include -#include -#include -#include -#include - -#include "Common/CommonTypes.h" #include "Core/IOS/Device.h" #include "Core/IOS/IOS.h" #include "Core/IOS/USB/Host.h" - -class PointerWrap; +#include "Core/IOS/USB/USBV5.h" namespace IOS { @@ -23,53 +15,19 @@ namespace HLE { namespace Device { -class USB_VEN final : public USBHost +class USB_VEN final : public USBV5ResourceManager { public: - USB_VEN(Kernel& ios, const std::string& device_name); + using USBV5ResourceManager::USBV5ResourceManager; ~USB_VEN() override; ReturnCode Open(const OpenRequest& request) override; IPCCommandResult IOCtl(const IOCtlRequest& request) override; IPCCommandResult IOCtlV(const IOCtlVRequest& request) override; - void DoState(PointerWrap& p) override; - private: - struct USBV5Device; - USBV5Device* GetUSBV5Device(u32 in_buffer); - IPCCommandResult CancelEndpoint(USBV5Device& device, const IOCtlRequest& request); - IPCCommandResult GetDeviceChange(const IOCtlRequest& request); IPCCommandResult GetDeviceInfo(USBV5Device& device, const IOCtlRequest& request); - IPCCommandResult SetAlternateSetting(USBV5Device& device, const IOCtlRequest& request); - IPCCommandResult Shutdown(const IOCtlRequest& request); - IPCCommandResult SuspendResume(USBV5Device& device, const IOCtlRequest& request); - s32 SubmitTransfer(USB::Device& device, const IOCtlVRequest& request); - - using Handler = std::function; - IPCCommandResult HandleDeviceIOCtl(const IOCtlRequest& request, Handler handler); - - void OnDeviceChange(ChangeEvent, std::shared_ptr) override; - void OnDeviceChangeEnd() override; - void TriggerDeviceChangeReply(); - - bool m_devicechange_first_call = true; - std::mutex m_devicechange_hook_address_mutex; - std::unique_ptr m_devicechange_hook_request; - - // Each interface of a USB device is internally considered as a unique device. - // USBv5 resource managers can handle up to 32 devices/interfaces. - struct USBV5Device - { - bool in_use = false; - u8 interface_number; - u16 number; - u64 host_id; - }; - std::array m_usbv5_devices; - mutable std::mutex m_usbv5_devices_mutex; - u16 m_current_device_number = 0x21; }; } // namespace Device } // namespace HLE From ff52333b14a1acf113f79b2ffb5fd759a1bf6921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Thu, 2 Nov 2017 23:12:58 +0100 Subject: [PATCH 5/9] USB: Implement HIDv5 This implements /dev/usb/hid v5, found in IOS57, IOS58 and IOS59. This is an initial implementation that ignores some differences with VEN because I lack understanding of what IOS is actually doing sometimes. These are documented on the WiiBrew article: https://wiibrew.org/wiki//dev/usb/hid_(v5) One major difference that this implementation handles is about IDs. It turns out Nintendo has decided to include the interface number in the top byte of HIDv5 device IDs, unlike VEN -- even though everything else about ioctl 1 is otherwise the same! --- Source/Core/Core/IOS/USB/USBV5.cpp | 17 ++- Source/Core/Core/IOS/USB/USBV5.h | 1 + Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp | 144 ++++++++++++++++----- Source/Core/Core/IOS/USB/USB_HID/HIDv5.h | 19 ++- Source/Core/Core/IOS/USB/USB_VEN/VEN.h | 2 + 5 files changed, 139 insertions(+), 44 deletions(-) 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 From ac3b866083b763137aaff89cdcda2c5d8deb6d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 3 Nov 2017 11:17:12 +0100 Subject: [PATCH 6/9] USB_HIDv5: Submit interrupt transfers to the correct endpoint Unlike VEN, the endpoint is determined by the value at 8-12. If it's non-zero, HID submits the request to the interrupt OUT endpoint. Otherwise, the request is submitted to the IN endpoint. This commit changes HIDv5 to keep track of endpoints (like IOS does) and use them when submitting interrupt transfers. --- Source/Core/Core/IOS/USB/USBV5.cpp | 17 ---------- Source/Core/Core/IOS/USB/USBV5.h | 1 - Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp | 39 ++++++++++++++++++++-- Source/Core/Core/IOS/USB/USB_HID/HIDv5.h | 7 ++++ Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp | 17 ++++++++++ Source/Core/Core/IOS/USB/USB_VEN/VEN.h | 1 + 6 files changed, 62 insertions(+), 20 deletions(-) diff --git a/Source/Core/Core/IOS/USB/USBV5.cpp b/Source/Core/Core/IOS/USB/USBV5.cpp index d4712c9488..804989b75a 100644 --- a/Source/Core/Core/IOS/USB/USBV5.cpp +++ b/Source/Core/Core/IOS/USB/USBV5.cpp @@ -174,23 +174,6 @@ IPCCommandResult USBV5ResourceManager::SuspendResume(USBV5Device& device, return GetDefaultReply(IPC_SUCCESS); } -s32 USBV5ResourceManager::SubmitTransfer(USB::Device& device, const IOCtlVRequest& ioctlv) -{ - switch (ioctlv.request) - { - case USB::IOCTLV_USBV5_CTRLMSG: - return device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); - case USB::IOCTLV_USBV5_INTRMSG: - return device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); - case USB::IOCTLV_USBV5_BULKMSG: - return device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); - case USB::IOCTLV_USBV5_ISOMSG: - return device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); - default: - return IPC_EINVAL; - } -} - IPCCommandResult USBV5ResourceManager::HandleDeviceIOCtl(const IOCtlRequest& request, Handler handler) { diff --git a/Source/Core/Core/IOS/USB/USBV5.h b/Source/Core/Core/IOS/USB/USBV5.h index fbfde46fb3..ceca6bcb71 100644 --- a/Source/Core/Core/IOS/USB/USBV5.h +++ b/Source/Core/Core/IOS/USB/USBV5.h @@ -86,7 +86,6 @@ protected: IPCCommandResult SetAlternateSetting(USBV5Device& device, const IOCtlRequest& request); IPCCommandResult Shutdown(const IOCtlRequest& request); IPCCommandResult SuspendResume(USBV5Device& device, const IOCtlRequest& request); - s32 SubmitTransfer(USB::Device& device, const IOCtlVRequest& request); using Handler = std::function; IPCCommandResult HandleDeviceIOCtl(const IOCtlRequest& request, Handler handler); diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp index dfc12f6b6a..c8f6b8f241 100644 --- a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp @@ -71,13 +71,40 @@ IPCCommandResult USB_HIDv5::IOCtlV(const IOCtlVRequest& request) 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); }); + [&, this]() { return SubmitTransfer(*device, *host_device, request); }); } default: return GetDefaultReply(IPC_EINVAL); } } +s32 USB_HIDv5::SubmitTransfer(USBV5Device& device, USB::Device& host_device, + const IOCtlVRequest& ioctlv) +{ + switch (ioctlv.request) + { + case USB::IOCTLV_USBV5_CTRLMSG: + return host_device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); + case USB::IOCTLV_USBV5_INTRMSG: + { + auto message = std::make_unique(m_ios, ioctlv); + + // Unlike VEN, the endpoint is determined by the value at 8-12. + // If it's non-zero, HID submits the request to the interrupt OUT endpoint. + // Otherwise, the request is submitted to the IN endpoint. + AdditionalDeviceData* data = &m_additional_device_data[&device - m_usbv5_devices.data()]; + if (Memory::Read_U32(ioctlv.in_vectors[0].address + 8) != 0) + message->endpoint = data->interrupt_out_endpoint; + else + message->endpoint = data->interrupt_in_endpoint; + + return host_device.SubmitTransfer(std::move(message)); + } + default: + return IPC_EINVAL; + } +} + IPCCommandResult USB_HIDv5::CancelEndpoint(USBV5Device& device, const IOCtlRequest& request) { // FIXME: Unlike VEN, there are 3 valid values for the endpoint, @@ -128,7 +155,15 @@ IPCCommandResult USB_HIDv5::GetDeviceInfo(USBV5Device& device, const IOCtlReques constexpr u8 ENDPOINT_IN = 0x80; if (endpoint.bmAttributes == ENDPOINT_INTERRUPT) { - const u32 offset = (endpoint.bEndpointAddress & ENDPOINT_IN) != 0 ? 80 : 88; + const bool is_in_endpoint = (endpoint.bEndpointAddress & ENDPOINT_IN) != 0; + + AdditionalDeviceData* data = &m_additional_device_data[&device - m_usbv5_devices.data()]; + if (is_in_endpoint) + data->interrupt_in_endpoint = endpoint.bEndpointAddress; + else + data->interrupt_out_endpoint = endpoint.bEndpointAddress; + + const u32 offset = is_in_endpoint ? 80 : 88; endpoint.Swap(); Memory::CopyToEmu(request.buffer_out + offset, &endpoint, sizeof(endpoint)); } diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.h b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.h index 625a810f54..82709898fa 100644 --- a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.h +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.h @@ -27,9 +27,16 @@ public: private: IPCCommandResult CancelEndpoint(USBV5Device& device, const IOCtlRequest& request); IPCCommandResult GetDeviceInfo(USBV5Device& device, const IOCtlRequest& request); + s32 SubmitTransfer(USBV5Device& device, USB::Device& host_device, const IOCtlVRequest& ioctlv); bool ShouldAddDevice(const USB::Device& device) const override; bool HasInterfaceNumberInIDs() const override { return true; } + struct AdditionalDeviceData + { + u8 interrupt_in_endpoint = 0; + u8 interrupt_out_endpoint = 0; + }; + std::array m_additional_device_data{}; }; } // namespace Device } // namespace HLE diff --git a/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp b/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp index fcc7e3792b..93fad5d0c1 100644 --- a/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp +++ b/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp @@ -95,6 +95,23 @@ IPCCommandResult USB_VEN::IOCtlV(const IOCtlVRequest& request) } } +s32 USB_VEN::SubmitTransfer(USB::Device& device, const IOCtlVRequest& ioctlv) +{ + switch (ioctlv.request) + { + case USB::IOCTLV_USBV5_CTRLMSG: + return device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); + case USB::IOCTLV_USBV5_INTRMSG: + return device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); + case USB::IOCTLV_USBV5_BULKMSG: + return device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); + case USB::IOCTLV_USBV5_ISOMSG: + return device.SubmitTransfer(std::make_unique(m_ios, ioctlv)); + default: + return IPC_EINVAL; + } +} + IPCCommandResult USB_VEN::CancelEndpoint(USBV5Device& device, const IOCtlRequest& request) { const u8 endpoint = static_cast(Memory::Read_U32(request.buffer_in + 8)); diff --git a/Source/Core/Core/IOS/USB/USB_VEN/VEN.h b/Source/Core/Core/IOS/USB/USB_VEN/VEN.h index 58c4af767e..8205b0ca2d 100644 --- a/Source/Core/Core/IOS/USB/USB_VEN/VEN.h +++ b/Source/Core/Core/IOS/USB/USB_VEN/VEN.h @@ -29,6 +29,7 @@ private: IPCCommandResult CancelEndpoint(USBV5Device& device, const IOCtlRequest& request); IPCCommandResult GetDeviceInfo(USBV5Device& device, const IOCtlRequest& request); + s32 SubmitTransfer(USB::Device& device, const IOCtlVRequest& ioctlv); bool HasInterfaceNumberInIDs() const override { return false; } }; } // namespace Device From 22310dfc3e895ce271748e566405d8d394f28e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 3 Nov 2017 12:23:57 +0100 Subject: [PATCH 7/9] USBv5: Read transfer parameters from the correct vector This is why static analysis is essential. --- Source/Core/Core/IOS/Device.cpp | 9 +++++++++ Source/Core/Core/IOS/Device.h | 1 + Source/Core/Core/IOS/USB/USBV5.cpp | 19 +++++++++---------- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Source/Core/Core/IOS/Device.cpp b/Source/Core/Core/IOS/Device.cpp index c5da4c662c..467ce5cfd1 100644 --- a/Source/Core/Core/IOS/Device.cpp +++ b/Source/Core/Core/IOS/Device.cpp @@ -7,6 +7,7 @@ #include #include +#include "Common/Assert.h" #include "Common/Logging/Log.h" #include "Common/StringUtil.h" #include "Core/HW/Memmap.h" @@ -77,6 +78,14 @@ IOCtlVRequest::IOCtlVRequest(const u32 address_) : Request(address_) } } +const IOCtlVRequest::IOVector* IOCtlVRequest::GetVector(size_t index) const +{ + _assert_(index < (in_vectors.size() + io_vectors.size())); + if (index < in_vectors.size()) + return &in_vectors[index]; + return &io_vectors[index - in_vectors.size()]; +} + bool IOCtlVRequest::HasNumberOfValidVectors(const size_t in_count, const size_t io_count) const { if (in_vectors.size() != in_count || io_vectors.size() != io_count) diff --git a/Source/Core/Core/IOS/Device.h b/Source/Core/Core/IOS/Device.h index e9191f163c..0723972be4 100644 --- a/Source/Core/Core/IOS/Device.h +++ b/Source/Core/Core/IOS/Device.h @@ -156,6 +156,7 @@ struct IOCtlVRequest final : Request // merging them into a single std::vector would make using the first out vector more complicated. std::vector in_vectors; std::vector io_vectors; + const IOVector* GetVector(size_t index) const; explicit IOCtlVRequest(u32 address); bool HasNumberOfValidVectors(size_t in_count, size_t io_count) const; void Dump(const std::string& description, LogTypes::LOG_TYPE type = LogTypes::IOS, diff --git a/Source/Core/Core/IOS/USB/USBV5.cpp b/Source/Core/Core/IOS/USB/USBV5.cpp index 804989b75a..424a3dc98f 100644 --- a/Source/Core/Core/IOS/USB/USBV5.cpp +++ b/Source/Core/Core/IOS/USB/USBV5.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include "Common/ChunkFile.h" #include "Common/Logging/Log.h" @@ -22,38 +21,38 @@ namespace HLE namespace USB { V5CtrlMessage::V5CtrlMessage(Kernel& ios, const IOCtlVRequest& ioctlv) - : CtrlMessage(ios, ioctlv, Memory::Read_U32(ioctlv.in_vectors[0].address + 16)) + : CtrlMessage(ios, ioctlv, ioctlv.GetVector(1)->address) { request_type = Memory::Read_U8(ioctlv.in_vectors[0].address + 8); request = Memory::Read_U8(ioctlv.in_vectors[0].address + 9); value = Memory::Read_U16(ioctlv.in_vectors[0].address + 10); index = Memory::Read_U16(ioctlv.in_vectors[0].address + 12); - length = Memory::Read_U16(ioctlv.in_vectors[0].address + 14); + length = static_cast(ioctlv.GetVector(1)->size); } V5BulkMessage::V5BulkMessage(Kernel& ios, const IOCtlVRequest& ioctlv) - : BulkMessage(ios, ioctlv, Memory::Read_U32(ioctlv.in_vectors[0].address + 8)) + : BulkMessage(ios, ioctlv, ioctlv.GetVector(1)->address) { - length = Memory::Read_U16(ioctlv.in_vectors[0].address + 12); + length = static_cast(ioctlv.GetVector(1)->size); endpoint = Memory::Read_U8(ioctlv.in_vectors[0].address + 18); } V5IntrMessage::V5IntrMessage(Kernel& ios, const IOCtlVRequest& ioctlv) - : IntrMessage(ios, ioctlv, Memory::Read_U32(ioctlv.in_vectors[0].address + 8)) + : IntrMessage(ios, ioctlv, ioctlv.GetVector(1)->address) { - length = Memory::Read_U16(ioctlv.in_vectors[0].address + 12); + length = static_cast(ioctlv.GetVector(1)->size); endpoint = Memory::Read_U8(ioctlv.in_vectors[0].address + 14); } V5IsoMessage::V5IsoMessage(Kernel& ios, const IOCtlVRequest& ioctlv) - : IsoMessage(ios, ioctlv, Memory::Read_U32(ioctlv.in_vectors[0].address + 8)) + : IsoMessage(ios, ioctlv, ioctlv.GetVector(2)->address) { num_packets = Memory::Read_U8(ioctlv.in_vectors[0].address + 16); endpoint = Memory::Read_U8(ioctlv.in_vectors[0].address + 17); - packet_sizes_addr = Memory::Read_U32(ioctlv.in_vectors[0].address + 12); + packet_sizes_addr = ioctlv.GetVector(1)->address; for (size_t i = 0; i < num_packets; ++i) packet_sizes.push_back(Memory::Read_U16(static_cast(packet_sizes_addr + i * sizeof(u16)))); - length = std::accumulate(packet_sizes.begin(), packet_sizes.end(), 0); + length = static_cast(ioctlv.GetVector(2)->size); } } // namespace USB From 5d826354498e2795ee9104c83fbc312b09a58ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 3 Nov 2017 12:43:36 +0100 Subject: [PATCH 8/9] USB_HIDv5: Implement CancelEndpoint --- Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp | 24 ++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp index c8f6b8f241..0b5dc3114a 100644 --- a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp @@ -107,12 +107,24 @@ s32 USB_HIDv5::SubmitTransfer(USBV5Device& device, USB::Device& host_device, 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)); + const u8 value = Memory::Read_U8(request.buffer_in + 8); + u8 endpoint = 0; + switch (value) + { + case 0: + // TODO: cancel all queued control transfers with return code -7022. + endpoint = 0; + break; + case 1: + // TODO: cancel all queued interrupt transfers with return code -7022. + endpoint = m_additional_device_data[&device - m_usbv5_devices.data()].interrupt_in_endpoint; + break; + case 2: + // TODO: cancel all queued interrupt transfers with return code -7022. + endpoint = m_additional_device_data[&device - m_usbv5_devices.data()].interrupt_out_endpoint; + break; + } + GetDeviceById(device.host_id)->CancelTransfer(endpoint); return GetDefaultReply(IPC_SUCCESS); } From 18a947a1bc407a89331c9511af648b262cc75028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 3 Nov 2017 13:18:42 +0100 Subject: [PATCH 9/9] USB: Work around a gcc bug that affects lambdas https://stackoverflow.com/questions/32097759 --- Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp | 17 ++++++++++------- Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp | 21 ++++++++++++--------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp index 0b5dc3114a..98e8fcba5b 100644 --- a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp @@ -37,14 +37,16 @@ IPCCommandResult USB_HIDv5::IOCtl(const IOCtlRequest& request) case USB::IOCTL_USBV5_SHUTDOWN: return Shutdown(request); case USB::IOCTL_USBV5_GETDEVPARAMS: - return HandleDeviceIOCtl(request, [&](auto& device) { return GetDeviceInfo(device, request); }); + return HandleDeviceIOCtl(request, + [&](USBV5Device& 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); }); + return HandleDeviceIOCtl(request, + [&](USBV5Device& device) { return SuspendResume(device, request); }); case USB::IOCTL_USBV5_CANCELENDPOINT: return HandleDeviceIOCtl(request, - [&](auto& device) { return CancelEndpoint(device, request); }); + [&](USBV5Device& device) { return CancelEndpoint(device, request); }); default: request.DumpUnknown(GetDeviceName(), LogTypes::IOS_USB, LogTypes::LERROR); return GetDefaultReply(IPC_SUCCESS); @@ -151,10 +153,11 @@ IPCCommandResult USB_HIDv5::GetDeviceInfo(USBV5Device& device, const IOCtlReques 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; - }); + auto it = std::find_if(interfaces.begin(), interfaces.end(), + [&](const USB::InterfaceDescriptor& interface) { + return interface.bInterfaceNumber == device.interface_number && + interface.bAlternateSetting == alt_setting; + }); if (it == interfaces.end()) return GetDefaultReply(IPC_EINVAL); it->Swap(); diff --git a/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp b/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp index 93fad5d0c1..8e71f5b6d9 100644 --- a/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp +++ b/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp @@ -45,17 +45,19 @@ IPCCommandResult USB_VEN::IOCtl(const IOCtlRequest& request) case USB::IOCTL_USBV5_SHUTDOWN: return Shutdown(request); case USB::IOCTL_USBV5_GETDEVPARAMS: - return HandleDeviceIOCtl(request, [&](auto& device) { return GetDeviceInfo(device, request); }); + return HandleDeviceIOCtl(request, + [&](USBV5Device& device) { return GetDeviceInfo(device, request); }); case USB::IOCTL_USBV5_ATTACHFINISH: return GetDefaultReply(IPC_SUCCESS); case USB::IOCTL_USBV5_SETALTERNATE: - return HandleDeviceIOCtl(request, - [&](auto& device) { return SetAlternateSetting(device, request); }); + return HandleDeviceIOCtl( + request, [&](USBV5Device& device) { return SetAlternateSetting(device, request); }); case USB::IOCTL_USBV5_SUSPEND_RESUME: - return HandleDeviceIOCtl(request, [&](auto& device) { return SuspendResume(device, request); }); + return HandleDeviceIOCtl(request, + [&](USBV5Device& device) { return SuspendResume(device, request); }); case USB::IOCTL_USBV5_CANCELENDPOINT: return HandleDeviceIOCtl(request, - [&](auto& device) { return CancelEndpoint(device, request); }); + [&](USBV5Device& device) { return CancelEndpoint(device, request); }); default: request.DumpUnknown(GetDeviceName(), LogTypes::IOS_USB, LogTypes::LERROR); return GetDefaultReply(IPC_SUCCESS); @@ -141,10 +143,11 @@ IPCCommandResult USB_VEN::GetDeviceInfo(USBV5Device& device, const IOCtlRequest& Memory::CopyToEmu(request.buffer_out + 40, &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; - }); + auto it = std::find_if(interfaces.begin(), interfaces.end(), + [&](const USB::InterfaceDescriptor& interface) { + return interface.bInterfaceNumber == device.interface_number && + interface.bAlternateSetting == alt_setting; + }); if (it == interfaces.end()) return GetDefaultReply(IPC_EINVAL); it->Swap();