559 lines
14 KiB
C
559 lines
14 KiB
C
/*
|
|
* Copyright (c) 2021 Xiaomi Corporation
|
|
* Copyright (c) 2018 Nordic Semiconductor ASA
|
|
* Copyright (c) 2017 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/debug/stack.h>
|
|
#include <zephyr/sys/iterable_sections.h>
|
|
#include <zephyr/net/buf.h>
|
|
#include <zephyr/bluetooth/bluetooth.h>
|
|
#include <zephyr/bluetooth/hci.h>
|
|
#include <zephyr/bluetooth/mesh.h>
|
|
|
|
#include "common/bt_str.h"
|
|
|
|
#include "host/hci_core.h"
|
|
|
|
#include "net.h"
|
|
#include "proxy.h"
|
|
#include "solicitation.h"
|
|
|
|
#define LOG_LEVEL CONFIG_BT_MESH_ADV_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(bt_mesh_adv_ext);
|
|
|
|
/* Convert from ms to 0.625ms units */
|
|
#define ADV_INT_FAST_MS 20
|
|
|
|
#ifndef CONFIG_BT_MESH_RELAY_ADV_SETS
|
|
#define CONFIG_BT_MESH_RELAY_ADV_SETS 0
|
|
#endif
|
|
|
|
enum {
|
|
/** Controller is currently advertising */
|
|
ADV_FLAG_ACTIVE,
|
|
/** Advertising sending completed */
|
|
ADV_FLAG_SENT,
|
|
/** Currently performing proxy advertising */
|
|
ADV_FLAG_PROXY,
|
|
/** The proxy has been start, but maybe pending. */
|
|
ADV_FLAG_PROXY_START,
|
|
/** The send-call has been pending. */
|
|
ADV_FLAG_SCHEDULE_PENDING,
|
|
/** Custom adv params have been set, we need to update the parameters on
|
|
* the next send.
|
|
*/
|
|
ADV_FLAG_UPDATE_PARAMS,
|
|
|
|
/** The advertiser is suspending. */
|
|
ADV_FLAG_SUSPENDING,
|
|
|
|
/* Number of adv flags. */
|
|
ADV_FLAGS_NUM
|
|
};
|
|
|
|
struct bt_mesh_ext_adv {
|
|
const enum bt_mesh_adv_tag_bit tags;
|
|
ATOMIC_DEFINE(flags, ADV_FLAGS_NUM);
|
|
struct bt_le_ext_adv *instance;
|
|
struct bt_mesh_adv *adv;
|
|
uint32_t timestamp;
|
|
struct k_work work;
|
|
struct bt_le_adv_param adv_param;
|
|
};
|
|
|
|
static void send_pending_adv(struct k_work *work);
|
|
static bool schedule_send(struct bt_mesh_ext_adv *ext_adv);
|
|
|
|
static struct bt_mesh_ext_adv advs[] = {
|
|
[0] = {
|
|
.tags = (
|
|
#if !defined(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE)
|
|
BT_MESH_ADV_TAG_BIT_FRIEND |
|
|
#endif
|
|
#if !defined(CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE)
|
|
BT_MESH_ADV_TAG_BIT_PROXY |
|
|
#endif /* !CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE */
|
|
#if defined(CONFIG_BT_MESH_ADV_EXT_RELAY_USING_MAIN_ADV_SET)
|
|
BT_MESH_ADV_TAG_BIT_RELAY |
|
|
#endif /* CONFIG_BT_MESH_ADV_EXT_RELAY_USING_MAIN_ADV_SET */
|
|
#if defined(CONFIG_BT_MESH_PB_ADV)
|
|
BT_MESH_ADV_TAG_BIT_PROV |
|
|
#endif /* CONFIG_BT_MESH_PB_ADV */
|
|
BT_MESH_ADV_TAG_BIT_LOCAL
|
|
),
|
|
.work = Z_WORK_INITIALIZER(send_pending_adv),
|
|
},
|
|
#if CONFIG_BT_MESH_RELAY_ADV_SETS
|
|
[1 ... CONFIG_BT_MESH_RELAY_ADV_SETS] = {
|
|
.tags = (
|
|
#if defined(CONFIG_BT_MESH_RELAY)
|
|
BT_MESH_ADV_TAG_BIT_RELAY |
|
|
#endif /* CONFIG_BT_MESH_RELAY */
|
|
#if defined(CONFIG_BT_MESH_PB_ADV_USE_RELAY_SETS)
|
|
BT_MESH_ADV_TAG_BIT_PROV |
|
|
#endif /* CONFIG_BT_MESH_PB_ADV_USE_RELAY_SETS */
|
|
0),
|
|
.work = Z_WORK_INITIALIZER(send_pending_adv),
|
|
},
|
|
#endif /* CONFIG_BT_MESH_RELAY_ADV_SETS */
|
|
#if defined(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE)
|
|
{
|
|
.tags = BT_MESH_ADV_TAG_BIT_FRIEND,
|
|
.work = Z_WORK_INITIALIZER(send_pending_adv),
|
|
},
|
|
#endif /* CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE */
|
|
#if defined(CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE)
|
|
{
|
|
.tags = BT_MESH_ADV_TAG_BIT_PROXY,
|
|
.work = Z_WORK_INITIALIZER(send_pending_adv),
|
|
},
|
|
#endif /* CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE */
|
|
};
|
|
|
|
BUILD_ASSERT(ARRAY_SIZE(advs) <= CONFIG_BT_EXT_ADV_MAX_ADV_SET,
|
|
"Insufficient adv instances");
|
|
|
|
static inline struct bt_mesh_ext_adv *relay_adv_get(void)
|
|
{
|
|
if (!!(CONFIG_BT_MESH_RELAY_ADV_SETS)) {
|
|
return &advs[1];
|
|
} else {
|
|
return &advs[0];
|
|
}
|
|
}
|
|
|
|
static inline struct bt_mesh_ext_adv *gatt_adv_get(void)
|
|
{
|
|
if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE)) {
|
|
return &advs[ARRAY_SIZE(advs) - 1];
|
|
} else {
|
|
return &advs[0];
|
|
}
|
|
}
|
|
|
|
static int adv_start(struct bt_mesh_ext_adv *ext_adv,
|
|
const struct bt_le_adv_param *param,
|
|
struct bt_le_ext_adv_start_param *start,
|
|
const struct bt_data *ad, size_t ad_len,
|
|
const struct bt_data *sd, size_t sd_len)
|
|
{
|
|
int err;
|
|
|
|
if (!ext_adv->instance) {
|
|
LOG_ERR("Mesh advertiser not enabled");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (atomic_test_and_set_bit(ext_adv->flags, ADV_FLAG_ACTIVE)) {
|
|
LOG_ERR("Advertiser is busy");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (atomic_test_bit(ext_adv->flags, ADV_FLAG_UPDATE_PARAMS)) {
|
|
err = bt_le_ext_adv_update_param(ext_adv->instance, param);
|
|
if (err) {
|
|
LOG_ERR("Failed updating adv params: %d", err);
|
|
atomic_clear_bit(ext_adv->flags, ADV_FLAG_ACTIVE);
|
|
return err;
|
|
}
|
|
|
|
atomic_set_bit_to(ext_adv->flags, ADV_FLAG_UPDATE_PARAMS,
|
|
param != &ext_adv->adv_param);
|
|
}
|
|
|
|
err = bt_le_ext_adv_set_data(ext_adv->instance, ad, ad_len, sd, sd_len);
|
|
if (err) {
|
|
LOG_ERR("Failed setting adv data: %d", err);
|
|
atomic_clear_bit(ext_adv->flags, ADV_FLAG_ACTIVE);
|
|
return err;
|
|
}
|
|
|
|
ext_adv->timestamp = k_uptime_get_32();
|
|
|
|
err = bt_le_ext_adv_start(ext_adv->instance, start);
|
|
if (err) {
|
|
LOG_ERR("Advertising failed: err %d", err);
|
|
atomic_clear_bit(ext_adv->flags, ADV_FLAG_ACTIVE);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int bt_data_send(struct bt_mesh_ext_adv *ext_adv, uint8_t num_events, uint16_t adv_interval,
|
|
const struct bt_data *ad, size_t ad_len)
|
|
{
|
|
struct bt_le_ext_adv_start_param start = {
|
|
.num_events = num_events,
|
|
};
|
|
|
|
adv_interval = MAX(ADV_INT_FAST_MS, adv_interval);
|
|
|
|
/* Only update advertising parameters if they're different */
|
|
if (ext_adv->adv_param.interval_min != BT_MESH_ADV_SCAN_UNIT(adv_interval)) {
|
|
ext_adv->adv_param.interval_min = BT_MESH_ADV_SCAN_UNIT(adv_interval);
|
|
ext_adv->adv_param.interval_max = ext_adv->adv_param.interval_min;
|
|
atomic_set_bit(ext_adv->flags, ADV_FLAG_UPDATE_PARAMS);
|
|
}
|
|
|
|
return adv_start(ext_adv, &ext_adv->adv_param, &start, ad, ad_len, NULL, 0);
|
|
}
|
|
|
|
static int adv_send(struct bt_mesh_ext_adv *ext_adv, struct bt_mesh_adv *adv)
|
|
{
|
|
uint8_t num_events = BT_MESH_TRANSMIT_COUNT(adv->ctx.xmit) + 1;
|
|
uint16_t duration, adv_int;
|
|
struct bt_data ad;
|
|
int err;
|
|
|
|
adv_int = BT_MESH_TRANSMIT_INT(adv->ctx.xmit);
|
|
/* Upper boundary estimate: */
|
|
duration = num_events * (adv_int + 10);
|
|
|
|
LOG_DBG("type %u len %u: %s", adv->ctx.type,
|
|
adv->b.len, bt_hex(adv->b.data, adv->b.len));
|
|
LOG_DBG("count %u interval %ums duration %ums",
|
|
num_events, adv_int, duration);
|
|
|
|
ad.type = bt_mesh_adv_type[adv->ctx.type];
|
|
ad.data_len = adv->b.len;
|
|
ad.data = adv->b.data;
|
|
|
|
err = bt_data_send(ext_adv, num_events, adv_int, &ad, 1);
|
|
if (!err) {
|
|
ext_adv->adv = bt_mesh_adv_ref(adv);
|
|
}
|
|
|
|
bt_mesh_adv_send_start(duration, err, &adv->ctx);
|
|
|
|
return err;
|
|
}
|
|
|
|
static const char * const adv_tag_to_str[] = {
|
|
[BT_MESH_ADV_TAG_LOCAL] = "local adv",
|
|
[BT_MESH_ADV_TAG_RELAY] = "relay adv",
|
|
[BT_MESH_ADV_TAG_PROXY] = "proxy adv",
|
|
[BT_MESH_ADV_TAG_FRIEND] = "friend adv",
|
|
[BT_MESH_ADV_TAG_PROV] = "prov adv",
|
|
};
|
|
|
|
static void send_pending_adv(struct k_work *work)
|
|
{
|
|
struct bt_mesh_ext_adv *ext_adv;
|
|
struct bt_mesh_adv *adv;
|
|
int err;
|
|
|
|
ext_adv = CONTAINER_OF(work, struct bt_mesh_ext_adv, work);
|
|
|
|
if (atomic_test_bit(ext_adv->flags, ADV_FLAG_SUSPENDING)) {
|
|
LOG_DBG("Advertiser is suspending");
|
|
return;
|
|
}
|
|
|
|
if (atomic_test_and_clear_bit(ext_adv->flags, ADV_FLAG_SENT)) {
|
|
LOG_DBG("Advertising stopped after %u ms for %s",
|
|
k_uptime_get_32() - ext_adv->timestamp,
|
|
ext_adv->adv ? adv_tag_to_str[ext_adv->adv->ctx.tag]
|
|
: adv_tag_to_str[BT_MESH_ADV_TAG_PROXY]);
|
|
|
|
atomic_clear_bit(ext_adv->flags, ADV_FLAG_ACTIVE);
|
|
atomic_clear_bit(ext_adv->flags, ADV_FLAG_PROXY);
|
|
atomic_clear_bit(ext_adv->flags, ADV_FLAG_PROXY_START);
|
|
|
|
if (ext_adv->adv) {
|
|
struct bt_mesh_adv_ctx ctx = ext_adv->adv->ctx;
|
|
|
|
ext_adv->adv->ctx.started = 0;
|
|
bt_mesh_adv_unref(ext_adv->adv);
|
|
bt_mesh_adv_send_end(0, &ctx);
|
|
|
|
ext_adv->adv = NULL;
|
|
}
|
|
}
|
|
|
|
while ((adv = bt_mesh_adv_get_by_tag(ext_adv->tags, K_NO_WAIT))) {
|
|
/* busy == 0 means this was canceled */
|
|
if (!adv->ctx.busy) {
|
|
bt_mesh_adv_unref(adv);
|
|
continue;
|
|
}
|
|
|
|
adv->ctx.busy = 0U;
|
|
err = adv_send(ext_adv, adv);
|
|
|
|
bt_mesh_adv_unref(adv);
|
|
|
|
if (!err) {
|
|
return; /* Wait for advertising to finish */
|
|
}
|
|
}
|
|
|
|
if (ext_adv->instance == NULL) {
|
|
LOG_DBG("Advertiser is suspended or deleted");
|
|
return;
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_MESH_PROXY_SOLICITATION) &&
|
|
!bt_mesh_sol_send()) {
|
|
return;
|
|
}
|
|
|
|
if (!IS_ENABLED(CONFIG_BT_MESH_GATT_SERVER) ||
|
|
!(ext_adv->tags & BT_MESH_ADV_TAG_BIT_PROXY)) {
|
|
return;
|
|
}
|
|
|
|
atomic_set_bit(ext_adv->flags, ADV_FLAG_PROXY_START);
|
|
|
|
if (!bt_mesh_adv_gatt_send()) {
|
|
atomic_set_bit(ext_adv->flags, ADV_FLAG_PROXY);
|
|
}
|
|
|
|
if (atomic_test_and_clear_bit(ext_adv->flags, ADV_FLAG_SCHEDULE_PENDING)) {
|
|
schedule_send(ext_adv);
|
|
}
|
|
}
|
|
|
|
static bool schedule_send(struct bt_mesh_ext_adv *ext_adv)
|
|
{
|
|
if (atomic_test_and_clear_bit(ext_adv->flags, ADV_FLAG_PROXY)) {
|
|
atomic_clear_bit(ext_adv->flags, ADV_FLAG_PROXY_START);
|
|
(void)bt_le_ext_adv_stop(ext_adv->instance);
|
|
|
|
atomic_clear_bit(ext_adv->flags, ADV_FLAG_ACTIVE);
|
|
}
|
|
|
|
if (atomic_test_bit(ext_adv->flags, ADV_FLAG_ACTIVE)) {
|
|
atomic_set_bit(ext_adv->flags, ADV_FLAG_SCHEDULE_PENDING);
|
|
return false;
|
|
} else if (k_work_is_pending(&ext_adv->work)) {
|
|
return false;
|
|
}
|
|
|
|
atomic_clear_bit(ext_adv->flags, ADV_FLAG_SCHEDULE_PENDING);
|
|
k_work_submit(&ext_adv->work);
|
|
|
|
return true;
|
|
}
|
|
|
|
void bt_mesh_adv_gatt_update(void)
|
|
{
|
|
(void)schedule_send(gatt_adv_get());
|
|
}
|
|
|
|
void bt_mesh_adv_local_ready(void)
|
|
{
|
|
(void)schedule_send(advs);
|
|
}
|
|
|
|
void bt_mesh_adv_relay_ready(void)
|
|
{
|
|
struct bt_mesh_ext_adv *ext_adv = relay_adv_get();
|
|
|
|
for (int i = 0; i < CONFIG_BT_MESH_RELAY_ADV_SETS; i++) {
|
|
if (schedule_send(&ext_adv[i])) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Attempt to use the main adv set for the sending of relay messages. */
|
|
if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_RELAY_USING_MAIN_ADV_SET)) {
|
|
(void)schedule_send(advs);
|
|
}
|
|
}
|
|
|
|
void bt_mesh_adv_friend_ready(void)
|
|
{
|
|
if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE)) {
|
|
schedule_send(&advs[1 + CONFIG_BT_MESH_RELAY_ADV_SETS]);
|
|
} else {
|
|
schedule_send(&advs[0]);
|
|
}
|
|
}
|
|
|
|
int bt_mesh_adv_terminate(struct bt_mesh_adv *adv)
|
|
{
|
|
int err;
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(advs); i++) {
|
|
struct bt_mesh_ext_adv *ext_adv = &advs[i];
|
|
|
|
if (ext_adv->adv != adv) {
|
|
continue;
|
|
}
|
|
|
|
if (!atomic_test_bit(ext_adv->flags, ADV_FLAG_ACTIVE)) {
|
|
return 0;
|
|
}
|
|
|
|
err = bt_le_ext_adv_stop(ext_adv->instance);
|
|
if (err) {
|
|
LOG_ERR("Failed to stop adv %d", err);
|
|
return err;
|
|
}
|
|
|
|
/* Do not call `cb:end`, since this user action */
|
|
adv->ctx.cb = NULL;
|
|
|
|
atomic_set_bit(ext_adv->flags, ADV_FLAG_SENT);
|
|
|
|
k_work_submit(&ext_adv->work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
void bt_mesh_adv_init(void)
|
|
{
|
|
struct bt_le_adv_param adv_param = {
|
|
.id = BT_ID_DEFAULT,
|
|
.interval_min = BT_MESH_ADV_SCAN_UNIT(ADV_INT_FAST_MS),
|
|
.interval_max = BT_MESH_ADV_SCAN_UNIT(ADV_INT_FAST_MS),
|
|
#if defined(CONFIG_BT_MESH_DEBUG_USE_ID_ADDR)
|
|
.options = BT_LE_ADV_OPT_USE_IDENTITY,
|
|
#endif
|
|
};
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(advs); i++) {
|
|
(void)memcpy(&advs[i].adv_param, &adv_param, sizeof(adv_param));
|
|
}
|
|
}
|
|
|
|
static struct bt_mesh_ext_adv *adv_instance_find(struct bt_le_ext_adv *instance)
|
|
{
|
|
for (int i = 0; i < ARRAY_SIZE(advs); i++) {
|
|
if (advs[i].instance == instance) {
|
|
return &advs[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void adv_sent(struct bt_le_ext_adv *instance,
|
|
struct bt_le_ext_adv_sent_info *info)
|
|
{
|
|
struct bt_mesh_ext_adv *ext_adv = adv_instance_find(instance);
|
|
|
|
if (!ext_adv) {
|
|
LOG_WRN("Unexpected adv instance");
|
|
return;
|
|
}
|
|
|
|
if (!atomic_test_bit(ext_adv->flags, ADV_FLAG_ACTIVE)) {
|
|
return;
|
|
}
|
|
|
|
atomic_set_bit(ext_adv->flags, ADV_FLAG_SENT);
|
|
|
|
k_work_submit(&ext_adv->work);
|
|
}
|
|
|
|
#if defined(CONFIG_BT_MESH_GATT_SERVER)
|
|
static void connected(struct bt_le_ext_adv *instance,
|
|
struct bt_le_ext_adv_connected_info *info)
|
|
{
|
|
struct bt_mesh_ext_adv *ext_adv = gatt_adv_get();
|
|
|
|
if (atomic_test_and_clear_bit(ext_adv->flags, ADV_FLAG_PROXY_START)) {
|
|
atomic_clear_bit(ext_adv->flags, ADV_FLAG_ACTIVE);
|
|
(void)schedule_send(ext_adv);
|
|
}
|
|
}
|
|
#endif /* CONFIG_BT_MESH_GATT_SERVER */
|
|
|
|
int bt_mesh_adv_enable(void)
|
|
{
|
|
int err;
|
|
|
|
static const struct bt_le_ext_adv_cb adv_cb = {
|
|
.sent = adv_sent,
|
|
#if defined(CONFIG_BT_MESH_GATT_SERVER)
|
|
.connected = connected,
|
|
#endif /* CONFIG_BT_MESH_GATT_SERVER */
|
|
};
|
|
|
|
if (advs[0].instance) {
|
|
/* Already initialized */
|
|
return 0;
|
|
}
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(advs); i++) {
|
|
err = bt_le_ext_adv_create(&advs[i].adv_param, &adv_cb,
|
|
&advs[i].instance);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_mesh_adv_disable(void)
|
|
{
|
|
int err;
|
|
struct k_work_sync sync;
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(advs); i++) {
|
|
atomic_set_bit(advs[i].flags, ADV_FLAG_SUSPENDING);
|
|
|
|
if (k_current_get() != &k_sys_work_q.thread ||
|
|
(k_work_busy_get(&advs[i].work) & K_WORK_RUNNING) == 0) {
|
|
k_work_flush(&advs[i].work, &sync);
|
|
}
|
|
|
|
err = bt_le_ext_adv_stop(advs[i].instance);
|
|
if (err) {
|
|
LOG_ERR("Failed to stop adv %d", err);
|
|
return err;
|
|
}
|
|
|
|
/* `adv_sent` is called to finish transmission of an adv buffer that was pushed to
|
|
* the host before the advertiser was stopped, but did not finish.
|
|
*/
|
|
adv_sent(advs[i].instance, NULL);
|
|
|
|
err = bt_le_ext_adv_delete(advs[i].instance);
|
|
if (err) {
|
|
LOG_ERR("Failed to delete adv %d", err);
|
|
return err;
|
|
}
|
|
|
|
advs[i].instance = NULL;
|
|
|
|
atomic_clear_bit(advs[i].flags, ADV_FLAG_SUSPENDING);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_mesh_adv_gatt_start(const struct bt_le_adv_param *param,
|
|
int32_t duration,
|
|
const struct bt_data *ad, size_t ad_len,
|
|
const struct bt_data *sd, size_t sd_len)
|
|
{
|
|
struct bt_mesh_ext_adv *ext_adv = gatt_adv_get();
|
|
struct bt_le_ext_adv_start_param start = {
|
|
/* Timeout is set in 10 ms steps, with 0 indicating "forever" */
|
|
.timeout = (duration == SYS_FOREVER_MS) ? 0 : MAX(1, duration / 10),
|
|
};
|
|
|
|
LOG_DBG("Start advertising %d ms", duration);
|
|
|
|
atomic_set_bit(ext_adv->flags, ADV_FLAG_UPDATE_PARAMS);
|
|
|
|
return adv_start(ext_adv, param, &start, ad, ad_len, sd, sd_len);
|
|
}
|
|
|
|
int bt_mesh_adv_bt_data_send(uint8_t num_events, uint16_t adv_interval,
|
|
const struct bt_data *ad, size_t ad_len)
|
|
{
|
|
return bt_data_send(advs, num_events, adv_interval, ad, ad_len);
|
|
}
|