IOS: Add base Host and USB::Device classes

The Host class will be used by the OH0, VEN and HID implementations
as the base class for the IOS HLE device. It handles scanning devices,
detecting device changes and everything that will be needed for OH0,
VEN and HID to be implemented, while mostly abstracting libusb away.

The Device class is for actual USB devices. This commit adds a
LibusbDevice which interacts with a real USB device and enables
USB passthrough.
This commit is contained in:
Léo Lam 2016-11-13 14:16:13 +01:00
parent b8c651eac4
commit 73e55ccf44
9 changed files with 1079 additions and 2 deletions

View File

@ -154,6 +154,7 @@ set(SRCS ActionReplay.cpp
IOS/SDIO/SDIOSlot0.cpp
IOS/STM/STM.cpp
IOS/USB/Common.cpp
IOS/USB/Host.cpp
IOS/USB/USBV0.cpp
IOS/USB/USB_KBD.cpp
IOS/USB/USB_VEN.cpp
@ -264,7 +265,8 @@ set(LIBS
if(LIBUSB_FOUND)
# Using shared LibUSB
set(LIBS ${LIBS} ${LIBUSB_LIBRARIES})
set(SRCS ${SRCS} IOS/USB/USB_HIDv4.cpp
set(SRCS ${SRCS} IOS/USB/LibusbDevice.cpp
IOS/USB/USB_HIDv4.cpp
IOS/USB/Bluetooth/BTReal.cpp)
endif()

View File

@ -186,6 +186,12 @@
<ClCompile Include="IOS\SDIO\SDIOSlot0.cpp" />
<ClCompile Include="IOS\STM\STM.cpp" />
<ClCompile Include="IOS\USB\Common.cpp" />
<ClCompile Include="IOS\USB\LibusbDevice.cpp">
<DisableSpecificWarnings>4200;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<ClCompile Include="IOS\USB\Host.cpp">
<DisableSpecificWarnings>4200;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<ClCompile Include="IOS\USB\USBV0.cpp" />
<ClCompile Include="IOS\USB\USB_HIDv4.cpp">
<!--
@ -419,6 +425,8 @@
<ClInclude Include="IOS\SDIO\SDIOSlot0.h" />
<ClInclude Include="IOS\STM\STM.h" />
<ClInclude Include="IOS\USB\Common.h" />
<ClInclude Include="IOS\USB\LibusbDevice.h" />
<ClInclude Include="IOS\USB\Host.h" />
<ClInclude Include="IOS\USB\USBV0.h" />
<ClInclude Include="IOS\USB\USB_HIDv4.h" />
<ClInclude Include="IOS\USB\USB_KBD.h" />

View File

@ -773,6 +773,12 @@
<ClCompile Include="IOS\USB\Common.cpp">
<Filter>IOS\USB</Filter>
</ClCompile>
<ClCompile Include="IOS\USB\LibusbDevice.cpp">
<Filter>IOS\USB</Filter>
</ClCompile>
<ClCompile Include="IOS\USB\Host.cpp">
<Filter>IOS\USB</Filter>
</ClCompile>
<ClCompile Include="IOS\USB\USBV0.cpp">
<Filter>IOS\USB</Filter>
</ClCompile>
@ -1353,6 +1359,12 @@
<ClInclude Include="IOS\USB\Common.h">
<Filter>IOS\USB</Filter>
</ClInclude>
<ClInclude Include="IOS\USB\LibusbDevice.h">
<Filter>IOS\USB</Filter>
</ClInclude>
<ClInclude Include="IOS\USB\Host.h">
<Filter>IOS\USB</Filter>
</ClInclude>
<ClInclude Include="IOS\USB\USBV0.h">
<Filter>IOS\USB</Filter>
</ClInclude>

View File

@ -2,10 +2,15 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "Core/IOS/USB/Common.h"
#include <algorithm>
#include "Common/Align.h"
#include "Common/Assert.h"
#include "Common/CommonFuncs.h"
#include "Common/CommonTypes.h"
#include "Common/StringUtil.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/USB/Common.h"
namespace IOS
{
@ -26,6 +31,121 @@ void TransferCommand::FillBuffer(const u8* src, const size_t size) const
_assert_msg_(IOS_USB, size == 0 || data_address != 0, "Invalid data_address");
Memory::CopyToEmu(data_address, src, size);
}
void IsoMessage::SetPacketReturnValue(const size_t packet_num, const u16 return_value) const
{
Memory::Write_U16(return_value, static_cast<u32>(packet_sizes_addr + packet_num * sizeof(u16)));
}
Device::~Device() = default;
u64 Device::GetId() const
{
return m_id;
}
u16 Device::GetVid() const
{
return GetDeviceDescriptor().idVendor;
}
u16 Device::GetPid() const
{
return GetDeviceDescriptor().idProduct;
}
bool Device::HasClass(const u8 device_class) const
{
if (GetDeviceDescriptor().bDeviceClass == device_class)
return true;
const auto interfaces = GetInterfaces(0);
return std::any_of(interfaces.begin(), interfaces.end(), [device_class](const auto& interface) {
return interface.bInterfaceClass == device_class;
});
}
static void CopyToBufferAligned(std::vector<u8>* buffer, const void* data, const size_t size)
{
buffer->insert(buffer->end(), static_cast<const u8*>(data), static_cast<const u8*>(data) + size);
const size_t number_of_padding_bytes = Common::AlignUp(size, 4) - size;
buffer->insert(buffer->end(), number_of_padding_bytes, 0);
}
static void CopyDescriptorToBuffer(std::vector<u8>* buffer, DeviceDescriptor descriptor)
{
descriptor.bcdUSB = Common::swap16(descriptor.bcdUSB);
descriptor.idVendor = Common::swap16(descriptor.idVendor);
descriptor.idProduct = Common::swap16(descriptor.idProduct);
descriptor.bcdDevice = Common::swap16(descriptor.bcdDevice);
CopyToBufferAligned(buffer, &descriptor, descriptor.bLength);
}
static void CopyDescriptorToBuffer(std::vector<u8>* buffer, ConfigDescriptor descriptor)
{
descriptor.wTotalLength = Common::swap16(descriptor.wTotalLength);
CopyToBufferAligned(buffer, &descriptor, descriptor.bLength);
}
static void CopyDescriptorToBuffer(std::vector<u8>* buffer, InterfaceDescriptor descriptor)
{
CopyToBufferAligned(buffer, &descriptor, descriptor.bLength);
}
static void CopyDescriptorToBuffer(std::vector<u8>* buffer, EndpointDescriptor descriptor)
{
descriptor.wMaxPacketSize = Common::swap16(descriptor.wMaxPacketSize);
// IOS only copies 8 bytes from the endpoint descriptor, regardless of the actual length
CopyToBufferAligned(buffer, &descriptor, sizeof(descriptor));
}
std::vector<u8> Device::GetDescriptorsUSBV4() const
{
return GetDescriptors([](const auto& descriptor) { return true; });
}
std::vector<u8> Device::GetDescriptorsUSBV5(const u8 interface, const u8 alt_setting) const
{
return GetDescriptors([interface, alt_setting](const auto& descriptor) {
// The USBV5 interfaces present each interface as a different device,
// and the descriptors are filtered by alternate setting.
return descriptor.bInterfaceNumber == interface && descriptor.bAlternateSetting == alt_setting;
});
}
std::vector<u8>
Device::GetDescriptors(std::function<bool(const InterfaceDescriptor&)> predicate) const
{
std::vector<u8> buffer;
const auto device_descriptor = GetDeviceDescriptor();
CopyDescriptorToBuffer(&buffer, device_descriptor);
const auto configurations = GetConfigurations();
for (size_t c = 0; c < configurations.size(); ++c)
{
const auto& config_descriptor = configurations[c];
CopyDescriptorToBuffer(&buffer, config_descriptor);
const auto interfaces = GetInterfaces(static_cast<u8>(c));
for (size_t i = interfaces.size(); i-- > 0;)
{
const auto& descriptor = interfaces[i];
if (!predicate(descriptor))
continue;
CopyDescriptorToBuffer(&buffer, descriptor);
for (const auto& endpoint_descriptor : GetEndpoints(
static_cast<u8>(c), descriptor.bInterfaceNumber, descriptor.bAlternateSetting))
CopyDescriptorToBuffer(&buffer, endpoint_descriptor);
}
}
return buffer;
}
std::string Device::GetErrorName(const int error_code) const
{
return StringFromFormat("unknown error %d", error_code);
}
} // namespace USB
} // namespace HLE
} // namespace IOS

