You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
496 lines
13 KiB
496 lines
13 KiB
/* |
|
* 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 "adv.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 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_ARRAY(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 bt_data_send(struct bt_mesh_ext_adv *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 (adv->adv_param.interval_min != BT_MESH_ADV_SCAN_UNIT(adv_interval)) { |
|
adv->adv_param.interval_min = BT_MESH_ADV_SCAN_UNIT(adv_interval); |
|
adv->adv_param.interval_max = adv->adv_param.interval_min; |
|
atomic_set_bit(adv->flags, ADV_FLAG_UPDATE_PARAMS); |
|
} |
|
|
|
return adv_start(adv, &adv->adv_param, &start, ad, ad_len, NULL, 0); |
|
} |
|
|
|
static int buf_send(struct bt_mesh_ext_adv *adv, struct net_buf *buf) |
|
{ |
|
uint8_t 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 = BT_MESH_TRANSMIT_INT(BT_MESH_ADV(buf)->xmit); |
|
/* Upper boundary estimate: */ |
|
duration = 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", |
|
num_events, adv_int, duration); |
|
|
|
ad.type = bt_mesh_adv_type[BT_MESH_ADV(buf)->type]; |
|
ad.data_len = buf->len; |
|
ad.data = buf->data; |
|
|
|
err = bt_data_send(adv, num_events, adv_int, &ad, 1); |
|
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 (IS_ENABLED(CONFIG_BT_MESH_PROXY_SOLICITATION) && |
|
!bt_mesh_sol_send()) { |
|
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; |
|
|
|
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) || |
|
(CONFIG_BT_MESH_RELAY_ADV_SETS > 0 && adv->tag == BT_MESH_RELAY_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); |
|
} |
|
|
|
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(&adv_main, num_events, adv_interval, ad, ad_len); |
|
}
|
|
|