blob: 56dc226a07ec620779db4d526a43a4200490e513 [file] [log] [blame]
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <zephyr/sys/byteorder.h>
#include "hal/ccm.h"
#include "hal/radio.h"
#include "hal/ticker.h"
#include "util/util.h"
#include "util/mem.h"
#include "util/memq.h"
#include "util/dbuf.h"
#include "pdu_df.h"
#include "pdu_vendor.h"
#include "pdu.h"
#include "lll.h"
#include "lll_vendor.h"
#include "lll_clock.h"
#include "lll_chan.h"
#include "lll_df_types.h"
#include "lll_conn.h"
#include "lll_conn_iso.h"
#include "lll_central_iso.h"
#include "lll_iso_tx.h"
#include "lll_internal.h"
#include "lll_tim_internal.h"
#include "ll_feat.h"
#include "hal/debug.h"
static int init_reset(void);
static inline void lll_flush_tx(struct lll_conn_iso_stream *cis_lll);
static inline void lll_flush_rx(struct lll_conn_iso_stream *cis_lll);
static int prepare_cb(struct lll_prepare_param *p);
static void abort_cb(struct lll_prepare_param *prepare_param, void *param);
static void isr_tx(void *param);
static void isr_rx(void *param);
static void isr_prepare_subevent(void *param);
static void isr_done(void *param);
static uint16_t next_cis_chan_remap_idx;
static uint16_t next_cis_chan_prn_s;
static uint16_t data_chan_remap_idx;
static uint16_t data_chan_prn_s;
static uint8_t next_chan_use;
static uint8_t next_cis_chan;
static uint32_t trx_performed_bitmask;
static uint16_t cis_offset_first;
static uint16_t cis_handle_curr;
static uint8_t se_curr;
#if defined(CONFIG_BT_CTLR_LE_ENC)
static uint8_t mic_state;
#endif /* CONFIG_BT_CTLR_LE_ENC */
int lll_central_iso_init(void)
{
int err;
err = init_reset();
if (err) {
return err;
}
return 0;
}
int lll_central_iso_reset(void)
{
int err;
err = init_reset();
if (err) {
return err;
}
return 0;
}
void lll_central_iso_prepare(void *param)
{
struct lll_conn_iso_group *cig_lll;
struct lll_prepare_param *p;
uint16_t elapsed;
int err;
/* Initiate HF clock start up */
err = lll_hfclock_on();
LL_ASSERT(err >= 0);
/* Instants elapsed */
p = param;
elapsed = p->lazy + 1U;
/* Save the (latency + 1) for use in event and/or supervision timeout */
cig_lll = p->param;
cig_lll->latency_prepare += elapsed;
/* Invoke common pipeline handling of prepare */
err = lll_prepare(lll_is_abort_cb, abort_cb, prepare_cb, 0U, param);
LL_ASSERT(!err || err == -EINPROGRESS);
}
static int init_reset(void)
{
return 0;
}
static inline void lll_flush_tx(struct lll_conn_iso_stream *cis_lll)
{
/* sn and nesn are 1-bit, only Least Significant bit is needed */
uint8_t sn_update = cis_lll->tx.bn + 1U - cis_lll->tx.bn_curr;
/* we'll re-use sn_update when implementing flush timeout */
cis_lll->sn += sn_update;
}
static inline void lll_flush_rx(struct lll_conn_iso_stream *cis_lll)
{
/* sn and nesn are 1-bit, only Least Significant bit is needed */
uint8_t nesn_update = cis_lll->rx.bn + 1U - cis_lll->rx.bn_curr;
/* we'll re-use sn_update when implementing flush timeout */
cis_lll->nesn += nesn_update;
}
static int prepare_cb(struct lll_prepare_param *p)
{
struct lll_conn_iso_group *cig_lll = p->param;
struct lll_conn_iso_stream *cis_lll;
const struct lll_conn *conn_lll;
uint32_t ticks_at_event;
uint32_t ticks_at_start;
struct pdu_cis *pdu_tx;
uint16_t event_counter;
uint64_t payload_count;
uint16_t data_chan_id;
uint8_t data_chan_use;
uint16_t cis_handle;
struct ull_hdr *ull;
uint32_t remainder;
uint32_t start_us;
uint16_t lazy;
uint32_t ret;
uint8_t phy;
DEBUG_RADIO_START_M(1);
/* Reset global static variables */
trx_performed_bitmask = 0U;
#if defined(CONFIG_BT_CTLR_LE_ENC)
mic_state = LLL_CONN_MIC_NONE;
#endif /* CONFIG_BT_CTLR_LE_ENC */
/* Get the first CIS */
cis_handle_curr = UINT16_MAX;
do {
cis_lll = ull_conn_iso_lll_stream_get_by_group(cig_lll, &cis_handle_curr);
} while (cis_lll && !cis_lll->active);
LL_ASSERT(cis_lll);
/* Save first active CIS offset */
cis_offset_first = cis_lll->offset;
/* Get reference to ACL context */
conn_lll = ull_conn_lll_get(cis_lll->acl_handle);
/* Event counter value, 0-15 bit of cisEventCounter */
event_counter = cis_lll->event_count;
/* Calculate the radio channel to use for ISO event */
data_chan_id = lll_chan_id(cis_lll->access_addr);
data_chan_use = lll_chan_iso_event(event_counter, data_chan_id,
conn_lll->data_chan_map,
conn_lll->data_chan_count,
&data_chan_prn_s,
&data_chan_remap_idx);
/* Store the current event latency */
cig_lll->latency_event = cig_lll->latency_prepare;
lazy = cig_lll->latency_prepare - 1U;
/* Reset accumulated latencies */
cig_lll->latency_prepare = 0U;
/* Adjust the SN and NESN for skipped CIG events */
if (cis_lll->event_count) {
uint16_t cis_lazy;
if (lazy > cis_lll->event_count) {
cis_lazy = lazy - cis_lll->event_count;
} else {
cis_lazy = lazy;
}
/* sn and nesn are 1-bit, only Least Significant bit is needed */
cis_lll->sn += cis_lll->tx.bn * cis_lazy;
cis_lll->nesn += cis_lll->rx.bn * cis_lazy;
}
se_curr = 1U;
cis_lll->tx.bn_curr = 1U;
cis_lll->rx.bn_curr = 1U;
/* Start setting up of Radio h/w */
radio_reset();
#if defined(CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL)
radio_tx_power_set(cis_lll->tx_pwr_lvl);
#else /* !CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL */
radio_tx_power_set(RADIO_TXP_DEFAULT);
#endif /* !CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL */
phy = cis_lll->tx.phy;
radio_phy_set(phy, cis_lll->tx.phy_flags);
radio_aa_set(cis_lll->access_addr);
radio_crc_configure(PDU_CRC_POLYNOMIAL, sys_get_le24(conn_lll->crc_init));
lll_chan_set(data_chan_use);
/* Get ISO data PDU */
if (cis_lll->tx.bn_curr > cis_lll->tx.bn) {
payload_count = 0U;
cis_lll->npi = 1U;
pdu_tx = radio_pkt_empty_get();
pdu_tx->ll_id = PDU_CIS_LLID_START_CONTINUE;
pdu_tx->nesn = cis_lll->nesn;
pdu_tx->sn = 0U; /* reserved RFU for NULL PDU */
pdu_tx->cie = (cis_lll->rx.bn_curr > cis_lll->rx.bn);
pdu_tx->npi = 1U;
pdu_tx->len = 0U;
} else {
struct node_tx_iso *node_tx;
memq_link_t *link;
payload_count = cis_lll->event_count * cis_lll->tx.bn;
do {
link = memq_peek(cis_lll->memq_tx.head,
cis_lll->memq_tx.tail,
(void **)&node_tx);
if (!link) {
break;
}
if (node_tx->payload_count < payload_count) {
memq_dequeue(cis_lll->memq_tx.tail,
&cis_lll->memq_tx.head,
NULL);
node_tx->next = link;
ull_iso_lll_ack_enqueue(cis_lll->handle,
node_tx);
} else if (node_tx->payload_count >= (payload_count + cis_lll->tx.bn)) {
link = NULL;
} else {
if (node_tx->payload_count != payload_count) {
link = NULL;
}
break;
}
} while (link);
if (!link) {
cis_lll->npi = 1U;
pdu_tx = radio_pkt_empty_get();
pdu_tx->ll_id = PDU_CIS_LLID_START_CONTINUE;
pdu_tx->nesn = cis_lll->nesn;
pdu_tx->cie = (cis_lll->tx.bn_curr > cis_lll->tx.bn) &&
(cis_lll->rx.bn_curr > cis_lll->rx.bn);
pdu_tx->len = 0U;
pdu_tx->sn = 0U; /* reserved RFU for NULL PDU */
pdu_tx->npi = 1U;
} else {
cis_lll->npi = 0U;
pdu_tx = (void *)node_tx->pdu;
pdu_tx->nesn = cis_lll->nesn;
pdu_tx->sn = cis_lll->sn;
pdu_tx->cie = 0U;
pdu_tx->npi = 0U;
}
}
/* Initialize reserve bit */
pdu_tx->rfu0 = 0U;
pdu_tx->rfu1 = 0U;
/* Encryption */
if (false) {
#if defined(CONFIG_BT_CTLR_LE_ENC)
} else if (pdu_tx->len && conn_lll->enc_tx) {
uint8_t pkt_flags;
cis_lll->tx.ccm.counter = payload_count;
pkt_flags = RADIO_PKT_CONF_FLAGS(RADIO_PKT_CONF_PDU_TYPE_DC,
phy,
RADIO_PKT_CONF_CTE_DISABLED);
radio_pkt_configure(RADIO_PKT_CONF_LENGTH_8BIT,
(cis_lll->tx.max_pdu + PDU_MIC_SIZE),
pkt_flags);
radio_pkt_tx_set(radio_ccm_tx_pkt_set(&cis_lll->tx.ccm,
pdu_tx));
#endif /* CONFIG_BT_CTLR_LE_ENC */
} else {
uint8_t pkt_flags;
pkt_flags = RADIO_PKT_CONF_FLAGS(RADIO_PKT_CONF_PDU_TYPE_DC,
phy,
RADIO_PKT_CONF_CTE_DISABLED);
radio_pkt_configure(RADIO_PKT_CONF_LENGTH_8BIT,
cis_lll->tx.max_pdu, pkt_flags);
radio_pkt_tx_set(pdu_tx);
}
radio_isr_set(isr_tx, cis_lll);
radio_tmr_tifs_set(EVENT_IFS_US);
#if defined(CONFIG_BT_CTLR_PHY)
radio_switch_complete_and_rx(cis_lll->rx.phy);
#else /* !CONFIG_BT_CTLR_PHY */
radio_switch_complete_and_rx(0U);
#endif /* !CONFIG_BT_CTLR_PHY */
ticks_at_event = p->ticks_at_expire;
ull = HDR_LLL2ULL(cig_lll);
ticks_at_event += lll_event_offset_get(ull);
ticks_at_start = ticks_at_event;
ticks_at_start += HAL_TICKER_US_TO_TICKS(EVENT_OVERHEAD_START_US +
cis_offset_first);
remainder = p->remainder;
start_us = radio_tmr_start(1U, ticks_at_start, remainder);
/* Save radio ready timestamp, use it to schedule next subevent */
radio_tmr_ready_save(start_us);
/* capture end of Tx-ed PDU, used to calculate HCTO. */
radio_tmr_end_capture();
#if defined(HAL_RADIO_GPIO_HAVE_PA_PIN)
radio_gpio_pa_setup();
#if defined(CONFIG_BT_CTLR_PHY)
radio_gpio_pa_lna_enable(start_us +
radio_tx_ready_delay_get(phy, PHY_FLAGS_S8) -
HAL_RADIO_GPIO_PA_OFFSET);
#else /* !CONFIG_BT_CTLR_PHY */
radio_gpio_pa_lna_enable(start_us +
radio_tx_ready_delay_get(0U, 0U) -
HAL_RADIO_GPIO_PA_OFFSET);
#endif /* !CONFIG_BT_CTLR_PHY */
#else /* !HAL_RADIO_GPIO_HAVE_PA_PIN */
ARG_UNUSED(start_us);
#endif /* !HAL_RADIO_GPIO_HAVE_PA_PIN */
if (false) {
#if defined(CONFIG_BT_CTLR_XTAL_ADVANCED) && \
(EVENT_OVERHEAD_PREEMPT_US <= EVENT_OVERHEAD_PREEMPT_MIN_US)
/* check if preempt to start has changed */
} else if (lll_preempt_calc(ull,
(TICKER_ID_CONN_ISO_BASE + cig_lll->handle),
ticks_at_event)) {
radio_isr_set(lll_isr_abort, cig_lll);
radio_disable();
return -ECANCELED;
#endif /* CONFIG_BT_CTLR_XTAL_ADVANCED */
}
/* Adjust the SN and NESN for skipped CIG events */
cis_handle = cis_handle_curr;
do {
cis_lll = ull_conn_iso_lll_stream_get_by_group(cig_lll, &cis_handle);
if (cis_lll && cis_lll->active) {
if (cis_lll->event_count) {
uint16_t cis_lazy;
if (lazy > cis_lll->event_count) {
cis_lazy = lazy - cis_lll->event_count;
} else {
cis_lazy = lazy;
}
/* sn and nesn are 1-bit, only Least Significant bit is needed */
cis_lll->sn += cis_lll->tx.bn * cis_lazy;
cis_lll->nesn += cis_lll->rx.bn * cis_lazy;
}
}
} while (cis_lll);
/* Prepare is done */
ret = lll_prepare_done(cig_lll);
LL_ASSERT(!ret);
DEBUG_RADIO_START_M(1);
return 0;
}
static void abort_cb(struct lll_prepare_param *prepare_param, void *param)
{
int err;
/* NOTE: This is not a prepare being cancelled */
if (!prepare_param) {
struct lll_conn_iso_stream *cis_lll;
cis_lll = ull_conn_iso_lll_stream_get(cis_handle_curr);
/* Perform event abort here.
* After event has been cleanly aborted, clean up resources
* and dispatch event done.
*/
radio_isr_set(isr_done, cis_lll);
radio_disable();
return;
}
/* NOTE: Else clean the top half preparations of the aborted event
* currently in preparation pipeline.
*/
err = lll_hfclock_off();
LL_ASSERT(err >= 0);
lll_done(param);
}
static void isr_tx(void *param)
{
struct lll_conn_iso_stream *cis_lll;
struct node_rx_pdu *node_rx;
uint32_t hcto;
/* Clear radio tx status and events */
lll_isr_tx_status_reset();
/* Close subevent, one tx-rx chain */
radio_switch_complete_and_disable();
/* Get reference to CIS LLL context */
cis_lll = param;
#if defined(CONFIG_BT_CTLR_LE_ENC)
/* Get reference to ACL context */
const struct lll_conn *conn_lll = ull_conn_lll_get(cis_lll->acl_handle);
#endif /* CONFIG_BT_CTLR_LE_ENC */
/* Acquire rx node for reception */
node_rx = ull_iso_pdu_rx_alloc_peek(1U);
LL_ASSERT(node_rx);
/* Encryption */
if (false) {
#if defined(CONFIG_BT_CTLR_LE_ENC)
} else if (conn_lll->enc_rx) {
uint64_t payload_count;
uint8_t pkt_flags;
payload_count = (cis_lll->event_count * cis_lll->rx.bn) +
(cis_lll->rx.bn_curr - 1U);
cis_lll->rx.ccm.counter = payload_count;
pkt_flags = RADIO_PKT_CONF_FLAGS(RADIO_PKT_CONF_PDU_TYPE_DC,
cis_lll->rx.phy,
RADIO_PKT_CONF_CTE_DISABLED);
radio_pkt_configure(RADIO_PKT_CONF_LENGTH_8BIT,
(cis_lll->rx.max_pdu + PDU_MIC_SIZE),
pkt_flags);
radio_pkt_rx_set(radio_ccm_rx_pkt_set(&cis_lll->rx.ccm,
cis_lll->rx.phy,
node_rx->pdu));
#endif /* CONFIG_BT_CTLR_LE_ENC */
} else {
uint8_t pkt_flags;
pkt_flags = RADIO_PKT_CONF_FLAGS(RADIO_PKT_CONF_PDU_TYPE_DC,
cis_lll->rx.phy,
RADIO_PKT_CONF_CTE_DISABLED);
radio_pkt_configure(RADIO_PKT_CONF_LENGTH_8BIT,
cis_lll->rx.max_pdu, pkt_flags);
radio_pkt_rx_set(node_rx->pdu);
}
/* assert if radio packet ptr is not set and radio started rx */
LL_ASSERT(!radio_is_ready());
/* +/- 2us active clock jitter, +1 us hcto compensation */
hcto = radio_tmr_tifs_base_get() + EVENT_IFS_US +
(EVENT_CLOCK_JITTER_US << 1) + RANGE_DELAY_US +
HCTO_START_DELAY_US;
#if defined(CONFIG_BT_CTLR_PHY)
hcto += radio_rx_chain_delay_get(cis_lll->rx.phy, PHY_FLAGS_S8);
hcto += addr_us_get(cis_lll->rx.phy);
hcto -= radio_tx_chain_delay_get(cis_lll->tx.phy,
cis_lll->tx.phy_flags);
#else /* !CONFIG_BT_CTLR_PHY */
hcto += radio_rx_chain_delay_get(0U, 0U);
hcto += addr_us_get(0U);
hcto -= radio_tx_chain_delay_get(0U, 0U);
#endif /* !CONFIG_BT_CTLR_PHY */
radio_tmr_hcto_configure(hcto);
#if defined(CONFIG_BT_CTLR_PROFILE_ISR) || \
defined(HAL_RADIO_GPIO_HAVE_PA_PIN)
radio_tmr_end_capture();
#endif /* CONFIG_BT_CTLR_PROFILE_ISR */
#if defined(HAL_RADIO_GPIO_HAVE_LNA_PIN)
radio_gpio_lna_setup();
#if defined(CONFIG_BT_CTLR_PHY)
radio_gpio_pa_lna_enable(radio_tmr_tifs_base_get() + EVENT_IFS_US -
(EVENT_CLOCK_JITTER_US << 1) -
radio_tx_chain_delay_get(cis_lll->tx.phy,
cis_lll->tx.phy_flags) -
HAL_RADIO_GPIO_LNA_OFFSET);
#else /* !CONFIG_BT_CTLR_PHY */
radio_gpio_pa_lna_enable(radio_tmr_tifs_base_get() + EVENT_IFS_US -
(EVENT_CLOCK_JITTER_US << 1) -
radio_tx_chain_delay_get(0U, 0U) -
HAL_RADIO_GPIO_LNA_OFFSET);
#endif /* !CONFIG_BT_CTLR_PHY */
#endif /* HAL_RADIO_GPIO_HAVE_LNA_PIN */
radio_isr_set(isr_rx, param);
/* Schedule next subevent */
if (se_curr < cis_lll->nse) {
const struct lll_conn *conn_lll;
uint16_t data_chan_id;
uint32_t subevent_us;
uint32_t start_us;
subevent_us = radio_tmr_ready_restore();
subevent_us += cis_lll->offset - cis_offset_first +
(cis_lll->sub_interval * se_curr);
start_us = radio_tmr_start_us(1U, subevent_us);
LL_ASSERT(start_us == (subevent_us + 1U));
/* Get reference to ACL context */
conn_lll = ull_conn_lll_get(cis_lll->acl_handle);
/* Calculate the radio channel to use for next subevent */
data_chan_id = lll_chan_id(cis_lll->access_addr);
next_chan_use = lll_chan_iso_subevent(data_chan_id,
conn_lll->data_chan_map,
conn_lll->data_chan_count,
&data_chan_prn_s,
&data_chan_remap_idx);
} else {
struct lll_conn_iso_stream *next_cis_lll;
struct lll_conn_iso_group *cig_lll;
struct lll_conn *next_conn_lll;
struct node_tx_iso *node_tx;
uint64_t payload_count;
uint16_t event_counter;
uint16_t data_chan_id;
uint32_t subevent_us;
uint16_t cis_handle;
uint32_t start_us;
memq_link_t *link;
/* Calculate channel for next CIS */
cig_lll = ull_conn_iso_lll_group_get_by_stream(cis_lll);
cis_handle = cis_handle_curr;
do {
next_cis_lll = ull_conn_iso_lll_stream_get_by_group(cig_lll, &cis_handle);
} while (next_cis_lll && !next_cis_lll->active);
if (!next_cis_lll) {
return;
}
/* Get reference to ACL context */
next_conn_lll = ull_conn_lll_get(next_cis_lll->acl_handle);
/* Event counter value, 0-15 bit of cisEventCounter */
event_counter = next_cis_lll->event_count;
/* Calculate the radio channel to use for ISO event */
data_chan_id = lll_chan_id(next_cis_lll->access_addr);
next_cis_chan = lll_chan_iso_event(event_counter, data_chan_id,
next_conn_lll->data_chan_map,
next_conn_lll->data_chan_count,
&next_cis_chan_prn_s,
&next_cis_chan_remap_idx);
subevent_us = radio_tmr_ready_restore();
subevent_us += next_cis_lll->offset - cis_offset_first;
start_us = radio_tmr_start_us(1U, subevent_us);
LL_ASSERT(start_us == (subevent_us + 1U));
cis_lll = next_cis_lll;
/* Tx Ack stale ISO Data */
payload_count = cis_lll->event_count * cis_lll->tx.bn;
do {
link = memq_peek(cis_lll->memq_tx.head,
cis_lll->memq_tx.tail,
(void **)&node_tx);
if (!link) {
break;
}
if (node_tx->payload_count < payload_count) {
memq_dequeue(cis_lll->memq_tx.tail,
&cis_lll->memq_tx.head,
NULL);
node_tx->next = link;
ull_iso_lll_ack_enqueue(cis_lll->handle,
node_tx);
} else if (node_tx->payload_count >=
(payload_count + cis_lll->tx.bn)) {
link = NULL;
} else {
if (node_tx->payload_count !=
payload_count) {
link = NULL;
}
break;
}
} while (link);
}
}
static void isr_rx(void *param)
{
struct lll_conn_iso_stream *cis_lll;
struct node_rx_pdu *node_rx;
struct pdu_cis *pdu_rx;
uint8_t ack_pending;
uint8_t trx_done;
uint8_t crc_ok;
uint8_t cie;
/* Read radio status and events */
trx_done = radio_is_done();
if (trx_done) {
crc_ok = radio_crc_is_valid();
} else {
crc_ok = 0U;
}
/* Clear radio status and events */
lll_isr_rx_sub_status_reset();
/* Initialize Close Isochronous Event */
ack_pending = 0U;
cie = 0U;
/* Get reference to CIS LLL context */
cis_lll = param;
/* No Rx */
if (!trx_done) {
goto isr_rx_next_subevent;
}
/* FIXME: Do not call this for every event/subevent */
ull_conn_iso_lll_cis_established(param);
/* Set the bit corresponding to CIS index */
trx_performed_bitmask |= (1U << LL_CIS_IDX_FROM_HANDLE(cis_lll->handle));
/* Get reference to received PDU */
node_rx = ull_iso_pdu_rx_alloc_peek(1U);
LL_ASSERT(node_rx);
pdu_rx = (void *)node_rx->pdu;
if (crc_ok) {
/* Tx ACK */
if (pdu_rx->nesn != cis_lll->sn) {
/* Increment sequence number */
cis_lll->sn++;
/* Increment burst number */
if (cis_lll->tx.bn_curr <= cis_lll->tx.bn) {
cis_lll->tx.bn_curr++;
}
/* TODO: Implement early Tx Ack. Currently Tx Ack
* generated as stale Tx Ack when payload count
* has elapsed.
*/
}
/* Handle valid ISO data Rx */
if (!pdu_rx->npi &&
(cis_lll->rx.bn_curr <= cis_lll->rx.bn) &&
(pdu_rx->sn == cis_lll->nesn) &&
ull_iso_pdu_rx_alloc_peek(2U)) {
struct node_rx_iso_meta *iso_meta;
/* Increment next expected sequence number */
cis_lll->nesn++;
#if defined(CONFIG_BT_CTLR_LE_ENC)
/* Get reference to ACL context */
const struct lll_conn *conn_lll = ull_conn_lll_get(cis_lll->acl_handle);
/* If required, wait for CCM to finish
*/
if (pdu_rx->len && conn_lll->enc_rx) {
uint32_t done;
done = radio_ccm_is_done();
LL_ASSERT(done);
if (!radio_ccm_mic_is_valid()) {
/* Record MIC invalid */
mic_state = LLL_CONN_MIC_FAIL;
goto isr_rx_done;
}
/* Record MIC valid */
mic_state = LLL_CONN_MIC_PASS;
}
#endif /* CONFIG_BT_CTLR_LE_ENC */
/* Enqueue Rx ISO PDU */
node_rx->hdr.type = NODE_RX_TYPE_ISO_PDU;
node_rx->hdr.handle = cis_lll->handle;
iso_meta = &node_rx->hdr.rx_iso_meta;
iso_meta->payload_number = (cis_lll->event_count *
cis_lll->rx.bn) +
(cis_lll->rx.bn_curr - 1U);
iso_meta->timestamp =
HAL_TICKER_TICKS_TO_US(radio_tmr_start_get()) +
radio_tmr_ready_restore();
iso_meta->timestamp %=
HAL_TICKER_TICKS_TO_US(BIT(HAL_TICKER_CNTR_MSBIT + 1U));
iso_meta->status = 0U;
ull_iso_pdu_rx_alloc();
iso_rx_put(node_rx->hdr.link, node_rx);
#if !defined(CONFIG_BT_CTLR_LOW_LAT_ULL)
iso_rx_sched();
#endif /* CONFIG_BT_CTLR_LOW_LAT_ULL */
/* Increment burst number */
cis_lll->rx.bn_curr++;
/* Need to be acked */
ack_pending = 1U;
/* Handle NULL PDU indication received */
} else if (pdu_rx->npi) {
/* Source could not send ISO data, increment NESN as if
* we received and expect to receive the next PDU in the
* burst.
*/
if (cis_lll->rx.bn_curr <= cis_lll->rx.bn) {
/* Increment next expected serial number */
cis_lll->nesn++;
/* Increment burst number */
cis_lll->rx.bn_curr++;
}
/* Not NPI, or more than the BN, or no free Rx ISO PDU buffers.
*/
} else {
/* Do nothing, ignore the Rx buffer */
}
/* Close Isochronous Event */
cie = cie || pdu_rx->cie;
}
/* Close Isochronous Event */
cie = cie || ((cis_lll->rx.bn_curr > cis_lll->rx.bn) &&
(cis_lll->tx.bn_curr > cis_lll->tx.bn) &&
!ack_pending);
isr_rx_next_subevent:
if (cie || (se_curr == cis_lll->nse)) {
struct lll_conn_iso_stream *old_cis_lll;
struct lll_conn_iso_stream *next_cis_lll;
struct lll_conn_iso_group *cig_lll;
struct lll_conn *next_conn_lll;
uint8_t phy;
uint8_t bn;
/* Fetch next CIS */
/* TODO: Use a new ull_conn_iso_lll_stream_get_active_by_group()
* in the future.
*/
cig_lll = ull_conn_iso_lll_group_get_by_stream(cis_lll);
do {
next_cis_lll = ull_conn_iso_lll_stream_get_by_group(cig_lll,
&cis_handle_curr);
} while (next_cis_lll && !next_cis_lll->active);
if (!next_cis_lll) {
goto isr_rx_done;
}
/* Adjust sn when flushing Tx */
/* FIXME: When Flush Timeout is implemented */
if (cis_lll->tx.bn_curr <= cis_lll->tx.bn) {
lll_flush_tx(cis_lll);
}
/* Adjust nesn when flushing Rx */
/* FIXME: When Flush Timeout is implemented */
if (cis_lll->tx.bn_curr <= cis_lll->rx.bn) {
lll_flush_rx(cis_lll);
}
/* Get reference to ACL context */
next_conn_lll = ull_conn_lll_get(next_cis_lll->acl_handle);
/* Calculate CIS channel if not already calculated */
if (se_curr < cis_lll->nse) {
struct node_tx_iso *node_tx;
uint64_t payload_count;
uint16_t event_counter;
uint16_t data_chan_id;
uint32_t subevent_us;
uint32_t start_us;
memq_link_t *link;
/* Event counter value, 0-15 bit of cisEventCounter */
event_counter = next_cis_lll->event_count;
/* Calculate the radio channel to use for ISO event */
data_chan_id = lll_chan_id(next_cis_lll->access_addr);
next_cis_chan = lll_chan_iso_event(event_counter, data_chan_id,
next_conn_lll->data_chan_map,
next_conn_lll->data_chan_count,
&next_cis_chan_prn_s,
&next_cis_chan_remap_idx);
subevent_us = radio_tmr_ready_restore();
subevent_us += next_cis_lll->offset - cis_offset_first;
start_us = radio_tmr_start_us(1U, subevent_us);
LL_ASSERT(start_us == (subevent_us + 1U));
old_cis_lll = cis_lll;
cis_lll = next_cis_lll;
payload_count = cis_lll->event_count * cis_lll->tx.bn;
do {
link = memq_peek(cis_lll->memq_tx.head,
cis_lll->memq_tx.tail,
(void **)&node_tx);
if (!link) {
break;
}
if (node_tx->payload_count < payload_count) {
memq_dequeue(cis_lll->memq_tx.tail,
&cis_lll->memq_tx.head,
NULL);
node_tx->next = link;
ull_iso_lll_ack_enqueue(cis_lll->handle,
node_tx);
} else if (node_tx->payload_count >=
(payload_count + cis_lll->tx.bn)) {
link = NULL;
} else {
if (node_tx->payload_count !=
payload_count) {
link = NULL;
}
break;
}
} while (link);
cis_lll = old_cis_lll;
}
/* Generate ISO Data Invalid Status */
bn = cis_lll->rx.bn_curr;
while (bn <= cis_lll->rx.bn) {
struct node_rx_iso_meta *iso_meta;
struct node_rx_pdu *node_rx;
/* Ensure there is always one free for reception
* of ISO PDU by the radio h/w DMA, hence peek
* for two available ISO PDU when using one for
* generating invalid ISO data.
*/
node_rx = ull_iso_pdu_rx_alloc_peek(2U);
if (!node_rx) {
break;
}
node_rx->hdr.type = NODE_RX_TYPE_ISO_PDU;
node_rx->hdr.handle = cis_lll->handle;
iso_meta = &node_rx->hdr.rx_iso_meta;
iso_meta->payload_number = (cis_lll->event_count *
cis_lll->rx.bn) + (bn - 1U);
iso_meta->timestamp =
HAL_TICKER_TICKS_TO_US(radio_tmr_start_get()) +
radio_tmr_ready_restore();
iso_meta->timestamp %=
HAL_TICKER_TICKS_TO_US(BIT(HAL_TICKER_CNTR_MSBIT + 1U));
iso_meta->status = 1U;
ull_iso_pdu_rx_alloc();
iso_rx_put(node_rx->hdr.link, node_rx);
bn++;
}
#if !defined(CONFIG_BT_CTLR_LOW_LAT_ULL)
if (bn != cis_lll->rx.bn_curr) {
iso_rx_sched();
}
#endif /* CONFIG_BT_CTLR_LOW_LAT_ULL */
/* Reset indices for the next CIS */
se_curr = 0U; /* isr_prepare_subevent() will increase se_curr */
next_cis_lll->tx.bn_curr = 1U;
next_cis_lll->rx.bn_curr = 1U;
#if defined(CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL)
radio_tx_power_set(next_cis_lll->tx_pwr_lvl);
#else
radio_tx_power_set(RADIO_TXP_DEFAULT);
#endif
phy = next_cis_lll->tx.phy;
radio_phy_set(phy, next_cis_lll->tx.phy_flags);
radio_aa_set(next_cis_lll->access_addr);
radio_crc_configure(PDU_CRC_POLYNOMIAL,
sys_get_le24(next_conn_lll->crc_init));
param = next_cis_lll;
next_chan_use = next_cis_chan;
data_chan_prn_s = next_cis_chan_prn_s;
data_chan_remap_idx = next_cis_chan_remap_idx;
}
isr_prepare_subevent(param);
return;
isr_rx_done:
radio_isr_set(isr_done, param);
radio_disable();
}
static void isr_prepare_subevent(void *param)
{
struct lll_conn_iso_stream *cis_lll;
struct pdu_cis *pdu_tx;
uint64_t payload_count;
uint8_t payload_index;
uint32_t subevent_us;
uint32_t start_us;
/* Get reference to CIS LLL context */
cis_lll = param;
/* Get ISO data PDU */
if (cis_lll->tx.bn_curr > cis_lll->tx.bn) {
payload_count = 0U;
cis_lll->npi = 1U;
pdu_tx = radio_pkt_empty_get();
pdu_tx->ll_id = PDU_CIS_LLID_START_CONTINUE;
pdu_tx->nesn = cis_lll->nesn;
pdu_tx->sn = 0U; /* reserved RFU for NULL PDU */
pdu_tx->cie = (cis_lll->rx.bn_curr > cis_lll->rx.bn);
pdu_tx->npi = 1U;
pdu_tx->len = 0U;
} else {
struct node_tx_iso *node_tx;
memq_link_t *link;
payload_index = cis_lll->tx.bn_curr - 1U;
payload_count = cis_lll->event_count * cis_lll->tx.bn +
payload_index;
link = memq_peek_n(cis_lll->memq_tx.head, cis_lll->memq_tx.tail,
payload_index, (void **)&node_tx);
if (!link || (node_tx->payload_count != payload_count)) {
payload_index = 0U;
do {
link = memq_peek_n(cis_lll->memq_tx.head,
cis_lll->memq_tx.tail,
payload_index,
(void **)&node_tx);
payload_index++;
} while (link &&
(node_tx->payload_count < payload_count));
}
if (!link || (node_tx->payload_count != payload_count)) {
cis_lll->npi = 1U;
pdu_tx = radio_pkt_empty_get();
pdu_tx->ll_id = PDU_CIS_LLID_START_CONTINUE;
pdu_tx->nesn = cis_lll->nesn;
pdu_tx->cie = (cis_lll->tx.bn_curr > cis_lll->tx.bn) &&
(cis_lll->rx.bn_curr > cis_lll->rx.bn);
pdu_tx->len = 0U;
pdu_tx->sn = 0U; /* reserved RFU for NULL PDU */
pdu_tx->npi = 1U;
} else {
cis_lll->npi = 0U;
pdu_tx = (void *)node_tx->pdu;
pdu_tx->nesn = cis_lll->nesn;
pdu_tx->sn = cis_lll->sn;
pdu_tx->cie = 0U;
pdu_tx->npi = 0U;
}
}
/* Initialize reserve bit */
pdu_tx->rfu0 = 0U;
pdu_tx->rfu1 = 0U;
#if defined(CONFIG_BT_CTLR_LE_ENC)
/* Get reference to ACL context */
const struct lll_conn *conn_lll = ull_conn_lll_get(cis_lll->acl_handle);
#endif /* CONFIG_BT_CTLR_LE_ENC */
/* Encryption */
if (false) {
#if defined(CONFIG_BT_CTLR_LE_ENC)
} else if (pdu_tx->len && conn_lll->enc_tx) {
uint8_t pkt_flags;
cis_lll->tx.ccm.counter = payload_count;
pkt_flags = RADIO_PKT_CONF_FLAGS(RADIO_PKT_CONF_PDU_TYPE_DC,
cis_lll->tx.phy,
RADIO_PKT_CONF_CTE_DISABLED);
radio_pkt_configure(RADIO_PKT_CONF_LENGTH_8BIT,
(cis_lll->tx.max_pdu + PDU_MIC_SIZE), pkt_flags);
radio_pkt_tx_set(radio_ccm_tx_pkt_set(&cis_lll->tx.ccm,
pdu_tx));
#endif /* CONFIG_BT_CTLR_LE_ENC */
} else {
uint8_t pkt_flags;
pkt_flags = RADIO_PKT_CONF_FLAGS(RADIO_PKT_CONF_PDU_TYPE_DC,
cis_lll->tx.phy,
RADIO_PKT_CONF_CTE_DISABLED);
radio_pkt_configure(RADIO_PKT_CONF_LENGTH_8BIT,
cis_lll->tx.max_pdu, pkt_flags);
radio_pkt_tx_set(pdu_tx);
}
lll_chan_set(next_chan_use);
radio_tmr_rx_disable();
radio_tmr_tx_enable();
radio_tmr_tifs_set(EVENT_IFS_US);
#if defined(CONFIG_BT_CTLR_PHY)
radio_switch_complete_and_rx(cis_lll->rx.phy);
#else /* !CONFIG_BT_CTLR_PHY */
radio_switch_complete_and_rx(0U);
#endif /* !CONFIG_BT_CTLR_PHY */
subevent_us = radio_tmr_ready_restore();
subevent_us += cis_lll->offset - cis_offset_first +
(cis_lll->sub_interval * se_curr);
/* Compensate for the 1 us added by radio_tmr_start_us() */
start_us = subevent_us + 1U;
/* capture end of Tx-ed PDU, used to calculate HCTO. */
radio_tmr_end_capture();
#if defined(HAL_RADIO_GPIO_HAVE_PA_PIN)
radio_gpio_pa_setup();
#if defined(CONFIG_BT_CTLR_PHY)
radio_gpio_pa_lna_enable(start_us +
radio_tx_ready_delay_get(cis_lll->rx.phy,
PHY_FLAGS_S8) -
HAL_RADIO_GPIO_PA_OFFSET);
#else /* !CONFIG_BT_CTLR_PHY */
radio_gpio_pa_lna_enable(start_us +
radio_tx_ready_delay_get(0U, 0U) -
HAL_RADIO_GPIO_PA_OFFSET);
#endif /* !CONFIG_BT_CTLR_PHY */
#else /* !HAL_RADIO_GPIO_HAVE_PA_PIN */
ARG_UNUSED(start_us);
#endif /* !HAL_RADIO_GPIO_HAVE_PA_PIN */
/* assert if radio packet ptr is not set and radio started tx */
LL_ASSERT(!radio_is_ready());
radio_isr_set(isr_tx, param);
/* Next subevent */
se_curr++;
}
static void isr_done(void *param)
{
struct lll_conn_iso_stream *cis_lll;
struct event_done_extra *e;
uint8_t bn;
lll_isr_status_reset();
/* Get reference to CIS LLL context */
cis_lll = param;
/* Adjust sn when flushing Tx */
/* FIXME: When Flush Timeout is implemented */
if (cis_lll->tx.bn_curr <= cis_lll->tx.bn) {
lll_flush_tx(cis_lll);
}
/* Adjust nesn when flushing Rx */
/* FIXME: When Flush Timeout is implemented */
if (cis_lll->rx.bn_curr <= cis_lll->rx.bn) {
lll_flush_rx(cis_lll);
}
/* Generate ISO Data Invalid Status */
bn = cis_lll->rx.bn_curr;
while (bn <= cis_lll->rx.bn) {
struct node_rx_iso_meta *iso_meta;
struct node_rx_pdu *node_rx;
/* Ensure there is always one free for reception of ISO PDU by
* the radio h/w DMA, hence peek for two available ISO PDU when
* using one for generating invalid ISO data.
*/
node_rx = ull_iso_pdu_rx_alloc_peek(2U);
if (!node_rx) {
break;
}
node_rx->hdr.type = NODE_RX_TYPE_ISO_PDU;
node_rx->hdr.handle = cis_lll->handle;
iso_meta = &node_rx->hdr.rx_iso_meta;
iso_meta->payload_number = (cis_lll->event_count *
cis_lll->rx.bn) + (bn - 1U);
iso_meta->timestamp =
HAL_TICKER_TICKS_TO_US(radio_tmr_start_get()) +
radio_tmr_ready_restore();
iso_meta->timestamp %=
HAL_TICKER_TICKS_TO_US(BIT(HAL_TICKER_CNTR_MSBIT + 1U));
iso_meta->status = 1U;
ull_iso_pdu_rx_alloc();
iso_rx_put(node_rx->hdr.link, node_rx);
bn++;
}
#if !defined(CONFIG_BT_CTLR_LOW_LAT_ULL)
if (bn != cis_lll->rx.bn_curr) {
iso_rx_sched();
}
#endif /* CONFIG_BT_CTLR_LOW_LAT_ULL */
e = ull_event_done_extra_get();
LL_ASSERT(e);
e->type = EVENT_DONE_EXTRA_TYPE_CIS;
e->trx_performed_bitmask = trx_performed_bitmask;
e->crc_valid = 1U;
#if defined(CONFIG_BT_CTLR_LE_ENC)
e->mic_state = mic_state;
#endif /* CONFIG_BT_CTLR_LE_ENC */
lll_isr_cleanup(param);
}