Use a single libusb context

libusb on Windows is limited to only a single context. Trying to open
more than one can cause device enumerations to fail randomly.

libusb is thread-safe and we don't use the manual polling support (with
`poll()`) so this should be safe.
This commit is contained in:
Léo Lam 2017-01-15 20:50:26 +01:00
parent 73e55ccf44
commit c8a6dc6c23
11 changed files with 103 additions and 41 deletions

View File

@ -34,6 +34,11 @@ set(SRCS Analytics.cpp
Crypto/ec.cpp Crypto/ec.cpp
Logging/LogManager.cpp) Logging/LogManager.cpp)
if(LIBUSB_FOUND)
set(LIBS ${LIBS} ${LIBUSB_LIBRARIES})
set(SRCS ${SRCS} LibusbContext.cpp)
endif(LIBUSB_FOUND)
if(ANDROID) if(ANDROID)
set(SRCS ${SRCS} set(SRCS ${SRCS}
Logging/ConsoleListenerDroid.cpp) Logging/ConsoleListenerDroid.cpp)

View File

@ -113,6 +113,7 @@
<ClInclude Include="Hash.h" /> <ClInclude Include="Hash.h" />
<ClInclude Include="IniFile.h" /> <ClInclude Include="IniFile.h" />
<ClInclude Include="JitRegister.h" /> <ClInclude Include="JitRegister.h" />
<ClInclude Include="LibusbContext.h" />
<ClInclude Include="LinearDiskCache.h" /> <ClInclude Include="LinearDiskCache.h" />
<ClInclude Include="MathUtil.h" /> <ClInclude Include="MathUtil.h" />
<ClInclude Include="MD5.h" /> <ClInclude Include="MD5.h" />
@ -159,6 +160,9 @@
<ClCompile Include="Hash.cpp" /> <ClCompile Include="Hash.cpp" />
<ClCompile Include="IniFile.cpp" /> <ClCompile Include="IniFile.cpp" />
<ClCompile Include="JitRegister.cpp" /> <ClCompile Include="JitRegister.cpp" />
<ClCompile Include="LibusbContext.cpp">
<DisableSpecificWarnings>4200;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>
<ClCompile Include="Logging\ConsoleListenerWin.cpp" /> <ClCompile Include="Logging\ConsoleListenerWin.cpp" />
<ClCompile Include="MathUtil.cpp" /> <ClCompile Include="MathUtil.cpp" />
<ClCompile Include="MD5.cpp" /> <ClCompile Include="MD5.cpp" />

View File

@ -45,6 +45,7 @@
<ClInclude Include="FPURoundMode.h" /> <ClInclude Include="FPURoundMode.h" />
<ClInclude Include="Hash.h" /> <ClInclude Include="Hash.h" />
<ClInclude Include="IniFile.h" /> <ClInclude Include="IniFile.h" />
<ClInclude Include="LibusbContext.h" />
<ClInclude Include="LinearDiskCache.h" /> <ClInclude Include="LinearDiskCache.h" />
<ClInclude Include="MathUtil.h" /> <ClInclude Include="MathUtil.h" />
<ClInclude Include="MemArena.h" /> <ClInclude Include="MemArena.h" />
@ -236,6 +237,7 @@
<ClCompile Include="FileUtil.cpp" /> <ClCompile Include="FileUtil.cpp" />
<ClCompile Include="Hash.cpp" /> <ClCompile Include="Hash.cpp" />
<ClCompile Include="IniFile.cpp" /> <ClCompile Include="IniFile.cpp" />
<ClCompile Include="LibusbContext.cpp" />
<ClCompile Include="MathUtil.cpp" /> <ClCompile Include="MathUtil.cpp" />
<ClCompile Include="MemArena.cpp" /> <ClCompile Include="MemArena.cpp" />
<ClCompile Include="MemoryUtil.cpp" /> <ClCompile Include="MemoryUtil.cpp" />

View File

