/* * Copyright (c) 2017 - 2022, Nordic Semiconductor ASA * All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of Nordic Semiconductor ASA nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ /** * @file * This file implements CSMA-CA procedure for the 802.15.4 driver. * */ #define NRF_802154_MODULE_ID NRF_802154_DRV_MODULE_ID_CSMACA #include "nrf_802154_csma_ca.h" #include "nrf_802154_config.h" #if NRF_802154_CSMA_CA_ENABLED #include #include #include #include "nrf_802154_const.h" #include "nrf_802154_debug.h" #include "nrf_802154_notification.h" #include "nrf_802154_pib.h" #include "nrf_802154_request.h" #include "nrf_802154_tx_power.h" #include "nrf_802154_stats.h" #include "platform/nrf_802154_random.h" #include "rsch/nrf_802154_rsch.h" #include "nrf_802154_sl_timer.h" #include "nrf_802154_sl_atomics.h" /** * @brief States of the CSMA-CA procedure. */ typedef enum { CSMA_CA_STATE_IDLE, ///< The CSMA-CA procedure is inactive. CSMA_CA_STATE_BACKOFF, ///< The CSMA-CA procedure is in backoff stage. CSMA_CA_STATE_ONGOING, ///< The frame is being sent. CSMA_CA_STATE_ABORTED ///< The CSMA-CA procedure is being aborted. } csma_ca_state_t; static uint8_t m_nb; ///< The number of times the CSMA-CA algorithm was required to back off while attempting the current transmission. 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. static uint8_t * mp_data; ///< Pointer to a buffer containing PHR and PSDU of the frame being transmitted. static nrf_802154_transmitted_frame_props_t m_data_props; ///< Structure containing detailed properties of data in buffer. static nrf_802154_fal_tx_power_split_t m_tx_power; ///< Power to be used when transmitting the frame split into components. static csma_ca_state_t m_state; ///< The current state of the CSMA-CA procedure. /** * @brief Perform appropriate actions for busy channel conditions. * * According to CSMA-CA description in 802.15.4 specification, when channel is busy NB and BE shall * be incremented and the device shall wait random delay before next CCA procedure. If NB reaches * macMaxCsmaBackoffs procedure fails. * * @retval true Procedure failed and TX failure should be notified to the next higher layer. * @retval false Procedure is still ongoing and TX failure should be handled internally. */ static bool channel_busy(void); static bool csma_ca_state_set(csma_ca_state_t expected, csma_ca_state_t desired) { nrf_802154_sl_log_function_enter(NRF_802154_LOG_VERBOSITY_HIGH); bool result = nrf_802154_sl_atomic_cas_u8(&m_state, &expected, desired); if (result) { nrf_802154_sl_log_local_event(NRF_802154_LOG_VERBOSITY_LOW, NRF_802154_LOG_LOCAL_EVENT_ID_CSMACA__SET_STATE, (uint32_t)desired); } nrf_802154_sl_log_function_exit(NRF_802154_LOG_VERBOSITY_HIGH); return result; } static void priority_leverage(void) { nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_HIGH); bool first_transmit_attempt = (0 == m_nb); bool coex_requires_boosted_prio = (nrf_802154_pib_coex_tx_request_mode_get() == NRF_802154_COEX_TX_REQUEST_MODE_CCA_START); // Leverage priority only after the first backoff in the specified Coex TX request mode if (first_transmit_attempt && coex_requires_boosted_prio) { // It should always be possible to update this timeslot's priority here if (!nrf_802154_rsch_delayed_timeslot_priority_update(NRF_802154_RESERVED_CSMACA_ID, RSCH_PRIO_TX)) { assert(false); } } nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_HIGH); } /** * @brief Notify MAC layer that CSMA-CA failed * * @param[in] error The error that caused the failure */ static void notify_failed(nrf_802154_tx_error_t error) { // core rejected attempt, use my current frame_props nrf_802154_transmit_done_metadata_t metadata = {}; metadata.frame_props = m_data_props; nrf_802154_notify_transmit_failed(mp_data, error, &metadata); } /** * @brief Notify MAC layer that channel is busy if tx request failed and there are no retries left. * * @param[in] result Result of TX request. */ static void notify_busy_channel(bool result) { nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_HIGH); nrf_802154_rsch_delayed_timeslot_cancel(NRF_802154_RESERVED_CSMACA_ID, true); // The 802.15.4 specification requires CSMA-CA to continue until m_nb is strictly greater // than nrf_802154_pib_csmaca_max_backoffs_get(), but at the moment this function is executed // the value of m_nb has not yet been incremented to reflect the latest attempt. Therefore // the comparison uses `greater or equal` instead of `greater than`. if (!result && (m_nb >= nrf_802154_pib_csmaca_max_backoffs_get())) { notify_failed(NRF_802154_TX_ERROR_BUSY_CHANNEL); } nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_HIGH); } /** * @brief Perform CCA procedure followed by frame transmission. * * If transmission is requested, CSMA-CA module waits for notification from the FSM module. * If transmission request fails, CSMA-CA module performs procedure for busy channel condition * @sa channel_busy(). * * @param[in] dly_ts_id Delayed timeslot identifier. */ static void frame_transmit(rsch_dly_ts_id_t dly_ts_id) { assert(dly_ts_id == NRF_802154_RESERVED_CSMACA_ID); nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_LOW); if (csma_ca_state_set(CSMA_CA_STATE_BACKOFF, CSMA_CA_STATE_ONGOING)) { priority_leverage(); nrf_802154_transmit_params_t params = { .frame_props = m_data_props, .tx_power = m_tx_power, .cca = true, .immediate = NRF_802154_CSMA_CA_WAIT_FOR_TIMESLOT ? false : true, }; if (!nrf_802154_request_transmit(NRF_802154_TERM_NONE, REQ_ORIG_CSMA_CA, mp_data, ¶ms, notify_busy_channel)) { (void)channel_busy(); } } else { nrf_802154_rsch_delayed_timeslot_cancel(dly_ts_id, true); } nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_LOW); } /** * @brief Calculates number of backoff periods as random value according to IEEE Std. 802.15.4. */ static uint8_t backoff_periods_calc_random(void) { return nrf_802154_random_get() % (1U << m_be); } /** * @brief Calculates number of backoff periods to wait before the next CCA attempt of CSMA/CA * * @return Number of backoff periods */ static uint8_t backoff_periods_calc(void) { uint8_t result; #if NRF_802154_TEST_MODES_ENABLED switch (nrf_802154_pib_test_mode_csmaca_backoff_get()) { case NRF_802154_TEST_MODE_CSMACA_BACKOFF_RANDOM: result = backoff_periods_calc_random(); break; case NRF_802154_TEST_MODE_CSMACA_BACKOFF_ALWAYS_MAX: result = (1U << m_be) - 1U; break; case NRF_802154_TEST_MODE_CSMACA_BACKOFF_ALWAYS_MIN: result = 0U; break; default: result = backoff_periods_calc_random(); assert(false); break; } #else result = backoff_periods_calc_random(); #endif return result; } /** * @brief Delay CCA procedure for random (2^BE - 1) unit backoff periods. */ static void random_backoff_start(void) { nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_HIGH); uint64_t backoff_us = backoff_periods_calc() * UNIT_BACKOFF_PERIOD; rsch_dly_ts_param_t backoff_ts_param = { .trigger_time = nrf_802154_sl_timer_current_time_get() + backoff_us, .op = RSCH_DLY_TS_OP_CSMACA, .type = RSCH_DLY_TS_TYPE_RELAXED, .started_callback = frame_transmit, .id = NRF_802154_RESERVED_CSMACA_ID, }; switch (nrf_802154_pib_coex_tx_request_mode_get()) { case NRF_802154_COEX_TX_REQUEST_MODE_FRAME_READY: // To request Coex precondition immediately, priority must be leveraged backoff_ts_param.prio = RSCH_PRIO_TX; break; case NRF_802154_COEX_TX_REQUEST_MODE_CCA_START: // Coex should be requested for all backoff periods but the first one backoff_ts_param.prio = (m_nb == 0) ? RSCH_PRIO_IDLE_LISTENING : RSCH_PRIO_TX; break; case NRF_802154_COEX_TX_REQUEST_MODE_CCA_DONE: case NRF_802154_COEX_TX_REQUEST_MODE_ON_CCA_TOGGLE: // Coex should not be requested during backoff periods backoff_ts_param.prio = RSCH_PRIO_IDLE_LISTENING; break; default: assert(false); break; } // Delayed timeslot with these parameters should always be scheduled if (!nrf_802154_rsch_delayed_timeslot_request(&backoff_ts_param)) { assert(false); } nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_HIGH); } static bool channel_busy(void) { bool result = true; nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_LOW); if (csma_ca_state_set(CSMA_CA_STATE_ONGOING, CSMA_CA_STATE_BACKOFF)) { m_nb++; if (m_be < nrf_802154_pib_csmaca_max_be_get()) { m_be++; } if (m_nb > nrf_802154_pib_csmaca_max_backoffs_get()) { mp_data = NULL; bool ret = csma_ca_state_set(CSMA_CA_STATE_BACKOFF, CSMA_CA_STATE_IDLE); assert(ret); (void)ret; } else { random_backoff_start(); result = false; } } nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_LOW); return result; } bool nrf_802154_csma_ca_start(uint8_t * p_data, const nrf_802154_transmit_csma_ca_metadata_t * p_metadata) { nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_LOW); #if (NRF_802154_FRAME_TIMESTAMP_ENABLED) uint64_t ts = nrf_802154_sl_timer_current_time_get(); nrf_802154_stat_timestamp_write(last_csmaca_start_timestamp, ts); #endif bool result = csma_ca_state_set(CSMA_CA_STATE_IDLE, CSMA_CA_STATE_BACKOFF); assert(result); (void)result; mp_data = p_data; m_data_props = p_metadata->frame_props; m_nb = 0; m_be = nrf_802154_pib_csmaca_min_be_get(); (void)nrf_802154_tx_power_convert_metadata_to_tx_power_split(nrf_802154_pib_channel_get(), p_metadata->tx_power, &m_tx_power); random_backoff_start(); nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_LOW); return true; } bool nrf_802154_csma_ca_abort(nrf_802154_term_t term_lvl, req_originator_t req_orig) { nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_LOW); bool result = true; if (((req_orig != REQ_ORIG_CORE) && (req_orig != REQ_ORIG_HIGHER_LAYER)) || (CSMA_CA_STATE_IDLE == nrf_802154_sl_atomic_load_u8(&m_state))) { // The request does not originate from core or the higher layer or the procedure // is stopped already. Ignore the abort request and return success, no matter // the termination level. } else if (term_lvl >= NRF_802154_TERM_802154) { // The procedure is active and the termination level allows the abort // request to be executed. Force aborted state. Don't clear the frame // pointer - it might be needed to notify failure. nrf_802154_sl_atomic_store_u8(&m_state, CSMA_CA_STATE_ABORTED); nrf_802154_rsch_delayed_timeslot_cancel(NRF_802154_RESERVED_CSMACA_ID, false); } else { // The procedure is active and the termination level does not allow // the abort request to be executed. Return failure result = false; } nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_LOW); return result; } bool nrf_802154_csma_ca_tx_failed_hook(uint8_t * p_frame, nrf_802154_tx_error_t error) { bool result = true; nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_LOW); switch (error) { // Below errors mean a failure occurred during the frame processing and the frame cannot be // transmitted unless a higher layer takes appropriate actions, hence the CSMA-CA procedure // shall be stopped. case NRF_802154_TX_ERROR_KEY_ID_INVALID: /* Fallthrough. */ case NRF_802154_TX_ERROR_FRAME_COUNTER_ERROR: if (mp_data == p_frame) { mp_data = NULL; nrf_802154_sl_atomic_store_u8(&m_state, CSMA_CA_STATE_IDLE); } break; default: if (csma_ca_state_set(CSMA_CA_STATE_ABORTED, CSMA_CA_STATE_IDLE)) { // The procedure was successfully aborted. if (p_frame != mp_data) { // The procedure was aborted while another operation was holding // frame pointer in the core - hence p_frame points to a different // frame than mp_data. CSMA-CA failure must be notified directly. notify_failed(error); } } else if (p_frame == mp_data) { // The procedure is active and transmission attempt failed. Try again result = channel_busy(); } else { // Intentionally empty. } break; } nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_LOW); return result; } bool nrf_802154_csma_ca_tx_started_hook(uint8_t * p_frame) { nrf_802154_log_function_enter(NRF_802154_LOG_VERBOSITY_LOW); if (mp_data == p_frame) { mp_data = NULL; nrf_802154_sl_atomic_store_u8(&m_state, CSMA_CA_STATE_IDLE); } nrf_802154_log_function_exit(NRF_802154_LOG_VERBOSITY_LOW); return true; } #endif // NRF_802154_CSMA_CA_ENABLED