/* Bluetooth Mesh */ /* * Copyright (c) 2018 Nordic Semiconductor ASA * Copyright (c) 2017 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #define MESH_LOG_MODULE BLE_MESH_ADV_LOG #include "adv.h" #include "net.h" #include "proxy.h" #include "pb_gatt_srv.h" #include "nimble_syscfg.h" #include "host/ble_gap.h" #if MYNEWT_VAL(BLE_MESH_ADV_EXT) /* Convert from ms to 0.625ms units */ #define ADV_INT_FAST_MS 20 #define BT_ID_DEFAULT 0 static struct ble_gap_ext_adv_params adv_param = { .itvl_min = BT_MESH_ADV_SCAN_UNIT(ADV_INT_FAST_MS), .itvl_max = BT_MESH_ADV_SCAN_UNIT(ADV_INT_FAST_MS), }; bool ext_adv_configured = false; 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 os_mbuf *buf; int64_t timestamp; struct k_work_delayable work; } adv; static void schedule_send(void) { int64_t timestamp = adv.timestamp; int64_t delta; if (atomic_test_and_clear_bit(adv.flags, ADV_FLAG_PROXY)) { ble_gap_ext_adv_stop(BT_ID_DEFAULT); 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)); } static int ble_mesh_ext_adv_event_handler(struct ble_gap_event *event, void *arg) { int64_t duration; switch (event->type) { case BLE_GAP_EVENT_CONNECT: if (atomic_test_and_clear_bit(adv.flags, ADV_FLAG_PROXY)) { atomic_clear_bit(adv.flags, ADV_FLAG_ACTIVE); schedule_send(); } break; case BLE_GAP_EVENT_ADV_COMPLETE: /* 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. */ 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(); break; default: return 0; } return 0; } static int adv_start(const struct ble_gap_ext_adv_params *param, uint32_t timeout, const struct bt_data *ad, size_t ad_len, const struct bt_data *sd, size_t sd_len) { int err; struct os_mbuf *ad_data; struct os_mbuf *sd_data; ad_data = os_msys_get_pkthdr(BLE_HS_ADV_MAX_SZ, 0); assert(ad_data); sd_data = os_msys_get_pkthdr(BLE_HS_ADV_MAX_SZ, 0); assert(sd_data); if (!adv.instance) { BT_ERR("Mesh advertiser not enabled"); err = -ENODEV; goto error; } if (atomic_test_and_set_bit(adv.flags, ADV_FLAG_ACTIVE)) { BT_ERR("Advertiser is busy"); err = -EBUSY; goto error; } if (atomic_test_bit(adv.flags, ADV_FLAG_UPDATE_PARAMS)) { err = ble_gap_ext_adv_configure(BT_ID_DEFAULT, param, NULL, ble_mesh_ext_adv_event_handler, NULL); if (err) { BT_ERR("Failed updating adv params: %d", err); atomic_clear_bit(adv.flags, ADV_FLAG_ACTIVE); goto error; } atomic_set_bit_to(adv.flags, ADV_FLAG_UPDATE_PARAMS, param != &adv_param); } assert(ad_data); err = os_mbuf_append(ad_data, ad, ad_len); if (err) { goto error; } err = ble_gap_ext_adv_set_data(BT_ID_DEFAULT, ad_data); if (err) { BT_ERR("Failed setting adv data: %d", err); atomic_clear_bit(adv.flags, ADV_FLAG_ACTIVE); goto error; } err = os_mbuf_append(sd_data, sd, sd_len); if (err) { goto error; } err = ble_gap_ext_adv_rsp_set_data(BT_ID_DEFAULT, sd_data); if (err) { BT_ERR("Failed setting scan response data: %d", err); atomic_clear_bit(adv.flags, ADV_FLAG_ACTIVE); goto error; } adv.timestamp = k_uptime_get(); err = ble_gap_ext_adv_start(BT_ID_DEFAULT, timeout, 0); if (err) { BT_ERR("Advertising failed: err %d", err); atomic_clear_bit(adv.flags, ADV_FLAG_ACTIVE); } error: if (ad_data) { os_mbuf_free_chain(ad_data); } if (sd_data) { os_mbuf_free_chain(sd_data); } return err; } static int buf_send(struct os_mbuf *buf) { static const uint8_t bt_mesh_adv_type[] = { [BT_MESH_ADV_PROV] = BLE_HS_ADV_TYPE_MESH_PROV, [BT_MESH_ADV_DATA] = BLE_HS_ADV_TYPE_MESH_MESSAGE, [BT_MESH_ADV_BEACON] = BLE_HS_ADV_TYPE_MESH_BEACON, [BT_MESH_ADV_URI] = BLE_HS_ADV_TYPE_URI, }; 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->om_len, bt_hex(buf->om_data, buf->om_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->om_len; ad.data = buf->om_data; /* Only update advertising parameters if they're different */ if (adv_param.itvl_min != BT_MESH_ADV_SCAN_UNIT(adv_int)) { adv_param.itvl_min = BT_MESH_ADV_SCAN_UNIT(adv_int); adv_param.itvl_max = adv_param.itvl_min; atomic_set_bit(adv.flags, ADV_FLAG_UPDATE_PARAMS); } err = adv_start(&adv_param, duration, &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 ble_npl_event *work) { struct os_mbuf *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 */ } } if (!MYNEWT_VAL(BLE_MESH_GATT_SERVER)) { return; } /* No more pending buffers */ if (bt_mesh_is_provisioned()) { if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) { err = bt_mesh_proxy_adv_start(); BT_DBG("Proxy Advertising"); } } else if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT)) { err = bt_mesh_pb_gatt_adv_start(); BT_DBG("PB-GATT Advertising"); } if (!err) { atomic_set_bit(adv.flags, ADV_FLAG_PROXY); } } 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) { int rc; rc = os_mempool_init(&adv_buf_mempool, MYNEWT_VAL(BLE_MESH_ADV_BUF_COUNT), BT_MESH_ADV_DATA_SIZE + BT_MESH_MBUF_HEADER_SIZE, adv_buf_mem, "adv_buf_pool"); assert(rc == 0); rc = os_mbuf_pool_init(&adv_os_mbuf_pool, &adv_buf_mempool, BT_MESH_ADV_DATA_SIZE + BT_MESH_MBUF_HEADER_SIZE, MYNEWT_VAL(BLE_MESH_ADV_BUF_COUNT)); assert(rc == 0); ble_npl_eventq_init(&bt_mesh_adv_queue); k_work_init_delayable(&adv.work, send_pending_adv); } int bt_mesh_adv_enable(void) { /* No need to initialize extended advertiser instance here */ return 0; } int bt_mesh_adv_start(const struct ble_gap_adv_params *param, int32_t duration, const struct bt_data *ad, size_t ad_len, const struct bt_data *sd, size_t sd_len) { static uint32_t adv_timeout; struct ble_gap_ext_adv_params params = { .itvl_min = param->itvl_min, .itvl_max = param->itvl_max }; /* In NimBLE duration is in ms, not 10ms units */ adv_timeout = (duration == BLE_HS_FOREVER) ? 0 : duration; BT_DBG("Start advertising %d ms", duration); atomic_set_bit(adv.flags, ADV_FLAG_UPDATE_PARAMS); return adv_start(¶ms, adv_timeout, ad, ad_len, sd, sd_len); } #endif