mirror of
https://github.com/espressif/openthread.git
synced 2026-08-05 02:17:47 +00:00
[posix-host] add SPI interface support (#4431)
This commit is contained in:
committed by
Jonathan Hui
parent
ef4530a187
commit
f0c063f8e9
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
+117
-29
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -156,9 +156,15 @@ static void LogIfFail(const char *aText, otError aError)
|
||||
}
|
||||
}
|
||||
|
||||
void SpinelInterface::Callbacks::HandleReceivedFrame(void)
|
||||
{
|
||||
static_cast<RadioSpinel *>(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<size_t>(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS);
|
||||
|
||||
SuccessOrExit(error = mHdlcInterface.SendFrame(buffer, static_cast<uint16_t>(packed)));
|
||||
SuccessOrExit(error = mSpinelInterface.SendFrame(buffer, static_cast<uint16_t>(packed)));
|
||||
|
||||
sleep(0);
|
||||
|
||||
@@ -1328,7 +1333,7 @@ otError RadioSpinel::SendCommand(uint32_t aCommand,
|
||||
offset += static_cast<uint16_t>(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);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,14 @@
|
||||
|
||||
#include <openthread/platform/radio.h>
|
||||
|
||||
#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.
|
||||
|
||||
@@ -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 <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/file.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/ucontext.h>
|
||||
|
||||
#if OPENTHREAD_POSIX_RCP_SPI_ENABLE
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/spi/spidev.h>
|
||||
|
||||
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<useconds_t>(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<uintptr_t>(mSpiTxFrameBuffer);
|
||||
transfer[1].rx_buf = reinterpret_cast<uintptr_t>(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
|
||||
@@ -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 <openthread-system.h>
|
||||
|
||||
#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_
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user