soc inf (native): Refactor into a top and bottom

Where the bottom is the only one which interacts with
the host operating system.
And the top the only one that interacts or is aware
of the hosted operating system (Zephyr).

The bottom uses the native simulator CPU
start/stop emulation.
By now we replicate its code as a provisional measure,
until the native simulator becomes standard.

Signed-off-by: Alberto Escolar Piedras <alberto.escolar.piedras@nordicsemi.no>
This commit is contained in:
Alberto Escolar Piedras 2023-05-26 16:42:09 +02:00 committed by Alberto Escolar
commit 7ee41b8776
8 changed files with 447 additions and 148 deletions

View file

@ -6,7 +6,13 @@ zephyr_library_sources(
cpuhalt.c
fatal.c
irq.c
nsi_compat/nsi_compat.c
nsi_compat/nce.c
posix_core.c
swap.c
thread.c
)
zephyr_include_directories(
nsi_compat/
)

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,26 @@
/*
* 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 */
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,49 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* This module exist to provide a basic compatibility shim
* from Native simulator components into the POSIX architecture.
*
* It is a transitional component, intended to facilitate
* the migration towards the Native simulator.
*/
#include "zephyr/arch/posix/posix_trace.h"
void nsi_print_error_and_exit(const char *format, ...)
{
va_list variable_args;
va_start(variable_args, format);
posix_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);
posix_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);
posix_vprint_trace(format, variable_args);
va_end(variable_args);
}
void nsi_exit(int exit_code)
{
extern void posix_exit(int exit_code);
posix_exit(exit_code);
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ARCH_POSIX_CORE_NSI_COMPAT_H
#define ARCH_POSIX_CORE_NSI_COMPAT_H
#include "nsi_tracing.h"
#include "nsi_safe_call.h"
#ifdef __cplusplus
extern "C" {
#endif
void nsi_exit(int exit_code);
#ifdef __cplusplus
}
#endif
#endif /* ARCH_POSIX_CORE_NSI_COMPAT_H */

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ARCH_POSIX_CORE_NSI_SAFE_CALLL_H
#define ARCH_POSIX_CORE_NSI_SAFE_CALLL_H
#include "nsi_tracing.h"
#include "posix_arch_internal.h"
#define NSI_SAFE_CALL PC_SAFE_CALL
#endif /* ARCH_POSIX_CORE_NSI_SAFE_CALLL_H */

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ARCH_POSIX_CORE_NSI_TRACING_H
#define ARCH_POSIX_CORE_NSI_TRACING_H
#ifdef __cplusplus
extern "C" {
#endif
void nsi_print_error_and_exit(const char *format, ...);
void nsi_print_warning(const char *format, ...);
void nsi_print_trace(const char *format, ...);
#ifdef __cplusplus
}
#endif
#endif /* ARCH_POSIX_CORE_NSI_TRACING_H */

View file

@ -24,9 +24,6 @@
*
*/
#include <pthread.h>
#include <stdbool.h>
#include <unistd.h>
#include <zephyr/arch/posix/posix_soc_if.h>
#include "posix_soc.h"
#include "posix_board_if.h"
@ -34,34 +31,15 @@
#include "posix_arch_internal.h"
#include "kernel_internal.h"
#include "soc.h"
#include "nce_if.h"
#define POSIX_ARCH_SOC_DEBUG_PRINTS 0
#define PREFIX "POSIX SOC: "
#define ERPREFIX PREFIX"error on "
#if POSIX_ARCH_SOC_DEBUG_PRINTS
#define PS_DEBUG(fmt, ...) posix_print_trace(PREFIX fmt, __VA_ARGS__)
#else
#define PS_DEBUG(...)
#endif
/* Conditional variable to know if the CPU is running or halted/idling */
static pthread_cond_t cond_cpu = PTHREAD_COND_INITIALIZER;
/* Mutex for the conditional variable posix_soc_cond_cpu */
static pthread_mutex_t mtx_cpu = PTHREAD_MUTEX_INITIALIZER;
/* Variable which tells if the CPU is halted (1) or not (0) */
static bool cpu_halted = true;
static bool soc_terminate; /* Is the program being closed */
static void *nce_st;
int posix_is_cpu_running(void)
{
return !cpu_halted;
return nce_is_cpu_running(nce_st);
}
/**
* Helper function which changes the status of the CPU (halted or running)
* and waits until somebody else changes it to the opposite
@ -75,31 +53,11 @@ int posix_is_cpu_running(void)
*/
void posix_change_cpu_state_and_wait(bool halted)
{
PC_SAFE_CALL(pthread_mutex_lock(&mtx_cpu));
PS_DEBUG("Going to halted = %d\n", halted);
cpu_halted = halted;
/* We let the other side know the CPU has changed state */
PC_SAFE_CALL(pthread_cond_broadcast(&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 (cpu_halted == halted) {
/* Here we unlock the mutex while waiting */
pthread_cond_wait(&cond_cpu, &mtx_cpu);
if (halted) {
nce_halt_cpu(nce_st);
} else {
nce_wake_cpu(nce_st);
}
PS_DEBUG("Awaken after halted = %d\n", halted);
PC_SAFE_CALL(pthread_mutex_unlock(&mtx_cpu));
}
/**
@ -111,15 +69,7 @@ void posix_interrupt_raised(void)
/* We change the CPU to running state (we awake it), and block this
* thread until the CPU is halted again
*/
posix_change_cpu_state_and_wait(false);
/*
* If while the SW was running it was decided to terminate the execution
* we stop immediately.
*/
if (soc_terminate) {
posix_exit(0);
}
nce_wake_cpu(nce_st);
}
@ -136,7 +86,7 @@ void posix_halt_cpu(void)
* We set the CPU in the halted state (this blocks this pthread
* until the CPU is awoken again by the HW models)
*/
posix_change_cpu_state_and_wait(true);
nce_halt_cpu(nce_st);
/* We are awoken, normally that means some interrupt has just come
* => let the "irq handler" check if/what interrupt was raised
@ -164,34 +114,6 @@ void posix_atomic_halt_cpu(unsigned int imask)
posix_irq_unlock(imask);
}
/**
* Just a wrapper function to call Zephyr's z_cstart()
* called from posix_boot_cpu()
*/
static void *zephyr_wrapper(void *a)
{
/* Ensure posix_boot_cpu has reached the cond loop */
PC_SAFE_CALL(pthread_mutex_lock(&mtx_cpu));
PC_SAFE_CALL(pthread_mutex_unlock(&mtx_cpu));
#if (POSIX_ARCH_SOC_DEBUG_PRINTS)
pthread_t zephyr_thread = pthread_self();
PS_DEBUG("Zephyr init started (%lu)\n",
zephyr_thread);
#endif
posix_arch_init();
/* Start Zephyr: */
z_cstart();
CODE_UNREACHABLE;
return NULL;
}
/**
* The HW models will call this function to "boot" the CPU
* == spawn the Zephyr init thread, which will then spawn
@ -199,24 +121,9 @@ static void *zephyr_wrapper(void *a)
*/
void posix_boot_cpu(void)
{
PC_SAFE_CALL(pthread_mutex_lock(&mtx_cpu));
cpu_halted = false;
pthread_t zephyr_thread;
/* Create a thread for Zephyr init: */
PC_SAFE_CALL(pthread_create(&zephyr_thread, NULL, zephyr_wrapper, NULL));
/* And we wait until Zephyr has run til completion (has gone to idle) */
while (cpu_halted == false) {
pthread_cond_wait(&cond_cpu, &mtx_cpu);
}
PC_SAFE_CALL(pthread_mutex_unlock(&mtx_cpu));
if (soc_terminate) {
posix_exit(0);
}
nce_st = nce_init();
posix_arch_init();
nce_boot_cpu(nce_st, z_cstart);
}
/**
@ -226,47 +133,7 @@ void posix_boot_cpu(void)
*/
void posix_soc_clean_up(void)
{
/* 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 (cpu_halted) {
posix_arch_clean_up();
run_native_tasks(_NATIVE_ON_EXIT_LEVEL);
} else if (soc_terminate == false) {
soc_terminate = true;
PC_SAFE_CALL(pthread_mutex_lock(&mtx_cpu));
cpu_halted = true;
PC_SAFE_CALL(pthread_cond_broadcast(&cond_cpu));
PC_SAFE_CALL(pthread_mutex_unlock(&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 */
nce_terminate(nce_st);
posix_arch_clean_up();
run_native_tasks(_NATIVE_ON_EXIT_LEVEL);
}
/*
* 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
*
*/