zephyr/subsys/bluetooth/mesh/adv_ext.c

559 lines
14 KiB
C
Raw Normal View History

/*
* 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,
Bluetooth: Mesh: Allow to suspend mesh from bt_mesh_send_cb callbacks This commit allows to suspend the mesh stack from `bt_mesh_send_cb` callbacks by removing the deadlock caused by `k_work_flush` in the extended advertiser. In case of the extended advertiser there are 2 cases: - when the `bt_mesh_adv_disable` is called from any of `bt_mesh_send_cb` callbacks which are called from the advertiser work item, or - when it is called from any other context. When it is called from `bt_mesh_send_cb` callbacks, since these callbacks are called from the delayable work which is running on the system workqueue, the advertiser can check the current context and its work state. If the function is called from the advertiser work, it can disable the advertising set straight away because all ble host APIs have already been called in `adv_start` function. Before sending anything else, the advertiser checks the `instance` value in `adv_start` function, which is also reset to NULL in `bt_mesh_adv_disable` call, and aborts all next advertisements. The `ADV_FLAG_SUSPENDING` tells the advertiser work to abort processing while `bt_mesh_adv_disable` function didn't finish stopping advertising set. This can happen if the work has been already scheduled and the schedler ran it while sleeping inside the `bt_le_ext_adv_stop` or `bt_le_ext_adv_disable` functions. When `bt_mesh_adv_disable` is called from any other context or from the system workqueue but not from the advertiser work, then `k_work_flush` can be called safely as it won't cause any deadlocks. The `adv_sent` function is inside the `bt_mesh_adv_disable` function to schedule the advertiser work (`send_pending_adv`) and abort all pending advertisements that have been already added to the pool. In case of the legacy advertiser, if the `bt_mesh_adv_disable` is called form the advertiser thread (this happens when it is called from `bt_mesh_send_cb.start` or `bt_mesh_send_cb.end` callbacks), then `k_thread_join` returns `-EDEADLK`. But the `enabled` flag is set to false and the thread will abort the current advertisement and the pending advertisements. Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
2024-02-01 11:38:18 +01:00
/** 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);
Bluetooth: Mesh: Allow to suspend mesh from bt_mesh_send_cb callbacks This commit allows to suspend the mesh stack from `bt_mesh_send_cb` callbacks by removing the deadlock caused by `k_work_flush` in the extended advertiser. In case of the extended advertiser there are 2 cases: - when the `bt_mesh_adv_disable` is called from any of `bt_mesh_send_cb` callbacks which are called from the advertiser work item, or - when it is called from any other context. When it is called from `bt_mesh_send_cb` callbacks, since these callbacks are called from the delayable work which is running on the system workqueue, the advertiser can check the current context and its work state. If the function is called from the advertiser work, it can disable the advertising set straight away because all ble host APIs have already been called in `adv_start` function. Before sending anything else, the advertiser checks the `instance` value in `adv_start` function, which is also reset to NULL in `bt_mesh_adv_disable` call, and aborts all next advertisements. The `ADV_FLAG_SUSPENDING` tells the advertiser work to abort processing while `bt_mesh_adv_disable` function didn't finish stopping advertising set. This can happen if the work has been already scheduled and the schedler ran it while sleeping inside the `bt_le_ext_adv_stop` or `bt_le_ext_adv_disable` functions. When `bt_mesh_adv_disable` is called from any other context or from the system workqueue but not from the advertiser work, then `k_work_flush` can be called safely as it won't cause any deadlocks. The `adv_sent` function is inside the `bt_mesh_adv_disable` function to schedule the advertiser work (`send_pending_adv`) and abort all pending advertisements that have been already added to the pool. In case of the legacy advertiser, if the `bt_mesh_adv_disable` is called form the advertiser thread (this happens when it is called from `bt_mesh_send_cb.start` or `bt_mesh_send_cb.end` callbacks), then `k_thread_join` returns `-EDEADLK`. But the `enabled` flag is set to false and the thread will abort the current advertisement and the pending advertisements. Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
2024-02-01 11:38:18 +01:00
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 */
}
}
Bluetooth: Mesh: Allow to suspend mesh from bt_mesh_send_cb callbacks This commit allows to suspend the mesh stack from `bt_mesh_send_cb` callbacks by removing the deadlock caused by `k_work_flush` in the extended advertiser. In case of the extended advertiser there are 2 cases: - when the `bt_mesh_adv_disable` is called from any of `bt_mesh_send_cb` callbacks which are called from the advertiser work item, or - when it is called from any other context. When it is called from `bt_mesh_send_cb` callbacks, since these callbacks are called from the delayable work which is running on the system workqueue, the advertiser can check the current context and its work state. If the function is called from the advertiser work, it can disable the advertising set straight away because all ble host APIs have already been called in `adv_start` function. Before sending anything else, the advertiser checks the `instance` value in `adv_start` function, which is also reset to NULL in `bt_mesh_adv_disable` call, and aborts all next advertisements. The `ADV_FLAG_SUSPENDING` tells the advertiser work to abort processing while `bt_mesh_adv_disable` function didn't finish stopping advertising set. This can happen if the work has been already scheduled and the schedler ran it while sleeping inside the `bt_le_ext_adv_stop` or `bt_le_ext_adv_disable` functions. When `bt_mesh_adv_disable` is called from any other context or from the system workqueue but not from the advertiser work, then `k_work_flush` can be called safely as it won't cause any deadlocks. The `adv_sent` function is inside the `bt_mesh_adv_disable` function to schedule the advertiser work (`send_pending_adv`) and abort all pending advertisements that have been already added to the pool. In case of the legacy advertiser, if the `bt_mesh_adv_disable` is called form the advertiser thread (this happens when it is called from `bt_mesh_send_cb.start` or `bt_mesh_send_cb.end` callbacks), then `k_thread_join` returns `-EDEADLK`. But the `enabled` flag is set to false and the thread will abort the current advertisement and the pending advertisements. Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
2024-02-01 11:38:18 +01:00
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++) {
Bluetooth: Mesh: Allow to suspend mesh from bt_mesh_send_cb callbacks This commit allows to suspend the mesh stack from `bt_mesh_send_cb` callbacks by removing the deadlock caused by `k_work_flush` in the extended advertiser. In case of the extended advertiser there are 2 cases: - when the `bt_mesh_adv_disable` is called from any of `bt_mesh_send_cb` callbacks which are called from the advertiser work item, or - when it is called from any other context. When it is called from `bt_mesh_send_cb` callbacks, since these callbacks are called from the delayable work which is running on the system workqueue, the advertiser can check the current context and its work state. If the function is called from the advertiser work, it can disable the advertising set straight away because all ble host APIs have already been called in `adv_start` function. Before sending anything else, the advertiser checks the `instance` value in `adv_start` function, which is also reset to NULL in `bt_mesh_adv_disable` call, and aborts all next advertisements. The `ADV_FLAG_SUSPENDING` tells the advertiser work to abort processing while `bt_mesh_adv_disable` function didn't finish stopping advertising set. This can happen if the work has been already scheduled and the schedler ran it while sleeping inside the `bt_le_ext_adv_stop` or `bt_le_ext_adv_disable` functions. When `bt_mesh_adv_disable` is called from any other context or from the system workqueue but not from the advertiser work, then `k_work_flush` can be called safely as it won't cause any deadlocks. The `adv_sent` function is inside the `bt_mesh_adv_disable` function to schedule the advertiser work (`send_pending_adv`) and abort all pending advertisements that have been already added to the pool. In case of the legacy advertiser, if the `bt_mesh_adv_disable` is called form the advertiser thread (this happens when it is called from `bt_mesh_send_cb.start` or `bt_mesh_send_cb.end` callbacks), then `k_thread_join` returns `-EDEADLK`. But the `enabled` flag is set to false and the thread will abort the current advertisement and the pending advertisements. Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
2024-02-01 11:38:18 +01:00
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;
}
Bluetooth: Mesh: Allow to suspend mesh from bt_mesh_send_cb callbacks This commit allows to suspend the mesh stack from `bt_mesh_send_cb` callbacks by removing the deadlock caused by `k_work_flush` in the extended advertiser. In case of the extended advertiser there are 2 cases: - when the `bt_mesh_adv_disable` is called from any of `bt_mesh_send_cb` callbacks which are called from the advertiser work item, or - when it is called from any other context. When it is called from `bt_mesh_send_cb` callbacks, since these callbacks are called from the delayable work which is running on the system workqueue, the advertiser can check the current context and its work state. If the function is called from the advertiser work, it can disable the advertising set straight away because all ble host APIs have already been called in `adv_start` function. Before sending anything else, the advertiser checks the `instance` value in `adv_start` function, which is also reset to NULL in `bt_mesh_adv_disable` call, and aborts all next advertisements. The `ADV_FLAG_SUSPENDING` tells the advertiser work to abort processing while `bt_mesh_adv_disable` function didn't finish stopping advertising set. This can happen if the work has been already scheduled and the schedler ran it while sleeping inside the `bt_le_ext_adv_stop` or `bt_le_ext_adv_disable` functions. When `bt_mesh_adv_disable` is called from any other context or from the system workqueue but not from the advertiser work, then `k_work_flush` can be called safely as it won't cause any deadlocks. The `adv_sent` function is inside the `bt_mesh_adv_disable` function to schedule the advertiser work (`send_pending_adv`) and abort all pending advertisements that have been already added to the pool. In case of the legacy advertiser, if the `bt_mesh_adv_disable` is called form the advertiser thread (this happens when it is called from `bt_mesh_send_cb.start` or `bt_mesh_send_cb.end` callbacks), then `k_thread_join` returns `-EDEADLK`. But the `enabled` flag is set to false and the thread will abort the current advertisement and the pending advertisements. Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
2024-02-01 11:38:18 +01:00
advs[i].instance = NULL;
Bluetooth: Mesh: Allow to suspend mesh from bt_mesh_send_cb callbacks This commit allows to suspend the mesh stack from `bt_mesh_send_cb` callbacks by removing the deadlock caused by `k_work_flush` in the extended advertiser. In case of the extended advertiser there are 2 cases: - when the `bt_mesh_adv_disable` is called from any of `bt_mesh_send_cb` callbacks which are called from the advertiser work item, or - when it is called from any other context. When it is called from `bt_mesh_send_cb` callbacks, since these callbacks are called from the delayable work which is running on the system workqueue, the advertiser can check the current context and its work state. If the function is called from the advertiser work, it can disable the advertising set straight away because all ble host APIs have already been called in `adv_start` function. Before sending anything else, the advertiser checks the `instance` value in `adv_start` function, which is also reset to NULL in `bt_mesh_adv_disable` call, and aborts all next advertisements. The `ADV_FLAG_SUSPENDING` tells the advertiser work to abort processing while `bt_mesh_adv_disable` function didn't finish stopping advertising set. This can happen if the work has been already scheduled and the schedler ran it while sleeping inside the `bt_le_ext_adv_stop` or `bt_le_ext_adv_disable` functions. When `bt_mesh_adv_disable` is called from any other context or from the system workqueue but not from the advertiser work, then `k_work_flush` can be called safely as it won't cause any deadlocks. The `adv_sent` function is inside the `bt_mesh_adv_disable` function to schedule the advertiser work (`send_pending_adv`) and abort all pending advertisements that have been already added to the pool. In case of the legacy advertiser, if the `bt_mesh_adv_disable` is called form the advertiser thread (this happens when it is called from `bt_mesh_send_cb.start` or `bt_mesh_send_cb.end` callbacks), then `k_thread_join` returns `-EDEADLK`. But the `enabled` flag is set to false and the thread will abort the current advertisement and the pending advertisements. Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
2024-02-01 11:38:18 +01:00
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);
}