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.
This commit is contained in:
Léo Lam 2017-11-02 19:11:06 +01:00
parent c8710d0861
commit ef8b3cb960
2 changed files with 116 additions and 118 deletions

View File

@ -4,6 +4,7 @@
#include "Core/IOS/USB/USB_VEN/VEN.h" #include "Core/IOS/USB/USB_VEN/VEN.h"
#include <algorithm>
#include <cstddef> #include <cstddef>
#include <cstring> #include <cstring>
#include <memory> #include <memory>
@ -26,6 +27,28 @@ namespace HLE
{ {
namespace Device 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) 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)) if (request.in_vectors.size() + request.io_vectors.size() != s_num_vectors.at(request.request))
return GetDefaultReply(IPC_EINVAL); return GetDefaultReply(IPC_EINVAL);
const s32 device_id = Memory::Read_U32(request.in_vectors[0].address); std::lock_guard<std::mutex> lock{m_usbv5_devices_mutex};
auto device = GetDeviceByIOSID(device_id); USBV5Device* device = GetUSBV5Device(request.in_vectors[0].address);
if (!device || !device->Attach(GetInterfaceNumber(device_id))) if (!device)
return GetDefaultReply(IPC_ENOENT); return GetDefaultReply(IPC_EINVAL);
return HandleTransfer(device, request.request, auto host_device = GetDeviceById(device->host_id);
[&, this]() { return SubmitTransfer(*device, request); }); host_device->Attach(device->interface_number);
return HandleTransfer(host_device, request.request,
[&, this]() { return SubmitTransfer(*host_device, request); });
} }
default: default:
return GetDefaultReply(IPC_EINVAL); return GetDefaultReply(IPC_EINVAL);
@ -112,38 +137,37 @@ void USB_VEN::DoState(PointerWrap& p)
else else
m_devicechange_hook_request.reset(); m_devicechange_hook_request.reset();
p.Do(m_device_number); p.Do(m_usbv5_devices);
p.Do(m_ios_ids);
p.Do(m_device_ids);
USBHost::DoState(p); USBHost::DoState(p);
} }
std::shared_ptr<USB::Device> USB_VEN::GetDeviceByIOSID(const s32 ios_id) const USB_VEN::USBV5Device* USB_VEN::GetUSBV5Device(u32 in_buffer)
{ {
std::lock_guard<std::mutex> lk{m_id_map_mutex}; const u8 index = Memory::Read_U8(in_buffer + offsetof(DeviceID, index));
const auto iter = m_ios_ids.find(ios_id); const u16 number = Memory::Read_U16(in_buffer + offsetof(DeviceID, number));
if (iter == m_ios_ids.cend())
if (index >= m_usbv5_devices.size())
return nullptr; 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); const u8 endpoint = static_cast<u8>(Memory::Read_U32(request.buffer_in + 8));
DeviceID device_id; GetDeviceById(device.host_id)->CancelTransfer(endpoint);
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); return GetDefaultReply(IPC_SUCCESS);
} }
IPCCommandResult USB_VEN::GetDeviceChange(const IOCtlRequest& request) IPCCommandResult USB_VEN::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}; std::lock_guard<std::mutex> lk{m_devicechange_hook_address_mutex};
m_devicechange_hook_request = std::make_unique<IOCtlRequest>(request.address); 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) // 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(); 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) if (request.buffer_out == 0 || request.buffer_out_size != 0xc0)
return GetDefaultReply(IPC_EINVAL); return GetDefaultReply(IPC_EINVAL);
const auto host_device = GetDeviceById(device.host_id);
const u8 alt_setting = Memory::Read_U8(request.buffer_in + 8); 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()) if (descriptors.empty())
return GetDefaultReply(IPC_ENOENT); 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."); 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::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::Write_U32(1, request.buffer_out + 4);
Memory::CopyToEmu(request.buffer_out + 20, descriptors.data(), descriptors.size()); Memory::CopyToEmu(request.buffer_out + 20, descriptors.data(), descriptors.size());
return GetDefaultReply(IPC_SUCCESS); 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); const auto host_device = GetDeviceById(device.host_id);
if (!device.Attach(GetInterfaceNumber(device_id))) if (!host_device->Attach(device.interface_number))
return GetDefaultReply(-1); return GetDefaultReply(-1);
const u8 alt_setting = Memory::Read_U8(request.buffer_in + 2 * sizeof(s32)); 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); return GetDefaultReply(success ? IPC_SUCCESS : IPC_EINVAL);
} }
@ -207,15 +231,15 @@ IPCCommandResult USB_VEN::Shutdown(const IOCtlRequest& request)
return GetDefaultReply(IPC_SUCCESS); 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 auto host_device = GetDeviceById(device.host_id);
const s32 resumed = Memory::Read_U32(request.buffer_in + 2 * sizeof(s32)); 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 // Note: this is unimplemented because there's no easy way to do this in a
// platform-independant way (libusb does not support power management). // platform-independant way (libusb does not support power management).
INFO_LOG(IOS_USB, "[%04x:%04x %d] Received %s command", device.GetVid(), device.GetPid(), INFO_LOG(IOS_USB, "[%04x:%04x %d] Received %s command", host_device->GetVid(),
GetInterfaceNumber(device_id), resumed == 0 ? "suspend" : "resume"); host_device->GetPid(), device.interface_number, resumed == 0 ? "suspend" : "resume");
return GetDefaultReply(IPC_SUCCESS); 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) if (request.buffer_in == 0 || request.buffer_in_size != 0x20)
return GetDefaultReply(IPC_EINVAL); return GetDefaultReply(IPC_EINVAL);
const s32 device_id = Memory::Read_U32(request.buffer_in); std::lock_guard<std::mutex> lock{m_usbv5_devices_mutex};
const auto device = GetDeviceByIOSID(device_id); USBV5Device* device = GetUSBV5Device(request.buffer_in);
if (!device) if (!device)
return GetDefaultReply(IPC_ENOENT); return GetDefaultReply(IPC_EINVAL);
return handler(this, *device, request); return handler(this, *device, request);
} }
void USB_VEN::OnDeviceChange(const ChangeEvent event, std::shared_ptr<USB::Device> device) 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}; std::lock_guard<std::mutex> lock{m_usbv5_devices_mutex};
const u64 host_device_id = device->GetId();
if (event == ChangeEvent::Inserted) if (event == ChangeEvent::Inserted)
{ {
for (const auto& interface : device->GetInterfaces(0)) for (const auto& interface : device->GetInterfaces(0))
@ -258,24 +283,24 @@ void USB_VEN::OnDeviceChange(const ChangeEvent event, std::shared_ptr<USB::Devic
if (interface.bAlternateSetting != 0) if (interface.bAlternateSetting != 0)
continue; continue;
DeviceID id; auto it = std::find_if(m_usbv5_devices.rbegin(), m_usbv5_devices.rend(),
id.unknown = 0xe7; [](const USBV5Device& entry) { return !entry.in_use; });
id.interface_plus_1e = interface.bInterfaceNumber + 0x1e; if (it == m_usbv5_devices.rend())
id.zero = 0x00; return;
id.counter = m_device_number;
s32 ios_device_id = 0; it->in_use = true;
std::memcpy(&ios_device_id, &id, sizeof(id)); it->interface_number = interface.bInterfaceNumber;
ios_device_id = Common::swap32(ios_device_id); it->number = m_current_device_number;
m_ios_ids[ios_device_id] = device->GetId(); it->host_id = host_device_id;
m_device_ids[device->GetId()].insert(ios_device_id);
} }
} }
else if (event == ChangeEvent::Removed) else if (event == ChangeEvent::Removed)
{ {
for (const s32 ios_id : m_device_ids[device->GetId()]) for (USBV5Device& entry : m_usbv5_devices)
m_ios_ids.erase(ios_id); {
m_device_ids.erase(device->GetId()); if (entry.host_id == host_device_id)
entry.in_use = false;
}
} }
} }
@ -283,7 +308,7 @@ void USB_VEN::OnDeviceChangeEnd()
{ {
std::lock_guard<std::mutex> lk{m_devicechange_hook_address_mutex}; std::lock_guard<std::mutex> lk{m_devicechange_hook_address_mutex};
TriggerDeviceChangeReply(); TriggerDeviceChangeReply();
++m_device_number; ++m_current_device_number;
} }
void USB_VEN::TriggerDeviceChangeReply() void USB_VEN::TriggerDeviceChangeReply()
@ -291,40 +316,30 @@ void USB_VEN::TriggerDeviceChangeReply()
if (!m_devicechange_hook_request) if (!m_devicechange_hook_request)
return; return;
std::lock_guard<std::mutex> id_map_lock{m_id_map_mutex}; std::lock_guard<std::mutex> lock{m_usbv5_devices_mutex};
u8 num_devices = 0; u8 num_devices = 0;
const size_t max_num = m_devicechange_hook_request->buffer_out_size / sizeof(DeviceEntry); for (auto it = m_usbv5_devices.crbegin(); it != m_usbv5_devices.crend(); ++it)
for (const auto& ios_device : m_ios_ids)
{ {
if (num_devices >= max_num) const USBV5Device& usbv5_device = *it;
{ if (!usbv5_device.in_use)
WARN_LOG(IOS_USB, "Too many devices (%d ≥ %zu), skipping", num_devices, max_num); continue;
break;
}
const s32 ios_device_id = ios_device.first; const auto device = GetDeviceById(usbv5_device.host_id);
const auto device = GetDeviceById(m_ios_ids.at(ios_device_id));
if (!device) if (!device)
continue; 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; DeviceEntry entry;
entry.device_id = Common::swap32(ios_device_id); // 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.ipc_address_shifted = 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.vid = Common::swap16(device->GetVid());
entry.pid = Common::swap16(device->GetPid()); entry.pid = Common::swap16(device->GetPid());
entry.unknown = 0x00; entry.number = Common::swap16(usbv5_device.number);
entry.device_number = ios_device_id & 0xff; entry.interface_number = usbv5_device.interface_number;
entry.interface_number = interface_number; entry.num_altsettings = device->GetNumberOfAltSettings(entry.interface_number);
entry.num_altsettings = device->GetNumberOfAltSettings(interface_number);
Memory::CopyToEmu(m_devicechange_hook_request->buffer_out + sizeof(entry) * num_devices++, Memory::CopyToEmu(m_devicechange_hook_request->buffer_out + sizeof(entry) * num_devices++,
&entry, sizeof(entry)); &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_ios.EnqueueIPCReply(*m_devicechange_hook_request, num_devices, 0, CoreTiming::FromThread::ANY);
m_devicechange_hook_request.reset(); 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 Device
} // namespace HLE } // namespace HLE

View File

@ -5,10 +5,8 @@
#pragma once #pragma once
#include <functional> #include <functional>
#include <map>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <set>
#include <string> #include <string>
#include <vector> #include <vector>
@ -38,39 +36,18 @@ public:
void DoState(PointerWrap& p) override; void DoState(PointerWrap& p) override;
private: private:
#pragma pack(push, 1) struct USBV5Device;
struct DeviceID USBV5Device* GetUSBV5Device(u32 in_buffer);
{
u8 unknown;
u8 interface_plus_1e;
u8 zero;
u8 counter;
};
struct DeviceEntry IPCCommandResult CancelEndpoint(USBV5Device& device, const IOCtlRequest& request);
{
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 GetDeviceChange(const IOCtlRequest& request);
IPCCommandResult GetDeviceInfo(USB::Device& device, const IOCtlRequest& request); IPCCommandResult GetDeviceInfo(USBV5Device& device, const IOCtlRequest& request);
IPCCommandResult SetAlternateSetting(USB::Device& device, const IOCtlRequest& request); IPCCommandResult SetAlternateSetting(USBV5Device& device, const IOCtlRequest& request);
IPCCommandResult Shutdown(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); s32 SubmitTransfer(USB::Device& device, const IOCtlVRequest& request);
using Handler = std::function<IPCCommandResult(USB_VEN*, USB::Device&, const IOCtlRequest&)>; using Handler = std::function<IPCCommandResult(USB_VEN*, USBV5Device&, const IOCtlRequest&)>;
IPCCommandResult HandleDeviceIOCtl(const IOCtlRequest& request, Handler handler); IPCCommandResult HandleDeviceIOCtl(const IOCtlRequest& request, Handler handler);
void OnDeviceChange(ChangeEvent, std::shared_ptr<USB::Device>) override; void OnDeviceChange(ChangeEvent, std::shared_ptr<USB::Device>) override;
@ -83,12 +60,18 @@ private:
std::mutex m_devicechange_hook_address_mutex; std::mutex m_devicechange_hook_address_mutex;
std::unique_ptr<IOCtlRequest> m_devicechange_hook_request; std::unique_ptr<IOCtlRequest> m_devicechange_hook_request;
mutable std::mutex m_id_map_mutex; // Each interface of a USB device is internally considered as a unique device.
u8 m_device_number = 0x21; // USBv5 resource managers can handle up to 32 devices/interfaces.
// IOS device IDs => USB device IDs (one to one) struct USBV5Device
std::map<s32, u64> m_ios_ids; {
// USB device IDs => IOS device IDs (one to many, because VEN exposes one device per interface) bool in_use = false;
std::map<u64, std::set<s32>> m_device_ids; u8 interface_number;
u16 number;
u64 host_id;
};
std::array<USBV5Device, 32> m_usbv5_devices;
mutable std::mutex m_usbv5_devices_mutex;
u16 m_current_device_number = 0x21;
}; };
} // namespace Device } // namespace Device
} // namespace HLE } // namespace HLE