zephyr/subsys/bluetooth/host/adv.c
Carles Cufi 1325edff48 Bluetooth: hci: Align terms with the Bluetooth v5.3 spec
The new inclusive naming terminology changes in v5.3 of the Bluetooth
specification affect the HCI layer, so apply all relevant changes to
align with it.

Signed-off-by: Carles Cufi <carles.cufi@nordicsemi.no>
2021-09-17 16:05:01 +02:00

1883 lines
44 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"
enum adv_name_type {
ADV_NAME_TYPE_NONE,
ADV_NAME_TYPE_AD,
ADV_NAME_TYPE_SD,
};
enum adv_name_type get_adv_name_type(const struct bt_le_ext_adv *adv)
{
if (atomic_test_bit(adv->flags, BT_ADV_INCLUDE_NAME_SD)) {
return ADV_NAME_TYPE_SD;
}
if (atomic_test_bit(adv->flags, BT_ADV_INCLUDE_NAME_AD)) {
return ADV_NAME_TYPE_AD;
}
return ADV_NAME_TYPE_NONE;
}
enum adv_name_type get_adv_name_type_param(const struct bt_le_adv_param *param)
{
if (param->options & BT_LE_ADV_OPT_USE_NAME) {
if (param->options & BT_LE_ADV_OPT_FORCE_NAME_IN_AD) {
return ADV_NAME_TYPE_AD;
}
if ((param->options & BT_LE_ADV_OPT_EXT_ADV) &&
!(param->options & BT_LE_ADV_OPT_SCANNABLE)) {
return ADV_NAME_TYPE_AD;
}
return ADV_NAME_TYPE_SD;
}
return ADV_NAME_TYPE_NONE;
}
#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) */
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_DEV_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_DEV_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 ((param->options & BT_LE_ADV_OPT_EXT_ADV) &&
(param->options & BT_LE_ADV_OPT_SCANNABLE) &&
(param->options & BT_LE_ADV_OPT_FORCE_NAME_IN_AD)) {
/* Advertising data is not permitted for an extended
* scannable advertiser.
*/
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_complete(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_complete(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);
}
static int hci_set_adv_ext_complete(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_complete(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 hci_set_adv_ext_fragmented(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;
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;
size_t offset = 0;
/* We can't necessarily set one AD field in a single step. */
while (offset < data[j].data_len) {
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));
set_data->handle = adv->handle;
set_data->frag_pref = BT_HCI_LE_EXT_ADV_FRAG_DISABLED;
/* Determine the operation parameter value. */
if ((i == 0) && (j == 0) && (offset == 0)) {
set_data->op = BT_HCI_LE_EXT_ADV_OP_FIRST_FRAG;
} else if ((i == ad_len - 1) && (j == ad[i].len - 1)) {
/* The last AD field may be split into
* one or two commands.
*/
if (offset != 0) {
/* We can always set the data in two operations
* Therefore, we know that this is the last.
*/
set_data->op = BT_HCI_LE_EXT_ADV_OP_LAST_FRAG;
} else if (len + 2 <= BT_HCI_LE_EXT_ADV_FRAG_MAX_LEN) {
/* First part fits. */
set_data->op = BT_HCI_LE_EXT_ADV_OP_LAST_FRAG;
} else {
/* The data must be split into two
* commands.
*/
set_data->op = BT_HCI_LE_EXT_ADV_OP_INTERM_FRAG;
}
} else {
set_data->op = BT_HCI_LE_EXT_ADV_OP_INTERM_FRAG;
}
if (offset == 0) {
set_data->len = MIN(len + 2,
BT_HCI_LE_EXT_ADV_FRAG_MAX_LEN);
} else {
/* No need to take min operation here,
* as we can always fit the second part.
*/
set_data->len = len - offset;
}
if (offset == 0) {
set_data->data[0] = len + 1;
set_data->data[1] = type;
memcpy(&set_data->data[2], data[j].data, set_data->len);
offset += set_data->len - 2;
} else {
memcpy(&set_data->data[0], &data[j].data[offset],
set_data->len);
offset += set_data->len;
}
err = bt_hci_cmd_send_sync(hci_op, buf, NULL);
if (err) {
return err;
}
}
}
}
return 0;
}
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)
{
size_t total_len_bytes = 0;
for (size_t i = 0; i < ad_len; i++) {
for (size_t j = 0; j < ad[i].len; j++) {
total_len_bytes += ad[i].data[j].data_len + 2;
}
}
if ((total_len_bytes > BT_HCI_LE_EXT_ADV_FRAG_MAX_LEN) &&
atomic_test_bit(adv->flags, BT_ADV_ENABLED)) {
/* It is not allowed to set advertising data in multiple
* operations while the advertiser is running.
*/
return -EAGAIN;
}
if (total_len_bytes <= BT_HCI_LE_EXT_ADV_FRAG_MAX_LEN) {
/* If possible, set all data at once.
* This allows us to update advertising data while advertising.
*/
return hci_set_adv_ext_complete(adv, hci_op, ad, ad_len);
} else {
return hci_set_adv_ext_fragmented(adv, hci_op, ad, ad_len);
}
return 0;
}
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_DEV_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_DEV_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 bool ad_is_limited(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_FLAGS &&
ad[i].data_len == sizeof(uint8_t) &&
ad[i].data != NULL) {
if (ad[i].data[0] & BT_LE_AD_LIMITED) {
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,
enum adv_name_type name_type)
{
struct bt_ad d[2] = {};
struct bt_data data;
size_t d_len;
int err;
if (name_type != ADV_NAME_TYPE_NONE) {
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 (name_type == ADV_NAME_TYPE_AD) {
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 (name_type == ADV_NAME_TYPE_SD) {
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;
if (!adv) {
return -EINVAL;
}
if (!atomic_test_bit(adv->flags, BT_ADV_ENABLED)) {
return -EAGAIN;
}
scannable = atomic_test_bit(adv->flags, BT_ADV_SCANNABLE);
return le_adv_update(adv, ad, ad_len, sd, sd_len, false, scannable,
get_adv_name_type(adv));
}
static uint8_t get_filter_policy(uint32_t options)
{
if (!IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST)) {
return BT_LE_ADV_FP_NO_FILTER;
} else if ((options & BT_LE_ADV_OPT_FILTER_SCAN_REQ) &&
(options & BT_LE_ADV_OPT_FILTER_CONN)) {
return BT_LE_ADV_FP_FILTER_BOTH;
} else if (options & BT_LE_ADV_OPT_FILTER_SCAN_REQ) {
return BT_LE_ADV_FP_FILTER_SCAN_REQ;
} else if (options & BT_LE_ADV_OPT_FILTER_CONN) {
return BT_LE_ADV_FP_FILTER_CONN_IND;
} else {
return BT_LE_ADV_FP_NO_FILTER;
}
}
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_conn *conn;
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 = false;
enum adv_name_type name_type;
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);
}
name_type = get_adv_name_type_param(param);
if (param->options & BT_LE_ADV_OPT_CONNECTABLE) {
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 {
scannable = true;
set_param.type = BT_HCI_ADV_IND;
}
} else if ((param->options & BT_LE_ADV_OPT_SCANNABLE) || sd ||
(name_type == ADV_NAME_TYPE_SD)) {
scannable = true;
set_param.type = BT_HCI_ADV_SCAN_IND;
} else {
set_param.type = 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, name_type);
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_AD,
name_type == ADV_NAME_TYPE_AD);
atomic_set_bit_to(adv->flags, BT_ADV_INCLUDE_NAME_SD,
name_type == ADV_NAME_TYPE_SD);
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;
enum adv_name_type name_type;
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);
}
name_type = get_adv_name_type_param(param);
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 ||
(name_type == ADV_NAME_TYPE_SD)) {
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_AD,
name_type == ADV_NAME_TYPE_AD);
atomic_set_bit_to(adv->flags, BT_ADV_INCLUDE_NAME_SD,
name_type == ADV_NAME_TYPE_SD);
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 != NULL);
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;
}
static void adv_timeout(struct k_work *work);
int bt_le_lim_adv_cancel_timeout(struct bt_le_ext_adv *adv)
{
return k_work_cancel_delayable(&adv->lim_adv_timeout_work);
}
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_DEV_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();
}
if (ad_is_limited(ad, ad_len)) {
k_work_init_delayable(&adv->lim_adv_timeout_work, adv_timeout);
k_work_reschedule(&adv->lim_adv_timeout_work,
K_SECONDS(CONFIG_BT_LIM_ADV_TIMEOUT));
}
return err;
}
int bt_le_adv_stop(void)
{
struct bt_le_ext_adv *adv = bt_le_adv_lookup_legacy();
int err;
(void)bt_le_lim_adv_cancel_timeout(adv);
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_DEV_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_DEV_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 (atomic_test_bit(adv->flags, BT_ADV_ENABLED)) {
return -EALREADY;
}
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 (get_adv_name_type(adv) != ADV_NAME_TYPE_NONE &&
!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)
{
(void)bt_le_lim_adv_cancel_timeout(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;
ext_adv = atomic_test_bit(adv->flags, BT_ADV_EXT_ADV);
scannable = atomic_test_bit(adv->flags, BT_ADV_SCANNABLE);
return le_adv_update(adv, ad, ad_len, sd, sd_len, ext_adv, scannable,
get_adv_name_type(adv));
}
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_DEV_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) */
static void adv_timeout(struct k_work *work)
{
int err = 0;
struct k_work_delayable *dwork;
struct bt_le_ext_adv *adv;
dwork = k_work_delayable_from_work(work);
adv = CONTAINER_OF(dwork, struct bt_le_ext_adv, lim_adv_timeout_work);
#if defined(CONFIG_BT_EXT_ADV)
if (adv == bt_dev.adv) {
err = bt_le_adv_stop();
} else {
err = bt_le_ext_adv_stop(adv);
}
#else
err = bt_le_adv_stop();
#endif
BT_WARN("Failed to stop advertising: %d", err);
}
#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 < BT_GAP_PER_ADV_MIN_INTERVAL ||
param->interval_max > BT_GAP_PER_ADV_MAX_INTERVAL ||
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_complete(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);
(void)bt_le_lim_adv_cancel_timeout(adv);
#if (CONFIG_BT_ID_MAX > 1) && (CONFIG_BT_EXT_ADV_MAX_ADV_SET > 1)
bt_dev.adv_conn_id = adv->id;
for (int i = 0; i < ARRAY_SIZE(bt_dev.cached_conn_complete); i++) {
if (bt_dev.cached_conn_complete[i].valid &&
bt_dev.cached_conn_complete[i].evt.handle == evt->conn_handle) {
if (atomic_test_bit(adv->flags, BT_ADV_ENABLED)) {
/* Process the cached connection complete event
* now that the corresponding advertising set is known.
*
* If the advertiser has been stopped before the connection
* complete event has been raised to the application, we
* discard the event.
*/
bt_hci_le_enh_conn_complete(&bt_dev.cached_conn_complete[i].evt);
}
bt_dev.cached_conn_complete[i].valid = false;
}
}
#endif
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) */