1651 lines
38 KiB
C
1651 lines
38 KiB
C
/*
|
|
* Copyright (c) 2017-2021 Nordic Semiconductor ASA
|
|
* Copyright (c) 2015-2016 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/byteorder.h>
|
|
|
|
#include <bluetooth/bluetooth.h>
|
|
#include <bluetooth/hci.h>
|
|
#include <bluetooth/buf.h>
|
|
|
|
#include "hci_core.h"
|
|
#include "conn_internal.h"
|
|
#include "id.h"
|
|
#include "scan.h"
|
|
|
|
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_CORE)
|
|
#define LOG_MODULE_NAME bt_adv
|
|
#include "common/log.h"
|
|
|
|
|
|
#if defined(CONFIG_BT_EXT_ADV)
|
|
static struct bt_le_ext_adv adv_pool[CONFIG_BT_EXT_ADV_MAX_ADV_SET];
|
|
#endif /* defined(CONFIG_BT_EXT_ADV) */
|
|
|
|
|
|
#if defined(CONFIG_BT_EXT_ADV)
|
|
uint8_t bt_le_ext_adv_get_index(struct bt_le_ext_adv *adv)
|
|
{
|
|
ptrdiff_t index = adv - adv_pool;
|
|
|
|
__ASSERT(index >= 0 && index < ARRAY_SIZE(adv_pool),
|
|
"Invalid bt_adv pointer");
|
|
return (uint8_t)index;
|
|
}
|
|
|
|
static struct bt_le_ext_adv *adv_new(void)
|
|
{
|
|
struct bt_le_ext_adv *adv = NULL;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(adv_pool); i++) {
|
|
if (!atomic_test_bit(adv_pool[i].flags, BT_ADV_CREATED)) {
|
|
adv = &adv_pool[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!adv) {
|
|
return NULL;
|
|
}
|
|
|
|
(void)memset(adv, 0, sizeof(*adv));
|
|
atomic_set_bit(adv_pool[i].flags, BT_ADV_CREATED);
|
|
adv->handle = i;
|
|
|
|
return adv;
|
|
}
|
|
|
|
static void adv_delete(struct bt_le_ext_adv *adv)
|
|
{
|
|
atomic_clear_bit(adv->flags, BT_ADV_CREATED);
|
|
}
|
|
|
|
#if defined(CONFIG_BT_BROADCASTER)
|
|
static struct bt_le_ext_adv *bt_adv_lookup_handle(uint8_t handle)
|
|
{
|
|
if (handle < ARRAY_SIZE(adv_pool) &&
|
|
atomic_test_bit(adv_pool[handle].flags, BT_ADV_CREATED)) {
|
|
return &adv_pool[handle];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
#endif /* CONFIG_BT_BROADCASTER */
|
|
#endif /* defined(CONFIG_BT_EXT_ADV) */
|
|
|
|
|
|
static void adv_id_check_connectable_func(struct bt_le_ext_adv *adv, void *data)
|
|
{
|
|
struct bt_adv_id_check_data *check_data = data;
|
|
|
|
if (atomic_test_bit(adv->flags, BT_ADV_ENABLED) &&
|
|
atomic_test_bit(adv->flags, BT_ADV_CONNECTABLE) &&
|
|
check_data->id != adv->id) {
|
|
check_data->adv_enabled = true;
|
|
}
|
|
}
|
|
|
|
void bt_le_ext_adv_foreach(void (*func)(struct bt_le_ext_adv *adv, void *data),
|
|
void *data)
|
|
{
|
|
#if defined(CONFIG_BT_EXT_ADV)
|
|
for (size_t i = 0; i < ARRAY_SIZE(adv_pool); i++) {
|
|
if (atomic_test_bit(adv_pool[i].flags, BT_ADV_CREATED)) {
|
|
func(&adv_pool[i], data);
|
|
}
|
|
}
|
|
#else
|
|
func(&bt_dev.adv, data);
|
|
#endif /* defined(CONFIG_BT_EXT_ADV) */
|
|
}
|
|
|
|
static struct bt_le_ext_adv *adv_new_legacy(void)
|
|
{
|
|
#if defined(CONFIG_BT_EXT_ADV)
|
|
if (bt_dev.adv) {
|
|
return NULL;
|
|
}
|
|
|
|
bt_dev.adv = adv_new();
|
|
return bt_dev.adv;
|
|
#else
|
|
return &bt_dev.adv;
|
|
#endif
|
|
}
|
|
|
|
void bt_le_adv_delete_legacy(void)
|
|
{
|
|
#if defined(CONFIG_BT_EXT_ADV)
|
|
if (bt_dev.adv) {
|
|
atomic_clear_bit(bt_dev.adv->flags, BT_ADV_CREATED);
|
|
bt_dev.adv = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
struct bt_le_ext_adv *bt_le_adv_lookup_legacy(void)
|
|
{
|
|
#if defined(CONFIG_BT_EXT_ADV)
|
|
return bt_dev.adv;
|
|
#else
|
|
return &bt_dev.adv;
|
|
#endif
|
|
}
|
|
|
|
int bt_le_adv_set_enable_legacy(struct bt_le_ext_adv *adv, bool enable)
|
|
{
|
|
struct net_buf *buf;
|
|
struct bt_hci_cmd_state_set state;
|
|
int err;
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_ADV_ENABLE, 1);
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
if (enable) {
|
|
net_buf_add_u8(buf, BT_HCI_LE_ADV_ENABLE);
|
|
} else {
|
|
net_buf_add_u8(buf, BT_HCI_LE_ADV_DISABLE);
|
|
}
|
|
|
|
bt_hci_cmd_state_set_init(buf, &state, adv->flags, BT_ADV_ENABLED, enable);
|
|
|
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_ADV_ENABLE, buf, NULL);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_le_adv_set_enable_ext(struct bt_le_ext_adv *adv,
|
|
bool enable,
|
|
const struct bt_le_ext_adv_start_param *param)
|
|
{
|
|
struct net_buf *buf;
|
|
struct bt_hci_cmd_state_set state;
|
|
int err;
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_EXT_ADV_ENABLE, 6);
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
if (enable) {
|
|
net_buf_add_u8(buf, BT_HCI_LE_ADV_ENABLE);
|
|
} else {
|
|
net_buf_add_u8(buf, BT_HCI_LE_ADV_DISABLE);
|
|
}
|
|
|
|
net_buf_add_u8(buf, 1);
|
|
|
|
net_buf_add_u8(buf, adv->handle);
|
|
net_buf_add_le16(buf, param ? sys_cpu_to_le16(param->timeout) : 0);
|
|
net_buf_add_u8(buf, param ? param->num_events : 0);
|
|
|
|
bt_hci_cmd_state_set_init(buf, &state, adv->flags, BT_ADV_ENABLED, enable);
|
|
|
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_EXT_ADV_ENABLE, buf, NULL);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_le_adv_set_enable(struct bt_le_ext_adv *adv, bool enable)
|
|
{
|
|
if (IS_ENABLED(CONFIG_BT_EXT_ADV) &&
|
|
BT_FEAT_LE_EXT_ADV(bt_dev.le.features)) {
|
|
return bt_le_adv_set_enable_ext(adv, enable, NULL);
|
|
}
|
|
|
|
return bt_le_adv_set_enable_legacy(adv, enable);
|
|
}
|
|
|
|
static bool valid_adv_ext_param(const struct bt_le_adv_param *param)
|
|
{
|
|
if (IS_ENABLED(CONFIG_BT_EXT_ADV) &&
|
|
BT_FEAT_LE_EXT_ADV(bt_dev.le.features)) {
|
|
if (param->peer &&
|
|
!(param->options & BT_LE_ADV_OPT_EXT_ADV) &&
|
|
!(param->options & BT_LE_ADV_OPT_CONNECTABLE)) {
|
|
/* Cannot do directed non-connectable advertising
|
|
* without extended advertising.
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
if (param->peer &&
|
|
(param->options & BT_LE_ADV_OPT_EXT_ADV) &&
|
|
!(param->options & BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY)) {
|
|
/* High duty cycle directed connectable advertising
|
|
* shall not be used with Extended Advertising.
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
if (!(param->options & BT_LE_ADV_OPT_EXT_ADV) &&
|
|
param->options & (BT_LE_ADV_OPT_EXT_ADV |
|
|
BT_LE_ADV_OPT_NO_2M |
|
|
BT_LE_ADV_OPT_CODED |
|
|
BT_LE_ADV_OPT_ANONYMOUS |
|
|
BT_LE_ADV_OPT_USE_TX_POWER)) {
|
|
/* Extended options require extended advertising. */
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_PRIVACY) &&
|
|
param->peer &&
|
|
(param->options & BT_LE_ADV_OPT_USE_IDENTITY) &&
|
|
(param->options & BT_LE_ADV_OPT_DIR_ADDR_RPA)) {
|
|
/* own addr type used for both RPAs in directed advertising. */
|
|
return false;
|
|
}
|
|
|
|
if (param->id >= bt_dev.id_count ||
|
|
!bt_addr_le_cmp(&bt_dev.id_addr[param->id], BT_ADDR_LE_ANY)) {
|
|
return false;
|
|
}
|
|
|
|
if (!(param->options & BT_LE_ADV_OPT_CONNECTABLE)) {
|
|
/*
|
|
* BT Core 4.2 [Vol 2, Part E, 7.8.5]
|
|
* The Advertising_Interval_Min and Advertising_Interval_Max
|
|
* shall not be set to less than 0x00A0 (100 ms) if the
|
|
* Advertising_Type is set to ADV_SCAN_IND or ADV_NONCONN_IND.
|
|
*/
|
|
if (bt_dev.hci_version < BT_HCI_VERSION_5_0 &&
|
|
param->interval_min < 0x00a0) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ((param->options & (BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY |
|
|
BT_LE_ADV_OPT_DIR_ADDR_RPA)) &&
|
|
!param->peer) {
|
|
return false;
|
|
}
|
|
|
|
if ((param->options & BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY) ||
|
|
!param->peer) {
|
|
if (param->interval_min > param->interval_max ||
|
|
param->interval_min < 0x0020 ||
|
|
param->interval_max > 0x4000) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ((param->options & BT_LE_ADV_OPT_DISABLE_CHAN_37) &&
|
|
(param->options & BT_LE_ADV_OPT_DISABLE_CHAN_38) &&
|
|
(param->options & BT_LE_ADV_OPT_DISABLE_CHAN_39)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool valid_adv_param(const struct bt_le_adv_param *param)
|
|
{
|
|
if (param->options & BT_LE_ADV_OPT_EXT_ADV) {
|
|
return false;
|
|
}
|
|
|
|
if (param->peer && !(param->options & BT_LE_ADV_OPT_CONNECTABLE)) {
|
|
return false;
|
|
}
|
|
|
|
return valid_adv_ext_param(param);
|
|
}
|
|
|
|
|
|
struct bt_ad {
|
|
const struct bt_data *data;
|
|
size_t len;
|
|
};
|
|
|
|
static int set_data_add(uint8_t *set_data, uint8_t set_data_len_max,
|
|
const struct bt_ad *ad, size_t ad_len, uint8_t *data_len)
|
|
{
|
|
uint8_t set_data_len = 0;
|
|
|
|
for (size_t i = 0; i < ad_len; i++) {
|
|
const struct bt_data *data = ad[i].data;
|
|
|
|
for (size_t j = 0; j < ad[i].len; j++) {
|
|
size_t len = data[j].data_len;
|
|
uint8_t type = data[j].type;
|
|
|
|
/* Check if ad fit in the remaining buffer */
|
|
if ((set_data_len + len + 2) > set_data_len_max) {
|
|
ssize_t shortened_len = set_data_len_max -
|
|
(set_data_len + 2);
|
|
|
|
if (!(type == BT_DATA_NAME_COMPLETE &&
|
|
shortened_len > 0)) {
|
|
BT_ERR("Too big advertising data");
|
|
return -EINVAL;
|
|
}
|
|
|
|
type = BT_DATA_NAME_SHORTENED;
|
|
len = shortened_len;
|
|
}
|
|
|
|
set_data[set_data_len++] = len + 1;
|
|
set_data[set_data_len++] = type;
|
|
|
|
memcpy(&set_data[set_data_len], data[j].data, len);
|
|
set_data_len += len;
|
|
}
|
|
}
|
|
|
|
*data_len = set_data_len;
|
|
return 0;
|
|
}
|
|
|
|
static int hci_set_ad(uint16_t hci_op, const struct bt_ad *ad, size_t ad_len)
|
|
{
|
|
struct bt_hci_cp_le_set_adv_data *set_data;
|
|
struct net_buf *buf;
|
|
int err;
|
|
|
|
buf = bt_hci_cmd_create(hci_op, sizeof(*set_data));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
set_data = net_buf_add(buf, sizeof(*set_data));
|
|
(void)memset(set_data, 0, sizeof(*set_data));
|
|
|
|
err = set_data_add(set_data->data, BT_GAP_ADV_MAX_ADV_DATA_LEN,
|
|
ad, ad_len, &set_data->len);
|
|
if (err) {
|
|
net_buf_unref(buf);
|
|
return err;
|
|
}
|
|
|
|
return bt_hci_cmd_send_sync(hci_op, buf, NULL);
|
|
}
|
|
|
|
/* Set legacy data using Extended Advertising HCI commands */
|
|
static int hci_set_ad_ext(struct bt_le_ext_adv *adv, uint16_t hci_op,
|
|
const struct bt_ad *ad, size_t ad_len)
|
|
{
|
|
struct bt_hci_cp_le_set_ext_adv_data *set_data;
|
|
struct net_buf *buf;
|
|
int err;
|
|
|
|
buf = bt_hci_cmd_create(hci_op, sizeof(*set_data));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
set_data = net_buf_add(buf, sizeof(*set_data));
|
|
(void)memset(set_data, 0, sizeof(*set_data));
|
|
|
|
err = set_data_add(set_data->data, BT_HCI_LE_EXT_ADV_FRAG_MAX_LEN,
|
|
ad, ad_len, &set_data->len);
|
|
if (err) {
|
|
net_buf_unref(buf);
|
|
return err;
|
|
}
|
|
|
|
set_data->handle = adv->handle;
|
|
set_data->op = BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA;
|
|
set_data->frag_pref = BT_HCI_LE_EXT_ADV_FRAG_DISABLED;
|
|
|
|
return bt_hci_cmd_send_sync(hci_op, buf, NULL);
|
|
}
|
|
|
|
static int set_ad(struct bt_le_ext_adv *adv, const struct bt_ad *ad,
|
|
size_t ad_len)
|
|
{
|
|
if (IS_ENABLED(CONFIG_BT_EXT_ADV) &&
|
|
BT_FEAT_LE_EXT_ADV(bt_dev.le.features)) {
|
|
return hci_set_ad_ext(adv, BT_HCI_OP_LE_SET_EXT_ADV_DATA,
|
|
ad, ad_len);
|
|
}
|
|
|
|
return hci_set_ad(BT_HCI_OP_LE_SET_ADV_DATA, ad, ad_len);
|
|
}
|
|
|
|
static int set_sd(struct bt_le_ext_adv *adv, const struct bt_ad *sd,
|
|
size_t sd_len)
|
|
{
|
|
if (IS_ENABLED(CONFIG_BT_EXT_ADV) &&
|
|
BT_FEAT_LE_EXT_ADV(bt_dev.le.features)) {
|
|
return hci_set_ad_ext(adv, BT_HCI_OP_LE_SET_EXT_SCAN_RSP_DATA,
|
|
sd, sd_len);
|
|
}
|
|
|
|
return hci_set_ad(BT_HCI_OP_LE_SET_SCAN_RSP_DATA, sd, sd_len);
|
|
}
|
|
|
|
static inline bool ad_has_name(const struct bt_data *ad, size_t ad_len)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < ad_len; i++) {
|
|
if (ad[i].type == BT_DATA_NAME_COMPLETE ||
|
|
ad[i].type == BT_DATA_NAME_SHORTENED) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int le_adv_update(struct bt_le_ext_adv *adv,
|
|
const struct bt_data *ad, size_t ad_len,
|
|
const struct bt_data *sd, size_t sd_len,
|
|
bool ext_adv, bool scannable, bool use_name)
|
|
{
|
|
struct bt_ad d[2] = {};
|
|
struct bt_data data;
|
|
size_t d_len;
|
|
int err;
|
|
|
|
if (use_name) {
|
|
const char *name = bt_get_name();
|
|
|
|
if ((ad && ad_has_name(ad, ad_len)) ||
|
|
(sd && ad_has_name(sd, sd_len))) {
|
|
/* Cannot use name if name is already set */
|
|
return -EINVAL;
|
|
}
|
|
|
|
data = (struct bt_data)BT_DATA(
|
|
BT_DATA_NAME_COMPLETE,
|
|
name, strlen(name));
|
|
}
|
|
|
|
if (!(ext_adv && scannable)) {
|
|
d_len = 1;
|
|
d[0].data = ad;
|
|
d[0].len = ad_len;
|
|
|
|
if (use_name && !scannable) {
|
|
d[1].data = &data;
|
|
d[1].len = 1;
|
|
d_len = 2;
|
|
}
|
|
|
|
err = set_ad(adv, d, d_len);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
if (scannable) {
|
|
d_len = 1;
|
|
d[0].data = sd;
|
|
d[0].len = sd_len;
|
|
|
|
if (use_name) {
|
|
d[1].data = &data;
|
|
d[1].len = 1;
|
|
d_len = 2;
|
|
}
|
|
|
|
err = set_sd(adv, d, d_len);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
atomic_set_bit(adv->flags, BT_ADV_DATA_SET);
|
|
return 0;
|
|
}
|
|
|
|
int bt_le_adv_update_data(const struct bt_data *ad, size_t ad_len,
|
|
const struct bt_data *sd, size_t sd_len)
|
|
{
|
|
struct bt_le_ext_adv *adv = bt_le_adv_lookup_legacy();
|
|
bool scannable, use_name;
|
|
|
|
if (!adv) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!atomic_test_bit(adv->flags, BT_ADV_ENABLED)) {
|
|
return -EAGAIN;
|
|
}
|
|
|
|
scannable = atomic_test_bit(adv->flags, BT_ADV_SCANNABLE);
|
|
use_name = atomic_test_bit(adv->flags, BT_ADV_INCLUDE_NAME);
|
|
|
|
return le_adv_update(adv, ad, ad_len, sd, sd_len, false, scannable,
|
|
use_name);
|
|
}
|
|
|
|
static uint8_t get_filter_policy(uint32_t options)
|
|
{
|
|
if (!IS_ENABLED(CONFIG_BT_WHITELIST)) {
|
|
return BT_LE_ADV_FP_NO_WHITELIST;
|
|
} else if ((options & BT_LE_ADV_OPT_FILTER_SCAN_REQ) &&
|
|
(options & BT_LE_ADV_OPT_FILTER_CONN)) {
|
|
return BT_LE_ADV_FP_WHITELIST_BOTH;
|
|
} else if (options & BT_LE_ADV_OPT_FILTER_SCAN_REQ) {
|
|
return BT_LE_ADV_FP_WHITELIST_SCAN_REQ;
|
|
} else if (options & BT_LE_ADV_OPT_FILTER_CONN) {
|
|
return BT_LE_ADV_FP_WHITELIST_CONN_IND;
|
|
} else {
|
|
return BT_LE_ADV_FP_NO_WHITELIST;
|
|
}
|
|
}
|
|
|
|
static uint8_t get_adv_channel_map(uint32_t options)
|
|
{
|
|
uint8_t channel_map = 0x07;
|
|
|
|
if (options & BT_LE_ADV_OPT_DISABLE_CHAN_37) {
|
|
channel_map &= ~0x01;
|
|
}
|
|
|
|
if (options & BT_LE_ADV_OPT_DISABLE_CHAN_38) {
|
|
channel_map &= ~0x02;
|
|
}
|
|
|
|
if (options & BT_LE_ADV_OPT_DISABLE_CHAN_39) {
|
|
channel_map &= ~0x04;
|
|
}
|
|
|
|
return channel_map;
|
|
}
|
|
|
|
static int le_adv_start_add_conn(const struct bt_le_ext_adv *adv,
|
|
struct bt_conn **out_conn)
|
|
{
|
|
struct bt_adv_id_check_data check_data = {
|
|
.id = adv->id,
|
|
.adv_enabled = false
|
|
};
|
|
struct bt_conn *conn;
|
|
|
|
bt_le_ext_adv_foreach(adv_id_check_connectable_func, &check_data);
|
|
if (check_data.adv_enabled) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
bt_dev.adv_conn_id = adv->id;
|
|
|
|
if (!bt_addr_le_cmp(&adv->target_addr, BT_ADDR_LE_ANY)) {
|
|
/* Undirected advertising */
|
|
conn = bt_conn_add_le(adv->id, BT_ADDR_LE_NONE);
|
|
if (!conn) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
bt_conn_set_state(conn, BT_CONN_CONNECT_ADV);
|
|
*out_conn = conn;
|
|
return 0;
|
|
}
|
|
|
|
if (bt_conn_exists_le(adv->id, &adv->target_addr)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
conn = bt_conn_add_le(adv->id, &adv->target_addr);
|
|
if (!conn) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
bt_conn_set_state(conn, BT_CONN_CONNECT_DIR_ADV);
|
|
*out_conn = conn;
|
|
return 0;
|
|
}
|
|
|
|
static void le_adv_stop_free_conn(const struct bt_le_ext_adv *adv, uint8_t status)
|
|
{
|
|
struct bt_conn *conn;
|
|
|
|
if (!bt_addr_le_cmp(&adv->target_addr, BT_ADDR_LE_ANY)) {
|
|
conn = bt_conn_lookup_state_le(adv->id, BT_ADDR_LE_NONE,
|
|
BT_CONN_CONNECT_ADV);
|
|
} else {
|
|
conn = bt_conn_lookup_state_le(adv->id, &adv->target_addr,
|
|
BT_CONN_CONNECT_DIR_ADV);
|
|
}
|
|
|
|
if (conn) {
|
|
conn->err = status;
|
|
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
|
|
bt_conn_unref(conn);
|
|
}
|
|
}
|
|
|
|
int bt_le_adv_start_legacy(struct bt_le_ext_adv *adv,
|
|
const struct bt_le_adv_param *param,
|
|
const struct bt_data *ad, size_t ad_len,
|
|
const struct bt_data *sd, size_t sd_len)
|
|
{
|
|
struct bt_hci_cp_le_set_adv_param set_param;
|
|
struct bt_conn *conn = NULL;
|
|
struct net_buf *buf;
|
|
bool dir_adv = (param->peer != NULL), scannable;
|
|
int err;
|
|
|
|
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
|
|
return -EAGAIN;
|
|
}
|
|
|
|
if (!valid_adv_param(param)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!bt_id_adv_random_addr_check(param)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (atomic_test_bit(adv->flags, BT_ADV_ENABLED)) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
(void)memset(&set_param, 0, sizeof(set_param));
|
|
|
|
set_param.min_interval = sys_cpu_to_le16(param->interval_min);
|
|
set_param.max_interval = sys_cpu_to_le16(param->interval_max);
|
|
set_param.channel_map = get_adv_channel_map(param->options);
|
|
set_param.filter_policy = get_filter_policy(param->options);
|
|
|
|
if (adv->id != param->id) {
|
|
atomic_clear_bit(bt_dev.flags, BT_DEV_RPA_VALID);
|
|
}
|
|
|
|
adv->id = param->id;
|
|
bt_dev.adv_conn_id = adv->id;
|
|
|
|
err = bt_id_set_adv_own_addr(adv, param->options, dir_adv,
|
|
&set_param.own_addr_type);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
if (dir_adv) {
|
|
bt_addr_le_copy(&adv->target_addr, param->peer);
|
|
} else {
|
|
bt_addr_le_copy(&adv->target_addr, BT_ADDR_LE_ANY);
|
|
}
|
|
|
|
if (param->options & BT_LE_ADV_OPT_CONNECTABLE) {
|
|
scannable = true;
|
|
|
|
if (dir_adv) {
|
|
if (param->options & BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY) {
|
|
set_param.type = BT_HCI_ADV_DIRECT_IND_LOW_DUTY;
|
|
} else {
|
|
set_param.type = BT_HCI_ADV_DIRECT_IND;
|
|
}
|
|
|
|
bt_addr_le_copy(&set_param.direct_addr, param->peer);
|
|
} else {
|
|
set_param.type = BT_HCI_ADV_IND;
|
|
}
|
|
} else {
|
|
scannable = sd || (param->options & BT_LE_ADV_OPT_USE_NAME);
|
|
|
|
set_param.type = scannable ? BT_HCI_ADV_SCAN_IND :
|
|
BT_HCI_ADV_NONCONN_IND;
|
|
}
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_ADV_PARAM, sizeof(set_param));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
net_buf_add_mem(buf, &set_param, sizeof(set_param));
|
|
|
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_ADV_PARAM, buf, NULL);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
if (!dir_adv) {
|
|
err = le_adv_update(adv, ad, ad_len, sd, sd_len, false,
|
|
scannable,
|
|
param->options & BT_LE_ADV_OPT_USE_NAME);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_PERIPHERAL) &&
|
|
(param->options & BT_LE_ADV_OPT_CONNECTABLE)) {
|
|
err = le_adv_start_add_conn(adv, &conn);
|
|
if (err) {
|
|
if (err == -ENOMEM && !dir_adv &&
|
|
!(param->options & BT_LE_ADV_OPT_ONE_TIME)) {
|
|
goto set_adv_state;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
}
|
|
|
|
err = bt_le_adv_set_enable(adv, true);
|
|
if (err) {
|
|
BT_ERR("Failed to start advertiser");
|
|
if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn) {
|
|
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
|
|
bt_conn_unref(conn);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn) {
|
|
/* If undirected connectable advertiser we have created a
|
|
* connection object that we don't yet give to the application.
|
|
* Since we don't give the application a reference to manage in
|
|
* this case, we need to release this reference here
|
|
*/
|
|
bt_conn_unref(conn);
|
|
}
|
|
|
|
set_adv_state:
|
|
atomic_set_bit_to(adv->flags, BT_ADV_PERSIST, !dir_adv &&
|
|
!(param->options & BT_LE_ADV_OPT_ONE_TIME));
|
|
|
|
atomic_set_bit_to(adv->flags, BT_ADV_INCLUDE_NAME,
|
|
param->options & BT_LE_ADV_OPT_USE_NAME);
|
|
|
|
atomic_set_bit_to(adv->flags, BT_ADV_CONNECTABLE,
|
|
param->options & BT_LE_ADV_OPT_CONNECTABLE);
|
|
|
|
atomic_set_bit_to(adv->flags, BT_ADV_SCANNABLE, scannable);
|
|
|
|
atomic_set_bit_to(adv->flags, BT_ADV_USE_IDENTITY,
|
|
param->options & BT_LE_ADV_OPT_USE_IDENTITY);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int le_ext_adv_param_set(struct bt_le_ext_adv *adv,
|
|
const struct bt_le_adv_param *param,
|
|
bool has_scan_data)
|
|
{
|
|
struct bt_hci_cp_le_set_ext_adv_param *cp;
|
|
bool dir_adv = param->peer != NULL, scannable;
|
|
struct net_buf *buf, *rsp;
|
|
int err;
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_EXT_ADV_PARAM, sizeof(*cp));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
(void)memset(cp, 0, sizeof(*cp));
|
|
|
|
err = bt_id_set_adv_own_addr(adv, param->options, dir_adv,
|
|
&cp->own_addr_type);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
if (dir_adv) {
|
|
bt_addr_le_copy(&adv->target_addr, param->peer);
|
|
} else {
|
|
bt_addr_le_copy(&adv->target_addr, BT_ADDR_LE_ANY);
|
|
}
|
|
|
|
cp->handle = adv->handle;
|
|
sys_put_le24(param->interval_min, cp->prim_min_interval);
|
|
sys_put_le24(param->interval_max, cp->prim_max_interval);
|
|
cp->prim_channel_map = get_adv_channel_map(param->options);
|
|
cp->filter_policy = get_filter_policy(param->options);
|
|
cp->tx_power = BT_HCI_LE_ADV_TX_POWER_NO_PREF;
|
|
|
|
cp->prim_adv_phy = BT_HCI_LE_PHY_1M;
|
|
if (param->options & BT_LE_ADV_OPT_EXT_ADV) {
|
|
if (param->options & BT_LE_ADV_OPT_NO_2M) {
|
|
cp->sec_adv_phy = BT_HCI_LE_PHY_1M;
|
|
} else {
|
|
cp->sec_adv_phy = BT_HCI_LE_PHY_2M;
|
|
}
|
|
}
|
|
|
|
if (param->options & BT_LE_ADV_OPT_CODED) {
|
|
cp->prim_adv_phy = BT_HCI_LE_PHY_CODED;
|
|
cp->sec_adv_phy = BT_HCI_LE_PHY_CODED;
|
|
}
|
|
|
|
if (!(param->options & BT_LE_ADV_OPT_EXT_ADV)) {
|
|
cp->props |= BT_HCI_LE_ADV_PROP_LEGACY;
|
|
}
|
|
|
|
if (param->options & BT_LE_ADV_OPT_USE_TX_POWER) {
|
|
cp->props |= BT_HCI_LE_ADV_PROP_TX_POWER;
|
|
}
|
|
|
|
if (param->options & BT_LE_ADV_OPT_ANONYMOUS) {
|
|
cp->props |= BT_HCI_LE_ADV_PROP_ANON;
|
|
}
|
|
|
|
if (param->options & BT_LE_ADV_OPT_NOTIFY_SCAN_REQ) {
|
|
cp->scan_req_notify_enable = BT_HCI_LE_ADV_SCAN_REQ_ENABLE;
|
|
}
|
|
|
|
if (param->options & BT_LE_ADV_OPT_CONNECTABLE) {
|
|
cp->props |= BT_HCI_LE_ADV_PROP_CONN;
|
|
if (!dir_adv && !(param->options & BT_LE_ADV_OPT_EXT_ADV)) {
|
|
/* When using non-extended adv packets then undirected
|
|
* advertising has to be scannable as well.
|
|
* We didn't require this option to be set before, so
|
|
* it is implicitly set instead in this case.
|
|
*/
|
|
cp->props |= BT_HCI_LE_ADV_PROP_SCAN;
|
|
}
|
|
}
|
|
|
|
if ((param->options & BT_LE_ADV_OPT_SCANNABLE) || has_scan_data) {
|
|
cp->props |= BT_HCI_LE_ADV_PROP_SCAN;
|
|
}
|
|
|
|
scannable = !!(cp->props & BT_HCI_LE_ADV_PROP_SCAN);
|
|
|
|
if (dir_adv) {
|
|
cp->props |= BT_HCI_LE_ADV_PROP_DIRECT;
|
|
if (!(param->options & BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY)) {
|
|
cp->props |= BT_HCI_LE_ADV_PROP_HI_DC_CONN;
|
|
}
|
|
|
|
bt_addr_le_copy(&cp->peer_addr, param->peer);
|
|
}
|
|
|
|
cp->sid = param->sid;
|
|
|
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_EXT_ADV_PARAM, buf, &rsp);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_EXT_ADV)
|
|
struct bt_hci_rp_le_set_ext_adv_param *rp = (void *)rsp->data;
|
|
|
|
adv->tx_power = rp->tx_power;
|
|
#endif /* defined(CONFIG_BT_EXT_ADV) */
|
|
|
|
net_buf_unref(rsp);
|
|
|
|
atomic_set_bit(adv->flags, BT_ADV_PARAMS_SET);
|
|
|
|
if (atomic_test_and_clear_bit(adv->flags, BT_ADV_RANDOM_ADDR_PENDING)) {
|
|
err = bt_id_set_adv_random_addr(adv, &adv->random_addr.a);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
/* Flag only used by bt_le_adv_start API. */
|
|
atomic_set_bit_to(adv->flags, BT_ADV_PERSIST, false);
|
|
|
|
atomic_set_bit_to(adv->flags, BT_ADV_INCLUDE_NAME,
|
|
param->options & BT_LE_ADV_OPT_USE_NAME);
|
|
|
|
atomic_set_bit_to(adv->flags, BT_ADV_CONNECTABLE,
|
|
param->options & BT_LE_ADV_OPT_CONNECTABLE);
|
|
|
|
atomic_set_bit_to(adv->flags, BT_ADV_SCANNABLE, scannable);
|
|
|
|
atomic_set_bit_to(adv->flags, BT_ADV_USE_IDENTITY,
|
|
param->options & BT_LE_ADV_OPT_USE_IDENTITY);
|
|
|
|
atomic_set_bit_to(adv->flags, BT_ADV_EXT_ADV,
|
|
param->options & BT_LE_ADV_OPT_EXT_ADV);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_le_adv_start_ext(struct bt_le_ext_adv *adv,
|
|
const struct bt_le_adv_param *param,
|
|
const struct bt_data *ad, size_t ad_len,
|
|
const struct bt_data *sd, size_t sd_len)
|
|
{
|
|
struct bt_le_ext_adv_start_param start_param = {
|
|
.timeout = 0,
|
|
.num_events = 0,
|
|
};
|
|
bool dir_adv = (param->peer != NULL);
|
|
struct bt_conn *conn = NULL;
|
|
int err;
|
|
|
|
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
|
|
return -EAGAIN;
|
|
}
|
|
|
|
if (!valid_adv_param(param)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (atomic_test_bit(adv->flags, BT_ADV_ENABLED)) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
adv->id = param->id;
|
|
err = le_ext_adv_param_set(adv, param, sd ||
|
|
(param->options & BT_LE_ADV_OPT_USE_NAME));
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
if (!dir_adv) {
|
|
err = bt_le_ext_adv_set_data(adv, ad, ad_len, sd, sd_len);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
} else {
|
|
if (!(param->options & BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY)) {
|
|
start_param.timeout =
|
|
BT_GAP_ADV_HIGH_DUTY_CYCLE_MAX_TIMEOUT;
|
|
atomic_set_bit(adv->flags, BT_ADV_LIMITED);
|
|
}
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_PERIPHERAL) &&
|
|
(param->options & BT_LE_ADV_OPT_CONNECTABLE)) {
|
|
err = le_adv_start_add_conn(adv, &conn);
|
|
if (err) {
|
|
if (err == -ENOMEM && !dir_adv &&
|
|
!(param->options & BT_LE_ADV_OPT_ONE_TIME)) {
|
|
goto set_adv_state;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
}
|
|
|
|
err = bt_le_adv_set_enable_ext(adv, true, &start_param);
|
|
if (err) {
|
|
BT_ERR("Failed to start advertiser");
|
|
if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn) {
|
|
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
|
|
bt_conn_unref(conn);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn) {
|
|
/* If undirected connectable advertiser we have created a
|
|
* connection object that we don't yet give to the application.
|
|
* Since we don't give the application a reference to manage in
|
|
* this case, we need to release this reference here
|
|
*/
|
|
bt_conn_unref(conn);
|
|
}
|
|
|
|
set_adv_state:
|
|
/* Flag always set to false by le_ext_adv_param_set */
|
|
atomic_set_bit_to(adv->flags, BT_ADV_PERSIST, !dir_adv &&
|
|
!(param->options & BT_LE_ADV_OPT_ONE_TIME));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_le_adv_start(const struct bt_le_adv_param *param,
|
|
const struct bt_data *ad, size_t ad_len,
|
|
const struct bt_data *sd, size_t sd_len)
|
|
{
|
|
struct bt_le_ext_adv *adv = adv_new_legacy();
|
|
int err;
|
|
|
|
if (!adv) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_EXT_ADV) &&
|
|
BT_FEAT_LE_EXT_ADV(bt_dev.le.features)) {
|
|
err = bt_le_adv_start_ext(adv, param, ad, ad_len, sd, sd_len);
|
|
} else {
|
|
err = bt_le_adv_start_legacy(adv, param, ad, ad_len, sd, sd_len);
|
|
}
|
|
|
|
if (err) {
|
|
bt_le_adv_delete_legacy();
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int bt_le_adv_stop(void)
|
|
{
|
|
struct bt_le_ext_adv *adv = bt_le_adv_lookup_legacy();
|
|
int err;
|
|
|
|
if (!adv) {
|
|
BT_ERR("No valid legacy adv");
|
|
return 0;
|
|
}
|
|
|
|
/* Make sure advertising is not re-enabled later even if it's not
|
|
* currently enabled (i.e. BT_DEV_ADVERTISING is not set).
|
|
*/
|
|
atomic_clear_bit(adv->flags, BT_ADV_PERSIST);
|
|
|
|
if (!atomic_test_bit(adv->flags, BT_ADV_ENABLED)) {
|
|
/* Legacy advertiser exists, but is not currently advertising.
|
|
* This happens when keep advertising behavior is active but
|
|
* no conn object is available to do connectable advertising.
|
|
*/
|
|
bt_le_adv_delete_legacy();
|
|
return 0;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_PERIPHERAL) &&
|
|
atomic_test_bit(adv->flags, BT_ADV_CONNECTABLE)) {
|
|
le_adv_stop_free_conn(adv, 0);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_EXT_ADV) &&
|
|
BT_FEAT_LE_EXT_ADV(bt_dev.le.features)) {
|
|
err = bt_le_adv_set_enable_ext(adv, false, NULL);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
} else {
|
|
err = bt_le_adv_set_enable_legacy(adv, false);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
bt_le_adv_delete_legacy();
|
|
|
|
#if defined(CONFIG_BT_OBSERVER)
|
|
if (!(IS_ENABLED(CONFIG_BT_EXT_ADV) &&
|
|
BT_FEAT_LE_EXT_ADV(bt_dev.le.features)) &&
|
|
!IS_ENABLED(CONFIG_BT_PRIVACY) &&
|
|
!IS_ENABLED(CONFIG_BT_SCAN_WITH_IDENTITY)) {
|
|
/* If scan is ongoing set back NRPA */
|
|
if (atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING)) {
|
|
bt_le_scan_set_enable(BT_HCI_LE_SCAN_DISABLE);
|
|
bt_id_set_private_addr(BT_ID_DEFAULT);
|
|
bt_le_scan_set_enable(BT_HCI_LE_SCAN_ENABLE);
|
|
}
|
|
}
|
|
#endif /* defined(CONFIG_BT_OBSERVER) */
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_BT_PERIPHERAL)
|
|
void bt_le_adv_resume(void)
|
|
{
|
|
struct bt_le_ext_adv *adv = bt_le_adv_lookup_legacy();
|
|
struct bt_conn *conn;
|
|
bool persist_paused = false;
|
|
int err;
|
|
|
|
if (!adv) {
|
|
BT_DBG("No valid legacy adv");
|
|
return;
|
|
}
|
|
|
|
if (!(atomic_test_bit(adv->flags, BT_ADV_PERSIST) &&
|
|
!atomic_test_bit(adv->flags, BT_ADV_ENABLED))) {
|
|
return;
|
|
}
|
|
|
|
if (!atomic_test_bit(adv->flags, BT_ADV_CONNECTABLE)) {
|
|
return;
|
|
}
|
|
|
|
err = le_adv_start_add_conn(adv, &conn);
|
|
if (err) {
|
|
BT_DBG("Host cannot resume connectable advertising (%d)", err);
|
|
return;
|
|
}
|
|
|
|
BT_DBG("Resuming connectable advertising");
|
|
|
|
if (IS_ENABLED(CONFIG_BT_PRIVACY) &&
|
|
!atomic_test_bit(adv->flags, BT_ADV_USE_IDENTITY)) {
|
|
bt_id_set_adv_private_addr(adv);
|
|
}
|
|
|
|
err = bt_le_adv_set_enable(adv, true);
|
|
if (err) {
|
|
BT_DBG("Controller cannot resume connectable advertising (%d)",
|
|
err);
|
|
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
|
|
|
|
/* Temporarily clear persist flag to avoid recursion in
|
|
* bt_conn_unref if the flag is still set.
|
|
*/
|
|
persist_paused = atomic_test_and_clear_bit(adv->flags,
|
|
BT_ADV_PERSIST);
|
|
}
|
|
|
|
/* Since we don't give the application a reference to manage in
|
|
* this case, we need to release this reference here.
|
|
*/
|
|
bt_conn_unref(conn);
|
|
if (persist_paused) {
|
|
atomic_set_bit(adv->flags, BT_ADV_PERSIST);
|
|
}
|
|
}
|
|
#endif /* defined(CONFIG_BT_PERIPHERAL) */
|
|
|
|
#if defined(CONFIG_BT_EXT_ADV)
|
|
int bt_le_ext_adv_get_info(const struct bt_le_ext_adv *adv,
|
|
struct bt_le_ext_adv_info *info)
|
|
{
|
|
info->id = adv->id;
|
|
info->tx_power = adv->tx_power;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_le_ext_adv_create(const struct bt_le_adv_param *param,
|
|
const struct bt_le_ext_adv_cb *cb,
|
|
struct bt_le_ext_adv **out_adv)
|
|
{
|
|
struct bt_le_ext_adv *adv;
|
|
int err;
|
|
|
|
if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) {
|
|
return -EAGAIN;
|
|
}
|
|
|
|
if (!valid_adv_ext_param(param)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
adv = adv_new();
|
|
if (!adv) {
|
|
return -ENOMEM;
|
|
}
|
|
|
|
adv->id = param->id;
|
|
adv->cb = cb;
|
|
|
|
err = le_ext_adv_param_set(adv, param, false);
|
|
if (err) {
|
|
adv_delete(adv);
|
|
return err;
|
|
}
|
|
|
|
*out_adv = adv;
|
|
return 0;
|
|
}
|
|
|
|
int bt_le_ext_adv_update_param(struct bt_le_ext_adv *adv,
|
|
const struct bt_le_adv_param *param)
|
|
{
|
|
if (!valid_adv_ext_param(param)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_PER_ADV) &&
|
|
atomic_test_bit(adv->flags, BT_PER_ADV_PARAMS_SET)) {
|
|
/* If params for per adv has been set, do not allow setting
|
|
* connectable, scanable or use legacy adv
|
|
*/
|
|
if (param->options & BT_LE_ADV_OPT_CONNECTABLE ||
|
|
param->options & BT_LE_ADV_OPT_SCANNABLE ||
|
|
!(param->options & BT_LE_ADV_OPT_EXT_ADV) ||
|
|
param->options & BT_LE_ADV_OPT_ANONYMOUS) {
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
if (atomic_test_bit(adv->flags, BT_ADV_ENABLED)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (param->id != adv->id) {
|
|
atomic_clear_bit(adv->flags, BT_ADV_RPA_VALID);
|
|
}
|
|
|
|
return le_ext_adv_param_set(adv, param, false);
|
|
}
|
|
|
|
int bt_le_ext_adv_start(struct bt_le_ext_adv *adv,
|
|
struct bt_le_ext_adv_start_param *param)
|
|
{
|
|
struct bt_conn *conn = NULL;
|
|
int err;
|
|
|
|
if (IS_ENABLED(CONFIG_BT_PERIPHERAL) &&
|
|
atomic_test_bit(adv->flags, BT_ADV_CONNECTABLE)) {
|
|
err = le_adv_start_add_conn(adv, &conn);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
atomic_set_bit_to(adv->flags, BT_ADV_LIMITED, param &&
|
|
(param->timeout > 0 || param->num_events > 0));
|
|
|
|
if (atomic_test_bit(adv->flags, BT_ADV_CONNECTABLE)) {
|
|
if (IS_ENABLED(CONFIG_BT_PRIVACY) &&
|
|
!atomic_test_bit(adv->flags, BT_ADV_USE_IDENTITY)) {
|
|
bt_id_set_adv_private_addr(adv);
|
|
}
|
|
} else {
|
|
if (!atomic_test_bit(adv->flags, BT_ADV_USE_IDENTITY)) {
|
|
bt_id_set_adv_private_addr(adv);
|
|
}
|
|
}
|
|
|
|
if (atomic_test_bit(adv->flags, BT_ADV_INCLUDE_NAME) &&
|
|
!atomic_test_bit(adv->flags, BT_ADV_DATA_SET)) {
|
|
/* Set the advertiser name */
|
|
bt_le_ext_adv_set_data(adv, NULL, 0, NULL, 0);
|
|
}
|
|
|
|
err = bt_le_adv_set_enable_ext(adv, true, param);
|
|
if (err) {
|
|
BT_ERR("Failed to start advertiser");
|
|
if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn) {
|
|
bt_conn_set_state(conn, BT_CONN_DISCONNECTED);
|
|
bt_conn_unref(conn);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn) {
|
|
/* If undirected connectable advertiser we have created a
|
|
* connection object that we don't yet give to the application.
|
|
* Since we don't give the application a reference to manage in
|
|
* this case, we need to release this reference here
|
|
*/
|
|
bt_conn_unref(conn);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_le_ext_adv_stop(struct bt_le_ext_adv *adv)
|
|
{
|
|
atomic_clear_bit(adv->flags, BT_ADV_PERSIST);
|
|
|
|
if (!atomic_test_bit(adv->flags, BT_ADV_ENABLED)) {
|
|
return 0;
|
|
}
|
|
|
|
if (atomic_test_and_clear_bit(adv->flags, BT_ADV_LIMITED)) {
|
|
atomic_clear_bit(adv->flags, BT_ADV_RPA_VALID);
|
|
|
|
#if defined(CONFIG_BT_SMP)
|
|
bt_id_pending_keys_update();
|
|
#endif
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_PERIPHERAL) &&
|
|
atomic_test_bit(adv->flags, BT_ADV_CONNECTABLE)) {
|
|
le_adv_stop_free_conn(adv, 0);
|
|
}
|
|
|
|
return bt_le_adv_set_enable_ext(adv, false, NULL);
|
|
}
|
|
|
|
int bt_le_ext_adv_set_data(struct bt_le_ext_adv *adv,
|
|
const struct bt_data *ad, size_t ad_len,
|
|
const struct bt_data *sd, size_t sd_len)
|
|
{
|
|
bool ext_adv, scannable, use_name;
|
|
|
|
ext_adv = atomic_test_bit(adv->flags, BT_ADV_EXT_ADV);
|
|
scannable = atomic_test_bit(adv->flags, BT_ADV_SCANNABLE);
|
|
use_name = atomic_test_bit(adv->flags, BT_ADV_INCLUDE_NAME);
|
|
|
|
return le_adv_update(adv, ad, ad_len, sd, sd_len, ext_adv, scannable,
|
|
use_name);
|
|
}
|
|
|
|
int bt_le_ext_adv_delete(struct bt_le_ext_adv *adv)
|
|
{
|
|
struct bt_hci_cp_le_remove_adv_set *cp;
|
|
struct net_buf *buf;
|
|
int err;
|
|
|
|
if (!BT_FEAT_LE_EXT_ADV(bt_dev.le.features)) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* Advertising set should be stopped first */
|
|
if (atomic_test_bit(adv->flags, BT_ADV_ENABLED)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_REMOVE_ADV_SET, sizeof(*cp));
|
|
if (!buf) {
|
|
BT_WARN("No HCI buffers");
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
cp->handle = adv->handle;
|
|
|
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_REMOVE_ADV_SET, buf, NULL);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
adv_delete(adv);
|
|
|
|
return 0;
|
|
}
|
|
#endif /* defined(CONFIG_BT_EXT_ADV) */
|
|
|
|
|
|
#if defined(CONFIG_BT_PER_ADV)
|
|
int bt_le_per_adv_set_param(struct bt_le_ext_adv *adv,
|
|
const struct bt_le_per_adv_param *param)
|
|
{
|
|
struct bt_hci_cp_le_set_per_adv_param *cp;
|
|
struct net_buf *buf;
|
|
int err;
|
|
|
|
if (!BT_FEAT_LE_EXT_PER_ADV(bt_dev.le.features)) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (atomic_test_bit(adv->flags, BT_ADV_SCANNABLE)) {
|
|
return -EINVAL;
|
|
} else if (atomic_test_bit(adv->flags, BT_ADV_CONNECTABLE)) {
|
|
return -EINVAL;
|
|
} else if (!atomic_test_bit(adv->flags, BT_ADV_EXT_ADV)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (param->interval_min < 0x0006 ||
|
|
param->interval_max > 0xFFFF ||
|
|
param->interval_min > param->interval_max) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_PER_ADV_PARAM, sizeof(*cp));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
(void)memset(cp, 0, sizeof(*cp));
|
|
|
|
cp->handle = adv->handle;
|
|
cp->min_interval = sys_cpu_to_le16(param->interval_min);
|
|
cp->max_interval = sys_cpu_to_le16(param->interval_max);
|
|
|
|
if (param->options & BT_LE_PER_ADV_OPT_USE_TX_POWER) {
|
|
cp->props |= BT_HCI_LE_ADV_PROP_TX_POWER;
|
|
}
|
|
|
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_PER_ADV_PARAM, buf, NULL);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
atomic_set_bit(adv->flags, BT_PER_ADV_PARAMS_SET);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_le_per_adv_set_data(const struct bt_le_ext_adv *adv,
|
|
const struct bt_data *ad, size_t ad_len)
|
|
{
|
|
struct bt_hci_cp_le_set_per_adv_data *cp;
|
|
struct net_buf *buf;
|
|
struct bt_ad d = { .data = ad, .len = ad_len };
|
|
int err;
|
|
|
|
if (!BT_FEAT_LE_EXT_PER_ADV(bt_dev.le.features)) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (!atomic_test_bit(adv->flags, BT_PER_ADV_PARAMS_SET)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!ad_len || !ad) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ad_len > BT_HCI_LE_PER_ADV_FRAG_MAX_LEN) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_PER_ADV_DATA, sizeof(*cp));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
(void)memset(cp, 0, sizeof(*cp));
|
|
|
|
cp->handle = adv->handle;
|
|
|
|
/* TODO: If data is longer than what the controller can manage,
|
|
* split the data. Read size from controller on boot.
|
|
*/
|
|
cp->op = BT_HCI_LE_PER_ADV_OP_COMPLETE_DATA;
|
|
|
|
err = set_data_add(cp->data, BT_HCI_LE_PER_ADV_FRAG_MAX_LEN, &d, 1,
|
|
&cp->len);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_PER_ADV_DATA, buf, NULL);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bt_le_per_adv_enable(struct bt_le_ext_adv *adv, bool enable)
|
|
{
|
|
struct bt_hci_cp_le_set_per_adv_enable *cp;
|
|
struct net_buf *buf;
|
|
struct bt_hci_cmd_state_set state;
|
|
int err;
|
|
|
|
if (!BT_FEAT_LE_EXT_PER_ADV(bt_dev.le.features)) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* TODO: We could setup some default ext adv params if not already set*/
|
|
if (!atomic_test_bit(adv->flags, BT_PER_ADV_PARAMS_SET)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (atomic_test_bit(adv->flags, BT_PER_ADV_ENABLED) == enable) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_SET_PER_ADV_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->enable = enable ? 1 : 0;
|
|
|
|
bt_hci_cmd_state_set_init(buf, &state, adv->flags,
|
|
BT_PER_ADV_ENABLED, enable);
|
|
|
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_PER_ADV_ENABLE, buf, NULL);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_le_per_adv_start(struct bt_le_ext_adv *adv)
|
|
{
|
|
return bt_le_per_adv_enable(adv, true);
|
|
}
|
|
|
|
int bt_le_per_adv_stop(struct bt_le_ext_adv *adv)
|
|
{
|
|
return bt_le_per_adv_enable(adv, false);
|
|
}
|
|
|
|
#if defined(CONFIG_BT_CONN)
|
|
int bt_le_per_adv_set_info_transfer(const struct bt_le_ext_adv *adv,
|
|
const struct bt_conn *conn,
|
|
uint16_t service_data)
|
|
{
|
|
struct bt_hci_cp_le_per_adv_set_info_transfer *cp;
|
|
struct net_buf *buf;
|
|
|
|
|
|
if (!BT_FEAT_LE_EXT_PER_ADV(bt_dev.le.features)) {
|
|
return -ENOTSUP;
|
|
} else if (!BT_FEAT_LE_PAST_SEND(bt_dev.le.features)) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
buf = bt_hci_cmd_create(BT_HCI_OP_LE_PER_ADV_SET_INFO_TRANSFER,
|
|
sizeof(*cp));
|
|
if (!buf) {
|
|
return -ENOBUFS;
|
|
}
|
|
|
|
cp = net_buf_add(buf, sizeof(*cp));
|
|
(void)memset(cp, 0, sizeof(*cp));
|
|
|
|
cp->conn_handle = sys_cpu_to_le16(conn->handle);
|
|
cp->adv_handle = adv->handle;
|
|
cp->service_data = sys_cpu_to_le16(service_data);
|
|
|
|
return bt_hci_cmd_send_sync(BT_HCI_OP_LE_PER_ADV_SET_INFO_TRANSFER, buf,
|
|
NULL);
|
|
}
|
|
#endif /* CONFIG_BT_CONN */
|
|
#endif /* CONFIG_BT_PER_ADV */
|
|
|
|
#if defined(CONFIG_BT_EXT_ADV)
|
|
#if defined(CONFIG_BT_BROADCASTER)
|
|
void bt_hci_le_adv_set_terminated(struct net_buf *buf)
|
|
{
|
|
struct bt_hci_evt_le_adv_set_terminated *evt;
|
|
struct bt_le_ext_adv *adv;
|
|
uint16_t conn_handle;
|
|
|
|
evt = (void *)buf->data;
|
|
adv = bt_adv_lookup_handle(evt->adv_handle);
|
|
conn_handle = sys_le16_to_cpu(evt->conn_handle);
|
|
|
|
BT_DBG("status 0x%02x adv_handle %u conn_handle 0x%02x num %u",
|
|
evt->status, evt->adv_handle, conn_handle,
|
|
evt->num_completed_ext_adv_evts);
|
|
|
|
if (!adv) {
|
|
BT_ERR("No valid adv");
|
|
return;
|
|
}
|
|
|
|
atomic_clear_bit(adv->flags, BT_ADV_ENABLED);
|
|
|
|
if (evt->status && IS_ENABLED(CONFIG_BT_PERIPHERAL) &&
|
|
atomic_test_bit(adv->flags, BT_ADV_CONNECTABLE)) {
|
|
/* Only set status for legacy advertising API.
|
|
* This will call connected callback for high duty cycle
|
|
* directed advertiser timeout.
|
|
*/
|
|
le_adv_stop_free_conn(adv, adv == bt_dev.adv ? evt->status : 0);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_CONN) && !evt->status) {
|
|
struct bt_conn *conn = bt_conn_lookup_handle(conn_handle);
|
|
|
|
if (conn) {
|
|
if (IS_ENABLED(CONFIG_BT_PRIVACY) &&
|
|
!atomic_test_bit(adv->flags, BT_ADV_USE_IDENTITY)) {
|
|
/* Set Responder address unless already set */
|
|
conn->le.resp_addr.type = BT_ADDR_LE_RANDOM;
|
|
if (bt_addr_cmp(&conn->le.resp_addr.a,
|
|
BT_ADDR_ANY) == 0) {
|
|
bt_addr_copy(&conn->le.resp_addr.a,
|
|
&adv->random_addr.a);
|
|
}
|
|
} else {
|
|
bt_addr_le_copy(&conn->le.resp_addr,
|
|
&bt_dev.id_addr[conn->id]);
|
|
}
|
|
|
|
if (adv->cb && adv->cb->connected) {
|
|
struct bt_le_ext_adv_connected_info info = {
|
|
.conn = conn,
|
|
};
|
|
|
|
adv->cb->connected(adv, &info);
|
|
}
|
|
|
|
bt_conn_unref(conn);
|
|
}
|
|
}
|
|
|
|
if (atomic_test_and_clear_bit(adv->flags, BT_ADV_LIMITED)) {
|
|
atomic_clear_bit(adv->flags, BT_ADV_RPA_VALID);
|
|
|
|
#if defined(CONFIG_BT_SMP)
|
|
bt_id_pending_keys_update();
|
|
#endif
|
|
|
|
if (adv->cb && adv->cb->sent) {
|
|
struct bt_le_ext_adv_sent_info info = {
|
|
.num_sent = evt->num_completed_ext_adv_evts,
|
|
};
|
|
|
|
adv->cb->sent(adv, &info);
|
|
}
|
|
}
|
|
|
|
if (!atomic_test_bit(adv->flags, BT_ADV_PERSIST) && adv == bt_dev.adv) {
|
|
bt_le_adv_delete_legacy();
|
|
}
|
|
}
|
|
|
|
void bt_hci_le_scan_req_received(struct net_buf *buf)
|
|
{
|
|
struct bt_hci_evt_le_scan_req_received *evt;
|
|
struct bt_le_ext_adv *adv;
|
|
|
|
evt = (void *)buf->data;
|
|
adv = bt_adv_lookup_handle(evt->handle);
|
|
|
|
BT_DBG("handle %u peer %s", evt->handle, bt_addr_le_str(&evt->addr));
|
|
|
|
if (!adv) {
|
|
BT_ERR("No valid adv");
|
|
return;
|
|
}
|
|
|
|
if (adv->cb && adv->cb->scanned) {
|
|
struct bt_le_ext_adv_scanned_info info;
|
|
bt_addr_le_t id_addr;
|
|
|
|
if (evt->addr.type == BT_ADDR_LE_PUBLIC_ID ||
|
|
evt->addr.type == BT_ADDR_LE_RANDOM_ID) {
|
|
bt_addr_le_copy(&id_addr, &evt->addr);
|
|
id_addr.type -= BT_ADDR_LE_PUBLIC_ID;
|
|
} else {
|
|
bt_addr_le_copy(&id_addr,
|
|
bt_lookup_id_addr(adv->id, &evt->addr));
|
|
}
|
|
|
|
info.addr = &id_addr;
|
|
adv->cb->scanned(adv, &info);
|
|
}
|
|
}
|
|
#endif /* defined(CONFIG_BT_BROADCASTER) */
|
|
#endif /* defined(CONFIG_BT_EXT_ADV) */
|