View File

@ -5,7 +5,9 @@
#pragma once
#include <cstddef>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "Common/CommonTypes.h"
@ -17,6 +19,29 @@ namespace HLE
{
namespace USB
{
enum StandardDeviceRequestCodes
{
REQUEST_GET_DESCRIPTOR = 6,
REQUEST_SET_CONFIGURATION = 9,
REQUEST_GET_INTERFACE = 10,
REQUEST_SET_INTERFACE = 11,
};
enum ControlRequestTypes
{
DIR_HOST2DEVICE = 0,
DIR_DEVICE2HOST = 1,
TYPE_STANDARD = 0,
TYPE_VENDOR = 2,
REC_DEVICE = 0,
REC_INTERFACE = 1,
};
constexpr u16 USBHDR(u8 dir, u8 type, u8 recipient, u8 request)
{
return static_cast<u16>(((dir << 7 | type << 5 | recipient) << 8) | request);
}
struct DeviceDescriptor
{
u8 bLength;
@ -109,6 +134,49 @@ struct IntrMessage : TransferCommand
u8 endpoint = 0;
using TransferCommand::TransferCommand;
};
struct IsoMessage : TransferCommand
{
u32 packet_sizes_addr = 0;
std::vector<u16> packet_sizes;
u16 length = 0;
u8 num_packets = 0;
u8 endpoint = 0;
using TransferCommand::TransferCommand;
void SetPacketReturnValue(size_t packet_num, u16 return_value) const;
};
class Device
{
public:
virtual ~Device();
u64 GetId() const;
u16 GetVid() const;
u16 GetPid() const;
bool HasClass(u8 device_class) const;
std::vector<u8> GetDescriptorsUSBV4() const;
std::vector<u8> GetDescriptorsUSBV5(u8 interface, u8 alt_setting) const;
virtual DeviceDescriptor GetDeviceDescriptor() const = 0;
virtual std::vector<ConfigDescriptor> GetConfigurations() const = 0;
virtual std::vector<InterfaceDescriptor> GetInterfaces(u8 config) const = 0;
virtual std::vector<EndpointDescriptor> GetEndpoints(u8 config, u8 interface, u8 alt) const = 0;
virtual std::string GetErrorName(int error_code) const;
virtual bool Attach(u8 interface) = 0;
virtual int CancelTransfer(u8 endpoint) = 0;
virtual int ChangeInterface(u8 interface) = 0;
virtual int GetNumberOfAltSettings(u8 interface) = 0;
virtual int SetAltSetting(u8 alt_setting) = 0;
virtual int SubmitTransfer(std::unique_ptr<CtrlMessage> message) = 0;
virtual int SubmitTransfer(std::unique_ptr<BulkMessage> message) = 0;
virtual int SubmitTransfer(std::unique_ptr<IntrMessage> message) = 0;
virtual int SubmitTransfer(std::unique_ptr<IsoMessage> message) = 0;
protected:
std::vector<u8> GetDescriptors(std::function<bool(const InterfaceDescriptor&)> predicate) const;
u64 m_id = 0xFFFFFFFFFFFFFFFF;
};
} // namespace USB
} // namespace HLE
} // namespace IOS

