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
parent feaf0ff576
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
*/

File diff suppressed because it is too large Load Diff

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)();
}
}
}

Some files were not shown because too many files have changed in this diff Show More