/*
 * 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 "os/util.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_VERSION) >= 54
    uint8_t pri_phy_opt;
    uint8_t sec_phy_opt;
#endif
#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;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_ADI_SUPPORT)
    uint8_t periodic_include_adi : 1;
    uint16_t periodic_adv_adi;
#endif
    uint8_t periodic_num_used_chans;
    uint8_t periodic_chanmap[BLE_LL_CHAN_MAP_LEN];
    uint16_t periodic_adv_itvl;

    uint32_t padv_itvl_us;

    uint16_t padv_anchor_offset;
    uint32_t padv_anchor;
    uint8_t padv_anchor_rem_us;

    uint32_t padv_event_start;
    uint8_t padv_event_start_rem_us;

    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;
    bool has_adva;

    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;

    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;
    }

    has_adva = !MYNEWT_VAL(BLE_LL_EXT_ADV_ADVA_IN_AUX) &&
               !(advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_ANON_ADV);

    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);

    if (has_adva) {
        ext_hdr_len += BLE_LL_EXT_ADV_ADVA_SIZE;
        ext_hdr_flags |= (1 << BLE_LL_EXT_ADV_ADVA_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;

    if (has_adva) {
        /* AdvA */
        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;
    }

    /* 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);

    *hdr_byte = pdu_type;

    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_cntr,
                        uint8_t *dptr)
{
    uint32_t offset_us;
    uint32_t offset;
    uint32_t anchor_ticks;
    uint8_t anchor_rem_us;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_SYNC_TRANSFER)
    uint16_t anchor_event_cntr;
#endif
    uint32_t event_start;
    uint8_t event_start_us;
    uint32_t event_cntr_offset = 0;

    if (connsm) {
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_SYNC_TRANSFER)
        ble_ll_conn_anchor_get(connsm, &anchor_event_cntr,
                               &anchor_ticks, &anchor_rem_us);

        /* connEventCount */
        put_le16(conn_event_cntr, anchor_event_cntr);
#else
        BLE_LL_ASSERT(0);
#endif
    } else {
        anchor_ticks = AUX_CURRENT(advsm)->start_time;
        anchor_rem_us = 0;
    }

    event_start = advsm->padv_event_start;
    event_start_us = advsm->padv_event_start_rem_us;

    /* Find padv event that is past anchor item */
    while (LL_TMR_LEQ(event_start, anchor_ticks)) {
        ble_ll_tmr_add(&event_start, &event_start_us, advsm->padv_itvl_us);
        event_cntr_offset++;
    }

    offset_us = ble_ll_tmr_t2u(event_start - anchor_ticks);
    offset_us += event_start_us;
    offset_us -= anchor_rem_us;

    /* Sync Packet Offset (13 bits), Offset Units (1 bit), Offset Adjust (1 bit),
     * RFU (1 bit)
     */
    if (offset_us > 245700) {
        offset = offset_us / 300;
        if (offset >= 0x2000) {
            if (connsm) {
                offset -= 0x2000;
                /* Offset Units = 1, Offset Adjust = 1 */
                offset |= 0x6000;
            } else {
                offset = 0;
                event_cntr_offset = 0;
            }
        } else {
            /* Offset Units = 1, Offset Adjust = 0 */
            offset |= 0x2000;
        }

    } else {
        offset = offset_us / 30;
        /* Offset Units = 0, Offset Adjust = 0 */
    }

    put_le16(&dptr[0], offset);

    /* 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_cntr_offset);
}
#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;
#if MYNEWT_VAL(BLE_LL_PHY)
    uint8_t phy_mode;
#endif

    /* 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 MYNEWT_VAL(BLE_VERSION) >= 54
    phy_mode = ble_ll_phy_to_phy_mode(advsm->pri_phy, advsm->pri_phy_opt);
#else
    phy_mode = ble_ll_phy_to_phy_mode(advsm->pri_phy, BLE_HCI_LE_PHY_CODED_ANY);
#endif
    if (advsm->props & BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY) {
        phy_mode = BLE_PHY_MODE_1M;
    }
#else
    phy_mode = BLE_PHY_MODE_1M;
#endif
    ble_phy_mode_set(phy_mode, phy_mode);
#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;
#if MYNEWT_VAL(BLE_LL_PHY)
    uint8_t phy_mode;
#endif

    /* 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)
#if MYNEWT_VAL(BLE_VERSION) >= 54
    phy_mode = ble_ll_phy_to_phy_mode(advsm->sec_phy, advsm->sec_phy_opt);
#else
    phy_mode = ble_ll_phy_to_phy_mode(advsm->sec_phy, BLE_HCI_LE_PHY_CODED_ANY);
#endif
    /* Set phy mode */
    ble_phy_mode_set(phy_mode, phy_mode);
