blob: 391fdacd5f33f5ee1534ecb9e1cd7fa6146411f1 [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 includes the implementation for the HDLC interface to radio (RCP).
*/
#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 <openthread/logging.h>
#include "common/code_utils.hpp"
#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 // __APPLE__
#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)
, mBaudRate(0)
, mHdlcDecoder(aFrameBuffer, HandleHdlcFrame, this)
, mRadioUrl(nullptr)
{
}
void HdlcInterface::OnRcpReset(void)
{
mHdlcDecoder.Reset();
}
otError HdlcInterface::Init(const Url::Url &aRadioUrl)
{
otError error = OT_ERROR_NONE;
struct stat st;
VerifyOrExit(mSockFd == -1, error = OT_ERROR_ALREADY);
VerifyOrDie(stat(aRadioUrl.GetPath(), &st) == 0, OT_EXIT_INVALID_ARGUMENTS);
if (S_ISCHR(st.st_mode))
{
mSockFd = OpenFile(aRadioUrl);
VerifyOrExit(mSockFd != -1, error = OT_ERROR_INVALID_ARGS);
}
#if OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
else if (S_ISREG(st.st_mode))
{
mSockFd = ForkPty(aRadioUrl);
VerifyOrExit(mSockFd != -1, error = OT_ERROR_INVALID_ARGS);
}
#endif // OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
else
{
otLogCritPlat("Radio file '%s' not supported", aRadioUrl.GetPath());
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
mRadioUrl = &aRadioUrl;
exit:
return error;
}
HdlcInterface::~HdlcInterface(void)
{
Deinit();
}
void HdlcInterface::Deinit(void)
{
CloseFile();
}
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 == aLength)
{
break;
}
else if (rval > 0)
{
aLength -= static_cast<uint16_t>(rval);
aFrame += static_cast<uint16_t>(rval);
}
else if (rval < 0)
{
VerifyOrDie((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EINTR), OT_EXIT_ERROR_ERRNO);
}
SuccessOrExit(error = WaitForWritable());
}
exit:
#endif // OPENTHREAD_POSIX_VIRTUAL_TIME
return error;
}
otError HdlcInterface::WaitForFrame(uint64_t aTimeoutUs)
{
otError error = OT_ERROR_NONE;
struct timeval timeout;
#if OPENTHREAD_POSIX_VIRTUAL_TIME
struct VirtualTimeEvent 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 // OPENTHREAD_POSIX_VIRTUAL_TIME
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, nullptr, &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 // OPENTHREAD_POSIX_VIRTUAL_TIME
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, nullptr, &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 Url::Url &aRadioUrl)
{
int fd = -1;
int rval = 0;
fd = open(aRadioUrl.GetPath(), O_RDWR | O_NOCTTY | O_NONBLOCK | O_CLOEXEC);
if (fd == -1)
{
perror("open uart failed");
ExitNow();
}
if (isatty(fd))
{
struct termios tios;
const char * value;
speed_t speed;
int stopBit = 1;
uint32_t baudrate = 115200;
VerifyOrExit((rval = tcgetattr(fd, &tios)) == 0);
cfmakeraw(&tios);
tios.c_cflag = CS8 | HUPCL | CREAD | CLOCAL;
if ((value = aRadioUrl.GetValue("uart-parity")) != nullptr)
{
if (strncmp(value, "odd", 3) == 0)
{
tios.c_cflag |= PARENB;
tios.c_cflag |= PARODD;
}
else if (strncmp(value, "even", 4) == 0)
{
tios.c_cflag |= PARENB;
}
else
{
DieNow(OT_EXIT_INVALID_ARGUMENTS);
}
}
if ((value = aRadioUrl.GetValue("uart-stop")) != nullptr)
{
stopBit = atoi(value);
}
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 ((value = aRadioUrl.GetValue("uart-baudrate")))
{
baudrate = static_cast<uint32_t>(atoi(value));
}
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;
}
mBaudRate = baudrate;
if (aRadioUrl.GetValue("uart-flow-control") != nullptr)
{
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);
}
exit:
if (rval != 0)
{
DieNow(OT_EXIT_FAILURE);
}
return fd;
}
void HdlcInterface::CloseFile(void)
{
VerifyOrExit(mSockFd != -1);
VerifyOrExit(0 == close(mSockFd), perror("close RCP"));
VerifyOrExit(-1 != wait(nullptr) || errno == ECHILD, perror("wait RCP"));
mSockFd = -1;
exit:
return;
}
#if OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
int HdlcInterface::ForkPty(const Url::Url &aRadioUrl)
{
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;
VerifyOrDie((pid = forkpty(&fd, nullptr, &tios, nullptr)) != -1, OT_EXIT_ERROR_ERRNO);
}
if (0 == pid)
{
constexpr int kMaxArguments = 32;
char * argv[kMaxArguments + 1];
size_t index = 0;
argv[index++] = const_cast<char *>(aRadioUrl.GetPath());
for (const char *arg = nullptr;
index < OT_ARRAY_LENGTH(argv) && (arg = aRadioUrl.GetValue("forkpty-arg", arg)) != nullptr;
argv[index++] = const_cast<char *>(arg))
{
}
if (index < OT_ARRAY_LENGTH(argv))
{
argv[index] = nullptr;
}
else
{
DieNowWithMessage("Too many arguments!", OT_EXIT_INVALID_ARGUMENTS);
}
VerifyOrDie((rval = execvp(argv[0], argv)) != -1, OT_EXIT_ERROR_ERRNO);
}
else
{
VerifyOrDie((rval = fcntl(fd, F_GETFL)) != -1, OT_EXIT_ERROR_ERRNO);
VerifyOrDie((rval = fcntl(fd, F_SETFL, rval | O_NONBLOCK | O_CLOEXEC)) != -1, OT_EXIT_ERROR_ERRNO);
}
return fd;
}
#endif // OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
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));
}
}
otError HdlcInterface::ResetConnection(void)
{
otError error = OT_ERROR_NONE;
uint64_t end;
if (mRadioUrl->GetValue("uart-reset") != nullptr)
{
usleep(static_cast<useconds_t>(kRemoveRcpDelay) * US_PER_MS);
CloseFile();
end = otPlatTimeGet() + kResetTimeout * US_PER_MS;
do
{
mSockFd = OpenFile(*mRadioUrl);
if (mSockFd != -1)
{
ExitNow();
}
usleep(static_cast<useconds_t>(kOpenFileDelay) * US_PER_MS);
} while (end > otPlatTimeGet());
otLogCritPlat("Failed to reopen UART connection after resetting the RCP device.");
error = OT_ERROR_FAILED;
}
exit:
return error;
}
} // namespace Posix
} // namespace ot
#endif // OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_UART