| /* |
| * Copyright (c) 2017-2021 Nordic Semiconductor ASA |
| * Copyright (c) 2015-2016 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/sys/byteorder.h> |
| |
| |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/hci.h> |
| #include <zephyr/bluetooth/buf.h> |
| |
| #include "common/bt_str.h" |
| |
| #include "hci_core.h" |
| #include "conn_internal.h" |
| #include "keys.h" |
| |
| #define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(bt_br); |
| |
| static bt_br_discovery_cb_t *discovery_cb; |
| struct bt_br_discovery_result *discovery_results; |
| static size_t discovery_results_size; |
| static size_t discovery_results_count; |
| |
| static int reject_conn(const bt_addr_t *bdaddr, uint8_t reason) |
| { |
| struct bt_hci_cp_reject_conn_req *cp; |
| struct net_buf *buf; |
| int err; |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_REJECT_CONN_REQ, sizeof(*cp)); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| cp = net_buf_add(buf, sizeof(*cp)); |
| bt_addr_copy(&cp->bdaddr, bdaddr); |
| cp->reason = reason; |
| |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_REJECT_CONN_REQ, buf, NULL); |
| if (err) { |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static int accept_sco_conn(const bt_addr_t *bdaddr, struct bt_conn *sco_conn) |
| { |
| struct bt_hci_cp_accept_sync_conn_req *cp; |
| struct net_buf *buf; |
| int err; |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_ACCEPT_SYNC_CONN_REQ, sizeof(*cp)); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| cp = net_buf_add(buf, sizeof(*cp)); |
| bt_addr_copy(&cp->bdaddr, bdaddr); |
| cp->pkt_type = sco_conn->sco.pkt_type; |
| cp->tx_bandwidth = 0x00001f40; |
| cp->rx_bandwidth = 0x00001f40; |
| cp->max_latency = 0x0007; |
| cp->retrans_effort = 0x01; |
| cp->content_format = BT_VOICE_CVSD_16BIT; |
| |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_ACCEPT_SYNC_CONN_REQ, buf, NULL); |
| if (err) { |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static int accept_conn(const bt_addr_t *bdaddr) |
| { |
| struct bt_hci_cp_accept_conn_req *cp; |
| struct net_buf *buf; |
| int err; |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_ACCEPT_CONN_REQ, sizeof(*cp)); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| cp = net_buf_add(buf, sizeof(*cp)); |
| bt_addr_copy(&cp->bdaddr, bdaddr); |
| cp->role = BT_HCI_ROLE_PERIPHERAL; |
| |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_ACCEPT_CONN_REQ, buf, NULL); |
| if (err) { |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static void bt_esco_conn_req(struct bt_hci_evt_conn_request *evt) |
| { |
| struct bt_conn *sco_conn; |
| |
| sco_conn = bt_conn_add_sco(&evt->bdaddr, evt->link_type); |
| if (!sco_conn) { |
| reject_conn(&evt->bdaddr, BT_HCI_ERR_INSUFFICIENT_RESOURCES); |
| return; |
| } |
| |
| if (accept_sco_conn(&evt->bdaddr, sco_conn)) { |
| LOG_ERR("Error accepting connection from %s", bt_addr_str(&evt->bdaddr)); |
| reject_conn(&evt->bdaddr, BT_HCI_ERR_UNSPECIFIED); |
| bt_sco_cleanup(sco_conn); |
| return; |
| } |
| |
| sco_conn->role = BT_HCI_ROLE_PERIPHERAL; |
| bt_conn_set_state(sco_conn, BT_CONN_CONNECTING); |
| bt_conn_unref(sco_conn); |
| } |
| |
| void bt_hci_conn_req(struct net_buf *buf) |
| { |
| struct bt_hci_evt_conn_request *evt = (void *)buf->data; |
| struct bt_conn *conn; |
| |
| LOG_DBG("conn req from %s, type 0x%02x", bt_addr_str(&evt->bdaddr), evt->link_type); |
| |
| if (evt->link_type != BT_HCI_ACL) { |
| bt_esco_conn_req(evt); |
| return; |
| } |
| |
| conn = bt_conn_add_br(&evt->bdaddr); |
| if (!conn) { |
| reject_conn(&evt->bdaddr, BT_HCI_ERR_INSUFFICIENT_RESOURCES); |
| return; |
| } |
| |
| accept_conn(&evt->bdaddr); |
| conn->role = BT_HCI_ROLE_PERIPHERAL; |
| bt_conn_set_state(conn, BT_CONN_CONNECTING); |
| bt_conn_unref(conn); |
| } |
| |
| static bool br_sufficient_key_size(struct bt_conn *conn) |
| { |
| struct bt_hci_cp_read_encryption_key_size *cp; |
| struct bt_hci_rp_read_encryption_key_size *rp; |
| struct net_buf *buf, *rsp; |
| uint8_t key_size; |
| int err; |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_READ_ENCRYPTION_KEY_SIZE, |
| sizeof(*cp)); |
| if (!buf) { |
| LOG_ERR("Failed to allocate command buffer"); |
| return false; |
| } |
| |
| cp = net_buf_add(buf, sizeof(*cp)); |
| cp->handle = sys_cpu_to_le16(conn->handle); |
| |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_ENCRYPTION_KEY_SIZE, |
| buf, &rsp); |
| if (err) { |
| LOG_ERR("Failed to read encryption key size (err %d)", err); |
| return false; |
| } |
| |
| if (rsp->len < sizeof(*rp)) { |
| LOG_ERR("Too small command complete for encryption key size"); |
| net_buf_unref(rsp); |
| return false; |
| } |
| |
| rp = (void *)rsp->data; |
| key_size = rp->key_size; |
| net_buf_unref(rsp); |
| |
| LOG_DBG("Encryption key size is %u", key_size); |
| |
| if (conn->sec_level == BT_SECURITY_L4) { |
| return key_size == BT_HCI_ENCRYPTION_KEY_SIZE_MAX; |
| } |
| |
| return key_size >= BT_HCI_ENCRYPTION_KEY_SIZE_MIN; |
| } |
| |
| bool bt_br_update_sec_level(struct bt_conn *conn) |
| { |
| if (!conn->encrypt) { |
| conn->sec_level = BT_SECURITY_L1; |
| return true; |
| } |
| |
| if (conn->br.link_key) { |
| if (conn->br.link_key->flags & BT_LINK_KEY_AUTHENTICATED) { |
| if (conn->encrypt == 0x02) { |
| conn->sec_level = BT_SECURITY_L4; |
| } else { |
| conn->sec_level = BT_SECURITY_L3; |
| } |
| } else { |
| conn->sec_level = BT_SECURITY_L2; |
| } |
| } else { |
| LOG_WRN("No BR/EDR link key found"); |
| conn->sec_level = BT_SECURITY_L2; |
| } |
| |
| if (!br_sufficient_key_size(conn)) { |
| LOG_ERR("Encryption key size is not sufficient"); |
| bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL); |
| return false; |
| } |
| |
| if (conn->required_sec_level > conn->sec_level) { |
| LOG_ERR("Failed to set required security level"); |
| bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void bt_hci_synchronous_conn_complete(struct net_buf *buf) |
| { |
| struct bt_hci_evt_sync_conn_complete *evt = (void *)buf->data; |
| struct bt_conn *sco_conn; |
| uint16_t handle = sys_le16_to_cpu(evt->handle); |
| |
| LOG_DBG("status 0x%02x, handle %u, type 0x%02x", evt->status, handle, evt->link_type); |
| |
| sco_conn = bt_conn_lookup_addr_sco(&evt->bdaddr); |
| if (!sco_conn) { |
| LOG_ERR("Unable to find conn for %s", bt_addr_str(&evt->bdaddr)); |
| return; |
| } |
| |
| if (evt->status) { |
| sco_conn->err = evt->status; |
| bt_conn_set_state(sco_conn, BT_CONN_DISCONNECTED); |
| bt_conn_unref(sco_conn); |
| return; |
| } |
| |
| sco_conn->handle = handle; |
| bt_conn_set_state(sco_conn, BT_CONN_CONNECTED); |
| bt_conn_unref(sco_conn); |
| } |
| |
| void bt_hci_conn_complete(struct net_buf *buf) |
| { |
| struct bt_hci_evt_conn_complete *evt = (void *)buf->data; |
| struct bt_conn *conn; |
| struct bt_hci_cp_read_remote_features *cp; |
| uint16_t handle = sys_le16_to_cpu(evt->handle); |
| |
| LOG_DBG("status 0x%02x, handle %u, type 0x%02x", evt->status, handle, evt->link_type); |
| |
| conn = bt_conn_lookup_addr_br(&evt->bdaddr); |
| if (!conn) { |
| LOG_ERR("Unable to find conn for %s", bt_addr_str(&evt->bdaddr)); |
| return; |
| } |
| |
| if (evt->status) { |
| conn->err = evt->status; |
| bt_conn_set_state(conn, BT_CONN_DISCONNECTED); |
| bt_conn_unref(conn); |
| return; |
| } |
| |
| conn->handle = handle; |
| conn->err = 0U; |
| conn->encrypt = evt->encr_enabled; |
| |
| if (!bt_br_update_sec_level(conn)) { |
| bt_conn_unref(conn); |
| return; |
| } |
| |
| bt_conn_set_state(conn, BT_CONN_CONNECTED); |
| bt_conn_unref(conn); |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_READ_REMOTE_FEATURES, sizeof(*cp)); |
| if (!buf) { |
| return; |
| } |
| |
| cp = net_buf_add(buf, sizeof(*cp)); |
| cp->handle = evt->handle; |
| |
| bt_hci_cmd_send_sync(BT_HCI_OP_READ_REMOTE_FEATURES, buf, NULL); |
| } |
| |
| struct discovery_priv { |
| uint16_t clock_offset; |
| uint8_t pscan_rep_mode; |
| uint8_t resolving; |
| } __packed; |
| |
| static int request_name(const bt_addr_t *addr, uint8_t pscan, uint16_t offset) |
| { |
| struct bt_hci_cp_remote_name_request *cp; |
| struct net_buf *buf; |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_REMOTE_NAME_REQUEST, sizeof(*cp)); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| cp = net_buf_add(buf, sizeof(*cp)); |
| |
| bt_addr_copy(&cp->bdaddr, addr); |
| cp->pscan_rep_mode = pscan; |
| cp->reserved = 0x00; /* reserved, should be set to 0x00 */ |
| cp->clock_offset = offset; |
| |
| return bt_hci_cmd_send_sync(BT_HCI_OP_REMOTE_NAME_REQUEST, buf, NULL); |
| } |
| |
| #define EIR_SHORT_NAME 0x08 |
| #define EIR_COMPLETE_NAME 0x09 |
| |
| static bool eir_has_name(const uint8_t *eir) |
| { |
| int len = 240; |
| |
| while (len) { |
| if (len < 2) { |
| break; |
| } |
| |
| /* Look for early termination */ |
| if (!eir[0]) { |
| break; |
| } |
| |
| /* Check if field length is correct */ |
| if (eir[0] > len - 1) { |
| break; |
| } |
| |
| switch (eir[1]) { |
| case EIR_SHORT_NAME: |
| case EIR_COMPLETE_NAME: |
| if (eir[0] > 1) { |
| return true; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| /* Parse next AD Structure */ |
| len -= eir[0] + 1; |
| eir += eir[0] + 1; |
| } |
| |
| return false; |
| } |
| |
| void bt_br_discovery_reset(void) |
| { |
| discovery_cb = NULL; |
| discovery_results = NULL; |
| discovery_results_size = 0; |
| discovery_results_count = 0; |
| } |
| |
| static void report_discovery_results(void) |
| { |
| bool resolving_names = false; |
| int i; |
| |
| for (i = 0; i < discovery_results_count; i++) { |
| struct discovery_priv *priv; |
| |
| priv = (struct discovery_priv *)&discovery_results[i]._priv; |
| |
| if (eir_has_name(discovery_results[i].eir)) { |
| continue; |
| } |
| |
| if (request_name(&discovery_results[i].addr, |
| priv->pscan_rep_mode, priv->clock_offset)) { |
| continue; |
| } |
| |
| priv->resolving = 1U; |
| resolving_names = true; |
| } |
| |
| if (resolving_names) { |
| return; |
| } |
| |
| atomic_clear_bit(bt_dev.flags, BT_DEV_INQUIRY); |
| |
| if (discovery_cb) { |
| discovery_cb(discovery_results, discovery_results_count); |
| } |
| bt_br_discovery_reset(); |
| } |
| |
| void bt_hci_inquiry_complete(struct net_buf *buf) |
| { |
| struct bt_hci_evt_inquiry_complete *evt = (void *)buf->data; |
| |
| if (evt->status) { |
| LOG_ERR("Failed to complete inquiry"); |
| } |
| |
| report_discovery_results(); |
| } |
| |
| static struct bt_br_discovery_result *get_result_slot(const bt_addr_t *addr, |
| int8_t rssi) |
| { |
| struct bt_br_discovery_result *result = NULL; |
| size_t i; |
| |
| /* check if already present in results */ |
| for (i = 0; i < discovery_results_count; i++) { |
| if (bt_addr_eq(addr, &discovery_results[i].addr)) { |
| return &discovery_results[i]; |
| } |
| } |
| |
| /* Pick a new slot (if available) */ |
| if (discovery_results_count < discovery_results_size) { |
| bt_addr_copy(&discovery_results[discovery_results_count].addr, |
| addr); |
| return &discovery_results[discovery_results_count++]; |
| } |
| |
| /* ignore if invalid RSSI */ |
| if (rssi == 0xff) { |
| return NULL; |
| } |
| |
| /* |
| * Pick slot with smallest RSSI that is smaller then passed RSSI |
| * TODO handle TX if present |
| */ |
| for (i = 0; i < discovery_results_size; i++) { |
| if (discovery_results[i].rssi > rssi) { |
| continue; |
| } |
| |
| if (!result || result->rssi > discovery_results[i].rssi) { |
| result = &discovery_results[i]; |
| } |
| } |
| |
| if (result) { |
| LOG_DBG("Reusing slot (old %s rssi %d dBm)", bt_addr_str(&result->addr), |
| result->rssi); |
| |
| bt_addr_copy(&result->addr, addr); |
| } |
| |
| return result; |
| } |
| |
| void bt_hci_inquiry_result_with_rssi(struct net_buf *buf) |
| { |
| uint8_t num_reports = net_buf_pull_u8(buf); |
| |
| if (!atomic_test_bit(bt_dev.flags, BT_DEV_INQUIRY)) { |
| return; |
| } |
| |
| LOG_DBG("number of results: %u", num_reports); |
| |
| while (num_reports--) { |
| struct bt_hci_evt_inquiry_result_with_rssi *evt; |
| struct bt_br_discovery_result *result; |
| struct discovery_priv *priv; |
| |
| if (buf->len < sizeof(*evt)) { |
| LOG_ERR("Unexpected end to buffer"); |
| return; |
| } |
| |
| evt = net_buf_pull_mem(buf, sizeof(*evt)); |
| LOG_DBG("%s rssi %d dBm", bt_addr_str(&evt->addr), evt->rssi); |
| |
| result = get_result_slot(&evt->addr, evt->rssi); |
| if (!result) { |
| return; |
| } |
| |
| priv = (struct discovery_priv *)&result->_priv; |
| priv->pscan_rep_mode = evt->pscan_rep_mode; |
| priv->clock_offset = evt->clock_offset; |
| |
| memcpy(result->cod, evt->cod, 3); |
| result->rssi = evt->rssi; |
| |
| /* we could reuse slot so make sure EIR is cleared */ |
| (void)memset(result->eir, 0, sizeof(result->eir)); |
| } |
| } |
| |
| void bt_hci_extended_inquiry_result(struct net_buf *buf) |
| { |
| struct bt_hci_evt_extended_inquiry_result *evt = (void *)buf->data; |
| struct bt_br_discovery_result *result; |
| struct discovery_priv *priv; |
| |
| if (!atomic_test_bit(bt_dev.flags, BT_DEV_INQUIRY)) { |
| return; |
| } |
| |
| LOG_DBG("%s rssi %d dBm", bt_addr_str(&evt->addr), evt->rssi); |
| |
| result = get_result_slot(&evt->addr, evt->rssi); |
| if (!result) { |
| return; |
| } |
| |
| priv = (struct discovery_priv *)&result->_priv; |
| priv->pscan_rep_mode = evt->pscan_rep_mode; |
| priv->clock_offset = evt->clock_offset; |
| |
| result->rssi = evt->rssi; |
| memcpy(result->cod, evt->cod, 3); |
| memcpy(result->eir, evt->eir, sizeof(result->eir)); |
| } |
| |
| void bt_hci_remote_name_request_complete(struct net_buf *buf) |
| { |
| struct bt_hci_evt_remote_name_req_complete *evt = (void *)buf->data; |
| struct bt_br_discovery_result *result; |
| struct discovery_priv *priv; |
| int eir_len = 240; |
| uint8_t *eir; |
| int i; |
| |
| result = get_result_slot(&evt->bdaddr, 0xff); |
| if (!result) { |
| return; |
| } |
| |
| priv = (struct discovery_priv *)&result->_priv; |
| priv->resolving = 0U; |
| |
| if (evt->status) { |
| goto check_names; |
| } |
| |
| eir = result->eir; |
| |
| while (eir_len) { |
| if (eir_len < 2) { |
| break; |
| } |
| |
| /* Look for early termination */ |
| if (!eir[0]) { |
| size_t name_len; |
| |
| eir_len -= 2; |
| |
| /* name is null terminated */ |
| name_len = strlen((const char *)evt->name); |
| |
| if (name_len > eir_len) { |
| eir[0] = eir_len + 1; |
| eir[1] = EIR_SHORT_NAME; |
| } else { |
| eir[0] = name_len + 1; |
| eir[1] = EIR_SHORT_NAME; |
| } |
| |
| memcpy(&eir[2], evt->name, eir[0] - 1); |
| |
| break; |
| } |
| |
| /* Check if field length is correct */ |
| if (eir[0] > eir_len - 1) { |
| break; |
| } |
| |
| /* next EIR Structure */ |
| eir_len -= eir[0] + 1; |
| eir += eir[0] + 1; |
| } |
| |
| check_names: |
| /* if still waiting for names */ |
| for (i = 0; i < discovery_results_count; i++) { |
| struct discovery_priv *priv; |
| |
| priv = (struct discovery_priv *)&discovery_results[i]._priv; |
| |
| if (priv->resolving) { |
| return; |
| } |
| } |
| |
| /* all names resolved, report discovery results */ |
| atomic_clear_bit(bt_dev.flags, BT_DEV_INQUIRY); |
| |
| if (discovery_cb) { |
| discovery_cb(discovery_results, discovery_results_count); |
| } |
| |
| } |
| |
| void bt_hci_read_remote_features_complete(struct net_buf *buf) |
| { |
| struct bt_hci_evt_remote_features *evt = (void *)buf->data; |
| uint16_t handle = sys_le16_to_cpu(evt->handle); |
| struct bt_hci_cp_read_remote_ext_features *cp; |
| struct bt_conn *conn; |
| |
| LOG_DBG("status 0x%02x handle %u", evt->status, handle); |
| |
| conn = bt_conn_lookup_handle(handle); |
| if (!conn) { |
| LOG_ERR("Can't find conn for handle %u", handle); |
| return; |
| } |
| |
| if (evt->status) { |
| goto done; |
| } |
| |
| memcpy(conn->br.features[0], evt->features, sizeof(evt->features)); |
| |
| if (!BT_FEAT_EXT_FEATURES(conn->br.features)) { |
| goto done; |
| } |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_READ_REMOTE_EXT_FEATURES, |
| sizeof(*cp)); |
| if (!buf) { |
| goto done; |
| } |
| |
| /* Read remote host features (page 1) */ |
| cp = net_buf_add(buf, sizeof(*cp)); |
| cp->handle = evt->handle; |
| cp->page = 0x01; |
| |
| bt_hci_cmd_send_sync(BT_HCI_OP_READ_REMOTE_EXT_FEATURES, buf, NULL); |
| |
| done: |
| bt_conn_unref(conn); |
| } |
| |
| void bt_hci_read_remote_ext_features_complete(struct net_buf *buf) |
| { |
| struct bt_hci_evt_remote_ext_features *evt = (void *)buf->data; |
| uint16_t handle = sys_le16_to_cpu(evt->handle); |
| struct bt_conn *conn; |
| |
| LOG_DBG("status 0x%02x handle %u", evt->status, handle); |
| |
| conn = bt_conn_lookup_handle(handle); |
| if (!conn) { |
| LOG_ERR("Can't find conn for handle %u", handle); |
| return; |
| } |
| |
| if (!evt->status && evt->page == 0x01) { |
| memcpy(conn->br.features[1], evt->features, |
| sizeof(conn->br.features[1])); |
| } |
| |
| bt_conn_unref(conn); |
| } |
| |
| void bt_hci_role_change(struct net_buf *buf) |
| { |
| struct bt_hci_evt_role_change *evt = (void *)buf->data; |
| struct bt_conn *conn; |
| |
| LOG_DBG("status 0x%02x role %u addr %s", evt->status, evt->role, bt_addr_str(&evt->bdaddr)); |
| |
| if (evt->status) { |
| return; |
| } |
| |
| conn = bt_conn_lookup_addr_br(&evt->bdaddr); |
| if (!conn) { |
| LOG_ERR("Can't find conn for %s", bt_addr_str(&evt->bdaddr)); |
| return; |
| } |
| |
| if (evt->role) { |
| conn->role = BT_CONN_ROLE_PERIPHERAL; |
| } else { |
| conn->role = BT_CONN_ROLE_CENTRAL; |
| } |
| |
| bt_conn_unref(conn); |
| } |
| |
| static int read_ext_features(void) |
| { |
| int i; |
| |
| /* Read Local Supported Extended Features */ |
| for (i = 1; i < LMP_FEAT_PAGES_COUNT; i++) { |
| struct bt_hci_cp_read_local_ext_features *cp; |
| struct bt_hci_rp_read_local_ext_features *rp; |
| struct net_buf *buf, *rsp; |
| int err; |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_READ_LOCAL_EXT_FEATURES, |
| sizeof(*cp)); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| cp = net_buf_add(buf, sizeof(*cp)); |
| cp->page = i; |
| |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_LOCAL_EXT_FEATURES, |
| buf, &rsp); |
| if (err) { |
| return err; |
| } |
| |
| rp = (void *)rsp->data; |
| |
| memcpy(&bt_dev.features[i], rp->ext_features, |
| sizeof(bt_dev.features[i])); |
| |
| if (rp->max_page <= i) { |
| net_buf_unref(rsp); |
| break; |
| } |
| |
| net_buf_unref(rsp); |
| } |
| |
| return 0; |
| } |
| |
| void device_supported_pkt_type(void) |
| { |
| /* Device supported features and sco packet types */ |
| if (BT_FEAT_HV2_PKT(bt_dev.features)) { |
| bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_HV2); |
| } |
| |
| if (BT_FEAT_HV3_PKT(bt_dev.features)) { |
| bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_HV3); |
| } |
| |
| if (BT_FEAT_LMP_ESCO_CAPABLE(bt_dev.features)) { |
| bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_EV3); |
| } |
| |
| if (BT_FEAT_EV4_PKT(bt_dev.features)) { |
| bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_EV4); |
| } |
| |
| if (BT_FEAT_EV5_PKT(bt_dev.features)) { |
| bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_EV5); |
| } |
| |
| if (BT_FEAT_2EV3_PKT(bt_dev.features)) { |
| bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_2EV3); |
| } |
| |
| if (BT_FEAT_3EV3_PKT(bt_dev.features)) { |
| bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_3EV3); |
| } |
| |
| if (BT_FEAT_3SLOT_PKT(bt_dev.features)) { |
| bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_2EV5 | |
| HCI_PKT_TYPE_ESCO_3EV5); |
| } |
| } |
| |
| static void read_buffer_size_complete(struct net_buf *buf) |
| { |
| struct bt_hci_rp_read_buffer_size *rp = (void *)buf->data; |
| uint16_t pkts; |
| |
| LOG_DBG("status 0x%02x", rp->status); |
| |
| bt_dev.br.mtu = sys_le16_to_cpu(rp->acl_max_len); |
| pkts = sys_le16_to_cpu(rp->acl_max_num); |
| |
| LOG_DBG("ACL BR/EDR buffers: pkts %u mtu %u", pkts, bt_dev.br.mtu); |
| |
| k_sem_init(&bt_dev.br.pkts, pkts, pkts); |
| } |
| |
| int bt_br_init(void) |
| { |
| struct net_buf *buf; |
| struct bt_hci_cp_write_ssp_mode *ssp_cp; |
| struct bt_hci_cp_write_inquiry_mode *inq_cp; |
| struct bt_hci_write_local_name *name_cp; |
| int err; |
| |
| /* Read extended local features */ |
| if (BT_FEAT_EXT_FEATURES(bt_dev.features)) { |
| err = read_ext_features(); |
| if (err) { |
| return err; |
| } |
| } |
| |
| /* Add local supported packet types to bt_dev */ |
| device_supported_pkt_type(); |
| |
| /* Get BR/EDR buffer size */ |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_BUFFER_SIZE, NULL, &buf); |
| if (err) { |
| return err; |
| } |
| |
| read_buffer_size_complete(buf); |
| net_buf_unref(buf); |
| |
| /* Set SSP mode */ |
| buf = bt_hci_cmd_create(BT_HCI_OP_WRITE_SSP_MODE, sizeof(*ssp_cp)); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| ssp_cp = net_buf_add(buf, sizeof(*ssp_cp)); |
| ssp_cp->mode = 0x01; |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_SSP_MODE, buf, NULL); |
| if (err) { |
| return err; |
| } |
| |
| /* Enable Inquiry results with RSSI or extended Inquiry */ |
| buf = bt_hci_cmd_create(BT_HCI_OP_WRITE_INQUIRY_MODE, sizeof(*inq_cp)); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| inq_cp = net_buf_add(buf, sizeof(*inq_cp)); |
| inq_cp->mode = 0x02; |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_INQUIRY_MODE, buf, NULL); |
| if (err) { |
| return err; |
| } |
| |
| /* Set local name */ |
| buf = bt_hci_cmd_create(BT_HCI_OP_WRITE_LOCAL_NAME, sizeof(*name_cp)); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| name_cp = net_buf_add(buf, sizeof(*name_cp)); |
| strncpy((char *)name_cp->local_name, CONFIG_BT_DEVICE_NAME, |
| sizeof(name_cp->local_name)); |
| |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_LOCAL_NAME, buf, NULL); |
| if (err) { |
| return err; |
| } |
| |
| /* Set page timeout*/ |
| buf = bt_hci_cmd_create(BT_HCI_OP_WRITE_PAGE_TIMEOUT, sizeof(uint16_t)); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| net_buf_add_le16(buf, CONFIG_BT_PAGE_TIMEOUT); |
| |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_PAGE_TIMEOUT, buf, NULL); |
| if (err) { |
| return err; |
| } |
| |
| /* Enable BR/EDR SC if supported */ |
| if (BT_FEAT_SC(bt_dev.features)) { |
| struct bt_hci_cp_write_sc_host_supp *sc_cp; |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_WRITE_SC_HOST_SUPP, |
| sizeof(*sc_cp)); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| sc_cp = net_buf_add(buf, sizeof(*sc_cp)); |
| sc_cp->sc_support = 0x01; |
| |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_SC_HOST_SUPP, buf, |
| NULL); |
| if (err) { |
| return err; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int br_start_inquiry(const struct bt_br_discovery_param *param) |
| { |
| const uint8_t iac[3] = { 0x33, 0x8b, 0x9e }; |
| struct bt_hci_op_inquiry *cp; |
| struct net_buf *buf; |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_INQUIRY, sizeof(*cp)); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| cp = net_buf_add(buf, sizeof(*cp)); |
| |
| cp->length = param->length; |
| cp->num_rsp = 0xff; /* we limit discovery only by time */ |
| |
| memcpy(cp->lap, iac, 3); |
| if (param->limited) { |
| cp->lap[0] = 0x00; |
| } |
| |
| return bt_hci_cmd_send_sync(BT_HCI_OP_INQUIRY, buf, NULL); |
| } |
| |
| static bool valid_br_discov_param(const struct bt_br_discovery_param *param, |
| size_t num_results) |
| { |
| if (!num_results || num_results > 255) { |
| return false; |
| } |
| |
| if (!param->length || param->length > 0x30) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| int bt_br_discovery_start(const struct bt_br_discovery_param *param, |
| struct bt_br_discovery_result *results, size_t cnt, |
| bt_br_discovery_cb_t cb) |
| { |
| int err; |
| |
| LOG_DBG(""); |
| |
| if (!valid_br_discov_param(param, cnt)) { |
| return -EINVAL; |
| } |
| |
| if (atomic_test_bit(bt_dev.flags, BT_DEV_INQUIRY)) { |
| return -EALREADY; |
| } |
| |
| err = br_start_inquiry(param); |
| if (err) { |
| return err; |
| } |
| |
| atomic_set_bit(bt_dev.flags, BT_DEV_INQUIRY); |
| |
| (void)memset(results, 0, sizeof(*results) * cnt); |
| |
| discovery_cb = cb; |
| discovery_results = results; |
| discovery_results_size = cnt; |
| discovery_results_count = 0; |
| |
| return 0; |
| } |
| |
| int bt_br_discovery_stop(void) |
| { |
| int err; |
| int i; |
| |
| LOG_DBG(""); |
| |
| if (!atomic_test_bit(bt_dev.flags, BT_DEV_INQUIRY)) { |
| return -EALREADY; |
| } |
| |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_INQUIRY_CANCEL, NULL, NULL); |
| if (err) { |
| return err; |
| } |
| |
| for (i = 0; i < discovery_results_count; i++) { |
| struct discovery_priv *priv; |
| struct bt_hci_cp_remote_name_cancel *cp; |
| struct net_buf *buf; |
| |
| priv = (struct discovery_priv *)&discovery_results[i]._priv; |
| |
| if (!priv->resolving) { |
| continue; |
| } |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_REMOTE_NAME_CANCEL, |
| sizeof(*cp)); |
| if (!buf) { |
| continue; |
| } |
| |
| cp = net_buf_add(buf, sizeof(*cp)); |
| bt_addr_copy(&cp->bdaddr, &discovery_results[i].addr); |
| |
| bt_hci_cmd_send_sync(BT_HCI_OP_REMOTE_NAME_CANCEL, buf, NULL); |
| } |
| |
| atomic_clear_bit(bt_dev.flags, BT_DEV_INQUIRY); |
| |
| discovery_cb = NULL; |
| discovery_results = NULL; |
| discovery_results_size = 0; |
| discovery_results_count = 0; |
| |
| return 0; |
| } |
| |
| static int write_scan_enable(uint8_t scan) |
| { |
| struct net_buf *buf; |
| int err; |
| |
| LOG_DBG("type %u", scan); |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_WRITE_SCAN_ENABLE, 1); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| net_buf_add_u8(buf, scan); |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_SCAN_ENABLE, buf, NULL); |
| if (err) { |
| return err; |
| } |
| |
| atomic_set_bit_to(bt_dev.flags, BT_DEV_ISCAN, |
| (scan & BT_BREDR_SCAN_INQUIRY)); |
| atomic_set_bit_to(bt_dev.flags, BT_DEV_PSCAN, |
| (scan & BT_BREDR_SCAN_PAGE)); |
| |
| return 0; |
| } |
| |
| int bt_br_set_connectable(bool enable) |
| { |
| if (enable) { |
| if (atomic_test_bit(bt_dev.flags, BT_DEV_PSCAN)) { |
| return -EALREADY; |
| } else { |
| return write_scan_enable(BT_BREDR_SCAN_PAGE); |
| } |
| } else { |
| if (!atomic_test_bit(bt_dev.flags, BT_DEV_PSCAN)) { |
| return -EALREADY; |
| } else { |
| return write_scan_enable(BT_BREDR_SCAN_DISABLED); |
| } |
| } |
| } |
| |
| int bt_br_set_discoverable(bool enable) |
| { |
| if (enable) { |
| if (atomic_test_bit(bt_dev.flags, BT_DEV_ISCAN)) { |
| return -EALREADY; |
| } |
| |
| if (!atomic_test_bit(bt_dev.flags, BT_DEV_PSCAN)) { |
| return -EPERM; |
| } |
| |
| return write_scan_enable(BT_BREDR_SCAN_INQUIRY | |
| BT_BREDR_SCAN_PAGE); |
| } else { |
| if (!atomic_test_bit(bt_dev.flags, BT_DEV_ISCAN)) { |
| return -EALREADY; |
| } |
| |
| return write_scan_enable(BT_BREDR_SCAN_PAGE); |
| } |
| } |