/* * Copyright 2022 The Android Open Source Project * * 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. */ #include #include #include #include #include #include "hci/controller_interface.h" #include "main/shim/entry.h" #include "osi/include/properties.h" #include "stack/btm/btm_sco_hfp_hal.h" #include "stack/include/hcimsgs.h" #include "stack/include/sdpdefs.h" extern int GetAdapterIndex(); using bluetooth::legacy::hci::GetInterface; namespace hfp_hal_interface { namespace { bool offload_supported = false; bool offload_enabled = false; typedef struct cached_codec_info { struct bt_codec inner; size_t pkt_size; } cached_codec_info; std::vector cached_codecs; #define RETRY_ON_INTR(fn) \ do { \ } while ((fn) == -1 && errno == EINTR) #define MGMT_EV_SIZE_MAX 1024 #define MGMT_PKT_HDR_SIZE 6 struct mgmt_pkt { uint16_t opcode; uint16_t index; uint16_t len; uint8_t data[MGMT_EV_SIZE_MAX]; } __attribute__((packed)); #define MGMT_EV_COMMAND_COMPLETE 0x1 struct mgmt_ev_cmd_complete { uint16_t opcode; uint8_t status; uint8_t data[]; } __attribute__((packed)); #define MGMT_OP_GET_SCO_CODEC_CAPABILITIES 0x0100 #define MGMT_SCO_CODEC_CVSD 0x1 #define MGMT_SCO_CODEC_MSBC_TRANSPARENT 0x2 #define MGMT_SCO_CODEC_MSBC 0x3 struct mgmt_cp_get_codec_capabilities { uint16_t hci_dev; } __attribute__((packed)); struct mgmt_rp_get_codec_capabilities { uint16_t hci_dev; uint8_t transparent_wbs_supported; uint8_t hci_data_path_id; uint32_t wbs_pkt_len; } __attribute__((packed)); #define MGMT_POLL_TIMEOUT_MS 2000 void cache_codec_capabilities(struct mgmt_rp_get_codec_capabilities* rp) { const uint8_t kCodecMsbc = 0x5; // TODO(b/323087725): Query the codec capabilities and fill in c.inner.data. // The capabilities are not used currently so it's safe to keep this for a // while. // CVSD is mandatory in HFP. cached_codecs.push_back({ .inner = {.codec = codec::CVSD}, }); // No need to check GetLocalSupportedBrEdrCodecIds. Some legacy devices don't // even support HCI command Read Local Supported Codecs so WBS quirk is more // reliable. if (rp->transparent_wbs_supported) { cached_codecs.push_back({ .inner = {.codec = codec::MSBC_TRANSPARENT}, .pkt_size = rp->wbs_pkt_len, }); } auto codecs = bluetooth::shim::GetController()->GetLocalSupportedBrEdrCodecIds(); if (std::find(codecs.begin(), codecs.end(), kCodecMsbc) != codecs.end()) { offload_supported = true; cached_codecs.push_back({ .inner = {.codec = codec::MSBC, .data_path = rp->hci_data_path_id}, .pkt_size = rp->wbs_pkt_len, }); } for (const auto& c : cached_codecs) { bluetooth::log::info("Caching HFP codec {}, data path {}, data len {}, pkt_size {}", (uint64_t)c.inner.codec, c.inner.data_path, c.inner.data.size(), c.pkt_size); } } struct sockaddr_hci { sa_family_t hci_family; uint16_t hci_dev; uint16_t hci_channel; }; constexpr uint8_t BTPROTO_HCI = 1; constexpr uint16_t HCI_CHANNEL_CONTROL = 3; constexpr uint16_t HCI_DEV_NONE = 0xffff; int btsocket_open_mgmt(uint16_t hci) { int fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_NONBLOCK, BTPROTO_HCI); if (fd < 0) { bluetooth::log::debug("Failed to open BT socket, hci: %u", hci); return -errno; } struct sockaddr_hci addr = { .hci_family = AF_BLUETOOTH, .hci_dev = HCI_DEV_NONE, .hci_channel = HCI_CHANNEL_CONTROL, }; int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr)); if (ret < 0) { bluetooth::log::debug("Failed to bind BT socket."); close(fd); return -errno; } return fd; } int mgmt_get_codec_capabilities(int fd, uint16_t hci) { struct mgmt_pkt ev; ev.opcode = MGMT_OP_GET_SCO_CODEC_CAPABILITIES; ev.index = HCI_DEV_NONE; ev.len = sizeof(struct mgmt_cp_get_codec_capabilities); struct mgmt_cp_get_codec_capabilities* cp = reinterpret_cast(ev.data); cp->hci_dev = hci; int ret; struct pollfd writable[1]; writable[0].fd = fd; writable[0].events = POLLOUT; do { ret = poll(writable, 1, MGMT_POLL_TIMEOUT_MS); if (ret > 0) { RETRY_ON_INTR(ret = write(fd, &ev, MGMT_PKT_HDR_SIZE + ev.len)); if (ret < 0) { bluetooth::log::debug("Failed to call MGMT_OP_GET_SCO_CODEC_CAPABILITIES: {}", -errno); return -errno; } break; } } while (ret > 0); if (ret <= 0) { bluetooth::log::debug("Failed waiting for mgmt socket to be writable."); return -1; } struct pollfd fds[1]; fds[0].fd = fd; fds[0].events = POLLIN; do { ret = poll(fds, 1, MGMT_POLL_TIMEOUT_MS); if (ret > 0) { if (fds[0].revents & POLLIN) { RETRY_ON_INTR(ret = read(fd, &ev, sizeof(ev))); if (ret < 0) { bluetooth::log::debug("Failed to read mgmt socket: {}", -errno); return -errno; } else if (ret == 0) { // unlikely to happen, just a safeguard. bluetooth::log::debug("Failed to read mgmt socket: EOF"); return -1; } if (ev.opcode == MGMT_EV_COMMAND_COMPLETE) { struct mgmt_ev_cmd_complete* cc = reinterpret_cast(ev.data); if (cc->opcode == MGMT_OP_GET_SCO_CODEC_CAPABILITIES && cc->status == 0) { struct mgmt_rp_get_codec_capabilities* rp = reinterpret_cast(cc->data); if (rp->hci_dev == hci) { cache_codec_capabilities(rp); return 0; } } } } } else if (ret == 0) { bluetooth::log::debug("Timeout while waiting for codec capabilities response."); ret = -1; } } while (ret > 0); return ret; } #define MGMT_OP_NOTIFY_SCO_CONNECTION_CHANGE 0x0101 struct mgmt_cp_notify_sco_connection_change { uint16_t hci_dev; uint8_t addr[6]; uint8_t addr_type; uint8_t connected; uint8_t codec; } __attribute__((packed)); int mgmt_notify_sco_connection_change(int fd, int hci, RawAddress device, bool is_connected, int codec) { struct mgmt_pkt ev; ev.opcode = MGMT_OP_NOTIFY_SCO_CONNECTION_CHANGE; ev.index = HCI_DEV_NONE; ev.len = sizeof(struct mgmt_cp_notify_sco_connection_change); struct mgmt_cp_notify_sco_connection_change* cp = reinterpret_cast(ev.data); cp->hci_dev = hci; cp->connected = is_connected; cp->codec = codec; memcpy(cp->addr, device.address, sizeof(cp->addr)); cp->addr_type = 0; int ret; struct pollfd writable[1]; writable[0].fd = fd; writable[0].events = POLLOUT; do { ret = poll(writable, 1, MGMT_POLL_TIMEOUT_MS); if (ret > 0) { RETRY_ON_INTR(ret = write(fd, &ev, MGMT_PKT_HDR_SIZE + ev.len)); if (ret < 0) { bluetooth::log::error("Failed to call MGMT_OP_NOTIFY_SCO_CONNECTION_CHANGE: {}", -errno); return -errno; } break; } } while (ret > 0); if (ret <= 0) { bluetooth::log::debug("Failed waiting for mgmt socket to be writable."); return -1; } return 0; } } // namespace void init() { int hci = GetAdapterIndex(); int fd = btsocket_open_mgmt(hci); if (fd < 0) { bluetooth::log::error("Failed to open mgmt channel, error= {}.", fd); return; } int ret = mgmt_get_codec_capabilities(fd, hci); if (ret) { bluetooth::log::error("Failed to get codec capabilities with error = {}.", ret); } else { bluetooth::log::info("Successfully queried SCO codec capabilities."); } #ifndef TARGET_FLOSS // If hfp software path is not enabled, fallback to offload path. if (!osi_property_get_bool("bluetooth.hfp.software_datapath.enabled", false)) { enable_offload(true); } #endif close(fd); } // Check if the specified coding format is supported by the adapter. bool is_coding_format_supported(esco_coding_format_t coding_format) { if (coding_format != ESCO_CODING_FORMAT_TRANSPNT && coding_format != ESCO_CODING_FORMAT_MSBC) { bluetooth::log::warn("Unsupported coding format to query: {}", coding_format); return false; } for (cached_codec_info c : cached_codecs) { if (c.inner.codec == MSBC_TRANSPARENT && coding_format == ESCO_CODING_FORMAT_TRANSPNT) { return true; } if (c.inner.codec == MSBC && coding_format == ESCO_CODING_FORMAT_MSBC) { return true; } } return false; } // Check if wideband speech is supported on local device bool get_wbs_supported() { return is_coding_format_supported(ESCO_CODING_FORMAT_TRANSPNT) || is_coding_format_supported(ESCO_CODING_FORMAT_MSBC); } // Check if super-wideband speech is supported on local device bool get_swb_supported() { #ifdef TARGET_FLOSS // We only support SWB via transparent mode. return is_coding_format_supported(ESCO_CODING_FORMAT_TRANSPNT); #else return is_coding_format_supported(ESCO_CODING_FORMAT_TRANSPNT) && osi_property_get_bool("bluetooth.hfp.swb.supported", true); // TODO: add SWB for desktop #endif } // Checks the supported codecs bt_codecs get_codec_capabilities(uint64_t codecs) { bt_codecs codec_list = {.offload_capable = offload_supported}; for (auto c : cached_codecs) { if (c.inner.codec & codecs) { codec_list.codecs.push_back(c.inner); } } return codec_list; } // Check if hardware offload is supported bool get_offload_supported() { return offload_supported; } // Check if hardware offload is enabled bool get_offload_enabled() { return offload_supported && offload_enabled; } // Set offload enable/disable bool enable_offload(bool enable) { if (!offload_supported && enable) { bluetooth::log::error("Cannot enable SCO-offload since it is not supported."); return false; } offload_enabled = enable; return true; } static bool get_single_codec(int codec, bt_codec** out) { for (cached_codec_info& c : cached_codecs) { if (c.inner.codec == static_cast(codec)) { *out = &c.inner; return true; } } return false; } constexpr uint8_t OFFLOAD_DATAPATH = 0x01; // Notify the codec datapath to lower layer for offload mode void set_codec_datapath(tBTA_AG_UUID_CODEC codec_uuid) { bool found; bt_codec* codec; uint8_t codec_id; if (codec_uuid == tBTA_AG_UUID_CODEC::UUID_CODEC_LC3 && get_offload_enabled()) { bluetooth::log::error("Offload path for LC3 is not implemented."); return; } switch (codec_uuid) { case tBTA_AG_UUID_CODEC::UUID_CODEC_CVSD: codec_id = codec::CVSD; break; case tBTA_AG_UUID_CODEC::UUID_CODEC_MSBC: codec_id = get_offload_enabled() ? codec::MSBC : codec::MSBC_TRANSPARENT; break; case tBTA_AG_UUID_CODEC::UUID_CODEC_LC3: codec_id = get_offload_enabled() ? codec::LC3 : codec::MSBC_TRANSPARENT; break; default: bluetooth::log::warn("Unsupported codec ({}). Won't set datapath.", bta_ag_uuid_codec_text(codec_uuid)); return; } found = get_single_codec(codec_id, &codec); if (!found) { bluetooth::log::error("Failed to find codec config for codec ({}). Won't set datapath.", bta_ag_uuid_codec_text(codec_uuid)); return; } bluetooth::log::info("Configuring datapath for codec ({})", bta_ag_uuid_codec_text(codec_uuid)); if (codec->codec == codec::MSBC && !get_offload_enabled()) { bluetooth::log::error( "Tried to configure offload data path for format ({}) with offload " "disabled. Won't set datapath.", bta_ag_uuid_codec_text(codec_uuid)); return; } if (get_offload_enabled()) { std::vector data; switch (codec_uuid) { case tBTA_AG_UUID_CODEC::UUID_CODEC_CVSD: data = {0x00}; break; case tBTA_AG_UUID_CODEC::UUID_CODEC_MSBC: data = {0x01}; break; default: break; } GetInterface().ConfigureDataPath(hci_data_direction_t::CONTROLLER_TO_HOST, OFFLOAD_DATAPATH, data); GetInterface().ConfigureDataPath(hci_data_direction_t::HOST_TO_CONTROLLER, OFFLOAD_DATAPATH, data); } } size_t get_packet_size(int codec) { for (const cached_codec_info& c : cached_codecs) { if (c.inner.codec == static_cast(codec)) { return c.pkt_size; } } return kDefaultPacketSize; } void notify_sco_connection_change(RawAddress device, bool is_connected, int codec) { int hci = GetAdapterIndex(); int fd = btsocket_open_mgmt(hci); if (fd < 0) { bluetooth::log::error("Failed to open mgmt channel, error= {}.", fd); return; } if (codec == codec::LC3) { bluetooth::log::error("Offload path for LC3 is not implemented."); return; } int converted_codec; switch (codec) { case codec::MSBC: converted_codec = MGMT_SCO_CODEC_MSBC; break; case codec::MSBC_TRANSPARENT: converted_codec = MGMT_SCO_CODEC_MSBC_TRANSPARENT; break; default: converted_codec = MGMT_SCO_CODEC_CVSD; } int ret = mgmt_notify_sco_connection_change(fd, hci, device, is_connected, converted_codec); if (ret) { bluetooth::log::error( "Failed to notify HAL of connection change: hci {}, device {}, " "connected {}, codec {}", hci, device, is_connected, codec); } else { bluetooth::log::info( "Notified HAL of connection change: hci {}, device {}, connected {}, " "codec {}", hci, device, is_connected, codec); } close(fd); } void update_esco_parameters(enh_esco_params_t* p_parms) { if (get_offload_enabled()) { p_parms->input_transport_unit_size = 0x01; p_parms->output_transport_unit_size = 0x01; } else { p_parms->input_transport_unit_size = 0x00; p_parms->output_transport_unit_size = 0x00; } } } // namespace hfp_hal_interface