blob: 20cdbbf9d38224a795bf7f88625feb0e98478fdb [file] [log] [blame]
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <soc.h>
#include <sys/byteorder.h>
#include "hal/cpu.h"
#include "hal/ccm.h"
#include "hal/radio.h"
#include "hal/ticker.h"
#include "util/util.h"
#include "util/memq.h"
#include "pdu.h"
#include "lll.h"
#include "lll_vendor.h"
#include "lll_clock.h"
#include "lll_chan.h"
#include "lll_sync_iso.h"
#include "lll_internal.h"
#include "lll_tim_internal.h"
#include "ll_feat.h"
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER)
#define LOG_MODULE_NAME bt_ctlr_lll_sync_iso
#include "common/log.h"
#include "hal/debug.h"
static int init_reset(void);
static void prepare(void *param);
static void create_prepare_bh(void *param);
static void prepare_bh(void *param);
static int create_prepare_cb(struct lll_prepare_param *p);
static int prepare_cb(struct lll_prepare_param *p);
static int prepare_cb_common(struct lll_prepare_param *p);
static void isr_rx_estab(void *param);
static void isr_rx(void *param);
static void isr_rx_ctrl_recv(struct lll_sync_iso *lll, struct pdu_bis *pdu);
/* FIXME: Optimize by moving to a common place, as similar variable is used for
* connections too.
*/
static uint8_t trx_cnt;
static uint8_t crc_ok_anchor;
int lll_sync_iso_init(void)
{
int err;
err = init_reset();
if (err) {
return err;
}
return 0;
}
int lll_sync_iso_reset(void)
{
int err;
err = init_reset();
if (err) {
return err;
}
return 0;
}
void lll_sync_iso_create_prepare(void *param)
{
prepare(param);
create_prepare_bh(param);
}
void lll_sync_iso_prepare(void *param)
{
prepare(param);
prepare_bh(param);
}
static int init_reset(void)
{
return 0;
}
static void prepare(void *param)
{
struct lll_prepare_param *p;
struct lll_sync_iso *lll;
uint16_t elapsed;
int err;
err = lll_hfclock_on();
LL_ASSERT(err >= 0);
p = param;
/* Instants elapsed */
elapsed = p->lazy + 1U;
lll = p->param;
/* Save the (latency + 1) for use in event */
lll->latency_prepare += elapsed;
/* Accumulate window widening */
lll->window_widening_prepare_us += lll->window_widening_periodic_us *
elapsed;
if (lll->window_widening_prepare_us > lll->window_widening_max_us) {
lll->window_widening_prepare_us = lll->window_widening_max_us;
}
}
static void create_prepare_bh(void *param)
{
int err;
/* Invoke common pipeline handling of prepare */
err = lll_prepare(lll_is_abort_cb, lll_abort_cb, create_prepare_cb, 0U,
param);
LL_ASSERT(!err || err == -EINPROGRESS);
}
static void prepare_bh(void *param)
{
int err;
/* Invoke common pipeline handling of prepare */
err = lll_prepare(lll_is_abort_cb, lll_abort_cb, prepare_cb, 0U, param);
LL_ASSERT(!err || err == -EINPROGRESS);
}
static int create_prepare_cb(struct lll_prepare_param *p)
{
int err;
err = prepare_cb_common(p);
if (err) {
DEBUG_RADIO_START_O(1);
return 0;
}
radio_isr_set(isr_rx_estab, p->param);
DEBUG_RADIO_START_O(1);
return 0;
}
static int prepare_cb(struct lll_prepare_param *p)
{
int err;
err = prepare_cb_common(p);
if (err) {
DEBUG_RADIO_START_O(1);
return 0;
}
radio_isr_set(isr_rx, p->param);
DEBUG_RADIO_START_O(1);
return 0;
}
static int prepare_cb_common(struct lll_prepare_param *p)
{
struct node_rx_pdu *node_rx;
struct lll_sync_iso *lll;
uint32_t ticks_at_event;
uint32_t ticks_at_start;
uint16_t event_counter;
uint8_t access_addr[4];
uint16_t data_chan_id;
uint8_t data_chan_use;
uint32_t remainder_us;
uint8_t crc_init[3];
struct ull_hdr *ull;
uint32_t remainder;
uint32_t hcto;
uint8_t phy;
DEBUG_RADIO_START_O(1);
lll = p->param;
/* Deduce the latency */
lll->latency_event = lll->latency_prepare - 1U;
/* Calculate the current event counter value */
event_counter = (lll->payload_count / lll->bn) +
(lll->latency_event * lll->bn);
/* Update BIS packet counter to next value */
lll->payload_count += (lll->latency_prepare * lll->bn);
/* Reset accumulated latencies */
lll->latency_prepare = 0U;
/* Current window widening */
lll->window_widening_event_us += lll->window_widening_prepare_us;
lll->window_widening_prepare_us = 0U;
if (lll->window_widening_event_us > lll->window_widening_max_us) {
lll->window_widening_event_us = lll->window_widening_max_us;
}
/* Initialize to mandatory parameter values */
lll->bis_curr = 1U;
lll->ptc_curr = 0U;
lll->irc_curr = 1U;
lll->bn_curr = 1U;
/* Initialize control subevent flag */
lll->ctrl = 0U;
/* Initialize trx chain count */
trx_cnt = 0U;
/* Initialize anchor point CRC ok flag */
crc_ok_anchor = 0U;
/* Calculate the Access Address for the BIS event */
util_bis_aa_le32(lll->bis_curr, lll->seed_access_addr, access_addr);
data_chan_id = lll_chan_id(access_addr);
/* Calculate the CRC init value for the BIS event,
* preset with the BaseCRCInit value from the BIGInfo data the most
* significant 2 octets and the BIS_Number for the specific BIS in the
* least significant octet.
*/
crc_init[0] = lll->bis_curr;
(void)memcpy(&crc_init[1], lll->base_crc_init, sizeof(uint16_t));
/* Calculate the radio channel to use for ISO event */
data_chan_use = lll_chan_iso_event(event_counter, data_chan_id,
lll->data_chan_map,
lll->data_chan_count,
&lll->data_chan_prn_s,
&lll->data_chan_remap_idx);
lll->ctrl_chan_use = data_chan_use;
/* Start setting up of Radio h/w */
radio_reset();
#if defined(CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL)
radio_tx_power_set(lll->tx_pwr_lvl);
#else
radio_tx_power_set(RADIO_TXP_DEFAULT);
#endif
phy = lll->phy;
radio_phy_set(phy, PHY_FLAGS_S8);
radio_pkt_configure(RADIO_PKT_CONF_LENGTH_8BIT, lll->max_pdu,
RADIO_PKT_CONF_PHY(phy));
radio_aa_set(access_addr);
radio_crc_configure(PDU_CRC_POLYNOMIAL, sys_get_le24(crc_init));
lll_chan_set(data_chan_use);
node_rx = ull_iso_pdu_rx_alloc_peek(1U);
LL_ASSERT(node_rx);
radio_pkt_rx_set(node_rx->pdu);
radio_switch_complete_and_disable();
ticks_at_event = p->ticks_at_expire;
ull = HDR_LLL2ULL(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);
remainder = p->remainder;
remainder_us = radio_tmr_start(0U, ticks_at_start, remainder);
radio_tmr_ready_save(remainder_us);
radio_tmr_aa_save(0U);
radio_tmr_aa_capture();
hcto = remainder_us +
((EVENT_JITTER_US + EVENT_TICKER_RES_MARGIN_US +
lll->window_widening_event_us) << 1) +
lll->window_size_event_us;
hcto += radio_rx_ready_delay_get(lll->phy, PHY_FLAGS_S8);
hcto += addr_us_get(lll->phy);
hcto += radio_rx_chain_delay_get(lll->phy, PHY_FLAGS_S8);
radio_tmr_hcto_configure(hcto);
radio_tmr_end_capture();
radio_rssi_measure();
#if defined(HAL_RADIO_GPIO_HAVE_LNA_PIN)
radio_gpio_lna_setup();
radio_gpio_pa_lna_enable(remainder_us +
radio_rx_ready_delay_get(lll->phy, PHY_FLAGS_S8) -
HAL_RADIO_GPIO_LNA_OFFSET);
#endif /* HAL_RADIO_GPIO_HAVE_LNA_PIN */
if (0) {
#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_SCAN_SYNC_ISO_BASE +
ull_sync_iso_lll_handle_get(lll)),
ticks_at_event)) {
radio_isr_set(lll_isr_abort, lll);
radio_disable();
return -ECANCELED;
#endif /* CONFIG_BT_CTLR_XTAL_ADVANCED */
} else {
uint32_t ret;
ret = lll_prepare_done(lll);
LL_ASSERT(!ret);
}
return 0;
}
static void isr_rx_estab(void *param)
{
struct event_done_extra *e;
uint8_t trx_done;
uint8_t crc_ok;
/* Read radio status and events */
trx_done = radio_is_done();
if (trx_done) {
crc_ok = radio_crc_is_valid();
trx_cnt++;
} else {
crc_ok = 0U;
}
/* Clear radio rx status and events */
lll_isr_rx_status_reset();
/* Calculate and place the drift information in done event */
e = ull_event_done_extra_get();
LL_ASSERT(e);
e->type = EVENT_DONE_EXTRA_TYPE_SYNC_ISO_ESTAB;
e->trx_cnt = trx_cnt;
e->crc_valid = crc_ok;
if (trx_cnt) {
struct lll_sync_iso *lll;
lll = param;
e->drift.preamble_to_addr_us = addr_us_get(lll->phy);
e->drift.start_to_address_actual_us =
radio_tmr_aa_get() - radio_tmr_ready_get();
e->drift.window_widening_event_us =
lll->window_widening_event_us;
/* Reset window widening, as anchor point sync-ed */
lll->window_widening_event_us = 0U;
lll->window_size_event_us = 0U;
}
lll_isr_cleanup(param);
}
static void isr_rx(void *param)
{
struct node_rx_pdu *node_rx;
struct event_done_extra *e;
struct lll_sync_iso *lll;
uint8_t access_addr[4];
uint16_t data_chan_id;
uint8_t data_chan_use;
uint8_t payload_index;
uint8_t crc_init[3];
uint8_t rssi_ready;
uint32_t start_us;
uint8_t new_burst;
uint8_t trx_done;
uint8_t bis_idx;
uint8_t skipped;
uint8_t crc_ok;
uint32_t hcto;
uint8_t bis;
uint8_t bn;
/* Read radio status and events */
trx_done = radio_is_done();
if (!trx_done) {
/* Clear radio rx status and events */
lll_isr_rx_status_reset();
/* initialize LLL context reference */
lll = param;
/* BIS index */
bis_idx = lll->bis_curr - 1U;
goto isr_rx_done;
}
crc_ok = radio_crc_is_valid();
rssi_ready = radio_rssi_is_ready();
trx_cnt++;
/* Save the AA captured for the first anchor point sync */
if (!radio_tmr_aa_restore()) {
crc_ok_anchor = crc_ok;
radio_tmr_aa_save(radio_tmr_aa_get());
radio_tmr_ready_save(radio_tmr_ready_get());
}
/* Clear radio rx status and events */
lll_isr_rx_status_reset();
/* initialize LLL context reference */
lll = param;
/* BIS index */
bis_idx = lll->bis_curr - 1U;
/* Check CRC and generate ISO Data PDU */
if (crc_ok) {
struct pdu_bis *pdu;
/* Check if Control Subevent being received */
if ((lll->bn_curr == lll->bn) &&
(lll->irc_curr == lll->irc) &&
(lll->ptc_curr == lll->ptc) &&
(lll->bis_curr == lll->num_bis) &&
lll->ctrl) {
lll->cssn_curr = lll->cssn_next;
node_rx = ull_iso_pdu_rx_alloc_peek(1U);
LL_ASSERT(node_rx);
pdu = (void *)node_rx->pdu;
if (pdu->ll_id != PDU_BIS_LLID_CTRL) {
goto isr_rx_done;
}
isr_rx_ctrl_recv(lll, pdu);
} else {
node_rx = ull_iso_pdu_rx_alloc_peek(3U);
if (!node_rx) {
goto isr_rx_done;
}
}
pdu = (void *)node_rx->pdu;
/* Check for new control PDU in control subevent */
if (pdu->cstf && (pdu->cssn != lll->cssn_curr)) {
lll->cssn_next = pdu->cssn;
/* TODO: check same CSSN is used in every subevent */
}
payload_index = lll->payload_tail + (lll->bn_curr - 1U) +
(lll->ptc_curr * lll->pto);
if (payload_index >= lll->payload_count_max) {
payload_index -= lll->payload_count_max;
}
if (!lll->payload[bis_idx][payload_index] &&
((payload_index >= lll->payload_tail) ||
(payload_index < lll->payload_head))) {
struct node_rx_iso_meta *iso_meta;
ull_iso_pdu_rx_alloc();
node_rx->hdr.type = NODE_RX_TYPE_ISO_PDU;
node_rx->hdr.handle = lll->stream_handle[bis_idx];
iso_meta = &node_rx->hdr.rx_iso_meta;
iso_meta->payload_number = lll->payload_count +
(lll->bn_curr - 1U) +
(lll->ptc_curr * lll->pto) -
lll->bn;
iso_meta->timestamp =
HAL_TICKER_TICKS_TO_US(radio_tmr_start_get()) +
radio_tmr_aa_restore() - addr_us_get(lll->phy) +
(ceiling_fraction(lll->ptc_curr, lll->bn) *
lll->iso_interval * PERIODIC_INT_UNIT_US);
iso_meta->status = 0U;
lll->payload[bis_idx][payload_index] = node_rx;
}
}
isr_rx_done:
new_burst = 0U;
skipped = 0U;
isr_rx_find_subevent:
/* FIXME: Sequential or Interleaved BIS subevents decision */
/* Sequential Rx complete flow pseudo code */
while (lll->bn_curr < lll->bn) {
lll->bn_curr++;
payload_index = lll->payload_tail + (lll->bn_curr - 1U);
if (payload_index >= lll->payload_count_max) {
payload_index -= lll->payload_count_max;
}
if (!lll->payload[bis_idx][payload_index]) {
bis = lll->bis_curr;
/* Receive the (bn_curr)th Rx PDU of bis_curr */
goto isr_rx_next_subevent;
}
skipped++;
}
if (lll->irc_curr < lll->irc) {
if (!new_burst) {
lll->bn_curr = 1U;
lll->irc_curr++;
payload_index = lll->payload_tail;
if (payload_index >= lll->payload_count_max) {
payload_index -= lll->payload_count_max;
}
if (!lll->payload[bis_idx][payload_index]) {
bis = lll->bis_curr;
goto isr_rx_next_subevent;
} else {
skipped++;
new_burst = 1U;
/* Receive the missing (bn_curr)th Rx PDU of
* bis_curr
*/
goto isr_rx_find_subevent;
}
} else {
skipped += (lll->irc - lll->irc_curr) * lll->bn;
lll->irc_curr = lll->irc;
}
}
if (lll->ptc_curr < lll->ptc) {
lll->ptc_curr++;
/* Receive the (ptc_curr)th Rx PDU */
bis = lll->bis_curr;
goto isr_rx_next_subevent;
}
if (lll->bis_curr < lll->num_bis) {
lll->bis_curr++;
lll->ptc_curr = 0U;
lll->irc_curr = 1U;
lll->bn_curr = 1U;
/* Receive the (bn_curr)th PDU of bis_curr */
bis = lll->bis_curr;
goto isr_rx_next_subevent;
}
if (!lll->ctrl && (lll->cssn_next != lll->cssn_curr)) {
/* Receive the control PDU and close the BIG event
* there after.
*/
lll->ctrl = 1U;
/* control subevent to use bis = 0 and se_n = 1 */
bis = 0U;
goto isr_rx_next_subevent;
}
/* Enqueue PDUs to ULL */
node_rx = NULL;
payload_index = lll->payload_tail;
for (bis_idx = 0U; bis_idx < lll->num_bis; bis_idx++) {
uint8_t payload_tail;
payload_tail = lll->payload_tail;
bn = lll->bn;
while (bn--) {
if (lll->payload[bis_idx][payload_tail]) {
node_rx = lll->payload[bis_idx][payload_tail];
lll->payload[bis_idx][payload_tail] = NULL;
iso_rx_put(node_rx->hdr.link, node_rx);
}
payload_index = payload_tail + 1U;
if (payload_index >= lll->payload_count_max) {
payload_index = 0U;
}
payload_tail = payload_index;
}
}
lll->payload_tail = payload_index;
#if !defined(CONFIG_BT_CTLR_LOW_LAT_ULL)
if (node_rx) {
iso_rx_sched();
}
#endif /* CONFIG_BT_CTLR_LOW_LAT_ULL */
e = ull_event_done_extra_get();
LL_ASSERT(e);
/* Check if BIG terminate procedure received */
if (lll->term_reason) {
e->type = EVENT_DONE_EXTRA_TYPE_SYNC_ISO_TERMINATE;
lll_isr_cleanup(param);
return;
/* Check if BIG Channel Map Update */
} else if (lll->chm_chan_count) {
const uint16_t event_counter = lll->payload_count / lll->bn;
/* Bluetooth Core Specification v5.3 Vol 6, Part B,
* Section 5.5.2 BIG Control Procedures
*
* When a Synchronized Receiver receives such a PDU where
* (instant - bigEventCounter) mod 65536 is greater than or
* equal to 32767 (because the instant is in the past), the
* the Link Layer may stop synchronization with the BIG.
*/
/* Note: We are not validating whether the control PDU was
* received after the instant but apply the new channel map.
* If the channel map was new at or after the instant and the
* the channel at the event counter did not match then the
* control PDU would not have been received.
*/
if (((event_counter - lll->ctrl_instant) & 0xFFFF) <= 0x7FFF) {
(void)memcpy(lll->data_chan_map, lll->chm_chan_map,
sizeof(lll->data_chan_map));
lll->data_chan_count = lll->chm_chan_count;
lll->chm_chan_count = 0U;
}
}
/* Calculate and place the drift information in done event */
e->type = EVENT_DONE_EXTRA_TYPE_SYNC_ISO;
e->trx_cnt = trx_cnt;
e->crc_valid = crc_ok_anchor;
if (trx_cnt) {
e->drift.preamble_to_addr_us = addr_us_get(lll->phy);
e->drift.start_to_address_actual_us =
radio_tmr_aa_restore() - radio_tmr_ready_restore();
e->drift.window_widening_event_us =
lll->window_widening_event_us;
/* Reset window widening, as anchor point sync-ed */
lll->window_widening_event_us = 0U;
lll->window_size_event_us = 0U;
}
lll_isr_cleanup(param);
return;
isr_rx_next_subevent:
/* Calculate the Access Address for the BIS event */
util_bis_aa_le32(bis, lll->seed_access_addr, access_addr);
data_chan_id = lll_chan_id(access_addr);
/* Calculate the CRC init value for the BIS event,
* preset with the BaseCRCInit value from the BIGInfo data the most
* significant 2 octets and the BIS_Number for the specific BIS in the
* least significant octet.
*/
crc_init[0] = bis;
(void)memcpy(&crc_init[1], lll->base_crc_init, sizeof(uint16_t));
radio_aa_set(access_addr);
radio_crc_configure(PDU_CRC_POLYNOMIAL, sys_get_le24(crc_init));
/* Set the channel to use */
if (bis) {
do {
/* Calculate the radio channel to use for ISO event */
data_chan_use = lll_chan_iso_subevent(data_chan_id,
lll->data_chan_map,
lll->data_chan_count,
&lll->data_chan_prn_s,
&lll->data_chan_remap_idx);
} while (skipped--);
} else {
data_chan_use = lll->ctrl_chan_use;
}
lll_chan_set(data_chan_use);
node_rx = ull_iso_pdu_rx_alloc_peek(1U);
LL_ASSERT(node_rx);
radio_pkt_rx_set(node_rx->pdu);
radio_switch_complete_and_disable();
/* PDU Header Complete TimeOut, calculate the absolute timeout in
* microseconds by when a PDU header is to be received for each
* subevent.
*/
hcto = (lll->sub_interval *
(((lll->bis_curr - 1U) * ((lll->bn * lll->irc) + lll->ptc)) +
(((lll->irc_curr - 1U) * lll->bn) + (lll->bn_curr - 1U) +
lll->ptc_curr) + lll->ctrl));
if (trx_cnt) {
/* Setup radio packet timer header complete timeout for
* subsequent subevent PDU.
*/
/* Calculate the radio start with consideration of the drift
* based on the access address capture timestamp.
* Listen early considering +/- 2 us active clock jitter, i.e.
* listen early by 4 us.
*/
hcto += radio_tmr_aa_restore();
hcto -= radio_rx_chain_delay_get(lll->phy, PHY_FLAGS_S8);
hcto -= addr_us_get(lll->phy);
hcto -= radio_rx_ready_delay_get(lll->phy, PHY_FLAGS_S8);
hcto -= (EVENT_CLOCK_JITTER_US << 1);
start_us = hcto;
radio_tmr_start_us(0, start_us);
/* Add 4 us + 4 us, as radio was setup to listen 4 us early */
hcto += (EVENT_CLOCK_JITTER_US << 2);
} else {
/* Setup radio packet timer header complete timeout for
* first subevent PDU which is the BIG event anchor point.
*/
hcto += radio_tmr_ready_restore();
start_us = hcto;
radio_tmr_start_us(0U, start_us);
hcto += ((EVENT_JITTER_US + EVENT_TICKER_RES_MARGIN_US +
lll->window_widening_event_us) << 1) +
lll->window_size_event_us;
}
/* header complete timeout to consider the radio ready delay, chain
* delay and access address duration.
*/
hcto += radio_rx_ready_delay_get(lll->phy, PHY_FLAGS_S8);
hcto += addr_us_get(lll->phy);
hcto += radio_rx_chain_delay_get(lll->phy, PHY_FLAGS_S8);
/* setup absolute PDU header reception timeout */
radio_tmr_hcto_configure(hcto);
/* setup capture of PDU end timestamp */
radio_tmr_end_capture();
#if defined(HAL_RADIO_GPIO_HAVE_LNA_PIN)
radio_gpio_lna_setup();
radio_gpio_pa_lna_enable(start_us +
radio_rx_ready_delay_get(lll->phy,
PHY_FLAGS_S8) -
HAL_RADIO_GPIO_LNA_OFFSET);
#endif /* HAL_RADIO_GPIO_HAVE_LNA_PIN */
}
static void isr_rx_ctrl_recv(struct lll_sync_iso *lll, struct pdu_bis *pdu)
{
const uint8_t opcode = pdu->ctrl.opcode;
if (opcode == PDU_BIG_CTRL_TYPE_TERM_IND) {
if (!lll->term_reason) {
struct pdu_big_ctrl_term_ind *term;
term = (void *)&pdu->ctrl.term_ind;
lll->term_reason = term->reason;
lll->ctrl_instant = term->instant;
}
} else if (opcode == PDU_BIG_CTRL_TYPE_CHAN_MAP_IND) {
if (!lll->chm_chan_count) {
struct pdu_big_ctrl_chan_map_ind *chm;
uint8_t chan_count;
chm = (void *)&pdu->ctrl.chan_map_ind;
chan_count =
util_ones_count_get(chm->chm, sizeof(chm->chm));
if (chan_count >= CHM_USED_COUNT_MIN) {
lll->chm_chan_count = chan_count;
(void)memcpy(lll->chm_chan_map, chm->chm,
sizeof(lll->chm_chan_map));
lll->ctrl_instant = chm->instant;
}
}
} else {
/* Unknown control PDU, ignore */
}
}