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 1144 additions and 672 deletions

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()

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);

View File

@ -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

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

View File

@ -0,0 +1,596 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h"
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <ranges>
#include <thread>
#include <utility>
#include <fmt/format.h>
#include <libusb.h>
#include "Common/MsgHandler.h"
#include "Core/Config/MainSettings.h"
#include "Core/Core.h"
#include "Core/IOS/USB/Bluetooth/hci.h"
#include "Core/IOS/USB/Host.h"
namespace
{
constexpr std::size_t BUFFER_SIZE = 1024;
constexpr auto HCI_COMMAND_TIMEOUT = std::chrono::milliseconds{1000};
constexpr u8 REQUEST_TYPE =
u8(u8(LIBUSB_ENDPOINT_OUT) | u8(LIBUSB_REQUEST_TYPE_CLASS)) | u8(LIBUSB_RECIPIENT_DEVICE);
constexpr u8 HCI_EVENT = 0x81;
constexpr u8 ACL_DATA_IN = 0x82;
template <auto MemFun>
constexpr libusb_transfer_cb_fn LibUSBMemFunCallback()
{
return [](libusb_transfer* tr) {
std::invoke(MemFun, static_cast<Common::ObjectType<MemFun>*>(tr->user_data), tr);
};
}
bool IsBluetoothDevice(const libusb_device_descriptor& descriptor)
{
constexpr u8 SUBCLASS = 0x01;
constexpr u8 PROTOCOL_BLUETOOTH = 0x01;
const bool is_bluetooth_protocol = descriptor.bDeviceClass == LIBUSB_CLASS_WIRELESS &&
descriptor.bDeviceSubClass == SUBCLASS &&
descriptor.bDeviceProtocol == PROTOCOL_BLUETOOTH;
// Some devices misreport their class, so we avoid relying solely on descriptor checks and allow
// users to specify their own VID/PID.
return is_bluetooth_protocol || LibUSBBluetoothAdapter::IsConfiguredBluetoothDevice(
descriptor.idVendor, descriptor.idProduct);
}
} // namespace
bool LibUSBBluetoothAdapter::IsWiiBTModule() const
{
return m_is_wii_bt_module;
}
bool LibUSBBluetoothAdapter::HasConfiguredBluetoothDevice()
{
const int configured_vid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID);
const int configured_pid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID);
return configured_vid != -1 && configured_pid != -1;
}
bool LibUSBBluetoothAdapter::IsConfiguredBluetoothDevice(u16 vid, u16 pid)
{
const int configured_vid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID);
const int configured_pid = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID);
return configured_vid == vid && configured_pid == pid;
}
LibUSBBluetoothAdapter::LibUSBBluetoothAdapter()
{
if (!m_context.IsValid())
return;
m_last_open_error.clear();
const bool has_configured_bt = HasConfiguredBluetoothDevice();
const int ret = m_context.GetDeviceList([&](libusb_device* device) {
libusb_device_descriptor device_descriptor;
libusb_get_device_descriptor(device, &device_descriptor);
auto [make_config_descriptor_ret, config_descriptor] =
LibusbUtils::MakeConfigDescriptor(device);
if (make_config_descriptor_ret != LIBUSB_SUCCESS || !config_descriptor)
{
ERROR_LOG_FMT(IOS_WIIMOTE, "Failed to get config descriptor for device {:04x}:{:04x}: {}",
device_descriptor.idVendor, device_descriptor.idProduct,
LibusbUtils::ErrorWrap(make_config_descriptor_ret));
return true;
}
if (has_configured_bt &&
!IsConfiguredBluetoothDevice(device_descriptor.idVendor, device_descriptor.idProduct))
{
return true;
}
if (IsBluetoothDevice(device_descriptor) && OpenDevice(device_descriptor, device))
{
unsigned char manufacturer[50] = {}, product[50] = {}, serial_number[50] = {};
const int manufacturer_ret = libusb_get_string_descriptor_ascii(
m_handle, device_descriptor.iManufacturer, manufacturer, sizeof(manufacturer));
if (manufacturer_ret < LIBUSB_SUCCESS)
{
WARN_LOG_FMT(IOS_WIIMOTE,
"Failed to get string for manufacturer descriptor {:02x} for device "
"{:04x}:{:04x} (rev {:x}): {}",
device_descriptor.iManufacturer, device_descriptor.idVendor,
device_descriptor.idProduct, device_descriptor.bcdDevice,
LibusbUtils::ErrorWrap(manufacturer_ret));
manufacturer[0] = '?';
manufacturer[1] = '\0';
}
const int product_ret = libusb_get_string_descriptor_ascii(
m_handle, device_descriptor.iProduct, product, sizeof(product));
if (product_ret < LIBUSB_SUCCESS)
{
WARN_LOG_FMT(IOS_WIIMOTE,
"Failed to get string for product descriptor {:02x} for device "
"{:04x}:{:04x} (rev {:x}): {}",
device_descriptor.iProduct, device_descriptor.idVendor,
device_descriptor.idProduct, device_descriptor.bcdDevice,
LibusbUtils::ErrorWrap(product_ret));
product[0] = '?';
product[1] = '\0';
}
const int serial_ret = libusb_get_string_descriptor_ascii(
m_handle, device_descriptor.iSerialNumber, serial_number, sizeof(serial_number));
if (serial_ret < LIBUSB_SUCCESS)
{
WARN_LOG_FMT(IOS_WIIMOTE,
"Failed to get string for serial number descriptor {:02x} for device "
"{:04x}:{:04x} (rev {:x}): {}",
device_descriptor.iSerialNumber, device_descriptor.idVendor,
device_descriptor.idProduct, device_descriptor.bcdDevice,
LibusbUtils::ErrorWrap(serial_ret));
serial_number[0] = '?';
serial_number[1] = '\0';
}
NOTICE_LOG_FMT(IOS_WIIMOTE, "Using device {:04x}:{:04x} (rev {:x}) for Bluetooth: {} {} {}",
device_descriptor.idVendor, device_descriptor.idProduct,
device_descriptor.bcdDevice, reinterpret_cast<char*>(manufacturer),
reinterpret_cast<char*>(product), reinterpret_cast<char*>(serial_number));
m_is_wii_bt_module =
device_descriptor.idVendor == 0x57e && device_descriptor.idProduct == 0x305;
return false;
}
return true;
});
if (ret != LIBUSB_SUCCESS)
{
m_last_open_error =
Common::FmtFormatT("GetDeviceList failed: {0}", LibusbUtils::ErrorWrap(ret));
}
if (m_handle == nullptr)
{
if (m_last_open_error.empty())
{
CriticalAlertFmtT(
"Could not find any usable Bluetooth USB adapter for Bluetooth Passthrough.\n\n"
"The emulated console will now stop.");
}
else
{
CriticalAlertFmtT(
"Could not find any usable Bluetooth USB adapter for Bluetooth Passthrough.\n"
"The following error occurred when Dolphin tried to use an adapter:\n{0}\n\n"
"The emulated console will now stop.",
m_last_open_error);
}
Core::QueueHostJob(&Core::Stop);
return;
}
StartInputTransfers();
m_output_worker.Reset("Bluetooth Output",
std::bind_front(&LibUSBBluetoothAdapter::SubmitTimedTransfer, this));
}
LibUSBBluetoothAdapter::~LibUSBBluetoothAdapter()
{
if (m_handle == nullptr)
return;
// Wait for completion (or time out) of all HCI commands.
while (!m_pending_hci_transfers.empty() && !m_unacknowledged_commands.empty())
{
(void)ReceiveHCIEvent();
Common::YieldCPU();
}
m_output_worker.Shutdown();
// Stop the repeating input transfers.
if (std::lock_guard lg{m_transfers_mutex}; true)
m_run_input_transfers = false;
// Cancel all LibUSB transfers.
std::ranges::for_each(m_transfer_buffers | std::ranges::views::keys, libusb_cancel_transfer);
// Wait for transfer callbacks and clean up all the buffers.
while (!m_transfer_buffers.empty())
{
m_transfers_to_free.WaitForData();
CleanCompletedTransfers();
}
const int ret = libusb_release_interface(m_handle, 0);
if (ret != LIBUSB_SUCCESS)
WARN_LOG_FMT(IOS_WIIMOTE, "libusb_release_interface failed: {}", LibusbUtils::ErrorWrap(ret));
libusb_close(std::exchange(m_handle, nullptr));
}
void LibUSBBluetoothAdapter::Update()
{
// Remove timed out commands.
const auto expired_time = Clock::now() - HCI_COMMAND_TIMEOUT;
while (!m_unacknowledged_commands.empty() &&
m_unacknowledged_commands.front().submit_time < expired_time)
{
WARN_LOG_FMT(IOS_WIIMOTE, "HCI command 0x{:04x} timed out.",
m_unacknowledged_commands.front().opcode);
m_unacknowledged_commands.pop_front();
}
// Allow sending commands if none are pending acknowledgement.
if (m_unacknowledged_commands.empty())
m_num_hci_command_packets = 1;
// Push queued commands when the controller is ready for them.
if (!m_pending_hci_transfers.empty() && IsControllerReadyForCommand())
{
DEBUG_LOG_FMT(IOS_WIIMOTE, "Submitting queued HCI command.");
PushHCICommand(m_pending_hci_transfers.front());
m_pending_hci_transfers.pop();
}
CleanCompletedTransfers();
}
auto LibUSBBluetoothAdapter::ReceiveHCIEvent() -> BufferType
{
if (m_hci_event_queue.Empty())
{
Update();
return {};
}
auto buffer = std::move(m_hci_event_queue.Front());
m_hci_event_queue.Pop();
// We only care about "COMMAND_COMPL" and "COMMAND_STATUS" events here.
// The controller will reply with one of those for every command.
// We track which commands are still "in flight" to properly obey Num_HCI_Command_Packets.
const auto event = buffer[0];
if (event == HCI_EVENT_COMMAND_COMPL)
{
AcknowledgeCommand<hci_command_compl_ep>(buffer);
}
else if (event == HCI_EVENT_COMMAND_STATUS)
{
AcknowledgeCommand<hci_command_status_ep>(buffer);
}
Update();
return buffer;
}
auto LibUSBBluetoothAdapter::ReceiveACLData() -> BufferType
{
BufferType buffer;
m_acl_data_queue.Pop(buffer);
return buffer;
}
bool LibUSBBluetoothAdapter::IsControllerReadyForCommand() const
{
return m_num_hci_command_packets > m_unacknowledged_commands.size();
}
template <typename EventType>
void LibUSBBluetoothAdapter::AcknowledgeCommand(std::span<const u8> buffer)
{
if (buffer.size() < sizeof(hci_event_hdr_t) + sizeof(EventType)) [[unlikely]]
{
WARN_LOG_FMT(IOS_WIIMOTE, "Undersized HCI event");
return;
}
EventType ev;
std::memcpy(&ev, buffer.data() + sizeof(hci_event_hdr_t), sizeof(ev));
const auto it =
std::ranges::find(m_unacknowledged_commands, ev.opcode, &OutstandingCommand::opcode);
if (it != m_unacknowledged_commands.end())
{
DEBUG_LOG_FMT(IOS_WIIMOTE, "HCI command acknowledged: 0x{:04x}", ev.opcode);
m_unacknowledged_commands.erase(it);
}
else if (ev.opcode != 0x0000)
{
WARN_LOG_FMT(IOS_WIIMOTE, "Unexpected opcode acknowledgement: 0x{:04x}", ev.opcode);
}
m_num_hci_command_packets = ev.num_cmd_pkts;
}
libusb_transfer* LibUSBBluetoothAdapter::AllocateTransfer(std::size_t buffer_size)
{
auto& item =
*m_transfer_buffers.emplace(libusb_alloc_transfer(0), Common::UniqueBuffer<u8>{buffer_size})
.first;
auto* const transfer = item.first;
transfer->buffer = item.second.get();
return transfer;
}
void LibUSBBluetoothAdapter::FreeTransfer(libusb_transfer* transfer)
{
libusb_free_transfer(transfer);
m_transfer_buffers.erase(transfer);
}
void LibUSBBluetoothAdapter::CleanCompletedTransfers()
{
libusb_transfer* transfer{};
while (m_transfers_to_free.Pop(transfer))
FreeTransfer(transfer);
}
void LibUSBBluetoothAdapter::PushHCICommand(TimedTransfer transfer)
{
hci_cmd_hdr_t cmd;
std::memcpy(&cmd, libusb_control_transfer_get_data(transfer.transfer), sizeof(cmd));
// Push the time forward so our timeouts work properly if command was queued and delayed.
const auto submit_time = std::max(transfer.target_time, Clock::now());
m_unacknowledged_commands.emplace_back(OutstandingCommand{cmd.opcode, submit_time});
// This function is only invoked when this value is at least 1.
assert(m_num_hci_command_packets >= 1);
--m_num_hci_command_packets;
m_output_worker.EmplaceItem(transfer);
}
void LibUSBBluetoothAdapter::SubmitTimedTransfer(TimedTransfer transfer)
{
if (Config::Get(Config::MAIN_PRECISION_FRAME_TIMING))
m_precision_timer.SleepUntil(transfer.target_time);
else
std::this_thread::sleep_until(transfer.target_time);
SubmitTransfer(transfer.transfer);
}
void LibUSBBluetoothAdapter::SubmitTransfer(libusb_transfer* transfer)
{
const int ret = libusb_submit_transfer(transfer);
if (ret == LIBUSB_SUCCESS)
return;
ERROR_LOG_FMT(IOS_WIIMOTE, "libusb_submit_transfer: {}", LibusbUtils::ErrorWrap(ret));
// Failed transers will not invoke callbacks so we must mark the buffer to be free'd here.
std::lock_guard lk{m_transfers_mutex};
m_transfers_to_free.Emplace(transfer);
}
void LibUSBBluetoothAdapter::ScheduleBulkTransfer(u8 endpoint, std::span<const u8> data,
TimePoint target_time)
{
constexpr auto callback = LibUSBMemFunCallback<&LibUSBBluetoothAdapter::HandleOutputTransfer>();
auto* const transfer = AllocateTransfer(data.size());
libusb_fill_bulk_transfer(transfer, m_handle, endpoint, transfer->buffer, int(data.size()),
callback, this, 0);
std::ranges::copy(data, transfer->buffer);
m_output_worker.EmplaceItem(transfer, target_time);
}
void LibUSBBluetoothAdapter::ScheduleControlTransfer(u8 type, u8 request, u8 value, u8 index,
std::span<const u8> data,
TimePoint target_time)
{
constexpr auto callback = LibUSBMemFunCallback<&LibUSBBluetoothAdapter::HandleOutputTransfer>();
auto* const transfer = AllocateTransfer(data.size() + LIBUSB_CONTROL_SETUP_SIZE);
libusb_fill_control_setup(transfer->buffer, type, request, value, index, u16(data.size()));
libusb_fill_control_transfer(transfer, m_handle, transfer->buffer, callback, this, 0);
std::ranges::copy(data, transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE);
const TimedTransfer timed_transfer{transfer, target_time};
if (IsControllerReadyForCommand())
{
PushHCICommand(timed_transfer);
}
else
{
DEBUG_LOG_FMT(IOS_WIIMOTE, "Queueing HCI command while controller is busy.");
m_pending_hci_transfers.emplace(timed_transfer);
}
}
void LibUSBBluetoothAdapter::SendControlTransfer(std::span<const u8> data)
{
ScheduleControlTransfer(REQUEST_TYPE, 0, 0, 0, data, Clock::now());
}
void LibUSBBluetoothAdapter::StartInputTransfers()
{
constexpr auto callback = LibUSBMemFunCallback<&LibUSBBluetoothAdapter::HandleInputTransfer>();
// Incoming HCI events.
{
auto* const transfer = AllocateTransfer(BUFFER_SIZE);
libusb_fill_interrupt_transfer(transfer, m_handle, HCI_EVENT, transfer->buffer, BUFFER_SIZE,
callback, this, 0);
SubmitTransfer(transfer);
}
// Incoming ACL data.
{
auto* const transfer = AllocateTransfer(BUFFER_SIZE);
libusb_fill_bulk_transfer(transfer, m_handle, ACL_DATA_IN, transfer->buffer, BUFFER_SIZE,
callback, this, 0);
SubmitTransfer(transfer);
}
}
bool LibUSBBluetoothAdapter::OpenDevice(const libusb_device_descriptor& device_descriptor,
libusb_device* device)
{
const int ret = libusb_open(device, &m_handle);
if (ret != LIBUSB_SUCCESS)
{
m_last_open_error = Common::FmtFormatT("Failed to open Bluetooth device {:04x}:{:04x}: {}",
device_descriptor.idVendor, device_descriptor.idProduct,
LibusbUtils::ErrorWrap(ret));
return false;
}
// Detaching always fails as a regular user on FreeBSD
// https://lists.freebsd.org/pipermail/freebsd-usb/2016-March/014161.html
#ifndef __FreeBSD__
int result = libusb_set_auto_detach_kernel_driver(m_handle, 1);
if (result != LIBUSB_SUCCESS)
{
result = libusb_detach_kernel_driver(m_handle, INTERFACE);
if (result != LIBUSB_SUCCESS && result != LIBUSB_ERROR_NOT_FOUND &&
result != LIBUSB_ERROR_NOT_SUPPORTED)
{
m_last_open_error = Common::FmtFormatT(
"Failed to detach kernel driver for BT passthrough: {0}", LibusbUtils::ErrorWrap(result));
return false;
}
}
#endif
if (const int result2 = libusb_claim_interface(m_handle, INTERFACE); result2 != LIBUSB_SUCCESS)
{
m_last_open_error = Common::FmtFormatT("Failed to claim interface for BT passthrough: {0}",
LibusbUtils::ErrorWrap(result2));
return false;
}
return true;
}
std::vector<LibUSBBluetoothAdapter::BluetoothDeviceInfo> LibUSBBluetoothAdapter::ListDevices()
{
std::vector<BluetoothDeviceInfo> device_list;
LibusbUtils::Context context;
if (!context.IsValid())
return {};
int result = context.GetDeviceList([&device_list](libusb_device* device) {
auto [config_ret, config] = LibusbUtils::MakeConfigDescriptor(device, 0);
if (config_ret != LIBUSB_SUCCESS)
return true;
libusb_device_descriptor desc;
if (libusb_get_device_descriptor(device, &desc) != LIBUSB_SUCCESS)
return true;
if (IsBluetoothDevice(desc))
{
const std::string device_name =
IOS::HLE::USBHost::GetDeviceNameFromVIDPID(desc.idVendor, desc.idProduct);
device_list.push_back({desc.idVendor, desc.idProduct, device_name});
}
return true;
});
if (result < 0)
{
ERROR_LOG_FMT(IOS_USB, "Failed to get device list: {}", LibusbUtils::ErrorWrap(result));
return device_list;
}
return device_list;
}
void LibUSBBluetoothAdapter::HandleOutputTransfer(libusb_transfer* tr)
{
HandleTransferError(tr);
std::lock_guard lg{m_transfers_mutex};
m_transfers_to_free.Emplace(tr);
}
void LibUSBBluetoothAdapter::HandleInputTransfer(libusb_transfer* tr)
{
if (tr->status == LIBUSB_TRANSFER_COMPLETED)
{
const bool is_hci_event = tr->endpoint == HCI_EVENT;
auto& queue = is_hci_event ? m_hci_event_queue : m_acl_data_queue;
if (queue.Size() < MAX_INPUT_QUEUE_SIZE)
{
if (tr->actual_length != 0)
{
BufferType buffer(tr->actual_length);
std::copy_n(tr->buffer, tr->actual_length, buffer.data());
queue.Emplace(std::move(buffer));
}
m_showed_long_queue_drop = false;
}
else if (!std::exchange(m_showed_long_queue_drop, true))
{
// This will happen when pausing the emulator.
WARN_LOG_FMT(IOS_WIIMOTE, "{} queue is too long. Packets will be dropped.",
(is_hci_event ? "HCI" : "ACL"));
}
}
HandleTransferError(tr);
std::lock_guard lg{m_transfers_mutex};
if (m_run_input_transfers)
{
// Continuously repeat this input transfer so long as we're still running.
const auto ret = libusb_submit_transfer(tr);
if (ret == LIBUSB_SUCCESS)
return;
ERROR_LOG_FMT(IOS_WIIMOTE, "HandleInputTransfer libusb_submit_transfer: {}",
LibusbUtils::ErrorWrap(ret));
}
m_transfers_to_free.Emplace(tr);
}
void LibUSBBluetoothAdapter::HandleTransferError(libusb_transfer* transfer)
{
if (transfer->status != LIBUSB_TRANSFER_COMPLETED &&
transfer->status != LIBUSB_TRANSFER_CANCELLED)
{
ERROR_LOG_FMT(IOS_WIIMOTE, "libusb transfer failed: {}",
LibusbUtils::ErrorWrap(transfer->status));
if (!std::exchange(m_showed_failed_transfer, true))
{
Core::DisplayMessage("Failed to send a command to the Bluetooth adapter.", 10000);
Core::DisplayMessage("It may not be compatible with passthrough mode.", 10000);
}
}
else
{
m_showed_failed_transfer = false;
}
}

View File

@ -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);
};

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

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" />

View File

@ -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);

View File

@ -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();