diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index f7b43c463e..79aab432cd 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -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() diff --git a/Source/Core/Core/IOS/USB/Bluetooth/BTBase.h b/Source/Core/Core/IOS/USB/Bluetooth/BTBase.h index 44cc011290..7a93f4f7d4 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/BTBase.h +++ b/Source/Core/Core/IOS/USB/Bluetooth/BTBase.h @@ -3,9 +3,6 @@ #pragma once -#include -#include - #include "Common/CommonTypes.h" #include "Core/IOS/Device.h" #include "Core/IOS/IOS.h" @@ -15,6 +12,18 @@ class SysConf; namespace IOS::HLE { +template +static void DoStateForMessage(EmulationKernel& ios, PointerWrap& p, std::unique_ptr& 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(ios, request); + } +} + void BackUpBTInfoSection(const SysConf* sysconf); void RestoreBTInfoSection(SysConf* sysconf); diff --git a/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp b/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp index 8e89cc927f..000883ec3f 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp +++ b/Source/Core/Core/IOS/USB/Bluetooth/BTEmu.cpp @@ -79,18 +79,6 @@ BluetoothEmuDevice::BluetoothEmuDevice(EmulationKernel& ios, const std::string& BluetoothEmuDevice::~BluetoothEmuDevice() = default; -template -static void DoStateForMessage(EmulationKernel& ios, PointerWrap& p, std::unique_ptr& 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(ios, request); - } -} - void BluetoothEmuDevice::DoState(PointerWrap& p) { bool passthrough_bluetooth = false; diff --git a/Source/Core/Core/IOS/USB/Bluetooth/BTReal.cpp b/Source/Core/Core/IOS/USB/Bluetooth/BTReal.cpp index 7940c37fc0..0fc76cff55 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/BTReal.cpp +++ b/Source/Core/Core/IOS/USB/Bluetooth/BTReal.cpp @@ -4,53 +4,49 @@ #include "Core/IOS/USB/Bluetooth/BTReal.h" #include -#include +#include #include -#include -#include #include -#include -#include +#include #include #include -#include #include -#include #include "Common/ChunkFile.h" -#include "Common/EnumUtils.h" -#include "Common/Logging/Log.h" -#include "Common/MsgHandler.h" #include "Common/Network.h" #include "Common/StringUtil.h" #include "Common/Swap.h" + #include "Core/Config/MainSettings.h" #include "Core/Core.h" #include "Core/HW/Memmap.h" #include "Core/IOS/Device.h" #include "Core/IOS/USB/Host.h" #include "Core/System.h" + #include "VideoCommon/OnScreenDisplay.h" +namespace +{ +template +struct HCICommandPayload +{ + hci_cmd_hdr_t header{Opcode, sizeof(CommandType)}; + CommandType command{}; +}; + +template +requires(std::is_trivially_copyable_v) +constexpr auto AsU8Span(const T& obj) +{ + return std::span{reinterpret_cast(std::addressof(obj)), sizeof(obj)}; +} + +} // namespace + namespace IOS::HLE { -constexpr u8 REQUEST_TYPE = static_cast(LIBUSB_ENDPOINT_OUT) | - static_cast(LIBUSB_REQUEST_TYPE_CLASS) | - static_cast(LIBUSB_RECIPIENT_INTERFACE); - -static bool IsBluetoothDevice(const libusb_device_descriptor& descriptor) -{ - constexpr u8 SUBCLASS = 0x01; - constexpr u8 PROTOCOL_BLUETOOTH = 0x01; - // Some devices misreport their class, so we avoid relying solely on descriptor checks and allow - // users to specify their own VID/PID. - return BluetoothRealDevice::IsConfiguredBluetoothDevice(descriptor.idVendor, - descriptor.idProduct) || - (descriptor.bDeviceClass == LIBUSB_CLASS_WIRELESS && - descriptor.bDeviceSubClass == SUBCLASS && - descriptor.bDeviceProtocol == PROTOCOL_BLUETOOTH); -} BluetoothRealDevice::BluetoothRealDevice(EmulationKernel& ios, const std::string& device_name) : BluetoothBaseDevice(ios, device_name) @@ -60,271 +56,280 @@ BluetoothRealDevice::BluetoothRealDevice(EmulationKernel& ios, const std::string BluetoothRealDevice::~BluetoothRealDevice() { - if (m_handle != nullptr) - { - SendHCIResetCommand(); - WaitForHCICommandComplete(HCI_CMD_RESET); - 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(m_handle); - libusb_unref_device(m_device); - } SaveLinkKeys(); } -bool BluetoothRealDevice::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 BluetoothRealDevice::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; -} - std::optional BluetoothRealDevice::Open(const OpenRequest& request) { - if (!m_context.IsValid()) - return IPCReply(IPC_EACCES); - - m_last_open_error.clear(); - const int ret = m_context.GetDeviceList([this](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 (HasConfiguredBluetoothDevice() && - !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(manufacturer), - reinterpret_cast(product), reinterpret_cast(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 IPCReply(IPC_ENOENT); - } + m_lib_usb_bt_adapter = std::make_unique(); return Device::Open(request); } std::optional BluetoothRealDevice::Close(u32 fd) { - if (m_handle) - { - 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(m_handle); - libusb_unref_device(m_device); - m_handle = nullptr; - } + m_hci_endpoint.reset(); + m_acl_endpoint.reset(); + + m_fake_replies = {}; + + // FYI: LibUSBBluetoothAdapter destruction will attempt to wait for command completion. + SendHCIResetCommand(); + + m_lib_usb_bt_adapter.reset(); return Device::Close(fd); } std::optional BluetoothRealDevice::IOCtlV(const IOCtlVRequest& request) { - if (!m_is_wii_bt_module && m_need_reset_keys.TestAndClear()) - { - // Do this now before transferring any more data, so that this is fully transparent to games - SendHCIDeleteLinkKeyCommand(); - WaitForHCICommandComplete(HCI_CMD_DELETE_STORED_LINK_KEY); - if (SendHCIStoreLinkKeyCommand()) - WaitForHCICommandComplete(HCI_CMD_WRITE_STORED_LINK_KEY); - } - switch (request.request) { // HCI commands to the Bluetooth adapter case USB::IOCTLV_USBV0_CTRLMSG: { - auto& system = GetSystem(); - auto& memory = system.GetMemory(); + auto& memory = GetSystem().GetMemory(); - std::lock_guard lk(m_transfers_mutex); auto cmd = std::make_unique(GetEmulationKernel(), request); const u16 opcode = Common::swap16(memory.Read_U16(cmd->data_address)); - if (opcode == HCI_CMD_READ_BUFFER_SIZE) + if (!m_lib_usb_bt_adapter->IsWiiBTModule() && (opcode == 0xFC4C || opcode == 0xFC4F)) { - m_fake_read_buffer_size_reply.Set(); + m_fake_replies.emplace( + std::bind_front(&BluetoothRealDevice::FakeVendorCommandReply, this, opcode)); + } + else + { + if (opcode == HCI_CMD_DELETE_STORED_LINK_KEY) + { + INFO_LOG_FMT(IOS_WIIMOTE, "HCI_CMD_DELETE_STORED_LINK_KEY"); + + // Delete link key(s) from our own link key storage when the game tells the adapter to + hci_delete_stored_link_key_cp delete_cmd; + memory.CopyFromEmu(&delete_cmd, cmd->data_address, sizeof(delete_cmd)); + + if (delete_cmd.delete_all != 0) + m_link_keys.clear(); + else + m_link_keys.erase(delete_cmd.bdaddr); + } + else if (opcode == HCI_CMD_SNIFF_MODE) + { + // FYI: This is what causes Wii Remotes to operate at 200hz instead of 100hz. + DEBUG_LOG_FMT(IOS_WIIMOTE, "HCI_CMD_SNIFF_MODE"); + } + + const auto payload = memory.GetSpanForAddress(cmd->data_address).first(cmd->length); + m_lib_usb_bt_adapter->ScheduleControlTransfer(cmd->request_type, cmd->request, cmd->value, + cmd->index, payload, GetTargetTime()); + } + return IPCReply{cmd->length}; + } + // ACL data (incoming or outgoing) + case USB::IOCTLV_USBV0_BLKMSG: + { + auto cmd = std::make_unique(GetEmulationKernel(), request); + if (cmd->endpoint == ACL_DATA_IN) + { + m_acl_endpoint = std::move(cmd); + TryToFillACLEndpoint(); return std::nullopt; } - if (!m_is_wii_bt_module && (opcode == 0xFC4C || opcode == 0xFC4F)) - { - m_fake_vendor_command_reply.Set(); - m_fake_vendor_command_reply_opcode = opcode; - return std::nullopt; - } - if (opcode == HCI_CMD_DELETE_STORED_LINK_KEY) - { - // Delete link key(s) from our own link key storage when the game tells the adapter to - hci_delete_stored_link_key_cp delete_cmd; - memory.CopyFromEmu(&delete_cmd, cmd->data_address, sizeof(delete_cmd)); - if (delete_cmd.delete_all) - m_link_keys.clear(); - else - m_link_keys.erase(delete_cmd.bdaddr); - } - auto buffer = std::make_unique(cmd->length + LIBUSB_CONTROL_SETUP_SIZE); - libusb_fill_control_setup(buffer.get(), cmd->request_type, cmd->request, cmd->value, cmd->index, - cmd->length); - memory.CopyFromEmu(buffer.get() + LIBUSB_CONTROL_SETUP_SIZE, cmd->data_address, cmd->length); - libusb_transfer* transfer = libusb_alloc_transfer(0); - transfer->flags |= LIBUSB_TRANSFER_FREE_TRANSFER; - libusb_fill_control_transfer(transfer, m_handle, buffer.get(), nullptr, this, 0); - transfer->callback = [](libusb_transfer* tr) { - static_cast(tr->user_data)->HandleCtrlTransfer(tr); - }; - PendingTransfer pending_transfer{std::move(cmd), std::move(buffer)}; - m_current_transfers.emplace(transfer, std::move(pending_transfer)); - const int ret = libusb_submit_transfer(transfer); - if (ret != LIBUSB_SUCCESS) - WARN_LOG_FMT(IOS_WIIMOTE, "libusb_submit_transfer failed: {}", LibusbUtils::ErrorWrap(ret)); + + auto& memory = GetSystem().GetMemory(); + const auto payload = memory.GetSpanForAddress(cmd->data_address).first(cmd->length); + m_lib_usb_bt_adapter->ScheduleBulkTransfer(cmd->endpoint, payload, GetTargetTime()); break; } - // ACL data (incoming or outgoing) and incoming HCI events (respectively) - case USB::IOCTLV_USBV0_BLKMSG: + // Incoming HCI events case USB::IOCTLV_USBV0_INTRMSG: { - std::lock_guard lk(m_transfers_mutex); auto cmd = std::make_unique(GetEmulationKernel(), request); - if (request.request == USB::IOCTLV_USBV0_INTRMSG) + if (cmd->endpoint == HCI_EVENT) { - if (m_sync_button_state == SyncButtonState::Pressed) - { - Core::DisplayMessage("Scanning for Wii Remotes", 2000); - FakeSyncButtonPressedEvent(*cmd); - return std::nullopt; - } - if (m_sync_button_state == SyncButtonState::LongPressed) - { - Core::DisplayMessage("Reset saved Wii Remote pairings", 2000); - FakeSyncButtonHeldEvent(*cmd); - return std::nullopt; - } - if (m_fake_read_buffer_size_reply.TestAndClear()) - { - FakeReadBufferSizeReply(*cmd); - return std::nullopt; - } - if (m_fake_vendor_command_reply.TestAndClear()) - { - FakeVendorCommandReply(*cmd); - return std::nullopt; - } + m_hci_endpoint = std::move(cmd); + TryToFillHCIEndpoint(); + return std::nullopt; } - auto buffer = cmd->MakeBuffer(cmd->length); - libusb_transfer* transfer = libusb_alloc_transfer(0); - transfer->buffer = buffer.get(); - transfer->callback = [](libusb_transfer* tr) { - static_cast(tr->user_data)->HandleBulkOrIntrTransfer(tr); - }; - transfer->dev_handle = m_handle; - transfer->endpoint = cmd->endpoint; - transfer->flags |= LIBUSB_TRANSFER_FREE_TRANSFER; - transfer->length = cmd->length; - transfer->timeout = TIMEOUT; - transfer->type = request.request == USB::IOCTLV_USBV0_BLKMSG ? LIBUSB_TRANSFER_TYPE_BULK : - LIBUSB_TRANSFER_TYPE_INTERRUPT; - transfer->user_data = this; - PendingTransfer pending_transfer{std::move(cmd), std::move(buffer)}; - m_current_transfers.emplace(transfer, std::move(pending_transfer)); - const int ret = libusb_submit_transfer(transfer); - if (ret != LIBUSB_SUCCESS) - WARN_LOG_FMT(IOS_WIIMOTE, "libusb_submit_transfer failed: {}", LibusbUtils::ErrorWrap(ret)); + + ERROR_LOG_FMT(IOS_WIIMOTE, "IOCTLV_USBV0_INTRMSG: Unknown endpoint: 0x{:02x}", cmd->endpoint); + } + default: + ERROR_LOG_FMT(IOS_WIIMOTE, "IOCtlV: Unknown request: 0x{:08x}", request.request); break; } - } - // Replies are generated inside of the message handlers (and asynchronously). - return std::nullopt; + + return IPCReply{IPC_SUCCESS}; +} + +void BluetoothRealDevice::Update() +{ + TryToFillHCIEndpoint(); + TryToFillACLEndpoint(); +} + +void BluetoothRealDevice::TryToFillHCIEndpoint() +{ + if (!m_hci_endpoint) + return; + + if (m_sync_button_state == SyncButtonState::Pressed) + { + Core::DisplayMessage("Scanning for Wii Remotes", 2000); + FakeSyncButtonPressedEvent(*m_hci_endpoint); + } + else if (m_sync_button_state == SyncButtonState::LongPressed) + { + Core::DisplayMessage("Reset saved Wii Remote pairings", 2000); + FakeSyncButtonHeldEvent(*m_hci_endpoint); + } + else if (!m_fake_replies.empty()) + { + std::invoke(m_fake_replies.front(), *m_hci_endpoint); + m_fake_replies.pop(); + } + else + { + const auto buffer = ProcessHCIEvent(m_lib_usb_bt_adapter->ReceiveHCIEvent()); + if (buffer.empty()) + return; + + assert(buffer.size() <= m_hci_endpoint->length); + + m_hci_endpoint->FillBuffer(buffer.data(), buffer.size()); + GetEmulationKernel().EnqueueIPCReply(m_hci_endpoint->ios_request, s32(buffer.size())); + } + + m_hci_endpoint.reset(); +} + +auto BluetoothRealDevice::ProcessHCIEvent(BufferType buffer) -> BufferType +{ + if (buffer.empty()) + return buffer; + + const auto event = buffer[0]; + if (event == HCI_EVENT_LINK_KEY_NOTIFICATION) + { + INFO_LOG_FMT(IOS_WIIMOTE, "HCI_EVENT_LINK_KEY_NOTIFICATION"); + + hci_link_key_notification_ep notification; + std::memcpy(¬ification, buffer.data() + sizeof(hci_event_hdr_t), sizeof(notification)); + std::ranges::copy(notification.key, std::begin(m_link_keys[notification.bdaddr])); + } + + if (m_lib_usb_bt_adapter->IsWiiBTModule()) + return buffer; + + // Handle some quirks for non-Wii BT adapters below. + + if (event == HCI_EVENT_COMMAND_COMPL) + { + hci_command_compl_ep ev; + std::memcpy(&ev, buffer.data() + sizeof(hci_event_hdr_t), sizeof(ev)); + + if (ev.opcode == HCI_CMD_READ_BUFFER_SIZE) + { + hci_read_buffer_size_rp reply; + std::memcpy(&reply, buffer.data() + sizeof(hci_event_hdr_t) + sizeof(ev), sizeof(reply)); + + if (reply.status != 0x00) + { + ERROR_LOG_FMT(IOS_WIIMOTE, "HCI_CMD_READ_BUFFER_SIZE status: 0x{:02x}.", reply.status); + + reply.status = 0x00; + reply.num_acl_pkts = ACL_PKT_NUM; + } + else if (reply.num_acl_pkts > ACL_PKT_NUM) + { + // Due to how the widcomm stack which Nintendo uses is coded, we must never + // let the stack think the controller is buffering more than 10 data packets + // - it will cause a u8 underflow and royally screw things up. + // Therefore, the reply to this command has to be faked to avoid random, weird issues + // (including Wiimote disconnects and "event mismatch" warning messages). + reply.num_acl_pkts = ACL_PKT_NUM; + } + else if (reply.num_acl_pkts < ACL_PKT_NUM) + { + // The controller buffers fewer ACL packets than the original BT module. + WARN_LOG_FMT(IOS_WIIMOTE, "HCI_CMD_READ_BUFFER_SIZE num_acl_pkts({}) < ACL_PKT_NUM({})", + reply.num_acl_pkts, ACL_PKT_NUM); + } + + // Force the other the parameters to match that of the original BT module. + reply.max_acl_size = ACL_PKT_SIZE; + reply.max_sco_size = SCO_PKT_SIZE; + reply.num_sco_pkts = SCO_PKT_NUM; + + std::memcpy(buffer.data() + sizeof(hci_event_hdr_t) + sizeof(ev), &reply, sizeof(reply)); + } + else if (ev.opcode == HCI_CMD_RESET) + { + SendHCIDeleteLinkKeyCommand(); + SendHCIStoreLinkKeyCommand(); + } + } + else if (event == HCI_EVENT_CON_COMPL) + { + // Some devices (e.g. Sena UD100 0a12:0001) default to HCI_SERVICE_TYPE_BEST_EFFORT. + // This can cause less than 200hz input and drop-outs in some games (e.g. Brawl). + + // We configure HCI_SERVICE_TYPE_GUARANTEED for each new connection. + // This solves dropped input issues at least for the mentioned Sena adapter. + + INFO_LOG_FMT(IOS_WIIMOTE, "Sending HCI_CMD_QOS_SETUP"); + + HCICommandPayload payload; + + // Copy the connection handle. + std::memcpy(&payload.command.con_handle, buffer.data() + 3, sizeof(payload.command.con_handle)); + + payload.command.service_type = HCI_SERVICE_TYPE_GUARANTEED; + payload.command.token_rate = 0xffffffff; + payload.command.peak_bandwidth = 0xffffffff; + payload.command.latency = 10000; + payload.command.delay_variation = 0xffffffff; + + m_lib_usb_bt_adapter->SendControlTransfer(AsU8Span(payload)); + } + else if (event == HCI_EVENT_QOS_SETUP_COMPL) + { + const auto service_type = buffer[6]; + if (service_type != HCI_SERVICE_TYPE_GUARANTEED) + WARN_LOG_FMT(IOS_WIIMOTE, "Got HCI_EVENT_QOS_SETUP_COMPL service_type: {}", service_type); + else + INFO_LOG_FMT(IOS_WIIMOTE, "Got HCI_EVENT_QOS_SETUP_COMPL HCI_SERVICE_TYPE_GUARANTEED"); + } + + return buffer; +} + +void BluetoothRealDevice::TryToFillACLEndpoint() +{ + if (!m_acl_endpoint) + return; + + // Throttle to minimize input latency. + auto& core_timing = GetSystem().GetCoreTiming(); + core_timing.Throttle(core_timing.GetTicks()); + + const auto buffer = m_lib_usb_bt_adapter->ReceiveACLData(); + if (buffer.empty()) + return; + + assert(buffer.size() <= m_acl_endpoint->length); + + m_acl_endpoint->FillBuffer(buffer.data(), buffer.size()); + GetEmulationKernel().EnqueueIPCReply(m_acl_endpoint->ios_request, s32(buffer.size())); + + m_acl_endpoint.reset(); +} + +TimePoint BluetoothRealDevice::GetTargetTime() const +{ + auto& core_timing = GetSystem().GetCoreTiming(); + return core_timing.GetTargetHostTime(core_timing.GetTicks()); } -static bool s_has_shown_savestate_warning = false; void BluetoothRealDevice::DoState(PointerWrap& p) { bool passthrough_bluetooth = true; @@ -337,37 +342,19 @@ void BluetoothRealDevice::DoState(PointerWrap& p) } Device::DoState(p); - // Prevent the transfer callbacks from messing with m_current_transfers after we have started - // writing a savestate. We cannot use a scoped lock here because DoState is called twice and - // we would lose the lock between the two calls. - if (p.IsMeasureMode() || p.IsVerifyMode()) - m_transfers_mutex.lock(); - std::vector addresses_to_discard; - if (!p.IsReadMode()) - { - // Save addresses of transfer commands to discard on savestate load. - for (const auto& transfer : m_current_transfers) - addresses_to_discard.push_back(transfer.second.command->ios_request.address); - } - p.Do(addresses_to_discard); + DoStateForMessage(GetEmulationKernel(), p, m_hci_endpoint); + DoStateForMessage(GetEmulationKernel(), p, m_acl_endpoint); + if (p.IsReadMode()) { - // On load, discard any pending transfer to make sure the emulated software is not stuck - // waiting for the previous request to complete. This is usually not an issue as long as - // the Bluetooth state is the same (same Wii Remote connections). - auto& system = GetSystem(); - for (const auto& address_to_discard : addresses_to_discard) - GetEmulationKernel().EnqueueIPCReply(Request{system, address_to_discard}, 0); - - // Prevent the callbacks from replying to a request that has already been discarded. - m_current_transfers.clear(); - OSD::AddMessage("If the savestate does not load correctly, disconnect all Wii Remotes " "and reload it.", OSD::Duration::NORMAL); } + static bool s_has_shown_savestate_warning = false; + if (!s_has_shown_savestate_warning && p.IsWriteMode()) { OSD::AddMessage("Savestates may not work with Bluetooth passthrough in all cases.\n" @@ -376,10 +363,6 @@ void BluetoothRealDevice::DoState(PointerWrap& p) OSD::Duration::VERY_LONG); s_has_shown_savestate_warning = true; } - - // We have finished the savestate now, so the transfers mutex can be unlocked. - if (p.IsWriteMode()) - m_transfers_mutex.unlock(); } void BluetoothRealDevice::UpdateSyncButtonState(const bool is_held) @@ -410,55 +393,21 @@ void BluetoothRealDevice::TriggerSyncButtonHeldEvent() m_sync_button_state = SyncButtonState::LongPressed; } -void BluetoothRealDevice::WaitForHCICommandComplete(const u16 opcode) -{ - int actual_length; - SHCIEventCommand packet; - std::vector buffer(1024); - // Only try 100 transfers at most, to avoid being stuck in an infinite loop - for (int tries = 0; tries < 100; ++tries) - { - const int ret = libusb_interrupt_transfer(m_handle, HCI_EVENT, buffer.data(), - static_cast(buffer.size()), &actual_length, 20); - if (ret != LIBUSB_SUCCESS || actual_length < static_cast(sizeof(packet))) - continue; - std::memcpy(&packet, buffer.data(), sizeof(packet)); - if (packet.EventType == HCI_EVENT_COMMAND_COMPL && packet.Opcode == opcode) - break; - } -} - void BluetoothRealDevice::SendHCIResetCommand() { - u8 packet[3] = {}; - const u16 payload[] = {HCI_CMD_RESET}; - memcpy(packet, payload, sizeof(payload)); - const int ret = - libusb_control_transfer(m_handle, REQUEST_TYPE, 0, 0, 0, packet, sizeof(packet), TIMEOUT); - if (ret < LIBUSB_SUCCESS) - WARN_LOG_FMT(IOS_WIIMOTE, "libusb_control_transfer failed: {}", LibusbUtils::ErrorWrap(ret)); - else - INFO_LOG_FMT(IOS_WIIMOTE, "Sent a reset command to adapter"); + INFO_LOG_FMT(IOS_WIIMOTE, "SendHCIResetCommand"); + m_lib_usb_bt_adapter->SendControlTransfer(AsU8Span(hci_cmd_hdr_t{HCI_CMD_RESET, 0})); } void BluetoothRealDevice::SendHCIDeleteLinkKeyCommand() { - struct Payload - { - hci_cmd_hdr_t header; - hci_delete_stored_link_key_cp command; - }; - Payload payload; - payload.header.opcode = HCI_CMD_DELETE_STORED_LINK_KEY; - payload.header.length = sizeof(payload.command); - payload.command.bdaddr = {}; - payload.command.delete_all = true; + INFO_LOG_FMT(IOS_WIIMOTE, "SendHCIDeleteLinkKeyCommand"); - const int ret = - libusb_control_transfer(m_handle, REQUEST_TYPE, 0, 0, 0, reinterpret_cast(&payload), - static_cast(sizeof(payload)), TIMEOUT); - if (ret < LIBUSB_SUCCESS) - WARN_LOG_FMT(IOS_WIIMOTE, "libusb_control_transfer failed: {}", LibusbUtils::ErrorWrap(ret)); + HCICommandPayload payload; + payload.command.bdaddr = {}; + payload.command.delete_all = 0x01; + + m_lib_usb_bt_adapter->SendControlTransfer(AsU8Span(payload)); } bool BluetoothRealDevice::SendHCIStoreLinkKeyCommand() @@ -466,111 +415,78 @@ bool BluetoothRealDevice::SendHCIStoreLinkKeyCommand() if (m_link_keys.empty()) return false; - // The HCI command field is limited to uint8_t, and libusb to uint16_t. - const u8 payload_size = - static_cast(sizeof(hci_write_stored_link_key_cp)) + - (sizeof(bdaddr_t) + sizeof(linkkey_t)) * static_cast(m_link_keys.size()); - std::vector packet(sizeof(hci_cmd_hdr_t) + payload_size); + // Range: 0x01 to 0x0B per Bluetooth spec. + static constexpr std::size_t MAX_LINK_KEYS = 0x0B; + const auto num_link_keys = u8(std::min(m_link_keys.size(), MAX_LINK_KEYS)); - hci_cmd_hdr_t header{}; - header.opcode = HCI_CMD_WRITE_STORED_LINK_KEY; - header.length = payload_size; - std::memcpy(packet.data(), &header, sizeof(header)); + INFO_LOG_FMT(IOS_WIIMOTE, "SendHCIStoreLinkKeyCommand num_link_keys: {}", num_link_keys); - hci_write_stored_link_key_cp command{}; - command.num_keys_write = static_cast(m_link_keys.size()); - std::memcpy(packet.data() + sizeof(hci_cmd_hdr_t), &command, sizeof(command)); - - // This is really ugly, but necessary because of the HCI command structure: - // u8 num_keys; - // u8 bdaddr[6]; - // u8 key[16]; - // where the two last items are repeated num_keys times. - auto iterator = packet.begin() + sizeof(hci_cmd_hdr_t) + sizeof(hci_write_stored_link_key_cp); - for (const auto& entry : m_link_keys) + struct Payload { - std::ranges::copy(entry.first, iterator); - iterator += entry.first.size(); - std::ranges::copy(entry.second, iterator); - iterator += entry.second.size(); - } + hci_cmd_hdr_t header{HCI_CMD_WRITE_STORED_LINK_KEY}; + hci_write_stored_link_key_cp command{}; + struct LinkKey + { + bdaddr_t bdaddr; + linkkey_t linkkey; + }; + std::array link_keys{}; + } payload; + static_assert(sizeof(Payload) == 4 + (6 + 16) * MAX_LINK_KEYS); - const int ret = libusb_control_transfer(m_handle, REQUEST_TYPE, 0, 0, 0, packet.data(), - static_cast(packet.size()), TIMEOUT); - if (ret < LIBUSB_SUCCESS) - WARN_LOG_FMT(IOS_WIIMOTE, "libusb_control_transfer failed: {}", LibusbUtils::ErrorWrap(ret)); + const u8 payload_size = + sizeof(payload.header) + sizeof(payload.command) + (sizeof(Payload::LinkKey) * num_link_keys); + + payload.header.length = payload_size - sizeof(payload.header); + payload.command.num_keys_write = num_link_keys; + + int index = 0; + for (auto& [bdaddr, linkkey] : m_link_keys | std::views::take(num_link_keys)) + payload.link_keys[index++] = {bdaddr, linkkey}; + + m_lib_usb_bt_adapter->SendControlTransfer(AsU8Span(payload).first(payload_size)); return true; } -void BluetoothRealDevice::FakeVendorCommandReply(USB::V0IntrMessage& ctrl) +void BluetoothRealDevice::FakeVendorCommandReply(u16 opcode, USB::V0IntrMessage& ctrl) { - auto& system = GetSystem(); - auto& memory = system.GetMemory(); + DEBUG_LOG_FMT(IOS_WIIMOTE, "FakeVendorCommandReply"); - SHCIEventCommand hci_event; - memory.CopyFromEmu(&hci_event, ctrl.data_address, sizeof(hci_event)); - hci_event.EventType = HCI_EVENT_COMMAND_COMPL; - hci_event.PayloadLength = sizeof(SHCIEventCommand) - 2; - hci_event.PacketIndicator = 0x01; - hci_event.Opcode = m_fake_vendor_command_reply_opcode; - memory.CopyToEmu(ctrl.data_address, &hci_event, sizeof(hci_event)); - GetEmulationKernel().EnqueueIPCReply(ctrl.ios_request, static_cast(sizeof(hci_event))); + struct Payload + { + hci_event_hdr_t header{HCI_EVENT_COMMAND_COMPL}; + hci_command_compl_ep command{}; + } payload; + + payload.header.length = sizeof(payload) - sizeof(payload.header); + + payload.command.num_cmd_pkts = 0x01; + payload.command.opcode = opcode; + + assert(sizeof(payload) <= ctrl.length); + + GetSystem().GetMemory().CopyToEmu(ctrl.data_address, &payload, sizeof(payload)); + GetEmulationKernel().EnqueueIPCReply(ctrl.ios_request, s32(sizeof(payload))); } -// Due to how the widcomm stack which Nintendo uses is coded, we must never -// let the stack think the controller is buffering more than 10 data packets -// - it will cause a u8 underflow and royally screw things up. -// Therefore, the reply to this command has to be faked to avoid random, weird issues -// (including Wiimote disconnects and "event mismatch" warning messages). -void BluetoothRealDevice::FakeReadBufferSizeReply(USB::V0IntrMessage& ctrl) +void BluetoothRealDevice::FakeSyncButtonEvent(USB::V0IntrMessage& ctrl, std::span payload) { - auto& system = GetSystem(); - auto& memory = system.GetMemory(); + const hci_event_hdr_t hci_event{HCI_EVENT_VENDOR, u8(payload.size())}; + + auto& memory = GetSystem().GetMemory(); - SHCIEventCommand hci_event; - memory.CopyFromEmu(&hci_event, ctrl.data_address, sizeof(hci_event)); - hci_event.EventType = HCI_EVENT_COMMAND_COMPL; - hci_event.PayloadLength = sizeof(SHCIEventCommand) - 2 + sizeof(hci_read_buffer_size_rp); - hci_event.PacketIndicator = 0x01; - hci_event.Opcode = HCI_CMD_READ_BUFFER_SIZE; memory.CopyToEmu(ctrl.data_address, &hci_event, sizeof(hci_event)); - - hci_read_buffer_size_rp reply; - reply.status = 0x00; - reply.max_acl_size = ACL_PKT_SIZE; - reply.num_acl_pkts = ACL_PKT_NUM; - reply.max_sco_size = SCO_PKT_SIZE; - reply.num_sco_pkts = SCO_PKT_NUM; - memory.CopyToEmu(ctrl.data_address + sizeof(hci_event), &reply, sizeof(reply)); - GetEmulationKernel().EnqueueIPCReply(ctrl.ios_request, - static_cast(sizeof(hci_event) + sizeof(reply))); + memory.CopyToEmu(ctrl.data_address + sizeof(hci_event), payload.data(), payload.size()); + GetEmulationKernel().EnqueueIPCReply(ctrl.ios_request, s32(sizeof(hci_event) + payload.size())); } -void BluetoothRealDevice::FakeSyncButtonEvent(USB::V0IntrMessage& ctrl, const u8* payload, - const u8 size) -{ - auto& system = GetSystem(); - auto& memory = system.GetMemory(); - - hci_event_hdr_t hci_event; - memory.CopyFromEmu(&hci_event, ctrl.data_address, sizeof(hci_event)); - hci_event.event = HCI_EVENT_VENDOR; - hci_event.length = size; - memory.CopyToEmu(ctrl.data_address, &hci_event, sizeof(hci_event)); - memory.CopyToEmu(ctrl.data_address + sizeof(hci_event), payload, size); - GetEmulationKernel().EnqueueIPCReply(ctrl.ios_request, - static_cast(sizeof(hci_event) + size)); -} - -// When the red sync button is pressed, a HCI event is generated: -// > HCI Event: Vendor (0xff) plen 1 -// 08 +// When the red sync button is pressed, a HCI event is generated. // This causes the emulated software to perform a BT inquiry and connect to found Wiimotes. void BluetoothRealDevice::FakeSyncButtonPressedEvent(USB::V0IntrMessage& ctrl) { NOTICE_LOG_FMT(IOS_WIIMOTE, "Faking 'sync button pressed' (0x08) event packet"); constexpr u8 payload[1] = {0x08}; - FakeSyncButtonEvent(ctrl, payload, sizeof(payload)); + FakeSyncButtonEvent(ctrl, payload); m_sync_button_state = SyncButtonState::Ignored; } @@ -579,7 +495,7 @@ void BluetoothRealDevice::FakeSyncButtonHeldEvent(USB::V0IntrMessage& ctrl) { NOTICE_LOG_FMT(IOS_WIIMOTE, "Faking 'sync button held' (0x09) event packet"); constexpr u8 payload[1] = {0x09}; - FakeSyncButtonEvent(ctrl, payload, sizeof(payload)); + FakeSyncButtonEvent(ctrl, payload); m_sync_button_state = SyncButtonState::Ignored; } @@ -588,13 +504,13 @@ void BluetoothRealDevice::LoadLinkKeys() std::string entries = Config::Get(Config::MAIN_BLUETOOTH_PASSTHROUGH_LINK_KEYS); if (entries.empty()) return; - for (const auto& pair : SplitString(entries, ',')) + for (std::string_view pair : SplitString(entries, ',')) { const auto index = pair.find('='); if (index == std::string::npos) continue; - const std::string address_string = pair.substr(0, index); + const auto address_string = pair.substr(0, index); std::optional address = Common::StringToMacAddress(address_string); if (!address) { @@ -604,18 +520,14 @@ void BluetoothRealDevice::LoadLinkKeys() continue; } - auto& mac = address.value(); + auto& mac = *address; std::ranges::reverse(mac); - const std::string& key_string = pair.substr(index + 1); + const auto key_string = pair.substr(index + 1); linkkey_t key{}; size_t pos = 0; - for (size_t i = 0; i < key_string.length(); i = i + 2) - { - int value; - std::istringstream(key_string.substr(i, 2)) >> std::hex >> value; - key[pos++] = value; - } + for (size_t i = 0; i < key_string.length() && pos != key.size(); i += 2) + TryParse(std::string(key_string.substr(i, 2)), &key[pos++], 16); m_link_keys[mac] = key; } @@ -623,179 +535,25 @@ void BluetoothRealDevice::LoadLinkKeys() void BluetoothRealDevice::SaveLinkKeys() { - std::ostringstream oss; - for (const auto& entry : m_link_keys) + std::string config_string; + for (const auto& [bdaddr, linkkey] : m_link_keys) { bdaddr_t address; // Reverse the address so that it is stored in the correct order in the config file - std::ranges::reverse_copy(entry.first, address.begin()); - oss << Common::MacAddressToString(address); - oss << '='; - oss << std::hex; - for (u8 data : entry.second) - { - // We cast to u16 here in order to have it displayed as two nibbles. - oss << std::setfill('0') << std::setw(2) << static_cast(data); - } - oss << std::dec << ','; + std::ranges::reverse_copy(bdaddr, address.begin()); + + config_string += Common::MacAddressToString(address); + config_string += '='; + for (u8 c : linkkey) + config_string += fmt::format("{:02x}", c); + + config_string += ','; } - std::string config_string = oss.str(); + if (!config_string.empty()) config_string.pop_back(); + Config::SetBase(Config::MAIN_BLUETOOTH_PASSTHROUGH_LINK_KEYS, config_string); } -bool BluetoothRealDevice::OpenDevice(const libusb_device_descriptor& device_descriptor, - libusb_device* device) -{ - m_device = libusb_ref_device(device); - const int ret = libusb_open(m_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 BluetoothRealDevice::ListDevices() -{ - std::vector device_list; - LibusbUtils::Context context; - - if (!context.IsValid()) - return {}; - - int result = context.GetDeviceList([&device_list](libusb_device* device) { - libusb_device_descriptor desc; - - auto [config_ret, config] = LibusbUtils::MakeConfigDescriptor(device, 0); - if (config_ret != LIBUSB_SUCCESS) - return true; - - if (libusb_get_device_descriptor(device, &desc) != LIBUSB_SUCCESS) - return true; - - if (IsBluetoothDevice(desc)) - { - const std::string device_name = - 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; -} - -// The callbacks are called from libusb code on a separate thread. -void BluetoothRealDevice::HandleCtrlTransfer(libusb_transfer* tr) -{ - std::lock_guard lk(m_transfers_mutex); - if (!m_current_transfers.contains(tr)) - return; - - if (tr->status != LIBUSB_TRANSFER_COMPLETED && tr->status != LIBUSB_TRANSFER_NO_DEVICE) - { - ERROR_LOG_FMT(IOS_WIIMOTE, "libusb command transfer failed, status: {:#04x}", - Common::ToUnderlying(tr->status)); - if (!m_showed_failed_transfer.IsSet()) - { - Core::DisplayMessage("Failed to send a command to the Bluetooth adapter.", 10000); - Core::DisplayMessage("It may not be compatible with passthrough mode.", 10000); - m_showed_failed_transfer.Set(); - } - } - else - { - m_showed_failed_transfer.Clear(); - } - const auto& command = m_current_transfers.at(tr).command; - command->FillBuffer(libusb_control_transfer_get_data(tr), tr->actual_length); - GetEmulationKernel().EnqueueIPCReply(command->ios_request, tr->actual_length, 0, - CoreTiming::FromThread::ANY); - m_current_transfers.erase(tr); -} - -void BluetoothRealDevice::HandleBulkOrIntrTransfer(libusb_transfer* tr) -{ - std::lock_guard lk(m_transfers_mutex); - if (!m_current_transfers.contains(tr)) - return; - - if (tr->status != LIBUSB_TRANSFER_COMPLETED && tr->status != LIBUSB_TRANSFER_TIMED_OUT && - tr->status != LIBUSB_TRANSFER_NO_DEVICE) - { - ERROR_LOG_FMT(IOS_WIIMOTE, "libusb transfer failed, status: {:#04x}", - Common::ToUnderlying(tr->status)); - if (!m_showed_failed_transfer.IsSet()) - { - Core::DisplayMessage("Failed to transfer to or from to the Bluetooth adapter.", 10000); - Core::DisplayMessage("It may not be compatible with passthrough mode.", 10000); - m_showed_failed_transfer.Set(); - } - } - else - { - m_showed_failed_transfer.Clear(); - } - - if (tr->status == LIBUSB_TRANSFER_COMPLETED && tr->endpoint == HCI_EVENT) - { - const u8 event = tr->buffer[0]; - if (event == HCI_EVENT_LINK_KEY_NOTIFICATION) - { - hci_link_key_notification_ep notification; - std::memcpy(¬ification, tr->buffer + sizeof(hci_event_hdr_t), sizeof(notification)); - linkkey_t key; - std::ranges::copy(notification.key, std::begin(key)); - m_link_keys[notification.bdaddr] = key; - } - else if (event == HCI_EVENT_COMMAND_COMPL) - { - hci_command_compl_ep complete_event; - std::memcpy(&complete_event, tr->buffer + sizeof(hci_event_hdr_t), sizeof(complete_event)); - if (complete_event.opcode == HCI_CMD_RESET) - m_need_reset_keys.Set(); - } - } - - const auto& command = m_current_transfers.at(tr).command; - command->FillBuffer(tr->buffer, tr->actual_length); - GetEmulationKernel().EnqueueIPCReply(command->ios_request, tr->actual_length, 0, - CoreTiming::FromThread::ANY); - m_current_transfers.erase(tr); -} } // namespace IOS::HLE diff --git a/Source/Core/Core/IOS/USB/Bluetooth/BTReal.h b/Source/Core/Core/IOS/USB/Bluetooth/BTReal.h index b40ace416e..4ffc8fd969 100644 --- a/Source/Core/Core/IOS/USB/Bluetooth/BTReal.h +++ b/Source/Core/Core/IOS/USB/Bluetooth/BTReal.h @@ -6,25 +6,22 @@ #if defined(__LIBUSB__) #include #include +#include #include #include -#include +#include #include #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 Open(const OpenRequest& request) override; std::optional Close(u32 fd) override; std::optional 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 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 m_lib_usb_bt_adapter; + static constexpr u32 SYNC_BUTTON_HOLD_MS_TO_RESET = 10000; std::atomic 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 command_, std::unique_ptr buffer_) - : command(std::move(command_)), buffer(std::move(buffer_)) - { - } - std::unique_ptr command; - std::unique_ptr buffer; - }; - std::map 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> 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 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 m_hci_endpoint; + std::unique_ptr 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 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 diff --git a/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp b/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp new file mode 100644 index 0000000000..2264d4d956 --- /dev/null +++ b/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.cpp @@ -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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 +constexpr libusb_transfer_cb_fn LibUSBMemFunCallback() +{ + return [](libusb_transfer* tr) { + std::invoke(MemFun, static_cast*>(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(manufacturer), + reinterpret_cast(product), reinterpret_cast(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(buffer); + } + else if (event == HCI_EVENT_COMMAND_STATUS) + { + AcknowledgeCommand(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 +void LibUSBBluetoothAdapter::AcknowledgeCommand(std::span 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{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 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 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 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::ListDevices() +{ + std::vector 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; + } +} diff --git a/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h b/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h new file mode 100644 index 0000000000..6e0f8bc1de --- /dev/null +++ b/Source/Core/Core/IOS/USB/Bluetooth/LibUSBBluetoothAdapter.h @@ -0,0 +1,157 @@ +// Copyright 2025 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +#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; + + struct BluetoothDeviceInfo + { + u16 vid; + u16 pid; + std::string name; + }; + + static std::vector 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 data, TimePoint target_time); + void ScheduleControlTransfer(u8 type, u8 request, u8 value, u8 index, std::span data, + TimePoint target_time); + + // Schedule a transfer to be submitted as soon as possible. + void SendControlTransfer(std::span 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 m_transfers_to_free; + + // Incoming packets Push'd from libusb callback thread. + Common::SPSCQueue m_hci_event_queue; + Common::SPSCQueue m_acl_data_queue; + + // All transfers are alloc'd and kept here until complete. + std::map> 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 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 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 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 + void AcknowledgeCommand(std::span 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); +}; diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 49ff84dd30..bbfb92ebe5 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -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 diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 02a4894609..efcd9f5a92 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -402,6 +402,7 @@ + @@ -1079,6 +1080,7 @@ + diff --git a/Source/Core/DolphinQt/Config/WiimoteControllersWidget.cpp b/Source/Core/DolphinQt/Config/WiimoteControllersWidget.cpp index 1cc1271517..ef6989ad9b 100644 --- a/Source/Core/DolphinQt/Config/WiimoteControllersWidget.cpp +++ b/Source/Core/DolphinQt/Config/WiimoteControllersWidget.cpp @@ -15,9 +15,6 @@ #include #include -#include -#include - #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& devices) + const std::vector& 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(); + auto device_info = + m_bluetooth_adapters->itemData(index).value(); Config::SetBaseOrCurrent(Config::MAIN_BLUETOOTH_PASSTHROUGH_PID, device_info.pid); Config::SetBaseOrCurrent(Config::MAIN_BLUETOOTH_PASSTHROUGH_VID, device_info.vid); diff --git a/Source/Core/DolphinQt/Config/WiimoteControllersWidget.h b/Source/Core/DolphinQt/Config/WiimoteControllersWidget.h index b174c1b786..e8507d9866 100644 --- a/Source/Core/DolphinQt/Config/WiimoteControllersWidget.h +++ b/Source/Core/DolphinQt/Config/WiimoteControllersWidget.h @@ -8,7 +8,7 @@ #include #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& devices); + const std::vector& devices); void OnWiimoteRefreshPressed(); void OnWiimoteConfigure(size_t index); void StartBluetoothAdapterRefresh();