mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-15 13:57:57 -07:00
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:
parent
180ad8076c
commit
ff52333b14
@ -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());
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user