blob: 9e5554509849d708945e74c8664e72f2d565761d [file] [log] [blame]
/*
* Copyright (c) 2017-2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <soc.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/sys/byteorder.h>
#include "hal/cpu.h"
#include "hal/ccm.h"
#include "hal/ticker.h"
#include "util/util.h"
#include "util/mem.h"
#include "util/memq.h"
#include "util/mayfly.h"
#include "util/dbuf.h"
#include "ticker/ticker.h"
#include "pdu.h"
#include "lll.h"
#include "lll_clock.h"
#include "lll/lll_vendor.h"
#include "lll_chan.h"
#include "lll/lll_adv_types.h"
#include "lll_adv.h"
#include "lll/lll_adv_pdu.h"
#include "lll_adv_aux.h"
#include "lll/lll_df_types.h"
#include "lll_conn.h"
#include "ull_adv_types.h"
#include "ull_internal.h"
#include "ull_chan_internal.h"
#include "ull_adv_internal.h"
#include "ull_sched_internal.h"
#include "ll.h"
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER)
#define LOG_MODULE_NAME bt_ctlr_ull_adv_aux
#include "common/log.h"
#include "hal/debug.h"
static int init_reset(void);
#if (CONFIG_BT_CTLR_ADV_AUX_SET > 0)
static inline struct ll_adv_aux_set *aux_acquire(void);
static inline void aux_release(struct ll_adv_aux_set *aux);
static uint32_t aux_time_get(struct ll_adv_aux_set *aux, struct pdu_adv *pdu,
struct pdu_adv *pdu_scan);
static uint8_t aux_time_update(struct ll_adv_aux_set *aux, struct pdu_adv *pdu,
struct pdu_adv *pdu_scan);
static void mfy_aux_offset_get(void *param);
static void ticker_cb(uint32_t ticks_at_expire, uint32_t ticks_drift,
uint32_t remainder, uint16_t lazy, uint8_t force,
void *param);
static void ticker_op_cb(uint32_t status, void *param);
static struct ll_adv_aux_set ll_adv_aux_pool[CONFIG_BT_CTLR_ADV_AUX_SET];
static void *adv_aux_free;
#endif /* (CONFIG_BT_CTLR_ADV_AUX_SET > 0) */
static uint16_t did_unique[PDU_ADV_SID_COUNT];
uint8_t ll_adv_aux_random_addr_set(uint8_t handle, uint8_t const *const addr)
{
struct ll_adv_set *adv;
adv = ull_adv_is_created_get(handle);
if (!adv) {
return BT_HCI_ERR_UNKNOWN_ADV_IDENTIFIER;
}
/* TODO: Fail if connectable advertising is enabled */
if (0) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
(void)memcpy(adv->rnd_addr, addr, BDADDR_SIZE);
return 0;
}
uint8_t ll_adv_aux_ad_data_set(uint8_t handle, uint8_t op, uint8_t frag_pref,
uint8_t len, uint8_t const *const data)
{
uint8_t hdr_data[ULL_ADV_HDR_DATA_LEN_SIZE +
ULL_ADV_HDR_DATA_DATA_PTR_SIZE +
ULL_ADV_HDR_DATA_LEN_SIZE +
ULL_ADV_HDR_DATA_AUX_PTR_PTR_SIZE +
ULL_ADV_HDR_DATA_LEN_SIZE];
uint8_t pri_idx, sec_idx;
struct ll_adv_set *adv;
uint8_t *val_ptr;
uint8_t err;
#if defined(CONFIG_BT_CTLR_ADV_AUX_PDU_LINK)
struct pdu_adv *pdu_prev;
uint8_t ad_len_overflow;
uint8_t ad_len_chain;
struct pdu_adv *pdu;
#endif /* CONFIG_BT_CTLR_ADV_AUX_PDU_LINK */
/* Get the advertising set instance */
adv = ull_adv_is_created_get(handle);
if (!adv) {
return BT_HCI_ERR_UNKNOWN_ADV_IDENTIFIER;
}
/* Reject setting fragment when Extended Advertising is enabled */
if (adv->is_enabled && (op <= BT_HCI_LE_EXT_ADV_OP_LAST_FRAG)) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
/* Reject intermediate op before first op */
if (adv->is_ad_data_cmplt &&
((op == BT_HCI_LE_EXT_ADV_OP_INTERM_FRAG) ||
(op == BT_HCI_LE_EXT_ADV_OP_LAST_FRAG))) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
/* Reject unchanged op before complete status */
if (!adv->is_ad_data_cmplt &&
(op == BT_HCI_LE_EXT_ADV_OP_UNCHANGED_DATA)) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
/* Reject first, intermediate, last operation and len > 191 bytes if
* chain PDUs unsupported.
*/
if (!IS_ENABLED(CONFIG_BT_CTLR_ADV_AUX_PDU_LINK) &&
((op < BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA) ||
(len > PDU_AC_EXT_AD_DATA_LEN_MAX))) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
/* Prepare the AD data as parameter to update in PDU */
/* Use length = 0 and NULL pointer to retain old data in the PDU.
* Use length = 0 and valid pointer of `data` (auto/local variable) to
* remove old data.
* User length > 0 and valid pointer of `data` (auto/local variable) to
* set new data.
*/
val_ptr = hdr_data;
if (op == BT_HCI_LE_EXT_ADV_OP_INTERM_FRAG ||
op == BT_HCI_LE_EXT_ADV_OP_LAST_FRAG ||
op == BT_HCI_LE_EXT_ADV_OP_UNCHANGED_DATA) {
*val_ptr++ = 0U;
(void)memset((void *)val_ptr, 0U,
ULL_ADV_HDR_DATA_DATA_PTR_SIZE);
} else {
*val_ptr++ = len;
(void)memcpy(val_ptr, &data, sizeof(data));
}
if (!IS_ENABLED(CONFIG_BT_CTLR_ADV_AUX_PDU_LINK) ||
(op == BT_HCI_LE_EXT_ADV_OP_UNCHANGED_DATA)) {
err = ull_adv_aux_hdr_set_clear(adv,
ULL_ADV_PDU_HDR_FIELD_AD_DATA,
0U, hdr_data, &pri_idx,
&sec_idx);
#if defined(CONFIG_BT_CTLR_ADV_AUX_PDU_LINK)
/* No AD data overflow */
ad_len_overflow = 0U;
/* No AD data in chain PDU */
ad_len_chain = 0U;
/* local variables not used due to overflow being 0 */
pdu_prev = NULL;
pdu = NULL;
#endif /* CONFIG_BT_CTLR_ADV_AUX_PDU_LINK */
} else if (!IS_ENABLED(CONFIG_BT_CTLR_ADV_AUX_PDU_LINK) ||
(op == BT_HCI_LE_EXT_ADV_OP_FIRST_FRAG ||
op == BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA)) {
/* Add AD Data and remove any prior presence of Aux Ptr */
err = ull_adv_aux_hdr_set_clear(adv,
ULL_ADV_PDU_HDR_FIELD_AD_DATA,
ULL_ADV_PDU_HDR_FIELD_AUX_PTR,
hdr_data, &pri_idx, &sec_idx);
#if defined(CONFIG_BT_CTLR_ADV_AUX_PDU_LINK)
if (err == BT_HCI_ERR_PACKET_TOO_LONG) {
ad_len_overflow =
hdr_data[ULL_ADV_HDR_DATA_DATA_PTR_OFFSET +
ULL_ADV_HDR_DATA_DATA_PTR_SIZE];
/* Prepare the AD data as parameter to update in
* PDU
*/
val_ptr = hdr_data;
*val_ptr++ = len - ad_len_overflow;
(void)memcpy(val_ptr, &data, sizeof(data));
err = ull_adv_aux_hdr_set_clear(adv,
ULL_ADV_PDU_HDR_FIELD_AD_DATA,
ULL_ADV_PDU_HDR_FIELD_AUX_PTR,
hdr_data, &pri_idx, &sec_idx);
}
if (!err && adv->lll.aux) {
/* Fragment into chain PDU if len > 191 bytes */
if (len > PDU_AC_EXT_AD_DATA_LEN_MAX) {
uint8_t idx;
/* Prepare the AD data as parameter to update in
* PDU
*/
val_ptr = hdr_data;
*val_ptr++ = PDU_AC_EXT_AD_DATA_LEN_MAX;
(void)memcpy(val_ptr, &data, sizeof(data));
/* Traverse to next set clear hdr data
* parameter, as aux ptr reference to be
* returned, hence second parameter will be for
* AD data field.
*/
val_ptr += sizeof(data);
*val_ptr = PDU_AC_EXT_AD_DATA_LEN_MAX;
(void)memcpy(&val_ptr[ULL_ADV_HDR_DATA_DATA_PTR_OFFSET],
&data, sizeof(data));
/* Calculate the overflow chain PDU's AD data
* length
*/
ad_len_overflow =
len - PDU_AC_EXT_AD_DATA_LEN_MAX;
/* No AD data in chain PDU besides the
* overflow
*/
ad_len_chain = 0U;
/* Get the reference to auxiliary PDU chain */
pdu_prev = lll_adv_aux_data_alloc(adv->lll.aux,
&idx);
LL_ASSERT(idx == sec_idx);
/* Current auxiliary PDU */
pdu = pdu_prev;
} else {
struct pdu_adv *pdu_parent;
struct pdu_adv *pdu_chain;
uint8_t idx;
/* Get the reference to auxiliary PDU chain */
pdu_parent =
lll_adv_aux_data_alloc(adv->lll.aux,
&idx);
LL_ASSERT(idx == sec_idx);
/* Remove/Release any previous chain PDU
* allocations
*/
pdu_chain =
lll_adv_pdu_linked_next_get(pdu_parent);
if (pdu_chain) {
lll_adv_pdu_linked_append(NULL,
pdu_parent);
lll_adv_pdu_linked_release_all(pdu_chain);
}
/* No AD data overflow */
ad_len_overflow = 0U;
/* No AD data in chain PDU */
ad_len_chain = 0U;
/* local variables not used due to overflow
* being 0
*/
pdu_prev = NULL;
pdu = NULL;
}
} else {
/* No AD data overflow */
ad_len_overflow = 0U;
/* No AD data in chain PDU */
ad_len_chain = 0U;
/* local variables not used due to overflow being 0 */
pdu_prev = NULL;
pdu = NULL;
}
} else {
struct pdu_adv *pdu_chain_prev;
struct pdu_adv *pdu_chain;
uint16_t ad_len_total;
uint8_t ad_len_prev;
/* Traverse to next set clear hdr data parameter */
val_ptr += sizeof(data);
/* Get reference to previous secondary PDU data */
pdu_prev = lll_adv_aux_data_peek(adv->lll.aux);
/* Get the reference to auxiliary PDU chain */
pdu = lll_adv_aux_data_alloc(adv->lll.aux,
&sec_idx);
/* Traverse to the last chain PDU */
ad_len_total = 0U;
pdu_chain_prev = pdu_prev;
pdu_chain = pdu;
do {
/* Prepare for aux ptr field reference to be returned, hence
* second parameter will be for AD data field.
*/
*val_ptr = 0U;
(void)memset((void *)&val_ptr[ULL_ADV_HDR_DATA_DATA_PTR_OFFSET],
0U, ULL_ADV_HDR_DATA_DATA_PTR_SIZE);
pdu_prev = pdu_chain_prev;
pdu = pdu_chain;
/* Add Aux Ptr field if not already present */
err = ull_adv_aux_pdu_set_clear(adv, pdu_prev, pdu,
(ULL_ADV_PDU_HDR_FIELD_AD_DATA |
ULL_ADV_PDU_HDR_FIELD_AUX_PTR),
0, hdr_data);
LL_ASSERT(!err || (err == BT_HCI_ERR_PACKET_TOO_LONG));
/* Get PDUs previous AD data length */
ad_len_prev =
hdr_data[ULL_ADV_HDR_DATA_AUX_PTR_PTR_OFFSET +
ULL_ADV_HDR_DATA_AUX_PTR_PTR_SIZE];
/* Check of max supported AD data len */
ad_len_total += ad_len_prev;
if ((ad_len_total + len) >
CONFIG_BT_CTLR_ADV_DATA_LEN_MAX) {
/* NOTE: latest PDU was not consumed by LLL and
* as ull_adv_sync_pdu_alloc() has reverted back
* the double buffer with the first PDU, and
* returned the latest PDU as the new PDU, we
* need to enqueue back the new PDU which is
* infact the latest PDU.
*/
if (pdu_prev == pdu) {
lll_adv_aux_data_enqueue(adv->lll.aux,
sec_idx);
}
return BT_HCI_ERR_MEM_CAPACITY_EXCEEDED;
}
pdu_chain_prev = lll_adv_pdu_linked_next_get(pdu_prev);
pdu_chain = lll_adv_pdu_linked_next_get(pdu);
LL_ASSERT((pdu_chain_prev && pdu_chain) ||
(!pdu_chain_prev && !pdu_chain));
} while (pdu_chain_prev);
if (err == BT_HCI_ERR_PACKET_TOO_LONG) {
ad_len_overflow =
hdr_data[ULL_ADV_HDR_DATA_AUX_PTR_PTR_OFFSET +
ULL_ADV_HDR_DATA_AUX_PTR_PTR_SIZE +
ULL_ADV_HDR_DATA_DATA_PTR_OFFSET +
ULL_ADV_HDR_DATA_DATA_PTR_SIZE];
/* Prepare for aux ptr field reference to be returned,
* hence second parameter will be for AD data field.
* Fill it with reduced AD data length.
*/
*val_ptr = ad_len_prev - ad_len_overflow;
/* AD data len in chain PDU */
ad_len_chain = len;
/* Proceed to add chain PDU */
err = 0U;
} else {
/* No AD data overflow */
ad_len_overflow = 0U;
/* No AD data in chain PDU */
ad_len_chain = 0U;
}
#endif /* CONFIG_BT_CTLR_ADV_AUX_PDU_LINK */
}
if (err) {
return err;
}
if (!adv->lll.aux) {
return 0;
}
#if defined(CONFIG_BT_CTLR_ADV_AUX_PDU_LINK)
if ((op == BT_HCI_LE_EXT_ADV_OP_INTERM_FRAG) ||
(op == BT_HCI_LE_EXT_ADV_OP_LAST_FRAG) ||
ad_len_overflow) {
struct pdu_adv_com_ext_adv *com_hdr_chain;
struct pdu_adv_com_ext_adv *com_hdr;
struct pdu_adv_ext_hdr *hdr_chain;
struct pdu_adv_aux_ptr *aux_ptr;
struct pdu_adv *pdu_chain_prev;
struct pdu_adv_ext_hdr hdr;
struct pdu_adv *pdu_chain;
uint8_t *dptr_chain;
uint32_t offs_us;
uint16_t sec_len;
uint8_t *dptr;
/* Get reference to flags in superior PDU */
com_hdr = &pdu->adv_ext_ind;
if (com_hdr->ext_hdr_len) {
hdr = com_hdr->ext_hdr;
} else {
*(uint8_t *)&hdr = 0U;
}
dptr = com_hdr->ext_hdr.data;
/* Allocate new PDU */
pdu_chain = lll_adv_pdu_alloc_pdu_adv();
LL_ASSERT(pdu_chain);
/* Populate the appended chain PDU */
pdu_chain->type = PDU_ADV_TYPE_AUX_CHAIN_IND;
pdu_chain->rfu = 0U;
pdu_chain->chan_sel = 0U;
pdu_chain->tx_addr = 0U;
pdu_chain->rx_addr = 0U;
pdu_chain->len = 0U;
com_hdr_chain = &pdu_chain->adv_ext_ind;
hdr_chain = (void *)&com_hdr_chain->ext_hdr_adv_data[0];
dptr_chain = (void *)hdr_chain;
/* Flags */
*dptr_chain = 0U;
/* ADI flag, mandatory if superior PDU has it */
if (hdr.adi) {
hdr_chain->adi = 1U;
}
/* Proceed to next byte if any flags present */
if (*dptr_chain) {
dptr_chain++;
}
/* Start adding fields corresponding to flags here, if any */
/* AdvA flag */
if (hdr.adv_addr) {
dptr += BDADDR_SIZE;
}
/* TgtA flag */
if (hdr.tgt_addr) {
dptr += BDADDR_SIZE;
}
/* No CTEInfo in Extended Advertising */
/* ADI flag */
if (hdr_chain->adi) {
(void)memcpy(dptr_chain, dptr,
sizeof(struct pdu_adv_adi));
dptr += sizeof(struct pdu_adv_adi);
dptr_chain += sizeof(struct pdu_adv_adi);
}
/* non-connectable non-scannable chain pdu */
com_hdr_chain->adv_mode = 0;
/* Calc current chain PDU len */
sec_len = ull_adv_aux_hdr_len_calc(com_hdr_chain, &dptr_chain);
/* Prefix overflowed data to chain PDU and reduce the AD data in
* in the current PDU.
*/
if (ad_len_overflow) {
uint8_t *ad_overflow;
/* Copy overflowed AD data from previous PDU into this
* new chain PDU
*/
(void)memcpy(&ad_overflow,
&val_ptr[ULL_ADV_HDR_DATA_DATA_PTR_OFFSET],
sizeof(ad_overflow));
ad_overflow += *val_ptr;
(void)memcpy(dptr_chain, ad_overflow, ad_len_overflow);
dptr_chain += ad_len_overflow;
/* Reduce the AD data in the previous PDU */
err = ull_adv_aux_pdu_set_clear(adv, pdu_prev, pdu,
(ULL_ADV_PDU_HDR_FIELD_AD_DATA |
ULL_ADV_PDU_HDR_FIELD_AUX_PTR),
0, hdr_data);
if (err) {
/* NOTE: latest PDU was not consumed by LLL and
* as ull_adv_sync_pdu_alloc() has reverted back
* the double buffer with the first PDU, and
* returned the latest PDU as the new PDU, we
* need to enqueue back the new PDU which is
* infact the latest PDU.
*/
if (pdu_prev == pdu) {
lll_adv_aux_data_enqueue(adv->lll.aux,
sec_idx);
}
return err;
}
/* AD data len in chain PDU besides the overflow */
len = ad_len_chain;
}
/* Check AdvData overflow */
if ((sec_len + ad_len_overflow + len) >
PDU_AC_PAYLOAD_SIZE_MAX) {
/* NOTE: latest PDU was not consumed by LLL and
* as ull_adv_sync_pdu_alloc() has reverted back
* the double buffer with the first PDU, and
* returned the latest PDU as the new PDU, we
* need to enqueue back the new PDU which is
* infact the latest PDU.
*/
if (pdu_prev == pdu) {
lll_adv_aux_data_enqueue(adv->lll.aux, sec_idx);
}
return BT_HCI_ERR_PACKET_TOO_LONG;
}
/* Fill the chain PDU length */
ull_adv_aux_hdr_len_fill(com_hdr_chain, sec_len);
pdu_chain->len = sec_len + ad_len_overflow + len;
/* Fill AD Data in chain PDU */
(void)memcpy(dptr_chain, data, len);
/* Get reference to aux ptr in superior PDU */
(void)memcpy(&aux_ptr,
&hdr_data[ULL_ADV_HDR_DATA_AUX_PTR_PTR_OFFSET],
sizeof(aux_ptr));
/* Fill the aux offset in the previous AUX_SYNC_IND PDU */
offs_us = PDU_AC_US(pdu->len, adv->lll.phy_s,
adv->lll.phy_flags) +
EVENT_B2B_MAFS_US;
ull_adv_aux_ptr_fill(aux_ptr, offs_us, adv->lll.phy_s);
/* Remove/Release any previous chain PDUs */
pdu_chain_prev = lll_adv_pdu_linked_next_get(pdu);
if (pdu_chain_prev) {
lll_adv_pdu_linked_append(NULL, pdu);
lll_adv_pdu_linked_release_all(pdu_chain_prev);
}
/* Chain the PDU */
lll_adv_pdu_linked_append(pdu_chain, pdu);
}
#endif /* CONFIG_BT_CTLR_ADV_AUX_PDU_LINK */
if (adv->is_enabled) {
struct ll_adv_aux_set *aux;
struct pdu_adv *pdu;
uint8_t tmp_idx;
aux = HDR_LLL2ULL(adv->lll.aux);
if (!aux->is_started) {
uint32_t ticks_slot_overhead;
uint32_t ticks_anchor;
uint32_t ret;
/* Keep aux interval equal or higher than primary PDU
* interval.
* Use periodic interval units to represent the
* periodic behavior of scheduling of AUX_ADV_IND PDUs
* so that it is grouped with similar interval units
* used for ACL Connections, Periodic Advertising and
* BIG radio events.
*/
aux->interval =
ceiling_fraction(((uint64_t)adv->interval *
ADV_INT_UNIT_US) +
HAL_TICKER_TICKS_TO_US(
ULL_ADV_RANDOM_DELAY),
PERIODIC_INT_UNIT_US);
/* TODO: Find the anchor before the group of
* active Periodic Advertising events, so
* that auxiliary sets are grouped such
* that auxiliary sets and Periodic
* Advertising sets are non-overlapping
* for the same event interval.
*/
ticks_anchor = ticker_ticks_now_get();
ticks_slot_overhead =
ull_adv_aux_evt_init(aux, &ticks_anchor);
ret = ull_adv_aux_start(aux, ticks_anchor,
ticks_slot_overhead);
if (ret) {
/* NOTE: This failure, to start an auxiliary
* channel radio event shall not occur unless
* a defect in the controller design.
*/
return BT_HCI_ERR_INSUFFICIENT_RESOURCES;
}
aux->is_started = 1;
}
/* Update primary channel reservation */
pdu = lll_adv_data_alloc(&adv->lll, &tmp_idx);
err = ull_adv_time_update(adv, pdu, NULL);
if (err) {
return err;
}
ARG_UNUSED(tmp_idx);
}
lll_adv_aux_data_enqueue(adv->lll.aux, sec_idx);
if (op == BT_HCI_LE_EXT_ADV_OP_FIRST_FRAG ||
op == BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA ||
op == BT_HCI_LE_EXT_ADV_OP_UNCHANGED_DATA) {
lll_adv_data_enqueue(&adv->lll, pri_idx);
}
/* Check if Extended Advertising Data is complete */
adv->is_ad_data_cmplt = (op >= BT_HCI_LE_EXT_ADV_OP_LAST_FRAG);
return 0;
}
uint8_t ll_adv_aux_sr_data_set(uint8_t handle, uint8_t op, uint8_t frag_pref,
uint8_t len, uint8_t const *const data)
{
uint8_t hdr_data[ULL_ADV_HDR_DATA_LEN_SIZE +
ULL_ADV_HDR_DATA_ADI_PTR_SIZE +
ULL_ADV_HDR_DATA_LEN_SIZE +
ULL_ADV_HDR_DATA_AUX_PTR_PTR_SIZE +
ULL_ADV_HDR_DATA_LEN_SIZE +
ULL_ADV_HDR_DATA_DATA_PTR_SIZE +
ULL_ADV_HDR_DATA_LEN_SIZE];
struct pdu_adv *pri_pdu_prev;
struct pdu_adv *sec_pdu_prev;
struct pdu_adv *sr_pdu_prev;
uint8_t pri_idx, sec_idx;
uint16_t hdr_add_fields;
struct ll_adv_set *adv;
struct pdu_adv *sr_pdu;
struct lll_adv *lll;
uint8_t *val_ptr;
uint8_t sr_idx;
uint8_t err;
#if defined(CONFIG_BT_CTLR_ADV_AUX_PDU_LINK)
uint8_t ad_len_overflow;
uint8_t ad_len_chain;
#endif /* CONFIG_BT_CTLR_ADV_AUX_PDU_LINK */
/* Get the advertising set instance */
adv = ull_adv_is_created_get(handle);
if (!adv) {
return BT_HCI_ERR_UNKNOWN_ADV_IDENTIFIER;
}
/* Do not use Common Extended Advertising Header Format if not extended
* advertising.
*/
lll = &adv->lll;
pri_pdu_prev = lll_adv_data_peek(lll);
if (pri_pdu_prev->type != PDU_ADV_TYPE_EXT_IND) {
if ((op != BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA) ||
(len > PDU_AC_LEG_DATA_SIZE_MAX)) {
return BT_HCI_ERR_INVALID_PARAM;
}
return ull_scan_rsp_set(adv, len, data);
}
/* Can only set complete data if advertising is enabled */
if (adv->is_enabled && (op != BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA)) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
/* Data can be discarded only using 0x03 op */
if ((op != BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA) && !len) {
return BT_HCI_ERR_INVALID_PARAM;
}
/* Scannable advertising shall have aux context allocated */
LL_ASSERT(lll->aux);
/* Get reference to previous secondary channel PDU */
sec_pdu_prev = lll_adv_aux_data_peek(lll->aux);
/* Can only discard data on non-scannable instances */
if (!(sec_pdu_prev->adv_ext_ind.adv_mode & BT_HCI_LE_ADV_PROP_SCAN) &&
len) {
return BT_HCI_ERR_INVALID_PARAM;
}
/* Cannot discard scan response if scannable advertising is enabled */
if (adv->is_enabled &&
(sec_pdu_prev->adv_ext_ind.adv_mode & BT_HCI_LE_ADV_PROP_SCAN) &&
!len) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
/* Get reference to previous scan response PDU */
sr_pdu_prev = lll_adv_scan_rsp_peek(lll);
/* Get reference to next scan response PDU */
sr_pdu = lll_adv_scan_rsp_alloc(lll, &sr_idx);
/* Prepare the AD data as parameter to update in PDU */
/* Use length = 0 and NULL pointer to retain old data in the PDU.
* Use length = 0 and valid pointer of `data` (auto/local variable) to
* remove old data.
* User length > 0 and valid pointer of `data` (auto/local variable) to
* set new data.
*/
val_ptr = hdr_data;
if (op == BT_HCI_LE_EXT_ADV_OP_INTERM_FRAG ||
op == BT_HCI_LE_EXT_ADV_OP_LAST_FRAG ||
op == BT_HCI_LE_EXT_ADV_OP_UNCHANGED_DATA) {
*val_ptr++ = 0U;
(void)memset((void *)val_ptr, 0U,
ULL_ADV_HDR_DATA_DATA_PTR_SIZE);
} else {
*val_ptr++ = len;
(void)memcpy(val_ptr, &data, sizeof(data));
}
if (false) {
#if defined(CONFIG_BT_CTLR_ADV_AUX_PDU_LINK)
} else if (op == BT_HCI_LE_EXT_ADV_OP_UNCHANGED_DATA) {
hdr_add_fields = ULL_ADV_PDU_HDR_FIELD_AD_DATA;
err = ull_adv_aux_pdu_set_clear(adv, sr_pdu_prev, sr_pdu,
hdr_add_fields, 0U, hdr_data);
/* No AD data overflow */
ad_len_overflow = 0U;
/* No AD data in chain PDU */
ad_len_chain = 0U;
/* pri_idx and sec_idx not used later in code in this function
*/
pri_idx = 0U;
sec_idx = 0U;
#endif /* CONFIG_BT_CTLR_ADV_AUX_PDU_LINK */
} else if (!IS_ENABLED(CONFIG_BT_CTLR_ADV_AUX_PDU_LINK) ||
(op == BT_HCI_LE_EXT_ADV_OP_FIRST_FRAG ||
op == BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA)) {
struct pdu_adv_adi *adi;
/* If ADI in scan response is not supported then we do not
* need reference to ADI in auxiliary PDU
*/
hdr_add_fields = 0U;
/* Add ADI if support enabled */
if (IS_ENABLED(CONFIG_BT_CTRL_ADV_ADI_IN_SCAN_RSP)) {
/* We need to get reference to ADI in auxiliary PDU */
hdr_add_fields |= ULL_ADV_PDU_HDR_FIELD_ADI;
/* Update DID by passing NULL reference for ADI */
(void)memset((void *)val_ptr, 0,
sizeof(struct pdu_adv_adi *));
/* Data place holder is after ADI */
val_ptr += sizeof(struct pdu_adv_adi *);
/* Place holder and reference to data passed and
* old reference to be returned
*/
*val_ptr++ = len;
(void)memcpy(val_ptr, &data, sizeof(data));
}
/* Trigger DID update */
err = ull_adv_aux_hdr_set_clear(adv, hdr_add_fields, 0U,
hdr_data, &pri_idx, &sec_idx);
if (err) {
return err;
}
if ((op == BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA) && !len) {
sr_pdu->len = 0;
goto sr_data_set_did_update;
}
if (IS_ENABLED(CONFIG_BT_CTRL_ADV_ADI_IN_SCAN_RSP)) {
(void)memcpy(&adi,
&hdr_data[ULL_ADV_HDR_DATA_ADI_PTR_OFFSET],
sizeof(struct pdu_adv_adi *));
}
/* Add AD Data and remove any prior presence of Aux Ptr */
hdr_add_fields |= ULL_ADV_PDU_HDR_FIELD_ADVA |
ULL_ADV_PDU_HDR_FIELD_AD_DATA;
err = ull_adv_aux_pdu_set_clear(adv, sr_pdu_prev, sr_pdu,
hdr_add_fields,
ULL_ADV_PDU_HDR_FIELD_AUX_PTR,
hdr_data);
#if defined(CONFIG_BT_CTLR_ADV_AUX_PDU_LINK)
if (err == BT_HCI_ERR_PACKET_TOO_LONG) {
uint8_t ad_len_offset;
ad_len_offset = ULL_ADV_HDR_DATA_DATA_PTR_OFFSET +
ULL_ADV_HDR_DATA_DATA_PTR_SIZE;
if (hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_ADI) {
ad_len_offset +=
ULL_ADV_HDR_DATA_ADI_PTR_OFFSET +
ULL_ADV_HDR_DATA_ADI_PTR_SIZE;
}
ad_len_overflow = hdr_data[ad_len_offset];
/* Prepare the AD data as parameter to update in
* PDU
*/
val_ptr = hdr_data;
/* Place holder for ADI field reference to be
* returned
*/
if (hdr_add_fields &
ULL_ADV_PDU_HDR_FIELD_ADI) {
val_ptr++;
(void)memcpy(val_ptr, &adi,
sizeof(struct pdu_adv_adi *));
val_ptr += sizeof(struct pdu_adv_adi *);
}
/* Place holder and reference to data passed and
* old reference to be returned
*/
*val_ptr++ = len - ad_len_overflow;
(void)memcpy(val_ptr, &data, sizeof(data));
err = ull_adv_aux_pdu_set_clear(adv, sr_pdu_prev,
sr_pdu, hdr_add_fields,
ULL_ADV_PDU_HDR_FIELD_AUX_PTR,
hdr_data);
}
if (!err) {
/* Fragment into chain PDU if len > 191 bytes */
if (len > PDU_AC_EXT_AD_DATA_LEN_MAX) {
/* Prepare the AD data as parameter to update in
* PDU
*/
val_ptr = hdr_data;
/* Place holder for ADI field reference to be
* returned
*/
if (hdr_add_fields &
ULL_ADV_PDU_HDR_FIELD_ADI) {
val_ptr++;
val_ptr += sizeof(struct pdu_adv_adi *);
}
/* Place holder for aux ptr reference to be
* returned
*/
val_ptr++;
val_ptr += sizeof(uint8_t *);
/* Place holder and reference to data passed and
* old reference to be returned
*/
*val_ptr = PDU_AC_EXT_AD_DATA_LEN_MAX;
(void)memcpy(&val_ptr[ULL_ADV_HDR_DATA_DATA_PTR_OFFSET],
&data, sizeof(data));
/* Calculate the overflow chain PDU's AD data
* length
*/
ad_len_overflow =
len - PDU_AC_EXT_AD_DATA_LEN_MAX;
/* No AD data in chain PDU besides the
* overflow
*/
ad_len_chain = 0U;
} else {
struct pdu_adv *pdu_chain;
/* Remove/Release any previous chain PDU
* allocations
*/
pdu_chain = lll_adv_pdu_linked_next_get(sr_pdu);
if (pdu_chain) {
lll_adv_pdu_linked_append(NULL, sr_pdu);
lll_adv_pdu_linked_release_all(pdu_chain);
}
/* No AD data overflow */
ad_len_overflow = 0U;
/* No AD data in chain PDU */
ad_len_chain = 0U;
}
} else {
/* No AD data overflow */
ad_len_overflow = 0U;
/* No AD data in chain PDU */
ad_len_chain = 0U;
}
} else {
struct pdu_adv *pdu_chain_prev;
struct pdu_adv *pdu_chain;
uint16_t ad_len_total;
uint8_t ad_len_prev;
/* Traverse to next set clear hdr data parameter */
val_ptr += sizeof(data);
/* Traverse to the last chain PDU */
ad_len_total = 0U;
pdu_chain_prev = sr_pdu_prev;
pdu_chain = sr_pdu;
do {
/* Prepare for aux ptr field reference to be returned, hence
* second parameter will be for AD data field.
*/
*val_ptr = 0U;
(void)memset((void *)&val_ptr[ULL_ADV_HDR_DATA_DATA_PTR_OFFSET],
0U, ULL_ADV_HDR_DATA_DATA_PTR_SIZE);
sr_pdu_prev = pdu_chain_prev;
sr_pdu = pdu_chain;
/* Add Aux Ptr field if not already present */
hdr_add_fields = ULL_ADV_PDU_HDR_FIELD_AD_DATA |
ULL_ADV_PDU_HDR_FIELD_AUX_PTR;
err = ull_adv_aux_pdu_set_clear(adv, sr_pdu_prev,
sr_pdu, hdr_add_fields,
0U, hdr_data);
LL_ASSERT(!err || (err == BT_HCI_ERR_PACKET_TOO_LONG));
/* Get PDUs previous AD data length */
ad_len_prev =
hdr_data[ULL_ADV_HDR_DATA_AUX_PTR_PTR_OFFSET +
ULL_ADV_HDR_DATA_AUX_PTR_PTR_SIZE];
/* Check of max supported AD data len */
ad_len_total += ad_len_prev;
if ((ad_len_total + len) >
CONFIG_BT_CTLR_ADV_DATA_LEN_MAX) {
/* NOTE: latest PDU was not consumed by LLL and
* as ull_adv_sync_pdu_alloc() has reverted back
* the double buffer with the first PDU, and
* returned the latest PDU as the new PDU, we
* need to enqueue back the new PDU which is
* infact the latest PDU.
*/
if (sr_pdu_prev == sr_pdu) {
lll_adv_scan_rsp_enqueue(lll, sr_idx);
}
return BT_HCI_ERR_MEM_CAPACITY_EXCEEDED;
}
pdu_chain_prev = lll_adv_pdu_linked_next_get(sr_pdu_prev);
pdu_chain = lll_adv_pdu_linked_next_get(sr_pdu);
LL_ASSERT((pdu_chain_prev && pdu_chain) ||
(!pdu_chain_prev && !pdu_chain));
} while (pdu_chain_prev);
if (err == BT_HCI_ERR_PACKET_TOO_LONG) {
ad_len_overflow =
hdr_data[ULL_ADV_HDR_DATA_AUX_PTR_PTR_OFFSET +
ULL_ADV_HDR_DATA_AUX_PTR_PTR_SIZE +
ULL_ADV_HDR_DATA_DATA_PTR_OFFSET +
ULL_ADV_HDR_DATA_DATA_PTR_SIZE];
/* Prepare for aux ptr field reference to be returned,
* hence second parameter will be for AD data field.
* Fill it with reduced AD data length.
*/
*val_ptr = ad_len_prev - ad_len_overflow;
/* AD data len in chain PDU */
ad_len_chain = len;
/* Proceed to add chain PDU */
err = 0U;
} else {
/* No AD data overflow */
ad_len_overflow = 0U;
/* No AD data in chain PDU */
ad_len_chain = 0U;
}
/* pri_idx and sec_idx not used later in code in this function
*/
pri_idx = 0U;
sec_idx = 0U;
#endif /* CONFIG_BT_CTLR_ADV_AUX_PDU_LINK */
}
if (err) {
return err;
}
#if defined(CONFIG_BT_CTLR_ADV_AUX_PDU_LINK)
if ((op == BT_HCI_LE_EXT_ADV_OP_INTERM_FRAG) ||
(op == BT_HCI_LE_EXT_ADV_OP_LAST_FRAG) ||
ad_len_overflow) {
struct pdu_adv_com_ext_adv *com_hdr_chain;
struct pdu_adv_com_ext_adv *com_hdr;
struct pdu_adv_ext_hdr *hdr_chain;
struct pdu_adv_aux_ptr *aux_ptr;
struct pdu_adv *pdu_chain_prev;
struct pdu_adv_ext_hdr hdr;
struct pdu_adv *pdu_chain;
uint8_t aux_ptr_offset;
uint8_t *dptr_chain;
uint32_t offs_us;
uint16_t sec_len;
uint8_t *dptr;
/* Get reference to flags in superior PDU */
com_hdr = &sr_pdu->adv_ext_ind;
if (com_hdr->ext_hdr_len) {
hdr = com_hdr->ext_hdr;
} else {
*(uint8_t *)&hdr = 0U;
}
dptr = com_hdr->ext_hdr.data;
/* Allocate new PDU */
pdu_chain = lll_adv_pdu_alloc_pdu_adv();
LL_ASSERT(pdu_chain);
/* Populate the appended chain PDU */
pdu_chain->type = PDU_ADV_TYPE_AUX_CHAIN_IND;
pdu_chain->rfu = 0U;
pdu_chain->chan_sel = 0U;
pdu_chain->tx_addr = 0U;
pdu_chain->rx_addr = 0U;
pdu_chain->len = 0U;
com_hdr_chain = &pdu_chain->adv_ext_ind;
hdr_chain = (void *)&com_hdr_chain->ext_hdr_adv_data[0];
dptr_chain = (void *)hdr_chain;
/* Flags */
*dptr_chain = 0U;
/* ADI flag, mandatory if superior PDU has it */
if (hdr.adi) {
hdr_chain->adi = 1U;
}
/* Proceed to next byte if any flags present */
if (*dptr_chain) {
dptr_chain++;
}
/* Start adding fields corresponding to flags here, if any */
/* AdvA flag */
if (hdr.adv_addr) {
dptr += BDADDR_SIZE;
}
/* TgtA flag */
if (hdr.tgt_addr) {
dptr += BDADDR_SIZE;
}
/* No CTEInfo in Extended Advertising */
/* ADI flag */
if (hdr_chain->adi) {
(void)memcpy(dptr_chain, dptr,
sizeof(struct pdu_adv_adi));
dptr += sizeof(struct pdu_adv_adi);
dptr_chain += sizeof(struct pdu_adv_adi);
}
/* non-connectable non-scannable chain pdu */
com_hdr_chain->adv_mode = 0;
/* Calc current chain PDU len */
sec_len = ull_adv_aux_hdr_len_calc(com_hdr_chain, &dptr_chain);
/* Prefix overflowed data to chain PDU and reduce the AD data in
* in the current PDU.
*/
if (ad_len_overflow) {
uint8_t *ad_overflow;
/* Copy overflowed AD data from previous PDU into this
* new chain PDU
*/
(void)memcpy(&ad_overflow,
&val_ptr[ULL_ADV_HDR_DATA_DATA_PTR_OFFSET],
sizeof(ad_overflow));
ad_overflow += *val_ptr;
(void)memcpy(dptr_chain, ad_overflow, ad_len_overflow);
dptr_chain += ad_len_overflow;
hdr_add_fields |= ULL_ADV_PDU_HDR_FIELD_AUX_PTR;
/* Reduce the AD data in the previous PDU */
err = ull_adv_aux_pdu_set_clear(adv, sr_pdu_prev,
sr_pdu, hdr_add_fields,
0U, hdr_data);
if (err) {
/* NOTE: latest PDU was not consumed by LLL and
* as ull_adv_sync_pdu_alloc() has reverted back
* the double buffer with the first PDU, and
* returned the latest PDU as the new PDU, we
* need to enqueue back the new PDU which is
* infact the latest PDU.
*/
if (sr_pdu_prev == sr_pdu) {
lll_adv_scan_rsp_enqueue(lll, sr_idx);
}
return err;
}
/* AD data len in chain PDU besides the overflow */
len = ad_len_chain;
}
/* Check AdvData overflow */
if ((sec_len + ad_len_overflow + len) >
PDU_AC_PAYLOAD_SIZE_MAX) {
/* NOTE: latest PDU was not consumed by LLL and
* as ull_adv_sync_pdu_alloc() has reverted back
* the double buffer with the first PDU, and
* returned the latest PDU as the new PDU, we
* need to enqueue back the new PDU which is
* infact the latest PDU.
*/
if (sr_pdu_prev == sr_pdu) {
lll_adv_aux_data_enqueue(adv->lll.aux, sr_idx);
}
return BT_HCI_ERR_PACKET_TOO_LONG;
}
/* Fill the chain PDU length */
ull_adv_aux_hdr_len_fill(com_hdr_chain, sec_len);
pdu_chain->len = sec_len + ad_len_overflow + len;
/* Fill AD Data in chain PDU */
(void)memcpy(dptr_chain, data, len);
/* Get reference to aux ptr in superior PDU */
aux_ptr_offset = ULL_ADV_HDR_DATA_AUX_PTR_PTR_OFFSET;
if (hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_ADI) {
aux_ptr_offset += ULL_ADV_HDR_DATA_ADI_PTR_OFFSET +
ULL_ADV_HDR_DATA_ADI_PTR_SIZE;
}
(void)memcpy(&aux_ptr, &hdr_data[aux_ptr_offset],
sizeof(aux_ptr));
/* Fill the aux offset in the previous AUX_SYNC_IND PDU */
offs_us = PDU_AC_US(sr_pdu->len, adv->lll.phy_s,
adv->lll.phy_flags) +
EVENT_B2B_MAFS_US;
ull_adv_aux_ptr_fill(aux_ptr, offs_us, adv->lll.phy_s);
/* Remove/Release any previous chain PDUs */
pdu_chain_prev = lll_adv_pdu_linked_next_get(sr_pdu);
if (pdu_chain_prev) {
lll_adv_pdu_linked_append(NULL, sr_pdu);
lll_adv_pdu_linked_release_all(pdu_chain_prev);
}
/* Chain the PDU */
lll_adv_pdu_linked_append(pdu_chain, sr_pdu);
}
#endif /* CONFIG_BT_CTLR_ADV_AUX_PDU_LINK */
sr_data_set_did_update:
if ((op == BT_HCI_LE_EXT_ADV_OP_FIRST_FRAG) ||
(op == BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA)) {
/* NOTE: No update to primary channel PDU time reservation */
lll_adv_aux_data_enqueue(adv->lll.aux, sec_idx);
lll_adv_data_enqueue(&adv->lll, pri_idx);
sr_pdu->type = PDU_ADV_TYPE_AUX_SCAN_RSP;
sr_pdu->rfu = 0U;
sr_pdu->chan_sel = 0U;
sr_pdu->rx_addr = 0U;
if (sr_pdu->len) {
sr_pdu->adv_ext_ind.adv_mode = 0U;
sr_pdu->tx_addr = sec_pdu_prev->tx_addr;
(void)memcpy(&sr_pdu->adv_ext_ind.ext_hdr.data[ADVA_OFFSET],
&sec_pdu_prev->adv_ext_ind.ext_hdr.data[ADVA_OFFSET],
BDADDR_SIZE);
} else {
sr_pdu->tx_addr = 0U;
}
}
lll_adv_scan_rsp_enqueue(lll, sr_idx);
return 0;
}
uint16_t ll_adv_aux_max_data_length_get(void)
{
return CONFIG_BT_CTLR_ADV_DATA_LEN_MAX;
}
uint8_t ll_adv_aux_set_count_get(void)
{
return BT_CTLR_ADV_SET;
}
uint8_t ll_adv_aux_set_remove(uint8_t handle)
{
struct ll_adv_set *adv;
struct lll_adv *lll;
/* Get the advertising set instance */
adv = ull_adv_is_created_get(handle);
if (!adv) {
return BT_HCI_ERR_UNKNOWN_ADV_IDENTIFIER;
}
if (adv->is_enabled) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
lll = &adv->lll;
#if defined(CONFIG_BT_CTLR_ADV_PERIODIC)
if (lll->sync) {
struct ll_adv_sync_set *sync;
sync = HDR_LLL2ULL(lll->sync);
if (sync->is_enabled) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
lll->sync = NULL;
ull_adv_sync_release(sync);
}
#endif /* CONFIG_BT_CTLR_ADV_PERIODIC */
#if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX)
if (adv->df_cfg) {
if (adv->df_cfg->is_enabled) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
ull_df_adv_cfg_release(adv->df_cfg);
adv->df_cfg = NULL;
}
#endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */
/* Release auxiliary channel set */
if (lll->aux) {
struct ll_adv_aux_set *aux;
aux = HDR_LLL2ULL(lll->aux);
lll->aux = NULL;
ull_adv_aux_release(aux);
}
/* Dequeue and release, advertising and scan response data, to keep
* one initial primary channel PDU each for the advertising set.
* This is done to prevent common extended payload format contents from
* being overwritten and corrupted due to same primary PDU buffer being
* used to remove AdvA and other fields are moved over in its place when
* auxiliary PDU is allocated to new advertising set.
*/
(void)lll_adv_data_dequeue(&adv->lll.adv_data);
(void)lll_adv_data_dequeue(&adv->lll.scan_rsp);
/* Make the advertising set available for new advertisements */
adv->is_created = 0;
return BT_HCI_ERR_SUCCESS;
}
uint8_t ll_adv_aux_set_clear(void)
{
uint8_t retval = BT_HCI_ERR_SUCCESS;
uint8_t handle;
uint8_t err;
for (handle = 0; handle < BT_CTLR_ADV_SET; ++handle) {
err = ll_adv_aux_set_remove(handle);
if (err == BT_HCI_ERR_CMD_DISALLOWED) {
retval = err;
}
}
return retval;
}
int ull_adv_aux_init(void)
{
int err;
err = lll_rand_get(&did_unique, sizeof(did_unique));
if (err) {
return err;
}
err = init_reset();
if (err) {
return err;
}
return 0;
}
int ull_adv_aux_reset_finalize(void)
{
int err;
err = init_reset();
if (err) {
return err;
}
return 0;
}
uint8_t ull_adv_aux_chm_update(void)
{
/* For each created extended advertising set */
for (uint8_t handle = 0; handle < BT_CTLR_ADV_SET; ++handle) {
struct ll_adv_aux_set *aux;
struct ll_adv_set *adv;
uint8_t chm_last;
adv = ull_adv_is_created_get(handle);
if (!adv || !adv->lll.aux) {
continue;
}
aux = HDR_LLL2ULL(adv->lll.aux);
if (aux->chm_last != aux->chm_first) {
/* TODO: Handle previous Channel Map Update being in
* progress
*/
continue;
}
/* Append the channelMapNew that will be picked up by ULL */
chm_last = aux->chm_last + 1;
if (chm_last == DOUBLE_BUFFER_SIZE) {
chm_last = 0U;
}
aux->chm[chm_last].data_chan_count =
ull_chan_map_get(aux->chm[chm_last].data_chan_map);
aux->chm_last = chm_last;
}
/* TODO: Should failure due to Channel Map Update being already in
* progress be returned to caller?
*/
return 0;
}
uint8_t ull_adv_aux_hdr_set_clear(struct ll_adv_set *adv,
uint16_t sec_hdr_add_fields,
uint16_t sec_hdr_rem_fields,
void *hdr_data,
uint8_t *pri_idx, uint8_t *sec_idx)
{
struct pdu_adv_com_ext_adv *pri_com_hdr, *pri_com_hdr_prev;
struct pdu_adv_com_ext_adv *sec_com_hdr, *sec_com_hdr_prev;
struct pdu_adv_ext_hdr *hdr, pri_hdr, pri_hdr_prev;
struct pdu_adv_ext_hdr sec_hdr, sec_hdr_prev;
struct pdu_adv *pri_pdu, *pri_pdu_prev;
struct pdu_adv *sec_pdu_prev, *sec_pdu;
struct pdu_adv_adi *pri_adi, *sec_adi;
uint8_t *pri_dptr, *pri_dptr_prev;
uint8_t *sec_dptr, *sec_dptr_prev;
struct pdu_adv_aux_ptr *aux_ptr;
uint8_t pri_len, sec_len_prev;
struct lll_adv_aux *lll_aux;
struct ll_adv_aux_set *aux;
struct pdu_adv_adi *adi;
struct lll_adv *lll;
uint8_t is_aux_new;
uint8_t *ad_data;
uint16_t sec_len;
uint8_t ad_len;
uint16_t did;
lll = &adv->lll;
/* Can't have both flags set here since both use 'hdr_data' param */
LL_ASSERT(!(sec_hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_ADVA) ||
!(sec_hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_AD_DATA));
/* Get reference to previous primary PDU data */
pri_pdu_prev = lll_adv_data_peek(lll);
if (pri_pdu_prev->type != PDU_ADV_TYPE_EXT_IND) {
if (sec_hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_AD_DATA) {
/* pick the data length */
ad_len = *((uint8_t *)hdr_data);
hdr_data = (uint8_t *)hdr_data + sizeof(ad_len);
/* pick the reference to data */
(void)memcpy(&ad_data, hdr_data, sizeof(ad_data));
return ull_adv_data_set(adv, ad_len, ad_data);
}
return BT_HCI_ERR_CMD_DISALLOWED;
}
pri_com_hdr_prev = (void *)&pri_pdu_prev->adv_ext_ind;
hdr = (void *)pri_com_hdr_prev->ext_hdr_adv_data;
if (pri_com_hdr_prev->ext_hdr_len) {
pri_hdr_prev = *hdr;
} else {
*(uint8_t *)&pri_hdr_prev = 0U;
}
pri_dptr_prev = hdr->data;
/* Advertising data are not supported by scannable instances */
if ((sec_hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_AD_DATA) &&
(pri_com_hdr_prev->adv_mode & BT_HCI_LE_ADV_PROP_SCAN)) {
return BT_HCI_ERR_INVALID_PARAM;
}
/* Get reference to new primary PDU data buffer */
pri_pdu = lll_adv_data_alloc(lll, pri_idx);
pri_pdu->type = pri_pdu_prev->type;
pri_pdu->rfu = 0U;
pri_pdu->chan_sel = 0U;
pri_com_hdr = (void *)&pri_pdu->adv_ext_ind;
pri_com_hdr->adv_mode = pri_com_hdr_prev->adv_mode;
hdr = (void *)pri_com_hdr->ext_hdr_adv_data;
pri_dptr = hdr->data;
*(uint8_t *)&pri_hdr = 0U;
/* Get the reference to aux instance */
lll_aux = lll->aux;
if (!lll_aux) {
aux = ull_adv_aux_acquire(lll);
if (!aux) {
LL_ASSERT(pri_pdu != pri_pdu_prev);
return BT_HCI_ERR_MEM_CAPACITY_EXCEEDED;
}
lll_aux = &aux->lll;
is_aux_new = 1U;
} else {
aux = HDR_LLL2ULL(lll_aux);
is_aux_new = 0U;
}
/* Get reference to previous secondary PDU data */
sec_pdu_prev = lll_adv_aux_data_peek(lll_aux);
sec_com_hdr_prev = (void *)&sec_pdu_prev->adv_ext_ind;
hdr = (void *)sec_com_hdr_prev->ext_hdr_adv_data;
if (!is_aux_new) {
sec_hdr_prev = *hdr;
} else {
/* Initialize only those fields used to copy into new PDU
* buffer.
*/
sec_pdu_prev->tx_addr = 0U;
sec_pdu_prev->rx_addr = 0U;
sec_pdu_prev->len = PDU_AC_EXT_HEADER_SIZE_MIN;
*(uint8_t *)hdr = 0U;
*(uint8_t *)&sec_hdr_prev = 0U;
}
sec_dptr_prev = hdr->data;
/* Get reference to new secondary PDU data buffer */
sec_pdu = lll_adv_aux_data_alloc(lll_aux, sec_idx);
sec_pdu->type = pri_pdu->type;
sec_pdu->rfu = 0U;
sec_pdu->chan_sel = 0U;
sec_pdu->tx_addr = sec_pdu_prev->tx_addr;
sec_pdu->rx_addr = sec_pdu_prev->rx_addr;
sec_com_hdr = (void *)&sec_pdu->adv_ext_ind;
sec_com_hdr->adv_mode = pri_com_hdr->adv_mode;
hdr = (void *)sec_com_hdr->ext_hdr_adv_data;
sec_dptr = hdr->data;
*(uint8_t *)&sec_hdr = 0U;
/* AdvA flag */
/* NOTE: as we will use auxiliary packet, we remove AdvA in primary
* channel, i.e. do nothing to not add AdvA in the primary PDU.
* AdvA can be either set explicitly (i.e. needs own_addr_type to be
* set), can be copied from primary PDU (i.e. adding AD to existing set)
* or can be copied from previous secondary PDU.
*/
sec_hdr.adv_addr = 1;
if (sec_hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_ADVA) {
uint8_t own_addr_type = *(uint8_t *)hdr_data;
/* Move to next hdr_data */
hdr_data = (uint8_t *)hdr_data + sizeof(own_addr_type);
sec_pdu->tx_addr = own_addr_type & 0x1;
} else if (pri_hdr_prev.adv_addr) {
sec_pdu->tx_addr = pri_pdu_prev->tx_addr;
} else if (sec_hdr_prev.adv_addr) {
sec_pdu->tx_addr = sec_pdu_prev->tx_addr;
} else {
/* We do not have valid address info, this should not happen */
return BT_HCI_ERR_UNSPECIFIED;
}
pri_pdu->tx_addr = 0U;
if (pri_hdr_prev.adv_addr) {
pri_dptr_prev += BDADDR_SIZE;
}
if (sec_hdr_prev.adv_addr) {
sec_dptr_prev += BDADDR_SIZE;
}
sec_dptr += BDADDR_SIZE;
/* No TargetA in primary and secondary channel for undirected.
* Move from primary to secondary PDU, if present in primary PDU.
*/
if (pri_hdr_prev.tgt_addr) {
sec_hdr.tgt_addr = 1U;
sec_pdu->rx_addr = pri_pdu_prev->rx_addr;
sec_dptr += BDADDR_SIZE;
/* Retain the target address if present in the previous PDU */
} else if (!(sec_hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_ADVA) &&
sec_hdr_prev.tgt_addr) {
sec_hdr.tgt_addr = 1U;
sec_pdu->rx_addr = sec_pdu_prev->rx_addr;
sec_dptr += BDADDR_SIZE;
}
pri_pdu->rx_addr = 0U;
if (pri_hdr_prev.tgt_addr) {
pri_dptr_prev += BDADDR_SIZE;
}
if (sec_hdr_prev.tgt_addr) {
sec_dptr_prev += BDADDR_SIZE;
}
/* No CTEInfo flag in primary and secondary channel PDU */
/* ADI flag */
if (pri_hdr_prev.adi) {
pri_dptr_prev += sizeof(struct pdu_adv_adi);
}
pri_hdr.adi = 1;
pri_dptr += sizeof(struct pdu_adv_adi);
if (sec_hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_ADI) {
sec_hdr.adi = 1U;
/* return the size of ADI structure */
*(uint8_t *)hdr_data = sizeof(struct pdu_adv_adi);
hdr_data = (uint8_t *)hdr_data + sizeof(uint8_t);
/* pick the reference to ADI param */
(void)memcpy(&adi, hdr_data, sizeof(struct pdu_adv_adi *));
/* return the pointer to ADI struct inside the PDU */
(void)memcpy(hdr_data, &sec_dptr, sizeof(sec_dptr));
hdr_data = (uint8_t *)hdr_data + sizeof(sec_dptr);
sec_dptr += sizeof(struct pdu_adv_adi);
} else {
sec_hdr.adi = 1;
adi = NULL;
sec_dptr += sizeof(struct pdu_adv_adi);
}
if (sec_hdr_prev.adi) {
sec_dptr_prev += sizeof(struct pdu_adv_adi);
}
/* AuxPtr flag */
if (pri_hdr_prev.aux_ptr) {
pri_dptr_prev += sizeof(struct pdu_adv_aux_ptr);
}
pri_hdr.aux_ptr = 1;
pri_dptr += sizeof(struct pdu_adv_aux_ptr);
if (sec_hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_AUX_PTR) {
sec_hdr.aux_ptr = 1;
aux_ptr = NULL;
/* return the size of aux pointer structure */
*(uint8_t *)hdr_data = sizeof(struct pdu_adv_aux_ptr);
hdr_data = (uint8_t *)hdr_data + sizeof(uint8_t);
/* return the pointer to aux pointer struct inside the PDU
* buffer
*/
(void)memcpy(hdr_data, &sec_dptr, sizeof(sec_dptr));
hdr_data = (uint8_t *)hdr_data + sizeof(sec_dptr);
} else if (!(sec_hdr_rem_fields & ULL_ADV_PDU_HDR_FIELD_AUX_PTR) &&
sec_hdr_prev.aux_ptr) {
sec_hdr.aux_ptr = 1;
aux_ptr = (void *)sec_dptr_prev;
} else {
aux_ptr = NULL;
}
if (sec_hdr_prev.aux_ptr) {
sec_dptr_prev += sizeof(struct pdu_adv_aux_ptr);
}
if (sec_hdr.aux_ptr) {
sec_dptr += sizeof(struct pdu_adv_aux_ptr);
}
#if defined(CONFIG_BT_CTLR_ADV_PERIODIC)
struct pdu_adv_sync_info *sync_info;
/* No SyncInfo flag in primary channel PDU */
/* Add/Remove SyncInfo flag in secondary channel PDU */
if (sec_hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_SYNC_INFO) {
sec_hdr.sync_info = 1;
sync_info = NULL;
/* return the size of sync info structure */
*(uint8_t *)hdr_data = sizeof(*sync_info);
hdr_data = (uint8_t *)hdr_data + sizeof(uint8_t);
/* return the pointer to sync info struct inside the PDU
* buffer
*/
(void)memcpy(hdr_data, &sec_dptr, sizeof(sec_dptr));
hdr_data = (uint8_t *)hdr_data + sizeof(sec_dptr);
} else if (!(sec_hdr_rem_fields & ULL_ADV_PDU_HDR_FIELD_SYNC_INFO) &&
sec_hdr_prev.sync_info) {
sec_hdr.sync_info = 1;
sync_info = (void *)sec_dptr_prev;
} else {
sync_info = NULL;
}
if (sec_hdr_prev.sync_info) {
sec_dptr_prev += sizeof(*sync_info);
}
if (sec_hdr.sync_info) {
sec_dptr += sizeof(*sync_info);
}
#endif /* CONFIG_BT_CTLR_ADV_PERIODIC */
/* Tx Power flag */
if (pri_hdr_prev.tx_pwr) {
pri_dptr_prev++;
/* C1, Tx Power is optional on the LE 1M PHY, and
* reserved for future use on the LE Coded PHY.
*/
if (lll->phy_p != PHY_CODED) {
pri_hdr.tx_pwr = 1;
pri_dptr++;
} else {
sec_hdr.tx_pwr = 1;
}
}
if (sec_hdr_prev.tx_pwr) {
sec_dptr_prev++;
sec_hdr.tx_pwr = 1;
}
if (sec_hdr.tx_pwr) {
sec_dptr++;
}
/* No ACAD in primary channel PDU */
/* TODO: ACAD in secondary channel PDU */
/* Calc primary PDU len */
pri_len = ull_adv_aux_hdr_len_calc(pri_com_hdr, &pri_dptr);
/* Calc previous secondary PDU len */
sec_len_prev = ull_adv_aux_hdr_len_calc(sec_com_hdr_prev,
&sec_dptr_prev);
/* Did we parse beyond PDU length? */
if (sec_len_prev > sec_pdu_prev->len) {
/* we should not encounter invalid length */
/* FIXME: release allocations */
return BT_HCI_ERR_UNSPECIFIED;
}
/* Calc current secondary PDU len */
sec_len = ull_adv_aux_hdr_len_calc(sec_com_hdr, &sec_dptr);
/* AD Data, add or remove */
if (sec_hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_AD_DATA) {
uint8_t ad_len_prev;
/* remember the new ad data len */
ad_len = *(uint8_t *)hdr_data;
/* return prev ad data length */
ad_len_prev = sec_pdu_prev->len - sec_len_prev;
*(uint8_t *)hdr_data = ad_len_prev;
hdr_data = (uint8_t *)hdr_data + sizeof(ad_len);
/* remember the reference to new ad data */
(void)memcpy(&ad_data, hdr_data, sizeof(ad_data));
/* return the reference to prev ad data */
(void)memcpy(hdr_data, &sec_dptr_prev, sizeof(sec_dptr_prev));
hdr_data = (uint8_t *)hdr_data + sizeof(sec_dptr_prev);
/* unchanged data */
if (!ad_len && !ad_data) {
ad_len = ad_len_prev;
ad_data = sec_dptr_prev;
}
} else if (!(sec_hdr_rem_fields & ULL_ADV_PDU_HDR_FIELD_AD_DATA)) {
/* Calc the previous AD data length in auxiliary PDU */
ad_len = sec_pdu_prev->len - sec_len_prev;
ad_data = sec_dptr_prev;
} else {
ad_len = 0U;
ad_data = NULL;
}
/* Check Max Advertising Data Length */
if (ad_len > CONFIG_BT_CTLR_ADV_DATA_LEN_MAX) {
return BT_HCI_ERR_MEM_CAPACITY_EXCEEDED;
}
/* Check AdvData overflow */
/* TODO: need aux_chain_ind support */
if ((sec_len + ad_len) > PDU_AC_PAYLOAD_SIZE_MAX) {
/* return excess length */
*(uint8_t *)hdr_data = sec_len + ad_len -
PDU_AC_PAYLOAD_SIZE_MAX;
if (pri_pdu == pri_pdu_prev) {
lll_adv_data_enqueue(&adv->lll, *pri_idx);
}
if (sec_pdu == sec_pdu_prev) {
lll_adv_aux_data_enqueue(adv->lll.aux, *sec_idx);
}
/* Will use packet too long error to determine fragmenting
* long data
*/
return BT_HCI_ERR_PACKET_TOO_LONG;
}
/* set the primary PDU len */
ull_adv_aux_hdr_len_fill(pri_com_hdr, pri_len);
pri_pdu->len = pri_len;
/* set the secondary PDU len */
ull_adv_aux_hdr_len_fill(sec_com_hdr, sec_len);
sec_pdu->len = sec_len + ad_len;
/* Start filling pri and sec PDU payload based on flags from here
* ==============================================================
*/
/* No AdvData in primary channel PDU */
/* Fill AdvData in secondary PDU */
(void)memmove(sec_dptr, ad_data, ad_len);
/* Early exit if no flags set */
if (!sec_com_hdr->ext_hdr_len) {
return 0;
}
/* No ACAD in primary channel PDU */
/* TODO: Fill ACAD in secondary channel PDU */
/* Tx Power */
if (pri_hdr.tx_pwr) {
*--pri_dptr = *--pri_dptr_prev;
} else if (sec_hdr.tx_pwr) {
*--sec_dptr = *--sec_dptr_prev;
}
#if defined(CONFIG_BT_CTLR_ADV_PERIODIC)
/* No SyncInfo in primary channel PDU */
/* Fill SyncInfo in secondary channel PDU */
if (sec_hdr_prev.sync_info) {
sec_dptr_prev -= sizeof(*sync_info);
}
if (sec_hdr.sync_info) {
sec_dptr -= sizeof(*sync_info);
}
if (sync_info) {
(void)memmove(sec_dptr, sync_info, sizeof(*sync_info));
}
#endif /* CONFIG_BT_CTLR_ADV_PERIODIC */
/* AuxPtr */
if (pri_hdr_prev.aux_ptr) {
pri_dptr_prev -= sizeof(struct pdu_adv_aux_ptr);
}
pri_dptr -= sizeof(struct pdu_adv_aux_ptr);
ull_adv_aux_ptr_fill((void *)pri_dptr, 0U, lll->phy_s);
if (sec_hdr_prev.aux_ptr) {
sec_dptr_prev -= sizeof(struct pdu_adv_aux_ptr);
}
if (sec_hdr.aux_ptr) {
sec_dptr -= sizeof(struct pdu_adv_aux_ptr);
}
if (aux_ptr) {
(void)memmove(sec_dptr, aux_ptr, sizeof(*aux_ptr));
}
/* ADI */
if (pri_hdr_prev.adi) {
pri_dptr_prev -= sizeof(struct pdu_adv_adi);
}
if (sec_hdr_prev.adi) {
sec_dptr_prev -= sizeof(struct pdu_adv_adi);
}
pri_dptr -= sizeof(struct pdu_adv_adi);
sec_dptr -= sizeof(struct pdu_adv_adi);
pri_adi = (void *)pri_dptr;
sec_adi = (void *)sec_dptr;
pri_adi->sid = adv->sid;
sec_adi->sid = adv->sid;
if (!adi) {
/* The DID for a specific SID shall be unique.
*/
did = ull_adv_aux_did_next_unique_get(adv->sid);
} else {
did = adi->did;
}
pri_adi->did = sys_cpu_to_le16(did);
sec_adi->did = sys_cpu_to_le16(did);
/* No CTEInfo field in primary channel PDU */
/* No TargetA non-conn non-scan advertising, but present in directed
* advertising.
*/
if (sec_hdr.tgt_addr) {
void *bdaddr;
if (sec_hdr_prev.tgt_addr) {
sec_dptr_prev -= BDADDR_SIZE;
bdaddr = sec_dptr_prev;
} else {
pri_dptr_prev -= BDADDR_SIZE;
bdaddr = pri_dptr_prev;
}
sec_dptr -= BDADDR_SIZE;
(void)memcpy(sec_dptr, bdaddr, BDADDR_SIZE);
}
/* No AdvA in primary channel due to AuxPtr being added */
/* NOTE: AdvA in aux channel is also filled at enable and RPA
* timeout
*/
if (sec_hdr.adv_addr) {
void *bdaddr;
if (sec_hdr_prev.adv_addr) {
sec_dptr_prev -= BDADDR_SIZE;
bdaddr = sec_dptr_prev;
} else {
pri_dptr_prev -= BDADDR_SIZE;
bdaddr = pri_dptr_prev;
}
sec_dptr -= BDADDR_SIZE;
(void)memcpy(sec_dptr, bdaddr, BDADDR_SIZE);
}
/* Set the common extended header format flags in the current primary
* PDU
*/
if (pri_com_hdr->ext_hdr_len != 0) {
pri_com_hdr->ext_hdr = pri_hdr;
}
/* Set the common extended header format flags in the current secondary
* PDU
*/
if (sec_com_hdr->ext_hdr_len != 0) {
sec_com_hdr->ext_hdr = sec_hdr;
}
#if defined(CONFIG_BT_CTLR_ADV_AUX_PDU_LINK)
ull_adv_aux_chain_pdu_duplicate(sec_pdu_prev, sec_pdu, aux_ptr,
adv->lll.phy_s, adv->lll.phy_flags,
EVENT_B2B_MAFS_US);
#endif /* CONFIG_BT_CTLR_ADV_AUX_PDU_LINK */
/* Update auxiliary channel event time reservation */
if (aux->is_started) {
struct pdu_adv *pdu_scan;
uint8_t err;
pdu_scan = lll_adv_scan_rsp_peek(lll);
err = aux_time_update(aux, sec_pdu, pdu_scan);
if (err) {
return err;
}
}
return 0;
}
uint8_t ull_adv_aux_pdu_set_clear(struct ll_adv_set *adv,
struct pdu_adv *pdu_prev,
struct pdu_adv *pdu,
uint16_t hdr_add_fields,
uint16_t hdr_rem_fields,
void *hdr_data)
{
struct pdu_adv_com_ext_adv *com_hdr, *com_hdr_prev;
struct pdu_adv_ext_hdr hdr = { 0 }, hdr_prev = { 0 };
struct pdu_adv_aux_ptr *aux_ptr, *aux_ptr_prev;
uint8_t *dptr, *dptr_prev;
struct pdu_adv_adi *adi;
uint8_t acad_len_prev;
uint8_t hdr_buf_len;
uint8_t len_prev;
uint8_t *ad_data;
uint8_t acad_len;
#if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX)
uint8_t cte_info;
#endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */
uint8_t ad_len;
uint16_t len;
/* Get common pointers from reference to previous tertiary PDU data */
com_hdr_prev = (void *)&pdu_prev->adv_ext_ind;
if (pdu_prev->len && com_hdr_prev->ext_hdr_len) {
hdr_prev = com_hdr_prev->ext_hdr;
} else {
com_hdr_prev->ext_hdr_len = 0U;
}
dptr_prev = com_hdr_prev->ext_hdr.data;
/* Set common fields in reference to new tertiary PDU data buffer */
pdu->type = pdu_prev->type;
pdu->rfu = 0U;
pdu->chan_sel = 0U;
pdu->tx_addr = pdu_prev->tx_addr;
pdu->rx_addr = pdu_prev->rx_addr;
/* Get common pointers from current tertiary PDU data.
* It is possible that the current tertiary is the same as
* previous one. It may happen if update periodic advertising
* chain in place.
*/
com_hdr = (void *)&pdu->adv_ext_ind;
com_hdr->adv_mode = com_hdr_prev->adv_mode;
dptr = com_hdr->ext_hdr.data;
/* AdvA flag */
if (hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_ADVA) {
hdr.adv_addr = 1U;
dptr += BDADDR_SIZE;
} else if (!(hdr_rem_fields & ULL_ADV_PDU_HDR_FIELD_ADVA) &&
hdr_prev.adv_addr) {
hdr.adv_addr = 1U;
pdu->tx_addr = pdu_prev->tx_addr;
dptr += BDADDR_SIZE;
}
if (hdr_prev.adv_addr) {
dptr_prev += BDADDR_SIZE;
}
/* TargetA flag */
if (hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_TARGETA) {
hdr.tgt_addr = 1U;
dptr += BDADDR_SIZE;
} else if (!(hdr_rem_fields & ULL_ADV_PDU_HDR_FIELD_TARGETA) &&
hdr_prev.tgt_addr) {
hdr.tgt_addr = 1U;
pdu->rx_addr = pdu_prev->rx_addr;
dptr += BDADDR_SIZE;
}
if (hdr_prev.tgt_addr) {
dptr_prev += BDADDR_SIZE;
}
#if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX)
/* If requested add or update CTEInfo */
if (hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_CTE_INFO) {
hdr.cte_info = 1;
cte_info = *(uint8_t *)hdr_data;
hdr_data = (uint8_t *)hdr_data + 1;
dptr += sizeof(struct pdu_cte_info);
/* If CTEInfo exists in prev and is not requested to be removed */
} else if (!(hdr_rem_fields & ULL_ADV_PDU_HDR_FIELD_CTE_INFO) &&
hdr_prev.cte_info) {
hdr.cte_info = 1;
cte_info = 0U; /* value not used, will be read from prev PDU */
dptr += sizeof(struct pdu_cte_info);
} else {
cte_info = 0U; /* value not used */
}
/* If CTEInfo exists in prev PDU */
if (hdr_prev.cte_info) {
dptr_prev += sizeof(struct pdu_cte_info);
}
#endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */
/* ADI */
if (hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_ADI) {
hdr.adi = 1U;
/* return the size of ADI structure */
*(uint8_t *)hdr_data = sizeof(struct pdu_adv_adi);
hdr_data = (uint8_t *)hdr_data + sizeof(uint8_t);
/* pick the reference to ADI param */
(void)memcpy(&adi, hdr_data, sizeof(struct pdu_adv_adi *));
/* return the pointer to ADI struct inside the PDU */
(void)memcpy(hdr_data, &dptr, sizeof(dptr));
hdr_data = (uint8_t *)hdr_data + sizeof(dptr);
dptr += sizeof(struct pdu_adv_adi);
} else if (!(hdr_rem_fields & ULL_ADV_PDU_HDR_FIELD_ADI) &&
hdr_prev.adi) {
hdr.adi = 1U;
adi = (void *)dptr_prev;
dptr += sizeof(struct pdu_adv_adi);
} else {
adi = NULL;
}
if (hdr_prev.adi) {
dptr_prev += sizeof(struct pdu_adv_adi);
}
/* AuxPtr - will be added if AUX_CHAIN_IND is required */
if (hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_AUX_PTR) {
hdr.aux_ptr = 1;
aux_ptr_prev = NULL;
aux_ptr = (void *)dptr;
/* return the size of aux pointer structure */
*(uint8_t *)hdr_data = sizeof(struct pdu_adv_aux_ptr);
hdr_data = (uint8_t *)hdr_data + sizeof(uint8_t);
/* return the pointer to aux pointer struct inside the PDU
* buffer
*/
(void)memcpy(hdr_data, &dptr, sizeof(dptr));
hdr_data = (uint8_t *)hdr_data + sizeof(dptr);
} else if (!(hdr_rem_fields & ULL_ADV_PDU_HDR_FIELD_AUX_PTR) &&
hdr_prev.aux_ptr) {
hdr.aux_ptr = 1;
aux_ptr_prev = (void *)dptr_prev;
aux_ptr = (void *)dptr;
} else {
aux_ptr_prev = NULL;
aux_ptr = NULL;
}
if (hdr_prev.aux_ptr) {
dptr_prev += sizeof(struct pdu_adv_aux_ptr);
}
if (hdr.aux_ptr) {
dptr += sizeof(struct pdu_adv_aux_ptr);
}
/* SyncInfo flag */
if (hdr_prev.sync_info) {
hdr.sync_info = 1;
dptr_prev += sizeof(struct pdu_adv_sync_info);
dptr += sizeof(struct pdu_adv_sync_info);
}
/* Tx Power flag */
if (hdr_prev.tx_pwr) {
dptr_prev++;
hdr.tx_pwr = 1;
dptr++;
}
/* Calc previous ACAD len and update PDU len */
len_prev = dptr_prev - (uint8_t *)com_hdr_prev;
hdr_buf_len = com_hdr_prev->ext_hdr_len +
PDU_AC_EXT_HEADER_SIZE_MIN;
if (len_prev <= hdr_buf_len) {
/* There are some data, except ACAD, in extended header if len_prev
* equals to hdr_buf_len. There is ACAD if the size of len_prev
* is smaller than hdr_buf_len.
*/
acad_len_prev = hdr_buf_len - len_prev;
len_prev += acad_len_prev;
dptr_prev += acad_len_prev;
} else {
/* There are no data in extended header, all flags are zeros. */
acad_len_prev = 0;
/* NOTE: If no flags are set then extended header length will be
* zero. Under this condition the current len_prev
* value will be greater than extended header length,
* hence set len_prev to size of the length/mode
* field.
*/
len_prev = (pdu_prev->len) ? PDU_AC_EXT_HEADER_SIZE_MIN : 0U;
dptr_prev = (uint8_t *)com_hdr_prev + len_prev;
}
/* Did we parse beyond PDU length? */
if (len_prev > pdu_prev->len) {
/* we should not encounter invalid length */
return BT_HCI_ERR_UNSPECIFIED;
}
/* Add/Retain/Remove ACAD */
if (hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_ACAD) {
acad_len = *(uint8_t *)hdr_data;
/* If zero length ACAD then do not reduce ACAD but return
* return previous ACAD length.
*/
if (!acad_len) {
acad_len = acad_len_prev;
}
/* return prev ACAD length */
*(uint8_t *)hdr_data = acad_len_prev;
hdr_data = (uint8_t *)hdr_data + 1;
/* return the pointer to ACAD offset */
(void)memcpy(hdr_data, &dptr, sizeof(dptr));
hdr_data = (uint8_t *)hdr_data + sizeof(dptr);
dptr += acad_len;
} else if (!(hdr_rem_fields & ULL_ADV_PDU_HDR_FIELD_ACAD)) {
acad_len = acad_len_prev;
dptr += acad_len_prev;
} else {
acad_len = 0U;
}
/* Calc current tertiary PDU len so far without AD data added */
len = ull_adv_aux_hdr_len_calc(com_hdr, &dptr);
/* Get Adv data from function parameters */
if (hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_AD_DATA) {
uint8_t ad_len_prev;
/* remember the new ad data len */
ad_len = *(uint8_t *)hdr_data;
/* return prev ad data length */
ad_len_prev = pdu_prev->len - len_prev;
*(uint8_t *)hdr_data = ad_len_prev;
hdr_data = (uint8_t *)hdr_data + sizeof(ad_len);
/* remember the reference to new ad data */
(void)memcpy(&ad_data, hdr_data, sizeof(ad_data));
/* return the reference to prev ad data */
(void)memcpy(hdr_data, &dptr_prev, sizeof(dptr_prev));
hdr_data = (uint8_t *)hdr_data + sizeof(dptr_prev);
/* unchanged data */
if (!ad_len && !ad_data) {
ad_len = ad_len_prev;
ad_data = dptr_prev;
}
} else if (!(hdr_rem_fields & ULL_ADV_PDU_HDR_FIELD_AD_DATA)) {
ad_len = pdu_prev->len - len_prev;
ad_data = dptr_prev;
} else {
ad_len = 0;
ad_data = NULL;
}
/* Check Max Advertising Data Length */
if (ad_len > CONFIG_BT_CTLR_ADV_DATA_LEN_MAX) {
return BT_HCI_ERR_MEM_CAPACITY_EXCEEDED;
}
/* Check AdvData overflow */
if ((len + ad_len) > PDU_AC_PAYLOAD_SIZE_MAX) {
/* return excess length */
*(uint8_t *)hdr_data = len + ad_len -
PDU_AC_PAYLOAD_SIZE_MAX;
/* Will use packet too long error to determine fragmenting
* long data
*/
return BT_HCI_ERR_PACKET_TOO_LONG;
}
/* set the tertiary extended header and PDU length */
ull_adv_aux_hdr_len_fill(com_hdr, len);
pdu->len = len + ad_len;
/* Start filling tertiary PDU payload based on flags from here
* ==============================================================
*/
/* Fill AdvData in tertiary PDU */
(void)memmove(dptr, ad_data, ad_len);
/* Early exit if no flags set */
if (!com_hdr->ext_hdr_len) {
return 0;
}
/* Retain ACAD in tertiary PDU */
dptr_prev -= acad_len_prev;
if (acad_len) {
dptr -= acad_len;
(void)memmove(dptr, dptr_prev, acad_len_prev);
}
/* Tx Power */
if (hdr.tx_pwr) {
*--dptr = *--dptr_prev;
}
/* SyncInfo */
if (hdr.sync_info) {
dptr_prev -= sizeof(struct pdu_adv_sync_info);
dptr -= sizeof(struct pdu_adv_sync_info);
(void)memmove(dptr, dptr_prev,
sizeof(struct pdu_adv_sync_info));
}
/* AuxPtr */
if (hdr_prev.aux_ptr) {
dptr_prev -= sizeof(struct pdu_adv_aux_ptr);
}
if (hdr.aux_ptr) {
dptr -= sizeof(struct pdu_adv_aux_ptr);
}
if (aux_ptr_prev) {
(void)memmove(dptr, aux_ptr_prev, sizeof(*aux_ptr_prev));
}
/* ADI */
if (hdr_prev.adi) {
dptr_prev -= sizeof(struct pdu_adv_adi);
}
if (hdr.adi) {
struct pdu_adv_adi *adi_pdu;
dptr -= sizeof(struct pdu_adv_adi);
adi_pdu = (void *)dptr;
if (!adi) {
adi_pdu->sid = adv->sid;
/* The DID for a specific SID shall be unique.
*/
const uint16_t did =
ull_adv_aux_did_next_unique_get(adv->sid);
adi_pdu->did = sys_cpu_to_le16(did);
} else {
adi_pdu->sid = adi->sid;
adi_pdu->did = adi->did;
}
}
#if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX)
if (hdr.cte_info) {
if (hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_CTE_INFO) {
*--dptr = cte_info;
} else {
*--dptr = *--dptr_prev;
}
}
#endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */
/* No TargetA in non-conn non-scan advertising, but present in directed
* advertising.
*/
if (hdr.tgt_addr) {
dptr_prev -= BDADDR_SIZE;
dptr -= BDADDR_SIZE;
(void)memmove(dptr, dptr_prev, BDADDR_SIZE);
}
/* NOTE: AdvA in aux channel is also filled at enable and RPA
* timeout
*/
if (hdr.adv_addr) {
dptr_prev -= BDADDR_SIZE;
dptr -= BDADDR_SIZE;
(void)memmove(dptr, dptr_prev, BDADDR_SIZE);
}
if (com_hdr->ext_hdr_len != 0) {
com_hdr->ext_hdr = hdr;
}
return 0;
}
uint16_t ull_adv_aux_did_next_unique_get(uint8_t sid)
{
/* The DID is 12 bits and did_unique may overflow without any negative
* consequences.
*/
return BIT_MASK(12) & did_unique[sid]++;
}
void ull_adv_aux_ptr_fill(struct pdu_adv_aux_ptr *aux_ptr, uint32_t offs_us,
uint8_t phy_s)
{
uint32_t offs;
uint8_t phy;
/* NOTE: Channel Index and Aux Offset will be set on every advertiser's
* event prepare when finding the auxiliary event's ticker offset.
* Here we fill initial values.
*/
aux_ptr->chan_idx = 0U;
aux_ptr->ca = (lll_clock_ppm_local_get() <= SCA_50_PPM) ?
SCA_VALUE_50_PPM : SCA_VALUE_500_PPM;
offs = offs_us / OFFS_UNIT_30_US;
if (!!(offs >> OFFS_UNIT_BITS)) {
offs = offs / (OFFS_UNIT_300_US / OFFS_UNIT_30_US);
aux_ptr->offs_units = OFFS_UNIT_VALUE_300_US;
} else {
aux_ptr->offs_units = OFFS_UNIT_VALUE_30_US;
}
phy = find_lsb_set(phy_s) - 1;
aux_ptr->offs_phy_packed[0] = offs & 0xFF;
aux_ptr->offs_phy_packed[1] = ((offs>>8) & 0x1F) + (phy << 5);
}
#if (CONFIG_BT_CTLR_ADV_AUX_SET > 0)
inline uint8_t ull_adv_aux_handle_get(struct ll_adv_aux_set *aux)
{
return mem_index_get(aux, ll_adv_aux_pool,
sizeof(struct ll_adv_aux_set));
}
uint8_t ull_adv_aux_lll_handle_get(struct lll_adv_aux *lll)
{
return ull_adv_aux_handle_get((void *)lll->hdr.parent);
}
uint32_t ull_adv_aux_evt_init(struct ll_adv_aux_set *aux,
uint32_t *ticks_anchor)
{
uint32_t ticks_slot_overhead;
struct lll_adv_aux *lll_aux;
struct pdu_adv *pdu_scan;
struct pdu_adv *pdu;
struct lll_adv *lll;
uint32_t time_us;
lll_aux = &aux->lll;
lll = lll_aux->adv;
pdu = lll_adv_aux_data_peek(lll_aux);
pdu_scan = lll_adv_scan_rsp_peek(lll);
/* Calculate the PDU Tx Time and hence the radio event length */
time_us = aux_time_get(aux, pdu, pdu_scan);
/* TODO: active_to_start feature port */
aux->ull.ticks_active_to_start = 0;
aux->ull.ticks_prepare_to_start =
HAL_TICKER_US_TO_TICKS(EVENT_OVERHEAD_XTAL_US);
aux->ull.ticks_preempt_to_start =
HAL_TICKER_US_TO_TICKS(EVENT_OVERHEAD_PREEMPT_MIN_US);
aux->ull.ticks_slot = HAL_TICKER_US_TO_TICKS(time_us);
if (IS_ENABLED(CONFIG_BT_CTLR_LOW_LAT)) {
ticks_slot_overhead = MAX(aux->ull.ticks_active_to_start,
aux->ull.ticks_prepare_to_start);
} else {
ticks_slot_overhead = 0;
}
#if defined(CONFIG_BT_CTLR_SCHED_ADVANCED)
uint32_t ticks_anchor_aux;
uint32_t ticks_slot;
int err;
#if defined(CONFIG_BT_CTLR_ADV_RESERVE_MAX)
time_us = ull_adv_aux_time_get(aux, PDU_AC_PAYLOAD_SIZE_MAX,
PDU_AC_PAYLOAD_SIZE_MAX);
ticks_slot = HAL_TICKER_US_TO_TICKS(time_us);
#else
ticks_slot = aux->ull.ticks_slot;
#endif
err = ull_sched_adv_aux_sync_free_slot_get(TICKER_USER_ID_THREAD,
(ticks_slot +
ticks_slot_overhead),
&ticks_anchor_aux);
if (!err) {
*ticks_anchor = ticks_anchor_aux;
*ticks_anchor += HAL_TICKER_US_TO_TICKS(
EVENT_TICKER_RES_MARGIN_US);
}
#endif /* CONFIG_BT_CTLR_SCHED_ADVANCED */
return ticks_slot_overhead;
}
uint32_t ull_adv_aux_start(struct ll_adv_aux_set *aux, uint32_t ticks_anchor,
uint32_t ticks_slot_overhead)
{
uint32_t volatile ret_cb;
uint32_t interval_us;
uint8_t aux_handle;
uint32_t ret;
ull_hdr_init(&aux->ull);
aux_handle = ull_adv_aux_handle_get(aux);
interval_us = aux->interval * PERIODIC_INT_UNIT_US;
ret_cb = TICKER_STATUS_BUSY;
ret = ticker_start(TICKER_INSTANCE_ID_CTLR, TICKER_USER_ID_THREAD,
(TICKER_ID_ADV_AUX_BASE + aux_handle),
ticks_anchor, 0U,
HAL_TICKER_US_TO_TICKS(interval_us),
HAL_TICKER_REMAINDER(interval_us), TICKER_NULL_LAZY,
(aux->ull.ticks_slot + ticks_slot_overhead),
ticker_cb, aux,
ull_ticker_status_give, (void *)&ret_cb);
ret = ull_ticker_status_take(ret, &ret_cb);
return ret;
}
int ull_adv_aux_stop(struct ll_adv_aux_set *aux)
{
uint8_t aux_handle;
int err;
aux_handle = ull_adv_aux_handle_get(aux);
err = ull_ticker_stop_with_mark(TICKER_ID_ADV_AUX_BASE + aux_handle,
aux, &aux->lll);
LL_ASSERT(err == 0 || err == -EALREADY);
if (err) {
return err;
}
aux->is_started = 0U;
return 0;
}
struct ll_adv_aux_set *ull_adv_aux_acquire(struct lll_adv *lll)
{
struct lll_adv_aux *lll_aux;
struct ll_adv_aux_set *aux;
uint8_t chm_last;
int err;
aux = aux_acquire();
if (!aux) {
return aux;
}
lll_aux = &aux->lll;
lll->aux = lll_aux;
lll_aux->adv = lll;
lll_adv_data_reset(&lll_aux->data);
err = lll_adv_data_init(&lll_aux->data);
if (err) {
return NULL;
}
/* Initialize data channel calculation counter, data channel identifier,
* and channel map to use.
*/
lll_csrand_get(&lll_aux->data_chan_counter,
sizeof(lll_aux->data_chan_counter));
lll_csrand_get(&aux->data_chan_id, sizeof(aux->data_chan_id));
chm_last = aux->chm_first;
aux->chm_last = chm_last;
aux->chm[chm_last].data_chan_count =
ull_chan_map_get(aux->chm[chm_last].data_chan_map);
/* NOTE: ull_hdr_init(&aux->ull); is done on start */
lll_hdr_init(lll_aux, aux);
aux->is_started = 0U;
return aux;
}
void ull_adv_aux_release(struct ll_adv_aux_set *aux)
{
lll_adv_data_release(&aux->lll.data);
aux_release(aux);
}
struct ll_adv_aux_set *ull_adv_aux_get(uint8_t handle)
{
if (handle >= CONFIG_BT_CTLR_ADV_AUX_SET) {
return NULL;
}
return &ll_adv_aux_pool[handle];
}
uint32_t ull_adv_aux_time_get(const struct ll_adv_aux_set *aux, uint8_t pdu_len,
uint8_t pdu_scan_len)
{
const struct lll_adv_aux *lll_aux;
const struct lll_adv *lll;
const struct pdu_adv *pdu;
uint32_t time_us;
lll_aux = &aux->lll;
lll = lll_aux->adv;
if (IS_ENABLED(CONFIG_BT_CTLR_ADV_RESERVE_MAX) &&
(lll->phy_s == PHY_CODED)) {
pdu_len = PDU_AC_EXT_PAYLOAD_OVERHEAD;
pdu_scan_len = PDU_AC_EXT_PAYLOAD_OVERHEAD;
}
/* NOTE: 16-bit values are sufficient for minimum radio event time
* reservation, 32-bit are used here so that reservations for
* whole back-to-back chaining of PDUs can be accomodated where
* the required microseconds could overflow 16-bits, example,
* back-to-back chained Coded PHY PDUs.
*/
time_us = PDU_AC_US(pdu_len, lll->phy_s, lll->phy_flags) +
EVENT_OVERHEAD_START_US + EVENT_OVERHEAD_END_US;
pdu = lll_adv_aux_data_peek(lll_aux);
if ((pdu->adv_ext_ind.adv_mode & BT_HCI_LE_ADV_PROP_CONN) ==
BT_HCI_LE_ADV_PROP_CONN) {
const uint16_t conn_req_us =
PDU_AC_MAX_US((INITA_SIZE + ADVA_SIZE + LLDATA_SIZE),
lll->phy_s);
const uint16_t conn_rsp_us =
PDU_AC_US((PDU_AC_EXT_HEADER_SIZE_MIN + ADVA_SIZE +
TARGETA_SIZE), lll->phy_s, lll->phy_flags);
time_us += EVENT_IFS_MAX_US * 2 + conn_req_us + conn_rsp_us;
} else if ((pdu->adv_ext_ind.adv_mode & BT_HCI_LE_ADV_PROP_SCAN) ==
BT_HCI_LE_ADV_PROP_SCAN) {
const uint16_t scan_req_us =
PDU_AC_MAX_US((SCANA_SIZE + ADVA_SIZE), lll->phy_s);
const uint16_t scan_rsp_us =
PDU_AC_US(pdu_scan_len, lll->phy_s, lll->phy_flags);
time_us += EVENT_IFS_MAX_US * 2 + scan_req_us + scan_rsp_us;
}
return time_us;
}
void ull_adv_aux_offset_get(struct ll_adv_set *adv)
{
static memq_link_t link;
static struct mayfly mfy = {0, 0, &link, NULL, mfy_aux_offset_get};
uint32_t ret;
/* NOTE: Single mayfly instance is sufficient as primary channel PDUs
* use time reservation, and this mayfly shall complete within
* the radio event. Multiple advertising sets do not need
* independent mayfly allocations.
*/
mfy.param = adv;
ret = mayfly_enqueue(TICKER_USER_ID_ULL_HIGH, TICKER_USER_ID_ULL_LOW, 1,
&mfy);
LL_ASSERT(!ret);
}
struct pdu_adv_aux_ptr *ull_adv_aux_lll_offset_fill(struct pdu_adv *pdu,
uint32_t ticks_offset,
uint32_t remainder_us,
uint32_t start_us)
{
struct pdu_adv_com_ext_adv *pri_com_hdr;
struct pdu_adv_aux_ptr *aux_ptr;
struct pdu_adv_ext_hdr *h;
uint32_t offs;
uint8_t *ptr;
pri_com_hdr = (void *)&pdu->adv_ext_ind;
h = (void *)pri_com_hdr->ext_hdr_adv_data;
ptr = h->data;
/* traverse through adv_addr, if present */
if (h->adv_addr) {
ptr += BDADDR_SIZE;
}
/* traverse through tgt_addr, if present */
if (h->tgt_addr) {
ptr += BDADDR_SIZE;
}
/* No CTEInfo flag in primary and secondary channel PDU */
/* traverse through adi, if present */
if (h->adi) {
ptr += sizeof(struct pdu_adv_adi);
}
aux_ptr = (void *)ptr;
offs = HAL_TICKER_TICKS_TO_US(ticks_offset) + remainder_us - start_us;
offs = offs / OFFS_UNIT_30_US;
if (!!(offs >> OFFS_UNIT_BITS)) {
offs = offs / (OFFS_UNIT_300_US / OFFS_UNIT_30_US);
aux_ptr->offs_units = OFFS_UNIT_VALUE_300_US;
} else {
aux_ptr->offs_units = OFFS_UNIT_VALUE_30_US;
}
aux_ptr->offs_phy_packed[0] = offs & 0xFF;
aux_ptr->offs_phy_packed[1] = ((offs>>8) & 0x1F) + (aux_ptr->offs_phy_packed[1] & 0xE0);
return aux_ptr;
}
void ull_adv_aux_done(struct node_rx_event_done *done)
{
struct lll_adv_aux *lll_aux;
struct ll_adv_aux_set *aux;
struct ll_adv_set *adv;
/* Get reference to ULL context */
aux = CONTAINER_OF(done->param, struct ll_adv_aux_set, ull);
lll_aux = &aux->lll;
adv = HDR_LLL2ULL(lll_aux->adv);
/* Call the primary channel advertising done */
done->param = &adv->ull;
ull_adv_done(done);
}
#if defined(CONFIG_BT_CTLR_ADV_PDU_LINK)
/* @brief Duplicate previous chain of PDUs into current chain of PDUs, fill the
* aux ptr field of the parent primary channel PDU with the aux offset,
* and the secondary channel PDU's PHY.
*
* @param[in] pdu_prev Pointer to previous PDU's chain PDU
* @param[in] pdu Pointer to current PDU's chain PDU
* @param[in] aux_ptr Pointer to aux ptr field in the primary channel PDU
* @param[in] phy_s Secondary/auxiliary PDU PHY
* @param[in] phy_flags Secondary/auxiliary PDU coded PHY encoding (S2/S8)
* @param[in] mafs_us Minimum Aux Frame Spacing to use, in microseconds
*/
void ull_adv_aux_chain_pdu_duplicate(struct pdu_adv *pdu_prev,
struct pdu_adv *pdu,
struct pdu_adv_aux_ptr *aux_ptr,
uint8_t phy_s, uint8_t phy_flags,
uint32_t mafs_us)
{
/* Duplicate any chain PDUs */
while (aux_ptr) {
struct pdu_adv_com_ext_adv *com_hdr_chain;
struct pdu_adv_com_ext_adv *com_hdr;
struct pdu_adv_ext_hdr *hdr_chain;
struct pdu_adv_adi *adi_parent;
struct pdu_adv *pdu_chain_prev;
struct pdu_adv_ext_hdr *hdr;
struct pdu_adv *pdu_chain;
uint8_t *dptr_chain;
uint32_t offs_us;
uint8_t *dptr;
/* Get the next chain PDU */
pdu_chain_prev = lll_adv_pdu_linked_next_get(pdu_prev);
if (!pdu_chain_prev) {
break;
}
/* Fill the aux offset in the (first iteration, it is the
* primary channel ADV_EXT_IND PDU, rest it is AUX_ADV_IND and
* AUX_CHAIN_IND) parent PDU
*/
offs_us = PDU_AC_US(pdu->len, phy_s, phy_flags) + mafs_us;
ull_adv_aux_ptr_fill(aux_ptr, offs_us, phy_s);
/* Get reference to flags in superior PDU */
com_hdr = &pdu->adv_ext_ind;
hdr = (void *)&com_hdr->ext_hdr_adv_data[0];
dptr = (void *)hdr;
/* Get the next new chain PDU */
pdu_chain = lll_adv_pdu_linked_next_get(pdu);
if (!pdu_chain) {
/* Get a new chain PDU */
pdu_chain = lll_adv_pdu_alloc_pdu_adv();
LL_ASSERT(pdu_chain);
/* Copy previous chain PDU into new chain PDU */
(void)memcpy(pdu_chain, pdu_chain_prev,
offsetof(struct pdu_adv, payload) +
pdu_chain_prev->len);
/* Link the chain PDU to parent PDU */
lll_adv_pdu_linked_append(pdu_chain, pdu);
}
/* Get reference to common header format */
com_hdr_chain = &pdu_chain_prev->adv_ext_ind;
hdr_chain = (void *)&com_hdr_chain->ext_hdr_adv_data[0];
dptr_chain = (void *)hdr_chain;
/* Check for no Flags */
if (!com_hdr_chain->ext_hdr_len) {
break;
}
/* Proceed to next byte if any flags present */
if (*dptr) {
dptr++;
}
if (*dptr_chain) {
dptr_chain++;
}
/* AdvA flag */
if (hdr->adv_addr) {
dptr += BDADDR_SIZE;
}
if (hdr_chain->adv_addr) {
dptr_chain += BDADDR_SIZE;
}
/* TgtA flag */
if (hdr->tgt_addr) {
dptr += BDADDR_SIZE;
}
if (hdr_chain->tgt_addr) {
dptr_chain += BDADDR_SIZE;
}
/* CTE Info */
if (hdr->cte_info) {
dptr += sizeof(struct pdu_cte_info);
}
if (hdr_chain->cte_info) {
dptr_chain += sizeof(struct pdu_cte_info);
}
/* ADI */
if (hdr->adi) {
adi_parent = (void *)dptr;
dptr += sizeof(struct pdu_adv_adi);
} else {
adi_parent = NULL;
}
if (hdr_chain->adi) {
struct pdu_adv_adi *adi;
/* update DID to superior PDU DID */
adi = (void *)dptr_chain;
if (adi_parent) {
adi->did = adi_parent->did;
}
dptr_chain += sizeof(struct pdu_adv_adi);
}
/* No aux ptr, no further chain PDUs */
if (!hdr_chain->aux_ptr) {
break;
}
/* Remember the aux ptr to be populated */
aux_ptr = (void *)dptr_chain;
/* Progress to next chain PDU */
pdu_prev = pdu_chain_prev;
pdu = pdu_chain;
}
}
#endif /* CONFIG_BT_CTLR_ADV_PDU_LINK */
static int init_reset(void)
{
/* Initialize adv aux pool. */
mem_init(ll_adv_aux_pool, sizeof(struct ll_adv_aux_set),
sizeof(ll_adv_aux_pool) / sizeof(struct ll_adv_aux_set),
&adv_aux_free);
return 0;
}
static inline struct ll_adv_aux_set *aux_acquire(void)
{
return mem_acquire(&adv_aux_free);
}
static inline void aux_release(struct ll_adv_aux_set *aux)
{
mem_release(aux, &adv_aux_free);
}
static uint32_t aux_time_get(struct ll_adv_aux_set *aux, struct pdu_adv *pdu,
struct pdu_adv *pdu_scan)
{
struct lll_adv_aux *lll_aux;
struct lll_adv *lll;
uint32_t time_us;
/* NOTE: 16-bit values are sufficient for minimum radio event time
* reservation, 32-bit are used here so that reservations for
* whole back-to-back chaining of PDUs can be accomodated where
* the required microseconds could overflow 16-bits, example,
* back-to-back chained Coded PHY PDUs.
*/
lll_aux = &aux->lll;
lll = lll_aux->adv;
time_us = PDU_AC_US(pdu->len, lll->phy_s, lll->phy_flags) +
EVENT_OVERHEAD_START_US + EVENT_OVERHEAD_END_US;
if ((pdu->adv_ext_ind.adv_mode & BT_HCI_LE_ADV_PROP_CONN) ==
BT_HCI_LE_ADV_PROP_CONN) {
const uint16_t conn_req_us =
PDU_AC_MAX_US((INITA_SIZE + ADVA_SIZE + LLDATA_SIZE),
lll->phy_s);
const uint16_t conn_rsp_us =
PDU_AC_US((PDU_AC_EXT_HEADER_SIZE_MIN + ADVA_SIZE +
TARGETA_SIZE), lll->phy_s, lll->phy_flags);
time_us += EVENT_IFS_MAX_US * 2 + conn_req_us + conn_rsp_us;
} else if ((pdu->adv_ext_ind.adv_mode & BT_HCI_LE_ADV_PROP_SCAN) ==
BT_HCI_LE_ADV_PROP_SCAN) {
const uint16_t scan_req_us =
PDU_AC_MAX_US((SCANA_SIZE + ADVA_SIZE), lll->phy_s);
const uint16_t scan_rsp_us =
PDU_AC_US(pdu_scan->len, lll->phy_s, lll->phy_flags);
time_us += EVENT_IFS_MAX_US * 2 + scan_req_us + scan_rsp_us;
}
return time_us;
}
static uint8_t aux_time_update(struct ll_adv_aux_set *aux, struct pdu_adv *pdu,
struct pdu_adv *pdu_scan)
{
uint32_t volatile ret_cb;
uint32_t ticks_minus;
uint32_t ticks_plus;
uint32_t time_ticks;
uint32_t time_us;
uint32_t ret;
time_us = aux_time_get(aux, pdu, pdu_scan);
time_ticks = HAL_TICKER_US_TO_TICKS(time_us);
if (aux->ull.ticks_slot > time_ticks) {
ticks_minus = aux->ull.ticks_slot - time_ticks;
ticks_plus = 0U;
} else if (aux->ull.ticks_slot < time_ticks) {
ticks_minus = 0U;
ticks_plus = time_ticks - aux->ull.ticks_slot;
} else {
return BT_HCI_ERR_SUCCESS;
}
ret_cb = TICKER_STATUS_BUSY;
ret = ticker_update(TICKER_INSTANCE_ID_CTLR,
TICKER_USER_ID_THREAD,
(TICKER_ID_ADV_AUX_BASE +
ull_adv_aux_handle_get(aux)),
0, 0, ticks_plus, ticks_minus, 0, 0,
ull_ticker_status_give, (void *)&ret_cb);
ret = ull_ticker_status_take(ret, &ret_cb);
if (ret != TICKER_STATUS_SUCCESS) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
aux->ull.ticks_slot = time_ticks;
return BT_HCI_ERR_SUCCESS;
}
static void mfy_aux_offset_get(void *param)
{
struct pdu_adv_aux_ptr *aux_ptr;
struct lll_adv_aux *lll_aux;
struct ll_adv_aux_set *aux;
uint32_t ticks_to_expire;
uint8_t data_chan_count;
uint8_t *data_chan_map;
uint32_t ticks_current;
struct ll_adv_set *adv;
struct pdu_adv *pdu;
uint32_t remainder;
uint8_t ticker_id;
uint8_t retry;
uint8_t id;
adv = param;
lll_aux = adv->lll.aux;
aux = HDR_LLL2ULL(lll_aux);
ticker_id = TICKER_ID_ADV_AUX_BASE + ull_adv_aux_handle_get(aux);
id = TICKER_NULL;
ticks_to_expire = 0U;
ticks_current = 0U;
retry = 4U;
do {
uint32_t volatile ret_cb;
uint32_t ticks_previous;
uint32_t ret;
bool success;
ticks_previous = ticks_current;
ret_cb = TICKER_STATUS_BUSY;
ret = ticker_next_slot_get_ext(TICKER_INSTANCE_ID_CTLR,
TICKER_USER_ID_ULL_LOW,
&id, &ticks_current,
&ticks_to_expire, &remainder,
NULL, NULL, NULL,
ticker_op_cb, (void *)&ret_cb);
if (ret == TICKER_STATUS_BUSY) {
while (ret_cb == TICKER_STATUS_BUSY) {
ticker_job_sched(TICKER_INSTANCE_ID_CTLR,
TICKER_USER_ID_ULL_LOW);
}
}
success = (ret_cb == TICKER_STATUS_SUCCESS);
LL_ASSERT(success);
LL_ASSERT((ticks_current == ticks_previous) || retry--);
LL_ASSERT(id != TICKER_NULL);
} while (id != ticker_id);
/* Adjust ticks to expire based on remainder value */
hal_ticker_remove_jitter(&ticks_to_expire, &remainder);
/* Store the ticks offset for population in other advertising primary
* channel PDUs.
*/
lll_aux->ticks_pri_pdu_offset = ticks_to_expire;
/* NOTE: as first primary channel PDU does not use remainder, the packet
* timer is started one tick in advance to start the radio with
* microsecond precision, hence compensate for the higher start_us value
* captured at radio start of the first primary channel PDU.
*/
lll_aux->ticks_pri_pdu_offset += 1U;
/* Store the microsecond remainder offset for population in other
* advertising primary channel PDUs.
*/
lll_aux->us_pri_pdu_offset = remainder;
/* Fill the aux offset in the first Primary channel PDU */
/* FIXME: we are in ULL_LOW context, fill offset in LLL context? */
pdu = lll_adv_data_latest_peek(&adv->lll);
aux_ptr = ull_adv_aux_lll_offset_fill(pdu, ticks_to_expire, remainder,
0U);
/* Process channel map update, if any */
if (aux->chm_first != aux->chm_last) {
/* Use channelMapNew */
aux->chm_first = aux->chm_last;
}
/* Calculate the radio channel to use */
data_chan_map = aux->chm[aux->chm_first].data_chan_map;
data_chan_count = aux->chm[aux->chm_first].data_chan_count;
aux_ptr->chan_idx = lll_chan_sel_2(lll_aux->data_chan_counter,
aux->data_chan_id,
data_chan_map, data_chan_count);
}
static void ticker_cb(uint32_t ticks_at_expire, uint32_t ticks_drift,
uint32_t remainder, uint16_t lazy, uint8_t force,
void *param)
{
static memq_link_t link;
static struct mayfly mfy = {0, 0, &link, NULL, lll_adv_aux_prepare};
static struct lll_prepare_param p;
struct ll_adv_aux_set *aux = param;
struct lll_adv_aux *lll;
uint32_t ret;
uint8_t ref;
DEBUG_RADIO_PREPARE_A(1);
lll = &aux->lll;
/* Increment prepare reference count */
ref = ull_ref_inc(&aux->ull);
LL_ASSERT(ref);
/* Append timing parameters */
p.ticks_at_expire = ticks_at_expire;
p.remainder = remainder;
p.lazy = lazy;
p.force = force;
p.param = lll;
mfy.param = &p;
/* Kick LLL prepare */
ret = mayfly_enqueue(TICKER_USER_ID_ULL_HIGH,
TICKER_USER_ID_LLL, 0, &mfy);
LL_ASSERT(!ret);
#if defined(CONFIG_BT_CTLR_ADV_PERIODIC)
struct ll_adv_set *adv;
adv = HDR_LLL2ULL(lll->adv);
if (adv->lll.sync) {
struct ll_adv_sync_set *sync;
sync = HDR_LLL2ULL(adv->lll.sync);
if (sync->is_started) {
sync->aux_remainder = remainder;
ull_adv_sync_offset_get(adv);
}
}
#endif /* CONFIG_BT_CTLR_ADV_PERIODIC */
DEBUG_RADIO_PREPARE_A(1);
}
static void ticker_op_cb(uint32_t status, void *param)
{
*((uint32_t volatile *)param) = status;
}
#else /* !(CONFIG_BT_CTLR_ADV_AUX_SET > 0) */
static int init_reset(void)
{
return 0;
}
#endif /* !(CONFIG_BT_CTLR_ADV_AUX_SET > 0) */