This is an automated email from the ASF dual-hosted git repository.

andk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-nimble.git


The following commit(s) were added to refs/heads/master by this push:
     new 0474bee43 nimble/phy/nrf5x: Fix races in TIMER0 setup
0474bee43 is described below

commit 0474bee43a27ff73f1dcbff29a11777f91b20b93
Author: Andrzej Kaczmarek <andrzej.kaczma...@codecoup.pl>
AuthorDate: Fri Jul 4 10:21:38 2025 +0200

    nimble/phy/nrf5x: Fix races in TIMER0 setup
    
    In cases where RADIO ISR is delayed during transitions it's possible
    that TIMER0 is not set properly and that could stall phy in waiting
    state. This is more likely to happen on non-Mynewt systems where IRQs
    can be configured/handled differently than on Mynewt.
    
    There are few issues handled here:
    1. Make sure we always set CC register in proper order, i.e. clear
       COMPARE event, then enable related PPI, then set the proper CC value.
       This ensures that whenever COMPARE event is triggered, radio is ready
       to handle it.
    2. Handle case where CC may be set to the current value of TIMER0. This
       would not trigger COMPARE event so it should be handled as a miss.
    3. Reset all CC registers to 0 on start of each event. This prevents
       spurious triggers of COMPARE event due to some leftovers from
       previous event. We don't need to do this on each TX/RX because at the
       start of each TX/RX the values in CC registers are either start times
       or captured timer of previous TX/RX thus they are always in the past.
---
 nimble/drivers/nrf5x/src/ble_phy.c | 70 +++++++++++++++++++++++++-------------
 1 file changed, 46 insertions(+), 24 deletions(-)

diff --git a/nimble/drivers/nrf5x/src/ble_phy.c 
b/nimble/drivers/nrf5x/src/ble_phy.c
index 634d58071..6f9e05142 100644
--- a/nimble/drivers/nrf5x/src/ble_phy.c
+++ b/nimble/drivers/nrf5x/src/ble_phy.c
@@ -349,6 +349,32 @@ struct nrf_ccm_data
 struct nrf_ccm_data g_nrf_ccm_data;
 #endif
 
+static void
+timer0_reset(void)
+{
+    /* Reset CC registers to avoid triggering spurious COMPARE events */
+    NRF_TIMER0->CC[0] = 0;
+    NRF_TIMER0->CC[1] = 0;
+    NRF_TIMER0->CC[2] = 0;
+    NRF_TIMER0->CC[3] = 0;
+}
+
+static bool
+timer0_did_miss(int cc, int cc_scratch)
+{
+    uint32_t now, target;
+
+    nrf_timer_task_trigger(NRF_TIMER0, nrf_timer_capture_task_get(cc_scratch));
+    target = NRF_TIMER0->CC[cc];
+    now = NRF_TIMER0->CC[cc_scratch];
+
+    /* COMPARE event is not triggered when CC is set to the current value of
+     * TIMER0 (at the time it was set), so the case of equal CC values without
+     * a COMPARE event should be also considered a miss here.
+     */
+    return (now >= target) && !NRF_TIMER0->EVENTS_COMPARE[cc];
+}
+
 #if MYNEWT_VAL(BLE_LL_PHY)
 
 /* Packet start offset (in usecs). This is the preamble plus access address.
@@ -706,6 +732,8 @@ ble_phy_set_start_time(uint32_t cputime, uint8_t rem_us, 
bool tx)
         return -1;
     }
 
+    timer0_reset();
+
     /* Clear and set TIMER0 to fire off at proper time */
     nrf_timer_task_trigger(NRF_TIMER0, NRF_TIMER_TASK_CLEAR);
     nrf_timer_cc_set(NRF_TIMER0, 0, radio_rem_us + rem_us_corr);
@@ -885,27 +913,24 @@ ble_phy_wfr_enable(int txrx, uint8_t tx_phy_mode, 
uint32_t wfr_usecs)
     /* Adjust for delay between actual access address RX and EVENT_ADDRESS */
     end_time += g_ble_phy_t_rxaddrdelay[phy];
 
-    /* wfr_secs is the time from rxen until timeout */
-    nrf_timer_cc_set(NRF_TIMER0, 3, end_time);
-    NRF_TIMER0->EVENTS_COMPARE[3] = 0;
-
     /* Enable wait for response PPI */
+    NRF_TIMER0->EVENTS_COMPARE[3] = 0;
     phy_ppi_wfr_enable();
+    nrf_timer_cc_set(NRF_TIMER0, 3, end_time);
 
     /*
-     * It may happen that if CPU is halted for a brief moment (e.g. during 
flash
-     * erase or write), TIMER0 already counted past CC[3] and thus wfr will not
-     * fire as expected. In case this happened, let's just disable PPIs for wfr
-     * and trigger wfr manually (i.e. disable radio).
+     * It may happen that if CPU is halted for a brief moment (e.g. during
+     * flash, erase or write), TIMER0 already counted past CC[3] and thus wfr
+     * will not fire as expected. In case this happened, let's just disable
+     * PPIs for wfr and trigger wfr manually (i.e. disable radio).
      *
      * Note that the same applies to RX start time set in CC[0] but since it
      * should fire earlier than wfr, fixing wfr is enough.
      *
-     * CC[1] is only used as a reference on RX start, we do not need it here so
-     * it can be used to read TIMER0 counter.
+     * CC[1] is only used as a reference on RX start. We do not need it here,
+     * so we can use it as a scratch register.
      */
