blob: c6c4e65318832cb7b6a6734abeb5e55a8f9e25a5 [file] [log] [blame]
/*
* Copyright (c) 2018, 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 spinel based radio transceiver.
*/
#include "platform-posix.h"
#include "radio_spinel.hpp"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#if OPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE
#ifdef OPENTHREAD_TARGET_DARWIN
#include <util.h>
#else
#include <pty.h>
#endif
#endif
#include <stdarg.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <syslog.h>
#include <termios.h>
#include <unistd.h>
#include <common/code_utils.hpp>
#include <common/encoding.hpp>
#include <common/logging.hpp>
#include <openthread/platform/alarm-milli.h>
#include <openthread/platform/diag.h>
#include <openthread/platform/radio.h>
#ifndef SOCKET_UTILS_DEFAULT_SHELL
#define SOCKET_UTILS_DEFAULT_SHELL "/bin/sh"
#endif
enum
{
IEEE802154_MIN_LENGTH = 5,
IEEE802154_MAX_LENGTH = 127,
IEEE802154_ACK_LENGTH = 5,
IEEE802154_BROADCAST = 0xffff,
IEEE802154_FRAME_TYPE_ACK = 2 << 0,
IEEE802154_FRAME_TYPE_MACCMD = 3 << 0,
IEEE802154_FRAME_TYPE_MASK = 7 << 0,
IEEE802154_SECURITY_ENABLED = 1 << 3,
IEEE802154_FRAME_PENDING = 1 << 4,
IEEE802154_ACK_REQUEST = 1 << 5,
IEEE802154_PANID_COMPRESSION = 1 << 6,
IEEE802154_DST_ADDR_NONE = 0 << 2,
IEEE802154_DST_ADDR_SHORT = 2 << 2,
IEEE802154_DST_ADDR_EXT = 3 << 2,
IEEE802154_DST_ADDR_MASK = 3 << 2,
IEEE802154_SRC_ADDR_NONE = 0 << 6,
IEEE802154_SRC_ADDR_SHORT = 2 << 6,
IEEE802154_SRC_ADDR_EXT = 3 << 6,
IEEE802154_SRC_ADDR_MASK = 3 << 6,
IEEE802154_DSN_OFFSET = 2,
IEEE802154_DSTPAN_OFFSET = 3,
IEEE802154_DSTADDR_OFFSET = 5,
IEEE802154_SEC_LEVEL_MASK = 7 << 0,
IEEE802154_KEY_ID_MODE_0 = 0 << 3,
IEEE802154_KEY_ID_MODE_1 = 1 << 3,
IEEE802154_KEY_ID_MODE_2 = 2 << 3,
IEEE802154_KEY_ID_MODE_3 = 3 << 3,
IEEE802154_KEY_ID_MODE_MASK = 3 << 3,
IEEE802154_MACCMD_DATA_REQ = 4,
};
enum
{
kIdle,
kSent,
kDone,
};
static ot::RadioSpinel sRadioSpinel;
static inline otPanId getDstPan(const uint8_t *frame)
{
return static_cast<otPanId>((frame[IEEE802154_DSTPAN_OFFSET + 1] << 8) | frame[IEEE802154_DSTPAN_OFFSET]);
}
static inline otShortAddress getShortAddress(const uint8_t *frame)
{
return static_cast<otShortAddress>((frame[IEEE802154_DSTADDR_OFFSET + 1] << 8) | frame[IEEE802154_DSTADDR_OFFSET]);
}
static inline void getExtAddress(const uint8_t *frame, otExtAddress *address)
{
size_t i;
for (i = 0; i < sizeof(otExtAddress); i++)
{
address->m8[i] = frame[IEEE802154_DSTADDR_OFFSET + (sizeof(otExtAddress) - 1 - i)];
}
}
static inline bool isAckRequested(const uint8_t *frame)
{
return (frame[0] & IEEE802154_ACK_REQUEST) != 0;
}
static inline void SuccessOrDie(otError aError)
{
if (aError != OT_ERROR_NONE)
{
// fprintf(stderr, "Operation failed: %s\r\n", otThreadErrorToString(aError));
exit(EXIT_FAILURE);
}
}
static inline void VerifyOrDie(bool aCondition)
{
if (!aCondition)
{
exit(EXIT_FAILURE);
}
}
namespace ot {
static otError SpinelStatusToOtError(spinel_status_t aError)
{
otError ret;
switch (aError)
{
case SPINEL_STATUS_OK:
ret = OT_ERROR_NONE;
break;
case SPINEL_STATUS_FAILURE:
ret = OT_ERROR_FAILED;
break;
case SPINEL_STATUS_DROPPED:
ret = OT_ERROR_DROP;
break;
case SPINEL_STATUS_NOMEM:
ret = OT_ERROR_NO_BUFS;
break;
case SPINEL_STATUS_BUSY:
ret = OT_ERROR_BUSY;
break;
case SPINEL_STATUS_PARSE_ERROR:
ret = OT_ERROR_PARSE;
break;
case SPINEL_STATUS_INVALID_ARGUMENT:
ret = OT_ERROR_INVALID_ARGS;
break;
case SPINEL_STATUS_UNIMPLEMENTED:
ret = OT_ERROR_NOT_IMPLEMENTED;
break;
case SPINEL_STATUS_INVALID_STATE:
ret = OT_ERROR_INVALID_STATE;
break;
case SPINEL_STATUS_NO_ACK:
ret = OT_ERROR_NO_ACK;
break;
case SPINEL_STATUS_CCA_FAILURE:
ret = OT_ERROR_CHANNEL_ACCESS_FAILURE;
break;
case SPINEL_STATUS_ALREADY:
ret = OT_ERROR_ALREADY;
break;
case SPINEL_STATUS_PROP_NOT_FOUND:
case SPINEL_STATUS_ITEM_NOT_FOUND:
ret = OT_ERROR_NOT_FOUND;
break;
default:
if (aError >= SPINEL_STATUS_STACK_NATIVE__BEGIN && aError <= SPINEL_STATUS_STACK_NATIVE__END)
{
ret = static_cast<otError>(aError - SPINEL_STATUS_STACK_NATIVE__BEGIN);
}
else
{
ret = OT_ERROR_FAILED;
}
break;
}
return ret;
}
static void LogIfFail(otInstance *aInstance, const char *aText, otError aError)
{
if (aError != OT_ERROR_NONE)
{
otLogWarnPlat(aInstance, "%s: %s", aText, otThreadErrorToString(aError));
}
OT_UNUSED_VARIABLE(aInstance);
OT_UNUSED_VARIABLE(aText);
OT_UNUSED_VARIABLE(aError);
}
#if OPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE
static int ForkPty(const char *aCommand, const char *aArguments)
{
int fd = -1;
int pid = -1;
{
struct termios tios;
memset(&tios, 0, sizeof(tios));
cfmakeraw(&tios);
tios.c_cflag = CS8 | HUPCL | CREAD | CLOCAL;
pid = forkpty(&fd, NULL, &tios, NULL);
VerifyOrExit(pid >= 0);
}
if (0 == pid)
{
const int kMaxCommand = 255;
char cmd[kMaxCommand];
int rval;
struct rlimit limit;
rval = getrlimit(RLIMIT_NOFILE, &limit);
rval = setenv("SHELL", SOCKET_UTILS_DEFAULT_SHELL, 0);
VerifyOrExit(rval == 0, perror("setenv failed"));
// Close all file descriptors larger than STDERR_FILENO.
for (rlim_t i = (STDERR_FILENO + 1); i < limit.rlim_cur; i++)
{
close(static_cast<int>(i));
}
rval = snprintf(cmd, sizeof(cmd), "exec %s %s", aCommand, aArguments);
VerifyOrExit(rval > 0 && static_cast<size_t>(rval) < sizeof(cmd),
otLogCritPlat(mInstance, "NCP file and configuration is too long!"));
execl(getenv("SHELL"), getenv("SHELL"), "-c", cmd, NULL);
perror("open pty failed");
exit(EXIT_FAILURE);
}
else
{
int rval = fcntl(fd, F_GETFL);
if (rval != -1)
{
rval = fcntl(fd, F_SETFL, rval | O_NONBLOCK);
}
if (rval == -1)
{
perror("set nonblock failed");
close(fd);
fd = -1;
}
}
exit:
return fd;
}
#endif // OPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE
static int OpenFile(const char *aFile, const char *aConfig)
{
int fd = -1;
int rval = 0;
fd = open(aFile, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd == -1)
{
perror("open uart failed");
ExitNow();
}
if (isatty(fd))
{
struct termios tios;
int speed = 115200;
int cstopb = 1;
char parity = 'N';
VerifyOrExit((rval = tcgetattr(fd, &tios)) == 0);
cfmakeraw(&tios);
tios.c_cflag = CS8 | HUPCL | CREAD | CLOCAL;
// example: 115200N1
sscanf(aConfig, "%u%c%d", &speed, &parity, &cstopb);
switch (parity)
{
case 'N':
break;
case 'E':
tios.c_cflag |= PARENB;
break;
case 'O':
tios.c_cflag |= (PARENB | PARODD);
break;
default:
// not supported
assert(false);
exit(EXIT_FAILURE);
break;
}
switch (cstopb)
{
case 1:
tios.c_cflag &= ~CSTOPB;
break;
case 2:
tios.c_cflag |= CSTOPB;
break;
default:
assert(false);
exit(EXIT_FAILURE);
break;
}
switch (speed)
{
case 9600:
speed = B9600;
break;
case 19200:
speed = B19200;
break;
case 38400:
speed = B38400;
break;
case 57600:
speed = B57600;
break;
case 115200:
speed = B115200;
break;
#ifdef B230400
case 230400:
speed = B230400;
break;
#endif
#ifdef B460800
case 460800:
speed = B460800;
break;
#endif
#ifdef B500000
case 500000:
speed = B500000;
break;
#endif
#ifdef B576000
case 576000:
speed = B576000;
break;
#endif
#ifdef B921600
case 921600:
speed = B921600;
break;
#endif
#ifdef B1000000
case 1000000:
speed = B1000000;
break;
#endif
#ifdef B1152000
case 1152000:
speed = B1152000;
break;
#endif
#ifdef B1500000
case 1500000:
speed = B1500000;
break;
#endif
#ifdef B2000000
case 2000000:
speed = B2000000;
break;
#endif
#ifdef B2500000
case 2500000:
speed = B2500000;
break;
#endif
#ifdef B3000000
case 3000000:
speed = B3000000;
break;
#endif
#ifdef B3500000
case 3500000:
speed = B3500000;
break;
#endif
#ifdef B4000000
case 4000000:
speed = B4000000;
break;
#endif
default:
assert(false);
exit(EXIT_FAILURE);
break;
}
VerifyOrExit((rval = cfsetspeed(&tios, speed)) == 0, perror("cfsetspeed"));
VerifyOrExit((rval = tcsetattr(fd, TCSANOW, &tios)) == 0, perror("tcsetattr"));
VerifyOrExit((rval = tcflush(fd, TCIOFLUSH)) == 0);
}
exit:
if (rval != 0)
{
exit(EXIT_FAILURE);
}
return fd;
}
class UartTxBuffer : public Hdlc::Encoder::BufferWriteIterator
{
public:
UartTxBuffer(void)
{
mWritePointer = mBuffer;
mRemainingLength = sizeof(mBuffer);
}
uint16_t GetLength(void) const { return static_cast<uint16_t>(mWritePointer - mBuffer); }
const uint8_t *GetBuffer(void) const { return mBuffer; }
private:
enum
{
kUartTxBufferSize = 512, // Uart tx buffer size.
};
uint8_t mBuffer[kUartTxBufferSize];
};
RadioSpinel::RadioSpinel(void)
: mCmdTidsInUse(0)
, mCmdNextTid(1)
, mTxRadioTid(0)
, mWaitingTid(0)
, mWaitingKey(SPINEL_PROP_LAST_STATUS)
, mHdlcDecoder(mHdlcBuffer, sizeof(mHdlcBuffer), HandleSpinelFrame, HandleHdlcError, this)
, mRxSensitivity(0)
, mTxState(kIdle)
, mSockFd(-1)
, mState(OT_RADIO_STATE_DISABLED)
, mIsAckRequested(false)
, mIsDecoding(false)
, mIsPromiscuous(false)
, mIsReady(false)
#if OPENTHREAD_ENABLE_DIAG
, mDiagMode(false)
, mDiagOutput(NULL)
, mDiagOutputMaxLen(0)
#endif
{
mVersion[0] = '\0';
}
void RadioSpinel::Init(const char *aRadioFile, const char *aRadioConfig)
{
otError error = OT_ERROR_NONE;
struct stat st;
// not allowed to initialize again.
assert(mSockFd == -1);
VerifyOrExit(stat(aRadioFile, &st) == 0, perror("stat ncp file failed"));
if (S_ISCHR(st.st_mode))
{
mSockFd = OpenFile(aRadioFile, aRadioConfig);
VerifyOrExit(mSockFd != -1, error = OT_ERROR_INVALID_ARGS);
}
#if OPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE
else if (S_ISREG(st.st_mode))
{
mSockFd = ForkPty(aRadioFile, aRadioConfig);
VerifyOrExit(mSockFd != -1, error = OT_ERROR_INVALID_ARGS);
}
#endif // OPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE
SuccessOrExit(error = SendReset());
SuccessOrExit(error = WaitResponse());
VerifyOrExit(mIsReady, error = OT_ERROR_FAILED);
SuccessOrExit(error = Get(SPINEL_PROP_NCP_VERSION, SPINEL_DATATYPE_UTF8_S, mVersion, sizeof(mVersion)));
SuccessOrExit(error = Get(SPINEL_PROP_HWADDR, SPINEL_DATATYPE_UINT64_S, &gNodeId));
gNodeId = ot::Encoding::BigEndian::HostSwap64(gNodeId);
{
unsigned int versionMajor;
unsigned int versionMinor;
SuccessOrExit(error = Get(SPINEL_PROP_PROTOCOL_VERSION,
(SPINEL_DATATYPE_UINT_PACKED_S SPINEL_DATATYPE_UINT_PACKED_S), &versionMajor,
&versionMinor));
if ((versionMajor != SPINEL_PROTOCOL_VERSION_THREAD_MAJOR) ||
(versionMinor != SPINEL_PROTOCOL_VERSION_THREAD_MINOR))
{
otLogCritPlat(mInstance, "Spinel version mismatch - PosixApp:%d.%d, RCP:%d.%d",
SPINEL_PROTOCOL_VERSION_THREAD_MAJOR, SPINEL_PROTOCOL_VERSION_THREAD_MINOR, versionMajor,
versionMinor);
ExitNow(error = OT_ERROR_FAILED);
}
}
{
const otRadioCaps kRequiredRadioCaps =
OT_RADIO_CAPS_ACK_TIMEOUT | OT_RADIO_CAPS_TRANSMIT_RETRIES | OT_RADIO_CAPS_CSMA_BACKOFF;
unsigned int caps;
SuccessOrExit(error = Get(SPINEL_PROP_RADIO_CAPS, SPINEL_DATATYPE_UINT_PACKED_S, &caps));
mRadioCaps = static_cast<otRadioCaps>(caps);
VerifyOrExit((mRadioCaps & kRequiredRadioCaps) == kRequiredRadioCaps, error = OT_ERROR_NOT_CAPABLE);
}
mRxRadioFrame.mPsdu = mRxPsdu;
mTxRadioFrame.mPsdu = mTxPsdu;
exit:
SuccessOrDie(error);
}
void RadioSpinel::Deinit(void)
{
// this function is only allowed after successfully initialized.
assert(mSockFd != -1);
VerifyOrExit(0 == close(mSockFd), perror("close NCP"));
VerifyOrExit(-1 != wait(NULL), perror("wait NCP"));
exit:
return;
}
void RadioSpinel::HandleSpinelFrame(const uint8_t *aBuffer, uint16_t aLength)
{
otError error = OT_ERROR_NONE;
uint8_t header;
spinel_ssize_t rval;
rval = spinel_datatype_unpack(aBuffer, aLength, "C", &header);
VerifyOrExit(rval > 0 && (header & SPINEL_HEADER_FLAG) == SPINEL_HEADER_FLAG && SPINEL_HEADER_GET_IID(header) == 0,
error = OT_ERROR_PARSE);
if (SPINEL_HEADER_GET_TID(header) == 0)
{
HandleNotification(aBuffer, aLength);
}
else
{
HandleResponse(aBuffer, aLength);
}
exit:
LogIfFail(mInstance, "Error handling hdlc frame", error);
}
void RadioSpinel::HandleHdlcError(void *aContext, otError aError, uint8_t *aBuffer, uint16_t aLength)
{
otLogWarnPlat(static_cast<RadioSpinel *>(aContext)->mInstance, "Error decoding hdlc frame: %s",
otThreadErrorToString(aError));
OT_UNUSED_VARIABLE(aContext);
OT_UNUSED_VARIABLE(aError);
OT_UNUSED_VARIABLE(aBuffer);
OT_UNUSED_VARIABLE(aLength);
}
void RadioSpinel::HandleNotification(const uint8_t *aBuffer, uint16_t aLength)
{
spinel_prop_key_t key;
spinel_size_t len = 0;
spinel_ssize_t unpacked;
uint8_t * data = NULL;
uint32_t cmd;
uint8_t header;
otError error = OT_ERROR_NONE;
unpacked = spinel_datatype_unpack(aBuffer, aLength, "CiiD", &header, &cmd, &key, &data, &len);
VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
VerifyOrExit(SPINEL_HEADER_GET_TID(header) == 0, error = OT_ERROR_PARSE);
switch (cmd)
{
case SPINEL_CMD_PROP_VALUE_IS:
// Some spinel properties cannot be handled during `WaitResponse()`, we must cache these events.
// `mWaitingTid` is released immediately after received the response. And `mWaitingKey` is be set
// to `SPINEL_PROP_LAST_STATUS` at the end of `WaitResponse()`.
if (!IsSafeToHandleNow(key))
{
assert(aLength <= 255);
error = mFrameQueue.Push(aBuffer, static_cast<uint8_t>(aLength));
ExitNow();
}
HandleValueIs(key, data, static_cast<uint16_t>(len));
break;
case SPINEL_CMD_PROP_VALUE_INSERTED:
case SPINEL_CMD_PROP_VALUE_REMOVED:
otLogInfoPlat(mInstance, "Ignored command %d", cmd);
break;
default:
ExitNow(error = OT_ERROR_PARSE);
}
exit:
LogIfFail(mInstance, "Error processing notification", error);
}
void RadioSpinel::HandleResponse(const uint8_t *aBuffer, uint16_t aLength)
{
spinel_prop_key_t key;
uint8_t * data = NULL;
spinel_size_t len = 0;
uint8_t header = 0;
uint32_t cmd = 0;
spinel_ssize_t rval = 0;
otError error = OT_ERROR_NONE;
rval = spinel_datatype_unpack(aBuffer, aLength, "CiiD", &header, &cmd, &key, &data, &len);
VerifyOrExit(rval > 0 && cmd >= SPINEL_CMD_PROP_VALUE_IS && cmd <= SPINEL_CMD_PROP_VALUE_REMOVED,
error = OT_ERROR_PARSE);
if (mWaitingTid == SPINEL_HEADER_GET_TID(header))
{
HandleWaitingResponse(cmd, key, data, static_cast<uint16_t>(len));
FreeTid(mWaitingTid);
mWaitingTid = 0;
}
else if (mTxRadioTid == SPINEL_HEADER_GET_TID(header))
{
HandleTransmitDone(cmd, key, data, static_cast<uint16_t>(len));
FreeTid(mTxRadioTid);
mTxRadioTid = 0;
}
else
{
otLogWarnPlat(mInstance, "Unexpected Spinel transaction message: %u", SPINEL_HEADER_GET_TID(header));
error = OT_ERROR_DROP;
}
exit:
LogIfFail(mInstance, "Error processing response", error);
}
void RadioSpinel::HandleWaitingResponse(uint32_t aCommand,
spinel_prop_key_t aKey,
const uint8_t * aBuffer,
uint16_t aLength)
{
if (aKey == SPINEL_PROP_LAST_STATUS)
{
spinel_status_t status;
spinel_ssize_t unpacked = spinel_datatype_unpack(aBuffer, aLength, "i", &status);
VerifyOrExit(unpacked > 0, mError = OT_ERROR_PARSE);
mError = SpinelStatusToOtError(status);
}
#if OPENTHREAD_ENABLE_DIAG
else if (aKey == SPINEL_PROP_NEST_STREAM_MFG)
{
spinel_ssize_t unpacked;
VerifyOrExit(mDiagOutput != NULL);
unpacked =
spinel_datatype_unpack_in_place(aBuffer, aLength, SPINEL_DATATYPE_UTF8_S, mDiagOutput, &mDiagOutputMaxLen);
VerifyOrExit(unpacked > 0, mError = OT_ERROR_PARSE);
}
#endif
else if (aKey == mWaitingKey)
{
if (mPropertyFormat)
{
spinel_ssize_t unpacked =
spinel_datatype_vunpack_in_place(aBuffer, aLength, mPropertyFormat, mPropertyArgs);
VerifyOrExit(unpacked > 0, mError = OT_ERROR_PARSE);
mError = OT_ERROR_NONE;
}
else
{
if (aCommand == mExpectedCommand)
{
mError = OT_ERROR_NONE;
}
else
{
mError = OT_ERROR_DROP;
}
}
}
else
{
mError = OT_ERROR_DROP;
}
exit:
LogIfFail(mInstance, "Error processing result", mError);
}
void RadioSpinel::HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, uint16_t aLength)
{
otError error = OT_ERROR_NONE;
if (aKey == SPINEL_PROP_STREAM_RAW)
{
SuccessOrExit(error = ParseRadioFrame(mRxRadioFrame, aBuffer, aLength));
RadioReceive();
}
else if (aKey == SPINEL_PROP_LAST_STATUS)
{
spinel_status_t status = SPINEL_STATUS_OK;
spinel_ssize_t unpacked;
unpacked = spinel_datatype_unpack(aBuffer, aLength, "i", &status);
VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
if (status >= SPINEL_STATUS_RESET__BEGIN && status <= SPINEL_STATUS_RESET__END)
{
otLogCritPlat(mInstance, "RCP reset: %s", spinel_status_to_cstr(status));
mIsReady = true;
// If RCP crashes/resets while radio was enabled, posix app exits.
VerifyOrDie(!IsEnabled());
}
else
{
otLogInfoPlat(mInstance, "RCP last status: %s", spinel_status_to_cstr(status));
}
}
else if (aKey == SPINEL_PROP_MAC_ENERGY_SCAN_RESULT)
{
uint8_t scanChannel;
int8_t maxRssi;
spinel_ssize_t unpacked;
unpacked = spinel_datatype_unpack(aBuffer, aLength, "Cc", &scanChannel, &maxRssi);
VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
otPlatRadioEnergyScanDone(mInstance, maxRssi);
}
else if (aKey == SPINEL_PROP_STREAM_DEBUG)
{
char logStream[OPENTHREAD_CONFIG_NCP_SPINEL_LOG_MAX_SIZE + 1];
unsigned int len = sizeof(logStream);
spinel_ssize_t unpacked;
unpacked = spinel_datatype_unpack_in_place(aBuffer, aLength, SPINEL_DATATYPE_DATA_S, logStream, &len);
assert(len < sizeof(logStream));
VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
logStream[len] = '\0';
otLogDebgPlat(mInstance, "RCP DEBUG INFO: %s", logStream);
}
exit:
LogIfFail(mInstance, "Failed to handle ValueIs", error);
}
otError RadioSpinel::ParseRadioFrame(otRadioFrame &aFrame, const uint8_t *aBuffer, uint16_t aLength)
{
otError error = OT_ERROR_NONE;
uint16_t packetLength = 0;
spinel_ssize_t unpacked;
uint16_t flags = 0;
int8_t noiseFloor = -128;
spinel_size_t size = OT_RADIO_FRAME_MAX_SIZE;
unpacked = spinel_datatype_unpack(aBuffer, aLength, SPINEL_DATATYPE_UINT16_S, &packetLength);
VerifyOrExit(unpacked > 0 && packetLength <= OT_RADIO_FRAME_MAX_SIZE, error = OT_ERROR_PARSE);
aFrame.mLength = static_cast<uint8_t>(packetLength);
unpacked = spinel_datatype_unpack_in_place(aBuffer, aLength,
SPINEL_DATATYPE_DATA_WLEN_S SPINEL_DATATYPE_INT8_S SPINEL_DATATYPE_INT8_S
SPINEL_DATATYPE_UINT16_S SPINEL_DATATYPE_STRUCT_S( // PHY-data
SPINEL_DATATYPE_UINT8_S // 802.15.4 channel
SPINEL_DATATYPE_UINT8_S // 802.15.4 LQI
),
aFrame.mPsdu, &size, &aFrame.mInfo.mRxInfo.mRssi, &noiseFloor, &flags,
&aFrame.mChannel, &aFrame.mInfo.mRxInfo.mLqi);
VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
exit:
LogIfFail(mInstance, "Handle radio frame failed", error);
return error;
}
void RadioSpinel::DecodeHdlc(const uint8_t *aData, uint16_t aLength)
{
mIsDecoding = true;
mHdlcDecoder.Decode(aData, aLength);
mIsDecoding = false;
}
void RadioSpinel::ReadAll(void)
{
uint8_t buf[kMaxSpinelFrame];
ssize_t rval = read(mSockFd, buf, sizeof(buf));
if (rval < 0)
{
perror("read spinel");
if (errno != EAGAIN)
{
abort();
}
}
if (rval > 0)
{
DecodeHdlc(buf, static_cast<uint16_t>(rval));
}
}
void RadioSpinel::ProcessFrameQueue(void)
{
uint8_t length;
uint8_t buffer[kMaxSpinelFrame];
const uint8_t *frame;
while ((frame = mFrameQueue.Shift(buffer, length)) != NULL)
{
HandleNotification(frame, length);
}
}
void RadioSpinel::RadioReceive(void)
{
otError error = OT_ERROR_NONE;
otPanId dstpan;
otShortAddress shortAddress;
otExtAddress extAddress;
VerifyOrExit(mIsPromiscuous == false, error = OT_ERROR_NONE);
VerifyOrExit((mState == OT_RADIO_STATE_RECEIVE || mState == OT_RADIO_STATE_TRANSMIT), error = OT_ERROR_DROP);
switch (mRxRadioFrame.mPsdu[1] & IEEE802154_DST_ADDR_MASK)
{
case IEEE802154_DST_ADDR_NONE:
break;
case IEEE802154_DST_ADDR_SHORT:
dstpan = getDstPan(mRxRadioFrame.mPsdu);
shortAddress = getShortAddress(mRxRadioFrame.mPsdu);
VerifyOrExit((dstpan == IEEE802154_BROADCAST || dstpan == mPanid) &&
(shortAddress == IEEE802154_BROADCAST || shortAddress == mShortAddress),
error = OT_ERROR_ABORT);
break;
case IEEE802154_DST_ADDR_EXT:
dstpan = getDstPan(mRxRadioFrame.mPsdu);
getExtAddress(mRxRadioFrame.mPsdu, &extAddress);
VerifyOrExit((dstpan == IEEE802154_BROADCAST || dstpan == mPanid) &&
memcmp(&extAddress, mExtendedAddress.m8, sizeof(extAddress)) == 0,
error = OT_ERROR_ABORT);
break;
default:
error = OT_ERROR_ABORT;
goto exit;
}
exit:
#if OPENTHREAD_ENABLE_DIAG
if (otPlatDiagModeGet())
{
otPlatDiagRadioReceiveDone(mInstance, error == OT_ERROR_NONE ? &mRxRadioFrame : NULL, error);
}
else
#endif
{
otPlatRadioReceiveDone(mInstance, error == OT_ERROR_NONE ? &mRxRadioFrame : NULL, error);
}
}
void RadioSpinel::UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout)
{
if ((mState != OT_RADIO_STATE_TRANSMIT || mTxState == kSent))
{
FD_SET(mSockFd, &aReadFdSet);
if (aMaxFd < mSockFd)
{
aMaxFd = mSockFd;
}
}
if (mState == OT_RADIO_STATE_TRANSMIT && mTxState == kIdle)
{
FD_SET(mSockFd, &aWriteFdSet);
if (aMaxFd < mSockFd)
{
aMaxFd = mSockFd;
}
}
if (!mFrameQueue.IsEmpty() || (mState == OT_RADIO_STATE_TRANSMIT && mTxState == kDone))
{
aTimeout.tv_sec = 0;
aTimeout.tv_usec = 0;
}
}
void RadioSpinel::Process(const fd_set &aReadFdSet, const fd_set &aWriteFdSet)
{
if (FD_ISSET(mSockFd, &aReadFdSet) || !mFrameQueue.IsEmpty())
{
// Handle frames received during WaitResponse()
ProcessFrameQueue();
if (FD_ISSET(mSockFd, &aReadFdSet))
{
ReadAll();
ProcessFrameQueue();
}
}
if (mState == OT_RADIO_STATE_TRANSMIT && mTxState == kDone)
{
mState = OT_RADIO_STATE_RECEIVE;
#if OPENTHREAD_ENABLE_DIAG
if (otPlatDiagModeGet())
{
otPlatDiagRadioTransmitDone(mInstance, mTransmitFrame, mTxError);
}
else
#endif
{
otPlatRadioTxDone(mInstance, mTransmitFrame, (mIsAckRequested ? &mRxRadioFrame : NULL), mTxError);
}
mTxState = kIdle;
}
if (FD_ISSET(mSockFd, &aWriteFdSet))
{
if (mState == OT_RADIO_STATE_TRANSMIT && mTxState == kIdle)
{
RadioTransmit();
}
}
}
otError RadioSpinel::SetPromiscuous(bool aEnable)
{
otError error;
uint8_t mode = (aEnable ? SPINEL_MAC_PROMISCUOUS_MODE_NETWORK : SPINEL_MAC_PROMISCUOUS_MODE_OFF);
SuccessOrExit(error = Set(SPINEL_PROP_MAC_PROMISCUOUS_MODE, SPINEL_DATATYPE_UINT8_S, mode));
mIsPromiscuous = aEnable;
exit:
return error;
}
otError RadioSpinel::SetShortAddress(uint16_t aAddress)
{
otError error;
SuccessOrExit(error = sRadioSpinel.Set(SPINEL_PROP_MAC_15_4_SADDR, SPINEL_DATATYPE_UINT16_S, aAddress));
mShortAddress = aAddress;
exit:
return error;
}
otError RadioSpinel::GetIeeeEui64(uint8_t *aIeeeEui64)
{
return Get(SPINEL_PROP_HWADDR, SPINEL_DATATYPE_EUI64_S, aIeeeEui64);
}
otError RadioSpinel::SetExtendedAddress(const otExtAddress &aExtAddress)
{
otError error;
SuccessOrExit(error = Set(SPINEL_PROP_MAC_15_4_LADDR, SPINEL_DATATYPE_EUI64_S, aExtAddress.m8));
mExtendedAddress = aExtAddress;
exit:
return error;
}
otError RadioSpinel::SetPanId(uint16_t aPanId)
{
otError error;
SuccessOrExit(error = Set(SPINEL_PROP_MAC_15_4_PANID, SPINEL_DATATYPE_UINT16_S, aPanId));
mPanid = aPanId;
exit:
return error;
}
otError RadioSpinel::EnableSrcMatch(bool aEnable)
{
return Set(SPINEL_PROP_MAC_SRC_MATCH_ENABLED, SPINEL_DATATYPE_BOOL_S, aEnable);
}
otError RadioSpinel::AddSrcMatchShortEntry(const uint16_t aShortAddress)
{
return Insert(SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES, SPINEL_DATATYPE_UINT16_S, aShortAddress);
}
otError RadioSpinel::AddSrcMatchExtEntry(const otExtAddress &aExtAddress)
{
return Insert(SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES, SPINEL_DATATYPE_EUI64_S, aExtAddress.m8);
}
otError RadioSpinel::ClearSrcMatchShortEntry(const uint16_t aShortAddress)
{
return Remove(SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES, SPINEL_DATATYPE_UINT16_S, aShortAddress);
}
otError RadioSpinel::ClearSrcMatchExtEntry(const otExtAddress &aExtAddress)
{
return Remove(SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES, SPINEL_DATATYPE_EUI64_S, aExtAddress.m8);
}
otError RadioSpinel::ClearSrcMatchShortEntries(void)
{
return Set(SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES, NULL);
}
otError RadioSpinel::ClearSrcMatchExtEntries(void)
{
return Set(SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES, NULL);
}
otError RadioSpinel::GetTransmitPower(int8_t &aPower)
{
otError error = Get(SPINEL_PROP_PHY_TX_POWER, SPINEL_DATATYPE_INT8_S, &aPower);
LogIfFail(mInstance, "Get transmit power failed", error);
return error;
}
int8_t RadioSpinel::GetRssi(void)
{
int8_t rssi = OT_RADIO_RSSI_INVALID;
otError error = Get(SPINEL_PROP_PHY_RSSI, SPINEL_DATATYPE_INT8_S, &rssi);
LogIfFail(mInstance, "Get RSSI failed", error);
return rssi;
}
otError RadioSpinel::SetTransmitPower(int8_t aPower)
{
otError error = Set(SPINEL_PROP_PHY_TX_POWER, SPINEL_DATATYPE_INT8_S, aPower);
LogIfFail(mInstance, "Set transmit power failed", error);
return error;
}
otError RadioSpinel::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration)
{
otError error;
VerifyOrExit(mRadioCaps & OT_RADIO_CAPS_ENERGY_SCAN, error = OT_ERROR_NOT_CAPABLE);
SuccessOrExit(error = Set(SPINEL_PROP_MAC_SCAN_MASK, SPINEL_DATATYPE_DATA_S, &aScanChannel, sizeof(uint8_t)));
SuccessOrExit(error = Set(SPINEL_PROP_MAC_SCAN_PERIOD, SPINEL_DATATYPE_UINT16_S, aScanDuration));
SuccessOrExit(error = Set(SPINEL_PROP_MAC_SCAN_STATE, SPINEL_DATATYPE_UINT8_S, SPINEL_SCAN_STATE_ENERGY));
exit:
return error;
}
otError RadioSpinel::Get(spinel_prop_key_t aKey, const char *aFormat, ...)
{
otError error;
assert(mWaitingTid == 0);
mPropertyFormat = aFormat;
va_start(mPropertyArgs, aFormat);
error = RequestV(true, SPINEL_CMD_PROP_VALUE_GET, aKey, NULL, mPropertyArgs);
va_end(mPropertyArgs);
mPropertyFormat = NULL;
return error;
}
otError RadioSpinel::Set(spinel_prop_key_t aKey, const char *aFormat, ...)
{
otError error;
assert(mWaitingTid == 0);
mExpectedCommand = SPINEL_CMD_PROP_VALUE_IS;
va_start(mPropertyArgs, aFormat);
error = RequestV(true, SPINEL_CMD_PROP_VALUE_SET, aKey, aFormat, mPropertyArgs);
va_end(mPropertyArgs);
mExpectedCommand = SPINEL_CMD_NOOP;
return error;
}
otError RadioSpinel::Insert(spinel_prop_key_t aKey, const char *aFormat, ...)
{
otError error;
assert(mWaitingTid == 0);
mExpectedCommand = SPINEL_CMD_PROP_VALUE_INSERTED;
va_start(mPropertyArgs, aFormat);
error = RequestV(true, SPINEL_CMD_PROP_VALUE_INSERT, aKey, aFormat, mPropertyArgs);
va_end(mPropertyArgs);
mExpectedCommand = SPINEL_CMD_NOOP;
return error;
}
otError RadioSpinel::Remove(spinel_prop_key_t aKey, const char *aFormat, ...)
{
otError error;
assert(mWaitingTid == 0);
mExpectedCommand = SPINEL_CMD_PROP_VALUE_REMOVED;
va_start(mPropertyArgs, aFormat);
error = RequestV(true, SPINEL_CMD_PROP_VALUE_REMOVE, aKey, aFormat, mPropertyArgs);
va_end(mPropertyArgs);
mExpectedCommand = SPINEL_CMD_NOOP;
return error;
}
otError RadioSpinel::WaitResponse(void)
{
struct timeval end;
struct timeval now;
struct timeval timeout = {kMaxWaitTime / 1000, (kMaxWaitTime % 1000) * 1000};
otSysGetTime(&now);
timeradd(&now, &timeout, &end);
do
{
#if OPENTHREAD_POSIX_VIRTUAL_TIME
struct Event event;
otSimSendSleepEvent(&timeout);
otSimReceiveEvent(&event);
switch (event.mEvent)
{
case OT_SIM_EVENT_RADIO_SPINEL_WRITE:
DecodeHdlc(event.mData, event.mDataLength);
break;
case OT_SIM_EVENT_ALARM_FIRED:
FreeTid(mWaitingTid);
mWaitingTid = 0;
ExitNow(mError = OT_ERROR_RESPONSE_TIMEOUT);
break;
default:
assert(false);
break;
}
#else // OPENTHREAD_POSIX_VIRTUAL_TIME
fd_set read_fds;
fd_set error_fds;
int rval;
FD_ZERO(&read_fds);
FD_ZERO(&error_fds);
FD_SET(mSockFd, &read_fds);
FD_SET(mSockFd, &error_fds);
rval = select(mSockFd + 1, &read_fds, NULL, &error_fds, &timeout);
if (rval > 0)
{
if (FD_ISSET(mSockFd, &read_fds))
{
ReadAll();
}
else if (FD_ISSET(mSockFd, &error_fds))
{
fprintf(stderr, "NCP error\r\n");
exit(EXIT_FAILURE);
}
else
{
assert(false);
exit(EXIT_FAILURE);
}
}
else if (rval == 0)
{
FreeTid(mWaitingTid);
mWaitingTid = 0;
ExitNow(mError = OT_ERROR_RESPONSE_TIMEOUT);
}
else if (errno != EINTR)
{
perror("wait response");
exit(EXIT_FAILURE);
}
#endif // OPENTHREAD_POSIX_VIRTUAL_TIME
otSysGetTime(&now);
if (timercmp(&end, &now, >))
{
timersub(&end, &now, &timeout);
}
else
{
mWaitingTid = 0;
mError = OT_ERROR_RESPONSE_TIMEOUT;
}
} while (mWaitingTid || !mIsReady);
exit:
LogIfFail(mInstance, "Error waiting response", mError);
// This indicates end of waiting repsonse.
mWaitingKey = SPINEL_PROP_LAST_STATUS;
return mError;
}
spinel_tid_t RadioSpinel::GetNextTid(void)
{
spinel_tid_t tid = 0;
if (((1 << mCmdNextTid) & mCmdTidsInUse) == 0)
{
tid = mCmdNextTid;
mCmdNextTid = SPINEL_GET_NEXT_TID(mCmdNextTid);
mCmdTidsInUse |= (1 << tid);
}
return tid;
}
/**
* This method delievers the radio frame to transceiver.
*
* otPlatRadioTxStarted() is triggered immediately for now, which may be earlier than real started time.
*
*/
void RadioSpinel::RadioTransmit(void)
{
otError error;
assert(mTransmitFrame != NULL);
otPlatRadioTxStarted(mInstance, mTransmitFrame);
assert(mTxState == kIdle);
mIsAckRequested = isAckRequested(mTransmitFrame->mPsdu) && !mIsPromiscuous;
error = Request(true, SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_STREAM_RAW,
SPINEL_DATATYPE_DATA_WLEN_S SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_INT8_S, mTransmitFrame->mPsdu,
mTransmitFrame->mLength, mTransmitFrame->mChannel, mTransmitFrame->mInfo.mRxInfo.mRssi);
if (error == OT_ERROR_NONE)
{
mTxState = kSent;
}
else
{
mState = OT_RADIO_STATE_RECEIVE;
#if OPENTHREAD_ENABLE_DIAG
if (otPlatDiagModeGet())
{
otPlatDiagRadioTransmitDone(mInstance, mTransmitFrame, error);
}
else
#endif
{
otPlatRadioTxDone(mInstance, mTransmitFrame, NULL, error);
}
mTxState = kIdle;
}
}
otError RadioSpinel::WriteAll(const uint8_t *aBuffer, uint16_t aLength)
{
otError error = OT_ERROR_NONE;
#if OPENTHREAD_POSIX_VIRTUAL_TIME
otSimSendRadioSpinelWriteEvent(aBuffer, aLength);
#else
while (aLength)
{
ssize_t rval = write(mSockFd, aBuffer, aLength);
if (rval > 0)
{
aLength -= static_cast<uint16_t>(rval);
aBuffer += static_cast<uint16_t>(rval);
}
else if (rval < 0)
{
perror("send command failed");
ExitNow(error = OT_ERROR_FAILED);
}
else
{
ExitNow(error = OT_ERROR_FAILED);
}
}
exit:
#endif
return error;
}
otError RadioSpinel::SendReset(void)
{
otError error = OT_ERROR_NONE;
uint8_t buffer[kMaxSpinelFrame];
UartTxBuffer txBuffer;
spinel_ssize_t packed;
// Pack the header, command and key
packed =
spinel_datatype_pack(buffer, sizeof(buffer), "Ci", SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_RESET);
VerifyOrExit(packed > 0 && static_cast<size_t>(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS);
mHdlcEncoder.Init(txBuffer);
for (spinel_ssize_t i = 0; i < packed; i++)
{
error = mHdlcEncoder.Encode(buffer[i], txBuffer);
VerifyOrExit(error == OT_ERROR_NONE);
}
mHdlcEncoder.Finalize(txBuffer);
error = WriteAll(txBuffer.GetBuffer(), txBuffer.GetLength());
VerifyOrExit(error == OT_ERROR_NONE);
sleep(0);
exit:
return error;
}
otError RadioSpinel::SendCommand(uint32_t aCommand,
spinel_prop_key_t aKey,
spinel_tid_t tid,
const char * aFormat,
va_list args)
{
otError error = OT_ERROR_NONE;
uint8_t buffer[kMaxSpinelFrame];
UartTxBuffer txBuffer;
spinel_ssize_t packed;
uint16_t offset;
// Pack the header, command and key
packed = spinel_datatype_pack(buffer, sizeof(buffer), "Cii", SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0 | tid,
aCommand, aKey);
VerifyOrExit(packed > 0 && static_cast<size_t>(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS);
offset = static_cast<uint16_t>(packed);
// Pack the data (if any)
if (aFormat)
{
packed = spinel_datatype_vpack(buffer + offset, sizeof(buffer) - offset, aFormat, args);
VerifyOrExit(packed > 0 && static_cast<size_t>(packed + offset) <= sizeof(buffer), error = OT_ERROR_NO_BUFS);
offset += static_cast<uint16_t>(packed);
}
mHdlcEncoder.Init(txBuffer);
for (uint8_t i = 0; i < offset; i++)
{
error = mHdlcEncoder.Encode(buffer[i], txBuffer);
VerifyOrExit(error == OT_ERROR_NONE);
}
mHdlcEncoder.Finalize(txBuffer);
error = WriteAll(txBuffer.GetBuffer(), txBuffer.GetLength());
exit:
return error;
}
otError RadioSpinel::RequestV(bool aWait, uint32_t command, spinel_prop_key_t aKey, const char *aFormat, va_list aArgs)
{
otError error = OT_ERROR_NONE;
spinel_tid_t tid = (aWait ? GetNextTid() : 0);
VerifyOrExit(!aWait || tid > 0, error = OT_ERROR_BUSY);
error = SendCommand(command, aKey, tid, aFormat, aArgs);
VerifyOrExit(error == OT_ERROR_NONE);
if (aKey == SPINEL_PROP_STREAM_RAW)
{
// not allowed to send another frame before the last frame is done.
assert(mTxRadioTid == 0);
VerifyOrExit(mTxRadioTid == 0, error = OT_ERROR_BUSY);
mTxRadioTid = tid;
}
else if (aWait)
{
mWaitingKey = aKey;
mWaitingTid = tid;
error = WaitResponse();
}
exit:
return error;
}
otError RadioSpinel::Request(bool aWait, uint32_t aCommand, spinel_prop_key_t aKey, const char *aFormat, ...)
{
va_list args;
va_start(args, aFormat);
otError status = RequestV(aWait, aCommand, aKey, aFormat, args);
va_end(args);
return status;
}
void RadioSpinel::HandleTransmitDone(uint32_t aCommand,
spinel_prop_key_t aKey,
const uint8_t * aBuffer,
uint16_t aLength)
{
otError error = OT_ERROR_NONE;
spinel_status_t status = SPINEL_STATUS_OK;
spinel_ssize_t unpacked;
VerifyOrExit(aCommand == SPINEL_CMD_PROP_VALUE_IS && aKey == SPINEL_PROP_LAST_STATUS, error = OT_ERROR_FAILED);
unpacked = spinel_datatype_unpack(aBuffer, aLength, SPINEL_DATATYPE_UINT_PACKED_S, &status);
VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
aBuffer += unpacked;
aLength -= static_cast<uint16_t>(unpacked);
if (status == SPINEL_STATUS_OK)
{
bool framePending = false;
unpacked = spinel_datatype_unpack(aBuffer, aLength, SPINEL_DATATYPE_BOOL_S, &framePending);
OT_UNUSED_VARIABLE(framePending);
VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE);
aBuffer += unpacked;
aLength -= static_cast<spinel_size_t>(unpacked);
if (mIsAckRequested)
{
VerifyOrExit(aLength > 0, error = OT_ERROR_FAILED);
SuccessOrExit(error = ParseRadioFrame(mRxRadioFrame, aBuffer, aLength));
}
}
else
{
otLogWarnPlat(mInstance, "Spinel status: %d.", status);
error = SpinelStatusToOtError(status);
}
exit:
mTxState = kDone;
mTxError = error;
LogIfFail(mInstance, "Handle transmit done failed", error);
}
otError RadioSpinel::Transmit(otRadioFrame &aFrame)
{
otError error = OT_ERROR_INVALID_STATE;
VerifyOrExit(mState == OT_RADIO_STATE_RECEIVE);
mState = OT_RADIO_STATE_TRANSMIT;
error = OT_ERROR_NONE;
mTransmitFrame = &aFrame;
exit:
return error;
}
otError RadioSpinel::Receive(uint8_t aChannel)
{
otError error = OT_ERROR_NONE;
VerifyOrExit(mState != OT_RADIO_STATE_DISABLED, error = OT_ERROR_INVALID_STATE);
if (mChannel != aChannel)
{
error = Set(SPINEL_PROP_PHY_CHAN, SPINEL_DATATYPE_UINT8_S, aChannel);
VerifyOrExit(error == OT_ERROR_NONE);
mChannel = aChannel;
}
if (mState == OT_RADIO_STATE_SLEEP)
{
error = Set(SPINEL_PROP_MAC_RAW_STREAM_ENABLED, SPINEL_DATATYPE_BOOL_S, true);
VerifyOrExit(error == OT_ERROR_NONE);
}
if (mTxRadioTid != 0)
{
FreeTid(mTxRadioTid);
mTxRadioTid = 0;
}
mTxState = kIdle;
mState = OT_RADIO_STATE_RECEIVE;
exit:
assert(error == OT_ERROR_NONE);
return error;
}
otError RadioSpinel::Sleep(void)
{
otError error = OT_ERROR_NONE;
switch (mState)
{
case OT_RADIO_STATE_RECEIVE:
error = sRadioSpinel.Set(SPINEL_PROP_MAC_RAW_STREAM_ENABLED, SPINEL_DATATYPE_BOOL_S, false);
VerifyOrExit(error == OT_ERROR_NONE);
mState = OT_RADIO_STATE_SLEEP;
break;
case OT_RADIO_STATE_SLEEP:
break;
default:
error = OT_ERROR_INVALID_STATE;
break;
}
exit:
return error;
}
otError RadioSpinel::Enable(otInstance *aInstance)
{
otError error = OT_ERROR_NONE;
if (!otPlatRadioIsEnabled(mInstance))
{
mInstance = aInstance;
error = Set(SPINEL_PROP_PHY_ENABLED, SPINEL_DATATYPE_BOOL_S, true);
VerifyOrExit(error == OT_ERROR_NONE);
error = Get(SPINEL_PROP_PHY_RX_SENSITIVITY, SPINEL_DATATYPE_INT8_S, &mRxSensitivity);
VerifyOrExit(error == OT_ERROR_NONE);
mState = OT_RADIO_STATE_SLEEP;
}
exit:
assert(error == OT_ERROR_NONE);
return error;
}
otError RadioSpinel::Disable(void)
{
otError error = OT_ERROR_NONE;
if (otPlatRadioIsEnabled(mInstance))
{
mInstance = NULL;
error = sRadioSpinel.Set(SPINEL_PROP_PHY_ENABLED, SPINEL_DATATYPE_BOOL_S, false);
VerifyOrExit(error == OT_ERROR_NONE);
mState = OT_RADIO_STATE_DISABLED;
}
exit:
return error;
}
#if OPENTHREAD_ENABLE_DIAG
otError RadioSpinel::PlatDiagProcess(const char *aString, char *aOutput, size_t aOutputMaxLen)
{
otError error;
mDiagOutput = aOutput;
mDiagOutputMaxLen = aOutputMaxLen;
error = Set(SPINEL_PROP_NEST_STREAM_MFG, SPINEL_DATATYPE_UTF8_S, aString);
mDiagOutput = NULL;
mDiagOutputMaxLen = 0;
return error;
}
#endif
} // namespace ot
void otPlatRadioGetIeeeEui64(otInstance *aInstance, uint8_t *aIeeeEui64)
{
SuccessOrDie(sRadioSpinel.GetIeeeEui64(aIeeeEui64));
OT_UNUSED_VARIABLE(aInstance);
}
void otPlatRadioSetPanId(otInstance *aInstance, uint16_t panid)
{
SuccessOrDie(sRadioSpinel.SetPanId(panid));
OT_UNUSED_VARIABLE(aInstance);
}
void otPlatRadioSetExtendedAddress(otInstance *aInstance, const otExtAddress *aAddress)
{
otExtAddress addr;
for (size_t i = 0; i < sizeof(addr); i++)
{
addr.m8[i] = aAddress->m8[sizeof(addr) - 1 - i];
}
SuccessOrDie(sRadioSpinel.SetExtendedAddress(addr));
OT_UNUSED_VARIABLE(aInstance);
}
void otPlatRadioSetShortAddress(otInstance *aInstance, uint16_t aAddress)
{
SuccessOrDie(sRadioSpinel.SetShortAddress(aAddress));
OT_UNUSED_VARIABLE(aInstance);
}
void otPlatRadioSetPromiscuous(otInstance *aInstance, bool aEnable)
{
SuccessOrDie(sRadioSpinel.SetPromiscuous(aEnable));
OT_UNUSED_VARIABLE(aInstance);
}
void platformRadioInit(const char *aRadioFile, const char *aRadioConfig)
{
sRadioSpinel.Init(aRadioFile, aRadioConfig);
}
void platformRadioDeinit(void)
{
sRadioSpinel.Deinit();
}
bool otPlatRadioIsEnabled(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.IsEnabled();
}
otError otPlatRadioEnable(otInstance *aInstance)
{
return sRadioSpinel.Enable(aInstance);
}
otError otPlatRadioDisable(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.Disable();
}
otError otPlatRadioSleep(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.Sleep();
}
otError otPlatRadioReceive(otInstance *aInstance, uint8_t aChannel)
{
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.Receive(aChannel);
}
otError otPlatRadioTransmit(otInstance *aInstance, otRadioFrame *aFrame)
{
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.Transmit(*aFrame);
}
otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return &sRadioSpinel.GetTransmitFrame();
}
int8_t otPlatRadioGetRssi(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.GetRssi();
}
otRadioCaps otPlatRadioGetCaps(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.GetRadioCaps();
}
const char *otPlatRadioGetVersionString(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.GetVersion();
}
bool otPlatRadioGetPromiscuous(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.IsPromiscuous();
}
void platformRadioUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, int *aMaxFd, struct timeval *aTimeout)
{
sRadioSpinel.UpdateFdSet(*aReadFdSet, *aWriteFdSet, *aMaxFd, *aTimeout);
}
void platformRadioProcess(otInstance *aInstance, fd_set *aReadFdSet, fd_set *aWriteFdSet)
{
sRadioSpinel.Process(*aReadFdSet, *aWriteFdSet);
OT_UNUSED_VARIABLE(aInstance);
}
void otPlatRadioEnableSrcMatch(otInstance *aInstance, bool aEnable)
{
SuccessOrDie(sRadioSpinel.EnableSrcMatch(aEnable));
OT_UNUSED_VARIABLE(aInstance);
}
otError otPlatRadioAddSrcMatchShortEntry(otInstance *aInstance, const uint16_t aShortAddress)
{
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.AddSrcMatchShortEntry(aShortAddress);
}
otError otPlatRadioAddSrcMatchExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress)
{
otExtAddress addr;
for (size_t i = 0; i < sizeof(addr); i++)
{
addr.m8[i] = aExtAddress->m8[sizeof(addr) - 1 - i];
}
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.AddSrcMatchExtEntry(addr);
}
otError otPlatRadioClearSrcMatchShortEntry(otInstance *aInstance, const uint16_t aShortAddress)
{
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.ClearSrcMatchShortEntry(aShortAddress);
}
otError otPlatRadioClearSrcMatchExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress)
{
otExtAddress addr;
for (size_t i = 0; i < sizeof(addr); i++)
{
addr.m8[i] = aExtAddress->m8[sizeof(addr) - 1 - i];
}
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.ClearSrcMatchExtEntry(addr);
}
void otPlatRadioClearSrcMatchShortEntries(otInstance *aInstance)
{
SuccessOrDie(sRadioSpinel.ClearSrcMatchShortEntries());
OT_UNUSED_VARIABLE(aInstance);
}
void otPlatRadioClearSrcMatchExtEntries(otInstance *aInstance)
{
SuccessOrDie(sRadioSpinel.ClearSrcMatchExtEntries());
OT_UNUSED_VARIABLE(aInstance);
}
otError otPlatRadioEnergyScan(otInstance *aInstance, uint8_t aScanChannel, uint16_t aScanDuration)
{
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.EnergyScan(aScanChannel, aScanDuration);
}
otError otPlatRadioGetTransmitPower(otInstance *aInstance, int8_t *aPower)
{
assert(aPower != NULL);
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.GetTransmitPower(*aPower);
}
otError otPlatRadioSetTransmitPower(otInstance *aInstance, int8_t aPower)
{
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.SetTransmitPower(aPower);
}
int8_t otPlatRadioGetReceiveSensitivity(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return sRadioSpinel.GetReceiveSensitivity();
}
#if OPENTHREAD_POSIX_VIRTUAL_TIME
void ot::RadioSpinel::Process(const Event &aEvent)
{
if (!mFrameQueue.IsEmpty())
{
ProcessFrameQueue();
}
// The current event can be other event types
if (aEvent.mEvent == OT_SIM_EVENT_RADIO_SPINEL_WRITE)
{
mHdlcDecoder.Decode(aEvent.mData, aEvent.mDataLength);
ProcessFrameQueue();
}
if (mState == OT_RADIO_STATE_TRANSMIT && mTxState == kDone)
{
mState = OT_RADIO_STATE_RECEIVE;
#if OPENTHREAD_ENABLE_DIAG
if (otPlatDiagModeGet())
{
otPlatDiagRadioTransmitDone(mInstance, mTransmitFrame, mTxError);
}
else
#endif
{
otPlatRadioTxDone(mInstance, mTransmitFrame, (mIsAckRequested ? &mRxRadioFrame : NULL), mTxError);
}
mTxState = kIdle;
}
if (mState == OT_RADIO_STATE_TRANSMIT && mTxState == kIdle)
{
RadioTransmit();
}
}
void ot::RadioSpinel::Update(struct timeval &aTimeout)
{
// Prevent sleep event when transmitting
if (mState == OT_RADIO_STATE_TRANSMIT && mTxState == kIdle)
{
aTimeout.tv_sec = 0;
aTimeout.tv_usec = 0;
}
}
void otSimRadioSpinelUpdate(struct timeval *aTimeout)
{
sRadioSpinel.Update(*aTimeout);
}
void otSimRadioSpinelProcess(otInstance *aInstance, const struct Event *aEvent)
{
sRadioSpinel.Process(*aEvent);
OT_UNUSED_VARIABLE(aInstance);
}
#endif // OPENTHREAD_POSIX_VIRTUAL_TIME
#if OPENTHREAD_ENABLE_DIAG
void otPlatDiagProcess(otInstance *aInstance, int argc, char *argv[], char *aOutput, size_t aOutputMaxLen)
{
// deliver the platform specific diags commands to radio only ncp.
OT_UNUSED_VARIABLE(aInstance);
char cmd[OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE] = {'\0'};
char *cur = cmd;
char *end = cmd + sizeof(cmd);
for (int index = 0; index < argc; index++)
{
cur += snprintf(cur, end - cur, "%s ", argv[index]);
}
sRadioSpinel.PlatDiagProcess(cmd, aOutput, aOutputMaxLen);
}
void otPlatDiagModeSet(bool aMode)
{
SuccessOrExit(sRadioSpinel.PlatDiagProcess(aMode ? "start" : "stop", NULL, 0));
sRadioSpinel.SetDiagEnabled(aMode);
exit:
return;
}
bool otPlatDiagModeGet(void)
{
return sRadioSpinel.IsDiagEnabled();
}
void otPlatDiagTxPowerSet(int8_t aTxPower)
{
char cmd[OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE];
snprintf(cmd, sizeof(cmd), "power %d", aTxPower);
SuccessOrExit(sRadioSpinel.PlatDiagProcess(cmd, NULL, 0));
exit:
return;
}
void otPlatDiagChannelSet(uint8_t aChannel)
{
char cmd[OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE];
snprintf(cmd, sizeof(cmd), "channel %d", aChannel);
SuccessOrExit(sRadioSpinel.PlatDiagProcess(cmd, NULL, 0));
exit:
return;
}
void otPlatDiagRadioReceived(otInstance *aInstance, otRadioFrame *aFrame, otError aError)
{
(void)aInstance;
(void)aFrame;
(void)aError;
}
void otPlatDiagAlarmCallback(otInstance *aInstance)
{
(void)aInstance;
}
#endif // OPENTHREAD_ENABLE_DIAG