1 /*
2 * Copyright (c) 2018, The OpenThread Authors.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * 3. Neither the name of the copyright holder nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "platform/openthread-posix-config.h"
30
31 #include <openthread/config.h>
32
33 #include <assert.h>
34 #include <errno.h>
35 #include <getopt.h>
36 #include <libgen.h>
37 #include <setjmp.h>
38 #include <signal.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <syslog.h>
43 #include <unistd.h>
44 #ifdef __linux__
45 #include <sys/prctl.h>
46 #endif
47
48 #ifndef HAVE_LIBEDIT
49 #define HAVE_LIBEDIT 0
50 #endif
51
52 #ifndef HAVE_LIBREADLINE
53 #define HAVE_LIBREADLINE 0
54 #endif
55
56 #include <openthread/cli.h>
57 #include <openthread/diag.h>
58 #include <openthread/logging.h>
59 #include <openthread/tasklet.h>
60 #include <openthread/thread.h>
61 #include <openthread/platform/radio.h>
62 #if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
63 #include <openthread/cli.h>
64 #include "cli/cli_config.h"
65 #endif
66 #include <common/code_utils.hpp>
67 #include <common/logging.hpp>
68 #include <lib/platform/exit_code.h>
69 #include <openthread/openthread-system.h>
70 #include <openthread/platform/misc.h>
71
72 #ifndef OPENTHREAD_ENABLE_COVERAGE
73 #define OPENTHREAD_ENABLE_COVERAGE 0
74 #endif
75
76 /**
77 * This function initializes NCP app.
78 *
79 * @param[in] aInstance A pointer to the OpenThread instance.
80 *
81 */
82 void otAppNcpInit(otInstance *aInstance);
83
84 /**
85 * This function deinitializes NCP app.
86 *
87 */
88 void otAppNcpUpdate(otSysMainloopContext *aContext);
89
90 /**
91 * This function updates the file descriptor sets with file descriptors used by console.
92 *
93 * @param[inout] aMainloop A pointer to the mainloop context.
94 *
95 */
96 void otAppNcpProcess(const otSysMainloopContext *aContext);
97
98 /**
99 * This function initializes CLI app.
100 *
101 * @param[in] aInstance A pointer to the OpenThread instance.
102 *
103 */
104 void otAppCliInit(otInstance *aInstance);
105
106 /**
107 * This function deinitializes CLI app.
108 *
109 */
110 void otAppCliDeinit(void);
111
112 /**
113 * This function updates the file descriptor sets with file descriptors used by console.
114 *
115 * @param[inout] aMainloop A pointer to the mainloop context.
116 *
117 */
118 void otAppCliUpdate(otSysMainloopContext *aMainloop);
119
120 /**
121 * This function performs console driver processing.
122 *
123 * @param[in] aMainloop A pointer to the mainloop context.
124 *
125 */
126 void otAppCliProcess(const otSysMainloopContext *aMainloop);
127
128 typedef struct PosixConfig
129 {
130 otPlatformConfig mPlatformConfig; ///< Platform configuration.
131 otLogLevel mLogLevel; ///< Debug level of logging.
132 bool mIsDryRun; ///< Dry run.
133 bool mPrintRadioVersion; ///< Whether to print radio firmware version.
134 bool mIsVerbose; ///< Whether to print log to stderr.
135 } PosixConfig;
136
137 static jmp_buf gResetJump;
138
139 void __gcov_flush();
140
141 /**
142 * This enumeration defines the argument return values.
143 *
144 */
145 enum
146 {
147 OT_POSIX_OPT_BACKBONE_INTERFACE_NAME = 'B',
148 OT_POSIX_OPT_DEBUG_LEVEL = 'd',
149 OT_POSIX_OPT_DRY_RUN = 'n',
150 OT_POSIX_OPT_HELP = 'h',
151 OT_POSIX_OPT_INTERFACE_NAME = 'I',
152 OT_POSIX_OPT_TIME_SPEED = 's',
153 OT_POSIX_OPT_VERBOSE = 'v',
154
155 OT_POSIX_OPT_SHORT_MAX = 128,
156
157 OT_POSIX_OPT_RADIO_VERSION,
158 OT_POSIX_OPT_REAL_TIME_SIGNAL,
159 };
160
161 static const struct option kOptions[] = {
162 {"backbone-interface-name", required_argument, NULL, OT_POSIX_OPT_BACKBONE_INTERFACE_NAME},
163 {"debug-level", required_argument, NULL, OT_POSIX_OPT_DEBUG_LEVEL},
164 {"dry-run", no_argument, NULL, OT_POSIX_OPT_DRY_RUN},
165 {"help", no_argument, NULL, OT_POSIX_OPT_HELP},
166 {"interface-name", required_argument, NULL, OT_POSIX_OPT_INTERFACE_NAME},
167 {"radio-version", no_argument, NULL, OT_POSIX_OPT_RADIO_VERSION},
168 {"real-time-signal", required_argument, NULL, OT_POSIX_OPT_REAL_TIME_SIGNAL},
169 {"time-speed", required_argument, NULL, OT_POSIX_OPT_TIME_SPEED},
170 {"verbose", no_argument, NULL, OT_POSIX_OPT_VERBOSE},
171 {0, 0, 0, 0}};
172
PrintUsage(const char * aProgramName,FILE * aStream,int aExitCode)173 static void PrintUsage(const char *aProgramName, FILE *aStream, int aExitCode)
174 {
175 fprintf(aStream,
176 "Syntax:\n"
177 " %s [Options] RadioURL [RadioURL]\n"
178 "Options:\n"
179 " -B --backbone-interface-name Backbone network interface name.\n"
180 " -d --debug-level Debug level of logging.\n"
181 " -h --help Display this usage information.\n"
182 " -I --interface-name name Thread network interface name.\n"
183 " -n --dry-run Just verify if arguments is valid and radio spinel is compatible.\n"
184 " --radio-version Print radio firmware version.\n"
185 " -s --time-speed factor Time speed up factor.\n"
186 " -v --verbose Also log to stderr.\n",
187 aProgramName);
188 #ifdef __linux__
189 fprintf(aStream,
190 " --real-time-signal (Linux only) The real-time signal number for microsecond timer.\n"
191 " Use +N for relative value to SIGRTMIN, and use N for absolute value.\n");
192
193 #endif
194 fprintf(aStream, "%s", otSysGetRadioUrlHelpString());
195 exit(aExitCode);
196 }
197
ParseArg(int aArgCount,char * aArgVector[],PosixConfig * aConfig)198 static void ParseArg(int aArgCount, char *aArgVector[], PosixConfig *aConfig)
199 {
200 memset(aConfig, 0, sizeof(*aConfig));
201
202 aConfig->mPlatformConfig.mSpeedUpFactor = 1;
203 aConfig->mLogLevel = OT_LOG_LEVEL_CRIT;
204 #ifdef __linux__
205 aConfig->mPlatformConfig.mRealTimeSignal = SIGRTMIN;
206 #endif
207
208 optind = 1;
209
210 while (true)
211 {
212 int index = 0;
213 int option = getopt_long(aArgCount, aArgVector, "B:d:hI:ns:v", kOptions, &index);
214
215 if (option == -1)
216 {
217 break;
218 }
219
220 switch (option)
221 {
222 case OT_POSIX_OPT_DEBUG_LEVEL:
223 aConfig->mLogLevel = (otLogLevel)atoi(optarg);
224 break;
225 case OT_POSIX_OPT_HELP:
226 PrintUsage(aArgVector[0], stdout, OT_EXIT_SUCCESS);
227 break;
228 case OT_POSIX_OPT_INTERFACE_NAME:
229 aConfig->mPlatformConfig.mInterfaceName = optarg;
230 break;
231 case OT_POSIX_OPT_BACKBONE_INTERFACE_NAME:
232 aConfig->mPlatformConfig.mBackboneInterfaceName = optarg;
233 break;
234 case OT_POSIX_OPT_DRY_RUN:
235 aConfig->mIsDryRun = true;
236 break;
237 case OT_POSIX_OPT_TIME_SPEED:
238 {
239 char *endptr = NULL;
240
241 aConfig->mPlatformConfig.mSpeedUpFactor = (uint32_t)strtol(optarg, &endptr, 0);
242
243 if (*endptr != '\0' || aConfig->mPlatformConfig.mSpeedUpFactor == 0)
244 {
245 fprintf(stderr, "Invalid value for TimerSpeedUpFactor: %s\n", optarg);
246 exit(OT_EXIT_INVALID_ARGUMENTS);
247 }
248 break;
249 }
250 case OT_POSIX_OPT_VERBOSE:
251 aConfig->mIsVerbose = true;
252 break;
253 case OT_POSIX_OPT_RADIO_VERSION:
254 aConfig->mPrintRadioVersion = true;
255 break;
256 #ifdef __linux__
257 case OT_POSIX_OPT_REAL_TIME_SIGNAL:
258 if (optarg[0] == '+')
259 {
260 aConfig->mPlatformConfig.mRealTimeSignal = SIGRTMIN + atoi(&optarg[1]);
261 }
262 else
263 {
264 aConfig->mPlatformConfig.mRealTimeSignal = atoi(optarg);
265 }
266 break;
267 #endif // __linux__
268 case '?':
269 PrintUsage(aArgVector[0], stderr, OT_EXIT_INVALID_ARGUMENTS);
270 break;
271 default:
272 assert(false);
273 break;
274 }
275 }
276
277 for (; optind < aArgCount; optind++)
278 {
279 VerifyOrDie(aConfig->mPlatformConfig.mRadioUrlNum < OT_ARRAY_LENGTH(aConfig->mPlatformConfig.mRadioUrls),
280 OT_EXIT_INVALID_ARGUMENTS);
281 aConfig->mPlatformConfig.mRadioUrls[aConfig->mPlatformConfig.mRadioUrlNum++] = aArgVector[optind];
282 }
283
284 if (aConfig->mPlatformConfig.mRadioUrlNum == 0)
285 {
286 PrintUsage(aArgVector[0], stderr, OT_EXIT_INVALID_ARGUMENTS);
287 }
288 }
289
InitInstance(PosixConfig * aConfig)290 static otInstance *InitInstance(PosixConfig *aConfig)
291 {
292 otInstance *instance = NULL;
293
294 syslog(LOG_INFO, "Running %s", otGetVersionString());
295 syslog(LOG_INFO, "Thread version: %hu", otThreadGetVersion());
296 IgnoreError(otLoggingSetLevel(aConfig->mLogLevel));
297
298 instance = otSysInit(&aConfig->mPlatformConfig);
299 VerifyOrDie(instance != NULL, OT_EXIT_FAILURE);
300 syslog(LOG_INFO, "Thread interface: %s", otSysGetThreadNetifName());
301
302 if (aConfig->mPrintRadioVersion)
303 {
304 printf("%s\n", otPlatRadioGetVersionString(instance));
305 }
306 else
307 {
308 syslog(LOG_INFO, "RCP version: %s", otPlatRadioGetVersionString(instance));
309 }
310
311 if (aConfig->mIsDryRun)
312 {
313 exit(OT_EXIT_SUCCESS);
314 }
315
316 return instance;
317 }
318
otTaskletsSignalPending(otInstance * aInstance)319 void otTaskletsSignalPending(otInstance *aInstance)
320 {
321 OT_UNUSED_VARIABLE(aInstance);
322 }
323
otPlatReset(otInstance * aInstance)324 void otPlatReset(otInstance *aInstance)
325 {
326 OT_UNUSED_VARIABLE(aInstance);
327
328 gPlatResetReason = OT_PLAT_RESET_REASON_SOFTWARE;
329
330 otSysDeinit();
331
332 longjmp(gResetJump, 1);
333 assert(false);
334 }
335
ProcessNetif(void * aContext,uint8_t aArgsLength,char * aArgs[])336 static void ProcessNetif(void *aContext, uint8_t aArgsLength, char *aArgs[])
337 {
338 OT_UNUSED_VARIABLE(aContext);
339 OT_UNUSED_VARIABLE(aArgsLength);
340 OT_UNUSED_VARIABLE(aArgs);
341
342 otCliOutputFormat("%s:%u\r\n", otSysGetThreadNetifName(), otSysGetThreadNetifIndex());
343 }
344
345 #if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
ProcessExit(void * aContext,uint8_t aArgsLength,char * aArgs[])346 static void ProcessExit(void *aContext, uint8_t aArgsLength, char *aArgs[])
347 {
348 OT_UNUSED_VARIABLE(aContext);
349 OT_UNUSED_VARIABLE(aArgsLength);
350 OT_UNUSED_VARIABLE(aArgs);
351
352 exit(EXIT_SUCCESS);
353 }
354 #endif
355
356 static const otCliCommand kCommands[] = {
357 #if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
358 {"exit", ProcessExit},
359 #endif
360 {"netif", ProcessNetif},
361 };
362
main(int argc,char * argv[])363 int main(int argc, char *argv[])
364 {
365 otInstance *instance;
366 int rval = 0;
367 PosixConfig config;
368
369 #ifdef __linux__
370 // Ensure we terminate this process if the
371 // parent process dies.
372 prctl(PR_SET_PDEATHSIG, SIGHUP);
373 #endif
374
375 if (setjmp(gResetJump))
376 {
377 alarm(0);
378 #if OPENTHREAD_ENABLE_COVERAGE
379 __gcov_flush();
380 #endif
381 execvp(argv[0], argv);
382 }
383
384 ParseArg(argc, argv, &config);
385 openlog(argv[0], LOG_PID | (config.mIsVerbose ? LOG_PERROR : 0), LOG_DAEMON);
386 setlogmask(setlogmask(0) & LOG_UPTO(LOG_DEBUG));
387 instance = InitInstance(&config);
388
389 #if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
390 otAppCliInit(instance);
391 #endif
392 otCliSetUserCommands(kCommands, OT_ARRAY_LENGTH(kCommands), instance);
393
394 while (true)
395 {
396 otSysMainloopContext mainloop;
397
398 otTaskletsProcess(instance);
399
400 FD_ZERO(&mainloop.mReadFdSet);
401 FD_ZERO(&mainloop.mWriteFdSet);
402 FD_ZERO(&mainloop.mErrorFdSet);
403
404 mainloop.mMaxFd = -1;
405 mainloop.mTimeout.tv_sec = 10;
406 mainloop.mTimeout.tv_usec = 0;
407
408 #if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
409 otAppCliUpdate(&mainloop);
410 #endif
411
412 otSysMainloopUpdate(instance, &mainloop);
413
414 if (otSysMainloopPoll(&mainloop) >= 0)
415 {
416 otSysMainloopProcess(instance, &mainloop);
417 #if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
418 otAppCliProcess(&mainloop);
419 #endif
420 }
421 else if (errno != EINTR)
422 {
423 perror("select");
424 ExitNow(rval = OT_EXIT_FAILURE);
425 }
426 }
427
428 #if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
429 otAppCliDeinit();
430 #endif
431
432 exit:
433 otSysDeinit();
434
435 return rval;
436 }
437