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) #pragma pack(push, 1)
struct DeviceID struct DeviceID
{ {
u8 ipc_address_shifted; u8 reserved;
u8 index; u8 index;
u16 number; u16 number;
}; };
@ -263,10 +263,17 @@ void USBV5ResourceManager::TriggerDeviceChangeReply()
continue; continue;
DeviceEntry entry; DeviceEntry entry;
if (HasInterfaceNumberInIDs())
{
entry.id.reserved = usbv5_device.interface_number;
}
else
{
// The actual value is static_cast<u8>(hook_internal_ipc_request >> 8). // The actual value is static_cast<u8>(hook_internal_ipc_request >> 8).
// Since we don't actually emulate the IOS kernel and internal IPC, // Since we don't actually emulate the IOS kernel and internal IPC,
// just pretend the value is 0xe7 (most common value according to hwtests). // just pretend the value is 0xe7 (most common value according to hwtests).
entry.id.ipc_address_shifted = 0xe7; entry.id.reserved = 0xe7;
}
entry.id.index = static_cast<u8>(std::distance(m_usbv5_devices.cbegin(), it.base()) - 1); entry.id.index = static_cast<u8>(std::distance(m_usbv5_devices.cbegin(), it.base()) - 1);
entry.id.number = Common::swap16(usbv5_device.number); entry.id.number = Common::swap16(usbv5_device.number);
entry.vid = Common::swap16(device->GetVid()); 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 OnDeviceChange(ChangeEvent event, std::shared_ptr<USB::Device> device) override;
void OnDeviceChangeEnd() override; void OnDeviceChangeEnd() override;
void TriggerDeviceChangeReply(); void TriggerDeviceChangeReply();
virtual bool HasInterfaceNumberInIDs() const = 0;
bool m_devicechange_first_call = true; bool m_devicechange_first_call = true;
std::mutex m_devicechange_hook_address_mutex; std::mutex m_devicechange_hook_address_mutex;

View File

@ -4,12 +4,15 @@
#include "Core/IOS/USB/USB_HID/HIDv5.h" #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/HW/Memmap.h"
#include "Core/IOS/Device.h"
#include "Core/IOS/USB/Common.h" #include "Core/IOS/USB/Common.h"
#include "Core/IOS/USB/USBV5.h"
namespace IOS namespace IOS
{ {
@ -17,14 +20,9 @@ namespace HLE
{ {
namespace Device 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() USB_HIDv5::~USB_HIDv5() = default;
{
StopThreads();
}
IPCCommandResult USB_HIDv5::IOCtl(const IOCtlRequest& request) IPCCommandResult USB_HIDv5::IOCtl(const IOCtlRequest& request)
{ {
@ -32,32 +30,120 @@ IPCCommandResult USB_HIDv5::IOCtl(const IOCtlRequest& request)
switch (request.request) switch (request.request)
{ {
case USB::IOCTL_USBV5_GETVERSION: 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_SHUTDOWN:
if (m_hanging_request)
{
IOCtlRequest hanging_request{m_hanging_request};
m_ios.EnqueueIPCReply(hanging_request, IPC_SUCCESS);
}
return GetDefaultReply(IPC_SUCCESS); return GetDefaultReply(IPC_SUCCESS);
case USB::IOCTL_USBV5_GETDEVICECHANGE: case USB::IOCTL_USBV5_GETDEVICECHANGE:
if (m_devicechange_replied) return GetDeviceChange(request);
{ case USB::IOCTL_USBV5_SHUTDOWN:
m_hanging_request = request.address; return Shutdown(request);
return GetNoReply(); case USB::IOCTL_USBV5_GETDEVPARAMS:
} return HandleDeviceIOCtl(request, [&](auto& device) { return GetDeviceInfo(device, request); });
else case USB::IOCTL_USBV5_ATTACHFINISH:
{
m_devicechange_replied = true;
return GetDefaultReply(IPC_SUCCESS); 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: default:
request.DumpUnknown(GetDeviceName(), LogTypes::IOS_USB); request.DumpUnknown(GetDeviceName(), LogTypes::IOS_USB, LogTypes::LERROR);
return GetDefaultReply(IPC_SUCCESS); 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 Device
} // namespace HLE } // namespace HLE
} // namespace IOS } // namespace IOS

View File

@ -4,11 +4,10 @@
#pragma once #pragma once
#include <string> #include "Core/IOS/Device.h"
#include "Common/CommonTypes.h"
#include "Core/IOS/IOS.h" #include "Core/IOS/IOS.h"
#include "Core/IOS/USB/Host.h" #include "Core/IOS/USB/Host.h"
#include "Core/IOS/USB/USBV5.h"
namespace IOS namespace IOS
{ {
@ -16,22 +15,22 @@ namespace HLE
{ {
namespace Device namespace Device
{ {
// Stub implementation that only gets DQX to boot. class USB_HIDv5 final : public USBV5ResourceManager
class USB_HIDv5 : public USBHost
{ {
public: public:
USB_HIDv5(Kernel& ios, const std::string& device_name); using USBV5ResourceManager::USBV5ResourceManager;
~USB_HIDv5() override; ~USB_HIDv5() override;
IPCCommandResult IOCtl(const IOCtlRequest& request) override; IPCCommandResult IOCtl(const IOCtlRequest& request) override;
IPCCommandResult IOCtlV(const IOCtlVRequest& request) override;
private: 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 ShouldAddDevice(const USB::Device& device) const override;
bool m_devicechange_replied = false; bool HasInterfaceNumberInIDs() const override { return true; }
}; };
} // namespace Device } // namespace Device
} // namespace HLE } // namespace HLE
} // namespace IOS } // namespace IOS

View File

@ -28,6 +28,8 @@ public:
private: private:
IPCCommandResult CancelEndpoint(USBV5Device& device, const IOCtlRequest& request); IPCCommandResult CancelEndpoint(USBV5Device& device, const IOCtlRequest& request);
IPCCommandResult GetDeviceInfo(USBV5Device& device, const IOCtlRequest& request); IPCCommandResult GetDeviceInfo(USBV5Device& device, const IOCtlRequest& request);
bool HasInterfaceNumberInIDs() const override { return false; }
}; };
} // namespace Device } // namespace Device
} // namespace HLE } // namespace HLE