Merge pull request #13751 from jordan-woyak/btreal-improvements

Bluetooth Passthrough Improvements
This commit is contained in:
JMC47
2025-06-29 18:00:02 -04:00
committed by GitHub
11 changed files with 1143 additions and 671 deletions
+2
View File
@@ -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()
+12 -3
View File
@@ -3,9 +3,6 @@
#pragma once
#include <cstddef>
#include <string>
#include "Common/CommonTypes.h"
#include "Core/IOS/Device.h"
#include "Core/IOS/IOS.h"
@@ -15,6 +12,18 @@ class SysConf;
namespace IOS::HLE
{
template <typename T>
static void DoStateForMessage(EmulationKernel& ios, PointerWrap& p, std::unique_ptr<T>& message)
{
u32 request_address = (message != nullptr) ? message->ios_request.address : 0;
p.Do(request_address);
if (request_address != 0)
{
IOCtlVRequest request{ios.GetSystem(), request_address};
message = std::make_unique<T>(ios, request);
}
}
void BackUpBTInfoSection(const SysConf* sysconf);
void RestoreBTInfoSection(SysConf* sysconf);
@@ -79,18 +79,6 @@ BluetoothEmuDevice::BluetoothEmuDevice(EmulationKernel& ios, const std::string&
BluetoothEmuDevice::~BluetoothEmuDevice() = default;
template <typename T>
static void DoStateForMessage(EmulationKernel& ios, PointerWrap& p, std::unique_ptr<T>& message)
{
u32 request_address = (message != nullptr) ? message->ios_request.address : 0;
p.Do(request_address);
if (request_address != 0)
{
IOCtlVRequest request{ios.GetSystem(), request_address};
message = std::make_unique<T>(ios, request);
}
}
void BluetoothEmuDevice::DoState(PointerWrap& p)
{
bool passthrough_bluetooth = false;
File diff suppressed because it is too large Load Diff
+25 -60
View File
@@ -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);
};
+1 -1
View File
@@ -95,7 +95,7 @@ static size_t s_state_writes_in_queue;
static std::condition_variable s_state_write_queue_is_empty;
// Don't forget to increase this after doing changes on the savestate system
constexpr u32 STATE_VERSION = 174; // Last changed in PR 13342
constexpr u32 STATE_VERSION = 175; // Last changed in PR 13751
// Increase this if the StateExtendedHeader definition changes
constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217
+2
View File
@@ -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();