@ -0,0 +1,46 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <libusb.h>
#include <memory>
#include <mutex>
#include "Common/LibusbContext.h"
#include "Common/MsgHandler.h"
namespace LibusbContext
{
static std::shared_ptr<libusb_context> s_libusb_context;
static std::once_flag s_context_initialized;
static libusb_context* Create()
{
libusb_context* context;
const int ret = libusb_init(&context);
if (ret < LIBUSB_SUCCESS)
{
bool is_windows = false;
#ifdef _WIN32
is_windows = true;
#endif
if (is_windows && ret == LIBUSB_ERROR_NOT_FOUND)
PanicAlertT("Failed to initialize libusb because usbdk is not installed.");
else
PanicAlertT("Failed to initialize libusb: %s", libusb_error_name(ret));
return nullptr;
}
return context;
}
std::shared_ptr<libusb_context> Get()
{
std::call_once(s_context_initialized, []() {
s_libusb_context.reset(Create(), [](auto* context) {
if (context != nullptr)
libusb_exit(context);
});
});
return s_libusb_context;
}
}

View File

@ -0,0 +1,15 @@
// Copyright 2017 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <memory>
struct libusb_context;
namespace LibusbContext
{
// libusb on Windows is limited to only a single context. Trying to open more
// than one can cause issues with device enumerations.
// libusb is thread-safe so this context can be safely used from different threads.
std::shared_ptr<libusb_context> Get();
}

View File

@ -16,6 +16,7 @@
#include "Common/Assert.h" #include "Common/Assert.h"
#include "Common/ChunkFile.h" #include "Common/ChunkFile.h"
#include "Common/CommonFuncs.h" #include "Common/CommonFuncs.h"
#include "Common/LibusbContext.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/MsgHandler.h" #include "Common/MsgHandler.h"
#include "Common/Network.h" #include "Common/Network.h"
@ -59,8 +60,8 @@ namespace Device
BluetoothReal::BluetoothReal(u32 device_id, const std::string& device_name) BluetoothReal::BluetoothReal(u32 device_id, const std::string& device_name)
: BluetoothBase(device_id, device_name) : BluetoothBase(device_id, device_name)
{ {
const int ret = libusb_init(&m_libusb_context); m_libusb_context = LibusbContext::Get();
_assert_msg_(IOS_WIIMOTE, ret == 0, "Failed to init libusb."); _assert_msg_(IOS_WIIMOTE, m_libusb_context, "Failed to init libusb.");
LoadLinkKeys(); LoadLinkKeys();
} }
@ -78,15 +79,13 @@ BluetoothReal::~BluetoothReal()
libusb_unref_device(m_device); libusb_unref_device(m_device);
} }
libusb_exit(m_libusb_context);
SaveLinkKeys(); SaveLinkKeys();
} }
ReturnCode BluetoothReal::Open(const OpenRequest& request) ReturnCode BluetoothReal::Open(const OpenRequest& request)
{ {
libusb_device** list; libusb_device** list;
const ssize_t cnt = libusb_get_device_list(m_libusb_context, &list); const ssize_t cnt = libusb_get_device_list(m_libusb_context.get(), &list);
_dbg_assert_msg_(IOS, cnt > 0, "Couldn't get device list"); _dbg_assert_msg_(IOS, cnt > 0, "Couldn't get device list");
for (ssize_t i = 0; i < cnt; ++i) for (ssize_t i = 0; i < cnt; ++i)
{ {
@ -601,7 +600,7 @@ void BluetoothReal::TransferThread()
Common::SetCurrentThreadName("BT USB Thread"); Common::SetCurrentThreadName("BT USB Thread");
while (m_thread_running.IsSet()) while (m_thread_running.IsSet())
{ {
libusb_handle_events_completed(m_libusb_context, nullptr); libusb_handle_events_completed(m_libusb_context.get(), nullptr);
} }
} }

View File

@ -76,7 +76,7 @@ private:
libusb_device* m_device = nullptr; libusb_device* m_device = nullptr;
libusb_device_handle* m_handle = nullptr; libusb_device_handle* m_handle = nullptr;
libusb_context* m_libusb_context = nullptr; std::shared_ptr<libusb_context> m_libusb_context;
Common::Flag m_thread_running; Common::Flag m_thread_running;
std::thread m_thread; std::thread m_thread;

View File

@ -13,6 +13,7 @@
#include "Common/Assert.h" #include "Common/Assert.h"
#include "Common/ChunkFile.h" #include "Common/ChunkFile.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/LibusbContext.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/Thread.h" #include "Common/Thread.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
@ -30,16 +31,8 @@ namespace Device
USBHost::USBHost(u32 device_id, const std::string& device_name) : Device(device_id, device_name) USBHost::USBHost(u32 device_id, const std::string& device_name) : Device(device_id, device_name)
{ {
#ifdef __LIBUSB__ #ifdef __LIBUSB__
const int ret = libusb_init(&m_libusb_context); m_libusb_context = LibusbContext::Get();
_assert_msg_(WII_IPC_USB, ret == 0, "Failed to init libusb."); _assert_msg_(IOS_USB, m_libusb_context, "Failed to init libusb.");
libusb_set_debug(m_libusb_context, LIBUSB_LOG_LEVEL_WARNING);
#endif
}
USBHost::~USBHost()
{
#ifdef __LIBUSB__
libusb_exit(m_libusb_context);
#endif #endif
} }
@ -125,7 +118,7 @@ bool USBHost::AddNewDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks
{ {
#ifdef __LIBUSB__ #ifdef __LIBUSB__
libusb_device** list; libusb_device** list;
const ssize_t count = libusb_get_device_list(m_libusb_context, &list); const ssize_t count = libusb_get_device_list(m_libusb_context.get(), &list);
if (count < 0) if (count < 0)
{ {
WARN_LOG(IOS_USB, "Failed to get device list: %s", libusb_error_name(static_cast<int>(count))); WARN_LOG(IOS_USB, "Failed to get device list: %s", libusb_error_name(static_cast<int>(count)));
@ -210,7 +203,7 @@ void USBHost::StartThreads()
while (m_event_thread_running.IsSet()) while (m_event_thread_running.IsSet())
{ {
static timeval tv = {0, 50000}; static timeval tv = {0, 50000};
libusb_handle_events_timeout_completed(m_libusb_context, &tv, nullptr); libusb_handle_events_timeout_completed(m_libusb_context.get(), &tv, nullptr);
} }
}); });
} }

