mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-15 13:57:57 -07:00
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:
parent
c8710d0861
commit
ef8b3cb960
@ -4,6 +4,7 @@
|
||||
|
||||
#include "Core/IOS/USB/USB_VEN/VEN.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
@ -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<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);
|
||||
@ -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::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 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<u8>(Memory::Read_U32(request.buffer_in + 2 * sizeof(s32)));
|
||||
device.CancelTransfer(endpoint);
|
||||
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::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)
|
||||
@ -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<std::mutex> 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<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)
|
||||
{
|
||||
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)
|
||||
continue;
|
||||
|
||||
DeviceID id;
|
||||
id.unknown = 0xe7;
|
||||
id.interface_plus_1e = interface.bInterfaceNumber + 0x1e;
|
||||
id.zero = 0x00;
|
||||
id.counter = m_device_number;
|
||||
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;
|
||||
|
||||
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);
|
||||
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<std::mutex> 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<std::mutex> id_map_lock{m_id_map_mutex};
|
||||
std::lock_guard<std::mutex> 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<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.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
|
||||
|
@ -5,10 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -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<USB::Device> 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<IPCCommandResult(USB_VEN*, USB::Device&, const IOCtlRequest&)>;
|
||||
using Handler = std::function<IPCCommandResult(USB_VEN*, USBV5Device&, const IOCtlRequest&)>;
|
||||
IPCCommandResult HandleDeviceIOCtl(const IOCtlRequest& request, Handler handler);
|
||||
|
||||
void OnDeviceChange(ChangeEvent, std::shared_ptr<USB::Device>) override;
|
||||
@ -83,12 +60,18 @@ private:
|
||||
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;
|
||||
// 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<USBV5Device, 32> m_usbv5_devices;
|
||||
mutable std::mutex m_usbv5_devices_mutex;
|
||||
u16 m_current_device_number = 0x21;
|
||||
};
|
||||
} // namespace Device
|
||||
} // namespace HLE
|
||||
|
Loading…
Reference in New Issue
Block a user