blob: 552b470011e8e6d8d3de24b261c97df2abbf188c [file] [log] [blame]
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr.h>
#include <sys/byteorder.h>
#include <bluetooth/bluetooth.h>
#include "hal/cpu.h"
#include "hal/ccm.h"
#include "hal/ticker.h"
#include "util/util.h"
#include "util/mem.h"
#include "util/memq.h"
#include "util/mfifo.h"
#include "util/mayfly.h"
#include "ticker/ticker.h"
#include "pdu.h"
#include "lll.h"
#include "lll/lll_vendor.h"
#include "lll/lll_adv_types.h"
#include "lll_adv.h"
#include "lll/lll_adv_pdu.h"
#include "lll_conn.h"
#include "ull_internal.h"
#include "ull_adv_types.h"
#include "ull_adv_internal.h"
#include "ll.h"
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER)
#define LOG_MODULE_NAME bt_ctlr_ull_adv_iso
#include "common/log.h"
#include "hal/debug.h"
static struct ll_adv_iso ll_adv_iso[BT_CTLR_ADV_SET];
static void *adv_iso_free;
static uint32_t ull_adv_iso_start(struct ll_adv_iso *adv_iso,
uint32_t ticks_anchor);
static inline struct ll_adv_iso *ull_adv_iso_get(uint8_t handle);
static int init_reset(void);
static void ticker_cb(uint32_t ticks_at_expire, uint32_t ticks_drift,
uint32_t remainder, uint16_t lazy, uint8_t force,
void *param);
static void tx_lll_flush(void *param);
static void ticker_op_stop_cb(uint32_t status, void *param);
uint8_t ll_big_create(uint8_t big_handle, uint8_t adv_handle, uint8_t num_bis,
uint32_t sdu_interval, uint16_t max_sdu,
uint16_t max_latency, uint8_t rtn, uint8_t phy,
uint8_t packing, uint8_t framing, uint8_t encryption,
uint8_t *bcode)
{
uint8_t hdr_data[1 + sizeof(uint8_t *)];
struct lll_adv_sync *lll_adv_sync;
struct lll_adv_iso *lll_adv_iso;
struct pdu_adv *pdu_prev, *pdu;
struct node_rx_pdu *node_rx;
struct ll_adv_iso *adv_iso;
struct ll_adv_set *adv;
uint8_t ter_idx;
uint8_t *acad;
uint8_t err;
adv_iso = ull_adv_iso_get(big_handle);
/* Already created */
if (!adv_iso || adv_iso->lll.adv) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
/* No advertising set created */
adv = ull_adv_is_created_get(adv_handle);
if (!adv) {
return BT_HCI_ERR_UNKNOWN_ADV_IDENTIFIER;
}
/* Does not identify a periodic advertising train or
* the periodic advertising trains is already associated
* with another BIG.
*/
lll_adv_sync = adv->lll.sync;
if (!lll_adv_sync || lll_adv_sync->iso) {
return BT_HCI_ERR_UNKNOWN_ADV_IDENTIFIER;
}
if (IS_ENABLED(CONFIG_BT_CTLR_PARAM_CHECK)) {
if (num_bis == 0 || num_bis > 0x1F) {
return BT_HCI_ERR_INVALID_PARAM;
}
if (sdu_interval < 0x000100 || sdu_interval > 0x0FFFFF) {
return BT_HCI_ERR_INVALID_PARAM;
}
if (max_sdu < 0x0001 || max_sdu > 0x0FFF) {
return BT_HCI_ERR_INVALID_PARAM;
}
if (max_latency > 0x0FA0) {
return BT_HCI_ERR_INVALID_PARAM;
}
if (rtn > 0x0F) {
return BT_HCI_ERR_INVALID_PARAM;
}
if (phy > (BT_HCI_LE_EXT_SCAN_PHY_1M |
BT_HCI_LE_EXT_SCAN_PHY_2M |
BT_HCI_LE_EXT_SCAN_PHY_CODED)) {
return BT_HCI_ERR_INVALID_PARAM;
}
if (packing > 1) {
return BT_HCI_ERR_INVALID_PARAM;
}
if (framing > 1) {
return BT_HCI_ERR_INVALID_PARAM;
}
if (encryption > 1) {
return BT_HCI_ERR_INVALID_PARAM;
}
}
/* TODO: Allow more than 1 BIS in a BIG */
if (num_bis != 1) {
return BT_HCI_ERR_MEM_CAPACITY_EXCEEDED;
}
/* Allocate next PDU */
err = ull_adv_sync_pdu_alloc(adv, ULL_ADV_PDU_EXTRA_DATA_ALLOC_IF_EXIST, &pdu_prev, &pdu,
NULL, NULL, &ter_idx);
if (err) {
return err;
}
/* Add ACAD to AUX_SYNC_IND */
hdr_data[0] = sizeof(struct pdu_big_info) + 2;
err = ull_adv_sync_pdu_set_clear(lll_adv_sync, pdu_prev, pdu, ULL_ADV_PDU_HDR_FIELD_ACAD,
0U, hdr_data);
if (err) {
return err;
}
memcpy(&acad, &hdr_data[1], sizeof(acad));
acad[0] = sizeof(struct pdu_big_info) + 1;
acad[1] = BT_DATA_BIG_INFO;
lll_adv_sync_data_enqueue(lll_adv_sync, ter_idx);
/* TODO: For now we can just use the unique BIG handle as the BIS
* handle until we support multiple BIS
*/
adv_iso->bis_handle = big_handle;
adv_iso->num_bis = num_bis;
adv_iso->sdu_interval = sdu_interval;
adv_iso->max_sdu = max_sdu;
adv_iso->max_latency = max_latency;
adv_iso->rtn = rtn;
adv_iso->phy = phy;
adv_iso->packing = packing;
adv_iso->framing = framing;
adv_iso->encryption = encryption;
memcpy(adv_iso->bcode, bcode, sizeof(adv_iso->bcode));
/* TODO: start sending BIS empty data packet for each BIS */
ull_adv_iso_start(adv_iso, 0 /* TODO: Calc ticks_anchor */);
/* Associate the ISO instance with a Periodic Advertising and
* an Extended Advertising instance
*/
lll_adv_iso = &adv_iso->lll;
lll_adv_sync->iso = lll_adv_iso;
lll_adv_iso->adv = &adv->lll;
/* Prepare BIG complete event */
/* TODO: Implement custom node_rx struct for optimization */
node_rx = (void *)&adv_iso->node_rx_complete;
node_rx->hdr.type = NODE_RX_TYPE_BIG_COMPLETE;
node_rx->hdr.handle = big_handle;
node_rx->hdr.rx_ftr.param = adv_iso;
return BT_HCI_ERR_SUCCESS;
}
uint8_t ll_big_test_create(uint8_t big_handle, uint8_t adv_handle,
uint8_t num_bis, uint32_t sdu_interval,
uint16_t iso_interval, uint8_t nse, uint16_t max_sdu,
uint16_t max_pdu, uint8_t phy, uint8_t packing,
uint8_t framing, uint8_t bn, uint8_t irc,
uint8_t pto, uint8_t encryption, uint8_t *bcode)
{
/* TODO: Implement */
ARG_UNUSED(big_handle);
ARG_UNUSED(adv_handle);
ARG_UNUSED(num_bis);
ARG_UNUSED(sdu_interval);
ARG_UNUSED(iso_interval);
ARG_UNUSED(nse);
ARG_UNUSED(max_sdu);
ARG_UNUSED(max_pdu);
ARG_UNUSED(phy);
ARG_UNUSED(packing);
ARG_UNUSED(framing);
ARG_UNUSED(bn);
ARG_UNUSED(irc);
ARG_UNUSED(pto);
ARG_UNUSED(encryption);
ARG_UNUSED(bcode);
return BT_HCI_ERR_CMD_DISALLOWED;
}
uint8_t ll_big_terminate(uint8_t big_handle, uint8_t reason)
{
struct lll_adv_sync *lll_adv_sync;
struct lll_adv_iso *lll_adv_iso;
struct pdu_adv *pdu_prev, *pdu;
struct node_rx_pdu *node_rx;
struct ll_adv_iso *adv_iso;
struct lll_adv *lll_adv;
struct ll_adv_set *adv;
uint8_t ter_idx;
uint32_t ret;
uint8_t err;
adv_iso = ull_adv_iso_get(big_handle);
if (!adv_iso) {
return BT_HCI_ERR_UNKNOWN_ADV_IDENTIFIER;
}
lll_adv_iso = &adv_iso->lll;
lll_adv = lll_adv_iso->adv;
if (!lll_adv) {
return BT_HCI_ERR_UNKNOWN_ADV_IDENTIFIER;
}
lll_adv_sync = lll_adv->sync;
adv = HDR_LLL2ULL(lll_adv);
/* Allocate next PDU */
err = ull_adv_sync_pdu_alloc(adv, ULL_ADV_PDU_EXTRA_DATA_ALLOC_IF_EXIST, &pdu_prev, &pdu,
NULL, NULL, &ter_idx);
if (err) {
return err;
}
/* Remove ACAD to AUX_SYNC_IND */
err = ull_adv_sync_pdu_set_clear(lll_adv_sync, pdu_prev, pdu,
0U, ULL_ADV_PDU_HDR_FIELD_ACAD, NULL);
if (err) {
return err;
}
lll_adv_sync_data_enqueue(lll_adv_sync, ter_idx);
/* TODO: Terminate all BIS data paths */
ret = ticker_stop(TICKER_INSTANCE_ID_CTLR, TICKER_USER_ID_ULL_HIGH,
TICKER_ID_ADV_ISO_BASE + adv_iso->bis_handle,
ticker_op_stop_cb, adv_iso);
lll_adv_iso->adv = NULL;
lll_adv_sync->iso = NULL;
/* Prepare BIG terminate event */
node_rx = (void *)&adv_iso->node_rx_terminate;
node_rx->hdr.type = NODE_RX_TYPE_BIG_TERMINATE;
node_rx->hdr.handle = big_handle;
node_rx->hdr.rx_ftr.param = adv_iso;
*((uint8_t *)node_rx->pdu) = reason;
return BT_HCI_ERR_SUCCESS;
}
int ull_adv_iso_init(void)
{
int err;
err = init_reset();
if (err) {
return err;
}
return 0;
}
int ull_adv_iso_reset(void)
{
int err;
err = init_reset();
if (err) {
return err;
}
return 0;
}
#if defined(CONFIG_BT_CTLR_HCI_ADV_HANDLE_MAPPING)
uint8_t ll_adv_iso_by_hci_handle_get(uint8_t hci_handle, uint8_t *handle)
{
struct ll_adv_iso *adv_iso;
uint8_t idx;
adv_iso = &ll_adv_iso[0];
for (idx = 0U; idx < BT_CTLR_ADV_SET; idx++, adv_iso++) {
if (adv_iso->lll.adv &&
(adv_iso->hci_handle == hci_handle)) {
*handle = idx;
return 0;
}
}
return BT_HCI_ERR_UNKNOWN_ADV_IDENTIFIER;
}
uint8_t ll_adv_iso_by_hci_handle_new(uint8_t hci_handle, uint8_t *handle)
{
struct ll_adv_iso *adv_iso, *adv_iso_empty;
uint8_t idx;
adv_iso = &ll_adv_iso[0];
adv_iso_empty = NULL;
for (idx = 0U; idx < BT_CTLR_ADV_SET; idx++, adv_iso++) {
if (adv_iso->lll.adv) {
if (adv_iso->hci_handle == hci_handle) {
return BT_HCI_ERR_CMD_DISALLOWED;
}
} else if (!adv_iso_empty) {
adv_iso_empty = adv_iso;
*handle = idx;
}
}
if (adv_iso_empty) {
memset(adv_iso_empty, 0, sizeof(*adv_iso_empty));
adv_iso_empty->hci_handle = hci_handle;
return 0;
}
return BT_HCI_ERR_MEM_CAPACITY_EXCEEDED;
}
#endif /* CONFIG_BT_CTLR_HCI_ADV_HANDLE_MAPPING */
static uint32_t ull_adv_iso_start(struct ll_adv_iso *adv_iso,
uint32_t ticks_anchor)
{
uint32_t ticks_slot_overhead;
uint32_t volatile ret_cb;
uint32_t iso_interval_us;
uint32_t slot_us;
uint32_t ret;
ull_hdr_init(&adv_iso->ull);
/* TODO: Calc slot_us */
slot_us = EVENT_OVERHEAD_START_US + EVENT_OVERHEAD_END_US;
slot_us += 1000;
adv_iso->ull.ticks_active_to_start = 0;
adv_iso->ull.ticks_prepare_to_start =
HAL_TICKER_US_TO_TICKS(EVENT_OVERHEAD_XTAL_US);
adv_iso->ull.ticks_preempt_to_start =
HAL_TICKER_US_TO_TICKS(EVENT_OVERHEAD_PREEMPT_MIN_US);
adv_iso->ull.ticks_slot = HAL_TICKER_US_TO_TICKS(slot_us);
if (IS_ENABLED(CONFIG_BT_CTLR_LOW_LAT)) {
ticks_slot_overhead = MAX(adv_iso->ull.ticks_active_to_start,
adv_iso->ull.ticks_prepare_to_start);
} else {
ticks_slot_overhead = 0;
}
/* TODO: Calculate ISO interval */
/* iso_interval shall be at least SDU interval,
* or integer multiple of SDU interval for unframed PDUs
*/
iso_interval_us = adv_iso->sdu_interval;
ret_cb = TICKER_STATUS_BUSY;
ret = ticker_start(TICKER_INSTANCE_ID_CTLR, TICKER_USER_ID_ULL_HIGH,
(TICKER_ID_ADV_ISO_BASE + adv_iso->bis_handle),
ticks_anchor, 0,
HAL_TICKER_US_TO_TICKS(iso_interval_us),
HAL_TICKER_REMAINDER(iso_interval_us),
TICKER_NULL_LAZY,
(ll_adv_iso->ull.ticks_slot + ticks_slot_overhead),
ticker_cb, ll_adv_iso,
ull_ticker_status_give, (void *)&ret_cb);
ret = ull_ticker_status_take(ret, &ret_cb);
return 0;
}
static inline struct ll_adv_iso *ull_adv_iso_get(uint8_t handle)
{
if (handle >= BT_CTLR_ADV_SET) {
return NULL;
}
return &ll_adv_iso[handle];
}
static int init_reset(void)
{
/* Initialize pool. */
mem_init(ll_adv_iso, sizeof(struct ll_adv_iso),
sizeof(ll_adv_iso) / sizeof(struct ll_adv_iso), &adv_iso_free);
return 0;
}
static void ticker_cb(uint32_t ticks_at_expire, uint32_t ticks_drift,
uint32_t remainder, uint16_t lazy, uint8_t force,
void *param)
{
/* TODO: LLL support for ADV ISO */
#if 0
static memq_link_t link;
static struct mayfly mfy = {0, 0, &link, NULL, lll_adv_iso_prepare};
static struct lll_prepare_param p;
struct ll_adv_iso *adv_iso = param;
struct lll_adv_iso *lll;
uint32_t ret;
uint8_t ref;
DEBUG_RADIO_PREPARE_A(1);
lll = &adv_iso->lll;
/* Increment prepare reference count */
ref = ull_ref_inc(&adv_iso->ull);
LL_ASSERT(ref);
/* Append timing parameters */
p.ticks_at_expire = ticks_at_expire;
p.remainder = remainder;
p.lazy = lazy;
p.force = force;
p.param = lll;
mfy.param = &p;
/* Kick LLL prepare */
ret = mayfly_enqueue(TICKER_USER_ID_ULL_HIGH,
TICKER_USER_ID_LLL, 0, &mfy);
LL_ASSERT(!ret);
DEBUG_RADIO_PREPARE_A(1);
#endif
}
static void tx_lll_flush(void *param)
{
/* TODO: LLL support for ADV ISO */
/* TODO: Send terminate complete event to host */
#if 0
/* TODO: Flush TX */
struct ll_adv_iso *lll = param;
#endif
}
static void ticker_op_stop_cb(uint32_t status, void *param)
{
uint32_t retval;
static memq_link_t link;
static struct mayfly mfy = {0, 0, &link, NULL, tx_lll_flush};
LL_ASSERT(status == TICKER_STATUS_SUCCESS);
mfy.param = param;
/* Flush pending tx PDUs in LLL (using a mayfly) */
retval = mayfly_enqueue(TICKER_USER_ID_ULL_LOW, TICKER_USER_ID_LLL, 0,
&mfy);
LL_ASSERT(!retval);
}