diff --git a/Externals/libusb/libusb_static_2013.vcxproj b/Externals/libusb/libusb_static_2013.vcxproj index 2aac3e8db1..74397eb70d 100644 --- a/Externals/libusb/libusb_static_2013.vcxproj +++ b/Externals/libusb/libusb_static_2013.vcxproj @@ -52,7 +52,7 @@ - + @@ -65,7 +65,7 @@ - + diff --git a/Externals/libusb/msvc/libusb_usbdk_static_2015.vcxproj b/Externals/libusb/msvc/libusb_usbdk_static_2015.vcxproj index 67944bafb7..b02ad158f6 100644 --- a/Externals/libusb/msvc/libusb_usbdk_static_2015.vcxproj +++ b/Externals/libusb/msvc/libusb_usbdk_static_2015.vcxproj @@ -159,4 +159,4 @@ - \ No newline at end of file + diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 2a5ff1d4d8..5f910a1670 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -34,6 +34,11 @@ set(SRCS Analytics.cpp Crypto/ec.cpp Logging/LogManager.cpp) +if(LIBUSB_FOUND) + set(LIBS ${LIBS} ${LIBUSB_LIBRARIES}) + set(SRCS ${SRCS} LibusbContext.cpp) +endif(LIBUSB_FOUND) + if(ANDROID) set(SRCS ${SRCS} Logging/ConsoleListenerDroid.cpp) diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj index 52f408e776..831dcad4a7 100644 --- a/Source/Core/Common/Common.vcxproj +++ b/Source/Core/Common/Common.vcxproj @@ -113,6 +113,7 @@ + @@ -159,6 +160,9 @@ + + 4200;%(DisableSpecificWarnings) + diff --git a/Source/Core/Common/Common.vcxproj.filters b/Source/Core/Common/Common.vcxproj.filters index 7731482020..3c9b952742 100644 --- a/Source/Core/Common/Common.vcxproj.filters +++ b/Source/Core/Common/Common.vcxproj.filters @@ -45,6 +45,7 @@ + @@ -236,6 +237,7 @@ + diff --git a/Source/Core/Common/LibusbContext.cpp b/Source/Core/Common/LibusbContext.cpp new file mode 100644 index 0000000000..a2ed128200 --- /dev/null +++ b/Source/Core/Common/LibusbContext.cpp @@ -0,0 +1,46 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include +#include + +#include "Common/LibusbContext.h" +#include "Common/MsgHandler.h" + +namespace LibusbContext +{ +static std::shared_ptr s_libusb_context; +static std::once_flag s_context_initialized; + +static libusb_context* Create() +{ + libusb_context* context; + const int ret = libusb_init(&context); + if (ret < LIBUSB_SUCCESS) + { + bool is_windows = false; +#ifdef _WIN32 + is_windows = true; +#endif + if (is_windows && ret == LIBUSB_ERROR_NOT_FOUND) + PanicAlertT("Failed to initialize libusb because usbdk is not installed."); + else + PanicAlertT("Failed to initialize libusb: %s", libusb_error_name(ret)); + return nullptr; + } + return context; +} + +std::shared_ptr Get() +{ + std::call_once(s_context_initialized, []() { + s_libusb_context.reset(Create(), [](auto* context) { + if (context != nullptr) + libusb_exit(context); + }); + }); + return s_libusb_context; +} +} diff --git a/Source/Core/Common/LibusbContext.h b/Source/Core/Common/LibusbContext.h new file mode 100644 index 0000000000..99af12ba4c --- /dev/null +++ b/Source/Core/Common/LibusbContext.h @@ -0,0 +1,15 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +struct libusb_context; + +namespace LibusbContext +{ +// libusb on Windows is limited to only a single context. Trying to open more +// than one can cause issues with device enumerations. +// libusb is thread-safe so this context can be safely used from different threads. +std::shared_ptr Get(); +} diff --git a/Source/Core/Common/Logging/Log.h b/Source/Core/Common/Logging/Log.h index ce46bcb8e2..3ef07acbf1 100644 --- a/Source/Core/Common/Logging/Log.h +++ b/Source/Core/Common/Logging/Log.h @@ -31,7 +31,6 @@ enum LOG_TYPE IOS_DI, IOS_ES, IOS_FILEIO, - IOS_HID, IOS_NET, IOS_SD, IOS_SSL, diff --git a/Source/Core/Common/Logging/LogManager.cpp b/Source/Core/Common/Logging/LogManager.cpp index 4cb786095b..964a8df973 100644 --- a/Source/Core/Common/Logging/LogManager.cpp +++ b/Source/Core/Common/Logging/LogManager.cpp @@ -65,7 +65,6 @@ LogManager::LogManager() m_Log[LogTypes::IOS_DI] = new LogContainer("IOS_DI", "IOS - Drive Interface"); m_Log[LogTypes::IOS_ES] = new LogContainer("IOS_ES", "IOS - ETicket Services"); m_Log[LogTypes::IOS_FILEIO] = new LogContainer("IOS_FILEIO", "IOS - FileIO"); - m_Log[LogTypes::IOS_HID] = new LogContainer("IOS_HID", "IOS - USB_HID"); m_Log[LogTypes::IOS_SD] = new LogContainer("IOS_SD", "IOS - SDIO"); m_Log[LogTypes::IOS_SSL] = new LogContainer("IOS_SSL", "IOS - SSL"); m_Log[LogTypes::IOS_STM] = new LogContainer("IOS_STM", "IOS - State Transition Manager"); diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index ed0cecdcc5..166eb158bf 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -154,9 +154,15 @@ set(SRCS ActionReplay.cpp IOS/SDIO/SDIOSlot0.cpp IOS/STM/STM.cpp IOS/USB/Common.cpp + IOS/USB/Host.cpp + IOS/USB/OH0/OH0.cpp + IOS/USB/OH0/OH0Device.cpp + IOS/USB/USB_HID/HIDv4.cpp + IOS/USB/USB_VEN/VEN.cpp IOS/USB/USBV0.cpp + IOS/USB/USBV4.cpp + IOS/USB/USBV5.cpp IOS/USB/USB_KBD.cpp - IOS/USB/USB_VEN.cpp IOS/USB/Bluetooth/BTBase.cpp IOS/USB/Bluetooth/BTEmu.cpp IOS/USB/Bluetooth/BTStub.cpp @@ -264,7 +270,7 @@ 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/Bluetooth/BTReal.cpp) endif() diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 6240a10272..c5e2b5891a 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -77,6 +77,7 @@ void SConfig::SaveSettings() SaveAnalyticsSettings(ini); SaveNetworkSettings(ini); SaveBluetoothPassthroughSettings(ini); + SaveUSBPassthroughSettings(ini); SaveSysconfSettings(ini); ini.Save(File::GetUserPath(F_DOLPHINCONFIG_IDX)); @@ -343,6 +344,20 @@ void SConfig::SaveBluetoothPassthroughSettings(IniFile& ini) section->Set("LinkKeys", m_bt_passthrough_link_keys); } +void SConfig::SaveUSBPassthroughSettings(IniFile& ini) +{ + IniFile::Section* section = ini.GetOrCreateSection("USBPassthrough"); + + std::ostringstream oss; + for (const auto& device : m_usb_passthrough_devices) + oss << StringFromFormat("%04x:%04x", device.first, device.second) << ','; + std::string devices_string = oss.str(); + if (!devices_string.empty()) + devices_string.pop_back(); + + section->Set("Devices", devices_string); +} + void SConfig::SaveSysconfSettings(IniFile& ini) { IniFile::Section* section = ini.GetOrCreateSection("Sysconf"); @@ -400,6 +415,7 @@ void SConfig::LoadSettings() LoadNetworkSettings(ini); LoadAnalyticsSettings(ini); LoadBluetoothPassthroughSettings(ini); + LoadUSBPassthroughSettings(ini); LoadSysconfSettings(ini); } @@ -666,6 +682,27 @@ void SConfig::LoadBluetoothPassthroughSettings(IniFile& ini) section->Get("LinkKeys", &m_bt_passthrough_link_keys, ""); } +void SConfig::LoadUSBPassthroughSettings(IniFile& ini) +{ + IniFile::Section* section = ini.GetOrCreateSection("USBPassthrough"); + m_usb_passthrough_devices.clear(); + std::string devices_string; + std::vector pairs; + section->Get("Devices", &devices_string, ""); + SplitString(devices_string, ',', pairs); + for (const auto& pair : pairs) + { + const auto index = pair.find(':'); + if (index == std::string::npos) + continue; + + const u16 vid = static_cast(strtol(pair.substr(0, index).c_str(), nullptr, 16)); + const u16 pid = static_cast(strtol(pair.substr(index + 1).c_str(), nullptr, 16)); + if (vid && pid) + m_usb_passthrough_devices.emplace(vid, pid); + } +} + void SConfig::LoadSysconfSettings(IniFile& ini) { IniFile::Section* section = ini.GetOrCreateSection("Sysconf"); @@ -757,6 +794,11 @@ void SConfig::LoadDefaults() m_revision = 0; } +bool SConfig::IsUSBDeviceWhitelisted(const std::pair vid_pid) const +{ + return m_usb_passthrough_devices.find(vid_pid) != m_usb_passthrough_devices.end(); +} + const char* SConfig::GetDirectoryForRegion(DiscIO::Region region) { switch (region) diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index 8aeb3ed5da..081e63632f 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -5,7 +5,9 @@ #pragma once #include +#include #include +#include #include #include "Common/IniFile.h" @@ -151,6 +153,10 @@ struct SConfig : NonCopyable int m_bt_passthrough_vid = -1; std::string m_bt_passthrough_link_keys; + // USB passthrough settings + std::set> m_usb_passthrough_devices; + bool IsUSBDeviceWhitelisted(std::pair vid_pid) const; + // SYSCONF settings int m_sensor_bar_position = 0x01; int m_sensor_bar_sensitivity = 0x03; @@ -351,6 +357,7 @@ private: void SaveNetworkSettings(IniFile& ini); void SaveAnalyticsSettings(IniFile& ini); void SaveBluetoothPassthroughSettings(IniFile& ini); + void SaveUSBPassthroughSettings(IniFile& ini); void SaveSysconfSettings(IniFile& ini); void LoadGeneralSettings(IniFile& ini); @@ -365,6 +372,7 @@ private: void LoadNetworkSettings(IniFile& ini); void LoadAnalyticsSettings(IniFile& ini); void LoadBluetoothPassthroughSettings(IniFile& ini); + void LoadUSBPassthroughSettings(IniFile& ini); void LoadSysconfSettings(IniFile& ini); bool SetRegion(DiscIO::Region region, std::string* directory_name); diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 8abf7ac0c7..f580b6f795 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -54,7 +54,7 @@ #include "Core/HW/SystemTimers.h" #include "Core/HW/VideoInterface.h" #include "Core/HW/Wiimote.h" -#include "Core/IOS/Network/Socket.h" +#include "Core/IOS/IPC.h" #include "Core/IOS/USB/Bluetooth/BTEmu.h" #include "Core/IOS/USB/Bluetooth/WiimoteDevice.h" #include "Core/Movie.h" @@ -975,7 +975,7 @@ void UpdateWantDeterminism(bool initial) bool was_unpaused = Core::PauseAndLock(true); g_want_determinism = new_want_determinism; - IOS::HLE::WiiSockMan::GetInstance().UpdateWantDeterminism(new_want_determinism); + IOS::HLE::UpdateWantDeterminism(new_want_determinism); Fifo::UpdateWantDeterminism(new_want_determinism); // We need to clear the cache because some parts of the JIT depend on want_determinism, e.g. use // of FMA. diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj index 42496fe3e0..dc6bcd4a36 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -186,16 +186,20 @@ - - - + 4200;%(DisableSpecificWarnings) + + 4200;%(DisableSpecificWarnings) + + + + + + + + - @@ -419,10 +423,16 @@ + + + + + + - + + - diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters index f85e4e64db..109608d2bb 100644 --- a/Source/Core/Core/Core.vcxproj.filters +++ b/Source/Core/Core/Core.vcxproj.filters @@ -773,18 +773,36 @@ IOS\USB + + IOS\USB + + + IOS\USB + + + IOS\USB + + + IOS\USB + + + IOS\USB + + + IOS\USB + IOS\USB - + + IOS\USB + + IOS\USB IOS\USB - - IOS\USB - IOS\WFS @@ -1353,18 +1371,36 @@ IOS\USB + + IOS\USB + + + IOS\USB + + + IOS\USB + + + IOS\USB + + + IOS\USB + + + IOS\USB + IOS\USB - + + IOS\USB + + IOS\USB IOS\USB - - IOS\USB - IOS\WFS diff --git a/Source/Core/Core/IOS/Device.cpp b/Source/Core/Core/IOS/Device.cpp index dd5de8071f..cf01110caa 100644 --- a/Source/Core/Core/IOS/Device.cpp +++ b/Source/Core/Core/IOS/Device.cpp @@ -76,6 +76,16 @@ bool IOCtlVRequest::HasInputVectorWithAddress(const u32 vector_address) const [&](const auto& in_vector) { return in_vector.address == vector_address; }); } +bool IOCtlVRequest::HasNumberOfValidVectors(const size_t in_count, const size_t io_count) const +{ + if (in_vectors.size() != in_count || io_vectors.size() != io_count) + return false; + + auto IsValidVector = [](const auto& vector) { return vector.size == 0 || vector.address != 0; }; + return std::all_of(in_vectors.begin(), in_vectors.end(), IsValidVector) && + std::all_of(io_vectors.begin(), io_vectors.end(), IsValidVector); +} + void IOCtlRequest::Log(const std::string& device_name, LogTypes::LOG_TYPE type, LogTypes::LOG_LEVELS verbosity) const { diff --git a/Source/Core/Core/IOS/Device.h b/Source/Core/Core/IOS/Device.h index 23aa9d1dc5..c66665ddae 100644 --- a/Source/Core/Core/IOS/Device.h +++ b/Source/Core/Core/IOS/Device.h @@ -42,6 +42,7 @@ enum ReturnCode : s32 FS_EDIRDEPTH = -116, // Max directory depth exceeded FS_EBUSY = -118, // Resource busy IPC_EESEXHAUSTED = -1016, // Max of 2 ES handles exceeded + USB_ECANCELED = -7022, // USB OH0 insertion hook cancelled }; struct Request @@ -122,6 +123,7 @@ struct IOCtlVRequest final : Request std::vector io_vectors; explicit IOCtlVRequest(u32 address); bool HasInputVectorWithAddress(u32 vector_address) const; + bool HasNumberOfValidVectors(size_t in_count, size_t io_count) const; void Dump(const std::string& description, LogTypes::LOG_TYPE type = LogTypes::IOS, LogTypes::LOG_LEVELS level = LogTypes::LINFO) const; void DumpUnknown(const std::string& description, LogTypes::LOG_TYPE type = LogTypes::IOS, @@ -137,6 +139,7 @@ public: { Static, // Devices which appear in s_device_map. FileIO, // FileIO devices which are created dynamically. + OH0, // OH0 child devices which are created dynamically. }; Device(u32 device_id, const std::string& device_name, DeviceType type = DeviceType::Static); @@ -159,6 +162,7 @@ public: virtual IPCCommandResult IOCtl(const IOCtlRequest& ioctl) { return Unsupported(ioctl); } virtual IPCCommandResult IOCtlV(const IOCtlVRequest& ioctlv) { return Unsupported(ioctlv); } virtual void Update() {} + virtual void UpdateWantDeterminism(bool new_want_determinism) {} virtual DeviceType GetDeviceType() const { return m_device_type; } virtual bool IsOpened() const { return m_is_active; } static IPCCommandResult GetDefaultReply(s32 return_value); diff --git a/Source/Core/Core/IOS/IPC.cpp b/Source/Core/Core/IOS/IPC.cpp index 371942c9eb..1580791a80 100644 --- a/Source/Core/Core/IOS/IPC.cpp +++ b/Source/Core/Core/IOS/IPC.cpp @@ -44,12 +44,16 @@ #include "Core/IOS/IPC.h" #include "Core/IOS/Network/Net.h" #include "Core/IOS/Network/SSL.h" +#include "Core/IOS/Network/Socket.h" #include "Core/IOS/SDIO/SDIOSlot0.h" #include "Core/IOS/STM/STM.h" #include "Core/IOS/USB/Bluetooth/BTEmu.h" #include "Core/IOS/USB/Bluetooth/BTReal.h" +#include "Core/IOS/USB/OH0/OH0.h" +#include "Core/IOS/USB/OH0/OH0Device.h" +#include "Core/IOS/USB/USB_HID/HIDv4.h" #include "Core/IOS/USB/USB_KBD.h" -#include "Core/IOS/USB/USB_VEN.h" +#include "Core/IOS/USB/USB_VEN/VEN.h" #include "Core/IOS/WFS/WFSI.h" #include "Core/IOS/WFS/WFSSRV.h" @@ -58,10 +62,6 @@ namespace CoreTiming struct EventType; } // namespace CoreTiming -#if defined(__LIBUSB__) -#include "Core/IOS/USB/USB_HIDv4.h" -#endif - namespace IOS { namespace HLE @@ -85,6 +85,8 @@ static CoreTiming::EventType* s_event_sdio_notify; static u64 s_last_reply_time; +static u64 s_active_title_id; + static constexpr u64 ENQUEUE_REQUEST_FLAG = 0x100000000ULL; static constexpr u64 ENQUEUE_ACKNOWLEDGEMENT_FLAG = 0x200000000ULL; @@ -411,6 +413,14 @@ static void SDIO_EventNotify_CPUThread(u64 userdata, s64 cycles_late) device->EventNotify(); } +// The title ID is a u64 where the first 32 bits are used for the title type. +// For IOS title IDs, the type will always be 00000001 (system), and the lower 32 bits +// are used for the IOS major version -- which is what we want here. +u32 GetVersion() +{ + return static_cast(s_active_title_id); +} + bool SetupMemory(u64 ios_title_id) { auto target_imv = std::find_if( @@ -423,6 +433,8 @@ bool SetupMemory(u64 ios_title_id) return false; } + s_active_title_id = ios_title_id; + Memory::Write_U32(target_imv->mem1_physical_size, ADDR_MEM1_SIZE); Memory::Write_U32(target_imv->mem1_simulated_size, ADDR_MEM1_SIM_SIZE); Memory::Write_U32(target_imv->mem1_end, ADDR_MEM1_END); @@ -495,15 +507,12 @@ void Reinit() AddDevice("/dev/net/ip/top"); AddDevice("/dev/net/ssl"); AddDevice("/dev/usb/kbd"); - AddDevice("/dev/usb/ven"); AddDevice("/dev/sdio/slot0"); AddDevice("/dev/sdio/slot1"); -#if defined(__LIBUSB__) AddDevice("/dev/usb/hid"); -#else - AddDevice("/dev/usb/hid"); -#endif + AddDevice("/dev/usb/oh0"); AddDevice("/dev/usb/oh1"); + AddDevice("/dev/usb/ven"); AddDevice("/dev/usb/wfssrv"); AddDevice("/dev/wfsi"); } @@ -644,6 +653,10 @@ void DoState(PointerWrap& p) s_fdmap[i] = std::make_shared(i, ""); s_fdmap[i]->DoState(p); break; + case Device::Device::DeviceType::OH0: + s_fdmap[i] = std::make_shared(i, ""); + s_fdmap[i]->DoState(p); + break; } } } @@ -710,6 +723,10 @@ static s32 OpenDevice(const OpenRequest& request) if (!device) return IPC_EESEXHAUSTED; } + else if (request.path.find("/dev/usb/oh0/") == 0 && !GetDeviceByName(request.path)) + { + device = std::make_shared(new_fd, request.path); + } else if (request.path.find("/dev/") == 0) { device = GetDeviceByName(request.path); @@ -851,5 +868,12 @@ void UpdateDevices() } } } + +void UpdateWantDeterminism(const bool new_want_determinism) +{ + WiiSockMan::GetInstance().UpdateWantDeterminism(new_want_determinism); + for (const auto& device : s_device_map) + device.second->UpdateWantDeterminism(new_want_determinism); +} } // namespace HLE } // namespace IOS diff --git a/Source/Core/Core/IOS/IPC.h b/Source/Core/Core/IOS/IPC.h index 1c6146b793..7508be12f9 100644 --- a/Source/Core/Core/IOS/IPC.h +++ b/Source/Core/Core/IOS/IPC.h @@ -51,6 +51,8 @@ void Init(); // Needs to be called after Reset(true) to recreate the device tree void Reinit(); +u32 GetVersion(); + bool SetupMemory(u64 ios_title_id); // Shutdown @@ -77,6 +79,8 @@ void Update(); // Update Devices void UpdateDevices(); +void UpdateWantDeterminism(bool new_want_determinism); + void ExecuteCommand(u32 address); void EnqueueRequest(u32 address); diff --git a/Source/Core/Core/IOS/USB/Bluetooth/BTReal.cpp b/Source/Core/Core/IOS/USB/Bluetooth/BTReal.cpp index 65390e5d49..0e7267b614 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/BTReal.cpp +++ b/Source/Core/Core/IOS/USB/Bluetooth/BTReal.cpp @@ -16,6 +16,7 @@ #include "Common/Assert.h" #include "Common/ChunkFile.h" #include "Common/CommonFuncs.h" +#include "Common/LibusbContext.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Common/Network.h" @@ -59,8 +60,8 @@ namespace Device BluetoothReal::BluetoothReal(u32 device_id, const std::string& device_name) : BluetoothBase(device_id, device_name) { - const int ret = libusb_init(&m_libusb_context); - _assert_msg_(IOS_WIIMOTE, ret == 0, "Failed to init libusb."); + m_libusb_context = LibusbContext::Get(); + _assert_msg_(IOS_WIIMOTE, m_libusb_context, "Failed to init libusb."); LoadLinkKeys(); } @@ -78,15 +79,13 @@ BluetoothReal::~BluetoothReal() libusb_unref_device(m_device); } - libusb_exit(m_libusb_context); - SaveLinkKeys(); } ReturnCode BluetoothReal::Open(const OpenRequest& request) { libusb_device** list; - const ssize_t cnt = libusb_get_device_list(m_libusb_context, &list); + const ssize_t cnt = libusb_get_device_list(m_libusb_context.get(), &list); _dbg_assert_msg_(IOS, cnt > 0, "Couldn't get device list"); for (ssize_t i = 0; i < cnt; ++i) { @@ -601,7 +600,7 @@ void BluetoothReal::TransferThread() Common::SetCurrentThreadName("BT USB Thread"); while (m_thread_running.IsSet()) { - libusb_handle_events_completed(m_libusb_context, nullptr); + libusb_handle_events_completed(m_libusb_context.get(), nullptr); } } diff --git a/Source/Core/Core/IOS/USB/Bluetooth/BTReal.h b/Source/Core/Core/IOS/USB/Bluetooth/BTReal.h index dc1679925a..ba98483c7c 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/BTReal.h +++ b/Source/Core/Core/IOS/USB/Bluetooth/BTReal.h @@ -76,7 +76,7 @@ private: libusb_device* m_device = nullptr; libusb_device_handle* m_handle = nullptr; - libusb_context* m_libusb_context = nullptr; + std::shared_ptr m_libusb_context; Common::Flag m_thread_running; std::thread m_thread; diff --git a/Source/Core/Core/IOS/USB/Common.cpp b/Source/Core/Core/IOS/USB/Common.cpp index 12a8891df0..0c1a7317a7 100644 --- a/Source/Core/Core/IOS/USB/Common.cpp +++ b/Source/Core/Core/IOS/USB/Common.cpp @@ -2,10 +2,15 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include "Core/IOS/USB/Common.h" +#include + +#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(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* buffer, const void* data, const size_t size) +{ + buffer->insert(buffer->end(), static_cast(data), static_cast(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* 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* buffer, ConfigDescriptor descriptor) +{ + descriptor.wTotalLength = Common::swap16(descriptor.wTotalLength); + CopyToBufferAligned(buffer, &descriptor, descriptor.bLength); +} + +static void CopyDescriptorToBuffer(std::vector* buffer, InterfaceDescriptor descriptor) +{ + CopyToBufferAligned(buffer, &descriptor, descriptor.bLength); +} + +static void CopyDescriptorToBuffer(std::vector* 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 Device::GetDescriptorsUSBV4() const +{ + return GetDescriptors([](const auto& descriptor) { return true; }); +} + +std::vector 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 +Device::GetDescriptors(std::function predicate) const +{ + std::vector 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(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(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 diff --git a/Source/Core/Core/IOS/USB/Common.h b/Source/Core/Core/IOS/USB/Common.h index 3871ac9e00..6c9bb13913 100644 --- a/Source/Core/Core/IOS/USB/Common.h +++ b/Source/Core/Core/IOS/USB/Common.h @@ -5,7 +5,9 @@ #pragma once #include +#include #include +#include #include #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(((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 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 GetDescriptorsUSBV4() const; + std::vector GetDescriptorsUSBV5(u8 interface, u8 alt_setting) const; + + virtual DeviceDescriptor GetDeviceDescriptor() const = 0; + virtual std::vector GetConfigurations() const = 0; + virtual std::vector GetInterfaces(u8 config) const = 0; + virtual std::vector 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 message) = 0; + virtual int SubmitTransfer(std::unique_ptr message) = 0; + virtual int SubmitTransfer(std::unique_ptr message) = 0; + virtual int SubmitTransfer(std::unique_ptr message) = 0; + +protected: + std::vector GetDescriptors(std::function predicate) const; + u64 m_id = 0xFFFFFFFFFFFFFFFF; +}; } // namespace USB } // namespace HLE } // namespace IOS diff --git a/Source/Core/Core/IOS/USB/Host.cpp b/Source/Core/Core/IOS/USB/Host.cpp new file mode 100644 index 0000000000..875dc451f1 --- /dev/null +++ b/Source/Core/Core/IOS/USB/Host.cpp @@ -0,0 +1,244 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include +#include + +#ifdef __LIBUSB__ +#include +#endif + +#include "Common/Assert.h" +#include "Common/ChunkFile.h" +#include "Common/CommonTypes.h" +#include "Common/LibusbContext.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__ + m_libusb_context = LibusbContext::Get(); + _assert_msg_(IOS_USB, m_libusb_context, "Failed to init libusb."); +#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 device) +{ + std::lock_guard 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 USBHost::GetDeviceById(const u64 device_id) const +{ + std::lock_guard 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 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 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& 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.get(), &list); + if (count < 0) + { + WARN_LOG(IOS_USB, "Failed to get device list: %s", libusb_error_name(static_cast(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(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& plugged_devices, DeviceChangeHooks& hooks) +{ + std::lock_guard 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.get(), &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(), hooks); + DispatchHooks(hooks); +#ifdef __LIBUSB__ + if (m_event_thread_running.TestAndClear()) + m_event_thread.join(); +#endif +} + +IPCCommandResult USBHost::HandleTransfer(std::shared_ptr device, u32 request, + std::function 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 diff --git a/Source/Core/Core/IOS/USB/Host.h b/Source/Core/Core/IOS/USB/Host.h new file mode 100644 index 0000000000..40c09ca709 --- /dev/null +++ b/Source/Core/Core/IOS/USB/Host.h @@ -0,0 +1,85 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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() = default; + + 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, ChangeEvent>; + + std::map> m_devices; + mutable std::mutex m_devices_mutex; + + std::shared_ptr GetDeviceById(u64 device_id) const; + virtual void OnDeviceChange(ChangeEvent event, std::shared_ptr changed_device); + virtual void OnDeviceChangeEnd(); + virtual bool ShouldAddDevice(const USB::Device& device) const; + void StartThreads(); + void StopThreads(); + + IPCCommandResult HandleTransfer(std::shared_ptr device, u32 request, + std::function submit) const; + +private: + bool AddDevice(std::unique_ptr device); + bool UpdateDevices(bool always_add_hooks = false); + + bool AddNewDevices(std::set& new_devices, DeviceChangeHooks& hooks, bool always_add_hooks); + void DetectRemovedDevices(const std::set& plugged_devices, DeviceChangeHooks& hooks); + void DispatchHooks(const DeviceChangeHooks& hooks); + +#ifdef __LIBUSB__ + std::shared_ptr m_libusb_context; + // 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 diff --git a/Source/Core/Core/IOS/USB/LibusbDevice.cpp b/Source/Core/Core/IOS/USB/LibusbDevice.cpp new file mode 100644 index 0000000000..69b51e3fba --- /dev/null +++ b/Source/Core/Core/IOS/USB/LibusbDevice.cpp @@ -0,0 +1,437 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include + +#include + +#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(m_vid) << 32 | static_cast(m_pid) << 16 | + static_cast(libusb_get_bus_number(device)) << 8 | + static_cast(libusb_get_device_address(device))); + + for (u8 i = 0; i < descriptor.bNumConfigurations; ++i) + m_config_descriptors.emplace_back(std::make_unique(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 LibusbDevice::GetConfigurations() const +{ + std::vector 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 LibusbDevice::GetInterfaces(const u8 config) const +{ + std::vector 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 +LibusbDevice::GetEndpoints(const u8 config, const u8 interface_number, const u8 alt_setting) const +{ + std::vector 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 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(cmd->index) != m_active_interface) + { + const int ret = ChangeInterface(static_cast(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(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(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 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 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 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(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(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(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(IPC_SUCCESS); + } + default: + cmd.FillBuffer(transfer->buffer, transfer->actual_length); + return static_cast(transfer->actual_length); + } + }); +} + +static const std::map 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 command, + libusb_transfer* transfer) +{ + std::lock_guard lk{m_transfers_mutex}; + m_transfers.emplace(transfer, std::move(command)); +} + +void LibusbDevice::TransferEndpoint::HandleTransfer(libusb_transfer* transfer, + std::function fn) +{ + std::lock_guard 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 buffer(transfer->buffer); + const auto& cmd = *iterator->second.get(); + const auto* device = static_cast(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 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 diff --git a/Source/Core/Core/IOS/USB/LibusbDevice.h b/Source/Core/Core/IOS/USB/LibusbDevice.h new file mode 100644 index 0000000000..331aae2593 --- /dev/null +++ b/Source/Core/Core/IOS/USB/LibusbDevice.h @@ -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 +#include +#include +#include +#include +#include +#include + +#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 GetConfigurations() const override; + std::vector GetInterfaces(u8 config) const override; + std::vector 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 message) override; + int SubmitTransfer(std::unique_ptr message) override; + int SubmitTransfer(std::unique_ptr message) override; + int SubmitTransfer(std::unique_ptr message) override; + +private: + std::vector> 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 command, libusb_transfer* transfer); + void HandleTransfer(libusb_transfer* tr, std::function function); + void CancelTransfers(); + + private: + std::mutex m_transfers_mutex; + std::map> m_transfers; + }; + std::map 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 diff --git a/Source/Core/Core/IOS/USB/OH0/OH0.cpp b/Source/Core/Core/IOS/USB/OH0/OH0.cpp new file mode 100644 index 0000000000..08461f9f99 --- /dev/null +++ b/Source/Core/Core/IOS/USB/OH0/OH0.cpp @@ -0,0 +1,356 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include + +#include "Common/Assert.h" +#include "Common/ChunkFile.h" +#include "Common/CommonFuncs.h" +#include "Common/Logging/Log.h" +#include "Core/Core.h" +#include "Core/CoreTiming.h" +#include "Core/HW/Memmap.h" +#include "Core/IOS/USB/Common.h" +#include "Core/IOS/USB/OH0/OH0.h" +#include "Core/IOS/USB/USBV0.h" + +namespace IOS +{ +namespace HLE +{ +namespace Device +{ +OH0::OH0(u32 device_id, const std::string& device_name) : USBHost(device_id, device_name) +{ +} + +OH0::~OH0() +{ + StopThreads(); +} + +ReturnCode OH0::Open(const OpenRequest& request) +{ + const u32 ios_major_version = GetVersion(); + if (ios_major_version == 57 || ios_major_version == 58 || ios_major_version == 59) + return IPC_EACCES; + return USBHost::Open(request); +} + +IPCCommandResult OH0::IOCtl(const IOCtlRequest& request) +{ + request.Log(GetDeviceName(), LogTypes::IOS_USB); + switch (request.request) + { + case USB::IOCTL_USBV0_GETRHDESCA: + return GetRhDesca(request); + case USB::IOCTL_USBV0_CANCEL_INSERT_HOOK: + return CancelInsertionHook(request); + default: + return GetDefaultReply(IPC_EINVAL); + } +} + +IPCCommandResult OH0::IOCtlV(const IOCtlVRequest& request) +{ + INFO_LOG(IOS_USB, "/dev/usb/oh0 - IOCtlV %u", request.request); + switch (request.request) + { + case USB::IOCTLV_USBV0_GETDEVLIST: + return GetDeviceList(request); + case USB::IOCTLV_USBV0_GETRHPORTSTATUS: + return GetRhPortStatus(request); + case USB::IOCTLV_USBV0_SETRHPORTSTATUS: + return SetRhPortStatus(request); + case USB::IOCTLV_USBV0_DEVINSERTHOOK: + return RegisterInsertionHook(request); + case USB::IOCTLV_USBV0_DEVICECLASSCHANGE: + return RegisterClassChangeHook(request); + case USB::IOCTLV_USBV0_DEVINSERTHOOKID: + return RegisterInsertionHookWithID(request); + default: + return GetDefaultReply(IPC_EINVAL); + } +} + +void OH0::DoState(PointerWrap& p) +{ + if (p.GetMode() == PointerWrap::MODE_READ) + { + Core::DisplayMessage("It is suggested that you unplug and replug all connected USB devices.", + 5000); + Core::DisplayMessage("If USB doesn't work properly, an emulation reset may be needed.", 5000); + } + p.Do(m_insertion_hooks); + p.Do(m_removal_hooks); + p.Do(m_opened_devices); + USBHost::DoState(p); +} + +IPCCommandResult OH0::CancelInsertionHook(const IOCtlRequest& request) +{ + if (!request.buffer_in || request.buffer_in_size != 4) + return GetDefaultReply(IPC_EINVAL); + + // IOS assigns random IDs, but ours are simply the VID + PID (see RegisterInsertionHookWithID) + TriggerHook(m_insertion_hooks, + {Memory::Read_U16(request.buffer_in), Memory::Read_U16(request.buffer_in + 2)}, + USB_ECANCELED); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult OH0::GetDeviceList(const IOCtlVRequest& request) const +{ + if (!request.HasNumberOfValidVectors(2, 2)) + return GetDefaultReply(IPC_EINVAL); + + const u8 max_entries_count = Memory::Read_U8(request.in_vectors[0].address); + if (request.io_vectors[1].size != max_entries_count * sizeof(DeviceEntry)) + return GetDefaultReply(IPC_EINVAL); + + const u8 interface_class = Memory::Read_U8(request.in_vectors[1].address); + u8 entries_count = 0; + std::lock_guard lk(m_devices_mutex); + for (const auto& device : m_devices) + { + if (entries_count >= max_entries_count) + break; + if (!device.second->HasClass(interface_class)) + continue; + + DeviceEntry entry; + entry.unknown = 0; + entry.vid = Common::swap16(device.second->GetVid()); + entry.pid = Common::swap16(device.second->GetPid()); + Memory::CopyToEmu(request.io_vectors[1].address + 8 * entries_count++, &entry, 8); + } + Memory::Write_U8(entries_count, request.io_vectors[0].address); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult OH0::GetRhDesca(const IOCtlRequest& request) const +{ + if (!request.buffer_out || request.buffer_out_size != 4) + return GetDefaultReply(IPC_EINVAL); + + // Based on a hardware test, this ioctl seems to return a constant value + Memory::Write_U32(0x02000302, request.buffer_out); + request.Dump(GetDeviceName(), LogTypes::IOS_USB, LogTypes::LWARNING); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult OH0::GetRhPortStatus(const IOCtlVRequest& request) const +{ + if (!request.HasNumberOfValidVectors(1, 1)) + return GetDefaultReply(IPC_EINVAL); + + ERROR_LOG(IOS_USB, "Unimplemented IOCtlV: IOCTLV_USBV0_GETRHPORTSTATUS"); + request.Dump(GetDeviceName(), LogTypes::IOS_USB, LogTypes::LERROR); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult OH0::SetRhPortStatus(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(2, 0)) + return GetDefaultReply(IPC_EINVAL); + + ERROR_LOG(IOS_USB, "Unimplemented IOCtlV: IOCTLV_USBV0_SETRHPORTSTATUS"); + request.Dump(GetDeviceName(), LogTypes::IOS_USB, LogTypes::LERROR); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult OH0::RegisterRemovalHook(const u64 device_id, const IOCtlRequest& request) +{ + std::lock_guard lock{m_hooks_mutex}; + // IOS only allows a single device removal hook. + if (m_removal_hooks.find(device_id) != m_removal_hooks.end()) + return GetDefaultReply(IPC_EEXIST); + m_removal_hooks.insert({device_id, request.address}); + return GetNoReply(); +} + +IPCCommandResult OH0::RegisterInsertionHook(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(2, 0)) + return GetDefaultReply(IPC_EINVAL); + + const u16 vid = Memory::Read_U16(request.in_vectors[0].address); + const u16 pid = Memory::Read_U16(request.in_vectors[1].address); + if (HasDeviceWithVidPid(vid, pid)) + return GetDefaultReply(IPC_SUCCESS); + + std::lock_guard lock{m_hooks_mutex}; + // TODO: figure out whether IOS allows more than one hook. + m_insertion_hooks[{vid, pid}] = request.address; + return GetNoReply(); +} + +IPCCommandResult OH0::RegisterInsertionHookWithID(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(3, 1)) + return GetDefaultReply(IPC_EINVAL); + + std::lock_guard lock{m_hooks_mutex}; + const u16 vid = Memory::Read_U16(request.in_vectors[0].address); + const u16 pid = Memory::Read_U16(request.in_vectors[1].address); + const bool trigger_only_for_new_device = Memory::Read_U8(request.in_vectors[2].address) == 1; + if (!trigger_only_for_new_device && HasDeviceWithVidPid(vid, pid)) + return GetDefaultReply(IPC_SUCCESS); + // TODO: figure out whether IOS allows more than one hook. + m_insertion_hooks.insert({{vid, pid}, request.address}); + // The output vector is overwritten with an ID to use with ioctl 31 for cancelling the hook. + Memory::Write_U32(vid << 16 | pid, request.io_vectors[0].address); + return GetNoReply(); +} + +IPCCommandResult OH0::RegisterClassChangeHook(const IOCtlVRequest& request) +{ + if (!request.HasNumberOfValidVectors(1, 0)) + return GetDefaultReply(IPC_EINVAL); + WARN_LOG(IOS_USB, "Unimplemented IOCtlV: USB::IOCTLV_USBV0_DEVICECLASSCHANGE (no reply)"); + request.Dump(GetDeviceName(), LogTypes::IOS_USB, LogTypes::LWARNING); + return GetNoReply(); +} + +bool OH0::HasDeviceWithVidPid(const u16 vid, const u16 pid) const +{ + return std::any_of(m_devices.begin(), m_devices.end(), [=](const auto& device) { + return device.second->GetVid() == vid && device.second->GetPid() == pid; + }); +} + +void OH0::OnDeviceChange(const ChangeEvent event, std::shared_ptr device) +{ + std::lock_guard lk(m_devices_mutex); + if (event == ChangeEvent::Inserted) + TriggerHook(m_insertion_hooks, {device->GetVid(), device->GetPid()}, IPC_SUCCESS); + else if (event == ChangeEvent::Removed) + TriggerHook(m_removal_hooks, device->GetId(), IPC_SUCCESS); +} + +template +void OH0::TriggerHook(std::map& hooks, T value, const ReturnCode return_value) +{ + std::lock_guard lk{m_hooks_mutex}; + const auto hook = hooks.find(value); + if (hook == hooks.end()) + return; + EnqueueReply(Request{hook->second}, return_value, 0, CoreTiming::FromThread::ANY); + hooks.erase(hook); +} + +std::pair OH0::DeviceOpen(const u16 vid, const u16 pid) +{ + std::lock_guard lk(m_devices_mutex); + + bool has_device_with_vid_pid = false; + for (const auto& device : m_devices) + { + if (device.second->GetVid() != vid || device.second->GetPid() != pid) + continue; + has_device_with_vid_pid = true; + + if (m_opened_devices.find(device.second->GetId()) != m_opened_devices.cend() || + !device.second->Attach(0)) + continue; + + m_opened_devices.emplace(device.second->GetId()); + return {IPC_SUCCESS, device.second->GetId()}; + } + // IOS doesn't allow opening the same device more than once (IPC_EEXIST) + return {has_device_with_vid_pid ? IPC_EEXIST : IPC_ENOENT, 0}; +} + +void OH0::DeviceClose(const u64 device_id) +{ + TriggerHook(m_removal_hooks, device_id, IPC_ENOENT); + m_opened_devices.erase(device_id); +} + +IPCCommandResult OH0::DeviceIOCtl(const u64 device_id, const IOCtlRequest& request) +{ + const auto device = GetDeviceById(device_id); + if (!device) + return GetDefaultReply(IPC_ENOENT); + + switch (request.request) + { + case USB::IOCTL_USBV0_DEVREMOVALHOOK: + return RegisterRemovalHook(device_id, request); + case USB::IOCTL_USBV0_SUSPENDDEV: + case USB::IOCTL_USBV0_RESUMEDEV: + // Unimplemented because libusb doesn't do power management. + return GetDefaultReply(IPC_SUCCESS); + case USB::IOCTL_USBV0_RESET_DEVICE: + TriggerHook(m_removal_hooks, device_id, IPC_SUCCESS); + return GetDefaultReply(IPC_SUCCESS); + default: + return GetDefaultReply(IPC_EINVAL); + } +} + +IPCCommandResult OH0::DeviceIOCtlV(const u64 device_id, const IOCtlVRequest& request) +{ + const auto device = GetDeviceById(device_id); + if (!device) + return GetDefaultReply(IPC_ENOENT); + + switch (request.request) + { + case USB::IOCTLV_USBV0_CTRLMSG: + case USB::IOCTLV_USBV0_BLKMSG: + case USB::IOCTLV_USBV0_LBLKMSG: + case USB::IOCTLV_USBV0_INTRMSG: + case USB::IOCTLV_USBV0_ISOMSG: + return HandleTransfer(device, request.request, + [&, this]() { return SubmitTransfer(*device, request); }); + case USB::IOCTLV_USBV0_UNKNOWN_32: + request.DumpUnknown(GetDeviceName(), LogTypes::IOS_USB); + return GetDefaultReply(IPC_SUCCESS); + default: + return GetDefaultReply(IPC_EINVAL); + } +} + +s32 OH0::SubmitTransfer(USB::Device& device, const IOCtlVRequest& ioctlv) +{ + switch (ioctlv.request) + { + case USB::IOCTLV_USBV0_CTRLMSG: + if (!ioctlv.HasNumberOfValidVectors(6, 1) || + Common::swap16(Memory::Read_U16(ioctlv.in_vectors[4].address)) != ioctlv.io_vectors[0].size) + return IPC_EINVAL; + return device.SubmitTransfer(std::make_unique(ioctlv)); + + case USB::IOCTLV_USBV0_BLKMSG: + case USB::IOCTLV_USBV0_LBLKMSG: + if (!ioctlv.HasNumberOfValidVectors(2, 1) || + Memory::Read_U16(ioctlv.in_vectors[1].address) != ioctlv.io_vectors[0].size) + return IPC_EINVAL; + return device.SubmitTransfer( + std::make_unique(ioctlv, ioctlv.request == USB::IOCTLV_USBV0_LBLKMSG)); + + case USB::IOCTLV_USBV0_INTRMSG: + if (!ioctlv.HasNumberOfValidVectors(2, 1) || + Memory::Read_U16(ioctlv.in_vectors[1].address) != ioctlv.io_vectors[0].size) + return IPC_EINVAL; + return device.SubmitTransfer(std::make_unique(ioctlv)); + + case USB::IOCTLV_USBV0_ISOMSG: + if (!ioctlv.HasNumberOfValidVectors(3, 2)) + return IPC_EINVAL; + return device.SubmitTransfer(std::make_unique(ioctlv)); + + default: + return IPC_EINVAL; + } +} +} // namespace Device +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/USB/OH0/OH0.h b/Source/Core/Core/IOS/USB/OH0/OH0.h new file mode 100644 index 0000000000..df732a8c09 --- /dev/null +++ b/Source/Core/Core/IOS/USB/OH0/OH0.h @@ -0,0 +1,88 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Core/IOS/Device.h" +#include "Core/IOS/USB/Host.h" + +class PointerWrap; + +namespace IOS +{ +namespace HLE +{ +namespace USB +{ +struct DeviceInfo +{ + u16 vid; + u16 pid; + bool operator<(const DeviceInfo& other) const { return vid < other.vid; } +}; +} // namespace USB + +namespace Device +{ +// /dev/usb/oh0 +class OH0 final : public USBHost +{ +public: + OH0(u32 device_id, const std::string& device_name); + ~OH0() override; + + ReturnCode Open(const OpenRequest& request) override; + IPCCommandResult IOCtl(const IOCtlRequest& request) override; + IPCCommandResult IOCtlV(const IOCtlVRequest& request) override; + + std::pair DeviceOpen(u16 vid, u16 pid); + void DeviceClose(u64 device_id); + IPCCommandResult DeviceIOCtl(u64 device_id, const IOCtlRequest& request); + IPCCommandResult DeviceIOCtlV(u64 device_id, const IOCtlVRequest& request); + + void DoState(PointerWrap& p) override; + +private: + IPCCommandResult CancelInsertionHook(const IOCtlRequest& request); + IPCCommandResult GetDeviceList(const IOCtlVRequest& request) const; + IPCCommandResult GetRhDesca(const IOCtlRequest& request) const; + IPCCommandResult GetRhPortStatus(const IOCtlVRequest& request) const; + IPCCommandResult SetRhPortStatus(const IOCtlVRequest& request); + IPCCommandResult RegisterRemovalHook(u64 device_id, const IOCtlRequest& request); + IPCCommandResult RegisterInsertionHook(const IOCtlVRequest& request); + IPCCommandResult RegisterInsertionHookWithID(const IOCtlVRequest& request); + IPCCommandResult RegisterClassChangeHook(const IOCtlVRequest& request); + s32 SubmitTransfer(USB::Device& device, const IOCtlVRequest& request); + + bool HasDeviceWithVidPid(u16 vid, u16 pid) const; + void OnDeviceChange(ChangeEvent event, std::shared_ptr device) override; + template + void TriggerHook(std::map& hooks, T value, ReturnCode return_value); + + struct DeviceEntry + { + u32 unknown; + u16 vid; + u16 pid; + }; + static_assert(sizeof(DeviceEntry) == 8, "sizeof(DeviceEntry) must be 8"); + + // Device info (VID, PID) → command address for pending hook requests + std::map m_insertion_hooks; + std::map m_removal_hooks; + std::set m_opened_devices; + std::mutex m_hooks_mutex; +}; +} // namespace Device +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/USB/OH0/OH0Device.cpp b/Source/Core/Core/IOS/USB/OH0/OH0Device.cpp new file mode 100644 index 0000000000..61bbd447db --- /dev/null +++ b/Source/Core/Core/IOS/USB/OH0/OH0Device.cpp @@ -0,0 +1,87 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include +#include + +#include "Common/ChunkFile.h" +#include "Core/IOS/IPC.h" +#include "Core/IOS/USB/OH0/OH0.h" +#include "Core/IOS/USB/OH0/OH0Device.h" + +namespace IOS +{ +namespace HLE +{ +namespace Device +{ +static void GetVidPidFromDevicePath(const std::string& device_path, u16& vid, u16& pid) +{ + std::stringstream stream{device_path}; + std::string segment; + std::vector list; + while (std::getline(stream, segment, '/')) + if (!segment.empty()) + list.push_back(segment); + + if (list.size() != 5) + return; + + std::stringstream ss; + ss << std::hex << list[3]; + ss >> vid; + ss.clear(); + ss << std::hex << list[4]; + ss >> pid; +} + +OH0Device::OH0Device(u32 id, const std::string& name) : Device(id, name, DeviceType::OH0) +{ + if (!name.empty()) + GetVidPidFromDevicePath(name, m_vid, m_pid); +} + +void OH0Device::DoState(PointerWrap& p) +{ + m_oh0 = std::static_pointer_cast(GetDeviceByName("/dev/usb/oh0")); + p.Do(m_name); + p.Do(m_vid); + p.Do(m_pid); + p.Do(m_device_id); +} + +ReturnCode OH0Device::Open(const OpenRequest& request) +{ + const u32 ios_major_version = GetVersion(); + if (ios_major_version == 57 || ios_major_version == 58 || ios_major_version == 59) + return IPC_ENOENT; + + if (m_vid == 0 && m_pid == 0) + return IPC_ENOENT; + + m_oh0 = std::static_pointer_cast(GetDeviceByName("/dev/usb/oh0")); + + ReturnCode return_code; + std::tie(return_code, m_device_id) = m_oh0->DeviceOpen(m_vid, m_pid); + return return_code; +} + +void OH0Device::Close() +{ + m_oh0->DeviceClose(m_device_id); +} + +IPCCommandResult OH0Device::IOCtl(const IOCtlRequest& request) +{ + return m_oh0->DeviceIOCtl(m_device_id, request); +} + +IPCCommandResult OH0Device::IOCtlV(const IOCtlVRequest& request) +{ + return m_oh0->DeviceIOCtlV(m_device_id, request); +} +} // namespace Device +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/USB/OH0/OH0Device.h b/Source/Core/Core/IOS/USB/OH0/OH0Device.h new file mode 100644 index 0000000000..239622d227 --- /dev/null +++ b/Source/Core/Core/IOS/USB/OH0/OH0Device.h @@ -0,0 +1,41 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include "Common/CommonTypes.h" +#include "Core/IOS/Device.h" + +class PointerWrap; + +namespace IOS +{ +namespace HLE +{ +namespace Device +{ +class OH0; +class OH0Device final : public Device +{ +public: + OH0Device(u32 device_id, const std::string& device_name); + + ReturnCode Open(const OpenRequest& request) override; + void Close() override; + IPCCommandResult IOCtl(const IOCtlRequest& request) override; + IPCCommandResult IOCtlV(const IOCtlVRequest& request) override; + void DoState(PointerWrap& p) override; + +private: + std::shared_ptr m_oh0; + u16 m_vid = 0; + u16 m_pid = 0; + u64 m_device_id = 0; +}; +} // namespace Device +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USBV0.cpp b/Source/Core/Core/IOS/USB/USBV0.cpp index 13084aa438..fb844f0f09 100644 --- a/Source/Core/Core/IOS/USB/USBV0.cpp +++ b/Source/Core/Core/IOS/USB/USBV0.cpp @@ -42,6 +42,17 @@ V0IntrMessage::V0IntrMessage(const IOCtlVRequest& ioctlv) endpoint = Memory::Read_U8(ioctlv.in_vectors[0].address); length = Memory::Read_U16(ioctlv.in_vectors[1].address); } + +V0IsoMessage::V0IsoMessage(const IOCtlVRequest& ioctlv) + : IsoMessage(ioctlv, ioctlv.io_vectors[1].address) +{ + endpoint = Memory::Read_U8(ioctlv.in_vectors[0].address); + length = Memory::Read_U16(ioctlv.in_vectors[1].address); + num_packets = Memory::Read_U8(ioctlv.in_vectors[2].address); + packet_sizes_addr = ioctlv.io_vectors[0].address; + for (size_t i = 0; i < num_packets; ++i) + packet_sizes.push_back(Memory::Read_U16(static_cast(packet_sizes_addr + i * sizeof(u16)))); +} } // namespace USB } // namespace HLE } // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USBV0.h b/Source/Core/Core/IOS/USB/USBV0.h index 32efb8f25f..9af2c1308f 100644 --- a/Source/Core/Core/IOS/USB/USBV0.h +++ b/Source/Core/Core/IOS/USB/USBV0.h @@ -4,8 +4,6 @@ #pragma once -#include - #include "Common/CommonTypes.h" #include "Core/IOS/USB/Common.h" @@ -24,6 +22,21 @@ enum V0Requests IOCTLV_USBV0_CTRLMSG = 0, IOCTLV_USBV0_BLKMSG = 1, IOCTLV_USBV0_INTRMSG = 2, + IOCTL_USBV0_SUSPENDDEV = 5, + IOCTL_USBV0_RESUMEDEV = 6, + IOCTLV_USBV0_ISOMSG = 9, + IOCTLV_USBV0_LBLKMSG = 10, + IOCTLV_USBV0_GETDEVLIST = 12, + IOCTL_USBV0_GETRHDESCA = 15, + IOCTLV_USBV0_GETRHPORTSTATUS = 20, + IOCTLV_USBV0_SETRHPORTSTATUS = 25, + IOCTL_USBV0_DEVREMOVALHOOK = 26, + IOCTLV_USBV0_DEVINSERTHOOK = 27, + IOCTLV_USBV0_DEVICECLASSCHANGE = 28, + IOCTL_USBV0_RESET_DEVICE = 29, + IOCTLV_USBV0_DEVINSERTHOOKID = 30, + IOCTL_USBV0_CANCEL_INSERT_HOOK = 31, + IOCTLV_USBV0_UNKNOWN_32 = 32, }; struct V0CtrlMessage final : CtrlMessage @@ -40,6 +53,11 @@ struct V0IntrMessage final : IntrMessage { explicit V0IntrMessage(const IOCtlVRequest& ioctlv); }; + +struct V0IsoMessage final : IsoMessage +{ + explicit V0IsoMessage(const IOCtlVRequest& ioctlv); +}; } // namespace USB } // namespace HLE } // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USBV4.cpp b/Source/Core/Core/IOS/USB/USBV4.cpp new file mode 100644 index 0000000000..fae4e2011a --- /dev/null +++ b/Source/Core/Core/IOS/USB/USBV4.cpp @@ -0,0 +1,97 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include +#include + +#include "Common/CommonFuncs.h" +#include "Common/CommonTypes.h" +#include "Core/HW/Memmap.h" +#include "Core/IOS/Device.h" +#include "Core/IOS/USB/USBV4.h" + +namespace IOS +{ +namespace HLE +{ +namespace USB +{ +// Source: https://wiibrew.org/w/index.php?title=/dev/usb/hid&oldid=96809 +#pragma pack(push, 1) +struct HIDRequest +{ + u8 padding[16]; + s32 device_no; + union + { + struct + { + u8 bmRequestType; + u8 bmRequest; + u16 wValue; + u16 wIndex; + u16 wLength; + } control; + struct + { + u32 endpoint; + u32 length; + } interrupt; + struct + { + u8 bIndex; + } string; + }; + u32 data_addr; +}; +#pragma pack(pop) + +V4CtrlMessage::V4CtrlMessage(const IOCtlRequest& ioctl) : CtrlMessage(ioctl, -1) +{ + HIDRequest hid_request; + Memory::CopyFromEmu(&hid_request, ioctl.buffer_in, sizeof(hid_request)); + request_type = hid_request.control.bmRequestType; + request = hid_request.control.bmRequest; + value = Common::swap16(hid_request.control.wValue); + index = Common::swap16(hid_request.control.wIndex); + length = Common::swap16(hid_request.control.wLength); + data_address = Common::swap32(hid_request.data_addr); +} + +// Since this is just a standard control request, but with additional requirements +// (US for the language and replacing non-ASCII characters with '?'), +// we can simply submit it as a usual control request. +V4GetUSStringMessage::V4GetUSStringMessage(const IOCtlRequest& ioctl) : CtrlMessage(ioctl, -1) +{ + HIDRequest hid_request; + Memory::CopyFromEmu(&hid_request, ioctl.buffer_in, sizeof(hid_request)); + request_type = 0x80; + request = REQUEST_GET_DESCRIPTOR; + value = (0x03 << 8) | hid_request.string.bIndex; + index = 0x0409; // language US + length = 255; + data_address = Common::swap32(hid_request.data_addr); +} + +void V4GetUSStringMessage::OnTransferComplete() const +{ + const std::locale& c_locale = std::locale::classic(); + std::string message = Memory::GetString(data_address); + std::replace_if(message.begin(), message.end(), + [&c_locale](char c) { return !std::isprint(c, c_locale); }, '?'); + Memory::CopyToEmu(data_address, message.c_str(), message.size()); +} + +V4IntrMessage::V4IntrMessage(const IOCtlRequest& ioctl) : IntrMessage(ioctl, -1) +{ + HIDRequest hid_request; + Memory::CopyFromEmu(&hid_request, ioctl.buffer_in, sizeof(hid_request)); + length = static_cast(Common::swap32(hid_request.interrupt.length)); + endpoint = static_cast(Common::swap32(hid_request.interrupt.endpoint)); + data_address = Common::swap32(hid_request.data_addr); +} +} // namespace USB +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USBV4.h b/Source/Core/Core/IOS/USB/USBV4.h new file mode 100644 index 0000000000..93ccd7ee21 --- /dev/null +++ b/Source/Core/Core/IOS/USB/USBV4.h @@ -0,0 +1,50 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "Common/CommonTypes.h" +#include "Core/IOS/USB/Common.h" + +// Used by an early version of /dev/usb/hid. + +namespace IOS +{ +namespace HLE +{ +struct IOCtlRequest; + +namespace USB +{ +enum V4Requests +{ + IOCTL_USBV4_GETDEVICECHANGE = 0, + IOCTL_USBV4_SET_SUSPEND = 1, + IOCTL_USBV4_CTRLMSG = 2, + IOCTL_USBV4_INTRMSG_IN = 3, + IOCTL_USBV4_INTRMSG_OUT = 4, + IOCTL_USBV4_GET_US_STRING = 5, + IOCTL_USBV4_GETVERSION = 6, + IOCTL_USBV4_SHUTDOWN = 7, + IOCTL_USBV4_CANCELINTERRUPT = 8, +}; + +struct V4CtrlMessage final : CtrlMessage +{ + explicit V4CtrlMessage(const IOCtlRequest& ioctl); +}; + +struct V4GetUSStringMessage final : CtrlMessage +{ + explicit V4GetUSStringMessage(const IOCtlRequest& ioctl); + void OnTransferComplete() const override; +}; + +struct V4IntrMessage final : IntrMessage +{ + explicit V4IntrMessage(const IOCtlRequest& ioctl); +}; +} // namespace USB +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USBV5.cpp b/Source/Core/Core/IOS/USB/USBV5.cpp new file mode 100644 index 0000000000..bf4fd09399 --- /dev/null +++ b/Source/Core/Core/IOS/USB/USBV5.cpp @@ -0,0 +1,55 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include + +#include "Common/CommonTypes.h" +#include "Core/HW/Memmap.h" +#include "Core/IOS/Device.h" +#include "Core/IOS/USB/USBV5.h" + +namespace IOS +{ +namespace HLE +{ +namespace USB +{ +V5CtrlMessage::V5CtrlMessage(const IOCtlVRequest& ioctlv) + : CtrlMessage(ioctlv, Memory::Read_U32(ioctlv.in_vectors[0].address + 16)) +{ + request_type = Memory::Read_U8(ioctlv.in_vectors[0].address + 8); + request = Memory::Read_U8(ioctlv.in_vectors[0].address + 9); + value = Memory::Read_U16(ioctlv.in_vectors[0].address + 10); + index = Memory::Read_U16(ioctlv.in_vectors[0].address + 12); + length = Memory::Read_U16(ioctlv.in_vectors[0].address + 14); +} + +V5BulkMessage::V5BulkMessage(const IOCtlVRequest& ioctlv) + : BulkMessage(ioctlv, Memory::Read_U32(ioctlv.in_vectors[0].address + 8)) +{ + length = Memory::Read_U16(ioctlv.in_vectors[0].address + 12); + endpoint = Memory::Read_U8(ioctlv.in_vectors[0].address + 18); +} + +V5IntrMessage::V5IntrMessage(const IOCtlVRequest& ioctlv) + : IntrMessage(ioctlv, Memory::Read_U32(ioctlv.in_vectors[0].address + 8)) +{ + length = Memory::Read_U16(ioctlv.in_vectors[0].address + 12); + endpoint = Memory::Read_U8(ioctlv.in_vectors[0].address + 14); +} + +V5IsoMessage::V5IsoMessage(const IOCtlVRequest& ioctlv) + : IsoMessage(ioctlv, Memory::Read_U32(ioctlv.in_vectors[0].address + 8)) +{ + num_packets = Memory::Read_U8(ioctlv.in_vectors[0].address + 16); + endpoint = Memory::Read_U8(ioctlv.in_vectors[0].address + 17); + packet_sizes_addr = Memory::Read_U32(ioctlv.in_vectors[0].address + 12); + for (size_t i = 0; i < num_packets; ++i) + packet_sizes.push_back(Memory::Read_U16(static_cast(packet_sizes_addr + i * sizeof(u16)))); + length = std::accumulate(packet_sizes.begin(), packet_sizes.end(), 0); +} +} // namespace USB +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USBV5.h b/Source/Core/Core/IOS/USB/USBV5.h new file mode 100644 index 0000000000..871d0e7856 --- /dev/null +++ b/Source/Core/Core/IOS/USB/USBV5.h @@ -0,0 +1,58 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "Common/CommonTypes.h" +#include "Core/IOS/USB/Common.h" + +// Used by late USB interfaces for /dev/usb/ven and /dev/usb/hid (since IOS57 which +// reorganised the USB modules in IOS). + +namespace IOS +{ +namespace HLE +{ +struct IOCtlRequest; + +namespace USB +{ +enum V5Requests +{ + IOCTL_USBV5_GETVERSION = 0, + IOCTL_USBV5_GETDEVICECHANGE = 1, + IOCTL_USBV5_SHUTDOWN = 2, + IOCTL_USBV5_GETDEVPARAMS = 3, + IOCTL_USBV5_ATTACHFINISH = 6, + IOCTL_USBV5_SETALTERNATE = 7, + IOCTL_USBV5_SUSPEND_RESUME = 16, + IOCTL_USBV5_CANCELENDPOINT = 17, + IOCTLV_USBV5_CTRLMSG = 18, + IOCTLV_USBV5_INTRMSG = 19, + IOCTLV_USBV5_ISOMSG = 20, + IOCTLV_USBV5_BULKMSG = 21 +}; + +struct V5CtrlMessage final : CtrlMessage +{ + explicit V5CtrlMessage(const IOCtlVRequest& ioctlv); +}; + +struct V5BulkMessage final : BulkMessage +{ + explicit V5BulkMessage(const IOCtlVRequest& ioctlv); +}; + +struct V5IntrMessage final : IntrMessage +{ + explicit V5IntrMessage(const IOCtlVRequest& ioctlv); +}; + +struct V5IsoMessage final : IsoMessage +{ + explicit V5IsoMessage(const IOCtlVRequest& cmd_buffer); +}; +} // namespace USB +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp b/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp new file mode 100644 index 0000000000..4734490f1c --- /dev/null +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp @@ -0,0 +1,230 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include + +#include "Common/Align.h" +#include "Common/ChunkFile.h" +#include "Common/CommonFuncs.h" +#include "Common/Logging/Log.h" +#include "Core/CoreTiming.h" +#include "Core/HW/Memmap.h" +#include "Core/IOS/Device.h" +#include "Core/IOS/USB/Common.h" +#include "Core/IOS/USB/USBV4.h" +#include "Core/IOS/USB/USB_HID/HIDv4.h" + +namespace IOS +{ +namespace HLE +{ +namespace Device +{ +USB_HIDv4::USB_HIDv4(u32 device_id, const std::string& device_name) + : USBHost(device_id, device_name) +{ +} + +USB_HIDv4::~USB_HIDv4() +{ + StopThreads(); +} + +IPCCommandResult USB_HIDv4::IOCtl(const IOCtlRequest& request) +{ + request.Log(GetDeviceName(), LogTypes::IOS_USB); + switch (request.request) + { + case USB::IOCTL_USBV4_GETVERSION: + return GetDefaultReply(VERSION); + case USB::IOCTL_USBV4_GETDEVICECHANGE: + return GetDeviceChange(request); + case USB::IOCTL_USBV4_SHUTDOWN: + return Shutdown(request); + case USB::IOCTL_USBV4_SET_SUSPEND: + // Not implemented in IOS + return GetDefaultReply(IPC_SUCCESS); + case USB::IOCTL_USBV4_CANCELINTERRUPT: + return CancelInterrupt(request); + case USB::IOCTL_USBV4_GET_US_STRING: + case USB::IOCTL_USBV4_CTRLMSG: + case USB::IOCTL_USBV4_INTRMSG_IN: + case USB::IOCTL_USBV4_INTRMSG_OUT: + { + if (request.buffer_in == 0 || request.buffer_in_size != 32) + return GetDefaultReply(IPC_EINVAL); + const auto device = GetDeviceByIOSID(Memory::Read_U32(request.buffer_in + 16)); + if (!device->Attach(0)) + return GetDefaultReply(IPC_EINVAL); + return HandleTransfer(device, request.request, + [&, this]() { return SubmitTransfer(*device, request); }); + } + default: + request.DumpUnknown(GetDeviceName(), LogTypes::IOS_USB); + return GetDefaultReply(IPC_SUCCESS); + } +} + +IPCCommandResult USB_HIDv4::CancelInterrupt(const IOCtlRequest& request) +{ + if (request.buffer_in == 0 || request.buffer_in_size != 8) + return GetDefaultReply(IPC_EINVAL); + + auto device = GetDeviceByIOSID(Memory::Read_U32(request.buffer_in)); + if (!device) + return GetDefaultReply(IPC_ENOENT); + device->CancelTransfer(Memory::Read_U8(request.buffer_in + 4)); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult USB_HIDv4::GetDeviceChange(const IOCtlRequest& request) +{ + std::lock_guard lk{m_devicechange_hook_address_mutex}; + if (request.buffer_out == 0 || request.buffer_out_size != 0x600) + return GetDefaultReply(IPC_EINVAL); + + m_devicechange_hook_request = std::make_unique(request.address); + // On the first call, the reply is sent immediately (instead of on device insertion/removal) + if (m_devicechange_first_call) + { + TriggerDeviceChangeReply(); + m_devicechange_first_call = false; + } + return GetNoReply(); +} + +IPCCommandResult USB_HIDv4::Shutdown(const IOCtlRequest& request) +{ + std::lock_guard lk{m_devicechange_hook_address_mutex}; + if (m_devicechange_hook_request != 0) + { + Memory::Write_U32(0xffffffff, m_devicechange_hook_request->buffer_out); + EnqueueReply(*m_devicechange_hook_request, -1); + m_devicechange_hook_request.reset(); + } + return GetDefaultReply(IPC_SUCCESS); +} + +s32 USB_HIDv4::SubmitTransfer(USB::Device& device, const IOCtlRequest& request) +{ + switch (request.request) + { + case USB::IOCTL_USBV4_CTRLMSG: + return device.SubmitTransfer(std::make_unique(request)); + case USB::IOCTL_USBV4_GET_US_STRING: + return device.SubmitTransfer(std::make_unique(request)); + case USB::IOCTL_USBV4_INTRMSG_IN: + case USB::IOCTL_USBV4_INTRMSG_OUT: + return device.SubmitTransfer(std::make_unique(request)); + default: + return IPC_EINVAL; + } +} + +void USB_HIDv4::DoState(PointerWrap& p) +{ + p.Do(m_devicechange_first_call); + u32 hook_address = m_devicechange_hook_request ? m_devicechange_hook_request->address : 0; + p.Do(hook_address); + if (hook_address != 0) + m_devicechange_hook_request = std::make_unique(hook_address); + else + m_devicechange_hook_request.reset(); + + p.Do(m_ios_ids); + p.Do(m_device_ids); + + USBHost::DoState(p); +} + +std::shared_ptr USB_HIDv4::GetDeviceByIOSID(const s32 ios_id) const +{ + std::lock_guard lk{m_id_map_mutex}; + const auto iterator = m_ios_ids.find(ios_id); + if (iterator == m_ios_ids.cend()) + return nullptr; + return GetDeviceById(iterator->second); +} + +void USB_HIDv4::OnDeviceChange(ChangeEvent event, std::shared_ptr device) +{ + { + std::lock_guard id_map_lock{m_id_map_mutex}; + if (event == ChangeEvent::Inserted) + { + s32 new_id = 0; + while (m_ios_ids.find(new_id) != m_ios_ids.cend()) + ++new_id; + m_ios_ids[new_id] = device->GetId(); + m_device_ids[device->GetId()] = new_id; + } + else if (event == ChangeEvent::Removed && + m_device_ids.find(device->GetId()) != m_device_ids.cend()) + { + m_ios_ids.erase(m_device_ids.at(device->GetId())); + m_device_ids.erase(device->GetId()); + } + } + + { + std::lock_guard lk{m_devicechange_hook_address_mutex}; + TriggerDeviceChangeReply(); + } +} + +bool USB_HIDv4::ShouldAddDevice(const USB::Device& device) const +{ + return device.HasClass(HID_CLASS); +} + +void USB_HIDv4::TriggerDeviceChangeReply() +{ + if (!m_devicechange_hook_request) + return; + + { + std::lock_guard lk(m_devices_mutex); + const u32 dest = m_devicechange_hook_request->buffer_out; + u32 offset = 0; + for (const auto& device : m_devices) + { + const std::vector device_section = GetDeviceEntry(*device.second.get()); + if (offset + device_section.size() > m_devicechange_hook_request->buffer_out_size - 1) + { + WARN_LOG(IOS_USB, "Too many devices connected, skipping"); + break; + } + Memory::CopyToEmu(dest + offset, device_section.data(), device_section.size()); + offset += Common::AlignUp(static_cast(device_section.size()), 4); + } + // IOS writes 0xffffffff to the buffer when there are no more devices + Memory::Write_U32(0xffffffff, dest + offset); + } + + EnqueueReply(*m_devicechange_hook_request, IPC_SUCCESS, 0, CoreTiming::FromThread::ANY); + m_devicechange_hook_request.reset(); +} + +std::vector USB_HIDv4::GetDeviceEntry(const USB::Device& device) const +{ + std::lock_guard id_map_lock{m_id_map_mutex}; + + // The structure for a device section is as follows: + // 0-4 bytes: total size of the device data, including the size and the device ID + // 4-8 bytes: device ID + // the rest of the buffer is device descriptors data + std::vector entry(8); + const std::vector descriptors = device.GetDescriptorsUSBV4(); + const u32 entry_size = Common::swap32(static_cast(entry.size() + descriptors.size())); + const u32 ios_device_id = Common::swap32(m_device_ids.at(device.GetId())); + std::memcpy(entry.data(), &entry_size, sizeof(entry_size)); + std::memcpy(entry.data() + 4, &ios_device_id, sizeof(ios_device_id)); + entry.insert(entry.end(), descriptors.begin(), descriptors.end()); + + return entry; +} +} // namespace Device +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv4.h b/Source/Core/Core/IOS/USB/USB_HID/HIDv4.h new file mode 100644 index 0000000000..4048f3c339 --- /dev/null +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv4.h @@ -0,0 +1,62 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Core/IOS/IPC.h" +#include "Core/IOS/USB/Host.h" + +class PointerWrap; + +namespace IOS +{ +namespace HLE +{ +namespace Device +{ +class USB_HIDv4 final : public USBHost +{ +public: + USB_HIDv4(u32 device_id, const std::string& device_name); + ~USB_HIDv4() override; + + IPCCommandResult IOCtl(const IOCtlRequest& request) override; + + void DoState(PointerWrap& p) override; + +private: + std::shared_ptr GetDeviceByIOSID(s32 ios_id) const; + + IPCCommandResult CancelInterrupt(const IOCtlRequest& request); + IPCCommandResult GetDeviceChange(const IOCtlRequest& request); + IPCCommandResult Shutdown(const IOCtlRequest& request); + s32 SubmitTransfer(USB::Device& device, const IOCtlRequest& request); + + void TriggerDeviceChangeReply(); + std::vector GetDeviceEntry(const USB::Device& device) const; + void OnDeviceChange(ChangeEvent, std::shared_ptr) override; + bool ShouldAddDevice(const USB::Device& device) const override; + + static constexpr u32 VERSION = 0x40001; + static constexpr u8 HID_CLASS = 0x03; + + bool m_devicechange_first_call = true; + std::mutex m_devicechange_hook_address_mutex; + std::unique_ptr m_devicechange_hook_request; + + mutable std::mutex m_id_map_mutex; + // IOS device IDs <=> USB device IDs + std::map m_ios_ids; + std::map m_device_ids; +}; +} // namespace Device +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USB_HIDv4.cpp b/Source/Core/Core/IOS/USB/USB_HIDv4.cpp deleted file mode 100644 index f52fe5607a..0000000000 --- a/Source/Core/Core/IOS/USB/USB_HIDv4.cpp +++ /dev/null @@ -1,575 +0,0 @@ -// Copyright 2012 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include - -#include - -#include "Common/Align.h" -#include "Common/CommonFuncs.h" -#include "Common/Logging/Log.h" -#include "Core/Core.h" -#include "Core/CoreTiming.h" -#include "Core/Debugger/Debugger_SymbolMap.h" -#include "Core/HW/Memmap.h" -#include "Core/IOS/IPC.h" -#include "Core/IOS/USB/USB_HIDv4.h" - -namespace IOS -{ -namespace HLE -{ -namespace Device -{ -constexpr int MAX_DEVICE_DEVNUM = 256; -static u64 hidDeviceAliases[MAX_DEVICE_DEVNUM]; - -// Regular thread -void USB_HIDv4::checkUsbUpdates(USB_HIDv4* hid) -{ - timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 500; - while (hid->usb_thread_running) - { - static u16 timeToFill = 0; - if (timeToFill == 0) - { - std::lock_guard lk(hid->m_device_list_reply_mutex); - if (hid->deviceCommandAddress != 0) - { - IOCtlRequest request{hid->deviceCommandAddress}; - hid->FillOutDevices(request); - EnqueueReply(request, IPC_SUCCESS, 0, CoreTiming::FromThread::NON_CPU); - hid->deviceCommandAddress = 0; - } - } - timeToFill += 8; - libusb_handle_events_timeout(nullptr, &tv); - } - - return; -} - -void USB_HIDv4::handleUsbUpdates(struct libusb_transfer* transfer) -{ - s32 ret = IPC_EINVAL; - u32 replyAddress = (u32)(size_t)transfer->user_data; - if (transfer->status == LIBUSB_TRANSFER_COMPLETED) - { - ret = transfer->length; - } - - IOCtlRequest request{replyAddress}; - EnqueueReply(request, ret, 0, CoreTiming::FromThread::NON_CPU); -} - -USB_HIDv4::USB_HIDv4(u32 device_id, const std::string& device_name) : Device(device_id, device_name) -{ - deviceCommandAddress = 0; - memset(hidDeviceAliases, 0, sizeof(hidDeviceAliases)); - int ret = libusb_init(nullptr); - if (ret) - { - ERROR_LOG(IOS_HID, "libusb_init failed with error: %d", ret); - } - else - { - usb_thread_running = true; - usb_thread = std::thread(checkUsbUpdates, this); - } -} - -USB_HIDv4::~USB_HIDv4() -{ - bool deinit_libusb = false; - if (usb_thread_running) - { - usb_thread_running = false; - usb_thread.join(); - deinit_libusb = true; - } - - for (const auto& device : m_open_devices) - { - libusb_close(device.second); - } - m_open_devices.clear(); - - if (deinit_libusb) - libusb_exit(nullptr); -} - -IPCCommandResult USB_HIDv4::IOCtl(const IOCtlRequest& request) -{ - if (Core::g_want_determinism) - { - return GetDefaultReply(IPC_EACCES); - } - - s32 return_value = IPC_SUCCESS; - switch (request.request) - { - case IOCTL_HID_GET_ATTACHED: - { - deviceCommandAddress = request.address; - return GetNoReply(); - } - case IOCTL_HID_OPEN: - { - // hid version, apparently - return_value = 0x40001; - break; - } - case IOCTL_HID_SET_SUSPEND: - { - // not actually implemented in IOS - return_value = IPC_SUCCESS; - break; - } - case IOCTL_HID_CANCEL_INTERRUPT: - { - return_value = IPC_SUCCESS; - break; - } - case IOCTL_HID_CONTROL: - { - /* - ERROR CODES: - -4 Can't find device specified - */ - - u32 dev_num = Memory::Read_U32(request.buffer_in + 0x10); - u8 bmRequestType = Memory::Read_U8(request.buffer_in + 0x14); - u8 bRequest = Memory::Read_U8(request.buffer_in + 0x15); - u16 wValue = Memory::Read_U16(request.buffer_in + 0x16); - u16 wIndex = Memory::Read_U16(request.buffer_in + 0x18); - u16 wLength = Memory::Read_U16(request.buffer_in + 0x1A); - u32 data = Memory::Read_U32(request.buffer_in + 0x1C); - - return_value = IPC_EINVAL; - - libusb_device_handle* dev_handle = GetDeviceByDevNum(dev_num); - - if (dev_handle == nullptr) - { - INFO_LOG(IOS_HID, "Could not find handle: %X", dev_num); - break; - } - struct libusb_transfer* transfer = libusb_alloc_transfer(0); - transfer->flags |= LIBUSB_TRANSFER_FREE_BUFFER | LIBUSB_TRANSFER_FREE_TRANSFER; - - u8* buffer = (u8*)malloc(wLength + LIBUSB_CONTROL_SETUP_SIZE); - libusb_fill_control_setup(buffer, bmRequestType, bRequest, wValue, wIndex, wLength); - Memory::CopyFromEmu(buffer + LIBUSB_CONTROL_SETUP_SIZE, data, wLength); - libusb_fill_control_transfer(transfer, dev_handle, buffer, handleUsbUpdates, - (void*)(size_t)request.address, /* no timeout */ 0); - libusb_submit_transfer(transfer); - - // DEBUG_LOG(IOS_HID, "HID::IOCtl(Control)(%02X, %02X) (BufferIn: (%08x, %i), - // request.buffer_out: - // (%08x, %i)", - // bmRequestType, bRequest, BufferIn, request.buffer_in_size, request.buffer_out, - // request.buffer_out_size); - - // It's the async way! - return GetNoReply(); - } - case IOCTL_HID_INTERRUPT_OUT: - case IOCTL_HID_INTERRUPT_IN: - { - u32 dev_num = Memory::Read_U32(request.buffer_in + 0x10); - u32 endpoint = Memory::Read_U32(request.buffer_in + 0x14); - u32 length = Memory::Read_U32(request.buffer_in + 0x18); - - u32 data = Memory::Read_U32(request.buffer_in + 0x1C); - - return_value = IPC_EINVAL; - - libusb_device_handle* dev_handle = GetDeviceByDevNum(dev_num); - - if (dev_handle == nullptr) - { - INFO_LOG(IOS_HID, "Could not find handle: %X", dev_num); - break; - } - - struct libusb_transfer* transfer = libusb_alloc_transfer(0); - transfer->flags |= LIBUSB_TRANSFER_FREE_TRANSFER; - libusb_fill_interrupt_transfer(transfer, dev_handle, endpoint, Memory::GetPointer(data), length, - handleUsbUpdates, (void*)(size_t)request.address, 0); - libusb_submit_transfer(transfer); - - // It's the async way! - return GetNoReply(); - } - case IOCTL_HID_SHUTDOWN: - { - std::lock_guard lk(m_device_list_reply_mutex); - if (deviceCommandAddress != 0) - { - IOCtlRequest pending_request{deviceCommandAddress}; - Memory::Write_U32(0xFFFFFFFF, pending_request.buffer_out); - EnqueueReply(pending_request, -1); - deviceCommandAddress = 0; - } - INFO_LOG(IOS_HID, "HID::IOCtl(Shutdown) (BufferIn: (%08x, %i), BufferOut: (%08x, %i)", - request.buffer_in, request.buffer_in_size, request.buffer_out, - request.buffer_out_size); - break; - } - default: - request.Log(GetDeviceName(), LogTypes::IOS_HID); - } - - return GetDefaultReply(return_value); -} - -bool USB_HIDv4::ClaimDevice(libusb_device_handle* dev) -{ - int ret = 0; - if ((ret = libusb_kernel_driver_active(dev, 0)) == 1) - { - if ((ret = libusb_detach_kernel_driver(dev, 0)) && ret != LIBUSB_ERROR_NOT_SUPPORTED) - { - ERROR_LOG(IOS_HID, "libusb_detach_kernel_driver failed with error: %d", ret); - return false; - } - } - else if (ret != 0 && ret != LIBUSB_ERROR_NOT_SUPPORTED) - { - ERROR_LOG(IOS_HID, "libusb_kernel_driver_active error ret = %d", ret); - return false; - } - - if ((ret = libusb_claim_interface(dev, 0))) - { - ERROR_LOG(IOS_HID, "libusb_claim_interface failed with error: %d", ret); - return false; - } - - return true; -} - -IPCCommandResult USB_HIDv4::IOCtlV(const IOCtlVRequest& request) -{ - Dolphin_Debugger::PrintCallstack(LogTypes::IOS_HID, LogTypes::LWARNING); - request.DumpUnknown(GetDeviceName(), LogTypes::IOS_HID); - return GetDefaultReply(IPC_SUCCESS); -} - -void USB_HIDv4::ConvertDeviceToWii(USB::DeviceDescriptor* dest, const libusb_device_descriptor* src) -{ - dest->bLength = src->bLength; - dest->bDescriptorType = src->bDescriptorType; - dest->bcdUSB = Common::swap16(src->bcdUSB); - dest->bDeviceClass = src->bDeviceClass; - dest->bDeviceSubClass = src->bDeviceSubClass; - dest->bDeviceProtocol = src->bDeviceProtocol; - dest->bMaxPacketSize0 = src->bMaxPacketSize0; - dest->idVendor = Common::swap16(src->idVendor); - dest->idProduct = Common::swap16(src->idProduct); - dest->bcdDevice = Common::swap16(src->bcdDevice); - dest->iManufacturer = src->iManufacturer; - dest->iProduct = src->iProduct; - dest->iSerialNumber = src->iSerialNumber; - dest->bNumConfigurations = src->bNumConfigurations; -} - -void USB_HIDv4::ConvertConfigToWii(USB::ConfigDescriptor* dest, const libusb_config_descriptor* src) -{ - memcpy(dest, src, sizeof(USB::ConfigDescriptor)); - dest->wTotalLength = Common::swap16(dest->wTotalLength); -} - -void USB_HIDv4::ConvertInterfaceToWii(USB::InterfaceDescriptor* dest, - const libusb_interface_descriptor* src) -{ - memcpy(dest, src, sizeof(USB::InterfaceDescriptor)); -} - -void USB_HIDv4::ConvertEndpointToWii(USB::EndpointDescriptor* dest, - const libusb_endpoint_descriptor* src) -{ - memcpy(dest, src, sizeof(USB::EndpointDescriptor)); - dest->wMaxPacketSize = Common::swap16(dest->wMaxPacketSize); -} - -void USB_HIDv4::FillOutDevices(const IOCtlRequest& request) -{ - static u16 check = 1; - int OffsetBuffer = request.buffer_out; - int OffsetStart = 0; - // int OffsetDevice = 0; - int d, c, ic, i, e; /* config, interface container, interface, endpoint */ - - libusb_device** list; - // libusb_device *found = nullptr; - ssize_t cnt = libusb_get_device_list(nullptr, &list); - INFO_LOG(IOS_HID, "Found %ld viable USB devices.", cnt); - for (d = 0; d < cnt; d++) - { - libusb_device* device = list[d]; - struct libusb_device_descriptor desc; - int dRet = libusb_get_device_descriptor(device, &desc); - if (dRet) - { - // could not aquire the descriptor, no point in trying to use it. - WARN_LOG(IOS_HID, "libusb_get_device_descriptor failed with error: %d", dRet); - continue; - } - OffsetStart = OffsetBuffer; - OffsetBuffer += 4; // skip length for now, fill at end - - OffsetBuffer += 4; // skip devNum for now - - USB::DeviceDescriptor wii_device; - ConvertDeviceToWii(&wii_device, &desc); - Memory::CopyToEmu(OffsetBuffer, &wii_device, wii_device.bLength); - OffsetBuffer += Common::AlignUp(wii_device.bLength, 4); - bool deviceValid = true; - bool isHID = false; - - for (c = 0; deviceValid && c < desc.bNumConfigurations; c++) - { - struct libusb_config_descriptor* config = nullptr; - int cRet = libusb_get_config_descriptor(device, c, &config); - // do not try to use usb devices with more than one interface, games can crash - if (cRet == 0 && config->bNumInterfaces <= MAX_HID_INTERFACES) - { - USB::ConfigDescriptor wii_config; - ConvertConfigToWii(&wii_config, config); - Memory::CopyToEmu(OffsetBuffer, &wii_config, wii_config.bLength); - OffsetBuffer += Common::AlignUp(wii_config.bLength, 4); - - for (ic = 0; ic < config->bNumInterfaces; ic++) - { - const struct libusb_interface* interfaceContainer = &config->interface[ic]; - - for (i = 0; i < interfaceContainer->num_altsetting; i++) - { - const struct libusb_interface_descriptor* interface = - &interfaceContainer->altsetting[i]; - - if (interface->bInterfaceClass == LIBUSB_CLASS_HID) - isHID = true; - - USB::InterfaceDescriptor wii_interface; - ConvertInterfaceToWii(&wii_interface, interface); - Memory::CopyToEmu(OffsetBuffer, &wii_interface, wii_interface.bLength); - OffsetBuffer += Common::AlignUp(wii_interface.bLength, 4); - - for (e = 0; e < interface->bNumEndpoints; e++) - { - const struct libusb_endpoint_descriptor* endpoint = &interface->endpoint[e]; - - USB::EndpointDescriptor wii_endpoint; - ConvertEndpointToWii(&wii_endpoint, endpoint); - Memory::CopyToEmu(OffsetBuffer, &wii_endpoint, wii_endpoint.bLength); - OffsetBuffer += Common::AlignUp(wii_endpoint.bLength, 4); - - } // endpoints - } // interfaces - } // interface containters - libusb_free_config_descriptor(config); - config = nullptr; - } - else - { - if (cRet) - WARN_LOG(IOS_HID, "libusb_get_config_descriptor failed with: %d", cRet); - deviceValid = false; - OffsetBuffer = OffsetStart; - } - } // configs - - if (!isHID) - { - deviceValid = false; - OffsetBuffer = OffsetStart; - } - - if (deviceValid) - { - Memory::Write_U32(OffsetBuffer - OffsetStart, OffsetStart); // fill in length - - int devNum = GetAvailableDevNum(desc.idVendor, desc.idProduct, libusb_get_bus_number(device), - libusb_get_device_address(device), check); - if (devNum < 0) - { - // too many devices to handle. - ERROR_LOG(IOS_HID, "Exhausted device list, there are way too many usb devices plugged in."); - OffsetBuffer = OffsetStart; - continue; - } - - INFO_LOG(IOS_HID, "Found device with Vendor: %X Product: %X Devnum: %d", desc.idVendor, - desc.idProduct, devNum); - - Memory::Write_U32(devNum, OffsetStart + 4); // write device num - } - } - - // Find devices that no longer exists and free them - for (i = 0; i < MAX_DEVICE_DEVNUM; i++) - { - u16 check_cur = (u16)(hidDeviceAliases[i] >> 48); - if (hidDeviceAliases[i] != 0 && check_cur != check) - { - INFO_LOG(IOS_HID, "Removing: device %d %hX %hX", i, check, check_cur); - std::lock_guard lk(m_open_devices_mutex); - if (m_open_devices.find(i) != m_open_devices.end()) - { - libusb_device_handle* handle = m_open_devices[i]; - libusb_close(handle); - m_open_devices.erase(i); - } - hidDeviceAliases[i] = 0; - } - } - check++; - - libusb_free_device_list(list, 1); - - Memory::Write_U32(0xFFFFFFFF, OffsetBuffer); // no more devices -} - -libusb_device_handle* USB_HIDv4::GetDeviceByDevNum(u32 devNum) -{ - libusb_device** list; - libusb_device_handle* handle = nullptr; - ssize_t cnt; - - if (devNum >= MAX_DEVICE_DEVNUM) - return nullptr; - - std::lock_guard lk(m_open_devices_mutex); - - if (m_open_devices.find(devNum) != m_open_devices.end()) - { - handle = m_open_devices[devNum]; - if (libusb_kernel_driver_active(handle, 0) != LIBUSB_ERROR_NO_DEVICE) - { - return handle; - } - else - { - libusb_close(handle); - m_open_devices.erase(devNum); - } - } - - cnt = libusb_get_device_list(nullptr, &list); - - if (cnt < 0) - return nullptr; - -#ifdef _WIN32 - static bool has_warned_about_drivers = false; -#endif - - for (ssize_t i = 0; i < cnt; i++) - { - libusb_device* device = list[i]; - struct libusb_device_descriptor desc; - int dRet = libusb_get_device_descriptor(device, &desc); - u8 bus = libusb_get_bus_number(device); - u8 port = libusb_get_device_address(device); - u64 unique_id = - ((u64)desc.idVendor << 32) | ((u64)desc.idProduct << 16) | ((u64)bus << 8) | (u64)port; - if ((hidDeviceAliases[devNum] & HID_ID_MASK) == unique_id) - { - int ret = libusb_open(device, &handle); - if (ret) - { - if (ret == LIBUSB_ERROR_ACCESS) - { - if (dRet) - { - ERROR_LOG(IOS_HID, "Dolphin does not have access to this device: Bus %03d Device " - "%03d: ID ????:???? (couldn't get id).", - bus, port); - } - else - { - ERROR_LOG( - IOS_HID, - "Dolphin does not have access to this device: Bus %03d Device %03d: ID %04X:%04X.", - bus, port, desc.idVendor, desc.idProduct); - } - } -#ifdef _WIN32 - else if (ret == LIBUSB_ERROR_NOT_SUPPORTED) - { - if (!has_warned_about_drivers) - { - // Max of one warning. - has_warned_about_drivers = true; - WARN_LOG(IOS_HID, "Please install the libusb drivers for the device %04X:%04X", - desc.idVendor, desc.idProduct); - } - } -#endif - else - { - ERROR_LOG(IOS_HID, "libusb_open failed to open device with error = %d", ret); - } - continue; - } - - if (!ClaimDevice(handle)) - { - ERROR_LOG(IOS_HID, "Could not claim the device for handle: %X", devNum); - libusb_close(handle); - continue; - } - - m_open_devices[devNum] = handle; - break; - } - else - { - handle = nullptr; - } - } - - libusb_free_device_list(list, 1); - - return handle; -} - -int USB_HIDv4::GetAvailableDevNum(u16 idVendor, u16 idProduct, u8 bus, u8 port, u16 check) -{ - int pos = -1; - u64 unique_id = ((u64)idVendor << 32) | ((u64)idProduct << 16) | ((u64)bus << 8) | (u64)port; - - for (int i = 0; i < MAX_DEVICE_DEVNUM; i++) - { - u64 id = hidDeviceAliases[i] & HID_ID_MASK; - if (id == 0 && pos == -1) - { - pos = i; - } - else if (id == unique_id) - { - hidDeviceAliases[i] = id | ((u64)check << 48); - return i; - } - } - - if (pos != -1) - { - hidDeviceAliases[pos] = unique_id | ((u64)check << 48); - return pos; - } - - return -1; -} -} // namespace Device -} // namespace HLE -} // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USB_HIDv4.h b/Source/Core/Core/IOS/USB/USB_HIDv4.h deleted file mode 100644 index 0732a91b44..0000000000 --- a/Source/Core/Core/IOS/USB/USB_HIDv4.h +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2012 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include - -#include "Common/CommonTypes.h" -#include "Core/IOS/Device.h" -#include "Core/IOS/IPC.h" -#include "Core/IOS/USB/Common.h" - -// Forward declare things which we need from libusb header. -// This prevents users of this file from indirectly pulling in libusb. -#if defined(_WIN32) -#define LIBUSB_CALL WINAPI -#else -#define LIBUSB_CALL -#endif -struct libusb_config_descriptor; -struct libusb_device_descriptor; -struct libusb_device_handle; -struct libusb_endpoint_descriptor; -struct libusb_interface_descriptor; -struct libusb_transfer; - -namespace IOS -{ -namespace HLE -{ -#define HID_ID_MASK 0x0000FFFFFFFFFFFF -#define MAX_HID_INTERFACES 1 - -namespace Device -{ -class USB_HIDv4 : public Device -{ -public: - USB_HIDv4(u32 _DeviceID, const std::string& _rDeviceName); - - virtual ~USB_HIDv4(); - - IPCCommandResult IOCtlV(const IOCtlVRequest& request) override; - IPCCommandResult IOCtl(const IOCtlRequest& request) override; - -private: - enum - { - IOCTL_HID_GET_ATTACHED = 0x00, - IOCTL_HID_SET_SUSPEND = 0x01, - IOCTL_HID_CONTROL = 0x02, - IOCTL_HID_INTERRUPT_IN = 0x03, - IOCTL_HID_INTERRUPT_OUT = 0x04, - IOCTL_HID_GET_US_STRING = 0x05, - IOCTL_HID_OPEN = 0x06, - IOCTL_HID_SHUTDOWN = 0x07, - IOCTL_HID_CANCEL_INTERRUPT = 0x08, - }; - - u32 deviceCommandAddress; - void FillOutDevices(const IOCtlRequest& request); - int GetAvailableDevNum(u16 idVendor, u16 idProduct, u8 bus, u8 port, u16 check); - bool ClaimDevice(libusb_device_handle* dev); - - void ConvertDeviceToWii(USB::DeviceDescriptor* dest, const libusb_device_descriptor* src); - void ConvertConfigToWii(USB::ConfigDescriptor* dest, const libusb_config_descriptor* src); - void ConvertInterfaceToWii(USB::InterfaceDescriptor* dest, - const libusb_interface_descriptor* src); - void ConvertEndpointToWii(USB::EndpointDescriptor* dest, const libusb_endpoint_descriptor* src); - - static void checkUsbUpdates(USB_HIDv4* hid); - static void LIBUSB_CALL handleUsbUpdates(libusb_transfer* transfer); - - libusb_device_handle* GetDeviceByDevNum(u32 devNum); - std::map m_open_devices; - std::mutex m_open_devices_mutex; - std::mutex m_device_list_reply_mutex; - - std::thread usb_thread; - bool usb_thread_running; -}; -} // namespace Device -} // namespace HLE -} // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USB_VEN.cpp b/Source/Core/Core/IOS/USB/USB_VEN.cpp deleted file mode 100644 index 811ef83443..0000000000 --- a/Source/Core/Core/IOS/USB/USB_VEN.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include "Core/IOS/USB/USB_VEN.h" -#include "Common/Logging/Log.h" -#include "Core/HW/Memmap.h" - -namespace IOS -{ -namespace HLE -{ -namespace Device -{ -USB_VEN::USB_VEN(u32 device_id, const std::string& device_name) : Device(device_id, device_name) -{ -} - -IPCCommandResult USB_VEN::IOCtlV(const IOCtlVRequest& request) -{ - request.Dump(GetDeviceName()); - return GetNoReply(); -} - -IPCCommandResult USB_VEN::IOCtl(const IOCtlRequest& request) -{ - request.Log(GetDeviceName(), LogTypes::OSHLE); - - IPCCommandResult reply = GetDefaultReply(IPC_SUCCESS); - switch (request.request) - { - case USBV5_IOCTL_GETVERSION: - Memory::Write_U32(0x50001, request.buffer_out); - reply = GetDefaultReply(IPC_SUCCESS); - break; - - case USBV5_IOCTL_GETDEVICECHANGE: - { - // sent on change - static bool firstcall = true; - if (firstcall) - { - reply = GetDefaultReply(IPC_SUCCESS); - firstcall = false; - } - - // num devices - reply = GetDefaultReply(0); - return reply; - } - break; - - case USBV5_IOCTL_ATTACHFINISH: - reply = GetDefaultReply(IPC_SUCCESS); - break; - - case USBV5_IOCTL_SUSPEND_RESUME: - DEBUG_LOG(OSHLE, "Device: %i Resumed: %i", Memory::Read_U32(request.buffer_in), - Memory::Read_U32(request.buffer_in + 4)); - reply = GetDefaultReply(IPC_SUCCESS); - break; - - case USBV5_IOCTL_GETDEVPARAMS: - { - s32 device = Memory::Read_U32(request.buffer_in); - u32 unk = Memory::Read_U32(request.buffer_in + 4); - - DEBUG_LOG(OSHLE, "USBV5_IOCTL_GETDEVPARAMS device: %i unk: %i", device, unk); - - Memory::Write_U32(0, request.buffer_out); - - reply = GetDefaultReply(IPC_SUCCESS); - } - break; - - default: - request.Log(GetDeviceName(), LogTypes::OSHLE, LogTypes::LDEBUG); - } - return reply; -} -} // namespace Device -} // namespace HLE -} // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USB_VEN.h b/Source/Core/Core/IOS/USB/USB_VEN.h deleted file mode 100644 index c52b67cf32..0000000000 --- a/Source/Core/Core/IOS/USB/USB_VEN.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2016 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include - -#include "Common/CommonTypes.h" -#include "Core/IOS/Device.h" -#include "Core/IOS/IPC.h" - -namespace IOS -{ -namespace HLE -{ -namespace Device -{ -class USB_VEN final : public Device -{ -public: - USB_VEN(u32 device_id, const std::string& device_name); - - IPCCommandResult IOCtlV(const IOCtlVRequest& request) override; - IPCCommandResult IOCtl(const IOCtlRequest& request) override; - -private: - enum USBIOCtl - { - USBV5_IOCTL_GETVERSION = 0, - USBV5_IOCTL_GETDEVICECHANGE = 1, - USBV5_IOCTL_SHUTDOWN = 2, - USBV5_IOCTL_GETDEVPARAMS = 3, - USBV5_IOCTL_ATTACHFINISH = 6, - USBV5_IOCTL_SETALTERNATE = 7, - USBV5_IOCTL_SUSPEND_RESUME = 16, - USBV5_IOCTL_CANCELENDPOINT = 17, - USBV5_IOCTL_CTRLMSG = 18, - USBV5_IOCTL_INTRMSG = 19, - USBV5_IOCTL_ISOMSG = 20, - USBV5_IOCTL_BULKMSG = 21 - }; -}; -} // namespace Device -} // namespace HLE -} // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp b/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp new file mode 100644 index 0000000000..a8171fcedb --- /dev/null +++ b/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp @@ -0,0 +1,333 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include "Common/ChunkFile.h" +#include "Common/CommonFuncs.h" +#include "Common/Logging/Log.h" +#include "Core/CoreTiming.h" +#include "Core/HW/Memmap.h" +#include "Core/IOS/Device.h" +#include "Core/IOS/USB/Common.h" +#include "Core/IOS/USB/USBV5.h" +#include "Core/IOS/USB/USB_VEN/VEN.h" + +namespace IOS +{ +namespace HLE +{ +namespace Device +{ +USB_VEN::USB_VEN(u32 device_id, const std::string& device_name) : USBHost(device_id, device_name) +{ +} + +USB_VEN::~USB_VEN() +{ + StopThreads(); +} + +ReturnCode USB_VEN::Open(const OpenRequest& request) +{ + const u32 ios_major_version = GetVersion(); + if (ios_major_version != 57 && ios_major_version != 58 && ios_major_version != 59) + return IPC_ENOENT; + return USBHost::Open(request); +} + +IPCCommandResult USB_VEN::IOCtl(const IOCtlRequest& request) +{ + request.Log(GetDeviceName(), LogTypes::IOS_USB); + switch (request.request) + { + case USB::IOCTL_USBV5_GETVERSION: + Memory::Write_U32(VERSION, request.buffer_out); + return GetDefaultReply(IPC_SUCCESS); + case USB::IOCTL_USBV5_GETDEVICECHANGE: + return GetDeviceChange(request); + case USB::IOCTL_USBV5_SHUTDOWN: + return Shutdown(request); + case USB::IOCTL_USBV5_GETDEVPARAMS: + return HandleDeviceIOCtl(request, &USB_VEN::GetDeviceInfo); + case USB::IOCTL_USBV5_ATTACHFINISH: + return GetDefaultReply(IPC_SUCCESS); + case USB::IOCTL_USBV5_SETALTERNATE: + return HandleDeviceIOCtl(request, &USB_VEN::SetAlternateSetting); + case USB::IOCTL_USBV5_SUSPEND_RESUME: + return HandleDeviceIOCtl(request, &USB_VEN::SuspendResume); + case USB::IOCTL_USBV5_CANCELENDPOINT: + return HandleDeviceIOCtl(request, &USB_VEN::CancelEndpoint); + default: + request.DumpUnknown(GetDeviceName(), LogTypes::IOS_USB, LogTypes::LERROR); + return GetDefaultReply(IPC_SUCCESS); + } +} + +IPCCommandResult USB_VEN::IOCtlV(const IOCtlVRequest& request) +{ + static const std::map s_num_vectors = { + {USB::IOCTLV_USBV5_CTRLMSG, 2}, + {USB::IOCTLV_USBV5_INTRMSG, 2}, + {USB::IOCTLV_USBV5_BULKMSG, 2}, + {USB::IOCTLV_USBV5_ISOMSG, 4}, + }; + + switch (request.request) + { + case USB::IOCTLV_USBV5_CTRLMSG: + case USB::IOCTLV_USBV5_INTRMSG: + case USB::IOCTLV_USBV5_BULKMSG: + case USB::IOCTLV_USBV5_ISOMSG: + { + 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); }); + } + default: + return GetDefaultReply(IPC_EINVAL); + } +} + +void USB_VEN::DoState(PointerWrap& p) +{ + p.Do(m_devicechange_first_call); + u32 hook_address = m_devicechange_hook_request ? m_devicechange_hook_request->address : 0; + p.Do(hook_address); + if (hook_address != 0) + m_devicechange_hook_request = std::make_unique(hook_address); + else + m_devicechange_hook_request.reset(); + + p.Do(m_device_number); + p.Do(m_ios_ids); + p.Do(m_device_ids); + USBHost::DoState(p); +} + +std::shared_ptr USB_VEN::GetDeviceByIOSID(const s32 ios_id) const +{ + std::lock_guard lk{m_id_map_mutex}; + const auto iter = m_ios_ids.find(ios_id); + if (iter == m_ios_ids.cend()) + return nullptr; + return GetDeviceById(iter->second); +} + +u8 USB_VEN::GetInterfaceNumber(const s32 ios_id) const +{ + 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(Memory::Read_U32(request.buffer_in + 2 * sizeof(s32))); + device.CancelTransfer(endpoint); + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult USB_VEN::GetDeviceChange(const IOCtlRequest& request) +{ + std::lock_guard lk{m_devicechange_hook_address_mutex}; + m_devicechange_hook_request = std::make_unique(request.address); + // On the first call, the reply is sent immediately (instead of on device insertion/removal) + if (m_devicechange_first_call) + { + TriggerDeviceChangeReply(); + m_devicechange_first_call = false; + } + return GetNoReply(); +} + +IPCCommandResult USB_VEN::GetDeviceInfo(USB::Device& 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 u8 alt_setting = Memory::Read_U8(request.buffer_in + 8); + auto descriptors = device.GetDescriptorsUSBV5(GetInterfaceNumber(device_id), alt_setting); + if (descriptors.empty()) + return GetDefaultReply(IPC_ENOENT); + + descriptors.resize(request.buffer_out_size - 20); + if (descriptors.size() > request.buffer_out_size - 20) + 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(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) +{ + const s32 device_id = Memory::Read_U32(request.buffer_in); + if (!device.Attach(GetInterfaceNumber(device_id))) + return GetDefaultReply(-1); + + const u8 alt_setting = Memory::Read_U8(request.buffer_in + 2 * sizeof(s32)); + + const bool success = device.SetAltSetting(alt_setting) == 0; + return GetDefaultReply(success ? IPC_SUCCESS : IPC_EINVAL); +} + +IPCCommandResult USB_VEN::Shutdown(const IOCtlRequest& request) +{ + if (request.buffer_in != 0 || request.buffer_in_size != 0 || request.buffer_out != 0 || + request.buffer_out_size != 0) + { + return GetDefaultReply(IPC_EINVAL); + } + + std::lock_guard lk{m_devicechange_hook_address_mutex}; + if (m_devicechange_hook_request) + { + EnqueueReply(*m_devicechange_hook_request, IPC_SUCCESS); + m_devicechange_hook_request.reset(); + } + return GetDefaultReply(IPC_SUCCESS); +} + +IPCCommandResult USB_VEN::SuspendResume(USB::Device& 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)); + + // 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"); + return GetDefaultReply(IPC_SUCCESS); +} + +s32 USB_VEN::SubmitTransfer(USB::Device& device, const IOCtlVRequest& ioctlv) +{ + switch (ioctlv.request) + { + case USB::IOCTLV_USBV5_CTRLMSG: + return device.SubmitTransfer(std::make_unique(ioctlv)); + case USB::IOCTLV_USBV5_INTRMSG: + return device.SubmitTransfer(std::make_unique(ioctlv)); + case USB::IOCTLV_USBV5_BULKMSG: + return device.SubmitTransfer(std::make_unique(ioctlv)); + case USB::IOCTLV_USBV5_ISOMSG: + return device.SubmitTransfer(std::make_unique(ioctlv)); + default: + return IPC_EINVAL; + } +} + +IPCCommandResult USB_VEN::HandleDeviceIOCtl(const IOCtlRequest& request, Handler 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); + if (!device) + return GetDefaultReply(IPC_ENOENT); + return handler(this, *device, request); +} + +void USB_VEN::OnDeviceChange(const ChangeEvent event, std::shared_ptr device) +{ + std::lock_guard id_map_lock{m_id_map_mutex}; + if (event == ChangeEvent::Inserted) + { + for (const auto& interface : device->GetInterfaces(0)) + { + 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; + + 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); + } + } + 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()); + } +} + +void USB_VEN::OnDeviceChangeEnd() +{ + std::lock_guard lk{m_devicechange_hook_address_mutex}; + TriggerDeviceChangeReply(); + ++m_device_number; +} + +void USB_VEN::TriggerDeviceChangeReply() +{ + if (!m_devicechange_hook_request) + return; + + std::lock_guard id_map_lock{m_id_map_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) + { + if (num_devices >= max_num) + { + WARN_LOG(IOS_USB, "Too many devices (%d ≥ %zu), skipping", num_devices, max_num); + break; + } + + const s32 ios_device_id = ios_device.first; + const auto device = GetDeviceById(m_ios_ids.at(ios_device_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); + 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); + + Memory::CopyToEmu(m_devicechange_hook_request->buffer_out + sizeof(entry) * num_devices++, + &entry, sizeof(entry)); + } + + EnqueueReply(*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); +} +} // namespace Device +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/IOS/USB/USB_VEN/VEN.h b/Source/Core/Core/IOS/USB/USB_VEN/VEN.h new file mode 100644 index 0000000000..b35ea15384 --- /dev/null +++ b/Source/Core/Core/IOS/USB/USB_VEN/VEN.h @@ -0,0 +1,95 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Core/IOS/Device.h" +#include "Core/IOS/IPC.h" +#include "Core/IOS/USB/Host.h" + +class PointerWrap; + +namespace IOS +{ +namespace HLE +{ +namespace Device +{ +class USB_VEN final : public USBHost +{ +public: + USB_VEN(u32 device_id, const std::string& device_name); + ~USB_VEN() override; + + ReturnCode Open(const OpenRequest& request) override; + IPCCommandResult IOCtl(const IOCtlRequest& request) override; + IPCCommandResult IOCtlV(const IOCtlVRequest& request) override; + + void DoState(PointerWrap& p) override; + +private: +#pragma pack(push, 1) + struct DeviceID + { + u8 unknown; + u8 interface_plus_1e; + u8 zero; + u8 counter; + }; + + 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 GetDeviceByIOSID(s32 ios_id) const; + u8 GetInterfaceNumber(s32 ios_id) const; + + IPCCommandResult CancelEndpoint(USB::Device& 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 Shutdown(const IOCtlRequest& request); + IPCCommandResult SuspendResume(USB::Device& device, const IOCtlRequest& request); + s32 SubmitTransfer(USB::Device& device, const IOCtlVRequest& request); + + using Handler = std::function; + IPCCommandResult HandleDeviceIOCtl(const IOCtlRequest& request, Handler handler); + + void OnDeviceChange(ChangeEvent, std::shared_ptr) override; + void OnDeviceChangeEnd() override; + void TriggerDeviceChangeReply(); + + static constexpr u32 VERSION = 0x50001; + + bool m_devicechange_first_call = true; + std::mutex m_devicechange_hook_address_mutex; + std::unique_ptr 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 m_ios_ids; + // USB device IDs => IOS device IDs (one to many, because VEN exposes one device per interface) + std::map> m_device_ids; +}; +} // namespace Device +} // namespace HLE +} // namespace IOS diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index e51b9386e7..9f1b96da03 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -71,7 +71,7 @@ static Common::Event g_compressAndDumpStateSyncEvent; static std::thread g_save_thread; // Don't forget to increase this after doing changes on the savestate system -static const u32 STATE_VERSION = 73; // Last changed in PR 4651 +static const u32 STATE_VERSION = 74; // Last changed in PR 4408 // Maps savestate versions to Dolphin versions. // Versions after 42 don't need to be added to this list, diff --git a/Source/Core/DolphinWX/CMakeLists.txt b/Source/Core/DolphinWX/CMakeLists.txt index 020b7bbd59..364d720cc8 100644 --- a/Source/Core/DolphinWX/CMakeLists.txt +++ b/Source/Core/DolphinWX/CMakeLists.txt @@ -7,6 +7,7 @@ set(GUI_SRCS Cheats/CheatsWindow.cpp Cheats/CreateCodeDialog.cpp Cheats/GeckoCodeDiag.cpp + Config/AddUSBDeviceDiag.cpp Config/AdvancedConfigPane.cpp Config/AudioConfigPane.cpp Config/ConfigMain.cpp diff --git a/Source/Core/DolphinWX/Config/AddUSBDeviceDiag.cpp b/Source/Core/DolphinWX/Config/AddUSBDeviceDiag.cpp new file mode 100644 index 0000000000..c6f1154af5 --- /dev/null +++ b/Source/Core/DolphinWX/Config/AddUSBDeviceDiag.cpp @@ -0,0 +1,164 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "Common/StringUtil.h" +#include "Core/ConfigManager.h" +#include "DolphinWX/Config/AddUSBDeviceDiag.h" +#include "DolphinWX/WxUtils.h" +#include "UICommon/USBUtils.h" + +AddUSBDeviceDiag::AddUSBDeviceDiag(wxWindow* const parent) + : wxDialog(parent, wxID_ANY, _("Add New USB Device")) +{ + InitControls(); + + RefreshDeviceList(); + Bind(wxEVT_TIMER, &AddUSBDeviceDiag::OnRefreshDevicesTimer, this, + m_refresh_devices_timer.GetId()); + m_refresh_devices_timer.Start(DEVICE_REFRESH_INTERVAL_MS, wxTIMER_CONTINUOUS); + + auto* const btn_sizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL); + btn_sizer->GetAffirmativeButton()->SetLabel(_("Add")); + Bind(wxEVT_BUTTON, &AddUSBDeviceDiag::OnSave, this, wxID_OK); + + auto* const sizer = new wxBoxSizer(wxVERTICAL); + const int space5 = FromDIP(5); + sizer->AddSpacer(FromDIP(10)); + sizer->Add(new wxStaticText(this, wxID_ANY, _("Enter USB device ID"), wxDefaultPosition, + wxDefaultSize, wxALIGN_CENTRE_HORIZONTAL), + 0, wxEXPAND | wxBOTTOM, FromDIP(10)); + sizer->Add(CreateManualControlsSizer(), 0, wxEXPAND | wxLEFT | wxRIGHT, space5); + sizer->Add(new wxStaticText(this, wxID_ANY, _("or select a device"), wxDefaultPosition, + wxDefaultSize, wxALIGN_CENTRE_HORIZONTAL), + 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(10)); + auto* const device_list_sizer = CreateDeviceListSizer(); + sizer->Add(device_list_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, space5); + sizer->SetItemMinSize(device_list_sizer, FromDIP(350), FromDIP(150)); + sizer->Add(btn_sizer, 0, wxEXPAND); + sizer->AddSpacer(space5); + + SetSizerAndFit(sizer); + Center(); +} + +void AddUSBDeviceDiag::InitControls() +{ + m_new_device_vid_ctrl = new wxTextCtrl(this, wxID_ANY); + m_new_device_pid_ctrl = new wxTextCtrl(this, wxID_ANY); + // i18n: VID means Vendor ID (in the context of a USB device) + m_new_device_vid_ctrl->SetHint(_("Device VID (e.g., 057e)")); + // i18n: PID means Product ID (in the context of a USB device), not Process ID + m_new_device_pid_ctrl->SetHint(_("Device PID (e.g., 0305)")); + + m_inserted_devices_listbox = new wxListBox(this, wxID_ANY); + m_inserted_devices_listbox->Bind(wxEVT_LISTBOX, &AddUSBDeviceDiag::OnDeviceSelection, this); + m_inserted_devices_listbox->Bind(wxEVT_LISTBOX_DCLICK, &AddUSBDeviceDiag::OnSave, this); +} + +void AddUSBDeviceDiag::RefreshDeviceList() +{ + const auto& current_devices = USBUtils::GetInsertedDevices(); + if (current_devices == m_shown_devices) + return; + + m_inserted_devices_listbox->Freeze(); + const auto selection_string = m_inserted_devices_listbox->GetStringSelection(); + m_inserted_devices_listbox->Clear(); + for (const auto& device : current_devices) + { + if (SConfig::GetInstance().IsUSBDeviceWhitelisted(device.first)) + continue; + m_inserted_devices_listbox->Append(device.second, new USBPassthroughDeviceEntry(device.first)); + } + if (!selection_string.empty()) + m_inserted_devices_listbox->SetStringSelection(selection_string); + m_inserted_devices_listbox->Thaw(); + + m_shown_devices = current_devices; +} + +wxSizer* AddUSBDeviceDiag::CreateManualControlsSizer() +{ + const int space5 = FromDIP(5); + auto* const sizer = new wxBoxSizer(wxHORIZONTAL); + + sizer->Add(m_new_device_vid_ctrl, 1, wxEXPAND | wxLEFT | wxRIGHT, space5); + sizer->Add(m_new_device_pid_ctrl, 1, wxEXPAND | wxLEFT | wxRIGHT, space5); + + return sizer; +} + +wxSizer* AddUSBDeviceDiag::CreateDeviceListSizer() +{ + const int space5 = FromDIP(5); + auto* const sizer = new wxBoxSizer(wxVERTICAL); + + sizer->Add(m_inserted_devices_listbox, 1, wxEXPAND | wxLEFT | wxRIGHT, space5); + + return sizer; +} + +static bool IsValidUSBIDString(const std::string& string) +{ + if (string.empty() || string.length() > 4) + return false; + return std::all_of(string.begin(), string.end(), + [](const auto character) { return std::isxdigit(character) != 0; }); +} + +void AddUSBDeviceDiag::OnRefreshDevicesTimer(wxTimerEvent&) +{ + RefreshDeviceList(); +} + +void AddUSBDeviceDiag::OnDeviceSelection(wxCommandEvent&) +{ + const int index = m_inserted_devices_listbox->GetSelection(); + if (index == wxNOT_FOUND) + return; + auto* const entry = static_cast( + m_inserted_devices_listbox->GetClientObject(index)); + m_new_device_vid_ctrl->SetValue(StringFromFormat("%04x", entry->m_vid)); + m_new_device_pid_ctrl->SetValue(StringFromFormat("%04x", entry->m_pid)); +} + +void AddUSBDeviceDiag::OnSave(wxCommandEvent&) +{ + const std::string vid_string = StripSpaces(WxStrToStr(m_new_device_vid_ctrl->GetValue())); + const std::string pid_string = StripSpaces(WxStrToStr(m_new_device_pid_ctrl->GetValue())); + if (!IsValidUSBIDString(vid_string)) + { + // i18n: Here, VID means Vendor ID (for a USB device). + WxUtils::ShowErrorDialog(_("The entered VID is invalid.")); + return; + } + if (!IsValidUSBIDString(pid_string)) + { + // i18n: Here, PID means Product ID (for a USB device). + WxUtils::ShowErrorDialog(_("The entered PID is invalid.")); + return; + } + + const u16 vid = static_cast(std::stoul(vid_string, nullptr, 16)); + const u16 pid = static_cast(std::stoul(pid_string, nullptr, 16)); + + if (SConfig::GetInstance().IsUSBDeviceWhitelisted({vid, pid})) + { + WxUtils::ShowErrorDialog(_("This USB device is already whitelisted.")); + return; + } + + SConfig::GetInstance().m_usb_passthrough_devices.emplace(vid, pid); + AcceptAndClose(); +} diff --git a/Source/Core/DolphinWX/Config/AddUSBDeviceDiag.h b/Source/Core/DolphinWX/Config/AddUSBDeviceDiag.h new file mode 100644 index 0000000000..38b65c34db --- /dev/null +++ b/Source/Core/DolphinWX/Config/AddUSBDeviceDiag.h @@ -0,0 +1,56 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +#include +#include +#include + +#include "Common/CommonTypes.h" + +class wxListBox; +class wxSizer; +class wxTextCtrl; + +class USBPassthroughDeviceEntry final : public wxClientData +{ +public: + explicit USBPassthroughDeviceEntry(const std::pair pair) + : m_vid(pair.first), m_pid(pair.second) + { + } + const u16 m_vid; + const u16 m_pid; +}; + +// This dialog is used to add a new USB device to the USB passthrough whitelist, +// either by selecting a connected USB device or by entering the PID/VID manually. +class AddUSBDeviceDiag final : public wxDialog +{ +public: + explicit AddUSBDeviceDiag(wxWindow* parent); + +private: + static constexpr int DEVICE_REFRESH_INTERVAL_MS = 100; + + void InitControls(); + void RefreshDeviceList(); + wxSizer* CreateManualControlsSizer(); + wxSizer* CreateDeviceListSizer(); + + void OnRefreshDevicesTimer(wxTimerEvent&); + void OnDeviceSelection(wxCommandEvent&); + void OnSave(wxCommandEvent&); + + std::map, std::string> m_shown_devices; + wxTimer m_refresh_devices_timer{this}; + + wxTextCtrl* m_new_device_vid_ctrl; + wxTextCtrl* m_new_device_pid_ctrl; + wxListBox* m_inserted_devices_listbox; +}; diff --git a/Source/Core/DolphinWX/Config/WiiConfigPane.cpp b/Source/Core/DolphinWX/Config/WiiConfigPane.cpp index a98778eb2d..0d2ede097d 100644 --- a/Source/Core/DolphinWX/Config/WiiConfigPane.cpp +++ b/Source/Core/DolphinWX/Config/WiiConfigPane.cpp @@ -2,11 +2,11 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include "DolphinWX/Config/WiiConfigPane.h" - +#include #include #include #include +#include #include #include #include @@ -14,9 +14,12 @@ #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/IOS/IPC.h" +#include "DolphinWX/Config/AddUSBDeviceDiag.h" +#include "DolphinWX/Config/WiiConfigPane.h" #include "DolphinWX/DolphinSlider.h" #include "DolphinWX/WxEventUtils.h" #include "DolphinWX/WxUtils.h" +#include "UICommon/USBUtils.h" WiiConfigPane::WiiConfigPane(wxWindow* parent, wxWindowID id) : wxPanel(parent, id) { @@ -52,6 +55,10 @@ void WiiConfigPane::InitializeGUI() new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_system_language_strings); m_sd_card_checkbox = new wxCheckBox(this, wxID_ANY, _("Insert SD Card")); m_connect_keyboard_checkbox = new wxCheckBox(this, wxID_ANY, _("Connect USB Keyboard")); + m_usb_passthrough_devices_listbox = + new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 100)); + m_usb_passthrough_add_device_btn = new wxButton(this, wxID_ANY, _("Add...")); + m_usb_passthrough_rem_device_btn = new wxButton(this, wxID_ANY, _("Remove")); m_bt_sensor_bar_pos = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_bt_sensor_bar_pos_strings); m_bt_sensor_bar_sens = new DolphinSlider(this, wxID_ANY, 0, 0, 4); @@ -79,6 +86,12 @@ void WiiConfigPane::InitializeGUI() misc_settings_grid_sizer->Add(m_system_language_choice, wxGBPosition(3, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL); + auto* const usb_passthrough_btn_sizer = new wxBoxSizer(wxHORIZONTAL); + usb_passthrough_btn_sizer->AddStretchSpacer(); + usb_passthrough_btn_sizer->Add(m_usb_passthrough_add_device_btn, 0, + wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, space5); + usb_passthrough_btn_sizer->Add(m_usb_passthrough_rem_device_btn, 0, wxALIGN_CENTER_VERTICAL); + auto* const bt_sensor_bar_pos_sizer = new wxBoxSizer(wxHORIZONTAL); bt_sensor_bar_pos_sizer->Add(new wxStaticText(this, wxID_ANY, _("Min")), 0, wxALIGN_CENTER_VERTICAL); @@ -123,6 +136,15 @@ void WiiConfigPane::InitializeGUI() device_settings_sizer->Add(m_connect_keyboard_checkbox, 0, wxLEFT | wxRIGHT, space5); device_settings_sizer->AddSpacer(space5); + auto* const usb_passthrough_sizer = + new wxStaticBoxSizer(wxVERTICAL, this, _("Whitelisted USB Passthrough Devices")); + usb_passthrough_sizer->AddSpacer(space5); + usb_passthrough_sizer->Add(m_usb_passthrough_devices_listbox, 0, wxEXPAND | wxLEFT | wxRIGHT, + space5); + usb_passthrough_sizer->AddSpacer(space5); + usb_passthrough_sizer->Add(usb_passthrough_btn_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, space5); + usb_passthrough_sizer->AddSpacer(space5); + auto* const bt_settings_static_sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Wii Remote Settings")); bt_settings_static_sizer->AddSpacer(space5); @@ -135,6 +157,8 @@ void WiiConfigPane::InitializeGUI() main_sizer->AddSpacer(space5); main_sizer->Add(device_settings_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, space5); main_sizer->AddSpacer(space5); + main_sizer->Add(usb_passthrough_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, space5); + main_sizer->AddSpacer(space5); main_sizer->Add(bt_settings_static_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, space5); main_sizer->AddSpacer(space5); @@ -151,12 +175,26 @@ void WiiConfigPane::LoadGUIValues() m_sd_card_checkbox->SetValue(SConfig::GetInstance().m_WiiSDCard); m_connect_keyboard_checkbox->SetValue(SConfig::GetInstance().m_WiiKeyboard); + PopulateUSBPassthroughListbox(); + m_bt_sensor_bar_pos->SetSelection(SConfig::GetInstance().m_sensor_bar_position); m_bt_sensor_bar_sens->SetValue(SConfig::GetInstance().m_sensor_bar_sensitivity); m_bt_speaker_volume->SetValue(SConfig::GetInstance().m_speaker_volume); m_bt_wiimote_motor->SetValue(SConfig::GetInstance().m_wiimote_motor); } +void WiiConfigPane::PopulateUSBPassthroughListbox() +{ + m_usb_passthrough_devices_listbox->Freeze(); + m_usb_passthrough_devices_listbox->Clear(); + for (const auto& device : SConfig::GetInstance().m_usb_passthrough_devices) + { + m_usb_passthrough_devices_listbox->Append(USBUtils::GetDeviceName(device), + new USBPassthroughDeviceEntry(device)); + } + m_usb_passthrough_devices_listbox->Thaw(); +} + void WiiConfigPane::BindEvents() { m_screensaver_checkbox->Bind(wxEVT_CHECKBOX, &WiiConfigPane::OnScreenSaverCheckBoxChanged, this); @@ -186,6 +224,38 @@ void WiiConfigPane::BindEvents() m_bt_wiimote_motor->Bind(wxEVT_CHECKBOX, &WiiConfigPane::OnWiimoteMotorChanged, this); m_bt_wiimote_motor->Bind(wxEVT_UPDATE_UI, &WxEventUtils::OnEnableIfCoreNotRunning); + + m_usb_passthrough_add_device_btn->Bind(wxEVT_BUTTON, &WiiConfigPane::OnUSBWhitelistAddButton, + this); + + m_usb_passthrough_rem_device_btn->Bind(wxEVT_BUTTON, &WiiConfigPane::OnUSBWhitelistRemoveButton, + this); + m_usb_passthrough_rem_device_btn->Bind(wxEVT_UPDATE_UI, + &WiiConfigPane::OnUSBWhitelistRemoveButtonUpdate, this); +} + +void WiiConfigPane::OnUSBWhitelistAddButton(wxCommandEvent&) +{ + AddUSBDeviceDiag add_dialog{this}; + // Reload the USB device whitelist + if (add_dialog.ShowModal() == wxID_OK) + PopulateUSBPassthroughListbox(); +} + +void WiiConfigPane::OnUSBWhitelistRemoveButton(wxCommandEvent&) +{ + const int index = m_usb_passthrough_devices_listbox->GetSelection(); + if (index == wxNOT_FOUND) + return; + auto* const entry = static_cast( + m_usb_passthrough_devices_listbox->GetClientObject(index)); + SConfig::GetInstance().m_usb_passthrough_devices.erase({entry->m_vid, entry->m_pid}); + m_usb_passthrough_devices_listbox->Delete(index); +} + +void WiiConfigPane::OnUSBWhitelistRemoveButtonUpdate(wxUpdateUIEvent& event) +{ + event.Enable(m_usb_passthrough_devices_listbox->GetSelection() != wxNOT_FOUND); } void WiiConfigPane::OnScreenSaverCheckBoxChanged(wxCommandEvent& event) diff --git a/Source/Core/DolphinWX/Config/WiiConfigPane.h b/Source/Core/DolphinWX/Config/WiiConfigPane.h index bb68f89e4e..557ab624f1 100644 --- a/Source/Core/DolphinWX/Config/WiiConfigPane.h +++ b/Source/Core/DolphinWX/Config/WiiConfigPane.h @@ -6,11 +6,12 @@ #include #include -#include "Common/CommonTypes.h" class DolphinSlider; +class wxButton; class wxCheckBox; class wxChoice; +class wxListBox; class wxSlider; class WiiConfigPane final : public wxPanel @@ -23,6 +24,8 @@ private: void LoadGUIValues(); void BindEvents(); + void PopulateUSBPassthroughListbox(); + void OnScreenSaverCheckBoxChanged(wxCommandEvent&); void OnPAL60CheckBoxChanged(wxCommandEvent&); void OnSDCardCheckBoxChanged(wxCommandEvent&); @@ -30,6 +33,10 @@ private: void OnSystemLanguageChoiceChanged(wxCommandEvent&); void OnAspectRatioChoiceChanged(wxCommandEvent&); + void OnUSBWhitelistAddButton(wxCommandEvent&); + void OnUSBWhitelistRemoveButton(wxCommandEvent&); + void OnUSBWhitelistRemoveButtonUpdate(wxUpdateUIEvent&); + void OnSensorBarPosChanged(wxCommandEvent&); void OnSensorBarSensChanged(wxCommandEvent&); void OnSpeakerVolumeChanged(wxCommandEvent&); @@ -46,6 +53,10 @@ private: wxChoice* m_system_language_choice; wxChoice* m_aspect_ratio_choice; + wxListBox* m_usb_passthrough_devices_listbox; + wxButton* m_usb_passthrough_add_device_btn; + wxButton* m_usb_passthrough_rem_device_btn; + wxChoice* m_bt_sensor_bar_pos; DolphinSlider* m_bt_sensor_bar_sens; DolphinSlider* m_bt_speaker_volume; diff --git a/Source/Core/DolphinWX/DolphinWX.vcxproj b/Source/Core/DolphinWX/DolphinWX.vcxproj index 81412afe65..bae3476422 100644 --- a/Source/Core/DolphinWX/DolphinWX.vcxproj +++ b/Source/Core/DolphinWX/DolphinWX.vcxproj @@ -62,6 +62,7 @@ + @@ -139,6 +140,7 @@ + diff --git a/Source/Core/DolphinWX/DolphinWX.vcxproj.filters b/Source/Core/DolphinWX/DolphinWX.vcxproj.filters index 5cc92cb65a..90842a6629 100644 --- a/Source/Core/DolphinWX/DolphinWX.vcxproj.filters +++ b/Source/Core/DolphinWX/DolphinWX.vcxproj.filters @@ -224,6 +224,9 @@ GUI\Config + + GUI\Config + GUI\Config @@ -443,6 +446,9 @@ GUI\Config + + GUI\Config + GUI\Config diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp index ec66f28136..c16b1ce635 100644 --- a/Source/Core/InputCommon/GCAdapter.cpp +++ b/Source/Core/InputCommon/GCAdapter.cpp @@ -4,9 +4,11 @@ #include #include +#include #include #include "Common/Flag.h" +#include "Common/LibusbContext.h" #include "Common/Logging/Log.h" #include "Common/Thread.h" #include "Core/ConfigManager.h" @@ -50,7 +52,7 @@ static Common::Flag s_adapter_detect_thread_running; static std::function s_detect_callback; static bool s_libusb_driver_not_supported = false; -static libusb_context* s_libusb_context = nullptr; +static std::shared_ptr s_libusb_context; #if defined(__FreeBSD__) && __FreeBSD__ >= 11 static bool s_libusb_hotplug_enabled = true; #else @@ -116,8 +118,8 @@ static void ScanThreadFunc() if (s_libusb_hotplug_enabled) { if (libusb_hotplug_register_callback( - s_libusb_context, (libusb_hotplug_event)(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | - LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT), + s_libusb_context.get(), (libusb_hotplug_event)(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT), LIBUSB_HOTPLUG_ENUMERATE, 0x057e, 0x0337, LIBUSB_HOTPLUG_MATCH_ANY, HotplugCallback, nullptr, &s_hotplug_handle) != LIBUSB_SUCCESS) s_libusb_hotplug_enabled = false; @@ -131,7 +133,7 @@ static void ScanThreadFunc() if (s_libusb_hotplug_enabled) { static timeval tv = {0, 500000}; - libusb_handle_events_timeout(s_libusb_context, &tv); + libusb_handle_events_timeout(s_libusb_context.get(), &tv); } else { @@ -168,11 +170,9 @@ void Init() s_libusb_driver_not_supported = false; - int ret = libusb_init(&s_libusb_context); - - if (ret) + s_libusb_context = LibusbContext::Get(); + if (!s_libusb_context) { - ERROR_LOG(SERIALINTERFACE, "libusb_init failed with error: %d", ret); s_libusb_driver_not_supported = true; Shutdown(); } @@ -203,7 +203,7 @@ void StopScanThread() static void Setup() { libusb_device** list; - ssize_t cnt = libusb_get_device_list(s_libusb_context, &list); + ssize_t cnt = libusb_get_device_list(s_libusb_context.get(), &list); for (int i = 0; i < MAX_SI_CHANNELS; i++) { @@ -335,16 +335,11 @@ void Shutdown() StopScanThread(); #if defined(LIBUSB_API_VERSION) && LIBUSB_API_VERSION >= 0x01000102 if (s_libusb_hotplug_enabled) - libusb_hotplug_deregister_callback(s_libusb_context, s_hotplug_handle); + libusb_hotplug_deregister_callback(s_libusb_context.get(), s_hotplug_handle); #endif Reset(); - if (s_libusb_context) - { - libusb_exit(s_libusb_context); - s_libusb_context = nullptr; - } - + s_libusb_context.reset(); s_libusb_driver_not_supported = false; } diff --git a/Source/Core/UICommon/CMakeLists.txt b/Source/Core/UICommon/CMakeLists.txt index 65630b6b8e..6bfc4be9bd 100644 --- a/Source/Core/UICommon/CMakeLists.txt +++ b/Source/Core/UICommon/CMakeLists.txt @@ -1,6 +1,10 @@ set(SRCS Disassembler.cpp - UICommon.cpp) + UICommon.cpp + USBUtils.cpp) set(LIBS common) +if(LIBUSB_FOUND) + set(LIBS ${LIBS} ${LIBUSB_LIBRARIES}) +endif() add_dolphin_library(uicommon "${SRCS}" "${LIBS}") diff --git a/Source/Core/UICommon/UICommon.cpp b/Source/Core/UICommon/UICommon.cpp index ece3d2d2ee..8de73bd151 100644 --- a/Source/Core/UICommon/UICommon.cpp +++ b/Source/Core/UICommon/UICommon.cpp @@ -17,6 +17,7 @@ #include "InputCommon/GCAdapter.h" #include "UICommon/UICommon.h" +#include "UICommon/USBUtils.h" #include "VideoCommon/VideoBackendBase.h" @@ -29,6 +30,7 @@ void Init() VideoBackendBase::PopulateList(); WiimoteReal::LoadSettings(); GCAdapter::Init(); + USBUtils::Init(); VideoBackendBase::ActivateBackend(SConfig::GetInstance().m_strVideoBackend); SetEnableAlert(SConfig::GetInstance().bUsePanicHandlers); @@ -40,6 +42,7 @@ void Shutdown() WiimoteReal::Shutdown(); VideoBackendBase::ClearList(); SConfig::Shutdown(); + USBUtils::Shutdown(); LogManager::Shutdown(); } diff --git a/Source/Core/UICommon/UICommon.vcxproj b/Source/Core/UICommon/UICommon.vcxproj index 24f4a1cfc5..567ba5f6ff 100644 --- a/Source/Core/UICommon/UICommon.vcxproj +++ b/Source/Core/UICommon/UICommon.vcxproj @@ -45,10 +45,14 @@ + + 4200;%(DisableSpecificWarnings) + + diff --git a/Source/Core/UICommon/USBUtils.cpp b/Source/Core/UICommon/USBUtils.cpp new file mode 100644 index 0000000000..da7c603580 --- /dev/null +++ b/Source/Core/UICommon/USBUtils.cpp @@ -0,0 +1,82 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#ifdef __LIBUSB__ +#include +#include "Common/LibusbContext.h" +#endif + +#include "Common/CommonTypes.h" +#include "Common/StringUtil.h" +#include "UICommon/USBUtils.h" + +#ifdef __LIBUSB__ +static std::shared_ptr s_libusb_context; +#endif + +// Because opening and getting the device name from devices is slow, especially on Windows +// with usbdk, we cannot do that for every single device. We should however still show +// device names for known Wii peripherals. +static const std::map, std::string> s_wii_peripherals = {{ + {{0x046d, 0x0a03}, "Logitech Microphone"}, + {{0x057e, 0x0308}, "Wii Speak"}, + {{0x057e, 0x0309}, "Nintendo USB Microphone"}, + {{0x057e, 0x030a}, "Ubisoft Motion Tracking Camera"}, + {{0x0e6f, 0x0129}, "Disney Infinity Reader (Portal Device)"}, + {{0x1430, 0x0100}, "Tony Hawk Ride Skateboard"}, + {{0x1430, 0x0150}, "Skylanders Portal"}, + {{0x1bad, 0x0004}, "Harmonix Guitar Controller"}, + {{0x1bad, 0x3110}, "Rock Band 3 Mustang Guitar Dongle"}, + {{0x1bad, 0x3430}, "Rock Band Drum Set"}, + {{0x21a4, 0xac40}, "EA Active NFL"}, +}}; + +namespace USBUtils +{ +void Init() +{ +#ifdef __LIBUSB__ + s_libusb_context = LibusbContext::Get(); +#endif +} + +void Shutdown() +{ +#ifdef __LIBUSB__ + s_libusb_context = nullptr; +#endif +} + +std::map, std::string> GetInsertedDevices() +{ + std::map, std::string> devices; + +#ifdef __LIBUSB__ + if (!s_libusb_context) + return devices; + + libusb_device** list; + const ssize_t cnt = libusb_get_device_list(s_libusb_context.get(), &list); + for (ssize_t i = 0; i < cnt; ++i) + { + libusb_device_descriptor descr; + libusb_get_device_descriptor(list[i], &descr); + const std::pair vid_pid{descr.idVendor, descr.idProduct}; + devices[vid_pid] = GetDeviceName(vid_pid); + } + libusb_free_device_list(list, 1); +#endif + + return devices; +} + +std::string GetDeviceName(const std::pair vid_pid) +{ + const auto iter = s_wii_peripherals.find(vid_pid); + const std::string device_name = iter == s_wii_peripherals.cend() ? "Unknown" : iter->second; + return StringFromFormat("%04x:%04x - %s", vid_pid.first, vid_pid.second, device_name.c_str()); +} +} // namespace USBUtils diff --git a/Source/Core/UICommon/USBUtils.h b/Source/Core/UICommon/USBUtils.h new file mode 100644 index 0000000000..6ae778e8a4 --- /dev/null +++ b/Source/Core/UICommon/USBUtils.h @@ -0,0 +1,20 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "Common/CommonTypes.h" + +namespace USBUtils +{ +void Init(); +void Shutdown(); + +std::map, std::string> GetInsertedDevices(); +std::string GetDeviceName(std::pair vid_pid); +} // namespace USBUtils diff --git a/Source/VSProps/Base.props b/Source/VSProps/Base.props index 0dbbdd59ff..768d03214b 100644 --- a/Source/VSProps/Base.props +++ b/Source/VSProps/Base.props @@ -51,7 +51,7 @@ $(ExternalsDir)xxhash;%(AdditionalIncludeDirectories) $(ExternalsDir)zlib;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_WARNINGS;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) - USE_UPNP;__LIBUSB__;%(PreprocessorDefinitions) + USE_UPNP;USE_USBDK;__LIBUSB__;%(PreprocessorDefinitions) PSAPI_VERSION=1;_M_X86=1;%(PreprocessorDefinitions) SFML_STATIC;%(PreprocessorDefinitions) CURL_STATICLIB;%(PreprocessorDefinitions)