Before this change, the bt_mesh_send_cb.end callback was called when all references to the adv were removed. If an implementation kept more references to the adv buffer after calling `bt_mesh_adv_send`, the end callback would not be called when the advertiser finished advertising this adv. With this change, the end callback is always called by the advertiser when the advertisement is finished regardless of the number of references. This allows an implementation to keep the adv buffer for the future use. As an example, pb_adv.c keeps advs for retransmission. Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
409 lines
9.3 KiB
C
409 lines
9.3 KiB
C
/*
|
|
* 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/util.h>
|
|
|
|
#include <zephyr/net/buf.h>
|
|
#include <zephyr/bluetooth/bluetooth.h>
|
|
#include <zephyr/bluetooth/hci.h>
|
|
#include <zephyr/bluetooth/conn.h>
|
|
#include <zephyr/bluetooth/mesh.h>
|
|
|
|
#include "common/bt_str.h"
|
|
|
|
#include "net.h"
|
|
#include "foundation.h"
|
|
#include "beacon.h"
|
|
#include "prov.h"
|
|
#include "proxy.h"
|
|
#include "pb_gatt_srv.h"
|
|
#include "solicitation.h"
|
|
#include "statistic.h"
|
|
|
|
#define LOG_LEVEL CONFIG_BT_MESH_ADV_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(bt_mesh_adv);
|
|
|
|
/* Window and Interval are equal for continuous scanning */
|
|
#define MESH_SCAN_INTERVAL BT_MESH_ADV_SCAN_UNIT(BT_MESH_SCAN_INTERVAL_MS)
|
|
#define MESH_SCAN_WINDOW BT_MESH_ADV_SCAN_UNIT(BT_MESH_SCAN_WINDOW_MS)
|
|
|
|
const uint8_t bt_mesh_adv_type[BT_MESH_ADV_TYPES] = {
|
|
[BT_MESH_ADV_PROV] = BT_DATA_MESH_PROV,
|
|
[BT_MESH_ADV_DATA] = BT_DATA_MESH_MESSAGE,
|
|
[BT_MESH_ADV_BEACON] = BT_DATA_MESH_BEACON,
|
|
[BT_MESH_ADV_URI] = BT_DATA_URI,
|
|
};
|
|
|
|
static bool active_scanning;
|
|
static K_FIFO_DEFINE(bt_mesh_adv_queue);
|
|
static K_FIFO_DEFINE(bt_mesh_relay_queue);
|
|
static K_FIFO_DEFINE(bt_mesh_friend_queue);
|
|
|
|
K_MEM_SLAB_DEFINE_STATIC(local_adv_pool, sizeof(struct bt_mesh_adv),
|
|
CONFIG_BT_MESH_ADV_BUF_COUNT, __alignof__(struct bt_mesh_adv));
|
|
|
|
#if defined(CONFIG_BT_MESH_RELAY_BUF_COUNT)
|
|
K_MEM_SLAB_DEFINE_STATIC(relay_adv_pool, sizeof(struct bt_mesh_adv),
|
|
CONFIG_BT_MESH_RELAY_BUF_COUNT, __alignof__(struct bt_mesh_adv));
|
|
#endif
|
|
|
|
#if defined(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE)
|
|
K_MEM_SLAB_DEFINE_STATIC(friend_adv_pool, sizeof(struct bt_mesh_adv),
|
|
CONFIG_BT_MESH_FRIEND_LPN_COUNT, __alignof__(struct bt_mesh_adv));
|
|
#endif
|
|
|
|
void bt_mesh_adv_send_start(uint16_t duration, int err, struct bt_mesh_adv_ctx *ctx)
|
|
{
|
|
if (!ctx->started) {
|
|
ctx->started = 1;
|
|
|
|
if (ctx->cb && ctx->cb->start) {
|
|
ctx->cb->start(duration, err, ctx->cb_data);
|
|
}
|
|
|
|
if (err) {
|
|
ctx->cb = NULL;
|
|
} else if (IS_ENABLED(CONFIG_BT_MESH_STATISTIC)) {
|
|
bt_mesh_stat_succeeded_count(ctx);
|
|
}
|
|
}
|
|
}
|
|
|
|
void bt_mesh_adv_send_end(int err, struct bt_mesh_adv_ctx const *ctx)
|
|
{
|
|
if (ctx->started && ctx->cb && ctx->cb->end) {
|
|
ctx->cb->end(err, ctx->cb_data);
|
|
}
|
|
}
|
|
|
|
static struct bt_mesh_adv *adv_create_from_pool(struct k_mem_slab *buf_pool,
|
|
enum bt_mesh_adv_type type,
|
|
enum bt_mesh_adv_tag tag,
|
|
uint8_t xmit, k_timeout_t timeout)
|
|
{
|
|
struct bt_mesh_adv_ctx *ctx;
|
|
struct bt_mesh_adv *adv;
|
|
int err;
|
|
|
|
if (atomic_test_bit(bt_mesh.flags, BT_MESH_SUSPENDED)) {
|
|
LOG_WRN("Refusing to allocate buffer while suspended");
|
|
return NULL;
|
|
}
|
|
|
|
err = k_mem_slab_alloc(buf_pool, (void **)&adv, timeout);
|
|
if (err) {
|
|
return NULL;
|
|
}
|
|
|
|
adv->__ref = 1;
|
|
|
|
net_buf_simple_init_with_data(&adv->b, adv->__bufs, BT_MESH_ADV_DATA_SIZE);
|
|
net_buf_simple_reset(&adv->b);
|
|
|
|
ctx = &adv->ctx;
|
|
|
|
(void)memset(ctx, 0, sizeof(*ctx));
|
|
|
|
ctx->type = type;
|
|
ctx->tag = tag;
|
|
ctx->xmit = xmit;
|
|
|
|
return adv;
|
|
}
|
|
|
|
struct bt_mesh_adv *bt_mesh_adv_ref(struct bt_mesh_adv *adv)
|
|
{
|
|
__ASSERT_NO_MSG(adv->__ref < UINT8_MAX);
|
|
|
|
adv->__ref++;
|
|
|
|
return adv;
|
|
}
|
|
|
|
void bt_mesh_adv_unref(struct bt_mesh_adv *adv)
|
|
{
|
|
__ASSERT_NO_MSG(adv->__ref > 0);
|
|
|
|
if (--adv->__ref > 0) {
|
|
return;
|
|
}
|
|
|
|
struct k_mem_slab *slab = &local_adv_pool;
|
|
|
|
#if defined(CONFIG_BT_MESH_RELAY)
|
|
if (adv->ctx.tag == BT_MESH_ADV_TAG_RELAY) {
|
|
slab = &relay_adv_pool;
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE)
|
|
if (adv->ctx.tag == BT_MESH_ADV_TAG_FRIEND) {
|
|
slab = &friend_adv_pool;
|
|
}
|
|
#endif
|
|
|
|
k_mem_slab_free(slab, (void *)adv);
|
|
}
|
|
|
|
struct bt_mesh_adv *bt_mesh_adv_create(enum bt_mesh_adv_type type,
|
|
enum bt_mesh_adv_tag tag,
|
|
uint8_t xmit, k_timeout_t timeout)
|
|
{
|
|
#if defined(CONFIG_BT_MESH_RELAY)
|
|
if (tag == BT_MESH_ADV_TAG_RELAY) {
|
|
return adv_create_from_pool(&relay_adv_pool,
|
|
type, tag, xmit, timeout);
|
|
}
|
|
#endif
|
|
|
|
#if defined(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE)
|
|
if (tag == BT_MESH_ADV_TAG_FRIEND) {
|
|
return adv_create_from_pool(&friend_adv_pool,
|
|
type, tag, xmit, timeout);
|
|
}
|
|
#endif
|
|
|
|
return adv_create_from_pool(&local_adv_pool, type,
|
|
tag, xmit, timeout);
|
|
}
|
|
|
|
static struct bt_mesh_adv *process_events(struct k_poll_event *ev, int count)
|
|
{
|
|
for (; count; ev++, count--) {
|
|
LOG_DBG("ev->state %u", ev->state);
|
|
|
|
switch (ev->state) {
|
|
case K_POLL_STATE_FIFO_DATA_AVAILABLE:
|
|
return k_fifo_get(ev->fifo, K_NO_WAIT);
|
|
case K_POLL_STATE_NOT_READY:
|
|
case K_POLL_STATE_CANCELLED:
|
|
break;
|
|
default:
|
|
LOG_WRN("Unexpected k_poll event state %u", ev->state);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct bt_mesh_adv *bt_mesh_adv_get(k_timeout_t timeout)
|
|
{
|
|
int err;
|
|
struct k_poll_event events[] = {
|
|
K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_FIFO_DATA_AVAILABLE,
|
|
K_POLL_MODE_NOTIFY_ONLY,
|
|
&bt_mesh_adv_queue,
|
|
0),
|
|
#if defined(CONFIG_BT_MESH_RELAY) && \
|
|
(defined(CONFIG_BT_MESH_ADV_LEGACY) || \
|
|
defined(CONFIG_BT_MESH_ADV_EXT_RELAY_USING_MAIN_ADV_SET) || \
|
|
!(CONFIG_BT_MESH_RELAY_ADV_SETS))
|
|
K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_FIFO_DATA_AVAILABLE,
|
|
K_POLL_MODE_NOTIFY_ONLY,
|
|
&bt_mesh_relay_queue,
|
|
0),
|
|
#endif
|
|
};
|
|
|
|
err = k_poll(events, ARRAY_SIZE(events), timeout);
|
|
if (err) {
|
|
return NULL;
|
|
}
|
|
|
|
return process_events(events, ARRAY_SIZE(events));
|
|
}
|
|
|
|
struct bt_mesh_adv *bt_mesh_adv_get_by_tag(enum bt_mesh_adv_tag_bit tags, k_timeout_t timeout)
|
|
{
|
|
if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE) &&
|
|
tags & BT_MESH_ADV_TAG_BIT_FRIEND) {
|
|
return k_fifo_get(&bt_mesh_friend_queue, timeout);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_MESH_RELAY) &&
|
|
!(tags & BT_MESH_ADV_TAG_BIT_LOCAL)) {
|
|
return k_fifo_get(&bt_mesh_relay_queue, timeout);
|
|
}
|
|
|
|
return bt_mesh_adv_get(timeout);
|
|
}
|
|
|
|
void bt_mesh_adv_get_cancel(void)
|
|
{
|
|
LOG_DBG("");
|
|
|
|
k_fifo_cancel_wait(&bt_mesh_adv_queue);
|
|
|
|
if (IS_ENABLED(CONFIG_BT_MESH_RELAY)) {
|
|
k_fifo_cancel_wait(&bt_mesh_relay_queue);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE)) {
|
|
k_fifo_cancel_wait(&bt_mesh_friend_queue);
|
|
}
|
|
}
|
|
|
|
void bt_mesh_adv_send(struct bt_mesh_adv *adv, const struct bt_mesh_send_cb *cb,
|
|
void *cb_data)
|
|
{
|
|
LOG_DBG("type 0x%02x len %u: %s", adv->ctx.type, adv->b.len,
|
|
bt_hex(adv->b.data, adv->b.len));
|
|
|
|
adv->ctx.cb = cb;
|
|
adv->ctx.cb_data = cb_data;
|
|
adv->ctx.busy = 1U;
|
|
|
|
if (IS_ENABLED(CONFIG_BT_MESH_STATISTIC)) {
|
|
bt_mesh_stat_planned_count(&adv->ctx);
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE) &&
|
|
adv->ctx.tag == BT_MESH_ADV_TAG_FRIEND) {
|
|
k_fifo_put(&bt_mesh_friend_queue, bt_mesh_adv_ref(adv));
|
|
bt_mesh_adv_friend_ready();
|
|
return;
|
|
}
|
|
|
|
if ((IS_ENABLED(CONFIG_BT_MESH_RELAY) &&
|
|
adv->ctx.tag == BT_MESH_ADV_TAG_RELAY) ||
|
|
(IS_ENABLED(CONFIG_BT_MESH_PB_ADV_USE_RELAY_SETS) &&
|
|
adv->ctx.tag == BT_MESH_ADV_TAG_PROV)) {
|
|
k_fifo_put(&bt_mesh_relay_queue, bt_mesh_adv_ref(adv));
|
|
bt_mesh_adv_relay_ready();
|
|
return;
|
|
}
|
|
|
|
k_fifo_put(&bt_mesh_adv_queue, bt_mesh_adv_ref(adv));
|
|
bt_mesh_adv_local_ready();
|
|
}
|
|
|
|
int bt_mesh_adv_gatt_send(void)
|
|
{
|
|
if (bt_mesh_is_provisioned()) {
|
|
if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) {
|
|
LOG_DBG("Proxy Advertising");
|
|
return bt_mesh_proxy_adv_start();
|
|
}
|
|
} else if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT)) {
|
|
LOG_DBG("PB-GATT Advertising");
|
|
return bt_mesh_pb_gatt_srv_adv_start();
|
|
}
|
|
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static void bt_mesh_scan_cb(const bt_addr_le_t *addr, int8_t rssi,
|
|
uint8_t adv_type, struct net_buf_simple *buf)
|
|
{
|
|
if (adv_type != BT_GAP_ADV_TYPE_ADV_NONCONN_IND) {
|
|
return;
|
|
}
|
|
|
|
LOG_DBG("len %u: %s", buf->len, bt_hex(buf->data, buf->len));
|
|
|
|
while (buf->len > 1) {
|
|
struct net_buf_simple_state state;
|
|
uint8_t len, type;
|
|
|
|
len = net_buf_simple_pull_u8(buf);
|
|
/* Check for early termination */
|
|
if (len == 0U) {
|
|
return;
|
|
}
|
|
|
|
if (len > buf->len) {
|
|
LOG_WRN("AD malformed");
|
|
return;
|
|
}
|
|
|
|
net_buf_simple_save(buf, &state);
|
|
|
|
type = net_buf_simple_pull_u8(buf);
|
|
|
|
buf->len = len - 1;
|
|
|
|
switch (type) {
|
|
case BT_DATA_MESH_MESSAGE:
|
|
bt_mesh_net_recv(buf, rssi, BT_MESH_NET_IF_ADV);
|
|
break;
|
|
#if defined(CONFIG_BT_MESH_PB_ADV)
|
|
case BT_DATA_MESH_PROV:
|
|
bt_mesh_pb_adv_recv(buf);
|
|
break;
|
|
#endif
|
|
case BT_DATA_MESH_BEACON:
|
|
bt_mesh_beacon_recv(buf);
|
|
break;
|
|
case BT_DATA_UUID16_SOME:
|
|
/* Fall through */
|
|
case BT_DATA_UUID16_ALL:
|
|
if (IS_ENABLED(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV)) {
|
|
/* Restore buffer with Solicitation PDU */
|
|
net_buf_simple_restore(buf, &state);
|
|
bt_mesh_sol_recv(buf, len - 1);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
net_buf_simple_restore(buf, &state);
|
|
net_buf_simple_pull(buf, len);
|
|
}
|
|
}
|
|
|
|
int bt_mesh_scan_active_set(bool active)
|
|
{
|
|
if (active_scanning == active) {
|
|
return 0;
|
|
}
|
|
|
|
active_scanning = active;
|
|
bt_mesh_scan_disable();
|
|
return bt_mesh_scan_enable();
|
|
}
|
|
|
|
int bt_mesh_scan_enable(void)
|
|
{
|
|
struct bt_le_scan_param scan_param = {
|
|
.type = active_scanning ? BT_HCI_LE_SCAN_ACTIVE :
|
|
BT_HCI_LE_SCAN_PASSIVE,
|
|
.interval = MESH_SCAN_INTERVAL,
|
|
.window = MESH_SCAN_WINDOW
|
|
};
|
|
int err;
|
|
|
|
LOG_DBG("");
|
|
|
|
err = bt_le_scan_start(&scan_param, bt_mesh_scan_cb);
|
|
if (err && err != -EALREADY) {
|
|
LOG_ERR("starting scan failed (err %d)", err);
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int bt_mesh_scan_disable(void)
|
|
{
|
|
int err;
|
|
|
|
LOG_DBG("");
|
|
|
|
err = bt_le_scan_stop();
|
|
if (err && err != -EALREADY) {
|
|
LOG_ERR("stopping scan failed (err %d)", err);
|
|
return err;
|
|
}
|
|
|
|
return 0;
|
|
}
|