| /* |
| * 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_PERSISTENT_INTERFACE = 'p', |
| 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}, |
| {"persistent-interface", no_argument, NULL, OT_POSIX_OPT_PERSISTENT_INTERFACE}, |
| {"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" |
| " -p --persistent-interface Persistent the created thread network interface\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.mPersistentInterface = false; |
| 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:nps: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_PERSISTENT_INTERFACE: |
| aConfig->mPlatformConfig.mPersistentInterface = true; |
| 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 otError 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()); |
| |
| return OT_ERROR_NONE; |
| } |
| |
| #if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE |
| static otError 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 |
| IgnoreError(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; |
| } |