mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-04-24 14:24:54 +00:00
Merge pull request #13490 from JosJuice/one-usb-scanner
IOS: Only have one USB scanning thread
This commit is contained in:
commit
9254a53397
27 changed files with 414 additions and 261 deletions
|
@ -154,6 +154,7 @@ add_library(common
|
||||||
VariantUtil.h
|
VariantUtil.h
|
||||||
Version.cpp
|
Version.cpp
|
||||||
Version.h
|
Version.h
|
||||||
|
WaitableFlag.h
|
||||||
WindowSystemInfo.h
|
WindowSystemInfo.h
|
||||||
WorkQueueThread.h
|
WorkQueueThread.h
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,10 +11,6 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <concrt.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
69
Source/Core/Common/WaitableFlag.h
Normal file
69
Source/Core/Common/WaitableFlag.h
Normal file
|
@ -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 <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#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<std::mutex> 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<std::mutex> lk(m_mutex);
|
||||||
|
m_condvar.wait(lk, [&] { return m_flag.IsSet() == expected_value; });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Rep, class Period>
|
||||||
|
bool WaitFor(bool expected_value, const std::chrono::duration<Rep, Period>& rel_time)
|
||||||
|
{
|
||||||
|
if (m_flag.IsSet() == expected_value)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> 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
|
|
@ -449,6 +449,8 @@ add_library(core
|
||||||
IOS/USB/USB_KBD.h
|
IOS/USB/USB_KBD.h
|
||||||
IOS/USB/USB_VEN/VEN.cpp
|
IOS/USB/USB_VEN/VEN.cpp
|
||||||
IOS/USB/USB_VEN/VEN.h
|
IOS/USB/USB_VEN/VEN.h
|
||||||
|
IOS/USB/USBScanner.cpp
|
||||||
|
IOS/USB/USBScanner.h
|
||||||
IOS/USB/USBV0.cpp
|
IOS/USB/USBV0.cpp
|
||||||
IOS/USB/USBV0.h
|
IOS/USB/USBV0.h
|
||||||
IOS/USB/USBV4.cpp
|
IOS/USB/USBV4.cpp
|
||||||
|
|
|
@ -15,6 +15,11 @@
|
||||||
|
|
||||||
namespace IOS::HLE::USB
|
namespace IOS::HLE::USB
|
||||||
{
|
{
|
||||||
|
EmulationKernel& TransferCommand::GetEmulationKernel() const
|
||||||
|
{
|
||||||
|
return m_ios;
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<u8[]> TransferCommand::MakeBuffer(const size_t size) const
|
std::unique_ptr<u8[]> TransferCommand::MakeBuffer(const size_t size) const
|
||||||
{
|
{
|
||||||
ASSERT_MSG(IOS_USB, data_address != 0, "Invalid data_address");
|
ASSERT_MSG(IOS_USB, data_address != 0, "Invalid data_address");
|
||||||
|
|
|
@ -106,6 +106,8 @@ struct TransferCommand
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
virtual ~TransferCommand() = default;
|
virtual ~TransferCommand() = default;
|
||||||
|
|
||||||
|
EmulationKernel& GetEmulationKernel() const;
|
||||||
// Called after a transfer has completed to reply to the IPC request.
|
// Called after a transfer has completed to reply to the IPC request.
|
||||||
// This can be overridden for additional processing before replying.
|
// This can be overridden for additional processing before replying.
|
||||||
virtual void OnTransferComplete(s32 return_value) const;
|
virtual void OnTransferComplete(s32 return_value) const;
|
||||||
|
|
|
@ -137,7 +137,7 @@ static constexpr std::array<u8, 32> SHA1_CONSTANT = {
|
||||||
static constexpr std::array<u8, 16> BLANK_BLOCK = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
static constexpr std::array<u8, 16> BLANK_BLOCK = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
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_vid = 0x0E6F;
|
||||||
m_pid = 0x0129;
|
m_pid = 0x0129;
|
||||||
|
@ -241,7 +241,7 @@ int InfinityUSB::SubmitTransfer(std::unique_ptr<IntrMessage> cmd)
|
||||||
DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Interrupt: length={:04x} endpoint={:02x}", m_vid,
|
DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Interrupt: length={:04x} endpoint={:02x}", m_vid,
|
||||||
m_pid, m_active_interface, cmd->length, cmd->endpoint);
|
m_pid, m_active_interface, cmd->length, cmd->endpoint);
|
||||||
|
|
||||||
auto& system = m_ios.GetSystem();
|
auto& system = cmd->GetEmulationKernel().GetSystem();
|
||||||
auto& memory = system.GetMemory();
|
auto& memory = system.GetMemory();
|
||||||
auto& infinity_base = system.GetInfinityBase();
|
auto& infinity_base = system.GetInfinityBase();
|
||||||
u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length);
|
u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length);
|
||||||
|
|
|
@ -32,7 +32,7 @@ struct InfinityFigure final
|
||||||
class InfinityUSB final : public Device
|
class InfinityUSB final : public Device
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
InfinityUSB(EmulationKernel& ios);
|
InfinityUSB();
|
||||||
~InfinityUSB() override;
|
~InfinityUSB() override;
|
||||||
DeviceDescriptor GetDeviceDescriptor() const override;
|
DeviceDescriptor GetDeviceDescriptor() const override;
|
||||||
std::vector<ConfigDescriptor> GetConfigurations() const override;
|
std::vector<ConfigDescriptor> GetConfigurations() const override;
|
||||||
|
@ -53,7 +53,6 @@ private:
|
||||||
void ScheduleTransfer(std::unique_ptr<TransferCommand> command, const std::array<u8, 32>& data,
|
void ScheduleTransfer(std::unique_ptr<TransferCommand> command, const std::array<u8, 32>& data,
|
||||||
u64 expected_time_us);
|
u64 expected_time_us);
|
||||||
|
|
||||||
EmulationKernel& m_ios;
|
|
||||||
u16 m_vid = 0;
|
u16 m_vid = 0;
|
||||||
u16 m_pid = 0;
|
u16 m_pid = 0;
|
||||||
u8 m_active_interface = 0;
|
u8 m_active_interface = 0;
|
||||||
|
|
|
@ -612,7 +612,7 @@ const std::map<const std::pair<const u16, const u16>, SkyData> list_skylanders =
|
||||||
{{3503, 0x4000}, {"Kaos Trophy", Game::Superchargers, Element::Other, Type::Trophy}},
|
{{3503, 0x4000}, {"Kaos Trophy", Game::Superchargers, Element::Other, Type::Trophy}},
|
||||||
};
|
};
|
||||||
|
|
||||||
SkylanderUSB::SkylanderUSB(EmulationKernel& ios) : m_ios(ios)
|
SkylanderUSB::SkylanderUSB()
|
||||||
{
|
{
|
||||||
m_vid = 0x1430;
|
m_vid = 0x1430;
|
||||||
m_pid = 0x0150;
|
m_pid = 0x0150;
|
||||||
|
@ -741,7 +741,7 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Skylander Portal Requests
|
// Skylander Portal Requests
|
||||||
auto& system = m_ios.GetSystem();
|
auto& system = cmd->GetEmulationKernel().GetSystem();
|
||||||
auto& memory = system.GetMemory();
|
auto& memory = system.GetMemory();
|
||||||
u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length);
|
u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length);
|
||||||
if (cmd->length == 0 || buf == nullptr)
|
if (cmd->length == 0 || buf == nullptr)
|
||||||
|
@ -1028,7 +1028,7 @@ int SkylanderUSB::SubmitTransfer(std::unique_ptr<IntrMessage> cmd)
|
||||||
DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Interrupt: length={} endpoint={}", m_vid, m_pid,
|
DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Interrupt: length={} endpoint={}", m_vid, m_pid,
|
||||||
m_active_interface, cmd->length, cmd->endpoint);
|
m_active_interface, cmd->length, cmd->endpoint);
|
||||||
|
|
||||||
auto& system = m_ios.GetSystem();
|
auto& system = cmd->GetEmulationKernel().GetSystem();
|
||||||
auto& memory = system.GetMemory();
|
auto& memory = system.GetMemory();
|
||||||
u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length);
|
u8* buf = memory.GetPointerForRange(cmd->data_address, cmd->length);
|
||||||
if (cmd->length == 0 || buf == nullptr)
|
if (cmd->length == 0 || buf == nullptr)
|
||||||
|
|
|
@ -72,7 +72,7 @@ extern const std::map<const std::pair<const u16, const u16>, SkyData> list_skyla
|
||||||
class SkylanderUSB final : public Device
|
class SkylanderUSB final : public Device
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SkylanderUSB(EmulationKernel& ios);
|
SkylanderUSB();
|
||||||
~SkylanderUSB();
|
~SkylanderUSB();
|
||||||
DeviceDescriptor GetDeviceDescriptor() const override;
|
DeviceDescriptor GetDeviceDescriptor() const override;
|
||||||
std::vector<ConfigDescriptor> GetConfigurations() const override;
|
std::vector<ConfigDescriptor> GetConfigurations() const override;
|
||||||
|
@ -92,7 +92,6 @@ public:
|
||||||
s32 expected_count, u64 expected_time_us);
|
s32 expected_count, u64 expected_time_us);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EmulationKernel& m_ios;
|
|
||||||
u16 m_vid = 0;
|
u16 m_vid = 0;
|
||||||
u16 m_pid = 0;
|
u16 m_pid = 0;
|
||||||
u8 m_active_interface = 0;
|
u8 m_active_interface = 0;
|
||||||
|
|
|
@ -3,29 +3,20 @@
|
||||||
|
|
||||||
#include "Core/IOS/USB/Host.h"
|
#include "Core/IOS/USB/Host.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <set>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#ifdef __LIBUSB__
|
|
||||||
#include <libusb.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "Common/Assert.h"
|
|
||||||
#include "Common/ChunkFile.h"
|
#include "Common/ChunkFile.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/Thread.h"
|
|
||||||
#include "Core/Config/MainSettings.h"
|
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
#include "Core/IOS/USB/Common.h"
|
#include "Core/IOS/USB/Common.h"
|
||||||
#include "Core/IOS/USB/Emulated/Infinity.h"
|
#include "Core/IOS/USB/USBScanner.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/System.h"
|
||||||
|
|
||||||
namespace IOS::HLE
|
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<IPCReply> USBHost::Open(const OpenRequest& request)
|
std::optional<IPCReply> USBHost::Open(const OpenRequest& request)
|
||||||
{
|
{
|
||||||
if (!m_has_initialised)
|
if (!m_has_initialised)
|
||||||
{
|
{
|
||||||
GetScanThread().Start();
|
GetSystem().GetUSBScanner().AddClient(this);
|
||||||
// Force a device scan to complete, because some games (including Your Shape) only care
|
// Force a device scan to complete, because some games (including Your Shape) only care
|
||||||
// about the initial device list (in the first GETDEVICECHANGE reply).
|
// about the initial device list (in the first GETDEVICECHANGE reply).
|
||||||
GetScanThread().WaitForFirstScan();
|
GetSystem().GetUSBScanner().WaitForFirstScan();
|
||||||
|
OnDevicesChangedInternal(GetSystem().GetUSBScanner().GetDevices());
|
||||||
m_has_initialised = true;
|
m_has_initialised = true;
|
||||||
}
|
}
|
||||||
return IPCReply(IPC_SUCCESS);
|
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)
|
void USBHost::DoState(PointerWrap& p)
|
||||||
{
|
{
|
||||||
Device::DoState(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
|
// After a state has loaded, there may be insertion hooks for devices that were
|
||||||
// already plugged in, and which need to be triggered.
|
// 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<USB::Device> 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<USB::Device> USBHost::GetDeviceById(const u64 device_id) const
|
std::shared_ptr<USB::Device> USBHost::GetDeviceById(const u64 device_id) const
|
||||||
{
|
{
|
||||||
std::lock_guard lk(m_devices_mutex);
|
std::lock_guard lk(m_devices_mutex);
|
||||||
|
@ -104,156 +83,55 @@ bool USBHost::ShouldAddDevice(const USB::Device& device) const
|
||||||
void USBHost::Update()
|
void USBHost::Update()
|
||||||
{
|
{
|
||||||
if (Core::WantsDeterminism())
|
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.
|
void USBHost::OnDevicesChanged(const USBScanner::DeviceMap& new_devices)
|
||||||
bool USBHost::UpdateDevices(const bool always_add_hooks)
|
|
||||||
{
|
{
|
||||||
DeviceChangeHooks hooks;
|
|
||||||
std::set<u64> plugged_devices;
|
|
||||||
// If we failed to get a new, up-to-date list of devices, we cannot detect device removals.
|
|
||||||
if (!AddNewDevices(plugged_devices, hooks, always_add_hooks))
|
|
||||||
return false;
|
|
||||||
DetectRemovedDevices(plugged_devices, hooks);
|
|
||||||
DispatchHooks(hooks);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool USBHost::AddNewDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks,
|
|
||||||
const bool always_add_hooks)
|
|
||||||
{
|
|
||||||
AddEmulatedDevices(new_devices, hooks, always_add_hooks);
|
|
||||||
#ifdef __LIBUSB__
|
|
||||||
if (!Core::WantsDeterminism())
|
if (!Core::WantsDeterminism())
|
||||||
{
|
OnDevicesChangedInternal(new_devices);
|
||||||
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<USB::LibusbDevice>(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<u64>& plugged_devices, DeviceChangeHooks& hooks)
|
void USBHost::OnDevicesChangedInternal(const USBScanner::DeviceMap& new_devices)
|
||||||
{
|
{
|
||||||
std::lock_guard lk(m_devices_mutex);
|
std::lock_guard lk(m_devices_mutex);
|
||||||
|
|
||||||
|
bool changes = false;
|
||||||
|
|
||||||
for (auto it = m_devices.begin(); it != m_devices.end();)
|
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);
|
it = m_devices.erase(it);
|
||||||
|
OnDeviceChange(ChangeEvent::Removed, std::move(device_copy));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void USBHost::DispatchHooks(const DeviceChangeHooks& hooks)
|
for (const auto& [id, device] : new_devices)
|
||||||
{
|
|
||||||
for (const auto& hook : hooks)
|
|
||||||
{
|
{
|
||||||
INFO_LOG_FMT(IOS_USB, "{} - {} device: {:04x}:{:04x}", GetDeviceName(),
|
if (!m_devices.contains(id) && ShouldAddDevice(*device))
|
||||||
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<u64>& new_devices, DeviceChangeHooks& hooks,
|
|
||||||
bool always_add_hooks)
|
|
||||||
{
|
|
||||||
if (Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL) && !NetPlay::IsNetPlayRunning())
|
|
||||||
{
|
|
||||||
auto skylanderportal = std::make_unique<USB::SkylanderUSB>(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<USB::InfinityUSB>(GetEmulationKernel());
|
|
||||||
CheckAndAddDevice(std::move(infinity_base), new_devices, hooks, always_add_hooks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void USBHost::CheckAndAddDevice(std::unique_ptr<USB::Device> device, std::set<u64>& 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);
|
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()
|
if (changes)
|
||||||
{
|
OnDeviceChangeEnd();
|
||||||
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<u64>(), hooks);
|
|
||||||
m_host->DispatchHooks(hooks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<IPCReply> USBHost::HandleTransfer(std::shared_ptr<USB::Device> device, u32 request,
|
std::optional<IPCReply> USBHost::HandleTransfer(std::shared_ptr<USB::Device> device, u32 request,
|
||||||
|
@ -270,4 +148,5 @@ std::optional<IPCReply> USBHost::HandleTransfer(std::shared_ptr<USB::Device> dev
|
||||||
device->GetVid(), device->GetPid(), request, device->GetErrorName(ret));
|
device->GetVid(), device->GetPid(), request, device->GetErrorName(ret));
|
||||||
return IPCReply(ret <= 0 ? ret : IPC_EINVAL);
|
return IPCReply(ret <= 0 ? ret : IPC_EINVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace IOS::HLE
|
} // namespace IOS::HLE
|
||||||
|
|
|
@ -3,23 +3,18 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <set>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/Event.h"
|
|
||||||
#include "Common/Flag.h"
|
|
||||||
#include "Core/IOS/Device.h"
|
#include "Core/IOS/Device.h"
|
||||||
#include "Core/IOS/IOS.h"
|
#include "Core/IOS/IOS.h"
|
||||||
#include "Core/IOS/USB/Common.h"
|
#include "Core/IOS/USB/Common.h"
|
||||||
#include "Core/LibusbUtils.h"
|
#include "Core/IOS/USB/USBScanner.h"
|
||||||
|
|
||||||
class PointerWrap;
|
class PointerWrap;
|
||||||
|
|
||||||
|
@ -34,9 +29,10 @@ public:
|
||||||
|
|
||||||
std::optional<IPCReply> Open(const OpenRequest& request) override;
|
std::optional<IPCReply> Open(const OpenRequest& request) override;
|
||||||
|
|
||||||
void UpdateWantDeterminism(bool new_want_determinism) override;
|
|
||||||
void DoState(PointerWrap& p) override;
|
void DoState(PointerWrap& p) override;
|
||||||
|
|
||||||
|
void OnDevicesChanged(const USBScanner::DeviceMap& new_devices);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
enum class ChangeEvent
|
enum class ChangeEvent
|
||||||
{
|
{
|
||||||
|
@ -45,48 +41,21 @@ protected:
|
||||||
};
|
};
|
||||||
using DeviceChangeHooks = std::map<std::shared_ptr<USB::Device>, ChangeEvent>;
|
using DeviceChangeHooks = std::map<std::shared_ptr<USB::Device>, 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<u64, std::shared_ptr<USB::Device>> m_devices;
|
|
||||||
mutable std::mutex m_devices_mutex;
|
|
||||||
|
|
||||||
std::shared_ptr<USB::Device> GetDeviceById(u64 device_id) const;
|
std::shared_ptr<USB::Device> GetDeviceById(u64 device_id) const;
|
||||||
virtual void OnDeviceChange(ChangeEvent event, std::shared_ptr<USB::Device> changed_device);
|
virtual void OnDeviceChange(ChangeEvent event, std::shared_ptr<USB::Device> changed_device);
|
||||||
virtual void OnDeviceChangeEnd();
|
virtual void OnDeviceChangeEnd();
|
||||||
virtual bool ShouldAddDevice(const USB::Device& device) const;
|
virtual bool ShouldAddDevice(const USB::Device& device) const;
|
||||||
virtual ScanThread& GetScanThread() = 0;
|
|
||||||
|
|
||||||
std::optional<IPCReply> HandleTransfer(std::shared_ptr<USB::Device> device, u32 request,
|
std::optional<IPCReply> HandleTransfer(std::shared_ptr<USB::Device> device, u32 request,
|
||||||
std::function<s32()> submit) const;
|
std::function<s32()> submit) const;
|
||||||
|
|
||||||
|
std::map<u64, std::shared_ptr<USB::Device>> m_devices;
|
||||||
|
mutable std::recursive_mutex m_devices_mutex;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool AddDevice(std::unique_ptr<USB::Device> device);
|
|
||||||
void Update() override;
|
void Update() override;
|
||||||
bool UpdateDevices(bool always_add_hooks = false);
|
void OnDevicesChangedInternal(const USBScanner::DeviceMap& new_devices);
|
||||||
bool AddNewDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks, bool always_add_hooks);
|
|
||||||
void DetectRemovedDevices(const std::set<u64>& plugged_devices, DeviceChangeHooks& hooks);
|
|
||||||
void DispatchHooks(const DeviceChangeHooks& hooks);
|
|
||||||
void AddEmulatedDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks,
|
|
||||||
bool always_add_hooks);
|
|
||||||
void CheckAndAddDevice(std::unique_ptr<USB::Device> device, std::set<u64>& new_devices,
|
|
||||||
DeviceChangeHooks& hooks, bool always_add_hooks);
|
|
||||||
|
|
||||||
bool m_has_initialised = false;
|
bool m_has_initialised = false;
|
||||||
LibusbUtils::Context m_context;
|
|
||||||
};
|
};
|
||||||
} // namespace IOS::HLE
|
} // namespace IOS::HLE
|
||||||
|
|
|
@ -25,9 +25,8 @@
|
||||||
|
|
||||||
namespace IOS::HLE::USB
|
namespace IOS::HLE::USB
|
||||||
{
|
{
|
||||||
LibusbDevice::LibusbDevice(EmulationKernel& ios, libusb_device* device,
|
LibusbDevice::LibusbDevice(libusb_device* device, const libusb_device_descriptor& descriptor)
|
||||||
const libusb_device_descriptor& descriptor)
|
: m_device(device)
|
||||||
: m_ios(ios), m_device(device)
|
|
||||||
{
|
{
|
||||||
libusb_ref_device(m_device);
|
libusb_ref_device(m_device);
|
||||||
m_vid = descriptor.idVendor;
|
m_vid = descriptor.idVendor;
|
||||||
|
@ -226,7 +225,7 @@ int LibusbDevice::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
||||||
}
|
}
|
||||||
const int ret = SetAltSetting(static_cast<u8>(cmd->value));
|
const int ret = SetAltSetting(static_cast<u8>(cmd->value));
|
||||||
if (ret == LIBUSB_SUCCESS)
|
if (ret == LIBUSB_SUCCESS)
|
||||||
m_ios.EnqueueIPCReply(cmd->ios_request, cmd->length);
|
cmd->GetEmulationKernel().EnqueueIPCReply(cmd->ios_request, cmd->length);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
case USBHDR(DIR_HOST2DEVICE, TYPE_STANDARD, REC_DEVICE, REQUEST_SET_CONFIGURATION):
|
case USBHDR(DIR_HOST2DEVICE, TYPE_STANDARD, REC_DEVICE, REQUEST_SET_CONFIGURATION):
|
||||||
|
@ -238,7 +237,7 @@ int LibusbDevice::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
||||||
if (ret == LIBUSB_SUCCESS)
|
if (ret == LIBUSB_SUCCESS)
|
||||||
{
|
{
|
||||||
ClaimAllInterfaces(cmd->value);
|
ClaimAllInterfaces(cmd->value);
|
||||||
m_ios.EnqueueIPCReply(cmd->ios_request, cmd->length);
|
cmd->GetEmulationKernel().EnqueueIPCReply(cmd->ios_request, cmd->length);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -249,7 +248,7 @@ int LibusbDevice::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
||||||
libusb_fill_control_setup(buffer.get(), cmd->request_type, cmd->request, cmd->value, cmd->index,
|
libusb_fill_control_setup(buffer.get(), cmd->request_type, cmd->request, cmd->value, cmd->index,
|
||||||
cmd->length);
|
cmd->length);
|
||||||
|
|
||||||
auto& system = m_ios.GetSystem();
|
auto& system = cmd->GetEmulationKernel().GetSystem();
|
||||||
auto& memory = system.GetMemory();
|
auto& memory = system.GetMemory();
|
||||||
memory.CopyFromEmu(buffer.get() + LIBUSB_CONTROL_SETUP_SIZE, cmd->data_address, cmd->length);
|
memory.CopyFromEmu(buffer.get() + LIBUSB_CONTROL_SETUP_SIZE, cmd->data_address, cmd->length);
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,7 @@ namespace IOS::HLE::USB
|
||||||
class LibusbDevice final : public Device
|
class LibusbDevice final : public Device
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LibusbDevice(EmulationKernel& ios, libusb_device* device,
|
LibusbDevice(libusb_device* device, const libusb_device_descriptor& device_descriptor);
|
||||||
const libusb_device_descriptor& device_descriptor);
|
|
||||||
~LibusbDevice();
|
~LibusbDevice();
|
||||||
DeviceDescriptor GetDeviceDescriptor() const override;
|
DeviceDescriptor GetDeviceDescriptor() const override;
|
||||||
std::vector<ConfigDescriptor> GetConfigurations() const override;
|
std::vector<ConfigDescriptor> GetConfigurations() const override;
|
||||||
|
@ -46,8 +45,6 @@ public:
|
||||||
int SubmitTransfer(std::unique_ptr<IsoMessage> message) override;
|
int SubmitTransfer(std::unique_ptr<IsoMessage> message) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EmulationKernel& m_ios;
|
|
||||||
|
|
||||||
std::vector<LibusbUtils::ConfigDescriptor> m_config_descriptors;
|
std::vector<LibusbUtils::ConfigDescriptor> m_config_descriptors;
|
||||||
u16 m_vid = 0;
|
u16 m_vid = 0;
|
||||||
u16 m_pid = 0;
|
u16 m_pid = 0;
|
||||||
|
|
|
@ -26,10 +26,7 @@ OH0::OH0(EmulationKernel& ios, const std::string& device_name) : USBHost(ios, de
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
OH0::~OH0()
|
OH0::~OH0() = default;
|
||||||
{
|
|
||||||
m_scan_thread.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<IPCReply> OH0::Open(const OpenRequest& request)
|
std::optional<IPCReply> OH0::Open(const OpenRequest& request)
|
||||||
{
|
{
|
||||||
|
@ -76,11 +73,14 @@ std::optional<IPCReply> OH0::IOCtlV(const IOCtlVRequest& request)
|
||||||
|
|
||||||
void OH0::DoState(PointerWrap& p)
|
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.",
|
std::lock_guard lk(m_devices_mutex);
|
||||||
5000);
|
if (p.IsReadMode() && !m_devices.empty())
|
||||||
Core::DisplayMessage("If USB doesn't work properly, an emulation reset may be needed.", 5000);
|
{
|
||||||
|
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_insertion_hooks);
|
||||||
p.Do(m_removal_hooks);
|
p.Do(m_removal_hooks);
|
||||||
|
@ -209,12 +209,12 @@ std::optional<IPCReply> OH0::RegisterInsertionHookWithID(const IOCtlVRequest& re
|
||||||
auto& system = GetSystem();
|
auto& system = GetSystem();
|
||||||
auto& memory = system.GetMemory();
|
auto& memory = system.GetMemory();
|
||||||
|
|
||||||
std::lock_guard lock{m_hooks_mutex};
|
|
||||||
const u16 vid = memory.Read_U16(request.in_vectors[0].address);
|
const u16 vid = memory.Read_U16(request.in_vectors[0].address);
|
||||||
const u16 pid = memory.Read_U16(request.in_vectors[1].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;
|
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))
|
if (!trigger_only_for_new_device && HasDeviceWithVidPid(vid, pid))
|
||||||
return IPCReply(IPC_SUCCESS);
|
return IPCReply(IPC_SUCCESS);
|
||||||
|
std::lock_guard lock{m_hooks_mutex};
|
||||||
// TODO: figure out whether IOS allows more than one hook.
|
// TODO: figure out whether IOS allows more than one hook.
|
||||||
m_insertion_hooks.insert({{vid, pid}, request.address});
|
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.
|
// The output vector is overwritten with an ID to use with ioctl 31 for cancelling the hook.
|
||||||
|
@ -234,6 +234,7 @@ std::optional<IPCReply> OH0::RegisterClassChangeHook(const IOCtlVRequest& reques
|
||||||
|
|
||||||
bool OH0::HasDeviceWithVidPid(const u16 vid, const u16 pid) const
|
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 std::ranges::any_of(m_devices, [=](const auto& device) {
|
||||||
return device.second->GetVid() == vid && device.second->GetPid() == pid;
|
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<USB::Device> device)
|
void OH0::OnDeviceChange(const ChangeEvent event, std::shared_ptr<USB::Device> device)
|
||||||
{
|
{
|
||||||
std::lock_guard lk(m_devices_mutex);
|
|
||||||
if (event == ChangeEvent::Inserted)
|
if (event == ChangeEvent::Inserted)
|
||||||
TriggerHook(m_insertion_hooks, {device->GetVid(), device->GetPid()}, IPC_SUCCESS);
|
TriggerHook(m_insertion_hooks, {device->GetVid(), device->GetPid()}, IPC_SUCCESS);
|
||||||
else if (event == ChangeEvent::Removed)
|
else if (event == ChangeEvent::Removed)
|
||||||
|
|
|
@ -64,8 +64,6 @@ private:
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void TriggerHook(std::map<T, u32>& hooks, T value, ReturnCode return_value);
|
void TriggerHook(std::map<T, u32>& hooks, T value, ReturnCode return_value);
|
||||||
|
|
||||||
ScanThread& GetScanThread() override { return m_scan_thread; }
|
|
||||||
|
|
||||||
struct DeviceEntry
|
struct DeviceEntry
|
||||||
{
|
{
|
||||||
u32 unknown;
|
u32 unknown;
|
||||||
|
@ -79,7 +77,5 @@ private:
|
||||||
std::map<u64, u32> m_removal_hooks;
|
std::map<u64, u32> m_removal_hooks;
|
||||||
std::set<u64> m_opened_devices;
|
std::set<u64> m_opened_devices;
|
||||||
std::mutex m_hooks_mutex;
|
std::mutex m_hooks_mutex;
|
||||||
|
|
||||||
ScanThread m_scan_thread{this};
|
|
||||||
};
|
};
|
||||||
} // namespace IOS::HLE
|
} // namespace IOS::HLE
|
||||||
|
|
187
Source/Core/Core/IOS/USB/USBScanner.cpp
Normal file
187
Source/Core/Core/IOS/USB/USBScanner.cpp
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "Core/IOS/USB/USBScanner.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <ranges>
|
||||||
|
#include <set>
|
||||||
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#ifdef __LIBUSB__
|
||||||
|
#include <libusb.h>
|
||||||
|
#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<USB::LibusbDevice>(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<USB::SkylanderUSB>();
|
||||||
|
AddDevice(std::move(skylanderportal), new_devices);
|
||||||
|
}
|
||||||
|
if (Config::Get(Config::MAIN_EMULATE_INFINITY_BASE) && !NetPlay::IsNetPlayRunning())
|
||||||
|
{
|
||||||
|
auto infinity_base = std::make_unique<USB::InfinityUSB>();
|
||||||
|
AddDevice(std::move(infinity_base), new_devices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBScanner::AddDevice(std::unique_ptr<USB::Device> device, DeviceMap* new_devices)
|
||||||
|
{
|
||||||
|
(*new_devices)[device->GetId()] = std::move(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace IOS::HLE
|
61
Source/Core/Core/IOS/USB/USBScanner.h
Normal file
61
Source/Core/Core/IOS/USB/USBScanner.h
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright 2025 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <set>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#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<u64, std::shared_ptr<USB::Device>>;
|
||||||
|
|
||||||
|
~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<USB::Device> device, DeviceMap* new_devices);
|
||||||
|
|
||||||
|
DeviceMap m_devices;
|
||||||
|
mutable std::mutex m_devices_mutex;
|
||||||
|
|
||||||
|
std::set<USBHost*> 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
|
|
@ -27,10 +27,7 @@ USB_HIDv4::USB_HIDv4(EmulationKernel& ios, const std::string& device_name)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
USB_HIDv4::~USB_HIDv4()
|
USB_HIDv4::~USB_HIDv4() = default;
|
||||||
{
|
|
||||||
m_scan_thread.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<IPCReply> USB_HIDv4::IOCtl(const IOCtlRequest& request)
|
std::optional<IPCReply> USB_HIDv4::IOCtl(const IOCtlRequest& request)
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,7 +39,6 @@ private:
|
||||||
std::vector<u8> GetDeviceEntry(const USB::Device& device) const;
|
std::vector<u8> GetDeviceEntry(const USB::Device& device) const;
|
||||||
void OnDeviceChange(ChangeEvent, std::shared_ptr<USB::Device>) override;
|
void OnDeviceChange(ChangeEvent, std::shared_ptr<USB::Device>) override;
|
||||||
bool ShouldAddDevice(const USB::Device& device) const override;
|
bool ShouldAddDevice(const USB::Device& device) const override;
|
||||||
ScanThread& GetScanThread() override { return m_scan_thread; }
|
|
||||||
|
|
||||||
static constexpr u32 VERSION = 0x40001;
|
static constexpr u32 VERSION = 0x40001;
|
||||||
static constexpr u8 HID_CLASS = 0x03;
|
static constexpr u8 HID_CLASS = 0x03;
|
||||||
|
@ -53,7 +52,5 @@ private:
|
||||||
// IOS device IDs <=> USB device IDs
|
// IOS device IDs <=> USB device IDs
|
||||||
std::map<s32, u64> m_ios_ids;
|
std::map<s32, u64> m_ios_ids;
|
||||||
std::map<u64, s32> m_device_ids;
|
std::map<u64, s32> m_device_ids;
|
||||||
|
|
||||||
ScanThread m_scan_thread{this};
|
|
||||||
};
|
};
|
||||||
} // namespace IOS::HLE
|
} // namespace IOS::HLE
|
||||||
|
|
|
@ -18,10 +18,7 @@ namespace IOS::HLE
|
||||||
{
|
{
|
||||||
constexpr u32 USBV5_VERSION = 0x50001;
|
constexpr u32 USBV5_VERSION = 0x50001;
|
||||||
|
|
||||||
USB_HIDv5::~USB_HIDv5()
|
USB_HIDv5::~USB_HIDv5() = default;
|
||||||
{
|
|
||||||
m_scan_thread.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<IPCReply> USB_HIDv5::IOCtl(const IOCtlRequest& request)
|
std::optional<IPCReply> USB_HIDv5::IOCtl(const IOCtlRequest& request)
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,15 +27,11 @@ private:
|
||||||
bool ShouldAddDevice(const USB::Device& device) const override;
|
bool ShouldAddDevice(const USB::Device& device) const override;
|
||||||
bool HasInterfaceNumberInIDs() const override { return true; }
|
bool HasInterfaceNumberInIDs() const override { return true; }
|
||||||
|
|
||||||
ScanThread& GetScanThread() override { return m_scan_thread; }
|
|
||||||
|
|
||||||
struct AdditionalDeviceData
|
struct AdditionalDeviceData
|
||||||
{
|
{
|
||||||
u8 interrupt_in_endpoint = 0;
|
u8 interrupt_in_endpoint = 0;
|
||||||
u8 interrupt_out_endpoint = 0;
|
u8 interrupt_out_endpoint = 0;
|
||||||
};
|
};
|
||||||
std::array<AdditionalDeviceData, 32> m_additional_device_data{};
|
std::array<AdditionalDeviceData, 32> m_additional_device_data{};
|
||||||
|
|
||||||
ScanThread m_scan_thread{this};
|
|
||||||
};
|
};
|
||||||
} // namespace IOS::HLE
|
} // namespace IOS::HLE
|
||||||
|
|
|
@ -18,10 +18,7 @@ namespace IOS::HLE
|
||||||
{
|
{
|
||||||
constexpr u32 USBV5_VERSION = 0x50001;
|
constexpr u32 USBV5_VERSION = 0x50001;
|
||||||
|
|
||||||
USB_VEN::~USB_VEN()
|
USB_VEN::~USB_VEN() = default;
|
||||||
{
|
|
||||||
m_scan_thread.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<IPCReply> USB_VEN::IOCtl(const IOCtlRequest& request)
|
std::optional<IPCReply> USB_VEN::IOCtl(const IOCtlRequest& request)
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,9 +25,5 @@ private:
|
||||||
|
|
||||||
s32 SubmitTransfer(USB::Device& device, const IOCtlVRequest& ioctlv);
|
s32 SubmitTransfer(USB::Device& device, const IOCtlVRequest& ioctlv);
|
||||||
bool HasInterfaceNumberInIDs() const override { return false; }
|
bool HasInterfaceNumberInIDs() const override { return false; }
|
||||||
|
|
||||||
ScanThread& GetScanThread() override { return m_scan_thread; }
|
|
||||||
|
|
||||||
ScanThread m_scan_thread{this};
|
|
||||||
};
|
};
|
||||||
} // namespace IOS::HLE
|
} // namespace IOS::HLE
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#include "Core/PowerPC/PowerPC.h"
|
#include "Core/PowerPC/PowerPC.h"
|
||||||
#include "IOS/USB/Emulated/Infinity.h"
|
#include "IOS/USB/Emulated/Infinity.h"
|
||||||
#include "IOS/USB/Emulated/Skylanders/Skylander.h"
|
#include "IOS/USB/Emulated/Skylanders/Skylander.h"
|
||||||
|
#include "IOS/USB/USBScanner.h"
|
||||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||||
#include "VideoCommon/CommandProcessor.h"
|
#include "VideoCommon/CommandProcessor.h"
|
||||||
#include "VideoCommon/Fifo.h"
|
#include "VideoCommon/Fifo.h"
|
||||||
|
@ -89,6 +90,7 @@ struct System::Impl
|
||||||
SerialInterface::SerialInterfaceManager m_serial_interface;
|
SerialInterface::SerialInterfaceManager m_serial_interface;
|
||||||
Sram m_sram;
|
Sram m_sram;
|
||||||
SystemTimers::SystemTimersManager m_system_timers;
|
SystemTimers::SystemTimersManager m_system_timers;
|
||||||
|
IOS::HLE::USBScanner m_usb_scanner;
|
||||||
VertexShaderManager m_vertex_shader_manager;
|
VertexShaderManager m_vertex_shader_manager;
|
||||||
XFStateManager m_xf_state_manager;
|
XFStateManager m_xf_state_manager;
|
||||||
VideoInterface::VideoInterfaceManager m_video_interface;
|
VideoInterface::VideoInterfaceManager m_video_interface;
|
||||||
|
@ -313,6 +315,11 @@ SystemTimers::SystemTimersManager& System::GetSystemTimers() const
|
||||||
return m_impl->m_system_timers;
|
return m_impl->m_system_timers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IOS::HLE::USBScanner& System::GetUSBScanner() const
|
||||||
|
{
|
||||||
|
return m_impl->m_usb_scanner;
|
||||||
|
}
|
||||||
|
|
||||||
VertexShaderManager& System::GetVertexShaderManager() const
|
VertexShaderManager& System::GetVertexShaderManager() const
|
||||||
{
|
{
|
||||||
return m_impl->m_vertex_shader_manager;
|
return m_impl->m_vertex_shader_manager;
|
||||||
|
|
|
@ -56,7 +56,8 @@ class GPFifoManager;
|
||||||
namespace IOS::HLE
|
namespace IOS::HLE
|
||||||
{
|
{
|
||||||
class EmulationKernel;
|
class EmulationKernel;
|
||||||
}
|
class USBScanner;
|
||||||
|
} // namespace IOS::HLE
|
||||||
namespace HSP
|
namespace HSP
|
||||||
{
|
{
|
||||||
class HSPManager;
|
class HSPManager;
|
||||||
|
@ -192,6 +193,7 @@ public:
|
||||||
SerialInterface::SerialInterfaceManager& GetSerialInterface() const;
|
SerialInterface::SerialInterfaceManager& GetSerialInterface() const;
|
||||||
Sram& GetSRAM() const;
|
Sram& GetSRAM() const;
|
||||||
SystemTimers::SystemTimersManager& GetSystemTimers() const;
|
SystemTimers::SystemTimersManager& GetSystemTimers() const;
|
||||||
|
IOS::HLE::USBScanner& GetUSBScanner() const;
|
||||||
VertexShaderManager& GetVertexShaderManager() const;
|
VertexShaderManager& GetVertexShaderManager() const;
|
||||||
XFStateManager& GetXFStateManager() const;
|
XFStateManager& GetXFStateManager() const;
|
||||||
VideoInterface::VideoInterfaceManager& GetVideoInterface() const;
|
VideoInterface::VideoInterfaceManager& GetVideoInterface() const;
|
||||||
|
|
|
@ -171,6 +171,7 @@
|
||||||
<ClInclude Include="Common\UPnP.h" />
|
<ClInclude Include="Common\UPnP.h" />
|
||||||
<ClInclude Include="Common\VariantUtil.h" />
|
<ClInclude Include="Common\VariantUtil.h" />
|
||||||
<ClInclude Include="Common\Version.h" />
|
<ClInclude Include="Common\Version.h" />
|
||||||
|
<ClInclude Include="Common\WaitableFlag.h" />
|
||||||
<ClInclude Include="Common\WindowsRegistry.h" />
|
<ClInclude Include="Common\WindowsRegistry.h" />
|
||||||
<ClInclude Include="Common\WindowSystemInfo.h" />
|
<ClInclude Include="Common\WindowSystemInfo.h" />
|
||||||
<ClInclude Include="Common\WorkQueueThread.h" />
|
<ClInclude Include="Common\WorkQueueThread.h" />
|
||||||
|
@ -413,6 +414,7 @@
|
||||||
<ClInclude Include="Core\IOS\USB\USB_HID\HIDv5.h" />
|
<ClInclude Include="Core\IOS\USB\USB_HID\HIDv5.h" />
|
||||||
<ClInclude Include="Core\IOS\USB\USB_KBD.h" />
|
<ClInclude Include="Core\IOS\USB\USB_KBD.h" />
|
||||||
<ClInclude Include="Core\IOS\USB\USB_VEN\VEN.h" />
|
<ClInclude Include="Core\IOS\USB\USB_VEN\VEN.h" />
|
||||||
|
<ClInclude Include="Core\IOS\USB\USBScanner.h" />
|
||||||
<ClInclude Include="Core\IOS\USB\USBV0.h" />
|
<ClInclude Include="Core\IOS\USB\USBV0.h" />
|
||||||
<ClInclude Include="Core\IOS\USB\USBV4.h" />
|
<ClInclude Include="Core\IOS\USB\USBV4.h" />
|
||||||
<ClInclude Include="Core\IOS\USB\USBV5.h" />
|
<ClInclude Include="Core\IOS\USB\USBV5.h" />
|
||||||
|
@ -1077,6 +1079,7 @@
|
||||||
<ClCompile Include="Core\IOS\USB\USB_HID\HIDv5.cpp" />
|
<ClCompile Include="Core\IOS\USB\USB_HID\HIDv5.cpp" />
|
||||||
<ClCompile Include="Core\IOS\USB\USB_KBD.cpp" />
|
<ClCompile Include="Core\IOS\USB\USB_KBD.cpp" />
|
||||||
<ClCompile Include="Core\IOS\USB\USB_VEN\VEN.cpp" />
|
<ClCompile Include="Core\IOS\USB\USB_VEN\VEN.cpp" />
|
||||||
|
<ClCompile Include="Core\IOS\USB\USBScanner.cpp" />
|
||||||
<ClCompile Include="Core\IOS\USB\USBV0.cpp" />
|
<ClCompile Include="Core\IOS\USB\USBV0.cpp" />
|
||||||
<ClCompile Include="Core\IOS\USB\USBV4.cpp" />
|
<ClCompile Include="Core\IOS\USB\USBV4.cpp" />
|
||||||
<ClCompile Include="Core\IOS\USB\USBV5.cpp" />
|
<ClCompile Include="Core\IOS\USB\USBV5.cpp" />
|
||||||
|
|
Loading…
Add table
Reference in a new issue