diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 5ad1b256a..2ee6c79f0 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (295) +#define OPENTHREAD_API_VERSION (296) /** * @addtogroup api-instance diff --git a/include/openthread/platform/radio.h b/include/openthread/platform/radio.h index 5d90e501a..240ce45bc 100644 --- a/include/openthread/platform/radio.h +++ b/include/openthread/platform/radio.h @@ -277,6 +277,27 @@ typedef struct otRadioFrame uint8_t mMaxCsmaBackoffs; ///< Maximum number of backoffs attempts before declaring CCA failure. uint8_t mMaxFrameRetries; ///< Maximum number of retries allowed after a transmission failure. + /** + * The RX channel after frame TX is done (after all frame retries - ack received, or timeout, or abort). + * + * Radio platforms can choose to fully ignore this. OT stack will make sure to call `otPlatRadioReceive()` + * with the desired RX channel after a frame TX is done and signaled in `otPlatRadioTxDone()` callback. + * Radio platforms that don't provide `OT_RADIO_CAPS_TRANSMIT_RETRIES` must always ignore this. + * + * This is intended for situations where there may be delay in interactions between OT stack and radio, as + * an example this is used in RCP/host architecture to make sure RCP switches to PAN channel more quickly. + * In particular, this can help with CSL tx to a sleepy child, where the child may use a different channel + * for CSL than the PAN channel. After frame tx, we want the radio/RCP to go back to the PAN channel + * quickly to ensure that parent does not miss tx from child afterwards, e.g., child responding to the + * earlier CSL transmitted frame from parent using PAN channel while radio still staying on CSL channel. + * + * The switch to the RX channel MUST happen after the frame TX is fully done, i.e., after all retries and + * when ack is received (when "Ack Request" flag is set on the TX frame) or ack timeout. Note that ack is + * expected on the same channel that frame is sent on. + * + */ + uint8_t mRxChannelAfterTxDone; + /** * Indicates whether frame counter and CSL IEs are properly updated in the header. * diff --git a/src/core/mac/mac_frame.hpp b/src/core/mac/mac_frame.hpp index edfb1a0da..bd21317bf 100644 --- a/src/core/mac/mac_frame.hpp +++ b/src/core/mac/mac_frame.hpp @@ -836,14 +836,6 @@ public: */ uint8_t GetChannel(void) const { return mChannel; } - /** - * This method sets the IEEE 802.15.4 channel used for transmission or reception. - * - * @param[in] aChannel The IEEE 802.15.4 channel used for transmission or reception. - * - */ - void SetChannel(uint8_t aChannel) { mChannel = aChannel; } - /** * This method returns the IEEE 802.15.4 PSDU length. * @@ -1262,6 +1254,36 @@ public: class TxFrame : public Frame { public: + /** + * This method sets the channel on which to send the frame. + * + * It also sets the `RxChannelAfterTxDone` to the same channel. + * + * @param[in] aChannel The channel used for transmission. + * + */ + void SetChannel(uint8_t aChannel) + { + mChannel = aChannel; + SetRxChannelAfterTxDone(aChannel); + } + + /** + * This method gets the RX channel after frame TX is done. + * + * @returns The RX channel after frame TX is done. + * + */ + uint8_t GetRxChannelAfterTxDone(void) const { return mInfo.mTxInfo.mRxChannelAfterTxDone; } + + /** + * This method sets the RX channel after frame TX is done. + * + * @param[in] aChannel The RX channel after frame TX is done. + * + */ + void SetRxChannelAfterTxDone(uint8_t aChannel) { mInfo.mTxInfo.mRxChannelAfterTxDone = aChannel; } + /** * This method returns the maximum number of backoffs the CSMA-CA algorithm will attempt before declaring a channel * access failure. diff --git a/src/core/mac/sub_mac.cpp b/src/core/mac/sub_mac.cpp index ba16d12c8..194647023 100644 --- a/src/core/mac/sub_mac.cpp +++ b/src/core/mac/sub_mac.cpp @@ -619,6 +619,19 @@ void SubMac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aErro SetState(kStateReceive); +#if OPENTHREAD_RADIO + if (aFrame.GetChannel() != aFrame.GetRxChannelAfterTxDone()) + { + // On RCP build, we switch immediately to the specified RX + // channel if it is different from the channel on which frame + // was sent. On FTD or MTD builds we don't need to do + // the same as the `Mac` will switch the channel from the + // `mCallbacks.TransmitDone()`. + + IgnoreError(Get().Receive(aFrame.GetRxChannelAfterTxDone())); + } +#endif + mCallbacks.TransmitDone(aFrame, aAckFrame, aError); exit: diff --git a/src/core/thread/csl_tx_scheduler.cpp b/src/core/thread/csl_tx_scheduler.cpp index b929e6651..390fd303c 100644 --- a/src/core/thread/csl_tx_scheduler.cpp +++ b/src/core/thread/csl_tx_scheduler.cpp @@ -214,6 +214,11 @@ Mac::TxFrame *CslTxScheduler::HandleFrameRequest(Mac::TxFrames &aTxFrames) frame->SetChannel(mCslTxChild->GetCslChannel() == 0 ? Get().GetPanChannel() : mCslTxChild->GetCslChannel()); + if (frame->GetChannel() != Get().GetPanChannel()) + { + frame->SetRxChannelAfterTxDone(Get().GetPanChannel()); + } + delay = GetNextCslTransmissionDelay(*mCslTxChild, txDelay, /* aAheadUs */ 0); // We make sure that delay is less than `mCslFrameRequestAheadUs` diff --git a/src/core/thread/mesh_forwarder.cpp b/src/core/thread/mesh_forwarder.cpp index 07c676515..ccc6a6fbc 100644 --- a/src/core/thread/mesh_forwarder.cpp +++ b/src/core/thread/mesh_forwarder.cpp @@ -871,6 +871,7 @@ start: { case Message::kSubTypeMleAnnounce: aFrame.SetChannel(aMessage.GetChannel()); + aFrame.SetRxChannelAfterTxDone(Get().GetPanChannel()); panIds.mDestination = Mac::kPanIdBroadcast; break; diff --git a/src/lib/spinel/radio_spinel_impl.hpp b/src/lib/spinel/radio_spinel_impl.hpp index 155fc9012..d4d78f176 100644 --- a/src/lib/spinel/radio_spinel_impl.hpp +++ b/src/lib/spinel/radio_spinel_impl.hpp @@ -1980,21 +1980,23 @@ otError RadioSpinel::Transmit(otRadioFrame &a otPlatRadioTxStarted(mInstance, mTransmitFrame); error = Request(SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_STREAM_RAW, - SPINEL_DATATYPE_DATA_WLEN_S // Frame data - SPINEL_DATATYPE_UINT8_S // Channel - SPINEL_DATATYPE_UINT8_S // MaxCsmaBackoffs - SPINEL_DATATYPE_UINT8_S // MaxFrameRetries - SPINEL_DATATYPE_BOOL_S // CsmaCaEnabled - SPINEL_DATATYPE_BOOL_S // IsHeaderUpdated - SPINEL_DATATYPE_BOOL_S // IsARetx - SPINEL_DATATYPE_BOOL_S // SkipAes - SPINEL_DATATYPE_UINT32_S // TxDelay - SPINEL_DATATYPE_UINT32_S, // TxDelayBaseTime + SPINEL_DATATYPE_DATA_WLEN_S // Frame data + SPINEL_DATATYPE_UINT8_S // Channel + SPINEL_DATATYPE_UINT8_S // MaxCsmaBackoffs + SPINEL_DATATYPE_UINT8_S // MaxFrameRetries + SPINEL_DATATYPE_BOOL_S // CsmaCaEnabled + SPINEL_DATATYPE_BOOL_S // IsHeaderUpdated + SPINEL_DATATYPE_BOOL_S // IsARetx + SPINEL_DATATYPE_BOOL_S // IsSecurityProcessed + SPINEL_DATATYPE_UINT32_S // TxDelay + SPINEL_DATATYPE_UINT32_S // TxDelayBaseTime + SPINEL_DATATYPE_UINT8_S, // RxChannelAfterTxDone mTransmitFrame->mPsdu, mTransmitFrame->mLength, mTransmitFrame->mChannel, mTransmitFrame->mInfo.mTxInfo.mMaxCsmaBackoffs, mTransmitFrame->mInfo.mTxInfo.mMaxFrameRetries, mTransmitFrame->mInfo.mTxInfo.mCsmaCaEnabled, mTransmitFrame->mInfo.mTxInfo.mIsHeaderUpdated, mTransmitFrame->mInfo.mTxInfo.mIsARetx, mTransmitFrame->mInfo.mTxInfo.mIsSecurityProcessed, - mTransmitFrame->mInfo.mTxInfo.mTxDelay, mTransmitFrame->mInfo.mTxInfo.mTxDelayBaseTime); + mTransmitFrame->mInfo.mTxInfo.mTxDelay, mTransmitFrame->mInfo.mTxInfo.mTxDelayBaseTime, + mTransmitFrame->mInfo.mTxInfo.mRxChannelAfterTxDone); if (error == OT_ERROR_NONE) { diff --git a/src/lib/spinel/spinel.h b/src/lib/spinel/spinel.h index 6af510635..0324729ec 100644 --- a/src/lib/spinel/spinel.h +++ b/src/lib/spinel/spinel.h @@ -420,7 +420,7 @@ * Please see section "Spinel definition compatibility guideline" for more details. * */ -#define SPINEL_RCP_API_VERSION 8 +#define SPINEL_RCP_API_VERSION 9 /** * @def SPINEL_MIN_HOST_SUPPORTED_RCP_API_VERSION @@ -3511,19 +3511,27 @@ enum * over the radio. This allows the caller to use the radio directly. * * The frame meta data for the `CMD_PROP_VALUE_SET` contains the following - * optional fields. Default values are used for all unspecified fields. + * fields. Default values are used for all unspecified fields. * - * `C` : Channel (for frame tx) + * `C` : Channel (for frame tx) - MUST be included. * `C` : Maximum number of backoffs attempts before declaring CCA failure * (use Thread stack default if not specified) * `C` : Maximum number of retries allowed after a transmission failure * (use Thread stack default if not specified) * `b` : Set to true to enable CSMA-CA for this packet, false otherwise. * (default true). - * `b` : Set to true to indicate it is a retransmission packet, false otherwise. - * (default false). - * `b` : Set to true to indicate that SubMac should skip AES processing, false otherwise. - * (default false). + * `b` : Set to true to indicate if header is updated - related to + * `mIsHeaderUpdated` in `otRadioFrame` (default false). + * `b` : Set to true to indicate it is a retransmission - related to + * `mIsARetx` in `otRadioFrame` (default false). + * `b` : Set to true to indicate security was processed on tx frame + * `mIsSecurityProcessed` in `otRadioFrame` (default false). + * `L` : TX delay interval used for CSL - related to `mTxDelay` in + * `otRadioFrame` (default zero). + * `L` : TX delay based time used for CSL - related to `mTxDelayBaseTime` + * in `otRadioFrame` (default zero). + * `C` : RX channel after TX done (default assumed to be same as + * channel in metadata) * */ SPINEL_PROP_STREAM_RAW = SPINEL_PROP_STREAM__BEGIN + 1, diff --git a/src/ncp/ncp_base_radio.cpp b/src/ncp/ncp_base_radio.cpp index 4cd679981..9ebb3e290 100644 --- a/src/ncp/ncp_base_radio.cpp +++ b/src/ncp/ncp_base_radio.cpp @@ -408,14 +408,15 @@ otError NcpBase::DecodeStreamRawTxRequest(otRadioFrame &aFrame) SuccessOrExit(error = mDecoder.ReadUint8(aFrame.mChannel)); // Set the default value for all optional parameters. - aFrame.mInfo.mTxInfo.mMaxCsmaBackoffs = OPENTHREAD_CONFIG_MAC_MAX_CSMA_BACKOFFS_DIRECT; - aFrame.mInfo.mTxInfo.mMaxFrameRetries = OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_DIRECT; - aFrame.mInfo.mTxInfo.mCsmaCaEnabled = true; - aFrame.mInfo.mTxInfo.mIsHeaderUpdated = false; - aFrame.mInfo.mTxInfo.mIsARetx = false; - aFrame.mInfo.mTxInfo.mIsSecurityProcessed = false; - aFrame.mInfo.mTxInfo.mTxDelay = 0; - aFrame.mInfo.mTxInfo.mTxDelayBaseTime = 0; + aFrame.mInfo.mTxInfo.mRxChannelAfterTxDone = aFrame.mChannel; + aFrame.mInfo.mTxInfo.mMaxCsmaBackoffs = OPENTHREAD_CONFIG_MAC_MAX_CSMA_BACKOFFS_DIRECT; + aFrame.mInfo.mTxInfo.mMaxFrameRetries = OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_DIRECT; + aFrame.mInfo.mTxInfo.mCsmaCaEnabled = true; + aFrame.mInfo.mTxInfo.mIsHeaderUpdated = false; + aFrame.mInfo.mTxInfo.mIsARetx = false; + aFrame.mInfo.mTxInfo.mIsSecurityProcessed = false; + aFrame.mInfo.mTxInfo.mTxDelay = 0; + aFrame.mInfo.mTxInfo.mTxDelayBaseTime = 0; // All the next parameters are optional. Note that even if the // decoder fails to parse any of optional parameters we still want to @@ -430,6 +431,7 @@ otError NcpBase::DecodeStreamRawTxRequest(otRadioFrame &aFrame) SuccessOrExit(mDecoder.ReadBool(isSecurityProcessed)); SuccessOrExit(mDecoder.ReadUint32(aFrame.mInfo.mTxInfo.mTxDelay)); SuccessOrExit(mDecoder.ReadUint32(aFrame.mInfo.mTxInfo.mTxDelayBaseTime)); + SuccessOrExit(mDecoder.ReadUint8(aFrame.mInfo.mTxInfo.mRxChannelAfterTxDone)); aFrame.mInfo.mTxInfo.mCsmaCaEnabled = csmaEnable; aFrame.mInfo.mTxInfo.mIsHeaderUpdated = isHeaderUpdated; aFrame.mInfo.mTxInfo.mIsARetx = isARetx;