diff --git a/subsys/bluetooth/controller/Kconfig.df b/subsys/bluetooth/controller/Kconfig.df index 46f1434931b1f..4e161da7a0e0b 100644 --- a/subsys/bluetooth/controller/Kconfig.df +++ b/subsys/bluetooth/controller/Kconfig.df @@ -146,6 +146,16 @@ config BT_CTLR_DF_INIT_ANT_SEL_GPIOS In such case application is responsible for appropriate GPIOs initialization. +config BT_CTLR_DF_PER_ADV_CTE_NUM_MAX + int "Maximum number of transmitted PDUs with Constant Tone Extension in connectionless mode" + depends on BT_CTLR_DF_ADV_CTE_TX && BT_CTLR_ADV_PDU_LINK + range 1 16 + default 1 + help + Maximum supported number of PDUs, that have Constant Tone Extension, transmitted in single + periodic advertising chain. The range is taken from BT Core spec 5.1, Vol 4 Part E + section 7.8.82 HCI_LE_Set_Connectionless_IQ_Sampling_Enable Max_Sampled_CTEs parameter. + config BT_CTLR_DF_DEBUG_ENABLE bool "Bluetooth Direction Finding debug support enable" help diff --git a/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/radio/radio.c b/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/radio/radio.c index db0abfb556aee..1d1910b487682 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/radio/radio.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/radio/radio.c @@ -21,6 +21,8 @@ #include "ll_sw/pdu.h" +#include "radio_internal.h" + #if defined(CONFIG_BT_CTLR_GPIO_PA_PIN) #if ((CONFIG_BT_CTLR_GPIO_PA_PIN) > 31) #define NRF_GPIO_PA NRF_P1 @@ -490,19 +492,10 @@ void *radio_pkt_decrypt_get(void) #if !defined(CONFIG_BT_CTLR_TIFS_HW) -#define SW_SWITCH_PREV_RX 0 -#define SW_SWITCH_NEXT_RX 0 -#define SW_SWITCH_PREV_TX 1 -#define SW_SWITCH_NEXT_TX 1 -#define SW_SWITCH_PREV_PHY_1M 0 -#define SW_SWITCH_PREV_FLAGS_DONTCARE 0 -#define SW_SWITCH_NEXT_FLAGS_DONTCARE 0 - static uint8_t sw_tifs_toggle; -static inline void sw_switch(uint8_t dir_curr, uint8_t dir_next, - uint8_t phy_curr, uint8_t flags_curr, - uint8_t phy_next, uint8_t flags_next) +void sw_switch(uint8_t dir_curr, uint8_t dir_next, uint8_t phy_curr, uint8_t flags_curr, + uint8_t phy_next, uint8_t flags_next) { uint8_t ppi = HAL_SW_SWITCH_RADIO_ENABLE_PPI(sw_tifs_toggle); uint8_t cc = SW_SWITCH_TIMER_EVTS_COMP(sw_tifs_toggle); @@ -511,8 +504,8 @@ static inline void sw_switch(uint8_t dir_curr, uint8_t dir_next, hal_radio_sw_switch_setup(cc, ppi, sw_tifs_toggle); /* NOTE: As constants are passed to dir_curr and dir_next, the - * compiler should optimize out the redundant code path as - * this is an inline function. + * compiler should optimize out the redundant code path + * during the optimization. */ if (dir_next) { /* TX */ @@ -649,9 +642,8 @@ void radio_switch_complete_and_rx(uint8_t phy_rx) * across nRF5x radios, sw_switch assumes the 1M chain delay for * calculations. */ - sw_switch(SW_SWITCH_PREV_TX, SW_SWITCH_NEXT_RX, - SW_SWITCH_PREV_PHY_1M, SW_SWITCH_PREV_FLAGS_DONTCARE, - phy_rx, SW_SWITCH_NEXT_FLAGS_DONTCARE); + sw_switch(SW_SWITCH_TX, SW_SWITCH_RX, SW_SWITCH_PHY_1M, SW_SWITCH_FLAGS_DONTCARE, phy_rx, + SW_SWITCH_FLAGS_DONTCARE); #endif /* !CONFIG_BT_CTLR_TIFS_HW */ } @@ -666,8 +658,7 @@ void radio_switch_complete_and_tx(uint8_t phy_rx, uint8_t flags_rx, NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_END_DISABLE_Msk; - sw_switch(SW_SWITCH_PREV_RX, SW_SWITCH_NEXT_TX, - phy_rx, flags_rx, phy_tx, flags_tx); + sw_switch(SW_SWITCH_RX, SW_SWITCH_TX, phy_rx, flags_rx, phy_tx, flags_tx); #endif /* !CONFIG_BT_CTLR_TIFS_HW */ } @@ -682,8 +673,7 @@ void radio_switch_complete_and_b2b_tx(uint8_t phy_curr, uint8_t flags_curr, NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_END_DISABLE_Msk; - sw_switch(SW_SWITCH_PREV_TX, SW_SWITCH_NEXT_TX, - phy_curr, flags_curr, phy_next, flags_next); + sw_switch(SW_SWITCH_TX, SW_SWITCH_TX, phy_curr, flags_curr, phy_next, flags_next); #endif /* !CONFIG_BT_CTLR_TIFS_HW */ } diff --git a/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/radio/radio_df.c b/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/radio/radio_df.c index 5aabbd256c2db..ecb33ca672230 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/radio/radio_df.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/radio/radio_df.c @@ -13,6 +13,7 @@ #include "radio_nrf5.h" #include "radio_df.h" +#include "radio_internal.h" /* @brief Minimum antennas number required if antenna switching is enabled */ #define DF_ANT_NUM_MIN 2 @@ -315,7 +316,7 @@ void radio_df_ant_switch_pattern_clear(void) NRF_RADIO->CLEARPATTERN = RADIO_CLEARPATTERN_CLEARPATTERN_Clear; } -void radio_df_ant_switch_pattern_set(uint8_t *patterns, uint8_t len) +void radio_df_ant_switch_pattern_set(const uint8_t *patterns, uint8_t len) { /* SWITCHPATTERN is like a moving pointer to underlying buffer. * Each write stores a value and moves the pointer to new free position. @@ -359,6 +360,18 @@ void radio_switch_complete_and_phy_end_disable(void) #endif /* !CONFIG_BT_CTLR_TIFS_HW */ } +void radio_switch_complete_and_phy_end_b2b_tx(uint8_t phy_curr, uint8_t flags_curr, + uint8_t phy_next, uint8_t flags_next) +{ +#if defined(CONFIG_BT_CTLR_TIFS_HW) + NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_PHYEND_DISABLE_Msk | + RADIO_SHORTS_DISABLED_TXEN_Msk; +#else /* !CONFIG_BT_CTLR_TIFS_HW */ + NRF_RADIO->SHORTS = RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_PHYEND_DISABLE_Msk; + sw_switch(SW_SWITCH_TX, SW_SWITCH_TX, phy_curr, flags_curr, phy_next, flags_next); +#endif /* !CONFIG_BT_CTLR_TIFS_HW */ +} + void radio_df_iq_data_packet_set(uint8_t *buffer, size_t len) { nrf_radio_dfe_buffer_set(NRF_RADIO, (uint32_t *)buffer, len); diff --git a/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/radio/radio_df.h b/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/radio/radio_df.h index 8a13b6667237f..84934fde60daa 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/radio/radio_df.h +++ b/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/radio/radio_df.h @@ -39,7 +39,7 @@ void radio_df_ant_switch_pattern_clear(void); * Radio internal list. Before start of new patterns clear the list * by call to @ref radio_df_ant_switch_pattern_clear. */ -void radio_df_ant_switch_pattern_set(uint8_t *patterns, uint8_t len); +void radio_df_ant_switch_pattern_set(const uint8_t *patterns, uint8_t len); /* Provides switch pattern of antenna used to transmit PDU that is used to * transmit CTE */ @@ -50,6 +50,10 @@ void radio_df_reset(void); /* Completes switching and enables shortcut between PHYEND and DISABLE events */ void radio_switch_complete_and_phy_end_disable(void); +/* Completes switching and enables shortcut between PHYEND and TXEN events */ +void radio_switch_complete_and_phy_end_b2b_tx(uint8_t phy_curr, uint8_t flags_curr, + uint8_t phy_next, uint8_t flags_next); + /* Enables CTE inline configuration to automatically setup sampling and * switching according to CTEInfo in received PDU. */ diff --git a/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/radio/radio_internal.h b/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/radio/radio_internal.h new file mode 100644 index 0000000000000..ad1bd3936c11f --- /dev/null +++ b/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/radio/radio_internal.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define SW_SWITCH_RX 0 +#define SW_SWITCH_TX 1 +#define SW_SWITCH_PHY_1M 0 +#define SW_SWITCH_FLAGS_DONTCARE 0 + +void sw_switch(uint8_t dir_curr, uint8_t dir_next, uint8_t phy_curr, uint8_t flags_curr, + uint8_t phy_next, uint8_t flags_next); diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c index ea960b5a6c519..b07a037437284 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv.c @@ -82,9 +82,10 @@ static inline bool isr_rx_ci_adva_check(uint8_t tx_addr, uint8_t *addr, struct pdu_adv *ci); #if defined(CONFIG_BT_CTLR_ADV_EXT) -#define PAYLOAD_FRAG_COUNT ((CONFIG_BT_CTLR_ADV_DATA_LEN_MAX + \ - PDU_AC_PAYLOAD_SIZE_MAX - 1) / \ - PDU_AC_PAYLOAD_SIZE_MAX) +#define PAYLOAD_BASED_FRAG_COUNT ((CONFIG_BT_CTLR_ADV_DATA_LEN_MAX + \ + PDU_AC_PAYLOAD_SIZE_MAX - 1) / \ + PDU_AC_PAYLOAD_SIZE_MAX) +#define PAYLOAD_FRAG_COUNT MAX(PAYLOAD_BASED_FRAG_COUNT, BT_CTLR_DF_PER_ADV_CTE_NUM_MAX) #define BT_CTLR_ADV_AUX_SET CONFIG_BT_CTLR_ADV_AUX_SET #if defined(CONFIG_BT_CTLR_ADV_PERIODIC) #define BT_CTLR_ADV_SYNC_SET CONFIG_BT_CTLR_ADV_SYNC_SET @@ -493,13 +494,36 @@ struct pdu_adv *lll_adv_pdu_and_extra_data_latest_get(struct lll_adv_pdu *pdu, uint8_t pdu_idx; void *p; - if (!MFIFO_ENQUEUE_IDX_GET(pdu_free, &pdu_free_idx)) { - return NULL; - } - pdu_idx = first; + p = pdu->pdu[pdu_idx]; ed = pdu->extra_data[pdu_idx]; + do { + void *next; + + /* Store partial list in current data index if there is + * no free slot in mfifo. It can be released on next + * switch attempt (on next event). + */ + if (!MFIFO_ENQUEUE_IDX_GET(pdu_free, &pdu_free_idx)) { + pdu->pdu[pdu_idx] = p; + return NULL; + } + +#if defined(CONFIG_BT_CTLR_ADV_PDU_LINK) + next = lll_adv_pdu_linked_next_get(p); +#else + next = NULL; +#endif + + MFIFO_BY_IDX_ENQUEUE(pdu_free, pdu_free_idx, p); + k_sem_give(&sem_pdu_free); + + p = next; + } while (p); + + pdu->pdu[pdu_idx] = NULL; + if (ed && (!MFIFO_ENQUEUE_IDX_GET(extra_data_free, &ed_free_idx))) { /* No pdu_free_idx clean up is required, sobsequent @@ -516,12 +540,8 @@ struct pdu_adv *lll_adv_pdu_and_extra_data_latest_get(struct lll_adv_pdu *pdu, pdu->first = first; *is_modified = 1U; - p = pdu->pdu[pdu_idx]; pdu->pdu[pdu_idx] = NULL; - MFIFO_BY_IDX_ENQUEUE(pdu_free, pdu_free_idx, p); - k_sem_give(&sem_pdu_free); - if (ed) { pdu->extra_data[pdu_idx] = NULL; diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv_pdu.h b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv_pdu.h index 3d314a12668b7..2e8dc0081ef19 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv_pdu.h +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv_pdu.h @@ -138,6 +138,13 @@ static inline struct pdu_adv *lll_adv_sync_data_peek(struct lll_adv_sync *lll, return (void *)lll->data.pdu[last]; } + +#if defined(CONFIG_BT_CTLR_ADV_EXT_PDU_EXTRA_DATA_MEMORY) +static inline void *lll_adv_sync_extra_data_peek(struct lll_adv_sync *lll) +{ + return lll->data.extra_data[lll->data.last]; +} +#endif /* CONFIG_BT_CTLR_ADV_EXT_PDU_EXTRA_DATA_MEMORY */ #endif /* CONFIG_BT_CTLR_ADV_PERIODIC */ #endif /* CONFIG_BT_CTLR_ADV_EXT */ diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv_sync.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv_sync.c index 59f85e389aef2..b988a74af3dfa 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv_sync.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_adv_sync.c @@ -42,19 +42,23 @@ #include "hal/debug.h" #if defined(CONFIG_BT_CTLR_ADV_SYNC_PDU_BACK2BACK) -#define ADV_SYNC_PDU_B2B_AFS (CONFIG_BT_CTLR_ADV_SYNC_PDU_BACK2BACK_AFS) +#define ADV_SYNC_PDU_B2B_AFS (CONFIG_BT_CTLR_ADV_SYNC_PDU_BACK2BACK_AFS) +#else +#define ADV_SYNC_PDU_B2B_AFS 0 #endif static int init_reset(void); static int prepare_cb(struct lll_prepare_param *p); static void abort_cb(struct lll_prepare_param *prepare_param, void *param); static void isr_done(void *param); +static void switch_radio_complete_and_phy_end_disable(const struct lll_adv_sync *lll); + #if defined(CONFIG_BT_CTLR_ADV_SYNC_PDU_BACK2BACK) static void isr_tx(void *param); -static void pdu_b2b_update(struct lll_adv_sync *lll, struct pdu_adv *pdu); -static void pdu_b2b_aux_ptr_update(struct pdu_adv *pdu, uint8_t phy, - uint8_t flags, uint8_t chan_idx, - uint32_t tifs); +static void pdu_b2b_update(struct lll_adv_sync *lll, struct pdu_adv *pdu, uint32_t cte_len_us); +static void pdu_b2b_aux_ptr_update(struct pdu_adv *pdu, uint8_t phy, uint8_t flags, + uint8_t chan_idx, uint32_t offset_us, uint32_t cte_len_us); +static void switch_radio_complete_and_b2b_tx(const struct lll_adv_sync *lll, uint8_t phy_s); #endif /* CONFIG_BT_CTLR_ADV_SYNC_PDU_BACK2BACK */ int lll_adv_sync_init(void) @@ -100,9 +104,6 @@ static int init_reset(void) static int prepare_cb(struct lll_prepare_param *p) { -#if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX) - struct lll_df_adv_cfg *df_cfg; -#endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */ struct lll_adv_sync *lll; uint32_t ticks_at_event; uint32_t ticks_at_start; @@ -110,9 +111,9 @@ static int prepare_cb(struct lll_prepare_param *p) uint8_t data_chan_use; struct pdu_adv *pdu; struct ull_hdr *ull; + uint32_t cte_len_us; uint32_t remainder; uint32_t start_us; - void *extra_data; uint8_t phy_s; uint8_t upd; @@ -157,30 +158,23 @@ static int prepare_cb(struct lll_prepare_param *p) ((uint32_t)lll->crc_init[0]))); lll_chan_set(data_chan_use); - pdu = lll_adv_sync_data_latest_get(lll, &extra_data, &upd); + pdu = lll_adv_sync_data_latest_get(lll, NULL, &upd); LL_ASSERT(pdu); +#if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX) + lll_df_cte_tx_enable(lll, pdu, &cte_len_us); +#else + cte_len_us = 0U; +#endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX) */ + #if defined(CONFIG_BT_CTLR_ADV_SYNC_PDU_BACK2BACK) if (upd) { /* AuxPtr offsets for b2b TX are fixed for given chain so we can * calculate them here in advance. */ - pdu_b2b_update(lll, pdu); + pdu_b2b_update(lll, pdu, cte_len_us); } #endif - -#if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX) - if (extra_data) { - df_cfg = (struct lll_df_adv_cfg *)extra_data; - lll_df_conf_cte_tx_enable(df_cfg->cte_type, df_cfg->cte_length, - df_cfg->ant_sw_len, df_cfg->ant_ids); - lll->cte_started = 1U; - } else { - df_cfg = NULL; - lll->cte_started = 0U; - } -#endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */ - radio_pkt_tx_set(pdu); #if defined(CONFIG_BT_CTLR_ADV_SYNC_PDU_BACK2BACK) @@ -188,21 +182,13 @@ static int prepare_cb(struct lll_prepare_param *p) lll->last_pdu = pdu; radio_isr_set(isr_tx, lll); - radio_tmr_tifs_set(ADV_SYNC_PDU_B2B_AFS); - radio_switch_complete_and_b2b_tx(phy_s, 0, phy_s, 0); -#else - if (0) { + radio_tmr_tifs_set(ADV_SYNC_PDU_B2B_AFS + cte_len_us); + switch_radio_complete_and_b2b_tx(lll, phy_s); + } else #endif /* CONFIG_BT_CTLR_ADV_SYNC_PDU_BACK2BACK */ - } else { + { radio_isr_set(isr_done, lll); -#if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX) - if (df_cfg) { - radio_switch_complete_and_phy_end_disable(); - } else -#endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */ - { - radio_switch_complete_and_disable(); - } + switch_radio_complete_and_phy_end_disable(lll); } ticks_at_event = p->ticks_at_expire; @@ -294,6 +280,7 @@ static void isr_tx(void *param) struct lll_adv_sync *lll_sync; struct pdu_adv *pdu; struct lll_adv *lll; + uint32_t cte_len_us; if (IS_ENABLED(CONFIG_BT_CTLR_PROFILE_ISR)) { lll_prof_latency_capture(); @@ -312,21 +299,20 @@ static void isr_tx(void *param) LL_ASSERT(pdu); lll_sync->last_pdu = pdu; +#if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX) + lll_df_cte_tx_enable(lll_sync, pdu, &cte_len_us); +#else + cte_len_us = 0; +#endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */ + /* setup tIFS switching */ if (pdu->adv_ext_ind.ext_hdr_len && pdu->adv_ext_ind.ext_hdr.aux_ptr) { - radio_tmr_tifs_set(ADV_SYNC_PDU_B2B_AFS); + radio_tmr_tifs_set(ADV_SYNC_PDU_B2B_AFS + cte_len_us); radio_isr_set(isr_tx, lll_sync); - radio_switch_complete_and_b2b_tx(lll->phy_s, 0, lll->phy_s, 0); + switch_radio_complete_and_b2b_tx(lll_sync, lll->phy_s); } else { - radio_isr_set(lll_isr_done, lll); -#if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX) - if (lll_sync->cte_started) { - radio_switch_complete_and_phy_end_disable(); - } else -#endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */ - { - radio_switch_complete_and_disable(); - } + radio_isr_set(isr_done, lll); + switch_radio_complete_and_phy_end_disable(lll_sync); } radio_pkt_tx_set(pdu); @@ -352,8 +338,7 @@ static void isr_tx(void *param) } radio_gpio_lna_setup(); - radio_gpio_pa_lna_enable(radio_tmr_tifs_base_get() + - ADV_SYNC_PDU_B2B_AFS - 4 - + radio_gpio_pa_lna_enable(radio_tmr_tifs_base_get() + ADV_SYNC_PDU_B2B_AFS - 4 + cte_len_us - radio_tx_chain_delay_get(lll->phy_s, 0) - CONFIG_BT_CTLR_GPIO_LNA_OFFSET); #endif /* CONFIG_BT_CTLR_GPIO_LNA_PIN */ @@ -363,23 +348,21 @@ static void isr_tx(void *param) } } -static void pdu_b2b_update(struct lll_adv_sync *lll, struct pdu_adv *pdu) +static void pdu_b2b_update(struct lll_adv_sync *lll, struct pdu_adv *pdu, uint32_t cte_len_us) { while (pdu) { - pdu_b2b_aux_ptr_update(pdu, lll->adv->phy_s, 0, 0, - ADV_SYNC_PDU_B2B_AFS); + pdu_b2b_aux_ptr_update(pdu, lll->adv->phy_s, 0, 0, ADV_SYNC_PDU_B2B_AFS, + cte_len_us); pdu = lll_adv_pdu_linked_next_get(pdu); } } -static void pdu_b2b_aux_ptr_update(struct pdu_adv *pdu, uint8_t phy, - uint8_t flags, uint8_t chan_idx, - uint32_t tifs) +static void pdu_b2b_aux_ptr_update(struct pdu_adv *pdu, uint8_t phy, uint8_t flags, + uint8_t chan_idx, uint32_t offset_us, uint32_t cte_len_us) { struct pdu_adv_com_ext_adv *com_hdr; struct pdu_adv_ext_hdr *hdr; struct pdu_adv_aux_ptr *aux; - uint32_t offs; uint8_t *dptr; com_hdr = &pdu->adv_ext_ind; @@ -402,17 +385,47 @@ static void pdu_b2b_aux_ptr_update(struct pdu_adv *pdu, uint8_t phy, /* Update AuxPtr */ aux = (void *)dptr; - offs = PKT_AC_US(pdu->len, phy) + tifs; - offs = offs / OFFS_UNIT_30_US; - if ((offs >> 13) != 0) { - aux->offs = offs / (OFFS_UNIT_300_US / OFFS_UNIT_30_US); + offset_us += PKT_AC_US(pdu->len, phy); + /* Add CTE length to PDUs that have CTE attached. + * Periodic advertising chain may include PDUs without CTE. + */ + if (hdr->cte_info) { + offset_us += cte_len_us; + } + offset_us = offset_us / OFFS_UNIT_30_US; + if ((offset_us >> 13) != 0) { + aux->offs = offset_us / (OFFS_UNIT_300_US / OFFS_UNIT_30_US); aux->offs_units = 1U; } else { - aux->offs = offs; + aux->offs = offset_us; aux->offs_units = 0U; } aux->chan_idx = chan_idx; aux->ca = 0; aux->phy = find_lsb_set(phy) - 1; } + +static void switch_radio_complete_and_b2b_tx(const struct lll_adv_sync *lll, uint8_t phy_s) +{ +#if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX) + if (lll->cte_started) { + radio_switch_complete_and_phy_end_b2b_tx(phy_s, 0, phy_s, 0); + } else +#endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */ + { + radio_switch_complete_and_b2b_tx(phy_s, 0, phy_s, 0); + } +} #endif /* CONFIG_BT_CTLR_ADV_SYNC_PDU_BACK2BACK */ + +static void switch_radio_complete_and_phy_end_disable(const struct lll_adv_sync *lll) +{ +#if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX) + if (lll->cte_started) { + radio_switch_complete_and_phy_end_disable(); + } else +#endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */ + { + radio_switch_complete_and_disable(); + } +} diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_df.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_df.c index af5222af70586..eb4fc27b59b77 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_df.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_df.c @@ -9,6 +9,7 @@ #include "util/util.h" #include "util/memq.h" +#include "util/mem.h" #include "hal/cpu.h" #include "hal/radio_df.h" @@ -16,6 +17,9 @@ #include "pdu.h" #include "lll.h" +#include "lll_adv_types.h" +#include "lll_adv.h" +#include "lll_adv_pdu.h" #include "lll_df.h" #include "lll_df_types.h" #include "lll_df_internal.h" @@ -26,13 +30,10 @@ #include #include "hal/debug.h" -/* @brief Function performs common steps for initialization and reset - * of Direction Finding LLL module. - * - * @return Zero in case of success, other value in case of failure. - */ static int init_reset(void); - +#if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX) +static void df_cte_tx_configure(const struct lll_df_adv_cfg *df_cfg); +#endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */ /* @brief Function performs Direction Finding initialization * * @return Zero in case of success, other value in case of failure. @@ -65,44 +66,60 @@ uint8_t lll_df_ant_num_get(void) return radio_df_ant_num_get(); } -/* @brief Function initializes transmission of Constant Tone Extension. - * - * @param[in] type Type of CTE: AoA, AoD 1us, AoD 2us - * @param[in] length Duration of CTE in 8us units - * @param[in] ant_num Number of antennas in switch pattern - * @param[in] ant_ids Antenna identifiers that create switch pattern. +#if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX) +/* @brief Function enables transmission of Constant Tone Extension. * - * @Note Pay attention that first two antenna identifiers in a switch - * pattern has special purpose. First one is used in guard period, second - * in reference period. Actual switching is processed from third antenna. - * - * In case of AoA mode ant_num and ant_ids parameters are not used. + * @param lll_sync Pointer to LLL sync. object associated with + * periodic advertising event. + * @param pdu Pointer to PDU that will be transmitted. + * @param[out] cte_len_us Pointer to store actual CTE length in [us] */ -void lll_df_conf_cte_tx_enable(uint8_t type, uint8_t length, - uint8_t ant_num, uint8_t *ant_ids) +void lll_df_cte_tx_enable(struct lll_adv_sync *lll_sync, const struct pdu_adv *pdu, + uint32_t *cte_len_us) { - if (type == BT_HCI_LE_AOA_CTE) { - radio_df_mode_set_aoa(); - radio_df_cte_tx_aoa_set(length); - } -#if defined(CONFIG_BT_CTLR_DF_ANT_SWITCH_TX) || \ - defined(CONFIG_BT_CTLR_DF_ANT_SWITCH_RX) - else { - radio_df_mode_set_aod(); + if (pdu->adv_ext_ind.ext_hdr_len) { + const struct pdu_adv_ext_hdr *ext_hdr; + + ext_hdr = &pdu->adv_ext_ind.ext_hdr; - if (type == BT_HCI_LE_AOD_CTE_1US) { - radio_df_cte_tx_aod_2us_set(length); + /* Check if particular extended PDU has cte_info. It is possible that + * not all PDUs in a periodic advertising chain have CTE attached. + * Nevertheless whole periodic advertising chain has the same CTE + * configuration. Checking for existence of CTE configuration in lll_sync + * is not enough. + */ + if (ext_hdr->cte_info) { + const struct lll_df_adv_cfg *df_cfg; + + df_cfg = lll_adv_sync_extra_data_peek(lll_sync); + LL_ASSERT(df_cfg); + + df_cte_tx_configure(df_cfg); + + lll_sync->cte_started = 1U; + *cte_len_us = CTE_LEN_US(df_cfg->cte_length); } else { - radio_df_cte_tx_aod_4us_set(length); + if (lll_sync->cte_started) { + lll_df_conf_cte_tx_disable(); + lll_sync->cte_started = 0U; + } + *cte_len_us = 0U; } - - radio_df_ant_switching_pin_sel_cfg(); - radio_df_ant_switch_pattern_clear(); - radio_df_ant_switch_pattern_set(ant_ids, ant_num); + } else { + if (lll_sync->cte_started) { + lll_df_conf_cte_tx_disable(); + lll_sync->cte_started = 0U; + } + *cte_len_us = 0U; } -#endif /* CONFIG_BT_CTLR_DF_ANT_SWITCH_TX || CONFIG_BT_CTLR_DF_ANT_SWITCH_RX */ } +void lll_df_conf_cte_tx_disable(void) +{ + radio_df_reset(); +} +#endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */ + #if defined(CONFIG_BT_CTLR_DF_SCAN_CTE_RX) struct lll_df_sync_cfg *lll_df_sync_cfg_alloc(struct lll_df_sync *df_cfg, uint8_t *idx) @@ -161,14 +178,7 @@ struct lll_df_sync_cfg *lll_df_sync_cfg_latest_get(struct lll_df_sync *df_cfg, return &df_cfg->cfg[first]; } -#endif /* CONFIG_BT_CTLR_DF_SCAN_CTE_RX */ -void lll_df_conf_cte_tx_disable(void) -{ - radio_df_reset(); -} - -#if defined(CONFIG_BT_CTLR_DF_SCAN_CTE_RX) /* @brief Function initializes reception of Constant Tone Extension. * * @param slot_duration Switching and sampling slots duration (1us or 2us). @@ -207,7 +217,47 @@ void lll_df_conf_cte_rx_enable(uint8_t slot_duration, uint8_t ant_num, uint8_t * } #endif /* CONFIG_BT_CTLR_DF_SCAN_CTE_RX */ +/* @brief Function performs common steps for initialization and reset + * of Direction Finding LLL module. + * + * @return Zero in case of success, other value in case of failure. + */ static int init_reset(void) { return 0; } + +#if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX) +/* @brief Function initializes transmission of Constant Tone Extension. + * + * @param df_cfg Pointer to direction finding configuration + * + * @Note Pay attention that first two antenna identifiers in a switch + * pattern df_cfg->ant_ids has special purpose. First one is used in guard period, + * second in reference period. Actual switching is processed from third antenna. + * + * In case of AoA mode df_ant_sw_len and df->ant_ids members are not used. + */ +static void df_cte_tx_configure(const struct lll_df_adv_cfg *df_cfg) +{ + if (df_cfg->cte_type == BT_HCI_LE_AOA_CTE) { + radio_df_mode_set_aoa(); + radio_df_cte_tx_aoa_set(df_cfg->cte_length); + } +#if defined(CONFIG_BT_CTLR_DF_ANT_SWITCH_TX) + else { + radio_df_mode_set_aod(); + + if (df_cfg->cte_type == BT_HCI_LE_AOD_CTE_1US) { + radio_df_cte_tx_aod_2us_set(df_cfg->cte_length); + } else { + radio_df_cte_tx_aod_4us_set(df_cfg->cte_length); + } + + radio_df_ant_switching_pin_sel_cfg(); + radio_df_ant_switch_pattern_clear(); + radio_df_ant_switch_pattern_set(df_cfg->ant_ids, df_cfg->ant_sw_len); + } +#endif /* CONFIG_BT_CTLR_DF_ANT_SWITCH_TX */ +} +#endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */ diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_df_internal.h b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_df_internal.h index cf9f94df567fd..600208646de83 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_df_internal.h +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_df_internal.h @@ -4,9 +4,12 @@ * SPDX-License-Identifier: Apache-2.0 */ +/* Forward declaration to avoid unnecessary includes. */ +struct lll_adv_sync; + /* Enables CTE transmission according to provided configuration */ -void lll_df_conf_cte_tx_enable(uint8_t type, uint8_t length, - uint8_t ant_num, uint8_t *ant_ids); +void lll_df_cte_tx_enable(struct lll_adv_sync *lll_sync, const struct pdu_adv *pdu, + uint32_t *cte_len_us); /* Disables CTE transmission */ void lll_df_conf_cte_tx_disable(void); diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_df_types.h b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_df_types.h index a5b3de7b0db9b..9da19883cba2f 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_df_types.h +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_df_types.h @@ -22,6 +22,12 @@ #define BT_CTLR_DF_MAX_ANT_SW_PATTERN_LEN 0 #endif +#if defined(CONFIG_BT_CTLR_DF_PER_ADV_CTE_NUM_MAX) +#define BT_CTLR_DF_PER_ADV_CTE_NUM_MAX CONFIG_BT_CTLR_DF_PER_ADV_CTE_NUM_MAX +#else +#define BT_CTLR_DF_PER_ADV_CTE_NUM_MAX 0 +#endif + /* @brief Configuration of Constant Tone Extension for connectionless * transmission. */ diff --git a/subsys/bluetooth/controller/ll_sw/pdu.h b/subsys/bluetooth/controller/ll_sw/pdu.h index b9ab8188a9d87..1a5b7a6d8a0df 100644 --- a/subsys/bluetooth/controller/ll_sw/pdu.h +++ b/subsys/bluetooth/controller/ll_sw/pdu.h @@ -32,6 +32,8 @@ #define BYTES2US(bytes, phy) (((bytes)<<3)/BIT((phy&0x3)>>1)) +/* Advertisement channel minimum payload size */ +#define PDU_AC_PAYLOAD_SIZE_MIN 1 /* Advertisement channel maximum legacy payload size */ #define PDU_AC_LEG_PAYLOAD_SIZE_MAX 37 /* Advertisement channel maximum extended payload size */ diff --git a/subsys/bluetooth/controller/ll_sw/ull_adv_internal.h b/subsys/bluetooth/controller/ll_sw/ull_adv_internal.h index 2a67e86879768..41e17fe5adafc 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_adv_internal.h +++ b/subsys/bluetooth/controller/ll_sw/ull_adv_internal.h @@ -116,6 +116,12 @@ uint8_t ull_adv_aux_hdr_set_clear(struct ll_adv_set *adv, struct pdu_adv_adi *adi, uint8_t *pri_idx); +/* helper to initialize extended advertising PDU */ +void ull_adv_sync_pdu_init(struct pdu_adv *pdu, uint8_t ext_hdr_flags); + +/* helper to add cte_info field to extended advertising header */ +uint8_t ull_adv_sync_pdu_cte_info_set(struct pdu_adv *pdu, const struct pdu_cte_info *cte_info); + /* helper function to calculate common ext adv payload header length and * adjust the data pointer. * NOTE: This function reverts the header data pointer if there is no diff --git a/subsys/bluetooth/controller/ll_sw/ull_adv_sync.c b/subsys/bluetooth/controller/ll_sw/ull_adv_sync.c index 77ecd6b7e283e..d2f669cbe11b6 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_adv_sync.c +++ b/subsys/bluetooth/controller/ll_sw/ull_adv_sync.c @@ -65,7 +65,7 @@ static void ticker_op_cb(uint32_t status, void *param); static struct ll_adv_sync_set ll_adv_sync_pool[CONFIG_BT_CTLR_ADV_SYNC_SET]; static void *adv_sync_free; -static void adv_sync_pdu_init(struct pdu_adv *pdu, uint8_t ext_hdr_flags) +void ull_adv_sync_pdu_init(struct pdu_adv *pdu, uint8_t ext_hdr_flags) { struct pdu_adv_com_ext_adv *com_hdr; struct pdu_adv_ext_hdr *ext_hdr; @@ -238,8 +238,10 @@ static uint8_t adv_sync_pdu_init_from_prev_pdu(struct pdu_adv *pdu, return 0; } -static uint8_t adv_sync_pdu_ad_data_set(struct pdu_adv *pdu, - const uint8_t *data, uint8_t len) +/* Note: Function made global because it is temporarily not used and causes compilation warning. + * It will be used when fragmentation of periodic advertising PDU is implemented. + */ +uint8_t adv_sync_pdu_ad_data_set(struct pdu_adv *pdu, const uint8_t *data, uint8_t len) { struct pdu_adv_com_ext_adv *com_hdr; uint8_t len_max; @@ -263,8 +265,7 @@ static uint8_t adv_sync_pdu_ad_data_set(struct pdu_adv *pdu, return 0; } -static uint8_t adv_sync_pdu_cte_info_set(struct pdu_adv *pdu, - const struct pdu_cte_info *cte_info) +uint8_t ull_adv_sync_pdu_cte_info_set(struct pdu_adv *pdu, const struct pdu_cte_info *cte_info) { struct pdu_adv_com_ext_adv *com_hdr; struct pdu_adv_ext_hdr *ext_hdr; @@ -370,7 +371,7 @@ uint8_t ll_adv_sync_param_set(uint8_t handle, uint16_t interval, uint16_t flags) sync->is_started = 0U; ter_pdu = lll_adv_sync_data_peek(lll_sync, NULL); - adv_sync_pdu_init(ter_pdu, 0); + ull_adv_sync_pdu_init(ter_pdu, 0); } else { sync = HDR_LLL2ULL(lll_sync); } @@ -885,7 +886,7 @@ uint8_t ull_adv_sync_pdu_set_clear(struct lll_adv_sync *lll_sync, void *hdr_data) { struct pdu_adv_com_ext_adv *ter_com_hdr, *ter_com_hdr_prev; - struct pdu_adv_ext_hdr *ter_hdr, ter_hdr_prev; + struct pdu_adv_ext_hdr ter_hdr = { 0 }, ter_hdr_prev = { 0 }; uint8_t *ter_dptr, *ter_dptr_prev; uint8_t acad_len_prev; uint8_t ter_len_prev; @@ -900,13 +901,10 @@ uint8_t ull_adv_sync_pdu_set_clear(struct lll_adv_sync *lll_sync, /* Get common pointers from reference to previous tertiary PDU data */ ter_com_hdr_prev = (void *)&ter_pdu_prev->adv_ext_ind; - ter_hdr = (void *)ter_com_hdr_prev->ext_hdr_adv_data; - if (ter_com_hdr_prev->ext_hdr_len) { - ter_hdr_prev = *ter_hdr; - } else { - *(uint8_t *)&ter_hdr_prev = 0U; + if (ter_com_hdr_prev->ext_hdr_len != 0) { + ter_hdr_prev = ter_com_hdr_prev->ext_hdr; } - ter_dptr_prev = ter_hdr->data; + ter_dptr_prev = ter_com_hdr_prev->ext_hdr.data; /* Set common fields in reference to new tertiary PDU data buffer */ ter_pdu->type = ter_pdu_prev->type; @@ -916,11 +914,14 @@ uint8_t ull_adv_sync_pdu_set_clear(struct lll_adv_sync *lll_sync, ter_pdu->tx_addr = ter_pdu_prev->tx_addr; ter_pdu->rx_addr = ter_pdu_prev->rx_addr; + /* Get common pointers from current tertiary PDU data. + * It is possbile that the current tertiary is the same as + * previous one. It may happen if update periodic advertising + * chain in place. + */ ter_com_hdr = (void *)&ter_pdu->adv_ext_ind; ter_com_hdr->adv_mode = ter_com_hdr_prev->adv_mode; - ter_hdr = (void *)ter_com_hdr->ext_hdr_adv_data; - ter_dptr = ter_hdr->data; - *(uint8_t *)ter_hdr = 0U; + ter_dptr = ter_com_hdr->ext_hdr.data; /* No AdvA in AUX_SYNC_IND */ /* No TargetA in AUX_SYNC_IND */ @@ -928,14 +929,14 @@ uint8_t ull_adv_sync_pdu_set_clear(struct lll_adv_sync *lll_sync, #if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX) /* If requested add or update CTEInfo */ if (hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_CTE_INFO) { - ter_hdr->cte_info = 1; + ter_hdr.cte_info = 1; cte_info = *(uint8_t *)hdr_data; hdr_data = (uint8_t *)hdr_data + 1; ter_dptr += sizeof(struct pdu_cte_info); /* If CTEInfo exists in prev and is not requested to be removed */ } else if (!(hdr_rem_fields & ULL_ADV_PDU_HDR_FIELD_CTE_INFO) && ter_hdr_prev.cte_info) { - ter_hdr->cte_info = 1; + ter_hdr.cte_info = 1; ter_dptr += sizeof(struct pdu_cte_info); } @@ -951,9 +952,9 @@ uint8_t ull_adv_sync_pdu_set_clear(struct lll_adv_sync *lll_sync, if ((hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_AUX_PTR) || (!(hdr_rem_fields & ULL_ADV_PDU_HDR_FIELD_AUX_PTR) && ter_hdr_prev.aux_ptr)) { - ter_hdr->aux_ptr = 1; + ter_hdr.aux_ptr = 1; } - if (ter_hdr->aux_ptr) { + if (ter_hdr.aux_ptr) { ter_dptr += sizeof(struct pdu_adv_aux_ptr); } if (ter_hdr_prev.aux_ptr) { @@ -966,7 +967,7 @@ uint8_t ull_adv_sync_pdu_set_clear(struct lll_adv_sync *lll_sync, if (ter_hdr_prev.tx_pwr) { ter_dptr_prev++; - ter_hdr->tx_pwr = 1; + ter_hdr.tx_pwr = 1; ter_dptr++; } @@ -975,10 +976,15 @@ uint8_t ull_adv_sync_pdu_set_clear(struct lll_adv_sync *lll_sync, hdr_buf_len = ter_com_hdr_prev->ext_hdr_len + PDU_AC_EXT_HEADER_SIZE_MIN; if (ter_len_prev <= hdr_buf_len) { + /* There are some data, except ACAD, in extended header if ter_len_prev + * equals to hdr_buf_len. There is ACAD if the size of ter_len_prev + * is smaller than hdr_buf_len. + */ acad_len_prev = hdr_buf_len - ter_len_prev; ter_len_prev += acad_len_prev; ter_dptr_prev += acad_len_prev; } else { + /* There are no data in extended header, all flags are zeros. */ acad_len_prev = 0; /* NOTE: If no flags are set then extended header length will be * zero. Under this condition the current ter_len_prev @@ -1061,14 +1067,14 @@ uint8_t ull_adv_sync_pdu_set_clear(struct lll_adv_sync *lll_sync, } /* Tx Power */ - if (ter_hdr->tx_pwr) { + if (ter_hdr.tx_pwr) { *--ter_dptr = *--ter_dptr_prev; } /* No SyncInfo in AUX_SYNC_IND */ /* AuxPtr */ - if (ter_hdr->aux_ptr) { + if (ter_hdr.aux_ptr) { /* ToDo Update setup of aux_ptr - check documentation */ if (ter_hdr_prev.aux_ptr) { ter_dptr_prev -= sizeof(struct pdu_adv_aux_ptr); @@ -1083,7 +1089,7 @@ uint8_t ull_adv_sync_pdu_set_clear(struct lll_adv_sync *lll_sync, /* No ADI in AUX_SYNC_IND*/ #if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX) - if (ter_hdr->cte_info) { + if (ter_hdr.cte_info) { if (hdr_add_fields & ULL_ADV_PDU_HDR_FIELD_CTE_INFO) { *--ter_dptr = cte_info; } else { @@ -1095,6 +1101,10 @@ uint8_t ull_adv_sync_pdu_set_clear(struct lll_adv_sync *lll_sync, /* No TargetA in AUX_SYNC_IND */ /* No AdvA in AUX_SYNC_IND */ + if (ter_com_hdr->ext_hdr_len != 0) { + ter_com_hdr->ext_hdr = ter_hdr; + } + return 0; } diff --git a/subsys/bluetooth/controller/ll_sw/ull_df.c b/subsys/bluetooth/controller/ll_sw/ull_df.c index 1a2133d7b1159..a7fd54e374614 100644 --- a/subsys/bluetooth/controller/ll_sw/ull_df.c +++ b/subsys/bluetooth/controller/ll_sw/ull_df.c @@ -81,6 +81,8 @@ static uint8_t mem_link_iq_report_quota_pdu; #if defined(CONFIG_BT_CTLR_DF_ADV_CTE_TX) static struct lll_df_adv_cfg lll_df_adv_cfg_pool[CONFIG_BT_CTLR_ADV_AUX_SET]; static void *df_adv_cfg_free; +static uint8_t cte_info_clear(struct ll_adv_set *adv, struct lll_df_adv_cfg *df_cfg, + uint8_t *ter_idx, struct pdu_adv **first_pdu); #endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */ /* @brief Function performs common steps for initialization and reset @@ -98,6 +100,9 @@ static int init_reset(void); * @return Pointer to lll_df_adv_cfg or NULL if there is no more free memory. */ static struct lll_df_adv_cfg *df_adv_cfg_acquire(void); + +static uint8_t cte_info_set(struct ll_adv_set *adv, struct lll_df_adv_cfg *df_cfg, uint8_t *ter_idx, + struct pdu_adv **first_pdu); #endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */ /* @brief Function performs ULL Direction Finding initialization @@ -256,13 +261,12 @@ uint8_t ll_df_set_cl_cte_tx_params(uint8_t adv_handle, uint8_t cte_len, */ uint8_t ll_df_set_cl_cte_tx_enable(uint8_t adv_handle, uint8_t cte_enable) { - void *extra_data_prev, *extra_data; - struct pdu_adv *pdu_prev, *pdu; struct lll_adv_sync *lll_sync; struct lll_df_adv_cfg *df_cfg; struct ll_adv_sync_set *sync; struct ll_adv_set *adv; uint8_t err, ter_idx; + struct pdu_adv *pdu; /* Get the advertising set instance */ adv = ull_adv_is_created_get(adv_handle); @@ -297,53 +301,18 @@ uint8_t ll_df_set_cl_cte_tx_enable(uint8_t adv_handle, uint8_t cte_enable) return BT_HCI_ERR_CMD_DISALLOWED; } - err = ull_adv_sync_pdu_alloc(adv, ULL_ADV_PDU_EXTRA_DATA_ALLOC_NEVER, &pdu_prev, - &pdu, &extra_data_prev, &extra_data, &ter_idx); - if (err) { - return err; - } - - if (extra_data) { - ull_adv_sync_extra_data_set_clear(extra_data_prev, - extra_data, 0, - ULL_ADV_PDU_HDR_FIELD_CTE_INFO, - NULL); - } - - err = ull_adv_sync_pdu_set_clear(lll_sync, pdu_prev, pdu, 0, - ULL_ADV_PDU_HDR_FIELD_CTE_INFO, - NULL); + err = cte_info_clear(adv, df_cfg, &ter_idx, &pdu); if (err) { return err; } df_cfg->is_enabled = 0U; } else { - struct pdu_cte_info cte_info; - void *hdr_data; - if (df_cfg->is_enabled) { return BT_HCI_ERR_CMD_DISALLOWED; } - cte_info.type = df_cfg->cte_type; - cte_info.time = df_cfg->cte_length; - hdr_data = (uint8_t *)&cte_info; - - err = ull_adv_sync_pdu_alloc(adv, ULL_ADV_PDU_EXTRA_DATA_ALLOC_ALWAYS, &pdu_prev, - &pdu, &extra_data_prev, &extra_data, &ter_idx); - if (err) { - return err; - } - - if (extra_data) { - ull_adv_sync_extra_data_set_clear(extra_data_prev, extra_data, - ULL_ADV_PDU_HDR_FIELD_CTE_INFO, 0, - df_cfg); - } - - err = ull_adv_sync_pdu_set_clear(lll_sync, pdu_prev, pdu, - ULL_ADV_PDU_HDR_FIELD_CTE_INFO, 0, hdr_data); + err = cte_info_set(adv, df_cfg, &ter_idx, &pdu); if (err) { return err; } @@ -665,4 +634,363 @@ static struct lll_df_adv_cfg *df_adv_cfg_acquire(void) return df_adv_cfg; } + +#if (CONFIG_BT_CTLR_DF_PER_ADV_CTE_NUM_MAX > 1) +/* + * @brief Function sets content of cte_info field in periodic advertising chain. + * + * The function allocates new PDU (or chain of PDUs) for periodic advertising to + * fill it with information about CTE, that is going to be transmitted with the PDU. + * If there is already allocated PDU, it will be updated to hold information about + * CTE. + * + * @param lll_sync Pointer to periodic advertising sync object. + * @param pdu_prev Pointer to a PDU that is already in use by LLL or was updated with new PDU + * payload. + * @param pdu Pointer to a new head of periodic advertising chain. The pointer may have + * the same value as @p pdu_prev, if payload of PDU pointerd by @p pdu_prev + * was already updated. + * @param cte_count Number of CTEs that should be transmitted in periodic advertising chain. + * @param cte_into Pointer to instence of cte_info stuctuct that is added to PDUs extended + * advertising header. + * + * @return Zero in case of success, other value in case of failure. + */ +static uint8_t per_adv_chain_cte_info_set(struct lll_adv_sync *lll_sync, struct pdu_adv *pdu_prev, + struct pdu_adv *pdu, uint8_t cte_count, + struct pdu_cte_info *cte_info) +{ + uint8_t pdu_add_field_flags; + struct pdu_adv *pdu_next; + uint8_t cte_index = 1; + bool new_chain; + uint8_t err; + + new_chain = (pdu_prev == pdu ? false : true); + + pdu_add_field_flags = ULL_ADV_PDU_HDR_FIELD_CTE_INFO; + + pdu_prev = lll_adv_pdu_linked_next_get(pdu_prev); + + /* Update PDUs in existing chain. Add cte_info to extended advertising header. */ + while (pdu_prev) { + if (new_chain) { + pdu_next = lll_adv_pdu_alloc_pdu_adv(); + lll_adv_pdu_linked_append(pdu_next, pdu); + pdu = pdu_next; + } else { + pdu = lll_adv_pdu_linked_next_get(pdu); + } + + pdu_next = lll_adv_pdu_linked_next_get(pdu_prev); + /* If all CTEs were added to chain, remove CTE from flags */ + if (cte_index >= cte_count) { + pdu_add_field_flags = 0; + } else { + ++cte_index; + /* If it is last PDU in existing chain and there are CTE to be included + * add aux_ptr to flags. + */ + if (!pdu_next && cte_index < cte_count) { + pdu_add_field_flags |= ULL_ADV_PDU_HDR_FIELD_AUX_PTR; + } + } + + err = ull_adv_sync_pdu_set_clear(lll_sync, pdu_prev, pdu, pdu_add_field_flags, 0, + cte_info); + if (err != BT_HCI_ERR_SUCCESS) { + /* TODO: implement gracefull error handling, cleanup of + * changed PDUs and notify host abut issue during start of CTE + * transmission. + */ + return err; + } + pdu_prev = pdu_next; + } + + /* If there is missing only one CTE do not add aux_ptr to PDU */ + if (cte_count - cte_index >= 2) { + pdu_add_field_flags |= ULL_ADV_PDU_HDR_FIELD_AUX_PTR; + } else { + pdu_add_field_flags = ULL_ADV_PDU_HDR_FIELD_CTE_INFO; + } + + /* Add new PDUs if the number of PDUs in existing chain is lower than requested number + * of CTEs. + */ + while (cte_index < cte_count) { + pdu_prev = pdu; + pdu = lll_adv_pdu_alloc_pdu_adv(); + if (!pdu) { + /* TODO: implement graceful error handling, cleanup of + * changed PDUs. + */ + return BT_HCI_ERR_MEM_CAPACITY_EXCEEDED; + } + ull_adv_sync_pdu_init(pdu, pdu_add_field_flags); + ull_adv_sync_pdu_cte_info_set(pdu, cte_info); + + /* Link PDU into a chain */ + lll_adv_pdu_linked_append(pdu, pdu_prev); + + ++cte_index; + /* If next PDU in a chain is last PDU, then remove aux_ptr field flag from + * extended advertising header. + */ + if (cte_index == cte_count - 1) { + pdu_add_field_flags &= (~ULL_ADV_PDU_HDR_FIELD_AUX_PTR); + } + } + + return BT_HCI_ERR_SUCCESS; +} +#endif /* CONFIG_BT_CTLR_DF_PER_ADV_CTE_NUM_MAX > 1 */ + +/* + * @brief Function sets content of cte_info field for periodic advertising + * + * @param adv Pointer to periodic advertising set. + * @param df_cfg Pointer to direction finding configuration + * @param[out] ter_idx Pointer used to return index of allocated or updated PDU. + * Index is required for scheduling the PDU for transmission in LLL. + * @param[out] first_pdu Pointer to return address of first PDU in a periodic advertising chain + * + * @return Zero in case of success, other value in case of failure. + */ +static uint8_t cte_info_set(struct ll_adv_set *adv, struct lll_df_adv_cfg *df_cfg, uint8_t *ter_idx, + struct pdu_adv **first_pdu) +{ + struct pdu_adv *pdu_prev, *pdu; + struct lll_adv_sync *lll_sync; + struct pdu_cte_info cte_info; + uint8_t pdu_add_field_flags; + void *extra_data; + uint8_t err; + + lll_sync = adv->lll.sync; + + cte_info.type = df_cfg->cte_type; + cte_info.time = df_cfg->cte_length; + + /* Note: ULL_ADV_PDU_HDR_FIELD_CTE_INFO is just information that extra_data + * is required in case of this ull_adv_sync_pdu_alloc. + * Other flags here do not change anything. It may be changed to use + * true/false flag for extra_data allocation. + */ + err = ull_adv_sync_pdu_alloc(adv, ULL_ADV_PDU_EXTRA_DATA_ALLOC_ALWAYS, &pdu_prev, &pdu, + NULL, &extra_data, ter_idx); + if (err != BT_HCI_ERR_SUCCESS) { + return err; + } + + ull_adv_sync_extra_data_set_clear(NULL, extra_data, ULL_ADV_PDU_HDR_FIELD_CTE_INFO, 0, + df_cfg); + +#if (CONFIG_BT_CTLR_DF_PER_ADV_CTE_NUM_MAX > 1) + if (df_cfg->cte_count > 1) { + pdu_add_field_flags = + ULL_ADV_PDU_HDR_FIELD_CTE_INFO | ULL_ADV_PDU_HDR_FIELD_AUX_PTR; + } else +#endif /* CONFIG_BT_CTLR_DF_PER_ADV_CTE_NUM_MAX > 1 */ + { + pdu_add_field_flags = ULL_ADV_PDU_HDR_FIELD_CTE_INFO; + } + + err = ull_adv_sync_pdu_set_clear(lll_sync, pdu_prev, pdu, pdu_add_field_flags, 0, + &cte_info); + if (err != BT_HCI_ERR_SUCCESS) { + return err; + } + + *first_pdu = pdu; +#if (CONFIG_BT_CTLR_DF_PER_ADV_CTE_NUM_MAX > 1) + err = per_adv_chain_cte_info_set(lll_sync, pdu_prev, pdu, df_cfg->cte_count, &cte_info); + if (err != BT_HCI_ERR_SUCCESS) { + return err; + } +#endif /* CONFIG_BT_CTLR_DF_PER_ADV_CTE_NUM_MAX > 1 */ + + return BT_HCI_ERR_SUCCESS; +} + +#if (CONFIG_BT_CTLR_DF_PER_ADV_CTE_NUM_MAX > 1) +static bool pdu_ext_adv_is_empty_without_cte(const struct pdu_adv *pdu) +{ + if (pdu->len != PDU_AC_PAYLOAD_SIZE_MIN) { + const struct pdu_adv_ext_hdr *ext_hdr; + uint8_t size_rem = 0; + + if ((pdu->adv_ext_ind.ext_hdr_len + PDU_AC_EXT_HEADER_SIZE_MIN) != pdu->len) { + /* There are adv. data in PDU */ + return false; + } + + /* Check size of the ext. header without cte_info and aux_ptr. If that is minimum + * extended PDU size then the PDU was allocated to transport CTE only. + */ + ext_hdr = &pdu->adv_ext_ind.ext_hdr; + + if (ext_hdr->cte_info) { + size_rem += sizeof(struct pdu_cte_info); + } + if (ext_hdr->aux_ptr) { + size_rem += sizeof(struct pdu_adv_aux_ptr); + } + + if ((pdu->adv_ext_ind.ext_hdr_len - size_rem) != PDU_AC_EXT_HEADER_SIZE_MIN) { + return false; + } + } + + return true; +} + +/* + * @brief Function removes content of cte_info field in periodic advertising chain. + * + * The function removes cte_info from extended advertising header in all PDUs in periodic + * advertising chain. If particular PDU is empty (holds cte_info only) it will be removed from + * chain. + * + * @param lll_sync Pointer to periodic advertising sync object. + * @param pdu_prev Pointer to a PDU that is already in use by LLL or was updated with new PDU + * payload. + * @param pdu Pointer to a new head of periodic advertising chain. The pointer may have + * the same value as @p pdu_prev, if payload of PDU pointerd by @p pdu_prev was + * already updated. + * + * @return Zero in case of success, other value in case of failure. + */ +static uint8_t rem_cte_info_from_per_adv_chain(struct lll_adv_sync *lll_sync, + struct pdu_adv *pdu_prev, struct pdu_adv *pdu) +{ + struct pdu_adv *pdu_new, *pdu_chained; + uint8_t pdu_rem_field_flags; + bool new_chain; + uint8_t err; + + pdu_rem_field_flags = ULL_ADV_PDU_HDR_FIELD_CTE_INFO; + + /* It is possible that the function is called after e.g. advertising data were updated. + * In such situation the function will run on already allocated chain. Do not allocate + * new chain then. Reuse already allocated PDUs and allocate new ones only if the chain + * was not updated yet. + */ + new_chain = (pdu_prev == pdu ? false : true); + + /* Get next PDU in a chain. Alway use pdu_prev because it points to actual + * former chain. + */ + pdu_chained = lll_adv_pdu_linked_next_get(pdu_prev); + + /* Go through existing chain and remove CTE info. */ + while (pdu_chained) { + if (pdu_ext_adv_is_empty_without_cte(pdu_chained)) { + /* If there is an empty PDU then all remaining PDUs shoudl be released. */ + lll_adv_pdu_linked_release_all(pdu_chained); + pdu_chained = NULL; + /* Set new end of chain in PDUs linked list. If pdu differs from prev_pdu + * then it is alread end of a chain. If it doesn't differ, then chain end + * is changed in rigth place by use of pdu_prev. That makes sure there + * is no PDU released twice (here and when LLL swaps PDU buffers). + */ + lll_adv_pdu_linked_append(NULL, pdu_prev); + } else { + /* Update one before pdu_chained */ + err = ull_adv_sync_pdu_set_clear(lll_sync, pdu_prev, pdu, 0, + pdu_rem_field_flags, NULL); + if (err != BT_HCI_ERR_SUCCESS) { + /* TODO: return here leaves periodic advertising chain in + * an inconsisten state. Add gracefull return or assert. + */ + return err; + } + + /* Prepare for next iteration. Allocate new PDU or move to next one in + * a chain. + */ + if (new_chain) { + pdu_new = lll_adv_pdu_alloc_pdu_adv(); + lll_adv_pdu_linked_append(pdu_new, pdu); + pdu = pdu_new; + } else { + pdu = lll_adv_pdu_linked_next_get(pdu); + } + + /* Move to next chained PDU (it moves through chain that is in use + * by LLL or is new one with updated advertising payload). + */ + pdu_prev = pdu_chained; + pdu_chained = lll_adv_pdu_linked_next_get(pdu_prev); + } + } + + return BT_HCI_ERR_SUCCESS; +} +#endif /* (CONFIG_BT_CTLR_DF_PER_ADV_CTE_NUM_MAX > 1) */ + +/* + * @brief Function removes content of cte_info field from periodic advertising PDUs. + * + * @param adv Pointer to periodic advertising set. + * @param df_cfg Pointer to direction finding configuration + * @param[out] ter_idx Pointer used to return index of allocated or updated PDU. + * Index is required for scheduling the PDU for transmission in LLL. + * @param[out] first_pdu Pointer to return address of first PDU in a chain + * + * @return Zero in case of success, other value in case of failure. + */ +static uint8_t cte_info_clear(struct ll_adv_set *adv, struct lll_df_adv_cfg *df_cfg, + uint8_t *ter_idx, struct pdu_adv **first_pdu) +{ + void *extra_data_prev, *extra_data; + struct pdu_adv *pdu_prev, *pdu; + struct lll_adv_sync *lll_sync; + uint8_t pdu_rem_field_flags; + uint8_t err; + + lll_sync = adv->lll.sync; + + /* NOTE: ULL_ADV_PDU_HDR_FIELD_CTE_INFO is just information that extra_data + * should be removed in case of this call ull_adv_sync_pdu_alloc. + * Other flags here do not change anything. It may be changed to use + * true/false flag for extra_data allocation. + */ + err = ull_adv_sync_pdu_alloc(adv, ULL_ADV_PDU_EXTRA_DATA_ALLOC_NEVER, &pdu_prev, &pdu, + &extra_data_prev, &extra_data, ter_idx); + if (err != BT_HCI_ERR_SUCCESS) { + return err; + } + + if (extra_data) { + ull_adv_sync_extra_data_set_clear(extra_data_prev, extra_data, 0, + ULL_ADV_PDU_HDR_FIELD_CTE_INFO, NULL); + } + + *first_pdu = pdu; + + pdu_rem_field_flags = ULL_ADV_PDU_HDR_FIELD_CTE_INFO; + +#if (CONFIG_BT_CTLR_DF_PER_ADV_CTE_NUM_MAX > 1) + err = rem_cte_info_from_per_adv_chain(lll_sync, pdu_prev, pdu); + if (err != BT_HCI_ERR_SUCCESS) { + return err; + } + + /* Update last PDU in a chain. It may not have aux_ptr. + * NOTE: If there is no AuxPtr flag in the PDU, attempt to remove it does not harm. + */ + pdu_rem_field_flags |= ULL_ADV_PDU_HDR_FIELD_AUX_PTR; +#endif /* CONFIG_BT_CTLR_DF_PER_ADV_CTE_NUM_MAX > 1 */ + + err = ull_adv_sync_pdu_set_clear(lll_sync, pdu_prev, pdu, 0, pdu_rem_field_flags, NULL); + if (err != BT_HCI_ERR_SUCCESS) { + /* TODO: return here leaves periodic advertising chain in an inconsisten state. + * Add gracefull return or assert. + */ + return err; + } + + return BT_HCI_ERR_SUCCESS; +} #endif /* CONFIG_BT_CTLR_DF_ADV_CTE_TX */ diff --git a/tests/bluetooth/df/common/src/radio_df_stub.c b/tests/bluetooth/df/common/src/radio_df_stub.c index e4ddbb7ea29ca..462af776c87b9 100644 --- a/tests/bluetooth/df/common/src/radio_df_stub.c +++ b/tests/bluetooth/df/common/src/radio_df_stub.c @@ -38,6 +38,11 @@ void radio_switch_complete_and_phy_end_disable(void) { } +void radio_switch_complete_and_phy_end_b2b_tx(uint8_t phy_curr, uint8_t flags_curr, + uint8_t phy_next, uint8_t flags_next) +{ +} + void radio_df_cte_tx_aod_2us_set(uint8_t cte_len) { } diff --git a/tests/bluetooth/df/connectionless_cte_chains/CMakeLists.txt b/tests/bluetooth/df/connectionless_cte_chains/CMakeLists.txt new file mode 100644 index 0000000000000..1137dc530c26c --- /dev/null +++ b/tests/bluetooth/df/connectionless_cte_chains/CMakeLists.txt @@ -0,0 +1,27 @@ +# +# Copyright (c) 2021 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.13.1) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(bluetooth_df_connectionless_cte_tx) + +FILE(GLOB app_sources src/*.c) +SET(common_sources ${CMAKE_CURRENT_SOURCE_DIR}/../common/src/radio_df_stub.c) + +target_sources(app PRIVATE ${common_sources} ${app_sources}) + +# Include paths to allow access to functions implemented in BLE controller +target_include_directories(app PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../common/include) +target_include_directories(app PRIVATE + ${ZEPHYR_BASE}/subsys/bluetooth/controller) +target_include_directories(app PRIVATE + ${ZEPHYR_BASE}/subsys/bluetooth/controller/include) +target_include_directories(app PRIVATE + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw) +target_include_directories(app PRIVATE + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/nordic) +target_include_directories(app PRIVATE + ${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/nordic/lll) diff --git a/tests/bluetooth/df/connectionless_cte_chains/Kconfig b/tests/bluetooth/df/connectionless_cte_chains/Kconfig new file mode 100644 index 0000000000000..5230506c0599f --- /dev/null +++ b/tests/bluetooth/df/connectionless_cte_chains/Kconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config BT_CTLR_DF_SUPPORT + bool + default y + +# Include Zephyr's Kconfig. +source "Kconfig" diff --git a/tests/bluetooth/df/connectionless_cte_chains/prj.conf b/tests/bluetooth/df/connectionless_cte_chains/prj.conf new file mode 100644 index 0000000000000..45e5534c44d7a --- /dev/null +++ b/tests/bluetooth/df/connectionless_cte_chains/prj.conf @@ -0,0 +1,15 @@ +CONFIG_TEST=y +CONFIG_ZTEST=y + +CONFIG_BT=y +CONFIG_BT_EXT_ADV=y +CONFIG_BT_PER_ADV=y + +CONFIG_BT_CTLR=y +CONFIG_BT_CTLR_ADV_EXT=y +CONFIG_BT_CTLR_ADV_PERIODIC=y +CONFIG_BT_CTLR_DF=y + +CONFIG_BT_CTLR_ADVANCED_FEATURES=y +CONFIG_BT_CTLR_ADV_SYNC_PDU_BACK2BACK=y +CONFIG_BT_CTLR_DF_PER_ADV_CTE_NUM_MAX=5 diff --git a/tests/bluetooth/df/connectionless_cte_chains/src/common.c b/tests/bluetooth/df/connectionless_cte_chains/src/common.c new file mode 100644 index 0000000000000..01be0a73be0ac --- /dev/null +++ b/tests/bluetooth/df/connectionless_cte_chains/src/common.c @@ -0,0 +1,472 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "util/util.h" +#include "util/memq.h" +#include "util/mem.h" + +#include "pdu.h" + +#include "lll.h" +#include "lll/lll_adv_types.h" +#include "lll_adv.h" +#include "lll/lll_adv_pdu.h" +#include "lll_adv_sync.h" +#include "lll/lll_df_types.h" + +#include "ull_adv_types.h" +#include "ull_adv_internal.h" + +#include "ll.h" +#include "common.h" + +#define PDU_PAULOAD_BUFF_SIZE 100 +#define TEST_CTE_LENGTH 20 + +/* Controller code uses static function sync_acquire to get adv. sync. + * For test purposes it is used as global variable to avoid complete + * creation of advertising set. + */ +static struct ll_adv_sync_set g_sync_set; +static struct lll_df_adv_cfg g_df_cfg; + +static void common_pdu_adv_data_set(struct pdu_adv *pdu, const uint8_t *data, uint8_t len); + +/* + * @brief Helper function to create advertising set. + * + * The function creates advertising set to an extent required to test adding CTE to periodic + * advertising chains. The function returns handle to advertising set that may be used + * in calls to ULL functions related with advertising. + * + * @param hci_handle equivalent of a handle received from HCI command. + * + * @return Handle to created advertising set. + */ +struct ll_adv_set *common_create_adv_set(uint8_t hci_handle) +{ + struct lll_adv_sync *lll_sync; + struct ll_adv_set *adv_set; + uint8_t handle; + uint8_t err; + + zassert_true(hci_handle < BT_HCI_LE_ADV_HANDLE_MAX, + "Advertising set handle: %" PRIu8 " exceeds maximum handles value %" PRIu8, + hci_handle, BT_HCI_LE_ADV_HANDLE_MAX); + err = ll_adv_set_by_hci_handle_get_or_new(hci_handle, &handle); + zassert_equal(err, 0, "Unexpected error while create new advertising set, err: %" PRIu8, + err); + + adv_set = ull_adv_set_get(handle); + zassert_not_null(adv_set, 0, "Unexpectedly advertising set is NULL"); + /* Note: there is single lll_adv_sync instance. If created more than one advertising set + * all will have reference to the same lll_adv_sync instance. + */ + lll_sync = &g_sync_set.lll; + adv_set->lll.sync = &g_sync_set.lll; + g_sync_set.lll.adv = &adv_set->lll; + + err = lll_adv_init(); + zassert_equal(err, 0, "Unexpected error while initialization advertising set, err: %d", + err); + + lll_hdr_init(lll_sync, &g_sync_set); + + lll_adv_data_reset(&lll_sync->data); + err = lll_adv_data_init(&lll_sync->data); + zassert_equal(err, 0, + "Unexpected error while initialization advertising data init, err: %d", err); + + adv_set->is_created = 1U; + + return adv_set; +} + +/* + * @brief Release advertising set. + * + * @param adv_set Pointer to advertising set to be released. + */ +void common_release_adv_set(struct ll_adv_set *adv_set) +{ + struct ll_adv_sync_set *sync; + + if (adv_set->lll.sync) { + sync = HDR_LLL2ULL(adv_set->lll.sync); + if (sync) { + sync->is_started = 0U; + } + } + adv_set->lll.sync = NULL; + if (adv_set->df_cfg->is_enabled) { + adv_set->df_cfg->is_enabled = 0U; + } + adv_set->df_cfg = NULL; + adv_set->is_created = 0U; +} + +/* + * @brief Helper function that creates periodic advertising chain. + * + * The function creates periodic advertising chain with provided number of PDUs @p pdu_count. + * The created chain is enqueued in provided advertising set. Number of requested PDUs includes + * head of a chain AUX_SYNC_IND. + * Each created PDU will hold payload data in following pattern: + * "test%d test%d test%d", where '%d' is substituted by PDU index. + * + * @param adv_set Pointer to advertising set to enqueue created chain. + * @param pdu_count Requested number of PDUs in a chain. + */ +void common_create_per_adv_chain(struct ll_adv_set *adv_set, uint8_t pdu_count) +{ + struct pdu_adv *pdu_prev, *pdu, *pdu_new; + char pdu_buff[PDU_PAULOAD_BUFF_SIZE]; + void *extra_data_prev, *extra_data; + struct lll_adv_sync *lll_sync; + uint8_t err, pdu_idx; + + lll_sync = adv_set->lll.sync; + pdu = lll_adv_sync_data_peek(lll_sync, NULL); + ull_adv_sync_pdu_init(pdu, 0); + + err = ull_adv_sync_pdu_alloc(adv_set, 0, 0, NULL, &pdu_prev, &pdu, &extra_data_prev, + &extra_data, &pdu_idx); + zassert_equal(err, 0, "Unexpected error while PDU allocation, err: %d", err); + + if (extra_data) { + ull_adv_sync_extra_data_set_clear(extra_data_prev, extra_data, 0, 0, NULL); + } + + /* Create AUX_SYNC_IND PDU as a head of chain */ + err = ull_adv_sync_pdu_set_clear(lll_sync, pdu_prev, pdu, + (pdu_count > 1 ? ULL_ADV_PDU_HDR_FIELD_AUX_PTR : 0), 0, + NULL); + zassert_equal(err, 0, "Unexpected error during initialization of extended PDU, err: %d", + err); + + /* Add some AD for testing */ + snprintf(pdu_buff, ARRAY_SIZE(pdu_buff), "test%" PRIu8 " test%" PRIu8 " test%" PRIu8 "", 0, + 0, 0); + common_pdu_adv_data_set(pdu, pdu_buff, strlen(pdu_buff)); + /* Create AUX_CHAIN_IND PDUs. Start from 1, AUX_SYNC_IND is first PDU. */ + for (uint8_t idx = 1; idx < pdu_count; ++idx) { + snprintf(pdu_buff, ARRAY_SIZE(pdu_buff), + "test%" PRIu8 " test%" PRIu8 " test%" PRIu8 "", idx, idx, idx); + /* Allocate new PDU */ + pdu_new = lll_adv_pdu_alloc_pdu_adv(); + zassert_not_null(pdu_new, "Cannot allocate new PDU."); + /* Initialize new empty PDU. Last AUX_CHAIN_IND may not include AuxPtr. */ + if (idx < pdu_count - 1) { + ull_adv_sync_pdu_init(pdu_new, ULL_ADV_PDU_HDR_FIELD_AUX_PTR); + } else { + ull_adv_sync_pdu_init(pdu_new, 0); + } + /* Add some AD for testing */ + common_pdu_adv_data_set(pdu_new, pdu_buff, strlen(pdu_buff)); + /* Link to previous PDU */ + lll_adv_pdu_linked_append(pdu_new, pdu); + pdu = pdu_new; + } + + lll_adv_sync_data_enqueue(lll_sync, pdu_idx); +} + +/* + * @brief Helper function to release periodic advertising chain that was enqueued for + * advertising set. + * + * @param adv_set Pointer to advertising set to release a PDUs chain. + */ +void common_release_per_adv_chain(struct ll_adv_set *adv_set) +{ + struct lll_adv_sync *lll_sync; + struct pdu_adv *pdu; + + lll_sync = adv_set->lll.sync; + pdu = lll_adv_sync_data_peek(lll_sync, NULL); + + lll_adv_pdu_linked_release_all(pdu); +} + +/* + * @brief Helper function that validates content of periodic advertising PDU. + * + * The function verifies if content of periodic advertising PDU as as expected. The function + * verifies two types of PDUs: AUX_SYNC_IND and AUX_CHAIN_IND. AUX_CHAIN_IND is validated + * as if its superior PDU is AUX_SYNC_IND only. + * + * Expected fields in extended advertising header are provided by @p exp_ext_hdr_flags. + * + * Note: The function expects that there is no ACAD data in the PDU. + * + * @param pdu Pointer to PDU to be verified. + * @param type Type of the PDU. + * @param exp_ext_hdr_flags Bitfield with expected extended header flags. + */ +void common_validate_per_adv_pdu(struct pdu_adv *pdu, enum test_pdu_ext_adv_type type, + uint16_t exp_ext_hdr_flags) +{ + struct pdu_adv_com_ext_adv *com_hdr; + struct pdu_adv_ext_hdr *ext_hdr; + uint8_t ext_hdr_len; + uint8_t *dptr; + + if (pdu->len > 1) { + com_hdr = &pdu->adv_ext_ind; + /* Non-connectable and Non-scannable adv mode */ + zassert_equal(com_hdr->adv_mode, 0U, + "Unexpected mode of extended advertising PDU: %" PRIu8, + com_hdr->adv_mode); + + ext_hdr = &com_hdr->ext_hdr; + dptr = ext_hdr->data; + + if (com_hdr->ext_hdr_len > 0) { + zassert_false(ext_hdr->adv_addr, + "Unexpected AdvA field in extended advertising header"); + zassert_false(ext_hdr->tgt_addr, + "Unexpected TargetA field in extended advertising header"); + if (type == TEST_PDU_EXT_ADV_SYNC_IND) { + zassert_false( + ext_hdr->adi, + "Unexpected ADI field in extended advertising header"); + } + zassert_false(ext_hdr->sync_info, + "Unexpected SyncInfo field in extended advertising header"); + + if (exp_ext_hdr_flags & ULL_ADV_PDU_HDR_FIELD_CTE_INFO) { + zassert_true( + ext_hdr->cte_info, + "Missing expected CteInfo field in extended advertising header"); + dptr += sizeof(struct pdu_cte_info); + } else { + zassert_false( + ext_hdr->cte_info, + "Unexpected CteInfo field in extended advertising header"); + } + if (type == TEST_PDU_EXT_ADV_SYNC_IND && + (exp_ext_hdr_flags & ULL_ADV_PDU_HDR_FIELD_ADI)) { + zassert_true( + ext_hdr->adi, + "Missing expected ADI field in extended advertising header"); + dptr += sizeof(struct pdu_adv_adi); + } else { + zassert_false( + ext_hdr->adi, + "Unexpected ADI field in extended advertising header"); + } + if (exp_ext_hdr_flags & ULL_ADV_PDU_HDR_FIELD_AUX_PTR) { + zassert_true( + ext_hdr->aux_ptr, + "Missing expected AuxPtr field in extended advertising header"); + dptr += sizeof(struct pdu_adv_aux_ptr); + } else { + zassert_false( + ext_hdr->aux_ptr, + "Unexpected AuxPtr field in extended advertising header"); + } + if (exp_ext_hdr_flags & ULL_ADV_PDU_HDR_FIELD_TX_POWER) { + zassert_true( + ext_hdr->tx_pwr, + "Missing expected CteInfo field in extended advertising header"); + dptr += sizeof(uint8_t); + } else { + zassert_false( + ext_hdr->tx_pwr, + "Unexpected CteInfo field in extended advertising header"); + } + + /* Calculate extended header len, ACAD should not be available. + * ull_adv_aux_hdr_len_calc returns ext hdr length without it. + */ + ext_hdr_len = ull_adv_aux_hdr_len_calc(com_hdr, &dptr); + zassert_equal(com_hdr->ext_hdr_len, + ext_hdr_len - PDU_AC_EXT_HEADER_SIZE_MIN, + "Extended header length: %" PRIu8 + " different than expected %" PRIu8, + ext_hdr_len, com_hdr->ext_hdr_len); + + if (exp_ext_hdr_flags & ULL_ADV_PDU_HDR_FIELD_AD_DATA) { + zassert_true((pdu->len - ext_hdr_len) > 0, + "Missing expected advertising data in PDU"); + } else { + zassert_equal(pdu->len - ext_hdr_len, 0, + "Unexpected advertising data in PDU"); + } + } else { + zassert_equal(exp_ext_hdr_flags, ULL_ADV_PDU_HDR_FIELD_AD_DATA, + "Unexpected extended header flags: %" PRIu16, + exp_ext_hdr_flags); + } + } +} + +/* + * @brief Helper function to prepre CTE configuration for a given advertising set. + * + * Note: There is a single instance of CTE configuration. In case there is a need + * to use multiple advertising sets at once, all will use the same CTE configuration. + * + * @param adv Pointer to advertising set + * @param cte_count Requested number of CTEs to be send + */ +void common_prepare_df_cfg(struct ll_adv_set *adv, uint8_t cte_count) +{ + /* Prepare CTE configuration */ + g_df_cfg.cte_count = cte_count; + g_df_cfg.cte_length = TEST_CTE_LENGTH; + g_df_cfg.cte_type = BT_HCI_LE_AOD_CTE_2US; + + adv->df_cfg = &g_df_cfg; +} + +/* + * @brief Helper function that validates correctness of periodic advertising chain. + * + * The function expects that all periodic advertising chain PDUs will have advertising data. + * + * @param adv Pointer to advertising set + * @param pdu_count Number of PDUs in a chain + */ +void common_validate_per_adv_chain(struct ll_adv_set *adv, uint8_t pdu_count) +{ + uint16_t ext_hdr_flags; + struct pdu_adv *pdu; + + pdu = lll_adv_sync_data_peek(adv->lll.sync, NULL); + + /* Validate AUX_SYNC_IND */ + if (pdu_count > 1) { + ext_hdr_flags = ULL_ADV_PDU_HDR_FIELD_AUX_PTR | ULL_ADV_PDU_HDR_FIELD_AD_DATA; + } else { + ext_hdr_flags = ULL_ADV_PDU_HDR_FIELD_AD_DATA; + } + common_validate_per_adv_pdu(pdu, TEST_PDU_EXT_ADV_SYNC_IND, ext_hdr_flags); + pdu = lll_adv_pdu_linked_next_get(pdu); + if (pdu_count > 1) { + zassert_not_null(pdu, "Expected PDU in periodic advertising chain is NULL"); + } else { + zassert_is_null(pdu, "Unexpected PDU in a single PDU periodic advertising chain"); + } + /* Validate AUX_CHAIN_IND PDUs in a periodic advertising chain. Start from 1, because + * first PDU is AUX_SYNC_IND. + */ + for (uint8_t idx = 1; idx < pdu_count; ++idx) { + if (idx != (pdu_count - 1)) { + ext_hdr_flags = + ULL_ADV_PDU_HDR_FIELD_AUX_PTR | ULL_ADV_PDU_HDR_FIELD_AD_DATA; + } else { + ext_hdr_flags = ULL_ADV_PDU_HDR_FIELD_AD_DATA; + } + common_validate_per_adv_pdu(pdu, TEST_PDU_EXT_ADV_CHAIN_IND, ext_hdr_flags); + pdu = lll_adv_pdu_linked_next_get(pdu); + if (idx != (pdu_count - 1)) { + zassert_not_null(pdu, "Expected PDU in periodic advertising chain is NULL"); + } else { + zassert_is_null(pdu, "Unexpected PDU at end of periodic advertising chain"); + } + } +} + +/* + * @brief Helper function that validates correctness of periodic advertising chain including CTE + * + * The number of PDUs including advertising data or CTE is provided by appropriate function + * arguments. PUDs including CTE are always first #N PDUs. The same rule applies to PDUs including + * advertising data. So maximum number of PDUs in a chain is maximum value from pair @p cte_count + * and @p ad_data_count. + * + * @param adv Pointer to advertising set + * @param cte_count Number of PDUs including CTE + * @param ad_data_pdu_count Number of PDUs including advertising data + */ +void common_validate_chain_with_cte(struct ll_adv_set *adv, uint8_t cte_count, + uint8_t ad_data_pdu_count) +{ + uint16_t ext_hdr_flags; + struct pdu_adv *pdu; + uint8_t pdu_count; + + pdu = lll_adv_sync_data_peek(adv->lll.sync, NULL); + if (cte_count > 1) { + ext_hdr_flags = ULL_ADV_PDU_HDR_FIELD_AUX_PTR | ULL_ADV_PDU_HDR_FIELD_CTE_INFO; + + } else { + ext_hdr_flags = ULL_ADV_PDU_HDR_FIELD_CTE_INFO; + } + if (ad_data_pdu_count > 0) { + ext_hdr_flags |= ULL_ADV_PDU_HDR_FIELD_AD_DATA; + } + common_validate_per_adv_pdu(pdu, TEST_PDU_EXT_ADV_SYNC_IND, ext_hdr_flags); + + pdu_count = MAX(cte_count, ad_data_pdu_count); + + pdu = lll_adv_pdu_linked_next_get(pdu); + if (pdu_count > 1) { + zassert_not_null(pdu, "Expected PDU in periodic advertising chain is NULL"); + } else { + zassert_is_null(pdu, "Unexpected PDU in a single PDU periodic advertising chain"); + } + + for (uint8_t idx = 1; idx < pdu_count; ++idx) { + if (idx < pdu_count - 1) { + ext_hdr_flags = ULL_ADV_PDU_HDR_FIELD_AUX_PTR; + } else { + ext_hdr_flags = 0U; + } + if (idx < cte_count) { + ext_hdr_flags |= ULL_ADV_PDU_HDR_FIELD_CTE_INFO; + } + if (idx < ad_data_pdu_count) { + ext_hdr_flags |= ULL_ADV_PDU_HDR_FIELD_AD_DATA; + } + common_validate_per_adv_pdu(pdu, TEST_PDU_EXT_ADV_CHAIN_IND, ext_hdr_flags); + + pdu = lll_adv_pdu_linked_next_get(pdu); + if (idx < (pdu_count - 1)) { + zassert_not_null(pdu, "Expected PDU in periodic advertising chain is NULL"); + } else { + zassert_is_null(pdu, "Unexpected PDU at end of periodic advertising chain"); + } + } +} + +/* + * @brief Helper function to add payload data to extended advertising PDU. + * + * @param pdu Pointer to extended advertising PDU. + * @param data Pointer to data to be added. + * @param len Length of the data. + */ +static void common_pdu_adv_data_set(struct pdu_adv *pdu, const uint8_t *data, uint8_t len) +{ + struct pdu_adv_com_ext_adv *com_hdr; + uint8_t len_max; + uint8_t *dptr; + + com_hdr = &pdu->adv_ext_ind; + + dptr = &com_hdr->ext_hdr_adv_data[com_hdr->ext_hdr_len]; + + len_max = PDU_AC_PAYLOAD_SIZE_MAX - (dptr - pdu->payload); + zassert_false(len > len_max, + "Provided data length exceeds maximum supported payload length: %" PRIu8, + len_max); + + memcpy(dptr, data, len); + dptr += len; + + pdu->len = dptr - pdu->payload; +} diff --git a/tests/bluetooth/df/connectionless_cte_chains/src/common.h b/tests/bluetooth/df/connectionless_cte_chains/src/common.h new file mode 100644 index 0000000000000..aa4e7f4c3e9cd --- /dev/null +++ b/tests/bluetooth/df/connectionless_cte_chains/src/common.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +enum test_pdu_ext_adv_type { + TEST_PDU_EXT_ADV_SYNC_IND, + TEST_PDU_EXT_ADV_CHAIN_IND, +}; + +struct ll_adv_set *common_create_adv_set(uint8_t hci_handle); +void common_release_adv_set(struct ll_adv_set *adv_set); +void common_create_per_adv_chain(struct ll_adv_set *adv_set, uint8_t pdu_count); +void common_validate_per_adv_pdu(struct pdu_adv *pdu, enum test_pdu_ext_adv_type type, + uint16_t exp_ext_hrd_flags); +void common_release_per_adv_chain(struct ll_adv_set *adv_set); +void common_prepare_df_cfg(struct ll_adv_set *adv, uint8_t cte_count); +void common_validate_per_adv_chain(struct ll_adv_set *adv, uint8_t pdu_count); +void common_validate_chain_with_cte(struct ll_adv_set *adv, uint8_t cte_count, + uint8_t ad_data_pdu_count); diff --git a/tests/bluetooth/df/connectionless_cte_chains/src/main.c b/tests/bluetooth/df/connectionless_cte_chains/src/main.c new file mode 100644 index 0000000000000..b9a4450c206f6 --- /dev/null +++ b/tests/bluetooth/df/connectionless_cte_chains/src/main.c @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "test_add_cte_to_chain.h" +#include "test_remove_cte_from_chain.h" + +/* Test case main entry */ +void test_main(void) +{ + run_add_cte_to_per_adv_chain_tests(); + run_remove_cte_to_per_adv_chain_tests(); +} diff --git a/tests/bluetooth/df/connectionless_cte_chains/src/test_add_cte_to_chain.c b/tests/bluetooth/df/connectionless_cte_chains/src/test_add_cte_to_chain.c new file mode 100644 index 0000000000000..93f01f273a2e0 --- /dev/null +++ b/tests/bluetooth/df/connectionless_cte_chains/src/test_add_cte_to_chain.c @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "util/util.h" +#include "util/memq.h" +#include "util/mem.h" + +#include "pdu.h" + +#include "lll.h" +#include "lll/lll_adv_types.h" +#include "lll_adv.h" +#include "lll/lll_adv_pdu.h" +#include "lll_adv_sync.h" +#include "lll/lll_df_types.h" + +#include "ull_adv_types.h" +#include "ull_adv_internal.h" + +#include "ll.h" +#include "common.h" + +#define TEST_ADV_SET_HANDLE 0 +#define TEST_PER_ADV_CHAIN_LENGTH 5 +#define TEST_PER_ADV_SINGLE_PDU 1 +#define TEST_CTE_COUNT 3 +#define TEST_CTE_SINGLE 1 +/* It does not matter for purpose of this tests what is the type or length of CTE used. */ +#define TEST_CTE_TYPE BT_HCI_LE_AOD_CTE_2US + +void test_add_number_of_cte_to_sigle_pdu_chain(void) +{ + struct ll_adv_set *adv; + uint8_t handle; + int err; + + /* Setup for test */ + adv = common_create_adv_set(TEST_ADV_SET_HANDLE); + common_prepare_df_cfg(adv, TEST_CTE_COUNT); + common_create_per_adv_chain(adv, TEST_PER_ADV_SINGLE_PDU); + common_validate_per_adv_chain(adv, TEST_PER_ADV_SINGLE_PDU); + + handle = ull_adv_handle_get(adv); + + err = ll_df_set_cl_cte_tx_enable(handle, true); + zassert_equal(err, 0, + "Unexpected error while enabling CTE for periodic avertising chain, err: %d", + err); + + /* Validate result */ + common_validate_chain_with_cte(adv, TEST_CTE_COUNT, TEST_PER_ADV_SINGLE_PDU); + + /* Teardown */ + common_release_per_adv_chain(adv); + common_release_adv_set(adv); +} + +void test_add_cte_for_each_pdu_in_chain(void) +{ + struct ll_adv_set *adv; + uint8_t handle; + int err; + + /* Setup for test */ + adv = common_create_adv_set(TEST_ADV_SET_HANDLE); + /* Use the same number for PDUs in a chain as for CTE request */ + common_prepare_df_cfg(adv, TEST_CTE_COUNT); + common_create_per_adv_chain(adv, TEST_CTE_COUNT); + common_validate_per_adv_chain(adv, TEST_CTE_COUNT); + + handle = ull_adv_handle_get(adv); + + err = ll_df_set_cl_cte_tx_enable(handle, true); + zassert_equal(err, 0, + "Unexpected error while enabling CTE for periodic avertising chain, err: %d", + err); + + /* Validate result */ + common_validate_chain_with_cte(adv, TEST_CTE_COUNT, TEST_CTE_COUNT); + + /* Teardown */ + common_release_per_adv_chain(adv); + common_release_adv_set(adv); +} + +void test_add_cte_for_not_all_pdu_in_chain(void) +{ + struct ll_adv_set *adv; + uint8_t handle; + int err; + + /* Setup for test */ + adv = common_create_adv_set(TEST_ADV_SET_HANDLE); + /* Use the same number for PDUs in a chain as for CTE request */ + common_prepare_df_cfg(adv, TEST_CTE_COUNT); + common_create_per_adv_chain(adv, TEST_PER_ADV_CHAIN_LENGTH); + common_validate_per_adv_chain(adv, TEST_PER_ADV_CHAIN_LENGTH); + + handle = ull_adv_handle_get(adv); + + err = ll_df_set_cl_cte_tx_enable(handle, true); + zassert_equal(err, 0, + "Unexpected error while enabling CTE for periodic avertising chain, err: %d", + err); + + /* Validate result */ + common_validate_chain_with_cte(adv, TEST_CTE_COUNT, TEST_PER_ADV_CHAIN_LENGTH); + + /* Teardown */ + common_release_per_adv_chain(adv); + common_release_adv_set(adv); +} + +void test_add_cte_for_single_pdu_chain(void) +{ + struct ll_adv_set *adv; + uint8_t handle; + int err; + + /* Setup for test */ + adv = common_create_adv_set(TEST_ADV_SET_HANDLE); + /* Use the same number for PDUs in a chain as for CTE request */ + common_prepare_df_cfg(adv, TEST_CTE_SINGLE); + common_create_per_adv_chain(adv, TEST_PER_ADV_SINGLE_PDU); + common_validate_per_adv_chain(adv, TEST_PER_ADV_SINGLE_PDU); + + handle = ull_adv_handle_get(adv); + + err = ll_df_set_cl_cte_tx_enable(handle, true); + zassert_equal(err, 0, + "Unexpected error while enabling CTE for periodic avertising chain, err: %d", + err); + + /* Validate result */ + common_validate_chain_with_cte(adv, TEST_CTE_SINGLE, TEST_PER_ADV_SINGLE_PDU); + + /* Teardown */ + common_release_per_adv_chain(adv); + common_release_adv_set(adv); +} + +void run_add_cte_to_per_adv_chain_tests(void) +{ + ztest_test_suite(test_add_cte_to_per_adv_chain, + ztest_unit_test(test_add_number_of_cte_to_sigle_pdu_chain), + ztest_unit_test(test_add_cte_for_each_pdu_in_chain), + ztest_unit_test(test_add_cte_for_not_all_pdu_in_chain), + ztest_unit_test(test_add_cte_for_single_pdu_chain)); + ztest_run_test_suite(test_add_cte_to_per_adv_chain); +} diff --git a/tests/bluetooth/df/connectionless_cte_chains/src/test_add_cte_to_chain.h b/tests/bluetooth/df/connectionless_cte_chains/src/test_add_cte_to_chain.h new file mode 100644 index 0000000000000..453017e2ad82c --- /dev/null +++ b/tests/bluetooth/df/connectionless_cte_chains/src/test_add_cte_to_chain.h @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Runs tests of adding CTE to a chain of periodic advertising PDUs */ +void run_add_cte_to_per_adv_chain_tests(void); diff --git a/tests/bluetooth/df/connectionless_cte_chains/src/test_remove_cte_from_chain.c b/tests/bluetooth/df/connectionless_cte_chains/src/test_remove_cte_from_chain.c new file mode 100644 index 0000000000000..fcff6a52f2910 --- /dev/null +++ b/tests/bluetooth/df/connectionless_cte_chains/src/test_remove_cte_from_chain.c @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "util/util.h" +#include "util/memq.h" +#include "util/mem.h" + +#include "pdu.h" + +#include "lll.h" +#include "lll/lll_adv_types.h" +#include "lll_adv.h" +#include "lll/lll_adv_pdu.h" +#include "lll_adv_sync.h" +#include "lll/lll_df_types.h" + +#include "ull_adv_types.h" +#include "ull_adv_internal.h" + +#include "ll.h" +#include "common.h" + +#define TEST_ADV_SET_HANDLE 0 +#define TEST_PER_ADV_CHAIN_LENGTH 5 +#define TEST_PER_ADV_SINGLE_PDU 1 +#define TEST_CTE_COUNT 3 +#define TEST_CTE_SINGLE 1 +/* It does not matter for purpose of this tests what is the type or length of CTE used. */ +#define TEST_CTE_TYPE BT_HCI_LE_AOD_CTE_2US + +void test_remove_cte_from_chain_extended_to_tx_all_cte(void) +{ + struct ll_adv_set *adv; + uint8_t handle; + int err; + + /* Setup for test */ + adv = common_create_adv_set(TEST_ADV_SET_HANDLE); + common_prepare_df_cfg(adv, TEST_CTE_COUNT); + common_create_per_adv_chain(adv, TEST_PER_ADV_SINGLE_PDU); + + handle = ull_adv_handle_get(adv); + + ll_df_set_cl_cte_tx_enable(handle, true); + + err = ll_df_set_cl_cte_tx_enable(handle, false); + zassert_equal(err, 0, + "Unexpected error while disabling CTE for periodic avertising chain, err: %d", + err); + /* Validate result */ + common_validate_per_adv_chain(adv, TEST_PER_ADV_SINGLE_PDU); + + /* Teardown */ + common_release_per_adv_chain(adv); + common_release_adv_set(adv); +} + +void test_remove_cte_from_chain_where_each_pdu_includes_cte(void) +{ + struct ll_adv_set *adv; + uint8_t handle; + int err; + + /* Setup for test */ + adv = common_create_adv_set(TEST_ADV_SET_HANDLE); + /* Use the same number for PDUs in a chain as for CTE request */ + common_prepare_df_cfg(adv, TEST_CTE_COUNT); + common_create_per_adv_chain(adv, TEST_CTE_COUNT); + + handle = ull_adv_handle_get(adv); + + err = ll_df_set_cl_cte_tx_enable(handle, true); + zassert_equal(err, 0, + "Unexpected error while enabling CTE for periodic avertising chain, err: %d", + err); + + err = ll_df_set_cl_cte_tx_enable(handle, false); + zassert_equal(err, 0, + "Unexpected error while disabling CTE for periodic avertising chain, err: %d", + err); + /* Validate result */ + common_validate_per_adv_chain(adv, TEST_CTE_COUNT); + + /* Teardown */ + common_release_per_adv_chain(adv); + common_release_adv_set(adv); +} + +void test_remove_cte_from_chain_with_more_pdu_than_cte(void) +{ + struct ll_adv_set *adv; + uint8_t handle; + int err; + + /* Setup for test */ + adv = common_create_adv_set(TEST_ADV_SET_HANDLE); + /* Use the same number for PDUs in a chain as for CTE request */ + common_prepare_df_cfg(adv, TEST_CTE_COUNT); + common_create_per_adv_chain(adv, TEST_PER_ADV_CHAIN_LENGTH); + + handle = ull_adv_handle_get(adv); + + ll_df_set_cl_cte_tx_enable(handle, true); + + err = ll_df_set_cl_cte_tx_enable(handle, false); + zassert_equal(err, 0, + "Unexpected error while disabling CTE for periodic avertising chain, err: %d", + err); + /* Validate result */ + common_validate_per_adv_chain(adv, TEST_PER_ADV_CHAIN_LENGTH); + + /* Teardown */ + common_release_per_adv_chain(adv); + common_release_adv_set(adv); +} + +void test_remove_cte_from_single_pdu_chain(void) +{ + struct ll_adv_set *adv; + uint8_t handle; + int err; + + /* Setup for test */ + adv = common_create_adv_set(TEST_ADV_SET_HANDLE); + /* Use the same number for PDUs in a chain as for CTE request */ + common_prepare_df_cfg(adv, TEST_CTE_SINGLE); + common_create_per_adv_chain(adv, TEST_PER_ADV_SINGLE_PDU); + + handle = ull_adv_handle_get(adv); + + ll_df_set_cl_cte_tx_enable(handle, true); + + err = ll_df_set_cl_cte_tx_enable(handle, false); + zassert_equal(err, 0, + "Unexpected error while disabling CTE for periodic avertising chain, err: %d", + err); + /* Validate result */ + common_validate_per_adv_chain(adv, TEST_PER_ADV_SINGLE_PDU); + + /* Teardown */ + common_release_per_adv_chain(adv); + common_release_adv_set(adv); +} + +void run_remove_cte_to_per_adv_chain_tests(void) +{ + ztest_test_suite(test_add_cte_to_per_adv_chain, + ztest_unit_test(test_remove_cte_from_chain_extended_to_tx_all_cte), + ztest_unit_test(test_remove_cte_from_chain_where_each_pdu_includes_cte), + ztest_unit_test(test_remove_cte_from_chain_with_more_pdu_than_cte), + ztest_unit_test(test_remove_cte_from_single_pdu_chain)); + ztest_run_test_suite(test_add_cte_to_per_adv_chain); +} diff --git a/tests/bluetooth/df/connectionless_cte_chains/src/test_remove_cte_from_chain.h b/tests/bluetooth/df/connectionless_cte_chains/src/test_remove_cte_from_chain.h new file mode 100644 index 0000000000000..89abacfb71f54 --- /dev/null +++ b/tests/bluetooth/df/connectionless_cte_chains/src/test_remove_cte_from_chain.h @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Runs tests of removing CTE from a chain of periodic advertising PDUs */ +void run_remove_cte_to_per_adv_chain_tests(void); diff --git a/tests/bluetooth/df/connectionless_cte_chains/testcase.yaml b/tests/bluetooth/df/connectionless_cte_chains/testcase.yaml new file mode 100644 index 0000000000000..5211bace7337e --- /dev/null +++ b/tests/bluetooth/df/connectionless_cte_chains/testcase.yaml @@ -0,0 +1,6 @@ +tests: + bluetooth.df.conectionless_cte_tx: + platform_allow: nrf52_bsim + integration_platforms: + - nrf52_bsim + tags: bluetooth