blob: f194ec21c9cdbb6ebd7a1ddc65bdf3d0ecac488a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "syscfg/syscfg.h"
#include "os/os.h"
#include "ble/xcvr.h"
#include "nimble/ble.h"
#include "nimble/nimble_opt.h"
#include "nimble/hci_common.h"
#include "controller/ble_phy.h"
#include "controller/ble_hw.h"
#include "controller/ble_ll.h"
#include "controller/ble_ll_pdu.h"
#include "controller/ble_ll_hci.h"
#include "controller/ble_ll_adv.h"
#include "controller/ble_ll_sched.h"
#include "controller/ble_ll_scan.h"
#include "controller/ble_ll_whitelist.h"
#include "controller/ble_ll_resolv.h"
#include "controller/ble_ll_tmr.h"
#include "controller/ble_ll_trace.h"
#include "controller/ble_ll_utils.h"
#include "controller/ble_ll_rfmgmt.h"
#include "controller/ble_ll_iso_big.h"
#include "ble_ll_conn_priv.h"
#include "ble_ll_priv.h"
#if MYNEWT_VAL(BLE_LL_ROLE_BROADCASTER)
/* XXX: TODO
* 1) Need to look at advertising and scan request PDUs. Do I allocate these
* once? Do I use a different pool for smaller ones? Do I statically declare
* them?
* 3) How do features get supported? What happens if device does not support
* advertising? (for example)
* 4) How to determine the advertising interval we will actually use. As of
* now, we set it to max.
*/
/* Scheduling data for secondary channel */
struct ble_ll_adv_aux {
struct ble_ll_sched_item sch;
uint32_t start_time;
uint16_t data_offset;
uint8_t chan;
uint8_t ext_hdr_flags;
uint8_t data_len;
uint8_t payload_len;
uint8_t auxptr_zero;
};
/* Scheduling data for sync PDUs */
struct ble_ll_adv_sync {
struct ble_ll_sched_item sch;
uint32_t start_time;
uint16_t data_offset;
uint8_t chan;
uint8_t ext_hdr_flags;
uint8_t data_len;
uint8_t payload_len;
uint8_t auxptr_zero;
#if MYNEWT_VAL(BLE_LL_ISO_BROADCASTER)
struct ble_ll_iso_big *big;
#endif
};
/*
* Advertising state machine
*
* The advertising state machine data structure.
*
* adv_pdu_len
* The length of the advertising PDU that will be sent. This does not
* include the preamble, access address and CRC.
*
* initiator_addr:
* This is the address that we send in directed advertisements (the
* INITA field). If we are using Privacy this is a RPA that we need to
* generate. We reserve space in the advsm to save time when creating
* the ADV_DIRECT_IND. If own address type is not 2 or 3, this is simply
* the peer address from the set advertising parameters.
*/
struct ble_ll_adv_sm
{
uint8_t adv_enabled;
uint8_t adv_instance;
uint8_t adv_chanmask;
uint8_t adv_filter_policy;
uint8_t own_addr_type;
uint8_t peer_addr_type;
uint8_t adv_chan;
uint8_t retry_event;
uint8_t adv_pdu_len;
int8_t adv_rpa_index;
int8_t tx_power;
uint16_t flags;
uint16_t props;
uint32_t adv_itvl_usecs;
uint32_t adv_event_start_time;
uint32_t adv_pdu_start_time;
uint32_t adv_end_time;
uint8_t adva[BLE_DEV_ADDR_LEN];
uint8_t adv_rpa[BLE_DEV_ADDR_LEN];
uint8_t peer_addr[BLE_DEV_ADDR_LEN];
uint8_t initiator_addr[BLE_DEV_ADDR_LEN];
struct os_mbuf *adv_data;
struct os_mbuf *new_adv_data;
struct os_mbuf *scan_rsp_data;
struct os_mbuf *new_scan_rsp_data;
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
uint8_t *conn_comp_ev;
#endif
struct ble_npl_event adv_txdone_ev;
struct ble_ll_sched_item adv_sch;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CSA2)
uint16_t channel_id;
uint16_t event_cntr;
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
uint8_t aux_active : 1;
uint8_t aux_index : 1;
uint8_t aux_first_pdu : 1;
uint8_t aux_not_scanned : 1;
uint8_t aux_dropped : 1;
struct ble_mbuf_hdr *rx_ble_hdr;
struct os_mbuf **aux_data;
struct ble_ll_adv_aux aux[2];
struct ble_npl_event adv_sec_txdone_ev;
uint16_t duration;
uint16_t adi;
uint8_t adv_random_addr[BLE_DEV_ADDR_LEN];
uint8_t events_max;
uint8_t events;
uint8_t pri_phy;
uint8_t sec_phy;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
struct os_mbuf *periodic_adv_data;
struct os_mbuf *periodic_new_data;
uint32_t periodic_crcinit; /* only 3 bytes are used */
uint32_t periodic_access_addr;
uint16_t periodic_adv_props;
uint16_t periodic_channel_id;
uint16_t periodic_event_cntr;
uint16_t periodic_chain_event_cntr;
uint8_t periodic_adv_enabled : 1;
uint8_t periodic_adv_active : 1;
uint8_t periodic_sync_active : 1;
uint8_t periodic_sync_index : 1;
uint8_t periodic_num_used_chans;
uint8_t periodic_chanmap[BLE_LL_CHAN_MAP_LEN];
uint16_t periodic_adv_itvl;
uint32_t periodic_adv_itvl_ticks;
uint8_t periodic_adv_itvl_rem_us;
uint8_t periodic_adv_event_start_time_remainder;
uint32_t periodic_adv_event_start_time;
struct ble_ll_adv_sync periodic_sync[2];
struct ble_npl_event adv_periodic_txdone_ev;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_SYNC_TRANSFER)
uint16_t periodic_event_cntr_last_sent;
#endif
#endif
#if MYNEWT_VAL(BLE_LL_ISO_BROADCASTER)
struct ble_ll_iso_big *big;
#endif /* BLE_LL_ISO_BROADCASTER */
#endif
};
#define BLE_LL_ADV_SM_FLAG_TX_ADD 0x0001
#define BLE_LL_ADV_SM_FLAG_RX_ADD 0x0002
#define BLE_LL_ADV_SM_FLAG_SCAN_REQ_NOTIF 0x0004
#define BLE_LL_ADV_SM_FLAG_CONN_RSP_TXD 0x0008
#define BLE_LL_ADV_SM_FLAG_ACTIVE_CHANSET_MASK 0x0030 /* use helpers! */
#define BLE_LL_ADV_SM_FLAG_ADV_DATA_INCOMPLETE 0x0040
#define BLE_LL_ADV_SM_FLAG_CONFIGURED 0x0080
#define BLE_LL_ADV_SM_FLAG_ADV_RPA_TMO 0x0100
#define BLE_LL_ADV_SM_FLAG_NEW_ADV_DATA 0x0200
#define BLE_LL_ADV_SM_FLAG_NEW_SCAN_RSP_DATA 0x0400
#define BLE_LL_ADV_SM_FLAG_PERIODIC_CONFIGURED 0x0800
#define BLE_LL_ADV_SM_FLAG_PERIODIC_DATA_INCOMPLETE 0x1000
#define BLE_LL_ADV_SM_FLAG_PERIODIC_SYNC_SENDING 0x2000
#define BLE_LL_ADV_SM_FLAG_PERIODIC_NEW_DATA 0x4000
#define BLE_LL_ADV_SM_FLAG_CONN_RSP_TXD_ERR 0x8000
#define ADV_DATA_LEN(_advsm) \
(((_advsm)->adv_data) ? OS_MBUF_PKTLEN((_advsm)->adv_data) : 0)
#define SCAN_RSP_DATA_LEN(_advsm) \
(((_advsm)->scan_rsp_data) ? OS_MBUF_PKTLEN((_advsm)->scan_rsp_data) : 0)
#define AUX_CURRENT(_advsm) \
(&((_advsm)->aux[(_advsm)->aux_index]))
#define AUX_NEXT(_advsm) \
(&((_advsm)->aux[(_advsm)->aux_index ^ 1]))
#define AUX_DATA_LEN(_advsm) \
(*((_advsm)->aux_data) ? OS_MBUF_PKTLEN(*(_advsm)->aux_data) : 0)
#define SYNC_CURRENT(_advsm) \
(&((_advsm)->periodic_sync[(_advsm)->periodic_sync_index]))
#define SYNC_NEXT(_advsm) \
(&((_advsm)->periodic_sync[(_advsm)->periodic_sync_index ^ 1]))
#define SYNC_DATA_LEN(_advsm) \
((_advsm)->periodic_adv_data ? OS_MBUF_PKTLEN((_advsm)->periodic_adv_data) : 0)
/* The advertising state machine global object */
struct ble_ll_adv_sm g_ble_ll_adv_sm[BLE_ADV_INSTANCES];
struct ble_ll_adv_sm *g_ble_ll_cur_adv_sm;
static void ble_ll_adv_drop_event(struct ble_ll_adv_sm *advsm, bool preempted);
static struct ble_ll_adv_sm *
ble_ll_adv_sm_find_configured(uint8_t instance)
{
struct ble_ll_adv_sm *advsm;
unsigned int i;
/* in legacy mode we only allow instance 0 */
if (!ble_ll_hci_adv_mode_ext()) {
BLE_LL_ASSERT(instance == 0);
return &g_ble_ll_adv_sm[0];
}
for (i = 0; i < ARRAY_SIZE(g_ble_ll_adv_sm); i++) {
advsm = &g_ble_ll_adv_sm[i];
if ((advsm->flags & BLE_LL_ADV_SM_FLAG_CONFIGURED) &&
(advsm->adv_instance == instance)) {
return advsm;
}
}
return NULL;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
static int
ble_ll_adv_active_chanset_is_pri(struct ble_ll_adv_sm *advsm)
{
return (advsm->flags & BLE_LL_ADV_SM_FLAG_ACTIVE_CHANSET_MASK) == 0x10;
}
static int
ble_ll_adv_active_chanset_is_sec(struct ble_ll_adv_sm *advsm)
{
return (advsm->flags & BLE_LL_ADV_SM_FLAG_ACTIVE_CHANSET_MASK) == 0x20;
}
#endif
static void
ble_ll_adv_active_chanset_clear(struct ble_ll_adv_sm *advsm)
{
os_sr_t sr;
OS_ENTER_CRITICAL(sr);
advsm->flags &= ~BLE_LL_ADV_SM_FLAG_ACTIVE_CHANSET_MASK;
OS_EXIT_CRITICAL(sr);
}
static void
ble_ll_adv_active_chanset_set_pri(struct ble_ll_adv_sm *advsm)
{
os_sr_t sr;
OS_ENTER_CRITICAL(sr);
BLE_LL_ASSERT((advsm->flags & BLE_LL_ADV_SM_FLAG_ACTIVE_CHANSET_MASK) == 0);
advsm->flags &= ~BLE_LL_ADV_SM_FLAG_ACTIVE_CHANSET_MASK;
advsm->flags |= 0x10;
OS_EXIT_CRITICAL(sr);
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
static void
ble_ll_adv_active_chanset_set_sec(struct ble_ll_adv_sm *advsm)
{
os_sr_t sr;
OS_ENTER_CRITICAL(sr);
BLE_LL_ASSERT((advsm->flags & BLE_LL_ADV_SM_FLAG_ACTIVE_CHANSET_MASK) == 0);
advsm->flags &= ~BLE_LL_ADV_SM_FLAG_ACTIVE_CHANSET_MASK;
advsm->flags |= 0x20;
OS_EXIT_CRITICAL(sr);
}
#endif
static void
ble_ll_adv_flags_set(struct ble_ll_adv_sm *advsm, uint16_t flags)
{
os_sr_t sr;
OS_ENTER_CRITICAL(sr);
advsm->flags |= flags;
OS_EXIT_CRITICAL(sr);
}
static void
ble_ll_adv_flags_clear(struct ble_ll_adv_sm *advsm, uint16_t flags)
{
os_sr_t sr;
OS_ENTER_CRITICAL(sr);
advsm->flags &= ~flags;
OS_EXIT_CRITICAL(sr);
}
static void ble_ll_adv_make_done(struct ble_ll_adv_sm *advsm, struct ble_mbuf_hdr *hdr);
static void ble_ll_adv_sm_init(struct ble_ll_adv_sm *advsm);
static void ble_ll_adv_sm_stop_timeout(struct ble_ll_adv_sm *advsm);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
static void
ble_ll_adv_rpa_update(struct ble_ll_adv_sm *advsm)
{
if (ble_ll_resolv_gen_rpa(advsm->peer_addr, advsm->peer_addr_type,
advsm->adva, 1) ||
(ble_ll_resolv_local_rpa_get(advsm->own_addr_type & 1,
advsm->adva) == 0)) {
ble_ll_adv_flags_set(advsm, BLE_LL_ADV_SM_FLAG_TX_ADD);
} else {
if (advsm->own_addr_type & 1) {
ble_ll_adv_flags_set(advsm, BLE_LL_ADV_SM_FLAG_TX_ADD);
} else {
ble_ll_adv_flags_clear(advsm, BLE_LL_ADV_SM_FLAG_TX_ADD);
}
}
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_DIRECTED) {
if (ble_ll_resolv_gen_rpa(advsm->peer_addr, advsm->peer_addr_type,
advsm->initiator_addr, 0)) {
ble_ll_adv_flags_set(advsm, BLE_LL_ADV_SM_FLAG_RX_ADD);
} else {
if (advsm->peer_addr_type & 1) {
ble_ll_adv_flags_set(advsm, BLE_LL_ADV_SM_FLAG_RX_ADD);
} else {
ble_ll_adv_flags_clear(advsm, BLE_LL_ADV_SM_FLAG_RX_ADD);
}
}
}
}
/**
* Called to change advertisers ADVA and INITA (for directed advertisements)
* as an advertiser needs to adhere to the resolvable private address generation
* timer.
*
* NOTE: the resolvable private address code uses its own timer to regenerate
* local resolvable private addresses. The advertising code uses its own
* timer to reset the INITA (for directed advertisements). This code also sets
* the appropriate txadd and rxadd bits that will go into the advertisement.
*
* Another thing to note: it is possible that an IRK is all zeroes in the
* resolving list. That is why we need to check if the generated address is
* in fact a RPA as a resolving list entry with all zeroes will use the
* identity address (which may be a private address or public).
*
* @param advsm
*/
void
ble_ll_adv_chk_rpa_timeout(struct ble_ll_adv_sm *advsm)
{
if (advsm->own_addr_type < BLE_HCI_ADV_OWN_ADDR_PRIV_PUB) {
return;
}
if (advsm->flags & BLE_LL_ADV_SM_FLAG_ADV_RPA_TMO) {
ble_ll_adv_rpa_update(advsm);
ble_ll_adv_flags_clear(advsm, BLE_LL_ADV_SM_FLAG_ADV_RPA_TMO);
}
}
void
ble_ll_adv_rpa_timeout(void)
{
struct ble_ll_adv_sm *advsm;
int i;
for (i = 0; i < BLE_ADV_INSTANCES; i++) {
advsm = &g_ble_ll_adv_sm[i];
if (advsm->adv_enabled &&
advsm->own_addr_type > BLE_HCI_ADV_OWN_ADDR_RANDOM) {
/* Mark RPA as timed out so we get a new RPA */
ble_ll_adv_flags_set(advsm, BLE_LL_ADV_SM_FLAG_ADV_RPA_TMO);
}
}
}
#endif
/**
* Calculate the first channel that we should advertise upon when we start
* an advertising event.
*
* @param advsm
*
* @return uint8_t The number of the first channel usable for advertising.
*/
static uint8_t
ble_ll_adv_first_chan(struct ble_ll_adv_sm *advsm)
{
uint8_t adv_chan;
/* Set first advertising channel */
if (advsm->adv_chanmask & 0x01) {
adv_chan = BLE_PHY_ADV_CHAN_START;
} else if (advsm->adv_chanmask & 0x02) {
adv_chan = BLE_PHY_ADV_CHAN_START + 1;
} else {
adv_chan = BLE_PHY_ADV_CHAN_START + 2;
}
return adv_chan;
}
/**
* Calculate the final channel that we should advertise upon when we start
* an advertising event.
*
* @param advsm
*
* @return uint8_t The number of the final channel usable for advertising.
*/
static uint8_t
ble_ll_adv_final_chan(struct ble_ll_adv_sm *advsm)
{
uint8_t adv_chan;
if (advsm->adv_chanmask & 0x04) {
adv_chan = BLE_PHY_ADV_CHAN_START + 2;
} else if (advsm->adv_chanmask & 0x02) {
adv_chan = BLE_PHY_ADV_CHAN_START + 1;
} else {
adv_chan = BLE_PHY_ADV_CHAN_START;
}
return adv_chan;
}
/**
* Create the advertising legacy PDU
*
* @param advsm Pointer to advertisement state machine
*/
static uint8_t
ble_ll_adv_legacy_pdu_make(uint8_t *dptr, void *pducb_arg, uint8_t *hdr_byte)
{
struct ble_ll_adv_sm *advsm;
uint8_t adv_data_len;
uint8_t pdulen;
uint8_t pdu_type;
advsm = pducb_arg;
/* assume this is not a direct ind */
adv_data_len = ADV_DATA_LEN(advsm);
pdulen = BLE_DEV_ADDR_LEN + adv_data_len;
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_DIRECTED) {
pdu_type = BLE_ADV_PDU_TYPE_ADV_DIRECT_IND;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CSA2)
pdu_type |= BLE_ADV_PDU_HDR_CHSEL;
#endif
if (advsm->flags & BLE_LL_ADV_SM_FLAG_RX_ADD) {
pdu_type |= BLE_ADV_PDU_HDR_RXADD_RAND;
}
adv_data_len = 0;
pdulen = BLE_ADV_DIRECT_IND_LEN;
} else if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE) {
pdu_type = BLE_ADV_PDU_TYPE_ADV_IND;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CSA2)
pdu_type |= BLE_ADV_PDU_HDR_CHSEL;
#endif
} else if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE) {
pdu_type = BLE_ADV_PDU_TYPE_ADV_SCAN_IND;
} else {
pdu_type = BLE_ADV_PDU_TYPE_ADV_NONCONN_IND;
}
/* An invalid advertising data length indicates a memory overwrite */
BLE_LL_ASSERT(adv_data_len <= BLE_ADV_LEGACY_DATA_MAX_LEN);
/* Set the PDU length in the state machine (includes header) */
advsm->adv_pdu_len = pdulen + BLE_LL_PDU_HDR_LEN;
/* Set TxAdd to random if needed. */
if (advsm->flags & BLE_LL_ADV_SM_FLAG_TX_ADD) {
pdu_type |= BLE_ADV_PDU_HDR_TXADD_RAND;
}
*hdr_byte = pdu_type;
/* Construct advertisement */
memcpy(dptr, advsm->adva, BLE_DEV_ADDR_LEN);
dptr += BLE_DEV_ADDR_LEN;
/* For ADV_DIRECT_IND add inita */
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_DIRECTED) {
memcpy(dptr, advsm->initiator_addr, BLE_DEV_ADDR_LEN);
}
/* Copy in advertising data, if any */
if (adv_data_len != 0) {
os_mbuf_copydata(advsm->adv_data, 0, adv_data_len, dptr);
}
return pdulen;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
static void
ble_ll_adv_put_aux_ptr(uint8_t chan, uint8_t phy, uint32_t offset,
uint8_t *dptr)
{
dptr[0] = chan;
if (offset > 245700) {
dptr[0] |= 0x80;
offset = offset / 300;
} else {
offset = offset / 30;
}
if (offset > 0x1fff) {
offset = 0;
}
/* offset is 13bits and PHY 3 bits */
dptr[1] = (offset & 0x000000ff);
dptr[2] = ((offset >> 8) & 0x0000001f) | (phy - 1) << 5;
}
/**
* Create the advertising PDU
*/
static uint8_t
ble_ll_adv_pdu_make(uint8_t *dptr, void *pducb_arg, uint8_t *hdr_byte)
{
struct ble_ll_adv_sm *advsm;
uint8_t pdu_type;
uint8_t adv_mode;
uint8_t ext_hdr_len;
uint8_t ext_hdr_flags;
uint32_t offset;
advsm = pducb_arg;
BLE_LL_ASSERT(ble_ll_adv_active_chanset_is_pri(advsm));
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
return ble_ll_adv_legacy_pdu_make(dptr, advsm, hdr_byte);
}
/* only ADV_EXT_IND goes on primary advertising channels */
pdu_type = BLE_ADV_PDU_TYPE_ADV_EXT_IND;
*hdr_byte = pdu_type;
adv_mode = 0;
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE) {
adv_mode |= BLE_LL_EXT_ADV_MODE_CONN;
}
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE) {
adv_mode |= BLE_LL_EXT_ADV_MODE_SCAN;
}
ext_hdr_len = BLE_LL_EXT_ADV_FLAGS_SIZE + BLE_LL_EXT_ADV_DATA_INFO_SIZE +
BLE_LL_EXT_ADV_AUX_PTR_SIZE;
ext_hdr_flags = (1 << BLE_LL_EXT_ADV_DATA_INFO_BIT) |
(1 << BLE_LL_EXT_ADV_AUX_PTR_BIT);
/* ext hdr len and adv mode */
dptr[0] = ext_hdr_len | (adv_mode << 6);
dptr += 1;
/* ext hdr flags */
dptr[0] = ext_hdr_flags;
dptr += 1;
/* ADI */
dptr[0] = advsm->adi & 0x00ff;
dptr[1] = advsm->adi >> 8;
dptr += BLE_LL_EXT_ADV_DATA_INFO_SIZE;
/* AuxPtr */
if (AUX_CURRENT(advsm)->sch.enqueued) {
offset = ble_ll_tmr_t2u(AUX_CURRENT(advsm)->start_time - advsm->adv_pdu_start_time);
} else {
offset = 0;
}
/* Always use channel from 1st AUX */
ble_ll_adv_put_aux_ptr(AUX_CURRENT(advsm)->chan, advsm->sec_phy,
offset, dptr);
return BLE_LL_EXT_ADV_HDR_LEN + ext_hdr_len;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
static void
ble_ll_adv_put_syncinfo(struct ble_ll_adv_sm *advsm,
struct ble_ll_conn_sm *connsm, uint8_t *conn_event_cnt,
uint8_t *dptr)
{
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_SYNC_TRANSFER)
uint16_t conn_cnt;
#endif
unsigned int event_cnt_off = 0;
uint32_t offset = 0;
uint32_t itvl_us;
uint32_t anchor_ticks;
uint8_t anchor_rem_us;
uint8_t units;
if (connsm) {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_SYNC_TRANSFER)
anchor_ticks = connsm->anchor_point;
anchor_rem_us = connsm->anchor_point_usecs;
conn_cnt = connsm->event_cntr;
/* get anchor for conn event that is before periodic_adv_event_start_time */
while (LL_TMR_GT(anchor_ticks, advsm->periodic_adv_event_start_time)) {
ble_ll_conn_get_anchor(connsm, --conn_cnt, &anchor_ticks, &anchor_rem_us);
}
offset = ble_ll_tmr_t2u(advsm->periodic_adv_event_start_time - anchor_ticks);
offset -= anchor_rem_us;
offset += advsm->periodic_adv_event_start_time_remainder;
/* connEventCount */
put_le16(conn_event_cnt, conn_cnt);
#endif
} else {
anchor_ticks = advsm->periodic_adv_event_start_time;
anchor_rem_us = advsm->periodic_adv_event_start_time_remainder;
itvl_us = advsm->periodic_adv_itvl * BLE_LL_ADV_PERIODIC_ITVL;
/* Get periodic event that is past AUX start time (so that we always
* provide valid offset if it is not too far in future). This can
* happen if advertising event is interleaved with periodic advertising
* event (when chaining).
*/
while (LL_TMR_GEQ(AUX_CURRENT(advsm)->start_time, anchor_ticks)) {
ble_ll_tmr_add(&anchor_ticks, &anchor_rem_us, itvl_us);
event_cnt_off++;
}
/* We always schedule aux with 0 rem_us so no need to include it here */
offset = ble_ll_tmr_t2u(anchor_ticks - AUX_CURRENT(advsm)->start_time);
offset += anchor_rem_us;
}
/* Sync Packet Offset (13 bits), Offset Units (1 bit), Offset Adjust (1 bit),
* RFU (1 bit)
*/
if (offset > 245700) {
units = 0x20;
offset = offset / 300;
if (offset >= 0x2000) {
if (connsm) {
offset -= 0x2000;
units |= 0x40;
} else {
/* not able to represent time in offset */
offset = 0;
units = 0x00;
event_cnt_off = 0;
}
}
} else {
units = 0x00;
offset = offset / 30;
}
dptr[0] = (offset & 0x000000ff);
dptr[1] = ((offset >> 8) & 0x0000001f) | units;
/* Interval (2 bytes) */
put_le16(&dptr[2], advsm->periodic_adv_itvl);
/* Channels Mask (37 bits) */
dptr[4] = advsm->periodic_chanmap[0];
dptr[5] = advsm->periodic_chanmap[1];
dptr[6] = advsm->periodic_chanmap[2];
dptr[7] = advsm->periodic_chanmap[3];
dptr[8] = advsm->periodic_chanmap[4] & 0x1f;
/* SCA (3 bits) */
dptr[8] |= BLE_LL_SCA_ENUM << 5;
/* AA (4 bytes) */
put_le32(&dptr[9], advsm->periodic_access_addr);
/* CRCInit (3 bytes) */
dptr[13] = (uint8_t)advsm->periodic_crcinit;
dptr[14] = (uint8_t)(advsm->periodic_crcinit >> 8);
dptr[15] = (uint8_t)(advsm->periodic_crcinit >> 16);
/* Event Counter (2 bytes) */
put_le16(&dptr[16], advsm->periodic_event_cntr + event_cnt_off);
}
#endif
/**
* Create the AUX PDU
*/
static uint8_t
ble_ll_adv_aux_pdu_make(uint8_t *dptr, void *pducb_arg, uint8_t *hdr_byte)
{
struct ble_ll_adv_sm *advsm;
struct ble_ll_adv_aux *aux;
uint8_t adv_mode;
uint8_t pdu_type;
uint8_t ext_hdr_len;
uint32_t offset;
advsm = pducb_arg;
aux = AUX_CURRENT(advsm);
BLE_LL_ASSERT(!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY));
BLE_LL_ASSERT(ble_ll_adv_active_chanset_is_sec(advsm));
/* It's the same for AUX_ADV_IND and AUX_CHAIN_IND */
pdu_type = BLE_ADV_PDU_TYPE_AUX_ADV_IND;
/* We do not create scannable PDUs here - this is handled separately */
adv_mode = 0;
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE) {
adv_mode |= BLE_LL_EXT_ADV_MODE_CONN;
}
ext_hdr_len = aux->payload_len - BLE_LL_EXT_ADV_HDR_LEN - aux->data_len;
dptr[0] = (adv_mode << 6) | ext_hdr_len;
dptr += 1;
/* only put flags if needed */
if (aux->ext_hdr_flags) {
dptr[0] = aux->ext_hdr_flags;
dptr += 1;
}
if (aux->ext_hdr_flags & (1 << BLE_LL_EXT_ADV_ADVA_BIT)) {
/* Set TxAdd to random if needed. */
if (advsm->flags & BLE_LL_ADV_SM_FLAG_TX_ADD) {
pdu_type |= BLE_ADV_PDU_HDR_TXADD_RAND;
}
memcpy(dptr, advsm->adva, BLE_LL_EXT_ADV_ADVA_SIZE);
dptr += BLE_LL_EXT_ADV_ADVA_SIZE;
}
if (aux->ext_hdr_flags & (1 << BLE_LL_EXT_ADV_TARGETA_BIT)) {
memcpy(dptr, advsm->initiator_addr, BLE_LL_EXT_ADV_TARGETA_SIZE);
dptr += BLE_LL_EXT_ADV_TARGETA_SIZE;
/* Set RxAdd to random if needed. */
if (advsm->flags & BLE_LL_ADV_SM_FLAG_RX_ADD) {
pdu_type |= BLE_ADV_PDU_HDR_RXADD_RAND;
}
}
if (aux->ext_hdr_flags & (1 << BLE_LL_EXT_ADV_DATA_INFO_BIT)) {
dptr[0] = advsm->adi & 0x00ff;
dptr[1] = advsm->adi >> 8;
dptr += BLE_LL_EXT_ADV_DATA_INFO_SIZE;
}
if (aux->ext_hdr_flags & (1 << BLE_LL_EXT_ADV_AUX_PTR_BIT)) {
if (!AUX_NEXT(advsm)->sch.enqueued) {
/*
* Trim data here in case we do not have next aux scheduled. This
* can happen if next aux was outside advertising set period and
* was removed from scheduler.
*/
offset = 0;
} else if (advsm->rx_ble_hdr) {
offset = ble_ll_tmr_t2u(AUX_NEXT(advsm)->start_time - advsm->rx_ble_hdr->beg_cputime);
offset -= (advsm->rx_ble_hdr->rem_usecs + ble_ll_pdu_us(12, advsm->sec_phy) + BLE_LL_IFS);
} else {
offset = ble_ll_tmr_t2u(AUX_NEXT(advsm)->start_time - aux->start_time);
}
aux->auxptr_zero = offset == 0;
ble_ll_adv_put_aux_ptr(AUX_NEXT(advsm)->chan, advsm->sec_phy,
offset, dptr);
dptr += BLE_LL_EXT_ADV_AUX_PTR_SIZE;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
if (aux->ext_hdr_flags & (1 << BLE_LL_EXT_ADV_SYNC_INFO_BIT)) {
ble_ll_adv_put_syncinfo(advsm, NULL, NULL, dptr);
dptr += BLE_LL_EXT_ADV_SYNC_INFO_SIZE;
}
#endif
if (aux->ext_hdr_flags & (1 << BLE_LL_EXT_ADV_TX_POWER_BIT)) {
dptr[0] = advsm->tx_power + g_ble_ll_tx_power_compensation;
dptr += BLE_LL_EXT_ADV_TX_POWER_SIZE;
}
if (aux->data_len) {
os_mbuf_copydata(*advsm->aux_data, aux->data_offset,
aux->data_len, dptr);
}
*hdr_byte = pdu_type;
return aux->payload_len;
}
static uint8_t
ble_ll_adv_aux_scannable_pdu_make(uint8_t *dptr, void *pducb_arg, uint8_t *hdr_byte)
{
struct ble_ll_adv_sm *advsm;
uint8_t pdu_type;
uint8_t *ext_hdr_len;
uint8_t *ext_hdr;
uint8_t pdulen;
advsm = pducb_arg;
BLE_LL_ASSERT(!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY));
BLE_LL_ASSERT(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE);
BLE_LL_ASSERT(advsm->aux_first_pdu);
BLE_LL_ASSERT(ble_ll_adv_active_chanset_is_sec(advsm));
pdu_type = BLE_ADV_PDU_TYPE_AUX_ADV_IND;
ext_hdr_len = &dptr[0];
ext_hdr = &dptr[1];
dptr += 2;
/* Flags always */
*ext_hdr_len = BLE_LL_EXT_ADV_FLAGS_SIZE;
*ext_hdr = 0;
/* AdvA when non anonymous */
if (!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_ANON_ADV)) {
/* Set TxAdd to random if needed. */
if (advsm->flags & BLE_LL_ADV_SM_FLAG_TX_ADD) {
pdu_type |= BLE_ADV_PDU_HDR_TXADD_RAND;
}
*ext_hdr_len += BLE_LL_EXT_ADV_ADVA_SIZE;
*ext_hdr |= (1 << BLE_LL_EXT_ADV_ADVA_BIT);
memcpy(dptr, advsm->adva, BLE_LL_EXT_ADV_ADVA_SIZE);
dptr += BLE_LL_EXT_ADV_ADVA_SIZE;
}
/* TargetA only for directed */
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_DIRECTED) {
*ext_hdr_len += BLE_LL_EXT_ADV_TARGETA_SIZE;
*ext_hdr |= (1 << BLE_LL_EXT_ADV_TARGETA_BIT);
memcpy(dptr, advsm->initiator_addr, BLE_LL_EXT_ADV_TARGETA_SIZE);
dptr += BLE_LL_EXT_ADV_TARGETA_SIZE;
/* Set RxAdd to random if needed. */
if (advsm->flags & BLE_LL_ADV_SM_FLAG_RX_ADD) {
pdu_type |= BLE_ADV_PDU_HDR_RXADD_RAND;
}
}
/* ADI always */
*ext_hdr_len += BLE_LL_EXT_ADV_DATA_INFO_SIZE;
*ext_hdr |= (1 << BLE_LL_EXT_ADV_DATA_INFO_BIT);
dptr[0] = advsm->adi & 0x00ff;
dptr[1] = advsm->adi >> 8;
dptr += BLE_LL_EXT_ADV_DATA_INFO_SIZE;
/* TxPower if configured */
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_INC_TX_PWR) {
*ext_hdr_len += BLE_LL_EXT_ADV_TX_POWER_SIZE;
*ext_hdr |= (1 << BLE_LL_EXT_ADV_TX_POWER_BIT);
dptr[0] = advsm->tx_power + g_ble_ll_tx_power_compensation;
dptr += BLE_LL_EXT_ADV_TX_POWER_SIZE;
}
pdulen = BLE_LL_EXT_ADV_HDR_LEN + *ext_hdr_len;
*hdr_byte = pdu_type;
*ext_hdr_len |= (BLE_LL_EXT_ADV_MODE_SCAN << 6);
return pdulen;
}
#endif
static uint8_t
ble_ll_adv_scan_rsp_legacy_pdu_make(uint8_t *dptr, void *pducb_arg,
uint8_t *hdr_byte)
{
struct ble_ll_adv_sm *advsm;
uint8_t scan_rsp_len;
uint8_t pdulen;
uint8_t hdr;
advsm = pducb_arg;
/* Make sure that the length is valid */
scan_rsp_len = SCAN_RSP_DATA_LEN(advsm);
BLE_LL_ASSERT(scan_rsp_len <= BLE_SCAN_RSP_LEGACY_DATA_MAX_LEN);
/* Set BLE transmit header */
pdulen = BLE_DEV_ADDR_LEN + scan_rsp_len;
hdr = BLE_ADV_PDU_TYPE_SCAN_RSP;
if (advsm->flags & BLE_LL_ADV_SM_FLAG_TX_ADD) {
hdr |= BLE_ADV_PDU_HDR_TXADD_RAND;
}
*hdr_byte = hdr;
/*
* The adva in this packet will be the same one that was being advertised
* and is based on the peer identity address in the set advertising
* parameters. If a different peer sends us a scan request (for some reason)
* we will reply with an adva that was not generated based on the local irk
* of the peer sending the scan request.
*/
/* Construct scan response */
memcpy(dptr, advsm->adva, BLE_DEV_ADDR_LEN);
if (scan_rsp_len != 0) {
os_mbuf_copydata(advsm->scan_rsp_data, 0, scan_rsp_len,
dptr + BLE_DEV_ADDR_LEN);
}
return pdulen;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
/**
* Create a scan response PDU
*
* @param advsm
*/
static uint8_t
ble_ll_adv_scan_rsp_pdu_make(uint8_t *dptr, void *pducb_arg, uint8_t *hdr_byte)
{
struct ble_ll_adv_sm *advsm;
advsm = pducb_arg;
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
return ble_ll_adv_scan_rsp_legacy_pdu_make(dptr, pducb_arg, hdr_byte);
}
return ble_ll_adv_aux_pdu_make(dptr, pducb_arg, hdr_byte);
}
struct aux_conn_rsp_data {
struct ble_ll_adv_sm *advsm;
uint8_t *peer;
uint8_t rxadd;
};
/**
* Create a AUX connect response PDU
*
* @param advsm
*/
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
static uint8_t
ble_ll_adv_aux_conn_rsp_pdu_make(uint8_t *dptr, void *pducb_arg,
uint8_t *hdr_byte)
{
struct aux_conn_rsp_data *rsp_data;
uint8_t pdulen;
uint8_t ext_hdr_len;
uint8_t ext_hdr_flags;
uint8_t hdr;
rsp_data = pducb_arg;
/* flags,AdvA and TargetA */
ext_hdr_len = BLE_LL_EXT_ADV_FLAGS_SIZE + BLE_LL_EXT_ADV_ADVA_SIZE +
BLE_LL_EXT_ADV_TARGETA_SIZE;
ext_hdr_flags = (1 << BLE_LL_EXT_ADV_ADVA_BIT);
ext_hdr_flags |= (1 << BLE_LL_EXT_ADV_TARGETA_BIT);
pdulen = BLE_LL_EXT_ADV_HDR_LEN + ext_hdr_len;
/* Set BLE transmit header */
hdr = BLE_ADV_PDU_TYPE_AUX_CONNECT_RSP;
if (rsp_data->rxadd) {
hdr |= BLE_ADV_PDU_HDR_RXADD_MASK;
}
if (rsp_data->advsm->flags & BLE_LL_ADV_SM_FLAG_TX_ADD) {
hdr |= BLE_ADV_PDU_HDR_TXADD_MASK;
}
*hdr_byte = hdr;
/* ext hdr len and adv mode (00b) */
dptr[0] = ext_hdr_len;
dptr += 1;
/* ext hdr flags */
dptr[0] = ext_hdr_flags;
dptr += 1;
memcpy(dptr, rsp_data->advsm->adva, BLE_LL_EXT_ADV_ADVA_SIZE);
dptr += BLE_LL_EXT_ADV_ADVA_SIZE;
memcpy(dptr, rsp_data->peer, BLE_LL_EXT_ADV_TARGETA_SIZE);
dptr += BLE_LL_EXT_ADV_ADVA_SIZE;
return pdulen;
}
#endif
#endif
/**
* Called to indicate the advertising event is over.
*
* Context: Interrupt
*
* @param advsm
*
*/
static void
ble_ll_adv_tx_done(void *arg)
{
struct ble_ll_adv_sm *advsm;
advsm = (struct ble_ll_adv_sm *)arg;
ble_ll_trace_u32x2(BLE_LL_TRACE_ID_ADV_TXDONE, advsm->adv_instance,
advsm->flags & BLE_LL_ADV_SM_FLAG_ACTIVE_CHANSET_MASK);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (ble_ll_adv_active_chanset_is_pri(advsm)) {
ble_ll_event_add(&advsm->adv_txdone_ev);
} else if (ble_ll_adv_active_chanset_is_sec(advsm)) {
ble_ll_event_add(&advsm->adv_sec_txdone_ev);
} else {
BLE_LL_ASSERT(0);
}
#else
ble_ll_event_add(&advsm->adv_txdone_ev);
#endif
ble_ll_state_set(BLE_LL_STATE_STANDBY);
ble_ll_adv_active_chanset_clear(advsm);
/* We no longer have a current state machine */
g_ble_ll_cur_adv_sm = NULL;
}
void
ble_ll_adv_preempted(struct ble_ll_adv_sm *advsm)
{
ble_ll_adv_drop_event(advsm, 1);
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
/*
* Called when a periodic event has been removed from the scheduler
* without being run.
*/
void
ble_ll_adv_periodic_rmvd_from_sched(struct ble_ll_adv_sm *advsm)
{
ble_ll_event_add(&advsm->adv_periodic_txdone_ev);
}
#endif
/**
* This is the scheduler callback (called from interrupt context) which
* transmits an advertisement.
*
* Context: Interrupt (scheduler)
*
* @param sch
*
* @return int
*/
static int
ble_ll_adv_tx_start_cb(struct ble_ll_sched_item *sch)
{
int rc;
uint8_t end_trans;
uint32_t txstart;
struct ble_ll_adv_sm *advsm;
/* Get the state machine for the event */
advsm = (struct ble_ll_adv_sm *)sch->cb_arg;
/* Set the current advertiser */
g_ble_ll_cur_adv_sm = advsm;
ble_ll_adv_active_chanset_set_pri(advsm);
if ((advsm->flags & BLE_LL_ADV_SM_FLAG_NEW_ADV_DATA) ||
(advsm->flags & BLE_LL_ADV_SM_FLAG_NEW_SCAN_RSP_DATA)) {
goto adv_tx_done;
}
/* Set channel */
rc = ble_phy_setchan(advsm->adv_chan, BLE_ACCESS_ADDR_ADV, BLE_LL_CRCINIT_ADV);
BLE_LL_ASSERT(rc == 0);
#if MYNEWT_VAL(BLE_LL_PHY)
/* Set phy mode */
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
ble_phy_mode_set(BLE_PHY_MODE_1M, BLE_PHY_MODE_1M);
} else {
ble_phy_mode_set(advsm->pri_phy, advsm->pri_phy);
}
#else
ble_phy_mode_set(BLE_PHY_MODE_1M, BLE_PHY_MODE_1M);
#endif
#endif
/* Set the power */
ble_ll_tx_power_set(advsm->tx_power);
/* Set transmit start time. */
txstart = sch->start_time + g_ble_ll_sched_offset_ticks;
rc = ble_phy_tx_set_start_time(txstart, sch->remainder);
if (rc) {
STATS_INC(ble_ll_stats, adv_late_starts);
goto adv_tx_done;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
ble_phy_encrypt_disable();
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
advsm->adv_rpa_index = -1;
if (ble_ll_resolv_enabled()) {
ble_phy_resolv_list_enable();
} else {
ble_phy_resolv_list_disable();
}
#endif
/* We switch to RX after connectable or scannable legacy packets. */
if ((advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) &&
((advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE) ||
(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE))) {
end_trans = BLE_PHY_TRANSITION_TX_RX;
ble_phy_set_txend_cb(NULL, NULL);
} else {
end_trans = BLE_PHY_TRANSITION_NONE;
ble_phy_set_txend_cb(ble_ll_adv_tx_done, advsm);
}
/* Transmit advertisement */
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
rc = ble_phy_tx(ble_ll_adv_pdu_make, advsm, end_trans);
#else
rc = ble_phy_tx(ble_ll_adv_legacy_pdu_make, advsm, end_trans);
#endif
if (rc) {
goto adv_tx_done;
}
/* Enable/disable whitelisting based on filter policy */
if (advsm->adv_filter_policy != BLE_HCI_ADV_FILT_NONE) {
ble_ll_whitelist_enable();
} else {
ble_ll_whitelist_disable();
}
/* Set link layer state to advertising */
ble_ll_state_set(BLE_LL_STATE_ADV);
/* Count # of adv. sent */
STATS_INC(ble_ll_stats, adv_txg);
return BLE_LL_SCHED_STATE_RUNNING;
adv_tx_done:
ble_ll_adv_tx_done(advsm);
return BLE_LL_SCHED_STATE_DONE;
}
static void
ble_ll_adv_set_sched(struct ble_ll_adv_sm *advsm)
{
uint32_t max_usecs;
struct ble_ll_sched_item *sch;
sch = &advsm->adv_sch;
sch->cb_arg = advsm;
sch->sched_cb = ble_ll_adv_tx_start_cb;
sch->sched_type = BLE_LL_SCHED_TYPE_ADV;
/* Set end time to maximum time this schedule item may take */
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
max_usecs = ble_ll_pdu_us(advsm->adv_pdu_len, BLE_PHY_MODE_1M);
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_DIRECTED) {
max_usecs += BLE_LL_SCHED_DIRECT_ADV_MAX_USECS;
} else if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE) {
max_usecs += BLE_LL_SCHED_ADV_MAX_USECS;
}
} else {
/*
* In ADV_EXT_IND we always set only ADI and AUX so the payload length
* is always 7 bytes.
*/
max_usecs = ble_ll_pdu_us(7, advsm->pri_phy);
}
#else
max_usecs = ble_ll_pdu_us(advsm->adv_pdu_len, BLE_PHY_MODE_1M);
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_DIRECTED) {
max_usecs += BLE_LL_SCHED_DIRECT_ADV_MAX_USECS;
} else if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE) {
max_usecs += BLE_LL_SCHED_ADV_MAX_USECS;
}
#endif
sch->start_time = advsm->adv_pdu_start_time - g_ble_ll_sched_offset_ticks;
sch->remainder = 0;
sch->end_time = advsm->adv_pdu_start_time + ble_ll_tmr_u2t_up(max_usecs);
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
static int
ble_ll_adv_secondary_tx_start_cb(struct ble_ll_sched_item *sch)
{
int rc;
uint8_t end_trans;
uint32_t txstart;
struct ble_ll_adv_sm *advsm;
ble_phy_tx_pducb_t pducb;
struct ble_ll_adv_aux *aux;
/* Get the state machine for the event */
advsm = (struct ble_ll_adv_sm *)sch->cb_arg;
/* Set the current advertiser */
g_ble_ll_cur_adv_sm = advsm;
ble_ll_adv_active_chanset_set_sec(advsm);
/* Set channel */
aux = AUX_CURRENT(advsm);
rc = ble_phy_setchan(aux->chan, BLE_ACCESS_ADDR_ADV,
BLE_LL_CRCINIT_ADV);
BLE_LL_ASSERT(rc == 0);
#if MYNEWT_VAL(BLE_LL_PHY)
/* Set phy mode */
ble_phy_mode_set(advsm->sec_phy, advsm->sec_phy);
#endif
/* Set the power */
ble_ll_tx_power_set(advsm->tx_power);
/* Set transmit start time. */
txstart = sch->start_time + g_ble_ll_sched_offset_ticks;
rc = ble_phy_tx_set_start_time(txstart, sch->remainder);
if (rc) {
STATS_INC(ble_ll_stats, adv_late_starts);
goto adv_aux_dropped;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
ble_phy_encrypt_disable();
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
advsm->adv_rpa_index = -1;
if (ble_ll_resolv_enabled()) {
ble_phy_resolv_list_enable();
} else {
ble_phy_resolv_list_disable();
}
#endif
/* Set phy mode based on type of advertisement */
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE) {
end_trans = BLE_PHY_TRANSITION_TX_RX;
ble_phy_set_txend_cb(NULL, NULL);
pducb = ble_ll_adv_aux_pdu_make;
} else if ((advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE) &&
advsm->aux_first_pdu) {
end_trans = BLE_PHY_TRANSITION_TX_RX;
ble_phy_set_txend_cb(NULL, NULL);
pducb = ble_ll_adv_aux_scannable_pdu_make;
} else {
end_trans = BLE_PHY_TRANSITION_NONE;
ble_phy_set_txend_cb(ble_ll_adv_tx_done, advsm);
pducb = ble_ll_adv_aux_pdu_make;
}
/* Transmit advertisement */
rc = ble_phy_tx(pducb, advsm, end_trans);
if (rc) {
goto adv_aux_dropped;
}
/* Enable/disable whitelisting based on filter policy */
if (advsm->adv_filter_policy != BLE_HCI_ADV_FILT_NONE) {
ble_ll_whitelist_enable();
} else {
ble_ll_whitelist_disable();
}
/* Set link layer state to advertising */
ble_ll_state_set(BLE_LL_STATE_ADV);
/* Count # of adv. sent */
STATS_INC(ble_ll_stats, adv_txg);
return BLE_LL_SCHED_STATE_RUNNING;
adv_aux_dropped:
advsm->aux_dropped = 1;
ble_ll_adv_tx_done(advsm);
return BLE_LL_SCHED_STATE_DONE;
}
static uint8_t
ble_ll_adv_aux_scannable_pdu_payload_len(struct ble_ll_adv_sm *advsm)
{
uint8_t len;
/* Flags, ADI always */
len = BLE_LL_EXT_ADV_HDR_LEN + BLE_LL_EXT_ADV_FLAGS_SIZE
+ BLE_LL_EXT_ADV_DATA_INFO_SIZE;
/* AdvA if not anonymous */
if (!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_ANON_ADV)) {
len += BLE_LL_EXT_ADV_ADVA_SIZE;
}
/* TargetA only for directed */
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_DIRECTED) {
len += BLE_LL_EXT_ADV_TARGETA_SIZE;
}
/* TxPower if configured */
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_INC_TX_PWR) {
len += BLE_LL_EXT_ADV_TX_POWER_SIZE;
}
return len;
}
static uint16_t
ble_ll_adv_aux_calculate_payload(struct ble_ll_adv_sm *advsm, uint16_t props,
struct os_mbuf *data, uint32_t data_offset,
uint8_t *data_len_o, uint8_t *ext_hdr_flags_o)
{
uint16_t rem_data_len;
uint8_t data_len;
uint8_t ext_hdr_flags;
uint8_t ext_hdr_len;
bool chainable;
bool first_pdu;
/* Note: advsm shall only be used to check if periodic advertising is
* enabled, other parameters in advsm may have different values than
* those we want to check (e.g. when reconfiguring instance).
*/
rem_data_len = (data ? OS_MBUF_PKTLEN(data) : 0) - data_offset;
BLE_LL_ASSERT((int16_t)rem_data_len >= 0);
first_pdu = (data_offset == 0);
chainable = !(props & BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE);
ext_hdr_flags = 0;
ext_hdr_len = BLE_LL_EXT_ADV_HDR_LEN;
/* ADI for anything but scannable */
if (!(props & BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE)) {
ext_hdr_flags |= (1 << BLE_LL_EXT_ADV_DATA_INFO_BIT);
ext_hdr_len += BLE_LL_EXT_ADV_DATA_INFO_SIZE;
}
/* AdvA in 1st PDU, except for anonymous */
if (first_pdu &&
!(props & BLE_HCI_LE_SET_EXT_ADV_PROP_ANON_ADV)) {
ext_hdr_flags |= (1 << BLE_LL_EXT_ADV_ADVA_BIT);
ext_hdr_len += BLE_LL_EXT_ADV_ADVA_SIZE;
}
/* TargetA in 1st PDU, if directed
*
* Note that for scannable this calculates AUX_SCAN_RSP which shall not
* include TargetA (see: Core 5.3, Vol 6, Part B, 2.3.2.3). For scannable
* TargetA is included in AUX_ADV_IND which is in that case calculated in
* ble_ll_adv_aux_schedule_first().
*/
if (first_pdu &&
(props & BLE_HCI_LE_SET_EXT_ADV_PROP_DIRECTED) &&
!(props & BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE)) {
ext_hdr_flags |= (1 << BLE_LL_EXT_ADV_TARGETA_BIT);
ext_hdr_len += BLE_LL_EXT_ADV_TARGETA_SIZE;
}
/* TxPower in 1st PDU, if configured */
if (first_pdu &&
(props & BLE_HCI_LE_SET_EXT_ADV_PROP_INC_TX_PWR)) {
ext_hdr_flags |= (1 << BLE_LL_EXT_ADV_TX_POWER_BIT);
ext_hdr_len += BLE_LL_EXT_ADV_TX_POWER_SIZE;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
/* SyncInfo in 1st PDU, if periodic advertising is enabled */
if (first_pdu && advsm->periodic_adv_active) {
ext_hdr_flags |= (1 << BLE_LL_EXT_ADV_SYNC_INFO_BIT);
ext_hdr_len += BLE_LL_EXT_ADV_SYNC_INFO_SIZE;
}
#endif
/* Flags, if any field is present in header
*
* Note that this does not account for AuxPtr which is added later if
* remaining data does not fit in single PDU.
*/
if (ext_hdr_flags) {
ext_hdr_len += BLE_LL_EXT_ADV_FLAGS_SIZE;
}
/* AdvData */
data_len = MIN(BLE_LL_MAX_PAYLOAD_LEN - ext_hdr_len, rem_data_len);
/* AuxPtr if there are more AdvData remaining that we can fit here */
if (chainable && (rem_data_len > data_len)) {
/* Add flags if not already added */
if (!ext_hdr_flags) {
ext_hdr_len += BLE_LL_EXT_ADV_FLAGS_SIZE;
data_len -= BLE_LL_EXT_ADV_FLAGS_SIZE;
}
ext_hdr_flags |= (1 << BLE_LL_EXT_ADV_AUX_PTR_BIT);
ext_hdr_len += BLE_LL_EXT_ADV_AUX_PTR_SIZE;
data_len -= BLE_LL_EXT_ADV_AUX_PTR_SIZE;
/* PDU payload should be full if adding AuxPtr */
BLE_LL_ASSERT(ext_hdr_len + data_len == BLE_LL_MAX_PAYLOAD_LEN);
}
*data_len_o = data_len;
*ext_hdr_flags_o = ext_hdr_flags;
return ext_hdr_len + data_len;
}
static void
ble_ll_adv_aux_calculate(struct ble_ll_adv_sm *advsm,
struct ble_ll_adv_aux *aux, uint16_t data_offset)
{
BLE_LL_ASSERT(!aux->sch.enqueued);
BLE_LL_ASSERT((AUX_DATA_LEN(advsm) > data_offset) ||
(AUX_DATA_LEN(advsm) == 0 && data_offset == 0));
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CSA2)
aux->chan = ble_ll_utils_dci_csa2(advsm->event_cntr++,
advsm->channel_id,
g_ble_ll_data.chan_map_used,
g_ble_ll_data.chan_map);
#else
aux->chan = ble_ll_utils_remapped_channel(ble_ll_rand() % BLE_PHY_NUM_DATA_CHANS,
g_ble_ll_data.chan_map);
#endif
aux->data_offset = data_offset;
aux->payload_len = ble_ll_adv_aux_calculate_payload(advsm, advsm->props,
*advsm->aux_data,
data_offset,
&aux->data_len,
&aux->ext_hdr_flags);
}
static bool
ble_ll_adv_aux_check_data_itvl(struct ble_ll_adv_sm *advsm, uint16_t props,
uint8_t pri_phy, uint8_t sec_phy,
struct os_mbuf *data, uint32_t interval_us)
{
uint32_t max_usecs;
uint16_t data_offset;
uint16_t pdu_len;
uint8_t data_len;
uint8_t ext_hdr_flags;
/* FIXME:
* We should include PDUs on primary channel when calculating advertising
* event duration, but the actual time varies a bit in our case due to
* scheduling. For now let's assume we always schedule all PDUs 300us apart
* and we use shortest possible payload (ADI+AuxPtr, no AdvA).
*
* Note that calculations below do not take channel map and max skip into
* account, but we do not support max skip anyway for now.
*/
max_usecs = 3 * (ble_ll_pdu_us(7, pri_phy) + 300) +
BLE_LL_MAFS + MYNEWT_VAL(BLE_LL_SCHED_AUX_MAFS_DELAY);
data_offset = 0;
do {
pdu_len = ble_ll_adv_aux_calculate_payload(advsm, props, data, data_offset,
&data_len, &ext_hdr_flags);
max_usecs += ble_ll_pdu_us(pdu_len, sec_phy);
max_usecs += BLE_LL_MAFS + MYNEWT_VAL(BLE_LL_SCHED_AUX_CHAIN_MAFS_DELAY);
data_offset += data_len;
} while (ext_hdr_flags & (1 << BLE_LL_EXT_ADV_AUX_PTR_BIT));
return max_usecs < interval_us;
}
static void
ble_ll_adv_aux_scheduled(struct ble_ll_adv_sm *advsm, uint32_t sch_start,
void *arg)
{
struct ble_ll_adv_aux *aux = arg;
aux->start_time = sch_start + g_ble_ll_sched_offset_ticks;
}
static void
ble_ll_adv_aux_schedule_next(struct ble_ll_adv_sm *advsm)
{
struct ble_ll_adv_aux *aux;
struct ble_ll_adv_aux *aux_next;
struct ble_ll_sched_item *sch;
uint16_t rem_data_len;
uint16_t next_data_offset;
uint32_t max_usecs;
BLE_LL_ASSERT(advsm->aux_active);
aux = AUX_CURRENT(advsm);
aux_next = AUX_NEXT(advsm);
BLE_LL_ASSERT(!aux_next->sch.enqueued);
/*
* Do not schedule next aux if current aux is no longer scheduled since we
* do not have reference time for scheduling.
*/
if (!aux->sch.enqueued) {
return;
}
/*
* Do not schedule next aux if current aux does not have AuxPtr in extended
* header as this means we do not need subsequent ADV_CHAIN_IND to be sent.
*/
if (!(aux->ext_hdr_flags & (1 << BLE_LL_EXT_ADV_AUX_PTR_BIT))) {
return;
}
next_data_offset = aux->data_offset + aux->data_len;
BLE_LL_ASSERT(AUX_DATA_LEN(advsm) >= next_data_offset);
rem_data_len = AUX_DATA_LEN(advsm) - next_data_offset;
BLE_LL_ASSERT(rem_data_len > 0);
ble_ll_adv_aux_calculate(advsm, aux_next, next_data_offset);
max_usecs = ble_ll_pdu_us(aux_next->payload_len, advsm->sec_phy);
aux_next->start_time = aux->sch.end_time +
ble_ll_tmr_u2t_up(BLE_LL_MAFS +
MYNEWT_VAL(BLE_LL_SCHED_AUX_CHAIN_MAFS_DELAY));
sch = &aux_next->sch;
sch->start_time = aux_next->start_time - g_ble_ll_sched_offset_ticks;
sch->remainder = 0;
sch->end_time = aux_next->start_time + ble_ll_tmr_u2t_up(max_usecs);
ble_ll_sched_adv_new(&aux_next->sch, ble_ll_adv_aux_scheduled, aux_next);
/* Remove aux if previous one was already sent with zero offset or new one
* is scheduled past advertising duration (if set).
*/
if (aux->auxptr_zero ||
(advsm->duration && LL_TMR_GT(aux_next->sch.end_time,
advsm->adv_end_time))) {
ble_ll_sched_rmv_elem(&aux_next->sch);
}
}
static void
ble_ll_adv_aux_schedule_first(struct ble_ll_adv_sm *advsm)
{
struct ble_ll_adv_aux *aux;
struct ble_ll_sched_item *sch;
uint32_t max_usecs;
BLE_LL_ASSERT(!advsm->aux_active);
BLE_LL_ASSERT(!advsm->aux[0].sch.enqueued);
BLE_LL_ASSERT(!advsm->aux[1].sch.enqueued);
advsm->aux_active = 1;
advsm->aux_index = 0;
advsm->aux_first_pdu = 1;
advsm->aux_not_scanned = 0;
advsm->aux_dropped = 0;
aux = AUX_CURRENT(advsm);
aux->auxptr_zero = 0;
ble_ll_adv_aux_calculate(advsm, aux, 0);
/* Set end time to maximum time this schedule item may take */
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE) {
max_usecs = ble_ll_pdu_us(aux->payload_len, advsm->sec_phy) +
BLE_LL_IFS +
/* AUX_CONN_REQ */
ble_ll_pdu_us(34 + 14, advsm->sec_phy) +
BLE_LL_IFS +
/* AUX_CONN_RSP */
ble_ll_pdu_us(14, advsm->sec_phy);
} else if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE) {
/* For scannable advertising we need to calculate how much time we
* need for AUX_ADV_IND along with AUX_SCAN_REQ, AUX_SCAN_RSP and
* IFS in between.
*
* Note:
* 1. aux->payload_len, which calculated by above ble_ll_adv_aux_calulcate(),
* contains AUX_SCAN_RSP length.
* 2. length of AUX_ADV_IND is calculated by special function:
* ble_ll_adv_aux_scannable_pdu_payload_len()
*/
max_usecs = ble_ll_pdu_us(ble_ll_adv_aux_scannable_pdu_payload_len(advsm),
advsm->sec_phy) +
BLE_LL_IFS +
/* AUX_SCAN_REQ */
ble_ll_pdu_us(12, advsm->sec_phy) +
BLE_LL_IFS +
/* AUX_SCAN_RSP */
ble_ll_pdu_us(aux->payload_len, advsm->sec_phy);
} else {
max_usecs = ble_ll_pdu_us(aux->payload_len, advsm->sec_phy);
}
sch = &aux->sch;
sch->start_time = aux->start_time - g_ble_ll_sched_offset_ticks;
sch->remainder = 0;
sch->end_time = aux->start_time + ble_ll_tmr_u2t_up(max_usecs);
ble_ll_sched_adv_new(sch, ble_ll_adv_aux_scheduled, aux);
}
static void
ble_ll_adv_aux_set_start_time(struct ble_ll_adv_sm *advsm)
{
static const uint8_t bits[8] = {0, 1, 1, 2, 1, 2, 2, 3};
struct ble_ll_sched_item *sched = &advsm->adv_sch;
uint32_t adv_pdu_dur;
uint32_t adv_event_dur;
uint8_t chans;
BLE_LL_ASSERT(!advsm->aux_active);
BLE_LL_ASSERT(!advsm->aux[0].sch.enqueued);
BLE_LL_ASSERT(!advsm->aux[1].sch.enqueued);
assert(advsm->adv_chanmask > 0 &&
advsm->adv_chanmask <= BLE_HCI_ADV_CHANMASK_DEF);
chans = bits[advsm->adv_chanmask];
adv_pdu_dur = (int32_t)(sched->end_time - sched->start_time) -
g_ble_ll_sched_offset_ticks;
/* The interval between advertising PDUs may vary due to scheduling, but in
* general we reserve 3 ticks for end-to-schedule time and add scheduler
* offset. That should be more that enough to make sure there's at least
* T_mafs delay between last advertising PDU and auxiliary PDU.
*
* TODO we can make this much more efficient with TX-TX transition
*/
adv_event_dur = (adv_pdu_dur * chans) +
((3 + g_ble_ll_sched_offset_ticks) * (chans - 1));
advsm->aux[0].start_time = advsm->adv_event_start_time + adv_event_dur +
ble_ll_tmr_u2t_up(BLE_LL_MAFS +
MYNEWT_VAL(BLE_LL_SCHED_AUX_MAFS_DELAY));
}
static void
ble_ll_adv_aux_schedule(struct ble_ll_adv_sm *advsm)
{
/*
* For secondary channel we always start by scheduling two consecutive
* auxiliary packets at once. Then, after sending one packet we try to
* schedule another one as long as there are some data left to send. This
* is to make sure we can always calculate AuxPtr to subsequent packet
* without need to scheduled it in an interrupt.
*/
ble_ll_adv_aux_set_start_time(advsm);
ble_ll_adv_aux_schedule_first(advsm);
ble_ll_adv_aux_schedule_next(advsm);
/*
* In case duration is set for advertising set we need to check if at least
* 1st aux will fit inside duration. If not, stop advertising now so we do
* not start extended advertising event which we cannot finish in time.
*/
if (advsm->duration &&
LL_TMR_GT(AUX_CURRENT(advsm)->sch.end_time, advsm->adv_end_time)) {
ble_ll_adv_sm_stop_timeout(advsm);
}
}
#endif
/**
* Called when advertising need to be halted. This normally should not be called
* and is only called when a scheduled item executes but advertising is still
* running.
*
* Context: Interrupt
*/
void
ble_ll_adv_halt(void)
{
struct ble_ll_adv_sm *advsm;
if (g_ble_ll_cur_adv_sm != NULL) {
advsm = g_ble_ll_cur_adv_sm;
ble_ll_trace_u32(BLE_LL_TRACE_ID_ADV_HALT, advsm->adv_instance);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
if (advsm->flags & BLE_LL_ADV_SM_FLAG_PERIODIC_SYNC_SENDING) {
ble_ll_adv_flags_clear(advsm,
BLE_LL_ADV_SM_FLAG_PERIODIC_SYNC_SENDING);
ble_ll_event_add(&advsm->adv_periodic_txdone_ev);
ble_ll_state_set(BLE_LL_STATE_STANDBY);
g_ble_ll_cur_adv_sm = NULL;
return;
}
#endif
ble_ll_event_add(&advsm->adv_txdone_ev);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY)) {
ble_ll_event_add(&advsm->adv_sec_txdone_ev);
}
#endif
ble_ll_state_set(BLE_LL_STATE_STANDBY);
ble_ll_adv_active_chanset_clear(g_ble_ll_cur_adv_sm);
g_ble_ll_cur_adv_sm = NULL;
} else {
ble_ll_trace_u32(BLE_LL_TRACE_ID_ADV_HALT, UINT32_MAX);
}
}
/**
* Called by the HCI command parser when a set advertising parameters command
* has been received.
*
* Context: Link Layer task (HCI command parser)
*
* @param cmd
*
* @return int
*/
int
ble_ll_adv_set_adv_params(const uint8_t *cmdbuf, uint8_t len)
{
const struct ble_hci_le_set_adv_params_cp *cmd = (const void *) cmdbuf;
struct ble_ll_adv_sm *advsm;
uint8_t adv_filter_policy;
uint16_t adv_itvl_min;
uint16_t adv_itvl_max;
uint32_t adv_itvl_usecs;
uint16_t props;
if (len != sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
advsm = &g_ble_ll_adv_sm[0];
if (advsm->adv_enabled) {
return BLE_ERR_CMD_DISALLOWED;
}
/* Make sure intervals are OK (along with advertising type */
adv_itvl_min = le16toh(cmd->min_interval);
adv_itvl_max = le16toh(cmd->max_interval);
/*
* Get the filter policy now since we will ignore it if we are doing
* directed advertising
*/
adv_filter_policy = cmd->filter_policy;
switch (cmd->type) {
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
case BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD:
adv_filter_policy = BLE_HCI_ADV_FILT_NONE;
memcpy(advsm->peer_addr, cmd->peer_addr, BLE_DEV_ADDR_LEN);
/* Ignore min/max interval */
adv_itvl_min = 0;
adv_itvl_max = 0;
props = BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY_HD_DIR ;
break;
case BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_LD:
adv_filter_policy = BLE_HCI_ADV_FILT_NONE;
memcpy(advsm->peer_addr, cmd->peer_addr, BLE_DEV_ADDR_LEN);
props = BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY_LD_DIR ;
break;
case BLE_HCI_ADV_TYPE_ADV_IND:
props = BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY_IND;
break;
#endif
case BLE_HCI_ADV_TYPE_ADV_NONCONN_IND:
props = BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY_NONCONN;
break;
case BLE_HCI_ADV_TYPE_ADV_SCAN_IND:
props = BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY_SCAN;
break;
default:
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* Make sure intervals values are valid
* (HD directed advertising ignores those parameters)
*/
if (!(props & BLE_HCI_LE_SET_EXT_ADV_PROP_HD_DIRECTED)) {
if ((adv_itvl_min > adv_itvl_max) ||
(adv_itvl_min < BLE_HCI_ADV_ITVL_MIN) ||
(adv_itvl_min > BLE_HCI_ADV_ITVL_MAX) ||
(adv_itvl_max < BLE_HCI_ADV_ITVL_MIN) ||
(adv_itvl_max > BLE_HCI_ADV_ITVL_MAX)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
}
if ((cmd->own_addr_type > BLE_HCI_ADV_OWN_ADDR_MAX) ||
(cmd->peer_addr_type > BLE_HCI_ADV_PEER_ADDR_MAX)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
advsm->tx_power = ble_ll_tx_power_round(g_ble_ll_tx_power - g_ble_ll_tx_power_compensation);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
if (cmd->own_addr_type > BLE_HCI_ADV_OWN_ADDR_RANDOM) {
/* Copy peer address */
memcpy(advsm->peer_addr, cmd->peer_addr, BLE_DEV_ADDR_LEN);
}
#else
/* If we dont support privacy some address types wont work */
if (cmd->own_addr_type > BLE_HCI_ADV_OWN_ADDR_RANDOM) {
return BLE_ERR_UNSUPPORTED;
}
#endif
/* There are only three adv channels, so check for any outside the range */
if (((cmd->chan_map & 0xF8) != 0) || (cmd->chan_map == 0)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* Check for valid filter policy */
if (adv_filter_policy > BLE_HCI_ADV_FILT_MAX) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* Determine the advertising interval we will use */
if (props & BLE_HCI_LE_SET_EXT_ADV_PROP_HD_DIRECTED) {
/* Set it to max. allowed for high duty cycle advertising */
adv_itvl_usecs = BLE_LL_ADV_PDU_ITVL_HD_MS_MAX;
} else {
adv_itvl_usecs = adv_itvl_max * BLE_LL_ADV_ITVL;
}
/* Fill out rest of advertising state machine */
advsm->own_addr_type = cmd->own_addr_type;
advsm->peer_addr_type = cmd->peer_addr_type;
advsm->adv_filter_policy = adv_filter_policy;
advsm->adv_chanmask = cmd->chan_map;
advsm->adv_itvl_usecs = adv_itvl_usecs;
advsm->props = props;
return 0;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
static void
ble_ll_adv_update_did(struct ble_ll_adv_sm *advsm)
{
uint16_t old_adi = advsm->adi;
/*
* The Advertising DID for a given advertising set shall be initialized
* with a randomly chosen value. Whenever the Host provides new advertising
* data or scan response data for a given advertising set (whether it is the
* same as the previous data or not), the Advertising DID shall be updated.
* The new value shall be a randomly chosen value that is not the same as
* the previously used value.
*/
do {
advsm->adi = (advsm->adi & 0xf000) | (ble_ll_rand() & 0x0fff);
} while (old_adi == advsm->adi);
}
#endif
static void
ble_ll_adv_update_adv_scan_rsp_data(struct ble_ll_adv_sm *advsm)
{
if (!(advsm->flags & BLE_LL_ADV_SM_FLAG_NEW_ADV_DATA) &&
!(advsm->flags & BLE_LL_ADV_SM_FLAG_NEW_SCAN_RSP_DATA)) {
return;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (advsm->aux_active) {
return;
}
#endif
if (advsm->flags & BLE_LL_ADV_SM_FLAG_NEW_ADV_DATA) {
if (advsm->new_adv_data) {
os_mbuf_free_chain(advsm->adv_data);
advsm->adv_data = advsm->new_adv_data;
advsm->new_adv_data = NULL;
}
ble_ll_adv_flags_clear(advsm, BLE_LL_ADV_SM_FLAG_NEW_ADV_DATA);
} else if (advsm->flags & BLE_LL_ADV_SM_FLAG_NEW_SCAN_RSP_DATA) {
os_mbuf_free_chain(advsm->scan_rsp_data);
advsm->scan_rsp_data = advsm->new_scan_rsp_data;
advsm->new_scan_rsp_data = NULL;
ble_ll_adv_flags_clear(advsm, BLE_LL_ADV_SM_FLAG_NEW_SCAN_RSP_DATA);
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
/* DID shall be updated when host provides new advertising data */
ble_ll_adv_update_did(advsm);
#endif
}
/**
* Stop advertising state machine
*
* Context: Link Layer task.
*
* @param advsm
*/
static void
ble_ll_adv_sm_stop(struct ble_ll_adv_sm *advsm)
{
os_sr_t sr;
if (advsm->adv_enabled) {
ble_ll_rfmgmt_release();
/* Remove any scheduled advertising items */
ble_ll_sched_rmv_elem(&advsm->adv_sch);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
advsm->aux_active = 0;
ble_ll_sched_rmv_elem(&advsm->aux[0].sch);
ble_ll_sched_rmv_elem(&advsm->aux[1].sch);
#endif
/* Set to standby if we are no longer advertising */
OS_ENTER_CRITICAL(sr);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if ((g_ble_ll_cur_adv_sm == advsm) &&
!(advsm->flags & BLE_LL_ADV_SM_FLAG_PERIODIC_SYNC_SENDING)) {
ble_phy_disable();
ble_ll_state_set(BLE_LL_STATE_STANDBY);
g_ble_ll_cur_adv_sm = NULL;
ble_ll_scan_chk_resume();
}
#else
if (ble_ll_state_get() == BLE_LL_STATE_ADV) {
ble_phy_disable();
ble_ll_state_set(BLE_LL_STATE_STANDBY);
g_ble_ll_cur_adv_sm = NULL;
ble_ll_scan_chk_resume();
}
#endif
OS_EXIT_CRITICAL(sr);
ble_ll_event_remove(&advsm->adv_txdone_ev);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
ble_ll_event_remove(&advsm->adv_sec_txdone_ev);
#endif
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
/* If there is an event buf we need to free it */
if (advsm->conn_comp_ev) {
ble_transport_free(advsm->conn_comp_ev);
advsm->conn_comp_ev = NULL;
}
#endif
ble_ll_adv_active_chanset_clear(advsm);
/* Disable advertising */
advsm->adv_enabled = 0;
/* Check if there is outstanding update */
ble_ll_adv_update_adv_scan_rsp_data(advsm);
}
}
static void
ble_ll_adv_sm_stop_timeout(struct ble_ll_adv_sm *advsm)
{
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (ble_ll_hci_adv_mode_ext()) {
ble_ll_hci_ev_send_adv_set_terminated(BLE_ERR_DIR_ADV_TMO,
advsm->adv_instance, 0,
advsm->events);
}
#endif
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
/*
* For high duty directed advertising we need to send connection
* complete event with proper status
*/
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_HD_DIRECTED) {
ble_ll_conn_comp_event_send(NULL, BLE_ERR_DIR_ADV_TMO,
advsm->conn_comp_ev, advsm);
advsm->conn_comp_ev = NULL;
}
#endif
/* Disable advertising */
ble_ll_adv_sm_stop(advsm);
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
static void
ble_ll_adv_sm_stop_limit_reached(struct ble_ll_adv_sm *advsm)
{
ble_ll_hci_ev_send_adv_set_terminated(BLE_ERR_LIMIT_REACHED,
advsm->adv_instance, 0,
advsm->events);
/*
* For high duty directed advertising we need to send connection
* complete event with proper status
*
* Spec is a bit unambiguous here since it doesn't define what code should
* be used if HD directed advertising was terminated before timeout due to
* events count limit. For now just use same code as with duration timeout.
*/
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_HD_DIRECTED) {
ble_ll_conn_comp_event_send(NULL, BLE_ERR_DIR_ADV_TMO,
advsm->conn_comp_ev, advsm);
advsm->conn_comp_ev = NULL;
}
#endif
/* Disable advertising */
ble_ll_adv_sm_stop(advsm);
}
#endif
static void
ble_ll_adv_scheduled(struct ble_ll_adv_sm *advsm, uint32_t sch_start, void *arg)
{
/* The event start time is when we start transmission of the adv PDU */
advsm->adv_event_start_time = sch_start + g_ble_ll_sched_offset_ticks;
advsm->adv_pdu_start_time = advsm->adv_event_start_time;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
/* this is validated for HD adv so no need to do additional checks here
* duration is in 10ms units
*/
if (advsm->duration) {
advsm->adv_end_time = advsm->adv_event_start_time +
ble_ll_tmr_u2t(advsm->duration * 10000);
}
#else
/* Set the time at which we must end directed, high-duty cycle advertising.
*/
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_HD_DIRECTED) {
advsm->adv_end_time = advsm->adv_event_start_time +
ble_ll_tmr_u2t(BLE_LL_ADV_STATE_HD_MAX * 1000);
}
#endif
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
static uint8_t
ble_ll_adv_sync_pdu_make(uint8_t *dptr, void *pducb_arg, uint8_t *hdr_byte)
{
struct ble_ll_adv_sm *advsm;
struct ble_ll_adv_sync *sync;
uint8_t adv_mode;
uint8_t pdu_type;
uint8_t ext_hdr_len;
#if MYNEWT_VAL(BLE_LL_ISO_BROADCASTER)
uint8_t biginfo_len;
#endif
uint32_t offset;
advsm = pducb_arg;
sync = SYNC_CURRENT(advsm);
BLE_LL_ASSERT(!ble_ll_adv_active_chanset_is_sec(advsm));
BLE_LL_ASSERT(advsm->flags & BLE_LL_ADV_SM_FLAG_PERIODIC_SYNC_SENDING);
/* It's the same for AUX_SYNC_IND and AUX_CHAIN_IND */
pdu_type = BLE_ADV_PDU_TYPE_AUX_SYNC_IND;
/* non-connectable and non-scannable */
adv_mode = 0;
ext_hdr_len = sync->payload_len - BLE_LL_EXT_ADV_HDR_LEN - sync->data_len;
dptr[0] = (adv_mode << 6) | ext_hdr_len;
dptr += 1;
/* only put flags if needed */
#if MYNEWT_VAL(BLE_LL_ISO_BROADCASTER)
if (sync->ext_hdr_flags || sync->big) {
dptr[0] = sync->ext_hdr_flags;
dptr += 1;
}
#else
if (sync->ext_hdr_flags) {
dptr[0] = sync->ext_hdr_flags;
dptr += 1;
}
#endif
if (sync->ext_hdr_flags & (1 << BLE_LL_EXT_ADV_AUX_PTR_BIT)) {
if (!SYNC_NEXT(advsm)->sch.enqueued) {
/*
* Trim data here in case we do not have next sync scheduled. This
* can happen if next sync was outside advertising set period and
* was removed from scheduler.
*/
offset = 0;
} else {
offset = ble_ll_tmr_t2u(SYNC_NEXT(advsm)->start_time -
sync->start_time);
}
sync->auxptr_zero = offset == 0;
ble_ll_adv_put_aux_ptr(SYNC_NEXT(advsm)->chan, advsm->sec_phy,
offset, dptr);
dptr += BLE_LL_EXT_ADV_AUX_PTR_SIZE;
}
if (sync->ext_hdr_flags & (1 << BLE_LL_EXT_ADV_TX_POWER_BIT)) {
dptr[0] = advsm->tx_power + g_ble_ll_tx_power_compensation;
dptr += BLE_LL_EXT_ADV_TX_POWER_SIZE;
}
#if MYNEWT_VAL(BLE_LL_ISO_BROADCASTER)
if (advsm->big) {
biginfo_len = ble_ll_iso_big_biginfo_copy(advsm->big, dptr,
sync->sch.start_time +
g_ble_ll_sched_offset_ticks,
sync->sch.remainder);
BLE_LL_ASSERT(biginfo_len > 0);
dptr += biginfo_len;
}
#endif
if (sync->data_len) {
os_mbuf_copydata(advsm->periodic_adv_data, sync->data_offset,
sync->data_len, dptr);
}
*hdr_byte = pdu_type;
return sync->payload_len;
}
static void
ble_ll_adv_sync_tx_done(struct ble_ll_adv_sm *advsm)
{
/* for sync we trace a no pri nor sec set */
ble_ll_trace_u32x2(BLE_LL_TRACE_ID_ADV_TXDONE, advsm->adv_instance, 0);
BLE_LL_ASSERT(advsm->flags & BLE_LL_ADV_SM_FLAG_PERIODIC_SYNC_SENDING);
BLE_LL_ASSERT(!ble_ll_adv_active_chanset_is_sec(advsm));
ble_ll_event_add(&advsm->adv_periodic_txdone_ev);
ble_ll_state_set(BLE_LL_STATE_STANDBY);
ble_ll_adv_flags_clear(advsm, BLE_LL_ADV_SM_FLAG_PERIODIC_SYNC_SENDING);
/* We no longer have a current state machine */
g_ble_ll_cur_adv_sm = NULL;
}
/**
* Called to indicate the advertising sync event is over.
*
* Context: Interrupt
*
* @param advsm
*
*/
static void
ble_ll_adv_sync_tx_end(void *arg)
{
struct ble_ll_adv_sm *advsm = arg;
ble_ll_adv_sync_tx_done(advsm);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_SYNC_TRANSFER)
/* store last sent periodic counter */
advsm->periodic_event_cntr_last_sent = advsm->periodic_event_cntr;
#endif
}
static int
ble_ll_adv_sync_tx_start_cb(struct ble_ll_sched_item *sch)
{
int rc;
uint32_t txstart;
struct ble_ll_adv_sm *advsm;
struct ble_ll_adv_sync *sync;
/* Get the state machine for the event */
advsm = (struct ble_ll_adv_sm *)sch->cb_arg;
/* Set the current advertiser */
g_ble_ll_cur_adv_sm = advsm;
ble_ll_adv_active_chanset_clear(advsm);
ble_ll_adv_flags_set(advsm, BLE_LL_ADV_SM_FLAG_PERIODIC_SYNC_SENDING);
/* Set channel */
sync = SYNC_CURRENT(advsm);
rc = ble_phy_setchan(sync->chan, advsm->periodic_access_addr,
advsm->periodic_crcinit);
BLE_LL_ASSERT(rc == 0);
#if MYNEWT_VAL(BLE_LL_PHY)
/* Set phy mode */
ble_phy_mode_set(advsm->sec_phy, advsm->sec_phy);
#endif
/* Set the power */
ble_ll_tx_power_set(advsm->tx_power);
/* Set transmit start time. */
txstart = sch->start_time + g_ble_ll_sched_offset_ticks;
rc = ble_phy_tx_set_start_time(txstart, sch->remainder);
if (rc) {
STATS_INC(ble_ll_stats, adv_late_starts);
goto adv_tx_done;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_ENCRYPTION)
ble_phy_encrypt_disable();
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
ble_phy_resolv_list_disable();
#endif
/* Transmit advertisement */
ble_phy_set_txend_cb(ble_ll_adv_sync_tx_end, advsm);
rc = ble_phy_tx(ble_ll_adv_sync_pdu_make, advsm, BLE_PHY_TRANSITION_NONE);
if (rc) {
goto adv_tx_done;
}
/* disable whitelisting, we are always non-connectable non-scannable */
ble_ll_whitelist_disable();
/* Set link layer state to advertising */
ble_ll_state_set(BLE_LL_STATE_ADV);
/* Count # of adv. sent */
STATS_INC(ble_ll_stats, adv_txg);
return BLE_LL_SCHED_STATE_RUNNING;
adv_tx_done:
ble_ll_adv_sync_tx_done(advsm);
return BLE_LL_SCHED_STATE_DONE;
}
static void
ble_ll_adv_sync_calculate(struct ble_ll_adv_sm *advsm,
struct ble_ll_adv_sync *sync, uint16_t data_offset,
uint8_t chan)
{
uint16_t rem_data_len;
uint8_t ext_hdr_len;
BLE_LL_ASSERT(!sync->sch.enqueued);
BLE_LL_ASSERT((SYNC_DATA_LEN(advsm) > data_offset) ||
(SYNC_DATA_LEN(advsm) == 0 && data_offset == 0));
sync->data_offset = data_offset;
sync->data_len = 0;
sync->payload_len = 0;
sync->ext_hdr_flags = 0;
sync->chan = chan;
rem_data_len = SYNC_DATA_LEN(advsm) - data_offset;
ext_hdr_len = BLE_LL_EXT_ADV_HDR_LEN;
/* TxPower if configured
* Note: TxPower shall not be present in chain PDU for SYNC
*/
if (data_offset == 0 &&
(advsm->periodic_adv_props & BLE_HCI_LE_SET_PERIODIC_ADV_PROP_INC_TX_PWR)) {
sync->ext_hdr_flags |= (1 << BLE_LL_EXT_ADV_TX_POWER_BIT);
ext_hdr_len += BLE_LL_EXT_ADV_TX_POWER_SIZE;
}
/* if we have any fields in ext header we need to add flags, note that Aux
* PTR is handled later and it will account for flags if needed
*
* This could be handled inside TxPower but lets keep code consistent with
* how Aux calculate works and this also make it easier to add more fields
* into flags if needed in future
*/
#if MYNEWT_VAL(BLE_LL_ISO_BROADCASTER)
sync->big = advsm->big;
/* If BIG is present flags will always be also present even if none is set
* to indicate ACAD is present.
*/
if (sync->ext_hdr_flags || sync->big) {
ext_hdr_len += BLE_LL_EXT_ADV_FLAGS_SIZE;
}
#else
if (sync->ext_hdr_flags) {
ext_hdr_len += BLE_LL_EXT_ADV_FLAGS_SIZE;
}
#endif
#if MYNEWT_VAL(BLE_LL_ISO_BROADCASTER)
if (advsm->big) {
ext_hdr_len += ble_ll_iso_big_biginfo_len(advsm->big);
}
#endif
/* AdvData always */
sync->data_len = MIN(BLE_LL_MAX_PAYLOAD_LEN - ext_hdr_len, rem_data_len);
/* AuxPtr if there are more AdvData remaining that we can fit here */
if ((rem_data_len > sync->data_len)) {
/* adjust for flags that needs to be added if AuxPtr is only field
* in Extended Header
*/
if (!sync->ext_hdr_flags) {
ext_hdr_len += BLE_LL_EXT_ADV_FLAGS_SIZE;
sync->data_len -= BLE_LL_EXT_ADV_FLAGS_SIZE;
}
sync->ext_hdr_flags |= (1 << BLE_LL_EXT_ADV_AUX_PTR_BIT);
ext_hdr_len += BLE_LL_EXT_ADV_AUX_PTR_SIZE;
sync->data_len -= BLE_LL_EXT_ADV_AUX_PTR_SIZE;
/* PDU payload should be full if chained */
BLE_LL_ASSERT(ext_hdr_len + sync->data_len == BLE_LL_MAX_PAYLOAD_LEN);
}
sync->payload_len = ext_hdr_len + sync->data_len;
}
static void
ble_ll_adv_periodic_schedule_first(struct ble_ll_adv_sm *advsm,
bool first_pdu)
{
struct ble_ll_adv_sync *sync;
struct ble_ll_sched_item *sch;
uint32_t max_usecs;
uint8_t chan;
int rc;
BLE_LL_ASSERT(!advsm->periodic_sync_active);
BLE_LL_ASSERT(!advsm->periodic_sync[0].sch.enqueued);
BLE_LL_ASSERT(!advsm->periodic_sync[1].sch.enqueued);
advsm->periodic_sync_active = 1;
advsm->periodic_sync_index = 0;
sync = SYNC_CURRENT(advsm);
sync->auxptr_zero = 0;
/* For first SYNC packet in chain we use separate CSA#2 state to maintain
* freq hopping as advertised in SyncInfo
*
* Preincrement event counter as we later send this in PDU so make sure
* same values are used
*/
chan = ble_ll_utils_dci_csa2(++advsm->periodic_event_cntr,
advsm->periodic_channel_id,
advsm->periodic_num_used_chans,
advsm->periodic_chanmap);
ble_ll_adv_sync_calculate(advsm, sync, 0, chan);
/* sync is always non-connectable and non-scannable*/
max_usecs = ble_ll_pdu_us(sync->payload_len, advsm->sec_phy);
sch = &sync->sch;
ble_ll_tmr_add_u(&advsm->periodic_adv_event_start_time,
&advsm->periodic_adv_event_start_time_remainder,
advsm->periodic_adv_itvl_rem_us);
sch->start_time = advsm->periodic_adv_event_start_time;
sch->remainder = advsm->periodic_adv_event_start_time_remainder;
sch->end_time = sch->start_time + ble_ll_tmr_u2t_up(max_usecs);
sch->start_time -= g_ble_ll_sched_offset_ticks;
rc = ble_ll_sched_periodic_adv(sch, first_pdu);
if (rc) {
STATS_INC(ble_ll_stats, periodic_adv_drop_event);
ble_ll_event_add(&advsm->adv_periodic_txdone_ev);
return;
}
sync->start_time = sch->start_time + g_ble_ll_sched_offset_ticks;
assert(first_pdu ||
(sync->start_time == advsm->periodic_adv_event_start_time));
/* The event start time is when we start transmission of the SYNC PDU */
advsm->periodic_adv_event_start_time = sync->start_time;
}
static void
ble_ll_adv_sync_next_scheduled(struct ble_ll_adv_sm *advsm, uint32_t sch_start,
void *arg)
{
struct ble_ll_adv_sync *sync = arg;
sync->start_time = sch_start + g_ble_ll_sched_offset_ticks;
}
static void
ble_ll_adv_periodic_schedule_next(struct ble_ll_adv_sm *advsm)
{
struct ble_ll_adv_sync *sync;
struct ble_ll_adv_sync *sync_next;
struct ble_ll_sched_item *sch;
uint16_t rem_data_len;
uint16_t next_data_offset;
uint32_t max_usecs;
uint8_t chan;
BLE_LL_ASSERT(advsm->periodic_sync_active);
sync = SYNC_CURRENT(advsm);
sync_next = SYNC_NEXT(advsm);
BLE_LL_ASSERT(!sync_next->sch.enqueued);
/*
* Do not schedule next sync if current sync is no longer scheduled since we
* do not have reference time for scheduling.
*/
if (!sync->sch.enqueued) {
return;
}
/*
* Do not schedule next sync if current sync does not have AuxPtr in extended
* header as this means we do not need subsequent ADV_CHAIN_IND to be sent.
*/
if (!(sync->ext_hdr_flags & (1 << BLE_LL_EXT_ADV_AUX_PTR_BIT))) {
return;
}
next_data_offset = sync->data_offset + sync->data_len;
BLE_LL_ASSERT(SYNC_DATA_LEN(advsm) >= next_data_offset);
rem_data_len = SYNC_DATA_LEN(advsm) - next_data_offset;
BLE_LL_ASSERT(rem_data_len > 0);
/* we use separate counter for chaining */
chan = ble_ll_utils_dci_csa2(advsm->periodic_chain_event_cntr++,
advsm->periodic_channel_id,
advsm->periodic_num_used_chans,
advsm->periodic_chanmap);
ble_ll_adv_sync_calculate(advsm, sync_next, next_data_offset, chan);
max_usecs = ble_ll_pdu_us(sync_next->payload_len, advsm->sec_phy);
sync_next->start_time = sync->sch.end_time +
ble_ll_tmr_u2t_up(BLE_LL_MAFS +
MYNEWT_VAL(BLE_LL_SCHED_AUX_CHAIN_MAFS_DELAY));
sch = &sync_next->sch;
sch->start_time = sync_next->start_time - g_ble_ll_sched_offset_ticks;
/* adjust for previous packets remainder */
sch->remainder = sync->sch.remainder;
sch->end_time = sync_next->start_time + ble_ll_tmr_u2t_up(max_usecs);
/* here we can use ble_ll_sched_adv_new as we don't care about timing */
ble_ll_sched_adv_new(&sync_next->sch, ble_ll_adv_sync_next_scheduled,
sync_next);
/* Remove aux if previous one was already sent with zero offset or new one
* is scheduled past advertising interval.
*/
if (sync->auxptr_zero ||
(LL_TMR_GT(sch->end_time, advsm->periodic_adv_event_start_time +
advsm->periodic_adv_itvl_ticks))) {
STATS_INC(ble_ll_stats, periodic_chain_drop_event);
ble_ll_sched_rmv_elem(&sync->sch);
}
}
static void
ble_ll_adv_sync_schedule(struct ble_ll_adv_sm *advsm, bool first_pdu)
{
/*
* For secondary channel we always start by scheduling two consecutive
* auxiliary packets at once. Then, after sending one packet we try to
* schedule another one as long as there are some data left to send. This
* is to make sure we can always calculate AuxPtr to subsequent packet
* without need to scheduled it in an interrupt.
*/
ble_ll_adv_periodic_schedule_first(advsm, first_pdu);
ble_ll_adv_periodic_schedule_next(advsm);
}
static void
ble_ll_adv_reschedule_periodic_event(struct ble_ll_adv_sm *advsm)
{
advsm->periodic_adv_event_start_time += advsm->periodic_adv_itvl_ticks;
ble_ll_adv_sync_schedule(advsm, false);
}
static void
ble_ll_adv_update_periodic_data(struct ble_ll_adv_sm *advsm)
{
if (!(advsm->flags & BLE_LL_ADV_SM_FLAG_PERIODIC_NEW_DATA)) {
return;
}
if (advsm->periodic_sync_active) {
return;
}
if (advsm->periodic_new_data) {
os_mbuf_free_chain(advsm->periodic_adv_data);
advsm->periodic_adv_data = advsm->periodic_new_data;
advsm->periodic_new_data = NULL;
}
ble_ll_adv_flags_clear(advsm, BLE_LL_ADV_SM_FLAG_PERIODIC_NEW_DATA);
}
/**
* Called when periodic packet is txd on secondary channel
*
* Context: Link Layer task.
*
* @param ev
*/
static void
ble_ll_adv_periodic_done(struct ble_ll_adv_sm *advsm)
{
struct ble_ll_adv_sync *sync;
struct ble_ll_adv_sync *sync_next;
BLE_LL_ASSERT(advsm->periodic_adv_enabled);
BLE_LL_ASSERT(advsm->periodic_adv_active);
BLE_LL_ASSERT(advsm->periodic_sync_active);
ble_ll_rfmgmt_release();
sync = SYNC_CURRENT(advsm);
sync_next = SYNC_NEXT(advsm);
/* Remove anything else scheduled for periodic */
ble_ll_sched_rmv_elem(&sync->sch);
ble_ll_event_remove(&advsm->adv_periodic_txdone_ev);
/* If we have next SYNC scheduled, try to schedule another one */
if (sync_next->sch.enqueued) {
advsm->periodic_sync_index ^= 1;
ble_ll_adv_periodic_schedule_next(advsm);
return;
}
/* Check if we need to resume scanning */
ble_ll_scan_chk_resume();
advsm->periodic_sync_active = 0;
ble_ll_adv_update_periodic_data(advsm);
ble_ll_adv_reschedule_periodic_event(advsm);
}
static void
ble_ll_adv_periodic_event_done(struct ble_npl_event *ev)
{
ble_ll_adv_periodic_done(ble_npl_event_get_arg(ev));
}
static void
ble_ll_adv_sm_start_periodic(struct ble_ll_adv_sm *advsm)
{
uint32_t usecs;
/*
* The Advertising DID is not required to change when a SyncInfo field is
* added to or removed from an advertising set. However, if it does not
* change, then scanners may fail to synchronize to periodic advertising
* because entries in the Advertising DID cache (see Section 4.3.3) mean
* they ignore the advertisements containing the SyncInfo field. Therefore,
* advertisers should update the Advertising DID when a periodic advertising
* train is enabled.
*/
ble_ll_adv_update_did(advsm);
advsm->periodic_adv_active = 1;
/* keep channel map since we cannot change it later on */
memcpy(advsm->periodic_chanmap, g_ble_ll_data.chan_map, BLE_LL_CHAN_MAP_LEN);
advsm->periodic_num_used_chans = g_ble_ll_data.chan_map_used;
advsm->periodic_event_cntr = 0;
/* for chaining we start with random counter as we share access addr */
advsm->periodic_chain_event_cntr = ble_ll_rand();
advsm->periodic_access_addr = ble_ll_utils_calc_aa();
advsm->periodic_channel_id = ((advsm->periodic_access_addr & 0xffff0000) >> 16) ^
(advsm->periodic_access_addr & 0x0000ffff);
advsm->periodic_crcinit = ble_ll_rand() & 0xffffff;
usecs = (uint32_t)advsm->periodic_adv_itvl * BLE_LL_ADV_PERIODIC_ITVL;
advsm->periodic_adv_itvl_ticks = ble_ll_tmr_u2t_r(usecs,
&advsm->periodic_adv_itvl_rem_us);
/* There is no point in starting periodic advertising until next advertising
* event since SyncInfo is needed for synchronization
*/
advsm->periodic_adv_event_start_time_remainder = 0;
advsm->periodic_adv_event_start_time = advsm->adv_pdu_start_time +
ble_ll_tmr_u2t(advsm->adv_itvl_usecs + 5000);
ble_ll_adv_sync_schedule(advsm, true);
}
static void
ble_ll_adv_sm_stop_periodic(struct ble_ll_adv_sm *advsm)
{
os_sr_t sr;
ble_ll_rfmgmt_release();
if (!advsm->periodic_adv_active) {
return;
}
/*
* The Advertising DID is not required to change when a SyncInfo field is
* added to or removed from an advertising set. However, if it does not
* change, then scanners may unnecessary try to synchronize to instance that
* no longer has periodic advertising enabled because entries in the
* Advertising DID cache (see Section 4.3.3) mean they ignore the
* advertisements no longer containing the SyncInfo field. Therefore,
* advertisers should update the Advertising DID when a periodic advertising
* train is disabled.
*/
ble_ll_adv_update_did(advsm);
/* Remove any scheduled advertising items */
advsm->periodic_adv_active = 0;
advsm->periodic_sync_active = 0;
ble_ll_sched_rmv_elem(&advsm->periodic_sync[0].sch);
ble_ll_sched_rmv_elem(&advsm->periodic_sync[1].sch);
/* Set to standby if we are no longer advertising */
OS_ENTER_CRITICAL(sr);
if ((g_ble_ll_cur_adv_sm == advsm) &&
(advsm->flags & BLE_LL_ADV_SM_FLAG_PERIODIC_SYNC_SENDING)) {
ble_phy_disable();
ble_ll_state_set(BLE_LL_STATE_STANDBY);
g_ble_ll_cur_adv_sm = NULL;
ble_ll_scan_chk_resume();
}
OS_EXIT_CRITICAL(sr);
ble_ll_adv_flags_clear(advsm, BLE_LL_ADV_SM_FLAG_PERIODIC_SYNC_SENDING);
ble_ll_event_remove(&advsm->adv_periodic_txdone_ev);
ble_ll_adv_update_periodic_data(advsm);
}
#endif
/**
* Start the advertising state machine. This is called when the host sends
* the "enable advertising" command and is not called again while in the
* advertising state.
*
* Context: Link-layer task.
*
* @param advsm Pointer to advertising state machine
*
* @return int
*/
static int
ble_ll_adv_sm_start(struct ble_ll_adv_sm *advsm)
{
uint8_t adv_chan;
uint8_t *addr;
uint32_t start_delay_us;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CSA2)
uint32_t access_addr;
#endif
const uint8_t *random_addr;
uint32_t earliest_start_time;
int32_t delta;
/* only clear flags that are not set from HCI */
ble_ll_adv_flags_clear(advsm, BLE_LL_ADV_SM_FLAG_TX_ADD |
BLE_LL_ADV_SM_FLAG_RX_ADD |
BLE_LL_ADV_SM_FLAG_CONN_RSP_TXD);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
random_addr = advsm->adv_random_addr;
#else
random_addr = g_random_addr;
#endif
if (!ble_ll_is_valid_own_addr_type(advsm->own_addr_type, random_addr)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/*
* Get an event with which to send the connection complete event if
* this is connectable
*/
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE) {
/* We expect this to be NULL but if not we wont allocate one... */
if (advsm->conn_comp_ev == NULL) {
advsm->conn_comp_ev = ble_transport_alloc_evt(0);
if (!advsm->conn_comp_ev) {
return BLE_ERR_MEM_CAPACITY;
}
}
}
#endif
/* Set advertising address */
if ((advsm->own_addr_type & 1) == 0) {
addr = g_dev_addr;
} else {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
addr = advsm->adv_random_addr;
#else
addr = g_random_addr;
#endif
advsm->flags |= BLE_LL_ADV_SM_FLAG_TX_ADD;
}
memcpy(advsm->adva, addr, BLE_DEV_ADDR_LEN);
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_DIRECTED) {
memcpy(advsm->initiator_addr, advsm->peer_addr, BLE_DEV_ADDR_LEN);
if (advsm->peer_addr_type & 1) {
advsm->flags |= BLE_LL_ADV_SM_FLAG_RX_ADD;
}
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
/* This will generate an RPA for both initiator addr and adva */
if (advsm->own_addr_type > BLE_HCI_ADV_OWN_ADDR_RANDOM) {
ble_ll_adv_rpa_update(advsm);
}
#endif
/* Set flag telling us that advertising is enabled */
advsm->adv_enabled = 1;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CSA2)
advsm->event_cntr = 0;
access_addr = ble_ll_utils_calc_aa();
advsm->channel_id = ((access_addr & 0xffff0000) >> 16) ^
(access_addr & 0x0000ffff);
#endif
/* Set first advertising channel */
adv_chan = ble_ll_adv_first_chan(advsm);
advsm->adv_chan = adv_chan;
/*
* Scheduling 1st PDU is a bit tricky.
* Earliest possible start time is after RF is enabled so just force RF to
* start here to see when if will be fully enabled - it will be too early,
* but this is the only reliable way to have it enabled on time.
* Next we calculate expected start time (randomize it a bit) and this is
* used to setup start time for scheduler item.
* Then we check if start time for scheduler item (which includes scheduler
* overhead) is no earlier than calculated earliest possible start time and
* adjust scheduler item if necessary.
*/
earliest_start_time = ble_ll_rfmgmt_enable_now();
start_delay_us = ble_ll_rand() % (BLE_LL_ADV_DELAY_MS_MAX * 1000);
advsm->adv_pdu_start_time = ble_ll_tmr_get() +
ble_ll_tmr_u2t(start_delay_us);
ble_ll_adv_set_sched(advsm);
delta = (int32_t)(advsm->adv_sch.start_time - earliest_start_time);
if (delta < 0) {
advsm->adv_sch.start_time -= delta;
advsm->adv_sch.end_time -= delta;
}
/* This does actual scheduling */
ble_ll_sched_adv_new(&advsm->adv_sch, ble_ll_adv_scheduled, NULL);
/* we start periodic before AE since we need PDU start time in SyncInfo */
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
if (advsm->periodic_adv_enabled && !advsm->periodic_adv_active) {
ble_ll_adv_sm_start_periodic(advsm);
}
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY)) {
ble_ll_adv_aux_schedule(advsm);
}
#endif
return BLE_ERR_SUCCESS;
}
/**
* Called when the LE HCI command read advertising channel tx power command
* has been received. Returns the current advertising transmit power.
*
* Context: Link Layer task (HCI command parser)
*
* @return int
*/
int
ble_ll_adv_read_txpwr(uint8_t *rspbuf, uint8_t *rsplen)
{
struct ble_hci_le_rd_adv_chan_txpwr_rp *rsp = (void *) rspbuf;
rsp->power_level = g_ble_ll_tx_power;
*rsplen = sizeof(*rsp);
return BLE_ERR_SUCCESS;
}
/**
* Turn advertising on/off.
*
* Context: Link Layer task
*
* @param cmd
*
* @return int
*/
static int
ble_ll_adv_set_enable(uint8_t instance, uint8_t enable, int duration,
uint8_t events)
{
int rc;
struct ble_ll_adv_sm *advsm;
advsm = ble_ll_adv_sm_find_configured(instance);
if (!advsm) {
return BLE_ERR_UNK_ADV_INDENT;
}
rc = BLE_ERR_SUCCESS;
if (enable == 1) {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (advsm->flags & BLE_LL_ADV_SM_FLAG_ADV_DATA_INCOMPLETE) {
return BLE_ERR_CMD_DISALLOWED;
}
if (ble_ll_hci_adv_mode_ext() &&
(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE) &&
!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) &&
SCAN_RSP_DATA_LEN(advsm) == 0) {
return BLE_ERR_CMD_DISALLOWED;
}
/* handle specifics of HD dir adv enabled in legacy way */
if (duration < 0) {
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_HD_DIRECTED) {
duration = BLE_LL_ADV_STATE_HD_MAX / 10;
} else {
duration = 0;
}
}
advsm->duration = duration;
advsm->events_max = events;
advsm->events = 0;
#endif
/* If already enabled, do nothing */
if (!advsm->adv_enabled) {
/* Start the advertising state machine */
rc = ble_ll_adv_sm_start(advsm);
}
} else if (enable == 0) {
ble_ll_adv_sm_stop(advsm);
} else {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
}
return rc;
}
int
ble_ll_hci_adv_set_enable(const uint8_t *cmdbuf, uint8_t len)
{
const struct ble_hci_le_set_adv_enable_cp *cmd = (const void *) cmdbuf;
if (len != sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
return ble_ll_adv_set_enable(0, cmd->enable, -1, 0);
}
static void
ble_ll_adv_update_data_mbuf(struct os_mbuf **omp, bool new_data, uint16_t maxlen,
const void *data, uint16_t datalen)
{
struct os_mbuf *om;
int ret;
om = *omp;
if (new_data) {
if (om) {
os_mbuf_free_chain(om);
}
om = os_msys_get_pkthdr(datalen, 0);
if (!om) {
goto done;
}
}
BLE_LL_ASSERT(om);
if (OS_MBUF_PKTLEN(om) + datalen > maxlen) {
os_mbuf_free_chain(om);
om = NULL;
goto done;
}
ret = os_mbuf_append(om, data, datalen);
if (ret) {
os_mbuf_free_chain(om);
om = NULL;
}
done:
*omp = om;
}
/**
* Set the scan response data that the controller will send.
*
* @param cmd
* @param len
*
* @return int
*/
static int
ble_ll_adv_set_scan_rsp_data(const uint8_t *data, uint8_t datalen,
uint8_t instance, uint8_t operation)
{
struct ble_ll_adv_sm *advsm;
bool new_data;
advsm = ble_ll_adv_sm_find_configured(instance);
if (!advsm) {
return BLE_ERR_UNK_ADV_INDENT;
}
/* check if type of advertising support scan rsp */
if (!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE)) {
if (!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
}
switch (operation) {
case BLE_HCI_LE_SET_DATA_OPER_COMPLETE:
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
if (datalen > BLE_SCAN_RSP_LEGACY_DATA_MAX_LEN) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
}
break;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
case BLE_HCI_LE_SET_DATA_OPER_LAST:
/* TODO mark scan rsp as complete? */
/* fall through */
case BLE_HCI_LE_SET_DATA_OPER_INT:
if (!advsm->scan_rsp_data) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
if (advsm->adv_enabled) {
return BLE_ERR_CMD_DISALLOWED;
}
if (!datalen) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
break;
case BLE_HCI_LE_SET_DATA_OPER_FIRST:
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
if (advsm->adv_enabled) {
return BLE_ERR_CMD_DISALLOWED;
}
if (!datalen) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
break;
#endif
default:
return BLE_ERR_INV_HCI_CMD_PARMS;
}
new_data = (operation == BLE_HCI_LE_SET_DATA_OPER_COMPLETE) ||
(operation == BLE_HCI_LE_SET_DATA_OPER_FIRST);
if (advsm->adv_enabled) {
if (advsm->new_scan_rsp_data) {
ble_ll_adv_flags_clear(advsm, BLE_LL_ADV_SM_FLAG_NEW_SCAN_RSP_DATA);
os_mbuf_free_chain(advsm->new_scan_rsp_data);
advsm->new_scan_rsp_data = NULL;
}
ble_ll_adv_update_data_mbuf(&advsm->new_scan_rsp_data, new_data,
BLE_ADV_DATA_MAX_LEN, data, datalen);
if (!advsm->new_scan_rsp_data) {
return BLE_ERR_MEM_CAPACITY;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) &&
!ble_ll_adv_aux_check_data_itvl(advsm, advsm->props, advsm->pri_phy,
advsm->sec_phy,
advsm->new_scan_rsp_data,
advsm->adv_itvl_usecs)) {
os_mbuf_free_chain(advsm->new_scan_rsp_data);
advsm->new_scan_rsp_data = NULL;
return BLE_ERR_PACKET_TOO_LONG;
}
#endif
ble_ll_adv_flags_set(advsm, BLE_LL_ADV_SM_FLAG_NEW_SCAN_RSP_DATA);
} else {
ble_ll_adv_update_data_mbuf(&advsm->scan_rsp_data, new_data,
BLE_SCAN_RSP_DATA_MAX_LEN, data, datalen);
if (!advsm->scan_rsp_data) {
return BLE_ERR_MEM_CAPACITY;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) &&
!ble_ll_adv_aux_check_data_itvl(advsm, advsm->props, advsm->pri_phy,
advsm->sec_phy,
advsm->scan_rsp_data,
advsm->adv_itvl_usecs)) {
os_mbuf_free_chain(advsm->scan_rsp_data);
advsm->scan_rsp_data = NULL;
return BLE_ERR_PACKET_TOO_LONG;
}
/* DID shall be updated when host provides new scan response data */
ble_ll_adv_update_did(advsm);
#endif
}
return BLE_ERR_SUCCESS;
}
int
ble_ll_hci_set_scan_rsp_data(const uint8_t *cmdbuf, uint8_t len)
{
const struct ble_hci_le_set_scan_rsp_data_cp *cmd = (const void *) cmdbuf;
if ((len != sizeof(*cmd)) || (cmd->scan_rsp_len > sizeof(cmd->scan_rsp))) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
return ble_ll_adv_set_scan_rsp_data(cmd->scan_rsp, cmd->scan_rsp_len, 0,
BLE_HCI_LE_SET_DATA_OPER_COMPLETE);
}
/**
* Called by the LL HCI command parser when a set advertising
* data command has been sent from the host to the controller.
*
* @param cmd Pointer to command data
* @param len Length of command data
*
* @return int 0: success; BLE_ERR_INV_HCI_CMD_PARMS otherwise.
*/
static int
ble_ll_adv_set_adv_data(const uint8_t *data, uint8_t datalen, uint8_t instance,
uint8_t operation)
{
struct ble_ll_adv_sm *advsm;
bool new_data;
advsm = ble_ll_adv_sm_find_configured(instance);
if (!advsm) {
return BLE_ERR_UNK_ADV_INDENT;
}
/* check if type of advertising support adv data */
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_DIRECTED) {
if (ble_ll_hci_adv_mode_ext()) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
}
} else {
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
}
switch (operation) {
case BLE_HCI_LE_SET_DATA_OPER_COMPLETE:
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
if (datalen > BLE_ADV_LEGACY_DATA_MAX_LEN) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
}
ble_ll_adv_flags_clear(advsm, BLE_LL_ADV_SM_FLAG_ADV_DATA_INCOMPLETE);
break;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
case BLE_HCI_LE_SET_DATA_OPER_UNCHANGED:
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
if (!advsm->adv_enabled || !ADV_DATA_LEN(advsm) || datalen) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* update DID only */
ble_ll_adv_update_did(advsm);
return BLE_ERR_SUCCESS;
case BLE_HCI_LE_SET_DATA_OPER_LAST:
ble_ll_adv_flags_clear(advsm, BLE_LL_ADV_SM_FLAG_ADV_DATA_INCOMPLETE);
/* fall through */
case BLE_HCI_LE_SET_DATA_OPER_INT:
if (!advsm->adv_data) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
if (!datalen) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
if (advsm->adv_enabled) {
return BLE_ERR_CMD_DISALLOWED;
}
break;
case BLE_HCI_LE_SET_DATA_OPER_FIRST:
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
if (advsm->adv_enabled) {
return BLE_ERR_CMD_DISALLOWED;
}
if (!datalen) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
ble_ll_adv_flags_set(advsm, BLE_LL_ADV_SM_FLAG_ADV_DATA_INCOMPLETE);
break;
#endif
default:
return BLE_ERR_INV_HCI_CMD_PARMS;
}
new_data = (operation == BLE_HCI_LE_SET_DATA_OPER_COMPLETE) ||
(operation == BLE_HCI_LE_SET_DATA_OPER_FIRST);
if (advsm->adv_enabled) {
if (advsm->new_adv_data) {
ble_ll_adv_flags_clear(advsm, BLE_LL_ADV_SM_FLAG_NEW_ADV_DATA);
os_mbuf_free_chain(advsm->new_adv_data);
advsm->new_adv_data = NULL;
}
ble_ll_adv_update_data_mbuf(&advsm->new_adv_data, new_data,
BLE_ADV_DATA_MAX_LEN, data, datalen);
if (!advsm->new_adv_data) {
return BLE_ERR_MEM_CAPACITY;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) &&
!ble_ll_adv_aux_check_data_itvl(advsm, advsm->props, advsm->pri_phy,
advsm->sec_phy, advsm->new_adv_data,
advsm->adv_itvl_usecs)) {
os_mbuf_free_chain(advsm->new_adv_data);
advsm->new_adv_data = NULL;
return BLE_ERR_PACKET_TOO_LONG;
}
#endif
ble_ll_adv_flags_set(advsm, BLE_LL_ADV_SM_FLAG_NEW_ADV_DATA);
} else {
ble_ll_adv_update_data_mbuf(&advsm->adv_data, new_data,
BLE_ADV_DATA_MAX_LEN, data, datalen);
if (!advsm->adv_data) {
return BLE_ERR_MEM_CAPACITY;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) &&
!ble_ll_adv_aux_check_data_itvl(advsm, advsm->props, advsm->pri_phy,
advsm->sec_phy, advsm->adv_data,
advsm->adv_itvl_usecs)) {
os_mbuf_free_chain(advsm->adv_data);
advsm->adv_data = NULL;
return BLE_ERR_PACKET_TOO_LONG;
}
/* DID shall be updated when host provides new advertising data */
ble_ll_adv_update_did(advsm);
#endif
}
return BLE_ERR_SUCCESS;
}
int
ble_ll_hci_set_adv_data(const uint8_t *cmdbuf, uint8_t len)
{
const struct ble_hci_le_set_adv_data_cp *cmd = (const void *) cmdbuf;
if ((len != sizeof(*cmd)) || (cmd->adv_data_len > sizeof(cmd->adv_data))) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
return ble_ll_adv_set_adv_data(cmd->adv_data, cmd->adv_data_len, 0,
BLE_HCI_LE_SET_DATA_OPER_COMPLETE);
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
static bool
pri_phy_valid(uint8_t phy)
{
switch (phy) {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
case BLE_HCI_LE_PHY_CODED:
#endif
case BLE_HCI_LE_PHY_1M:
return true;
default:
return false;
}
}
static bool
sec_phy_valid(uint8_t phy)
{
switch (phy) {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CODED_PHY)
case BLE_HCI_LE_PHY_CODED:
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_2M_PHY)
case BLE_HCI_LE_PHY_2M:
#endif
case BLE_HCI_LE_PHY_1M:
return true;
default:
return false;
}
}
static struct ble_ll_adv_sm *
ble_ll_adv_sm_get(uint8_t instance)
{
struct ble_ll_adv_sm *advsm;
unsigned int i;
advsm = ble_ll_adv_sm_find_configured(instance);
if (advsm) {
return advsm;
}
for (i = 0; i < ARRAY_SIZE(g_ble_ll_adv_sm); i++) {
advsm = &g_ble_ll_adv_sm[i];
if (!(advsm->flags & BLE_LL_ADV_SM_FLAG_CONFIGURED)) {
ble_ll_adv_sm_init(advsm);
/* configured flag is set by caller on success config */
advsm->adv_instance = instance;
return advsm;
}
}
return NULL;
}
int
ble_ll_adv_ext_set_param(const uint8_t *cmdbuf, uint8_t len,
uint8_t *rspbuf, uint8_t *rsplen)
{
const struct ble_hci_le_set_ext_adv_params_cp *cmd = (const void *) cmdbuf;
struct ble_hci_le_set_ext_adv_params_rp *rsp = (void *) rspbuf;
struct ble_ll_adv_sm *advsm;
uint32_t adv_itvl_min;
uint32_t adv_itvl_max;
uint32_t adv_itvl_usecs;
uint16_t props;
int rc;
if (len != sizeof(*cmd )) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
advsm = ble_ll_adv_sm_get(cmd->adv_handle);
if (!advsm) {
rc = BLE_ERR_MEM_CAPACITY;
goto done;
}
if (advsm->adv_enabled) {
rc = BLE_ERR_CMD_DISALLOWED;
goto done;
}
props = le16toh(cmd->props);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
/* If the Host issues this command when periodic advertising is enabled for
* the specified advertising set and connectable, scannable, legacy, or
* anonymous advertising is specified, the Controller shall return the
* error code Invalid HCI Command Parameters (0x12).
*/
if (advsm->flags & BLE_LL_ADV_SM_FLAG_PERIODIC_CONFIGURED) {
if (advsm->periodic_adv_enabled) {
if (props & (BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE |
BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE |
BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY |
BLE_HCI_LE_SET_EXT_ADV_PROP_ANON_ADV)) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
}
}
#endif
adv_itvl_min = cmd->pri_itvl_min[2] << 16 | cmd->pri_itvl_min[1] << 8 |
cmd->pri_itvl_min[0];
adv_itvl_max = cmd->pri_itvl_max[2] << 16 | cmd->pri_itvl_max[1] << 8 |
cmd->pri_itvl_max[0];
if (props & ~BLE_HCI_LE_SET_EXT_ADV_PROP_MASK) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
if (props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
if (ADV_DATA_LEN(advsm) > BLE_ADV_LEGACY_DATA_MAX_LEN ||
SCAN_RSP_DATA_LEN(advsm) > BLE_SCAN_RSP_LEGACY_DATA_MAX_LEN) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
/* if legacy bit is set possible values are limited */
switch (props) {
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
case BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY_IND:
case BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY_LD_DIR:
case BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY_HD_DIR:
#endif
case BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY_SCAN:
case BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY_NONCONN:
break;
default:
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
} else {
#if !MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
if (props & BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
#endif
/* HD directed advertising allowed only on legacy PDUs */
if (props & BLE_HCI_LE_SET_EXT_ADV_PROP_HD_DIRECTED) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
/* if ext advertising PDUs are used then it shall not be both
* connectable and scanable
*/
if ((props & BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE) &&
(props & BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE)) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
}
/* High Duty Directed advertising is special */
if (props & BLE_HCI_LE_SET_EXT_ADV_PROP_HD_DIRECTED) {
if (ADV_DATA_LEN(advsm) || SCAN_RSP_DATA_LEN(advsm)) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
/* Ignore min/max interval */
adv_itvl_min = 0;
adv_itvl_max = 0;
} else {
/* validate intervals for non HD-directed advertising */
if ((adv_itvl_min > adv_itvl_max) ||
(adv_itvl_min < BLE_HCI_ADV_ITVL_MIN) ||
(adv_itvl_max < BLE_HCI_ADV_ITVL_MIN)) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
/* TODO for now limit those to values from legacy advertising
*
* If the primary advertising interval range is outside the advertising
* interval range supported by the Controller, then the Controller shall
* return the error code Unsupported Feature or Parameter Value (0x11).
*/
if ((adv_itvl_min > BLE_HCI_ADV_ITVL_MAX) ||
(adv_itvl_max > BLE_HCI_ADV_ITVL_MAX)) {
rc = BLE_ERR_UNSUPPORTED;
goto done;
}
}
/* There are only three adv channels, so check for any outside the range */
if (((cmd->pri_chan_map & 0xF8) != 0) || (cmd->pri_chan_map == 0)) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
if (cmd->own_addr_type > BLE_HCI_ADV_OWN_ADDR_MAX) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
#if !MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
/* If we dont support privacy some address types wont work */
if (cmd->own_addr_type > BLE_HCI_ADV_OWN_ADDR_RANDOM) {
rc = BLE_ERR_UNSUPPORTED;
goto done;
}
#endif
/* peer address type is only valid for directed */
if ((props & BLE_HCI_LE_SET_EXT_ADV_PROP_DIRECTED) &&
(cmd->peer_addr_type > BLE_HCI_ADV_PEER_ADDR_MAX)) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
/* Check filter policy (valid only for undirected) */
if (!(props & BLE_HCI_LE_SET_EXT_ADV_PROP_DIRECTED) &&
cmd->filter_policy > BLE_HCI_ADV_FILT_MAX) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
if (!pri_phy_valid(cmd->pri_phy)) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
/* check secondary phy only if not using legacy PDUs */
if (!(props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) &&
!sec_phy_valid(cmd->sec_phy)) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
if (cmd->sid > 0x0f) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
if (cmd->scan_req_notif > 0x01) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
/* Determine the advertising interval we will use */
if (props & BLE_HCI_LE_SET_EXT_ADV_PROP_HD_DIRECTED) {
/* Set it to max. allowed for high duty cycle advertising */
adv_itvl_usecs = BLE_LL_ADV_PDU_ITVL_HD_MS_MAX;
} else {
adv_itvl_usecs = adv_itvl_max * BLE_LL_ADV_ITVL;
}
if (!ble_ll_adv_aux_check_data_itvl(advsm, props, cmd->pri_phy, cmd->sec_phy,
advsm->adv_data, adv_itvl_usecs) ||
!ble_ll_adv_aux_check_data_itvl(advsm, props, cmd->pri_phy, cmd->sec_phy,
advsm->scan_rsp_data, adv_itvl_usecs)) {
return BLE_ERR_PACKET_TOO_LONG;
}
rc = BLE_ERR_SUCCESS;
if (cmd->tx_power == 127) {
/* no preference */
advsm->tx_power = ble_ll_tx_power_round(g_ble_ll_tx_power - g_ble_ll_tx_power_compensation);
} else {
advsm->tx_power = ble_ll_tx_power_round(MIN(cmd->tx_power, MYNEWT_VAL(BLE_LL_TX_PWR_MAX_DBM)) -
g_ble_ll_tx_power_compensation);
}
/* we can always store as those are validated and used only when needed */
advsm->peer_addr_type = cmd->peer_addr_type;
memcpy(advsm->peer_addr, cmd->peer_addr, BLE_DEV_ADDR_LEN);
advsm->own_addr_type = cmd->own_addr_type;
advsm->adv_filter_policy = cmd->filter_policy;
advsm->adv_chanmask = cmd->pri_chan_map;
advsm->adv_itvl_usecs = adv_itvl_usecs;
advsm->pri_phy = cmd->pri_phy;
advsm->sec_phy = cmd->sec_phy;
/* Update SID only */
advsm->adi = (advsm->adi & 0x0fff) | ((cmd->sid << 12));
advsm->props = props;
/* Set proper mbuf chain for aux data */
if (props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
advsm->aux_data = NULL;
} else if (props & BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE) {
advsm->aux_data = &advsm->scan_rsp_data;
} else {
advsm->aux_data = &advsm->adv_data;
}
if (cmd->scan_req_notif) {
ble_ll_adv_flags_set(advsm, BLE_LL_ADV_SM_FLAG_SCAN_REQ_NOTIF);
} else {
ble_ll_adv_flags_clear(advsm, BLE_LL_ADV_SM_FLAG_SCAN_REQ_NOTIF);
}
ble_ll_adv_flags_set(advsm, BLE_LL_ADV_SM_FLAG_CONFIGURED);
done:
/* Update TX power */
rsp->tx_power = rc ? 0 : (advsm->tx_power + g_ble_ll_tx_power_compensation);
*rsplen = sizeof(*rsp);
return rc;
}
int
ble_ll_adv_ext_set_adv_data(const uint8_t *cmdbuf, uint8_t cmdlen)
{
const struct ble_hci_le_set_ext_adv_data_cp *cmd = (const void *) cmdbuf;
if (cmdlen < sizeof(*cmd )) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
if (cmd->adv_data_len > BLE_HCI_MAX_EXT_ADV_DATA_LEN ||
cmd->adv_data_len > cmdlen - sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* TODO fragment preference ignored for now */
return ble_ll_adv_set_adv_data(cmd->adv_data, cmd->adv_data_len,
cmd->adv_handle, cmd->operation);
}
int
ble_ll_adv_ext_set_scan_rsp(const uint8_t *cmdbuf, uint8_t cmdlen)
{
const struct ble_hci_le_set_ext_scan_rsp_data_cp *cmd = (const void *) cmdbuf;
if (cmdlen < sizeof(*cmd )) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
if (cmd->scan_rsp_len > BLE_HCI_MAX_EXT_ADV_DATA_LEN ||
cmd->scan_rsp_len > cmdlen - sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* TODO fragment preference ignored for now */
return ble_ll_adv_set_scan_rsp_data(cmd->scan_rsp, cmd->scan_rsp_len,
cmd->adv_handle, cmd->operation);
}
/**
* HCI LE extended advertising enable command
*
* @param cmd Pointer to command data
* @param len Command data length
*
* @return int BLE error code
*/
int
ble_ll_adv_ext_set_enable(const uint8_t *cmdbuf, uint8_t len)
{
const struct ble_hci_le_set_ext_adv_enable_cp *cmd = (const void *) cmdbuf;
struct ble_ll_adv_sm *advsm;
int i, j, rc;
if (len < sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* check if length is correct */
if (len != 2 + (cmd->num_sets * sizeof(cmd->sets[0]))) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
if (cmd->num_sets > BLE_ADV_INSTANCES) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
if (cmd->num_sets == 0) {
if (cmd->enable) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* disable all instances */
for (i = 0; i < BLE_ADV_INSTANCES; i++) {
ble_ll_adv_set_enable(i, 0, 0, 0);
}
return BLE_ERR_SUCCESS;
}
/* validate instances */
for (i = 0; i < cmd->num_sets; i++) {
/* validate duplicated sets */
for (j = i + 1; j < cmd->num_sets; j++) {
if (cmd->sets[i].adv_handle == cmd->sets[j].adv_handle) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
}
advsm = ble_ll_adv_sm_find_configured(cmd->sets[i].adv_handle);
if (!advsm) {
return BLE_ERR_UNK_ADV_INDENT;
}
if (cmd->enable) {
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_HD_DIRECTED) {
if (cmd->sets[i].duration == 0 ||
le16toh(cmd->sets[i].duration) > 128) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
}
}
}
for (i = 0; i < cmd->num_sets; i++) {
rc = ble_ll_adv_set_enable(cmd->sets[i].adv_handle, cmd->enable,
le16toh(cmd->sets[i].duration),
cmd->sets[i].max_events);
if (rc) {
return rc;
}
}
return BLE_ERR_SUCCESS;
}
int
ble_ll_adv_set_random_addr(const uint8_t *addr, uint8_t instance)
{
struct ble_ll_adv_sm *advsm;
advsm = ble_ll_adv_sm_find_configured(instance);
if (!advsm) {
return BLE_ERR_UNK_ADV_INDENT;
}
/*
* Reject if connectable advertising is on
* Core Spec Vol. 2 Part E 7.8.52
*/
if (advsm->adv_enabled &&
(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE)) {
return BLE_ERR_CMD_DISALLOWED;
}
memcpy(advsm->adv_random_addr, addr, BLE_DEV_ADDR_LEN);
return BLE_ERR_SUCCESS;
}
int
ble_ll_adv_hci_set_random_addr(const uint8_t *cmdbuf, uint8_t len)
{
const struct ble_hci_le_set_adv_set_rnd_addr_cp *cmd = (const void *) cmdbuf;
if (len != sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
return ble_ll_adv_set_random_addr(cmd->addr, cmd->adv_handle);
}
/**
* HCI LE extended advertising remove command
*
* @return int BLE error code
*/
int
ble_ll_adv_remove(const uint8_t *cmdbuf, uint8_t len)
{
const struct ble_hci_le_remove_adv_set_cp *cmd = (const void *) cmdbuf;
struct ble_ll_adv_sm *advsm;
if (len != sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
advsm = ble_ll_adv_sm_find_configured(cmd->adv_handle);
if (!advsm) {
return BLE_ERR_UNK_ADV_INDENT;
}
if (advsm->adv_enabled) {
return BLE_ERR_CMD_DISALLOWED;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
if (advsm->periodic_adv_enabled) {
return BLE_ERR_CMD_DISALLOWED;
}
if (advsm->periodic_adv_data) {
os_mbuf_free_chain(advsm->periodic_adv_data);
}
#endif
if (advsm->adv_data) {
os_mbuf_free_chain(advsm->adv_data);
}
if (advsm->scan_rsp_data) {
os_mbuf_free_chain(advsm->scan_rsp_data);
}
ble_ll_adv_sm_init(advsm);
return BLE_ERR_SUCCESS;
}
/**
* HCI LE extended advertising clear command
*
* @return int BLE error code
*/
int
ble_ll_adv_clear_all(void)
{
int i;
for (i = 0; i < BLE_ADV_INSTANCES; i++) {
if (g_ble_ll_adv_sm[i].adv_enabled) {
return BLE_ERR_CMD_DISALLOWED;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
if (g_ble_ll_adv_sm[i].periodic_adv_enabled) {
return BLE_ERR_CMD_DISALLOWED;
}
#endif
}
ble_ll_adv_reset();
return BLE_ERR_SUCCESS;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
static uint16_t
ble_ll_adv_sync_get_pdu_len(uint16_t data_len, uint16_t *data_offset,
uint16_t props)
{
uint16_t rem_data_len = data_len - *data_offset;
uint8_t hdr_len = BLE_LL_EXT_ADV_HDR_LEN;
uint8_t ext_hdr = 0;
/* TxPower if configured
* Note: TxPower shall not be present in chain PDU for SYNC
*/
if (*data_offset == 0 &&
(props & BLE_HCI_LE_SET_PERIODIC_ADV_PROP_INC_TX_PWR)) {
ext_hdr |= (1 << BLE_LL_EXT_ADV_TX_POWER_BIT);
hdr_len += BLE_LL_EXT_ADV_TX_POWER_SIZE;
}
/* if we have any fields in ext header we need to add flags, note that Aux
* PTR is handled later and it will account for flags if needed
*
* This could be handled inside TxPower but lets keep code consistent with
* how Aux calculate works and this also make it easier to add more fields
* into flags if needed in future
*/
if (ext_hdr) {
hdr_len += BLE_LL_EXT_ADV_FLAGS_SIZE;
}
/* AdvData always */
data_len = MIN(BLE_LL_MAX_PAYLOAD_LEN - hdr_len, rem_data_len);
/* AuxPtr if there are more AdvData remaining that we can fit here */
if (rem_data_len > data_len) {
/* adjust for flags that needs to be added if AuxPtr is only field
* in Extended Header
*/
if (!ext_hdr) {
hdr_len += BLE_LL_EXT_ADV_FLAGS_SIZE;
data_len -= BLE_LL_EXT_ADV_FLAGS_SIZE;
}
hdr_len += BLE_LL_EXT_ADV_AUX_PTR_SIZE;
data_len -= BLE_LL_EXT_ADV_AUX_PTR_SIZE;
/* PDU payload should be full if chained */
BLE_LL_ASSERT(hdr_len + data_len == BLE_LL_MAX_PAYLOAD_LEN);
}
*data_offset += data_len;
return hdr_len + data_len;
}
static bool
ble_ll_adv_periodic_check_data_itvl(uint16_t payload_len, uint16_t props,
uint16_t itvl, uint8_t phy)
{
uint32_t max_usecs = 0;
uint32_t itvl_usecs;
uint16_t offset = 0;
uint16_t pdu_len;
while (offset < payload_len) {
pdu_len = ble_ll_adv_sync_get_pdu_len(payload_len, &offset, props);
max_usecs += ble_ll_pdu_us(pdu_len, phy);
max_usecs += BLE_LL_MAFS + MYNEWT_VAL(BLE_LL_SCHED_AUX_CHAIN_MAFS_DELAY);
}
itvl_usecs = (uint32_t)itvl * BLE_LL_ADV_PERIODIC_ITVL;
return max_usecs < itvl_usecs;
}
int
ble_ll_adv_periodic_set_param(const uint8_t *cmdbuf, uint8_t len)
{
const struct ble_hci_le_set_periodic_adv_params_cp *cmd = (const void *) cmdbuf;
struct ble_ll_adv_sm *advsm;
uint16_t adv_itvl_min;
uint16_t adv_itvl_max;
uint16_t props;
if (len != sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
adv_itvl_min = le16toh(cmd->min_itvl);
adv_itvl_max = le16toh(cmd->max_itvl);
props = le16toh(cmd->props);
advsm = ble_ll_adv_sm_find_configured(cmd->adv_handle);
if (!advsm) {
return BLE_ERR_UNK_ADV_INDENT;
}
/* If the advertising set identified by the Advertising_Handle specified
* scannable, connectable, legacy, or anonymous advertising, the Controller
* shall return the error code Invalid HCI Command Parameters (0x12).
*/
if (advsm->props & (BLE_HCI_LE_SET_EXT_ADV_PROP_ANON_ADV |
BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE |
BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE |
BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* If the Host issues this command when periodic advertising is enabled for
* the specified advertising set, the Controller shall return the error code
* Command Disallowed (0x0C).
*/
if (advsm->periodic_adv_enabled) {
return BLE_ERR_CMD_DISALLOWED;
}
/* validate intervals */
if ((adv_itvl_min < 0x0006) || (adv_itvl_max < 0x006) ||
(adv_itvl_min > adv_itvl_max)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* validate properties */
if (props & ~BLE_HCI_LE_SET_PERIODIC_ADV_PROP_MASK) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
/* If the advertising set already contains periodic advertising data and the
* length of the data is greater than the maximum that the Controller can
* transmit within a periodic advertising interval of
* Periodic_Advertising_Interval_Max, the Controller shall return the error
* code Packet Too Long (0x45).
*/
if (!ble_ll_adv_periodic_check_data_itvl(SYNC_DATA_LEN(advsm), props,
adv_itvl_max, advsm->sec_phy)) {
return BLE_ERR_PACKET_TOO_LONG;
}
advsm->periodic_adv_itvl = adv_itvl_max;
advsm->periodic_adv_props = props;
ble_ll_adv_flags_set(advsm, BLE_LL_ADV_SM_FLAG_PERIODIC_CONFIGURED);
return BLE_ERR_SUCCESS;
}
int
ble_ll_adv_periodic_set_data(const uint8_t *cmdbuf, uint8_t len)
{
const struct ble_hci_le_set_periodic_adv_data_cp *cmd = (const void *) cmdbuf;
struct ble_ll_adv_sm *advsm;
uint16_t payload_total_len;
bool new_data = false;
if (len < sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
if (cmd->adv_data_len > BLE_HCI_MAX_PERIODIC_ADV_DATA_LEN ||
cmd->adv_data_len != len - sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
advsm = ble_ll_adv_sm_find_configured(cmd->adv_handle);
if (!advsm) {
return BLE_ERR_UNK_ADV_INDENT;
}
if (!(advsm->flags & BLE_LL_ADV_SM_FLAG_PERIODIC_CONFIGURED)) {
return BLE_ERR_CMD_DISALLOWED;
}
switch (cmd->operation) {
case BLE_HCI_LE_SET_DATA_OPER_LAST:
case BLE_HCI_LE_SET_DATA_OPER_INT:
if (advsm->periodic_adv_enabled) {
return BLE_ERR_CMD_DISALLOWED;
}
if (!(advsm->flags & BLE_LL_ADV_SM_FLAG_PERIODIC_DATA_INCOMPLETE)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
if (!advsm->periodic_adv_data || !cmd->adv_data_len) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
break;
case BLE_HCI_LE_SET_DATA_OPER_FIRST:
if (advsm->periodic_adv_enabled) {
return BLE_ERR_CMD_DISALLOWED;
}
if (!cmd->adv_data_len) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
new_data = true;
break;
case BLE_HCI_LE_SET_DATA_OPER_COMPLETE:
new_data = true;
break;
default:
return BLE_ERR_INV_HCI_CMD_PARMS;
}
payload_total_len = cmd->adv_data_len;
if (!new_data) {
payload_total_len += SYNC_DATA_LEN(advsm);
}
/* If the combined length of the data is greater than the maximum that the
* Controller can transmit within the current periodic advertising interval
* (if periodic advertising is currently enabled) or the
* Periodic_Advertising_Interval_Max for the advertising set (if currently
* disabled), all the data shall be discarded and the Controller shall
* return the error code Packet Too Long (0x45).
*/
if (!ble_ll_adv_periodic_check_data_itvl(payload_total_len,
advsm->periodic_adv_props,
advsm->periodic_adv_itvl,
advsm->sec_phy)) {
return BLE_ERR_PACKET_TOO_LONG;
}
if (advsm->periodic_adv_active) {
ble_ll_adv_flags_clear(advsm, BLE_LL_ADV_SM_FLAG_PERIODIC_NEW_DATA);
ble_ll_adv_update_data_mbuf(&advsm->periodic_new_data, true,
BLE_ADV_DATA_MAX_LEN,
cmd->adv_data, cmd->adv_data_len);
if (!advsm->periodic_new_data) {
return BLE_ERR_MEM_CAPACITY;
}
ble_ll_adv_flags_set(advsm, BLE_LL_ADV_SM_FLAG_PERIODIC_NEW_DATA);
} else {
ble_ll_adv_update_data_mbuf(&advsm->periodic_adv_data, new_data,
BLE_ADV_DATA_MAX_LEN, cmd->adv_data,
cmd->adv_data_len);
if (!advsm->periodic_adv_data) {
return BLE_ERR_MEM_CAPACITY;
}
}
/* set/clear incomplete data flag only on success */
switch (cmd->operation) {
case BLE_HCI_LE_SET_DATA_OPER_LAST:
case BLE_HCI_LE_SET_DATA_OPER_COMPLETE:
ble_ll_adv_flags_clear(advsm,
BLE_LL_ADV_SM_FLAG_PERIODIC_DATA_INCOMPLETE);
break;
case BLE_HCI_LE_SET_DATA_OPER_INT:
case BLE_HCI_LE_SET_DATA_OPER_FIRST:
default:
ble_ll_adv_flags_set(advsm,
BLE_LL_ADV_SM_FLAG_PERIODIC_DATA_INCOMPLETE);
break;
}
return BLE_ERR_SUCCESS;
}
int
ble_ll_adv_periodic_enable(const uint8_t *cmdbuf, uint8_t len)
{
const struct ble_hci_le_set_periodic_adv_enable_cp *cmd = (const void *)cmdbuf;
struct ble_ll_adv_sm *advsm;
if (len != sizeof(*cmd)) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
advsm = ble_ll_adv_sm_find_configured(cmd->adv_handle);
if (!advsm) {
return BLE_ERR_UNK_ADV_INDENT;
}
#if MYNEWT_VAL(BLE_VERSION) >= 53
if (cmd->enable & 0x02) {
return BLE_ERR_UNSUPPORTED;
} else if (cmd->enable & 0xfc) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
#else
if (cmd->enable & 0xfe) {
return BLE_ERR_INV_HCI_CMD_PARMS;
}
#endif
if (cmd->enable) {
if (advsm->props & (BLE_HCI_LE_SET_EXT_ADV_PROP_ANON_ADV |
BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE |
BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE |
BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY)) {
return BLE_ERR_CMD_DISALLOWED;
}
if (advsm->flags & BLE_LL_ADV_SM_FLAG_PERIODIC_DATA_INCOMPLETE) {
return BLE_ERR_CMD_DISALLOWED;
}
if (!(advsm->flags & BLE_LL_ADV_SM_FLAG_PERIODIC_CONFIGURED)) {
return BLE_ERR_CMD_DISALLOWED;
}
/* If Enable is set to 0x01 and the length of the periodic advertising
* data is greater than the maximum that the Controller can transmit
* within the chosen periodicadvertising interval, the Controller shall
* return the error code Packet Too Long (0x45).
*/
if (!ble_ll_adv_periodic_check_data_itvl(SYNC_DATA_LEN(advsm),
advsm->periodic_adv_props,
advsm->periodic_adv_itvl,
advsm->sec_phy)) {
return BLE_ERR_PACKET_TOO_LONG;
}
/* If the advertising set is not currently enabled (see the
* LE_Set_Extended_Advertising_Enable command), the periodic advertising
* is not started until the advertising set is enabled.
*/
if (advsm->adv_enabled && !advsm->periodic_adv_active) {
/* Start the periodic advertising state machine */
ble_ll_adv_sm_start_periodic(advsm);
}
} else {
/* Stop the periodic advertising state machine */
ble_ll_adv_sm_stop_periodic(advsm);
}
advsm->periodic_adv_enabled = cmd->enable;
return BLE_ERR_SUCCESS;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_SYNC_TRANSFER)
static int
ble_ll_adv_periodic_send_sync_ind(struct ble_ll_adv_sm *advsm,
struct ble_ll_conn_sm *connsm,
uint16_t service_data)
{
struct os_mbuf *om;
uint8_t *sync_ind;
om = os_msys_get_pkthdr(BLE_LL_CTRL_MAX_PDU_LEN,
sizeof(struct ble_mbuf_hdr));
if (!om) {
return BLE_ERR_MEM_CAPACITY;
}
om->om_data[0] = BLE_LL_CTRL_PERIODIC_SYNC_IND;
sync_ind = om->om_data + 1;
/* ID (service_data), already in LE order */
memcpy(sync_ind, &service_data, sizeof(service_data));
/* fill in syncinfo */
ble_ll_adv_put_syncinfo(advsm, connsm, sync_ind + 20, sync_ind + 2);
/* lastPaEventCounter */
put_le16(sync_ind + 22, advsm->periodic_event_cntr_last_sent);
/* SID, AType, SCA */
sync_ind[24] = (advsm->adi >> 12);
sync_ind[24] |= !!(advsm->flags & BLE_LL_ADV_SM_FLAG_TX_ADD) << 4;
sync_ind[24] |= BLE_LL_SCA_ENUM << 5;
/* PHY */
sync_ind[25] = (0x01 << (advsm->sec_phy - 1));
/* AdvA */
memcpy(sync_ind + 26, advsm->adva, BLE_DEV_ADDR_LEN);
/* syncConnEventCount */
put_le16(sync_ind + 32, connsm->event_cntr);
ble_ll_conn_enqueue_pkt(connsm, om, BLE_LL_LLID_CTRL,
BLE_LL_CTRL_PERIODIC_SYNC_IND_LEN + 1);
return BLE_ERR_SUCCESS;
}
int
ble_ll_adv_periodic_set_info_transfer(const uint8_t *cmdbuf, uint8_t len,
uint8_t *rspbuf, uint8_t *rsplen)
{
const struct ble_hci_le_periodic_adv_set_info_transfer_cp *cmd = (const void *)cmdbuf;
struct ble_hci_le_periodic_adv_set_info_transfer_rp *rsp = (void *) rspbuf;
struct ble_ll_conn_sm *connsm;
struct ble_ll_adv_sm *advsm;
uint16_t handle;
int rc;
if (len != sizeof(*cmd)) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
advsm = ble_ll_adv_sm_find_configured(cmd->adv_handle);
if (!advsm) {
rc = BLE_ERR_UNK_ADV_INDENT;
goto done;
}
if (!advsm->periodic_adv_active) {
rc = BLE_ERR_CMD_DISALLOWED;
goto done;
}
handle = le16toh(cmd->conn_handle);
if (handle > 0xeff) {
rc = BLE_ERR_INV_HCI_CMD_PARMS;
goto done;
}
connsm = ble_ll_conn_find_by_handle(handle);
if (!connsm) {
rc = BLE_ERR_UNK_CONN_ID;
goto done;
}
/* TODO should not need to shift
* byte 3 (0 byte is conn_feature) , bit 1
*
* Allow initiate LL procedure only if remote supports it.
*/
if (!ble_ll_conn_rem_feature_check(connsm, BLE_LL_FEAT_SYNC_TRANS_RECV)) {
rc = BLE_ERR_UNSUPP_REM_FEATURE;
goto done;
}
rc = ble_ll_adv_periodic_send_sync_ind(advsm, connsm, cmd->service_data);
done:
rsp->conn_handle = cmd->conn_handle;
*rsplen = sizeof(*rsp);
return rc;
}
#endif
#endif
#endif
/**
* Called when the LL receives a scan request or connection request
*
* Context: Called from interrupt context.
*
* @param rxbuf
*
* @return -1: request not for us or is a connect request.
* 0: request (scan) is for us and we successfully went from rx to tx.
* > 0: PHY error attempting to go from rx to tx.
*/
static int
ble_ll_adv_rx_req(uint8_t pdu_type, struct os_mbuf *rxpdu)
{
int rc;
int resolved;
uint8_t chk_wl;
uint8_t txadd;
uint8_t peer_addr_type;
uint8_t *rxbuf;
uint8_t *adva;
uint8_t *peer;
struct ble_mbuf_hdr *ble_hdr;
struct ble_ll_adv_sm *advsm;
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
struct aux_conn_rsp_data rsp_data;
#endif
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
struct ble_ll_resolv_entry *rl;
#endif
/* See if adva in the request (scan or connect) matches what we sent */
advsm = g_ble_ll_cur_adv_sm;
rxbuf = rxpdu->om_data;
adva = rxbuf + BLE_LL_PDU_HDR_LEN + BLE_DEV_ADDR_LEN;
if (memcmp(advsm->adva, adva, BLE_DEV_ADDR_LEN)) {
return -1;
}
/* Set device match bit if we are whitelisting */
if (pdu_type == BLE_ADV_PDU_TYPE_SCAN_REQ) {
chk_wl = advsm->adv_filter_policy & 1;
} else {
chk_wl = advsm->adv_filter_policy & 2;
}
/* Get the peer address type */
if (rxbuf[0] & BLE_ADV_PDU_HDR_TXADD_MASK) {
txadd = BLE_ADDR_RANDOM;
} else {
txadd = BLE_ADDR_PUBLIC;
}
ble_hdr = BLE_MBUF_HDR_PTR(rxpdu);
peer = rxbuf + BLE_LL_PDU_HDR_LEN;
peer_addr_type = txadd;
resolved = 0;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
rl = NULL;
if (ble_ll_resolv_enabled()) {
if (ble_ll_is_rpa(peer, txadd)) {
advsm->adv_rpa_index = ble_hw_resolv_list_match();
if (advsm->adv_rpa_index >= 0) {
ble_hdr->rxinfo.flags |= BLE_MBUF_HDR_F_RESOLVED;
rl = &g_ble_ll_resolv_list[advsm->adv_rpa_index];
if (chk_wl) {
peer = rl->rl_identity_addr;
peer_addr_type = rl->rl_addr_type;
resolved = 1;
}
} else {
if (chk_wl) {
return -1;
}
}
} else {
/* Verify privacy mode */
rl = ble_ll_resolv_list_find(peer, peer_addr_type);
if (rl && (rl->rl_priv_mode == BLE_HCI_PRIVACY_NETWORK) &&
rl->rl_has_peer) {
return -1;
}
}
}
#endif
/* Set device match bit if we are whitelisting */
if (chk_wl && !ble_ll_whitelist_match(peer, peer_addr_type, resolved)) {
return -1;
}
/*
* We set the device match bit to tell the upper layer that we will
* accept the request
*/
ble_hdr->rxinfo.flags |= BLE_MBUF_HDR_F_DEVMATCH;
/* Setup to transmit the scan response if appropriate */
rc = -1;
if (pdu_type == BLE_ADV_PDU_TYPE_SCAN_REQ) {
/* PHY used for scan requests shall be the same as the PHY used for the
* PDU that they reply to so no need to change PHY mode.
*/
ble_phy_set_txend_cb(ble_ll_adv_tx_done, advsm);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (advsm->flags & BLE_LL_ADV_SM_FLAG_SCAN_REQ_NOTIF) {
ble_ll_hci_ev_send_scan_req_recv(advsm->adv_instance, peer,
peer_addr_type);
}
/*
* We need to store current rxed packet header temporarily so AuxPtr
* can be calculated (if necessary) relative to AUX_SCAN_RSP instead of
* AUX_ADV_IND.
*/
advsm->rx_ble_hdr = ble_hdr;
rc = ble_phy_tx(ble_ll_adv_scan_rsp_pdu_make, advsm,
BLE_PHY_TRANSITION_NONE);
advsm->rx_ble_hdr = NULL;
#else
rc = ble_phy_tx(ble_ll_adv_scan_rsp_legacy_pdu_make, advsm,
BLE_PHY_TRANSITION_NONE);
#endif
if (!rc) {
ble_hdr->rxinfo.flags |= BLE_MBUF_HDR_F_SCAN_RSP_TXD;
STATS_INC(ble_ll_stats, scan_rsp_txg);
}
} else if (pdu_type == BLE_ADV_PDU_TYPE_AUX_CONNECT_REQ) {
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
/* See if the device is already connected */
if (ble_ll_conn_find_by_peer_addr(peer, peer_addr_type)) {
return -1;
}
/*
* Only accept connect requests from the desired address if we
* are doing directed advertising
*/
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_DIRECTED) {
if (memcmp(advsm->initiator_addr, peer, BLE_DEV_ADDR_LEN)) {
return -1;
}
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
return -1;
}
/* use remote address used over the air */
rsp_data.advsm = advsm;
rsp_data.peer = rxbuf + BLE_LL_PDU_HDR_LEN;
rsp_data.rxadd = rxbuf[0] & BLE_ADV_PDU_HDR_TXADD_MASK;
ble_phy_set_txend_cb(ble_ll_adv_tx_done, advsm);
rc = ble_phy_tx(ble_ll_adv_aux_conn_rsp_pdu_make, &rsp_data,
BLE_PHY_TRANSITION_NONE);
if (!rc) {
ble_ll_adv_flags_set(advsm, BLE_LL_ADV_SM_FLAG_CONN_RSP_TXD);
STATS_INC(ble_ll_stats, aux_conn_rsp_tx);
}
#endif
#endif
}
return rc;
}
/**
* Called when a connect request has been received.
*
* Context: Link Layer
*
* @param rxbuf
* @param flags
*
* @return 0: no connection started. 1: connection started
*/
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
static int
ble_ll_adv_conn_req_rxd(uint8_t *rxbuf, struct ble_mbuf_hdr *hdr,
struct ble_ll_adv_sm *advsm)
{
int valid;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
uint8_t resolved;
#endif
uint8_t addr_type;
uint8_t *inita;
uint8_t *ident_addr;
/* Don't create connection if AUX_CONNECT_RSP was not send */
if (!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY)) {
if (!(advsm->flags & BLE_LL_ADV_SM_FLAG_CONN_RSP_TXD)) {
return 0;
}
}
/* Check filter policy. */
valid = 0;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
resolved = BLE_MBUF_HDR_RESOLVED(hdr);
#endif
inita = rxbuf + BLE_LL_PDU_HDR_LEN;
if (hdr->rxinfo.flags & BLE_MBUF_HDR_F_DEVMATCH) {
valid = 1;
if (rxbuf[0] & BLE_ADV_PDU_HDR_TXADD_MASK) {
addr_type = BLE_ADDR_RANDOM;
} else {
addr_type = BLE_ADDR_PUBLIC;
}
/*
* Only accept connect requests from the desired address if we
* are doing directed advertising
*/
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_DIRECTED) {
ident_addr = inita;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
if (resolved) {
ident_addr = g_ble_ll_resolv_list[advsm->adv_rpa_index].rl_identity_addr;
addr_type = g_ble_ll_resolv_list[advsm->adv_rpa_index].rl_addr_type;
}
#endif
if ((addr_type != advsm->peer_addr_type) ||
memcmp(advsm->peer_addr, ident_addr, BLE_DEV_ADDR_LEN)) {
valid = 0;
}
}
}
if (valid) {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
if (resolved) {
/* Retain the resolvable private address that we received. */
memcpy(advsm->adv_rpa, inita, BLE_DEV_ADDR_LEN);
/* Update resolving list with current peer RPA */
ble_ll_resolv_set_peer_rpa(advsm->adv_rpa_index, inita);
/*
* Overwrite received inita with identity address since that
* is used from now on.
*/
memcpy(inita,
g_ble_ll_resolv_list[advsm->adv_rpa_index].rl_identity_addr,
BLE_DEV_ADDR_LEN);
/* Peer address type is an identity address */
addr_type = g_ble_ll_resolv_list[advsm->adv_rpa_index].rl_addr_type;
addr_type += 2;
}
#endif
/* Try to start peripheral connection. If successful, stop advertising */
valid = ble_ll_conn_periph_start(rxbuf, addr_type, hdr,
!(advsm->props &
BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY));
if (valid) {
/* stop advertising only if not transmitting connection response */
if (!(advsm->flags & BLE_LL_ADV_SM_FLAG_CONN_RSP_TXD)) {
ble_ll_adv_sm_stop(advsm);
}
} else if (advsm->flags & BLE_LL_ADV_SM_FLAG_CONN_RSP_TXD) {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
ble_ll_adv_flags_set(advsm, BLE_LL_ADV_SM_FLAG_CONN_RSP_TXD_ERR);
valid = 1;
#endif
}
}
return valid;
}
#endif
/**
* Called on phy rx pdu end when in advertising state.
*
* There are only two pdu types we care about in this state: scan requests
* and connection requests. When we receive a scan request we must determine if
* we need to send a scan response and that needs to be acted on within T_IFS.
*
* When we receive a connection request, we need to determine if we will allow
* this device to start a connection with us. However, no immediate response is
* sent so we handle this at the link layer task.
*
* Context: Interrupt
*
* @param pdu_type Type of pdu received.
* @param rxpdu Pointer to received PDU
*
* @return int
* < 0: Disable the phy after reception.
* == 0: Do not disable the PHY
* > 0: Do not disable PHY as that has already been done.
*/
int
ble_ll_adv_rx_isr_end(uint8_t pdu_type, struct os_mbuf *rxpdu, int crcok)
{
int rc;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
struct ble_mbuf_hdr *rxhdr;
#endif
rc = -1;
if (rxpdu == NULL) {
ble_ll_adv_tx_done(g_ble_ll_cur_adv_sm);
} else {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
rxhdr = BLE_MBUF_HDR_PTR(rxpdu);
rxhdr->rxinfo.user_data = g_ble_ll_cur_adv_sm;
if (ble_ll_adv_active_chanset_is_sec(g_ble_ll_cur_adv_sm)) {
rxhdr->rxinfo.flags |= BLE_MBUF_HDR_F_EXT_ADV_SEC;
} else {
BLE_LL_ASSERT(ble_ll_adv_active_chanset_is_pri(g_ble_ll_cur_adv_sm));
}
#endif
if (crcok) {
if ((pdu_type == BLE_ADV_PDU_TYPE_SCAN_REQ) ||
(pdu_type == BLE_ADV_PDU_TYPE_CONNECT_IND)) {
/* Process request */
rc = ble_ll_adv_rx_req(pdu_type, rxpdu);
}
}
if (rc) {
/* We no longer have a current state machine */
g_ble_ll_cur_adv_sm = NULL;
}
}
if (rc) {
ble_ll_state_set(BLE_LL_STATE_STANDBY);
}
return rc;
}
/**
* Process a received packet at the link layer task when in the advertising
* state
*
* Context: Link Layer
*
*
* @param ptype
* @param rxbuf
* @param hdr
*
* @return int
*/
void
ble_ll_adv_rx_pkt_in(uint8_t ptype, uint8_t *rxbuf, struct ble_mbuf_hdr *hdr)
{
int adv_event_over;
struct ble_ll_adv_sm *advsm;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
advsm = (struct ble_ll_adv_sm *)hdr->rxinfo.user_data;
#else
advsm = &g_ble_ll_adv_sm[0];
#endif
/*
* It is possible that advertising was stopped and a packet placed on the
* LL receive packet queue. In this case, just ignore the received packet
* as the advertising state machine is no longer "valid"
*/
if (!advsm->adv_enabled) {
return;
}
/*
* If we have received a scan request and we are transmitting a response
* or we have received a valid connect request, dont "end" the advertising
* event. In the case of a connect request we will stop advertising. In
* the case of the scan response transmission we will get a transmit
* end callback.
*/
adv_event_over = 1;
if (BLE_MBUF_HDR_CRC_OK(hdr)) {
if (ptype == BLE_ADV_PDU_TYPE_CONNECT_IND) {
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
if (ble_ll_adv_conn_req_rxd(rxbuf, hdr, advsm)) {
adv_event_over = 0;
}
#endif
} else {
if ((ptype == BLE_ADV_PDU_TYPE_SCAN_REQ) &&
(hdr->rxinfo.flags & BLE_MBUF_HDR_F_SCAN_RSP_TXD)) {
adv_event_over = 0;
}
}
}
if (adv_event_over) {
ble_ll_adv_make_done(advsm, hdr);
}
}
/**
* Called when a receive PDU has started and we are advertising.
*
* Context: interrupt
*
* @param pdu_type
* @param rxpdu
*
* @return int
* < 0: A frame we dont want to receive.
* = 0: Continue to receive frame. Dont go from rx to tx
* > 0: Continue to receive frame and go from rx to tx when done
*/
int
ble_ll_adv_rx_isr_start(uint8_t pdu_type)
{
int rc;
struct ble_ll_adv_sm *advsm;
/* Assume we will abort the frame */
rc = -1;
/* If we get a scan request we must tell the phy to go from rx to tx */
advsm = g_ble_ll_cur_adv_sm;
if (pdu_type == BLE_ADV_PDU_TYPE_SCAN_REQ) {
/* Only accept scan requests if we are indirect adv or scan adv */
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE) {
rc = 1;
}
} else {
/* Only accept connect requests if connectable advertising event */
if (pdu_type == BLE_ADV_PDU_TYPE_CONNECT_IND) {
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE) {
/* Need transition to TX if extended adv */
rc = !(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY);
}
}
}
/*
* If we abort the frame, we need to post the LL task to check if the
* advertising event is over.
*/
if (rc < 0) {
ble_ll_adv_tx_done(advsm);
}
return rc;
}
static void
ble_ll_adv_drop_event(struct ble_ll_adv_sm *advsm, bool preempted)
{
os_sr_t sr;
STATS_INC(ble_ll_stats, adv_drop_event);
ble_ll_sched_rmv_elem(&advsm->adv_sch);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
ble_ll_sched_rmv_elem(&advsm->aux[0].sch);
ble_ll_sched_rmv_elem(&advsm->aux[1].sch);
ble_ll_event_remove(&advsm->adv_sec_txdone_ev);
advsm->aux_active = 0;
#endif
if (preempted) {
OS_ENTER_CRITICAL(sr);
advsm->retry_event = !(advsm->flags & BLE_LL_ADV_SM_FLAG_ACTIVE_CHANSET_MASK);
OS_EXIT_CRITICAL(sr);
}
advsm->adv_chan = ble_ll_adv_final_chan(advsm);
ble_ll_event_add(&advsm->adv_txdone_ev);
}
static void
ble_ll_adv_reschedule_event(struct ble_ll_adv_sm *advsm)
{
struct ble_ll_sched_item *sch;
uint32_t max_delay_ticks;
int rc;
BLE_LL_ASSERT(advsm->adv_enabled);
sch = &advsm->adv_sch;
if (!sch->enqueued) {
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_HD_DIRECTED) {
max_delay_ticks = 0;
} else {
max_delay_ticks = ble_ll_tmr_u2t(BLE_LL_ADV_DELAY_MS_MAX * 1000);
}
rc = ble_ll_sched_adv_reschedule(sch, max_delay_ticks);
if (rc) {
ble_ll_adv_drop_event(advsm, 0);
return;
}
advsm->adv_event_start_time = sch->start_time +
g_ble_ll_sched_offset_ticks;
advsm->adv_pdu_start_time = advsm->adv_event_start_time;
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) &&
!advsm->aux_active) {
ble_ll_adv_aux_schedule(advsm);
}
#endif
}
/**
* Called when an advertising event is over.
*
* Context: Link Layer task.
*
* @param arg Pointer to advertising state machine.
*/
static void
ble_ll_adv_done(struct ble_ll_adv_sm *advsm)
{
int rc;
int resched_pdu;
uint8_t mask;
uint8_t final_adv_chan;
int32_t delta_t;
uint32_t itvl;
uint32_t tick_itvl;
uint32_t start_time;
BLE_LL_ASSERT(advsm->adv_enabled);
ble_ll_rfmgmt_release();
ble_ll_adv_update_adv_scan_rsp_data(advsm);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
/* stop advertising this was due to transmitting connection response */
if (advsm->flags & BLE_LL_ADV_SM_FLAG_CONN_RSP_TXD) {
ble_ll_adv_sm_stop(advsm);
return;
}
}
#endif
/* Remove the element from the schedule if it is still there. */
ble_ll_sched_rmv_elem(&advsm->adv_sch);
ble_ll_event_remove(&advsm->adv_txdone_ev);
/*
* Check if we have ended our advertising event. If our last advertising
* packet was sent on the last channel, it means we are done with this
* event.
*/
final_adv_chan = ble_ll_adv_final_chan(advsm);
if (advsm->adv_chan == final_adv_chan) {
ble_ll_scan_chk_resume();
/* This event is over. Set adv channel to first one */
advsm->adv_chan = ble_ll_adv_first_chan(advsm);
itvl = advsm->adv_itvl_usecs;
tick_itvl = ble_ll_tmr_u2t(itvl);
/* do not calculate new event time if current event should be retried;
* this happens if event was preempted, so we just try to schedule one
* more time with the same start time
*/
if (!advsm->retry_event) {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (advsm->events_max) {
advsm->events++;
}
#endif
/*
* Calculate start time of next advertising event. NOTE: we do not
* add the random advDelay as the scheduling code will do that.
*/
advsm->adv_event_start_time += tick_itvl;
advsm->adv_pdu_start_time = advsm->adv_event_start_time;
} else {
advsm->retry_event = 0;
}
/*
* The scheduled time better be in the future! If it is not, we will
* just keep advancing until we the time is in the future
*/
start_time = advsm->adv_pdu_start_time - g_ble_ll_sched_offset_ticks;
delta_t = (int32_t)(start_time - ble_ll_tmr_get());
if (delta_t < 0) {
/*
* NOTE: we just the same interval that we calculated earlier.
* No real need to keep recalculating a new interval.
*/
while (delta_t < 0) {
advsm->adv_event_start_time += tick_itvl;
advsm->adv_pdu_start_time = advsm->adv_event_start_time;
delta_t += (int32_t)tick_itvl;
}
}
resched_pdu = 0;
} else {
/*
* Move to next advertising channel. If not in the mask, just
* increment by 1. We can do this because we already checked if we
* just transmitted on the last advertising channel
*/
++advsm->adv_chan;
mask = 1 << (advsm->adv_chan - BLE_PHY_ADV_CHAN_START);
if ((mask & advsm->adv_chanmask) == 0) {
++advsm->adv_chan;
}
/* We want to send next PDU right away so start time is set to "now"
* plus scheduling offset. Add an extra tick since LL timer may tick
* when we calculate other things in the meantime.
*/
advsm->adv_pdu_start_time = ble_ll_tmr_get() +
g_ble_ll_sched_offset_ticks + 1;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
/* If we're past aux (unlikely, but can happen), just drop an event */
if (!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) &&
advsm->aux_active &&
LL_TMR_GT(advsm->adv_pdu_start_time,
AUX_CURRENT(advsm)->start_time)) {
ble_ll_adv_drop_event(advsm, 0);
return;
}
#endif
resched_pdu = 1;
}
/* check if advertising timed out */
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (advsm->duration &&
LL_TMR_GEQ(advsm->adv_pdu_start_time, advsm->adv_end_time)) {
/* Legacy PDUs need to be stop here.
* For ext adv it will be stopped when AUX is done (unless it was
* dropped so check if AUX is active here as well).
*/
if ((advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) ||
!advsm->aux_active) {
ble_ll_adv_sm_stop_timeout(advsm);
}
return;
}
#else
if ((advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_HD_DIRECTED) &&
LL_TMR_GEQ(advsm->adv_pdu_start_time, advsm->adv_end_time)) {
ble_ll_adv_sm_stop_timeout(advsm);
return;
}
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (advsm->events_max && (advsm->events >= advsm->events_max)) {
/* Legacy PDUs need to be stop here.
* For ext adv it will be stopped when AUX is done (unless it was
* dropped so check if AUX is active here as well).
*/
if ((advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) ||
!advsm->aux_active) {
ble_ll_adv_sm_stop_limit_reached(advsm);
}
return;
}
#endif
/* We need to regenerate our RPA's if we have passed timeout */
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY)
ble_ll_adv_chk_rpa_timeout(advsm);
#endif
/* Schedule advertising transmit */
ble_ll_adv_set_sched(advsm);
if (!resched_pdu) {
ble_ll_adv_reschedule_event(advsm);
return;
}
/*
* In the unlikely event we can't reschedule this, just post a done event
* and we will reschedule the next advertising PDU.
*/
rc = ble_ll_sched_adv_resched_pdu(&advsm->adv_sch);
if (rc) {
STATS_INC(ble_ll_stats, adv_resched_pdu_fail);
ble_ll_event_add(&advsm->adv_txdone_ev);
}
}
static void
ble_ll_adv_event_done(struct ble_npl_event *ev)
{
ble_ll_adv_done(ble_npl_event_get_arg(ev));
}
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
/**
* Called when auxiliary packet is txd on secondary channel
*
* Context: Link Layer task.
*
* @param ev
*/
static void
ble_ll_adv_sec_done(struct ble_ll_adv_sm *advsm)
{
struct ble_ll_adv_aux *aux;
struct ble_ll_adv_aux *aux_next;
BLE_LL_ASSERT(advsm->adv_enabled);
BLE_LL_ASSERT(advsm->aux_active);
aux = AUX_CURRENT(advsm);
aux_next = AUX_NEXT(advsm);
/* We don't need RF anymore */
ble_ll_rfmgmt_release();
if (advsm->aux_dropped) {
ble_ll_adv_drop_event(advsm, 0);
return;
}
if (advsm->aux_not_scanned) {
ble_ll_sched_rmv_elem(&aux_next->sch);
}
/* Remove anything else scheduled for secondary channel */
ble_ll_sched_rmv_elem(&aux->sch);
ble_ll_event_remove(&advsm->adv_sec_txdone_ev);
/* Stop advertising due to transmitting connection response */
if (advsm->flags & BLE_LL_ADV_SM_FLAG_CONN_RSP_TXD) {
if (!(advsm->flags & BLE_LL_ADV_SM_FLAG_CONN_RSP_TXD_ERR)) {
ble_ll_adv_sm_stop(advsm);
return;
} else {
ble_ll_adv_flags_clear(advsm, BLE_LL_ADV_SM_FLAG_CONN_RSP_TXD |
BLE_LL_ADV_SM_FLAG_CONN_RSP_TXD_ERR);
}
}
/* If we have next AUX scheduled, try to schedule another one */
if (aux_next->sch.enqueued) {
advsm->aux_index ^= 1;
advsm->aux_first_pdu = 0;
ble_ll_adv_aux_schedule_next(advsm);
return;
}
ble_ll_scan_chk_resume();
/* Check if advertising timed out */
if (advsm->duration &&
LL_TMR_GEQ(advsm->adv_pdu_start_time, advsm->adv_end_time)) {
ble_ll_adv_sm_stop_timeout(advsm);
return;
}
if (advsm->events_max && (advsm->events >= advsm->events_max)) {
ble_ll_adv_sm_stop_limit_reached(advsm);
return;
}
advsm->aux_active = 0;
ble_ll_adv_update_adv_scan_rsp_data(advsm);
ble_ll_adv_reschedule_event(advsm);
}
static void
ble_ll_adv_sec_event_done(struct ble_npl_event *ev)
{
ble_ll_adv_sec_done(ble_npl_event_get_arg(ev));
}
#endif
static void
ble_ll_adv_make_done(struct ble_ll_adv_sm *advsm, struct ble_mbuf_hdr *hdr)
{
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (BLE_MBUF_HDR_EXT_ADV_SEC(hdr)) {
BLE_LL_ASSERT(!(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY));
BLE_LL_ASSERT(ble_ll_adv_active_chanset_is_sec(advsm));
ble_ll_adv_active_chanset_clear(advsm);
ble_ll_adv_sec_done(advsm);
} else {
BLE_LL_ASSERT(ble_ll_adv_active_chanset_is_pri(advsm));
ble_ll_adv_active_chanset_clear(advsm);
ble_ll_adv_done(advsm);
}
#else
ble_ll_adv_active_chanset_clear(advsm);
ble_ll_adv_done(advsm);
#endif
}
/**
* Checks if the controller can change the whitelist. If advertising is enabled
* and is using the whitelist the controller is not allowed to change the
* whitelist.
*
* @return int 0: not allowed to change whitelist; 1: change allowed.
*/
int
ble_ll_adv_can_chg_whitelist(void)
{
struct ble_ll_adv_sm *advsm;
int rc;
int i;
rc = 1;
for (i = 0; i < BLE_ADV_INSTANCES; ++i) {
advsm = &g_ble_ll_adv_sm[i];
if (advsm->adv_enabled &&
(advsm->adv_filter_policy != BLE_HCI_ADV_FILT_NONE)) {
rc = 0;
break;
}
}
return rc;
}
/**
* Sends the connection complete event when advertising a connection starts.
*
* @return uint8_t* Pointer to event buffer
*/
#if MYNEWT_VAL(BLE_LL_ROLE_PERIPHERAL)
void
ble_ll_adv_send_conn_comp_ev(struct ble_ll_conn_sm *connsm,
struct ble_mbuf_hdr *rxhdr)
{
struct ble_ll_adv_sm *advsm;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
advsm = (struct ble_ll_adv_sm *)rxhdr->rxinfo.user_data;
#else
advsm = &g_ble_ll_adv_sm[0];
#endif
BLE_LL_ASSERT(advsm->conn_comp_ev != NULL);
ble_ll_conn_comp_event_send(connsm, BLE_ERR_SUCCESS, advsm->conn_comp_ev,
advsm);
advsm->conn_comp_ev = NULL;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LE_CSA2)
ble_ll_hci_ev_le_csa(connsm);
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
if (ble_ll_hci_adv_mode_ext()) {
ble_ll_hci_ev_send_adv_set_terminated(0, advsm->adv_instance,
connsm->conn_handle, advsm->events);
}
#endif
}
#endif
/**
* Returns the local resolvable private address currently being using by
* the advertiser
*
* @return uint8_t*
*/
uint8_t *
ble_ll_adv_get_local_rpa(struct ble_ll_adv_sm *advsm)
{
uint8_t *rpa = NULL;
if (advsm->own_addr_type > BLE_HCI_ADV_OWN_ADDR_RANDOM) {
if ((advsm->flags & BLE_LL_ADV_SM_FLAG_TX_ADD) &&
ble_ll_is_rpa(advsm->adva, 1)) {
rpa = advsm->adva;
}
}
return rpa;
}
/**
* Returns the peer resolvable private address of last device connecting to us
*
* @return uint8_t*
*/
uint8_t *
ble_ll_adv_get_peer_rpa(struct ble_ll_adv_sm *advsm)
{
/* XXX: should this go into IRK list or connection? */
return advsm->adv_rpa;
}
/**
* Called when the LL wait for response timer expires while in the advertising
* state. Disables the phy and
*
*/
void
ble_ll_adv_wfr_timer_exp(void)
{
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
g_ble_ll_cur_adv_sm->aux_not_scanned = 1;
#endif
ble_phy_disable();
ble_ll_adv_tx_done(g_ble_ll_cur_adv_sm);
}
/**
* Reset the advertising state machine.
*
* Context: Link Layer task
*
*/
void
ble_ll_adv_reset(void)
{
int i;
struct ble_ll_adv_sm *advsm;
for (i = 0; i < BLE_ADV_INSTANCES; ++i) {
advsm = &g_ble_ll_adv_sm[i];
/* Stop advertising state machine */
ble_ll_adv_sm_stop(advsm);
/* clear any data present */
os_mbuf_free_chain(advsm->adv_data);
os_mbuf_free_chain(advsm->scan_rsp_data);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
/* Stop periodic advertising state machine */
ble_ll_adv_sm_stop_periodic(advsm);
/* clear any periodic data present */
os_mbuf_free_chain(advsm->periodic_adv_data);
#endif
/* re-initialize the advertiser state machine */
ble_ll_adv_sm_init(advsm);
}
}
/* Called to determine if advertising is enabled.
*/
uint8_t
ble_ll_adv_enabled(void)
{
int i;
for (i = 0; i < BLE_ADV_INSTANCES; i++) {
if (g_ble_ll_adv_sm[i].adv_enabled) {
return 1;
}
}
return 0;
}
static void
ble_ll_adv_sm_init(struct ble_ll_adv_sm *advsm)
{
memset(advsm, 0, sizeof(struct ble_ll_adv_sm));
advsm->adv_chanmask = BLE_HCI_ADV_CHANMASK_DEF;
/* Initialize advertising tx done event */
ble_npl_event_init(&advsm->adv_txdone_ev, ble_ll_adv_event_done, advsm);
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
ble_npl_event_init(&advsm->adv_sec_txdone_ev, ble_ll_adv_sec_event_done, advsm);
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
ble_npl_event_init(&advsm->adv_periodic_txdone_ev,
ble_ll_adv_periodic_event_done, advsm);
#endif
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_EXT_ADV)
/* Initialize aux schedulers */
advsm->aux_active = 0;
advsm->aux[0].sch.cb_arg = advsm;
advsm->aux[0].sch.sched_cb = ble_ll_adv_secondary_tx_start_cb;
advsm->aux[0].sch.sched_type = BLE_LL_SCHED_TYPE_ADV;
advsm->aux[1].sch.cb_arg = advsm;
advsm->aux[1].sch.sched_cb = ble_ll_adv_secondary_tx_start_cb;
advsm->aux[1].sch.sched_type = BLE_LL_SCHED_TYPE_ADV;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV)
/* Initialize sync schedulers */
advsm->periodic_sync_active = 0;
advsm->periodic_sync[0].sch.cb_arg = advsm;
advsm->periodic_sync[0].sch.sched_cb = ble_ll_adv_sync_tx_start_cb;
advsm->periodic_sync[0].sch.sched_type = BLE_LL_SCHED_TYPE_PERIODIC;
advsm->periodic_sync[1].sch.cb_arg = advsm;
advsm->periodic_sync[1].sch.sched_cb = ble_ll_adv_sync_tx_start_cb;
advsm->periodic_sync[1].sch.sched_type = BLE_LL_SCHED_TYPE_PERIODIC;
#endif
#endif
/* Configure instances to be legacy on start */
advsm->props |= BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE;
advsm->props |= BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY;
}
#if MYNEWT_VAL(BLE_LL_ISO_BROADCASTER)
struct ble_ll_adv_sm *
ble_ll_adv_sync_get(uint8_t handle)
{
struct ble_ll_adv_sm *advsm;
advsm = ble_ll_adv_sm_find_configured(handle);
if (!advsm) {
return NULL;
}
if (!(advsm->flags & BLE_LL_ADV_SM_FLAG_PERIODIC_CONFIGURED)) {
return NULL;
}
return advsm;
}
int
ble_ll_adv_sync_sched_get(struct ble_ll_adv_sm *advsm, uint32_t *start_time,
uint32_t *end_time)
{
struct ble_ll_adv_sync *sync;
if (!advsm || !advsm->periodic_adv_active) {
return -EIO;
}
sync = SYNC_CURRENT(advsm);
*start_time = sync->sch.start_time + g_ble_ll_sched_offset_ticks;
*end_time = sync->sch.end_time;
return 0;
}
int
ble_ll_adv_sync_big_add(struct ble_ll_adv_sm *advsm,
struct ble_ll_iso_big *big)
{
if (!(advsm->flags & BLE_LL_ADV_SM_FLAG_PERIODIC_CONFIGURED)) {
return -EINVAL;
}
if (advsm->big && (advsm->big != big)) {
return -EBUSY;
}
advsm->big = big;
return 0;
}
int
ble_ll_adv_sync_big_remove(struct ble_ll_adv_sm *advsm,
struct ble_ll_iso_big *big)
{
if (advsm->big != big) {
return -EINVAL;
}
advsm->big = NULL;
return 0;
}
#endif /* BLE_LL_ISO_BROADCASTER */
/**
* Initialize the advertising functionality of a BLE device. This should
* be called once on initialization
*/
void
ble_ll_adv_init(void)
{
int i;
/* Set default advertising parameters */
for (i = 0; i < BLE_ADV_INSTANCES; ++i) {
ble_ll_adv_sm_init(&g_ble_ll_adv_sm[i]);
}
}
#endif