/* * Copyright (c) 2023, The OpenThread Authors. * All rights reserved. * * 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 the copyright holder 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 the OpenThread platform abstraction for the diagnostics. * */ #include #include #include #include #ifdef SL_COMPONENT_CATALOG_PRESENT #include "sl_component_catalog.h" #endif // SL_COMPONENT_CATALOG_PRESENT #include #include #include #include #include #include #include "common/code_utils.hpp" #include "common/debug.hpp" #include "common/logging.hpp" #include "diag.h" #include "sl_gpio.h" #include "sl_hal_gpio.h" #include "platform-band.h" #include "platform-efr32.h" #include "rail_ieee802154.h" #include "sl_status.h" #if OPENTHREAD_CONFIG_DIAG_ENABLE #ifdef SL_COMPONENT_CATALOG_PRESENT #include "sl_component_catalog.h" #endif // SL_COMPONENT_CATALOG_PRESENT #ifdef SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT #include "sl_rail_util_ant_div.h" #endif #define GPIO_PIN_BITMASK 0xFFFFUL #define GPIO_PORT_BITMASK (0xFFFFUL << 16) #define GET_GPIO_PIN(x) (x & GPIO_PIN_BITMASK) #define GET_GPIO_PORT(x) ((x & GPIO_PORT_BITMASK) >> 16) // To cache the transmit power, so that we don't override it while loading the // channel config or setting the channel. static int8_t sTxPower = OPENTHREAD_CONFIG_DEFAULT_TRANSMIT_POWER; struct PlatformDiagCommand { const char *mName; otError (*mCommand)(otInstance *aInstance, uint8_t aArgsLength, char *aArgs[]); }; // Diagnostics mode variables. static bool sDiagMode = false; static otPlatDiagOutputCallback sDiagOutputCallback = NULL; static void *sDiagCallbackContext = NULL; static void diagOutput(const char *aFormat, ...) { va_list args; va_start(args, aFormat); if (sDiagOutputCallback != NULL) { sDiagOutputCallback(aFormat, args, sDiagCallbackContext); } va_end(args); } static void appendErrorResult(otError aError) { if (aError != OT_ERROR_NONE) { diagOutput("failed\r\nstatus %#x\r\n", aError); } } // ***************************************************************************** // CLI functions // ***************************************************************************** static otError processAddressMatch(otInstance *aInstance, uint8_t aArgsLength, char *aArgs[]) { OT_UNUSED_VARIABLE(aInstance); otError error = OT_ERROR_INVALID_ARGS; VerifyOrExit(otPlatDiagModeGet(), error = OT_ERROR_INVALID_STATE); VerifyOrExit(aArgsLength > 0, error = OT_ERROR_INVALID_ARGS); if (strcmp(aArgs[0], "enable") == 0) { error = otPlatDiagRadioAddressMatch(true); } else if (strcmp(aArgs[0], "disable") == 0) { error = otPlatDiagRadioAddressMatch(false); } exit: appendErrorResult(error); return error; } static otError processAutoAck(otInstance *aInstance, uint8_t aArgsLength, char *aArgs[]) { OT_UNUSED_VARIABLE(aInstance); otError error = OT_ERROR_INVALID_ARGS; VerifyOrExit(otPlatDiagModeGet(), error = OT_ERROR_INVALID_STATE); VerifyOrExit(aArgsLength > 0, error = OT_ERROR_INVALID_ARGS); if (strcmp(aArgs[0], "enable") == 0) { error = otPlatDiagRadioAutoAck(true); } else if (strcmp(aArgs[0], "disable") == 0) { error = otPlatDiagRadioAutoAck(false); } exit: appendErrorResult(error); return error; } // ***************************************************************************** // Add more platform specific diagnostic's CLI features here. // ***************************************************************************** const struct PlatformDiagCommand sCommands[] = { {"addr-match", &processAddressMatch}, {"auto-ack", &processAutoAck}, }; otError otPlatDiagProcess(otInstance *aInstance, uint8_t aArgsLength, char *aArgs[]) { otError error = OT_ERROR_INVALID_COMMAND; size_t i; for (i = 0; i < otARRAY_LENGTH(sCommands); i++) { if (strcmp(aArgs[0], sCommands[i].mName) == 0) { error = sCommands[i].mCommand(aInstance, aArgsLength - 1, aArgsLength > 1 ? &aArgs[1] : NULL); break; } } return error; } // ***************************************************************************** // Implement platform specific diagnostic's APIs. // ***************************************************************************** void otPlatDiagSetOutputCallback(otInstance *aInstance, otPlatDiagOutputCallback aCallback, void *aContext) { OT_UNUSED_VARIABLE(aInstance); sDiagOutputCallback = aCallback; sDiagCallbackContext = aContext; } void otPlatDiagModeSet(bool aMode) { sDiagMode = aMode; } bool otPlatDiagModeGet() { return sDiagMode; } static RAIL_Status_t startTxStream(RAIL_StreamMode_t aMode) { uint16_t txChannel; RAIL_Status_t status; SuccessOrExit(status = RAIL_GetChannel(gRailHandle, &txChannel)); #ifdef SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT RAIL_TxOptions_t txOptions = RAIL_TX_OPTIONS_DEFAULT; // Translate Tx antenna diversity mode into RAIL Tx Antenna options: // If enabled, use the currently-selected antenna, otherwise leave // both options 0 so Tx antenna tracks Rx antenna. if (sl_rail_util_ant_div_get_tx_antenna_mode() != SL_RAIL_UTIL_ANTENNA_MODE_DISABLED) { txOptions |= ((sl_rail_util_ant_div_get_tx_antenna_selected() == SL_RAIL_UTIL_ANTENNA_SELECT_ANTENNA1) ? RAIL_TX_OPTION_ANTENNA0 : RAIL_TX_OPTION_ANTENNA1); } status = RAIL_StartTxStreamAlt(gRailHandle, txChannel, aMode, txOptions); #else // !SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT status = RAIL_StartTxStream(gRailHandle, txChannel, aMode); #endif // SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT exit: return status; } static RAIL_Status_t stopTxStream(void) { RAIL_Status_t status; uint16_t currentChannel; RAIL_SchedulerInfo_t rxSchedulerInfo = { .priority = SL_802154_RADIO_PRIO_BACKGROUND_RX_VALUE, }; SuccessOrExit(status = RAIL_StopTxStream(gRailHandle)); // Since start transmit stream turn off the radio state, // call the RAIL_StartRx to turn on radio IgnoreError(RAIL_GetChannel(gRailHandle, ¤tChannel)); status = RAIL_StartRx(gRailHandle, currentChannel, &rxSchedulerInfo); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); exit: return status; } otError otPlatDiagRadioTransmitCarrier(otInstance *aInstance, bool aEnable) { OT_UNUSED_VARIABLE(aInstance); RAIL_Status_t status; if (aEnable) { otLogInfoPlat("Diag CARRIER-WAVE/Tone start"); status = startTxStream(RAIL_STREAM_CARRIER_WAVE); } else { otLogInfoPlat("Diag CARRIER-WAVE/Tone stop"); status = stopTxStream(); } return (status != RAIL_STATUS_NO_ERROR ? OT_ERROR_FAILED : OT_ERROR_NONE); } otError otPlatDiagRadioTransmitStream(otInstance *aInstance, bool aEnable) { OT_UNUSED_VARIABLE(aInstance); RAIL_Status_t status; if (aEnable) { otLogInfoPlat("Diag Stream PN9 start"); status = startTxStream(RAIL_STREAM_PN9_STREAM); } else { otLogInfoPlat("Diag Stream stop"); status = stopTxStream(); } return (status != RAIL_STATUS_NO_ERROR ? OT_ERROR_FAILED : OT_ERROR_NONE); } otError otPlatDiagRadioAddressMatch(bool aEnable) { RAIL_Status_t status; otLogInfoPlat("Diag address-match %s", aEnable ? "enable" : "disable"); status = RAIL_IEEE802154_SetPromiscuousMode(gRailHandle, !aEnable); return (status != RAIL_STATUS_NO_ERROR ? OT_ERROR_FAILED : OT_ERROR_NONE); } otError otPlatDiagRadioAutoAck(bool aAutoAckEnabled) { otLogInfoPlat("Diag auto-ack %s", aAutoAckEnabled ? "enable" : "disable"); RAIL_PauseRxAutoAck(gRailHandle, !aAutoAckEnabled); return OT_ERROR_NONE; } void otPlatDiagChannelSet(uint8_t aChannel) { otError error = OT_ERROR_NONE; RAIL_Status_t status; RAIL_SchedulerInfo_t bgRxSchedulerInfo = { .priority = SL_802154_RADIO_PRIO_BACKGROUND_RX_VALUE, // sliptime/transaction time is not used for bg rx }; error = efr32RadioLoadChannelConfig(aChannel, sTxPower); OT_ASSERT(error == OT_ERROR_NONE); status = RAIL_StartRx(gRailHandle, aChannel, &bgRxSchedulerInfo); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); } void otPlatDiagTxPowerSet(int8_t aTxPower) { RAIL_Status_t status; // RAIL_SetTxPowerDbm() takes power in units of deci-dBm (0.1dBm) // Multiply by 10 because aPower is supposed be in units dBm status = RAIL_SetTxPowerDbm(gRailHandle, ((RAIL_TxPower_t)aTxPower) * 10); OT_ASSERT(status == RAIL_STATUS_NO_ERROR); sTxPower = aTxPower; } void otPlatDiagRadioReceived(otInstance *aInstance, otRadioFrame *aFrame, otError aError) { OT_UNUSED_VARIABLE(aInstance); OT_UNUSED_VARIABLE(aFrame); OT_UNUSED_VARIABLE(aError); } void otPlatDiagAlarmCallback(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); } static otError getGpioPortAndPin(uint32_t aGpio, uint16_t *aPort, uint16_t *aPin) { otError error = OT_ERROR_NONE; *aPort = GET_GPIO_PORT(aGpio); *aPin = GET_GPIO_PIN(aGpio); #if defined(SL_CATALOG_GPIO_PRESENT) if (*aPort > SL_HAL_GPIO_PORT_MAX || *aPin > SL_HAL_GPIO_PIN_MAX) #else if (*aPort > GPIO_PORT_MAX || *aPin > GPIO_PIN_MAX) #endif { ExitNow(error = OT_ERROR_INVALID_ARGS); } exit: return error; } otError otPlatDiagGpioSet(uint32_t aGpio, bool aValue) { otError error = OT_ERROR_NONE; uint16_t port; uint16_t pin; SuccessOrExit(error = getGpioPortAndPin(aGpio, &port, &pin)); sl_gpio_t gpio; gpio.port = (uint8_t)port; gpio.pin = (uint8_t)pin; if (aValue) { VerifyOrExit(sl_gpio_set_pin(&gpio) == SL_STATUS_OK, error = OT_ERROR_INVALID_ARGS); } else { VerifyOrExit(sl_gpio_clear_pin(&gpio) == SL_STATUS_OK, error = OT_ERROR_INVALID_ARGS); } exit: return error; } otError otPlatDiagGpioGet(uint32_t aGpio, bool *aValue) { otError error = OT_ERROR_NONE; uint16_t port; uint16_t pin; SuccessOrExit(error = getGpioPortAndPin(aGpio, &port, &pin)); sl_gpio_t gpio; gpio.port = (uint8_t)port; gpio.pin = (uint8_t)pin; VerifyOrExit(sl_gpio_get_pin_input(&gpio, aValue) == SL_STATUS_OK, error = OT_ERROR_INVALID_ARGS); exit: return error; } otError otPlatDiagGpioSetMode(uint32_t aGpio, otGpioMode aMode) { otError error = OT_ERROR_NONE; uint16_t port; uint16_t pin; sl_gpio_mode_t mode; SuccessOrExit(error = getGpioPortAndPin(aGpio, &port, &pin)); mode = (aMode == OT_GPIO_MODE_INPUT) ? SL_GPIO_MODE_INPUT : SL_GPIO_MODE_PUSH_PULL; sl_gpio_t gpio; gpio.port = (uint8_t)port; gpio.pin = (uint8_t)pin; error = sl_gpio_set_pin_mode(&gpio, mode, 0 /*out*/); // Convert to otError. VerifyOrExit(error == SL_STATUS_OK, error = (error == SL_STATUS_INVALID_STATE ? OT_ERROR_INVALID_STATE : OT_ERROR_INVALID_ARGS)); exit: return error; } otError otPlatDiagGpioGetMode(uint32_t aGpio, otGpioMode *aMode) { otError error = OT_ERROR_NONE; uint16_t port; uint16_t pin; sl_gpio_mode_t mode; SuccessOrExit(error = getGpioPortAndPin(aGpio, &port, &pin)); sl_gpio_t gpio; sl_gpio_pin_config_t pin_config; gpio.port = (uint8_t)port; gpio.pin = (uint8_t)pin; VerifyOrExit(sl_gpio_get_pin_config(&gpio, &pin_config) == SL_STATUS_OK, error = OT_ERROR_INVALID_ARGS); mode = pin_config.mode; *aMode = (mode == SL_GPIO_MODE_INPUT) ? OT_GPIO_MODE_INPUT : OT_GPIO_MODE_OUTPUT; exit: return error; } #endif // OPENTHREAD_CONFIG_DIAG_ENABLE