| /* |
| * 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; |
| } |
| } |
| |
| /* Use the main adv set for the sending of relay messages. */ |
| if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_RELAY_USING_MAIN_ADV_SET) || |
| CONFIG_BT_MESH_RELAY_ADV_SETS == 0) { |
| (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); |
| } |