-    nrf_timer_task_trigger(NRF_TIMER0, NRF_TIMER_TASK_CAPTURE1);
-    if (NRF_TIMER0->CC[1] > NRF_TIMER0->CC[3]) {
+    if (timer0_did_miss(3, 1)) {
         phy_ppi_wfr_disable();
         nrf_radio_task_trigger(NRF_RADIO, NRF_RADIO_TASK_DISABLE);
     }
@@ -1096,15 +1121,15 @@ ble_phy_tx_end_isr(void)
 
 #if PHY_USE_FEM_LNA
         fem_time = rx_time - MYNEWT_VAL(BLE_FEM_LNA_TURN_ON_US);
-        nrf_timer_cc_set(NRF_TIMER0, 2, fem_time);
         NRF_TIMER0->EVENTS_COMPARE[2] = 0;
         phy_fem_enable_lna();
+        nrf_timer_cc_set(NRF_TIMER0, 2, fem_time);
 #endif
 
         radio_time = rx_time - BLE_PHY_T_RXENFAST;
-        nrf_timer_cc_set(NRF_TIMER0, 0, radio_time);
         NRF_TIMER0->EVENTS_COMPARE[0] = 0;
         phy_ppi_timer0_compare0_to_radio_rxen_enable();
+        nrf_timer_cc_set(NRF_TIMER0, 0, radio_time);
 
         /* In case TIMER0 did already count past CC[0] and/or CC[2], radio
          * and/or LNA may not be enabled. In any case we won't be stuck since
@@ -1142,18 +1167,17 @@ ble_phy_tx_end_isr(void)
         tx_time -= g_ble_phy_t_txdelay[g_ble_phy_data.phy_cur_phy_mode];
 
         radio_time = tx_time - BLE_PHY_T_TXENFAST;
-        nrf_timer_cc_set(NRF_TIMER0, 0, radio_time);
         NRF_TIMER0->EVENTS_COMPARE[0] = 0;
         phy_ppi_timer0_compare0_to_radio_txen_enable();
+        nrf_timer_cc_set(NRF_TIMER0, 0, radio_time);
 
 #if PHY_USE_FEM_PA
-        nrf_timer_cc_set(NRF_TIMER0, 2, fem_time);
         NRF_TIMER0->EVENTS_COMPARE[2] = 0;
         phy_fem_enable_pa();
+        nrf_timer_cc_set(NRF_TIMER0, 2, fem_time);
 #endif
 
-        nrf_timer_task_trigger(NRF_TIMER0, NRF_TIMER_TASK_CAPTURE3);
-        if (NRF_TIMER0->CC[3] > NRF_TIMER0->CC[0]) {
+        if (timer0_did_miss(0, 3)) {
             phy_ppi_timer0_compare0_to_radio_txen_disable();
             g_ble_phy_data.phy_transition_late = 1;
         }
@@ -1292,14 +1316,14 @@ ble_phy_rx_end_isr(void)
     tx_time -= g_ble_phy_t_txdelay[g_ble_phy_data.phy_cur_phy_mode];
 
     radio_time = tx_time - BLE_PHY_T_TXENFAST;
-    nrf_timer_cc_set(NRF_TIMER0, 0, radio_time);
     NRF_TIMER0->EVENTS_COMPARE[0] = 0;
     phy_ppi_timer0_compare0_to_radio_txen_enable();
+    nrf_timer_cc_set(NRF_TIMER0, 0, radio_time);
 
 #if PHY_USE_FEM_PA
-    nrf_timer_cc_set(NRF_TIMER0, 2, fem_time);
     NRF_TIMER0->EVENTS_COMPARE[2] = 0;
     phy_fem_enable_pa();
+    nrf_timer_cc_set(NRF_TIMER0, 2, fem_time);
 #endif
 
     /* Need to check if TIMER0 did not already count past CC[0] and/or CC[2], 
so
@@ -1308,11 +1332,9 @@ ble_phy_rx_end_isr(void)
      *
      * Note: CC[3] is used only for wfr which we do not need here.
      */
-    nrf_timer_task_trigger(NRF_TIMER0, NRF_TIMER_TASK_CAPTURE3);
-    is_late = (NRF_TIMER0->CC[3] > radio_time) && 
!NRF_TIMER0->EVENTS_COMPARE[0];
+    is_late = timer0_did_miss(0, 3);
 #if PHY_USE_FEM_PA
-    is_late = is_late ||
-              ((NRF_TIMER0->CC[3] > fem_time) && 
!NRF_TIMER0->EVENTS_COMPARE[2]);
+    is_late = is_late || timer0_did_miss(2, 3);
 #endif
     if (is_late) {
         phy_ppi_timer0_compare0_to_radio_txen_disable();

Reply via email to