Align with native_simulator's upstream main 19293fafc9959b03ece651e5e2afb768cfa891cf Which includes: 19293fa misc trivial changes to please static analyzers 2a41263 nsi_tracing: Annotate functions as noreturn f0307c1 nct: Simplify and improve switching performance 9f0c825 nce: Optimize/improve performance 63ce7e2 nsi_utils: Add macro to define static inline functions 6035bd8 nsi_errno: Minor optimization with no functional impact Signed-off-by: Alberto Escolar Piedras <alberto.escolar.piedras@nordicsemi.no>
258 lines
6.4 KiB
C
258 lines
6.4 KiB
C
/*
|
|
* 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 <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <pthread.h>
|
|
#include <semaphore.h>
|
|
#include <errno.h>
|
|
#include "nsi_utils.h"
|
|
#include "nce_if.h"
|
|
#include "nsi_safe_call.h"
|
|
|
|
struct nce_status_t {
|
|
sem_t sem_sw; /* Semaphore to hold the CPU/SW thread(s) */
|
|
sem_t sem_hw; /* Semaphore to hold the HW thread */
|
|
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);
|
|
|
|
NSI_INLINE int nce_sem_rewait(sem_t *semaphore)
|
|
{
|
|
int ret;
|
|
|
|
while ((ret = sem_wait(semaphore)) == EINTR) {
|
|
/* Restart wait if we were interrupted */
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* 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(sem_init(&this->sem_sw, 0, 0));
|
|
NSI_SAFE_CALL(sem_init(&this->sem_hw, 0, 0));
|
|
|
|
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;
|
|
this->cpu_halted = true;
|
|
|
|
NSI_SAFE_CALL(sem_post(&this->sem_hw));
|
|
|
|
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 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 is blocked in nce_wake_cpu() */
|
|
NSI_SAFE_CALL(nce_sem_rewait(&this->sem_sw));
|
|
|
|
#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;
|
|
|
|
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));
|
|
|
|
nce_wake_cpu(this_arg);
|
|
}
|
|
|
|
/*
|
|
* 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");
|
|
}
|
|
this->cpu_halted = true;
|
|
|
|
NSI_SAFE_CALL(sem_post(&this->sem_hw));
|
|
NSI_SAFE_CALL(nce_sem_rewait(&this->sem_sw));
|
|
|
|
NCE_DEBUG("CPU awaken, HW thread held\n");
|
|
}
|
|
|
|
/*
|
|
* 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");
|
|
}
|
|
|
|
this->cpu_halted = false;
|
|
|
|
NSI_SAFE_CALL(sem_post(&this->sem_sw));
|
|
NSI_SAFE_CALL(nce_sem_rewait(&this->sem_hw));
|
|
|
|
NCE_DEBUG("CPU went to sleep, HW continues\n");
|
|
|
|
/*
|
|
* 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
|
|
*/
|