blob: afa09c7e68d5ebd81afa2f43a3985a6d825a6dc4 [file] [log] [blame]
/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <openthread/error.h>
#include <openthread/platform/alarm-milli.h>
#include <openthread/platform/diag.h>
#include <openthread/platform/logging.h>
#include <openthread/platform/radio.h>
#include "platform-zephyr.h"
#include "zephyr/sys/util.h"
enum {
DIAG_TRANSMIT_MODE_IDLE,
DIAG_TRANSMIT_MODE_PACKETS,
DIAG_TRANSMIT_MODE_CARRIER,
DIAG_TRANSMIT_MODE_MODCARRIER
} diag_trasmit_mode;
/**
* Diagnostics mode variables.
*
*/
static bool sDiagMode;
static void *sDiagCallbackContext;
static otPlatDiagOutputCallback sDiagOutputCallback;
static uint8_t sTransmitMode = DIAG_TRANSMIT_MODE_IDLE;
static uint8_t sChannel = 20;
static uint32_t sTxPeriod = 1;
static int32_t sTxCount;
static int32_t sTxRequestedCount = 1;
#if defined(CONFIG_OPENTHREAD_PLATFORM_CARRIER_FUNCTIONS)
static otError startModCarrier(otInstance *aInstance, uint8_t aArgsLength, char *aArgs[]);
#endif /* CONFIG_OPENTHREAD_PLATFORM_CARRIER_FUNCTIONS */
static otError processTransmit(otInstance *aInstance, uint8_t aArgsLength, char *aArgs[]);
static otError parse_long(char *aArgs, long *aValue)
{
char *endptr;
*aValue = strtol(aArgs, &endptr, 0);
return (*endptr == '\0') ? OT_ERROR_NONE : OT_ERROR_PARSE;
}
static void diag_output(const char *aFormat, ...)
{
va_list args;
va_start(args, aFormat);
if (sDiagOutputCallback != NULL) {
sDiagOutputCallback(aFormat, args, sDiagCallbackContext);
}
va_end(args);
}
void otPlatDiagSetOutputCallback(otInstance *aInstance,
otPlatDiagOutputCallback aCallback,
void *aContext)
{
OT_UNUSED_VARIABLE(aInstance);
sDiagOutputCallback = aCallback;
sDiagCallbackContext = aContext;
}
otError otPlatDiagProcess(otInstance *aInstance, uint8_t aArgsLength, char *aArgs[])
{
#if defined(CONFIG_OPENTHREAD_PLATFORM_CARRIER_FUNCTIONS)
if (strcmp(aArgs[0], "modcarrier") == 0) {
return startModCarrier(aInstance, aArgsLength - 1, aArgs + 1);
}
#endif /* CONFIG_OPENTHREAD_PLATFORM_CARRIER_FUNCTIONS */
if (strcmp(aArgs[0], "transmit") == 0) {
return processTransmit(aInstance, aArgsLength - 1, aArgs + 1);
}
/* Add more platform specific diagnostics features here. */
diag_output("diag feature '%s' is not supported\r\n", aArgs[0]);
return OT_ERROR_NOT_IMPLEMENTED;
}
void otPlatDiagModeSet(bool aMode)
{
otError error;
sDiagMode = aMode;
if (!sDiagMode) {
error = otPlatRadioSleep(NULL);
if (error != OT_ERROR_NONE) {
otPlatLog(OT_LOG_LEVEL_WARN, OT_LOG_REGION_PLATFORM,
"%s failed (%d)", "otPlatRadioSleep", error);
}
}
}
bool otPlatDiagModeGet(void)
{
return sDiagMode;
}
void otPlatDiagChannelSet(uint8_t aChannel)
{
sChannel = aChannel;
platformRadioChannelSet(aChannel);
}
void otPlatDiagTxPowerSet(int8_t aTxPower)
{
ARG_UNUSED(aTxPower);
}
void otPlatDiagRadioReceived(otInstance *aInstance,
otRadioFrame *aFrame,
otError aError)
{
ARG_UNUSED(aInstance);
ARG_UNUSED(aFrame);
ARG_UNUSED(aError);
}
#if defined(CONFIG_OPENTHREAD_PLATFORM_CARRIER_FUNCTIONS)
otError otPlatDiagRadioTransmitCarrier(otInstance *aInstance, bool aEnable)
{
if (sTransmitMode != DIAG_TRANSMIT_MODE_IDLE &&
sTransmitMode != DIAG_TRANSMIT_MODE_CARRIER) {
return OT_ERROR_INVALID_STATE;
}
if (aEnable) {
sTransmitMode = DIAG_TRANSMIT_MODE_CARRIER;
} else {
sTransmitMode = DIAG_TRANSMIT_MODE_IDLE;
}
return platformRadioTransmitCarrier(aInstance, aEnable);
}
#endif /* CONFIG_OPENTHREAD_PLATFORM_CARRIER_FUNCTIONS */
/*
* To enable gpio diag commands, in Devicetree create `openthread` node in `/options/` path
* with `compatible = "openthread,config"` property and `diag-gpios` property,
* which should contain array of GPIO pin's configuration properties containing controller phandles,
* pin numbers and pin flags. e.g:
*
* options {
* openthread {
* compatible = "openthread,config";
* diag-gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>,
* <&gpio1 0 GPIO_ACTIVE_LOW>;
* };
* };
*
* To enable reading current gpio pin mode, define
* `CONFIG_GPIO_GET_DIRECTION` in prj.conf.
*
* Note: `<gpio>` in `diag gpio` commands is an index of diag-gpios array. For example shown above,
* `ot diag gpio mode 0` will return current mode of pin nmb 0 controlled by `gpio0` controller.
*/
#if DT_HAS_COMPAT_STATUS_OKAY(openthread_config) && \
DT_NODE_HAS_PROP(DT_COMPAT_GET_ANY_STATUS_OKAY(openthread_config), diag_gpios)
static const struct gpio_dt_spec gpio_spec[] = {
DT_FOREACH_PROP_ELEM_SEP(DT_COMPAT_GET_ANY_STATUS_OKAY(openthread_config),
diag_gpios, GPIO_DT_SPEC_GET_BY_IDX, (,))};
static otError gpio_get_spec(uint32_t gpio_idx, const struct gpio_dt_spec **spec)
{
if (gpio_idx >= ARRAY_SIZE(gpio_spec)) {
return OT_ERROR_INVALID_ARGS;
}
*spec = &gpio_spec[gpio_idx];
if (!gpio_is_ready_dt(*spec)) {
return OT_ERROR_INVALID_ARGS;
}
const struct gpio_driver_config *const cfg =
(const struct gpio_driver_config *)((*spec)->port->config);
if ((cfg->port_pin_mask & (gpio_port_pins_t)BIT((*spec)->pin)) == 0U) {
return OT_ERROR_INVALID_ARGS;
}
return OT_ERROR_NONE;
}
otError otPlatDiagGpioSet(uint32_t aGpio, bool aValue)
{
const struct gpio_dt_spec *spec;
otError error;
error = gpio_get_spec(aGpio, &spec);
if (error != OT_ERROR_NONE) {
return error;
}
#if defined(CONFIG_GPIO_GET_DIRECTION)
if (gpio_pin_is_output_dt(spec) != 1) {
return OT_ERROR_INVALID_STATE;
}
#endif
if (gpio_pin_set_dt(spec, (int)aValue) != 0) {
return OT_ERROR_FAILED;
}
return OT_ERROR_NONE;
}
otError otPlatDiagGpioGet(uint32_t aGpio, bool *aValue)
{
const struct gpio_dt_spec *spec;
otError error;
int rv;
error = gpio_get_spec(aGpio, &spec);
if (error != OT_ERROR_NONE) {
return error;
}
if (aValue == NULL) {
return OT_ERROR_INVALID_ARGS;
}
#if defined(CONFIG_GPIO_GET_DIRECTION)
if (gpio_pin_is_input_dt(spec) != 1) {
return OT_ERROR_INVALID_STATE;
}
#endif
rv = gpio_pin_get_dt(spec);
if (rv < 0) {
return OT_ERROR_FAILED;
}
*aValue = (bool)rv;
return OT_ERROR_NONE;
}
otError otPlatDiagGpioSetMode(uint32_t aGpio, otGpioMode aMode)
{
const struct gpio_dt_spec *spec;
otError error;
int rv = 0;
error = gpio_get_spec(aGpio, &spec);
if (error != OT_ERROR_NONE) {
return error;
}
switch (aMode) {
case OT_GPIO_MODE_INPUT:
rv = gpio_pin_configure_dt(spec, GPIO_INPUT);
break;
case OT_GPIO_MODE_OUTPUT:
rv = gpio_pin_configure_dt(spec, GPIO_OUTPUT);
break;
default:
return OT_ERROR_INVALID_ARGS;
}
if (rv != 0) {
return OT_ERROR_FAILED;
}
return OT_ERROR_NONE;
}
#if defined(CONFIG_GPIO_GET_DIRECTION)
otError otPlatDiagGpioGetMode(uint32_t aGpio, otGpioMode *aMode)
{
const struct gpio_dt_spec *spec;
otError error;
gpio_port_pins_t pins_in, pins_out;
error = gpio_get_spec(aGpio, &spec);
if (error != OT_ERROR_NONE) {
return error;
}
if (aMode == NULL) {
return OT_ERROR_INVALID_ARGS;
}
if (gpio_port_get_direction(spec->port, BIT(spec->pin), &pins_in, &pins_out) < 0) {
return OT_ERROR_FAILED;
}
if (((gpio_port_pins_t)BIT(spec->pin) & pins_in) != 0U) {
*aMode = OT_GPIO_MODE_INPUT;
} else if (((gpio_port_pins_t)BIT(spec->pin) & pins_out) != 0U) {
*aMode = OT_GPIO_MODE_OUTPUT;
} else {
return OT_ERROR_FAILED;
}
return OT_ERROR_NONE;
}
#endif /* CONFIG_GPIO_GET_DIRECTION */
#endif /* DT_HAS_COMPAT_STATUS_OKAY(openthread_config) && \
* DT_NODE_HAS_PROP(DT_COMPAT_GET_ANY_STATUS_OKAY(openthread_config), diag_gpios)
*/
#if defined(CONFIG_OPENTHREAD_PLATFORM_CARRIER_FUNCTIONS)
static otError startModCarrier(otInstance *aInstance, uint8_t aArgsLength, char *aArgs[])
{
bool enable = true;
uint8_t data[OT_RADIO_FRAME_MAX_SIZE + 1];
if (aArgsLength <= 0) {
return OT_ERROR_INVALID_ARGS;
}
if (sTransmitMode != DIAG_TRANSMIT_MODE_IDLE &&
sTransmitMode != DIAG_TRANSMIT_MODE_MODCARRIER) {
return OT_ERROR_INVALID_STATE;
}
if (strcmp(aArgs[0], "stop") == 0) {
enable = false;
sTransmitMode = DIAG_TRANSMIT_MODE_IDLE;
} else {
if (hex2bin(aArgs[0], strlen(aArgs[0]), data, ARRAY_SIZE(data)) == 0) {
return OT_ERROR_INVALID_ARGS;
}
sTransmitMode = DIAG_TRANSMIT_MODE_MODCARRIER;
}
return platformRadioTransmitModulatedCarrier(aInstance, enable, data);
}
#endif /* CONFIG_OPENTHREAD_PLATFORM_CARRIER_FUNCTIONS */
void otPlatDiagAlarmCallback(otInstance *aInstance)
{
uint32_t now;
otError error;
otRadioFrame *txPacket;
const uint16_t diag_packet_len = 30;
if (sTransmitMode == DIAG_TRANSMIT_MODE_PACKETS) {
if ((sTxCount > 0) || (sTxCount == -1)) {
txPacket = otPlatRadioGetTransmitBuffer(aInstance);
txPacket->mInfo.mTxInfo.mTxDelayBaseTime = 0;
txPacket->mInfo.mTxInfo.mTxDelay = 0;
txPacket->mInfo.mTxInfo.mMaxCsmaBackoffs = 0;
txPacket->mInfo.mTxInfo.mMaxFrameRetries = 0;
txPacket->mInfo.mTxInfo.mRxChannelAfterTxDone = sChannel;
txPacket->mInfo.mTxInfo.mTxPower = OT_RADIO_POWER_INVALID;
txPacket->mInfo.mTxInfo.mIsHeaderUpdated = false;
txPacket->mInfo.mTxInfo.mIsARetx = false;
txPacket->mInfo.mTxInfo.mCsmaCaEnabled = false;
txPacket->mInfo.mTxInfo.mCslPresent = false;
txPacket->mInfo.mTxInfo.mIsSecurityProcessed = false;
txPacket->mLength = diag_packet_len;
for (uint8_t i = 0; i < diag_packet_len; i++) {
txPacket->mPsdu[i] = i;
}
error = otPlatRadioTransmit(aInstance, txPacket);
if (error != OT_ERROR_NONE) {
otPlatLog(OT_LOG_LEVEL_WARN, OT_LOG_REGION_PLATFORM,
"%s failed (%d)", "otPlatRadioTransmit", error);
}
if (sTxCount != -1) {
sTxCount--;
}
now = otPlatAlarmMilliGetNow();
otPlatAlarmMilliStartAt(aInstance, now, sTxPeriod);
} else {
sTransmitMode = DIAG_TRANSMIT_MODE_IDLE;
otPlatAlarmMilliStop(aInstance);
otPlatLog(OT_LOG_LEVEL_DEBG, OT_LOG_REGION_PLATFORM, "Transmit done");
}
}
}
static otError processTransmit(otInstance *aInstance, uint8_t aArgsLength, char *aArgs[])
{
otError error = OT_ERROR_NONE;
long value;
uint32_t now;
if (aArgsLength == 0) {
diag_output("transmit will send %" PRId32 " diagnostic messages with %" PRIu32
" ms interval\r\n",
sTxRequestedCount, sTxPeriod);
} else if (strcmp(aArgs[0], "stop") == 0) {
if (sTransmitMode == DIAG_TRANSMIT_MODE_IDLE) {
return OT_ERROR_INVALID_STATE;
}
otPlatAlarmMilliStop(aInstance);
diag_output("diagnostic message transmission is stopped\r\n");
sTransmitMode = DIAG_TRANSMIT_MODE_IDLE;
error = otPlatRadioReceive(aInstance, sChannel);
if (error != OT_ERROR_NONE) {
otPlatLog(OT_LOG_LEVEL_WARN, OT_LOG_REGION_PLATFORM,
"%s failed (%d)", "otPlatRadioReceive", error);
}
} else if (strcmp(aArgs[0], "start") == 0) {
if (sTransmitMode != DIAG_TRANSMIT_MODE_IDLE) {
return OT_ERROR_INVALID_STATE;
}
otPlatAlarmMilliStop(aInstance);
sTransmitMode = DIAG_TRANSMIT_MODE_PACKETS;
sTxCount = sTxRequestedCount;
now = otPlatAlarmMilliGetNow();
otPlatAlarmMilliStartAt(aInstance, now, sTxPeriod);
diag_output("sending %" PRId32 " diagnostic messages with %" PRIu32
" ms interval\r\n",
sTxRequestedCount, sTxPeriod);
} else if (strcmp(aArgs[0], "interval") == 0) {
if (aArgsLength != 2) {
return OT_ERROR_INVALID_ARGS;
}
error = parse_long(aArgs[1], &value);
if (error != OT_ERROR_NONE) {
return error;
}
if (value <= 0) {
return OT_ERROR_INVALID_ARGS;
}
sTxPeriod = (uint32_t)(value);
diag_output("set diagnostic messages interval to %" PRIu32
" ms\r\n", sTxPeriod);
} else if (strcmp(aArgs[0], "count") == 0) {
if (aArgsLength != 2) {
return OT_ERROR_INVALID_ARGS;
}
error = parse_long(aArgs[1], &value);
if (error != OT_ERROR_NONE) {
return error;
}
if ((value <= 0) && (value != -1)) {
return OT_ERROR_INVALID_ARGS;
}
sTxRequestedCount = (uint32_t)(value);
diag_output("set diagnostic messages count to %" PRId32 "\r\n",
sTxRequestedCount);
} else {
return OT_ERROR_INVALID_ARGS;
}
return error;
}