View File

@ -0,0 +1,251 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <algorithm>
#include <memory>
#include <utility>
#ifdef __LIBUSB__
#include <libusb.h>
#endif
#include "Common/Assert.h"
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
#include "Common/Thread.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/IOS/USB/Common.h"
#include "Core/IOS/USB/Host.h"
#include "Core/IOS/USB/LibusbDevice.h"
namespace IOS
{
namespace HLE
{
namespace Device
{
USBHost::USBHost(u32 device_id, const std::string& device_name) : Device(device_id, device_name)
{
#ifdef __LIBUSB__
const int ret = libusb_init(&m_libusb_context);
_assert_msg_(WII_IPC_USB, ret == 0, "Failed to init libusb.");
libusb_set_debug(m_libusb_context, LIBUSB_LOG_LEVEL_WARNING);
#endif
}
USBHost::~USBHost()
{
#ifdef __LIBUSB__
libusb_exit(m_libusb_context);
#endif
}
ReturnCode USBHost::Open(const OpenRequest& request)
{
// Force a device scan to complete, because some games (including Your Shape) only care
// about the initial device list (in the first GETDEVICECHANGE reply).
while (!UpdateDevices())
{
}
StartThreads();
return IPC_SUCCESS;
}
void USBHost::UpdateWantDeterminism(const bool new_want_determinism)
{
if (new_want_determinism)
StopThreads();
else if (IsOpened())
StartThreads();
}
void USBHost::DoState(PointerWrap& p)
{
if (IsOpened() && p.GetMode() == PointerWrap::MODE_READ)
{
// After a state has loaded, there may be insertion hooks for devices that were
// already plugged in, and which need to be triggered.
UpdateDevices(true);
}
}
bool USBHost::AddDevice(std::unique_ptr<USB::Device> device)
{
std::lock_guard<std::mutex> lk(m_devices_mutex);
if (m_devices.find(device->GetId()) != m_devices.end())
return false;
m_devices[device->GetId()] = std::move(device);
return true;
}
std::shared_ptr<USB::Device> USBHost::GetDeviceById(const u64 device_id) const
{
std::lock_guard<std::mutex> lk(m_devices_mutex);
const auto it = m_devices.find(device_id);
if (it == m_devices.end())
return nullptr;
return it->second;
}
void USBHost::OnDeviceChange(ChangeEvent event, std::shared_ptr<USB::Device> changed_device)
{
}
void USBHost::OnDeviceChangeEnd()
{
}
bool USBHost::ShouldAddDevice(const USB::Device& device) const
{
return true;
}
// This is called from the scan thread. Returns false if we failed to update the device list.
bool USBHost::UpdateDevices(const bool always_add_hooks)
{
if (Core::g_want_determinism)
return true;
DeviceChangeHooks hooks;
std::set<u64> plugged_devices;
// If we failed to get a new, up-to-date list of devices, we cannot detect device removals.
if (!AddNewDevices(plugged_devices, hooks, always_add_hooks))
return false;
DetectRemovedDevices(plugged_devices, hooks);
DispatchHooks(hooks);
return true;
}
bool USBHost::AddNewDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks,
const bool always_add_hooks)
{
#ifdef __LIBUSB__
libusb_device** list;
const ssize_t count = libusb_get_device_list(m_libusb_context, &list);
if (count < 0)
{
WARN_LOG(IOS_USB, "Failed to get device list: %s", libusb_error_name(static_cast<int>(count)));
return false;
}
for (ssize_t i = 0; i < count; ++i)
{
libusb_device* device = list[i];
libusb_device_descriptor descriptor;
libusb_get_device_descriptor(device, &descriptor);
if (!SConfig::GetInstance().IsUSBDeviceWhitelisted({descriptor.idVendor, descriptor.idProduct}))
continue;
auto usb_device = std::make_unique<USB::LibusbDevice>(device, descriptor);
if (!ShouldAddDevice(*usb_device))
continue;
const u64 id = usb_device->GetId();
new_devices.insert(id);
if (AddDevice(std::move(usb_device)) || always_add_hooks)
hooks.emplace(GetDeviceById(id), ChangeEvent::Inserted);
}
libusb_free_device_list(list, 1);
#endif
return true;
}
void USBHost::DetectRemovedDevices(const std::set<u64>& plugged_devices, DeviceChangeHooks& hooks)
{
std::lock_guard<std::mutex> lk(m_devices_mutex);
for (auto it = m_devices.begin(); it != m_devices.end();)
{
if (plugged_devices.find(it->second->GetId()) == plugged_devices.end())
{
hooks.emplace(it->second, ChangeEvent::Removed);
it = m_devices.erase(it);
}
else
{
++it;
}
}
}
void USBHost::DispatchHooks(const DeviceChangeHooks& hooks)
{
for (const auto& hook : hooks)
{
INFO_LOG(IOS_USB, "%s - %s device: %04x:%04x", GetDeviceName().c_str(),
hook.second == ChangeEvent::Inserted ? "New" : "Removed", hook.first->GetVid(),
hook.first->GetPid());
OnDeviceChange(hook.second, hook.first);
}
if (!hooks.empty())
OnDeviceChangeEnd();
}
void USBHost::StartThreads()
{
if (Core::g_want_determinism)
return;
if (!m_scan_thread_running.IsSet())
{
m_scan_thread_running.Set();
m_scan_thread = std::thread([this] {
Common::SetCurrentThreadName("USB Scan Thread");
while (m_scan_thread_running.IsSet())
{
UpdateDevices();
Common::SleepCurrentThread(50);
}
});
}
#ifdef __LIBUSB__
if (!m_event_thread_running.IsSet())
{
m_event_thread_running.Set();
m_event_thread = std::thread([this] {
Common::SetCurrentThreadName("USB Passthrough Thread");
while (m_event_thread_running.IsSet())
{
static timeval tv = {0, 50000};
libusb_handle_events_timeout_completed(m_libusb_context, &tv, nullptr);
}
});
}
#endif
}
void USBHost::StopThreads()
{
if (m_scan_thread_running.TestAndClear())
m_scan_thread.join();
// Clear all devices and dispatch removal hooks.
DeviceChangeHooks hooks;
DetectRemovedDevices(std::set<u64>(), hooks);
DispatchHooks(hooks);
#ifdef __LIBUSB__
if (m_event_thread_running.TestAndClear())
m_event_thread.join();
#endif
}
IPCCommandResult USBHost::HandleTransfer(std::shared_ptr<USB::Device> device, u32 request,
std::function<s32()> submit) const
{
if (!device)
return GetDefaultReply(IPC_ENOENT);
const s32 ret = submit();
if (ret == IPC_SUCCESS)
return GetNoReply();
ERROR_LOG(IOS_USB, "[%04x:%04x] Failed to submit transfer (request %u): %s", device->GetVid(),
device->GetPid(), request, device->GetErrorName(ret).c_str());
return GetDefaultReply(ret <= 0 ? ret : IPC_EINVAL);
}
} // namespace Device
} // namespace HLE
} // namespace IOS

