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/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 e66b08c646..e85c3a52f6 100644 --- a/Source/Core/Core/IOS/USB/Host.cpp +++ b/Source/Core/Core/IOS/USB/Host.cpp @@ -17,6 +17,7 @@ #include "Core/Core.h" #include "Core/IOS/USB/Common.h" #include "Core/IOS/USB/USBScanner.h" +#include "Core/System.h" namespace IOS::HLE { @@ -27,18 +28,18 @@ USBHost::USBHost(EmulationKernel& ios, const std::string& device_name) USBHost::~USBHost() { - m_usb_scanner.Stop(); + GetSystem().GetUSBScanner().RemoveClient(this); } std::optional USBHost::Open(const OpenRequest& request) { if (!m_has_initialised) { - m_usb_scanner.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). - m_usb_scanner.WaitForFirstScan(); - OnDevicesChangedInternal(m_usb_scanner.GetDevices()); + GetSystem().GetUSBScanner().WaitForFirstScan(); + OnDevicesChangedInternal(GetSystem().GetUSBScanner().GetDevices()); m_has_initialised = true; } return IPCReply(IPC_SUCCESS); @@ -53,7 +54,7 @@ void USBHost::DoState(PointerWrap& p) // already plugged in, and which need to be triggered. std::lock_guard lk(m_devices_mutex); m_devices.clear(); - OnDevicesChanged(m_usb_scanner.GetDevices()); + OnDevicesChanged(GetSystem().GetUSBScanner().GetDevices()); } } @@ -82,7 +83,7 @@ bool USBHost::ShouldAddDevice(const USB::Device& device) const void USBHost::Update() { if (Core::WantsDeterminism()) - OnDevicesChangedInternal(m_usb_scanner.GetDevices()); + OnDevicesChangedInternal(GetSystem().GetUSBScanner().GetDevices()); } void USBHost::OnDevicesChanged(const USBScanner::DeviceMap& new_devices) @@ -118,7 +119,7 @@ void USBHost::OnDevicesChangedInternal(const USBScanner::DeviceMap& new_devices) for (const auto& [id, device] : new_devices) { - if (!m_devices.contains(id)) + if (!m_devices.contains(id) && ShouldAddDevice(*device)) { INFO_LOG_FMT(IOS_USB, "{} - New device: {:04x}:{:04x}", GetDeviceName(), device->GetVid(), device->GetPid()); diff --git a/Source/Core/Core/IOS/USB/Host.h b/Source/Core/Core/IOS/USB/Host.h index 92da3e911b..6c1c488ee9 100644 --- a/Source/Core/Core/IOS/USB/Host.h +++ b/Source/Core/Core/IOS/USB/Host.h @@ -31,8 +31,6 @@ public: void DoState(PointerWrap& p) override; - virtual bool ShouldAddDevice(const USB::Device& device) const; - void OnDevicesChanged(const USBScanner::DeviceMap& new_devices); protected: @@ -46,6 +44,7 @@ protected: 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; std::optional HandleTransfer(std::shared_ptr device, u32 request, std::function submit) const; @@ -53,8 +52,6 @@ protected: std::map> m_devices; mutable std::recursive_mutex m_devices_mutex; - USBScanner m_usb_scanner{this}; - private: void Update() override; void OnDevicesChangedInternal(const USBScanner::DeviceMap& new_devices); 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/USBScanner.cpp b/Source/Core/Core/IOS/USB/USBScanner.cpp index be0d1c02df..8a8ef2da0b 100644 --- a/Source/Core/Core/IOS/USB/USBScanner.cpp +++ b/Source/Core/Core/IOS/USB/USBScanner.cpp @@ -30,24 +30,62 @@ namespace IOS::HLE { -USBScanner::USBScanner(USBHost* host) : m_host(host) -{ -} - USBScanner::~USBScanner() { - Stop(); + StopScanning(); } void USBScanner::WaitForFirstScan() { if (m_thread_running.IsSet()) { - m_first_scan_complete_event.Wait(); + m_first_scan_complete_flag.Wait(true); } } -void USBScanner::Start() +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()) { @@ -56,14 +94,16 @@ void USBScanner::Start() while (m_thread_running.IsSet()) { if (UpdateDevices()) - m_first_scan_complete_event.Set(); + m_first_scan_complete_flag.Set(true); Common::SleepCurrentThread(50); } + m_devices.clear(); + m_first_scan_complete_flag.Set(false); }); } } -void USBScanner::Stop() +void USBScanner::StopScanning() { if (m_thread_running.TestAndClear()) m_thread.join(); @@ -86,7 +126,10 @@ bool USBScanner::UpdateDevices() if (!std::ranges::equal(std::views::keys(m_devices), std::views::keys(new_devices))) { m_devices = std::move(new_devices); - m_host->OnDevicesChanged(m_devices); + + std::lock_guard clients_lock(m_clients_mutex); + for (USBHost* client : m_clients) + client->OnDevicesChanged(m_devices); } return true; @@ -110,8 +153,7 @@ bool USBScanner::AddNewDevices(DeviceMap* new_devices) const if (!whitelist.contains({descriptor.idVendor, descriptor.idProduct})) return true; - auto usb_device = - std::make_unique(m_host->GetEmulationKernel(), device, descriptor); + auto usb_device = std::make_unique(device, descriptor); AddDevice(std::move(usb_device), new_devices); return true; }); @@ -123,24 +165,23 @@ bool USBScanner::AddNewDevices(DeviceMap* new_devices) const return true; } -void USBScanner::AddEmulatedDevices(DeviceMap* new_devices) const +void USBScanner::AddEmulatedDevices(DeviceMap* new_devices) { if (Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL) && !NetPlay::IsNetPlayRunning()) { - auto skylanderportal = std::make_unique(m_host->GetEmulationKernel()); + 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(m_host->GetEmulationKernel()); + auto infinity_base = std::make_unique(); AddDevice(std::move(infinity_base), new_devices); } } -void USBScanner::AddDevice(std::unique_ptr device, DeviceMap* new_devices) const +void USBScanner::AddDevice(std::unique_ptr device, DeviceMap* new_devices) { - if (m_host->ShouldAddDevice(*device)) - (*new_devices)[device->GetId()] = std::move(device); + (*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 index 3d80565926..60445cb67f 100644 --- a/Source/Core/Core/IOS/USB/USBScanner.h +++ b/Source/Core/Core/IOS/USB/USBScanner.h @@ -10,8 +10,8 @@ #include #include "Common/CommonTypes.h" -#include "Common/Event.h" #include "Common/Flag.h" +#include "Common/WaitableFlag.h" #include "Core/IOS/USB/Common.h" #include "Core/LibusbUtils.h" @@ -26,28 +26,34 @@ class USBScanner final public: using DeviceMap = std::map>; - explicit USBScanner(USBHost* host); ~USBScanner(); - void Start(); - void Stop(); + 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; - void AddEmulatedDevices(DeviceMap* new_devices) const; - void AddDevice(std::unique_ptr device, 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; - USBHost* m_host = nullptr; + std::set m_clients; + std::mutex m_clients_mutex; + Common::Flag m_thread_running; std::thread m_thread; - Common::Event m_first_scan_complete_event; + std::mutex m_thread_start_stop_mutex; + Common::WaitableFlag m_first_scan_complete_flag; LibusbUtils::Context m_context; }; 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 0f88b7620c..f5b5d4f673 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -171,6 +171,7 @@ +