diff --git a/.travis.yml b/.travis.yml index 0ba5cb8f9..b6e37dd55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -69,6 +69,10 @@ jobs: os: linux compiler: clang script: .travis/script.sh + - env: BUILD_TARGET="posix-app-spi" VERBOSE=1 COVERAGE=1 + os: linux + compiler: gcc + script: .travis/script.sh - env: BUILD_TARGET="android-build" VERBOSE=1 os: linux dist: trusty diff --git a/.travis/script.sh b/.travis/script.sh index 83fc469bf..2f174675a 100755 --- a/.travis/script.sh +++ b/.travis/script.sh @@ -626,6 +626,11 @@ build_samr21() { REFERENCE_DEVICE=1 COVERAGE=1 PYTHONUNBUFFERED=1 OT_NCP_PATH="$(pwd)/$(ls output/posix/*/bin/ot-ncp)" RADIO_DEVICE="$(pwd)/$(ls output/*/bin/ot-rcp)" NODE_TYPE=ncp-sim make -f src/posix/Makefile-posix check || die } +[ $BUILD_TARGET != posix-app-spi ] || { + ./bootstrap || die + REFERENCE_DEVICE=1 READLINE=readline RCP_SPI=1 make -f src/posix/Makefile-posix || die +} + [ $BUILD_TARGET != posix-ncp ] || { ./bootstrap || die REFERENCE_DEVICE=1 COVERAGE=1 PYTHONUNBUFFERED=1 NODE_TYPE=ncp-sim make -f examples/Makefile-posix check || die diff --git a/Android.mk b/Android.mk index 8ca162741..b9ef5c645 100644 --- a/Android.mk +++ b/Android.mk @@ -99,6 +99,7 @@ LOCAL_CFLAGS := \ -DOPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE=1 \ -DOPENTHREAD_FTD=1 \ -DOPENTHREAD_POSIX=1 \ + -DOPENTHREAD_POSIX_RCP_UART_ENABLE=1 \ -DSPINEL_PLATFORM_HEADER=\"spinel_platform.h\" \ $(OPENTHREAD_PROJECT_CFLAGS) \ $(NULL) @@ -242,6 +243,7 @@ LOCAL_SRC_FILES := \ src/posix/platform/misc.c \ src/posix/platform/radio_spinel.cpp \ src/posix/platform/settings.cpp \ + src/posix/platform/spi_interface.cpp \ src/posix/platform/system.c \ src/posix/platform/uart.c \ third_party/mbedtls/repo/library/md.c \ @@ -299,6 +301,7 @@ LOCAL_CFLAGS := \ -DOPENTHREAD_FTD=1 \ -DOPENTHREAD_POSIX=1 \ -DOPENTHREAD_POSIX_APP_TYPE=2 \ + -DOPENTHREAD_POSIX_RCP_UART_ENABLE=1 \ -DSPINEL_PLATFORM_HEADER=\"spinel_platform.h\" \ $(OPENTHREAD_PROJECT_CFLAGS) \ $(NULL) @@ -350,6 +353,7 @@ LOCAL_CFLAGS := \ -DOPENTHREAD_FTD=1 \ -DOPENTHREAD_POSIX=1 \ -DOPENTHREAD_POSIX_APP_TYPE=1 \ + -DOPENTHREAD_POSIX_RCP_UART_ENABLE=1 \ -DSPINEL_PLATFORM_HEADER=\"spinel_platform.h\" \ $(OPENTHREAD_PROJECT_CFLAGS) \ $(NULL) diff --git a/src/ncp/ncp_spi.hpp b/src/ncp/ncp_spi.hpp index b2f542048..5018f29ec 100644 --- a/src/ncp/ncp_spi.hpp +++ b/src/ncp/ncp_spi.hpp @@ -163,6 +163,14 @@ public: */ bool IsValid(void) const { return ((mBuffer[kIndexFlagByte] & kFlagPatternMask) == kFlagPattern); } + /** + * This method indicates whether or not the "RST" bit is set. + * + * @returns TRUE if the "RST" bit is set, FALSE otherwise. + * + */ + bool IsResetFlagSet(void) const { return ((mBuffer[kIndexFlagByte] & kFlagReset) == kFlagReset); } + /** * This method sets the "flag byte" field in the SPI frame header. * @@ -171,6 +179,14 @@ public: */ void SetHeaderFlagByte(bool aResetFlag) { mBuffer[kIndexFlagByte] = kFlagPattern | (aResetFlag ? kFlagReset : 0); } + /** + * This method gets the "flag byte" field in the SPI frame header. + * + * @returns The flag byte. + * + */ + uint8_t GetHeaderFlagByte(void) const { return mBuffer[kIndexFlagByte]; } + /** * This method sets the "accept len" field in the SPI frame header. * diff --git a/src/posix/Makefile-posix b/src/posix/Makefile-posix index e37b7f0fe..2c9a4d7b0 100644 --- a/src/posix/Makefile-posix +++ b/src/posix/Makefile-posix @@ -102,6 +102,12 @@ ifneq ($(READLINE),) configure_OPTIONS += --with-readline=$(READLINE) endif +ifeq ($(RCP_SPI),1) +COMMONCFLAGS += -DOPENTHREAD_POSIX_RCP_SPI_ENABLE=1 +else +COMMONCFLAGS += -DOPENTHREAD_POSIX_RCP_UART_ENABLE=1 +endif + ifeq ($(VIRTUAL_TIME),1) COMMONCFLAGS += -DOPENTHREAD_POSIX_VIRTUAL_TIME=1 endif diff --git a/src/posix/main.c b/src/posix/main.c index 9ccdeeb14..144de83a7 100644 --- a/src/posix/main.c +++ b/src/posix/main.c @@ -90,15 +90,48 @@ static jmp_buf gResetJump; void __gcov_flush(); +/** + * This enumeration defines the argument return values. + * + */ +enum +{ + ARG_PRINT_RADIO_VERSION = 1001, + ARG_NO_RADIO_RESET = 1002, + ARG_RESTORE_NCP_DATASET = 1003, + ARG_SPI_GPIO_INT_DEV = 1011, + ARG_SPI_GPIO_INT_LINE = 1012, + ARG_SPI_GPIO_RESET_DEV = 1013, + ARG_SPI_GPIO_RESET_LINE = 1014, + ARG_SPI_MODE = 1015, + ARG_SPI_SPEED = 1016, + ARG_SPI_CS_DELAY = 1017, + ARG_SPI_RESET_DELAY = 1018, + ARG_SPI_ALIGN_ALLOWANCE = 1019, + ARG_SPI_SMALL_PACKET = 1020, +}; + static const struct option kOptions[] = {{"debug-level", required_argument, NULL, 'd'}, {"dry-run", no_argument, NULL, 'n'}, {"help", no_argument, NULL, 'h'}, {"interface-name", required_argument, NULL, 'I'}, - {"no-reset", no_argument, NULL, 0}, - {"radio-version", no_argument, NULL, 0}, + {"no-reset", no_argument, NULL, ARG_NO_RADIO_RESET}, + {"radio-version", no_argument, NULL, ARG_PRINT_RADIO_VERSION}, + {"ncp-dataset", no_argument, NULL, ARG_RESTORE_NCP_DATASET}, {"time-speed", required_argument, NULL, 's'}, {"verbose", no_argument, NULL, 'v'}, - {"ncp-dataset", no_argument, NULL, 0}, +#if OPENTHREAD_POSIX_RCP_SPI_ENABLE + {"gpio-int-dev", required_argument, NULL, ARG_SPI_GPIO_INT_DEV}, + {"gpio-int-line", required_argument, NULL, ARG_SPI_GPIO_INT_LINE}, + {"gpio-reset-dev", required_argument, NULL, ARG_SPI_GPIO_RESET_DEV}, + {"gpio-reset-line", required_argument, NULL, ARG_SPI_GPIO_RESET_LINE}, + {"spi-mode", required_argument, NULL, ARG_SPI_MODE}, + {"spi-speed", required_argument, NULL, ARG_SPI_SPEED}, + {"spi-cs-delay", required_argument, NULL, ARG_SPI_CS_DELAY}, + {"spi-reset-delay", required_argument, NULL, ARG_SPI_RESET_DELAY}, + {"spi-align-allowance", required_argument, NULL, ARG_SPI_ALIGN_ALLOWANCE}, + {"spi-small-packet", required_argument, NULL, ARG_SPI_SMALL_PACKET}, +#endif {0, 0, 0, 0}}; static void PrintUsage(const char *aProgramName, FILE *aStream, int aExitCode) @@ -107,15 +140,38 @@ static void PrintUsage(const char *aProgramName, FILE *aStream, int aExitCode) "Syntax:\n" " %s [Options] NodeId|Device|Command [DeviceConfig|CommandArgs]\n" "Options:\n" - " -I --interface-name name Thread network interface name.\n" - " -d --debug-level Debug level of logging.\n" - " -n --dry-run Just verify if arguments is valid and radio spinel is compatible.\n" - " --no-reset Do not reset RCP on initialization\n" - " --radio-version Print radio firmware version\n" - " --ncp-dataset Retrieve and save NCP dataset to file\n" - " -s --time-speed factor Time speed up factor.\n" - " -v --verbose Also log to stderr.\n" - " -h --help Display this usage information.\n", + " -I --interface-name name Thread network interface name.\n" + " -d --debug-level Debug level of logging.\n" + " -n --dry-run Just verify if arguments is valid and radio spinel is compatible.\n" + " --no-reset Do not send Spinel reset command to RCP on initialization.\n" + " --radio-version Print radio firmware version.\n" + " --ncp-dataset Retrieve and save NCP dataset to file.\n" + " -s --time-speed factor Time speed up factor.\n" + " -v --verbose Also log to stderr.\n" +#if OPENTHREAD_POSIX_RCP_SPI_ENABLE + " --gpio-int-dev[=gpio-device-path]\n" + " Specify a path to the Linux sysfs-exported GPIO device for the\n" + " `I̅N̅T̅` pin. If not specified, `SPI` interface will fall back to\n" + " polling, which is inefficient.\n" + " --gpio-int-line[=line-offset]\n" + " The offset index of `I̅N̅T̅` pin for the associated GPIO device.\n" + " If not specified, `SPI` interface will fall back to polling,\n" + " which is inefficient.\n" + " --gpio-reset-dev[=gpio-device-path]\n" + " Specify a path to the Linux sysfs-exported GPIO device for the\n" + " `R̅E̅S̅` pin.\n" + " --gpio-reset-line[=line-offset]" + " The offset index of `R̅E̅S̅` pin for the associated GPIO device.\n" + " --spi-mode[=mode] Specify the SPI mode to use (0-3).\n" + " --spi-speed[=hertz] Specify the SPI speed in hertz.\n" + " --spi-cs-delay[=usec] Specify the delay after C̅S̅ assertion, in µsec.\n" + " --spi-reset-delay[=ms] Specify the delay after R̅E̅S̅E̅T̅ assertion, in milliseconds.\n" + " --spi-align-allowance[=n] Specify the maximum number of 0xFF bytes to clip from start of\n" + " MISO frame. Max value is 16.\n" + " --spi-small-packet=[n] Specify the smallest packet we can receive in a single transaction.\n" + " (larger packets will require two transactions). Default value is 32.\n" +#endif + " -h --help Display this usage information.\n", aProgramName); exit(aExitCode); } @@ -124,9 +180,15 @@ static void ParseArg(int aArgCount, char *aArgVector[], PosixConfig *aConfig) { memset(aConfig, 0, sizeof(PosixConfig)); - aConfig->mPlatformConfig.mSpeedUpFactor = 1; - aConfig->mPlatformConfig.mResetRadio = true; - aConfig->mLogLevel = OT_LOG_LEVEL_CRIT; + aConfig->mPlatformConfig.mSpeedUpFactor = 1; + aConfig->mPlatformConfig.mResetRadio = true; + aConfig->mPlatformConfig.mSpiSpeed = OT_PLATFORM_CONFIG_SPI_DEFAULT_SPEED_HZ; + aConfig->mPlatformConfig.mSpiCsDelay = OT_PLATFORM_CONFIG_SPI_DEFAULT_CS_DELAY_US; + aConfig->mPlatformConfig.mSpiResetDelay = OT_PLATFORM_CONFIG_SPI_DEFAULT_RESET_DELAY_MS; + aConfig->mPlatformConfig.mSpiAlignAllowance = OT_PLATFORM_CONFIG_SPI_DEFAULT_ALIGN_ALLOWANCE; + aConfig->mPlatformConfig.mSpiSmallPacketSize = OT_PLATFORM_CONFIG_SPI_DEFAULT_SMALL_PACKET_SIZE; + aConfig->mPlatformConfig.mSpiMode = OT_PLATFORM_CONFIG_SPI_DEFAULT_MODE; + aConfig->mLogLevel = OT_LOG_LEVEL_CRIT; optind = 1; @@ -170,21 +232,47 @@ static void ParseArg(int aArgCount, char *aArgVector[], PosixConfig *aConfig) case 'v': aConfig->mIsVerbose = true; break; - - case 0: - if (!strcmp(kOptions[index].name, "radio-version")) - { - aConfig->mPrintRadioVersion = true; - } - else if (!strcmp(kOptions[index].name, "no-reset")) - { - aConfig->mPlatformConfig.mResetRadio = false; - } - else if (!strcmp(kOptions[index].name, "ncp-dataset")) - { - aConfig->mPlatformConfig.mRestoreDatasetFromNcp = true; - } + case ARG_PRINT_RADIO_VERSION: + aConfig->mPrintRadioVersion = true; break; + case ARG_NO_RADIO_RESET: + aConfig->mPlatformConfig.mResetRadio = false; + break; + case ARG_RESTORE_NCP_DATASET: + aConfig->mPlatformConfig.mRestoreDatasetFromNcp = true; + break; +#if OPENTHREAD_POSIX_RCP_SPI_ENABLE + case ARG_SPI_GPIO_INT_DEV: + aConfig->mPlatformConfig.mSpiGpioIntDevice = optarg; + break; + case ARG_SPI_GPIO_INT_LINE: + aConfig->mPlatformConfig.mSpiGpioIntLine = (uint8_t)atoi(optarg); + break; + case ARG_SPI_GPIO_RESET_DEV: + aConfig->mPlatformConfig.mSpiGpioResetDevice = optarg; + break; + case ARG_SPI_GPIO_RESET_LINE: + aConfig->mPlatformConfig.mSpiGpioResetLine = (uint8_t)atoi(optarg); + break; + case ARG_SPI_MODE: + aConfig->mPlatformConfig.mSpiMode = (uint8_t)atoi(optarg); + break; + case ARG_SPI_SPEED: + aConfig->mPlatformConfig.mSpiSpeed = atoi(optarg); + break; + case ARG_SPI_CS_DELAY: + aConfig->mPlatformConfig.mSpiCsDelay = atoi(optarg); + break; + case ARG_SPI_RESET_DELAY: + aConfig->mPlatformConfig.mSpiResetDelay = atoi(optarg); + break; + case ARG_SPI_ALIGN_ALLOWANCE: + aConfig->mPlatformConfig.mSpiAlignAllowance = atoi(optarg); + break; + case ARG_SPI_SMALL_PACKET: + aConfig->mPlatformConfig.mSpiSmallPacketSize = atoi(optarg); + break; +#endif // OPENTHREAD_POSIX_RCP_SPI_ENABLE case '?': PrintUsage(aArgVector[0], stderr, OT_EXIT_INVALID_ARGUMENTS); break; diff --git a/src/posix/platform/CMakeLists.txt b/src/posix/platform/CMakeLists.txt index 5e32fb068..6cdc73e45 100644 --- a/src/posix/platform/CMakeLists.txt +++ b/src/posix/platform/CMakeLists.txt @@ -37,6 +37,7 @@ list(APPEND OT_PLATFORM_DEFINES "OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE=1" "OPENTHREAD_CONFIG_NCP_UART_ENABLE=1" "OPENTHREAD_POSIX=1" + "OPENTHREAD_POSIX_RCP_UART_ENABLE=1" "OPENTHREAD_PROJECT_CORE_CONFIG_FILE=\"openthread-core-posix-config.h\"" ) set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE) @@ -51,6 +52,7 @@ add_library(openthread-posix radio_spinel.cpp settings.cpp sim.c + spi_interface.cpp system.c uart.c udp.cpp diff --git a/src/posix/platform/Makefile.am b/src/posix/platform/Makefile.am index 487ac21a7..3d36340f7 100644 --- a/src/posix/platform/Makefile.am +++ b/src/posix/platform/Makefile.am @@ -51,6 +51,7 @@ libopenthread_posix_a_SOURCES = \ radio_spinel.cpp \ settings.cpp \ sim.c \ + spi_interface.cpp \ system.c \ uart.c \ udp.cpp \ @@ -73,6 +74,7 @@ dist_openthread_HEADERS = $(openthread_headers) PRETTY_FILES = \ $(libopenthread_posix_a_SOURCES) \ $(noinst_HEADERS) \ + $(openthread_HEADERS) \ $(NULL) if OPENTHREAD_BUILD_COVERAGE diff --git a/src/posix/platform/hdlc_interface.cpp b/src/posix/platform/hdlc_interface.cpp index d0e062795..fd1b87346 100644 --- a/src/posix/platform/hdlc_interface.cpp +++ b/src/posix/platform/hdlc_interface.cpp @@ -119,14 +119,16 @@ #endif // __APPLE__ +#if OPENTHREAD_POSIX_RCP_UART_ENABLE + namespace ot { namespace PosixApp { -HdlcInterface::HdlcInterface(Callbacks &aCallbacks) - : SpinelInterface() - , mCallbacks(aCallbacks) +HdlcInterface::HdlcInterface(SpinelInterface::Callbacks &aCallback, SpinelInterface::RxFrameBuffer &aFrameBuffer) + : mCallbacks(aCallback) + , mRxFrameBuffer(aFrameBuffer) , mSockFd(-1) - , mHdlcDecoder(mRxFrameBuffer, HandleHdlcFrame, this) + , mHdlcDecoder(aFrameBuffer, HandleHdlcFrame, this) { } @@ -247,14 +249,15 @@ exit: return error; } -otError HdlcInterface::WaitForFrame(struct timeval &aTimeout) +otError HdlcInterface::WaitForFrame(const struct timeval &aTimeout) { - otError error = OT_ERROR_NONE; + otError error = OT_ERROR_NONE; + struct timeval timeout = aTimeout; #if OPENTHREAD_POSIX_VIRTUAL_TIME struct Event event; - platformSimSendSleepEvent(&aTimeout); + platformSimSendSleepEvent(&timeout); platformSimReceiveEvent(&event); switch (event.mEvent) @@ -281,7 +284,7 @@ otError HdlcInterface::WaitForFrame(struct timeval &aTimeout) FD_SET(mSockFd, &read_fds); FD_SET(mSockFd, &error_fds); - rval = select(mSockFd + 1, &read_fds, NULL, &error_fds, &aTimeout); + rval = select(mSockFd + 1, &read_fds, NULL, &error_fds, &timeout); if (rval > 0) { @@ -623,3 +626,4 @@ void HdlcInterface::HandleHdlcFrame(otError aError) } // namespace PosixApp } // namespace ot +#endif // OPENTHREAD_POSIX_RCP_UART_ENABLE diff --git a/src/posix/platform/hdlc_interface.hpp b/src/posix/platform/hdlc_interface.hpp index d67ed513e..14b321aed 100644 --- a/src/posix/platform/hdlc_interface.hpp +++ b/src/posix/platform/hdlc_interface.hpp @@ -36,9 +36,10 @@ #include "platform-config.h" #include "spinel_interface.hpp" - #include "ncp/hdlc.hpp" +#if OPENTHREAD_POSIX_RCP_UART_ENABLE + namespace ot { namespace PosixApp { @@ -46,16 +47,17 @@ namespace PosixApp { * This class defines an HDLC interface to the Radio Co-processor (RCP) * */ -class HdlcInterface : public SpinelInterface +class HdlcInterface { public: /** * This constructor initializes the object. * - * @param[in] aCallback A reference to a `Callback` object. + * @param[in] aCallback A reference to a `Callback` object. + * @param[in] aFrameBuffer A reference to a `RxFrameBuffer` object. * */ - explicit HdlcInterface(Callbacks &aCallbacks); + HdlcInterface(SpinelInterface::Callbacks &aCallback, SpinelInterface::RxFrameBuffer &aFrameBuffer); /** * This destructor deinitializes the object. @@ -108,7 +110,7 @@ public: * @retval OT_ERROR_RESPONSE_TIMEOUT No spinel frame is received within @p aTimeout. * */ - otError WaitForFrame(struct timeval &aTimeout); + otError WaitForFrame(const struct timeval &aTimeout); /** * This method updates the file descriptor sets with file descriptors used by the radio driver. @@ -199,7 +201,15 @@ private: static int ForkPty(const char *aCommand, const char *aArguments); #endif - Callbacks & mCallbacks; + enum + { + kMaxFrameSize = SpinelInterface::kMaxFrameSize, + kMaxWaitTime = 2000, ///< Maximum wait time in Milliseconds for socket to become writable (see `SendFrame`). + }; + + SpinelInterface::Callbacks & mCallbacks; + SpinelInterface::RxFrameBuffer &mRxFrameBuffer; + int mSockFd; Hdlc::Decoder mHdlcDecoder; }; @@ -207,4 +217,5 @@ private: } // namespace PosixApp } // namespace ot +#endif // OPENTHREAD_POSIX_RCP_UART_ENABLE #endif // POSIX_APP_HDLC_INTERFACE_HPP_ diff --git a/src/posix/platform/openthread-system.h b/src/posix/platform/openthread-system.h index aa95c4dd0..e582cbfe3 100644 --- a/src/posix/platform/openthread-system.h +++ b/src/posix/platform/openthread-system.h @@ -84,6 +84,22 @@ enum OT_EXIT_ERROR_ERRNO = 5, }; +/** + * This enumeration represents default parameters for the SPI interface. + * + */ +enum +{ + OT_PLATFORM_CONFIG_SPI_DEFAULT_MODE = 0, ///< Default SPI Mode: CPOL=0, CPHA=0. + OT_PLATFORM_CONFIG_SPI_DEFAULT_SPEED_HZ = 1000000, ///< Default SPI speed in hertz. + OT_PLATFORM_CONFIG_SPI_DEFAULT_CS_DELAY_US = 20, ///< Default delay after SPI C̅S̅ assertion, in µsec. + OT_PLATFORM_CONFIG_SPI_DEFAULT_RESET_DELAY_MS = 0, ///< Default delay after R̅E̅S̅E̅T̅ assertion, in miliseconds. + OT_PLATFORM_CONFIG_SPI_DEFAULT_ALIGN_ALLOWANCE = + 16, ///< Default maximum number of 0xFF bytes to clip from start of MISO frame. + OT_PLATFORM_CONFIG_SPI_DEFAULT_SMALL_PACKET_SIZE = + 32, ///< Default smallest SPI packet size we can receive in a single transaction. +}; + /** * This structure represents platform specific configurations. * @@ -97,6 +113,17 @@ typedef struct otPlatformConfig const char *mRadioConfig; ///< Radio configurations. bool mResetRadio; ///< Whether to reset RCP when initializing. bool mRestoreDatasetFromNcp; ///< Whether to retrieve dataset from NCP and save to file. + + char * mSpiGpioIntDevice; ///< Path to the Linux GPIO character device for the `I̅N̅T̅` pin. + char * mSpiGpioResetDevice; ///< Path to the Linux GPIO character device for the `R̅E̅S̅E̅T̅` pin. + uint8_t mSpiGpioIntLine; ///< Line index of the `I̅N̅T̅` pin for the associated GPIO character device. + uint8_t mSpiGpioResetLine; ///< Line index of the `R̅E̅S̅E̅T̅` pin for the associated GPIO character device. + uint8_t mSpiMode; ///< SPI mode to use (0-3). + uint32_t mSpiSpeed; ///< SPI speed in hertz. + uint32_t mSpiResetDelay; ///< The delay after R̅E̅S̅E̅T̅ assertion, in miliseconds. + uint16_t mSpiCsDelay; ///< The delay after SPI C̅S̅ assertion, in µsec. + uint8_t mSpiAlignAllowance; ///< Maximum number of 0xFF bytes to clip from start of MISO frame. + uint8_t mSpiSmallPacketSize; ///< Smallest SPI packet size we can receive in a single transaction. } otPlatformConfig; /** diff --git a/src/posix/platform/radio_spinel.cpp b/src/posix/platform/radio_spinel.cpp index 040018ffa..ba6168ed8 100644 --- a/src/posix/platform/radio_spinel.cpp +++ b/src/posix/platform/radio_spinel.cpp @@ -156,9 +156,15 @@ static void LogIfFail(const char *aText, otError aError) } } +void SpinelInterface::Callbacks::HandleReceivedFrame(void) +{ + static_cast(this)->HandleReceivedFrame(); +} + RadioSpinel::RadioSpinel(void) : mInstance(NULL) - , mHdlcInterface(*this) + , mRxFrameBuffer() + , mSpinelInterface(*this, mRxFrameBuffer) , mCmdTidsInUse(0) , mCmdNextTid(1) , mTxRadioTid(0) @@ -192,7 +198,7 @@ void RadioSpinel::Init(const otPlatformConfig &aPlatformConfig) otError error = OT_ERROR_NONE; bool isRcp; - SuccessOrExit(error = mHdlcInterface.Init(aPlatformConfig)); + SuccessOrExit(error = mSpinelInterface.Init(aPlatformConfig)); if (aPlatformConfig.mResetRadio) { @@ -339,19 +345,18 @@ exit: void RadioSpinel::Deinit(void) { - mHdlcInterface.Deinit(); + mSpinelInterface.Deinit(); // This allows implementing pseudo reset. new (this) RadioSpinel(); } void RadioSpinel::HandleReceivedFrame(void) { - otError error = OT_ERROR_NONE; - SpinelInterface::RxFrameBuffer &frameBuffer = mHdlcInterface.GetRxFrameBuffer(); - uint8_t header; - spinel_ssize_t unpacked; + otError error = OT_ERROR_NONE; + uint8_t header; + spinel_ssize_t unpacked; - unpacked = spinel_datatype_unpack(frameBuffer.GetFrame(), frameBuffer.GetLength(), "C", &header); + unpacked = spinel_datatype_unpack(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), "C", &header); VerifyOrExit(unpacked > 0 && (header & SPINEL_HEADER_FLAG) == SPINEL_HEADER_FLAG && SPINEL_HEADER_GET_IID(header) == 0, @@ -359,23 +364,23 @@ void RadioSpinel::HandleReceivedFrame(void) if (SPINEL_HEADER_GET_TID(header) == 0) { - HandleNotification(frameBuffer); + HandleNotification(mRxFrameBuffer); } else { - HandleResponse(frameBuffer.GetFrame(), frameBuffer.GetLength()); - frameBuffer.DiscardFrame(); + HandleResponse(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength()); + mRxFrameBuffer.DiscardFrame(); } exit: if (error != OT_ERROR_NONE) { - frameBuffer.DiscardFrame(); + mRxFrameBuffer.DiscardFrame(); otLogWarnPlat("Error handling hdlc frame: %s", otThreadErrorToString(error)); } } -void RadioSpinel::HandleNotification(HdlcInterface::RxFrameBuffer &aFrameBuffer) +void RadioSpinel::HandleNotification(SpinelInterface::RxFrameBuffer &aFrameBuffer) { spinel_prop_key_t key; spinel_size_t len = 0; @@ -858,12 +863,12 @@ void RadioSpinel::ProcessFrameQueue(void) uint8_t *frame = NULL; uint16_t length; - while (mHdlcInterface.GetRxFrameBuffer().GetNextSavedFrame(frame, length) == OT_ERROR_NONE) + while (mRxFrameBuffer.GetNextSavedFrame(frame, length) == OT_ERROR_NONE) { HandleNotification(frame, length); } - mHdlcInterface.GetRxFrameBuffer().ClearSavedFrames(); + mRxFrameBuffer.ClearSavedFrames(); } void RadioSpinel::RadioReceive(void) @@ -914,7 +919,7 @@ void RadioSpinel::TransmitDone(otRadioFrame *aFrame, otRadioFrame *aAckFrame, ot void RadioSpinel::UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout) { - mHdlcInterface.UpdateFdSet(aReadFdSet, aWriteFdSet, aMaxFd, aTimeout); + mSpinelInterface.UpdateFdSet(aReadFdSet, aWriteFdSet, aMaxFd, aTimeout); if (mState == kStateTransmitting) { @@ -937,7 +942,7 @@ void RadioSpinel::UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMax } } - if (mHdlcInterface.GetRxFrameBuffer().HasSavedFrame() || (mState == kStateTransmitDone)) + if (mRxFrameBuffer.HasSavedFrame() || (mState == kStateTransmitDone)) { aTimeout.tv_sec = 0; aTimeout.tv_usec = 0; @@ -946,15 +951,15 @@ void RadioSpinel::UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMax void RadioSpinel::Process(const fd_set &aReadFdSet, const fd_set &aWriteFdSet) { - if (mHdlcInterface.GetRxFrameBuffer().HasSavedFrame()) + if (mRxFrameBuffer.HasSavedFrame()) { // Handle frames received and saved during `WaitResponse()` ProcessFrameQueue(); } - mHdlcInterface.Process(aReadFdSet, aWriteFdSet); + mSpinelInterface.Process(aReadFdSet, aWriteFdSet); - if (mHdlcInterface.GetRxFrameBuffer().HasSavedFrame()) + if (mRxFrameBuffer.HasSavedFrame()) { ProcessFrameQueue(); } @@ -1236,7 +1241,7 @@ otError RadioSpinel::WaitResponse(void) do { - if (mHdlcInterface.WaitForFrame(timeout) == OT_ERROR_RESPONSE_TIMEOUT) + if (mSpinelInterface.WaitForFrame(timeout) == OT_ERROR_RESPONSE_TIMEOUT) { FreeTid(mWaitingTid); mWaitingTid = 0; @@ -1292,7 +1297,7 @@ otError RadioSpinel::SendReset(void) VerifyOrExit(packed > 0 && static_cast(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS); - SuccessOrExit(error = mHdlcInterface.SendFrame(buffer, static_cast(packed))); + SuccessOrExit(error = mSpinelInterface.SendFrame(buffer, static_cast(packed))); sleep(0); @@ -1328,7 +1333,7 @@ otError RadioSpinel::SendCommand(uint32_t aCommand, offset += static_cast(packed); } - error = mHdlcInterface.SendFrame(buffer, offset); + error = mSpinelInterface.SendFrame(buffer, offset); exit: return error; @@ -1844,7 +1849,7 @@ exit: #if OPENTHREAD_POSIX_VIRTUAL_TIME void ot::PosixApp::RadioSpinel::Process(const Event &aEvent) { - if (mHdlcInterface.GetRxFrameBuffer().HasSavedFrame()) + if (mRxFrameBuffer.HasSavedFrame()) { ProcessFrameQueue(); } @@ -1852,10 +1857,10 @@ void ot::PosixApp::RadioSpinel::Process(const Event &aEvent) // The current event can be other event types if (aEvent.mEvent == OT_SIM_EVENT_RADIO_SPINEL_WRITE) { - mHdlcInterface.ProcessReadData(aEvent.mData, aEvent.mDataLength); + mSpinelInterface.ProcessReadData(aEvent.mData, aEvent.mDataLength); } - if (mHdlcInterface.GetRxFrameBuffer().HasSavedFrame()) + if (mRxFrameBuffer.HasSavedFrame()) { ProcessFrameQueue(); } @@ -1958,17 +1963,3 @@ uint32_t otPlatRadioGetPreferredChannelMask(otInstance *aInstance) OT_UNUSED_VARIABLE(aInstance); return sRadioSpinel.GetRadioChannelMask(true); } - -/* - * Calling a pure virtual function is illegal. If the code calls a pure virtual function, then the compiler - * calls the function "__cxa_pure_virtual()" to handle it. The call to this function should never happen in - * the normal application run. If it happens it means there is a bug. - * - * This function is defined here to avoid that some libraries don't define it when COVERAGE is set to 1. - * - */ -extern "C" void __cxa_pure_virtual() -{ - otLogCritPlat("__cxa_pure_virtual() is called"); - exit(OT_EXIT_FAILURE); -} diff --git a/src/posix/platform/radio_spinel.hpp b/src/posix/platform/radio_spinel.hpp index b1263249d..05d0b660a 100644 --- a/src/posix/platform/radio_spinel.hpp +++ b/src/posix/platform/radio_spinel.hpp @@ -36,7 +36,14 @@ #include +#if OPENTHREAD_POSIX_RCP_UART_ENABLE #include "hdlc_interface.hpp" +#endif + +#if OPENTHREAD_POSIX_RCP_SPI_ENABLE +#include "spi_interface.hpp" +#endif + #include "spinel_interface.hpp" #include "ncp/ncp_config.h" #include "ncp/spinel.h" @@ -522,7 +529,7 @@ public: private: enum { - kMaxSpinelFrame = HdlcInterface::kMaxFrameSize, + kMaxSpinelFrame = SpinelInterface::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. @@ -632,7 +639,7 @@ private: return !(aKey == SPINEL_PROP_STREAM_RAW || aKey == SPINEL_PROP_MAC_ENERGY_SCAN_RESULT); } - void HandleNotification(HdlcInterface::RxFrameBuffer &aFrameBuffer); + void HandleNotification(SpinelInterface::RxFrameBuffer &aFrameBuffer); void HandleNotification(const uint8_t *aBuffer, uint16_t aLength); void HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, uint16_t aLength); @@ -657,7 +664,15 @@ private: otInstance *mInstance; - HdlcInterface mHdlcInterface; + SpinelInterface::RxFrameBuffer mRxFrameBuffer; + +#if OPENTHREAD_POSIX_RCP_UART_ENABLE + HdlcInterface mSpinelInterface; +#endif + +#if OPENTHREAD_POSIX_RCP_SPI_ENABLE + SpiInterface mSpinelInterface; +#endif uint16_t mCmdTidsInUse; ///< Used transaction ids. spinel_tid_t mCmdNextTid; ///< Next available transaction id. diff --git a/src/posix/platform/spi_interface.cpp b/src/posix/platform/spi_interface.cpp new file mode 100644 index 000000000..eb3c77c1a --- /dev/null +++ b/src/posix/platform/spi_interface.cpp @@ -0,0 +1,782 @@ +/* + * Copyright (c) 2019, 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 SPI interface to radio (RCP). + */ + +#include "openthread-core-config.h" + +#include "platform-posix.h" +#include "spi_interface.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#if OPENTHREAD_POSIX_RCP_SPI_ENABLE +#include +#include +#include + +namespace ot { +namespace PosixApp { + +SpiInterface::SpiInterface(SpinelInterface::Callbacks &aCallback, SpinelInterface::RxFrameBuffer &aFrameBuffer) + : mCallbacks(aCallback) + , mRxFrameBuffer(aFrameBuffer) + , mSpiDevFd(-1) + , mResetGpioValueFd(-1) + , mIntGpioValueFd(-1) + , mSlaveResetCount(0) + , mSpiFrameCount(0) + , mSpiValidFrameCount(0) + , mSpiGarbageFrameCount(0) + , mSpiDuplexFrameCount(0) + , mSpiUnresponsiveFrameCount(0) + , mSpiRxFrameCount(0) + , mSpiRxFrameByteCount(0) + , mSpiTxFrameCount(0) + , mSpiTxFrameByteCount(0) + , mSpiTxIsReady(false) + , mSpiTxRefusedCount(0) + , mSpiTxPayloadSize(0) + , mDidPrintRateLimitLog(false) + , mSpiSlaveDataLen(0) +{ +} + +otError SpiInterface::Init(const otPlatformConfig &aPlatformConfig) +{ + VerifyOrDie(aPlatformConfig.mSpiAlignAllowance <= kSpiAlignAllowanceMax, OT_EXIT_FAILURE); + + mSpiCsDelayUs = aPlatformConfig.mSpiCsDelay; + mSpiSmallPacketSize = aPlatformConfig.mSpiSmallPacketSize; + mSpiAlignAllowance = aPlatformConfig.mSpiAlignAllowance; + + if (aPlatformConfig.mSpiGpioIntDevice != NULL) + { + // If the interrupt pin is not set, SPI interface will use polling mode. + InitIntPin(aPlatformConfig.mSpiGpioIntDevice, aPlatformConfig.mSpiGpioIntLine); + otLogNotePlat("SPI interface enters polling mode."); + } + + InitResetPin(aPlatformConfig.mSpiGpioResetDevice, aPlatformConfig.mSpiGpioResetLine); + InitSpiDev(aPlatformConfig.mRadioFile, aPlatformConfig.mSpiMode, aPlatformConfig.mSpiSpeed); + + // Reset RCP chip. + TrigerReset(); + + // Waiting for the RCP chip starts up. + usleep(static_cast(aPlatformConfig.mSpiResetDelay) * kUsecPerMsec); + + return OT_ERROR_NONE; +} + +SpiInterface::~SpiInterface(void) +{ + Deinit(); +} + +void SpiInterface::Deinit(void) +{ + if (mSpiDevFd >= 0) + { + close(mSpiDevFd); + mSpiDevFd = -1; + } + + if (mResetGpioValueFd >= 0) + { + close(mResetGpioValueFd); + mResetGpioValueFd = -1; + } + + if (mIntGpioValueFd >= 0) + { + close(mIntGpioValueFd); + mIntGpioValueFd = -1; + } +} + +int SpiInterface::SetupGpioHandle(int aFd, uint8_t aLine, uint32_t aHandleFlags, const char *aLabel) +{ + struct gpiohandle_request req; + int ret; + + assert(strlen(aLabel) < sizeof(req.consumer_label)); + + req.flags = aHandleFlags; + req.lines = 1; + req.lineoffsets[0] = aLine; + req.default_values[0] = 1; + + snprintf(req.consumer_label, sizeof(req.consumer_label), "%s", aLabel); + + VerifyOrDie((ret = ioctl(aFd, GPIO_GET_LINEHANDLE_IOCTL, &req)) != -1, OT_EXIT_ERROR_ERRNO); + + return req.fd; +} + +int SpiInterface::SetupGpioEvent(int aFd, + uint8_t aLine, + uint32_t aHandleFlags, + uint32_t aEventFlags, + const char *aLabel) +{ + struct gpioevent_request req; + int ret; + + assert(strlen(aLabel) < sizeof(req.consumer_label)); + + req.lineoffset = aLine; + req.handleflags = aHandleFlags; + req.eventflags = aEventFlags; + snprintf(req.consumer_label, sizeof(req.consumer_label), "%s", aLabel); + + VerifyOrDie((ret = ioctl(aFd, GPIO_GET_LINEEVENT_IOCTL, &req)) != -1, OT_EXIT_ERROR_ERRNO); + + return req.fd; +} + +void SpiInterface::SetGpioValue(int aFd, uint8_t aValue) +{ + struct gpiohandle_data data; + + data.values[0] = aValue; + VerifyOrDie(ioctl(aFd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data) != -1, OT_EXIT_ERROR_ERRNO); +} + +uint8_t SpiInterface::GetGpioValue(int aFd) +{ + struct gpiohandle_data data; + + VerifyOrDie(ioctl(aFd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data) != -1, OT_EXIT_ERROR_ERRNO); + return data.values[0]; +} + +void SpiInterface::InitResetPin(const char *aCharDev, uint8_t aLine) +{ + char label[] = "SOC_THREAD_RESET"; + int fd; + + otLogDebgPlat("InitResetPin: charDev=%s, line=%" PRIu8, aCharDev, aLine); + + VerifyOrDie((aCharDev != NULL) && (aLine < GPIOHANDLES_MAX), OT_EXIT_INVALID_ARGUMENTS); + VerifyOrDie((fd = open(aCharDev, O_RDWR)) != -1, OT_EXIT_ERROR_ERRNO); + mResetGpioValueFd = SetupGpioHandle(fd, aLine, GPIOHANDLE_REQUEST_OUTPUT, label); + + close(fd); +} + +void SpiInterface::InitIntPin(const char *aCharDev, uint8_t aLine) +{ + char label[] = "THREAD_SOC_INT"; + int fd; + + otLogDebgPlat("InitIntPin: charDev=%s, line=%" PRIu8, aCharDev, aLine); + + VerifyOrDie((aCharDev != NULL) && (aLine < GPIOHANDLES_MAX), OT_EXIT_INVALID_ARGUMENTS); + VerifyOrDie((fd = open(aCharDev, O_RDWR)) != -1, OT_EXIT_ERROR_ERRNO); + + mIntGpioValueFd = SetupGpioEvent(fd, aLine, GPIOHANDLE_REQUEST_INPUT, GPIOEVENT_REQUEST_FALLING_EDGE, label); + + close(fd); +} + +void SpiInterface::InitSpiDev(const char *aPath, uint8_t aMode, uint32_t aSpeed) +{ + const uint8_t wordBits = kSpiBitsPerWord; + int fd; + + otLogDebgPlat("InitSpiDev: path=%s, mode=%" PRIu8 ", speed=%" PRIu32, aPath, aMode, aSpeed); + + VerifyOrDie((aPath != NULL) && (aMode <= kSpiModeMax), OT_EXIT_INVALID_ARGUMENTS); + VerifyOrDie((fd = open(aPath, O_RDWR | O_CLOEXEC)) != -1, OT_EXIT_ERROR_ERRNO); + VerifyOrExit(ioctl(fd, SPI_IOC_WR_MODE, &aMode) != -1, LogError("ioctl(SPI_IOC_WR_MODE)")); + VerifyOrExit(ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &aSpeed) != -1, LogError("ioctl(SPI_IOC_WR_MAX_SPEED_HZ)")); + VerifyOrExit(ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &wordBits) != -1, LogError("ioctl(SPI_IOC_WR_BITS_PER_WORD)")); + VerifyOrExit(flock(fd, LOCK_EX | LOCK_NB) != -1, LogError("flock")); + + mSpiDevFd = fd; + mSpiMode = aMode; + mSpiSpeedHz = aSpeed; + fd = -1; + +exit: + if (fd >= 0) + { + close(fd); + } +} + +void SpiInterface::TrigerReset(void) +{ + // Set Reset pin to low level. + SetGpioValue(mResetGpioValueFd, 0); + + usleep(kResetHoldOnUsec); + + // Set Reset pin to high level. + SetGpioValue(mResetGpioValueFd, 1); + + otLogNotePlat("Triggered hardware reset"); +} + +uint8_t *SpiInterface::GetRealRxFrameStart(void) +{ + uint8_t * ret = mSpiRxFrameBuffer; + const uint8_t *end = mSpiRxFrameBuffer + mSpiAlignAllowance; + + for (; ret != end && ret[0] == 0xff; ret++) + ; + + return ret; +} + +otError SpiInterface::DoSpiTransfer(uint32_t aLength) +{ + int ret; + struct spi_ioc_transfer transfer[2]; + + memset(&transfer[0], 0, sizeof(transfer)); + + // This part is the delay between C̅S̅ being asserted and the SPI clock + // starting. This is not supported by all Linux SPI drivers. + transfer[0].tx_buf = 0; + transfer[0].rx_buf = 0; + transfer[0].len = 0; + transfer[0].speed_hz = mSpiSpeedHz; + transfer[0].delay_usecs = mSpiCsDelayUs; + transfer[0].bits_per_word = kSpiBitsPerWord; + transfer[0].cs_change = false; + + // This part is the actual SPI transfer. + transfer[1].tx_buf = reinterpret_cast(mSpiTxFrameBuffer); + transfer[1].rx_buf = reinterpret_cast(mSpiRxFrameBuffer); + transfer[1].len = aLength + kSpiFrameHeaderSize + mSpiAlignAllowance; + transfer[1].speed_hz = mSpiSpeedHz; + transfer[1].delay_usecs = 0; + transfer[1].bits_per_word = kSpiBitsPerWord; + transfer[1].cs_change = false; + + if (mSpiCsDelayUs > 0) + { + // A C̅S̅ delay has been specified. Start transactions with both parts. + ret = ioctl(mSpiDevFd, SPI_IOC_MESSAGE(2), &transfer[0]); + } + else + { + // No C̅S̅ delay has been specified, so we skip the first part because it causes some SPI drivers to croak. + ret = ioctl(mSpiDevFd, SPI_IOC_MESSAGE(1), &transfer[1]); + } + + if (ret != -1) + { + otDumpDebg(OT_LOG_REGION_PLATFORM, "SPI-TX", mSpiTxFrameBuffer, transfer[1].len); + otDumpDebg(OT_LOG_REGION_PLATFORM, "SPI-RX", mSpiRxFrameBuffer, transfer[1].len); + + mSpiFrameCount++; + } + + return (ret < 0) ? OT_ERROR_FAILED : OT_ERROR_NONE; +} + +otError SpiInterface::PushPullSpi(void) +{ + otError error; + uint8_t * spiRxFrameBuffer = NULL; + uint16_t spiTransferBytes = 0; + uint8_t successfulExchanges = 0; + uint8_t slaveHeader; + uint16_t slaveAcceptLen; + Ncp::SpiFrame txFrame(mSpiTxFrameBuffer); + + if (mSpiValidFrameCount == 0) + { + // Set the reset flag to indicate to our slave that we are coming up from scratch. + txFrame.SetHeaderFlagByte(true); + } + else + { + txFrame.SetHeaderFlagByte(false); + } + + // Zero out our rx_accept and our data_len for now. + txFrame.SetHeaderAcceptLen(0); + txFrame.SetHeaderDataLen(0); + + // Sanity check. + if (mSpiSlaveDataLen > kMaxFrameSize) + { + mSpiSlaveDataLen = 0; + } + + if (mSpiTxIsReady) + { + // Go ahead and try to immediately send a frame if we have it queued up. + txFrame.SetHeaderDataLen(mSpiTxPayloadSize); + + if (mSpiTxPayloadSize > spiTransferBytes) + { + spiTransferBytes = mSpiTxPayloadSize; + } + } + + if (mSpiSlaveDataLen != 0) + { + // In a previous transaction the slave indicated it had something to send us. Make sure our transaction + // is large enough to handle it. + if (mSpiSlaveDataLen > spiTransferBytes) + { + spiTransferBytes = mSpiSlaveDataLen; + } + } + else + { + // Set up a minimum transfer size to allow small frames the slave wants to send us to be handled in a + // single transaction. + if (spiTransferBytes < mSpiSmallPacketSize) + { + spiTransferBytes = mSpiSmallPacketSize; + } + } + + txFrame.SetHeaderAcceptLen(spiTransferBytes); + + // Perform the SPI transaction. + error = DoSpiTransfer(spiTransferBytes); + + if (error != OT_ERROR_NONE) + { + otLogCritPlat("PushPullSpi:DoSpiTransfer: errno=%s", strerror(errno)); + + // Print out a helpful error message for a common error. + if ((mSpiCsDelayUs != 0) && (errno == EINVAL)) + { + otLogWarnPlat("SPI ioctl failed with EINVAL. Try adding `--spi-cs-delay=0` to command line arguments."); + } + + LogStats(); + DieNow(OT_EXIT_FAILURE); + } + + // Account for misalignment (0xFF bytes at the start) + spiRxFrameBuffer = GetRealRxFrameStart(); + + { + Ncp::SpiFrame rxFrame(spiRxFrameBuffer); + + otLogDebgPlat("spi_transfer TX: H:%02X ACCEPT:%" PRIu16 " DATA:%" PRIu16, txFrame.GetHeaderFlagByte(), + txFrame.GetHeaderAcceptLen(), txFrame.GetHeaderDataLen()); + otLogDebgPlat("spi_transfer RX: H:%02X ACCEPT:%" PRIu16 " DATA:%" PRIu16, rxFrame.GetHeaderFlagByte(), + rxFrame.GetHeaderAcceptLen(), rxFrame.GetHeaderDataLen()); + + slaveHeader = rxFrame.GetHeaderFlagByte(); + if ((slaveHeader == 0xFF) || (slaveHeader == 0x00)) + { + if ((slaveHeader == spiRxFrameBuffer[1]) && (slaveHeader == spiRxFrameBuffer[2]) && + (slaveHeader == spiRxFrameBuffer[3]) && (slaveHeader == spiRxFrameBuffer[4])) + { + // Device is off or in a bad state. In some cases may be induced by flow control. + if (mSpiSlaveDataLen == 0) + { + otLogDebgPlat("Slave did not respond to frame. (Header was all 0x%02X)", slaveHeader); + } + else + { + otLogWarnPlat("Slave did not respond to frame. (Header was all 0x%02X)", slaveHeader); + } + + mSpiUnresponsiveFrameCount++; + } + else + { + // Header is full of garbage + mSpiGarbageFrameCount++; + + otLogWarnPlat("Garbage in header : %02X %02X %02X %02X %02X", spiRxFrameBuffer[0], spiRxFrameBuffer[1], + spiRxFrameBuffer[2], spiRxFrameBuffer[3], spiRxFrameBuffer[4]); + otDumpWarn(OT_LOG_REGION_PLATFORM, "SPI-TX", mSpiTxFrameBuffer, + spiTransferBytes + kSpiFrameHeaderSize + mSpiAlignAllowance); + otDumpWarn(OT_LOG_REGION_PLATFORM, "SPI-RX", mSpiRxFrameBuffer, + spiTransferBytes + kSpiFrameHeaderSize + mSpiAlignAllowance); + } + + mSpiTxRefusedCount++; + ExitNow(); + } + + slaveAcceptLen = rxFrame.GetHeaderAcceptLen(); + mSpiSlaveDataLen = rxFrame.GetHeaderDataLen(); + + if (!rxFrame.IsValid() || (slaveAcceptLen > kMaxFrameSize) || (mSpiSlaveDataLen > kMaxFrameSize)) + { + mSpiGarbageFrameCount++; + mSpiTxRefusedCount++; + mSpiSlaveDataLen = 0; + + otLogWarnPlat("Garbage in header : %02X %02X %02X %02X %02X", spiRxFrameBuffer[0], spiRxFrameBuffer[1], + spiRxFrameBuffer[2], spiRxFrameBuffer[3], spiRxFrameBuffer[4]); + otDumpWarn(OT_LOG_REGION_PLATFORM, "SPI-TX", mSpiTxFrameBuffer, + spiTransferBytes + kSpiFrameHeaderSize + mSpiAlignAllowance); + otDumpWarn(OT_LOG_REGION_PLATFORM, "SPI-RX", mSpiRxFrameBuffer, + spiTransferBytes + kSpiFrameHeaderSize + mSpiAlignAllowance); + + ExitNow(); + } + + mSpiValidFrameCount++; + + if (rxFrame.IsResetFlagSet()) + { + mSlaveResetCount++; + + otLogNotePlat("Slave did reset (%" PRIu64 " resets so far)", mSlaveResetCount); + LogStats(); + } + + // Handle received packet, if any. + if ((mSpiSlaveDataLen != 0) && (mSpiSlaveDataLen <= txFrame.GetHeaderAcceptLen())) + { + mSpiRxFrameByteCount += mSpiSlaveDataLen; + mSpiSlaveDataLen = 0; + mSpiRxFrameCount++; + successfulExchanges++; + + HandleReceivedFrame(rxFrame); + } + } + + // Handle transmitted packet, if any. + if (mSpiTxIsReady && (mSpiTxPayloadSize == txFrame.GetHeaderDataLen())) + { + if (txFrame.GetHeaderDataLen() <= slaveAcceptLen) + { + // Our outbound packet has been successfully transmitted. Clear mSpiTxPayloadSize and mSpiTxIsReady so + // that uplayer can pull another packet for us to send. + successfulExchanges++; + + mSpiTxFrameCount++; + mSpiTxFrameByteCount += mSpiTxPayloadSize; + + mSpiTxIsReady = false; + mSpiTxPayloadSize = 0; + mSpiTxRefusedCount = 0; + } + else + { + // The slave wasn't ready for what we had to send them. Incrementing this counter will turn on rate + // limiting so that we don't waste a ton of CPU bombarding them with useless SPI transfers. + mSpiTxRefusedCount++; + } + } + + if (!mSpiTxIsReady) + { + mSpiTxRefusedCount = 0; + } + + if (successfulExchanges == 2) + { + mSpiDuplexFrameCount++; + } + +exit: + return error; +} + +bool SpiInterface::CheckInterrupt(void) +{ + return (mIntGpioValueFd >= 0) ? (GetGpioValue(mIntGpioValueFd) == kGpioIntAssertState) : true; +} + +void SpiInterface::UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout) +{ + struct timeval timeout = {kSecPerDay, 0}; + struct timeval pollingTimeout = {0, kSpiPollPeriodUs}; + + OT_UNUSED_VARIABLE(aWriteFdSet); + + if (mSpiTxIsReady) + { + // We have data to send to the slave. + timeout.tv_sec = 0; + timeout.tv_usec = 0; + } + + if (mIntGpioValueFd >= 0) + { + if (aMaxFd < mIntGpioValueFd) + { + aMaxFd = mIntGpioValueFd; + } + + if (CheckInterrupt()) + { + // Interrupt pin is asserted, set the timeout to be 0. + timeout.tv_sec = 0; + timeout.tv_usec = 0; + otLogDebgPlat("UpdateFdSet(): Interrupt."); + } + else + { + // The interrupt pin was not asserted, so we wait for the interrupt pin to be asserted by adding it to the + // read set. + FD_SET(mIntGpioValueFd, &aReadFdSet); + } + } + else if (timercmp(&pollingTimeout, &timeout, <)) + { + // In this case we don't have an interrupt, so we revert to SPI polling. + timeout = pollingTimeout; + } + + if (mSpiTxRefusedCount) + { + struct timeval minTimeout = {0, 0}; + + // We are being rate-limited by the slave. This is fairly normal behavior. Based on number of times slave has + // refused a transmission, we apply a minimum timeout. + if (mSpiTxRefusedCount < kImmediateRetryCount) + { + minTimeout.tv_usec = kImmediateRetryTimeoutUs; + } + else if (mSpiTxRefusedCount < kFastRetryCount) + { + minTimeout.tv_usec = kFastRetryTimeoutUs; + } + else + { + minTimeout.tv_usec = kSlowRetryTimeoutUs; + } + + if (timercmp(&timeout, &minTimeout, <)) + { + timeout = minTimeout; + } + + if (mSpiTxIsReady && !mDidPrintRateLimitLog && (mSpiTxRefusedCount > 1)) + { + // To avoid printing out this message over and over, we only print it out once the refused count is at two + // or higher when we actually have something to send the slave. And then, we only print it once. + otLogInfoPlat("Slave is rate limiting transactions"); + + mDidPrintRateLimitLog = true; + } + + if (mSpiTxRefusedCount == kSpiTxRefuseWarnCount) + { + // Ua-oh. The slave hasn't given us a chance to send it anything for over thirty frames. If this ever + // happens, print out a warning to the logs. + otLogWarnPlat("Slave seems stuck."); + } + else if (mSpiTxRefusedCount == kSpiTxRefuseExitCount) + { + // Double ua-oh. The slave hasn't given us a chance to send it anything for over a hundred frames. + // This almost certainly means that the slave has locked up or gotten into an unrecoverable state. + DieNowWithMessage("Slave seems REALLY stuck.", OT_EXIT_FAILURE); + } + } + else + { + mDidPrintRateLimitLog = false; + } + + if (timercmp(&timeout, &aTimeout, <)) + { + aTimeout = timeout; + } +} + +void SpiInterface::Process(const fd_set &aReadFdSet, const fd_set &aWriteFdSet) +{ + OT_UNUSED_VARIABLE(aWriteFdSet); + + if (FD_ISSET(mIntGpioValueFd, &aReadFdSet)) + { + struct gpioevent_data event; + + otLogDebgPlat("Process(): Interrupt."); + + // Read event data to clear interrupt. + VerifyOrDie(read(mIntGpioValueFd, &event, sizeof(event)) != -1, OT_EXIT_ERROR_ERRNO); + } + + // Service the SPI port if we can receive a packet or we have a packet to be sent. + if (mSpiTxIsReady || CheckInterrupt()) + { + // We guard this with the above check because we don't want to overwrite any previously received frames. + PushPullSpi(); + } +} + +otError SpiInterface::WaitForFrame(const struct timeval &aTimeout) +{ + otError error = OT_ERROR_NONE; + struct timeval timeout = {kSecPerDay, 0}; + fd_set readFdSet; + int ret; + + FD_ZERO(&readFdSet); + + if (mIntGpioValueFd >= 0) + { + if (CheckInterrupt()) + { + // Interrupt pin is asserted, set the timeout to be 0. + timeout.tv_sec = 0; + timeout.tv_usec = 0; + } + else + { + // The interrupt pin was not asserted, so we wait for the interrupt pin to be asserted by adding it to the + // read set. + FD_SET(mIntGpioValueFd, &readFdSet); + } + } + else + { + // In this case we don't have an interrupt, so we revert to SPI polling. + timeout.tv_sec = 0; + timeout.tv_usec = kSpiPollPeriodUs; + } + + if (timercmp(&aTimeout, &timeout, <)) + { + timeout = aTimeout; + } + + ret = select(mIntGpioValueFd + 1, &readFdSet, NULL, NULL, &timeout); + if (ret > 0) + { + if (FD_ISSET(mIntGpioValueFd, &readFdSet)) + { + struct gpioevent_data event; + + // Read event data to clear interrupt. + VerifyOrDie(read(mIntGpioValueFd, &event, sizeof(event)) != -1, OT_EXIT_FAILURE); + } + + // If we can receive a packet. + if (CheckInterrupt()) + { + otLogDebgPlat("WaitForFrame(): Interrupt."); + PushPullSpi(); + } + } + else if (ret == 0) + { + ExitNow(error = OT_ERROR_RESPONSE_TIMEOUT); + } + else if (errno != EINTR) + { + DieNow(OT_EXIT_ERROR_ERRNO); + } + +exit: + return error; +} + +otError SpiInterface::SendFrame(const uint8_t *aFrame, uint16_t aLength) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(aLength < (kMaxFrameSize - kSpiFrameHeaderSize), error = OT_ERROR_NO_BUFS); + VerifyOrExit(!mSpiTxIsReady, error = OT_ERROR_BUSY); + + memcpy(&mSpiTxFrameBuffer[kSpiFrameHeaderSize], aFrame, aLength); + + mSpiTxIsReady = true; + mSpiTxPayloadSize = aLength; + + PushPullSpi(); + +exit: + return error; +} + +void SpiInterface::HandleReceivedFrame(Ncp::SpiFrame &aSpiFrame) +{ + const uint8_t *spinelFrame = aSpiFrame.GetData(); + + for (uint16_t i = 0; i < aSpiFrame.GetHeaderDataLen(); i++) + { + if (mRxFrameBuffer.WriteByte(spinelFrame[i]) != OT_ERROR_NONE) + { + mRxFrameBuffer.DiscardFrame(); + otLogNotePlat("No enough memory buffers, drop packet"); + ExitNow(); + } + } + + mCallbacks.HandleReceivedFrame(); + +exit: + return; +} + +void SpiInterface::LogError(const char *aString) +{ + OT_UNUSED_VARIABLE(aString); + otLogWarnPlat("%s: %s", aString, strerror(errno)); +} + +void SpiInterface::LogStats(void) +{ + otLogInfoPlat("INFO: mSlaveResetCount=%" PRIu64, mSlaveResetCount); + otLogInfoPlat("INFO: mSpiFrameCount=%" PRIu64, mSpiFrameCount); + otLogInfoPlat("INFO: mSpiValidFrameCount=%" PRIu64, mSpiValidFrameCount); + otLogInfoPlat("INFO: mSpiDuplexFrameCount=%" PRIu64, mSpiDuplexFrameCount); + otLogInfoPlat("INFO: mSpiUnresponsiveFrameCount=%" PRIu64, mSpiUnresponsiveFrameCount); + otLogInfoPlat("INFO: mSpiGarbageFrameCount=%" PRIu64, mSpiGarbageFrameCount); + otLogInfoPlat("INFO: mSpiRxFrameCount=%" PRIu64, mSpiRxFrameCount); + otLogInfoPlat("INFO: mSpiRxFrameByteCount=%" PRIu64, mSpiRxFrameByteCount); + otLogInfoPlat("INFO: mSpiTxFrameCount=%" PRIu64, mSpiTxFrameCount); + otLogInfoPlat("INFO: mSpiTxFrameByteCount=%" PRIu64, mSpiTxFrameByteCount); +} +} // namespace PosixApp +} // namespace ot + +#endif // OPENTHREAD_POSIX_RCP_SPI_ENABLE diff --git a/src/posix/platform/spi_interface.hpp b/src/posix/platform/spi_interface.hpp new file mode 100644 index 000000000..3338d769d --- /dev/null +++ b/src/posix/platform/spi_interface.hpp @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2019, 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 SPI interface to radio (RCP). + */ + +#ifndef POSIX_APP_SPI_INTERFACE_HPP_ +#define POSIX_APP_SPI_INTERFACE_HPP_ + +#include "spinel_interface.hpp" +#include "ncp/hdlc.hpp" + +#include + +#if OPENTHREAD_POSIX_RCP_SPI_ENABLE + +#include "ncp/ncp_spi.hpp" + +namespace ot { +namespace PosixApp { + +/** + * This class defines an SPI interface to the Radio Co-processor (RCP). + * + */ +class SpiInterface +{ +public: + /** + * This constructor initializes the object. + * + * @param[in] aCallback A reference to a `Callback` object. + * @param[in] aFrameBuffer A reference to a `RxFrameBuffer` object. + * + */ + SpiInterface(SpinelInterface::Callbacks &aCallback, SpinelInterface::RxFrameBuffer &aFrameBuffer); + + /** + * This destructor deinitializes the object. + * + */ + ~SpiInterface(void); + + /** + * This method initializes the interface to the Radio Co-processor (RCP). + * + * @note This method should be called before reading and sending spinel frames to the interface. + * + * @param[in] aPlatformConfig Platform configuration structure. + * + * @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 otPlatformConfig &aPlatformConfig); + + /** + * This method deinitializes the interface to the RCP. + * + */ + void Deinit(void); + + /** + * This method encodes and sends a spinel frame to Radio Co-processor (RCP) over the socket. + * + * @param[in] aFrame A pointer to buffer containing the spinel frame to send. + * @param[in] aLength The length (number of bytes) in the frame. + * + * @retval OT_ERROR_NONE Successfully encoded and sent the spinel frame. + * @retval OT_ERROR_BUSY Failed due to another operation is on going. + * @retval OT_ERROR_NO_BUFS Insufficient buffer space available to encode the frame. + * @retval OT_ERROR_FAILED Failed to call the SPI driver to send the frame. + * + */ + otError SendFrame(const uint8_t *aFrame, uint16_t aLength); + + /** + * This method waits for receiving part or all of spinel frame within specified interval. + * + * @param[in] aTimeout A reference to the timeout. + * + * @retval OT_ERROR_NONE Part or all of spinel frame is received. + * @retval OT_ERROR_RESPONSE_TIMEOUT No spinel frame is received within @p aTimeout. + * + */ + otError WaitForFrame(const struct timeval &aTimeout); + + /** + * This method updates the file descriptor sets with file descriptors used by the radio driver. + * + * @param[inout] aReadFdSet A reference to the read file descriptors. + * @param[inout] aWriteFdSet A reference to the write file descriptors. + * @param[inout] aMaxFd A reference to the max file descriptor. + * @param[inout] aTimeout A reference to the timeout. + * + */ + void UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout); + + /** + * This method performs radio driver processing. + * + * @param[in] aReadFdSet A reference to the read file descriptors. + * @param[in] aWriteFdSet A reference to the write file descriptors. + * + */ + void Process(const fd_set &aReadFdSet, const fd_set &aWriteFdSet); + +private: + int SetupGpioHandle(int aFd, uint8_t aLine, uint32_t aHandleFlags, const char *aLabel); + int SetupGpioEvent(int aFd, uint8_t aLine, uint32_t aHandleFlags, uint32_t aEventFlags, const char *aLabel); + void SetGpioValue(int aFd, uint8_t aValue); + uint8_t GetGpioValue(int aFd); + + void InitResetPin(const char *aCharDev, uint8_t aLine); + void InitIntPin(const char *aCharDev, uint8_t aLine); + void InitSpiDev(const char *aPath, uint8_t aMode, uint32_t aSpeed); + void TrigerReset(void); + + uint8_t *GetRealRxFrameStart(void); + otError DoSpiTransfer(uint32_t aLength); + otError PushPullSpi(void); + + bool CheckInterrupt(void); + void HandleReceivedFrame(Ncp::SpiFrame &aSpiFrame); + void LogStats(void); + void LogError(const char * aString); + void LogBuffer(const char *aDesc, const uint8_t *aBuffer, uint16_t aLength, bool aForce); + + enum + { + kSpiModeMax = 3, + kSpiAlignAllowanceMax = 16, + kSpiFrameHeaderSize = 5, + kSpiBitsPerWord = 8, + kSpiTxRefuseWarnCount = 30, + kSpiTxRefuseExitCount = 100, + kImmediateRetryCount = 5, + kFastRetryCount = 15, + kDebugBytesPerLine = 16, + kGpioIntAssertState = 0, + kGpioResetAssertState = 0, + }; + + enum + { + kMsecPerSec = 1000, + kUsecPerMsec = 1000, + kSpiPollPeriodUs = kMsecPerSec * kUsecPerMsec / 30, + kSecPerDay = 60 * 60 * 24, + kResetHoldOnUsec = 10 * kUsecPerMsec, + kImmediateRetryTimeoutUs = 1 * kUsecPerMsec, + kFastRetryTimeoutUs = 10 * kUsecPerMsec, + kSlowRetryTimeoutUs = 33 * kUsecPerMsec, + }; + + enum + { + kMaxFrameSize = SpinelInterface::kMaxFrameSize, + }; + + SpinelInterface::Callbacks & mCallbacks; + SpinelInterface::RxFrameBuffer &mRxFrameBuffer; + + int mSpiDevFd; + int mResetGpioValueFd; + int mIntGpioValueFd; + + uint8_t mSpiMode; + uint8_t mSpiAlignAllowance; + uint16_t mSpiCsDelayUs; + uint16_t mSpiSmallPacketSize; + uint32_t mSpiSpeedHz; + + uint64_t mSlaveResetCount; + uint64_t mSpiFrameCount; + uint64_t mSpiValidFrameCount; + uint64_t mSpiGarbageFrameCount; + uint64_t mSpiDuplexFrameCount; + uint64_t mSpiUnresponsiveFrameCount; + uint64_t mSpiRxFrameCount; + uint64_t mSpiRxFrameByteCount; + uint64_t mSpiTxFrameCount; + uint64_t mSpiTxFrameByteCount; + + uint8_t mSpiRxFrameBuffer[kMaxFrameSize + kSpiAlignAllowanceMax]; + + bool mSpiTxIsReady; + uint16_t mSpiTxRefusedCount; + uint16_t mSpiTxPayloadSize; + uint8_t mSpiTxFrameBuffer[kMaxFrameSize + kSpiAlignAllowanceMax]; + + bool mDidPrintRateLimitLog; + uint16_t mSpiSlaveDataLen; +}; + +} // namespace PosixApp +} // namespace ot + +#endif // OPENTHREAD_POSIX_RCP_SPI_ENABLE +#endif // POSIX_APP_SPI_INTERFACE_HPP_ diff --git a/src/posix/platform/spinel_interface.hpp b/src/posix/platform/spinel_interface.hpp index f90ddbcaa..0e016c761 100644 --- a/src/posix/platform/spinel_interface.hpp +++ b/src/posix/platform/spinel_interface.hpp @@ -45,7 +45,6 @@ class SpinelInterface public: enum { - kMaxWaitTime = 2000, ///< Maximum wait time in Milliseconds for socket to become writable (see `SendFrame`). kMaxFrameSize = 2048, ///< Maximum frame size (number of bytes). }; @@ -74,106 +73,9 @@ public: * processed later. * */ - virtual void HandleReceivedFrame(void) = 0; + void HandleReceivedFrame(void); }; - - /** - * This constructor initializes the object. - * - */ - SpinelInterface() - : mRxFrameBuffer() - { - } - - /** - * This method gets the `RxFrameBuffer`. - * - * The receive frame buffer is an `Hdlc::MultiFrameBuffer` and therefore it is capable of storing multiple - * spinel frames in a FIFO queue manner. The `RxFrameBuffer` contains the received spinel frames. - * - * When during `Process()` or `WaitForFrame()` the `Callbacks::HandleReceivedFrame()` is invoked, the newly - * received spinel frame is available in the receive frame buffer. The callback is expected to either process - * and then discard the frame (using `RxFrameBuffer::DiscardFrame()` method) or save the frame - * (using `RxFrameBuffer::SaveFrame()` so that it can be read later. - * - * @returns A reference to receive frame buffer containing newly received spinel frame or previously saved spinel - * frames. - * - */ - RxFrameBuffer &GetRxFrameBuffer(void) { return mRxFrameBuffer; } - - /** - * This method initializes the interface to the Radio Co-processor (RCP) - * - * @note This method should be called before reading and sending spinel frames to the interface. - * - * @param[in] aPlatformConfig Platform configuration structure. - * - * @retval OT_ERROR_NONE The interface is initialized successfully - * @retval OT_ERROR_ALREADY The interface is already initialized. - * @retval OT_ERROR_INVALID_ARGS The device or executable cannot be found or failed to open/run. - * - */ - virtual otError Init(const otPlatformConfig &aPlatformConfig) = 0; - - /** - * This method deinitializes the interface to the Radio Co-processor (RCP). - * - */ - virtual void Deinit(void) = 0; - - /** - * This method encodes and sends a spinel frame to Radio Co-processor (RCP) over the socket. - * - * This is blocking call, i.e., if the socket is not writable, this method waits for it to become writable for - * up to `kMaxWaitTime` interval. - * - * @param[in] aFrame A pointer to buffer containing the spinel frame to send. - * @param[in] aLength The length (number of bytes) in the frame. - * - * @retval OT_ERROR_NONE Successfully encoded and sent the spinel frame. - * @retval OT_ERROR_NO_BUFS Insufficient buffer space available to encode the frame. - * @retval OT_ERROR_FAILED Failed to send due to socket not becoming writable within `kMaxWaitTime`. - * - */ - virtual otError SendFrame(const uint8_t *aFrame, uint16_t aLength) = 0; - - /** - * This method waits for receiving part or all of spinel frame within specified interval. - * - * @param[in] aTimeout A reference to the timeout. - * - * @retval OT_ERROR_NONE Part or all of spinel frame is received. - * @retval OT_ERROR_RESPONSE_TIMEOUT No spinel frame is received within @p aTimeout. - * - */ - virtual otError WaitForFrame(struct timeval &aTimeout) = 0; - - /** - * This method updates the file descriptor sets with file descriptors used by the radio driver. - * - * @param[inout] aReadFdSet A reference to the read file descriptors. - * @param[inout] aWriteFdSet A reference to the write file descriptors. - * @param[inout] aMaxFd A reference to the max file descriptor. - * @param[inout] aTimeout A reference to the timeout. - * - */ - virtual void UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout) = 0; - - /** - * This method performs radio driver processing. - * - * @param[in] aReadFdSet A reference to the read file descriptors. - * @param[in] aWriteFdSet A reference to the write file descriptors. - * - */ - virtual void Process(const fd_set &aReadFdSet, const fd_set &aWriteFdSet) = 0; - -protected: - RxFrameBuffer mRxFrameBuffer; }; - } // namespace PosixApp } // namespace ot diff --git a/tests/toranj/build.sh b/tests/toranj/build.sh index 12b5497f2..6c79e27db 100755 --- a/tests/toranj/build.sh +++ b/tests/toranj/build.sh @@ -126,7 +126,7 @@ case ${build_config} in echo "====================================================================================================" ./bootstrap || die ./configure \ - CPPFLAGS="$cppflags_config -DOPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE=1" \ + CPPFLAGS="$cppflags_config -DOPENTHREAD_CONFIG_POSIX_APP_ENABLE_PTY_DEVICE=1 -DOPENTHREAD_POSIX_RCP_UART_ENABLE=1" \ --enable-posix-app \ $configure_options || die make -j 8 || die