View File

@ -0,0 +1,85 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#include <cstddef>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <vector>
#include "Common/CommonTypes.h"
#include "Common/Flag.h"
#include "Core/IOS/Device.h"
#include "Core/IOS/IPC.h"
#include "Core/IOS/USB/Common.h"
class PointerWrap;
struct libusb_context;
namespace IOS
{
namespace HLE
{
namespace Device
{
// Common base class for USB host devices (such as /dev/usb/oh0 and /dev/usb/ven).
class USBHost : public Device
{
public:
USBHost(u32 device_id, const std::string& device_name);
virtual ~USBHost();
ReturnCode Open(const OpenRequest& request) override;
void UpdateWantDeterminism(bool new_want_determinism) override;
void DoState(PointerWrap& p) override;
protected:
enum class ChangeEvent
{
Inserted,
Removed,
};
using DeviceChangeHooks = std::map<std::shared_ptr<USB::Device>, ChangeEvent>;
std::map<u64, std::shared_ptr<USB::Device>> m_devices;
mutable std::mutex m_devices_mutex;
std::shared_ptr<USB::Device> GetDeviceById(u64 device_id) const;
virtual void OnDeviceChange(ChangeEvent event, std::shared_ptr<USB::Device> changed_device);
virtual void OnDeviceChangeEnd();
virtual bool ShouldAddDevice(const USB::Device& device) const;
void StartThreads();
void StopThreads();
IPCCommandResult HandleTransfer(std::shared_ptr<USB::Device> device, u32 request,
std::function<s32()> submit) const;
private:
bool AddDevice(std::unique_ptr<USB::Device> device);
bool UpdateDevices(bool always_add_hooks = false);
bool AddNewDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks, bool always_add_hooks);
void DetectRemovedDevices(const std::set<u64>& plugged_devices, DeviceChangeHooks& hooks);
void DispatchHooks(const DeviceChangeHooks& hooks);
#ifdef __LIBUSB__
libusb_context* m_libusb_context = nullptr;
// Event thread for libusb
Common::Flag m_event_thread_running;
std::thread m_event_thread;
#endif
// Device scanning thread
Common::Flag m_scan_thread_running;
std::thread m_scan_thread;
};
} // namespace Device
} // namespace HLE
} // namespace IOS

