diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index c368cde1d6..e72617ed87 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -154,6 +154,7 @@ add_library(common VariantUtil.h Version.cpp Version.h + WaitableFlag.h WindowSystemInfo.h WorkQueueThread.h ) diff --git a/Source/Core/Common/Event.h b/Source/Core/Common/Event.h index 6de3299287..67dbd1ed3a 100644 --- a/Source/Core/Common/Event.h +++ b/Source/Core/Common/Event.h @@ -11,10 +11,6 @@ #pragma once -#ifdef _WIN32 -#include -#endif - #include #include #include diff --git a/Source/Core/Common/WaitableFlag.h b/Source/Core/Common/WaitableFlag.h new file mode 100644 index 0000000000..09ab12a3bc --- /dev/null +++ b/Source/Core/Common/WaitableFlag.h @@ -0,0 +1,69 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// Wrapper around Flag that lets callers wait for the flag to change. + +#pragma once + +#include +#include +#include + +#include "Common/Flag.h" + +namespace Common +{ +class WaitableFlag final +{ +public: + void Set(bool value = true) + { + if (m_flag.TestAndSet(value)) + { + // Lock and immediately unlock m_mutex. + { + // Holding the lock at any time between the change of our flag and notify call + // is sufficient to prevent a race where both of these actions + // happen between the other thread's predicate test and wait call + // which would cause wait to block until the next spurious wakeup or timeout. + + // Unlocking before notification is a micro-optimization to prevent + // the notified thread from immediately blocking on the mutex. + std::lock_guard lk(m_mutex); + } + + m_condvar.notify_all(); + } + } + + void Reset() { Set(false); } + + void Wait(bool expected_value) + { + if (m_flag.IsSet() == expected_value) + return; + + std::unique_lock lk(m_mutex); + m_condvar.wait(lk, [&] { return m_flag.IsSet() == expected_value; }); + } + + template + bool WaitFor(bool expected_value, const std::chrono::duration& rel_time) + { + if (m_flag.IsSet() == expected_value) + return true; + + std::unique_lock lk(m_mutex); + bool signaled = + m_condvar.wait_for(lk, rel_time, [&] { return m_flag.IsSet() == expected_value; }); + + return signaled; + } + +private: + Flag m_flag; + std::condition_variable m_condvar; + std::mutex m_mutex; +}; + +} // namespace Common diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index 095b1d7a93..6036777bbb 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -449,6 +449,8 @@ add_library(core IOS/USB/USB_KBD.h IOS/USB/USB_VEN/VEN.cpp IOS/USB/USB_VEN/VEN.h + IOS/USB/USBScanner.cpp + IOS/USB/USBScanner.h IOS/USB/USBV0.cpp IOS/USB/USBV0.h IOS/USB/USBV4.cpp diff --git a/Source/Core/Core/IOS/USB/Common.cpp b/Source/Core/Core/IOS/USB/Common.cpp index 1eebbeb1c0..3bf56f4b77 100644 --- a/Source/Core/Core/IOS/USB/Common.cpp +++ b/Source/Core/Core/IOS/USB/Common.cpp @@ -15,6 +15,11 @@ namespace IOS::HLE::USB { +EmulationKernel& TransferCommand::GetEmulationKernel() const +{ + return m_ios; +} + std::unique_ptr TransferCommand::MakeBuffer(const size_t size) const { ASSERT_MSG(IOS_USB, data_address != 0, "Invalid data_address"); diff --git a/Source/Core/Core/IOS/USB/Common.h b/Source/Core/Core/IOS/USB/Common.h index 1c5fd07670..fbd1de202d 100644 --- a/Source/Core/Core/IOS/USB/Common.h +++ b/Source/Core/Core/IOS/USB/Common.h @@ -106,6 +106,8 @@ struct TransferCommand { } virtual ~TransferCommand() = default; + + EmulationKernel& GetEmulationKernel() const; // Called after a transfer has completed to reply to the IPC request. // This can be overridden for additional processing before replying. virtual void OnTransferComplete(s32 return_value) const; diff --git a/Source/Core/Core/IOS/USB/Emulated/Infinity.cpp b/Source/Core/Core/IOS/USB/Emulated/Infinity.cpp index 666fe56b85..fcfa203742 100644 --- a/Source/Core/Core/IOS/USB/Emulated/Infinity.cpp +++ b/Source/Core/Core/IOS/USB/Emulated/Infinity.cpp @@ -137,7 +137,7 @@ static constexpr std::array SHA1_CONSTANT = { static constexpr std::array BLANK_BLOCK = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; -InfinityUSB::InfinityUSB(EmulationKernel& ios) : m_ios(ios) +InfinityUSB::InfinityUSB() { m_vid = 0x0E6F; m_pid = 0x0129; @@ -241,7 +241,7 @@ int InfinityUSB::SubmitTransfer(std::unique_ptr cmd) DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Interrupt: length={:04x} endpoint={:02x}", m_vid, m_pid, m_active_interface, cmd->length, cmd->endpoint); - auto& system = m_ios.GetSystem(); + auto& system = cmd->GetEmulationKernel().GetSystem(); auto& memory = system.GetMemory(); auto& infinity_base = system.GetInfinityBase(); u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length); diff --git a/Source/Core/Core/IOS/USB/Emulated/Infinity.h b/Source/Core/Core/IOS/USB/Emulated/Infinity.h index 532db45fbb..8bafefd64b 100644 --- a/Source/Core/Core/IOS/USB/Emulated/Infinity.h +++ b/Source/Core/Core/IOS/USB/Emulated/Infinity.h @@ -32,7 +32,7 @@ struct InfinityFigure final class InfinityUSB final : public Device { public: - InfinityUSB(EmulationKernel& ios); + InfinityUSB(); ~InfinityUSB() override; DeviceDescriptor GetDeviceDescriptor() const override; std::vector GetConfigurations() const override; @@ -53,7 +53,6 @@ private: void ScheduleTransfer(std::unique_ptr command, const std::array& data, u64 expected_time_us); - EmulationKernel& m_ios; u16 m_vid = 0; u16 m_pid = 0; u8 m_active_interface = 0; diff --git a/Source/Core/Core/IOS/USB/Emulated/Skylanders/Skylander.cpp b/Source/Core/Core/IOS/USB/Emulated/Skylanders/Skylander.cpp index b526965d9b..02b16194d5 100644 --- a/Source/Core/Core/IOS/USB/Emulated/Skylanders/Skylander.cpp +++ b/Source/Core/Core/IOS/USB/Emulated/Skylanders/Skylander.cpp @@ -612,7 +612,7 @@ const std::map, SkyData> list_skylanders = {{3503, 0x4000}, {"Kaos Trophy", Game::Superchargers, Element::Other, Type::Trophy}}, }; -SkylanderUSB::SkylanderUSB(EmulationKernel& ios) : m_ios(ios) +SkylanderUSB::SkylanderUSB() { m_vid = 0x1430; m_pid = 0x0150; @@ -741,7 +741,7 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) else { // Skylander Portal Requests - auto& system = m_ios.GetSystem(); + auto& system = cmd->GetEmulationKernel().GetSystem(); auto& memory = system.GetMemory(); u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length); if (cmd->length == 0 || buf == nullptr) @@ -1028,7 +1028,7 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr cmd) DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Interrupt: length={} endpoint={}", m_vid, m_pid, m_active_interface, cmd->length, cmd->endpoint); - auto& system = m_ios.GetSystem(); + auto& system = cmd->GetEmulationKernel().GetSystem(); auto& memory = system.GetMemory(); u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length); if (cmd->length == 0 || buf == nullptr) diff --git a/Source/Core/Core/IOS/USB/Emulated/Skylanders/Skylander.h b/Source/Core/Core/IOS/USB/Emulated/Skylanders/Skylander.h index 7d3da99209..f53b04484f 100644 --- a/Source/Core/Core/IOS/USB/Emulated/Skylanders/Skylander.h +++ b/Source/Core/Core/IOS/USB/Emulated/Skylanders/Skylander.h @@ -72,7 +72,7 @@ extern const std::map, SkyData> list_skyla class SkylanderUSB final : public Device { public: - SkylanderUSB(EmulationKernel& ios); + SkylanderUSB(); ~SkylanderUSB(); DeviceDescriptor GetDeviceDescriptor() const override; std::vector GetConfigurations() const override; @@ -92,7 +92,6 @@ public: s32 expected_count, u64 expected_time_us); private: - EmulationKernel& m_ios; u16 m_vid = 0; u16 m_pid = 0; u8 m_active_interface = 0; diff --git a/Source/Core/Core/IOS/USB/Host.cpp b/Source/Core/Core/IOS/USB/Host.cpp index c8d55a0bf5..e85c3a52f6 100644 --- a/Source/Core/Core/IOS/USB/Host.cpp +++ b/Source/Core/Core/IOS/USB/Host.cpp @@ -3,29 +3,20 @@ #include "Core/IOS/USB/Host.h" -#include +#include +#include #include #include -#include +#include #include #include -#ifdef __LIBUSB__ -#include -#endif - -#include "Common/Assert.h" #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" -#include "Common/Thread.h" -#include "Core/Config/MainSettings.h" #include "Core/Core.h" #include "Core/IOS/USB/Common.h" -#include "Core/IOS/USB/Emulated/Infinity.h" -#include "Core/IOS/USB/Emulated/Skylanders/Skylander.h" -#include "Core/IOS/USB/LibusbDevice.h" -#include "Core/NetPlayProto.h" +#include "Core/IOS/USB/USBScanner.h" #include "Core/System.h" namespace IOS::HLE @@ -35,29 +26,25 @@ USBHost::USBHost(EmulationKernel& ios, const std::string& device_name) { } -USBHost::~USBHost() = default; +USBHost::~USBHost() +{ + GetSystem().GetUSBScanner().RemoveClient(this); +} std::optional USBHost::Open(const OpenRequest& request) { if (!m_has_initialised) { - GetScanThread().Start(); + GetSystem().GetUSBScanner().AddClient(this); // Force a device scan to complete, because some games (including Your Shape) only care // about the initial device list (in the first GETDEVICECHANGE reply). - GetScanThread().WaitForFirstScan(); + GetSystem().GetUSBScanner().WaitForFirstScan(); + OnDevicesChangedInternal(GetSystem().GetUSBScanner().GetDevices()); m_has_initialised = true; } return IPCReply(IPC_SUCCESS); } -void USBHost::UpdateWantDeterminism(const bool new_want_determinism) -{ - if (new_want_determinism) - GetScanThread().Stop(); - else if (IsOpened()) - GetScanThread().Start(); -} - void USBHost::DoState(PointerWrap& p) { Device::DoState(p); @@ -65,20 +52,12 @@ void USBHost::DoState(PointerWrap& p) { // 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); + std::lock_guard lk(m_devices_mutex); + m_devices.clear(); + OnDevicesChanged(GetSystem().GetUSBScanner().GetDevices()); } } -bool USBHost::AddDevice(std::unique_ptr device) -{ - std::lock_guard lk(m_devices_mutex); - if (m_devices.contains(device->GetId())) - 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); @@ -104,156 +83,55 @@ bool USBHost::ShouldAddDevice(const USB::Device& device) const void USBHost::Update() { if (Core::WantsDeterminism()) - UpdateDevices(); + OnDevicesChangedInternal(GetSystem().GetUSBScanner().GetDevices()); } -// 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) +void USBHost::OnDevicesChanged(const USBScanner::DeviceMap& new_devices) { - 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) -{ - AddEmulatedDevices(new_devices, hooks, always_add_hooks); -#ifdef __LIBUSB__ if (!Core::WantsDeterminism()) - { - auto whitelist = Config::GetUSBDeviceWhitelist(); - if (whitelist.empty()) - return true; - - if (m_context.IsValid()) - { - const int ret = m_context.GetDeviceList([&](libusb_device* device) { - libusb_device_descriptor descriptor; - libusb_get_device_descriptor(device, &descriptor); - if (!whitelist.contains({descriptor.idVendor, descriptor.idProduct})) - return true; - - auto usb_device = - std::make_unique(GetEmulationKernel(), device, descriptor); - CheckAndAddDevice(std::move(usb_device), new_devices, hooks, always_add_hooks); - return true; - }); - if (ret != LIBUSB_SUCCESS) - WARN_LOG_FMT(IOS_USB, "GetDeviceList failed: {}", LibusbUtils::ErrorWrap(ret)); - } - } -#endif - return true; + OnDevicesChangedInternal(new_devices); } -void USBHost::DetectRemovedDevices(const std::set& plugged_devices, DeviceChangeHooks& hooks) +void USBHost::OnDevicesChangedInternal(const USBScanner::DeviceMap& new_devices) { std::lock_guard lk(m_devices_mutex); + + bool changes = false; + for (auto it = m_devices.begin(); it != m_devices.end();) { - if (!plugged_devices.contains(it->second->GetId())) + const auto& [id, device] = *it; + if (!new_devices.contains(id)) { - hooks.emplace(it->second, ChangeEvent::Removed); + INFO_LOG_FMT(IOS_USB, "{} - Removed device: {:04x}:{:04x}", GetDeviceName(), device->GetVid(), + device->GetPid()); + + changes = true; + auto device_copy = std::move(device); it = m_devices.erase(it); + OnDeviceChange(ChangeEvent::Removed, std::move(device_copy)); } else { ++it; } } -} -void USBHost::DispatchHooks(const DeviceChangeHooks& hooks) -{ - for (const auto& hook : hooks) + for (const auto& [id, device] : new_devices) { - INFO_LOG_FMT(IOS_USB, "{} - {} device: {:04x}:{:04x}", GetDeviceName(), - hook.second == ChangeEvent::Inserted ? "New" : "Removed", hook.first->GetVid(), - hook.first->GetPid()); - OnDeviceChange(hook.second, hook.first); - } - if (!hooks.empty()) - OnDeviceChangeEnd(); -} - -void USBHost::AddEmulatedDevices(std::set& new_devices, DeviceChangeHooks& hooks, - bool always_add_hooks) -{ - if (Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL) && !NetPlay::IsNetPlayRunning()) - { - auto skylanderportal = std::make_unique(GetEmulationKernel()); - CheckAndAddDevice(std::move(skylanderportal), new_devices, hooks, always_add_hooks); - } - if (Config::Get(Config::MAIN_EMULATE_INFINITY_BASE) && !NetPlay::IsNetPlayRunning()) - { - auto infinity_base = std::make_unique(GetEmulationKernel()); - CheckAndAddDevice(std::move(infinity_base), new_devices, hooks, always_add_hooks); - } -} - -void USBHost::CheckAndAddDevice(std::unique_ptr device, std::set& new_devices, - DeviceChangeHooks& hooks, bool always_add_hooks) -{ - if (ShouldAddDevice(*device)) - { - const u64 deviceid = device->GetId(); - new_devices.insert(deviceid); - if (AddDevice(std::move(device)) || always_add_hooks) + if (!m_devices.contains(id) && ShouldAddDevice(*device)) { - hooks.emplace(GetDeviceById(deviceid), ChangeEvent::Inserted); + INFO_LOG_FMT(IOS_USB, "{} - New device: {:04x}:{:04x}", GetDeviceName(), device->GetVid(), + device->GetPid()); + + changes = true; + m_devices.emplace(id, device); + OnDeviceChange(ChangeEvent::Inserted, device); } } -} -USBHost::ScanThread::~ScanThread() -{ - Stop(); -} - -void USBHost::ScanThread::WaitForFirstScan() -{ - if (m_thread_running.IsSet()) - { - m_first_scan_complete_event.Wait(); - } -} - -void USBHost::ScanThread::Start() -{ - if (Core::WantsDeterminism()) - { - m_host->UpdateDevices(); - return; - } - if (m_thread_running.TestAndSet()) - { - m_thread = std::thread([this] { - Common::SetCurrentThreadName("USB Scan Thread"); - while (m_thread_running.IsSet()) - { - if (m_host->UpdateDevices()) - m_first_scan_complete_event.Set(); - Common::SleepCurrentThread(50); - } - }); - } -} - -void USBHost::ScanThread::Stop() -{ - if (m_thread_running.TestAndClear()) - m_thread.join(); - - // Clear all devices and dispatch removal hooks. - DeviceChangeHooks hooks; - m_host->DetectRemovedDevices(std::set(), hooks); - m_host->DispatchHooks(hooks); + if (changes) + OnDeviceChangeEnd(); } std::optional USBHost::HandleTransfer(std::shared_ptr device, u32 request, @@ -270,4 +148,5 @@ std::optional USBHost::HandleTransfer(std::shared_ptr dev device->GetVid(), device->GetPid(), request, device->GetErrorName(ret)); return IPCReply(ret <= 0 ? ret : IPC_EINVAL); } + } // namespace IOS::HLE diff --git a/Source/Core/Core/IOS/USB/Host.h b/Source/Core/Core/IOS/USB/Host.h index 8342159cb5..6c1c488ee9 100644 --- a/Source/Core/Core/IOS/USB/Host.h +++ b/Source/Core/Core/IOS/USB/Host.h @@ -3,23 +3,18 @@ #pragma once -#include #include #include #include #include -#include +#include #include -#include -#include #include "Common/CommonTypes.h" -#include "Common/Event.h" -#include "Common/Flag.h" #include "Core/IOS/Device.h" #include "Core/IOS/IOS.h" #include "Core/IOS/USB/Common.h" -#include "Core/LibusbUtils.h" +#include "Core/IOS/USB/USBScanner.h" class PointerWrap; @@ -34,9 +29,10 @@ public: std::optional Open(const OpenRequest& request) override; - void UpdateWantDeterminism(bool new_want_determinism) override; void DoState(PointerWrap& p) override; + void OnDevicesChanged(const USBScanner::DeviceMap& new_devices); + protected: enum class ChangeEvent { @@ -45,48 +41,21 @@ protected: }; using DeviceChangeHooks = std::map, ChangeEvent>; - class ScanThread final - { - public: - explicit ScanThread(USBHost* host) : m_host(host) {} - ~ScanThread(); - void Start(); - void Stop(); - void WaitForFirstScan(); - - private: - USBHost* m_host = nullptr; - Common::Flag m_thread_running; - std::thread m_thread; - Common::Event m_first_scan_complete_event; - Common::Flag m_is_initialized; - }; - - 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; - virtual ScanThread& GetScanThread() = 0; std::optional HandleTransfer(std::shared_ptr device, u32 request, std::function submit) const; + std::map> m_devices; + mutable std::recursive_mutex m_devices_mutex; + private: - bool AddDevice(std::unique_ptr device); void Update() override; - 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); - void AddEmulatedDevices(std::set& new_devices, DeviceChangeHooks& hooks, - bool always_add_hooks); - void CheckAndAddDevice(std::unique_ptr device, std::set& new_devices, - DeviceChangeHooks& hooks, bool always_add_hooks); + void OnDevicesChangedInternal(const USBScanner::DeviceMap& new_devices); bool m_has_initialised = false; - LibusbUtils::Context m_context; }; } // namespace IOS::HLE diff --git a/Source/Core/Core/IOS/USB/LibusbDevice.cpp b/Source/Core/Core/IOS/USB/LibusbDevice.cpp index 6770990a9d..d5c38277cc 100644 --- a/Source/Core/Core/IOS/USB/LibusbDevice.cpp +++ b/Source/Core/Core/IOS/USB/LibusbDevice.cpp @@ -25,9 +25,8 @@ namespace IOS::HLE::USB { -LibusbDevice::LibusbDevice(EmulationKernel& ios, libusb_device* device, - const libusb_device_descriptor& descriptor) - : m_ios(ios), m_device(device) +LibusbDevice::LibusbDevice(libusb_device* device, const libusb_device_descriptor& descriptor) + : m_device(device) { libusb_ref_device(m_device); m_vid = descriptor.idVendor; @@ -226,7 +225,7 @@ int LibusbDevice::SubmitTransfer(std::unique_ptr cmd) } const int ret = SetAltSetting(static_cast(cmd->value)); if (ret == LIBUSB_SUCCESS) - m_ios.EnqueueIPCReply(cmd->ios_request, cmd->length); + cmd->GetEmulationKernel().EnqueueIPCReply(cmd->ios_request, cmd->length); return ret; } case USBHDR(DIR_HOST2DEVICE, TYPE_STANDARD, REC_DEVICE, REQUEST_SET_CONFIGURATION): @@ -238,7 +237,7 @@ int LibusbDevice::SubmitTransfer(std::unique_ptr cmd) if (ret == LIBUSB_SUCCESS) { ClaimAllInterfaces(cmd->value); - m_ios.EnqueueIPCReply(cmd->ios_request, cmd->length); + cmd->GetEmulationKernel().EnqueueIPCReply(cmd->ios_request, cmd->length); } return ret; } @@ -249,7 +248,7 @@ int LibusbDevice::SubmitTransfer(std::unique_ptr cmd) libusb_fill_control_setup(buffer.get(), cmd->request_type, cmd->request, cmd->value, cmd->index, cmd->length); - auto& system = m_ios.GetSystem(); + auto& system = cmd->GetEmulationKernel().GetSystem(); auto& memory = system.GetMemory(); memory.CopyFromEmu(buffer.get() + LIBUSB_CONTROL_SETUP_SIZE, cmd->data_address, cmd->length); diff --git a/Source/Core/Core/IOS/USB/LibusbDevice.h b/Source/Core/Core/IOS/USB/LibusbDevice.h index 11d940e75b..707fd0cee7 100644 --- a/Source/Core/Core/IOS/USB/LibusbDevice.h +++ b/Source/Core/Core/IOS/USB/LibusbDevice.h @@ -26,8 +26,7 @@ namespace IOS::HLE::USB class LibusbDevice final : public Device { public: - LibusbDevice(EmulationKernel& ios, libusb_device* device, - const libusb_device_descriptor& device_descriptor); + LibusbDevice(libusb_device* device, const libusb_device_descriptor& device_descriptor); ~LibusbDevice(); DeviceDescriptor GetDeviceDescriptor() const override; std::vector GetConfigurations() const override; @@ -46,8 +45,6 @@ public: int SubmitTransfer(std::unique_ptr message) override; private: - EmulationKernel& m_ios; - std::vector m_config_descriptors; u16 m_vid = 0; u16 m_pid = 0; diff --git a/Source/Core/Core/IOS/USB/OH0/OH0.cpp b/Source/Core/Core/IOS/USB/OH0/OH0.cpp index a2be34727b..dad3c182c9 100644 --- a/Source/Core/Core/IOS/USB/OH0/OH0.cpp +++ b/Source/Core/Core/IOS/USB/OH0/OH0.cpp @@ -26,10 +26,7 @@ OH0::OH0(EmulationKernel& ios, const std::string& device_name) : USBHost(ios, de { } -OH0::~OH0() -{ - m_scan_thread.Stop(); -} +OH0::~OH0() = default; std::optional OH0::Open(const OpenRequest& request) { @@ -76,11 +73,14 @@ std::optional OH0::IOCtlV(const IOCtlVRequest& request) void OH0::DoState(PointerWrap& p) { - if (p.IsReadMode() && !m_devices.empty()) { - 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); + std::lock_guard lk(m_devices_mutex); + if (p.IsReadMode() && !m_devices.empty()) + { + 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); @@ -209,12 +209,12 @@ std::optional OH0::RegisterInsertionHookWithID(const IOCtlVRequest& re auto& system = GetSystem(); auto& memory = system.GetMemory(); - 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 IPCReply(IPC_SUCCESS); + std::lock_guard lock{m_hooks_mutex}; // 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. @@ -234,6 +234,7 @@ std::optional OH0::RegisterClassChangeHook(const IOCtlVRequest& reques bool OH0::HasDeviceWithVidPid(const u16 vid, const u16 pid) const { + std::lock_guard lk(m_devices_mutex); return std::ranges::any_of(m_devices, [=](const auto& device) { return device.second->GetVid() == vid && device.second->GetPid() == pid; }); @@ -241,7 +242,6 @@ bool OH0::HasDeviceWithVidPid(const u16 vid, const u16 pid) const 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) diff --git a/Source/Core/Core/IOS/USB/OH0/OH0.h b/Source/Core/Core/IOS/USB/OH0/OH0.h index daf0772397..19f501a1fc 100644 --- a/Source/Core/Core/IOS/USB/OH0/OH0.h +++ b/Source/Core/Core/IOS/USB/OH0/OH0.h @@ -64,8 +64,6 @@ private: template void TriggerHook(std::map& hooks, T value, ReturnCode return_value); - ScanThread& GetScanThread() override { return m_scan_thread; } - struct DeviceEntry { u32 unknown; @@ -79,7 +77,5 @@ private: std::map m_removal_hooks; std::set m_opened_devices; std::mutex m_hooks_mutex; - - ScanThread m_scan_thread{this}; }; } // namespace IOS::HLE diff --git a/Source/Core/Core/IOS/USB/USBScanner.cpp b/Source/Core/Core/IOS/USB/USBScanner.cpp new file mode 100644 index 0000000000..8a8ef2da0b --- /dev/null +++ b/Source/Core/Core/IOS/USB/USBScanner.cpp @@ -0,0 +1,187 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/IOS/USB/USBScanner.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __LIBUSB__ +#include +#endif + +#include "Common/CommonTypes.h" +#include "Common/Logging/Log.h" +#include "Common/Thread.h" +#include "Core/Config/MainSettings.h" +#include "Core/Core.h" +#include "Core/IOS/USB/Common.h" +#include "Core/IOS/USB/Emulated/Infinity.h" +#include "Core/IOS/USB/Emulated/Skylanders/Skylander.h" +#include "Core/IOS/USB/Host.h" +#include "Core/IOS/USB/LibusbDevice.h" +#include "Core/NetPlayProto.h" +#include "Core/System.h" + +namespace IOS::HLE +{ +USBScanner::~USBScanner() +{ + StopScanning(); +} + +void USBScanner::WaitForFirstScan() +{ + if (m_thread_running.IsSet()) + { + m_first_scan_complete_flag.Wait(true); + } +} + +bool USBScanner::AddClient(USBHost* client) +{ + bool should_start_scanning; + bool client_is_new; + + std::lock_guard thread_lock(m_thread_start_stop_mutex); + + { + std::lock_guard clients_lock(m_clients_mutex); + should_start_scanning = m_clients.empty(); + auto [it, newly_added] = m_clients.insert(client); + client_is_new = newly_added; + } + + if (should_start_scanning) + StartScanning(); + + return client_is_new; +} + +bool USBScanner::RemoveClient(USBHost* client) +{ + std::lock_guard thread_lock(m_thread_start_stop_mutex); + + bool client_was_removed; + bool should_stop_scanning; + + { + std::lock_guard clients_lock(m_clients_mutex); + const bool was_empty = m_clients.empty(); + client_was_removed = m_clients.erase(client) != 0; + should_stop_scanning = !was_empty && m_clients.empty(); + } + + // The scan thread might be trying to lock m_clients_mutex, so we need to not hold a lock on + // m_clients_mutex when trying to join with the scan thread. + if (should_stop_scanning) + StopScanning(); + + return client_was_removed; +} + +void USBScanner::StartScanning() +{ + if (m_thread_running.TestAndSet()) + { + m_thread = std::thread([this] { + Common::SetCurrentThreadName("USB Scan Thread"); + while (m_thread_running.IsSet()) + { + if (UpdateDevices()) + m_first_scan_complete_flag.Set(true); + Common::SleepCurrentThread(50); + } + m_devices.clear(); + m_first_scan_complete_flag.Set(false); + }); + } +} + +void USBScanner::StopScanning() +{ + if (m_thread_running.TestAndClear()) + m_thread.join(); +} + +USBScanner::DeviceMap USBScanner::GetDevices() const +{ + std::lock_guard lk(m_devices_mutex); + return m_devices; +} + +// This is called from the scan thread. Returns false if we failed to update the device list. +bool USBScanner::UpdateDevices() +{ + DeviceMap new_devices; + if (!AddNewDevices(&new_devices)) + return false; + + std::lock_guard lk(m_devices_mutex); + if (!std::ranges::equal(std::views::keys(m_devices), std::views::keys(new_devices))) + { + m_devices = std::move(new_devices); + + std::lock_guard clients_lock(m_clients_mutex); + for (USBHost* client : m_clients) + client->OnDevicesChanged(m_devices); + } + + return true; +} + +bool USBScanner::AddNewDevices(DeviceMap* new_devices) const +{ + AddEmulatedDevices(new_devices); +#ifdef __LIBUSB__ + if (!Core::WantsDeterminism()) + { + auto whitelist = Config::GetUSBDeviceWhitelist(); + if (whitelist.empty()) + return true; + + if (m_context.IsValid()) + { + const int ret = m_context.GetDeviceList([&](libusb_device* device) { + libusb_device_descriptor descriptor; + libusb_get_device_descriptor(device, &descriptor); + if (!whitelist.contains({descriptor.idVendor, descriptor.idProduct})) + return true; + + auto usb_device = std::make_unique(device, descriptor); + AddDevice(std::move(usb_device), new_devices); + return true; + }); + if (ret != LIBUSB_SUCCESS) + WARN_LOG_FMT(IOS_USB, "GetDeviceList failed: {}", LibusbUtils::ErrorWrap(ret)); + } + } +#endif + return true; +} + +void USBScanner::AddEmulatedDevices(DeviceMap* new_devices) +{ + if (Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL) && !NetPlay::IsNetPlayRunning()) + { + auto skylanderportal = std::make_unique(); + AddDevice(std::move(skylanderportal), new_devices); + } + if (Config::Get(Config::MAIN_EMULATE_INFINITY_BASE) && !NetPlay::IsNetPlayRunning()) + { + auto infinity_base = std::make_unique(); + AddDevice(std::move(infinity_base), new_devices); + } +} + +void USBScanner::AddDevice(std::unique_ptr device, DeviceMap* new_devices) +{ + (*new_devices)[device->GetId()] = std::move(device); +} + +} // namespace IOS::HLE diff --git a/Source/Core/Core/IOS/USB/USBScanner.h b/Source/Core/Core/IOS/USB/USBScanner.h new file mode 100644 index 0000000000..60445cb67f --- /dev/null +++ b/Source/Core/Core/IOS/USB/USBScanner.h @@ -0,0 +1,61 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Common/Flag.h" +#include "Common/WaitableFlag.h" +#include "Core/IOS/USB/Common.h" +#include "Core/LibusbUtils.h" + +class PointerWrap; + +namespace IOS::HLE +{ +class USBHost; + +class USBScanner final +{ +public: + using DeviceMap = std::map>; + + ~USBScanner(); + + bool AddClient(USBHost* client); + bool RemoveClient(USBHost* client); + + void WaitForFirstScan(); + + DeviceMap GetDevices() const; + +private: + void StartScanning(); + void StopScanning(); + + bool UpdateDevices(); + bool AddNewDevices(DeviceMap* new_devices) const; + static void AddEmulatedDevices(DeviceMap* new_devices); + static void AddDevice(std::unique_ptr device, DeviceMap* new_devices); + + DeviceMap m_devices; + mutable std::mutex m_devices_mutex; + + std::set m_clients; + std::mutex m_clients_mutex; + + Common::Flag m_thread_running; + std::thread m_thread; + std::mutex m_thread_start_stop_mutex; + Common::WaitableFlag m_first_scan_complete_flag; + + LibusbUtils::Context m_context; +}; + +} // namespace IOS::HLE diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp b/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp index 0b464b13d6..f20928eba0 100644 --- a/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp @@ -27,10 +27,7 @@ USB_HIDv4::USB_HIDv4(EmulationKernel& ios, const std::string& device_name) { } -USB_HIDv4::~USB_HIDv4() -{ - m_scan_thread.Stop(); -} +USB_HIDv4::~USB_HIDv4() = default; std::optional USB_HIDv4::IOCtl(const IOCtlRequest& request) { diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv4.h b/Source/Core/Core/IOS/USB/USB_HID/HIDv4.h index 33fc31f461..4bc168a38c 100644 --- a/Source/Core/Core/IOS/USB/USB_HID/HIDv4.h +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv4.h @@ -39,7 +39,6 @@ private: std::vector GetDeviceEntry(const USB::Device& device) const; void OnDeviceChange(ChangeEvent, std::shared_ptr) override; bool ShouldAddDevice(const USB::Device& device) const override; - ScanThread& GetScanThread() override { return m_scan_thread; } static constexpr u32 VERSION = 0x40001; static constexpr u8 HID_CLASS = 0x03; @@ -53,7 +52,5 @@ private: // IOS device IDs <=> USB device IDs std::map m_ios_ids; std::map m_device_ids; - - ScanThread m_scan_thread{this}; }; } // namespace IOS::HLE diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp index f126ff19a5..e193c498f5 100644 --- a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp @@ -18,10 +18,7 @@ namespace IOS::HLE { constexpr u32 USBV5_VERSION = 0x50001; -USB_HIDv5::~USB_HIDv5() -{ - m_scan_thread.Stop(); -} +USB_HIDv5::~USB_HIDv5() = default; std::optional USB_HIDv5::IOCtl(const IOCtlRequest& request) { diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.h b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.h index a0a1876e3f..ca1d0843c7 100644 --- a/Source/Core/Core/IOS/USB/USB_HID/HIDv5.h +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv5.h @@ -27,15 +27,11 @@ private: bool ShouldAddDevice(const USB::Device& device) const override; bool HasInterfaceNumberInIDs() const override { return true; } - ScanThread& GetScanThread() override { return m_scan_thread; } - struct AdditionalDeviceData { u8 interrupt_in_endpoint = 0; u8 interrupt_out_endpoint = 0; }; std::array m_additional_device_data{}; - - ScanThread m_scan_thread{this}; }; } // namespace IOS::HLE diff --git a/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp b/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp index a0dd4393d8..d8d624e898 100644 --- a/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp +++ b/Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp @@ -18,10 +18,7 @@ namespace IOS::HLE { constexpr u32 USBV5_VERSION = 0x50001; -USB_VEN::~USB_VEN() -{ - m_scan_thread.Stop(); -} +USB_VEN::~USB_VEN() = default; std::optional USB_VEN::IOCtl(const IOCtlRequest& request) { diff --git a/Source/Core/Core/IOS/USB/USB_VEN/VEN.h b/Source/Core/Core/IOS/USB/USB_VEN/VEN.h index 9be70cab6b..981ffa114d 100644 --- a/Source/Core/Core/IOS/USB/USB_VEN/VEN.h +++ b/Source/Core/Core/IOS/USB/USB_VEN/VEN.h @@ -25,9 +25,5 @@ private: s32 SubmitTransfer(USB::Device& device, const IOCtlVRequest& ioctlv); bool HasInterfaceNumberInIDs() const override { return false; } - - ScanThread& GetScanThread() override { return m_scan_thread; } - - ScanThread m_scan_thread{this}; }; } // namespace IOS::HLE diff --git a/Source/Core/Core/System.cpp b/Source/Core/Core/System.cpp index 695d5861fc..d52975fe1b 100644 --- a/Source/Core/Core/System.cpp +++ b/Source/Core/Core/System.cpp @@ -32,6 +32,7 @@ #include "Core/PowerPC/PowerPC.h" #include "IOS/USB/Emulated/Infinity.h" #include "IOS/USB/Emulated/Skylanders/Skylander.h" +#include "IOS/USB/USBScanner.h" #include "VideoCommon/Assets/CustomAssetLoader.h" #include "VideoCommon/CommandProcessor.h" #include "VideoCommon/Fifo.h" @@ -89,6 +90,7 @@ struct System::Impl SerialInterface::SerialInterfaceManager m_serial_interface; Sram m_sram; SystemTimers::SystemTimersManager m_system_timers; + IOS::HLE::USBScanner m_usb_scanner; VertexShaderManager m_vertex_shader_manager; XFStateManager m_xf_state_manager; VideoInterface::VideoInterfaceManager m_video_interface; @@ -313,6 +315,11 @@ SystemTimers::SystemTimersManager& System::GetSystemTimers() const return m_impl->m_system_timers; } +IOS::HLE::USBScanner& System::GetUSBScanner() const +{ + return m_impl->m_usb_scanner; +} + VertexShaderManager& System::GetVertexShaderManager() const { return m_impl->m_vertex_shader_manager; diff --git a/Source/Core/Core/System.h b/Source/Core/Core/System.h index 0ab86e658d..67069c5e22 100644 --- a/Source/Core/Core/System.h +++ b/Source/Core/Core/System.h @@ -56,7 +56,8 @@ class GPFifoManager; namespace IOS::HLE { class EmulationKernel; -} +class USBScanner; +} // namespace IOS::HLE namespace HSP { class HSPManager; @@ -192,6 +193,7 @@ public: SerialInterface::SerialInterfaceManager& GetSerialInterface() const; Sram& GetSRAM() const; SystemTimers::SystemTimersManager& GetSystemTimers() const; + IOS::HLE::USBScanner& GetUSBScanner() const; VertexShaderManager& GetVertexShaderManager() const; XFStateManager& GetXFStateManager() const; VideoInterface::VideoInterfaceManager& GetVideoInterface() const; diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 4c7764202d..f5b5d4f673 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -171,6 +171,7 @@ + @@ -413,6 +414,7 @@ + @@ -1077,6 +1079,7 @@ +