soc (native): Use nce directly from native_simulator
Instead of keeping a replica of the nce code, now that the native_simulator is in tree, let's use it directly. Signed-off-by: Alberto Escolar Piedras <alberto.escolar.piedras@nordicsemi.no>
This commit is contained in:
parent
8e40304258
commit
dadacfaac2
2 changed files with 1 additions and 293 deletions
|
@ -17,7 +17,7 @@ if(CONFIG_NATIVE_APPLICATION)
|
|||
zephyr_library_sources(
|
||||
posix_core.c
|
||||
nsi_compat/nsi_compat.c
|
||||
nsi_compat/nce.c
|
||||
${ZEPHYR_BASE}/scripts/native_simulator/common/src/nce.c
|
||||
)
|
||||
else()
|
||||
zephyr_library_sources(
|
||||
|
|
|
@ -1,292 +0,0 @@
|
|||
/*
|
||||
* 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
|
||||
*/
|
Loading…
Add table
Add a link
Reference in a new issue