#include "hdlc_interface.hpp"
#include "platform-posix.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#if OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
#if defined(__APPLE__) || defined(__NetBSD__)
#include <util.h>
#elif defined(__FreeBSD__)
#include <libutil.h>
#else
#include <pty.h>
#endif
#endif
#include <stdarg.h>
#include <stdlib.h>
#include <sys/resource.h>
#include <sys/stat.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/logging.hpp"
#ifndef SOCKET_UTILS_DEFAULT_SHELL
#define SOCKET_UTILS_DEFAULT_SHELL "/bin/sh"
#endif
#ifdef __APPLE__
#ifndef B230400
#define B230400 230400
#endif
#ifndef B460800
#define B460800 460800
#endif
#ifndef B500000
#define B500000 500000
#endif
#ifndef B576000
#define B576000 576000
#endif
#ifndef B921600
#define B921600 921600
#endif
#ifndef B1000000
#define B1000000 1000000
#endif
#ifndef B1152000
#define B1152000 1152000
#endif
#ifndef B1500000
#define B1500000 1500000
#endif
#ifndef B2000000
#define B2000000 2000000
#endif
#ifndef B2500000
#define B2500000 2500000
#endif
#ifndef B3000000
#define B3000000 3000000
#endif
#ifndef B3500000
#define B3500000 3500000
#endif
#ifndef B4000000
#define B4000000 4000000
#endif
#endif
#if OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_UART
using ot::Spinel::SpinelInterface;
namespace ot {
namespace Posix {
HdlcInterface::HdlcInterface(SpinelInterface::ReceiveFrameCallback aCallback,
void * aCallbackContext,
SpinelInterface::RxFrameBuffer & aFrameBuffer)
: mReceiveFrameCallback(aCallback)
, mReceiveFrameContext(aCallbackContext)
, mReceiveFrameBuffer(aFrameBuffer)
, mSockFd(-1)
, mHdlcDecoder(aFrameBuffer, HandleHdlcFrame, this)
{
}
otError HdlcInterface::Init(Arguments &aArguments)
{
otError error = OT_ERROR_NONE;
struct stat st;
VerifyOrExit(mSockFd == -1, error = OT_ERROR_ALREADY);
VerifyOrDie(stat(aArguments.GetPath(), &st) == 0, OT_EXIT_INVALID_ARGUMENTS);
if (S_ISCHR(st.st_mode))
{
mSockFd = OpenFile(aArguments.GetPath(), aArguments);
VerifyOrExit(mSockFd != -1, error = OT_ERROR_INVALID_ARGS);
}
#if OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
else if (S_ISREG(st.st_mode))
{
mSockFd = ForkPty(aArguments.GetPath(), aArguments.GetValue("arg"));
VerifyOrExit(mSockFd != -1, error = OT_ERROR_INVALID_ARGS);
}
#endif else
{
otLogCritPlat("Radio file '%s' not supported", aArguments.GetPath());
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
exit:
return error;
}
HdlcInterface::~HdlcInterface(void)
{
Deinit();
}
void HdlcInterface::Deinit(void)
{
VerifyOrExit(mSockFd != -1, OT_NOOP);
VerifyOrExit(0 == close(mSockFd), perror("close RCP"));
VerifyOrExit(-1 != wait(NULL) || errno == ECHILD, perror("wait RCP"));
mSockFd = -1;
exit:
return;
}
void HdlcInterface::Read(void)
{
uint8_t buffer[kMaxFrameSize];
ssize_t rval;
rval = read(mSockFd, buffer, sizeof(buffer));
if (rval > 0)
{
Decode(buffer, static_cast<uint16_t>(rval));
}
else if ((rval < 0) && (errno != EAGAIN) && (errno != EINTR))
{
DieNow(OT_EXIT_ERROR_ERRNO);
}
}
void HdlcInterface::Decode(const uint8_t *aBuffer, uint16_t aLength)
{
mHdlcDecoder.Decode(aBuffer, aLength);
}
otError HdlcInterface::SendFrame(const uint8_t *aFrame, uint16_t aLength)
{
otError error = OT_ERROR_NONE;
Hdlc::FrameBuffer<kMaxFrameSize> encoderBuffer;
Hdlc::Encoder hdlcEncoder(encoderBuffer);
SuccessOrExit(error = hdlcEncoder.BeginFrame());
SuccessOrExit(error = hdlcEncoder.Encode(aFrame, aLength));
SuccessOrExit(error = hdlcEncoder.EndFrame());
error = Write(encoderBuffer.GetFrame(), encoderBuffer.GetLength());
exit:
return error;
}
otError HdlcInterface::Write(const uint8_t *aFrame, uint16_t aLength)
{
otError error = OT_ERROR_NONE;
#if OPENTHREAD_POSIX_VIRTUAL_TIME
virtualTimeSendRadioSpinelWriteEvent(aFrame, aLength);
#else
while (aLength)
{
ssize_t rval = write(mSockFd, aFrame, aLength);
if (rval > 0)
{
aLength -= static_cast<uint16_t>(rval);
aFrame += static_cast<uint16_t>(rval);
continue;
}
if ((rval < 0) && (errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINTR))
{
DieNow(OT_EXIT_ERROR_ERRNO);
}
SuccessOrExit(error = WaitForWritable());
}
exit:
#endif return error;
}
otError HdlcInterface::WaitForFrame(uint64_t aTimeoutUs)
{
otError error = OT_ERROR_NONE;
struct timeval timeout;
#if OPENTHREAD_POSIX_VIRTUAL_TIME
struct Event event;
timeout.tv_sec = static_cast<time_t>(aTimeoutUs / US_PER_S);
timeout.tv_usec = static_cast<suseconds_t>(aTimeoutUs % US_PER_S);
virtualTimeSendSleepEvent(&timeout);
virtualTimeReceiveEvent(&event);
switch (event.mEvent)
{
case OT_SIM_EVENT_RADIO_SPINEL_WRITE:
Decode(event.mData, event.mDataLength);
break;
case OT_SIM_EVENT_ALARM_FIRED:
VerifyOrExit(event.mDelay <= aTimeoutUs, error = OT_ERROR_RESPONSE_TIMEOUT);
break;
default:
assert(false);
break;
}
#else
timeout.tv_sec = static_cast<time_t>(aTimeoutUs / US_PER_S);
timeout.tv_usec = static_cast<suseconds_t>(aTimeoutUs % US_PER_S);
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))
{
Read();
}
else if (FD_ISSET(mSockFd, &error_fds))
{
DieNowWithMessage("NCP error", OT_EXIT_FAILURE);
}
else
{
DieNow(OT_EXIT_FAILURE);
}
}
else if (rval == 0)
{
ExitNow(error = OT_ERROR_RESPONSE_TIMEOUT);
}
else if (errno != EINTR)
{
DieNowWithMessage("wait response", OT_EXIT_FAILURE);
}
#endif
exit:
return error;
}
void HdlcInterface::UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout)
{
OT_UNUSED_VARIABLE(aWriteFdSet);
OT_UNUSED_VARIABLE(aTimeout);
FD_SET(mSockFd, &aReadFdSet);
if (aMaxFd < mSockFd)
{
aMaxFd = mSockFd;
}
}
void HdlcInterface::Process(const RadioProcessContext &aContext)
{
if (FD_ISSET(mSockFd, aContext.mReadFdSet))
{
Read();
}
}
otError HdlcInterface::WaitForWritable(void)
{
otError error = OT_ERROR_NONE;
struct timeval timeout = {kMaxWaitTime / 1000, (kMaxWaitTime % 1000) * 1000};
uint64_t now = otPlatTimeGet();
uint64_t end = now + kMaxWaitTime * US_PER_MS;
fd_set writeFds;
fd_set errorFds;
int rval;
while (true)
{
FD_ZERO(&writeFds);
FD_ZERO(&errorFds);
FD_SET(mSockFd, &writeFds);
FD_SET(mSockFd, &errorFds);
rval = select(mSockFd + 1, NULL, &writeFds, &errorFds, &timeout);
if (rval > 0)
{
if (FD_ISSET(mSockFd, &writeFds))
{
ExitNow();
}
else if (FD_ISSET(mSockFd, &errorFds))
{
DieNow(OT_EXIT_FAILURE);
}
else
{
assert(false);
}
}
else if ((rval < 0) && (errno != EINTR))
{
DieNow(OT_EXIT_ERROR_ERRNO);
}
now = otPlatTimeGet();
if (end > now)
{
uint64_t remain = end - now;
timeout.tv_sec = static_cast<time_t>(remain / US_PER_S);
timeout.tv_usec = static_cast<suseconds_t>(remain % US_PER_S);
}
else
{
break;
}
}
error = OT_ERROR_FAILED;
exit:
return error;
}
int HdlcInterface::OpenFile(const char *aFile, Arguments &aArguments)
{
int fd = -1;
int rval = 0;
const char *parity = aArguments.GetValue("uart-parity");
uint32_t baudrate = 115200;
fd = open(aFile, O_RDWR | O_NOCTTY | O_NONBLOCK | O_CLOEXEC);
if (fd == -1)
{
perror("open uart failed");
ExitNow();
}
if (isatty(fd))
{
struct termios tios;
unsigned int speed = 115200;
int stopBit = 1;
VerifyOrExit((rval = tcgetattr(fd, &tios)) == 0, OT_NOOP);
cfmakeraw(&tios);
tios.c_cflag = CS8 | HUPCL | CREAD | CLOCAL;
if (parity)
{
if (strncmp(parity, "odd", 3) == 0)
{
tios.c_cflag |= PARENB;
tios.c_cflag |= PARODD;
}
else if (strncmp(parity, "even", 4) == 0)
{
tios.c_cflag |= PARENB;
}
else
{
DieNow(OT_EXIT_INVALID_ARGUMENTS);
}
}
if (aArguments.GetValue("uart-stop"))
{
stopBit = atoi(aArguments.GetValue("uart-stop"));
}
switch (stopBit)
{
case 1:
tios.c_cflag &= static_cast<unsigned long>(~CSTOPB);
break;
case 2:
tios.c_cflag |= CSTOPB;
break;
default:
DieNow(OT_EXIT_INVALID_ARGUMENTS);
break;
}
if (aArguments.GetValue("baudrate"))
{
baudrate = static_cast<uint32_t>(atoi(aArguments.GetValue("baudrate")));
}
switch (baudrate)
{
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:
DieNow(OT_EXIT_INVALID_ARGUMENTS);
break;
}
if (aArguments.GetValue("uart-flow-control") != NULL)
{
tios.c_cflag |= CRTSCTS;
}
VerifyOrExit((rval = cfsetspeed(&tios, static_cast<speed_t>(speed))) == 0, perror("cfsetspeed"));
VerifyOrExit((rval = tcsetattr(fd, TCSANOW, &tios)) == 0, perror("tcsetattr"));
VerifyOrExit((rval = tcflush(fd, TCIOFLUSH)) == 0, OT_NOOP);
}
exit:
if (rval != 0)
{
DieNow(OT_EXIT_FAILURE);
}
return fd;
}
#if OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
int HdlcInterface::ForkPty(const char *aCommand, const char *aArguments)
{
int fd = -1;
int pid = -1;
int rval = -1;
{
struct termios tios;
memset(&tios, 0, sizeof(tios));
cfmakeraw(&tios);
tios.c_cflag = CS8 | HUPCL | CREAD | CLOCAL;
VerifyOrExit((pid = forkpty(&fd, NULL, &tios, NULL)) != -1, perror("forkpty()"));
}
if (0 == pid)
{
const int kMaxCommand = 255;
char cmd[kMaxCommand];
rval = snprintf(cmd, sizeof(cmd), "exec %s %s", aCommand, aArguments);
VerifyOrExit(rval > 0 && static_cast<size_t>(rval) < sizeof(cmd),
fprintf(stderr, "NCP file and configuration is too long!");
rval = -1);
VerifyOrExit((rval = execl(SOCKET_UTILS_DEFAULT_SHELL, SOCKET_UTILS_DEFAULT_SHELL, "-c", cmd,
static_cast<char *>(NULL))) != -1,
perror("execl(OT_RCP)"));
}
else
{
VerifyOrExit((rval = fcntl(fd, F_GETFL)) != -1, perror("fcntl(F_GETFL)"));
VerifyOrExit((rval = fcntl(fd, F_SETFL, rval | O_NONBLOCK | O_CLOEXEC)) != -1, perror("fcntl(F_SETFL)"));
}
exit:
VerifyOrDie(rval == 0, OT_EXIT_ERROR_ERRNO);
return fd;
}
#endif
void HdlcInterface::HandleHdlcFrame(void *aContext, otError aError)
{
static_cast<HdlcInterface *>(aContext)->HandleHdlcFrame(aError);
}
void HdlcInterface::HandleHdlcFrame(otError aError)
{
if (aError == OT_ERROR_NONE)
{
mReceiveFrameCallback(mReceiveFrameContext);
}
else
{
mReceiveFrameBuffer.DiscardFrame();
otLogWarnPlat("Error decoding hdlc frame: %s", otThreadErrorToString(aError));
}
}
} } #endif