blob: df014ea5fd0ea9ad6b2f47ced20ee101806dd663 [file] [log] [blame]
/*
* Copyright (c) 2018, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "platform/openthread-posix-config.h"
#include <openthread/config.h>
#include <assert.h>
#include <errno.h>
#include <getopt.h>
#include <libgen.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#ifdef __linux__
#include <sys/prctl.h>
#endif
#ifndef HAVE_LIBEDIT
#define HAVE_LIBEDIT 0
#endif
#ifndef HAVE_LIBREADLINE
#define HAVE_LIBREADLINE 0
#endif
#include <openthread/cli.h>
#include <openthread/diag.h>
#include <openthread/logging.h>
#include <openthread/tasklet.h>
#include <openthread/thread.h>
#include <openthread/platform/radio.h>
#if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
#include <openthread/cli.h>
#include "cli/cli_config.h"
#endif
#include <common/code_utils.hpp>
#include <lib/platform/exit_code.h>
#include <openthread/openthread-system.h>
#include <openthread/platform/misc.h>
#include "lib/platform/reset_util.h"
/**
* This function initializes NCP app.
*
* @param[in] aInstance A pointer to the OpenThread instance.
*
*/
void otAppNcpInit(otInstance *aInstance);
/**
* This function deinitializes NCP app.
*
*/
void otAppNcpUpdate(otSysMainloopContext *aContext);
/**
* This function updates the file descriptor sets with file descriptors used by console.
*
* @param[in,out] aMainloop A pointer to the mainloop context.
*
*/
void otAppNcpProcess(const otSysMainloopContext *aContext);
/**
* This function initializes CLI app.
*
* @param[in] aInstance A pointer to the OpenThread instance.
*
*/
void otAppCliInit(otInstance *aInstance);
/**
* This function deinitializes CLI app.
*
*/
void otAppCliDeinit(void);
/**
* This function updates the file descriptor sets with file descriptors used by console.
*
* @param[in,out] aMainloop A pointer to the mainloop context.
*
*/
void otAppCliUpdate(otSysMainloopContext *aMainloop);
/**
* This function performs console driver processing.
*
* @param[in] aMainloop A pointer to the mainloop context.
*
*/
void otAppCliProcess(const otSysMainloopContext *aMainloop);
typedef struct PosixConfig
{
otPlatformConfig mPlatformConfig; ///< Platform configuration.
otLogLevel mLogLevel; ///< Debug level of logging.
bool mPrintRadioVersion; ///< Whether to print radio firmware version.
bool mIsVerbose; ///< Whether to print log to stderr.
} PosixConfig;
/**
* This enumeration defines the argument return values.
*
*/
enum
{
OT_POSIX_OPT_BACKBONE_INTERFACE_NAME = 'B',
OT_POSIX_OPT_DEBUG_LEVEL = 'd',
OT_POSIX_OPT_DRY_RUN = 'n',
OT_POSIX_OPT_HELP = 'h',
OT_POSIX_OPT_INTERFACE_NAME = 'I',
OT_POSIX_OPT_TIME_SPEED = 's',
OT_POSIX_OPT_VERBOSE = 'v',
OT_POSIX_OPT_SHORT_MAX = 128,
OT_POSIX_OPT_RADIO_VERSION,
OT_POSIX_OPT_REAL_TIME_SIGNAL,
};
static const struct option kOptions[] = {
{"backbone-interface-name", required_argument, NULL, OT_POSIX_OPT_BACKBONE_INTERFACE_NAME},
{"debug-level", required_argument, NULL, OT_POSIX_OPT_DEBUG_LEVEL},
{"dry-run", no_argument, NULL, OT_POSIX_OPT_DRY_RUN},
{"help", no_argument, NULL, OT_POSIX_OPT_HELP},
{"interface-name", required_argument, NULL, OT_POSIX_OPT_INTERFACE_NAME},
{"radio-version", no_argument, NULL, OT_POSIX_OPT_RADIO_VERSION},
{"real-time-signal", required_argument, NULL, OT_POSIX_OPT_REAL_TIME_SIGNAL},
{"time-speed", required_argument, NULL, OT_POSIX_OPT_TIME_SPEED},
{"verbose", no_argument, NULL, OT_POSIX_OPT_VERBOSE},
{0, 0, 0, 0}};
static void PrintUsage(const char *aProgramName, FILE *aStream, int aExitCode)
{
fprintf(aStream,
"Syntax:\n"
" %s [Options] RadioURL [RadioURL]\n"
"Options:\n"
" -B --backbone-interface-name Backbone network interface name.\n"
" -d --debug-level Debug level of logging.\n"
" -h --help Display this usage information.\n"
" -I --interface-name name Thread network interface name.\n"
" -n --dry-run Just verify if arguments is valid and radio spinel is compatible.\n"
" --radio-version Print radio firmware version.\n"
" -s --time-speed factor Time speed up factor.\n"
" -v --verbose Also log to stderr.\n",
aProgramName);
#ifdef __linux__
fprintf(aStream,
" --real-time-signal (Linux only) The real-time signal number for microsecond timer.\n"
" Use +N for relative value to SIGRTMIN, and use N for absolute value.\n");
#endif
fprintf(aStream, "%s", otSysGetRadioUrlHelpString());
exit(aExitCode);
}
static void ParseArg(int aArgCount, char *aArgVector[], PosixConfig *aConfig)
{
memset(aConfig, 0, sizeof(*aConfig));
aConfig->mPlatformConfig.mSpeedUpFactor = 1;
aConfig->mLogLevel = OT_LOG_LEVEL_CRIT;
#ifdef __linux__
aConfig->mPlatformConfig.mRealTimeSignal = SIGRTMIN;
#endif
optind = 1;
while (true)
{
int index = 0;
int option = getopt_long(aArgCount, aArgVector, "B:d:hI:ns:v", kOptions, &index);
if (option == -1)
{
break;
}
switch (option)
{
case OT_POSIX_OPT_DEBUG_LEVEL:
aConfig->mLogLevel = (otLogLevel)atoi(optarg);
break;
case OT_POSIX_OPT_HELP:
PrintUsage(aArgVector[0], stdout, OT_EXIT_SUCCESS);
break;
case OT_POSIX_OPT_INTERFACE_NAME:
aConfig->mPlatformConfig.mInterfaceName = optarg;
break;
case OT_POSIX_OPT_BACKBONE_INTERFACE_NAME:
aConfig->mPlatformConfig.mBackboneInterfaceName = optarg;
break;
case OT_POSIX_OPT_DRY_RUN:
aConfig->mPlatformConfig.mDryRun = true;
break;
case OT_POSIX_OPT_TIME_SPEED:
{
char *endptr = NULL;
aConfig->mPlatformConfig.mSpeedUpFactor = (uint32_t)strtol(optarg, &endptr, 0);
if (*endptr != '\0' || aConfig->mPlatformConfig.mSpeedUpFactor == 0)
{
fprintf(stderr, "Invalid value for TimerSpeedUpFactor: %s\n", optarg);
exit(OT_EXIT_INVALID_ARGUMENTS);
}
break;
}
case OT_POSIX_OPT_VERBOSE:
aConfig->mIsVerbose = true;
break;
case OT_POSIX_OPT_RADIO_VERSION:
aConfig->mPrintRadioVersion = true;
break;
#ifdef __linux__
case OT_POSIX_OPT_REAL_TIME_SIGNAL:
if (optarg[0] == '+')
{
aConfig->mPlatformConfig.mRealTimeSignal = SIGRTMIN + atoi(&optarg[1]);
}
else
{
aConfig->mPlatformConfig.mRealTimeSignal = atoi(optarg);
}
break;
#endif // __linux__
case '?':
PrintUsage(aArgVector[0], stderr, OT_EXIT_INVALID_ARGUMENTS);
break;
default:
assert(false);
break;
}
}
for (; optind < aArgCount; optind++)
{
VerifyOrDie(aConfig->mPlatformConfig.mRadioUrlNum < OT_ARRAY_LENGTH(aConfig->mPlatformConfig.mRadioUrls),
OT_EXIT_INVALID_ARGUMENTS);
aConfig->mPlatformConfig.mRadioUrls[aConfig->mPlatformConfig.mRadioUrlNum++] = aArgVector[optind];
}
if (aConfig->mPlatformConfig.mRadioUrlNum == 0)
{
PrintUsage(aArgVector[0], stderr, OT_EXIT_INVALID_ARGUMENTS);
}
}
static otInstance *InitInstance(PosixConfig *aConfig)
{
otInstance *instance = NULL;
syslog(LOG_INFO, "Running %s", otGetVersionString());
syslog(LOG_INFO, "Thread version: %hu", otThreadGetVersion());
IgnoreError(otLoggingSetLevel(aConfig->mLogLevel));
instance = otSysInit(&aConfig->mPlatformConfig);
VerifyOrDie(instance != NULL, OT_EXIT_FAILURE);
syslog(LOG_INFO, "Thread interface: %s", otSysGetThreadNetifName());
if (aConfig->mPrintRadioVersion)
{
printf("%s\n", otPlatRadioGetVersionString(instance));
}
else
{
syslog(LOG_INFO, "RCP version: %s", otPlatRadioGetVersionString(instance));
}
if (aConfig->mPlatformConfig.mDryRun)
{
exit(OT_EXIT_SUCCESS);
}
return instance;
}
void otTaskletsSignalPending(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
}
void otPlatReset(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
gPlatResetReason = OT_PLAT_RESET_REASON_SOFTWARE;
otSysDeinit();
longjmp(gResetJump, 1);
assert(false);
}
static void ProcessNetif(void *aContext, uint8_t aArgsLength, char *aArgs[])
{
OT_UNUSED_VARIABLE(aContext);
OT_UNUSED_VARIABLE(aArgsLength);
OT_UNUSED_VARIABLE(aArgs);
otCliOutputFormat("%s:%u\r\n", otSysGetThreadNetifName(), otSysGetThreadNetifIndex());
}
#if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
static void ProcessExit(void *aContext, uint8_t aArgsLength, char *aArgs[])
{
OT_UNUSED_VARIABLE(aContext);
OT_UNUSED_VARIABLE(aArgsLength);
OT_UNUSED_VARIABLE(aArgs);
exit(EXIT_SUCCESS);
}
#endif
static const otCliCommand kCommands[] = {
#if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
{"exit", ProcessExit},
#endif
{"netif", ProcessNetif},
};
int main(int argc, char *argv[])
{
otInstance *instance;
int rval = 0;
PosixConfig config;
#ifdef __linux__
// Ensure we terminate this process if the
// parent process dies.
prctl(PR_SET_PDEATHSIG, SIGHUP);
#endif
OT_SETUP_RESET_JUMP(argv);
ParseArg(argc, argv, &config);
openlog(argv[0], LOG_PID | (config.mIsVerbose ? LOG_PERROR : 0), LOG_DAEMON);
setlogmask(setlogmask(0) & LOG_UPTO(LOG_DEBUG));
instance = InitInstance(&config);
#if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
otAppCliInit(instance);
#endif
otCliSetUserCommands(kCommands, OT_ARRAY_LENGTH(kCommands), instance);
while (true)
{
otSysMainloopContext mainloop;
otTaskletsProcess(instance);
FD_ZERO(&mainloop.mReadFdSet);
FD_ZERO(&mainloop.mWriteFdSet);
FD_ZERO(&mainloop.mErrorFdSet);
mainloop.mMaxFd = -1;
mainloop.mTimeout.tv_sec = 10;
mainloop.mTimeout.tv_usec = 0;
#if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
otAppCliUpdate(&mainloop);
#endif
otSysMainloopUpdate(instance, &mainloop);
if (otSysMainloopPoll(&mainloop) >= 0)
{
otSysMainloopProcess(instance, &mainloop);
#if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
otAppCliProcess(&mainloop);
#endif
}
else if (errno != EINTR)
{
perror("select");
ExitNow(rval = OT_EXIT_FAILURE);
}
}
#if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
otAppCliDeinit();
#endif
exit:
otSysDeinit();
return rval;
}