diff --git a/Android.mk b/Android.mk index d7bcfac11..eed5fc320 100644 --- a/Android.mk +++ b/Android.mk @@ -180,15 +180,16 @@ LOCAL_SRC_FILES := \ src/ncp/spinel_decoder.cpp \ src/ncp/spinel_encoder.cpp \ src/posix/platform/alarm.c \ - src/posix/platform/misc.c \ + src/posix/platform/frame_queue.cpp \ + src/posix/platform/hdlc_interface.cpp \ src/posix/platform/logging.c \ + src/posix/platform/misc.c \ + src/posix/platform/radio_spinel.cpp \ src/posix/platform/random.c \ + src/posix/platform/settings.cpp \ + src/posix/platform/spi-stubs.c \ src/posix/platform/system.c \ src/posix/platform/uart.c \ - src/posix/platform/spi-stubs.c \ - src/posix/platform/frame_queue.cpp \ - src/posix/platform/radio_spinel.cpp \ - src/posix/platform/settings.cpp \ third_party/mbedtls/repo/library/md.c \ third_party/mbedtls/repo/library/md_wrap.c \ third_party/mbedtls/repo/library/memory_buffer_alloc.c \ diff --git a/src/posix/platform/Makefile.am b/src/posix/platform/Makefile.am index c57314fb6..b69f7a6b5 100644 --- a/src/posix/platform/Makefile.am +++ b/src/posix/platform/Makefile.am @@ -42,6 +42,7 @@ libopenthread_posix_a_CPPFLAGS = \ libopenthread_posix_a_SOURCES = \ alarm.c \ frame_queue.cpp \ + hdlc_interface.cpp \ logging.c \ misc.c \ radio_spinel.cpp \ @@ -75,6 +76,7 @@ noinst_HEADERS = \ frame_queue.hpp \ openthread-system.h \ platform-posix.h \ + hdlc_interface.hpp \ radio_spinel.hpp \ $(NULL) diff --git a/src/posix/platform/hdlc_interface.cpp b/src/posix/platform/hdlc_interface.cpp new file mode 100644 index 000000000..8270a97b2 --- /dev/null +++ b/src/posix/platform/hdlc_interface.cpp @@ -0,0 +1,457 @@ +/* + * 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 "radio_spinel.hpp" + +#include +#include +#include +#if OPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE +#ifdef OPENTHREAD_TARGET_DARWIN +#include +#else +#include +#endif +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifndef SOCKET_UTILS_DEFAULT_SHELL +#define SOCKET_UTILS_DEFAULT_SHELL "/bin/sh" +#endif + +namespace ot { +namespace PosixApp { + +class EncoderBuffer : public Hdlc::Encoder::BufferWriteIterator +{ +public: + EncoderBuffer(void) + { + mWritePointer = mBuffer; + mRemainingLength = sizeof(mBuffer); + } + + uint16_t GetLength(void) const { return static_cast(mWritePointer - mBuffer); } + const uint8_t *GetBuffer(void) const { return mBuffer; } + +private: + uint8_t mBuffer[HdlcInterface::kMaxFrameSize]; +}; + +HdlcInterface::HdlcInterface(Callbacks &aCallbacks) + : mCallbacks(aCallbacks) + , mSockFd(-1) + , mIsDecoding(false) + , mHdlcDecoder(mDecoderBuffer, sizeof(mDecoderBuffer), HandleHdlcFrame, HandleHdlcError, this) +{ +} + +otError HdlcInterface::Init(const char *aRadioFile, const char *aRadioConfig) +{ + otError error = OT_ERROR_NONE; + struct stat st; + + VerifyOrExit(mSockFd == -1, error = OT_ERROR_ALREADY); + + VerifyOrExit(stat(aRadioFile, &st) == 0, perror("stat ncp file failed"); error = OT_ERROR_INVALID_ARGS); + + 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 + else + { + otLogCritPlat("Radio file '%s' not supported", aRadioFile); + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + +exit: + return error; +} + +void HdlcInterface::Deinit(void) +{ + assert(mSockFd != -1); + + VerifyOrExit(0 == close(mSockFd), perror("close NCP")); + VerifyOrExit(-1 != wait(NULL), perror("wait NCP")); + + mSockFd = -1; + +exit: + return; +} + +void HdlcInterface::Read(void) +{ + uint8_t buffer[kMaxFrameSize]; + ssize_t rval; + + rval = read(mSockFd, buffer, sizeof(buffer)); + + if (rval < 0) + { + perror("HdlcInterface::Read()"); + + if (errno != EAGAIN) + { + abort(); + } + } + + if (rval > 0) + { + Decode(buffer, static_cast(rval)); + } +} + +void HdlcInterface::Decode(const uint8_t *aBuffer, uint16_t aLength) +{ + mIsDecoding = true; + mHdlcDecoder.Decode(aBuffer, aLength); + mIsDecoding = false; +} + +otError HdlcInterface::SendFrame(const uint8_t *aFrame, uint16_t aLength) +{ + otError error = OT_ERROR_NONE; + Hdlc::Encoder hdlcEncoder; + EncoderBuffer encoderBuffer; + + SuccessOrExit(error = hdlcEncoder.Init(encoderBuffer)); + SuccessOrExit(error = hdlcEncoder.Encode(aFrame, aLength, encoderBuffer)); + SuccessOrExit(error = hdlcEncoder.Finalize(encoderBuffer)); + + SuccessOrExit(error = Write(encoderBuffer.GetBuffer(), 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 + otSimSendRadioSpinelWriteEvent(aFrame, aLength); +#else + while (aLength) + { + ssize_t rval = write(mSockFd, aFrame, aLength); + + if (rval > 0) + { + aLength -= static_cast(rval); + aFrame += static_cast(rval); + } + else if (rval < 0) + { + perror("HdlcInterface::Write"); + ExitNow(error = OT_ERROR_FAILED); + } + else + { + ExitNow(error = OT_ERROR_FAILED); + } + } +exit: +#endif + return error; +} + +int HdlcInterface::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(OT_EXIT_INVALID_ARGUMENTS); + break; + } + + switch (cstopb) + { + case 1: + tios.c_cflag &= static_cast(~CSTOPB); + break; + case 2: + tios.c_cflag |= CSTOPB; + break; + default: + assert(false); + exit(OT_EXIT_INVALID_ARGUMENTS); + 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(OT_EXIT_INVALID_ARGUMENTS); + break; + } + + VerifyOrExit((rval = cfsetspeed(&tios, static_cast(speed))) == 0, perror("cfsetspeed")); + VerifyOrExit((rval = tcsetattr(fd, TCSANOW, &tios)) == 0, perror("tcsetattr")); + VerifyOrExit((rval = tcflush(fd, TCIOFLUSH)) == 0); + } + +exit: + if (rval != 0) + { + exit(OT_EXIT_FAILURE); + } + + return fd; +} + +#if OPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE +int HdlcInterface::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(i)); + } + + rval = snprintf(cmd, sizeof(cmd), "exec %s %s", aCommand, aArguments); + VerifyOrExit(rval > 0 && static_cast(rval) < sizeof(cmd), + otLogCritPlat("NCP file and configuration is too long!")); + + execl(getenv("SHELL"), getenv("SHELL"), "-c", cmd, NULL); + perror("open pty failed"); + exit(OT_EXIT_INVALID_ARGUMENTS); + } + 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 + +void HdlcInterface::HandleHdlcFrame(void *aContext, uint8_t *aFrame, uint16_t aFrameLength) +{ + static_cast(aContext)->mCallbacks.HandleReceivedFrame(aFrame, aFrameLength); +} + +void HdlcInterface::HandleHdlcError(void *aContext, otError aError, uint8_t *aFrame, uint16_t aFrameLength) +{ + otLogWarnPlat("Error decoding hdlc frame: %s", otThreadErrorToString(aError)); + + OT_UNUSED_VARIABLE(aContext); + OT_UNUSED_VARIABLE(aError); + OT_UNUSED_VARIABLE(aFrame); + OT_UNUSED_VARIABLE(aFrameLength); +} + +} // namespace PosixApp +} // namespace ot diff --git a/src/posix/platform/hdlc_interface.hpp b/src/posix/platform/hdlc_interface.hpp new file mode 100644 index 000000000..85a084e13 --- /dev/null +++ b/src/posix/platform/hdlc_interface.hpp @@ -0,0 +1,178 @@ +/* + * 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 definitions for the HDLC interface to radio (RCP). + */ + +#ifndef POSIX_APP_HDLC_INTERFACE_HPP_ +#define POSIX_APP_HDLC_INTERFACE_HPP_ + +#include "openthread-core-config.h" + +#include "hdlc.hpp" + +namespace ot { +namespace PosixApp { + +/** + * This class defines an HDLC interface to the Radio Co-processor (RCP) + * + */ +class HdlcInterface +{ +public: + enum + { + kMaxFrameSize = 2048, ///< Maximum frame size (number of bytes). + }; + + /** + * This class defines the callbacks provided by `HdlcInterfac` to its owner/user. + * + */ + class Callbacks + { + public: + /** + * This callback is invoked to notify owner/user of `HdlcInterface` of a received (and decoded) frame. + * + * @param[in] aFrame A pointer to buffer containing the received frame. + * @param[in] aLength The length (number of bytes) of the received frame. + * + */ + void HandleReceivedFrame(const uint8_t *aFrame, uint16_t aLength); + }; + + /** + * This constructor initializes the object. + * + * @param[in] aCallback A reference to a `Callback` object. + * + */ + explicit HdlcInterface(Callbacks &aCallbacks); + + /** + * This method initializes the interface to the Radio Co-processor (RCP) + * + * @note This method should be called before reading and sending frames to the interface. + * + * + * @param[in] aRadioFile The path to either a UART device or an executable. + * @param[in] aRadioConfig Parameters to be given to the device or executable. + * + * @retval OT_ERROR_NONE The interface is initialized successfully + * @retval OT_ERROR_ALREADY The interface is already initialized. + * @retval OT_ERROR_INVALID_ARGS The UART device or executable cannot be found or failed to open/run. + * + */ + otError Init(const char *aRadioFile, const char *aRadioConfig); + + /** + * This method deinitializes the interface to the RCP. + * + */ + void Deinit(void); + + /** + * + * This method returns the socket file descriptor associate with the interface + * + * @returns The associated socket file descriptor, or -1 if interface is not initializes. + * + */ + int GetSocket(void) const { return mSockFd; } + + /** + * This method indicates whether the `HdclInterface` is currently decoding a received frame or not. + * + * @returns TRUE if currently decoding a received frame, FALSE otherwise. + * + */ + bool IsDecoding(void) const { return mIsDecoding; } + + /** + * This method instructs `HdlcInterface` to read and decode data from radio over the socket. + * + * If a full HDLC frame is decoded while reading data, this method invokes the `HandleReceivedFrame()` (on the + * `aCallback` object from constructor) to pass the received frame to be processed. + * + */ + void Read(void); + + /** + * This method encodes and sends a frame to Radio Co-processor (RCP) over the socket. + * + * @param[in] aFrame A pointer to buffer containing the frame to send. + * @param[in] aLength The length (number of bytes) in the frame + * + * @retval OT_ERROR_NONE Successfully encoded and sent the frame. + * @retval OT_ERROR_NO_BUFS Insufficient buffer space available to encode the frame. + * @retval OT_ERROR_FAILED Failed to send frame due to socket write failure. + * + */ + otError SendFrame(const uint8_t *aFrame, uint16_t aLength); + +#if OPENTHREAD_POSIX_VIRTUAL_TIME + /** + * This method process read data (decode the data). + * + * This method is intended only for virtual time simulation. Its behavior is similar to `Read()` but instead of + * reading the data from the radio socket, it uses the given data in the buffer `aBuffer`. + * + * @param[in] aBuffer A pointer to buffer containing data. + * @param[in] aLength The length (number of bytes) in the buffer. + * + */ + void ProcessReadData(const uint8_t *aBuffer, uint16_t aLength) { Decode(aBuffer, aLength); } +#endif + +private: + otError Write(const uint8_t *aFrame, uint16_t aLength); + void Decode(const uint8_t *aBuffer, uint16_t aLength); + + static void HandleHdlcFrame(void *aContext, uint8_t *aFrame, uint16_t aFrameLength); + static void HandleHdlcError(void *aContext, otError aError, uint8_t *aFrame, uint16_t aFrameLength); + + static int OpenFile(const char *aFile, const char *aConfig); +#if OPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE + static int ForkPty(const char *aCommand, const char *aArguments); +#endif + + Callbacks & mCallbacks; + int mSockFd; + bool mIsDecoding; + Hdlc::Decoder mHdlcDecoder; + uint8_t mDecoderBuffer[kMaxFrameSize]; +}; + +} // namespace PosixApp +} // namespace ot + +#endif // POSIX_APP_HDLC_INTERFACE_HPP_ diff --git a/src/posix/platform/radio_spinel.cpp b/src/posix/platform/radio_spinel.cpp index 0738d8b1c..f0c4814f6 100644 --- a/src/posix/platform/radio_spinel.cpp +++ b/src/posix/platform/radio_spinel.cpp @@ -37,14 +37,6 @@ #include #include -#include -#if OPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE -#ifdef OPENTHREAD_TARGET_DARWIN -#include -#else -#include -#endif -#endif #include #include #include @@ -62,10 +54,6 @@ #include #include -#ifndef SOCKET_UTILS_DEFAULT_SHELL -#define SOCKET_UTILS_DEFAULT_SHELL "/bin/sh" -#endif - enum { IEEE802154_MIN_LENGTH = 5, @@ -115,7 +103,7 @@ enum kDone, }; -static ot::RadioSpinel sRadioSpinel; +static ot::PosixApp::RadioSpinel sRadioSpinel; static inline otPanId getDstPan(const uint8_t *frame) { @@ -143,6 +131,7 @@ static inline bool isAckRequested(const uint8_t *frame) } namespace ot { +namespace PosixApp { static otError SpinelStatusToOtError(spinel_status_t aError) { @@ -229,266 +218,23 @@ static void LogIfFail(const char *aText, otError aError) OT_UNUSED_VARIABLE(aError); } -#if OPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE -static int ForkPty(const char *aCommand, const char *aArguments) +void HdlcInterface::Callbacks::HandleReceivedFrame(const uint8_t *aBuffer, uint16_t aLength) { - 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(i)); - } - - rval = snprintf(cmd, sizeof(cmd), "exec %s %s", aCommand, aArguments); - VerifyOrExit(rval > 0 && static_cast(rval) < sizeof(cmd), - otLogCritPlat("NCP file and configuration is too long!")); - - execl(getenv("SHELL"), getenv("SHELL"), "-c", cmd, NULL); - perror("open pty failed"); - exit(OT_EXIT_INVALID_ARGUMENTS); - } - 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; + static_cast(this)->HandleSpinelFrame(aBuffer, aLength); } -#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(OT_EXIT_INVALID_ARGUMENTS); - break; - } - - switch (cstopb) - { - case 1: - tios.c_cflag &= static_cast(~CSTOPB); - break; - case 2: - tios.c_cflag |= CSTOPB; - break; - default: - assert(false); - exit(OT_EXIT_INVALID_ARGUMENTS); - 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(OT_EXIT_INVALID_ARGUMENTS); - break; - } - - VerifyOrExit((rval = cfsetspeed(&tios, static_cast(speed))) == 0, perror("cfsetspeed")); - VerifyOrExit((rval = tcsetattr(fd, TCSANOW, &tios)) == 0, perror("tcsetattr")); - VerifyOrExit((rval = tcflush(fd, TCIOFLUSH)) == 0); - } - -exit: - if (rval != 0) - { - exit(OT_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(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) + : mInstance(NULL) + , mHdlcInterface(*this) + , 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) , mSupportsLogStream(false) @@ -503,31 +249,9 @@ RadioSpinel::RadioSpinel(void) void RadioSpinel::Init(const char *aRadioFile, const char *aRadioConfig) { - otError error = OT_ERROR_NONE; - struct stat st; + otError error = OT_ERROR_NONE; - // not allowed to initialize again. - assert(mSockFd == -1); - - VerifyOrExit(stat(aRadioFile, &st) == 0, perror("stat ncp file failed"); error = OT_ERROR_INVALID_ARGS); - - 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 - else - { - otLogCritPlat("Radio file '%s' not supported", aRadioFile); - ExitNow(error = OT_ERROR_INVALID_ARGS); - } + SuccessOrExit(error = mHdlcInterface.Init(aRadioFile, aRadioConfig)); SuccessOrExit(error = SendReset()); SuccessOrExit(error = WaitResponse()); @@ -640,14 +364,7 @@ exit: 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; + mHdlcInterface.Deinit(); } void RadioSpinel::HandleSpinelFrame(const uint8_t *aBuffer, uint16_t aLength) @@ -946,33 +663,6 @@ exit: 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(rval)); - } -} - void RadioSpinel::ProcessFrameQueue(void) { uint8_t length; @@ -1038,23 +728,25 @@ exit: void RadioSpinel::UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout) { + int sockFd = mHdlcInterface.GetSocket(); + if ((mState != OT_RADIO_STATE_TRANSMIT || mTxState == kSent)) { - FD_SET(mSockFd, &aReadFdSet); + FD_SET(sockFd, &aReadFdSet); - if (aMaxFd < mSockFd) + if (aMaxFd < sockFd) { - aMaxFd = mSockFd; + aMaxFd = sockFd; } } if (mState == OT_RADIO_STATE_TRANSMIT && mTxState == kIdle) { - FD_SET(mSockFd, &aWriteFdSet); + FD_SET(sockFd, &aWriteFdSet); - if (aMaxFd < mSockFd) + if (aMaxFd < sockFd) { - aMaxFd = mSockFd; + aMaxFd = sockFd; } } @@ -1067,14 +759,14 @@ void RadioSpinel::UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMax void RadioSpinel::Process(const fd_set &aReadFdSet, const fd_set &aWriteFdSet) { - if (FD_ISSET(mSockFd, &aReadFdSet) || !mFrameQueue.IsEmpty()) + if (FD_ISSET(mHdlcInterface.GetSocket(), &aReadFdSet) || !mFrameQueue.IsEmpty()) { // Handle frames received during WaitResponse() ProcessFrameQueue(); - if (FD_ISSET(mSockFd, &aReadFdSet)) + if (FD_ISSET(mHdlcInterface.GetSocket(), &aReadFdSet)) { - ReadAll(); + mHdlcInterface.Read(); ProcessFrameQueue(); } } @@ -1097,7 +789,7 @@ void RadioSpinel::Process(const fd_set &aReadFdSet, const fd_set &aWriteFdSet) mTxState = kIdle; } - if (FD_ISSET(mSockFd, &aWriteFdSet)) + if (FD_ISSET(mHdlcInterface.GetSocket(), &aWriteFdSet)) { if (mState == OT_RADIO_STATE_TRANSMIT && mTxState == kIdle) { @@ -1308,7 +1000,7 @@ otError RadioSpinel::WaitResponse(void) switch (event.mEvent) { case OT_SIM_EVENT_RADIO_SPINEL_WRITE: - DecodeHdlc(event.mData, event.mDataLength); + mHdlcInterface.ProcessReadData(event.mData, event.mDataLength); break; case OT_SIM_EVENT_ALARM_FIRED: @@ -1322,24 +1014,25 @@ otError RadioSpinel::WaitResponse(void) break; } #else // OPENTHREAD_POSIX_VIRTUAL_TIME + int sockFd = mHdlcInterface.GetSocket(); 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); + FD_SET(sockFd, &read_fds); + FD_SET(sockFd, &error_fds); - rval = select(mSockFd + 1, &read_fds, NULL, &error_fds, &timeout); + rval = select(sockFd + 1, &read_fds, NULL, &error_fds, &timeout); if (rval > 0) { - if (FD_ISSET(mSockFd, &read_fds)) + if (FD_ISSET(sockFd, &read_fds)) { - ReadAll(); + mHdlcInterface.Read(); } - else if (FD_ISSET(mSockFd, &error_fds)) + else if (FD_ISSET(sockFd, &error_fds)) { fprintf(stderr, "NCP error\r\n"); exit(OT_EXIT_FAILURE); @@ -1397,7 +1090,7 @@ spinel_tid_t RadioSpinel::GetNextTid(void) } /** - * This method delievers the radio frame to transceiver. + * This method delivers the radio frame to transceiver. * * otPlatRadioTxStarted() is triggered immediately for now, which may be earlier than real started time. * @@ -1440,42 +1133,10 @@ void RadioSpinel::RadioTransmit(void) } } -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(rval); - aBuffer += static_cast(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 @@ -1484,16 +1145,7 @@ otError RadioSpinel::SendReset(void) VerifyOrExit(packed > 0 && static_cast(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); + SuccessOrExit(error = mHdlcInterface.SendFrame(buffer, static_cast(packed))); sleep(0); @@ -1509,7 +1161,6 @@ otError RadioSpinel::SendCommand(uint32_t aCommand, { otError error = OT_ERROR_NONE; uint8_t buffer[kMaxSpinelFrame]; - UartTxBuffer txBuffer; spinel_ssize_t packed; uint16_t offset; @@ -1530,15 +1181,7 @@ otError RadioSpinel::SendCommand(uint32_t aCommand, offset += static_cast(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()); + error = mHdlcInterface.SendFrame(buffer, offset); exit: return error; @@ -1753,6 +1396,7 @@ otError RadioSpinel::PlatDiagProcess(const char *aString, char *aOutput, size_t } #endif +} // namespace PosixApp } // namespace ot void otPlatRadioGetIeeeEui64(otInstance *aInstance, uint8_t *aIeeeEui64) @@ -1960,7 +1604,7 @@ int8_t otPlatRadioGetReceiveSensitivity(otInstance *aInstance) } #if OPENTHREAD_POSIX_VIRTUAL_TIME -void ot::RadioSpinel::Process(const Event &aEvent) +void ot::PosixApp::RadioSpinel::Process(const Event &aEvent) { if (!mFrameQueue.IsEmpty()) { @@ -1970,7 +1614,7 @@ void ot::RadioSpinel::Process(const Event &aEvent) // The current event can be other event types if (aEvent.mEvent == OT_SIM_EVENT_RADIO_SPINEL_WRITE) { - mHdlcDecoder.Decode(aEvent.mData, aEvent.mDataLength); + mHdlcInterface.ProcessReadData(aEvent.mData, aEvent.mDataLength); ProcessFrameQueue(); } @@ -1998,7 +1642,7 @@ void ot::RadioSpinel::Process(const Event &aEvent) } } -void ot::RadioSpinel::Update(struct timeval &aTimeout) +void ot::PosixApp::RadioSpinel::Update(struct timeval &aTimeout) { // Prevent sleep event when transmitting if (mState == OT_RADIO_STATE_TRANSMIT && mTxState == kIdle) diff --git a/src/posix/platform/radio_spinel.hpp b/src/posix/platform/radio_spinel.hpp index da1ed8a57..880dd582a 100644 --- a/src/posix/platform/radio_spinel.hpp +++ b/src/posix/platform/radio_spinel.hpp @@ -37,12 +37,13 @@ #include #include "frame_queue.hpp" -#include "hdlc.hpp" +#include "hdlc_interface.hpp" #include "spinel.h" namespace ot { +namespace PosixApp { -class RadioSpinel +class RadioSpinel : public HdlcInterface::Callbacks { public: /** @@ -439,10 +440,19 @@ public: otError PlatDiagProcess(const char *aString, char *aOutput, size_t aOutputMaxLen); #endif + /** + * This method processes a received Spinel frame. + * + * @param[in] aBuffer A pointer to buffer containing the frame. + * @param[in] aLength Length (number of bytes) in the received frame. + * + */ + void HandleSpinelFrame(const uint8_t *aFrame, uint16_t aLength); + private: enum { - kMaxSpinelFrame = 2048, ///< Max size in bytes for transferring spinel frames. + kMaxSpinelFrame = HdlcInterface::kMaxFrameSize, kMaxWaitTime = 2000, ///< Max time to wait for response in milliseconds. kVersionStringSize = 128, ///< Max size of version string. kCapsBufferSize = 100, ///< Max buffer size used to store `SPINEL_PROP_CAPS` value. @@ -451,9 +461,6 @@ private: otError CheckSpinelVersion(void); otError CheckCapabilities(void); otError CheckRadioCapabilities(void); - void DecodeHdlc(const uint8_t *aData, uint16_t aLength); - void ReadAll(void); - otError WriteAll(const uint8_t *aBuffer, uint16_t aLength); void ProcessFrameQueue(void); /** @@ -531,7 +538,6 @@ private: { static_cast(aContext)->HandleSpinelFrame(aBuffer, aLength); } - void HandleSpinelFrame(const uint8_t *aBuffer, uint16_t aLength); /** * This method returns if the property changed event is safe to be handled now. @@ -546,7 +552,7 @@ private: */ bool IsSafeToHandleNow(spinel_prop_key_t aKey) const { - return !((mIsDecoding || mWaitingKey != SPINEL_PROP_LAST_STATUS) && + return !((mHdlcInterface.IsDecoding() || mWaitingKey != SPINEL_PROP_LAST_STATUS) && (aKey == SPINEL_PROP_STREAM_RAW || aKey == SPINEL_PROP_MAC_ENERGY_SCAN_RESULT)); } @@ -562,6 +568,8 @@ private: otInstance *mInstance; + HdlcInterface mHdlcInterface; + uint16_t mCmdTidsInUse; ///< Used transaction ids. spinel_tid_t mCmdNextTid; ///< Next available transaction id. spinel_tid_t mTxRadioTid; ///< The transaction id used to send a radio frame. @@ -572,10 +580,7 @@ private: uint32_t mExpectedCommand; ///< Expected response command of current transaction. otError mError; ///< The result of current transaction. - uint8_t mHdlcBuffer[kMaxSpinelFrame]; - Hdlc::Decoder mHdlcDecoder; - Hdlc::Encoder mHdlcEncoder; - FrameQueue mFrameQueue; + FrameQueue mFrameQueue; uint8_t mRxPsdu[OT_RADIO_FRAME_MAX_SIZE]; uint8_t mTxPsdu[OT_RADIO_FRAME_MAX_SIZE]; @@ -593,10 +598,8 @@ private: otError mTxError; char mVersion[kVersionStringSize]; - int mSockFd; otRadioState mState; bool mIsAckRequested : 1; ///< Ack requested. - bool mIsDecoding : 1; ///< Decoding hdlc frames. bool mIsPromiscuous : 1; ///< Promiscuous mode. bool mIsReady : 1; ///< NCP ready. bool mSupportsLogStream : 1; ///< RCP supports `LOG_STREAM` property with OpenThread log meta-data format. @@ -608,6 +611,7 @@ private: #endif }; +} // namespace PosixApp } // namespace ot #endif // RADIO_SPINEL_HPP_