diff --git a/examples/platforms/simulation/radio.c b/examples/platforms/simulation/radio.c index 2e856dc15..fc9219de1 100644 --- a/examples/platforms/simulation/radio.c +++ b/examples/platforms/simulation/radio.c @@ -39,6 +39,7 @@ #include #include +#include "common/code_utils.hpp" #include "utils/code_utils.h" #include "utils/mac_frame.h" #include "utils/soft_source_match_table.h" @@ -113,6 +114,14 @@ static int8_t sTxPower = 0; static int8_t sCcaEdThresh = -74; static int8_t sLnaGain = 0; +enum +{ + kMinChannel = 11, + kMaxChannel = 26, +}; +static int8_t sChannelMaxTransmitPower[kMaxChannel - kMinChannel + 1]; +static uint8_t sCurrentChannel = kMinChannel; + static bool sSrcMatchEnabled = false; #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 @@ -359,6 +368,11 @@ void platformRadioInit(void) #else sTransmitFrame.mInfo.mTxInfo.mIeInfo = NULL; #endif + + for (size_t i = 0; i <= kMaxChannel - kMinChannel; i++) + { + sChannelMaxTransmitPower[i] = OT_RADIO_POWER_INVALID; + } } #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE @@ -433,6 +447,7 @@ otError otPlatRadioReceive(otInstance *aInstance, uint8_t aChannel) sState = OT_RADIO_STATE_RECEIVE; sTxWait = false; sReceiveFrame.mChannel = aChannel; + sCurrentChannel = aChannel; } return error; @@ -450,8 +465,9 @@ otError otPlatRadioTransmit(otInstance *aInstance, otRadioFrame *aFrame) if (sState == OT_RADIO_STATE_RECEIVE) { - error = OT_ERROR_NONE; - sState = OT_RADIO_STATE_TRANSMIT; + error = OT_ERROR_NONE; + sState = OT_RADIO_STATE_TRANSMIT; + sCurrentChannel = aFrame->mChannel; } return error; @@ -945,9 +961,11 @@ otError otPlatRadioGetTransmitPower(otInstance *aInstance, int8_t *aPower) { OT_UNUSED_VARIABLE(aInstance); + int8_t maxPower = sChannelMaxTransmitPower[sCurrentChannel - kMinChannel]; + assert(aInstance != NULL); - *aPower = sTxPower; + *aPower = sTxPower < maxPower ? sTxPower : maxPower; return OT_ERROR_NONE; } @@ -1155,3 +1173,16 @@ void otPlatRadioSetMacFrameCounter(otInstance *aInstance, uint32_t aMacFrameCoun sMacFrameCounter = aMacFrameCounter; } + +otError otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aChannel, int8_t aMaxPower) +{ + OT_UNUSED_VARIABLE(aInstance); + + otError error = OT_ERROR_NONE; + + VerifyOrExit(aChannel >= kMinChannel && aChannel <= kMaxChannel, error = OT_ERROR_INVALID_ARGS); + sChannelMaxTransmitPower[aChannel - kMinChannel] = aMaxPower; + +exit: + return error; +} diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 90610b077..aa726b0e4 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 (52) +#define OPENTHREAD_API_VERSION (53) /** * @addtogroup api-instance diff --git a/include/openthread/platform/radio.h b/include/openthread/platform/radio.h index 806fdf4d3..e1cb45dd0 100644 --- a/include/openthread/platform/radio.h +++ b/include/openthread/platform/radio.h @@ -72,9 +72,10 @@ enum OT_RADIO_BIT_RATE = 250000, ///< 2.4 GHz IEEE 802.15.4 (bits per second) OT_RADIO_BITS_PER_OCTET = 8, ///< Number of bits per octet - OT_RADIO_SYMBOL_TIME = ((OT_RADIO_BITS_PER_OCTET / OT_RADIO_SYMBOLS_PER_OCTET) * 1000000) / OT_RADIO_BIT_RATE, - OT_RADIO_LQI_NONE = 0, ///< LQI measurement not supported - OT_RADIO_RSSI_INVALID = 127, ///< Invalid or unknown RSSI value + OT_RADIO_SYMBOL_TIME = ((OT_RADIO_BITS_PER_OCTET / OT_RADIO_SYMBOLS_PER_OCTET) * 1000000) / OT_RADIO_BIT_RATE, + OT_RADIO_LQI_NONE = 0, ///< LQI measurement not supported + OT_RADIO_RSSI_INVALID = 127, ///< Invalid or unknown RSSI value + OT_RADIO_POWER_INVALID = 127, ///< Invalid or unknown power value }; /** @@ -946,6 +947,21 @@ otError otPlatRadioEnableCsl(otInstance *aInstance, uint32_t aCslPeriod, const o */ void otPlatRadioUpdateCslSampleTime(otInstance *aInstance, uint32_t aCslSampleTime); +/** + * Set the max transmit power for a specific channel. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aChannel The radio channel. + * @param[in] aMaxPower The max power in dBm, passing OT_RADIO_RSSI_INVALID will disable this channel. + * + * @retval OT_ERROR_NOT_IMPLEMENTED The feature is not implemented + * @retval OT_ERROR_INVALID_ARGS The specified channel is not valid. + * @retval OT_ERROR_FAILED Other platform specific errors. + * @retval OT_ERROR_NONE Successfully set max transmit poewr. + * + */ +otError otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aChannel, int8_t aMaxPower); + /** * @} * diff --git a/src/posix/platform/max_power_table.hpp b/src/core/radio/max_power_table.hpp similarity index 79% rename from src/posix/platform/max_power_table.hpp rename to src/core/radio/max_power_table.hpp index f72cfa5dc..2bd27a4e9 100644 --- a/src/posix/platform/max_power_table.hpp +++ b/src/core/radio/max_power_table.hpp @@ -26,27 +26,27 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#ifndef OT_POSIX_PLATFORM_MAX_POWER_TABLE_HPP_ -#define OT_POSIX_PLATFORM_MAX_POWER_TABLE_HPP_ +#ifndef OT_CORE_RADIO_MAX_POWER_TABLE_HPP_ +#define OT_CORE_RADIO_MAX_POWER_TABLE_HPP_ #include "core/radio/radio.hpp" +#include "openthread/platform/radio.h" namespace ot { -namespace Posix { class MaxPowerTable { public: static const int8_t kPowerDefault = 30; ///< Default power 1 watt (30 dBm). - MaxPowerTable(void) { memset(mPowerTable, kPowerForbidden, sizeof(mPowerTable)); } + MaxPowerTable(void) { memset(mPowerTable, kPowerDefault, sizeof(mPowerTable)); } /** * This method gets the max allowed transmit power of channel @p aChannel. * * @params[in] aChannel The radio channel number. * - * @returns The max allowed transmit power in dBm. + * @returns The max supported transmit power in dBm. * */ int8_t GetTransmitPower(uint8_t aChannel) const { return mPowerTable[aChannel - Radio::kChannelMin]; } @@ -55,24 +55,22 @@ public: * This method sets the max allowed transmit power of channel @p aChannel. * * @params[in] aChannel The radio channel number. - * @params[in] aPower The max allowed transmit power in dBm. + * @params[in] aPower The max supported transmit power in dBm. * */ void SetTransmitPower(uint8_t aChannel, int8_t aPower) { mPowerTable[aChannel - Radio::kChannelMin] = aPower; } /** - * This method gets the allowed channel masks. - * - * All channels of max power value of 0x7f is considered forbidden. + * This method gets the supported channel masks. * */ - uint32_t GetAllowedChannelMask(void) const + uint32_t GetSupportedChannelMask(void) const { uint32_t channelMask = 0; for (uint8_t i = Radio::kChannelMin; i <= Radio::kChannelMax; ++i) { - if (mPowerTable[i - Radio::kChannelMin] != kPowerForbidden) + if (mPowerTable[i - Radio::kChannelMin] != OT_RADIO_POWER_INVALID) { channelMask |= (1 << i); } @@ -82,12 +80,9 @@ public: } private: - static const int8_t kPowerForbidden = 0x7f; - int8_t mPowerTable[Radio::kChannelMax - Radio::kChannelMin + 1]; }; -} // namespace Posix } // namespace ot -#endif // OT_POSIX_PLATFORM_MAX_POWER_TABLE_HPP_ +#endif // OT_CORE_RADIO_MAX_POWER_TABLE_HPP_ diff --git a/src/core/radio/radio_platform.cpp b/src/core/radio/radio_platform.cpp index bc9853033..605ddb781 100644 --- a/src/core/radio/radio_platform.cpp +++ b/src/core/radio/radio_platform.cpp @@ -181,3 +181,12 @@ OT_TOOL_WEAK otError otPlatRadioSetFemLnaGain(otInstance *aInstance, int8_t aGai return OT_ERROR_NOT_IMPLEMENTED; } + +OT_TOOL_WEAK otError otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aChannel, int8_t aMaxPower) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aChannel); + OT_UNUSED_VARIABLE(aMaxPower); + + return OT_ERROR_NOT_IMPLEMENTED; +} diff --git a/src/lib/spinel/radio_spinel.hpp b/src/lib/spinel/radio_spinel.hpp index 3c9794b57..df57b34f7 100644 --- a/src/lib/spinel/radio_spinel.hpp +++ b/src/lib/spinel/radio_spinel.hpp @@ -39,6 +39,7 @@ #include "openthread-spinel-config.h" #include "spinel.h" #include "spinel_interface.hpp" +#include "core/radio/max_power_table.hpp" #include "ncp/ncp_config.h" namespace ot { @@ -704,6 +705,18 @@ public: */ uint32_t GetBusSpeed(void) const; + /** + * This method sets the max transmit power. + * + * @param[in] aChannel The radio channel. + * @param[in] aPower The max transmit power in dBm. + * + * @retval OT_ERROR_NONE Succesfully set the max transmit power. + * @retval OT_ERROR_INVALID_ARGS Channel is not in valid range. + * + */ + otError SetChannelMaxTransmitPower(uint8_t aChannel, int8_t aPower); + private: enum { @@ -968,6 +981,8 @@ private: uint64_t mTxRadioEndUs; uint64_t mRadioTimeRecalcStart; ///< When to recalculate RCP time offset. int64_t mRadioTimeOffset; ///< Time difference with estimated RCP time minus host time. + + MaxPowerTable mMaxPowerTable; }; } // namespace Spinel diff --git a/src/lib/spinel/radio_spinel_impl.hpp b/src/lib/spinel/radio_spinel_impl.hpp index cd7400864..2fec895fb 100644 --- a/src/lib/spinel/radio_spinel_impl.hpp +++ b/src/lib/spinel/radio_spinel_impl.hpp @@ -52,6 +52,7 @@ #include "lib/spinel/spinel_decoder.hpp" #include "meshcop/dataset.hpp" #include "meshcop/meshcop_tlvs.hpp" +#include "radio/radio.hpp" #ifndef MS_PER_S #define MS_PER_S 1000 @@ -2047,6 +2048,8 @@ uint32_t RadioSpinel::GetRadioChannelMask(boo maskLength -= static_cast(unpacked); } + channelMask &= mMaxPowerTable.GetSupportedChannelMask(); + exit: LogIfFail("Get radio channel mask failed", error); return channelMask; @@ -2289,9 +2292,37 @@ void RadioSpinel::RestoreProperties(void) SuccessOrDie(Set(SPINEL_PROP_PHY_FEM_LNA_GAIN, SPINEL_DATATYPE_INT8_S, mFemLnaGain)); } + for (uint8_t channel = Radio::kChannelMin; channel <= Radio::kChannelMax; channel++) + { + int8_t power = mMaxPowerTable.GetTransmitPower(channel); + + if (power != OT_RADIO_POWER_INVALID) + { + // Some old RCPs doesn't support max transmit power + otError error = SetChannelMaxTransmitPower(channel, power); + + if (error != OT_ERROR_NONE && error != OT_ERROR_NOT_FOUND) + { + DieNow(OT_EXIT_FAILURE); + } + } + } + CalcRcpTimeOffset(); } #endif // OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 +template +otError RadioSpinel::SetChannelMaxTransmitPower(uint8_t aChannel, int8_t aMaxPower) +{ + otError error = OT_ERROR_NONE; + VerifyOrExit(aChannel >= Radio::kChannelMin && aChannel <= Radio::kChannelMax, error = OT_ERROR_INVALID_ARGS); + mMaxPowerTable.SetTransmitPower(aChannel, aMaxPower); + error = Set(SPINEL_PROP_PHY_CHAN_MAX_POWER, SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_INT8_S, aChannel, aMaxPower); + +exit: + return error; +} + } // namespace Spinel } // namespace ot diff --git a/src/lib/spinel/spinel.c b/src/lib/spinel/spinel.c index f314f8142..e8389b1e7 100644 --- a/src/lib/spinel/spinel.c +++ b/src/lib/spinel/spinel.c @@ -1399,6 +1399,10 @@ const char *spinel_prop_key_to_cstr(spinel_prop_key_t prop_key) ret = "PHY_CHAN_PREFERRED"; break; + case SPINEL_PROP_PHY_CHAN_MAX_POWER: + ret = "PHY_CHAN_MAX_POWER"; + break; + case SPINEL_PROP_JAM_DETECT_ENABLE: ret = "JAM_DETECT_ENABLE"; break; diff --git a/src/lib/spinel/spinel.h b/src/lib/spinel/spinel.h index e98f78f26..07cd7f8c1 100644 --- a/src/lib/spinel/spinel.h +++ b/src/lib/spinel/spinel.h @@ -377,7 +377,7 @@ * Please see section "Spinel definition compatibility guideline" for more details. * */ -#define SPINEL_RCP_API_VERSION 1 +#define SPINEL_RCP_API_VERSION 2 /** * @def SPINEL_MIN_HOST_SUPPORTED_RCP_API_VERSION @@ -1568,7 +1568,15 @@ enum SPINEL_PROP_PHY_PCAP_ENABLED = SPINEL_PROP_PHY__BEGIN + 8, ///< [b] SPINEL_PROP_PHY_CHAN_PREFERRED = SPINEL_PROP_PHY__BEGIN + 9, ///< [A(C)] SPINEL_PROP_PHY_FEM_LNA_GAIN = SPINEL_PROP_PHY__BEGIN + 10, ///< dBm [c] - SPINEL_PROP_PHY__END = 0x30, + + /// Signal the max power for a channel + /** Format: `Cc` + * + * First byte is the channel then the max transmit power, write-only. + */ + SPINEL_PROP_PHY_CHAN_MAX_POWER = SPINEL_PROP_PHY__BEGIN + 11, + + SPINEL_PROP_PHY__END = 0x30, SPINEL_PROP_PHY_EXT__BEGIN = 0x1200, diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp index bdd7b8bc0..5435dea57 100644 --- a/src/ncp/ncp_base.cpp +++ b/src/ncp/ncp_base.cpp @@ -2238,6 +2238,20 @@ exit: return error; } +template <> otError NcpBase::HandlePropertySet(void) +{ + uint8_t channel; + int8_t maxPower; + otError error = OT_ERROR_NONE; + + SuccessOrExit(error = mDecoder.ReadUint8(channel)); + SuccessOrExit(error = mDecoder.ReadInt8(maxPower)); + error = otPlatRadioSetChannelMaxTransmitPower(mInstance, channel, maxPower); + +exit: + return error; +} + template <> otError NcpBase::HandlePropertyGet(void) { #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION diff --git a/src/ncp/ncp_base_dispatcher.cpp b/src/ncp/ncp_base_dispatcher.cpp index 629b942c6..6e67adbe6 100644 --- a/src/ncp/ncp_base_dispatcher.cpp +++ b/src/ncp/ncp_base_dispatcher.cpp @@ -368,6 +368,7 @@ NcpBase::PropertyHandler NcpBase::FindSetPropertyHandler(spinel_prop_key_t aKey) OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_PHY_PCAP_ENABLED), #endif OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_PHY_FEM_LNA_GAIN), + OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_PHY_CHAN_MAX_POWER), OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MAC_SCAN_STATE), OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MAC_SCAN_MASK), OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MAC_SCAN_PERIOD), diff --git a/src/posix/platform/radio.cpp b/src/posix/platform/radio.cpp index 1545307b5..09d2b4fa3 100644 --- a/src/posix/platform/radio.cpp +++ b/src/posix/platform/radio.cpp @@ -51,12 +51,6 @@ static ot::Spinel::RadioSpinel sRa #error "OPENTHREAD_POSIX_CONFIG_RCP_BUS only allows OT_POSIX_RCP_BUS_UART and OT_POSIX_RCP_BUS_SPI!" #endif -#if OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE -#include "posix/platform/max_power_table.hpp" - -static ot::Posix::MaxPowerTable sMaxPowerTable; -#endif - void otPlatRadioGetIeeeEui64(otInstance *aInstance, uint8_t *aIeeeEui64) { OT_UNUSED_VARIABLE(aInstance); @@ -101,30 +95,7 @@ void platformRadioInit(otUrl *aRadioUrl) bool restoreDataset = (radioUrl.GetValue("ncp-dataset") != nullptr); const char * parameterValue; #if OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE - uint8_t channel = ot::Radio::kChannelMin; - int8_t power = ot::Posix::MaxPowerTable::kPowerDefault; - const char *maxPowerTable = radioUrl.GetValue("max-power-table"); - - if (maxPowerTable != nullptr) - { - const char *str = nullptr; - - for (str = strtok(const_cast(maxPowerTable), ","); str != nullptr && channel <= ot::Radio::kChannelMax; - str = strtok(nullptr, ",")) - { - power = static_cast(strtol(str, nullptr, 0)); - sMaxPowerTable.SetTransmitPower(channel++, power); - } - - VerifyOrDie(str == nullptr, OT_EXIT_INVALID_ARGUMENTS); - } - - // Use the last power if omitted. - while (channel <= ot::Radio::kChannelMax) - { - sMaxPowerTable.SetTransmitPower(channel, power); - ++channel; - } + const char *maxPowerTable; #endif SuccessOrDie(sRadioSpinel.GetSpinelInterface().Init(radioUrl)); @@ -147,6 +118,43 @@ void platformRadioInit(otUrl *aRadioUrl) VerifyOrDie(INT8_MIN <= ccaThreshold && ccaThreshold <= INT8_MAX, OT_EXIT_INVALID_ARGUMENTS); SuccessOrDie(sRadioSpinel.SetCcaEnergyDetectThreshold(static_cast(ccaThreshold))); } + +#if OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE + maxPowerTable = radioUrl.GetValue("max-power-table"); + if (maxPowerTable != nullptr) + { + constexpr int8_t kPowerDefault = 30; // Default power 1 watt (30 dBm). + const char * str = nullptr; + uint8_t channel = ot::Radio::kChannelMin; + int8_t power = kPowerDefault; + otError error; + + for (str = strtok(const_cast(maxPowerTable), ","); str != nullptr && channel <= ot::Radio::kChannelMax; + str = strtok(nullptr, ",")) + { + power = static_cast(strtol(str, nullptr, 0)); + error = sRadioSpinel.SetChannelMaxTransmitPower(channel, power); + if (error != OT_ERROR_NONE && error != OT_ERROR_NOT_FOUND) + { + DieNow(OT_ERROR_FAILED); + } + ++channel; + } + + // Use the last power if omitted. + while (channel <= ot::Radio::kChannelMax) + { + error = sRadioSpinel.SetChannelMaxTransmitPower(channel, power); + if (error != OT_ERROR_NONE && error != OT_ERROR_NOT_FOUND) + { + DieNow(OT_ERROR_FAILED); + } + ++channel; + } + + VerifyOrDie(str == nullptr, OT_EXIT_INVALID_ARGUMENTS); + } +#endif // OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE } void platformRadioDeinit(void) @@ -183,12 +191,6 @@ otError otPlatRadioReceive(otInstance *aInstance, uint8_t aChannel) otError error; -#if OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE - if (sRadioSpinel.GetChannel() != aChannel) - { - SuccessOrExit(error = otPlatRadioSetTransmitPower(aInstance, sMaxPowerTable.GetTransmitPower(aChannel))); - } -#endif SuccessOrExit(error = sRadioSpinel.Receive(aChannel)); exit: @@ -495,21 +497,13 @@ void otPlatDiagAlarmCallback(otInstance *aInstance) uint32_t otPlatRadioGetSupportedChannelMask(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); - return -#if OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE - sMaxPowerTable.GetAllowedChannelMask() & -#endif - sRadioSpinel.GetRadioChannelMask(false); + return sRadioSpinel.GetRadioChannelMask(false); } uint32_t otPlatRadioGetPreferredChannelMask(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); - return -#if OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE - sMaxPowerTable.GetAllowedChannelMask() & -#endif - sRadioSpinel.GetRadioChannelMask(true); + return sRadioSpinel.GetRadioChannelMask(true); } otRadioState otPlatRadioGetState(otInstance *aInstance) @@ -546,3 +540,9 @@ uint32_t otPlatRadioGetBusSpeed(otInstance *aInstance) OT_UNUSED_VARIABLE(aInstance); return sRadioSpinel.GetBusSpeed(); } + +otError otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aChannel, int8_t aMaxPower) +{ + OT_UNUSED_VARIABLE(aInstance); + return sRadioSpinel.SetChannelMaxTransmitPower(aChannel, aMaxPower); +} diff --git a/tests/scripts/expect/posix-max-power-table.exp b/tests/scripts/expect/posix-max-power-table.exp index 4d5233964..733edfb34 100755 --- a/tests/scripts/expect/posix-max-power-table.exp +++ b/tests/scripts/expect/posix-max-power-table.exp @@ -37,6 +37,11 @@ expect "Done" send "channel preferred\n" expect "0x3fff800" expect "Done" +send "txpower 20\n" +expect "Done" +send "txpower\n" +expect "11 dBm" +expect "Done" send "\x04" expect eof # allows all channels by default