Merge pull request #6164 from leoetlino/usb-hid
IOS/USB: Implement HIDv5
This commit is contained in:
commit
a75546747e
|
@ -7,6 +7,7 @@
|
|||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
#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)
|
||||
|
|
|
@ -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<IOVector> in_vectors;
|
||||
std::vector<IOVector> 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,
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include <algorithm>
|
||||
|
||||
#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<u8>* buffer, const void* data, const size_t size)
|
||||
void DeviceDescriptor::Swap()
|
||||
{
|
||||
buffer->insert(buffer->end(), static_cast<const u8*>(data), static_cast<const u8*>(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<u8>* 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<u8>* buffer, ConfigDescriptor descriptor)
|
||||
void InterfaceDescriptor::Swap()
|
||||
{
|
||||
descriptor.wTotalLength = Common::swap16(descriptor.wTotalLength);
|
||||
CopyToBufferAligned(buffer, &descriptor, descriptor.bLength);
|
||||
}
|
||||
|
||||
static void CopyDescriptorToBuffer(std::vector<u8>* buffer, InterfaceDescriptor descriptor)
|
||||
void EndpointDescriptor::Swap()
|
||||
{
|
||||
CopyToBufferAligned(buffer, &descriptor, descriptor.bLength);
|
||||
}
|
||||
|
||||
static void CopyDescriptorToBuffer(std::vector<u8>* 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<u8> Device::GetDescriptorsUSBV4() const
|
||||
{
|
||||
return GetDescriptors([](const auto& descriptor) { return true; });
|
||||
}
|
||||
|
||||
std::vector<u8> 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<u8>
|
||||
Device::GetDescriptors(std::function<bool(const InterfaceDescriptor&)> predicate) const
|
||||
{
|
||||
std::vector<u8> 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<u8>(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<u8>(c), descriptor.bInterfaceNumber, descriptor.bAlternateSetting))
|
||||
CopyDescriptorToBuffer(&buffer, endpoint_descriptor);
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
wMaxPacketSize = Common::swap16(wMaxPacketSize);
|
||||
}
|
||||
|
||||
std::string Device::GetErrorName(const int error_code) const
|
||||
|
|
|
@ -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<u8> GetDescriptorsUSBV4() const;
|
||||
std::vector<u8> GetDescriptorsUSBV5(u8 interface, u8 alt_setting) const;
|
||||
|
||||
virtual DeviceDescriptor GetDeviceDescriptor() const = 0;
|
||||
virtual std::vector<ConfigDescriptor> GetConfigurations() const = 0;
|
||||
|
@ -178,7 +180,6 @@ public:
|
|||
virtual int SubmitTransfer(std::unique_ptr<IsoMessage> message) = 0;
|
||||
|
||||
protected:
|
||||
std::vector<u8> GetDescriptors(std::function<bool(const InterfaceDescriptor&)> predicate) const;
|
||||
u64 m_id = 0xFFFFFFFFFFFFFFFF;
|
||||
};
|
||||
} // namespace USB
|
||||
|
|
|
@ -4,13 +4,15 @@
|
|||
|
||||
#include "Core/IOS/USB/USBV5.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
#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
|
||||
{
|
||||
|
@ -19,39 +21,258 @@ 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<u16>(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<u16>(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<u16>(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<u32>(packet_sizes_addr + i * sizeof(u16))));
|
||||
length = std::accumulate(packet_sizes.begin(), packet_sizes.end(), 0);
|
||||
length = static_cast<u16>(ioctlv.GetVector(2)->size);
|
||||
}
|
||||
} // namespace USB
|
||||
|
||||
namespace Device
|
||||
{
|
||||
namespace
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
struct DeviceID
|
||||
{
|
||||
u8 reserved;
|
||||
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<IOCtlRequest>(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<std::mutex> lk{m_devicechange_hook_address_mutex};
|
||||
m_devicechange_hook_request = std::make_unique<IOCtlRequest>(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<std::mutex> 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);
|
||||
}
|
||||
|
||||
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<std::mutex> 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<USB::Device> device)
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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;
|
||||
if (HasInterfaceNumberInIDs())
|
||||
{
|
||||
entry.id.reserved = usbv5_device.interface_number;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The actual value is static_cast<u8>(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<u8>(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
|
||||
|
|
|
@ -4,8 +4,18 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#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);
|
||||
|
||||
using Handler = std::function<IPCCommandResult(USBV5Device&)>;
|
||||
IPCCommandResult HandleDeviceIOCtl(const IOCtlRequest& request, Handler handler);
|
||||
|
||||
void OnDeviceChange(ChangeEvent event, std::shared_ptr<USB::Device> 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;
|
||||
std::unique_ptr<IOCtlRequest> 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<USBV5Device, 32> m_usbv5_devices{};
|
||||
mutable std::mutex m_usbv5_devices_mutex;
|
||||
u16 m_current_device_number = 0x21;
|
||||
};
|
||||
} // namespace Device
|
||||
} // namespace HLE
|
||||
} // namespace IOS
|
||||
|
|
|
@ -210,6 +210,40 @@ void USB_HIDv4::TriggerDeviceChangeReply()
|
|||
m_devicechange_hook_request.reset();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void CopyDescriptorToBuffer(std::vector<u8>* buffer, T descriptor)
|
||||
{
|
||||
const size_t size = sizeof(descriptor);
|
||||
descriptor.Swap();
|
||||
buffer->insert(buffer->end(), reinterpret_cast<const u8*>(&descriptor),
|
||||
reinterpret_cast<const u8*>(&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<u8> GetDescriptors(const USB::Device& device)
|
||||
{
|
||||
std::vector<u8> 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<u8>(c));
|
||||
for (size_t i = interfaces.size(); i-- > 0;)
|
||||
{
|
||||
CopyDescriptorToBuffer(&buffer, interfaces[i]);
|
||||
for (const auto& endpoint_descriptor : device.GetEndpoints(
|
||||
static_cast<u8>(c), interfaces[i].bInterfaceNumber, interfaces[i].bAlternateSetting))
|
||||
{
|
||||
CopyDescriptorToBuffer(&buffer, endpoint_descriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::vector<u8> USB_HIDv4::GetDeviceEntry(const USB::Device& device) const
|
||||
{
|
||||
std::lock_guard<std::mutex> id_map_lock{m_id_map_mutex};
|
||||
|
@ -219,7 +253,7 @@ std::vector<u8> USB_HIDv4::GetDeviceEntry(const USB::Device& device) const
|
|||
// 4-8 bytes: device ID
|
||||
// the rest of the buffer is device descriptors data
|
||||
std::vector<u8> entry(8);
|
||||
const std::vector<u8> descriptors = device.GetDescriptorsUSBV4();
|
||||
const std::vector<u8> descriptors = GetDescriptors(device);
|
||||
const u32 entry_size = Common::swap32(static_cast<u32>(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));
|
||||
|
|
|
@ -4,12 +4,15 @@
|
|||
|
||||
#include "Core/IOS/USB/USB_HID/HIDv5.h"
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#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,170 @@ 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,
|
||||
[&](USBV5Device& device) { return GetDeviceInfo(device, request); });
|
||||
case USB::IOCTL_USBV5_ATTACHFINISH:
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
case USB::IOCTL_USBV5_SUSPEND_RESUME:
|
||||
return HandleDeviceIOCtl(request,
|
||||
[&](USBV5Device& device) { return SuspendResume(device, request); });
|
||||
case USB::IOCTL_USBV5_CANCELENDPOINT:
|
||||
return HandleDeviceIOCtl(request,
|
||||
[&](USBV5Device& 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<std::mutex> 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(*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<USB::V5CtrlMessage>(m_ios, ioctlv));
|
||||
case USB::IOCTLV_USBV5_INTRMSG:
|
||||
{
|
||||
auto message = std::make_unique<USB::V5IntrMessage>(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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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<USB::Device> 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<USB::InterfaceDescriptor> interfaces = host_device->GetInterfaces(0);
|
||||
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();
|
||||
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 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));
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -4,11 +4,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#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,29 @@ 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);
|
||||
s32 SubmitTransfer(USBV5Device& device, USB::Device& host_device, const IOCtlVRequest& ioctlv);
|
||||
|
||||
u32 m_hanging_request = 0;
|
||||
bool m_devicechange_replied = false;
|
||||
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<AdditionalDeviceData, 32> m_additional_device_data{};
|
||||
};
|
||||
|
||||
} // namespace Device
|
||||
} // namespace HLE
|
||||
} // namespace IOS
|
||||
|
|
|
@ -4,21 +4,15 @@
|
|||
|
||||
#include "Core/IOS/USB/USB_VEN/VEN.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#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
|
||||
{
|
||||
|
@ -26,14 +20,9 @@ namespace HLE
|
|||
{
|
||||
namespace Device
|
||||
{
|
||||
USB_VEN::USB_VEN(Kernel& ios, const std::string& device_name) : USBHost(ios, device_name)
|
||||
{
|
||||
}
|
||||
constexpr u32 USBV5_VERSION = 0x50001;
|
||||
|
||||
USB_VEN::~USB_VEN()
|
||||
{
|
||||
StopThreads();
|
||||
}
|
||||
USB_VEN::~USB_VEN() = default;
|
||||
|
||||
ReturnCode USB_VEN::Open(const OpenRequest& request)
|
||||
{
|
||||
|
@ -49,22 +38,26 @@ 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);
|
||||
case USB::IOCTL_USBV5_SHUTDOWN:
|
||||
return Shutdown(request);
|
||||
case USB::IOCTL_USBV5_GETDEVPARAMS:
|
||||
return HandleDeviceIOCtl(request, &USB_VEN::GetDeviceInfo);
|
||||
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, &USB_VEN::SetAlternateSetting);
|
||||
return HandleDeviceIOCtl(
|
||||
request, [&](USBV5Device& device) { return SetAlternateSetting(device, request); });
|
||||
case USB::IOCTL_USBV5_SUSPEND_RESUME:
|
||||
return HandleDeviceIOCtl(request, &USB_VEN::SuspendResume);
|
||||
return HandleDeviceIOCtl(request,
|
||||
[&](USBV5Device& device) { return SuspendResume(device, request); });
|
||||
case USB::IOCTL_USBV5_CANCELENDPOINT:
|
||||
return HandleDeviceIOCtl(request, &USB_VEN::CancelEndpoint);
|
||||
return HandleDeviceIOCtl(request,
|
||||
[&](USBV5Device& device) { return CancelEndpoint(device, request); });
|
||||
default:
|
||||
request.DumpUnknown(GetDeviceName(), LogTypes::IOS_USB, LogTypes::LERROR);
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
|
@ -90,135 +83,20 @@ 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<std::mutex> 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);
|
||||
}
|
||||
}
|
||||
|
||||
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<IOCtlRequest>(hook_address);
|
||||
else
|
||||
m_devicechange_hook_request.reset();
|
||||
|
||||
p.Do(m_device_number);
|
||||
p.Do(m_ios_ids);
|
||||
p.Do(m_device_ids);
|
||||
USBHost::DoState(p);
|
||||
}
|
||||
|
||||
std::shared_ptr<USB::Device> USB_VEN::GetDeviceByIOSID(const s32 ios_id) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lk{m_id_map_mutex};
|
||||
const auto iter = m_ios_ids.find(ios_id);
|
||||
if (iter == m_ios_ids.cend())
|
||||
return nullptr;
|
||||
return GetDeviceById(iter->second);
|
||||
}
|
||||
|
||||
u8 USB_VEN::GetInterfaceNumber(const s32 ios_id) const
|
||||
{
|
||||
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<u8>(Memory::Read_U32(request.buffer_in + 2 * sizeof(s32)));
|
||||
device.CancelTransfer(endpoint);
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
IPCCommandResult USB_VEN::GetDeviceChange(const IOCtlRequest& request)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk{m_devicechange_hook_address_mutex};
|
||||
m_devicechange_hook_request = std::make_unique<IOCtlRequest>(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(USB::Device& 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 u8 alt_setting = Memory::Read_U8(request.buffer_in + 8);
|
||||
auto descriptors = device.GetDescriptorsUSBV5(GetInterfaceNumber(device_id), 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(device_id, 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)
|
||||
{
|
||||
const s32 device_id = Memory::Read_U32(request.buffer_in);
|
||||
if (!device.Attach(GetInterfaceNumber(device_id)))
|
||||
return GetDefaultReply(-1);
|
||||
|
||||
const u8 alt_setting = Memory::Read_U8(request.buffer_in + 2 * sizeof(s32));
|
||||
|
||||
const bool success = 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<std::mutex> 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(USB::Device& 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));
|
||||
|
||||
// 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");
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
s32 USB_VEN::SubmitTransfer(USB::Device& device, const IOCtlVRequest& ioctlv)
|
||||
{
|
||||
switch (ioctlv.request)
|
||||
|
@ -236,103 +114,54 @@ s32 USB_VEN::SubmitTransfer(USB::Device& device, const IOCtlVRequest& ioctlv)
|
|||
}
|
||||
}
|
||||
|
||||
IPCCommandResult USB_VEN::HandleDeviceIOCtl(const IOCtlRequest& request, Handler handler)
|
||||
IPCCommandResult USB_VEN::CancelEndpoint(USBV5Device& device, const IOCtlRequest& request)
|
||||
{
|
||||
if (request.buffer_in == 0 || request.buffer_in_size != 0x20)
|
||||
const u8 endpoint = static_cast<u8>(Memory::Read_U32(request.buffer_in + 8));
|
||||
GetDeviceById(device.host_id)->CancelTransfer(endpoint);
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
|
||||
IPCCommandResult USB_VEN::GetDeviceInfo(USBV5Device& device, const IOCtlRequest& request)
|
||||
{
|
||||
if (request.buffer_out == 0 || request.buffer_out_size != 0xc0)
|
||||
return GetDefaultReply(IPC_EINVAL);
|
||||
|
||||
const s32 device_id = Memory::Read_U32(request.buffer_in);
|
||||
const auto device = GetDeviceByIOSID(device_id);
|
||||
if (!device)
|
||||
return GetDefaultReply(IPC_ENOENT);
|
||||
return handler(this, *device, request);
|
||||
}
|
||||
const std::shared_ptr<USB::Device> host_device = GetDeviceById(device.host_id);
|
||||
const u8 alt_setting = Memory::Read_U8(request.buffer_in + 8);
|
||||
|
||||
void USB_VEN::OnDeviceChange(const ChangeEvent event, std::shared_ptr<USB::Device> device)
|
||||
{
|
||||
std::lock_guard<std::mutex> id_map_lock{m_id_map_mutex};
|
||||
if (event == ChangeEvent::Inserted)
|
||||
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 + 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<USB::InterfaceDescriptor> interfaces = host_device->GetInterfaces(0);
|
||||
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();
|
||||
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)
|
||||
{
|
||||
for (const auto& interface : device->GetInterfaces(0))
|
||||
{
|
||||
if (interface.bAlternateSetting != 0)
|
||||
continue;
|
||||
|
||||
DeviceID id;
|
||||
id.unknown = 0xe7;
|
||||
id.interface_plus_1e = interface.bInterfaceNumber + 0x1e;
|
||||
id.zero = 0x00;
|
||||
id.counter = m_device_number;
|
||||
|
||||
s32 ios_device_id = 0;
|
||||
std::memcpy(&ios_device_id, &id, sizeof(id));
|
||||
ios_device_id = Common::swap32(ios_device_id);
|
||||
m_ios_ids[ios_device_id] = device->GetId();
|
||||
m_device_ids[device->GetId()].insert(ios_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());
|
||||
}
|
||||
}
|
||||
|
||||
void USB_VEN::OnDeviceChangeEnd()
|
||||
{
|
||||
std::lock_guard<std::mutex> lk{m_devicechange_hook_address_mutex};
|
||||
TriggerDeviceChangeReply();
|
||||
++m_device_number;
|
||||
}
|
||||
|
||||
void USB_VEN::TriggerDeviceChangeReply()
|
||||
{
|
||||
if (!m_devicechange_hook_request)
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> id_map_lock{m_id_map_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)
|
||||
{
|
||||
if (num_devices >= max_num)
|
||||
{
|
||||
WARN_LOG(IOS_USB, "Too many devices (%d ≥ %zu), skipping", num_devices, max_num);
|
||||
break;
|
||||
}
|
||||
|
||||
const s32 ios_device_id = ios_device.first;
|
||||
const auto device = GetDeviceById(m_ios_ids.at(ios_device_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);
|
||||
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);
|
||||
|
||||
Memory::CopyToEmu(m_devicechange_hook_request->buffer_out + sizeof(entry) * num_devices++,
|
||||
&entry, sizeof(entry));
|
||||
endpoints[i].Swap();
|
||||
Memory::CopyToEmu(request.buffer_out + 64 + 8 * static_cast<u8>(i), &endpoints[i],
|
||||
sizeof(endpoints[i]));
|
||||
}
|
||||
|
||||
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);
|
||||
return GetDefaultReply(IPC_SUCCESS);
|
||||
}
|
||||
} // namespace Device
|
||||
} // namespace HLE
|
||||
|
|
|
@ -4,20 +4,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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
|
||||
{
|
||||
|
@ -25,70 +15,22 @@ 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:
|
||||
#pragma pack(push, 1)
|
||||
struct DeviceID
|
||||
{
|
||||
u8 unknown;
|
||||
u8 interface_plus_1e;
|
||||
u8 zero;
|
||||
u8 counter;
|
||||
};
|
||||
IPCCommandResult CancelEndpoint(USBV5Device& device, const IOCtlRequest& request);
|
||||
IPCCommandResult GetDeviceInfo(USBV5Device& device, const IOCtlRequest& request);
|
||||
|
||||
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<USB::Device> GetDeviceByIOSID(s32 ios_id) const;
|
||||
u8 GetInterfaceNumber(s32 ios_id) const;
|
||||
|
||||
IPCCommandResult CancelEndpoint(USB::Device& 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 Shutdown(const IOCtlRequest& request);
|
||||
IPCCommandResult SuspendResume(USB::Device& device, const IOCtlRequest& request);
|
||||
s32 SubmitTransfer(USB::Device& device, const IOCtlVRequest& request);
|
||||
|
||||
using Handler = std::function<IPCCommandResult(USB_VEN*, USB::Device&, const IOCtlRequest&)>;
|
||||
IPCCommandResult HandleDeviceIOCtl(const IOCtlRequest& request, Handler handler);
|
||||
|
||||
void OnDeviceChange(ChangeEvent, std::shared_ptr<USB::Device>) override;
|
||||
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<IOCtlRequest> 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<s32, u64> m_ios_ids;
|
||||
// USB device IDs => IOS device IDs (one to many, because VEN exposes one device per interface)
|
||||
std::map<u64, std::set<s32>> m_device_ids;
|
||||
s32 SubmitTransfer(USB::Device& device, const IOCtlVRequest& ioctlv);
|
||||
bool HasInterfaceNumberInIDs() const override { return false; }
|
||||
};
|
||||
} // namespace Device
|
||||
} // namespace HLE
|
||||
|
|
Loading…
Reference in New Issue