1 /*
2  * Copyright (c) 2017, Nordic Semiconductor ASA
3  * All rights reserved.
4  *
5  * SPDX-License-Identifier: BSD-3-Clause
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright notice, this
11  *    list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
18  *    contributors may be used to endorse or promote products derived from this
19  *    software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31  * POSSIBILITY OF SUCH DAMAGE.
32  *
33  */
34 
35 /**
36  * @file
37  *   This file implements CSMA-CA procedure for the 802.15.4 driver.
38  *
39  */
40 
41 #define NRF_802154_MODULE_ID NRF_802154_DRV_MODULE_ID_CSMACA
42 
43 #include "nrf_802154_csma_ca.h"
44 
45 #include "nrf_802154_config.h"
46 #if NRF_802154_CSMA_CA_ENABLED
47 
48 #include "nrf_802154_assert.h"
49 #include <stdbool.h>
50 #include <stdint.h>
51 
52 #include "nrf_802154_const.h"
53 #include "nrf_802154_debug.h"
54 #include "nrf_802154_notification.h"
55 #include "nrf_802154_pib.h"
56 #include "nrf_802154_request.h"
57 #include "nrf_802154_tx_power.h"
58 #include "nrf_802154_stats.h"
59 #include "platform/nrf_802154_random.h"
60 #include "rsch/nrf_802154_rsch.h"
61 #include "nrf_802154_sl_timer.h"
62 #include "nrf_802154_sl_atomics.h"
63 
64 /**
65  * @brief States of the CSMA-CA procedure.
66  */
67 typedef enum
68 {
69     CSMA_CA_STATE_IDLE,                                   ///< The CSMA-CA procedure is inactive.
70     CSMA_CA_STATE_BACKOFF,                                ///< The CSMA-CA procedure is in backoff stage.
71     CSMA_CA_STATE_ONGOING,                                ///< The frame is being sent.
72     CSMA_CA_STATE_ABORTED                                 ///< The CSMA-CA procedure is being aborted.
73 } csma_ca_state_t;
74 
75 static uint8_t m_nb;                                      ///< The number of times the CSMA-CA algorithm was required to back off while attempting the current transmission.
76 static uint8_t m_be;                                      ///< Backoff exponent, which is related to how many backoff periods a device shall wait before attempting to assess a channel.
77 
78 static uint8_t                            * mp_data;      ///< Pointer to a buffer containing PHR and PSDU of the frame being transmitted.
79 static nrf_802154_transmitted_frame_props_t m_data_props; ///< Structure containing detailed properties of data in buffer.
80 static nrf_802154_fal_tx_power_split_t      m_tx_power;   ///< Power to be used when transmitting the frame split into components.
81 static uint8_t                              m_tx_channel; ///< Channel to be used to transmit the current frame.
82 static csma_ca_state_t                      m_state;      ///< The current state of the CSMA-CA procedure.
83 
84 /**
85  * @brief Perform appropriate actions for busy channel conditions.
86  *
87  * According to CSMA-CA description in 802.15.4 specification, when channel is busy NB and BE shall
88  * be incremented and the device shall wait random delay before next CCA procedure. If NB reaches
89  * macMaxCsmaBackoffs procedure fails.
90  *
91  * @retval true   Procedure failed and TX failure should be notified to the next higher layer.
92  * @retval false  Procedure is still ongoing and TX failure should be handled internally.
93  */
94 static bool channel_busy(void);
95 
csma_ca_state_set(csma_ca_state_t expected,csma_ca_state_t desired)96 static bool csma_ca_state_set(csma_ca_state_t expected, csma_ca_state_t desired)
97 {
98     nrf_802154_sl_log_function_enter(NRF_802154_LOG_VERBOSITY_HIGH);
99 
100     bool result = nrf_802154_sl_atomic_cas_u8(&m_state, &expected, desired);
101 
102     if (result)
103     {
104         nrf_802154_sl_log_local_event(NRF_802154_LOG_VERBOSITY_LOW,
105                                       NRF_802154_LOG_LOCAL_EVENT_ID_CSMACA__SET_STATE,
106                                       (uint32_t)desired);
107     }
108 
109     nrf_802154_sl_log_function_exit(NRF_802154_LOG_VERBOSITY_HIGH);
110 
111     return result;
112 }
113 
priority_leverage(void)114 static void priority_leverage(void)
115 {
116     nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_HIGH);
117 
118     bool first_transmit_attempt     = (0 == m_nb);
119     bool coex_requires_boosted_prio = (nrf_802154_pib_coex_tx_request_mode_get() ==
120                                        NRF_802154_COEX_TX_REQUEST_MODE_CCA_START);
121 
122     // Leverage priority only after the first backoff in the specified Coex TX request mode
123     if (first_transmit_attempt && coex_requires_boosted_prio)
124     {
125         // It should always be possible to update this timeslot's priority here
126         if (!nrf_802154_rsch_delayed_timeslot_priority_update(NRF_802154_RESERVED_CSMACA_ID,
127                                                               RSCH_PRIO_TX))
128         {
129             NRF_802154_ASSERT(false);
130         }
131     }
132 
133     nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_HIGH);
134 }
135 
136 /**
137  * @brief Notify MAC layer that CSMA-CA failed
138  *
139  * @param[in]  error  The error that caused the failure
140  */
notify_failed(nrf_802154_tx_error_t error)141 static void notify_failed(nrf_802154_tx_error_t error)
142 {
143     // core rejected attempt, use my current frame_props
144     nrf_802154_transmit_done_metadata_t metadata = {};
145 
146     metadata.frame_props = m_data_props;
147 
148     nrf_802154_notify_transmit_failed(mp_data, error, &metadata);
149 }
150 
151 /**
152  * @brief Notify MAC layer that channel is busy if tx request failed and there are no retries left.
153  *
154  * @param[in]  result  Result of TX request.
155  */
notify_busy_channel(bool result)156 static void notify_busy_channel(bool result)
157 {
158     nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_HIGH);
159 
160     nrf_802154_rsch_delayed_timeslot_cancel(NRF_802154_RESERVED_CSMACA_ID, true);
161 
162     // The 802.15.4 specification requires CSMA-CA to continue until m_nb is strictly greater
163     // than nrf_802154_pib_csmaca_max_backoffs_get(), but at the moment this function is executed
164     // the value of m_nb has not yet been incremented to reflect the latest attempt. Therefore
165     // the comparison uses `greater or equal` instead of `greater than`.
166     if (!result && (m_nb >= nrf_802154_pib_csmaca_max_backoffs_get()))
167     {
168         notify_failed(NRF_802154_TX_ERROR_BUSY_CHANNEL);
169     }
170 
171     nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_HIGH);
172 }
173 
174 /**
175  * @brief Perform CCA procedure followed by frame transmission.
176  *
177  * If transmission is requested, CSMA-CA module waits for notification from the FSM module.
178  * If transmission request fails, CSMA-CA module performs procedure for busy channel condition
179  * @sa channel_busy().
180  *
181  * @param[in] dly_ts_id  Delayed timeslot identifier.
182  */
frame_transmit(rsch_dly_ts_id_t dly_ts_id)183 static void frame_transmit(rsch_dly_ts_id_t dly_ts_id)
184 {
185     NRF_802154_ASSERT(dly_ts_id == NRF_802154_RESERVED_CSMACA_ID);
186 
187     nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_LOW);
188 
189     if (csma_ca_state_set(CSMA_CA_STATE_BACKOFF, CSMA_CA_STATE_ONGOING))
190     {
191         priority_leverage();
192 
193         nrf_802154_transmit_params_t params =
194         {
195             .frame_props        = m_data_props,
196             .tx_power           = m_tx_power,
197             .channel            = m_tx_channel,
198             .cca                = true,
199             .immediate          = NRF_802154_CSMA_CA_WAIT_FOR_TIMESLOT ? false : true,
200             .extra_cca_attempts = 0,
201         };
202 
203         if (!nrf_802154_request_transmit(NRF_802154_TERM_NONE,
204                                          REQ_ORIG_CSMA_CA,
205                                          mp_data,
206                                          &params,
207                                          notify_busy_channel))
208         {
209             (void)channel_busy();
210         }
211     }
212     else
213     {
214         nrf_802154_rsch_delayed_timeslot_cancel(dly_ts_id, true);
215     }
216 
217     nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_LOW);
218 }
219 
220 /**
221  * @brief Calculates number of backoff periods as random value according to IEEE Std. 802.15.4.
222  */
backoff_periods_calc_random(void)223 static uint8_t backoff_periods_calc_random(void)
224 {
225     return nrf_802154_random_get() % (1U << m_be);
226 }
227 
228 /**
229  * @brief Calculates number of backoff periods to wait before the next CCA attempt of CSMA/CA
230  *
231  * @return Number of backoff periods
232  */
backoff_periods_calc(void)233 static uint8_t backoff_periods_calc(void)
234 {
235     uint8_t result;
236 
237 #if NRF_802154_TEST_MODES_ENABLED
238 
239     switch (nrf_802154_pib_test_mode_csmaca_backoff_get())
240     {
241         case NRF_802154_TEST_MODE_CSMACA_BACKOFF_RANDOM:
242             result = backoff_periods_calc_random();
243             break;
244 
245         case NRF_802154_TEST_MODE_CSMACA_BACKOFF_ALWAYS_MAX:
246             result = (1U << m_be) - 1U;
247             break;
248 
249         case NRF_802154_TEST_MODE_CSMACA_BACKOFF_ALWAYS_MIN:
250             result = 0U;
251             break;
252 
253         default:
254             result = backoff_periods_calc_random();
255             NRF_802154_ASSERT(false);
256             break;
257     }
258 #else
259     result = backoff_periods_calc_random();
260 #endif
261 
262     return result;
263 }
264 
265 /**
266  * @brief Delay CCA procedure for random (2^BE - 1) unit backoff periods.
267  */
random_backoff_start(void)268 static void random_backoff_start(void)
269 {
270     nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_HIGH);
271 
272     uint64_t backoff_us = backoff_periods_calc() * UNIT_BACKOFF_PERIOD;
273 
274     rsch_dly_ts_param_t backoff_ts_param =
275     {
276         .trigger_time     = nrf_802154_sl_timer_current_time_get() + backoff_us,
277         .ppi_trigger_en   = false,
278         .op               = RSCH_DLY_TS_OP_CSMACA,
279         .type             = RSCH_DLY_TS_TYPE_RELAXED,
280         .started_callback = frame_transmit,
281         .id               = NRF_802154_RESERVED_CSMACA_ID,
282     };
283 
284     switch (nrf_802154_pib_coex_tx_request_mode_get())
285     {
286         case NRF_802154_COEX_TX_REQUEST_MODE_FRAME_READY:
287             // To request Coex precondition immediately, priority must be leveraged
288             backoff_ts_param.prio = RSCH_PRIO_TX;
289             break;
290 
291         case NRF_802154_COEX_TX_REQUEST_MODE_CCA_START:
292             // Coex should be requested for all backoff periods but the first one
293             backoff_ts_param.prio = (m_nb == 0) ? RSCH_PRIO_IDLE_LISTENING : RSCH_PRIO_TX;
294             break;
295 
296         case NRF_802154_COEX_TX_REQUEST_MODE_CCA_DONE:
297         case NRF_802154_COEX_TX_REQUEST_MODE_ON_CCA_TOGGLE:
298             // Coex should not be requested during backoff periods
299             backoff_ts_param.prio = RSCH_PRIO_IDLE_LISTENING;
300             break;
301 
302         default:
303             NRF_802154_ASSERT(false);
304             break;
305     }
306 
307     // Delayed timeslot with these parameters should always be scheduled
308     if (!nrf_802154_rsch_delayed_timeslot_request(&backoff_ts_param))
309     {
310         NRF_802154_ASSERT(false);
311     }
312 
313     nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_HIGH);
314 }
315 
channel_busy(void)316 static bool channel_busy(void)
317 {
318     bool result = true;
319 
320     nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_LOW);
321 
322     if (csma_ca_state_set(CSMA_CA_STATE_ONGOING, CSMA_CA_STATE_BACKOFF))
323     {
324         m_nb++;
325 
326         if (m_be < nrf_802154_pib_csmaca_max_be_get())
327         {
328             m_be++;
329         }
330 
331         if (m_nb > nrf_802154_pib_csmaca_max_backoffs_get())
332         {
333             mp_data = NULL;
334             bool ret = csma_ca_state_set(CSMA_CA_STATE_BACKOFF, CSMA_CA_STATE_IDLE);
335 
336             NRF_802154_ASSERT(ret);
337             (void)ret;
338         }
339         else
340         {
341             random_backoff_start();
342             result = false;
343         }
344     }
345 
346     nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_LOW);
347     return result;
348 }
349 
nrf_802154_csma_ca_start(uint8_t * p_data,const nrf_802154_transmit_csma_ca_metadata_t * p_metadata)350 bool nrf_802154_csma_ca_start(uint8_t                                      * p_data,
351                               const nrf_802154_transmit_csma_ca_metadata_t * p_metadata)
352 {
353     nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_LOW);
354 
355 #if (NRF_802154_FRAME_TIMESTAMP_ENABLED)
356     uint64_t ts = nrf_802154_sl_timer_current_time_get();
357 
358     nrf_802154_stat_timestamp_write(last_csmaca_start_timestamp, ts);
359 #endif
360 
361     bool result = csma_ca_state_set(CSMA_CA_STATE_IDLE, CSMA_CA_STATE_BACKOFF);
362 
363     NRF_802154_ASSERT(result);
364     (void)result;
365 
366     uint8_t channel =
367         p_metadata->tx_channel.use_metadata_value ? p_metadata->tx_channel.channel :
368         nrf_802154_pib_channel_get();
369 
370     mp_data      = p_data;
371     m_data_props = p_metadata->frame_props;
372     m_nb         = 0;
373     m_be         = nrf_802154_pib_csmaca_min_be_get();
374     m_tx_channel = channel;
375     (void)nrf_802154_tx_power_convert_metadata_to_tx_power_split(channel,
376                                                                  p_metadata->tx_power,
377                                                                  &m_tx_power);
378 
379     random_backoff_start();
380 
381     nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_LOW);
382 
383     return true;
384 }
385 
nrf_802154_csma_ca_abort(nrf_802154_term_t term_lvl,req_originator_t req_orig)386 bool nrf_802154_csma_ca_abort(nrf_802154_term_t term_lvl, req_originator_t req_orig)
387 {
388     nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_LOW);
389 
390     bool result = true;
391 
392     if (((req_orig != REQ_ORIG_CORE) && (req_orig != REQ_ORIG_HIGHER_LAYER)) ||
393         (CSMA_CA_STATE_IDLE == nrf_802154_sl_atomic_load_u8(&m_state)))
394     {
395         // The request does not originate from core or the higher layer or the procedure
396         // is stopped already. Ignore the abort request and return success, no matter
397         // the termination level.
398     }
399     else if (term_lvl >= NRF_802154_TERM_802154)
400     {
401         // The procedure is active and the termination level allows the abort
402         // request to be executed. Force aborted state. Don't clear the frame
403         // pointer - it might be needed to notify failure.
404         nrf_802154_sl_atomic_store_u8(&m_state, CSMA_CA_STATE_ABORTED);
405         nrf_802154_rsch_delayed_timeslot_cancel(NRF_802154_RESERVED_CSMACA_ID, false);
406     }
407     else
408     {
409         // The procedure is active and the termination level does not allow
410         // the abort request to be executed. Return failure
411         result = false;
412     }
413 
414     nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_LOW);
415 
416     return result;
417 }
418 
nrf_802154_csma_ca_tx_failed_hook(uint8_t * p_frame,nrf_802154_tx_error_t error)419 bool nrf_802154_csma_ca_tx_failed_hook(uint8_t * p_frame, nrf_802154_tx_error_t error)
420 {
421     bool result = true;
422 
423     nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_LOW);
424 
425     switch (error)
426     {
427         // Below errors mean a failure occurred during the frame processing and the frame cannot be
428         // transmitted unless a higher layer takes appropriate actions, hence the CSMA-CA procedure
429         // shall be stopped.
430 
431         case NRF_802154_TX_ERROR_KEY_ID_INVALID:
432         /* Fallthrough. */
433         case NRF_802154_TX_ERROR_FRAME_COUNTER_ERROR:
434             if (mp_data == p_frame)
435             {
436                 mp_data = NULL;
437                 nrf_802154_sl_atomic_store_u8(&m_state, CSMA_CA_STATE_IDLE);
438             }
439             break;
440 
441         default:
442             if (csma_ca_state_set(CSMA_CA_STATE_ABORTED, CSMA_CA_STATE_IDLE))
443             {
444                 // The procedure was successfully aborted.
445 
446                 if (p_frame != mp_data)
447                 {
448                     // The procedure was aborted while another operation was holding
449                     // frame pointer in the core - hence p_frame points to a different
450                     // frame than mp_data. CSMA-CA failure must be notified directly.
451                     notify_failed(error);
452                 }
453             }
454             else if (p_frame == mp_data)
455             {
456                 // The procedure is active and transmission attempt failed. Try again
457                 result = channel_busy();
458             }
459             else
460             {
461                 // Intentionally empty.
462             }
463             break;
464     }
465 
466     nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_LOW);
467 
468     return result;
469 }
470 
nrf_802154_csma_ca_tx_started_hook(uint8_t * p_frame)471 bool nrf_802154_csma_ca_tx_started_hook(uint8_t * p_frame)
472 {
473     nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_LOW);
474 
475     if (mp_data == p_frame)
476     {
477         mp_data = NULL;
478         nrf_802154_sl_atomic_store_u8(&m_state, CSMA_CA_STATE_IDLE);
479     }
480 
481     nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_LOW);
482     return true;
483 }
484 
485 #endif // NRF_802154_CSMA_CA_ENABLED
486