The purpose of this commit is to facilitate future improvements to the LPN and friendship feature. By being able to identify friendship related messages on the advertising layer, it will be possible to treat these in a specific manner so that message exchange between LPN and friend devices can be conducted in a more power efficient manner. This commit adds the following: - A separate tag for friendship related messages - A optional separate advertising set for friend related messages Signed-off-by: Anders Storrø <anders.storro@nordicsemi.no>
480 lines
12 KiB
C
480 lines
12 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/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 "adv.h"
|
|
#include "net.h"
|
|
#include "proxy.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 send-call has been scheduled. */
|
|
ADV_FLAG_SCHEDULED,
|
|
/** 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,
|
|
|
|
/* Number of adv flags. */
|
|
ADV_FLAGS_NUM
|
|
};
|
|
|
|
struct bt_mesh_ext_adv {
|
|
uint8_t tag;
|
|
ATOMIC_DEFINE(flags, ADV_FLAGS_NUM);
|
|
struct bt_le_ext_adv *instance;
|
|
struct net_buf *buf;
|
|
uint64_t timestamp;
|
|
struct k_work_delayable 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 *adv);
|
|
|
|
static STRUCT_SECTION_ITERABLE(bt_mesh_ext_adv, adv_main) = {
|
|
.tag = (
|
|
#if !defined(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE)
|
|
BT_MESH_FRIEND_ADV |
|
|
#endif
|
|
#if !defined(CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE)
|
|
BT_MESH_PROXY_ADV |
|
|
#endif /* !CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE */
|
|
#if defined(CONFIG_BT_MESH_ADV_EXT_RELAY_USING_MAIN_ADV_SET)
|
|
BT_MESH_RELAY_ADV |
|
|
#endif /* CONFIG_BT_MESH_ADV_EXT_RELAY_USING_MAIN_ADV_SET */
|
|
BT_MESH_LOCAL_ADV),
|
|
|
|
.work = Z_WORK_DELAYABLE_INITIALIZER(send_pending_adv),
|
|
};
|
|
|
|
#if CONFIG_BT_MESH_RELAY_ADV_SETS
|
|
static STRUCT_SECTION_ITERABLE(bt_mesh_ext_adv, adv_relay[CONFIG_BT_MESH_RELAY_ADV_SETS]) = {
|
|
[0 ... CONFIG_BT_MESH_RELAY_ADV_SETS - 1] = {
|
|
.tag = BT_MESH_RELAY_ADV,
|
|
.work = Z_WORK_DELAYABLE_INITIALIZER(send_pending_adv),
|
|
}
|
|
};
|
|
#endif /* CONFIG_BT_MESH_RELAY_ADV_SETS */
|
|
|
|
#if defined(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE)
|
|
#define ADV_EXT_FRIEND 1
|
|
static STRUCT_SECTION_ITERABLE(bt_mesh_ext_adv, adv_friend) = {
|
|
.tag = BT_MESH_FRIEND_ADV,
|
|
.work = Z_WORK_DELAYABLE_INITIALIZER(send_pending_adv),
|
|
};
|
|
#else /* CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE */
|
|
#define ADV_EXT_FRIEND 0
|
|
#endif /* CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE */
|
|
|
|
#if defined(CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE)
|
|
#define ADV_EXT_GATT 1
|
|
static STRUCT_SECTION_ITERABLE(bt_mesh_ext_adv, adv_gatt) = {
|
|
.tag = BT_MESH_PROXY_ADV,
|
|
.work = Z_WORK_DELAYABLE_INITIALIZER(send_pending_adv),
|
|
};
|
|
#else /* CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE */
|
|
#define ADV_EXT_GATT 0
|
|
#endif /* CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE */
|
|
|
|
#define BT_MESH_ADV_COUNT (1 + CONFIG_BT_MESH_RELAY_ADV_SETS + ADV_EXT_FRIEND + ADV_EXT_GATT)
|
|
|
|
BUILD_ASSERT(CONFIG_BT_EXT_ADV_MAX_ADV_SET >= BT_MESH_ADV_COUNT,
|
|
"Insufficient adv instances");
|
|
|
|
static inline struct bt_mesh_ext_adv *relay_adv_get(void)
|
|
{
|
|
#if CONFIG_BT_MESH_RELAY_ADV_SETS
|
|
return adv_relay;
|
|
#else /* !CONFIG_BT_MESH_RELAY_ADV_SETS */
|
|
return &adv_main;
|
|
#endif /* CONFIG_BT_MESH_RELAY_ADV_SETS */
|
|
}
|
|
|
|
static inline struct bt_mesh_ext_adv *gatt_adv_get(void)
|
|
{
|
|
#if defined(CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE)
|
|
return &adv_gatt;
|
|
#else /* !CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE */
|
|
return &adv_main;
|
|
#endif /* CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE */
|
|
}
|
|
|
|
static int adv_start(struct bt_mesh_ext_adv *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 (!adv->instance) {
|
|
LOG_ERR("Mesh advertiser not enabled");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (atomic_test_and_set_bit(adv->flags, ADV_FLAG_ACTIVE)) {
|
|
LOG_ERR("Advertiser is busy");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (atomic_test_bit(adv->flags, ADV_FLAG_UPDATE_PARAMS)) {
|
|
err = bt_le_ext_adv_update_param(adv->instance, param);
|
|
if (err) {
|
|
LOG_ERR("Failed updating adv params: %d", err);
|
|
atomic_clear_bit(adv->flags, ADV_FLAG_ACTIVE);
|
|
return err;
|
|
}
|
|
|
|
atomic_set_bit_to(adv->flags, ADV_FLAG_UPDATE_PARAMS,
|
|
param != &adv->adv_param);
|
|
}
|
|
|
|
err = bt_le_ext_adv_set_data(adv->instance, ad, ad_len, sd, sd_len);
|
|
if (err) {
|
|
LOG_ERR("Failed setting adv data: %d", err);
|
|
atomic_clear_bit(adv->flags, ADV_FLAG_ACTIVE);
|
|
return err;
|
|
}
|
|
|
|
adv->timestamp = k_uptime_get();
|
|
|
|
err = bt_le_ext_adv_start(adv->instance, start);
|
|
if (err) {
|
|
LOG_ERR("Advertising failed: err %d", err);
|
|
atomic_clear_bit(adv->flags, ADV_FLAG_ACTIVE);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int buf_send(struct bt_mesh_ext_adv *adv, struct net_buf *buf)
|
|
{
|
|
struct bt_le_ext_adv_start_param start = {
|
|
.num_events =
|
|
BT_MESH_TRANSMIT_COUNT(BT_MESH_ADV(buf)->xmit) + 1,
|
|
};
|
|
uint16_t duration, adv_int;
|
|
struct bt_data ad;
|
|
int err;
|
|
|
|
adv_int = MAX(ADV_INT_FAST_MS,
|
|
BT_MESH_TRANSMIT_INT(BT_MESH_ADV(buf)->xmit));
|
|
/* Upper boundary estimate: */
|
|
duration = start.num_events * (adv_int + 10);
|
|
|
|
LOG_DBG("type %u len %u: %s", BT_MESH_ADV(buf)->type, buf->len,
|
|
bt_hex(buf->data, buf->len));
|
|
LOG_DBG("count %u interval %ums duration %ums",
|
|
BT_MESH_TRANSMIT_COUNT(BT_MESH_ADV(buf)->xmit) + 1, adv_int, duration);
|
|
|
|
ad.type = bt_mesh_adv_type[BT_MESH_ADV(buf)->type];
|
|
ad.data_len = buf->len;
|
|
ad.data = buf->data;
|
|
|
|
/* Only update advertising parameters if they're different */
|
|
if (adv->adv_param.interval_min != BT_MESH_ADV_SCAN_UNIT(adv_int)) {
|
|
adv->adv_param.interval_min = BT_MESH_ADV_SCAN_UNIT(adv_int);
|
|
adv->adv_param.interval_max = adv->adv_param.interval_min;
|
|
atomic_set_bit(adv->flags, ADV_FLAG_UPDATE_PARAMS);
|
|
}
|
|
|
|
err = adv_start(adv, &adv->adv_param, &start, &ad, 1, NULL, 0);
|
|
if (!err) {
|
|
adv->buf = net_buf_ref(buf);
|
|
}
|
|
|
|
bt_mesh_adv_send_start(duration, err, BT_MESH_ADV(buf));
|
|
|
|
return err;
|
|
}
|
|
|
|
static const char *adv_tag_to_str(enum bt_mesh_adv_tag tag)
|
|
{
|
|
if (tag & BT_MESH_LOCAL_ADV) {
|
|
return "local adv";
|
|
} else if (tag & BT_MESH_PROXY_ADV) {
|
|
return "proxy adv";
|
|
} else if (tag & BT_MESH_RELAY_ADV) {
|
|
return "relay adv";
|
|
} else if (tag & BT_MESH_FRIEND_ADV) {
|
|
return "friend adv";
|
|
} else {
|
|
return "(unknown tag)";
|
|
}
|
|
}
|
|
|
|
static void send_pending_adv(struct k_work *work)
|
|
{
|
|
struct bt_mesh_ext_adv *adv;
|
|
struct net_buf *buf;
|
|
int err;
|
|
|
|
adv = CONTAINER_OF(work, struct bt_mesh_ext_adv, work.work);
|
|
|
|
if (atomic_test_and_clear_bit(adv->flags, ADV_FLAG_SENT)) {
|
|
/* Calling k_uptime_delta on a timestamp moves it to the current time.
|
|
* This is essential here, as schedule_send() uses the end of the event
|
|
* as a reference to avoid sending the next advertisement too soon.
|
|
*/
|
|
int64_t duration = k_uptime_delta(&adv->timestamp);
|
|
|
|
LOG_DBG("Advertising stopped after %u ms for (%u) %s", (uint32_t)duration, adv->tag,
|
|
adv_tag_to_str(adv->tag));
|
|
|
|
atomic_clear_bit(adv->flags, ADV_FLAG_ACTIVE);
|
|
atomic_clear_bit(adv->flags, ADV_FLAG_PROXY);
|
|
|
|
if (adv->buf) {
|
|
net_buf_unref(adv->buf);
|
|
adv->buf = NULL;
|
|
}
|
|
|
|
(void)schedule_send(adv);
|
|
|
|
return;
|
|
}
|
|
|
|
atomic_clear_bit(adv->flags, ADV_FLAG_SCHEDULED);
|
|
|
|
while ((buf = bt_mesh_adv_buf_get_by_tag(adv->tag, K_NO_WAIT))) {
|
|
/* busy == 0 means this was canceled */
|
|
if (!BT_MESH_ADV(buf)->busy) {
|
|
net_buf_unref(buf);
|
|
continue;
|
|
}
|
|
|
|
BT_MESH_ADV(buf)->busy = 0U;
|
|
err = buf_send(adv, buf);
|
|
|
|
net_buf_unref(buf);
|
|
|
|
if (!err) {
|
|
return; /* Wait for advertising to finish */
|
|
}
|
|
}
|
|
|
|
if (!IS_ENABLED(CONFIG_BT_MESH_GATT_SERVER) ||
|
|
!(adv->tag & BT_MESH_PROXY_ADV)) {
|
|
return;
|
|
}
|
|
|
|
if (!bt_mesh_adv_gatt_send()) {
|
|
atomic_set_bit(adv->flags, ADV_FLAG_PROXY);
|
|
}
|
|
|
|
if (atomic_test_and_clear_bit(adv->flags, ADV_FLAG_SCHEDULE_PENDING)) {
|
|
schedule_send(adv);
|
|
}
|
|
|
|
}
|
|
|
|
static bool schedule_send(struct bt_mesh_ext_adv *adv)
|
|
{
|
|
uint64_t timestamp;
|
|
int64_t delta;
|
|
|
|
if (!adv) {
|
|
return false;
|
|
}
|
|
|
|
timestamp = adv->timestamp;
|
|
|
|
if (atomic_test_and_clear_bit(adv->flags, ADV_FLAG_PROXY)) {
|
|
(void)bt_le_ext_adv_stop(adv->instance);
|
|
atomic_clear_bit(adv->flags, ADV_FLAG_ACTIVE);
|
|
}
|
|
|
|
if (atomic_test_bit(adv->flags, ADV_FLAG_ACTIVE)) {
|
|
atomic_set_bit(adv->flags, ADV_FLAG_SCHEDULE_PENDING);
|
|
return false;
|
|
} else if (atomic_test_and_set_bit(adv->flags, ADV_FLAG_SCHEDULED)) {
|
|
return false;
|
|
}
|
|
|
|
atomic_clear_bit(adv->flags, ADV_FLAG_SCHEDULE_PENDING);
|
|
|
|
if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE) && adv->tag & BT_MESH_FRIEND_ADV) {
|
|
k_work_reschedule(&adv->work, K_NO_WAIT);
|
|
} else {
|
|
/* The controller will send the next advertisement immediately.
|
|
* Introduce a delay here to avoid sending the next mesh packet closer
|
|
* to the previous packet than what's permitted by the specification.
|
|
*/
|
|
delta = k_uptime_delta(×tamp);
|
|
k_work_reschedule(&adv->work, K_MSEC(ADV_INT_FAST_MS - delta));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void bt_mesh_adv_gatt_update(void)
|
|
{
|
|
(void)schedule_send(gatt_adv_get());
|
|
}
|
|
|
|
void bt_mesh_adv_buf_local_ready(void)
|
|
{
|
|
(void)schedule_send(&adv_main);
|
|
}
|
|
|
|
void bt_mesh_adv_buf_relay_ready(void)
|
|
{
|
|
struct bt_mesh_ext_adv *adv = relay_adv_get();
|
|
|
|
for (int i = 0; i < CONFIG_BT_MESH_RELAY_ADV_SETS; i++) {
|
|
if (schedule_send(&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(&adv_main);
|
|
}
|
|
}
|
|
|
|
void bt_mesh_adv_buf_friend_ready(void)
|
|
{
|
|
#if defined(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE)
|
|
(void)schedule_send(&adv_friend);
|
|
#endif
|
|
}
|
|
|
|
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
|
|
};
|
|
STRUCT_SECTION_FOREACH(bt_mesh_ext_adv, adv) {
|
|
(void)memcpy(&adv->adv_param, &adv_param, sizeof(adv_param));
|
|
}
|
|
}
|
|
|
|
static struct bt_mesh_ext_adv *adv_instance_find(struct bt_le_ext_adv *instance)
|
|
{
|
|
STRUCT_SECTION_FOREACH(bt_mesh_ext_adv, adv) {
|
|
if (adv->instance == instance) {
|
|
return adv;
|
|
}
|
|
}
|
|
|
|
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 *adv = adv_instance_find(instance);
|
|
|
|
if (!adv) {
|
|
LOG_WRN("Unexpected adv instance");
|
|
return;
|
|
}
|
|
|
|
if (!atomic_test_bit(adv->flags, ADV_FLAG_ACTIVE)) {
|
|
return;
|
|
}
|
|
|
|
atomic_set_bit(adv->flags, ADV_FLAG_SENT);
|
|
|
|
k_work_submit(&adv->work.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 *adv = gatt_adv_get();
|
|
|
|
if (atomic_test_and_clear_bit(adv->flags, ADV_FLAG_PROXY)) {
|
|
atomic_clear_bit(adv->flags, ADV_FLAG_ACTIVE);
|
|
(void)schedule_send(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 (adv_main.instance) {
|
|
/* Already initialized */
|
|
return 0;
|
|
}
|
|
|
|
|
|
STRUCT_SECTION_FOREACH(bt_mesh_ext_adv, adv) {
|
|
err = bt_le_ext_adv_create(&adv->adv_param, &adv_cb,
|
|
&adv->instance);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
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 *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(adv->flags, ADV_FLAG_UPDATE_PARAMS);
|
|
|
|
return adv_start(adv, param, &start, ad, ad_len, sd, sd_len);
|
|
}
|