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 ¶ms,
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