#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 if:
     * - 1st PDU
     * - not anonymous
     * - scannable/connectable or ADVA_IN_AUX selected
     */
    if (first_pdu &&
        !(props & BLE_HCI_LE_SET_EXT_ADV_PROP_ANON_ADV) &&
        ((props & (BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE |
                   BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE)) ||
         MYNEWT_VAL(BLE_LL_EXT_ADV_ADVA_IN_AUX))) {
        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 uint16_t
ble_ll_adv_update_did(uint16_t old_adi)
{
    uint16_t new_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 {
        new_adi = (old_adi & 0xf000) | (ble_ll_rand() & 0x0fff);
    } while (old_adi == new_adi);

    return new_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 */
    advsm->adi = ble_ll_adv_update_did(advsm->adi);
#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 MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_ADI_SUPPORT)
    if (sync->ext_hdr_flags & (1 << BLE_LL_EXT_ADV_DATA_INFO_BIT)) {
        put_le16(dptr, advsm->periodic_adv_adi);
        dptr += BLE_LL_EXT_ADV_DATA_INFO_SIZE;
    }
#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);

        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;

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_ADI_SUPPORT)
    if (advsm->periodic_include_adi) {
        sync->ext_hdr_flags |= (1 << BLE_LL_EXT_ADV_DATA_INFO_BIT);
        ext_hdr_len += BLE_LL_EXT_ADV_DATA_INFO_SIZE;
    }
#endif

    /* 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;

    advsm->padv_event_start = advsm->padv_anchor;
    advsm->padv_event_start_rem_us = advsm->padv_anchor_rem_us;
    ble_ll_tmr_add(&advsm->padv_event_start, &advsm->padv_event_start_rem_us,
                   advsm->padv_itvl_us * advsm->padv_anchor_offset);

    if (advsm->padv_anchor_offset == UINT16_MAX) {
        advsm->padv_anchor_offset = 0;
        advsm->padv_anchor = advsm->padv_event_start;
        advsm->padv_anchor_rem_us = advsm->padv_event_start_rem_us;
    }

    sch->start_time = advsm->padv_event_start - g_ble_ll_sched_offset_ticks;
    sch->remainder = advsm->padv_event_start_rem_us;
    sch->end_time = advsm->padv_event_start + ble_ll_tmr_u2t_up(max_usecs);

    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->padv_event_start));

    if (first_pdu) {
        /* Need to update anchor on 1st PDU since it's start time can be changed
         * while scheduling */
        advsm->padv_anchor = sync->start_time;
        advsm->padv_event_start = 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->padv_event_start +
                   ble_ll_tmr_u2t(advsm->padv_itvl_us)))) {
        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_event_cntr++;
    advsm->padv_anchor_offset++;

    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;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_ADI_SUPPORT)
        advsm->periodic_adv_adi = ble_ll_adv_update_did(advsm->periodic_adv_adi);
#endif
    }

    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)
{
    /*
     * 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.
     */
    advsm->adi = ble_ll_adv_update_did(advsm->adi);

#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_ADI_SUPPORT)
    /*
     * Since periodic advertising can be started without data we initialize DID here
     * instead of after setting periodic advertising data.
     */
    advsm->periodic_adv_adi = ble_ll_adv_update_did(advsm->periodic_adv_adi);
#endif

    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;

    advsm->padv_anchor_offset = 1;
    advsm->padv_anchor = ble_ll_tmr_get();
    advsm->padv_anchor_rem_us = 0;

    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.
     */
    advsm->adi = ble_ll_adv_update_did(advsm->adi);

    /* 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 */
        advsm->adi = ble_ll_adv_update_did(advsm->adi);
#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 */
        advsm->adi = ble_ll_adv_update_did(advsm->adi);
        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 */
        advsm->adi = ble_ll_adv_update_did(advsm->adi);
#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 = get_le24(cmd->pri_itvl_min);
    adv_itvl_max = get_le24(cmd->pri_itvl_max);

    if (props & ~BLE_HCI_LE_SET_EXT_ADV_PROP_MASK) {
        rc = BLE_ERR_INV_HCI_CMD_PARMS;
        goto done;
    }

    /* Anonymous advertising only makes sense for non-connectable, non-scannable
     * and non-legacy. This is not explicitly stated anywhere in HCI/LL part of
     * the spec, but Vol 3, Part C, 9.1.1.2 (GAP) states:
     *   "A device in the Broadcast mode may send non-connectable and non-
     *    scannable undirected or non-connectable and non-scannable directed
     *    advertising events anonymously by excluding the Broadcaster's address."
     * And Vol 4, Part E, 7.8.53 (HCI) states:
     *   "If the Advertising_Event_Properties parameter does not describe an
     *    event type supported by the Controller, contains an invalid bit
     *    combination, or specifies a type that does not support advertising
     *    data when the advertising set already contains some, the Controller
     *    shall return the error code Invalid HCI Command Parameters (0x12)."
     *
     * So let's just assume anonymous on connectable/scannable/legacy is an
     * invalid bit combination.
     */
    if (props & BLE_HCI_LE_SET_EXT_ADV_PROP_ANON_ADV) {
        if (props & (BLE_HCI_LE_SET_EXT_ADV_PROP_CONNECTABLE |
                     BLE_HCI_LE_SET_EXT_ADV_PROP_SCANNABLE |
                     BLE_HCI_LE_SET_EXT_ADV_PROP_HD_DIRECTED |
                     BLE_HCI_LE_SET_EXT_ADV_PROP_LEGACY)) {
            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));
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_ADI_SUPPORT)
    advsm->periodic_adv_adi = (advsm->periodic_adv_adi & 0x0fff) | ((cmd->sid << 12));
