From c3f21245ce3ce73e1d745b155007aa47285e1327 Mon Sep 17 00:00:00 2001 From: Vinayak Kariappa Chettimada Date: Sun, 26 Oct 2025 16:47:49 +0100 Subject: [PATCH] Bluetooth: Controller: Fix Extended Scanning for slow CPUs Add implementation to gracefully handle failure due to high Radio ISR latencies due to higher CPU usage in LLL execution context just before the handling of Radio IRQs. Higher CPU time usage in LLL execution context can be due to slow CPUs, example, nRF51x SoC Series with 16 MHz ARM Cortex-M0 CPU. Signed-off-by: Vinayak Kariappa Chettimada --- .../ll_sw/nordic/lll/lll_scan_aux.c | 64 ++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan_aux.c b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan_aux.c index 6665dc7390e29..0e8fc96d2e8cf 100644 --- a/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan_aux.c +++ b/subsys/bluetooth/controller/ll_sw/nordic/lll/lll_scan_aux.c @@ -355,9 +355,69 @@ void lll_scan_aux_isr_aux_setup(void *param) aux_start_us -= window_widening_us; aux_start_us -= EVENT_JITTER_US; + /* NOTE: For slower CPUs, LLL execution context will introduce Radio ISR latency beyond + * the threshold to meet hard realtime deadline to setup auxiliary PDU reception. + * Detect the latency and gracefully close the radio event. If there is consecutive + * failure, then lets catch it in an assertion check. + */ + const uint8_t latency_skip_max = 3U; + static uint8_t latency_skip_countdown = latency_skip_max; + + /* Setup radio for hard realtime auxiliary PDU reception */ start_us = radio_tmr_start_us(0, aux_start_us); - LL_ASSERT_MSG(start_us == (aux_start_us + 1U), "aux_offset %u us, start_us %u != %u", - aux_offset_us, start_us, (aux_start_us + 1U)); + if (start_us != (aux_start_us + 1U)) { + /* Failed to setup radio at the required hard realtime deadline, lets have assertion + * check if we fail consecutively. + */ + if (latency_skip_countdown != 0U) { + latency_skip_countdown--; + + /* Gracefully close the radio event */ + if (lll_aux) { + /* Auxiliary channel radio event done */ + radio_isr_set(isr_done, lll_aux); + } else { + struct node_rx_pdu *node_rx2; + + node_rx2 = ull_pdu_rx_alloc(); + LL_ASSERT_ERR(node_rx2); + + node_rx2->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_rx2->rx_ftr.param = lll; + node_rx2->rx_ftr.lll_aux = lll->lll_aux; + + ull_rx_put_sched(node_rx2->hdr.link, node_rx2); + + /* Go back to resuming primary channel scanning */ + radio_isr_set(lll_scan_isr_resume, lll); + } + + radio_disable(); + + return; + } else if (IS_ENABLED(CONFIG_BT_CTLR_PROFILE_ISR)) { + /* If profiling is enabled, lets be verbose */ + lll_prof_cputime_capture(); + + LL_ASSERT_MSG(false, "%s: Radio ISR latency: %u us, aux_offset %u us," + " start_us %u != %u", __func__, lll_prof_latency_get(), + aux_offset_us, start_us, (aux_start_us + 1U)); + } else { + LL_ASSERT_ERR(false); + } + } else { + /* Radio has been setup successfully at the required hard realtime deadline */ + latency_skip_countdown = latency_skip_max; + } /* Setup header complete timeout */ hcto = start_us;