View File

@ -0,0 +1,437 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <algorithm>
#include <cstddef>
#include <cstring>
#include <map>
#include <utility>
#include <libusb.h>
#include "Common/Assert.h"
#include "Common/Logging/Log.h"
#include "Core/CoreTiming.h"
#include "Core/HW/Memmap.h"
#include "Core/IOS/Device.h"
#include "Core/IOS/IPC.h"
#include "Core/IOS/USB/LibusbDevice.h"
namespace IOS
{
namespace HLE
{
namespace USB
{
LibusbDevice::LibusbDevice(libusb_device* device, const libusb_device_descriptor& descriptor)
: m_device(device)
{
libusb_ref_device(m_device);
m_vid = descriptor.idVendor;
m_pid = descriptor.idProduct;
m_id = (static_cast<u64>(m_vid) << 32 | static_cast<u64>(m_pid) << 16 |
static_cast<u64>(libusb_get_bus_number(device)) << 8 |
static_cast<u64>(libusb_get_device_address(device)));
for (u8 i = 0; i < descriptor.bNumConfigurations; ++i)
m_config_descriptors.emplace_back(std::make_unique<LibusbConfigDescriptor>(m_device, i));
}
LibusbDevice::~LibusbDevice()
{
if (m_device_attached)
DetachInterface();
if (m_handle != nullptr)
libusb_close(m_handle);
libusb_unref_device(m_device);
}
DeviceDescriptor LibusbDevice::GetDeviceDescriptor() const
{
libusb_device_descriptor device_descriptor;
libusb_get_device_descriptor(m_device, &device_descriptor);
DeviceDescriptor descriptor;
// The libusb_device_descriptor struct is the same as the IOS one, and it's not going to change.
std::memcpy(&descriptor, &device_descriptor, sizeof(descriptor));
return descriptor;
}
std::vector<ConfigDescriptor> LibusbDevice::GetConfigurations() const
{
std::vector<ConfigDescriptor> descriptors;
for (const auto& config_descriptor : m_config_descriptors)
{
if (!config_descriptor->IsValid())
{
ERROR_LOG(IOS_USB, "Ignoring invalid config descriptor for %04x:%04x", m_vid, m_pid);
continue;
}
ConfigDescriptor descriptor;
std::memcpy(&descriptor, config_descriptor->Get(), sizeof(descriptor));
descriptors.push_back(descriptor);
}
return descriptors;
}
std::vector<InterfaceDescriptor> LibusbDevice::GetInterfaces(const u8 config) const
{
std::vector<InterfaceDescriptor> descriptors;
if (config >= m_config_descriptors.size() || !m_config_descriptors[config]->IsValid())
{
ERROR_LOG(IOS_USB, "Invalid config descriptor %u for %04x:%04x", config, m_vid, m_pid);
return descriptors;
}
for (u8 i = 0; i < m_config_descriptors[config]->Get()->bNumInterfaces; ++i)
{
const libusb_interface& interface = m_config_descriptors[config]->Get()->interface[i];
for (u8 a = 0; a < interface.num_altsetting; ++a)
{
InterfaceDescriptor descriptor;
std::memcpy(&descriptor, &interface.altsetting[a], sizeof(descriptor));
descriptors.push_back(descriptor);
}
}
return descriptors;
}
std::vector<EndpointDescriptor>
LibusbDevice::GetEndpoints(const u8 config, const u8 interface_number, const u8 alt_setting) const
{
std::vector<EndpointDescriptor> descriptors;
if (config >= m_config_descriptors.size() || !m_config_descriptors[config]->IsValid())
{
ERROR_LOG(IOS_USB, "Invalid config descriptor %u for %04x:%04x", config, m_vid, m_pid);
return descriptors;
}
_assert_(interface_number < m_config_descriptors[config]->Get()->bNumInterfaces);
const auto& interface = m_config_descriptors[config]->Get()->interface[interface_number];
_assert_(alt_setting < interface.num_altsetting);
const libusb_interface_descriptor& interface_descriptor = interface.altsetting[alt_setting];
for (u8 i = 0; i < interface_descriptor.bNumEndpoints; ++i)
{
EndpointDescriptor descriptor;
std::memcpy(&descriptor, &interface_descriptor.endpoint[i], sizeof(descriptor));
descriptors.push_back(descriptor);
}
return descriptors;
}
std::string LibusbDevice::GetErrorName(const int error_code) const
{
return libusb_error_name(error_code);
}
bool LibusbDevice::Attach(const u8 interface)
{
if (m_device_attached && interface != m_active_interface)
return ChangeInterface(interface) == 0;
if (m_device_attached)
return true;
m_device_attached = false;
NOTICE_LOG(IOS_USB, "[%04x:%04x] Opening device", m_vid, m_pid);
const int ret = libusb_open(m_device, &m_handle);
if (ret != 0)
{
ERROR_LOG(IOS_USB, "[%04x:%04x] Failed to open: %s", m_vid, m_pid, libusb_error_name(ret));
return false;
}
if (AttachInterface(interface) != 0)
return false;
m_device_attached = true;
return true;
}
int LibusbDevice::CancelTransfer(const u8 endpoint)
{
INFO_LOG(IOS_USB, "[%04x:%04x %d] Cancelling transfers (endpoint 0x%x)", m_vid, m_pid,
m_active_interface, endpoint);
const auto iterator = m_transfer_endpoints.find(endpoint);
if (iterator == m_transfer_endpoints.cend())
return IPC_ENOENT;
iterator->second.CancelTransfers();
return IPC_SUCCESS;
}
int LibusbDevice::ChangeInterface(const u8 interface)
{
if (!m_device_attached || interface >= m_config_descriptors[0]->Get()->bNumInterfaces)
return LIBUSB_ERROR_NOT_FOUND;
INFO_LOG(IOS_USB, "[%04x:%04x %d] Changing interface to %d", m_vid, m_pid, m_active_interface,
interface);
const int ret = DetachInterface();
if (ret < 0)
return ret;
return AttachInterface(interface);
}
int LibusbDevice::SetAltSetting(const u8 alt_setting)
{
if (!m_device_attached)
return LIBUSB_ERROR_NOT_FOUND;
INFO_LOG(IOS_USB, "[%04x:%04x %d] Setting alt setting %d", m_vid, m_pid, m_active_interface,
alt_setting);
return libusb_set_interface_alt_setting(m_handle, m_active_interface, alt_setting);
}
int LibusbDevice::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
{
if (!m_device_attached)
return LIBUSB_ERROR_NOT_FOUND;
switch ((cmd->request_type << 8) | cmd->request)
{
// The following requests have to go through libusb and cannot be directly sent to the device.
case USBHDR(DIR_HOST2DEVICE, TYPE_STANDARD, REC_INTERFACE, REQUEST_SET_INTERFACE):
{
if (static_cast<u8>(cmd->index) != m_active_interface)
{
const int ret = ChangeInterface(static_cast<u8>(cmd->index));
if (ret < 0)
{
ERROR_LOG(IOS_USB, "[%04x:%04x %d] Failed to change interface to %d: %s", m_vid, m_pid,
m_active_interface, cmd->index, libusb_error_name(ret));
return ret;
}
}
const int ret = SetAltSetting(static_cast<u8>(cmd->value));
if (ret == 0)
EnqueueReply(cmd->ios_request, cmd->length);
return ret;
}
case USBHDR(DIR_HOST2DEVICE, TYPE_STANDARD, REC_DEVICE, REQUEST_SET_CONFIGURATION):
{
const int ret = libusb_set_configuration(m_handle, cmd->value);
if (ret == 0)
EnqueueReply(cmd->ios_request, cmd->length);
return ret;
}
}
const size_t size = cmd->length + LIBUSB_CONTROL_SETUP_SIZE;
auto buffer = std::make_unique<u8[]>(size);
libusb_fill_control_setup(buffer.get(), cmd->request_type, cmd->request, cmd->value, cmd->index,
cmd->length);
Memory::CopyFromEmu(buffer.get() + LIBUSB_CONTROL_SETUP_SIZE, cmd->data_address, cmd->length);
libusb_transfer* transfer = libusb_alloc_transfer(0);
transfer->flags |= LIBUSB_TRANSFER_FREE_TRANSFER;
libusb_fill_control_transfer(transfer, m_handle, buffer.release(), CtrlTransferCallback, this, 0);
m_transfer_endpoints[0].AddTransfer(std::move(cmd), transfer);
return libusb_submit_transfer(transfer);
}
int LibusbDevice::SubmitTransfer(std::unique_ptr<BulkMessage> cmd)
{
if (!m_device_attached)
return LIBUSB_ERROR_NOT_FOUND;
libusb_transfer* transfer = libusb_alloc_transfer(0);
libusb_fill_bulk_transfer(transfer, m_handle, cmd->endpoint,
cmd->MakeBuffer(cmd->length).release(), cmd->length, TransferCallback,
this, 0);
transfer->flags |= LIBUSB_TRANSFER_FREE_TRANSFER;
m_transfer_endpoints[transfer->endpoint].AddTransfer(std::move(cmd), transfer);
return libusb_submit_transfer(transfer);
}
int LibusbDevice::SubmitTransfer(std::unique_ptr<IntrMessage> cmd)
{
if (!m_device_attached)
return LIBUSB_ERROR_NOT_FOUND;
libusb_transfer* transfer = libusb_alloc_transfer(0);
libusb_fill_interrupt_transfer(transfer, m_handle, cmd->endpoint,
cmd->MakeBuffer(cmd->length).release(), cmd->length,
TransferCallback, this, 0);
transfer->flags |= LIBUSB_TRANSFER_FREE_TRANSFER;
m_transfer_endpoints[transfer->endpoint].AddTransfer(std::move(cmd), transfer);
return libusb_submit_transfer(transfer);
}
int LibusbDevice::SubmitTransfer(std::unique_ptr<IsoMessage> cmd)
{
if (!m_device_attached)
return LIBUSB_ERROR_NOT_FOUND;
libusb_transfer* transfer = libusb_alloc_transfer(cmd->num_packets);
transfer->buffer = cmd->MakeBuffer(cmd->length).release();
transfer->callback = TransferCallback;
transfer->dev_handle = m_handle;
transfer->endpoint = cmd->endpoint;
transfer->flags |= LIBUSB_TRANSFER_FREE_TRANSFER;
for (size_t i = 0; i < cmd->num_packets; ++i)
transfer->iso_packet_desc[i].length = cmd->packet_sizes[i];
transfer->length = cmd->length;
transfer->num_iso_packets = cmd->num_packets;
transfer->timeout = 0;
transfer->type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS;
transfer->user_data = this;
m_transfer_endpoints[transfer->endpoint].AddTransfer(std::move(cmd), transfer);
return libusb_submit_transfer(transfer);
}
void LibusbDevice::CtrlTransferCallback(libusb_transfer* transfer)
{
auto* device = static_cast<LibusbDevice*>(transfer->user_data);
device->m_transfer_endpoints[0].HandleTransfer(transfer, [&](const auto& cmd) {
cmd.FillBuffer(libusb_control_transfer_get_data(transfer), transfer->actual_length);
// The return code is the total transfer length -- *including* the setup packet.
return transfer->length;
});
}
void LibusbDevice::TransferCallback(libusb_transfer* transfer)
{
auto* device = static_cast<LibusbDevice*>(transfer->user_data);
device->m_transfer_endpoints[transfer->endpoint].HandleTransfer(transfer, [&](const auto& cmd) {
switch (transfer->type)
{
case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
{
auto& iso_msg = static_cast<const IsoMessage&>(cmd);
cmd.FillBuffer(transfer->buffer, iso_msg.length);
for (size_t i = 0; i < iso_msg.num_packets; ++i)
iso_msg.SetPacketReturnValue(i, transfer->iso_packet_desc[i].actual_length);
// Note: isochronous transfers *must* return 0 as the return value. Anything else
// (such as the number of bytes transferred) is considered as a failure.
return static_cast<s32>(IPC_SUCCESS);
}
default:
cmd.FillBuffer(transfer->buffer, transfer->actual_length);
return static_cast<s32>(transfer->actual_length);
}
});
}
static const std::map<u8, const char*> s_transfer_types = {
{LIBUSB_TRANSFER_TYPE_CONTROL, "Control"},
{LIBUSB_TRANSFER_TYPE_ISOCHRONOUS, "Isochronous"},
{LIBUSB_TRANSFER_TYPE_BULK, "Bulk"},
{LIBUSB_TRANSFER_TYPE_INTERRUPT, "Interrupt"},
};
void LibusbDevice::TransferEndpoint::AddTransfer(std::unique_ptr<TransferCommand> command,
libusb_transfer* transfer)
{
std::lock_guard<std::mutex> lk{m_transfers_mutex};
m_transfers.emplace(transfer, std::move(command));
}
void LibusbDevice::TransferEndpoint::HandleTransfer(libusb_transfer* transfer,
std::function<s32(const TransferCommand&)> fn)
{
std::lock_guard<std::mutex> lk{m_transfers_mutex};
const auto iterator = m_transfers.find(transfer);
if (iterator == m_transfers.cend())
{
ERROR_LOG(IOS_USB, "No such transfer");
return;
}
const std::unique_ptr<u8[]> buffer(transfer->buffer);
const auto& cmd = *iterator->second.get();
const auto* device = static_cast<LibusbDevice*>(transfer->user_data);
s32 return_value = 0;
switch (transfer->status)
{
case LIBUSB_TRANSFER_COMPLETED:
return_value = fn(cmd);
break;
case LIBUSB_TRANSFER_ERROR:
case LIBUSB_TRANSFER_CANCELLED:
case LIBUSB_TRANSFER_TIMED_OUT:
case LIBUSB_TRANSFER_OVERFLOW:
case LIBUSB_TRANSFER_STALL:
ERROR_LOG(IOS_USB, "[%04x:%04x %d] %s transfer (endpoint 0x%02x) failed: %s", device->m_vid,
device->m_pid, device->m_active_interface, s_transfer_types.at(transfer->type),
transfer->endpoint, libusb_error_name(transfer->status));
return_value = transfer->status == LIBUSB_TRANSFER_STALL ? -7004 : -5;
break;
case LIBUSB_TRANSFER_NO_DEVICE:
return_value = IPC_ENOENT;
break;
}
cmd.OnTransferComplete();
EnqueueReply(cmd.ios_request, return_value, 0, CoreTiming::FromThread::NON_CPU);
m_transfers.erase(transfer);
}
void LibusbDevice::TransferEndpoint::CancelTransfers()
{
std::lock_guard<std::mutex> lk(m_transfers_mutex);
if (m_transfers.empty())
return;
INFO_LOG(IOS_USB, "Cancelling %ld transfer(s)", m_transfers.size());
for (const auto& pending_transfer : m_transfers)
libusb_cancel_transfer(pending_transfer.first);
}
int LibusbDevice::GetNumberOfAltSettings(const u8 interface_number)
{
return m_config_descriptors[0]->Get()->interface[interface_number].num_altsetting;
}
int LibusbDevice::AttachInterface(const u8 interface)
{
if (m_handle == nullptr)
{
ERROR_LOG(IOS_USB, "[%04x:%04x] Cannot attach without a valid device handle", m_vid, m_pid);
return -1;
}
INFO_LOG(IOS_USB, "[%04x:%04x] Attaching interface %d", m_vid, m_pid, interface);
const int ret = libusb_detach_kernel_driver(m_handle, interface);
if (ret < 0 && ret != LIBUSB_ERROR_NOT_FOUND && ret != LIBUSB_ERROR_NOT_SUPPORTED)
{
ERROR_LOG(IOS_USB, "[%04x:%04x] Failed to detach kernel driver: %s", m_vid, m_pid,
libusb_error_name(ret));
return ret;
}
const int r = libusb_claim_interface(m_handle, interface);
if (r < 0)
{
ERROR_LOG(IOS_USB, "[%04x:%04x] Couldn't claim interface %d: %s", m_vid, m_pid, interface,
libusb_error_name(r));
return r;
}
m_active_interface = interface;
return 0;
}
int LibusbDevice::DetachInterface()
{
if (m_handle == nullptr)
{
ERROR_LOG(IOS_USB, "[%04x:%04x] Cannot detach without a valid device handle", m_vid, m_pid);
return -1;
}
INFO_LOG(IOS_USB, "[%04x:%04x] Detaching interface %d", m_vid, m_pid, m_active_interface);
const int ret = libusb_release_interface(m_handle, m_active_interface);
if (ret < 0 && ret != LIBUSB_ERROR_NO_DEVICE)
{
ERROR_LOG(IOS_USB, "[%04x:%04x] Failed to release interface %d: %s", m_vid, m_pid,
m_active_interface, libusb_error_name(ret));
return ret;
}
return 0;
}
LibusbConfigDescriptor::LibusbConfigDescriptor(libusb_device* device, const u8 config_num)
{
if (libusb_get_config_descriptor(device, config_num, &m_descriptor) != LIBUSB_SUCCESS)
m_descriptor = nullptr;
}
LibusbConfigDescriptor::~LibusbConfigDescriptor()
{
if (m_descriptor != nullptr)
libusb_free_config_descriptor(m_descriptor);
}
} // namespace USB
} // namespace HLE
} // namespace IOS

