From 427e9c5ad2e284af054701553104f655bbc95361 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 6 Apr 2025 11:53:25 +0200 Subject: [PATCH 1/5] IOS: Move USB scanning thread to new class USBScanner --- Source/Core/Core/CMakeLists.txt | 2 + Source/Core/Core/IOS/USB/Host.cpp | 189 ++------------------ Source/Core/Core/IOS/USB/Host.h | 57 ++---- Source/Core/Core/IOS/USB/OH0/OH0.cpp | 19 +- Source/Core/Core/IOS/USB/OH0/OH0.h | 4 - Source/Core/Core/IOS/USB/USBScanner.cpp | 191 +++++++++++++++++++++ Source/Core/Core/IOS/USB/USBScanner.h | 64 +++++++ Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp | 9 +- Source/Core/Core/IOS/USB/USB_HID/HIDv4.h | 3 - Source/Core/Core/IOS/USB/USB_HID/HIDv5.cpp | 5 +- Source/Core/Core/IOS/USB/USB_HID/HIDv5.h | 4 - Source/Core/Core/IOS/USB/USB_VEN/VEN.cpp | 5 +- Source/Core/Core/IOS/USB/USB_VEN/VEN.h | 4 - Source/Core/DolphinLib.props | 2 + 14 files changed, 297 insertions(+), 261 deletions(-) create mode 100644 Source/Core/Core/IOS/USB/USBScanner.cpp create mode 100644 Source/Core/Core/IOS/USB/USBScanner.h 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/Host.cpp b/Source/Core/Core/IOS/USB/Host.cpp index c8d55a0bf5..cdcf5b5812 100644 --- a/Source/Core/Core/IOS/USB/Host.cpp +++ b/Source/Core/Core/IOS/USB/Host.cpp @@ -3,30 +3,17 @@ #include "Core/IOS/USB/Host.h" -#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/System.h" +#include "Core/IOS/USB/USBScanner.h" namespace IOS::HLE { @@ -35,16 +22,19 @@ USBHost::USBHost(EmulationKernel& ios, const std::string& device_name) { } -USBHost::~USBHost() = default; +USBHost::~USBHost() +{ + m_usb_scanner.Stop(); +} std::optional USBHost::Open(const OpenRequest& request) { if (!m_has_initialised) { - GetScanThread().Start(); + m_usb_scanner.Start(); // 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(); + m_usb_scanner.WaitForFirstScan(); m_has_initialised = true; } return IPCReply(IPC_SUCCESS); @@ -53,9 +43,9 @@ std::optional USBHost::Open(const OpenRequest& request) void USBHost::UpdateWantDeterminism(const bool new_want_determinism) { if (new_want_determinism) - GetScanThread().Stop(); + m_usb_scanner.Stop(); else if (IsOpened()) - GetScanThread().Start(); + m_usb_scanner.Start(); } void USBHost::DoState(PointerWrap& p) @@ -65,27 +55,13 @@ 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); + m_usb_scanner.UpdateDevices(true); } } -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); - const auto it = m_devices.find(device_id); - if (it == m_devices.end()) - return nullptr; - return it->second; + return m_usb_scanner.GetDeviceById(device_id); } void USBHost::OnDeviceChange(ChangeEvent event, std::shared_ptr changed_device) @@ -104,69 +80,7 @@ bool USBHost::ShouldAddDevice(const USB::Device& device) const void USBHost::Update() { if (Core::WantsDeterminism()) - UpdateDevices(); -} - -// 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) -{ - 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; -} - -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.contains(it->second->GetId())) - { - hooks.emplace(it->second, ChangeEvent::Removed); - it = m_devices.erase(it); - } - else - { - ++it; - } - } + m_usb_scanner.UpdateDevices(); } void USBHost::DispatchHooks(const DeviceChangeHooks& hooks) @@ -182,80 +96,6 @@ void USBHost::DispatchHooks(const DeviceChangeHooks& hooks) 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) - { - hooks.emplace(GetDeviceById(deviceid), ChangeEvent::Inserted); - } - } -} - -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); -} - std::optional USBHost::HandleTransfer(std::shared_ptr device, u32 request, std::function submit) const { @@ -270,4 +110,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..196104bc1a 100644 --- a/Source/Core/Core/IOS/USB/Host.h +++ b/Source/Core/Core/IOS/USB/Host.h @@ -3,23 +3,16 @@ #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; @@ -37,56 +30,26 @@ public: void UpdateWantDeterminism(bool new_want_determinism) override; void DoState(PointerWrap& p) override; + virtual bool ShouldAddDevice(const USB::Device& device) const; + + void DispatchHooks(const USBScanner::DeviceChangeHooks& hooks); + protected: - enum class ChangeEvent - { - Inserted, - Removed, - }; - 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; + using ChangeEvent = USBScanner::ChangeEvent; + using DeviceChangeHooks = USBScanner::DeviceChangeHooks; 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; + USBScanner m_usb_scanner{this}; + 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); bool m_has_initialised = false; - LibusbUtils::Context m_context; }; } // namespace IOS::HLE diff --git a/Source/Core/Core/IOS/USB/OH0/OH0.cpp b/Source/Core/Core/IOS/USB/OH0/OH0.cpp index a2be34727b..58eb344c8b 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,7 +73,7 @@ std::optional OH0::IOCtlV(const IOCtlVRequest& request) void OH0::DoState(PointerWrap& p) { - if (p.IsReadMode() && !m_devices.empty()) + if (p.IsReadMode() && !m_usb_scanner.m_devices.empty()) { Core::DisplayMessage("It is suggested that you unplug and replug all connected USB devices.", 5000); @@ -117,8 +114,8 @@ IPCReply OH0::GetDeviceList(const IOCtlVRequest& request) const 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) + std::lock_guard lk(m_usb_scanner.m_devices_mutex); + for (const auto& device : m_usb_scanner.m_devices) { if (entries_count >= max_entries_count) break; @@ -234,14 +231,14 @@ std::optional OH0::RegisterClassChangeHook(const IOCtlVRequest& reques bool OH0::HasDeviceWithVidPid(const u16 vid, const u16 pid) const { - return std::ranges::any_of(m_devices, [=](const auto& device) { + return std::ranges::any_of(m_usb_scanner.m_devices, [=](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); + std::lock_guard lk(m_usb_scanner.m_devices_mutex); if (event == ChangeEvent::Inserted) TriggerHook(m_insertion_hooks, {device->GetVid(), device->GetPid()}, IPC_SUCCESS); else if (event == ChangeEvent::Removed) @@ -262,10 +259,10 @@ void OH0::TriggerHook(std::map& hooks, T value, const ReturnCode return_ std::pair OH0::DeviceOpen(const u16 vid, const u16 pid) { - std::lock_guard lk(m_devices_mutex); + std::lock_guard lk(m_usb_scanner.m_devices_mutex); bool has_device_with_vid_pid = false; - for (const auto& device : m_devices) + for (const auto& device : m_usb_scanner.m_devices) { if (device.second->GetVid() != vid || device.second->GetPid() != pid) continue; 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..904eeb672d --- /dev/null +++ b/Source/Core/Core/IOS/USB/USBScanner.cpp @@ -0,0 +1,191 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/IOS/USB/USBScanner.h" + +#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(USBHost* host) : m_host(host) +{ +} + +USBScanner::~USBScanner() +{ + Stop(); +} + +void USBScanner::WaitForFirstScan() +{ + if (m_thread_running.IsSet()) + { + m_first_scan_complete_event.Wait(); + } +} + +void USBScanner::Start() +{ + if (Core::WantsDeterminism()) + { + UpdateDevices(); + return; + } + 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_event.Set(); + Common::SleepCurrentThread(50); + } + }); + } +} + +void USBScanner::Stop() +{ + if (m_thread_running.TestAndClear()) + m_thread.join(); + + // Clear all devices and dispatch removal hooks. + DeviceChangeHooks hooks; + DetectRemovedDevices(std::set(), hooks); + m_host->DispatchHooks(hooks); +} + +// This is called from the scan thread. Returns false if we failed to update the device list. +bool USBScanner::UpdateDevices(const bool always_add_hooks) +{ + 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); + m_host->DispatchHooks(hooks); + return true; +} + +bool USBScanner::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; +} + +bool USBScanner::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(m_host->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; +} + +void USBScanner::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.contains(it->second->GetId())) + { + hooks.emplace(it->second, ChangeEvent::Removed); + it = m_devices.erase(it); + } + else + { + ++it; + } + } +} + +void USBScanner::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(m_host->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(m_host->GetEmulationKernel()); + CheckAndAddDevice(std::move(infinity_base), new_devices, hooks, always_add_hooks); + } +} + +void USBScanner::CheckAndAddDevice(std::unique_ptr device, std::set& new_devices, + DeviceChangeHooks& hooks, bool always_add_hooks) +{ + if (m_host->ShouldAddDevice(*device)) + { + const u64 deviceid = device->GetId(); + new_devices.insert(deviceid); + if (AddDevice(std::move(device)) || always_add_hooks) + { + hooks.emplace(GetDeviceById(deviceid), ChangeEvent::Inserted); + } + } +} + +std::shared_ptr USBScanner::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; +} + +} // 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..5762beaeb2 --- /dev/null +++ b/Source/Core/Core/IOS/USB/USBScanner.h @@ -0,0 +1,64 @@ +// 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/Event.h" +#include "Common/Flag.h" +#include "Core/IOS/USB/Common.h" +#include "Core/LibusbUtils.h" + +class PointerWrap; + +namespace IOS::HLE +{ +class USBHost; + +class USBScanner final +{ +public: + explicit USBScanner(USBHost* host); + ~USBScanner(); + + void Start(); + void Stop(); + void WaitForFirstScan(); + bool UpdateDevices(bool always_add_hooks = false); + + std::shared_ptr GetDeviceById(u64 device_id) const; + + enum class ChangeEvent + { + Inserted, + Removed, + }; + using DeviceChangeHooks = std::map, ChangeEvent>; + + std::map> m_devices; + mutable std::mutex m_devices_mutex; + +private: + bool AddDevice(std::unique_ptr device); + bool AddNewDevices(std::set& new_devices, DeviceChangeHooks& hooks, bool always_add_hooks); + void DetectRemovedDevices(const std::set& plugged_devices, 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); + + USBHost* m_host = nullptr; + Common::Flag m_thread_running; + std::thread m_thread; + Common::Event m_first_scan_complete_event; + + 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..3214d7e771 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) { @@ -210,10 +207,10 @@ void USB_HIDv4::TriggerDeviceChangeReply() auto& memory = system.GetMemory(); { - std::lock_guard lk(m_devices_mutex); + std::lock_guard lk(m_usb_scanner.m_devices_mutex); const u32 dest = m_devicechange_hook_request->buffer_out; u32 offset = 0; - for (const auto& device : m_devices) + for (const auto& device : m_usb_scanner.m_devices) { const std::vector device_section = GetDeviceEntry(*device.second.get()); if (offset + device_section.size() > m_devicechange_hook_request->buffer_out_size - 1) 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/DolphinLib.props b/Source/Core/DolphinLib.props index 4c7764202d..0f88b7620c 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -413,6 +413,7 @@ + @@ -1077,6 +1078,7 @@ + From 50a8ae9d905941ac586b2e75267fc904c402ae9e Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 6 Apr 2025 14:43:45 +0200 Subject: [PATCH 2/5] IOS: Keep copy of m_devices in USBHost This gets rid of the ugly direct access to USBScanner::m_devices that was introduced by the previous commit. This also fixes a potential thread safety issue. USB_HIDv4::TriggerDeviceChangeReply loops through m_devices and calls GetDeviceEntry for each device. If USB_HIDv4::TriggerDeviceChangeReply is called after a new device is added to m_devices but before hooks are dispatched, GetDeviceEntry crashes, because the hook that's supposed to update m_device_ids hasn't run yet. With this commit, this issue can no longer happen, because USBHost::m_devices_mutex doesn't get unlocked in between updating m_devices and dispatching the hooks. --- Source/Core/Core/IOS/USB/Host.cpp | 25 +++++++++++++++++----- Source/Core/Core/IOS/USB/Host.h | 5 +++++ Source/Core/Core/IOS/USB/OH0/OH0.cpp | 13 ++++++----- Source/Core/Core/IOS/USB/USBScanner.h | 10 ++++----- Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp | 4 ++-- 5 files changed, 38 insertions(+), 19 deletions(-) diff --git a/Source/Core/Core/IOS/USB/Host.cpp b/Source/Core/Core/IOS/USB/Host.cpp index cdcf5b5812..605b03817b 100644 --- a/Source/Core/Core/IOS/USB/Host.cpp +++ b/Source/Core/Core/IOS/USB/Host.cpp @@ -4,7 +4,9 @@ #include "Core/IOS/USB/Host.h" #include +#include #include +#include #include #include @@ -61,7 +63,11 @@ void USBHost::DoState(PointerWrap& p) std::shared_ptr USBHost::GetDeviceById(const u64 device_id) const { - return m_usb_scanner.GetDeviceById(device_id); + 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) @@ -85,13 +91,22 @@ void USBHost::Update() void USBHost::DispatchHooks(const DeviceChangeHooks& hooks) { - for (const auto& hook : hooks) + std::lock_guard lk(m_devices_mutex); + + for (const auto& [device, event] : hooks) { 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); + event == ChangeEvent::Inserted ? "New" : "Removed", device->GetVid(), + device->GetPid()); + + if (event == ChangeEvent::Inserted) + m_devices.emplace(device->GetId(), device); + else if (event == ChangeEvent::Removed) + m_devices.erase(device->GetId()); + + OnDeviceChange(event, device); } + if (!hooks.empty()) OnDeviceChangeEnd(); } diff --git a/Source/Core/Core/IOS/USB/Host.h b/Source/Core/Core/IOS/USB/Host.h index 196104bc1a..ab4e9e8c6d 100644 --- a/Source/Core/Core/IOS/USB/Host.h +++ b/Source/Core/Core/IOS/USB/Host.h @@ -4,7 +4,9 @@ #pragma once #include +#include #include +#include #include #include @@ -45,6 +47,9 @@ protected: std::optional HandleTransfer(std::shared_ptr device, u32 request, std::function submit) const; + std::map> m_devices; + mutable std::recursive_mutex m_devices_mutex; + USBScanner m_usb_scanner{this}; private: diff --git a/Source/Core/Core/IOS/USB/OH0/OH0.cpp b/Source/Core/Core/IOS/USB/OH0/OH0.cpp index 58eb344c8b..e0bb771c18 100644 --- a/Source/Core/Core/IOS/USB/OH0/OH0.cpp +++ b/Source/Core/Core/IOS/USB/OH0/OH0.cpp @@ -73,7 +73,7 @@ std::optional OH0::IOCtlV(const IOCtlVRequest& request) void OH0::DoState(PointerWrap& p) { - if (p.IsReadMode() && !m_usb_scanner.m_devices.empty()) + if (p.IsReadMode() && !m_devices.empty()) { Core::DisplayMessage("It is suggested that you unplug and replug all connected USB devices.", 5000); @@ -114,8 +114,8 @@ IPCReply OH0::GetDeviceList(const IOCtlVRequest& request) const const u8 interface_class = memory.Read_U8(request.in_vectors[1].address); u8 entries_count = 0; - std::lock_guard lk(m_usb_scanner.m_devices_mutex); - for (const auto& device : m_usb_scanner.m_devices) + std::lock_guard lk(m_devices_mutex); + for (const auto& device : m_devices) { if (entries_count >= max_entries_count) break; @@ -231,14 +231,13 @@ std::optional OH0::RegisterClassChangeHook(const IOCtlVRequest& reques bool OH0::HasDeviceWithVidPid(const u16 vid, const u16 pid) const { - return std::ranges::any_of(m_usb_scanner.m_devices, [=](const auto& device) { + return std::ranges::any_of(m_devices, [=](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_usb_scanner.m_devices_mutex); if (event == ChangeEvent::Inserted) TriggerHook(m_insertion_hooks, {device->GetVid(), device->GetPid()}, IPC_SUCCESS); else if (event == ChangeEvent::Removed) @@ -259,10 +258,10 @@ void OH0::TriggerHook(std::map& hooks, T value, const ReturnCode return_ std::pair OH0::DeviceOpen(const u16 vid, const u16 pid) { - std::lock_guard lk(m_usb_scanner.m_devices_mutex); + std::lock_guard lk(m_devices_mutex); bool has_device_with_vid_pid = false; - for (const auto& device : m_usb_scanner.m_devices) + for (const auto& device : m_devices) { if (device.second->GetVid() != vid || device.second->GetPid() != pid) continue; diff --git a/Source/Core/Core/IOS/USB/USBScanner.h b/Source/Core/Core/IOS/USB/USBScanner.h index 5762beaeb2..c6594886ee 100644 --- a/Source/Core/Core/IOS/USB/USBScanner.h +++ b/Source/Core/Core/IOS/USB/USBScanner.h @@ -32,8 +32,6 @@ public: void WaitForFirstScan(); bool UpdateDevices(bool always_add_hooks = false); - std::shared_ptr GetDeviceById(u64 device_id) const; - enum class ChangeEvent { Inserted, @@ -41,9 +39,6 @@ public: }; using DeviceChangeHooks = std::map, ChangeEvent>; - std::map> m_devices; - mutable std::mutex m_devices_mutex; - private: bool AddDevice(std::unique_ptr device); bool AddNewDevices(std::set& new_devices, DeviceChangeHooks& hooks, bool always_add_hooks); @@ -53,6 +48,11 @@ private: void CheckAndAddDevice(std::unique_ptr device, std::set& new_devices, DeviceChangeHooks& hooks, bool always_add_hooks); + std::shared_ptr GetDeviceById(u64 device_id) const; + + std::map> m_devices; + mutable std::mutex m_devices_mutex; + USBHost* m_host = nullptr; Common::Flag m_thread_running; std::thread m_thread; diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp b/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp index 3214d7e771..f20928eba0 100644 --- a/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp @@ -207,10 +207,10 @@ void USB_HIDv4::TriggerDeviceChangeReply() auto& memory = system.GetMemory(); { - std::lock_guard lk(m_usb_scanner.m_devices_mutex); + std::lock_guard lk(m_devices_mutex); const u32 dest = m_devicechange_hook_request->buffer_out; u32 offset = 0; - for (const auto& device : m_usb_scanner.m_devices) + 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) From 24fdcc1a0e55e52c707fe2d117b20e870bb354f6 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 6 Apr 2025 15:25:25 +0200 Subject: [PATCH 3/5] IOS: Add missing locking for USBHost::m_devices Note: After adding the missing locking of m_devices_mutex, I had to move the locking of m_hooks_mutex to avoid a random deadlock between the CPU thread and USB scanning thread. (Either that or I would have to lock m_devices_mutex before m_hooks_mutex.) --- Source/Core/Core/IOS/USB/OH0/OH0.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Source/Core/Core/IOS/USB/OH0/OH0.cpp b/Source/Core/Core/IOS/USB/OH0/OH0.cpp index e0bb771c18..dad3c182c9 100644 --- a/Source/Core/Core/IOS/USB/OH0/OH0.cpp +++ b/Source/Core/Core/IOS/USB/OH0/OH0.cpp @@ -73,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); @@ -206,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. @@ -231,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; }); From 920a44aec213ffe706936de67d52ce8f209f27dd Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 6 Apr 2025 20:00:50 +0200 Subject: [PATCH 4/5] IOS: Diff device lists in USBHost instead of USBScanner Instead of having USBScanner create "hooks" as it scans for devices, let's have USBScanner present a list of devices to USBHost and have USBHost diff the new device list with its old device list to create the hook calls instead. This gets rid of some complex edge cases that the next commit otherwise would have to deal with, in particular regarding toggling determinism and adding new USBHosts to a USBScanner. --- Source/Core/Core/IOS/USB/Host.cpp | 66 ++++++++++++------ Source/Core/Core/IOS/USB/Host.h | 12 ++-- Source/Core/Core/IOS/USB/USBScanner.cpp | 91 +++++++------------------ Source/Core/Core/IOS/USB/USBScanner.h | 25 +++---- 4 files changed, 83 insertions(+), 111 deletions(-) diff --git a/Source/Core/Core/IOS/USB/Host.cpp b/Source/Core/Core/IOS/USB/Host.cpp index 605b03817b..e66b08c646 100644 --- a/Source/Core/Core/IOS/USB/Host.cpp +++ b/Source/Core/Core/IOS/USB/Host.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" @@ -37,19 +38,12 @@ std::optional 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). m_usb_scanner.WaitForFirstScan(); + OnDevicesChangedInternal(m_usb_scanner.GetDevices()); m_has_initialised = true; } return IPCReply(IPC_SUCCESS); } -void USBHost::UpdateWantDeterminism(const bool new_want_determinism) -{ - if (new_want_determinism) - m_usb_scanner.Stop(); - else if (IsOpened()) - m_usb_scanner.Start(); -} - void USBHost::DoState(PointerWrap& p) { Device::DoState(p); @@ -57,7 +51,9 @@ 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. - m_usb_scanner.UpdateDevices(true); + std::lock_guard lk(m_devices_mutex); + m_devices.clear(); + OnDevicesChanged(m_usb_scanner.GetDevices()); } } @@ -86,28 +82,54 @@ bool USBHost::ShouldAddDevice(const USB::Device& device) const void USBHost::Update() { if (Core::WantsDeterminism()) - m_usb_scanner.UpdateDevices(); + OnDevicesChangedInternal(m_usb_scanner.GetDevices()); } -void USBHost::DispatchHooks(const DeviceChangeHooks& hooks) +void USBHost::OnDevicesChanged(const USBScanner::DeviceMap& new_devices) +{ + if (!Core::WantsDeterminism()) + OnDevicesChangedInternal(new_devices); +} + +void USBHost::OnDevicesChangedInternal(const USBScanner::DeviceMap& new_devices) { std::lock_guard lk(m_devices_mutex); - for (const auto& [device, event] : hooks) + bool changes = false; + + for (auto it = m_devices.begin(); it != m_devices.end();) { - INFO_LOG_FMT(IOS_USB, "{} - {} device: {:04x}:{:04x}", GetDeviceName(), - event == ChangeEvent::Inserted ? "New" : "Removed", device->GetVid(), - device->GetPid()); + const auto& [id, device] = *it; + if (!new_devices.contains(id)) + { + INFO_LOG_FMT(IOS_USB, "{} - Removed device: {:04x}:{:04x}", GetDeviceName(), device->GetVid(), + device->GetPid()); - if (event == ChangeEvent::Inserted) - m_devices.emplace(device->GetId(), device); - else if (event == ChangeEvent::Removed) - m_devices.erase(device->GetId()); - - OnDeviceChange(event, device); + changes = true; + auto device_copy = std::move(device); + it = m_devices.erase(it); + OnDeviceChange(ChangeEvent::Removed, std::move(device_copy)); + } + else + { + ++it; + } } - if (!hooks.empty()) + for (const auto& [id, device] : new_devices) + { + if (!m_devices.contains(id)) + { + 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); + } + } + + if (changes) OnDeviceChangeEnd(); } diff --git a/Source/Core/Core/IOS/USB/Host.h b/Source/Core/Core/IOS/USB/Host.h index ab4e9e8c6d..92da3e911b 100644 --- a/Source/Core/Core/IOS/USB/Host.h +++ b/Source/Core/Core/IOS/USB/Host.h @@ -29,16 +29,19 @@ public: std::optional Open(const OpenRequest& request) override; - void UpdateWantDeterminism(bool new_want_determinism) override; void DoState(PointerWrap& p) override; virtual bool ShouldAddDevice(const USB::Device& device) const; - void DispatchHooks(const USBScanner::DeviceChangeHooks& hooks); + void OnDevicesChanged(const USBScanner::DeviceMap& new_devices); protected: - using ChangeEvent = USBScanner::ChangeEvent; - using DeviceChangeHooks = USBScanner::DeviceChangeHooks; + enum class ChangeEvent + { + Inserted, + Removed, + }; + using DeviceChangeHooks = std::map, ChangeEvent>; std::shared_ptr GetDeviceById(u64 device_id) const; virtual void OnDeviceChange(ChangeEvent event, std::shared_ptr changed_device); @@ -54,6 +57,7 @@ protected: private: void Update() override; + void OnDevicesChangedInternal(const USBScanner::DeviceMap& new_devices); bool m_has_initialised = false; }; diff --git a/Source/Core/Core/IOS/USB/USBScanner.cpp b/Source/Core/Core/IOS/USB/USBScanner.cpp index 904eeb672d..be0d1c02df 100644 --- a/Source/Core/Core/IOS/USB/USBScanner.cpp +++ b/Source/Core/Core/IOS/USB/USBScanner.cpp @@ -3,8 +3,10 @@ #include "Core/IOS/USB/USBScanner.h" +#include #include #include +#include #include #include #include @@ -47,11 +49,6 @@ void USBScanner::WaitForFirstScan() void USBScanner::Start() { - if (Core::WantsDeterminism()) - { - UpdateDevices(); - return; - } if (m_thread_running.TestAndSet()) { m_thread = std::thread([this] { @@ -70,40 +67,34 @@ void USBScanner::Stop() { if (m_thread_running.TestAndClear()) m_thread.join(); +} - // Clear all devices and dispatch removal hooks. - DeviceChangeHooks hooks; - DetectRemovedDevices(std::set(), hooks); - m_host->DispatchHooks(hooks); +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(const bool always_add_hooks) +bool USBScanner::UpdateDevices() { - 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)) + DeviceMap new_devices; + if (!AddNewDevices(&new_devices)) return false; - DetectRemovedDevices(plugged_devices, hooks); - m_host->DispatchHooks(hooks); - return true; -} -bool USBScanner::AddDevice(std::unique_ptr device) -{ std::lock_guard lk(m_devices_mutex); - if (m_devices.contains(device->GetId())) - return false; + 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); + } - m_devices[device->GetId()] = std::move(device); return true; } -bool USBScanner::AddNewDevices(std::set& new_devices, DeviceChangeHooks& hooks, - const bool always_add_hooks) +bool USBScanner::AddNewDevices(DeviceMap* new_devices) const { - AddEmulatedDevices(new_devices, hooks, always_add_hooks); + AddEmulatedDevices(new_devices); #ifdef __LIBUSB__ if (!Core::WantsDeterminism()) { @@ -121,7 +112,7 @@ bool USBScanner::AddNewDevices(std::set& new_devices, DeviceChangeHooks& ho auto usb_device = std::make_unique(m_host->GetEmulationKernel(), device, descriptor); - CheckAndAddDevice(std::move(usb_device), new_devices, hooks, always_add_hooks); + AddDevice(std::move(usb_device), new_devices); return true; }); if (ret != LIBUSB_SUCCESS) @@ -132,60 +123,24 @@ bool USBScanner::AddNewDevices(std::set& new_devices, DeviceChangeHooks& ho return true; } -void USBScanner::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.contains(it->second->GetId())) - { - hooks.emplace(it->second, ChangeEvent::Removed); - it = m_devices.erase(it); - } - else - { - ++it; - } - } -} - -void USBScanner::AddEmulatedDevices(std::set& new_devices, DeviceChangeHooks& hooks, - bool always_add_hooks) +void USBScanner::AddEmulatedDevices(DeviceMap* new_devices) const { if (Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL) && !NetPlay::IsNetPlayRunning()) { auto skylanderportal = std::make_unique(m_host->GetEmulationKernel()); - CheckAndAddDevice(std::move(skylanderportal), new_devices, hooks, always_add_hooks); + 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()); - CheckAndAddDevice(std::move(infinity_base), new_devices, hooks, always_add_hooks); + AddDevice(std::move(infinity_base), new_devices); } } -void USBScanner::CheckAndAddDevice(std::unique_ptr device, std::set& new_devices, - DeviceChangeHooks& hooks, bool always_add_hooks) +void USBScanner::AddDevice(std::unique_ptr device, DeviceMap* new_devices) const { if (m_host->ShouldAddDevice(*device)) - { - const u64 deviceid = device->GetId(); - new_devices.insert(deviceid); - if (AddDevice(std::move(device)) || always_add_hooks) - { - hooks.emplace(GetDeviceById(deviceid), ChangeEvent::Inserted); - } - } -} - -std::shared_ptr USBScanner::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; + (*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 c6594886ee..3d80565926 100644 --- a/Source/Core/Core/IOS/USB/USBScanner.h +++ b/Source/Core/Core/IOS/USB/USBScanner.h @@ -24,33 +24,24 @@ class USBHost; class USBScanner final { public: + using DeviceMap = std::map>; + explicit USBScanner(USBHost* host); ~USBScanner(); void Start(); void Stop(); void WaitForFirstScan(); - bool UpdateDevices(bool always_add_hooks = false); - enum class ChangeEvent - { - Inserted, - Removed, - }; - using DeviceChangeHooks = std::map, ChangeEvent>; + DeviceMap GetDevices() const; private: - bool AddDevice(std::unique_ptr device); - bool AddNewDevices(std::set& new_devices, DeviceChangeHooks& hooks, bool always_add_hooks); - void DetectRemovedDevices(const std::set& plugged_devices, 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); + bool UpdateDevices(); + bool AddNewDevices(DeviceMap* new_devices) const; + void AddEmulatedDevices(DeviceMap* new_devices) const; + void AddDevice(std::unique_ptr device, DeviceMap* new_devices) const; - std::shared_ptr GetDeviceById(u64 device_id) const; - - std::map> m_devices; + DeviceMap m_devices; mutable std::mutex m_devices_mutex; USBHost* m_host = nullptr; From e468e2359a4843e91d55a9d3869a971b64806959 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 6 Apr 2025 14:18:43 +0200 Subject: [PATCH 5/5] IOS: Only have one USBScanner Some games open two USB interfaces, e.g. /dev/usb/oh0 and /dev/usb/hid. This was causing us to run two scanning threads at once, using up more CPU time for scanning than we need to. --- Source/Core/Common/CMakeLists.txt | 1 + Source/Core/Common/Event.h | 4 - Source/Core/Common/WaitableFlag.h | 69 +++++++++++++++++ Source/Core/Core/IOS/USB/Common.cpp | 5 ++ Source/Core/Core/IOS/USB/Common.h | 2 + .../Core/Core/IOS/USB/Emulated/Infinity.cpp | 4 +- Source/Core/Core/IOS/USB/Emulated/Infinity.h | 3 +- .../IOS/USB/Emulated/Skylanders/Skylander.cpp | 6 +- .../IOS/USB/Emulated/Skylanders/Skylander.h | 3 +- Source/Core/Core/IOS/USB/Host.cpp | 15 ++-- Source/Core/Core/IOS/USB/Host.h | 5 +- Source/Core/Core/IOS/USB/LibusbDevice.cpp | 11 ++- Source/Core/Core/IOS/USB/LibusbDevice.h | 5 +- Source/Core/Core/IOS/USB/USBScanner.cpp | 77 ++++++++++++++----- Source/Core/Core/IOS/USB/USBScanner.h | 22 ++++-- Source/Core/Core/System.cpp | 7 ++ Source/Core/Core/System.h | 4 +- Source/Core/DolphinLib.props | 1 + 18 files changed, 183 insertions(+), 61 deletions(-) create mode 100644 Source/Core/Common/WaitableFlag.h 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 @@ +