Native simulator: Add first version in-tree

Add the first version of the native simulator.
The simultaor is taken as is from
https://github.com/BabbleSim/native_simulator/
sha: 74986abfe088a1780e604dae65f87470b4c2a0eb

Signed-off-by: Alberto Escolar Piedras <alberto.escolar.piedras@nordicsemi.no>
This commit is contained in:
Alberto Escolar Piedras 2023-05-26 18:04:42 +02:00 committed by Chris Friedt
commit 850fc2f22f
37 changed files with 4180 additions and 0 deletions

11
scripts/native_simulator/.gitignore vendored Normal file
View file

@ -0,0 +1,11 @@
_build/*
*.o
*.a
*.d
*.elf
*.gcda
*.gcno
*~
~*.~*
/.*project
.settings/*

View file

@ -0,0 +1,127 @@
# Copyright 2023 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
# Native Simulator (NSI) Makefile.
# It builds the simulator runner itself, and produces the final
# Linux executable by linking it to the the embedded cpu library
# By default all the build output is placed under the _build folder, but the user can override it
# setting the NSI_BUILD_PATH
NSI_CONFIG_FILE?=nsi_config
-include ${NSI_CONFIG_FILE}
NSI_PATH?=./
NSI_BUILD_PATH?=$(abspath _build/)
EXE_NAME?=native_simulator.exe
NSI_EXE?=${NSI_BUILD_PATH}/${EXE_NAME}
NSI_EMBEDDED_CPU_SW?=
NSI_ARCH?=-m32
NSI_COVERAGE?=--coverage
NSI_BUILD_OPTIONS?=${NSI_ARCH} ${NSI_COVERAGE}
NSI_LINK_OPTIONS?=${NSI_ARCH} ${NSI_COVERAGE}
NSI_EXTRA_SRCS?=
NSI_EXTRA_LIBS?=
SHELL?=bash
NSI_CC?=gcc
NSI_AR?=ar
NSI_OBJCOPY?=objcopy
no_default:
@echo "There is no default rule, please specify what you want to build,\
or run make help for more info"
NSI_DEBUG?=-g
NSI_OPT?=-O0
NSI_WARNINGS?=-Wall -Wpedantic
NSI_CPPFLAGS?=-D_POSIX_C_SOURCE=200809 -D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED
NO_PIE_CO:=-fno-pie -fno-pic
DEPENDFLAGS:=-MMD -MP
CFLAGS:=${NSI_DEBUG} ${NSI_WARNINGS} ${NSI_OPT} ${NO_PIE_CO} \
-ffunction-sections -fdata-sections ${DEPENDFLAGS} -std=c11 ${NSI_BUILD_OPTIONS}
FINALLINK_FLAGS:=${NO_PIE_CO} -no-pie ${NSI_WARNINGS} \
-Wl,--gc-sections -lm -ldl -pthread \
${NSI_LINK_OPTIONS}
RUNNER_LIB:=runner.a
SRCS:=$(shell ls ${NSI_PATH}common/src/*.c ${NSI_PATH}native/src/*.c )
INCLUDES:=-I${NSI_PATH}common/src/include/ \
-I${NSI_PATH}native/src/include/ \
-I${NSI_PATH}common/src
EXTRA_OBJS:=$(abspath $(addprefix $(NSI_BUILD_PATH)/,$(sort ${NSI_EXTRA_SRCS:%.c=%.o})))
OBJS:=$(abspath $(addprefix $(NSI_BUILD_PATH)/,${SRCS:${NSI_PATH}%.c=%.o})) ${EXTRA_OBJS}
DEPENDFILES:=$(addsuffix .d,$(basename ${OBJS}))
-include ${DEPENDFILES}
${NSI_BUILD_PATH}:
@if [ ! -d ${NSI_BUILD_PATH} ]; then mkdir -p ${NSI_BUILD_PATH}; fi
#Extra sources build:
${NSI_BUILD_PATH}/%.o: /%.c ${NSI_PATH}Makefile ${NSI_CONFIG_FILE}
@if [ ! -d $(dir $@) ]; then mkdir -p $(dir $@); fi
${NSI_CC} ${NSI_CPPFLAGS} ${INCLUDES} ${CFLAGS} -c $< -o $@
${NSI_BUILD_PATH}/%.o: ${NSI_PATH}/%.c ${NSI_PATH}Makefile ${NSI_CONFIG_FILE}
@if [ ! -d $(dir $@) ]; then mkdir -p $(dir $@); fi
${NSI_CC} ${NSI_CPPFLAGS} ${INCLUDES} ${CFLAGS} -c $< -o $@
${NSI_BUILD_PATH}/linker_script.ld : ${NSI_PATH}/common/other/linker_script.pre.ld | ${NSI_BUILD_PATH}
${NSI_CC} -x c -E -P $< -o $@ ${DEPENDFLAGS}
${NSI_BUILD_PATH}/${RUNNER_LIB}: ${OBJS}
if [ -f $@ ]; then rm $@ ; fi
${NSI_AR} -cr $@ ${OBJS}
${NSI_EXE}: ${NSI_BUILD_PATH}/${RUNNER_LIB} ${NSI_EMBEDDED_CPU_SW} ${NSI_EXTRA_LIBS} \
${NSI_BUILD_PATH}/linker_script.ld
@if [ -z ${NSI_EMBEDDED_CPU_SW} ] || [ ! -f ${NSI_EMBEDDED_CPU_SW} ]; then \
echo "Error: Input embedded CPU SW not found (NSI_EMBEDDED_CPU_SW=${NSI_EMBEDDED_CPU_SW} )"; \
false; \
fi
${NSI_OBJCOPY} --localize-hidden ${NSI_EMBEDDED_CPU_SW} ${NSI_BUILD_PATH}/cpu_0.sw.o \
-w --localize-symbol=_*
${NSI_CC} -Wl,--whole-archive ${NSI_BUILD_PATH}/cpu_0.sw.o ${NSI_BUILD_PATH}/${RUNNER_LIB} \
${NSI_EXTRA_LIBS} -Wl,--no-whole-archive \
-o $@ ${FINALLINK_FLAGS} -T ${NSI_BUILD_PATH}/linker_script.ld
Makefile: ;
link_with_esw: ${NSI_EXE};
runner_lib: ${NSI_BUILD_PATH}/${RUNNER_LIB}
all: link_with_esw
clean:
@echo "Deleting intermediate compilation results + libraries + executables (*.d .o .a .exe)"
find $(NSI_BUILD_PATH) -name "*.o" -or -name "*.exe" -or -name "*.a" -or -name "*.d" | xargs rm -f
clean_coverage:
find $(NSI_BUILD_PATH) -name "*.gcda" -or -name "*.gcno" | xargs rm -f ; true
clean_all: clean clean_coverage ;
.PHONY: clean clean_coverage clean_all link_with_esw runner_lib no_default all ${DEPENDFILES}
ifndef NSI_BUILD_VERBOSE
.SILENT:
endif
help:
@echo "*******************************"
@echo "* Native Simulator makefile *"
@echo "*******************************"
@echo "Provided rules:"
@echo " clean : clean all build output"
@echo " clean_coverage : clean all coverage files"
@echo " clean_all : clean + clean_coverage"
@echo " link_with_esw : Link the runner with the CPU embedded sw"
@echo " runner_lib : Build the runner itself (pending the embedded SW)"
@echo " all : link_with_esw"
@echo "Note that you can use TAB to autocomplete rules in the command line in modern OSs"

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Linker command/script file for the native simulator runner
*/
#define NSI_INIT_LEVEL(level) \
__nsi_##level##_tasks_start = .; \
KEEP(*(SORT(.nsi_##level[0-9]_task))); \
KEEP(*(SORT(.nsi_##level[1-9][0-9]_task))); \
KEEP(*(SORT(.nsi_##level[1-9][0-9][0-9]_task))); \
SECTIONS
{
nsi_tasks :
{
__nsi_tasks_start = .;
NSI_INIT_LEVEL(PRE_BOOT_1)
NSI_INIT_LEVEL(PRE_BOOT_2)
NSI_INIT_LEVEL(HW_INIT)
NSI_INIT_LEVEL(PRE_BOOT_3)
NSI_INIT_LEVEL(FIRST_SLEEP)
NSI_INIT_LEVEL(ON_EXIT_PRE)
NSI_INIT_LEVEL(ON_EXIT_POST)
__nsi_tasks_end = .;
}
nsi_hw_events :
{
__nsi_hw_events_callbacks_start = .;
KEEP(*(SORT(.nsi_hw_event[0-9]_callback))); \
KEEP(*(SORT(.nsi_hw_event[1-9][0-9]_callback))); \
KEEP(*(SORT(.nsi_hw_event[1-9][0-9][0-9]_callback)));
__nsi_hw_events_callbacks_end = .;
__nsi_hw_events_timers_start = .;
KEEP(*(SORT(.nsi_hw_event[0-9]_timer))); \
KEEP(*(SORT(.nsi_hw_event[1-9][0-9]_timer))); \
KEEP(*(SORT(.nsi_hw_event[1-9][0-9][0-9]_timer)));
__nsi_hw_events_timers_end = .;
}
} INSERT AFTER .data;
/*
* Note this script augments the default host linker script
*/

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef NSI_COMMON_SRC_INCL_NCE_IF_H
#define NSI_COMMON_SRC_INCL_NCE_IF_H
#ifdef __cplusplus
extern "C" {
#endif
/*
* Native simulator CPU start/stop emulation module interface
*
* Check docs/NCE.md for an overview.
*
* A descriptions of each function can be found in the .c file
*/
void *nce_init(void);
void nce_terminate(void *this);
void nce_boot_cpu(void *this, void (*start_routine)(void));
void nce_halt_cpu(void *this);
void nce_wake_cpu(void *this);
int nce_is_cpu_running(void *this);
#ifdef __cplusplus
}
#endif
#endif /* NSI_COMMON_SRC_INCL_NCE_IF_H */

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef NSI_COMMON_SRC_INCL_NCT_IF_H
#define NSI_COMMON_SRC_INCL_NCT_IF_H
#ifdef __cplusplus
extern "C" {
#endif
/*
* Interface provided by the Native simulator CPU threading emulation
*
* A description of each function can be found in the C file
*
* In docs/NCT.md you can find more information
*/
void *nct_init(void (*fptr)(void *));
void nct_clean_up(void *this);
void nct_swap_threads(void *this, int next_allowed_thread_nbr);
void nct_first_thread_start(void *this, int next_allowed_thread_nbr);
int nct_new_thread(void *this, void *payload);
void nct_abort_thread(void *this, int thread_idx);
int nct_get_unique_thread_id(void *this, int thread_idx);
#ifdef __cplusplus
}
#endif
#endif /* NSI_COMMON_SRC_INCL_NCT_IF_H */

View file

@ -0,0 +1,19 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* Interfaces the Native Simulator provides to
* the embedded CPU SW
*/
#ifndef NSI_COMMON_SRC_INCL_NSI_CPU_ES_IF_H
#define NSI_COMMON_SRC_INCL_NSI_CPU_ES_IF_H
#include "nsi_tracing.h"
#include "nsi_main.h"
#include "nsi_hw_scheduler.h"
#endif /* NSI_COMMON_SRC_INCL_NSI_CPU_ES_IF_H */

View file

@ -0,0 +1,100 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef NSI_COMMON_SRC_INCL_NSI_CPU_IF_H
#define NSI_COMMON_SRC_INCL_NSI_CPU_IF_H
#ifdef __cplusplus
extern "C" {
#endif
/*
* Any symbol annotated by this macro will be visible outside of the
* embedded SW library, both by the native simulator runner,
* and other possible embedded CPU's SW.
*/
#define NATIVE_SIMULATOR_IF __attribute__((visibility("default"))) \
__attribute__((__section__(".native_sim_if")))
/*
* Implementation note:
* The interface between the embedded SW and the native simulator is allocated in its
* own section to allow the embedded software developers to, using a linker script,
* direct the linker to keep those symbols even when doing its linking with garbage collection.
* It is also be possible for the embedded SW to require the linker to keep those
* symbols by requiring each of them to be kept explicitly by name (either by defining them
* as entry points, or as required in the output).
* It is also possible for the embedded SW developers to not use garbage collection
* during their SW linking.
*/
/*
* Interfaces the Native Simulator _expects_ from the embedded CPUs:
*/
/*
* Called during the earliest initialization (before command line parsing)
*
* The embedded SW library may provide this function to perform any
* early initialization, including registering its own command line arguments
* in the runner.
*/
NATIVE_SIMULATOR_IF void nsif_cpu0_pre_cmdline_hooks(void);
/*
* Called during initialization (before the HW models are initialized)
*
* The embedded SW library may provide this function to perform any
* early initialization, after the command line arguments have been parsed.
*/
NATIVE_SIMULATOR_IF void nsif_cpu0_pre_hw_init_hooks(void);
/*
* Called by the runner to boot the CPU.
*
* The embedded SW library must provide this function.
* This function is expected to return after the embedded CPU
* has gone to sleep for the first time.
*
* The expectation is that the embedded CPU SW will spawn a
* new pthread while in this call, and run the embedded SW
* initialization in that pthread.
*
* It is recommended for the embedded SW to use the NCE (CPU start/stop emulation)
* component to achieve this.
*/
NATIVE_SIMULATOR_IF void nsif_cpu0_boot(void);
/*
* Called by the runner when the simulation is ending/exiting
*
* The embedded SW library may provide this function.
* to do any cleanup it needs.
*/
NATIVE_SIMULATOR_IF void nsif_cpu0_cleanup(void);
/*
* Called by the runner each time an interrupt is raised by the HW
*
* The embedded SW library must provide this function.
* This function is expected to return after the embedded CPU
* has gone back to sleep.
*/
NATIVE_SIMULATOR_IF void nsif_cpu0_irq_raised(void);
/*
* Called by the runner each time an interrupt is raised in SW context itself.
* That is, when a embedded SW action in the HW models, causes an immediate
* interrupt to be raised (while the execution is still in the
* context of the calling SW thread).
*/
NATIVE_SIMULATOR_IF void nsif_cpu0_irq_raised_from_sw(void);
#ifdef __cplusplus
}
#endif
#endif /* NSI_COMMON_SRC_INCL_NSI_CPU_IF_H */

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*
* The native simulator provides a set of trampolines to some of the simplest
* host C library symbols.
* These are intended to facilitate test embedded code interacting with the host.
*
* We should never include here symbols which require host headers be exposed
* to the embedded side, for example due to non-basic types being used in
* function calls, as that would break the include path isolation
*
* Naming convention: nsi_hots_<fun>() where <func> is the name of the equivalent
* C library call we call thru.
*/
#ifndef NSI_COMMON_SRC_INCL_NSI_HOST_TRAMPOLINES_H
#define NSI_COMMON_SRC_INCL_NSI_HOST_TRAMPOLINES_H
#ifdef __cplusplus
extern "C" {
#endif
/* void nsi_host_exit (int status); Use nsi_exit() instead */
/* int nsi_host_printf (const char *fmt, ...); Use the nsi_tracing.h equivalents */
long nsi_host_random(void);
void nsi_host_srandom(unsigned int seed);
#ifdef __cplusplus
}
#endif
#endif /* NSI_COMMON_SRC_INCL_NSI_HOST_TRAMPOLINES_H */

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2017 Oticon A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef NSI_COMMON_SRC_INCL_HW_SCHEDULER_H
#define NSI_COMMON_SRC_INCL_HW_SCHEDULER_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define NSI_NEVER UINT64_MAX
/* API intended for the native simulator specific embedded drivers: */
uint64_t nsi_hws_get_time(void);
/* Internal APIs to the native_simulator and its HW models: */
void nsi_hws_init(void);
void nsi_hws_cleanup(void);
void nsi_hws_one_event(void);
void nsi_hws_set_end_of_time(uint64_t new_end_of_time);
void nsi_hws_find_next_event(void);
#ifdef __cplusplus
}
#endif
#endif /* NSI_COMMON_SRC_INCL_HW_SCHEDULER_H */

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef NSI_COMMON_SRC_INCL_HWS_MODELS_IF_H
#define NSI_COMMON_SRC_INCL_HWS_MODELS_IF_H
#include <stdint.h>
#include "nsi_utils.h"
#include "nsi_hw_scheduler.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* Register an event timer and event callback
*
* The HW scheduler will keep track of this event, and call its callback whenever its
* timer is reached.
* The ordering of events in the same microsecond is given by prio (lowest first),
* and if also in the same priority by alphabetical order of the callback.
* (Normally HW models will not care about the event ordering, and will simply set a prio like 100)
*
* Only very particular models will need to execute before or after others.
*/
#define NSI_HW_EVENT(timer, fn, prio) \
static void (* const NSI_CONCAT(__nsi_hw_event_cb_, fn))(void) \
__attribute__((__used__)) \
__attribute__((__section__(".nsi_hw_event" NSI_STRINGIFY(prio) "_callback")))\
= fn; \
static uint64_t * const NSI_CONCAT(__nsi_hw_event_ti_, fn) \
__attribute__((__used__)) \
__attribute__((__section__(".nsi_hw_event" NSI_STRINGIFY(prio) "_timer")))\
= &timer
#ifdef __cplusplus
}
#endif
#endif /* NSI_COMMON_SRC_INCL_HWS_MODELS_IF_H */

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef NSI_COMMON_SRC_INCL_NSI_MAIN_H
#define NSI_COMMON_SRC_INCL_NSI_MAIN_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Terminate the execution of the native simulator
*
* exit_code: Requested exit code to the shell
* Note that other components may have requested a different
* exit code which may have precedence if it was !=0
*/
void nsi_exit(int exit_code);
#ifdef __cplusplus
}
#endif
#endif /* NSI_COMMON_SRC_INCL_NSI_MAIN_H */

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef NSI_COMMON_SRC_INCL_NSI_TRACING_H
#define NSI_COMMON_SRC_INCL_NSI_TRACING_H
#include <stdarg.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* Native simulator tracing API:
* Print a message/warning/error to the tracing backend
* and in case of nsi_print_error_and_exit() also call nsi_exit()
*
* All print()/vprint() APIs take the same arguments as printf()/vprintf().
*/
void nsi_print_error_and_exit(const char *format, ...);
void nsi_print_warning(const char *format, ...);
void nsi_print_trace(const char *format, ...);
void nsi_vprint_error_and_exit(const char *format, va_list vargs);
void nsi_vprint_warning(const char *format, va_list vargs);
void nsi_vprint_trace(const char *format, va_list vargs);
/*
* @brief Is the tracing backend connected to a ptty/terminal or not
*
* @param nbr: Which output. Options are: 0 trace output, 1: warning and error output
*
* @return
* 0 : Not a ptty (i.e. probably a pipe to another program)
* 1 : Connected to a ptty (for ex. stdout/err to the invoking terminal)
* -1: Unknown at this point
*/
int nsi_trace_over_tty(int nbr);
#ifdef __cplusplus
}
#endif
#endif /* NSI_COMMON_SRC_INCL_NSI_TRACING_H */

View file

@ -0,0 +1,144 @@
/*
* Copyright (c) 2017 Oticon A/S
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* Native simulator entry point (main)
*
* Documentation can be found starting in docs/README.md
*/
#include <stdio.h>
#include <stdlib.h>
#include "nsi_cpu_if.h"
#include "nsi_tasks.h"
#include "nsi_cmdline.h"
#include "nsi_utils.h"
#include "nsi_hw_scheduler.h"
void nsi_exit(int exit_code)
{
static int max_exit_code;
max_exit_code = NSI_MAX(exit_code, max_exit_code);
/*
* nsif_cpu0_cleanup may not return if this is called from a SW thread,
* but instead it would get nsi_exit() recalled again
* ASAP from the HW thread
*/
nsif_cpu0_cleanup();
nsi_run_tasks(NSITASK_ON_EXIT_PRE_LEVEL);
nsi_hws_cleanup();
nsi_cleanup_cmd_line();
nsi_run_tasks(NSITASK_ON_EXIT_POST_LEVEL);
exit(max_exit_code);
}
/**
* Run all early native_posix initialization steps, including command
* line parsing and CPU start, until we are ready to let the HW models
* run via hwm_one_event()
*/
static void nsi_init(int argc, char *argv[])
{
/*
* Let's ensure that even if we are redirecting to a file, we get stdout
* and stderr line buffered (default for console)
* Note that glibc ignores size. But just in case we set a reasonable
* number in case somebody tries to compile against a different library
*/
setvbuf(stdout, NULL, _IOLBF, 512);
setvbuf(stderr, NULL, _IOLBF, 512);
nsi_run_tasks(NSITASK_PRE_BOOT_1_LEVEL);
nsif_cpu0_pre_cmdline_hooks();
nsi_handle_cmd_line(argc, argv);
nsi_run_tasks(NSITASK_PRE_BOOT_2_LEVEL);
nsif_cpu0_pre_hw_init_hooks();
nsi_run_tasks(NSITASK_HW_INIT_LEVEL);
nsi_hws_init();
nsi_run_tasks(NSITASK_PRE_BOOT_3_LEVEL);
nsif_cpu0_boot();
nsi_run_tasks(NSITASK_FIRST_SLEEP_LEVEL);
}
/**
* Execute the simulator for at least the specified timeout, then
* return. Note that this does not affect event timing, so the "next
* event" may be significantly after the request if the hardware has
* not been configured to e.g. send an interrupt when expected.
*/
void nsi_exec_for(uint64_t us)
{
uint64_t start = nsi_hws_get_time();
do {
nsi_hws_one_event();
} while (nsi_hws_get_time() < (start + us));
}
#ifndef NSI_LIBFUZZER
/**
*
* Note that this main() is not used when building fuzz cases,
* as libfuzzer has its own main(),
* and calls the "OS" through a per-case fuzz test entry point.
*/
int main(int argc, char *argv[])
{
nsi_init(argc, argv);
while (true) {
nsi_hws_one_event();
}
/* This line should be unreachable */
return 1; /* LCOV_EXCL_LINE */
}
#else /* NSI_LIBFUZZER */
/**
* Entry point for fuzzing (when enabled). Works by placing the data
* into two known symbols, triggering an app-visible interrupt, and
* then letting the simulator run for a fixed amount of time (intended to be
* "long enough" to handle the event and reach a quiescent state
* again)
*/
uint8_t *nsi_fuzz_buf, nsi_fuzz_sz;
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz)
{
static bool nsi_initialized;
if (!nsi_initialized) {
nsi_init(0, NULL);
nsi_initialized = true;
}
/* Provide the fuzz data to the embedded OS as an interrupt, with
* "DMA-like" data placed into nsi_fuzz_buf/sz
*/
nsi_fuzz_buf = (void *)data;
nsi_fuzz_sz = sz;
hw_irq_ctrl_set_irq(NSI_FUZZ_IRQ);
/* Give the OS time to process whatever happened in that
* interrupt and reach an idle state.
*/
nsi_exec_for(NSI_FUZZ_TIME);
return 0;
}
#endif /* NSI_LIBFUZZER */

View file

@ -0,0 +1,292 @@
/*
* Copyright (c) 2017 Oticon A/S
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* Native simulator CPU emulator,
* an *optional* module provided by the native simulator
* the hosted embedded OS / SW can use to emulate the CPU
* being started and stopped.
*
* Its mode of operation is that it step-locks the HW
* and SW operation, so that only one of them executes at
* a time. Check the docs for more info.
*/
#include <pthread.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include "nce_if.h"
#include "nsi_safe_call.h"
struct nce_status_t {
/* Conditional variable to know if the CPU is running or halted/idling */
pthread_cond_t cond_cpu;
/* Mutex for the conditional variable cond_cpu */
pthread_mutex_t mtx_cpu;
/* Variable which tells if the CPU is halted (1) or not (0) */
bool cpu_halted;
bool terminate; /* Are we terminating the program == cleaning up */
void (*start_routine)(void);
};
#define NCE_DEBUG_PRINTS 0
#define PREFIX "NCE: "
#define ERPREFIX PREFIX"error on "
#define NO_MEM_ERR PREFIX"Can't allocate memory\n"
#if NCE_DEBUG_PRINTS
#define NCE_DEBUG(fmt, ...) nsi_print_trace(PREFIX fmt, __VA_ARGS__)
#else
#define NCE_DEBUG(...)
#endif
extern void nsi_exit(int exit_code);
/*
* Initialize an instance of the native simulator CPU emulator
* and return a pointer to it.
* That pointer should be passed to all subsequent calls to this module.
*/
void *nce_init(void)
{
struct nce_status_t *this;
this = calloc(1, sizeof(struct nce_status_t));
if (this == NULL) { /* LCOV_EXCL_BR_LINE */
nsi_print_error_and_exit(NO_MEM_ERR); /* LCOV_EXCL_LINE */
}
this->cpu_halted = true;
this->terminate = false;
NSI_SAFE_CALL(pthread_cond_init(&this->cond_cpu, NULL));
NSI_SAFE_CALL(pthread_mutex_init(&this->mtx_cpu, NULL));
return (void *)this;
}
/*
* This function will:
*
* If called from a SW thread, release the HW thread which is blocked in
* a nce_wake_cpu() and never return.
*
* If called from a HW thread, do the necessary clean up of this nce instance
* and return right away.
*/
void nce_terminate(void *this_arg)
{
struct nce_status_t *this = (struct nce_status_t *)this_arg;
/* LCOV_EXCL_START */ /* See Note1 */
/*
* If we are being called from a HW thread we can cleanup
*
* Otherwise (!cpu_halted) we give back control to the HW thread and
* tell it to terminate ASAP
*/
if (this == NULL || this->cpu_halted) {
/*
* Note: The nce_status structure cannot be safely free'd up
* as the user is allowed to call nce_clean_up()
* repeatedly on the same structure.
* Instead we rely of on the host OS process cleanup.
* If you got here due to valgrind's leak report, please use the
* provided valgrind suppression file valgrind.supp
*/
return;
} else if (this->terminate == false) {
this->terminate = true;
NSI_SAFE_CALL(pthread_mutex_lock(&this->mtx_cpu));
this->cpu_halted = true;
NSI_SAFE_CALL(pthread_cond_broadcast(&this->cond_cpu));
NSI_SAFE_CALL(pthread_mutex_unlock(&this->mtx_cpu));
while (1) {
sleep(1);
/* This SW thread will wait until being cancelled from
* the HW thread. sleep() is a cancellation point, so it
* won't really wait 1 second
*/
}
}
/* LCOV_EXCL_STOP */
}
/**
* Helper function which changes the status of the CPU (halted or running)
* and waits until somebody else changes it to the opposite
*
* Both HW and SW threads will use this function to transfer control to the
* other side.
*
* This is how the idle thread halts the CPU and gets halted until the HW models
* raise a new interrupt; and how the HW models awake the CPU, and wait for it
* to complete and go to idle.
*/
static void change_cpu_state_and_wait(struct nce_status_t *this, bool halted)
{
NSI_SAFE_CALL(pthread_mutex_lock(&this->mtx_cpu));
NCE_DEBUG("Going to halted = %d\n", halted);
this->cpu_halted = halted;
/* We let the other side know the CPU has changed state */
NSI_SAFE_CALL(pthread_cond_broadcast(&this->cond_cpu));
/* We wait until the CPU state has been changed. Either:
* we just awoke it, and therefore wait until the CPU has run until
* completion before continuing (before letting the HW models do
* anything else)
* or
* we are just hanging it, and therefore wait until the HW models awake
* it again
*/
while (this->cpu_halted == halted) {
/* Here we unlock the mutex while waiting */
pthread_cond_wait(&this->cond_cpu, &this->mtx_cpu);
}
NCE_DEBUG("Awaken after halted = %d\n", halted);
NSI_SAFE_CALL(pthread_mutex_unlock(&this->mtx_cpu));
}
/*
* Helper function that wraps the SW start_routine
*/
static void *sw_wrapper(void *this_arg)
{
struct nce_status_t *this = (struct nce_status_t *)this_arg;
/* Ensure nce_boot_cpu has reached the cond loop */
NSI_SAFE_CALL(pthread_mutex_lock(&this->mtx_cpu));
NSI_SAFE_CALL(pthread_mutex_unlock(&this->mtx_cpu));
#if (NCE_DEBUG_PRINTS)
pthread_t sw_thread = pthread_self();
NCE_DEBUG("SW init started (%lu)\n",
sw_thread);
#endif
this->start_routine();
return NULL;
}
/*
* Boot the emulated CPU, that is:
* * Spawn a new pthread which will run the first embedded SW thread <start_routine>
* * Hold the caller until that embedded SW thread (or a child it spawns)
* calls nce_halt_cpu()
*
* Note that during this, an embedded SW thread may call nsi_exit(), which would result
* in this function never returning.
*/
void nce_boot_cpu(void *this_arg, void (*start_routine)(void))
{
struct nce_status_t *this = (struct nce_status_t *)this_arg;
NSI_SAFE_CALL(pthread_mutex_lock(&this->mtx_cpu));
this->cpu_halted = false;
this->start_routine = start_routine;
/* Create a thread for the embedded SW init: */
pthread_t sw_thread;
NSI_SAFE_CALL(pthread_create(&sw_thread, NULL, sw_wrapper, this_arg));
/* And we wait until the embedded OS has send the CPU to sleep for the first time */
while (this->cpu_halted == false) {
pthread_cond_wait(&this->cond_cpu, &this->mtx_cpu);
}
NSI_SAFE_CALL(pthread_mutex_unlock(&this->mtx_cpu));
if (this->terminate) {
nsi_exit(0);
}
}
/*
* Halt the CPU, that is:
* * Hold this embedded SW thread until the CPU is awaken again,
* and release the HW thread which had been held on
* nce_boot_cpu() or nce_wake_cpu().
*
* Note: Can only be called from embedded SW threads
* Calling it from a HW thread is a programming error.
*/
void nce_halt_cpu(void *this_arg)
{
struct nce_status_t *this = (struct nce_status_t *)this_arg;
if (this->cpu_halted == true) {
nsi_print_error_and_exit("Programming error on: %s ",
"This CPU was already halted\n");
}
change_cpu_state_and_wait(this, true);
}
/*
* Awake the CPU, that is:
* * Hold this HW thread until the CPU is set to idle again
* * Release the SW thread which had been held on nce_halt_cpu()
*
* Note: Can only be called from HW threads
* Calling it from a SW thread is a programming error.
*/
void nce_wake_cpu(void *this_arg)
{
struct nce_status_t *this = (struct nce_status_t *)this_arg;
if (this->cpu_halted == false) {
nsi_print_error_and_exit("Programming error on: %s ",
"This CPU was already awake\n");
}
change_cpu_state_and_wait(this, false);
/*
* If while the SW was running it was decided to terminate the execution
* we stop immediately.
*/
if (this->terminate) {
nsi_exit(0);
}
}
/*
* Return 0 if the CPU is sleeping (or terminated)
* and !=0 if the CPU is running
*/
int nce_is_cpu_running(void *this_arg)
{
struct nce_status_t *this = (struct nce_status_t *)this_arg;
if (this != NULL) {
return !this->cpu_halted;
} else {
return false;
}
}
/*
* Notes about coverage:
*
* Note1: When the application is closed due to a SIGTERM, the path in this
* function will depend on when that signal was received. Typically during a
* regression run, both paths will be covered. But in some cases they won't.
* Therefore and to avoid confusing developers with spurious coverage changes
* we exclude this function from the coverage check
*/

View file

@ -0,0 +1,690 @@
/*
* Copyright (c) 2017 Oticon A/S
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* Native simulator, CPU Thread emulation (nct)
*/
/**
* Native simulator single CPU threading emulation,
* an *optional* module provided by the Native simulator
* the hosted embedded OS / SW can use to emulate the threading
* context switching which would be handled by a OS CPU AL
*
* Principle of operation:
*
* The embedded OS threads are run as a set of native Linux pthreads.
* The embedded OS only sees one of this thread executing at a time.
*
* The hosted OS shall call nct_init() to initialize the state of an
* instance of this module, and nct_clean_up() once it desires to destroy it.
*
* For SOCs with several micro-controllers (AMP) one instance of this module
* would be instantiated per simulated uC and embedded OS.
*
* To create a new embedded thread, the hosted OS shall call nct_new_thread().
* To swap to a thread nct_swap_threads(), and to terminate a thread
* nct_abort_thread().
* The hosted OS can optionally use nct_first_thread_start() to swap
* to the "first thread".
*
* Whenever a thread calls nct_swap_threads(next_thread_idx) it will be blocked,
* and the thread identified by next_thread_idx will continue executing.
*
*
* Internal design:
*
* Which thread is running is controlled using {cond|mtx}_threads and
* currently_allowed_thread.
*
* The main part of the execution of each thread will occur in a fully
* synchronous and deterministic manner, and only when commanded by
* the embedded operating system kernel.
*
* The creation of a thread will spawn a new pthread whose start
* is asynchronous to the rest, until synchronized in nct_wait_until_allowed()
* below.
* Similarly aborting and canceling threads execute a tail in a quite an
* asynchronous manner.
*
* This implementation is meant to be portable in between fully compatible
* POSIX systems.
* A table (threads_table) is used to abstract the native pthreads.
* An index in this table is used to identify threads in the IF to the
* embedded OS.
*/
#define NCT_DEBUG_PRINTS 0
#include <pthread.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "nct_if.h"
#include "nsi_internal.h"
#include "nsi_safe_call.h"
#if NCT_DEBUG_PRINTS
#define NCT_DEBUG(fmt, ...) nsi_print_trace(PREFIX fmt, __VA_ARGS__)
#else
#define NCT_DEBUG(...)
#endif
#define PREFIX "Tread Simulator: "
#define ERPREFIX PREFIX"error on "
#define NO_MEM_ERR PREFIX"Can't allocate memory\n"
#define NCT_ALLOC_CHUNK_SIZE 64 /* In how big chunks we grow the thread table */
#define NCT_REUSE_ABORTED_ENTRIES 0
/* For the Zephyr OS, tests/kernel/threads/scheduling/schedule_api fails when setting
* NCT_REUSE_ABORTED_ENTRIES => don't set it by now
*/
struct te_status_t;
struct threads_table_el {
/* Pointer to the overall status of the threading emulator instance */
struct te_status_t *ts_status;
struct threads_table_el *next; /* Pointer to the next element of the table */
int thread_idx; /* Index of this element in the threads_table*/
enum {NOTUSED = 0, USED, ABORTING, ABORTED, FAILED} state;
bool running; /* Is this the currently running thread */
pthread_t thread; /* Actual pthread_t as returned by the native kernel */
int thead_cnt; /* For debugging: Unique, consecutive, thread number */
/*
* Pointer to data from the hosted OS architecture
* What that is, if anything, is up to that the hosted OS
*/
void *payload;
};
struct te_status_t {
struct threads_table_el *threads_table; /* Pointer to the threads table */
int thread_create_count; /* (For debugging) Thread creation counter */
int threads_table_size; /* Size of threads_table */
/* Pointer to the hosted OS function to be called when a thread is started */
void (*fptr)(void *payload);
/*
* Conditional variable to block/awake all threads during swaps.
* (we only need 1 mutex and 1 cond variable for all threads)
*/
pthread_cond_t cond_threads;
/* Mutex for the conditional variable cond_threads */
pthread_mutex_t mtx_threads;
/* Token which tells which thread is allowed to run now */
int currently_allowed_thread;
bool terminate; /* Are we terminating the program == cleaning up */
};
static void nct_exit_and_cleanup(struct te_status_t *this);
static struct threads_table_el *ttable_get_element(struct te_status_t *this, int index);
/**
* Helper function, run by a thread which is being aborted
*/
static void abort_tail(struct te_status_t *this, int this_th_nbr)
{
struct threads_table_el *tt_el = ttable_get_element(this, this_th_nbr);
NCT_DEBUG("Thread [%i] %i: %s: Aborting (exiting) (rel mut)\n",
tt_el->thead_cnt,
this_th_nbr,
__func__);
tt_el->running = false;
tt_el->state = ABORTED;
nct_exit_and_cleanup(this);
}
/**
* Helper function to block this thread until it is allowed again
*
* Note that we go out of this function (the while loop below)
* with the mutex locked by this particular thread.
* In normal circumstances, the mutex is only unlocked internally in
* pthread_cond_wait() while waiting for cond_threads to be signaled
*/
static void nct_wait_until_allowed(struct te_status_t *this, int this_th_nbr)
{
struct threads_table_el *tt_el = ttable_get_element(this, this_th_nbr);
tt_el->running = false;
NCT_DEBUG("Thread [%i] %i: %s: Waiting to be allowed to run (rel mut)\n",
tt_el->thead_cnt,
this_th_nbr,
__func__);
while (this_th_nbr != this->currently_allowed_thread) {
pthread_cond_wait(&this->cond_threads, &this->mtx_threads);
if (tt_el->state == ABORTING) {
abort_tail(this, this_th_nbr);
}
}
tt_el->running = true;
NCT_DEBUG("Thread [%i] %i: %s(): I'm allowed to run! (hav mut)\n",
tt_el->thead_cnt,
this_th_nbr,
__func__);
}
/**
* Helper function to let the thread <next_allowed_th> run
*
* Note: nct_let_run() can only be called with the mutex locked
*/
static void nct_let_run(struct te_status_t *this, int next_allowed_th)
{
#if NCT_DEBUG_PRINTS
struct threads_table_el *tt_el = ttable_get_element(this, next_allowed_th);
NCT_DEBUG("%s: We let thread [%i] %i run\n",
__func__,
tt_el->thead_cnt,
next_allowed_th);
#endif
this->currently_allowed_thread = next_allowed_th;
/*
* We let all threads know one is able to run now (it may even be us
* again if fancied)
* Note that as we hold the mutex, they are going to be blocked until
* we reach our own nct_wait_until_allowed() while loop or abort_tail()
* mutex release
*/
NSI_SAFE_CALL(pthread_cond_broadcast(&this->cond_threads));
}
/**
* Helper function, run by a thread which is being ended
*/
static void nct_exit_and_cleanup(struct te_status_t *this)
{
/*
* Release the mutex so the next allowed thread can run
*/
NSI_SAFE_CALL(pthread_mutex_unlock(&this->mtx_threads));
/* We detach ourselves so nobody needs to join to us */
pthread_detach(pthread_self());
pthread_exit(NULL);
}
/**
* Let the ready thread run and block this managed thread until it is allowed again
*
* The hosted OS shall call this when it has decided to swap in/out two of its threads,
* from the thread that is being swapped out.
*
* Note: If called without having ever let another managed thread run / from a thread not
* managed by this nct instance, it will behave like nct_first_thread_start(),
* and terminate the calling thread while letting the managed thread
* <next_allowed_thread_nbr> continue.
*
* inputs:
* this_arg: Pointer to this thread emulator instance as returned by nct_init()
* next_allowed_thread_nbr: Identifier of the thread the hosted OS wants to swap in
*/
void nct_swap_threads(void *this_arg, int next_allowed_thread_nbr)
{
struct te_status_t *this = (struct te_status_t *)this_arg;
int this_th_nbr = this->currently_allowed_thread;
nct_let_run(this, next_allowed_thread_nbr);
if (this_th_nbr == -1) { /* This is the first time a thread was swapped in */
NCT_DEBUG("%s: called from an unmanaged thread, terminating it\n",
__func__);
nct_exit_and_cleanup(this);
}
struct threads_table_el *tt_el = ttable_get_element(this, this_th_nbr);
if (tt_el->state == ABORTING) {
NCT_DEBUG("Thread [%i] %i: %s: Aborting curr.\n",
tt_el->thead_cnt,
this_th_nbr,
__func__);
abort_tail(this, this_th_nbr);
} else {
nct_wait_until_allowed(this, this_th_nbr);
}
}
/**
* Let the very first hosted thread run, and exit this thread.
*
* The hosted OS shall call this when it has decided to swap in into another
* thread, and wants to terminate the currently executing thread, which is not
* a thread managed by the thread emulator.
*
* This function allows to emulate a hosted OS doing its first swapping into one
* of its hosted threads from the init thread, abandoning/terminating the init
* thread.
*/
void nct_first_thread_start(void *this_arg, int next_allowed_thread_nbr)
{
struct te_status_t *this = (struct te_status_t *)this_arg;
nct_let_run(this, next_allowed_thread_nbr);
NCT_DEBUG("%s: Init thread dying now (rel mut)\n",
__func__);
nct_exit_and_cleanup(this);
}
/**
* Handler called when any thread is cancelled or exits
*/
static void nct_cleanup_handler(void *arg)
{
struct threads_table_el *element = (struct threads_table_el *)arg;
struct te_status_t *this = element->ts_status;
/*
* If we are not terminating, this is just an aborted thread,
* and the mutex was already released
* Otherwise, release the mutex so other threads which may be
* caught waiting for it could terminate
*/
if (!this->terminate) {
return;
}
NCT_DEBUG("Thread %i: %s: Canceling (rel mut)\n",
element->thread_idx,
__func__);
NSI_SAFE_CALL(pthread_mutex_unlock(&this->mtx_threads));
/* We detach ourselves so nobody needs to join to us */
pthread_detach(pthread_self());
}
/**
* Helper function to start a hosted thread as a POSIX thread:
* It will block the pthread until the embedded OS devices to "swap in"
* this thread.
*/
static void *nct_thread_starter(void *arg_el)
{
struct threads_table_el *tt_el = (struct threads_table_el *)arg_el;
struct te_status_t *this = tt_el->ts_status;
int thread_idx = tt_el->thread_idx;
NCT_DEBUG("Thread [%i] %i: %s: Starting\n",
tt_el->thead_cnt,
thread_idx,
__func__);
/*
* We block until all other running threads reach the while loop
* in nct_wait_until_allowed() and they release the mutex
*/
NSI_SAFE_CALL(pthread_mutex_lock(&this->mtx_threads));
/*
* The program may have been finished before this thread ever got to run
*/
/* LCOV_EXCL_START */ /* See Note1 */
if (!this->threads_table || this->terminate) {
nct_cleanup_handler(arg_el);
pthread_exit(NULL);
}
/* LCOV_EXCL_STOP */
pthread_cleanup_push(nct_cleanup_handler, arg_el);
NCT_DEBUG("Thread [%i] %i: %s: After start mutex (hav mut)\n",
tt_el->thead_cnt,
thread_idx,
__func__);
/*
* The thread would try to execute immediately, so we block it
* until allowed
*/
nct_wait_until_allowed(this, thread_idx);
this->fptr(tt_el->payload);
/*
* We only reach this point if the thread actually returns which should
* not happen. But we handle it gracefully just in case
*/
/* LCOV_EXCL_START */
nsi_print_trace(PREFIX"Thread [%i] %i [%lu] ended!?!\n",
tt_el->thead_cnt,
thread_idx,
pthread_self());
tt_el->running = false;
tt_el->state = FAILED;
pthread_cleanup_pop(1);
return NULL;
/* LCOV_EXCL_STOP */
}
static struct threads_table_el *ttable_get_element(struct te_status_t *this, int index)
{
struct threads_table_el *threads_table = this->threads_table;
if (index >= this->threads_table_size) { /* LCOV_EXCL_BR_LINE */
nsi_print_error_and_exit("%s: Programming error, attempted out of bound access to "
"thread table (%i>=%i)\n",
index, this->threads_table_size); /* LCOV_EXCL_LINE */
}
while (index >= NCT_ALLOC_CHUNK_SIZE) {
index -= NCT_ALLOC_CHUNK_SIZE;
threads_table = threads_table[NCT_ALLOC_CHUNK_SIZE - 1].next;
}
return &threads_table[index];
}
/**
* Return the first free entry index in the threads table
*/
static int ttable_get_empty_slot(struct te_status_t *this)
{
struct threads_table_el *tt_el = this->threads_table;
for (int i = 0; i < this->threads_table_size; i++, tt_el = tt_el->next) {
if ((tt_el->state == NOTUSED)
|| (NCT_REUSE_ABORTED_ENTRIES
&& (tt_el->state == ABORTED))) {
return i;
}
}
/*
* else, we run out of table without finding an index
* => we expand the table
*/
struct threads_table_el *new_chunk;
new_chunk = calloc(NCT_ALLOC_CHUNK_SIZE, sizeof(struct threads_table_el));
if (new_chunk == NULL) { /* LCOV_EXCL_BR_LINE */
nsi_print_error_and_exit(NO_MEM_ERR); /* LCOV_EXCL_LINE */
}
/* Link new chunk to last element */
tt_el = ttable_get_element(this, this->threads_table_size-1);
tt_el->next = new_chunk;
this->threads_table_size += NCT_ALLOC_CHUNK_SIZE;
/* Link all new elements together */
for (int i = 0 ; i < NCT_ALLOC_CHUNK_SIZE - 1; i++) {
new_chunk[i].next = &new_chunk[i+1];
}
new_chunk[NCT_ALLOC_CHUNK_SIZE - 1].next = NULL;
/* The first newly created entry is good, we return it */
return this->threads_table_size - NCT_ALLOC_CHUNK_SIZE;
}
/**
* Create a new pthread for the new hosted OS thread.
*
* Returns a unique integer thread identifier/index, which should be used
* to refer to this thread for future calls to the thread emulator.
*
* It takes as parameter a pointer which will be passed to
* function registered in nct_init when the thread is swapped in.
*
* Note that the thread is created but not swapped in.
* The new thread execution will be held until nct_swap_threads()
* (or nct_first_thread_start()) is called with this newly created
* thread number.
*/
int nct_new_thread(void *this_arg, void *payload)
{
struct te_status_t *this = (struct te_status_t *)this_arg;
struct threads_table_el *tt_el;
int t_slot;
t_slot = ttable_get_empty_slot(this);
tt_el = ttable_get_element(this, t_slot);
tt_el->state = USED;
tt_el->running = false;
tt_el->thead_cnt = this->thread_create_count++;
tt_el->payload = payload;
tt_el->ts_status = this;
tt_el->thread_idx = t_slot;
NSI_SAFE_CALL(pthread_create(&tt_el->thread,
NULL,
nct_thread_starter,
(void *)tt_el));
NCT_DEBUG("%s created thread [%i] %i [%lu]\n",
__func__,
tt_el->thead_cnt,
t_slot,
tt_el->thread);
return t_slot;
}
/**
* Initialize an instance of the threading emulator.
*
* Returns a pointer to the initialize threading emulator instance.
* This pointer shall be passed to all subsequent calls of the
* threading emulator when interacting with this particular instance.
*
* The input fptr is a pointer to the hosted OS function
* to be called each time a thread which is created on its request
* with nct_new_thread() is swapped in (from that thread context)
*/
void *nct_init(void (*fptr)(void *))
{
struct te_status_t *this;
/*
* Note: This (and the calloc below) won't be free'd by this code
* but left for the OS to clear at process end.
* This is a conscious choice, see nct_clean_up() for more info.
* If you got here due to valgrind's leak report, please use the
* provided valgrind suppression file valgrind.supp
*/
this = calloc(1, sizeof(struct te_status_t));
if (this == NULL) { /* LCOV_EXCL_BR_LINE */
nsi_print_error_and_exit(NO_MEM_ERR); /* LCOV_EXCL_LINE */
}
this->fptr = fptr;
this->thread_create_count = 0;
this->currently_allowed_thread = -1;
NSI_SAFE_CALL(pthread_cond_init(&this->cond_threads, NULL));
NSI_SAFE_CALL(pthread_mutex_init(&this->mtx_threads, NULL));
this->threads_table = calloc(NCT_ALLOC_CHUNK_SIZE,
sizeof(struct threads_table_el));
if (this->threads_table == NULL) { /* LCOV_EXCL_BR_LINE */
nsi_print_error_and_exit(NO_MEM_ERR); /* LCOV_EXCL_LINE */
}
this->threads_table_size = NCT_ALLOC_CHUNK_SIZE;
for (int i = 0 ; i < NCT_ALLOC_CHUNK_SIZE - 1; i++) {
this->threads_table[i].next = &this->threads_table[i+1];
}
this->threads_table[NCT_ALLOC_CHUNK_SIZE - 1].next = NULL;
NSI_SAFE_CALL(pthread_mutex_lock(&this->mtx_threads));
return (void *)this;
}
/**
* Free any allocated memory by the threading emulator and clean up.
* Note that this function cannot be called from a SW thread
* (the CPU is assumed halted. Otherwise we would cancel ourselves)
*
* Note: This function cannot guarantee the threads will be cancelled before the HW
* thread exists. The only way to do that, would be to wait for each of them in
* a join without detaching them, but that could lead to locks in some
* convoluted cases; as a call to this function can come due to a hosted OS
* assert or other error termination, we better do not assume things are working fine.
* => we prefer the supposed memory leak report from valgrind, and ensure we
* will not hang.
*/
void nct_clean_up(void *this_arg)
{
struct te_status_t *this = (struct te_status_t *)this_arg;
struct threads_table_el *tt_el;
if (!this || !this->threads_table) { /* LCOV_EXCL_BR_LINE */
return; /* LCOV_EXCL_LINE */
}
this->terminate = true;
tt_el = this->threads_table;
for (int i = 0; i < this->threads_table_size; i++, tt_el = tt_el->next) {
if (tt_el->state != USED) {
continue;
}
/* LCOV_EXCL_START */
if (pthread_cancel(tt_el->thread)) {
nsi_print_warning(
PREFIX"cleanup: could not stop thread %i\n",
i);
}
/* LCOV_EXCL_STOP */
}
/*
* This is the cleanup we do not do:
*
* free(this->threads_table);
* Including all chunks
* this->threads_table = NULL;
*
* (void)pthread_cond_destroy(&this->cond_threads);
* (void)pthread_mutex_destroy(&this->mtx_threads);
*
* free(this);
*/
}
/*
* Mark a thread as being aborted. This will result in the underlying pthread
* being terminated some time later:
* If the thread is marking itself as aborting, as soon as it is swapped out
* by the hosted (embedded) OS
* If it is marking another thread, at some non-specific time in the future
* (But note that no embedded part of the aborted thread will execute anymore)
*
* * thread_idx : The thread identifier as provided during creation (return from nct_new_thread())
*/
void nct_abort_thread(void *this_arg, int thread_idx)
{
struct te_status_t *this = (struct te_status_t *)this_arg;
struct threads_table_el *tt_el = ttable_get_element(this, thread_idx);
if (thread_idx == this->currently_allowed_thread) {
NCT_DEBUG("Thread [%i] %i: %s Marked myself "
"as aborting\n",
tt_el->thead_cnt,
thread_idx,
__func__);
} else {
if (tt_el->state != USED) { /* LCOV_EXCL_BR_LINE */
/* The thread may have been already aborted before */
return; /* LCOV_EXCL_LINE */
}
NCT_DEBUG("Aborting not scheduled thread [%i] %i\n",
tt_el->thead_cnt,
thread_idx);
}
tt_el->state = ABORTING;
/*
* Note: the native thread will linger in RAM until it catches the
* mutex or awakes on the condition.
* Note that even if we would pthread_cancel() the thread here, that
* would be the case, but with a pthread_cancel() the mutex state would
* be uncontrolled
*/
}
/*
* Return a unique thread identifier for this thread for this
* run. This identifier is only meant for debug purposes
*
* thread_idx is the value returned by nct_new_thread()
*/
int nct_get_unique_thread_id(void *this_arg, int thread_idx)
{
struct te_status_t *this = (struct te_status_t *)this_arg;
struct threads_table_el *tt_el = ttable_get_element(this, thread_idx);
return tt_el->thead_cnt;
}
/*
* Notes about coverage:
*
* Note1:
*
* This condition will only be triggered in very unlikely cases
* (once every few full regression runs).
* It is therefore excluded from the coverage report to avoid confusing
* developers.
*
* Background: A pthread is created as soon as the hosted kernel creates
* a hosted thread. A pthread creation is an asynchronous process handled by the
* host kernel.
*
* This emulator normally keeps only 1 thread executing at a time.
* But part of the pre-initialization during creation of a new thread
* and some cleanup at the tail of the thread termination are executed
* in parallel to other threads.
* That is, the execution of those code paths is a bit indeterministic.
*
* Only when the hosted kernel attempts to swap to a new thread does this
* emulator need to wait until its pthread is ready and initialized
* (has reached nct_wait_until_allowed())
*
* In some cases (tests) hosted threads are created which are never actually needed
* (typically the idle thread). That means the test may finish before that
* thread's underlying pthread has reached nct_wait_until_allowed().
*
* In this unlikely cases the initialization or cleanup of the thread follows
* non-typical code paths.
* This code paths are there to ensure things work always, no matter
* the load of the host. Without them, very rare & mysterious segfault crashes
* would occur.
* But as they are very atypical and only triggered with some host loads,
* they will be covered in the coverage reports only rarely.
*
* Note2:
*
* Some other code will never or only very rarely trigger and is therefore
* excluded with LCOV_EXCL_LINE
*
*/

View file

@ -0,0 +1,19 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*
* See description in header
*/
#include <stdlib.h>
long nsi_host_random(void)
{
return random();
}
void nsi_host_srandom(unsigned int seed)
{
srandom(seed);
}

View file

@ -0,0 +1,181 @@
/*
* Copyright (c) 2017 Oticon A/S
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* Overall HW models scheduler for the native simulator
*
* Models events are registered with NSI_HW_EVENT().
*/
#include <stdint.h>
#include <signal.h>
#include <stddef.h>
#include <inttypes.h>
#include "nsi_tracing.h"
#include "nsi_main.h"
#include "nsi_safe_call.h"
#include "nsi_hw_scheduler.h"
static uint64_t simu_time; /* The actual time as known by the HW models */
static uint64_t end_of_time = NSI_NEVER; /* When will this device stop */
extern uint64_t *__nsi_hw_events_timers_start[];
extern uint64_t *__nsi_hw_events_timers_end[];
extern void (*__nsi_hw_events_callbacks_start[])(void);
extern void (*__nsi_hw_events_callbacks_end[])(void);
static unsigned int number_of_timers;
static unsigned int number_of_callbacks;
static unsigned int next_timer_index;
static uint64_t next_timer_time;
/* Have we received a SIGTERM or SIGINT */
static volatile sig_atomic_t signaled_end;
/**
* Handler for SIGTERM and SIGINT
*/
static void nsi_hws_signal_end_handler(int sig)
{
signaled_end = 1;
}
/**
* Set the handler for SIGTERM and SIGINT which will cause the
* program to exit gracefully when they are received the 1st time
*
* Note that our handler only sets a variable indicating the signal was
* received, and in each iteration of the hw main loop this variable is
* evaluated.
* If for some reason (the program is stuck) we never evaluate it, the program
* would never exit.
* Therefore we set SA_RESETHAND: This way, the 2nd time the signal is received
* the default handler would be called to terminate the program no matter what.
*
* Note that SA_RESETHAND requires either _POSIX_C_SOURCE>=200809 or
* _XOPEN_SOURCE>=500
*/
static void nsi_hws_set_sig_handler(void)
{
struct sigaction act;
act.sa_handler = nsi_hws_signal_end_handler;
NSI_SAFE_CALL(sigemptyset(&act.sa_mask));
act.sa_flags = SA_RESETHAND;
NSI_SAFE_CALL(sigaction(SIGTERM, &act, NULL));
NSI_SAFE_CALL(sigaction(SIGINT, &act, NULL));
}
static void nsi_hws_sleep_until_next_event(void)
{
if (next_timer_time >= simu_time) { /* LCOV_EXCL_BR_LINE */
simu_time = next_timer_time;
} else {
/* LCOV_EXCL_START */
nsi_print_warning("next_timer_time corrupted (%"PRIu64"<= %"
PRIu64", timer idx=%i)\n",
(uint64_t)next_timer_time,
(uint64_t)simu_time,
next_timer_index);
/* LCOV_EXCL_STOP */
}
if (signaled_end || (simu_time > end_of_time)) {
nsi_print_trace("\nStopped at %.3Lfs\n",
((long double)simu_time)/1.0e6L);
nsi_exit(0);
}
}
/**
* Find in between all events timers which is the next one.
* (and update the internal next_timer_* accordingly)
*/
void nsi_hws_find_next_event(void)
{
next_timer_index = 0;
next_timer_time = *__nsi_hw_events_timers_start[0];
for (unsigned int i = 1; i < number_of_timers ; i++) {
if (next_timer_time > *__nsi_hw_events_timers_start[i]) {
next_timer_index = i;
next_timer_time = *__nsi_hw_events_timers_start[i];
}
}
}
/**
* Execute the next scheduled HW event
* (advancing time until that event would trigger)
*/
void nsi_hws_one_event(void)
{
nsi_hws_sleep_until_next_event();
if (next_timer_index < number_of_timers) { /* LCOV_EXCL_BR_LINE */
__nsi_hw_events_callbacks_start[next_timer_index]();
} else {
nsi_print_error_and_exit("next_timer_index corrupted\n"); /* LCOV_EXCL_LINE */
}
nsi_hws_find_next_event();
}
/**
* Set the simulated time when the process will stop
*/
void nsi_hws_set_end_of_time(uint64_t new_end_of_time)
{
end_of_time = new_end_of_time;
}
/**
* Return the current simulated time as known by the device
*/
uint64_t nsi_hws_get_time(void)
{
return simu_time;
}
/**
* Function to initialize the HW scheduler
*
* Note that the HW models should register their initialization functions
* as NSI_TASKS of HW_INIT level.
*/
void nsi_hws_init(void)
{
number_of_timers =
(__nsi_hw_events_timers_end - __nsi_hw_events_timers_start);
number_of_callbacks =
(__nsi_hw_events_callbacks_end - __nsi_hw_events_callbacks_start);
/* LCOV_EXCL_START */
if (number_of_timers != number_of_callbacks || number_of_timers == 0) {
nsi_print_error_and_exit("number_of_timers corrupted\n");
}
/* LCOV_EXCL_STOP */
nsi_hws_set_sig_handler();
nsi_hws_find_next_event();
}
/**
* Function to free any resources allocated by the HW scheduler
*
* Note that the HW models should register their initialization functions
* as NSI_TASKS of ON_EXIT_PRE/POST levels.
*/
void nsi_hws_cleanup(void)
{
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef NSI_COMMON_SRC_NSI_INTERNAL_H
#define NSI_COMMON_SRC_NSI_INTERNAL_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
*
* @brief find least significant bit set in a 32-bit word
*
* This routine finds the first bit set starting from the least significant bit
* in the argument passed in and returns the index of that bit. Bits are
* numbered starting at 1 from the least significant bit. A return value of
* zero indicates that the value passed is zero.
*
* @return least significant bit set, 0 if @a op is 0
*/
static inline unsigned int nsi_find_lsb_set(uint32_t op)
{
return __builtin_ffs(op);
}
#ifdef __cplusplus
}
#endif
#endif /* NSI_COMMON_SRC_NSI_INTERNAL_H */

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef NSI_COMMON_SRC_NSI_SAFE_CALLL_H
#define NSI_COMMON_SRC_NSI_SAFE_CALLL_H
#include <stdbool.h>
#include "nsi_tracing.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifndef nsi_unlikely
#define nsi_unlikely(x) (__builtin_expect((bool)!!(x), false) != 0L)
#endif
#define NSI_SAFE_CALL(a) nsi_safe_call(a, #a)
static inline void nsi_safe_call(int test, const char *test_str)
{
/* LCOV_EXCL_START */ /* See Note1 */
if (nsi_unlikely(test)) {
nsi_print_error_and_exit("Error on: %s\n",
test_str);
}
/* LCOV_EXCL_STOP */
}
#ifdef __cplusplus
}
#endif
#endif /* NSI_COMMON_SRC_NSI_SAFE_CALLL_H */

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief Run the set of special NSI tasks corresponding to the given level
*
* @param level One of NSITASK_*_LEVEL as defined in nsi_tasks.h
*/
void nsi_run_tasks(int level)
{
extern void (*__nsi_PRE_BOOT_1_tasks_start[])(void);
extern void (*__nsi_PRE_BOOT_2_tasks_start[])(void);
extern void (*__nsi_HW_INIT_tasks_start[])(void);
extern void (*__nsi_PRE_BOOT_3_tasks_start[])(void);
extern void (*__nsi_FIRST_SLEEP_tasks_start[])(void);
extern void (*__nsi_ON_EXIT_PRE_tasks_start[])(void);
extern void (*__nsi_ON_EXIT_POST_tasks_start[])(void);
extern void (*__nsi_tasks_end[])(void);
static void (**nsi_pre_tasks[])(void) = {
__nsi_PRE_BOOT_1_tasks_start,
__nsi_PRE_BOOT_2_tasks_start,
__nsi_HW_INIT_tasks_start,
__nsi_PRE_BOOT_3_tasks_start,
__nsi_FIRST_SLEEP_tasks_start,
__nsi_ON_EXIT_PRE_tasks_start,
__nsi_ON_EXIT_POST_tasks_start,
__nsi_tasks_end
};
void (**fptr)(void);
for (fptr = nsi_pre_tasks[level]; fptr < nsi_pre_tasks[level+1];
fptr++) {
if (*fptr) { /* LCOV_EXCL_BR_LINE */
(*fptr)();
}
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef NSI_COMMON_SRC_NSI_TASKS_H
#define NSI_COMMON_SRC_NSI_TASKS_H
#include "nsi_utils.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* NSI_TASK
*
* Register a function to be called at particular moments
* during the Native Simulator execution.
*
* There is 5 choices for when the function will be called (level):
* * PRE_BOOT_1: Will be called before the command line parameters are parsed,
* or the HW models are initialized
*
* * PRE_BOOT_2: Will be called after the command line parameters are parsed,
* but before the HW models are initialized
*
* * HW_INIT: Will be called during HW models initialization
*
* * PRE_BOOT_3: Will be called after the HW models initialization, right before
* the "CPUs are booted" and embedded SW in them is started.
*
* * FIRST_SLEEP: Will be called after the 1st time all CPUs are sent to sleep
*
* * ON_EXIT_PRE: Will be called during termination of the runner
* execution, as a first set.
*
* * ON_EXIT_POST: Will be called during termination of the runner
* execution, as the very last set before the program returns.
*
* The function must take no parameters and return nothing.
*/
#define NSI_TASK(fn, level, prio) \
static void (* const NSI_CONCAT(__nsi_task_, fn))() \
__attribute__((__used__)) \
__attribute__((__section__(".nsi_" #level NSI_STRINGIFY(prio) "_task")))\
= fn
#define NSITASK_PRE_BOOT_1_LEVEL 0
#define NSITASK_PRE_BOOT_2_LEVEL 1
#define NSITASK_HW_INIT_LEVEL 2
#define NSITASK_PRE_BOOT_3_LEVEL 3
#define NSITASK_FIRST_SLEEP_LEVEL 4
#define NSITASK_ON_EXIT_PRE_LEVEL 5
#define NSITASK_ON_EXIT_POST_LEVEL 6
/**
* @brief Run the set of special native tasks corresponding to the given level
*
* @param level One of NSITASK_*_LEVEL as defined in soc.h
*/
void nsi_run_tasks(int level);
#ifdef __cplusplus
}
#endif
#endif /* NSI_COMMON_SRC_NSI_TASKS_H */

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdarg.h>
#include "nsi_tracing.h"
void nsi_print_error_and_exit(const char *format, ...)
{
va_list variable_args;
va_start(variable_args, format);
nsi_vprint_error_and_exit(format, variable_args);
va_end(variable_args);
}
void nsi_print_warning(const char *format, ...)
{
va_list variable_args;
va_start(variable_args, format);
nsi_vprint_warning(format, variable_args);
va_end(variable_args);
}
void nsi_print_trace(const char *format, ...)
{
va_list variable_args;
va_start(variable_args, format);
nsi_vprint_trace(format, variable_args);
va_end(variable_args);
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2010-2014 Wind River Systems, Inc.
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef NSI_COMMON_SRC_INCL_NSI_UTILS_H
#define NSI_COMMON_SRC_INCL_NSI_UTILS_H
#define _NSI_STRINGIFY(x) #x
#define NSI_STRINGIFY(s) _NSI_STRINGIFY(s)
/* concatenate the values of the arguments into one */
#define NSI_DO_CONCAT(x, y) x ## y
#define NSI_CONCAT(x, y) NSI_DO_CONCAT(x, y)
#define NSI_MAX(a, b) (((a) > (b)) ? (a) : (b))
#define NSI_MIN(a, b) (((a) < (b)) ? (a) : (b))
#ifndef NSI_ARG_UNUSED
#define NSI_ARG_UNUSED(x) (void)(x)
#endif
#endif /* NSI_COMMON_SRC_INCL_NSI_UTILS_H */

View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include "nsi_cpu0_interrupts.h"
#include "irq_ctrl.h"
#include "nsi_tasks.h"
#include "nsi_hws_models_if.h"
static uint64_t hw_counter_timer;
static bool counter_running;
static uint64_t counter_value;
static uint64_t counter_target;
static uint64_t counter_period;
/**
* Initialize the counter with prescaler of HW
*/
static void hw_counter_init(void)
{
hw_counter_timer = NSI_NEVER;
counter_target = NSI_NEVER;
counter_value = 0;
counter_running = false;
counter_period = NSI_NEVER;
}
NSI_TASK(hw_counter_init, HW_INIT, 10);
static void hw_counter_triggered(void)
{
if (!counter_running) {
hw_counter_timer = NSI_NEVER;
return;
}
hw_counter_timer = nsi_hws_get_time() + counter_period;
counter_value = counter_value + 1;
if (counter_value == counter_target) {
hw_irq_ctrl_set_irq(COUNTER_EVENT_IRQ);
}
}
NSI_HW_EVENT(hw_counter_timer, hw_counter_triggered, 20);
/**
* Configures the counter period.
* The counter will be incremented every 'period' microseconds.
*/
void hw_counter_set_period(uint64_t period)
{
counter_period = period;
}
/**
* Starts the counter. It must be previously configured with
* hw_counter_set_period() and hw_counter_set_target().
*/
void hw_counter_start(void)
{
if (counter_running) {
return;
}
counter_running = true;
hw_counter_timer = nsi_hws_get_time() + counter_period;
nsi_hws_find_next_event();
}
/**
* Stops the counter at current value.
* On the next call to hw_counter_start, the counter will
* start from the value at which it was stopped.
*/
void hw_counter_stop(void)
{
counter_running = false;
hw_counter_timer = NSI_NEVER;
nsi_hws_find_next_event();
}
/**
* Returns the current counter value.
*/
uint64_t hw_counter_get_value(void)
{
return counter_value;
}
/**
* Configures the counter to generate an interrupt
* when its count value reaches target.
*/
void hw_counter_set_target(uint64_t target)
{
counter_target = target;
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief API to the native simulator - native HW counter
*/
#ifndef NATIVE_SIMULATOR_NATIVE_SRC_HW_COUNTER_H
#define NATIVE_SIMULATOR_NATIVE_SRC_HW_COUNTER_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
void hw_counter_triggered(void);
void hw_counter_set_period(uint64_t period);
void hw_counter_set_target(uint64_t counter_target);
void hw_counter_start(void);
void hw_counter_stop(void);
uint64_t hw_counter_get_value(void);
#ifdef __cplusplus
}
#endif
#endif /* NATIVE_SIMULATOR_NATIVE_SRC_HW_COUNTER_H */

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2017 Oticon A/S
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief API to the native simulator - native interrupt controller
*/
#ifndef NATIVE_SIMULATOR_NATIVE_SRC_IRQ_CTRL_H
#define NATIVE_SIMULATOR_NATIVE_SRC_IRQ_CTRL_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
void hw_irq_ctrl_set_cur_prio(int new);
int hw_irq_ctrl_get_cur_prio(void);
void hw_irq_ctrl_prio_set(unsigned int irq, unsigned int prio);
uint8_t hw_irq_ctrl_get_prio(unsigned int irq);
int hw_irq_ctrl_get_highest_prio_irq(void);
uint32_t hw_irq_ctrl_get_current_lock(void);
uint32_t hw_irq_ctrl_change_lock(uint32_t new_lock);
uint64_t hw_irq_ctrl_get_irq_status(void);
void hw_irq_ctrl_disable_irq(unsigned int irq);
int hw_irq_ctrl_is_irq_enabled(unsigned int irq);
void hw_irq_ctrl_clear_irq(unsigned int irq);
void hw_irq_ctrl_enable_irq(unsigned int irq);
void hw_irq_ctrl_set_irq(unsigned int irq);
void hw_irq_ctrl_raise_im(unsigned int irq);
void hw_irq_ctrl_raise_im_from_sw(unsigned int irq);
#define N_IRQS 32
#ifdef __cplusplus
}
#endif
#endif /* NATIVE_SIMULATOR_NATIVE_SRC_IRQ_CTRL_H */

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2018 Oticon A/S
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief API to the native simulator - native (Real) Time Clock
*/
#ifndef NATIVE_SIMULATOR_NATIVE_SRC_NATIVE_RTC_H
#define NATIVE_SIMULATOR_NATIVE_SRC_NATIVE_RTC_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* Types of clocks this RTC provides:
*/
/** Time since boot, cannot be offset. Microsecond resolution */
#define RTC_CLOCK_BOOT 0
/** Persistent clock, can be offset. Microsecond resolution */
#define RTC_CLOCK_REALTIME 1
/**
* Pseudo-host real time clock (Please see documentation).
* Nanosecond resolution
*/
#define RTC_CLOCK_PSEUDOHOSTREALTIME 2
/**
* @brief Get the value of a clock in microseconds
*
* @param clock_type Which clock to measure from
*
* @return Number of microseconds
*/
uint64_t native_rtc_gettime_us(int clock_type);
/**
* @brief Get the value of a clock split in in nsec and seconds
*
* @param clock_type Which clock to measure from
* @param nsec Pointer to store the nanoseconds
* @param nsec Pointer to store the seconds
*/
void native_rtc_gettime(int clock_type, uint32_t *nsec, uint64_t *sec);
/**
* @brief Offset the real time clock by a number of microseconds.
* Note that this only affects the RTC_CLOCK_REALTIME and
* RTC_CLOCK_PSEUDOHOSTREALTIME clocks.
*
* @param delta_us Number of microseconds to offset. The value is added to all
* offsetable clocks.
*/
void native_rtc_offset(int64_t delta_us);
/**
* @brief Adjust the speed of the clock source by a multiplicative factor
*
* @param clock_correction Factor by which to correct the clock speed
*/
void native_rtc_adjust_clock(double clock_correction);
#ifdef __cplusplus
}
#endif
#endif /* NATIVE_SIMULATOR_NATIVE_SRC_NATIVE_RTC_H */

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2018 Oticon A/S
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief API to the native simulator - native command line parsing utilities
*
* Note: The arguments structure definitions is kept fully compatible with Zephyr's native_posix
* and BabbleSim's command line options to enable to reuse components between them.
* And for APIs to be accessible thru a trivial shim.
*/
#ifndef NATIVE_SIMULATOR_NATIVE_SRC_NSI_CMDLINE_H
#define NATIVE_SIMULATOR_NATIVE_SRC_NSI_CMDLINE_H
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Prototype for a callback function when an option is found:
* inputs:
* argv: Whole argv[i] option as received in main
* offset: Offset to the end of the option string
* (including a possible ':' or '=')
* If the option had a value, it would be placed in &argv[offset]
*/
typedef void (*option_found_callback_f)(char *argv, int offset);
/*
* Structure defining each command line option
*/
struct args_struct_t {
/*
* if manual is set nsi_cmd_args_parse*() will ignore it except for
* displaying it the help messages and initializing <dest> to its
* default
*/
bool manual;
/* For help messages, should it be wrapped in "[]" */
bool is_mandatory;
/* It is just a switch: it does not have something to store after */
bool is_switch;
/* Option name we search for: --<option> */
char *option;
/*
* Name of the option destination in the help messages:
* "--<option>=<name>"
*/
char *name;
/* Type of option (see nsi_cmd_read_option_value()) */
char type;
/* Pointer to where the read value will be stored (may be NULL) */
void *dest;
/* Optional callback to be called when the switch is found */
option_found_callback_f call_when_found;
/* Long description for the help messages */
char *descript;
};
#define ARG_TABLE_ENDMARKER \
{false, false, false, NULL, NULL, 0, NULL, NULL, NULL}
void nsi_handle_cmd_line(int argc, char *argv[]);
void nsi_get_cmd_line_args(int *argc, char ***argv);
void nsi_get_test_cmd_line_args(int *argc, char ***argv);
void nsi_add_command_line_opts(struct args_struct_t *args);
void nsi_cleanup_cmd_line(void);
#ifdef __cplusplus
}
#endif
#endif /* NATIVE_SIMULATOR_NATIVE_SRC_NSI_CMDLINE_H */

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef NATIVE_SIMULATOR_NATIVE_SRC_NSI_CPU0_INTERRUPTS_H
#define NATIVE_SIMULATOR_NATIVE_SRC_NSI_CPU0_INTERRUPTS_H
#define TIMER_TICK_IRQ 0
#define OFFLOAD_SW_IRQ 1
#define COUNTER_EVENT_IRQ 2
/*
* This interrupt will awake the CPU if IRQs are not locked,
* This interrupt does not have an associated status bit or handler
*/
#define PHONY_WEAK_IRQ 0xFFFE
/*
* This interrupt will awake the CPU even if IRQs are locked,
* This interrupt does not have an associated status bit or handler
* (the lock is only ignored when the interrupt is raised from the HW models,
* SW threads should not try to use this)
*/
#define PHONY_HARD_IRQ 0xFFFF
#endif /* NATIVE_SIMULATOR_NATIVE_SRC_NSI_CPU0_INTERRUPTS_H */

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2017 Oticon A/S
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef NATIVE_SIMULATOR_NATIVE_SRC_NSI_TIMER_MODEL_H
#define NATIVE_SIMULATOR_NATIVE_SRC_NSI_TIMER_MODEL_H
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
void hwtimer_set_real_time_mode(bool new_rt);
void hwtimer_timer_reached(void);
void hwtimer_wake_in_time(uint64_t time);
void hwtimer_set_silent_ticks(int64_t sys_ticks);
void hwtimer_enable(uint64_t period);
int64_t hwtimer_get_pending_silent_ticks(void);
void hwtimer_reset_rtc(void);
void hwtimer_set_rtc_offset(int64_t offset);
void hwtimer_set_rt_ratio(double ratio);
void hwtimer_adjust_rtc_offset(int64_t offset_delta);
void hwtimer_adjust_rt_ratio(double ratio_correction);
int64_t hwtimer_get_simu_rtc_time(void);
void hwtimer_get_pseudohost_rtc_time(uint32_t *nsec, uint64_t *sec);
#ifdef __cplusplus
}
#endif
#endif /* NATIVE_SIMULATOR_NATIVE_SRC_NSI_TIMER_MODEL_H */

View file

@ -0,0 +1,268 @@
/*
* Copyright (c) 2017 Oticon A/S
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*
* HW IRQ controller model
*/
#include <stdint.h>
#include <stdbool.h>
#include "nsi_internal.h"
#include "nsi_cpu_if.h"
#include "nsi_cpu0_interrupts.h"
#include "irq_ctrl.h"
#include "nsi_tasks.h"
#include "nsi_hws_models_if.h"
static uint64_t irq_ctrl_timer = NSI_NEVER;
static uint64_t irq_status; /* pending interrupts */
static uint64_t irq_premask; /* interrupts before the mask */
/*
* Mask of which interrupts will actually cause the cpu to vector into its
* irq handler
* If an interrupt is masked in this way, it will be pending in the premask in
* case it is enabled later before clearing it.
* If the irq_mask enables and interrupt pending in irq_premask, it will cause
* the controller to raise the interrupt immediately
*/
static uint64_t irq_mask;
/*
* Interrupts lock/disable. When set, interrupts are registered
* (in the irq_status) but do not awake the cpu. if when unlocked,
* irq_status != 0 an interrupt will be raised immediately
*/
static bool irqs_locked;
static bool lock_ignore; /* For the hard fake IRQ, temporarily ignore lock */
static uint8_t irq_prio[N_IRQS]; /* Priority of each interrupt */
/* note that prio = 0 == highest, prio=255 == lowest */
static int currently_running_prio = 256; /* 255 is the lowest prio interrupt */
static void hw_irq_ctrl_init(void)
{
irq_mask = 0U; /* Let's assume all interrupts are disable at boot */
irq_premask = 0U;
irqs_locked = false;
lock_ignore = false;
for (int i = 0 ; i < N_IRQS; i++) {
irq_prio[i] = 255U;
}
}
NSI_TASK(hw_irq_ctrl_init, HW_INIT, 10);
void hw_irq_ctrl_set_cur_prio(int new)
{
currently_running_prio = new;
}
int hw_irq_ctrl_get_cur_prio(void)
{
return currently_running_prio;
}
void hw_irq_ctrl_prio_set(unsigned int irq, unsigned int prio)
{
irq_prio[irq] = prio;
}
uint8_t hw_irq_ctrl_get_prio(unsigned int irq)
{
return irq_prio[irq];
}
/**
* Get the currently pending highest priority interrupt which has a priority
* higher than a possibly currently running interrupt
*
* If none, return -1
*/
int hw_irq_ctrl_get_highest_prio_irq(void)
{
if (irqs_locked) {
return -1;
}
uint64_t irq_status = hw_irq_ctrl_get_irq_status();
int winner = -1;
int winner_prio = 256;
while (irq_status != 0U) {
int irq_nbr = nsi_find_lsb_set(irq_status) - 1;
irq_status &= ~((uint64_t) 1 << irq_nbr);
if ((winner_prio > (int)irq_prio[irq_nbr])
&& (currently_running_prio > (int)irq_prio[irq_nbr])) {
winner = irq_nbr;
winner_prio = irq_prio[irq_nbr];
}
}
return winner;
}
uint32_t hw_irq_ctrl_get_current_lock(void)
{
return irqs_locked;
}
uint32_t hw_irq_ctrl_change_lock(uint32_t new_lock)
{
uint32_t previous_lock = irqs_locked;
irqs_locked = new_lock;
if ((previous_lock == true) && (new_lock == false)) {
if (irq_status != 0U) {
nsif_cpu0_irq_raised_from_sw();
}
}
return previous_lock;
}
uint64_t hw_irq_ctrl_get_irq_status(void)
{
return irq_status;
}
void hw_irq_ctrl_clear_all_enabled_irqs(void)
{
irq_status = 0U;
irq_premask &= ~irq_mask;
}
void hw_irq_ctrl_clear_all_irqs(void)
{
irq_status = 0U;
irq_premask = 0U;
}
void hw_irq_ctrl_disable_irq(unsigned int irq)
{
irq_mask &= ~((uint64_t)1<<irq);
}
int hw_irq_ctrl_is_irq_enabled(unsigned int irq)
{
return (irq_mask & ((uint64_t)1 << irq))?1:0;
}
uint64_t hw_irq_ctrl_get_irq_mask(void)
{
return irq_mask;
}
void hw_irq_ctrl_clear_irq(unsigned int irq)
{
irq_status &= ~((uint64_t)1<<irq);
irq_premask &= ~((uint64_t)1<<irq);
}
/**
* Enable an interrupt
*
* This function may only be called from SW threads
*
* If the enabled interrupt is pending, it will immediately vector to its
* interrupt handler and continue (maybe with some swap() before)
*/
void hw_irq_ctrl_enable_irq(unsigned int irq)
{
irq_mask |= ((uint64_t)1<<irq);
if (irq_premask & ((uint64_t)1<<irq)) { /* if IRQ is pending */
hw_irq_ctrl_raise_im_from_sw(irq);
}
}
static inline void hw_irq_ctrl_irq_raise_prefix(unsigned int irq)
{
if (irq < N_IRQS) {
irq_premask |= ((uint64_t)1<<irq);
if (irq_mask & (1 << irq)) {
irq_status |= ((uint64_t)1<<irq);
}
} else if (irq == PHONY_HARD_IRQ) {
lock_ignore = true;
}
}
/**
* Set/Raise an interrupt
*
* This function is meant to be used by either the SW manual IRQ raising
* or by HW which wants the IRQ to be raised in one delta cycle from now
*/
void hw_irq_ctrl_set_irq(unsigned int irq)
{
hw_irq_ctrl_irq_raise_prefix(irq);
if ((irqs_locked == false) || (lock_ignore)) {
/*
* Awake CPU in 1 delta
* Note that we awake the CPU even if the IRQ is disabled
* => we assume the CPU is always idling in a WFE() like
* instruction and the CPU is allowed to awake just with the irq
* being marked as pending
*/
irq_ctrl_timer = nsi_hws_get_time();
nsi_hws_find_next_event();
}
}
static void irq_raising_from_hw_now(void)
{
/*
* We always awake the CPU even if the IRQ was masked,
* but not if irqs are locked unless this is due to a
* PHONY_HARD_IRQ
*/
if ((irqs_locked == false) || (lock_ignore)) {
lock_ignore = false;
nsif_cpu0_irq_raised();
}
}
/**
* Set/Raise an interrupt immediately.
* Like hw_irq_ctrl_set_irq() but awake immediately the CPU instead of in
* 1 delta cycle
*
* Call only from HW threads
*/
void hw_irq_ctrl_raise_im(unsigned int irq)
{
hw_irq_ctrl_irq_raise_prefix(irq);
irq_raising_from_hw_now();
}
/**
* Like hw_irq_ctrl_raise_im() but for SW threads
*
* Call only from SW threads
*/
void hw_irq_ctrl_raise_im_from_sw(unsigned int irq)
{
hw_irq_ctrl_irq_raise_prefix(irq);
if (irqs_locked == false) {
nsif_cpu0_irq_raised_from_sw();
}
}
static void hw_irq_ctrl_timer_triggered(void)
{
irq_ctrl_timer = NSI_NEVER;
irq_raising_from_hw_now();
}
NSI_HW_EVENT(irq_ctrl_timer, hw_irq_ctrl_timer_triggered, 900);

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2018 Oticon A/S
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "nsi_tracing.h"
#include "native_rtc.h"
#include "nsi_hw_scheduler.h"
#include "nsi_timer_model.h"
/**
* Return the (simulation) time in microseconds
* where clock_type is one of RTC_CLOCK_*
*/
uint64_t native_rtc_gettime_us(int clock_type)
{
if (clock_type == RTC_CLOCK_BOOT) {
return nsi_hws_get_time();
} else if (clock_type == RTC_CLOCK_REALTIME) { /* RTC_CLOCK_REALTIME */
return hwtimer_get_simu_rtc_time();
} else if (clock_type == RTC_CLOCK_PSEUDOHOSTREALTIME) {
uint32_t nsec;
uint64_t sec;
hwtimer_get_pseudohost_rtc_time(&nsec, &sec);
return sec * 1000000UL + nsec / 1000U;
}
nsi_print_error_and_exit("Unknown clock source %i\n",
clock_type);
return 0;
}
/**
* Similar to POSIX clock_gettime()
* get the simulation time split in nsec and seconds
* where clock_type is one of RTC_CLOCK_*
*/
void native_rtc_gettime(int clock_type, uint32_t *nsec, uint64_t *sec)
{
if (clock_type == RTC_CLOCK_BOOT || clock_type == RTC_CLOCK_REALTIME) {
uint64_t us = native_rtc_gettime_us(clock_type);
*nsec = (us % 1000000UL) * 1000U;
*sec = us / 1000000UL;
} else { /* RTC_CLOCK_PSEUDOHOSTREALTIME */
hwtimer_get_pseudohost_rtc_time(nsec, sec);
}
}
/**
* Offset the real time clock by a number of microseconds.
* Note that this only affects the RTC_CLOCK_REALTIME and
* RTC_CLOCK_PSEUDOHOSTREALTIME clocks.
*/
void native_rtc_offset(int64_t delta_us)
{
hwtimer_adjust_rtc_offset(delta_us);
}
/**
* Adjust the speed of the clock source by a multiplicative factor
*/
void native_rtc_adjust_clock(double clock_correction)
{
hwtimer_adjust_rt_ratio(clock_correction);
}

View file

@ -0,0 +1,152 @@
/*
* Copyright (c) 2018 Oticon A/S
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "nsi_cmdline.h"
#include "nsi_cmdline_internal.h"
#include "nsi_tracing.h"
#include "nsi_timer_model.h"
#include "nsi_hw_scheduler.h"
static int s_argc, test_argc;
static char **s_argv, **test_argv;
static struct args_struct_t *args_struct;
static int used_args;
static int args_aval;
#define ARGS_ALLOC_CHUNK_SIZE 20
void nsi_cleanup_cmd_line(void)
{
if (args_struct != NULL) { /* LCOV_EXCL_BR_LINE */
free(args_struct);
args_struct = NULL;
}
}
/**
* Add a set of command line options to the program.
*
* Each option to be added is described in one entry of the input <args>
* This input must be terminated with an entry containing ARG_TABLE_ENDMARKER.
*/
void nsi_add_command_line_opts(struct args_struct_t *args)
{
int count = 0;
while (args[count].option != NULL) {
count++;
}
count++; /*for the end marker*/
if (used_args + count >= args_aval) {
int growby = count;
/* reallocs are expensive let's do them only in big chunks */
if (growby < ARGS_ALLOC_CHUNK_SIZE) {
growby = ARGS_ALLOC_CHUNK_SIZE;
}
struct args_struct_t *new_args_struct = realloc(args_struct,
(args_aval + growby)*
sizeof(struct args_struct_t));
args_aval += growby;
/* LCOV_EXCL_START */
if (new_args_struct == NULL) {
nsi_print_error_and_exit("Could not allocate memory");
} else {
args_struct = new_args_struct;
}
/* LCOV_EXCL_STOP */
}
memcpy(&args_struct[used_args], args,
count*sizeof(struct args_struct_t));
used_args += count - 1;
/*
* -1 as the end marker should be overwritten next time something
* is added
*/
}
void nsi_add_testargs_option(void)
{
static struct args_struct_t testargs_options[] = {
{
.manual = true,
.option = "testargs",
.name = "arg",
.type = 'l',
.descript = "Any argument that follows will be ignored by the top level, "
"and made available for possible tests"
},
ARG_TABLE_ENDMARKER
};
nsi_add_command_line_opts(testargs_options);
}
static void print_invalid_opt_error(char *argv)
{
nsi_print_error_and_exit("Incorrect option '%s'. Did you misspell it?"
" Is that feature supported in this build?\n",
argv);
}
/**
* Handle possible command line arguments.
*
* We also store them for later use by possible test applications
*/
void nsi_handle_cmd_line(int argc, char *argv[])
{
int i;
nsi_add_testargs_option();
s_argv = argv;
s_argc = argc;
nsi_cmd_args_set_defaults(args_struct);
for (i = 1; i < argc; i++) {
if ((nsi_cmd_is_option(argv[i], "testargs", 0))) {
test_argc = argc - i - 1;
test_argv = &argv[i+1];
break;
}
if (!nsi_cmd_parse_one_arg(argv[i], args_struct)) {
nsi_cmd_print_switches_help(args_struct);
print_invalid_opt_error(argv[i]);
}
}
}
/**
* The application/test can use this function to inspect all the command line
* arguments
*/
void nsi_get_cmd_line_args(int *argc, char ***argv)
{
*argc = s_argc;
*argv = s_argv;
}
/**
* The application/test can use this function to inspect the command line
* arguments received after --testargs
*/
void nsi_get_test_cmd_line_args(int *argc, char ***argv)
{
*argc = test_argc;
*argv = test_argv;
}

View file

@ -0,0 +1,420 @@
/*
* Copyright (c) 2018 Oticon A/S
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "nsi_tracing.h"
#include "nsi_cmdline.h"
#include "nsi_cmdline_internal.h"
#include "nsi_cpu_es_if.h"
/**
* Check if <arg> is the option <option>
* The accepted syntax is:
* * For options without a value following:
* [-[-]]<option>
* * For options with value:
* [-[-]]<option>{:|=}<value>
*
* Returns 0 if it is not, or a number > 0 if it is.
* The returned number is the number of characters it went through
* to find the end of the option including the ':' or '=' character in case of
* options with value
*/
int nsi_cmd_is_option(const char *arg, const char *option, int with_value)
{
int of = 0;
size_t to_match_len = strlen(option);
if (arg[of] == '-') {
of++;
}
if (arg[of] == '-') {
of++;
}
if (!with_value) {
if (strcmp(&arg[of], option) != 0) {
return 0;
} else {
return of + to_match_len;
}
}
while (!(arg[of] == 0 && *option == 0)) {
if (*option == 0) {
if ((arg[of] == ':') || (arg[of] == '=')) {
of++;
break;
}
return 0;
}
if (arg[of] != *option) {
return 0;
}
of++;
option++;
}
if (arg[of] == 0) { /* we need a value to follow */
nsi_print_error_and_exit("Incorrect option syntax '%s'. The "
"value should follow the options. "
"For example --ratio=3\n",
arg);
}
return of;
}
/**
* Return 1 if <arg> matches an accepted help option.
* 0 otherwise
*
* Valid help options are [-[-]]{?|h|help}
* with the h or help in any case combination
*/
int nsi_cmd_is_help_option(const char *arg)
{
if (arg[0] == '-') {
arg++;
}
if (arg[0] == '-') {
arg++;
}
if ((strcasecmp(arg, "?") == 0) ||
(strcasecmp(arg, "h") == 0) ||
(strcasecmp(arg, "help") == 0)) {
return 1;
} else {
return 0;
}
}
#define CMD_TYPE_ERROR "Coding error: type %c not understood"
#define CMD_ERR_BOOL_SWI "Programming error: I only know how to "\
"automatically read boolean switches\n"
/**
* Read out a the value following an option from str, and store it into
* <dest>
* <type> indicates the type of parameter (and type of dest pointer)
* 'b' : boolean
* 's' : string (char *)
* 'u' : 32 bit unsigned integer
* 'U' : 64 bit unsigned integer
* 'i' : 32 bit signed integer
* 'I' : 64 bit signed integer
* 'd' : *double* float
*
* Note: list type ('l') cannot be handled by this function and must always be
* manual
*
* <long_d> is the long name of the option
*/
void nsi_cmd_read_option_value(const char *str, void *dest, const char type,
const char *option)
{
int error = 0;
char *endptr = NULL;
switch (type) {
case 'b':
if (strcasecmp(str, "false") == 0) {
*(bool *)dest = false;
endptr = (char *)str + 5;
} else if (strcmp(str, "0") == 0) {
*(bool *)dest = false;
endptr = (char *)str + 1;
} else if (strcasecmp(str, "true") == 0) {
*(bool *)dest = true;
endptr = (char *)str + 4;
} else if (strcmp(str, "1") == 0) {
*(bool *)dest = true;
endptr = (char *)str + 1;
} else {
error = 1;
}
break;
case 's':
*(char **)dest = (char *)str;
endptr = (char *)str + strlen(str);
break;
case 'u':
*(uint32_t *)dest = strtoul(str, &endptr, 0);
break;
case 'U':
*(uint64_t *)dest = strtoull(str, &endptr, 0);
break;
case 'i':
*(int32_t *)dest = strtol(str, &endptr, 0);
break;
case 'I':
*(int64_t *)dest = strtoll(str, &endptr, 0);
break;
case 'd':
*(double *)dest = strtod(str, &endptr);
break;
default:
nsi_print_error_and_exit(CMD_TYPE_ERROR, type);
/* Unreachable */
break;
}
if (!error && endptr && *endptr != 0) {
error = 1;
}
if (error) {
nsi_print_error_and_exit("Error reading value of %s '%s'. Use"
" --help for usage information\n",
option, str);
}
}
/**
* Initialize existing dest* to defaults based on type
*/
void nsi_cmd_args_set_defaults(struct args_struct_t args_struct[])
{
int count = 0;
while (args_struct[count].option != NULL) {
if (args_struct[count].dest == NULL) {
count++;
continue;
}
switch (args_struct[count].type) {
case 0: /* does not have storage */
break;
case 'b':
*(bool *)args_struct[count].dest = false;
break;
case 's':
*(char **)args_struct[count].dest = NULL;
break;
case 'u':
*(uint32_t *)args_struct[count].dest = UINT32_MAX;
break;
case 'U':
*(uint64_t *)args_struct[count].dest = UINT64_MAX;
break;
case 'i':
*(int32_t *)args_struct[count].dest = INT32_MAX;
break;
case 'I':
*(int64_t *)args_struct[count].dest = INT64_MAX;
break;
case 'd':
*(double *)args_struct[count].dest = (double)NAN;
break;
default:
nsi_print_error_and_exit(CMD_TYPE_ERROR,
args_struct[count].type);
break;
}
count++;
}
}
/**
* For the help messages:
* Generate a string containing how the option described by <args_s_el>
* should be used
*
* The string is saved in <buf> which has been allocated <size> bytes by the
* caller
*/
static void nsi_cmd_gen_switch_syntax(char *buf, int size,
struct args_struct_t *args_s_el)
{
int ret = 0;
if (size <= 0) {
return;
}
if (args_s_el->is_mandatory == false) {
*buf++ = '[';
size--;
}
if (args_s_el->is_switch == true) {
ret = snprintf(buf, size, "-%s", args_s_el->option);
} else {
if (args_s_el->type != 'l') {
ret = snprintf(buf, size, "-%s=<%s>",
args_s_el->option, args_s_el->name);
} else {
ret = snprintf(buf, size, "-%s <%s>...",
args_s_el->option, args_s_el->name);
}
}
if (ret < 0) {
nsi_print_error_and_exit("Unexpected error in %s %i\n",
__FILE__, __LINE__);
}
if (size - ret < 0) {
/*
* If we run out of space we can just stop,
* this is not critical
*/
return;
}
buf += ret;
size -= ret;
if (args_s_el->is_mandatory == false) {
snprintf(buf, size, "] ");
} else {
snprintf(buf, size, " ");
}
}
/**
* Print short list of available switches
*/
void nsi_cmd_print_switches_help(struct args_struct_t args_struct[])
{
int count = 0;
int printed_in_line = strlen(_HELP_SWITCH) + 1;
fprintf(stdout, "%s ", _HELP_SWITCH);
while (args_struct[count].option != NULL) {
char stringy[_MAX_STRINGY_LEN];
nsi_cmd_gen_switch_syntax(stringy, _MAX_STRINGY_LEN,
&args_struct[count]);
if (printed_in_line + strlen(stringy) > _MAX_LINE_WIDTH) {
fprintf(stdout, "\n");
printed_in_line = 0;
}
fprintf(stdout, "%s", stringy);
printed_in_line += strlen(stringy);
count++;
}
fprintf(stdout, "\n");
}
/**
* Print the long help message of the program
*/
void nsi_cmd_print_long_help(struct args_struct_t args_struct[])
{
int ret;
int count = 0;
int printed_in_line = 0;
char stringy[_MAX_STRINGY_LEN];
nsi_cmd_print_switches_help(args_struct);
fprintf(stdout, "\n %-*s:%s\n", _LONG_HELP_ALIGN-1,
_HELP_SWITCH, _HELP_DESCR);
while (args_struct[count].option != NULL) {
int printed_right;
char *toprint;
int total_to_print;
nsi_cmd_gen_switch_syntax(stringy, _MAX_STRINGY_LEN,
&args_struct[count]);
ret = fprintf(stdout, " %-*s:", _LONG_HELP_ALIGN-1, stringy);
printed_in_line = ret;
printed_right = 0;
toprint = args_struct[count].descript;
total_to_print = strlen(toprint);
ret = fprintf(stdout, "%.*s\n",
_MAX_LINE_WIDTH - printed_in_line,
&toprint[printed_right]);
printed_right += ret - 1;
while (printed_right < total_to_print) {
fprintf(stdout, "%*s", _LONG_HELP_ALIGN, "");
ret = fprintf(stdout, "%.*s\n",
_MAX_LINE_WIDTH - _LONG_HELP_ALIGN,
&toprint[printed_right]);
printed_right += ret - 1;
}
count++;
}
fprintf(stdout, "\n");
fprintf(stdout, "Note that which options are available depends on the "
"enabled features/drivers\n\n");
}
/*
* <argv> matched the argument described in <arg_element>
*
* If arg_element->dest points to a place to store a possible value, read it
* If there is a callback registered, call it after
*/
static void nsi_cmd_handle_this_matched_arg(char *argv, int offset,
struct args_struct_t *arg_element)
{
if (arg_element->dest != NULL) {
if (arg_element->is_switch) {
if (arg_element->type == 'b') {
*(bool *)arg_element->dest = true;
} else {
nsi_print_error_and_exit(CMD_ERR_BOOL_SWI);
}
} else { /* if not a switch we need to read its value */
nsi_cmd_read_option_value(&argv[offset],
arg_element->dest,
arg_element->type,
arg_element->option);
}
}
if (arg_element->call_when_found) {
arg_element->call_when_found(argv, offset);
}
}
/**
* Try to find if this argument is in the list (and it is not manual)
* if it does, try to parse it, set its dest accordingly, and return true
* if it is not found, return false
*/
bool nsi_cmd_parse_one_arg(char *argv, struct args_struct_t args_struct[])
{
int count = 0;
int ret;
if (nsi_cmd_is_help_option(argv)) {
nsi_cmd_print_long_help(args_struct);
nsi_exit(0);
}
while (args_struct[count].option != NULL) {
if (args_struct[count].manual) {
count++;
continue;
}
ret = nsi_cmd_is_option(argv, args_struct[count].option,
!args_struct[count].is_switch);
if (ret) {
nsi_cmd_handle_this_matched_arg(argv,
ret,
&args_struct[count]);
return true;
}
count++;
}
return false;
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2018 Oticon A/S
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef NATIVE_SIMULATOR_NATIVE_SRC_NSI_CMDLINE_INTERNAL_H
#define NATIVE_SIMULATOR_NATIVE_SRC_NSI_CMDLINE_INTERNAL_H
#include <stdbool.h>
#include <stddef.h>
#include "nsi_cmdline.h"
#ifdef __cplusplus
extern "C" {
#endif
#define _MAX_LINE_WIDTH 100 /*Total width of the help message*/
/* Horizontal alignment of the 2nd column of the help message */
#define _LONG_HELP_ALIGN 30
#define _MAXOPT_SWITCH_LEN 32 /* Maximum allowed length for a switch name */
#define _MAXOPT_NAME_LEN 32 /* Maximum allowed length for a variable name */
#define _HELP_SWITCH "[-h] [--h] [--help] [-?]"
#define _HELP_DESCR "Display this help"
#define _MAX_STRINGY_LEN (_MAXOPT_SWITCH_LEN + _MAXOPT_NAME_LEN + 2 + 1 + 2 + 1)
int nsi_cmd_is_option(const char *arg, const char *option, int with_value);
int nsi_cmd_is_help_option(const char *arg);
void nsi_cmd_read_option_value(const char *str, void *dest, const char type,
const char *option);
void nsi_cmd_args_set_defaults(struct args_struct_t args_struct[]);
bool nsi_cmd_parse_one_arg(char *argv, struct args_struct_t args_struct[]);
void nsi_cmd_print_switches_help(struct args_struct_t args_struct[]);
#ifdef __cplusplus
}
#endif
#endif /* NATIVE_SIMULATOR_NATIVE_SRC_NSI_CMDLINE_INTERNAL_H */

View file

@ -0,0 +1,157 @@
/*
* Copyright (c) 2017 Oticon A/S
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <stdio.h> /* for printfs */
#include <stdarg.h> /* for va args */
#include <unistd.h>
#include "nsi_tasks.h"
#include "nsi_cmdline.h"
#include "nsi_tracing.h"
#include "nsi_main.h"
/**
* Are stdout and stderr connected to a tty
* 0 = no
* 1 = yes
* -1 = we do not know yet
* Indexed 0:stdout, 1:stderr
*/
static int is_a_tty[2] = {-1, -1};
#define ST_OUT 0
#define ST_ERR 1
static void decide_about_color(void)
{
if (is_a_tty[0] == -1) {
is_a_tty[0] = isatty(STDOUT_FILENO);
}
if (is_a_tty[1] == -1) {
is_a_tty[1] = isatty(STDERR_FILENO);
}
}
#define ERROR 0
#define WARN 1
#define TRACE 2
static const char * const trace_type_esc_start[] = {
"\x1b[1;31m", /* ERROR - Foreground color = red, bold */
"\x1b[95m", /* WARNING - Foreground color = magenta */
"\x1b[0;39m", /* TRACE - reset all styles */
};
static const char trace_esc_end[] = "\x1b[0;39m"; /* Reset all styles */
void nsi_vprint_warning(const char *format, va_list vargs)
{
if (is_a_tty[ST_ERR] == -1) {
decide_about_color();
}
if (is_a_tty[ST_ERR]) {
fprintf(stderr, "%s", trace_type_esc_start[WARN]);
}
vfprintf(stderr, format, vargs);
if (is_a_tty[ST_ERR]) {
fprintf(stderr, "%s", trace_esc_end);
}
}
void nsi_vprint_error_and_exit(const char *format, va_list vargs)
{
if (is_a_tty[ST_ERR] == -1) {
decide_about_color();
}
if (is_a_tty[ST_ERR]) {
fprintf(stderr, "%s", trace_type_esc_start[ERROR]);
}
vfprintf(stderr, format, vargs);
if (is_a_tty[ST_ERR]) {
fprintf(stderr, "%s\n", trace_esc_end);
}
nsi_exit(1);
}
void nsi_vprint_trace(const char *format, va_list vargs)
{
if (is_a_tty[ST_OUT] == -1) {
decide_about_color();
}
if (is_a_tty[ST_OUT]) {
fprintf(stdout, "%s", trace_type_esc_start[TRACE]);
}
vfprintf(stdout, format, vargs);
if (is_a_tty[ST_OUT]) {
fprintf(stdout, "%s", trace_esc_end);
}
}
static void trace_disable_color(char *argv, int offset)
{
is_a_tty[0] = 0;
is_a_tty[1] = 0;
}
static void trace_enable_color(char *argv, int offset)
{
is_a_tty[0] = -1;
is_a_tty[1] = -1;
}
static void trace_force_color(char *argv, int offset)
{
is_a_tty[0] = 1;
is_a_tty[1] = 1;
}
int nsi_trace_over_tty(int file_number)
{
return is_a_tty[file_number];
}
NSI_TASK(decide_about_color, PRE_BOOT_2, 0);
static void nsi_add_tracing_options(void)
{
static struct args_struct_t trace_options[] = {
{
.is_switch = true,
.option = "color",
.type = 'b',
.call_when_found = trace_enable_color,
.descript = "(default) Enable color in traces if printing to console"
},
{
.is_switch = true,
.option = "no-color",
.type = 'b',
.call_when_found = trace_disable_color,
.descript = "Disable color in traces even if printing to console"
},
{
.is_switch = true,
.option = "force-color",
.type = 'b',
.call_when_found = trace_force_color,
.descript = "Enable color in traces even if printing to files/pipes"
},
ARG_TABLE_ENDMARKER
};
nsi_add_command_line_opts(trace_options);
}
NSI_TASK(nsi_add_tracing_options, PRE_BOOT_1, 0);

View file

@ -0,0 +1,540 @@
/*
* Copyright (c) 2017 Oticon A/S
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* This provides a model of:
* - A system tick timer
* - A real time clock
* - A one shot HW timer which can be used to awake the CPU at a given time
* - The clock source for all of this, and therefore for the native simulator
* in the native configuration
*/
#include <stdint.h>
#include <time.h>
#include <stdbool.h>
#include <math.h>
#include "nsi_utils.h"
#include "nsi_cmdline.h"
#include "nsi_tracing.h"
#include "nsi_cpu0_interrupts.h"
#include "irq_ctrl.h"
#include "nsi_tasks.h"
#include "nsi_hws_models_if.h"
#define DEBUG_NP_TIMER 0
#if DEBUG_NP_TIMER
/**
* Helper function to convert a 64 bit time in microseconds into a string.
* The format will always be: hh:mm:ss.ssssss\0
*
* Note: the caller has to allocate the destination buffer (at least 17 chars)
*/
#include <stdio.h>
static char *us_time_to_str(char *dest, uint64_t time)
{
if (time != NSI_NEVER) {
unsigned int hour;
unsigned int minute;
unsigned int second;
unsigned int us;
hour = (time / 3600U / 1000000U) % 24;
minute = (time / 60U / 1000000U) % 60;
second = (time / 1000000U) % 60;
us = time % 1000000;
sprintf(dest, "%02u:%02u:%02u.%06u", hour, minute, second, us);
} else {
sprintf(dest, " NEVER/UNKNOWN ");
}
return dest;
}
#endif
static uint64_t hw_timer_timer; /* Event timer exposed to the HW scheduler */
static uint64_t hw_timer_tick_timer;
static uint64_t hw_timer_awake_timer;
static uint64_t tick_p; /* Period of the ticker */
static int64_t silent_ticks;
static bool real_time_mode;
static bool reset_rtc; /*"Reset" the RTC on boot*/
/*
* When this executable started running, this value shall not be changed after
* boot
*/
static uint64_t boot_time;
/*
* Ratio of the simulated clock to the real host time
* For ex. a clock_ratio = 1+100e-6 means the simulated time is 100ppm faster
* than real time
*/
static double clock_ratio = 1.0;
#if DEBUG_NP_TIMER
/*
* Offset of the simulated time vs the real host time due to drift/clock ratio
* until "last_radj_*time"
*
* A positive value means simulated time is ahead of the host time
*
* This variable is only kept for debugging purposes
*/
static int64_t last_drift_offset;
#endif
/*
* Offsets of the RTC relative to the hardware models simu_time
* "simu_time" == simulated time which starts at 0 on boot
*/
static int64_t rtc_offset;
/* Last host/real time when the ratio was adjusted */
static uint64_t last_radj_rtime;
/* Last simulated time when the ratio was adjusted */
static uint64_t last_radj_stime;
void hwtimer_set_real_time_mode(bool new_rt)
{
real_time_mode = new_rt;
}
static void hwtimer_update_timer(void)
{
hw_timer_timer = NSI_MIN(hw_timer_tick_timer, hw_timer_awake_timer);
}
static inline void host_clock_gettime(struct timespec *tv)
{
#if defined(CLOCK_MONOTONIC_RAW)
clock_gettime(CLOCK_MONOTONIC_RAW, tv);
#else
clock_gettime(CLOCK_MONOTONIC, tv);
#endif
}
static uint64_t get_host_us_time(void)
{
struct timespec tv;
host_clock_gettime(&tv);
return (uint64_t)tv.tv_sec * 1e6 + tv.tv_nsec / 1000;
}
static void hwtimer_init(void)
{
silent_ticks = 0;
hw_timer_tick_timer = NSI_NEVER;
hw_timer_awake_timer = NSI_NEVER;
hwtimer_update_timer();
if (real_time_mode) {
boot_time = get_host_us_time();
last_radj_rtime = boot_time;
last_radj_stime = 0U;
}
if (!reset_rtc) {
struct timespec tv;
uint64_t realhosttime;
clock_gettime(CLOCK_REALTIME, &tv);
realhosttime = (uint64_t)tv.tv_sec * 1e6 + tv.tv_nsec / 1000;
rtc_offset += realhosttime;
}
}
NSI_TASK(hwtimer_init, HW_INIT, 10);
/**
* Enable the HW timer tick interrupts with a period <period> in microseconds
*/
void hwtimer_enable(uint64_t period)
{
tick_p = period;
hw_timer_tick_timer = nsi_hws_get_time() + tick_p;
hwtimer_update_timer();
nsi_hws_find_next_event();
}
static void hwtimer_tick_timer_reached(void)
{
if (real_time_mode) {
uint64_t expected_rt = (hw_timer_tick_timer - last_radj_stime)
/ clock_ratio
+ last_radj_rtime;
uint64_t real_time = get_host_us_time();
int64_t diff = expected_rt - real_time;
#if DEBUG_NP_TIMER
char es[30];
char rs[30];
us_time_to_str(es, expected_rt - boot_time);
us_time_to_str(rs, real_time - boot_time);
printf("tick @%5llims: diff = expected_rt - real_time = "
"%5lli = %s - %s\n",
hw_timer_tick_timer/1000U, diff, es, rs);
#endif
if (diff > 0) { /* we need to slow down */
struct timespec requested_time;
struct timespec remaining;
requested_time.tv_sec = diff / 1e6;
requested_time.tv_nsec = (diff -
requested_time.tv_sec*1e6)*1e3;
(void) nanosleep(&requested_time, &remaining);
}
}
hw_timer_tick_timer += tick_p;
hwtimer_update_timer();
if (silent_ticks > 0) {
silent_ticks -= 1;
} else {
hw_irq_ctrl_set_irq(TIMER_TICK_IRQ);
}
}
static void hwtimer_awake_timer_reached(void)
{
hw_timer_awake_timer = NSI_NEVER;
hwtimer_update_timer();
hw_irq_ctrl_set_irq(PHONY_HARD_IRQ);
}
static void hwtimer_timer_reached(void)
{
uint64_t Now = hw_timer_timer;
if (hw_timer_awake_timer == Now) {
hwtimer_awake_timer_reached();
}
if (hw_timer_tick_timer == Now) {
hwtimer_tick_timer_reached();
}
}
NSI_HW_EVENT(hw_timer_timer, hwtimer_timer_reached, 0);
/**
* The timer HW will awake the CPU (without an interrupt) at least when <time>
* comes (it may awake it earlier)
*
* If there was a previous request for an earlier time, the old one will prevail
*
* This is meant for busy_wait() like functionality
*/
void hwtimer_wake_in_time(uint64_t time)
{
if (hw_timer_awake_timer > time) {
hw_timer_awake_timer = time;
hwtimer_update_timer();
nsi_hws_find_next_event();
}
}
/**
* The kernel wants to skip the next sys_ticks tick interrupts
* If sys_ticks == 0, the next interrupt will be raised.
*/
void hwtimer_set_silent_ticks(int64_t sys_ticks)
{
silent_ticks = sys_ticks;
}
int64_t hwtimer_get_pending_silent_ticks(void)
{
return silent_ticks;
}
/**
* During boot set the real time clock simulated time not start
* from the real host time
*/
void hwtimer_reset_rtc(void)
{
reset_rtc = true;
}
/**
* Set a time offset (microseconds) of the RTC simulated time
* Note: This should not be used after starting
*/
void hwtimer_set_rtc_offset(int64_t offset)
{
rtc_offset = offset;
}
/**
* Set the ratio of the simulated time to host (real) time.
* Note: This should not be used after starting
*/
void hwtimer_set_rt_ratio(double ratio)
{
clock_ratio = ratio;
}
/**
* Increase or decrease the RTC simulated time by offset_delta
*/
void hwtimer_adjust_rtc_offset(int64_t offset_delta)
{
rtc_offset += offset_delta;
}
/**
* Adjust the ratio of the simulated time by a factor
*/
void hwtimer_adjust_rt_ratio(double ratio_correction)
{
uint64_t current_stime = nsi_hws_get_time();
int64_t s_diff = current_stime - last_radj_stime;
/* Accumulated real time drift time since last adjustment: */
last_radj_rtime += s_diff / clock_ratio;
last_radj_stime = current_stime;
#if DEBUG_NP_TIMER
char ct[30];
int64_t r_drift = (long double)(clock_ratio-1.0)/(clock_ratio)*s_diff;
last_drift_offset += r_drift;
us_time_to_str(ct, current_stime);
printf("%s(): @%s, s_diff= %llius after last adjust\n"
" during which we drifted %.3fms\n"
" total acc drift (last_drift_offset) = %.3fms\n"
" last_radj_rtime = %.3fms (+%.3fms )\n"
" Ratio adjusted to %f\n",
__func__, ct, s_diff,
r_drift/1000.0,
last_drift_offset/1000.0,
last_radj_rtime/1000.0,
s_diff/clock_ratio/1000.0,
clock_ratio*ratio_correction);
#endif
clock_ratio *= ratio_correction;
}
/**
* Return the current simulated RTC time in microseconds
*/
int64_t hwtimer_get_simu_rtc_time(void)
{
return nsi_hws_get_time() + rtc_offset;
}
/**
* Return a version of the host time which would have drifted as if the host
* real time clock had been running from the simulated clock, and adjusted
* both in rate and in offsets as the simulated one has been.
*
* Note that this time may be significantly ahead of the simulated time
* (the time the embedded kernel thinks it is).
* This will be the case in general if the linux runner is not able to run at or
* faster than real time.
*/
void hwtimer_get_pseudohost_rtc_time(uint32_t *nsec, uint64_t *sec)
{
/*
* Note: long double has a 64bits mantissa in x86.
* Therefore to avoid loss of precision after 500 odd years into
* the epoch, we first calculate the offset from the last adjustment
* time split in us and ns. So we keep the full precision for 500 odd
* years after the last clock ratio adjustment (or boot,
* whichever is latest).
* Meaning, we will still start to loose precision after 500 odd
* years of runtime without a clock ratio adjustment, but that really
* should not be much of a problem, given that the ns lower digits are
* pretty much noise anyhow.
* (So, all this is a huge overkill)
*
* The operation below in plain is just:
* st = (rt - last_rt_adj_time)*ratio + last_dt_adj_time
* where st = simulated time
* rt = real time
* last_rt_adj_time = time (real) when the last ratio
* adjustment took place
* last_st_adj_time = time (simulated) when the last ratio
* adjustment took place
* ratio = ratio between simulated time and real time
*/
struct timespec tv;
host_clock_gettime(&tv);
uint64_t rt_us = (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_nsec / 1000;
uint32_t rt_ns = tv.tv_nsec % 1000;
long double drt_us = (long double)rt_us - last_radj_rtime;
long double drt_ns = drt_us * 1000.0L + (long double)rt_ns;
long double st = drt_ns * (long double)clock_ratio +
(long double)(last_radj_stime + rtc_offset) * 1000.0L;
*nsec = fmodl(st, 1e9L);
*sec = st / 1e9L;
}
static struct {
double stop_at;
double rtc_offset;
double rt_drift;
double rt_ratio;
} args;
static void cmd_stop_at_found(char *argv, int offset)
{
NSI_ARG_UNUSED(offset);
if (args.stop_at < 0) {
nsi_print_error_and_exit("Error: stop-at must be positive "
"(%s)\n", argv);
}
nsi_hws_set_end_of_time(args.stop_at*1e6);
}
static void cmd_realtime_found(char *argv, int offset)
{
NSI_ARG_UNUSED(argv);
NSI_ARG_UNUSED(offset);
hwtimer_set_real_time_mode(true);
}
static void cmd_no_realtime_found(char *argv, int offset)
{
NSI_ARG_UNUSED(argv);
NSI_ARG_UNUSED(offset);
hwtimer_set_real_time_mode(false);
}
static void cmd_rtcoffset_found(char *argv, int offset)
{
NSI_ARG_UNUSED(argv);
NSI_ARG_UNUSED(offset);
hwtimer_set_rtc_offset(args.rtc_offset*1e6);
}
static void cmd_rt_drift_found(char *argv, int offset)
{
NSI_ARG_UNUSED(argv);
NSI_ARG_UNUSED(offset);
if (!(args.rt_drift > -1)) {
nsi_print_error_and_exit("The drift needs to be > -1. "
"Please use --help for more info\n");
}
args.rt_ratio = args.rt_drift + 1;
hwtimer_set_rt_ratio(args.rt_ratio);
}
static void cmd_rt_ratio_found(char *argv, int offset)
{
NSI_ARG_UNUSED(argv);
NSI_ARG_UNUSED(offset);
if ((args.rt_ratio <= 0)) {
nsi_print_error_and_exit("The ratio needs to be > 0. "
"Please use --help for more info\n");
}
hwtimer_set_rt_ratio(args.rt_ratio);
}
static void cmd_rtcreset_found(char *argv, int offset)
{
(void) argv;
(void) offset;
hwtimer_reset_rtc();
}
static void nsi_add_time_options(void)
{
static struct args_struct_t timer_options[] = {
{
.is_switch = true,
.option = "rt",
.type = 'b',
.call_when_found = cmd_realtime_found,
.descript = "Slow down the execution to the host real time, "
"or a ratio of it (see --rt-ratio below)"
},
{
.is_switch = true,
.option = "no-rt",
.type = 'b',
.call_when_found = cmd_no_realtime_found,
.descript = "Do NOT slow down the execution to real time, but advance "
"the simulated time as fast as possible and decoupled from "
"the host time"
},
{
.option = "rt-drift",
.name = "dratio",
.type = 'd',
.dest = (void *)&args.rt_drift,
.call_when_found = cmd_rt_drift_found,
.descript = "Drift of the simulated clock relative to the host real time. "
"Normally this would be set to a value of a few ppm (e.g. 50e-6"
") This option has no effect in non real time mode"
},
{
.option = "rt-ratio",
.name = "ratio",
.type = 'd',
.dest = (void *)&args.rt_ratio,
.call_when_found = cmd_rt_ratio_found,
.descript = "Relative speed of the simulated time vs real time. "
"For ex. set to 2 to have simulated time pass at double the "
"speed of real time. "
"Note that both rt-drift & rt-ratio adjust the same clock "
"speed, and therefore it does not make sense to use them "
"simultaneously. "
"This option has no effect in non real time mode"
},
{
.option = "rtc-offset",
.name = "time_offset",
.type = 'd',
.dest = (void *)&args.rtc_offset,
.call_when_found = cmd_rtcoffset_found,
.descript = "At boot, offset the RTC clock by this amount of seconds"
},
{
.is_switch = true,
.option = "rtc-reset",
.type = 'b',
.call_when_found = cmd_rtcreset_found,
.descript = "Start the simulated real time clock at 0. Otherwise it starts "
"matching the value provided by the host real time clock"
},
{
.option = "stop_at",
.name = "time",
.type = 'd',
.dest = (void *)&args.stop_at,
.call_when_found = cmd_stop_at_found,
.descript = "In simulated seconds, when to stop automatically"
},
ARG_TABLE_ENDMARKER};
nsi_add_command_line_opts(timer_options);
}
NSI_TASK(nsi_add_time_options, PRE_BOOT_1, 1);