View File

@ -0,0 +1,94 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#pragma once
#if defined(__LIBUSB__)
#include <cstddef>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "Common/CommonTypes.h"
#include "Core/IOS/USB/Common.h"
struct libusb_config_descriptor;
struct libusb_device;
struct libusb_device_descriptor;
struct libusb_device_handle;
struct libusb_transfer;
namespace IOS
{
namespace HLE
{
namespace USB
{
// Simple wrapper around libusb_get_config_descriptor and libusb_free_config_descriptor.
class LibusbConfigDescriptor final
{
public:
explicit LibusbConfigDescriptor(libusb_device* device, u8 config_num = 0);
~LibusbConfigDescriptor();
libusb_config_descriptor* Get() const { return m_descriptor; }
bool IsValid() const { return m_descriptor != nullptr; }
private:
libusb_config_descriptor* m_descriptor = nullptr;
};
class LibusbDevice final : public Device
{
public:
LibusbDevice(libusb_device* device, const libusb_device_descriptor& device_descriptor);
~LibusbDevice();
DeviceDescriptor GetDeviceDescriptor() const override;
std::vector<ConfigDescriptor> GetConfigurations() const override;
std::vector<InterfaceDescriptor> GetInterfaces(u8 config) const override;
std::vector<EndpointDescriptor> GetEndpoints(u8 config, u8 interface, u8 alt) const override;
std::string GetErrorName(int error_code) const override;
bool Attach(u8 interface) override;
int CancelTransfer(u8 endpoint) override;
int ChangeInterface(u8 interface) override;
int GetNumberOfAltSettings(u8 interface) override;
int SetAltSetting(u8 alt_setting) override;
int SubmitTransfer(std::unique_ptr<CtrlMessage> message) override;
int SubmitTransfer(std::unique_ptr<BulkMessage> message) override;
int SubmitTransfer(std::unique_ptr<IntrMessage> message) override;
int SubmitTransfer(std::unique_ptr<IsoMessage> message) override;
private:
std::vector<std::unique_ptr<LibusbConfigDescriptor>> m_config_descriptors;
u16 m_vid = 0;
u16 m_pid = 0;
u8 m_active_interface = 0;
bool m_device_attached = false;
libusb_device* m_device = nullptr;
libusb_device_handle* m_handle = nullptr;
class TransferEndpoint final
{
public:
void AddTransfer(std::unique_ptr<TransferCommand> command, libusb_transfer* transfer);
void HandleTransfer(libusb_transfer* tr, std::function<s32(const TransferCommand&)> function);
void CancelTransfers();
private:
std::mutex m_transfers_mutex;
std::map<libusb_transfer*, std::unique_ptr<TransferCommand>> m_transfers;
};
std::map<u8, TransferEndpoint> m_transfer_endpoints;
static void CtrlTransferCallback(libusb_transfer* transfer);
static void TransferCallback(libusb_transfer* transfer);
int AttachInterface(u8 interface);
int DetachInterface();
};
} // namespace USB
} // namespace HLE
} // namespace IOS
#endif