#endif

    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;
}

#if MYNEWT_VAL(BLE_VERSION) >= 54
static inline uint8_t
ble_ll_adv_convert_phy_opt(uint8_t cmd_phy_opt)
{
    switch (cmd_phy_opt) {
    case BLE_HCI_ADVERTISING_PHY_OPT_NO_PREF:
        return BLE_HCI_LE_PHY_CODED_ANY;
    case BLE_HCI_ADVERTISING_PHY_OPT_S2_PREF:
    case BLE_HCI_ADVERTISING_PHY_OPT_S2_REQ:
        return BLE_HCI_LE_PHY_CODED_S2_PREF;
    case BLE_HCI_ADVERTISING_PHY_OPT_S8_PREF:
    case BLE_HCI_ADVERTISING_PHY_OPT_S8_REQ:
        return BLE_HCI_LE_PHY_CODED_S8_PREF;
    default:
        BLE_LL_ASSERT(0);
    }
}

int
ble_ll_adv_ext_set_param_v2(const uint8_t *cmdbuf, uint8_t len,
                            uint8_t *rspbuf, uint8_t *rsplen)
{
    const struct ble_hci_le_set_ext_adv_params_v2_cp *cmd = (const void *) cmdbuf;
    struct ble_ll_adv_sm *advsm;
    int rc;

    if (len != sizeof(*cmd)) {
        return BLE_ERR_INV_HCI_CMD_PARMS;
    }

    if ((cmd->pri_phy_opt > BLE_HCI_ADVERTISING_PHY_OPT_S8_REQ) ||
        (cmd->sec_phy_opt > BLE_HCI_ADVERTISING_PHY_OPT_S8_REQ)) {
        return BLE_ERR_INV_HCI_CMD_PARMS;
    }

    rc = ble_ll_adv_ext_set_param(cmdbuf, len - 2, rspbuf, rsplen);
    if (rc != 0) {
        return rc;
    }

    advsm = ble_ll_adv_sm_get(cmd->params_v1.adv_handle);

    if (advsm->pri_phy == BLE_HCI_LE_PHY_CODED) {
        advsm->pri_phy_opt = ble_ll_adv_convert_phy_opt(cmd->pri_phy_opt);
    }
    if (advsm->sec_phy == BLE_HCI_LE_PHY_CODED) {
        advsm->sec_phy_opt = ble_ll_adv_convert_phy_opt(cmd->sec_phy_opt);
    }

    return rc;
}
#endif

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->padv_itvl_us = adv_itvl_max * BLE_LL_ADV_PERIODIC_ITVL;
    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;
#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_ADI_SUPPORT)
    case BLE_HCI_LE_SET_DATA_OPER_UNCHANGED:
        if (!advsm->periodic_adv_enabled) {
            return BLE_ERR_INV_HCI_CMD_PARMS;
        }

        if (advsm->periodic_adv_data->om_len == 0) {
            return BLE_ERR_INV_HCI_CMD_PARMS;
        }

        if (cmd->adv_data_len != 0) {
            return BLE_ERR_INV_HCI_CMD_PARMS;
        }

        advsm->periodic_adv_adi = ble_ll_adv_update_did(advsm->periodic_adv_adi);
        return BLE_ERR_SUCCESS;
#endif
    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_LL_CFG_FEAT_LL_PERIODIC_ADV_ADI_SUPPORT)
    if (cmd->enable & 0x02) {
        return BLE_ERR_UNSUPPORTED;
    }
#endif

#if MYNEWT_VAL(BLE_VERSION) >= 53
    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 & 0x1) {
        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 periodic advertising 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 MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PERIODIC_ADV_ADI_SUPPORT)
        advsm->periodic_include_adi = !!(cmd->enable & 0x2);
#endif

        /* 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_padv_event_start_get(struct ble_ll_adv_sm *advsm,
                                uint32_t *event_start,
                                uint8_t *event_start_rem_us)
{
    if (!advsm || !advsm->periodic_adv_active) {
        return -EINVAL;
    }

    *event_start = advsm->padv_event_start;
    if (event_start_rem_us) {
        *event_start_rem_us = advsm->padv_event_start_rem_us;
    }

    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