View File

@ -34,7 +34,7 @@ class USBHost : public Device
{ {
public: public:
USBHost(u32 device_id, const std::string& device_name); USBHost(u32 device_id, const std::string& device_name);
virtual ~USBHost(); virtual ~USBHost() = default;
ReturnCode Open(const OpenRequest& request) override; ReturnCode Open(const OpenRequest& request) override;
@ -71,7 +71,7 @@ private:
void DispatchHooks(const DeviceChangeHooks& hooks); void DispatchHooks(const DeviceChangeHooks& hooks);
#ifdef __LIBUSB__ #ifdef __LIBUSB__
libusb_context* m_libusb_context = nullptr; std::shared_ptr<libusb_context> m_libusb_context;
// Event thread for libusb // Event thread for libusb
Common::Flag m_event_thread_running; Common::Flag m_event_thread_running;
std::thread m_event_thread; std::thread m_event_thread;

View File

@ -4,9 +4,11 @@
#include <algorithm> #include <algorithm>
#include <libusb.h> #include <libusb.h>
#include <memory>
#include <mutex> #include <mutex>
#include "Common/Flag.h" #include "Common/Flag.h"
#include "Common/LibusbContext.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/Thread.h" #include "Common/Thread.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
@ -50,7 +52,7 @@ static Common::Flag s_adapter_detect_thread_running;
static std::function<void(void)> s_detect_callback; static std::function<void(void)> s_detect_callback;
static bool s_libusb_driver_not_supported = false; static bool s_libusb_driver_not_supported = false;
static libusb_context* s_libusb_context = nullptr; static std::shared_ptr<libusb_context> s_libusb_context;
#if defined(__FreeBSD__) && __FreeBSD__ >= 11 #if defined(__FreeBSD__) && __FreeBSD__ >= 11
static bool s_libusb_hotplug_enabled = true; static bool s_libusb_hotplug_enabled = true;
#else #else
@ -116,8 +118,8 @@ static void ScanThreadFunc()
if (s_libusb_hotplug_enabled) if (s_libusb_hotplug_enabled)
{ {
if (libusb_hotplug_register_callback( if (libusb_hotplug_register_callback(
s_libusb_context, (libusb_hotplug_event)(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | s_libusb_context.get(), (libusb_hotplug_event)(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |
LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT), LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT),
LIBUSB_HOTPLUG_ENUMERATE, 0x057e, 0x0337, LIBUSB_HOTPLUG_MATCH_ANY, HotplugCallback, LIBUSB_HOTPLUG_ENUMERATE, 0x057e, 0x0337, LIBUSB_HOTPLUG_MATCH_ANY, HotplugCallback,
nullptr, &s_hotplug_handle) != LIBUSB_SUCCESS) nullptr, &s_hotplug_handle) != LIBUSB_SUCCESS)
s_libusb_hotplug_enabled = false; s_libusb_hotplug_enabled = false;
@ -131,7 +133,7 @@ static void ScanThreadFunc()
if (s_libusb_hotplug_enabled) if (s_libusb_hotplug_enabled)
{ {
static timeval tv = {0, 500000}; static timeval tv = {0, 500000};
libusb_handle_events_timeout(s_libusb_context, &tv); libusb_handle_events_timeout(s_libusb_context.get(), &tv);
} }
else else
{ {
@ -168,11 +170,9 @@ void Init()
s_libusb_driver_not_supported = false; s_libusb_driver_not_supported = false;
int ret = libusb_init(&s_libusb_context); s_libusb_context = LibusbContext::Get();
if (!s_libusb_context)
if (ret)
{ {
ERROR_LOG(SERIALINTERFACE, "libusb_init failed with error: %d", ret);
s_libusb_driver_not_supported = true; s_libusb_driver_not_supported = true;
Shutdown(); Shutdown();
} }
@ -203,7 +203,7 @@ void StopScanThread()
static void Setup() static void Setup()
{ {
libusb_device** list; libusb_device** list;
ssize_t cnt = libusb_get_device_list(s_libusb_context, &list); ssize_t cnt = libusb_get_device_list(s_libusb_context.get(), &list);
for (int i = 0; i < MAX_SI_CHANNELS; i++) for (int i = 0; i < MAX_SI_CHANNELS; i++)
{ {
@ -335,16 +335,11 @@ void Shutdown()
StopScanThread(); StopScanThread();
#if defined(LIBUSB_API_VERSION) && LIBUSB_API_VERSION >= 0x01000102 #if defined(LIBUSB_API_VERSION) && LIBUSB_API_VERSION >= 0x01000102
if (s_libusb_hotplug_enabled) if (s_libusb_hotplug_enabled)
libusb_hotplug_deregister_callback(s_libusb_context, s_hotplug_handle); libusb_hotplug_deregister_callback(s_libusb_context.get(), s_hotplug_handle);
#endif #endif
Reset(); Reset();
if (s_libusb_context) s_libusb_context.reset();
{
libusb_exit(s_libusb_context);
s_libusb_context = nullptr;
}
s_libusb_driver_not_supported = false; s_libusb_driver_not_supported = false;
} }

View File

@ -2,8 +2,11 @@
// Licensed under GPLv2+ // Licensed under GPLv2+
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <memory>
#ifdef __LIBUSB__ #ifdef __LIBUSB__
#include <libusb.h> #include <libusb.h>
#include "Common/LibusbContext.h"
#endif #endif
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
@ -11,7 +14,7 @@
#include "UICommon/USBUtils.h" #include "UICommon/USBUtils.h"
#ifdef __LIBUSB__ #ifdef __LIBUSB__
static libusb_context* s_libusb_context; static std::shared_ptr<libusb_context> s_libusb_context;
#endif #endif
// Because opening and getting the device name from devices is slow, especially on Windows // Because opening and getting the device name from devices is slow, especially on Windows
@ -36,14 +39,14 @@ namespace USBUtils
void Init() void Init()
{ {
#ifdef __LIBUSB__ #ifdef __LIBUSB__
libusb_init(&s_libusb_context); s_libusb_context = LibusbContext::Get();
#endif #endif
} }
void Shutdown() void Shutdown()
{ {
#ifdef __LIBUSB__ #ifdef __LIBUSB__
libusb_exit(s_libusb_context); s_libusb_context = nullptr;
#endif #endif
} }
@ -56,7 +59,7 @@ std::map<std::pair<u16, u16>, std::string> GetInsertedDevices()
return devices; return devices;
libusb_device** list; libusb_device** list;
const ssize_t cnt = libusb_get_device_list(s_libusb_context, &list); const ssize_t cnt = libusb_get_device_list(s_libusb_context.get(), &list);
for (ssize_t i = 0; i < cnt; ++i) for (ssize_t i = 0; i < cnt; ++i)
{ {
libusb_device_descriptor descr; libusb_device_descriptor descr;