| /* |
| * Copyright (c) 2020 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <stdint.h> |
| |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/sys/util.h> |
| |
| #include <zephyr/bluetooth/hci.h> |
| |
| #include "hal/ccm.h" |
| #include "hal/radio.h" |
| #include "hal/ticker.h" |
| #include "hal/radio_df.h" |
| |
| #include "util/util.h" |
| #include "util/memq.h" |
| #include "util/dbuf.h" |
| #include "util/mayfly.h" |
| |
| #include "pdu.h" |
| |
| #include "lll.h" |
| #include "lll_vendor.h" |
| #include "lll_clock.h" |
| #include "lll_filter.h" |
| #include "lll_scan.h" |
| #include "lll_scan_aux.h" |
| #include "lll_df_types.h" |
| #include "lll_df_internal.h" |
| #include "lll_sync.h" |
| #include "lll_sync_iso.h" |
| #include "lll_conn.h" |
| #include "lll_sched.h" |
| |
| #include "lll_internal.h" |
| #include "lll_tim_internal.h" |
| #include "lll_prof_internal.h" |
| #include "lll_scan_internal.h" |
| #include "lll_sync_internal.h" |
| |
| #include "ll_feat.h" |
| |
| #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_DEBUG_HCI_DRIVER) |
| #define LOG_MODULE_NAME bt_ctlr_lll_scan_aux |
| #include "common/log.h" |
| #include <soc.h> |
| #include <ull_scan_types.h> |
| #include "hal/debug.h" |
| |
| static int init_reset(void); |
| static int prepare_cb(struct lll_prepare_param *p); |
| static int is_abort_cb(void *next, void *curr, lll_prepare_cb_t *resume_cb); |
| static void abort_cb(struct lll_prepare_param *prepare_param, void *param); |
| static void isr_done(void *param); |
| static void isr_rx_ull_schedule(void *param); |
| static void isr_rx_lll_schedule(void *param); |
| static void isr_rx(struct lll_scan *lll, struct lll_scan_aux *lll_aux, |
| uint8_t phy_aux); |
| static int isr_rx_pdu(struct lll_scan *lll, struct lll_scan_aux *lll_aux, |
| struct node_rx_pdu *node_rx, struct pdu_adv *pdu, |
| uint8_t phy_aux, uint8_t phy_aux_flags_rx, |
| uint8_t devmatch_ok, uint8_t devmatch_id, |
| uint8_t irkmatch_ok, uint8_t irkmatch_id, uint8_t rl_idx, |
| uint8_t rssi_ready); |
| static void isr_tx_scan_req_ull_schedule(void *param); |
| static void isr_tx_scan_req_lll_schedule(void *param); |
| #if defined(CONFIG_BT_CENTRAL) |
| static void isr_tx_connect_req(void *param); |
| static void isr_rx_connect_rsp(void *param); |
| static bool isr_rx_connect_rsp_check(struct lll_scan *lll, |
| struct pdu_adv *pdu_tx, |
| struct pdu_adv *pdu_rx, uint8_t rl_idx); |
| static void isr_early_abort(void *param); |
| #endif /* CONFIG_BT_CENTRAL */ |
| |
| static uint16_t trx_cnt; /* TODO: move to a union in lll.c, common to all roles |
| */ |
| |
| int lll_scan_aux_init(void) |
| { |
| int err; |
| |
| err = init_reset(); |
| if (err) { |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| int lll_scan_aux_reset(void) |
| { |
| int err; |
| |
| err = init_reset(); |
| if (err) { |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| void lll_scan_aux_prepare(void *param) |
| { |
| int err; |
| |
| err = lll_hfclock_on(); |
| LL_ASSERT(err >= 0); |
| |
| err = lll_prepare(is_abort_cb, abort_cb, prepare_cb, 0, param); |
| LL_ASSERT(!err || err == -EINPROGRESS); |
| } |
| |
| uint8_t lll_scan_aux_setup(struct pdu_adv *pdu, uint8_t pdu_phy, |
| uint8_t pdu_phy_flags_rx, radio_isr_cb_t setup_cb, |
| void *param) |
| { |
| struct pdu_adv_com_ext_adv *pri_com_hdr; |
| struct pdu_adv_ext_hdr *pri_hdr; |
| struct pdu_adv_aux_ptr *aux_ptr; |
| struct pdu_cte_info *cte_info; |
| struct node_rx_pdu *node_rx; |
| uint32_t window_widening_us; |
| uint32_t window_size_us; |
| struct node_rx_ftr *ftr; |
| uint32_t aux_offset_us; |
| uint32_t overhead_us; |
| uint8_t *pri_dptr; |
| uint8_t phy; |
| |
| LL_ASSERT(pdu->type == PDU_ADV_TYPE_EXT_IND); |
| |
| /* Get reference to extended header */ |
| pri_com_hdr = (void *)&pdu->adv_ext_ind; |
| if (!pdu->len || !pri_com_hdr->ext_hdr_len) { |
| return 0U; |
| } |
| |
| /* Get reference to flags and contents */ |
| pri_hdr = (void *)pri_com_hdr->ext_hdr_adv_data; |
| pri_dptr = pri_hdr->data; |
| |
| /* traverse through adv_addr, if present */ |
| if (pri_hdr->adv_addr) { |
| pri_dptr += BDADDR_SIZE; |
| } |
| |
| /* traverse through tgt_addr, if present */ |
| if (pri_hdr->tgt_addr) { |
| pri_dptr += BDADDR_SIZE; |
| } |
| |
| /* traverse through cte_info, if present */ |
| if (pri_hdr->cte_info) { |
| cte_info = (void *)pri_dptr; |
| pri_dptr += sizeof(struct pdu_cte_info); |
| } else { |
| cte_info = NULL; |
| } |
| |
| /* traverse through adi, if present */ |
| if (pri_hdr->adi) { |
| pri_dptr += sizeof(struct pdu_adv_adi); |
| } |
| |
| /* No need to scan further if no aux_ptr filled */ |
| aux_ptr = (void *)pri_dptr; |
| if (unlikely(!pri_hdr->aux_ptr || !aux_ptr->offs || |
| (aux_ptr->phy > EXT_ADV_AUX_PHY_LE_CODED))) { |
| return 0; |
| } |
| |
| /* Determine the window size */ |
| if (aux_ptr->offs_units) { |
| window_size_us = OFFS_UNIT_300_US; |
| } else { |
| window_size_us = OFFS_UNIT_30_US; |
| } |
| |
| /* Calculate the aux offset from start of the scan window */ |
| aux_offset_us = (uint32_t)aux_ptr->offs * window_size_us; |
| |
| /* Calculate the window widening that needs to be deducted */ |
| if (aux_ptr->ca) { |
| window_widening_us = SCA_DRIFT_50_PPM_US(aux_offset_us); |
| } else { |
| window_widening_us = SCA_DRIFT_500_PPM_US(aux_offset_us); |
| } |
| |
| phy = BIT(aux_ptr->phy); |
| |
| /* Calculate the minimum overhead to decide if LLL or ULL scheduling |
| * to be used for auxiliary PDU reception. |
| */ |
| overhead_us = PDU_AC_US(pdu->len, pdu_phy, pdu_phy_flags_rx); |
| #if defined(CONFIG_BT_CTLR_DF_SCAN_CTE_RX) |
| /* Add CTE time if samples are available (8us unit) */ |
| /* TODO: check if CTE was actually enabled for rx */ |
| if (cte_info && radio_df_iq_samples_amount_get()) { |
| overhead_us += cte_info->time << 3; |
| } |
| #endif |
| overhead_us += radio_rx_chain_delay_get(pdu_phy, pdu_phy_flags_rx); |
| overhead_us += lll_radio_rx_ready_delay_get(phy, PHY_FLAGS_S8); |
| overhead_us += window_widening_us; |
| overhead_us += EVENT_TICKER_RES_MARGIN_US; |
| overhead_us += EVENT_JITTER_US; |
| |
| /* Minimum prepare tick offset + minimum preempt tick offset are the |
| * overheads before ULL scheduling can setup radio for reception |
| */ |
| overhead_us += |
| HAL_TICKER_TICKS_TO_US(HAL_TICKER_CNTR_CMP_OFFSET_MIN << 1); |
| |
| /* CPU execution overhead to setup the radio for reception */ |
| overhead_us += EVENT_OVERHEAD_END_US + EVENT_OVERHEAD_START_US; |
| |
| /* Sufficient offset to ULL schedule the auxiliary PDU scan? */ |
| if (aux_offset_us > overhead_us) { |
| return 0; |
| } |
| |
| node_rx = ull_pdu_rx_alloc_peek(1); |
| LL_ASSERT(node_rx); |
| |
| /* Store the lll context, aux_ptr and start of PDU in footer */ |
| ftr = &(node_rx->hdr.rx_ftr); |
| ftr->param = param; |
| ftr->aux_ptr = aux_ptr; |
| ftr->radio_end_us = radio_tmr_end_get() - |
| radio_rx_chain_delay_get(pdu_phy, |
| pdu_phy_flags_rx) - |
| PDU_AC_US(pdu->len, pdu_phy, pdu_phy_flags_rx); |
| |
| radio_isr_set(setup_cb, node_rx); |
| radio_disable(); |
| |
| return 1; |
| } |
| |
| void lll_scan_aux_isr_aux_setup(void *param) |
| { |
| struct pdu_adv_aux_ptr *aux_ptr; |
| struct node_rx_pdu *node_rx; |
| uint32_t window_widening_us; |
| uint32_t window_size_us; |
| struct node_rx_ftr *ftr; |
| uint32_t aux_offset_us; |
| uint32_t aux_start_us; |
| struct lll_scan *lll; |
| uint8_t phy_aux; |
| uint32_t hcto; |
| |
| lll_isr_status_reset(); |
| |
| node_rx = param; |
| ftr = &node_rx->hdr.rx_ftr; |
| aux_ptr = ftr->aux_ptr; |
| phy_aux = BIT(aux_ptr->phy); |
| ftr->aux_phy = phy_aux; |
| |
| lll = ftr->param; |
| |
| /* Determine the window size */ |
| if (aux_ptr->offs_units) { |
| window_size_us = OFFS_UNIT_300_US; |
| } else { |
| window_size_us = OFFS_UNIT_30_US; |
| } |
| |
| /* Calculate the aux offset from start of the scan window */ |
| aux_offset_us = (uint32_t)aux_ptr->offs * window_size_us; |
| |
| /* Calculate the window widening that needs to be deducted */ |
| if (aux_ptr->ca) { |
| window_widening_us = SCA_DRIFT_50_PPM_US(aux_offset_us); |
| } else { |
| window_widening_us = SCA_DRIFT_500_PPM_US(aux_offset_us); |
| } |
| |
| /* Reset Tx/rx count */ |
| trx_cnt = 0U; |
| |
| /* Setup radio for auxiliary PDU scan */ |
| radio_phy_set(phy_aux, PHY_FLAGS_S8); |
| radio_pkt_configure(RADIO_PKT_CONF_LENGTH_8BIT, LL_EXT_OCTETS_RX_MAX, |
| RADIO_PKT_CONF_PHY(phy_aux)); |
| lll_chan_set(aux_ptr->chan_idx); |
| |
| radio_pkt_rx_set(node_rx->pdu); |
| |
| /* FIXME: we could (?) use isr_rx_ull_schedule if already have aux |
| * context allocated, i.e. some previous aux was scheduled from |
| * ull already. |
| */ |
| radio_isr_set(isr_rx_lll_schedule, node_rx); |
| |
| /* setup tIFS switching */ |
| radio_tmr_tifs_set(EVENT_IFS_US); |
| /* TODO: for passive scanning use complete_and_disable */ |
| radio_switch_complete_and_tx(phy_aux, 0, phy_aux, 1); |
| |
| /* TODO: skip filtering if AdvA was already found in previous PDU */ |
| |
| if (0) { |
| #if defined(CONFIG_BT_CTLR_PRIVACY) |
| } else if (ull_filter_lll_rl_enabled()) { |
| const struct lll_filter *fal = |
| ull_filter_lll_get((lll->filter_policy & |
| SCAN_FP_FILTER) != 0U); |
| uint8_t count, *irks = ull_filter_lll_irks_get(&count); |
| |
| radio_filter_configure(fal->enable_bitmask, |
| fal->addr_type_bitmask, |
| (uint8_t *)fal->bdaddr); |
| |
| radio_ar_configure(count, irks, (phy_aux << 2) | BIT(1)); |
| #endif /* CONFIG_BT_CTLR_PRIVACY */ |
| } else if (IS_ENABLED(CONFIG_BT_CTLR_FILTER_ACCEPT_LIST) && lll->filter_policy) { |
| /* Setup Radio Filter */ |
| const struct lll_filter *fal = ull_filter_lll_get(true); |
| |
| radio_filter_configure(fal->enable_bitmask, |
| fal->addr_type_bitmask, |
| (uint8_t *)fal->bdaddr); |
| } |
| |
| /* Setup radio rx on micro second offset. Note that radio_end_us stores |
| * PDU start time in this case. |
| */ |
| aux_start_us = ftr->radio_end_us + aux_offset_us; |
| aux_start_us -= lll_radio_rx_ready_delay_get(phy_aux, PHY_FLAGS_S8); |
| aux_start_us -= window_widening_us; |
| aux_start_us -= EVENT_JITTER_US; |
| radio_tmr_start_us(0, aux_start_us); |
| |
| /* Setup header complete timeout */ |
| hcto = ftr->radio_end_us + aux_offset_us; |
| hcto += window_size_us; |
| hcto += window_widening_us; |
| hcto += EVENT_JITTER_US; |
| hcto += radio_rx_chain_delay_get(phy_aux, PHY_FLAGS_S8); |
| hcto += addr_us_get(phy_aux); |
| radio_tmr_hcto_configure(hcto); |
| |
| /* capture end of Rx-ed PDU, extended scan to schedule auxiliary |
| * channel chaining, create connection or to create periodic sync. |
| */ |
| radio_tmr_end_capture(); |
| |
| /* scanner always measures RSSI */ |
| radio_rssi_measure(); |
| |
| #if defined(HAL_RADIO_GPIO_HAVE_LNA_PIN) |
| radio_gpio_lna_setup(); |
| |
| radio_gpio_pa_lna_enable(aux_start_us + |
| radio_rx_ready_delay_get(phy_aux, |
| PHY_FLAGS_S8) - |
| HAL_RADIO_GPIO_LNA_OFFSET); |
| #endif /* HAL_RADIO_GPIO_HAVE_LNA_PIN */ |
| } |
| |
| bool lll_scan_aux_addr_match_get(const struct lll_scan *lll, |
| const struct pdu_adv *pdu, |
| uint8_t *const devmatch_ok, |
| uint8_t *const devmatch_id, |
| uint8_t *const irkmatch_ok, |
| uint8_t *const irkmatch_id) |
| { |
| const struct pdu_adv_ext_hdr *ext_hdr; |
| |
| ext_hdr = &pdu->adv_ext_ind.ext_hdr; |
| if (!ext_hdr->adv_addr) { |
| return false; |
| } |
| |
| if (IS_ENABLED(CONFIG_BT_CTLR_PRIVACY) && ull_filter_lll_rl_enabled()) { |
| const struct lll_filter *fal = |
| ull_filter_lll_get((lll->filter_policy & |
| SCAN_FP_FILTER) != 0); |
| const uint8_t *adva = &ext_hdr->data[ADVA_OFFSET]; |
| |
| *devmatch_ok = ull_filter_lll_fal_match(fal, pdu->tx_addr, adva, |
| devmatch_id); |
| if (!*devmatch_ok && pdu->tx_addr) { |
| uint8_t count; |
| |
| (void)ull_filter_lll_irks_get(&count); |
| if (count) { |
| radio_ar_resolve(adva); |
| *irkmatch_ok = radio_ar_has_match(); |
| *irkmatch_id = radio_ar_match_get(); |
| } |
| } |
| } else if (IS_ENABLED(CONFIG_BT_CTLR_FILTER_ACCEPT_LIST) && |
| lll->filter_policy) { |
| const struct lll_filter *fal = ull_filter_lll_get(true); |
| const uint8_t *adva = &ext_hdr->data[ADVA_OFFSET]; |
| |
| *devmatch_ok = ull_filter_lll_fal_match(fal, pdu->tx_addr, adva, |
| devmatch_id); |
| } |
| |
| return true; |
| } |
| |
| static int init_reset(void) |
| { |
| return 0; |
| } |
| |
| static int prepare_cb(struct lll_prepare_param *p) |
| { |
| struct lll_scan_aux *lll_aux; |
| struct node_rx_pdu *node_rx; |
| uint32_t ticks_at_event; |
| uint32_t ticks_at_start; |
| uint32_t remainder_us; |
| struct lll_scan *lll; |
| struct ull_hdr *ull; |
| uint8_t is_lll_scan; |
| uint32_t remainder; |
| uint32_t hcto; |
| uint32_t aa; |
| |
| DEBUG_RADIO_START_O(1); |
| |
| lll_aux = p->param; |
| lll = ull_scan_aux_lll_parent_get(lll_aux, &is_lll_scan); |
| |
| /* Check if this aux scan is for periodic advertising train */ |
| if (IS_ENABLED(CONFIG_BT_CTLR_SYNC_PERIODIC) && !is_lll_scan) { |
| lll_sync_aux_prepare_cb((void *)lll, lll_aux); |
| |
| lll = NULL; |
| |
| goto sync_aux_prepare_done; |
| } |
| |
| #if defined(CONFIG_BT_CENTRAL) |
| /* Check if stopped (on connection establishment race between |
| * LL and ULL. |
| */ |
| if (unlikely(lll->is_stop || |
| (lll->conn && |
| (lll->conn->central.initiated || |
| lll->conn->central.cancelled)))) { |
| radio_isr_set(isr_early_abort, lll_aux); |
| radio_disable(); |
| |
| return 0; |
| } |
| #endif /* CONFIG_BT_CENTRAL */ |
| |
| /* Initialize scanning state */ |
| lll_aux->state = 0U; |
| |
| /* Reset Tx/rx count */ |
| trx_cnt = 0U; |
| |
| /* Start setting up 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 |
| |
| radio_phy_set(lll_aux->phy, PHY_FLAGS_S8); |
| radio_pkt_configure(RADIO_PKT_CONF_LENGTH_8BIT, LL_EXT_OCTETS_RX_MAX, |
| RADIO_PKT_CONF_PHY(lll_aux->phy)); |
| |
| node_rx = ull_pdu_rx_alloc_peek(1); |
| LL_ASSERT(node_rx); |
| |
| radio_pkt_rx_set(node_rx->pdu); |
| |
| aa = sys_cpu_to_le32(PDU_AC_ACCESS_ADDR); |
| radio_aa_set((uint8_t *)&aa); |
| radio_crc_configure(PDU_CRC_POLYNOMIAL, |
| PDU_AC_CRC_IV); |
| |
| lll_chan_set(lll_aux->chan); |
| |
| radio_isr_set(isr_rx_ull_schedule, lll_aux); |
| |
| /* setup tIFS switching */ |
| radio_tmr_tifs_set(EVENT_IFS_US); |
| /* TODO: for passive scanning use complete_and_disable */ |
| radio_switch_complete_and_tx(lll_aux->phy, 0, lll_aux->phy, 1); |
| |
| /* TODO: skip filtering if AdvA was already found in previous PDU */ |
| |
| if (0) { |
| #if defined(CONFIG_BT_CTLR_PRIVACY) |
| } else if (ull_filter_lll_rl_enabled()) { |
| struct lll_filter *filter = |
| ull_filter_lll_get((lll->filter_policy & |
| SCAN_FP_FILTER) != 0); |
| uint8_t count, *irks = ull_filter_lll_irks_get(&count); |
| |
| radio_filter_configure(filter->enable_bitmask, |
| filter->addr_type_bitmask, |
| (uint8_t *) filter->bdaddr); |
| |
| radio_ar_configure(count, irks, (lll_aux->phy << 2) | BIT(1)); |
| #endif /* CONFIG_BT_CTLR_PRIVACY */ |
| } else if (IS_ENABLED(CONFIG_BT_CTLR_FILTER_ACCEPT_LIST) && |
| lll->filter_policy) { |
| /* Setup Radio Filter */ |
| struct lll_filter *fal = ull_filter_lll_get(true); |
| |
| radio_filter_configure(fal->enable_bitmask, |
| fal->addr_type_bitmask, |
| (uint8_t *)fal->bdaddr); |
| } |
| |
| sync_aux_prepare_done: |
| /* Calculate event timings, coarse and fine */ |
| ticks_at_event = p->ticks_at_expire; |
| ull = HDR_LLL2ULL(lll_aux); |
| 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(0, ticks_at_start, remainder); |
| |
| hcto = remainder_us + lll_aux->window_size_us; |
| hcto += radio_rx_ready_delay_get(lll_aux->phy, PHY_FLAGS_S8); |
| hcto += addr_us_get(lll_aux->phy); |
| hcto += radio_rx_chain_delay_get(lll_aux->phy, PHY_FLAGS_S8); |
| radio_tmr_hcto_configure(hcto); |
| |
| /* capture end of Rx-ed PDU, extended scan to schedule auxiliary |
| * channel chaining, create connection or to create periodic sync. |
| */ |
| radio_tmr_end_capture(); |
| |
| /* scanner always measures RSSI */ |
| 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_aux->phy, |
| PHY_FLAGS_S8) - |
| HAL_RADIO_GPIO_LNA_OFFSET); |
| #endif /* HAL_RADIO_GPIO_HAVE_LNA_PIN */ |
| |
| #if defined(CONFIG_BT_CTLR_XTAL_ADVANCED) && \ |
| (EVENT_OVERHEAD_PREEMPT_US <= EVENT_OVERHEAD_PREEMPT_MIN_US) |
| /* check if preempt to start has changed */ |
| if (lll_preempt_calc(ull, (TICKER_ID_SCAN_AUX_BASE + |
| ull_scan_aux_lll_handle_get(lll_aux)), |
| ticks_at_event)) { |
| radio_isr_set(isr_done, lll_aux); |
| radio_disable(); |
| } else |
| #endif /* CONFIG_BT_CTLR_XTAL_ADVANCED */ |
| { |
| uint32_t ret; |
| |
| #if defined(CONFIG_BT_CENTRAL) && defined(CONFIG_BT_CTLR_SCHED_ADVANCED) |
| /* calc end of group in us for the anchor where next connection |
| * event to be placed. |
| */ |
| if (lll && lll->conn) { |
| static memq_link_t link; |
| static struct mayfly mfy_after_cen_offset_get = { |
| 0, 0, &link, NULL, |
| ull_sched_mfy_after_cen_offset_get}; |
| |
| /* NOTE: LLL scan instance passed, as done when |
| * establishing legacy connections. |
| */ |
| p->param = lll; |
| mfy_after_cen_offset_get.param = p; |
| |
| ret = mayfly_enqueue(TICKER_USER_ID_LLL, |
| TICKER_USER_ID_ULL_LOW, 1, |
| &mfy_after_cen_offset_get); |
| LL_ASSERT(!ret); |
| } |
| #endif /* CONFIG_BT_CENTRAL && CONFIG_BT_CTLR_SCHED_ADVANCED */ |
| |
| ret = lll_prepare_done(lll_aux); |
| LL_ASSERT(!ret); |
| } |
| |
| DEBUG_RADIO_START_O(1); |
| |
| return 0; |
| } |
| |
| static int is_abort_cb(void *next, void *curr, lll_prepare_cb_t *resume_cb) |
| { |
| struct lll_scan *lll; |
| |
| /* Auxiliary context shall not resume when being preempted, i.e. they |
| * shall not use -EAGAIN as return value. |
| */ |
| ARG_UNUSED(resume_cb); |
| |
| /* Auxiliary event shall not overlap as they are not periodically |
| * scheduled. |
| */ |
| LL_ASSERT(next != curr); |
| |
| lll = ull_scan_lll_is_valid_get(next); |
| if (lll) { |
| /* Next event is scan context, let the current auxiliary scan |
| * continue. |
| */ |
| return 0; |
| } |
| |
| /* Yield current auxiliary event to other than scan events */ |
| return -ECANCELED; |
| } |
| |
| static void abort_cb(struct lll_prepare_param *prepare_param, void *param) |
| { |
| int err; |
| |
| /* NOTE: This is not a prepare being cancelled */ |
| if (!prepare_param) { |
| /* Perform event abort here. |
| * After event has been cleanly aborted, clean up resources |
| * and dispatch event done. |
| */ |
| radio_isr_set(isr_done, param); |
| radio_disable(); |
| return; |
| } |
| |
| /* NOTE: Else clean the top half preparations of the aborted event |
| * currently in preparation pipeline. |
| */ |
| err = lll_hfclock_off(); |
| LL_ASSERT(err >= 0); |
| |
| lll_done(param); |
| } |
| |
| static void isr_done(void *param) |
| { |
| struct lll_sync *lll; |
| uint8_t is_lll_scan; |
| |
| lll_isr_status_reset(); |
| |
| if (param) { |
| lll = ull_scan_aux_lll_parent_get(param, &is_lll_scan); |
| } else { |
| lll = NULL; |
| } |
| |
| /* Check if this aux scan is for periodic advertising train */ |
| if (IS_ENABLED(CONFIG_BT_CTLR_SYNC_PERIODIC) && lll && !is_lll_scan) { |
| struct node_rx_pdu *node_rx; |
| |
| /* Generate message to release aux context and flag the report |
| * generated thereafter by HCI as incomplete. |
| */ |
| node_rx = ull_pdu_rx_alloc(); |
| LL_ASSERT(node_rx); |
| |
| node_rx->hdr.type = NODE_RX_TYPE_EXT_AUX_RELEASE; |
| |
| node_rx->hdr.rx_ftr.param = lll; |
| node_rx->hdr.rx_ftr.aux_failed = 1U; |
| |
| ull_rx_put(node_rx->hdr.link, node_rx); |
| ull_rx_sched(); |
| |
| } else if (!trx_cnt) { |
| struct event_done_extra *e; |
| |
| e = ull_done_extra_type_set(EVENT_DONE_EXTRA_TYPE_SCAN_AUX); |
| LL_ASSERT(e); |
| } |
| |
| lll_isr_cleanup(param); |
| } |
| |
| static void isr_rx_ull_schedule(void *param) |
| { |
| struct lll_scan_aux *lll_aux; |
| struct lll_scan *lll; |
| |
| lll_aux = param; |
| lll = ull_scan_aux_lll_parent_get(lll_aux, NULL); |
| |
| isr_rx(lll, lll_aux, lll_aux->phy); |
| } |
| |
| static void isr_rx_lll_schedule(void *param) |
| { |
| struct node_rx_pdu *node_rx; |
| struct lll_scan *lll; |
| uint8_t phy_aux; |
| |
| node_rx = param; |
| lll = node_rx->hdr.rx_ftr.param; |
| phy_aux = node_rx->hdr.rx_ftr.aux_phy; /* PHY remembered in node rx */ |
| |
| /* scan context has used LLL scheduling for aux reception */ |
| if (lll->is_aux_sched) { |
| isr_rx(lll, NULL, phy_aux); |
| } else { |
| /* `lll->lll_aux` would be allocated in ULL for LLL scheduled |
| * auxiliary PDU reception by scan context and for case |
| * where LLL scheduled chain PDU reception by aux context, it |
| * is assigned with the current aux context's LLL context. |
| */ |
| isr_rx(lll, lll->lll_aux, phy_aux); |
| } |
| } |
| |
| static void isr_rx(struct lll_scan *lll, struct lll_scan_aux *lll_aux, |
| uint8_t phy_aux) |
| { |
| struct node_rx_pdu *node_rx; |
| uint8_t phy_aux_flags_rx; |
| uint8_t devmatch_ok; |
| uint8_t devmatch_id; |
| uint8_t irkmatch_ok; |
| uint8_t irkmatch_id; |
| struct pdu_adv *pdu; |
| uint8_t rssi_ready; |
| uint8_t trx_done; |
| uint8_t crc_ok; |
| uint8_t rl_idx; |
| bool has_adva; |
| int err; |
| |
| if (IS_ENABLED(CONFIG_BT_CTLR_PROFILE_ISR)) { |
| lll_prof_latency_capture(); |
| } |
| |
| /* Read radio status and events */ |
| trx_done = radio_is_done(); |
| if (trx_done) { |
| crc_ok = radio_crc_is_valid(); |
| devmatch_ok = radio_filter_has_match(); |
| devmatch_id = radio_filter_match_get(); |
| if (IS_ENABLED(CONFIG_BT_CTLR_PRIVACY)) { |
| irkmatch_ok = radio_ar_has_match(); |
| irkmatch_id = radio_ar_match_get(); |
| } else { |
| irkmatch_ok = 0U; |
| irkmatch_id = FILTER_IDX_NONE; |
| } |
| rssi_ready = radio_rssi_is_ready(); |
| phy_aux_flags_rx = radio_phy_flags_rx_get(); |
| } else { |
| crc_ok = devmatch_ok = irkmatch_ok = rssi_ready = |
| phy_aux_flags_rx = 0U; |
| devmatch_id = irkmatch_id = FILTER_IDX_NONE; |
| } |
| |
| /* Clear radio rx status and events */ |
| lll_isr_rx_status_reset(); |
| |
| /* No Rx */ |
| if (!trx_done || !crc_ok) { |
| /*Â TODO: Combine the early exit with above if-then-else block |
| */ |
| err = -EINVAL; |
| |
| goto isr_rx_do_close; |
| } |
| |
| node_rx = ull_pdu_rx_alloc_peek(3); |
| if (!node_rx) { |
| err = -ENOBUFS; |
| |
| goto isr_rx_do_close; |
| } |
| |
| pdu = (void *)node_rx->pdu; |
| if ((pdu->type != PDU_ADV_TYPE_EXT_IND) || !pdu->len) { |
| err = -EINVAL; |
| |
| goto isr_rx_do_close; |
| } |
| |
| has_adva = lll_scan_aux_addr_match_get(lll, pdu, &devmatch_ok, |
| &devmatch_id, &irkmatch_ok, |
| &irkmatch_id); |
| |
| #if defined(CONFIG_BT_CTLR_PRIVACY) |
| rl_idx = devmatch_ok ? |
| ull_filter_lll_rl_idx(((lll->filter_policy & |
| SCAN_FP_FILTER) != 0U), |
| devmatch_id) : |
| irkmatch_ok ? ull_filter_lll_rl_irk_idx(irkmatch_id) : |
| FILTER_IDX_NONE; |
| #else |
| rl_idx = FILTER_IDX_NONE; |
| #endif /* CONFIG_BT_CTLR_PRIVACY */ |
| |
| if (has_adva) { |
| bool allow; |
| |
| allow = lll_scan_isr_rx_check(lll, irkmatch_ok, devmatch_ok, |
| rl_idx); |
| if (false) { |
| #if defined(CONFIG_BT_CTLR_SYNC_PERIODIC) && \ |
| defined(CONFIG_BT_CTLR_FILTER_ACCEPT_LIST) |
| } else if (allow || lll->is_sync) { |
| devmatch_ok = allow ? 1U : 0U; |
| #endif /* CONFIG_BT_CTLR_SYNC_PERIODIC && CONFIG_BT_CTLR_FILTER_ACCEPT_LIST */ |
| } else if (!allow) { |
| err = -EINVAL; |
| |
| goto isr_rx_do_close; |
| } |
| } |
| |
| err = isr_rx_pdu(lll, lll_aux, node_rx, pdu, phy_aux, phy_aux_flags_rx, |
| devmatch_ok, devmatch_id, irkmatch_ok, irkmatch_ok, |
| rl_idx, rssi_ready); |
| if (!err) { |
| if (IS_ENABLED(CONFIG_BT_CTLR_PROFILE_ISR)) { |
| lll_prof_send(); |
| } |
| |
| return; |
| } |
| |
| isr_rx_do_close: |
| if (lll_aux) { |
| radio_isr_set(isr_done, NULL); |
| } else { |
| /* Send message to flush Auxiliary PDU list */ |
| if (lll->is_aux_sched && err != -ECANCELED) { |
| struct node_rx_pdu *node_rx; |
| |
| node_rx = ull_pdu_rx_alloc(); |
| LL_ASSERT(node_rx); |
| |
| node_rx->hdr.type = NODE_RX_TYPE_EXT_AUX_RELEASE; |
| |
| /* Use LLL scan context pointer which will be resolved |
| * to LLL aux context in the `ull_scan_aux_release` |
| * function in ULL execution context. |
| * As ULL execution context is the one assigning the |
| * `lll->lll_aux`, if it has not been assigned then |
| * `ull_scan_aux_release` will not dereference it, but |
| * under race, if ULL execution did assign one, it will |
| * free it. |
| */ |
| node_rx->hdr.rx_ftr.param = lll; |
| |
| ull_rx_put(node_rx->hdr.link, node_rx); |
| ull_rx_sched(); |
| } |
| |
| /* Check if LLL scheduled auxiliary PDU reception by scan |
| * context or auxiliary PDU reception by aux context |
| */ |
| if (lll->is_aux_sched) { |
| lll->is_aux_sched = 0U; |
| |
| /* Go back to resuming primary channel scanning */ |
| radio_isr_set(lll_scan_isr_resume, lll); |
| } else { |
| /* auxiliary channel radio event done */ |
| radio_isr_set(isr_done, NULL); |
| } |
| } |
| radio_disable(); |
| } |
| |
| static int isr_rx_pdu(struct lll_scan *lll, struct lll_scan_aux *lll_aux, |
| struct node_rx_pdu *node_rx, struct pdu_adv *pdu, |
| uint8_t phy_aux, uint8_t phy_aux_flags_rx, |
| uint8_t devmatch_ok, uint8_t devmatch_id, |
| uint8_t irkmatch_ok, uint8_t irkmatch_id, uint8_t rl_idx, |
| uint8_t rssi_ready) |
| { |
| struct node_rx_ftr *ftr; |
| |
| bool dir_report = false; |
| |
| if (0) { |
| #if defined(CONFIG_BT_CENTRAL) |
| /* Initiator */ |
| } else if (lll->conn && !lll->conn->central.cancelled && |
| (pdu->adv_ext_ind.adv_mode & BT_HCI_LE_ADV_PROP_CONN) && |
| lll_scan_ext_tgta_check(lll, false, true, pdu, |
| rl_idx, NULL)) { |
| struct lll_scan_aux *lll_aux_to_use; |
| struct node_rx_ftr *ftr; |
| struct node_rx_pdu *rx; |
| struct pdu_adv *pdu_tx; |
| uint32_t conn_space_us; |
| struct ull_hdr *ull; |
| uint32_t pdu_end_us; |
| uint8_t init_tx_addr; |
| uint8_t *init_addr; |
| #if defined(CONFIG_BT_CTLR_PRIVACY) |
| bt_addr_t *lrpa; |
| #endif /* CONFIG_BT_CTLR_PRIVACY */ |
| |
| if (!lll_aux) { |
| lll_aux_to_use = lll->lll_aux; |
| } else { |
| lll_aux_to_use = lll_aux; |
| } |
| |
| if (!lll_aux_to_use) { |
| /* Return -ECHILD, as ULL execution has not yet assigned |
| * an aux context. This can happen only under LLL |
| * scheduling where in LLL auxiliary channel PDU |
| * reception is spawn from LLL primary channel scanning |
| * and on completion will join back to resume primary |
| * channel PDU scanning. |
| */ |
| return -ECHILD; |
| } |
| |
| /* Always use CSA#2 on secondary channel, we need 2 nodes for conn |
| * and CSA#2 events and 2 nodes are always reserved for connection. |
| */ |
| rx = ull_pdu_rx_alloc_peek(4); |
| if (!rx) { |
| return -ENOBUFS; |
| } |
| |
| pdu_end_us = radio_tmr_end_get(); |
| if (!lll->ticks_window) { |
| uint32_t scan_interval_us; |
| |
| /* FIXME: is this correct for continuous scanning? */ |
| scan_interval_us = lll->interval * SCAN_INT_UNIT_US; |
| pdu_end_us %= scan_interval_us; |
| } |
| |
| /* AUX_CONNECT_REQ is the same as CONNECT_IND */ |
| const uint8_t aux_connect_req_len = |
| sizeof(struct pdu_adv_connect_ind); |
| /* AUX_CONNECT_RSP has only AdvA and TargetA in extended common |
| * header |
| */ |
| const uint8_t aux_connect_rsp_len = |
| PDU_AC_EXT_HEADER_SIZE_MIN + |
| sizeof(struct pdu_adv_ext_hdr) + |
| ADVA_SIZE + TARGETA_SIZE; |
| |
| ull = HDR_LLL2ULL(lll); |
| if (pdu_end_us > (HAL_TICKER_TICKS_TO_US(ull->ticks_slot) - |
| EVENT_IFS_US - |
| PDU_AC_MAX_US(aux_connect_req_len, phy_aux) - |
| EVENT_IFS_US - |
| PDU_AC_MAX_US(aux_connect_rsp_len, phy_aux) - |
| EVENT_OVERHEAD_START_US - |
| EVENT_TICKER_RES_MARGIN_US)) { |
| return -ETIME; |
| } |
| |
| #if defined(CONFIG_BT_CTLR_PRIVACY) |
| lrpa = ull_filter_lll_lrpa_get(rl_idx); |
| if (lll->rpa_gen && lrpa) { |
| init_tx_addr = 1; |
| init_addr = lrpa->val; |
| } else { |
| #else |
| if (1) { |
| #endif |
| init_tx_addr = lll->init_addr_type; |
| init_addr = lll->init_addr; |
| } |
| |
| pdu_tx = radio_pkt_scratch_get(); |
| |
| lll_scan_prepare_connect_req(lll, pdu_tx, phy_aux, |
| pdu->tx_addr, |
| pdu->adv_ext_ind.ext_hdr.data, |
| init_tx_addr, init_addr, |
| &conn_space_us); |
| |
| radio_pkt_tx_set(pdu_tx); |
| |
| /* assert if radio packet ptr is not set and radio started tx */ |
| LL_ASSERT(!radio_is_ready()); |
| |
| if (IS_ENABLED(CONFIG_BT_CTLR_PROFILE_ISR)) { |
| lll_prof_cputime_capture(); |
| } |
| |
| /* capture end of Tx-ed PDU, used to calculate HCTO. */ |
| radio_tmr_end_capture(); |
| |
| radio_tmr_tifs_set(EVENT_IFS_US); |
| radio_switch_complete_and_rx(phy_aux); |
| radio_isr_set(isr_tx_connect_req, lll_aux_to_use); |
| |
| #if defined(HAL_RADIO_GPIO_HAVE_PA_PIN) |
| if (IS_ENABLED(CONFIG_BT_CTLR_PROFILE_ISR)) { |
| /* PA/LNA enable is overwriting packet end |
| * used in ISR profiling, hence back it up |
| * for later use. |
| */ |
| lll_prof_radio_end_backup(); |
| } |
| radio_gpio_pa_setup(); |
| radio_gpio_pa_lna_enable(radio_tmr_tifs_base_get() + |
| EVENT_IFS_US - |
| radio_rx_chain_delay_get(phy_aux, |
| phy_aux_flags_rx) - |
| HAL_RADIO_GPIO_PA_OFFSET); |
| #endif /* HAL_RADIO_GPIO_HAVE_PA_PIN */ |
| |
| #if defined(CONFIG_BT_CTLR_CONN_RSSI) |
| if (rssi_ready) { |
| lll->conn->rssi_latest = radio_rssi_get(); |
| } |
| #endif /* CONFIG_BT_CTLR_CONN_RSSI */ |
| |
| /* block CPU so that there is no CRC error on pdu tx, |
| * this is only needed if we want the CPU to sleep. |
| * while(!radio_has_disabled()) |
| * {cpu_sleep();} |
| * radio_status_reset(); |
| */ |
| |
| /* Stop further connection initiation */ |
| lll->conn->central.initiated = 1U; |
| |
| /* Stop further initiating events */ |
| lll->is_stop = 1U; |
| |
| /* Populate the connection complete message */ |
| rx = ull_pdu_rx_alloc(); |
| rx->hdr.type = NODE_RX_TYPE_CONNECTION; |
| rx->hdr.handle = 0xffff; |
| |
| (void)memcpy(rx->pdu, pdu_tx, |
| (offsetof(struct pdu_adv, connect_ind) + |
| sizeof(struct pdu_adv_connect_ind))); |
| |
| /* ChSel is RFU in AUX_ADV_IND but we do need to use CSA#2 for |
| * connections initiated on the secondary advertising channel |
| * thus overwrite chan_sel to make it work seamlessly. |
| */ |
| pdu = (void *)rx->pdu; |
| pdu->chan_sel = 1; |
| |
| ftr = &(rx->hdr.rx_ftr); |
| ftr->param = lll; |
| ftr->ticks_anchor = radio_tmr_start_get(); |
| ftr->radio_end_us = conn_space_us - |
| radio_rx_chain_delay_get(phy_aux, |
| phy_aux_flags_rx); |
| |
| #if defined(CONFIG_BT_CTLR_PRIVACY) |
| ftr->rl_idx = irkmatch_ok ? rl_idx : FILTER_IDX_NONE; |
| ftr->lrpa_used = lll->rpa_gen && lrpa; |
| #endif /* CONFIG_BT_CTLR_PRIVACY */ |
| |
| ftr->extra = ull_pdu_rx_alloc(); |
| |
| /* Hold onto connection event message until after successful |
| * reception of CONNECT_RSP |
| */ |
| lll_aux_to_use->node_conn_rx = rx; |
| |
| /* Increase trx count so as to not generate done extra event |
| * when LLL scheduling of Auxiliary PDU reception |
| */ |
| if (!lll_aux) { |
| trx_cnt++; |
| } |
| |
| return 0; |
| |
| /* Active scanner */ |
| } else if (!lll->conn && |
| lll->type && |
| ((lll_aux && !lll_aux->state) || |
| (lll->lll_aux && !lll->lll_aux->state)) && |
| (pdu->adv_ext_ind.adv_mode & BT_HCI_LE_ADV_PROP_SCAN) && |
| lll_scan_ext_tgta_check(lll, false, false, pdu, rl_idx, |
| &dir_report)) { |
| #else /* !CONFIG_BT_CENTRAL */ |
| } else if (lll && lll->type && |
| ((lll_aux && !lll_aux->state) || |
| (lll->lll_aux && !lll->lll_aux->state)) && |
| (pdu->adv_ext_ind.adv_mode & BT_HCI_LE_ADV_PROP_SCAN) && |
| lll_scan_ext_tgta_check(lll, false, false, pdu, rl_idx, |
| &dir_report)) { |
| #endif /* !CONFIG_BT_CENTRAL */ |
| struct node_rx_pdu *rx; |
| struct pdu_adv *pdu_tx; |
| #if defined(CONFIG_BT_CTLR_PRIVACY) |
| bt_addr_t *lrpa; |
| #endif /* CONFIG_BT_CTLR_PRIVACY */ |
| |
| /* Check if 4 nodes free, 2 will be utilized for aux PDU and |
| * scan response PDU; 2 more to ensure connections have them. |
| */ |
| rx = ull_pdu_rx_alloc_peek(4); |
| if (!rx) { |
| return -ENOBUFS; |
| } |
| |
| /* setup tIFS switching */ |
| radio_tmr_tifs_set(EVENT_IFS_US); |
| radio_switch_complete_and_rx(phy_aux); |
| |
| /* prepare the scan request packet */ |
| pdu_tx = (void *)radio_pkt_scratch_get(); |
| pdu_tx->type = PDU_ADV_TYPE_SCAN_REQ; |
| pdu_tx->rx_addr = pdu->tx_addr; |
| pdu_tx->len = sizeof(struct pdu_adv_scan_req); |
| #if defined(CONFIG_BT_CTLR_PRIVACY) |
| lrpa = ull_filter_lll_lrpa_get(rl_idx); |
| if (lll->rpa_gen && lrpa) { |
| pdu_tx->tx_addr = 1; |
| (void)memcpy(pdu_tx->scan_req.scan_addr, lrpa->val, |
| BDADDR_SIZE); |
| } else { |
| #else |
| if (1) { |
| #endif /* CONFIG_BT_CTLR_PRIVACY */ |
| pdu_tx->tx_addr = lll->init_addr_type; |
| (void)memcpy(pdu_tx->scan_req.scan_addr, lll->init_addr, |
| BDADDR_SIZE); |
| } |
| (void)memcpy(pdu_tx->scan_req.adv_addr, |
| &pdu->adv_ext_ind.ext_hdr.data[ADVA_OFFSET], |
| BDADDR_SIZE); |
| |
| radio_pkt_tx_set(pdu_tx); |
| |
| /* assert if radio packet ptr is not set and radio started tx */ |
| LL_ASSERT(!radio_is_ready()); |
| |
| if (IS_ENABLED(CONFIG_BT_CTLR_PROFILE_ISR)) { |
| lll_prof_cputime_capture(); |
| } |
| |
| /* capture end of Tx-ed PDU, used to calculate HCTO. */ |
| radio_tmr_end_capture(); |
| |
| #if defined(HAL_RADIO_GPIO_HAVE_PA_PIN) |
| if (IS_ENABLED(CONFIG_BT_CTLR_PROFILE_ISR)) { |
| /* PA/LNA enable is overwriting packet end |
| * used in ISR profiling, hence back it up |
| * for later use. |
| */ |
| lll_prof_radio_end_backup(); |
| } |
| |
| radio_gpio_pa_setup(); |
| radio_gpio_pa_lna_enable(radio_tmr_tifs_base_get() + |
| EVENT_IFS_US - |
| radio_rx_chain_delay_get(phy_aux, |
| phy_aux_flags_rx) - |
| HAL_RADIO_GPIO_PA_OFFSET); |
| #endif /* HAL_RADIO_GPIO_HAVE_PA_PIN */ |
| |
| (void)ull_pdu_rx_alloc(); |
| |
| node_rx->hdr.type = NODE_RX_TYPE_EXT_AUX_REPORT; |
| |
| ftr = &(node_rx->hdr.rx_ftr); |
| if (lll_aux) { |
| ftr->param = lll_aux; |
| radio_isr_set(isr_tx_scan_req_ull_schedule, |
| lll_aux); |
| lll_aux->state = 1U; |
| } else { |
| ftr->param = lll; |
| radio_isr_set(isr_tx_scan_req_lll_schedule, |
| node_rx); |
| lll->lll_aux->state = 1U; |
| } |
| ftr->ticks_anchor = radio_tmr_start_get(); |
| ftr->radio_end_us = radio_tmr_end_get() - |
| radio_rx_chain_delay_get(phy_aux, |
| phy_aux_flags_rx); |
| ftr->rssi = (rssi_ready) ? radio_rssi_get() : |
| BT_HCI_LE_RSSI_NOT_AVAILABLE; |
| ftr->scan_req = 1U; |
| ftr->scan_rsp = 0U; |
| |
| #if defined(CONFIG_BT_CTLR_PRIVACY) |
| ftr->rl_idx = irkmatch_ok ? rl_idx : FILTER_IDX_NONE; |
| #endif /* CONFIG_BT_CTLR_PRIVACY */ |
| |
| #if defined(CONFIG_BT_CTLR_EXT_SCAN_FP) |
| ftr->direct = dir_report; |
| #endif /* CONFIG_BT_CTLR_EXT_SCAN_FP */ |
| |
| #if defined(CONFIG_BT_CTLR_SYNC_PERIODIC) && \ |
| defined(CONFIG_BT_CTLR_FILTER_ACCEPT_LIST) |
| ftr->devmatch = devmatch_ok; |
| #endif /* CONFIG_BT_CTLR_SYNC_PERIODIC && CONFIG_BT_CTLR_FILTER_ACCEPT_LIST */ |
| |
| ftr->aux_lll_sched = 0U; |
| |
| ull_rx_put(node_rx->hdr.link, node_rx); |
| ull_rx_sched(); |
| |
| return 0; |
| |
| /* Passive scanner or scan responses */ |
| #if defined(CONFIG_BT_CENTRAL) |
| } else if (!lll->conn && |
| ((lll_aux && lll_aux->is_chain_sched) || |
| (lll->lll_aux && lll->lll_aux->is_chain_sched) || |
| lll_scan_ext_tgta_check(lll, false, false, pdu, rl_idx, |
| &dir_report))) { |
| #else /* !CONFIG_BT_CENTRAL */ |
| } else if ((lll_aux && lll_aux->is_chain_sched) || |
| (lll->lll_aux && lll->lll_aux->is_chain_sched) || |
| lll_scan_ext_tgta_check(lll, false, false, pdu, rl_idx, |
| &dir_report)) { |
| #endif /* !CONFIG_BT_CENTRAL */ |
| |
| ftr = &(node_rx->hdr.rx_ftr); |
| if (lll_aux) { |
| ftr->param = lll_aux; |
| ftr->scan_rsp = lll_aux->state; |
| |
| /* Further auxiliary PDU reception will be chain PDUs */ |
| lll_aux->is_chain_sched = 1U; |
| } else if (lll->lll_aux) { |
| ftr->param = lll; |
| ftr->scan_rsp = lll->lll_aux->state; |
| |
| /* Further auxiliary PDU reception will be chain PDUs */ |
| lll->lll_aux->is_chain_sched = 1U; |
| } else { |
| /* Return -ECHILD, as ULL execution has not yet assigned |
| * an aux context. This can happen only under LLL |
| * scheduling where in LLL auxiliary channel PDU |
| * reception is spawn from LLL primary channel scanning |
| * and on completion will join back to resume primary |
| * channel PDU scanning. |
| */ |
| return -ECHILD; |
| } |
| |
| /* Allocate before `lll_scan_aux_setup` call, so that a new |
| * free PDU buffer is used to receive auxiliary PDU when using |
| * LLL scheduling. |
| */ |
| (void)ull_pdu_rx_alloc(); |
| |
| ftr->ticks_anchor = radio_tmr_start_get(); |
| ftr->radio_end_us = radio_tmr_end_get() - |
| radio_rx_chain_delay_get(phy_aux, |
| phy_aux_flags_rx); |
| ftr->phy_flags = phy_aux_flags_rx; |
| ftr->rssi = (rssi_ready) ? radio_rssi_get() : |
| BT_HCI_LE_RSSI_NOT_AVAILABLE; |
| ftr->scan_req = 0U; |
| |
| #if defined(CONFIG_BT_CTLR_PRIVACY) |
| ftr->rl_idx = irkmatch_ok ? rl_idx : FILTER_IDX_NONE; |
| #endif /* CONFIG_BT_CTLR_PRIVACY */ |
| |
| #if defined(CONFIG_BT_CTLR_EXT_SCAN_FP) |
| ftr->direct = dir_report; |
| #endif /* CONFIG_BT_CTLR_EXT_SCAN_FP */ |
| |
| #if defined(CONFIG_BT_CTLR_SYNC_PERIODIC) && \ |
| defined(CONFIG_BT_CTLR_FILTER_ACCEPT_LIST) |
| ftr->devmatch = devmatch_ok; |
| #endif /* CONFIG_BT_CTLR_SYNC_PERIODIC && CONFIG_BT_CTLR_FILTER_ACCEPT_LIST */ |
| |
| ftr->aux_lll_sched = lll_scan_aux_setup(pdu, phy_aux, |
| phy_aux_flags_rx, |
| lll_scan_aux_isr_aux_setup, |
| lll); |
| |
| node_rx->hdr.type = NODE_RX_TYPE_EXT_AUX_REPORT; |
| |
| ull_rx_put(node_rx->hdr.link, node_rx); |
| |
| ull_rx_sched(); |
| |
| /* Next aux scan is scheduled from LLL, we already handled radio |
| * disable so prevent caller from doing it again. |
| */ |
| if (ftr->aux_lll_sched) { |
| if (!lll_aux) { |
| lll->is_aux_sched = 1U; |
| } |
| |
| return 0; |
| } |
| |
| /* Increase trx count so as to not generate done extra event |
| * as a valid Auxiliary PDU node rx is being reported to ULL. |
| */ |
| trx_cnt++; |
| |
| return -ECANCELED; |
| } |
| |
| return -EINVAL; |
| } |
| |
| static void isr_tx(struct lll_scan_aux *lll_aux, void *pdu_rx, |
| void (*isr)(void *), void *param) |
| { |
| uint32_t hcto; |
| |
| /* Clear radio tx status and events */ |
| lll_isr_tx_status_reset(); |
| |
| /* complete the reception and disable radio */ |
| radio_switch_complete_and_disable(); |
| |
| radio_pkt_rx_set(pdu_rx); |
| |
| /* assert if radio packet ptr is not set and radio started rx */ |
| LL_ASSERT(!radio_is_ready()); |
| |
| #if defined(CONFIG_BT_CTLR_PRIVACY) |
| if (ull_filter_lll_rl_enabled()) { |
| uint8_t count, *irks = ull_filter_lll_irks_get(&count); |
| |
| radio_ar_configure(count, irks, (lll_aux->phy << 2) | BIT(1)); |
| } |
| #endif /* CONFIG_BT_CTLR_PRIVACY */ |
| |
| /* +/- 2us active clock jitter, +1 us hcto compensation */ |
| hcto = radio_tmr_tifs_base_get() + EVENT_IFS_US + |
| (EVENT_CLOCK_JITTER_US << 1U) + RANGE_DELAY_US + 1U; |
| hcto += radio_rx_chain_delay_get(lll_aux->phy, PHY_FLAGS_S8); |
| hcto += addr_us_get(lll_aux->phy); |
| hcto -= radio_tx_chain_delay_get(lll_aux->phy, PHY_FLAGS_S8); |
| radio_tmr_hcto_configure(hcto); |
| |
| /* capture end of Rx-ed PDU, extended scan to schedule auxiliary |
| * channel chaining. |
| */ |
| radio_tmr_end_capture(); |
| |
| /* scanner always measures RSSI */ |
| radio_rssi_measure(); |
| |
| #if defined(HAL_RADIO_GPIO_HAVE_LNA_PIN) |
| if (IS_ENABLED(CONFIG_BT_CTLR_PROFILE_ISR)) { |
| /* PA/LNA enable is overwriting packet end used in ISR |
| * profiling, hence back it up for later use. |
| */ |
| lll_prof_radio_end_backup(); |
| } |
| |
| radio_gpio_lna_setup(); |
| radio_gpio_pa_lna_enable(radio_tmr_tifs_base_get() + EVENT_IFS_US - |
| (EVENT_CLOCK_JITTER_US << 1U) - |
| radio_tx_chain_delay_get(lll_aux->phy, |
| PHY_FLAGS_S8) - |
| HAL_RADIO_GPIO_LNA_OFFSET); |
| #endif /* HAL_RADIO_GPIO_HAVE_LNA_PIN */ |
| |
| radio_isr_set(isr, param); |
| |
| if (IS_ENABLED(CONFIG_BT_CTLR_PROFILE_ISR)) { |
| /* NOTE: as scratch packet is used to receive, it is safe to |
| * generate profile event using rx nodes. |
| */ |
| lll_prof_send(); |
| } |
| } |
| |
| static void isr_tx_scan_req_ull_schedule(void *param) |
| { |
| struct node_rx_pdu *node_rx; |
| |
| node_rx = ull_pdu_rx_alloc_peek(1); |
| LL_ASSERT(node_rx); |
| |
| isr_tx(param, node_rx->pdu, isr_rx_ull_schedule, param); |
| } |
| |
| static void isr_tx_scan_req_lll_schedule(void *param) |
| { |
| struct node_rx_pdu *node_rx_adv = param; |
| struct node_rx_pdu *node_rx; |
| struct lll_scan *lll; |
| |
| lll = node_rx_adv->hdr.rx_ftr.param; |
| |
| node_rx = ull_pdu_rx_alloc_peek(1); |
| LL_ASSERT(node_rx); |
| |
| isr_tx(lll->lll_aux, node_rx->pdu, isr_rx_lll_schedule, param); |
| } |
| |
| #if defined(CONFIG_BT_CENTRAL) |
| static void isr_tx_connect_req(void *param) |
| { |
| struct node_rx_pdu *node_rx; |
| |
| node_rx = ull_pdu_rx_alloc_peek(1); |
| LL_ASSERT(node_rx); |
| |
| isr_tx(param, (void *)node_rx->pdu, isr_rx_connect_rsp, param); |
| } |
| |
| static void isr_rx_connect_rsp(void *param) |
| { |
| struct lll_scan_aux *lll_aux; |
| struct pdu_adv *pdu_rx; |
| struct node_rx_pdu *rx; |
| struct lll_scan *lll; |
| uint8_t irkmatch_ok; |
| uint8_t irkmatch_id; |
| uint8_t trx_done; |
| uint8_t rl_idx; |
| uint8_t crc_ok; |
| |
| if (IS_ENABLED(CONFIG_BT_CTLR_PROFILE_ISR)) { |
| lll_prof_latency_capture(); |
| } |
| |
| /* Read radio status */ |
| trx_done = radio_is_done(); |
| if (trx_done) { |
| crc_ok = radio_crc_is_valid(); |
| if (IS_ENABLED(CONFIG_BT_CTLR_PRIVACY)) { |
| irkmatch_ok = radio_ar_has_match(); |
| irkmatch_id = radio_ar_match_get(); |
| } else { |
| irkmatch_ok = 0U; |
| irkmatch_id = FILTER_IDX_NONE; |
| } |
| } else { |
| crc_ok = irkmatch_ok = 0U; |
| irkmatch_id = FILTER_IDX_NONE; |
| } |
| |
| /* Clear radio rx status and events */ |
| lll_isr_rx_status_reset(); |
| |
| /* Get the reference to primary scanner's LLL context */ |
| lll_aux = param; |
| lll = ull_scan_aux_lll_parent_get(lll_aux, NULL); |
| |
| /* Use the reserved/saved node rx to generate connection complete or |
| * release it if failed to receive AUX_CONNECT_RSP PDU. |
| */ |
| rx = lll_aux->node_conn_rx; |
| LL_ASSERT(rx); |
| lll_aux->node_conn_rx = NULL; |
| |
| #if defined(CONFIG_BT_CTLR_PRIVACY) |
| rl_idx = irkmatch_ok ? ull_filter_lll_rl_irk_idx(irkmatch_id) : |
| FILTER_IDX_NONE; |
| #else |
| rl_idx = FILTER_IDX_NONE; |
| #endif /* CONFIG_BT_CTLR_PRIVACY */ |
| |
| /* Check for PDU reception */ |
| if (trx_done && crc_ok) { |
| struct node_rx_pdu *node_rx; |
| struct pdu_adv *pdu_tx; |
| |
| pdu_tx = radio_pkt_scratch_get(); |
| |
| node_rx = ull_pdu_rx_alloc_peek(1); |
| LL_ASSERT(node_rx); |
| pdu_rx = (void *)node_rx->pdu; |
| |
| trx_done = isr_rx_connect_rsp_check(lll, pdu_tx, pdu_rx, |
| rl_idx); |
| } else { |
| trx_done = 0U; |
| } |
| |
| /* No Rx or invalid PDU received */ |
| if (!trx_done) { |
| struct node_rx_ftr *ftr; |
| |
| /* Try again with connection initiation */ |
| lll->conn->central.initiated = 0U; |
| |
| /* Dont stop initiating events on primary channels */ |
| lll->is_stop = 0U; |
| |
| ftr = &(rx->hdr.rx_ftr); |
| |
| rx->hdr.type = NODE_RX_TYPE_RELEASE; |
| ull_rx_put(rx->hdr.link, rx); |
| |
| rx = ftr->extra; |
| rx->hdr.type = NODE_RX_TYPE_RELEASE; |
| goto isr_rx_do_close; |
| } |
| |
| /* Update the max Tx and Rx time; and connection PHY based on the |
| * extended advertising PHY used to establish the connection. |
| */ |
| #if defined(CONFIG_BT_CTLR_PHY) |
| struct lll_conn *conn_lll = lll->conn; |
| |
| #if defined(CONFIG_BT_CTLR_DATA_LENGTH) |
| #if defined(CONFIG_BT_LL_SW_LLCP_LEGACY) |
| conn_lll->max_tx_time = MAX(conn_lll->max_tx_time, |
| PDU_DC_MAX_US(PDU_DC_PAYLOAD_SIZE_MIN, |
| lll_aux->phy)); |
| conn_lll->max_rx_time = MAX(conn_lll->max_rx_time, |
| PDU_DC_MAX_US(PDU_DC_PAYLOAD_SIZE_MIN, |
| lll_aux->phy)); |
| #else |
| conn_lll->dle.eff.max_tx_time = MAX(conn_lll->dle.eff.max_tx_time, |
| PDU_DC_MAX_US(PDU_DC_PAYLOAD_SIZE_MIN, |
| lll_aux->phy)); |
| conn_lll->dle.eff.max_rx_time = MAX(conn_lll->dle.eff.max_rx_time, |
| PDU_DC_MAX_US(PDU_DC_PAYLOAD_SIZE_MIN, |
| lll_aux->phy)); |
| #endif /* CONFIG_BT_LL_SW_LLCP_LEGACY */ |
| #endif /* CONFIG_BT_CTLR_DATA_LENGTH*/ |
| |
| conn_lll->phy_tx = lll_aux->phy; |
| conn_lll->phy_tx_time = lll_aux->phy; |
| conn_lll->phy_rx = lll_aux->phy; |
| #endif /* CONFIG_BT_CTLR_PHY */ |
| |
| #if defined(CONFIG_BT_CTLR_PRIVACY) |
| if (irkmatch_ok) { |
| struct node_rx_ftr *ftr; |
| struct pdu_adv *pdu; |
| |
| pdu = (void *)rx->pdu; |
| pdu->rx_addr = pdu_rx->tx_addr; |
| (void)memcpy(pdu->connect_ind.adv_addr, |
| &pdu_rx->adv_ext_ind.ext_hdr.data[ADVA_OFFSET], |
| BDADDR_SIZE); |
| ftr = &(rx->hdr.rx_ftr); |
| ftr->rl_idx = rl_idx; |
| } |
| #endif /* CONFIG_BT_CTLR_PRIVACY */ |
| |
| isr_rx_do_close: |
| ull_rx_put(rx->hdr.link, rx); |
| ull_rx_sched(); |
| |
| if (lll->lll_aux) { |
| struct node_rx_pdu *node_rx; |
| |
| /* Send message to flush Auxiliary PDU list */ |
| node_rx = ull_pdu_rx_alloc(); |
| LL_ASSERT(node_rx); |
| |
| node_rx->hdr.type = NODE_RX_TYPE_EXT_AUX_RELEASE; |
| |
| node_rx->hdr.rx_ftr.param = lll->lll_aux; |
| |
| ull_rx_put(node_rx->hdr.link, node_rx); |
| ull_rx_sched(); |
| |
| radio_isr_set(lll_scan_isr_resume, lll); |
| } else { |
| radio_isr_set(isr_done, NULL); |
| } |
| |
| radio_disable(); |
| } |
| |
| static bool isr_rx_connect_rsp_check(struct lll_scan *lll, |
| struct pdu_adv *pdu_tx, |
| struct pdu_adv *pdu_rx, uint8_t rl_idx) |
| { |
| if (pdu_rx->type != PDU_ADV_TYPE_AUX_CONNECT_RSP) { |
| return false; |
| } |
| |
| if (pdu_rx->len != offsetof(struct pdu_adv_com_ext_adv, |
| ext_hdr_adv_data) + |
| offsetof(struct pdu_adv_ext_hdr, data) + ADVA_SIZE + |
| TARGETA_SIZE) { |
| return false; |
| } |
| |
| if (pdu_rx->adv_ext_ind.adv_mode || |
| !pdu_rx->adv_ext_ind.ext_hdr.adv_addr || |
| !pdu_rx->adv_ext_ind.ext_hdr.tgt_addr) { |
| return false; |
| } |
| |
| return lll_scan_adva_check(lll, pdu_rx->tx_addr, |
| &pdu_rx->adv_ext_ind.ext_hdr.data[ADVA_OFFSET], |
| rl_idx) && |
| (pdu_rx->rx_addr == pdu_tx->tx_addr) && |
| (memcmp(&pdu_rx->adv_ext_ind.ext_hdr.data[TGTA_OFFSET], |
| pdu_tx->connect_ind.init_addr, BDADDR_SIZE) == 0); |
| } |
| |
| static void isr_early_abort(void *param) |
| { |
| struct event_done_extra *e; |
| |
| e = ull_done_extra_type_set(EVENT_DONE_EXTRA_TYPE_SCAN_AUX); |
| LL_ASSERT(e); |
| |
| lll_isr_early_abort(param); |
| } |
| #endif /* CONFIG_BT_CENTRAL */ |