| /* |
| * Copyright (c) 2018 Nordic Semiconductor ASA |
| * Copyright (c) 2017 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr.h> |
| #include <debug/stack.h> |
| |
| #include <net/buf.h> |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/mesh.h> |
| |
| #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_MESH_DEBUG_ADV) |
| #define LOG_MODULE_NAME bt_mesh_adv_ext |
| #include "common/log.h" |
| |
| #include "host/hci_core.h" |
| |
| #include "adv.h" |
| #include "net.h" |
| #include "proxy.h" |
| |
| /* Convert from ms to 0.625ms units */ |
| #define ADV_INT_FAST_MS 20 |
| |
| static 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 |
| }; |
| |
| enum { |
| /** Controller is currently advertising */ |
| ADV_FLAG_ACTIVE, |
| /** Currently performing proxy advertising */ |
| ADV_FLAG_PROXY, |
| /** The send-call has been scheduled. */ |
| ADV_FLAG_SCHEDULED, |
| /** 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 |
| }; |
| |
| static struct { |
| ATOMIC_DEFINE(flags, ADV_FLAGS_NUM); |
| struct bt_le_ext_adv *instance; |
| struct net_buf *buf; |
| uint64_t timestamp; |
| struct k_work_delayable work; |
| } adv; |
| |
| static int adv_start(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) { |
| BT_ERR("Mesh advertiser not enabled"); |
| return -ENODEV; |
| } |
| |
| if (atomic_test_and_set_bit(adv.flags, ADV_FLAG_ACTIVE)) { |
| BT_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) { |
| BT_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_param); |
| } |
| |
| err = bt_le_ext_adv_set_data(adv.instance, ad, ad_len, sd, sd_len); |
| if (err) { |
| BT_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) { |
| BT_ERR("Advertising failed: err %d", err); |
| atomic_clear_bit(adv.flags, ADV_FLAG_ACTIVE); |
| } |
| |
| return err; |
| } |
| |
| static int buf_send(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); |
| |
| BT_DBG("type %u len %u: %s", BT_MESH_ADV(buf)->type, |
| buf->len, bt_hex(buf->data, buf->len)); |
| BT_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_param.interval_min != BT_MESH_ADV_SCAN_UNIT(adv_int)) { |
| adv_param.interval_min = BT_MESH_ADV_SCAN_UNIT(adv_int); |
| adv_param.interval_max = adv_param.interval_min; |
| atomic_set_bit(adv.flags, ADV_FLAG_UPDATE_PARAMS); |
| } |
| |
| err = adv_start(&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 void send_pending_adv(struct k_work *work) |
| { |
| struct net_buf *buf; |
| int err; |
| |
| atomic_clear_bit(adv.flags, ADV_FLAG_SCHEDULED); |
| |
| while ((buf = net_buf_get(&bt_mesh_adv_queue, 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(buf); |
| |
| net_buf_unref(buf); |
| |
| if (!err) { |
| return; /* Wait for advertising to finish */ |
| } |
| } |
| |
| /* No more pending buffers */ |
| if (IS_ENABLED(CONFIG_BT_MESH_GATT_SERVER)) { |
| BT_DBG("Proxy Advertising"); |
| err = bt_mesh_proxy_adv_start(); |
| if (!err) { |
| atomic_set_bit(adv.flags, ADV_FLAG_PROXY); |
| } |
| } |
| } |
| |
| static void schedule_send(void) |
| { |
| uint64_t timestamp = adv.timestamp; |
| int64_t delta; |
| |
| if (atomic_test_and_clear_bit(adv.flags, ADV_FLAG_PROXY)) { |
| 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_test_and_set_bit(adv.flags, ADV_FLAG_SCHEDULED)) { |
| return; |
| } |
| |
| /* 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)); |
| } |
| |
| void bt_mesh_adv_update(void) |
| { |
| BT_DBG(""); |
| |
| schedule_send(); |
| } |
| |
| void bt_mesh_adv_buf_ready(void) |
| { |
| schedule_send(); |
| } |
| |
| void bt_mesh_adv_init(void) |
| { |
| k_work_init_delayable(&adv.work, send_pending_adv); |
| } |
| |
| static void adv_sent(struct bt_le_ext_adv *instance, |
| struct bt_le_ext_adv_sent_info *info) |
| { |
| /* 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); |
| |
| BT_DBG("Advertising stopped after %u ms", (uint32_t)duration); |
| |
| atomic_clear_bit(adv.flags, ADV_FLAG_ACTIVE); |
| |
| if (!atomic_test_and_clear_bit(adv.flags, ADV_FLAG_PROXY)) { |
| net_buf_unref(adv.buf); |
| } |
| |
| schedule_send(); |
| } |
| |
| static void connected(struct bt_le_ext_adv *instance, |
| struct bt_le_ext_adv_connected_info *info) |
| { |
| if (atomic_test_and_clear_bit(adv.flags, ADV_FLAG_PROXY)) { |
| atomic_clear_bit(adv.flags, ADV_FLAG_ACTIVE); |
| schedule_send(); |
| } |
| } |
| |
| int bt_mesh_adv_enable(void) |
| { |
| static const struct bt_le_ext_adv_cb adv_cb = { |
| .sent = adv_sent, |
| .connected = connected, |
| }; |
| |
| if (adv.instance) { |
| /* Already initialized */ |
| return 0; |
| } |
| |
| return bt_le_ext_adv_create(&adv_param, &adv_cb, &adv.instance); |
| } |
| |
| int bt_mesh_adv_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_le_ext_adv_start_param start = { |
| /* Timeout is set in 10 ms steps, with 0 indicating "forever" */ |
| .timeout = (duration == SYS_FOREVER_MS) ? 0 : (duration / 10), |
| }; |
| |
| BT_DBG("Start advertising %d ms", duration); |
| |
| atomic_set_bit(adv.flags, ADV_FLAG_UPDATE_PARAMS); |
| |
| return adv_start(param, &start, ad, ad_len, sd, sd_len); |
| } |