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!
This commit is contained in:
Léo Lam 2017-11-02 23:12:58 +01:00
parent 180ad8076c
commit ff52333b14
5 changed files with 139 additions and 44 deletions

View File

@ -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<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;
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());

View File

@ -94,6 +94,7 @@ protected:
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;

View File

@ -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,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<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);
}
}
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<u8>(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<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 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

View File

@ -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,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

View File

@ -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