mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
BTReal: Improvements:
Separate LibUSB logic into LibUSBBluetoothAdapter class. Submit transfers on thread with proper timing. Throttle before ACL input for reduced input latency. Immediately send IPC replies for outgoing data. Continuously submit libusb transfers to fill HCI/ACL input queues. Simplify endpoint handling and state saving. Other cleanups.
This commit is contained in:
@@ -695,6 +695,8 @@ if(TARGET LibUSB::LibUSB)
|
||||
IOS/USB/LibusbDevice.h
|
||||
IOS/USB/Bluetooth/BTReal.cpp
|
||||
IOS/USB/Bluetooth/BTReal.h
|
||||
IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp
|
||||
IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,25 +6,22 @@
|
||||
#if defined(__LIBUSB__)
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Flag.h"
|
||||
#include "Common/Timer.h"
|
||||
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "Core/IOS/USB/Bluetooth/BTBase.h"
|
||||
#include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h"
|
||||
#include "Core/IOS/USB/Bluetooth/hci.h"
|
||||
#include "Core/IOS/USB/USBV0.h"
|
||||
#include "Core/LibusbUtils.h"
|
||||
|
||||
class PointerWrap;
|
||||
struct libusb_device;
|
||||
struct libusb_device_handle;
|
||||
struct libusb_device_descriptor;
|
||||
struct libusb_transfer;
|
||||
|
||||
namespace IOS::HLE
|
||||
{
|
||||
@@ -49,87 +46,55 @@ public:
|
||||
std::optional<IPCReply> Open(const OpenRequest& request) override;
|
||||
std::optional<IPCReply> Close(u32 fd) override;
|
||||
std::optional<IPCReply> IOCtlV(const IOCtlVRequest& request) override;
|
||||
void Update() override;
|
||||
|
||||
void DoState(PointerWrap& p) override;
|
||||
void UpdateSyncButtonState(bool is_held) override;
|
||||
void TriggerSyncButtonPressedEvent() override;
|
||||
void TriggerSyncButtonHeldEvent() override;
|
||||
|
||||
void HandleCtrlTransfer(libusb_transfer* finished_transfer);
|
||||
void HandleBulkOrIntrTransfer(libusb_transfer* finished_transfer);
|
||||
|
||||
static bool IsConfiguredBluetoothDevice(u16 vid, u16 pid);
|
||||
static bool HasConfiguredBluetoothDevice();
|
||||
|
||||
struct BluetoothDeviceInfo
|
||||
{
|
||||
u16 vid;
|
||||
u16 pid;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
static std::vector<BluetoothDeviceInfo> ListDevices();
|
||||
|
||||
private:
|
||||
static constexpr u8 INTERFACE = 0x00;
|
||||
// Arbitrarily chosen value that allows emulated software to send commands often enough
|
||||
// so that the sync button event is triggered at least every 200ms.
|
||||
// Ideally this should be equal to 0, so we don't trigger unnecessary libusb transfers.
|
||||
static constexpr u32 TIMEOUT = 200;
|
||||
using BufferType = LibUSBBluetoothAdapter::BufferType;
|
||||
|
||||
std::unique_ptr<LibUSBBluetoothAdapter> m_lib_usb_bt_adapter;
|
||||
|
||||
static constexpr u32 SYNC_BUTTON_HOLD_MS_TO_RESET = 10000;
|
||||
|
||||
std::atomic<SyncButtonState> m_sync_button_state{SyncButtonState::Unpressed};
|
||||
Common::Timer m_sync_button_held_timer;
|
||||
|
||||
std::string m_last_open_error;
|
||||
|
||||
LibusbUtils::Context m_context;
|
||||
libusb_device* m_device = nullptr;
|
||||
libusb_device_handle* m_handle = nullptr;
|
||||
|
||||
std::mutex m_transfers_mutex;
|
||||
struct PendingTransfer
|
||||
{
|
||||
PendingTransfer(std::unique_ptr<USB::TransferCommand> command_, std::unique_ptr<u8[]> buffer_)
|
||||
: command(std::move(command_)), buffer(std::move(buffer_))
|
||||
{
|
||||
}
|
||||
std::unique_ptr<USB::TransferCommand> command;
|
||||
std::unique_ptr<u8[]> buffer;
|
||||
};
|
||||
std::map<libusb_transfer*, PendingTransfer> m_current_transfers;
|
||||
|
||||
// Set when we received a command to which we need to fake a reply
|
||||
Common::Flag m_fake_read_buffer_size_reply;
|
||||
Common::Flag m_fake_vendor_command_reply;
|
||||
u16 m_fake_vendor_command_reply_opcode;
|
||||
// Enqueued when we observe a command to which we need to fake a reply.
|
||||
std::queue<std::function<void(USB::V0IntrMessage&)>> m_fake_replies;
|
||||
|
||||
// This stores the address of paired devices and associated link keys.
|
||||
// It is needed because some adapters forget all stored link keys when they are reset,
|
||||
// which breaks pairings because the Wii relies on the Bluetooth module to remember them.
|
||||
std::map<bdaddr_t, linkkey_t> m_link_keys;
|
||||
Common::Flag m_need_reset_keys;
|
||||
|
||||
// This flag is set when a libusb transfer failed (for reasons other than timing out)
|
||||
// and we showed an OSD message about it.
|
||||
Common::Flag m_showed_failed_transfer;
|
||||
// Endpoints provided by the emulated software.
|
||||
std::unique_ptr<USB::V0IntrMessage> m_hci_endpoint;
|
||||
std::unique_ptr<USB::V0BulkMessage> m_acl_endpoint;
|
||||
|
||||
bool m_is_wii_bt_module = false;
|
||||
// Used for proper Bluetooth packet timing, especially for Wii remote speaker data.
|
||||
TimePoint GetTargetTime() const;
|
||||
|
||||
void TryToFillHCIEndpoint();
|
||||
void TryToFillACLEndpoint();
|
||||
|
||||
[[nodiscard]] BufferType ProcessHCIEvent(BufferType buffer);
|
||||
|
||||
void WaitForHCICommandComplete(u16 opcode);
|
||||
void SendHCIResetCommand();
|
||||
void SendHCIDeleteLinkKeyCommand();
|
||||
bool SendHCIStoreLinkKeyCommand();
|
||||
void FakeVendorCommandReply(USB::V0IntrMessage& ctrl);
|
||||
void FakeReadBufferSizeReply(USB::V0IntrMessage& ctrl);
|
||||
void FakeSyncButtonEvent(USB::V0IntrMessage& ctrl, const u8* payload, u8 size);
|
||||
|
||||
void FakeVendorCommandReply(u16 opcode, USB::V0IntrMessage& ctrl);
|
||||
|
||||
void FakeSyncButtonEvent(USB::V0IntrMessage& ctrl, std::span<const u8> payload);
|
||||
void FakeSyncButtonPressedEvent(USB::V0IntrMessage& ctrl);
|
||||
void FakeSyncButtonHeldEvent(USB::V0IntrMessage& ctrl);
|
||||
|
||||
void LoadLinkKeys();
|
||||
void SaveLinkKeys();
|
||||
|
||||
bool OpenDevice(const libusb_device_descriptor& device_descriptor, libusb_device* device);
|
||||
};
|
||||
} // namespace IOS::HLE
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,157 @@
|
||||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include "Common/Buffer.h"
|
||||
#include "Common/Timer.h"
|
||||
#include "Common/WorkQueueThread.h"
|
||||
|
||||
#include "Core/LibusbUtils.h"
|
||||
|
||||
struct libusb_device_handle;
|
||||
struct libusb_device_descriptor;
|
||||
struct libusb_transfer;
|
||||
|
||||
class LibUSBBluetoothAdapter
|
||||
{
|
||||
public:
|
||||
using BufferType = Common::UniqueBuffer<u8>;
|
||||
|
||||
struct BluetoothDeviceInfo
|
||||
{
|
||||
u16 vid;
|
||||
u16 pid;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
static std::vector<BluetoothDeviceInfo> ListDevices();
|
||||
static bool IsConfiguredBluetoothDevice(u16 vid, u16 pid);
|
||||
static bool HasConfiguredBluetoothDevice();
|
||||
|
||||
// Public interface is intended to be used by a single thread.
|
||||
|
||||
LibUSBBluetoothAdapter();
|
||||
~LibUSBBluetoothAdapter();
|
||||
|
||||
// Return a packet at the front of the queue, or an empty buffer when nothing is queued.
|
||||
// ReceiveHCIEvent is expected to be called regularly to do some internal processing.
|
||||
[[nodiscard]] BufferType ReceiveHCIEvent();
|
||||
[[nodiscard]] BufferType ReceiveACLData();
|
||||
|
||||
// Schedule a transfer to submit at a specific time.
|
||||
void ScheduleBulkTransfer(u8 endpoint, std::span<const u8> data, TimePoint target_time);
|
||||
void ScheduleControlTransfer(u8 type, u8 request, u8 value, u8 index, std::span<const u8> data,
|
||||
TimePoint target_time);
|
||||
|
||||
// Schedule a transfer to be submitted as soon as possible.
|
||||
void SendControlTransfer(std::span<const u8> data);
|
||||
|
||||
bool IsWiiBTModule() const;
|
||||
|
||||
private:
|
||||
// Inputs will be dropped when queue is full.
|
||||
static constexpr std::size_t MAX_INPUT_QUEUE_SIZE = 100;
|
||||
|
||||
static constexpr u8 INTERFACE = 0x00;
|
||||
|
||||
std::string m_last_open_error;
|
||||
|
||||
LibusbUtils::Context m_context;
|
||||
libusb_device_handle* m_handle = nullptr;
|
||||
|
||||
// Protects run-flag and Push'ing to the completed transfer queue.
|
||||
std::mutex m_transfers_mutex;
|
||||
|
||||
bool m_run_input_transfers = true;
|
||||
|
||||
// callback/worker threads push transfers here that are ready for clean up.
|
||||
// This avoids locking around m_transfer_buffers.
|
||||
Common::WaitableSPSCQueue<libusb_transfer*> m_transfers_to_free;
|
||||
|
||||
// Incoming packets Push'd from libusb callback thread.
|
||||
Common::SPSCQueue<BufferType> m_hci_event_queue;
|
||||
Common::SPSCQueue<BufferType> m_acl_data_queue;
|
||||
|
||||
// All transfers are alloc'd and kept here until complete.
|
||||
std::map<libusb_transfer*, Common::UniqueBuffer<u8>> m_transfer_buffers;
|
||||
|
||||
struct TimedTransfer
|
||||
{
|
||||
libusb_transfer* transfer{};
|
||||
TimePoint target_time{};
|
||||
};
|
||||
|
||||
// Outgoing data is submitted on a worker thread.
|
||||
// This allows proper packet timing without throttling the core thread.
|
||||
Common::WorkQueueThreadSP<TimedTransfer> m_output_worker;
|
||||
|
||||
// Used by the output worker.
|
||||
Common::PrecisionTimer m_precision_timer;
|
||||
|
||||
// Set to reduce OSD and log spam.
|
||||
bool m_showed_failed_transfer = false;
|
||||
bool m_showed_long_queue_drop = false;
|
||||
|
||||
// Some responses need to be fabricated if we aren't using the Nintendo BT module.
|
||||
bool m_is_wii_bt_module = false;
|
||||
|
||||
// Bluetooth spec's Num_HCI_Command_Packets.
|
||||
// This is the number of hci commands that the host is allowed to send.
|
||||
// We track this to send commands only when the controller is ready.
|
||||
u8 m_num_hci_command_packets = 1;
|
||||
|
||||
// HCI commands are queued when the controller isn't ready for them.
|
||||
// They will be sent after COMMAND_COMPL or COMMAND_STATUS signal readiness.
|
||||
std::queue<TimedTransfer> m_pending_hci_transfers;
|
||||
|
||||
struct OutstandingCommand
|
||||
{
|
||||
u16 opcode{};
|
||||
TimePoint submit_time{};
|
||||
};
|
||||
|
||||
// Sent HCI commands that have yet to receive a COMMAND_COMPL or COMMAND_STATUS response.
|
||||
// Container size will be small, around 2.
|
||||
std::deque<OutstandingCommand> m_unacknowledged_commands;
|
||||
|
||||
bool IsControllerReadyForCommand() const;
|
||||
|
||||
// Give the transfer to the worker and track the command appropriately.
|
||||
// This should only be used when IsControllerReadyForCommand is true.
|
||||
void PushHCICommand(TimedTransfer);
|
||||
|
||||
template <typename EventType>
|
||||
void AcknowledgeCommand(std::span<const u8> buffer);
|
||||
|
||||
libusb_transfer* AllocateTransfer(std::size_t buffer_size);
|
||||
void FreeTransfer(libusb_transfer*);
|
||||
void CleanCompletedTransfers();
|
||||
|
||||
// Do some HCI logic and clean up completed transfers.
|
||||
void Update();
|
||||
|
||||
// Sleep until the target time and then submit the transfer.
|
||||
// Used by worker thread.
|
||||
void SubmitTimedTransfer(TimedTransfer);
|
||||
|
||||
// Immediately submit transfer with libusb.
|
||||
void SubmitTransfer(libusb_transfer*);
|
||||
|
||||
// Start repetitive transfers to fill the hci-event and acl-data queues.
|
||||
void StartInputTransfers();
|
||||
|
||||
// LibUSB callbacks. Invoked from the LibUSB context thread.
|
||||
void HandleOutputTransfer(libusb_transfer*);
|
||||
void HandleInputTransfer(libusb_transfer*);
|
||||
|
||||
// Error logging.
|
||||
void HandleTransferError(libusb_transfer*);
|
||||
|
||||
bool OpenDevice(const libusb_device_descriptor& device_descriptor, libusb_device* device);
|
||||
};
|
||||
@@ -402,6 +402,7 @@
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\BTEmu.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\BTReal.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\BTStub.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\LibUSBBluetoothAdapter.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\hci.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\l2cap.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteDevice.h" />
|
||||
@@ -1079,6 +1080,7 @@
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\BTEmu.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\BTReal.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\BTStub.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\LibUSBBluetoothAdapter.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteDevice.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Common.cpp" />
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
#include <QVBoxLayout>
|
||||
#include <QVariant>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
|
||||
#include "Common/Config/Config.h"
|
||||
#include "Common/WorkQueueThread.h"
|
||||
|
||||
@@ -28,7 +25,7 @@
|
||||
#include "Core/HW/Wiimote.h"
|
||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
#include "Core/IOS/IOS.h"
|
||||
#include "Core/IOS/USB/Bluetooth/BTReal.h"
|
||||
#include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
#include "Core/System.h"
|
||||
#include "Core/WiiUtils.h"
|
||||
@@ -40,8 +37,6 @@
|
||||
#include "DolphinQt/QtUtils/SignalBlocking.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include "UICommon/UICommon.h"
|
||||
|
||||
WiimoteControllersWidget::WiimoteControllersWidget(QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
CreateLayout();
|
||||
@@ -80,7 +75,7 @@ void WiimoteControllersWidget::StartBluetoothAdapterRefresh()
|
||||
|
||||
const auto scan_func = [this]() {
|
||||
INFO_LOG_FMT(COMMON, "Refreshing Bluetooth adapter list...");
|
||||
auto device_list = IOS::HLE::BluetoothRealDevice::ListDevices();
|
||||
auto device_list = LibUSBBluetoothAdapter::ListDevices();
|
||||
INFO_LOG_FMT(COMMON, "{} Bluetooth adapters available.", device_list.size());
|
||||
const auto refresh_complete_func = [this, devices = std::move(device_list)]() {
|
||||
OnBluetoothAdapterRefreshComplete(devices);
|
||||
@@ -92,7 +87,7 @@ void WiimoteControllersWidget::StartBluetoothAdapterRefresh()
|
||||
}
|
||||
|
||||
void WiimoteControllersWidget::OnBluetoothAdapterRefreshComplete(
|
||||
const std::vector<IOS::HLE::BluetoothRealDevice::BluetoothDeviceInfo>& devices)
|
||||
const std::vector<LibUSBBluetoothAdapter::BluetoothDeviceInfo>& devices)
|
||||
{
|
||||
const int configured_vid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID);
|
||||
const int configured_pid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID);
|
||||
@@ -114,7 +109,7 @@ void WiimoteControllersWidget::OnBluetoothAdapterRefreshComplete(
|
||||
m_bluetooth_adapters->addItem(device_info, QVariant::fromValue(device));
|
||||
|
||||
if (!found_configured_device &&
|
||||
IOS::HLE::BluetoothRealDevice::IsConfiguredBluetoothDevice(device.vid, device.pid))
|
||||
LibUSBBluetoothAdapter::IsConfiguredBluetoothDevice(device.vid, device.pid))
|
||||
{
|
||||
found_configured_device = true;
|
||||
m_bluetooth_adapters->setCurrentIndex(m_bluetooth_adapters->count() - 1);
|
||||
@@ -126,7 +121,7 @@ void WiimoteControllersWidget::OnBluetoothAdapterRefreshComplete(
|
||||
const QString name = QLatin1Char{'['} + tr("disconnected") + QLatin1Char(']');
|
||||
const std::string name_str = name.toStdString();
|
||||
|
||||
IOS::HLE::BluetoothRealDevice::BluetoothDeviceInfo disconnected_device;
|
||||
LibUSBBluetoothAdapter::BluetoothDeviceInfo disconnected_device;
|
||||
disconnected_device.vid = configured_vid;
|
||||
disconnected_device.pid = configured_pid;
|
||||
disconnected_device.name = name_str;
|
||||
@@ -314,8 +309,8 @@ void WiimoteControllersWidget::OnBluetoothPassthroughDeviceChanged(int index)
|
||||
return;
|
||||
}
|
||||
|
||||
auto device_info = m_bluetooth_adapters->itemData(index)
|
||||
.value<IOS::HLE::BluetoothRealDevice::BluetoothDeviceInfo>();
|
||||
auto device_info =
|
||||
m_bluetooth_adapters->itemData(index).value<LibUSBBluetoothAdapter::BluetoothDeviceInfo>();
|
||||
|
||||
Config::SetBaseOrCurrent(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID, device_info.pid);
|
||||
Config::SetBaseOrCurrent(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID, device_info.vid);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <QWidget>
|
||||
|
||||
#include "Common/WorkQueueThread.h"
|
||||
#include "Core/IOS/USB/Bluetooth/BTReal.h"
|
||||
#include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h"
|
||||
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
@@ -39,7 +39,7 @@ private:
|
||||
void OnBluetoothPassthroughSyncPressed();
|
||||
void OnBluetoothPassthroughResetPressed();
|
||||
void OnBluetoothAdapterRefreshComplete(
|
||||
const std::vector<IOS::HLE::BluetoothRealDevice::BluetoothDeviceInfo>& devices);
|
||||
const std::vector<LibUSBBluetoothAdapter::BluetoothDeviceInfo>& devices);
|
||||
void OnWiimoteRefreshPressed();
|
||||
void OnWiimoteConfigure(size_t index);
|
||||
void StartBluetoothAdapterRefresh();
|
||||
|
||||
Reference in New Issue
Block a user