| /* |
| * Copyright (c) 2020-2021 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #include <stdint.h> |
| #include <assert.h> |
| #include <zephyr/sys/check.h> |
| #include <zephyr/sys/byteorder.h> |
| |
| #include <zephyr/bluetooth/bluetooth.h> |
| #include <zephyr/bluetooth/conn.h> |
| #include <zephyr/bluetooth/l2cap.h> |
| #include <zephyr/bluetooth/hci.h> |
| #include <zephyr/bluetooth/hci_vs.h> |
| #include <zephyr/bluetooth/direction.h> |
| |
| #include "hci_core.h" |
| #include "scan.h" |
| #include "conn_internal.h" |
| #include "direction_internal.h" |
| |
| #include <zephyr/logging/log.h> |
| |
| LOG_MODULE_REGISTER(bt_df, CONFIG_BT_DF_LOG_LEVEL); |
| |
| /* @brief Antenna information for LE Direction Finding */ |
| struct bt_le_df_ant_info { |
| /* Bitfield holding optional switching and sampling rates */ |
| uint8_t switch_sample_rates; |
| /* Available antennae number */ |
| uint8_t num_ant; |
| /* Maximum supported antennae switching pattern length */ |
| uint8_t max_switch_pattern_len; |
| /* Maximum length of CTE in 8[us] units */ |
| uint8_t max_cte_len; |
| }; |
| |
| static struct bt_le_df_ant_info df_ant_info; |
| #if defined(CONFIG_BT_DF_CONNECTIONLESS_CTE_RX) || defined(CONFIG_BT_DF_CONNECTION_CTE_RX) |
| const static uint8_t df_dummy_switch_pattern[BT_HCI_LE_SWITCH_PATTERN_LEN_MIN] = { 0, 0 }; |
| #endif /* CONFIG_BT_DF_CONNECTIONLESS_CTE_RX || CONFIG_BT_DF_CONNECTION_CTE_RX */ |
| |
| #define DF_AOD_TX_1US_SUPPORT(supp) (supp & BT_HCI_LE_1US_AOD_TX) |
| #define DF_AOD_RX_1US_SUPPORT(supp) (supp & BT_HCI_LE_1US_AOD_RX) |
| #define DF_AOA_RX_1US_SUPPORT(supp) (supp & BT_HCI_LE_1US_AOA_RX) |
| |
| #define DF_SAMPLING_ANTENNA_NUMBER_MIN 0x2 |
| |
| #if defined(CONFIG_BT_DF_CONNECTIONLESS_CTE_RX) || defined(CONFIG_BT_DF_CONNECTION_CTE_RX) |
| static bool valid_cte_rx_common_params(uint8_t cte_types, uint8_t slot_durations, |
| uint8_t num_ant_ids, const uint8_t *ant_ids); |
| #endif /* CONFIG_BT_DF_CONNECTIONLESS_CTE_RX || CONFIG_BT_DF_CONNECTION_CTE_RX */ |
| |
| #if defined(CONFIG_BT_DF_CONNECTIONLESS_CTE_RX) |
| static bool valid_cl_cte_rx_params(const struct bt_df_per_adv_sync_cte_rx_param *params); |
| static int |
| prepare_cl_cte_rx_enable_cmd_params(struct net_buf **buf, struct bt_le_per_adv_sync *sync, |
| const struct bt_df_per_adv_sync_cte_rx_param *params, |
| bool enable); |
| static int hci_df_set_cl_cte_rx_enable(struct bt_le_per_adv_sync *sync, bool enable, |
| const struct bt_df_per_adv_sync_cte_rx_param *params); |
| #endif /* CONFIG_BT_DF_CONNECTIONLESS_CTE_RX */ |
| |
| #if defined(CONFIG_BT_DF_CONNECTION_CTE_RX) |
| static int prepare_conn_cte_rx_enable_cmd_params(struct net_buf **buf, struct bt_conn *conn, |
| const struct bt_df_conn_cte_rx_param *params, |
| bool enable); |
| static int hci_df_set_conn_cte_rx_enable(struct bt_conn *conn, bool enable, |
| const struct bt_df_conn_cte_rx_param *params); |
| #endif /* CONFIG_BT_DF_CONNECTION_CTE_RX */ |
| |
| static uint8_t get_hci_cte_type(enum bt_df_cte_type type) |
| { |
| switch (type) { |
| case BT_DF_CTE_TYPE_AOA: |
| return BT_HCI_LE_AOA_CTE; |
| case BT_DF_CTE_TYPE_AOD_1US: |
| return BT_HCI_LE_AOD_CTE_1US; |
| case BT_DF_CTE_TYPE_AOD_2US: |
| return BT_HCI_LE_AOD_CTE_2US; |
| default: |
| LOG_ERR("Wrong CTE type"); |
| return BT_HCI_LE_NO_CTE; |
| } |
| } |
| |
| static int hci_df_set_cl_cte_tx_params(const struct bt_le_ext_adv *adv, |
| const struct bt_df_adv_cte_tx_param *params) |
| { |
| struct bt_hci_cp_le_set_cl_cte_tx_params *cp; |
| struct net_buf *buf; |
| |
| /* If AoD is not enabled, ant_ids are ignored by controller: |
| * BT Core spec 5.2 Vol 4, Part E sec. 7.8.80. |
| */ |
| if (params->cte_type == BT_DF_CTE_TYPE_AOD_1US || |
| params->cte_type == BT_DF_CTE_TYPE_AOD_2US) { |
| if (!BT_FEAT_LE_ANT_SWITCH_TX_AOD(bt_dev.le.features)) { |
| return -EINVAL; |
| } |
| |
| if (params->cte_type == BT_DF_CTE_TYPE_AOD_1US && |
| !DF_AOD_TX_1US_SUPPORT(df_ant_info.switch_sample_rates)) { |
| return -EINVAL; |
| } |
| |
| if (params->num_ant_ids < BT_HCI_LE_SWITCH_PATTERN_LEN_MIN || |
| params->num_ant_ids > BT_HCI_LE_SWITCH_PATTERN_LEN_MAX || |
| !params->ant_ids) { |
| return -EINVAL; |
| } |
| } else if (params->cte_type != BT_DF_CTE_TYPE_AOA) { |
| return -EINVAL; |
| } |
| |
| if (params->cte_len < BT_HCI_LE_CTE_LEN_MIN || |
| params->cte_len > BT_HCI_LE_CTE_LEN_MAX) { |
| return -EINVAL; |
| } |
| |
| if (params->cte_count < BT_HCI_LE_CTE_COUNT_MIN || |
| params->cte_count > BT_HCI_LE_CTE_COUNT_MAX) { |
| return -EINVAL; |
| } |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_CL_CTE_TX_PARAMS, |
| sizeof(*cp) + params->num_ant_ids); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| cp = net_buf_add(buf, sizeof(*cp)); |
| cp->handle = adv->handle; |
| cp->cte_len = params->cte_len; |
| cp->cte_type = get_hci_cte_type(params->cte_type); |
| cp->cte_count = params->cte_count; |
| |
| if (params->num_ant_ids) { |
| uint8_t *dest_ant_ids = net_buf_add(buf, params->num_ant_ids); |
| |
| memcpy(dest_ant_ids, params->ant_ids, params->num_ant_ids); |
| cp->switch_pattern_len = params->num_ant_ids; |
| } else { |
| cp->switch_pattern_len = 0; |
| } |
| |
| return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_CL_CTE_TX_PARAMS, |
| buf, NULL); |
| } |
| |
| /* @brief Function provides information about DF antennae number and |
| * controller capabilities related with Constant Tone Extension. |
| * |
| * @param[out] switch_sample_rates Optional switching and sampling rates. |
| * @param[out] num_ant Antennae number. |
| * @param[out] max_switch_pattern_len Maximum supported antennae switching |
| * patterns length. |
| * @param[out] max_cte_len Maximum length of CTE in 8[us] units. |
| * |
| * @return Zero in case of success, other value in case of failure. |
| */ |
| static int hci_df_read_ant_info(uint8_t *switch_sample_rates, |
| uint8_t *num_ant, |
| uint8_t *max_switch_pattern_len, |
| uint8_t *max_cte_len) |
| { |
| __ASSERT_NO_MSG(switch_sample_rates); |
| __ASSERT_NO_MSG(num_ant); |
| __ASSERT_NO_MSG(max_switch_pattern_len); |
| __ASSERT_NO_MSG(max_cte_len); |
| |
| struct bt_hci_rp_le_read_ant_info *rp; |
| struct net_buf *rsp; |
| int err; |
| |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_ANT_INFO, NULL, &rsp); |
| if (err) { |
| LOG_ERR("Failed to read antenna information"); |
| return err; |
| } |
| |
| rp = (void *)rsp->data; |
| |
| LOG_DBG("DF: sw. sampl rates: %x ant num: %u , max sw. pattern len: %u," |
| "max CTE len %d", rp->switch_sample_rates, rp->num_ant, |
| rp->max_switch_pattern_len, rp->max_cte_len); |
| |
| *switch_sample_rates = rp->switch_sample_rates; |
| *num_ant = rp->num_ant; |
| *max_switch_pattern_len = rp->max_switch_pattern_len; |
| *max_cte_len = rp->max_cte_len; |
| |
| net_buf_unref(rsp); |
| |
| return 0; |
| } |
| |
| /* @brief Function handles send of HCI command to enable or disables CTE |
| * transmission for given advertising set. |
| * |
| * @param[in] adv Pointer to advertising set |
| * @param[in] enable Enable or disable CTE TX |
| * |
| * @return Zero in case of success, other value in case of failure. |
| */ |
| static int hci_df_set_adv_cte_tx_enable(struct bt_le_ext_adv *adv, |
| bool enable) |
| { |
| struct bt_hci_cp_le_set_cl_cte_tx_enable *cp; |
| struct bt_hci_cmd_state_set state; |
| struct net_buf *buf; |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_CL_CTE_TX_ENABLE, sizeof(*cp)); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| cp = net_buf_add(buf, sizeof(*cp)); |
| (void)memset(cp, 0, sizeof(*cp)); |
| |
| cp->handle = adv->handle; |
| cp->cte_enable = enable ? 1 : 0; |
| |
| bt_hci_cmd_state_set_init(buf, &state, adv->flags, BT_PER_ADV_CTE_ENABLED, |
| enable); |
| |
| return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_CL_CTE_TX_ENABLE, |
| buf, NULL); |
| } |
| |
| #if defined(CONFIG_BT_DF_CONNECTIONLESS_CTE_RX) || defined(CONFIG_BT_DF_CONNECTION_CTE_RX) |
| static bool valid_cte_rx_common_params(uint8_t cte_types, uint8_t slot_durations, |
| uint8_t num_ant_ids, const uint8_t *ant_ids) |
| { |
| if (!(cte_types & BT_DF_CTE_TYPE_ALL)) { |
| return false; |
| } |
| |
| if (cte_types & BT_DF_CTE_TYPE_AOA) { |
| if (df_ant_info.num_ant < DF_SAMPLING_ANTENNA_NUMBER_MIN || |
| !BT_FEAT_LE_ANT_SWITCH_RX_AOA(bt_dev.le.features)) { |
| return false; |
| } |
| |
| if (!(slot_durations == BT_HCI_LE_ANTENNA_SWITCHING_SLOT_2US || |
| (slot_durations == BT_HCI_LE_ANTENNA_SWITCHING_SLOT_1US && |
| DF_AOA_RX_1US_SUPPORT(df_ant_info.switch_sample_rates)))) { |
| return false; |
| } |
| |
| if (num_ant_ids < BT_HCI_LE_SWITCH_PATTERN_LEN_MIN || |
| num_ant_ids > df_ant_info.max_switch_pattern_len || !ant_ids) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| #endif /* CONFIG_BT_DF_CONNECTIONLESS_CTE_RX || CONFIG_BT_DF_CONNECTION_CTE_RX */ |
| |
| #if defined(CONFIG_BT_DF_CONNECTIONLESS_CTE_RX) |
| static bool valid_cl_cte_rx_params(const struct bt_df_per_adv_sync_cte_rx_param *params) |
| { |
| if (params->max_cte_count > BT_HCI_LE_SAMPLE_CTE_COUNT_MAX) { |
| return false; |
| } |
| |
| if (params->cte_types & BT_DF_CTE_TYPE_AOA) { |
| return valid_cte_rx_common_params(params->cte_types, params->slot_durations, |
| params->num_ant_ids, params->ant_ids); |
| } |
| |
| return true; |
| } |
| |
| static int |
| prepare_cl_cte_rx_enable_cmd_params(struct net_buf **buf, struct bt_le_per_adv_sync *sync, |
| const struct bt_df_per_adv_sync_cte_rx_param *params, |
| bool enable) |
| { |
| struct bt_hci_cp_le_set_cl_cte_sampling_enable *cp; |
| uint8_t switch_pattern_len; |
| |
| if (params->cte_types & BT_DF_CTE_TYPE_AOA) { |
| switch_pattern_len = params->num_ant_ids; |
| } else { |
| switch_pattern_len = ARRAY_SIZE(df_dummy_switch_pattern); |
| } |
| |
| /* If CTE Rx is enabled, command parameters total length must include |
| * antenna ids, so command size if extended by num_and_ids. |
| */ |
| *buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_CL_CTE_SAMPLING_ENABLE, |
| (sizeof(struct bt_hci_cp_le_set_cl_cte_sampling_enable) + |
| (enable ? switch_pattern_len : 0))); |
| if (!(*buf)) { |
| return -ENOBUFS; |
| } |
| |
| cp = net_buf_add(*buf, sizeof(*cp)); |
| (void)memset(cp, 0, sizeof(*cp)); |
| |
| cp->sync_handle = sys_cpu_to_le16(sync->handle); |
| cp->sampling_enable = enable ? 1 : 0; |
| |
| if (enable) { |
| const uint8_t *ant_ids; |
| uint8_t *dest_ant_ids; |
| |
| cp->max_sampled_cte = params->max_cte_count; |
| |
| if (params->cte_types & BT_DF_CTE_TYPE_AOA) { |
| cp->slot_durations = params->slot_durations; |
| ant_ids = params->ant_ids; |
| } else { |
| /* Those values are put here due to constraints from HCI command |
| * specification: Bluetooth Core Spec. 5.3 Vol 4,Part E, sec 7.8.82. |
| * There is no right way to successfully send the command to enable CTE |
| * receive for AoD mode (e.g. device equipped with single antenna). |
| */ |
| cp->slot_durations = BT_HCI_LE_ANTENNA_SWITCHING_SLOT_2US; |
| ant_ids = &df_dummy_switch_pattern[0]; |
| } |
| |
| cp->switch_pattern_len = switch_pattern_len; |
| dest_ant_ids = net_buf_add(*buf, cp->switch_pattern_len); |
| memcpy(dest_ant_ids, ant_ids, cp->switch_pattern_len); |
| } |
| |
| return 0; |
| } |
| |
| static int hci_df_set_cl_cte_rx_enable(struct bt_le_per_adv_sync *sync, bool enable, |
| const struct bt_df_per_adv_sync_cte_rx_param *params) |
| { |
| struct bt_hci_rp_le_set_cl_cte_sampling_enable *rp; |
| struct bt_hci_cmd_state_set state; |
| struct net_buf *buf, *rsp; |
| int err; |
| |
| if (enable) { |
| if (!valid_cl_cte_rx_params(params)) { |
| return -EINVAL; |
| } |
| } |
| |
| err = prepare_cl_cte_rx_enable_cmd_params(&buf, sync, params, enable); |
| if (err) { |
| return err; |
| } |
| |
| bt_hci_cmd_state_set_init(buf, &state, sync->flags, BT_PER_ADV_SYNC_CTE_ENABLED, enable); |
| |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_CL_CTE_SAMPLING_ENABLE, buf, &rsp); |
| if (err) { |
| return err; |
| } |
| |
| rp = (void *)rsp->data; |
| if (sync->handle != sys_le16_to_cpu(rp->sync_handle)) { |
| err = -EIO; |
| } else { |
| sync->cte_types = (enable ? params->cte_types : 0); |
| } |
| |
| net_buf_unref(rsp); |
| |
| return err; |
| } |
| |
| int hci_df_prepare_connectionless_iq_report(struct net_buf *buf, |
| struct bt_df_per_adv_sync_iq_samples_report *report, |
| struct bt_le_per_adv_sync **per_adv_sync_to_report) |
| { |
| struct bt_hci_evt_le_connectionless_iq_report *evt; |
| struct bt_le_per_adv_sync *per_adv_sync; |
| |
| if (buf->len < sizeof(*evt)) { |
| LOG_ERR("Unexpected end of buffer"); |
| return -EINVAL; |
| } |
| |
| evt = net_buf_pull_mem(buf, sizeof(*evt)); |
| |
| per_adv_sync = bt_hci_per_adv_sync_lookup_handle(sys_le16_to_cpu(evt->sync_handle)); |
| |
| if (!per_adv_sync) { |
| LOG_ERR("Unknown handle 0x%04X for iq samples report", |
| sys_le16_to_cpu(evt->sync_handle)); |
| return -EINVAL; |
| } |
| |
| if (!atomic_test_bit(per_adv_sync->flags, BT_PER_ADV_SYNC_CTE_ENABLED)) { |
| LOG_ERR("Received PA CTE report when CTE receive disabled"); |
| return -EINVAL; |
| } |
| |
| if (!(per_adv_sync->cte_types & BIT(evt->cte_type))) { |
| LOG_DBG("CTE filtered out by cte_type: %u", evt->cte_type); |
| return -EINVAL; |
| } |
| |
| report->chan_idx = evt->chan_idx; |
| report->rssi = sys_le16_to_cpu(evt->rssi); |
| report->rssi_ant_id = evt->rssi_ant_id; |
| report->cte_type = BIT(evt->cte_type); |
| report->packet_status = evt->packet_status; |
| report->slot_durations = evt->slot_durations; |
| report->per_evt_counter = sys_le16_to_cpu(evt->per_evt_counter); |
| report->sample_type = BT_DF_IQ_SAMPLE_8_BITS_INT; |
| report->sample_count = evt->sample_count; |
| report->sample = &evt->sample[0]; |
| |
| *per_adv_sync_to_report = per_adv_sync; |
| |
| return 0; |
| } |
| |
| int hci_df_vs_prepare_connectionless_iq_report(struct net_buf *buf, |
| struct bt_df_per_adv_sync_iq_samples_report *report, |
| struct bt_le_per_adv_sync **per_adv_sync_to_report) |
| { |
| struct bt_hci_evt_vs_le_connectionless_iq_report *evt; |
| struct bt_le_per_adv_sync *per_adv_sync; |
| |
| if (buf->len < sizeof(*evt)) { |
| LOG_ERR("Unexpected end of buffer"); |
| return -EINVAL; |
| } |
| |
| evt = net_buf_pull_mem(buf, sizeof(*evt)); |
| |
| per_adv_sync = bt_hci_per_adv_sync_lookup_handle(sys_le16_to_cpu(evt->sync_handle)); |
| |
| if (!per_adv_sync) { |
| LOG_ERR("Unknown handle 0x%04X for iq samples report", |
| sys_le16_to_cpu(evt->sync_handle)); |
| return -EINVAL; |
| } |
| |
| if (!atomic_test_bit(per_adv_sync->flags, BT_PER_ADV_SYNC_CTE_ENABLED)) { |
| LOG_ERR("Received PA CTE report when CTE receive disabled"); |
| return -EINVAL; |
| } |
| |
| if (!(per_adv_sync->cte_types & BIT(evt->cte_type))) { |
| LOG_DBG("CTE filtered out by cte_type: %u", evt->cte_type); |
| return -EINVAL; |
| } |
| |
| report->chan_idx = evt->chan_idx; |
| report->rssi = sys_le16_to_cpu(evt->rssi); |
| report->rssi_ant_id = evt->rssi_ant_id; |
| report->cte_type = BIT(evt->cte_type); |
| report->packet_status = evt->packet_status; |
| report->slot_durations = evt->slot_durations; |
| report->per_evt_counter = sys_le16_to_cpu(evt->per_evt_counter); |
| report->sample_count = evt->sample_count; |
| report->sample_type = BT_DF_IQ_SAMPLE_16_BITS_INT; |
| report->sample16 = &evt->sample[0]; |
| |
| *per_adv_sync_to_report = per_adv_sync; |
| |
| return 0; |
| } |
| #endif /* CONFIG_BT_DF_CONNECTIONLESS_CTE_RX */ |
| |
| #if defined(CONFIG_BT_DF_CONNECTION_CTE_TX) |
| static bool valid_conn_cte_tx_params(const struct bt_df_conn_cte_tx_param *params) |
| { |
| if (!(params->cte_types & BT_DF_CTE_TYPE_ALL)) { |
| return false; |
| } |
| |
| /* If AoD is not enabled, ant_ids are ignored by controller: |
| * BT Core spec 5.2 Vol 4, Part E sec. 7.8.84. |
| */ |
| if ((params->cte_types & BT_DF_CTE_TYPE_AOD_1US || |
| params->cte_types & BT_DF_CTE_TYPE_AOD_1US) && |
| (params->num_ant_ids < BT_HCI_LE_SWITCH_PATTERN_LEN_MIN || |
| params->num_ant_ids > BT_HCI_LE_SWITCH_PATTERN_LEN_MAX || !params->ant_ids || |
| !BT_FEAT_LE_ANT_SWITCH_TX_AOD(bt_dev.le.features))) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static void prepare_conn_cte_tx_params_cmd(struct net_buf *buf, const struct bt_conn *conn, |
| const struct bt_df_conn_cte_tx_param *params) |
| { |
| struct bt_hci_cp_le_set_conn_cte_tx_params *cp; |
| uint8_t *ant_ids; |
| |
| cp = net_buf_add(buf, sizeof(*cp)); |
| (void)memset(cp, 0, sizeof(*cp)); |
| |
| cp->handle = sys_cpu_to_le16(conn->handle); |
| cp->cte_types = params->cte_types; |
| |
| if (params->cte_types & (BT_DF_CTE_TYPE_AOD_1US | BT_DF_CTE_TYPE_AOD_2US)) { |
| cp->switch_pattern_len = params->num_ant_ids; |
| |
| ant_ids = net_buf_add(buf, cp->switch_pattern_len); |
| (void)memcpy(ant_ids, params->ant_ids, cp->switch_pattern_len); |
| } else { |
| cp->switch_pattern_len = 0U; |
| } |
| } |
| |
| /** |
| * @brief Function sets CTE parameters for connection object |
| * |
| * @param conn Connection object |
| * @param params CTE transmission parameters. |
| * |
| * @return Zero in case of success, other value in case of failure. |
| */ |
| static int hci_df_set_conn_cte_tx_param(struct bt_conn *conn, |
| const struct bt_df_conn_cte_tx_param *params) |
| { |
| struct bt_hci_rp_le_set_conn_cte_tx_params *rp; |
| struct bt_hci_cmd_state_set state; |
| struct net_buf *buf, *rsp; |
| uint8_t num_ant_ids; |
| int err; |
| |
| /* If AoD is not enabled, ant_ids are ignored by controller: |
| * BT Core spec 5.2 Vol 4, Part E sec. 7.8.84. |
| */ |
| if (!valid_conn_cte_tx_params(params)) { |
| return -EINVAL; |
| } |
| |
| num_ant_ids = ((params->cte_types & (BT_DF_CTE_TYPE_AOD_1US | BT_DF_CTE_TYPE_AOD_2US)) ? |
| params->num_ant_ids : 0); |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_CONN_CTE_TX_PARAMS, |
| sizeof(struct bt_hci_cp_le_set_conn_cte_tx_params) + num_ant_ids); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| prepare_conn_cte_tx_params_cmd(buf, conn, params); |
| |
| /* CTE transmission parameters must be set only once for connection lifetime, hence the |
| * flag BT_CONN_CTE_TX_PARAMS_SET is always set to true and never set to false. |
| */ |
| bt_hci_cmd_state_set_init(buf, &state, conn->flags, BT_CONN_CTE_TX_PARAMS_SET, true); |
| |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_CONN_CTE_TX_PARAMS, buf, &rsp); |
| if (err) { |
| return err; |
| } |
| |
| rp = (void *)rsp->data; |
| if (conn->handle != sys_le16_to_cpu(rp->handle)) { |
| err = -EIO; |
| } |
| |
| net_buf_unref(rsp); |
| |
| return err; |
| } |
| #endif /* CONFIG_BT_DF_CONNECTION_CTE_TX */ |
| |
| #if defined(CONFIG_BT_DF_CONNECTION_CTE_RX) |
| static int prepare_conn_cte_rx_enable_cmd_params(struct net_buf **buf, struct bt_conn *conn, |
| const struct bt_df_conn_cte_rx_param *params, |
| bool enable) |
| { |
| struct bt_hci_cp_le_set_conn_cte_rx_params *cp; |
| uint8_t switch_pattern_len; |
| |
| if (params->cte_types & BT_DF_CTE_TYPE_AOA) { |
| switch_pattern_len = params->num_ant_ids; |
| } else { |
| switch_pattern_len = ARRAY_SIZE(df_dummy_switch_pattern); |
| } |
| |
| /* If CTE Rx is enabled, command parameters total length must include |
| * antenna ids, so command size if extended by num_and_ids. |
| */ |
| *buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_CONN_CTE_RX_PARAMS, |
| (sizeof(struct bt_hci_cp_le_set_conn_cte_rx_params) + |
| (enable ? switch_pattern_len : 0))); |
| if (!(*buf)) { |
| return -ENOBUFS; |
| } |
| |
| cp = net_buf_add(*buf, sizeof(*cp)); |
| (void)memset(cp, 0, sizeof(*cp)); |
| |
| cp->handle = sys_cpu_to_le16(conn->handle); |
| cp->sampling_enable = enable ? 1 : 0; |
| |
| if (enable) { |
| const uint8_t *ant_ids; |
| uint8_t *dest_ant_ids; |
| |
| if (params->cte_types & BT_DF_CTE_TYPE_AOA) { |
| cp->slot_durations = params->slot_durations; |
| ant_ids = params->ant_ids; |
| } else { |
| /* Those values are put here due to constraints from HCI command |
| * specification: Bluetooth Core Spec. 5.3 Vol 4,Part E, sec 7.8.85. |
| * There is no right way to successfully send the command to enable CTE |
| * receive for AoD mode (e.g. device equipped with single antenna). |
| * There is no CTE type in the parameters list, so controller is forced |
| * to check correctness of all parameters always. |
| */ |
| cp->slot_durations = BT_HCI_LE_ANTENNA_SWITCHING_SLOT_2US; |
| ant_ids = &df_dummy_switch_pattern[0]; |
| } |
| |
| cp->switch_pattern_len = switch_pattern_len; |
| dest_ant_ids = net_buf_add(*buf, cp->switch_pattern_len); |
| (void)memcpy(dest_ant_ids, ant_ids, cp->switch_pattern_len); |
| } |
| |
| return 0; |
| } |
| |
| static int hci_df_set_conn_cte_rx_enable(struct bt_conn *conn, bool enable, |
| const struct bt_df_conn_cte_rx_param *params) |
| { |
| struct bt_hci_rp_le_set_conn_cte_rx_params *rp; |
| struct bt_hci_cmd_state_set state; |
| struct net_buf *buf, *rsp; |
| int err; |
| |
| if (enable) { |
| if (!valid_cte_rx_common_params(params->cte_types, params->slot_durations, |
| params->num_ant_ids, params->ant_ids)) { |
| return -EINVAL; |
| } |
| } |
| |
| err = prepare_conn_cte_rx_enable_cmd_params(&buf, conn, params, enable); |
| if (err) { |
| return err; |
| } |
| |
| bt_hci_cmd_state_set_init(buf, &state, conn->flags, BT_CONN_CTE_RX_ENABLED, enable); |
| |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_CONN_CTE_RX_PARAMS, buf, &rsp); |
| if (err) { |
| return err; |
| } |
| |
| rp = (void *)rsp->data; |
| if (conn->handle != sys_le16_to_cpu(rp->handle)) { |
| err = -EIO; |
| } else { |
| conn->cte_types = (enable ? params->cte_types : 0); |
| /* This flag is set once for connection object. It is never cleared because CTE RX |
| * params must be set at least once for connection object to successfully execute |
| * CTE REQ procedure. |
| */ |
| atomic_set_bit(conn->flags, BT_CONN_CTE_RX_PARAMS_SET); |
| } |
| |
| net_buf_unref(rsp); |
| |
| return err; |
| } |
| |
| int hci_df_prepare_connection_iq_report(struct net_buf *buf, |
| struct bt_df_conn_iq_samples_report *report, |
| struct bt_conn **conn_to_report) |
| { |
| struct bt_hci_evt_le_connection_iq_report *evt; |
| struct bt_conn *conn; |
| |
| if (buf->len < sizeof(*evt)) { |
| LOG_ERR("Unexpected end of buffer"); |
| return -EINVAL; |
| } |
| |
| evt = net_buf_pull_mem(buf, sizeof(*evt)); |
| |
| conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->conn_handle), BT_CONN_TYPE_LE); |
| if (!conn) { |
| LOG_ERR("Unknown conn handle 0x%04X for iq samples report", |
| sys_le16_to_cpu(evt->conn_handle)); |
| return -EINVAL; |
| } |
| |
| if (!atomic_test_bit(conn->flags, BT_CONN_CTE_RX_ENABLED)) { |
| LOG_ERR("Received conn CTE report when CTE receive disabled"); |
| bt_conn_unref(conn); |
| return -EINVAL; |
| } |
| |
| if (!(conn->cte_types & BIT(evt->cte_type))) { |
| LOG_DBG("CTE filtered out by cte_type: %u", evt->cte_type); |
| bt_conn_unref(conn); |
| return -EINVAL; |
| } |
| |
| report->err = BT_DF_IQ_REPORT_ERR_SUCCESS; |
| report->chan_idx = evt->data_chan_idx; |
| report->rx_phy = evt->rx_phy; |
| report->chan_idx = evt->data_chan_idx; |
| report->rssi = evt->rssi; |
| report->rssi_ant_id = evt->rssi_ant_id; |
| report->cte_type = BIT(evt->cte_type); |
| report->packet_status = evt->packet_status; |
| report->slot_durations = evt->slot_durations; |
| report->conn_evt_counter = sys_le16_to_cpu(evt->conn_evt_counter); |
| report->sample_type = BT_DF_IQ_SAMPLE_8_BITS_INT; |
| report->sample_count = evt->sample_count; |
| report->sample = evt->sample; |
| |
| *conn_to_report = conn; |
| |
| return 0; |
| } |
| |
| int hci_df_vs_prepare_connection_iq_report(struct net_buf *buf, |
| struct bt_df_conn_iq_samples_report *report, |
| struct bt_conn **conn_to_report) |
| { |
| struct bt_hci_evt_vs_le_connection_iq_report *evt; |
| struct bt_conn *conn; |
| |
| if (buf->len < sizeof(*evt)) { |
| LOG_ERR("Unexpected end of buffer"); |
| return -EINVAL; |
| } |
| |
| evt = net_buf_pull_mem(buf, sizeof(*evt)); |
| |
| conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->conn_handle), BT_CONN_TYPE_LE); |
| if (!conn) { |
| LOG_ERR("Unknown conn handle 0x%04X for iq samples report", |
| sys_le16_to_cpu(evt->conn_handle)); |
| return -EINVAL; |
| } |
| |
| if (!atomic_test_bit(conn->flags, BT_CONN_CTE_RX_ENABLED)) { |
| LOG_ERR("Received conn CTE report when CTE receive disabled"); |
| bt_conn_unref(conn); |
| return -EINVAL; |
| } |
| |
| if (!(conn->cte_types & BIT(evt->cte_type))) { |
| LOG_DBG("CTE filtered out by cte_type: %u", evt->cte_type); |
| bt_conn_unref(conn); |
| return -EINVAL; |
| } |
| |
| report->err = BT_DF_IQ_REPORT_ERR_SUCCESS; |
| report->chan_idx = evt->data_chan_idx; |
| report->rx_phy = evt->rx_phy; |
| report->chan_idx = evt->data_chan_idx; |
| report->rssi = evt->rssi; |
| report->rssi_ant_id = evt->rssi_ant_id; |
| report->cte_type = BIT(evt->cte_type); |
| report->packet_status = evt->packet_status; |
| report->slot_durations = evt->slot_durations; |
| report->conn_evt_counter = sys_le16_to_cpu(evt->conn_evt_counter); |
| report->sample_type = BT_DF_IQ_SAMPLE_16_BITS_INT; |
| report->sample_count = evt->sample_count; |
| report->sample16 = evt->sample; |
| |
| *conn_to_report = conn; |
| |
| return 0; |
| } |
| |
| #endif /* CONFIG_BT_DF_CONNECTION_CTE_RX */ |
| |
| #if defined(CONFIG_BT_DF_CONNECTION_CTE_REQ) |
| static bool valid_cte_req_params(const struct bt_conn *conn, uint8_t cte_type, |
| uint8_t cte_length) |
| { |
| if (!(conn->cte_types & cte_type)) { |
| return false; |
| } |
| |
| if (cte_length < BT_HCI_LE_CTE_LEN_MIN || cte_length > BT_HCI_LE_CTE_LEN_MAX) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static void prepare_conn_cte_req_enable_cmd_params(struct net_buf *buf, const struct bt_conn *conn, |
| const struct bt_df_conn_cte_req_params *params, |
| bool enable) |
| { |
| struct bt_hci_cp_le_conn_cte_req_enable *cp; |
| |
| cp = net_buf_add(buf, sizeof(*cp)); |
| (void)memset(cp, 0, sizeof(*cp)); |
| |
| cp->handle = sys_cpu_to_le16(conn->handle); |
| cp->enable = enable ? 1 : 0; |
| |
| if (enable) { |
| cp->cte_request_interval = params->interval; |
| cp->requested_cte_length = sys_cpu_to_le16(params->cte_length); |
| cp->requested_cte_type = get_hci_cte_type(params->cte_type); |
| } |
| } |
| |
| static int hci_df_set_conn_cte_req_enable(struct bt_conn *conn, bool enable, |
| const struct bt_df_conn_cte_req_params *params) |
| { |
| struct bt_hci_cp_le_conn_cte_req_enable *rp; |
| struct bt_hci_cmd_state_set state; |
| struct net_buf *buf, *rsp; |
| int err; |
| |
| if (enable && !valid_cte_req_params(conn, params->cte_type, params->cte_length)) { |
| return -EINVAL; |
| } |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_LE_CONN_CTE_REQ_ENABLE, |
| sizeof(struct bt_hci_cp_le_conn_cte_req_enable)); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| prepare_conn_cte_req_enable_cmd_params(buf, conn, params, enable); |
| |
| bt_hci_cmd_state_set_init(buf, &state, conn->flags, BT_CONN_CTE_REQ_ENABLED, enable); |
| |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_CONN_CTE_RX_PARAMS, buf, &rsp); |
| if (err) { |
| return err; |
| } |
| |
| rp = (void *)rsp->data; |
| if (conn->handle != sys_le16_to_cpu(rp->handle)) { |
| err = -EIO; |
| } |
| |
| net_buf_unref(rsp); |
| |
| return err; |
| } |
| |
| int hci_df_prepare_conn_cte_req_failed(struct net_buf *buf, |
| struct bt_df_conn_iq_samples_report *report, |
| struct bt_conn **conn_to_report) |
| { |
| struct bt_hci_evt_le_cte_req_failed *evt; |
| struct bt_conn *conn; |
| |
| if (buf->len < sizeof(*evt)) { |
| LOG_ERR("Unexpected end of buffer"); |
| return -EINVAL; |
| } |
| |
| evt = net_buf_pull_mem(buf, sizeof(*evt)); |
| |
| conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->conn_handle), BT_CONN_TYPE_LE); |
| if (!conn) { |
| LOG_ERR("Unknown conn handle 0x%04X for iq samples report", |
| sys_le16_to_cpu(evt->conn_handle)); |
| return -EINVAL; |
| } |
| |
| if (!atomic_test_bit(conn->flags, BT_CONN_CTE_REQ_ENABLED)) { |
| LOG_ERR("Received conn CTE request notification when CTE REQ disabled"); |
| bt_conn_unref(conn); |
| return -EINVAL; |
| } |
| |
| (void)memset(report, 0U, sizeof(*report)); |
| |
| if (evt->status == BT_HCI_CTE_REQ_STATUS_RSP_WITHOUT_CTE) { |
| report->err = BT_DF_IQ_REPORT_ERR_NO_CTE; |
| } else { |
| report->err = BT_DF_IQ_REPORT_ERR_PEER_REJECTED; |
| } |
| |
| *conn_to_report = conn; |
| |
| return 0; |
| } |
| #endif /* CONFIG_BT_DF_CONNECTION_CTE_REQ */ |
| |
| #if defined(CONFIG_BT_DF_CONNECTION_CTE_RSP) |
| static void prepare_conn_cte_rsp_enable_cmd_params(struct net_buf *buf, const struct bt_conn *conn, |
| bool enable) |
| { |
| struct bt_hci_cp_le_conn_cte_rsp_enable *cp; |
| |
| cp = net_buf_add(buf, sizeof(*cp)); |
| (void)memset(cp, 0, sizeof(*cp)); |
| |
| cp->handle = sys_cpu_to_le16(conn->handle); |
| cp->enable = enable ? 1U : 0U; |
| } |
| |
| static int hci_df_set_conn_cte_rsp_enable(struct bt_conn *conn, bool enable) |
| { |
| struct bt_hci_rp_le_conn_cte_rsp_enable *rp; |
| struct bt_hci_cmd_state_set state; |
| struct net_buf *buf, *rsp; |
| int err; |
| |
| buf = bt_hci_cmd_create(BT_HCI_OP_LE_CONN_CTE_RSP_ENABLE, |
| sizeof(struct bt_hci_cp_le_conn_cte_rsp_enable)); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| prepare_conn_cte_rsp_enable_cmd_params(buf, conn, enable); |
| |
| bt_hci_cmd_state_set_init(buf, &state, conn->flags, BT_CONN_CTE_RSP_ENABLED, enable); |
| |
| err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_CONN_CTE_RSP_ENABLE, buf, &rsp); |
| if (err) { |
| return err; |
| } |
| |
| rp = (void *)rsp->data; |
| if (conn->handle != sys_le16_to_cpu(rp->handle)) { |
| err = -EIO; |
| } |
| |
| net_buf_unref(rsp); |
| |
| return err; |
| } |
| #endif /* CONFIG_BT_DF_CONNECTION_CTE_RSP */ |
| |
| /* @brief Function initializes Direction Finding in Host |
| * |
| * @return Zero in case of success, other value in case of failure. |
| */ |
| int le_df_init(void) |
| { |
| uint8_t max_switch_pattern_len; |
| uint8_t switch_sample_rates; |
| uint8_t max_cte_len; |
| uint8_t num_ant; |
| int err; |
| |
| err = hci_df_read_ant_info(&switch_sample_rates, &num_ant, |
| &max_switch_pattern_len, &max_cte_len); |
| if (err) { |
| return err; |
| } |
| |
| df_ant_info.max_switch_pattern_len = max_switch_pattern_len; |
| df_ant_info.switch_sample_rates = switch_sample_rates; |
| df_ant_info.max_cte_len = max_cte_len; |
| df_ant_info.num_ant = num_ant; |
| |
| LOG_DBG("DF initialized."); |
| return 0; |
| } |
| |
| int bt_df_set_adv_cte_tx_param(struct bt_le_ext_adv *adv, |
| const struct bt_df_adv_cte_tx_param *params) |
| { |
| __ASSERT_NO_MSG(adv); |
| __ASSERT_NO_MSG(params); |
| |
| int err; |
| |
| if (!BT_FEAT_LE_CONNECTIONLESS_CTE_TX(bt_dev.le.features)) { |
| return -ENOTSUP; |
| } |
| |
| /* Check if BT_ADV_PARAMS_SET is set, because it implies the set |
| * has already been created. |
| */ |
| if (!atomic_test_bit(adv->flags, BT_ADV_PARAMS_SET)) { |
| return -EINVAL; |
| } |
| |
| if (atomic_test_bit(adv->flags, BT_PER_ADV_CTE_ENABLED)) { |
| return -EINVAL; |
| } |
| |
| err = hci_df_set_cl_cte_tx_params(adv, params); |
| if (err) { |
| return err; |
| } |
| |
| atomic_set_bit(adv->flags, BT_PER_ADV_CTE_PARAMS_SET); |
| |
| return 0; |
| } |
| |
| static int bt_df_set_adv_cte_tx_enabled(struct bt_le_ext_adv *adv, bool enable) |
| { |
| if (!atomic_test_bit(adv->flags, BT_PER_ADV_PARAMS_SET)) { |
| return -EINVAL; |
| } |
| |
| if (!atomic_test_bit(adv->flags, BT_PER_ADV_CTE_PARAMS_SET)) { |
| return -EINVAL; |
| } |
| |
| if (enable == atomic_test_bit(adv->flags, BT_PER_ADV_CTE_ENABLED)) { |
| return -EALREADY; |
| } |
| |
| return hci_df_set_adv_cte_tx_enable(adv, enable); |
| } |
| |
| int bt_df_adv_cte_tx_enable(struct bt_le_ext_adv *adv) |
| { |
| __ASSERT_NO_MSG(adv); |
| return bt_df_set_adv_cte_tx_enabled(adv, true); |
| } |
| |
| int bt_df_adv_cte_tx_disable(struct bt_le_ext_adv *adv) |
| { |
| __ASSERT_NO_MSG(adv); |
| return bt_df_set_adv_cte_tx_enabled(adv, false); |
| } |
| |
| #if defined(CONFIG_BT_DF_CONNECTIONLESS_CTE_RX) |
| static int |
| bt_df_set_per_adv_sync_cte_rx_enable(struct bt_le_per_adv_sync *sync, bool enable, |
| const struct bt_df_per_adv_sync_cte_rx_param *params) |
| { |
| if (!BT_FEAT_LE_CONNECTIONLESS_CTE_RX(bt_dev.le.features)) { |
| return -ENOTSUP; |
| } |
| |
| if (!atomic_test_bit(sync->flags, BT_PER_ADV_SYNC_SYNCED)) { |
| return -EINVAL; |
| } |
| |
| if (!enable && |
| !atomic_test_bit(sync->flags, BT_PER_ADV_SYNC_CTE_ENABLED)) { |
| return -EALREADY; |
| } |
| |
| return hci_df_set_cl_cte_rx_enable(sync, enable, params); |
| } |
| |
| int bt_df_per_adv_sync_cte_rx_enable(struct bt_le_per_adv_sync *sync, |
| const struct bt_df_per_adv_sync_cte_rx_param *params) |
| { |
| CHECKIF(!sync) { |
| return -EINVAL; |
| } |
| CHECKIF(!params) { |
| return -EINVAL; |
| } |
| |
| return bt_df_set_per_adv_sync_cte_rx_enable(sync, true, params); |
| } |
| |
| int bt_df_per_adv_sync_cte_rx_disable(struct bt_le_per_adv_sync *sync) |
| { |
| CHECKIF(!sync) { |
| return -EINVAL; |
| } |
| |
| return bt_df_set_per_adv_sync_cte_rx_enable(sync, false, NULL); |
| } |
| #endif /* CONFIG_BT_DF_CONNECTIONLESS_CTE_RX */ |
| |
| #if defined(CONFIG_BT_DF_CONNECTION_CTE_RX) |
| static int bt_df_set_conn_cte_rx_enable(struct bt_conn *conn, bool enable, |
| const struct bt_df_conn_cte_rx_param *params) |
| { |
| if (!BT_FEAT_LE_RX_CTE(bt_dev.le.features)) { |
| LOG_WRN("Receiving Constant Tone Extensions is not supported"); |
| return -ENOTSUP; |
| } |
| |
| if (conn->state != BT_CONN_CONNECTED) { |
| LOG_ERR("not connected!"); |
| return -ENOTCONN; |
| } |
| |
| return hci_df_set_conn_cte_rx_enable(conn, enable, params); |
| } |
| |
| int bt_df_conn_cte_rx_enable(struct bt_conn *conn, const struct bt_df_conn_cte_rx_param *params) |
| { |
| CHECKIF(!conn) { |
| return -EINVAL; |
| } |
| |
| CHECKIF(!params) { |
| return -EINVAL; |
| } |
| |
| return bt_df_set_conn_cte_rx_enable(conn, true, params); |
| } |
| |
| int bt_df_conn_cte_rx_disable(struct bt_conn *conn) |
| { |
| CHECKIF(!conn) { |
| return -EINVAL; |
| } |
| |
| return bt_df_set_conn_cte_rx_enable(conn, false, NULL); |
| } |
| #endif /* CONFIG_BT_DF_CONNECTION_CTE_RX */ |
| |
| #if defined(CONFIG_BT_DF_CONNECTION_CTE_TX) |
| int bt_df_set_conn_cte_tx_param(struct bt_conn *conn, const struct bt_df_conn_cte_tx_param *params) |
| { |
| CHECKIF(!conn) { |
| return -EINVAL; |
| } |
| |
| CHECKIF(!params) { |
| return -EINVAL; |
| } |
| |
| if (conn->state != BT_CONN_CONNECTED) { |
| LOG_ERR("not connected!"); |
| return -ENOTCONN; |
| } |
| |
| if (atomic_test_bit(conn->flags, BT_CONN_CTE_RSP_ENABLED)) { |
| LOG_WRN("CTE response procedure is enabled"); |
| return -EINVAL; |
| } |
| |
| return hci_df_set_conn_cte_tx_param(conn, params); |
| } |
| |
| #endif /* CONFIG_BT_DF_CONNECTION_CTE_TX */ |
| |
| #if defined(CONFIG_BT_DF_CONNECTION_CTE_REQ) |
| static int bt_df_set_conn_cte_req_enable(struct bt_conn *conn, bool enable, |
| const struct bt_df_conn_cte_req_params *params) |
| { |
| if (!BT_FEAT_LE_CONNECTION_CTE_REQ(bt_dev.le.features)) { |
| LOG_WRN("Constant Tone Extensions request procedure is not supported"); |
| return -ENOTSUP; |
| } |
| |
| if (conn->state != BT_CONN_CONNECTED) { |
| LOG_ERR("not connected!"); |
| return -ENOTCONN; |
| } |
| |
| if (!atomic_test_bit(conn->flags, BT_CONN_CTE_RX_PARAMS_SET)) { |
| LOG_ERR("Can't start CTE request procedure before CTE RX params setup"); |
| return -EINVAL; |
| } |
| |
| return hci_df_set_conn_cte_req_enable(conn, enable, params); |
| } |
| |
| int bt_df_conn_cte_req_enable(struct bt_conn *conn, const struct bt_df_conn_cte_req_params *params) |
| { |
| CHECKIF(!conn) { |
| return -EINVAL; |
| } |
| |
| CHECKIF(!params) { |
| return -EINVAL; |
| } |
| |
| return bt_df_set_conn_cte_req_enable(conn, true, params); |
| } |
| |
| int bt_df_conn_cte_req_disable(struct bt_conn *conn) |
| { |
| CHECKIF(!conn) { |
| return -EINVAL; |
| } |
| |
| return bt_df_set_conn_cte_req_enable(conn, false, NULL); |
| } |
| #endif /* CONFIG_BT_DF_CONNECTION_CTE_REQ */ |
| |
| #if defined(CONFIG_BT_DF_CONNECTION_CTE_RSP) |
| static int bt_df_set_conn_cte_rsp_enable(struct bt_conn *conn, bool enable) |
| { |
| CHECKIF(!conn) { |
| return -EINVAL; |
| } |
| |
| if (!BT_FEAT_LE_CONNECTION_CTE_RESP(bt_dev.le.features)) { |
| LOG_WRN("CTE response procedure is not supported"); |
| return -ENOTSUP; |
| } |
| |
| if (conn->state != BT_CONN_CONNECTED) { |
| LOG_ERR("not connected"); |
| return -ENOTCONN; |
| } |
| |
| if (!atomic_test_bit(conn->flags, BT_CONN_CTE_TX_PARAMS_SET)) { |
| LOG_ERR("Can't start CTE response procedure before CTE TX params setup"); |
| return -EINVAL; |
| } |
| |
| return hci_df_set_conn_cte_rsp_enable(conn, enable); |
| } |
| |
| int bt_df_conn_cte_rsp_enable(struct bt_conn *conn) |
| { |
| return bt_df_set_conn_cte_rsp_enable(conn, true); |
| } |
| |
| int bt_df_conn_cte_rsp_disable(struct bt_conn *conn) |
| { |
| return bt_df_set_conn_cte_rsp_enable(conn, false); |
| } |
| #endif /* CONFIG_BT_DF_CONNECTION_CTE_RSP */ |