| /* Bluetooth Mesh */ |
| |
| /* |
| * Copyright (c) 2018 Nordic Semiconductor ASA |
| * Copyright (c) 2017 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include "syscfg/syscfg.h" |
| #define MESH_LOG_MODULE BLE_MESH_ADV_LOG |
| |
| #include "mesh/mesh.h" |
| #include "host/ble_hs_adv.h" |
| #include "host/ble_gap.h" |
| #include "nimble/hci_common.h" |
| #include "mesh/porting.h" |
| |
| #include "adv.h" |
| #include "net.h" |
| #include "foundation.h" |
| #include "beacon.h" |
| #include "prov.h" |
| #include "proxy.h" |
| |
| /* Convert from ms to 0.625ms units */ |
| #define ADV_SCAN_UNIT(_ms) ((_ms) * 8 / 5) |
| |
| /* Window and Interval are equal for continuous scanning */ |
| #define MESH_SCAN_INTERVAL_MS 30 |
| #define MESH_SCAN_WINDOW_MS 30 |
| #define MESH_SCAN_INTERVAL ADV_SCAN_UNIT(MESH_SCAN_INTERVAL_MS) |
| #define MESH_SCAN_WINDOW ADV_SCAN_UNIT(MESH_SCAN_WINDOW_MS) |
| |
| /* Pre-5.0 controllers enforce a minimum interval of 100ms |
| * whereas 5.0+ controllers can go down to 20ms. |
| */ |
| #define ADV_INT_DEFAULT_MS 100 |
| #define ADV_INT_FAST_MS 20 |
| |
| static int32_t adv_int_min = ADV_INT_DEFAULT_MS; |
| |
| /* TinyCrypt PRNG consumes a lot of stack space, so we need to have |
| * an increased call stack whenever it's used. |
| */ |
| #if MYNEWT |
| OS_TASK_STACK_DEFINE(g_blemesh_stack, MYNEWT_VAL(BLE_MESH_ADV_STACK_SIZE)); |
| struct os_task adv_task; |
| #endif |
| |
| static struct ble_npl_eventq adv_queue; |
| extern uint8_t g_mesh_addr_type; |
| static int adv_initialized = false; |
| |
| static os_membuf_t adv_buf_mem[OS_MEMPOOL_SIZE( |
| MYNEWT_VAL(BLE_MESH_ADV_BUF_COUNT), |
| BT_MESH_ADV_DATA_SIZE + BT_MESH_MBUF_HEADER_SIZE)]; |
| |
| struct os_mbuf_pool adv_os_mbuf_pool; |
| static struct os_mempool adv_buf_mempool; |
| |
| static struct bt_mesh_adv adv_pool[CONFIG_BT_MESH_ADV_BUF_COUNT]; |
| |
| static struct bt_mesh_adv *adv_alloc(int id) |
| { |
| return &adv_pool[id]; |
| } |
| |
| static inline void adv_send_start(uint16_t duration, int err, |
| const struct bt_mesh_send_cb *cb, |
| void *cb_data) |
| { |
| if (cb && cb->start) { |
| cb->start(duration, err, cb_data); |
| } |
| } |
| |
| static inline void adv_send_end(int err, const struct bt_mesh_send_cb *cb, |
| void *cb_data) |
| { |
| if (cb && cb->end) { |
| cb->end(err, cb_data); |
| } |
| } |
| |
| static inline void adv_send(struct os_mbuf *buf) |
| { |
| static const uint8_t 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, |
| } ; |
| |
| const struct bt_mesh_send_cb *cb = BT_MESH_ADV(buf)->cb; |
| void *cb_data = BT_MESH_ADV(buf)->cb_data; |
| struct ble_gap_adv_params param = { 0 }; |
| uint16_t duration, adv_int; |
| struct bt_data ad; |
| int err; |
| |
| adv_int = max(adv_int_min, |
| BT_MESH_TRANSMIT_INT(BT_MESH_ADV(buf)->xmit)); |
| #if MYNEWT_VAL(BLE_CONTROLLER) |
| duration = ((BT_MESH_TRANSMIT_COUNT(BT_MESH_ADV(buf)->xmit) + 1) * |
| (adv_int + 10)); |
| #else |
| duration = (MESH_SCAN_WINDOW_MS + |
| ((BT_MESH_TRANSMIT_COUNT(BT_MESH_ADV(buf)->xmit) + 1) * |
| (adv_int + 10))); |
| #endif |
| |
| BT_DBG("type %u om_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 = adv_type[BT_MESH_ADV(buf)->type]; |
| ad.data_len = buf->om_len; |
| ad.data = buf->om_data; |
| |
| param.itvl_min = ADV_SCAN_UNIT(adv_int); |
| param.itvl_max = param.itvl_min; |
| param.conn_mode = BLE_GAP_CONN_MODE_NON; |
| |
| err = bt_le_adv_start(¶m, &ad, 1, NULL, 0); |
| net_buf_unref(buf); |
| adv_send_start(duration, err, cb, cb_data); |
| if (err) { |
| BT_ERR("Advertising failed: err %d", err); |
| return; |
| } |
| |
| BT_DBG("Advertising started. Sleeping %u ms", duration); |
| |
| k_sleep(K_MSEC(duration)); |
| |
| err = bt_le_adv_stop(false); |
| adv_send_end(err, cb, cb_data); |
| if (err) { |
| BT_ERR("Stopping advertising failed: err %d", err); |
| return; |
| } |
| |
| BT_DBG("Advertising stopped"); |
| } |
| |
| void |
| mesh_adv_thread(void *args) |
| { |
| static struct ble_npl_event *ev; |
| struct os_mbuf *buf; |
| #if (MYNEWT_VAL(BLE_MESH_PROXY)) |
| int32_t timeout; |
| #endif |
| |
| BT_DBG("started"); |
| |
| while (1) { |
| #if (MYNEWT_VAL(BLE_MESH_PROXY)) |
| ev = ble_npl_eventq_get(&adv_queue, 0); |
| while (!ev) { |
| timeout = bt_mesh_proxy_adv_start(); |
| BT_DBG("Proxy Advertising up to %d ms", (int) timeout); |
| |
| // FIXME: should we redefine K_SECONDS macro instead in glue? |
| if (timeout != K_FOREVER) { |
| timeout = ble_npl_time_ms_to_ticks32(timeout); |
| } |
| |
| ev = ble_npl_eventq_get(&adv_queue, timeout); |
| bt_mesh_proxy_adv_stop(); |
| } |
| #else |
| ev = ble_npl_eventq_get(&adv_queue, BLE_NPL_TIME_FOREVER); |
| #endif |
| |
| if (!ev || !ble_npl_event_get_arg(ev)) { |
| continue; |
| } |
| |
| buf = ble_npl_event_get_arg(ev); |
| |
| /* busy == 0 means this was canceled */ |
| if (BT_MESH_ADV(buf)->busy) { |
| BT_MESH_ADV(buf)->busy = 0; |
| adv_send(buf); |
| } else { |
| net_buf_unref(buf); |
| } |
| |
| /* os_sched(NULL); */ |
| } |
| } |
| |
| void bt_mesh_adv_update(void) |
| { |
| static struct ble_npl_event ev = { }; |
| |
| BT_DBG(""); |
| |
| ble_npl_eventq_put(&adv_queue, &ev); |
| } |
| |
| struct os_mbuf *bt_mesh_adv_create_from_pool(struct os_mbuf_pool *pool, |
| bt_mesh_adv_alloc_t get_id, |
| enum bt_mesh_adv_type type, |
| uint8_t xmit, int32_t timeout) |
| { |
| struct bt_mesh_adv *adv; |
| struct os_mbuf *buf; |
| |
| if (atomic_test_bit(bt_mesh.flags, BT_MESH_SUSPENDED)) { |
| BT_WARN("Refusing to allocate buffer while suspended"); |
| return NULL; |
| } |
| |
| buf = os_mbuf_get_pkthdr(pool, BT_MESH_ADV_USER_DATA_SIZE); |
| if (!buf) { |
| return NULL; |
| } |
| |
| adv = get_id(net_buf_id(buf)); |
| BT_MESH_ADV(buf) = adv; |
| |
| memset(adv, 0, sizeof(*adv)); |
| |
| adv->type = type; |
| adv->xmit = xmit; |
| |
| adv->ref_cnt = 1; |
| ble_npl_event_set_arg(&adv->ev, buf); |
| |
| return buf; |
| os_mbuf_free_chain(buf); |
| } |
| |
| struct os_mbuf *bt_mesh_adv_create(enum bt_mesh_adv_type type, uint8_t xmit, |
| int32_t timeout) |
| { |
| return bt_mesh_adv_create_from_pool(&adv_os_mbuf_pool, adv_alloc, type, |
| xmit, timeout); |
| } |
| |
| void bt_mesh_adv_send(struct os_mbuf *buf, const struct bt_mesh_send_cb *cb, |
| void *cb_data) |
| { |
| BT_DBG("buf %p, type 0x%02x len %u: %s", buf, BT_MESH_ADV(buf)->type, buf->om_len, |
| bt_hex(buf->om_data, buf->om_len)); |
| |
| BT_MESH_ADV(buf)->cb = cb; |
| BT_MESH_ADV(buf)->cb_data = cb_data; |
| BT_MESH_ADV(buf)->busy = 1; |
| |
| net_buf_put(&adv_queue, net_buf_ref(buf)); |
| } |
| |
| static void bt_mesh_scan_cb(const bt_addr_le_t *addr, int8_t rssi, |
| uint8_t adv_type, struct os_mbuf *buf) |
| { |
| if (adv_type != BLE_HCI_ADV_TYPE_ADV_NONCONN_IND) { |
| return; |
| } |
| |
| #if BT_MESH_EXTENDED_DEBUG |
| BT_DBG("len %u: %s", buf->om_len, bt_hex(buf->om_data, buf->om_len)); |
| #endif |
| |
| while (buf->om_len > 1) { |
| struct net_buf_simple_state state; |
| uint8_t len, type; |
| |
| len = net_buf_simple_pull_u8(buf); |
| /* Check for early termination */ |
| if (len == 0) { |
| return; |
| } |
| |
| if (len > buf->om_len) { |
| BT_WARN("AD malformed"); |
| return; |
| } |
| |
| os_mbuf_save(buf, &state); |
| |
| type = net_buf_simple_pull_u8(buf); |
| |
| switch (type) { |
| case BLE_HS_ADV_TYPE_MESH_MESSAGE: |
| bt_mesh_net_recv(buf, rssi, BT_MESH_NET_IF_ADV); |
| break; |
| #if MYNEWT_VAL(BLE_MESH_PB_ADV) |
| case BLE_HS_ADV_TYPE_MESH_PROV: |
| bt_mesh_pb_adv_recv(buf); |
| break; |
| #endif |
| case BLE_HS_ADV_TYPE_MESH_BEACON: |
| bt_mesh_beacon_recv(buf); |
| break; |
| default: |
| break; |
| } |
| |
| os_mbuf_restore(buf, &state); |
| net_buf_simple_pull_mem(buf, len); |
| } |
| } |
| |
| void bt_mesh_adv_init(void) |
| { |
| int rc; |
| |
| /* Advertising should only be initialized once. Calling |
| * os_task init the second time will result in an assert. */ |
| if (adv_initialized) { |
| return; |
| } |
| |
| 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(&adv_queue); |
| |
| #if MYNEWT |
| os_task_init(&adv_task, "mesh_adv", mesh_adv_thread, NULL, |
| MYNEWT_VAL(BLE_MESH_ADV_TASK_PRIO), OS_WAIT_FOREVER, |
| g_blemesh_stack, MYNEWT_VAL(BLE_MESH_ADV_STACK_SIZE)); |
| #endif |
| |
| /* For BT5 controllers we can have fast advertising interval */ |
| if (ble_hs_hci_get_hci_version() >= BLE_HCI_VER_BCS_5_0) { |
| adv_int_min = ADV_INT_FAST_MS; |
| } |
| |
| adv_initialized = true; |
| } |
| |
| int |
| ble_adv_gap_mesh_cb(struct ble_gap_event *event, void *arg) |
| { |
| #if MYNEWT_VAL(BLE_EXT_ADV) |
| struct ble_gap_ext_disc_desc *ext_desc; |
| #endif |
| struct ble_gap_disc_desc *desc; |
| struct os_mbuf *buf = NULL; |
| |
| #if BT_MESH_EXTENDED_DEBUG |
| BT_DBG("event->type %d", event->type); |
| #endif |
| |
| switch (event->type) { |
| #if MYNEWT_VAL(BLE_EXT_ADV) |
| case BLE_GAP_EVENT_EXT_DISC: |
| ext_desc = &event->ext_disc; |
| buf = os_mbuf_get_pkthdr(&adv_os_mbuf_pool, 0); |
| if (!buf || os_mbuf_append(buf, ext_desc->om_data, ext_desc->length_data)) { |
| BT_ERR("Could not append data"); |
| goto done; |
| } |
| bt_mesh_scan_cb(&ext_desc->addr, ext_desc->rssi, |
| ext_desc->legacy_event_type, buf); |
| break; |
| #endif |
| case BLE_GAP_EVENT_DISC: |
| desc = &event->disc; |
| buf = os_mbuf_get_pkthdr(&adv_os_mbuf_pool, 0); |
| if (!buf || os_mbuf_append(buf, desc->data, desc->length_data)) { |
| BT_ERR("Could not append data"); |
| goto done; |
| } |
| |
| bt_mesh_scan_cb(&desc->addr, desc->rssi, desc->event_type, buf); |
| break; |
| default: |
| break; |
| } |
| |
| done: |
| if (buf) { |
| os_mbuf_free_chain(buf); |
| } |
| |
| return 0; |
| } |
| |
| int bt_mesh_scan_enable(void) |
| { |
| int err; |
| |
| #if MYNEWT_VAL(BLE_EXT_ADV) |
| struct ble_gap_ext_disc_params uncoded_params = |
| { .itvl = MESH_SCAN_INTERVAL, .window = MESH_SCAN_WINDOW, |
| .passive = 1 }; |
| |
| BT_DBG(""); |
| |
| err = ble_gap_ext_disc(g_mesh_addr_type, 0, 0, 0, 0, 0, |
| &uncoded_params, NULL, NULL, NULL); |
| #else |
| struct ble_gap_disc_params scan_param = |
| { .passive = 1, .filter_duplicates = 0, .itvl = |
| MESH_SCAN_INTERVAL, .window = MESH_SCAN_WINDOW }; |
| |
| BT_DBG(""); |
| |
| err = ble_gap_disc(g_mesh_addr_type, BLE_HS_FOREVER, &scan_param, |
| NULL, NULL); |
| #endif |
| if (err && err != BLE_HS_EALREADY) { |
| BT_ERR("starting scan failed (err %d)", err); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| int bt_mesh_scan_disable(void) |
| { |
| int err; |
| |
| BT_DBG(""); |
| |
| err = ble_gap_disc_cancel(); |
| if (err && err != BLE_HS_EALREADY) { |
| BT_ERR("stopping scan failed (err %d)", err); |
| return err; |
| } |
| |
| return 0; |
| } |