/****************************************************************************** * * Copyright 2000-2012 Broadcom Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ /****************************************************************************** * * This file contains functions that handle SCO connections. This includes * operations such as connect, disconnect, change supported packet types. * ******************************************************************************/ #define LOG_TAG "btm_sco" #include "stack/btm/btm_sco.h" #include #include #include #include #include #include #include "common/bidi_queue.h" #include "device/include/device_iot_config.h" #include "hci/class_of_device.h" #include "hci/controller_interface.h" #include "hci/hci_layer.h" #include "hci/hci_packets.h" #include "hci/include/hci_layer.h" #include "internal_include/bt_target.h" #include "main/shim/entry.h" #include "main/shim/helpers.h" #include "osi/include/properties.h" #include "osi/include/stack_power_telemetry.h" #include "stack/btm/btm_int_types.h" #include "stack/btm/btm_sco_hfp_hal.h" #include "stack/btm/btm_sec.h" #include "stack/include/acl_api.h" #include "stack/include/bt_dev_class.h" #include "stack/include/btm_api.h" #include "stack/include/btm_api_types.h" #include "stack/include/btm_client_interface.h" #include "stack/include/btm_log_history.h" #include "stack/include/hci_error_code.h" #include "stack/include/hcimsgs.h" #include "stack/include/main_thread.h" #include "stack/include/sdpdefs.h" #include "stack/include/stack_metrics_logging.h" #include "types/raw_address.h" extern tBTM_CB btm_cb; /* Default to allow enhanced connections where supported. */ constexpr bool kDefaultDisableEnhancedConnection = false; /* Sysprops for SCO connection. */ static const char kPropertyDisableEnhancedConnection[] = "bluetooth.sco.disable_enhanced_connection"; namespace { /* Structure passed with SCO change command and events. * Used by both Sync and Enhanced sync messaging */ typedef struct { uint16_t max_latency_ms; uint16_t packet_types; uint8_t retransmission_effort; } tBTM_CHG_ESCO_PARAMS; constexpr char kBtmLogTag[] = "SCO"; }; // namespace using namespace bluetooth; using bluetooth::legacy::hci::GetInterface; // forward declaration for dequeueing packets static void btm_route_sco_data(bluetooth::hci::ScoView valid_packet); void btm_sco_conn_req(const RawAddress& bda, const DEV_CLASS& dev_class, uint8_t link_type); void btm_sco_on_disconnected(uint16_t hci_handle, tHCI_REASON reason); bool btm_sco_removed(uint16_t hci_handle, tHCI_REASON reason); namespace cpp { bluetooth::common::BidiQueueEnd* hci_sco_queue_end = nullptr; static bluetooth::os::EnqueueBuffer* pending_sco_data = nullptr; static void sco_data_callback() { if (hci_sco_queue_end == nullptr) { return; } auto packet = hci_sco_queue_end->TryDequeue(); log::assert_that(packet != nullptr, "assert failed: packet != nullptr"); if (!packet->IsValid()) { log::info("Dropping invalid packet of size {}", packet->size()); return; } if (do_in_main_thread(FROM_HERE, base::Bind(&btm_route_sco_data, *packet)) != BT_STATUS_SUCCESS) { log::error("do_in_main_thread failed from sco_data_callback"); } } static void register_for_sco() { hci_sco_queue_end = bluetooth::shim::GetHciLayer()->GetScoQueueEnd(); hci_sco_queue_end->RegisterDequeue( bluetooth::shim::GetGdShimHandler(), bluetooth::common::Bind(sco_data_callback)); pending_sco_data = new bluetooth::os::EnqueueBuffer( hci_sco_queue_end); // Register SCO for connection requests bluetooth::shim::GetHciLayer()->RegisterForScoConnectionRequests( get_main_thread()->Bind( [](bluetooth::hci::Address peer, bluetooth::hci::ClassOfDevice cod, bluetooth::hci::ConnectionRequestLinkType link_type) { auto peer_raw_address = bluetooth::ToRawAddress(peer); DEV_CLASS dev_class{cod.cod[0], cod.cod[1], cod.cod[2]}; if (link_type == bluetooth::hci::ConnectionRequestLinkType::ESCO) { btm_sco_conn_req(peer_raw_address, dev_class, android::bluetooth::LINK_TYPE_ESCO); } else { btm_sco_conn_req(peer_raw_address, dev_class, android::bluetooth::LINK_TYPE_SCO); } })); // Register SCO for disconnect notifications bluetooth::shim::GetHciLayer()->RegisterForDisconnects( get_main_thread()->Bind( [](uint16_t handle, bluetooth::hci::ErrorCode error_code) { auto reason = static_cast(error_code); btm_sco_on_disconnected(handle, reason); btm_sco_removed(handle, reason); })); } static void shut_down_sco() { if (pending_sco_data != nullptr) { pending_sco_data->Clear(); delete pending_sco_data; pending_sco_data = nullptr; } if (hci_sco_queue_end != nullptr) { hci_sco_queue_end->UnregisterDequeue(); hci_sco_queue_end = nullptr; } } }; // namespace cpp void tSCO_CB::Init() { hfp_hal_interface::init(); def_esco_parms = esco_parameters_for_codec( ESCO_CODEC_CVSD_S3, hfp_hal_interface::get_offload_enabled()); cpp::register_for_sco(); } void tSCO_CB::Free() { cpp::shut_down_sco(); bluetooth::audio::sco::cleanup(); } /******************************************************************************/ /* L O C A L D A T A D E F I N I T I O N S */ /******************************************************************************/ /* MACROs to convert from SCO packet types mask to ESCO and back */ #define BTM_SCO_PKT_TYPE_MASK \ (HCI_PKT_TYPES_MASK_HV1 | HCI_PKT_TYPES_MASK_HV2 | HCI_PKT_TYPES_MASK_HV3) /* Mask defining only the SCO types of an esco packet type */ #define BTM_ESCO_PKT_TYPE_MASK \ (ESCO_PKT_TYPES_MASK_HV1 | ESCO_PKT_TYPES_MASK_HV2 | ESCO_PKT_TYPES_MASK_HV3) #define BTM_ESCO_2_SCO(escotype) \ ((uint16_t)(((escotype)&BTM_ESCO_PKT_TYPE_MASK) << 5)) /* Define masks for supported and exception 2.0 SCO packet types */ #define BTM_SCO_SUPPORTED_PKTS_MASK \ (ESCO_PKT_TYPES_MASK_HV1 | ESCO_PKT_TYPES_MASK_HV2 | \ ESCO_PKT_TYPES_MASK_HV3 | ESCO_PKT_TYPES_MASK_EV3 | \ ESCO_PKT_TYPES_MASK_EV4 | ESCO_PKT_TYPES_MASK_EV5) #define BTM_SCO_EXCEPTION_PKTS_MASK \ (ESCO_PKT_TYPES_MASK_NO_2_EV3 | ESCO_PKT_TYPES_MASK_NO_3_EV3 | \ ESCO_PKT_TYPES_MASK_NO_2_EV5 | ESCO_PKT_TYPES_MASK_NO_3_EV5) /* Buffer used for reading PCM data from audio server that will be encoded into * mSBC packet. The BTM_SCO_DATA_SIZE_MAX should be set to a number divisible by * BTM_MSBC_CODE_SIZE(240) */ static uint8_t btm_pcm_buf[BTM_SCO_DATA_SIZE_MAX] = {0}; static uint8_t packet_buf[BTM_SCO_DATA_SIZE_MAX] = {0}; /* The read and write offset for btm_pcm_buf. * They are only used for WBS and the unit is byte. */ static size_t btm_pcm_buf_read_offset = 0; static size_t btm_pcm_buf_write_offset = 0; static bool btm_pcm_buf_write_mirror = false; static bool btm_pcm_buf_read_mirror = false; enum btm_pcm_buf_state { DECODE_BUF_EMPTY, DECODE_BUF_FULL, // Neither empty nor full. DECODE_BUF_PARTIAL, }; void incr_btm_pcm_buf_offset(size_t& offset, bool& mirror, size_t amount) { size_t bytes_remaining = BTM_SCO_DATA_SIZE_MAX - offset; if (bytes_remaining > amount) { offset += amount; return; } mirror = !mirror; offset = amount - bytes_remaining; } btm_pcm_buf_state btm_pcm_buf_status() { if (btm_pcm_buf_read_offset == btm_pcm_buf_write_offset) { if (btm_pcm_buf_read_mirror == btm_pcm_buf_write_mirror) return DECODE_BUF_EMPTY; return DECODE_BUF_FULL; } return DECODE_BUF_PARTIAL; } size_t btm_pcm_buf_data_len() { switch (btm_pcm_buf_status()) { case DECODE_BUF_EMPTY: return 0; case DECODE_BUF_FULL: return BTM_SCO_DATA_SIZE_MAX; case DECODE_BUF_PARTIAL: default: if (btm_pcm_buf_write_offset > btm_pcm_buf_read_offset) return btm_pcm_buf_write_offset - btm_pcm_buf_read_offset; return BTM_SCO_DATA_SIZE_MAX - (btm_pcm_buf_read_offset - btm_pcm_buf_write_offset); }; } size_t btm_pcm_buf_avail_len() { return BTM_SCO_DATA_SIZE_MAX - btm_pcm_buf_data_len(); } size_t write_btm_pcm_buf(uint8_t* source, size_t amount) { if (btm_pcm_buf_avail_len() < amount) { return 0; } size_t bytes_remaining = BTM_SCO_DATA_SIZE_MAX - btm_pcm_buf_write_offset; if (bytes_remaining > amount) { std::copy(source, source + amount, btm_pcm_buf + btm_pcm_buf_write_offset); } else { std::copy(source, source + bytes_remaining, btm_pcm_buf + btm_pcm_buf_write_offset); std::copy(source + bytes_remaining, source + amount, btm_pcm_buf); } incr_btm_pcm_buf_offset(btm_pcm_buf_write_offset, btm_pcm_buf_write_mirror, amount); return amount; } /******************************************************************************/ /* L O C A L F U N C T I O N P R O T O T Y P E S */ /******************************************************************************/ static tBTM_STATUS BTM_ChangeEScoLinkParms(uint16_t sco_inx, tBTM_CHG_ESCO_PARAMS* p_parms); static uint16_t btm_sco_voice_settings_to_legacy(enh_esco_params_t* p_parms); /******************************************************************************* * * Function btm_esco_conn_rsp * * Description This function is called upon receipt of an (e)SCO connection * request event (BTM_ESCO_CONN_REQ_EVT) to accept or reject * the request. Parameters used to negotiate eSCO links. * If p_parms is NULL, then default values are used. * If the link type of the incoming request is SCO, then only * the tx_bw, max_latency, content format, and packet_types are * valid. The hci_status parameter should be * ([0x0] to accept, [0x0d..0x0f] to reject) * * Returns void * ******************************************************************************/ static void btm_esco_conn_rsp(uint16_t sco_inx, uint8_t hci_status, const RawAddress& bda, enh_esco_params_t* p_parms) { tSCO_CONN* p_sco = NULL; if (BTM_MAX_SCO_LINKS == 0) return; if (sco_inx < BTM_MAX_SCO_LINKS) p_sco = &btm_cb.sco_cb.sco_db[sco_inx]; /* Reject the connect request if refused by caller or wrong state */ if (hci_status != HCI_SUCCESS || p_sco == NULL) { if (p_sco) { p_sco->state = (p_sco->state == SCO_ST_W4_CONN_RSP) ? SCO_ST_LISTENING : SCO_ST_UNUSED; } if (!btm_cb.sco_cb.esco_supported) { btsnd_hcic_reject_conn(bda, hci_status); } else { btsnd_hcic_reject_esco_conn(bda, hci_status); } } else { /* Connection is being accepted */ p_sco->state = SCO_ST_CONNECTING; enh_esco_params_t* p_setup = &p_sco->esco.setup; /* If parameters not specified use the default */ if (p_parms) { *p_setup = *p_parms; } else { /* Use the last setup passed thru BTM_SetEscoMode (or defaults) */ *p_setup = btm_cb.sco_cb.def_esco_parms; } /* Use Enhanced Synchronous commands if supported */ if (bluetooth::shim::GetController()->IsSupported( bluetooth::hci::OpCode::ENHANCED_SETUP_SYNCHRONOUS_CONNECTION) && !osi_property_get_bool(kPropertyDisableEnhancedConnection, kDefaultDisableEnhancedConnection)) { log::verbose( "txbw 0x{:x}, rxbw 0x{:x}, lat 0x{:x}, retrans 0x{:02x}, pkt " "0x{:04x}, path {}", p_setup->transmit_bandwidth, p_setup->receive_bandwidth, p_setup->max_latency_ms, p_setup->retransmission_effort, p_setup->packet_types, p_setup->input_data_path); btsnd_hcic_enhanced_accept_synchronous_connection(bda, p_setup); } else { /* Use legacy command if enhanced SCO setup is not supported */ uint16_t voice_content_format = btm_sco_voice_settings_to_legacy(p_setup); btsnd_hcic_accept_esco_conn( bda, p_setup->transmit_bandwidth, p_setup->receive_bandwidth, p_setup->max_latency_ms, voice_content_format, p_setup->retransmission_effort, p_setup->packet_types); } } } /* Return the active (first connected) SCO connection block */ static tSCO_CONN* btm_get_active_sco() { for (auto& link : btm_cb.sco_cb.sco_db) { if (link.state == SCO_ST_CONNECTED) { return &link; } } return nullptr; } /******************************************************************************* * * Function btm_route_sco_data * * Description Route received SCO data. * This function is triggered when we receive a packet of SCO * data. It regards the received SCO packet as a clock tick to * start the write and read to and from the audio server. It * also tries to balance the write/read data rate between the * Bluetooth and Audio stack by sending and receiving the same * amount of PCM data to and from the audio server. * * Returns void * ******************************************************************************/ static void btm_route_sco_data(bluetooth::hci::ScoView valid_packet) { uint16_t handle = valid_packet.GetHandle(); if (handle > HCI_HANDLE_MAX) { log::error("Dropping SCO data with invalid handle: 0x{:X} > 0x{:X},", handle, HCI_HANDLE_MAX); return; } tSCO_CONN* active_sco = btm_get_active_sco(); if (active_sco == nullptr) { log::error("Received SCO data when there is no active SCO connection"); return; } if (active_sco->hci_handle != handle) { log::error("Dropping packet with handle(0x{:X}) != active handle(0x{:X})", handle, active_sco->hci_handle); return; } const auto codec_type = active_sco->get_codec_type(); const std::string codec = sco_codec_type_text(codec_type); auto data = valid_packet.GetData(); auto rx_data = data.data(); const uint8_t* decoded = nullptr; size_t written = 0, rc = 0; if (codec_type == BTM_SCO_CODEC_MSBC || codec_type == BTM_SCO_CODEC_LC3) { auto status = valid_packet.GetPacketStatusFlag(); if (status != bluetooth::hci::PacketStatusFlag::CORRECTLY_RECEIVED) { log::debug("{} packet corrupted with status({})", codec, PacketStatusFlagText(status)); } auto enqueue_packet = codec_type == BTM_SCO_CODEC_LC3 ? &bluetooth::audio::sco::swb::enqueue_packet : &bluetooth::audio::sco::wbs::enqueue_packet; rc = enqueue_packet( data, status != bluetooth::hci::PacketStatusFlag::CORRECTLY_RECEIVED); if (!rc) log::debug("Failed to enqueue {} packet", codec); while (rc) { auto decode = codec_type == BTM_SCO_CODEC_LC3 ? &bluetooth::audio::sco::swb::decode : &bluetooth::audio::sco::wbs::decode; rc = decode(&decoded); if (rc == 0) break; written += bluetooth::audio::sco::write(decoded, rc); } } else { written = bluetooth::audio::sco::write(rx_data, data.size()); } /* For Chrome OS, we send the outgoing data after receiving an incoming one. * server, so that we can keep the data read/write rate balanced */ size_t read = 0; const uint8_t* encoded = nullptr; if (codec_type == BTM_SCO_CODEC_MSBC || codec_type == BTM_SCO_CODEC_LC3) { while (written) { size_t avail = btm_pcm_buf_avail_len(); if (avail) { size_t to_read = written < avail ? written : avail; // Read to the packet_buf first and then copy to the btm_pcm_buf. read = bluetooth::audio::sco::read(packet_buf, to_read); write_btm_pcm_buf(packet_buf, read); if (read != to_read) { log::info( "Requested to read {} bytes of {} data but got {} bytes of PCM " "data from audio server: WriteOffset:{} ReadOffset:{}", (unsigned long)to_read, codec, (unsigned long)read, (unsigned long)btm_pcm_buf_write_offset, (unsigned long)btm_pcm_buf_read_offset); if (read == 0) break; } written -= read; } else { /* We don't break here so that we can still decode the data in the * buffer to spare the buffer space when the buffer is full */ log::warn( "Buffer is full when we try to read {} packet from audio server", codec); } auto encode = codec_type == BTM_SCO_CODEC_LC3 ? &bluetooth::audio::sco::swb::encode : &bluetooth::audio::sco::wbs::encode; size_t data_len = btm_pcm_buf_data_len(); if (data_len) { // Copy all data to the packet_buf first and then call encode. size_t bytes_remaining = BTM_SCO_DATA_SIZE_MAX - btm_pcm_buf_read_offset; if (bytes_remaining > data_len) { std::copy(btm_pcm_buf + btm_pcm_buf_read_offset, btm_pcm_buf + btm_pcm_buf_read_offset + data_len, packet_buf); } else { std::copy(btm_pcm_buf + btm_pcm_buf_read_offset, btm_pcm_buf + BTM_SCO_DATA_SIZE_MAX, packet_buf); std::copy(btm_pcm_buf, btm_pcm_buf + data_len - bytes_remaining, packet_buf + bytes_remaining); } rc = encode((int16_t*)packet_buf, data_len); incr_btm_pcm_buf_offset(btm_pcm_buf_read_offset, btm_pcm_buf_read_mirror, rc); if (!rc) log::debug( "Failed to encode {} data starting at ReadOffset:{} to " "WriteOffset:{}", codec, (unsigned long)btm_pcm_buf_read_offset, (unsigned long)btm_pcm_buf_write_offset); } /* Send all of the available SCO packets buffered in the queue */ while (1) { auto dequeue_packet = codec_type == BTM_SCO_CODEC_LC3 ? &bluetooth::audio::sco::swb::dequeue_packet : &bluetooth::audio::sco::wbs::dequeue_packet; rc = dequeue_packet(&encoded); if (!rc) break; auto data = std::vector(encoded, encoded + rc); btm_send_sco_packet(std::move(data)); } } } else { while (written) { read = bluetooth::audio::sco::read( btm_pcm_buf, written < BTM_SCO_DATA_SIZE_MAX ? written : BTM_SCO_DATA_SIZE_MAX); if (read == 0) { log::info("Failed to read {} bytes of PCM data from audio server", (unsigned long)(written < BTM_SCO_DATA_SIZE_MAX ? written : BTM_SCO_DATA_SIZE_MAX)); break; } written -= read; /* In narrow-band, the CVSD encode is offloaded to controller so we can * send PCM data directly to SCO. * We don't maintain buffer read/write offset for NB as we send all data * that we read from the audio server. */ auto data = std::vector(btm_pcm_buf, btm_pcm_buf + read); btm_send_sco_packet(std::move(data)); } } } void btm_send_sco_packet(std::vector data) { auto* active_sco = btm_get_active_sco(); if (active_sco == nullptr || data.empty()) { return; } log::assert_that(data.size() <= BTM_SCO_DATA_SIZE_MAX, "Invalid SCO data size: {}", (unsigned long)data.size()); uint16_t handle_with_flags = active_sco->hci_handle; uint16_t handle = HCID_GET_HANDLE(handle_with_flags); log::assert_that(handle <= HCI_HANDLE_MAX, "Require handle <= 0x{:X}, but is 0x{:X}", HCI_HANDLE_MAX, handle); auto sco_packet = bluetooth::hci::ScoBuilder::Create( handle, bluetooth::hci::PacketStatusFlag::CORRECTLY_RECEIVED, std::move(data)); cpp::pending_sco_data->Enqueue(std::move(sco_packet), bluetooth::shim::GetGdShimHandler()); } /******************************************************************************* * * Function btm_send_connect_request * * Description This function is called to respond to SCO connect * indications * * Returns void * ******************************************************************************/ static tBTM_STATUS btm_send_connect_request(uint16_t acl_handle, enh_esco_params_t* p_setup) { /* Send connect request depending on version of spec */ if (!btm_cb.sco_cb.esco_supported) { log::info("sending non-eSCO request for handle={}", unsigned(acl_handle)); btsnd_hcic_add_SCO_conn(acl_handle, BTM_ESCO_2_SCO(p_setup->packet_types)); } else { /* Save the previous values in case command fails */ uint16_t saved_packet_types = p_setup->packet_types; uint8_t saved_retransmission_effort = p_setup->retransmission_effort; uint16_t saved_max_latency_ms = p_setup->max_latency_ms; uint16_t temp_packet_types = (p_setup->packet_types & static_cast(BTM_SCO_SUPPORTED_PKTS_MASK) & btm_cb.btm_sco_pkt_types_supported); /* OR in any exception packet types */ temp_packet_types |= ((p_setup->packet_types & BTM_SCO_EXCEPTION_PKTS_MASK) | (btm_cb.btm_sco_pkt_types_supported & BTM_SCO_EXCEPTION_PKTS_MASK)); /* Finally, remove EDR eSCO if the remote device doesn't support it */ /* UPF25: Only SCO was brought up in this case */ const RawAddress bd_addr = acl_address_from_handle(acl_handle); if (bd_addr != RawAddress::kEmpty) { if (!btm_peer_supports_esco_2m_phy(bd_addr)) { log::verbose("BTM Remote does not support 2-EDR eSCO"); temp_packet_types |= (ESCO_PKT_TYPES_MASK_NO_2_EV3 | ESCO_PKT_TYPES_MASK_NO_2_EV5); } if (!btm_peer_supports_esco_3m_phy(bd_addr)) { log::verbose("BTM Remote does not support 3-EDR eSCO"); temp_packet_types |= (ESCO_PKT_TYPES_MASK_NO_3_EV3 | ESCO_PKT_TYPES_MASK_NO_3_EV5); } if (!btm_peer_supports_esco_ev3(bd_addr)) { log::verbose("BTM Remote does not support EV3 eSCO"); // If EV3 is not supported, EV4 and EV% are not supported, either. temp_packet_types &= ~BTM_ESCO_LINK_ONLY_MASK; p_setup->retransmission_effort = ESCO_RETRANSMISSION_OFF; p_setup->max_latency_ms = 10; } /* Check to see if BR/EDR Secure Connections is being used ** If so, we cannot use SCO-only packet types (HFP 1.7) */ const bool local_supports_sc = bluetooth::shim::GetController()->SupportsSecureConnections(); const bool remote_supports_sc = BTM_PeerSupportsSecureConnections(bd_addr); if (local_supports_sc && remote_supports_sc) { temp_packet_types &= ~(BTM_SCO_PKT_TYPE_MASK); if (temp_packet_types == 0) { log::error( "SCO connection cannot support any packet types for " "acl_handle:0x{:04x}", acl_handle); return BTM_WRONG_MODE; } log::debug( "Both local and remote controllers support SCO secure connections " "handle:0x{:04x} pkt_types:0x{:04x}", acl_handle, temp_packet_types); } else if (!local_supports_sc && !remote_supports_sc) { log::debug( "Both local and remote controllers do not support secure " "connections for handle:0x{:04x}", acl_handle); } else if (remote_supports_sc) { log::debug( "Only remote controller supports secure connections for " "handle:0x{:04x}", acl_handle); } else { log::debug( "Only local controller supports secure connections for " "handle:0x{:04x}", acl_handle); } } else { log::error("Received SCO connect from unknown peer:{}", bd_addr); } p_setup->packet_types = temp_packet_types; /* Use Enhanced Synchronous commands if supported */ if (bluetooth::shim::GetController()->IsSupported( bluetooth::hci::OpCode::ENHANCED_SETUP_SYNCHRONOUS_CONNECTION) && !osi_property_get_bool(kPropertyDisableEnhancedConnection, kDefaultDisableEnhancedConnection)) { log::info("Sending enhanced SCO connect request over handle:0x{:04x}", acl_handle); log::info( "enhanced parameter list txbw=0x{:x}, rxbw=0x{}, latency_ms=0x{}, " "retransmit_effort=0x{}, pkt_type=0x{}, path=0x{}", unsigned(p_setup->transmit_bandwidth), unsigned(p_setup->receive_bandwidth), unsigned(p_setup->max_latency_ms), unsigned(p_setup->retransmission_effort), unsigned(p_setup->packet_types), unsigned(p_setup->input_data_path)); btsnd_hcic_enhanced_set_up_synchronous_connection(acl_handle, p_setup); p_setup->packet_types = saved_packet_types; p_setup->retransmission_effort = saved_retransmission_effort; p_setup->max_latency_ms = saved_max_latency_ms; } else { /* Use older command */ log::info("Sending eSCO connect request over handle:0x{:04x}", acl_handle); uint16_t voice_content_format = btm_sco_voice_settings_to_legacy(p_setup); log::info( "legacy parameter list txbw=0x{:x}, rxbw=0x{}, latency_ms=0x{}, " "retransmit_effort=0x{}, voice_content_format=0x{}, pkt_type=0x{}", unsigned(p_setup->transmit_bandwidth), unsigned(p_setup->receive_bandwidth), unsigned(p_setup->max_latency_ms), unsigned(p_setup->retransmission_effort), unsigned(voice_content_format), unsigned(p_setup->packet_types)); btsnd_hcic_setup_esco_conn( acl_handle, p_setup->transmit_bandwidth, p_setup->receive_bandwidth, p_setup->max_latency_ms, voice_content_format, p_setup->retransmission_effort, p_setup->packet_types); } } return (BTM_CMD_STARTED); } /******************************************************************************* * * Function BTM_CreateSco * * Description This function is called to create an SCO connection. If the * "is_orig" flag is true, the connection will be originated, * otherwise BTM will wait for the other side to connect. * * NOTE: If BTM_IGNORE_SCO_PKT_TYPE is passed in the pkt_types * parameter the default packet types is used. * * Returns BTM_UNKNOWN_ADDR if the ACL connection is not up * BTM_BUSY if another SCO being set up to * the same BD address * BTM_NO_RESOURCES if the max SCO limit has been reached * BTM_CMD_STARTED if the connection establishment is started. * In this case, "*p_sco_inx" is filled in * with the sco index used for the connection. * ******************************************************************************/ tBTM_STATUS BTM_CreateSco(const RawAddress* remote_bda, bool is_orig, uint16_t pkt_types, uint16_t* p_sco_inx, tBTM_SCO_CB* p_conn_cb, tBTM_SCO_CB* p_disc_cb) { enh_esco_params_t* p_setup; tSCO_CONN* p = &btm_cb.sco_cb.sco_db[0]; uint16_t xx; uint16_t acl_handle = HCI_INVALID_HANDLE; *p_sco_inx = BTM_INVALID_SCO_INDEX; if (BTM_MAX_SCO_LINKS == 0) { return BTM_NO_RESOURCES; } /* If originating, ensure that there is an ACL connection to the BD Address */ if (is_orig) { if (!remote_bda) { log::error("remote_bda is null"); return BTM_ILLEGAL_VALUE; } acl_handle = BTM_GetHCIConnHandle(*remote_bda, BT_TRANSPORT_BR_EDR); if (acl_handle == HCI_INVALID_HANDLE) { log::error("cannot find ACL handle for remote device {}", *remote_bda); return BTM_UNKNOWN_ADDR; } } if (remote_bda) { /* If any SCO is being established to the remote BD address, refuse this */ for (xx = 0; xx < BTM_MAX_SCO_LINKS; xx++, p++) { if (((p->state == SCO_ST_CONNECTING) || (p->state == SCO_ST_LISTENING) || (p->state == SCO_ST_PEND_UNPARK)) && (p->esco.data.bd_addr == *remote_bda)) { log::error("a sco connection is already going on for {}, at state {}", *remote_bda, unsigned(p->state)); return BTM_BUSY; } } } else { /* Support only 1 wildcard BD address at a time */ for (xx = 0; xx < BTM_MAX_SCO_LINKS; xx++, p++) { if ((p->state == SCO_ST_LISTENING) && (!p->rem_bd_known)) { log::error( "remote_bda is null and not known and we are still listening"); return BTM_BUSY; } } } /* Try to find an unused control block, and kick off the SCO establishment */ for (xx = 0, p = &btm_cb.sco_cb.sco_db[0]; xx < BTM_MAX_SCO_LINKS; xx++, p++) { if (p->state == SCO_ST_UNUSED) { if (remote_bda) { if (is_orig) { // can not create SCO link if in park mode tBTM_PM_STATE state; if (BTM_ReadPowerMode(*remote_bda, &state)) { if (state == BTM_PM_ST_SNIFF || state == BTM_PM_ST_PARK || state == BTM_PM_ST_PENDING) { log::info("{} in sniff, park or pending mode {}", *remote_bda, unsigned(state)); if (!BTM_SetLinkPolicyActiveMode(*remote_bda)) { log::warn("Unable to set link policy active"); } p->state = SCO_ST_PEND_UNPARK; } } else { log::error("failed to read power mode for {}", *remote_bda); } } p->esco.data.bd_addr = *remote_bda; p->rem_bd_known = true; } else p->rem_bd_known = false; p_setup = &p->esco.setup; *p_setup = btm_cb.sco_cb.def_esco_parms; /* Determine the packet types */ p_setup->packet_types = pkt_types & BTM_SCO_SUPPORTED_PKTS_MASK & btm_cb.btm_sco_pkt_types_supported; /* OR in any exception packet types */ if (bluetooth::shim::GetController() ->GetLocalVersionInformation() .hci_version_ >= bluetooth::hci::HciVersion::V_2_0) { p_setup->packet_types |= (pkt_types & BTM_SCO_EXCEPTION_PKTS_MASK) | (btm_cb.btm_sco_pkt_types_supported & BTM_SCO_EXCEPTION_PKTS_MASK); } p->p_conn_cb = p_conn_cb; p->p_disc_cb = p_disc_cb; p->hci_handle = HCI_INVALID_HANDLE; p->is_orig = is_orig; if (p->state != SCO_ST_PEND_UNPARK) { if (is_orig) { /* If role change is in progress, do not proceed with SCO setup * Wait till role change is complete */ if (!acl_is_switch_role_idle(*remote_bda, BT_TRANSPORT_BR_EDR)) { log::verbose("Role Change is in progress for ACL handle 0x{:04x}", acl_handle); p->state = SCO_ST_PEND_ROLECHANGE; } } } if (p->state != SCO_ST_PEND_UNPARK && p->state != SCO_ST_PEND_ROLECHANGE) { if (is_orig) { log::debug("Initiating (e)SCO link for ACL handle:0x{:04x}", acl_handle); if ((btm_send_connect_request(acl_handle, p_setup)) != BTM_CMD_STARTED) { log::error("failed to send connect request for {}", *remote_bda); return (BTM_NO_RESOURCES); } p->state = SCO_ST_CONNECTING; } else { log::debug("Listening for (e)SCO on ACL handle:0x{:04x}", acl_handle); p->state = SCO_ST_LISTENING; } } *p_sco_inx = xx; log::debug("SCO connection successfully requested"); if (p->state == SCO_ST_CONNECTING) { BTM_LogHistory( kBtmLogTag, *remote_bda, "Connecting", base::StringPrintf("local initiated acl:0x%04x", acl_handle)); } return BTM_CMD_STARTED; } } /* If here, all SCO blocks in use */ log::error("all SCO control blocks are in use"); return BTM_NO_RESOURCES; } /******************************************************************************* * * Function btm_sco_chk_pend_unpark * * Description This function is called by BTIF when there is a mode change * event to see if there are SCO commands waiting for the * unpark. * * Returns void * ******************************************************************************/ void btm_sco_chk_pend_unpark(tHCI_STATUS hci_status, uint16_t hci_handle) { tSCO_CONN* p = &btm_cb.sco_cb.sco_db[0]; for (uint16_t xx = 0; xx < BTM_MAX_SCO_LINKS; xx++, p++) { uint16_t acl_handle = BTM_GetHCIConnHandle(p->esco.data.bd_addr, BT_TRANSPORT_BR_EDR); if ((p->state == SCO_ST_PEND_UNPARK) && (acl_handle == hci_handle)) { log::info( "{} unparked, sending connection request, acl_handle={}, " "hci_status={}", p->esco.data.bd_addr, unsigned(acl_handle), unsigned(hci_status)); if (btm_send_connect_request(acl_handle, &p->esco.setup) == BTM_CMD_STARTED) { p->state = SCO_ST_CONNECTING; } else { log::error("failed to send connection request for {}", p->esco.data.bd_addr); } } } } /******************************************************************************* * * Function btm_sco_chk_pend_rolechange * * Description This function is called by BTIF when there is a role change * event to see if there are SCO commands waiting for the role * change. * * Returns void * ******************************************************************************/ void btm_sco_chk_pend_rolechange(uint16_t hci_handle) { uint16_t xx; uint16_t acl_handle; tSCO_CONN* p = &btm_cb.sco_cb.sco_db[0]; for (xx = 0; xx < BTM_MAX_SCO_LINKS; xx++, p++) { if ((p->state == SCO_ST_PEND_ROLECHANGE) && ((acl_handle = BTM_GetHCIConnHandle( p->esco.data.bd_addr, BT_TRANSPORT_BR_EDR)) == hci_handle)) { log::verbose( "btm_sco_chk_pend_rolechange -> (e)SCO Link for ACL handle 0x{:04x}", acl_handle); if ((btm_send_connect_request(acl_handle, &p->esco.setup)) == BTM_CMD_STARTED) p->state = SCO_ST_CONNECTING; } } } /******************************************************************************* * * Function btm_sco_disc_chk_pend_for_modechange * * Description This function is called by btm when there is a mode change * event to see if there are SCO disconnect commands waiting * for the mode change. * * Returns void * ******************************************************************************/ void btm_sco_disc_chk_pend_for_modechange(uint16_t hci_handle) { tSCO_CONN* p = &btm_cb.sco_cb.sco_db[0]; log::debug( "Checking for SCO pending mode change events hci_handle:0x{:04x} " "p->state:{}", hci_handle, sco_state_text(p->state)); for (uint16_t xx = 0; xx < BTM_MAX_SCO_LINKS; xx++, p++) { if ((p->state == SCO_ST_PEND_MODECHANGE) && (BTM_GetHCIConnHandle(p->esco.data.bd_addr, BT_TRANSPORT_BR_EDR)) == hci_handle) { log::debug("Removing SCO Link handle 0x{:04x}", p->hci_handle); if (get_btm_client_interface().sco.BTM_RemoveSco(xx) != BTM_SUCCESS) { log::warn("Unable to remove SCO link:{}", xx); } } } } /******************************************************************************* * * Function btm_sco_conn_req * * Description This function is called by BTU HCIF when an SCO connection * request is received from a remote. * * Returns void * ******************************************************************************/ void btm_sco_conn_req(const RawAddress& bda, const DEV_CLASS& dev_class, uint8_t link_type) { tSCO_CB* p_sco = &btm_cb.sco_cb; tSCO_CONN* p = &p_sco->sco_db[0]; tBTM_ESCO_CONN_REQ_EVT_DATA evt_data = {}; DEVICE_IOT_CONFIG_ADDR_INT_ADD_ONE(bda, IOT_CONF_KEY_HFP_SCO_CONN_COUNT); for (uint16_t sco_index = 0; sco_index < BTM_MAX_SCO_LINKS; sco_index++, p++) { /* * If the sco state is in the SCO_ST_CONNECTING state, we still need * to return accept sco to avoid race conditon for sco creation */ bool rem_bd_matches = p->rem_bd_known && p->esco.data.bd_addr == bda; if (((p->state == SCO_ST_CONNECTING) && rem_bd_matches) || ((p->state == SCO_ST_LISTENING) && (rem_bd_matches || !p->rem_bd_known))) { /* If this was a wildcard, it is not one any more */ p->rem_bd_known = true; p->esco.data.link_type = link_type; p->state = SCO_ST_W4_CONN_RSP; p->esco.data.bd_addr = bda; /* If no callback, auto-accept the connection if packet types match */ if (!p->esco.p_esco_cback) { /* If requesting eSCO reject if default parameters are SCO only */ if ((link_type == BTM_LINK_TYPE_ESCO && !(p_sco->def_esco_parms.packet_types & BTM_ESCO_LINK_ONLY_MASK) && ((p_sco->def_esco_parms.packet_types & BTM_SCO_EXCEPTION_PKTS_MASK) == BTM_SCO_EXCEPTION_PKTS_MASK)) /* Reject request if SCO is desired but no SCO packets delected */ || (link_type == BTM_LINK_TYPE_SCO && !(p_sco->def_esco_parms.packet_types & BTM_SCO_LINK_ONLY_MASK))) { btm_esco_conn_rsp(sco_index, HCI_ERR_HOST_REJECT_RESOURCES, bda, nullptr); } else { /* Accept the request */ btm_esco_conn_rsp(sco_index, HCI_SUCCESS, bda, nullptr); } } else { /* Notify upper layer of connect indication */ evt_data.bd_addr = bda; evt_data.dev_class = dev_class; evt_data.link_type = link_type; evt_data.sco_inx = sco_index; tBTM_ESCO_EVT_DATA btm_esco_evt_data = {}; btm_esco_evt_data.conn_evt = evt_data; p->esco.p_esco_cback(BTM_ESCO_CONN_REQ_EVT, &btm_esco_evt_data); } return; } } /* If here, no one wants the SCO connection. Reject it */ log::warn("rejecting SCO for {}", bda); btm_esco_conn_rsp(BTM_MAX_SCO_LINKS, HCI_ERR_HOST_REJECT_RESOURCES, bda, nullptr); } /******************************************************************************* * * Function btm_sco_connected * * Description This function is called by BTIF when an (e)SCO connection * is connected. * * Returns void * ******************************************************************************/ void btm_sco_connected(const RawAddress& bda, uint16_t hci_handle, tBTM_ESCO_DATA* p_esco_data) { tSCO_CONN* p = &btm_cb.sco_cb.sco_db[0]; uint16_t xx; bool spt = false; tBTM_CHG_ESCO_PARAMS parms = {}; int codec; for (xx = 0; xx < BTM_MAX_SCO_LINKS; xx++, p++) { if (((p->state == SCO_ST_CONNECTING) || (p->state == SCO_ST_LISTENING) || (p->state == SCO_ST_W4_CONN_RSP)) && (p->rem_bd_known) && (p->esco.data.bd_addr == bda)) { BTM_LogHistory( kBtmLogTag, bda, "Connection created", base::StringPrintf("sco_idx:%hu handle:0x%04x ", xx, hci_handle)); power_telemetry::GetInstance().LogLinkDetails(hci_handle, bda, true, false); if (p->state == SCO_ST_LISTENING) spt = true; p->state = SCO_ST_CONNECTED; p->hci_handle = hci_handle; BTM_LogHistory(kBtmLogTag, bda, "Connection success", base::StringPrintf("handle:0x%04x %s", hci_handle, (spt) ? "listener" : "initiator")); log::debug("Connected SCO link handle:0x{:04x} peer:{}", hci_handle, bda); if (!btm_cb.sco_cb.esco_supported) { p->esco.data.link_type = BTM_LINK_TYPE_SCO; if (spt) { parms.packet_types = p->esco.setup.packet_types; /* Keep the other parameters the same for SCO */ parms.max_latency_ms = p->esco.setup.max_latency_ms; parms.retransmission_effort = p->esco.setup.retransmission_effort; BTM_ChangeEScoLinkParms(xx, &parms); } } else { if (p_esco_data) p->esco.data = *p_esco_data; } (*p->p_conn_cb)(xx); codec = hfp_hal_interface::esco_coding_to_codec( p->esco.setup.transmit_coding_format.coding_format); hfp_hal_interface::notify_sco_connection_change( bda, /*is_connected=*/true, codec); /* In-band (non-offload) data path */ if (p->is_inband()) { const auto codec_type = p->get_codec_type(); if (codec_type == BTM_SCO_CODEC_MSBC || codec_type == BTM_SCO_CODEC_LC3) { btm_pcm_buf_read_offset = 0; btm_pcm_buf_write_offset = 0; auto init = codec_type == BTM_SCO_CODEC_LC3 ? &bluetooth::audio::sco::swb::init : &bluetooth::audio::sco::wbs::init; init(hfp_hal_interface::get_packet_size(codec)); } std::fill(std::begin(btm_pcm_buf), std::end(btm_pcm_buf), 0); bluetooth::audio::sco::open(); } return; } } } /******************************************************************************* * * Function btm_sco_connection_failed * * Description This function is called by BTIF when an (e)SCO connection * setup is failed. * * Returns void * ******************************************************************************/ void btm_sco_connection_failed(tHCI_STATUS hci_status, const RawAddress& bda, uint16_t hci_handle, tBTM_ESCO_DATA* /* p_esco_data */) { tSCO_CONN* p = &btm_cb.sco_cb.sco_db[0]; uint16_t xx; for (xx = 0; xx < BTM_MAX_SCO_LINKS; xx++, p++) { if (((p->state == SCO_ST_CONNECTING) || (p->state == SCO_ST_LISTENING) || (p->state == SCO_ST_W4_CONN_RSP)) && (p->rem_bd_known) && (p->esco.data.bd_addr == bda || bda == RawAddress::kEmpty)) { /* Report the error if originator, otherwise remain in Listen mode */ if (p->is_orig) { log::debug("SCO initiating connection failed handle:0x{:04x} reason:{}", hci_handle, hci_error_code_text(hci_status)); switch (hci_status) { case HCI_ERR_ROLE_SWITCH_PENDING: /* If role switch is pending, we need try again after role switch * is complete */ p->state = SCO_ST_PEND_ROLECHANGE; break; case HCI_ERR_LMP_ERR_TRANS_COLLISION: /* Avoid calling disconnect callback because of sco creation race */ break; default: /* Notify client about SCO failure */ p->state = SCO_ST_UNUSED; (*p->p_disc_cb)(xx); } BTM_LogHistory( kBtmLogTag, bda, "Connection failed", base::StringPrintf( "locally_initiated reason:%s", hci_reason_code_text(static_cast(hci_status)) .c_str())); } else { log::debug( "SCO terminating connection failed handle:0x{:04x} reason:{}", hci_handle, hci_error_code_text(hci_status)); if (p->state == SCO_ST_CONNECTING) { p->state = SCO_ST_UNUSED; (*p->p_disc_cb)(xx); } else { p->state = SCO_ST_LISTENING; if (bda != RawAddress::kEmpty) DEVICE_IOT_CONFIG_ADDR_INT_ADD_ONE( bda, IOT_CONF_KEY_HFP_SCO_CONN_FAIL_COUNT); } BTM_LogHistory( kBtmLogTag, bda, "Connection failed", base::StringPrintf( "remote_initiated reason:%s", hci_reason_code_text(static_cast(hci_status)) .c_str())); } return; } } } /******************************************************************************* * * Function BTM_RemoveSco * * Description This function is called to remove a specific SCO connection. * * Returns status of the operation * ******************************************************************************/ tBTM_STATUS BTM_RemoveSco(uint16_t sco_inx) { tSCO_CONN* p = &btm_cb.sco_cb.sco_db[sco_inx]; tBTM_PM_STATE state = BTM_PM_ST_INVALID; log::verbose(""); if (BTM_MAX_SCO_LINKS == 0) { return BTM_NO_RESOURCES; } /* Validity check */ if ((sco_inx >= BTM_MAX_SCO_LINKS) || (p->state == SCO_ST_UNUSED)) return (BTM_UNKNOWN_ADDR); /* If no HCI handle, simply drop the connection and return */ if (p->hci_handle == HCI_INVALID_HANDLE || p->state == SCO_ST_PEND_UNPARK) { p->hci_handle = HCI_INVALID_HANDLE; p->state = SCO_ST_UNUSED; p->esco.p_esco_cback = NULL; /* Deregister the eSCO event callback */ return (BTM_SUCCESS); } if (BTM_ReadPowerMode(p->esco.data.bd_addr, &state) && (state == BTM_PM_ST_PENDING)) { log::verbose("BTM_PM_ST_PENDING for ACL mapped with SCO Link 0x{:04x}", p->hci_handle); p->state = SCO_ST_PEND_MODECHANGE; return (BTM_CMD_STARTED); } tSCO_STATE old_state = p->state; p->state = SCO_ST_DISCONNECTING; GetInterface().Disconnect(p->Handle(), HCI_ERR_PEER_USER); log::debug("Disconnecting link sco_handle:0x{:04x} peer:{}", p->Handle(), p->esco.data.bd_addr); BTM_LogHistory( kBtmLogTag, p->esco.data.bd_addr, "Disconnecting", base::StringPrintf("local initiated handle:0x%04x previous_state:%s", p->Handle(), sco_state_text(old_state).c_str())); return (BTM_CMD_STARTED); } void BTM_RemoveSco(const RawAddress& bda) { tSCO_CONN* p = &btm_cb.sco_cb.sco_db[0]; uint16_t xx; for (xx = 0; xx < BTM_MAX_SCO_LINKS; xx++, p++) { if (p->rem_bd_known && p->esco.data.bd_addr == bda) { if (get_btm_client_interface().sco.BTM_RemoveSco(xx) != BTM_SUCCESS) { log::warn("Unable to remove SCO link:{}", xx); } } } } /******************************************************************************* * * Function btm_sco_removed * * Description This function is called by lower layers when an * disconnect is received. * * Returns true if the link is known about, else false * ******************************************************************************/ bool btm_sco_removed(uint16_t hci_handle, tHCI_REASON reason) { tSCO_CONN* p = &btm_cb.sco_cb.sco_db[0]; uint16_t xx; p = &btm_cb.sco_cb.sco_db[0]; for (xx = 0; xx < BTM_MAX_SCO_LINKS; xx++, p++) { if ((p->state != SCO_ST_UNUSED) && (p->state != SCO_ST_LISTENING) && (p->hci_handle == hci_handle)) { power_telemetry::GetInstance().LogLinkDetails( hci_handle, RawAddress::kEmpty, false, false); RawAddress bda(p->esco.data.bd_addr); p->state = SCO_ST_UNUSED; p->hci_handle = HCI_INVALID_HANDLE; p->rem_bd_known = false; p->esco.p_esco_cback = NULL; /* Deregister eSCO callback */ (*p->p_disc_cb)(xx); hfp_hal_interface::notify_sco_connection_change( bda, /*is_connected=*/false, hfp_hal_interface::esco_coding_to_codec( p->esco.setup.transmit_coding_format.coding_format)); log::debug("Disconnected SCO link handle:{} reason:{}", hci_handle, hci_reason_code_text(reason)); return true; } } return false; } void btm_sco_on_disconnected(uint16_t hci_handle, tHCI_REASON reason) { tSCO_CONN* p_sco = btm_cb.sco_cb.get_sco_connection_from_handle(hci_handle); if (p_sco == nullptr) { log::debug("Unable to find sco connection"); return; } if (!p_sco->is_active()) { log::info("Connection is not active handle:0x{:04x} reason:{}", hci_handle, hci_reason_code_text(reason)); return; } if (p_sco->state == SCO_ST_LISTENING) { log::info("Connection is in listening state handle:0x{:04x} reason:{}", hci_handle, hci_reason_code_text(reason)); return; } const RawAddress bd_addr(p_sco->esco.data.bd_addr); p_sco->state = SCO_ST_UNUSED; p_sco->hci_handle = HCI_INVALID_HANDLE; p_sco->rem_bd_known = false; p_sco->esco.p_esco_cback = NULL; /* Deregister eSCO callback */ (*p_sco->p_disc_cb)(btm_cb.sco_cb.get_index(p_sco)); log::debug("Disconnected SCO link handle:{} reason:{}", hci_handle, hci_reason_code_text(reason)); BTM_LogHistory(kBtmLogTag, bd_addr, "Disconnected", base::StringPrintf("handle:0x%04x reason:%s", hci_handle, hci_reason_code_text(reason).c_str())); hfp_hal_interface::notify_sco_connection_change( bd_addr, /*is_connected=*/false, hfp_hal_interface::esco_coding_to_codec( p_sco->esco.setup.transmit_coding_format.coding_format)); if (p_sco->is_inband()) { const auto codec_type = p_sco->get_codec_type(); if (codec_type == BTM_SCO_CODEC_MSBC || codec_type == BTM_SCO_CODEC_LC3) { auto fill_plc_stats = codec_type == BTM_SCO_CODEC_LC3 ? bluetooth::audio::sco::swb::fill_plc_stats : bluetooth::audio::sco::wbs::fill_plc_stats; int num_decoded_frames; double packet_loss_ratio; if (fill_plc_stats(&num_decoded_frames, &packet_loss_ratio)) { const int16_t codec_id = sco_codec_type_to_id(codec_type); const std::string codec = sco_codec_type_text(codec_type); log_hfp_audio_packet_loss_stats(bd_addr, num_decoded_frames, packet_loss_ratio, codec_id); log::debug( "Stopped SCO codec:{}, num_decoded_frames:{}, " "packet_loss_ratio:{:f}", codec, num_decoded_frames, packet_loss_ratio); } else { log::warn("Failed to get the packet loss stats"); } auto cleanup = codec_type == BTM_SCO_CODEC_LC3 ? bluetooth::audio::sco::swb::cleanup : bluetooth::audio::sco::wbs::cleanup; cleanup(); } bluetooth::audio::sco::cleanup(); } } /******************************************************************************* * * Function btm_sco_acl_removed * * Description This function is called when an ACL connection is * removed. If the BD address is NULL, it is assumed that * the local device is down, and all SCO links are removed. * If a specific BD address is passed, only SCO connections * to that BD address are removed. * * Returns void * ******************************************************************************/ void btm_sco_acl_removed(const RawAddress* bda) { tSCO_CONN* p = &btm_cb.sco_cb.sco_db[0]; uint16_t xx; for (xx = 0; xx < BTM_MAX_SCO_LINKS; xx++, p++) { if (p->state != SCO_ST_UNUSED) { if ((!bda) || (p->esco.data.bd_addr == *bda && p->rem_bd_known)) { p->state = SCO_ST_UNUSED; p->esco.p_esco_cback = NULL; /* Deregister eSCO callback */ (*p->p_disc_cb)(xx); } } } } /******************************************************************************* * * Function BTM_ReadScoBdAddr * * Description This function is read the remote BD Address for a specific * SCO connection, * * Returns pointer to BD address or NULL if not known * ******************************************************************************/ const RawAddress* BTM_ReadScoBdAddr(uint16_t sco_inx) { tSCO_CONN* p = &btm_cb.sco_cb.sco_db[sco_inx]; /* Validity check */ if ((sco_inx < BTM_MAX_SCO_LINKS) && (p->rem_bd_known)) return &(p->esco.data.bd_addr); else return (NULL); } /******************************************************************************* * * Function BTM_SetEScoMode * * Description This function sets up the negotiated parameters for SCO or * eSCO, and sets as the default mode used for outgoing calls * to BTM_CreateSco. It does not change any currently active * (e)SCO links. * Note: Incoming (e)SCO connections will always use packet * types supported by the controller. If eSCO is not * desired the feature should be disabled in the * controller's feature mask. * * Returns BTM_SUCCESS if the successful. * BTM_BUSY if there are one or more active (e)SCO links. * ******************************************************************************/ tBTM_STATUS BTM_SetEScoMode(enh_esco_params_t* p_parms) { log::assert_that(p_parms != nullptr, "eSCO parameters must have a value"); enh_esco_params_t* p_def = &btm_cb.sco_cb.def_esco_parms; if (btm_cb.sco_cb.esco_supported) { *p_def = *p_parms; log::debug( "Setting eSCO mode parameters txbw:0x{:08x} rxbw:0x{:08x} " "max_lat:0x{:04x} pkt:0x{:04x} rtx_effort:0x{:02x}", p_def->transmit_bandwidth, p_def->receive_bandwidth, p_def->max_latency_ms, p_def->packet_types, p_def->retransmission_effort); } else { /* Load defaults for SCO only */ *p_def = esco_parameters_for_codec( SCO_CODEC_CVSD_D1, hfp_hal_interface::get_offload_enabled()); log::warn("eSCO not supported so setting SCO parameters instead"); log::debug( "Setting SCO mode parameters txbw:0x{:08x} rxbw:0x{:08x} " "max_lat:0x{:04x} pkt:0x{:04x} rtx_effort:0x{:02x}", p_def->transmit_bandwidth, p_def->receive_bandwidth, p_def->max_latency_ms, p_def->packet_types, p_def->retransmission_effort); } return BTM_SUCCESS; } /******************************************************************************* * * Function BTM_RegForEScoEvts * * Description This function registers a SCO event callback with the * specified instance. It should be used to received * connection indication events and change of link parameter * events. * * Returns BTM_SUCCESS if the successful. * BTM_ILLEGAL_VALUE if there is an illegal sco_inx * BTM_MODE_UNSUPPORTED if controller version is not BT1.2 or * later or does not support eSCO. * ******************************************************************************/ tBTM_STATUS BTM_RegForEScoEvts(uint16_t sco_inx, tBTM_ESCO_CBACK* p_esco_cback) { if (BTM_MAX_SCO_LINKS == 0) { return BTM_MODE_UNSUPPORTED; } if (!btm_cb.sco_cb.esco_supported) { btm_cb.sco_cb.sco_db[sco_inx].esco.p_esco_cback = NULL; return (BTM_MODE_UNSUPPORTED); } if (sco_inx < BTM_MAX_SCO_LINKS && btm_cb.sco_cb.sco_db[sco_inx].state != SCO_ST_UNUSED) { btm_cb.sco_cb.sco_db[sco_inx].esco.p_esco_cback = p_esco_cback; return (BTM_SUCCESS); } return (BTM_ILLEGAL_VALUE); } /******************************************************************************* * * Function BTM_ChangeEScoLinkParms * * Description This function requests renegotiation of the parameters on * the current eSCO Link. If any of the changes are accepted * by the controllers, the BTM_ESCO_CHG_EVT event is sent in * the tBTM_ESCO_CBACK function with the current settings of * the link. The callback is registered through the call to * BTM_SetEScoMode. * * Note: If called over a SCO link (including 1.1 controller), * a change packet type request is sent out instead. * * Returns BTM_CMD_STARTED if command is successfully initiated. * BTM_NO_RESOURCES - not enough resources to initiate command. * BTM_WRONG_MODE if no connection with a peer device or bad * sco_inx. * ******************************************************************************/ static tBTM_STATUS BTM_ChangeEScoLinkParms(uint16_t sco_inx, tBTM_CHG_ESCO_PARAMS* p_parms) { /* Make sure sco handle is valid and on an active link */ if (sco_inx >= BTM_MAX_SCO_LINKS || btm_cb.sco_cb.sco_db[sco_inx].state != SCO_ST_CONNECTED) return (BTM_WRONG_MODE); tSCO_CONN* p_sco = &btm_cb.sco_cb.sco_db[sco_inx]; enh_esco_params_t* p_setup = &p_sco->esco.setup; /* Save the previous types in case command fails */ uint16_t saved_packet_types = p_setup->packet_types; /* If SCO connection OR eSCO not supported just send change packet types */ if (p_sco->esco.data.link_type == BTM_LINK_TYPE_SCO || !btm_cb.sco_cb.esco_supported) { p_setup->packet_types = p_parms->packet_types & (btm_cb.btm_sco_pkt_types_supported & BTM_SCO_LINK_ONLY_MASK); log::verbose("SCO Link for handle 0x{:04x}, pkt 0x{:04x}", p_sco->hci_handle, p_setup->packet_types); log::verbose("SCO Link for handle 0x{:04x}, pkt 0x{:04x}", p_sco->hci_handle, p_setup->packet_types); GetInterface().ChangeConnectionPacketType( p_sco->hci_handle, BTM_ESCO_2_SCO(p_setup->packet_types)); } else /* eSCO is supported and the link type is eSCO */ { uint16_t temp_packet_types = (p_parms->packet_types & BTM_SCO_SUPPORTED_PKTS_MASK & btm_cb.btm_sco_pkt_types_supported); /* OR in any exception packet types */ temp_packet_types |= ((p_parms->packet_types & BTM_SCO_EXCEPTION_PKTS_MASK) | (btm_cb.btm_sco_pkt_types_supported & BTM_SCO_EXCEPTION_PKTS_MASK)); p_setup->packet_types = temp_packet_types; log::verbose("-> eSCO Link for handle 0x{:04x}", p_sco->hci_handle); log::verbose( "txbw 0x{:x}, rxbw 0x{:x}, lat 0x{:x}, retrans 0x{:02x}, pkt 0x{:04x}", p_setup->transmit_bandwidth, p_setup->receive_bandwidth, p_parms->max_latency_ms, p_parms->retransmission_effort, temp_packet_types); /* Use Enhanced Synchronous commands if supported */ if (bluetooth::shim::GetController()->IsSupported( bluetooth::hci::OpCode::ENHANCED_SETUP_SYNCHRONOUS_CONNECTION) && !osi_property_get_bool(kPropertyDisableEnhancedConnection, kDefaultDisableEnhancedConnection)) { btsnd_hcic_enhanced_set_up_synchronous_connection(p_sco->hci_handle, p_setup); p_setup->packet_types = saved_packet_types; } else { /* Use older command */ uint16_t voice_content_format = btm_sco_voice_settings_to_legacy(p_setup); /* When changing an existing link, only change latency, retrans, and * pkts */ btsnd_hcic_setup_esco_conn(p_sco->hci_handle, p_setup->transmit_bandwidth, p_setup->receive_bandwidth, p_parms->max_latency_ms, voice_content_format, p_parms->retransmission_effort, p_setup->packet_types); } log::verbose( "txbw 0x{:x}, rxbw 0x{:x}, lat 0x{:x}, retrans 0x{:02x}, pkt 0x{:04x}", p_setup->transmit_bandwidth, p_setup->receive_bandwidth, p_parms->max_latency_ms, p_parms->retransmission_effort, temp_packet_types); } return (BTM_CMD_STARTED); } /******************************************************************************* * * Function BTM_EScoConnRsp * * Description This function is called upon receipt of an (e)SCO connection * request event (BTM_ESCO_CONN_REQ_EVT) to accept or reject * the request. Parameters used to negotiate eSCO links. * If p_parms is NULL, then values set through BTM_SetEScoMode * are used. * If the link type of the incoming request is SCO, then only * the tx_bw, max_latency, content format, and packet_types are * valid. The hci_status parameter should be * ([0x0] to accept, [0x0d..0x0f] to reject) * * * Returns void * ******************************************************************************/ void BTM_EScoConnRsp(uint16_t sco_inx, tHCI_STATUS hci_status, enh_esco_params_t* p_parms) { if (sco_inx < BTM_MAX_SCO_LINKS && btm_cb.sco_cb.sco_db[sco_inx].state == SCO_ST_W4_CONN_RSP) { btm_esco_conn_rsp(sco_inx, hci_status, btm_cb.sco_cb.sco_db[sco_inx].esco.data.bd_addr, p_parms); } } /******************************************************************************* * * Function BTM_GetNumScoLinks * * Description This function returns the number of active sco links. * * Returns uint8_t * ******************************************************************************/ uint8_t BTM_GetNumScoLinks(void) { tSCO_CONN* p = &btm_cb.sco_cb.sco_db[0]; uint16_t xx; uint8_t num_scos = 0; for (xx = 0; xx < BTM_MAX_SCO_LINKS; xx++, p++) { switch (p->state) { case SCO_ST_W4_CONN_RSP: case SCO_ST_CONNECTING: case SCO_ST_CONNECTED: case SCO_ST_DISCONNECTING: case SCO_ST_PEND_UNPARK: num_scos++; break; default: break; } } return (num_scos); } /******************************************************************************* * * Function BTM_IsScoActiveByBdaddr * * Description This function is called to see if a SCO connection is active * for a bd address. * * Returns bool * ******************************************************************************/ bool BTM_IsScoActiveByBdaddr(const RawAddress& remote_bda) { uint8_t xx; tSCO_CONN* p = &btm_cb.sco_cb.sco_db[0]; /* If any SCO is being established to the remote BD address, refuse this */ for (xx = 0; xx < BTM_MAX_SCO_LINKS; xx++, p++) { if (p->esco.data.bd_addr == remote_bda && p->state == SCO_ST_CONNECTED) { return (true); } } return (false); } /******************************************************************************* * * Function btm_sco_voice_settings_2_legacy * * Description This function is called to convert the Enhanced eSCO * parameters into voice setting parameter mask used * for legacy setup synchronous connection HCI commands * * Returns UINT16 - 16-bit mask for voice settings * * HCI_INP_CODING_LINEAR 0x0000 (0000000000) * HCI_INP_CODING_U_LAW 0x0100 (0100000000) * HCI_INP_CODING_A_LAW 0x0200 (1000000000) * HCI_INP_CODING_MASK 0x0300 (1100000000) * * HCI_INP_DATA_FMT_1S_COMPLEMENT 0x0000 (0000000000) * HCI_INP_DATA_FMT_2S_COMPLEMENT 0x0040 (0001000000) * HCI_INP_DATA_FMT_SIGN_MAGNITUDE 0x0080 (0010000000) * HCI_INP_DATA_FMT_UNSIGNED 0x00c0 (0011000000) * HCI_INP_DATA_FMT_MASK 0x00c0 (0011000000) * * HCI_INP_SAMPLE_SIZE_8BIT 0x0000 (0000000000) * HCI_INP_SAMPLE_SIZE_16BIT 0x0020 (0000100000) * HCI_INP_SAMPLE_SIZE_MASK 0x0020 (0000100000) * * HCI_INP_LINEAR_PCM_BIT_POS_MASK 0x001c (0000011100) * HCI_INP_LINEAR_PCM_BIT_POS_OFFS 2 * * HCI_AIR_CODING_FORMAT_CVSD 0x0000 (0000000000) * HCI_AIR_CODING_FORMAT_U_LAW 0x0001 (0000000001) * HCI_AIR_CODING_FORMAT_A_LAW 0x0002 (0000000010) * HCI_AIR_CODING_FORMAT_TRANSPNT 0x0003 (0000000011) * HCI_AIR_CODING_FORMAT_MASK 0x0003 (0000000011) * * default (0001100000) * HCI_DEFAULT_VOICE_SETTINGS (HCI_INP_CODING_LINEAR \ * | HCI_INP_DATA_FMT_2S_COMPLEMENT \ * | HCI_INP_SAMPLE_SIZE_16BIT \ * | HCI_AIR_CODING_FORMAT_CVSD) * ******************************************************************************/ static uint16_t btm_sco_voice_settings_to_legacy(enh_esco_params_t* p_params) { uint16_t voice_settings = 0; /* Convert Input Coding Format: If no uLaw or aLAW then Linear will be used * (0) */ if (p_params->input_coding_format.coding_format == ESCO_CODING_FORMAT_ULAW) voice_settings |= HCI_INP_CODING_U_LAW; else if (p_params->input_coding_format.coding_format == ESCO_CODING_FORMAT_ALAW) voice_settings |= HCI_INP_CODING_A_LAW; /* else default value of '0 is good 'Linear' */ /* Convert Input Data Format. Use 2's Compliment as the default */ switch (p_params->input_pcm_data_format) { case ESCO_PCM_DATA_FORMAT_1_COMP: /* voice_settings |= HCI_INP_DATA_FMT_1S_COMPLEMENT; value is '0' * already */ break; case ESCO_PCM_DATA_FORMAT_SIGN: voice_settings |= HCI_INP_DATA_FMT_SIGN_MAGNITUDE; break; case ESCO_PCM_DATA_FORMAT_UNSIGN: voice_settings |= HCI_INP_DATA_FMT_UNSIGNED; break; default: /* 2's Compliment */ voice_settings |= HCI_INP_DATA_FMT_2S_COMPLEMENT; break; } /* Convert Over the Air Coding. Use CVSD as the default */ switch (p_params->transmit_coding_format.coding_format) { case ESCO_CODING_FORMAT_ULAW: voice_settings |= HCI_AIR_CODING_FORMAT_U_LAW; break; case ESCO_CODING_FORMAT_ALAW: voice_settings |= HCI_AIR_CODING_FORMAT_A_LAW; break; case ESCO_CODING_FORMAT_TRANSPNT: case ESCO_CODING_FORMAT_MSBC: case ESCO_CODING_FORMAT_LC3: voice_settings |= HCI_AIR_CODING_FORMAT_TRANSPNT; break; default: /* CVSD (0) */ break; } /* Convert PCM payload MSB position (0000011100) */ voice_settings |= (uint16_t)(((p_params->input_pcm_payload_msb_position & 0x7) << HCI_INP_LINEAR_PCM_BIT_POS_OFFS)); /* Convert Input Sample Size (0000011100) */ if (p_params->input_coded_data_size == 16) voice_settings |= HCI_INP_SAMPLE_SIZE_16BIT; else /* Use 8 bit for all others */ voice_settings |= HCI_INP_SAMPLE_SIZE_8BIT; log::verbose("voice setting for legacy 0x{:03x}", voice_settings); return (voice_settings); } /******************************************************************************* * * Function BTM_GetScoDebugDump * * Description Get the status of SCO. This function is only used for * testing and debugging purposes. * * Returns Data with SCO related debug dump. * ******************************************************************************/ tBTM_SCO_DEBUG_DUMP BTM_GetScoDebugDump() { tSCO_CONN* active_sco = btm_get_active_sco(); tBTM_SCO_DEBUG_DUMP debug_dump = {}; debug_dump.is_active = active_sco != nullptr; if (!debug_dump.is_active) return debug_dump; tBTM_SCO_CODEC_TYPE codec_type = active_sco->get_codec_type(); debug_dump.codec_id = sco_codec_type_to_id(codec_type); if (debug_dump.codec_id != UUID_CODEC_MSBC && debug_dump.codec_id != UUID_CODEC_LC3) return debug_dump; auto fill_plc_stats = debug_dump.codec_id == UUID_CODEC_LC3 ? &bluetooth::audio::sco::swb::fill_plc_stats : &bluetooth::audio::sco::wbs::fill_plc_stats; if (!fill_plc_stats(&debug_dump.total_num_decoded_frames, &debug_dump.pkt_loss_ratio)) return debug_dump; auto get_pkt_status = debug_dump.codec_id == UUID_CODEC_LC3 ? &bluetooth::audio::sco::swb::get_pkt_status : &bluetooth::audio::sco::wbs::get_pkt_status; tBTM_SCO_PKT_STATUS* pkt_status = get_pkt_status(); if (pkt_status == nullptr) return debug_dump; tBTM_SCO_PKT_STATUS_DATA* data = &debug_dump.latest_data; data->begin_ts_raw_us = pkt_status->begin_ts_raw_us(); data->end_ts_raw_us = pkt_status->end_ts_raw_us(); data->status_in_hex = pkt_status->data_to_hex_string(); data->status_in_binary = pkt_status->data_to_binary_string(); return debug_dump; } bool btm_peer_supports_esco_2m_phy(RawAddress remote_bda) { uint8_t* features = BTM_ReadRemoteFeatures(remote_bda); if (features == nullptr) { log::warn("Checking remote features but remote feature read is incomplete"); return false; } return HCI_EDR_ESCO_2MPS_SUPPORTED(features); } bool btm_peer_supports_esco_3m_phy(RawAddress remote_bda) { uint8_t* features = BTM_ReadRemoteFeatures(remote_bda); if (features == nullptr) { log::warn("Checking remote features but remote feature read is incomplete"); return false; } return HCI_EDR_ESCO_3MPS_SUPPORTED(features); } bool btm_peer_supports_esco_ev3(RawAddress remote_bda) { uint8_t* features = BTM_ReadRemoteFeatures(remote_bda); if (features == nullptr) { log::warn("Checking remote features but remote feature read is incomplete"); return false; } return HCI_ESCO_EV3_SUPPORTED(features); }