blob: ffd901ad7dc741b0516cd7270b8403dcba1c8327 [file] [log] [blame]
/*
* Copyright (c) 2017 Oticon A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* For all purposes, Zephyr threads see a CPU running at an infinitely high
* clock.
*
* Therefore, the code will always run until completion after each interrupt,
* after which arch_cpu_idle() will be called releasing the execution back to
* the HW models.
*
* The HW models raising an interrupt will "awake the cpu" by calling
* posix_interrupt_raised() which will transfer control to the irq handler,
* which will run inside SW/Zephyr context. After which a arch_swap() to
* whatever Zephyr thread may follow. Again, once Zephyr is done, control is
* given back to the HW models.
*
* The Zephyr OS+APP code and the HW models are gated by a mutex +
* condition as there is no reason to let the zephyr threads run while the
* HW models run or vice versa
*
*/
#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"
#include "posix_core.h"
#include "posix_arch_internal.h"
#include "kernel_internal.h"
#include "soc.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 */
int posix_is_cpu_running(void)
{
return !cpu_halted;
}
/**
* 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.
*/
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);
}
PS_DEBUG("Awaken after halted = %d\n", halted);
PC_SAFE_CALL(pthread_mutex_unlock(&mtx_cpu));
}
/**
* HW models shall call this function to "awake the CPU"
* when they are raising an interrupt
*/
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);
}
}
/**
* Normally called from arch_cpu_idle():
* the idle loop will call this function to set the CPU to "sleep".
* Others may also call this function with care. The CPU will be set to sleep
* until some interrupt awakes it.
* Interrupts should be enabled before calling.
*/
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);
/* We are awoken, normally that means some interrupt has just come
* => let the "irq handler" check if/what interrupt was raised
* and call the appropriate irq handler.
*
* Note that, the interrupt handling may trigger a arch_swap() to
* another Zephyr thread. When posix_irq_handler() returns, the Zephyr
* kernel has swapped back to this thread again
*/
posix_irq_handler();
/*
* And we go back to whatever Zephyr thread called us.
*/
}
/**
* Implementation of arch_cpu_atomic_idle() for this SOC
*/
void posix_atomic_halt_cpu(unsigned int imask)
{
posix_irq_full_unlock();
posix_halt_cpu();
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_init_multithreading();
/* 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
* anything it wants, and run until the CPU is set back to idle again
*/
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);
}
}
/**
* @brief Run the set of special native tasks corresponding to the given level
*
* @param level One of _NATIVE_*_LEVEL as defined in soc.h
*/
void run_native_tasks(int level)
{
extern void (*__native_PRE_BOOT_1_tasks_start[])(void);
extern void (*__native_PRE_BOOT_2_tasks_start[])(void);
extern void (*__native_PRE_BOOT_3_tasks_start[])(void);
extern void (*__native_FIRST_SLEEP_tasks_start[])(void);
extern void (*__native_ON_EXIT_tasks_start[])(void);
extern void (*__native_tasks_end[])(void);
static void (**native_pre_tasks[])(void) = {
__native_PRE_BOOT_1_tasks_start,
__native_PRE_BOOT_2_tasks_start,
__native_PRE_BOOT_3_tasks_start,
__native_FIRST_SLEEP_tasks_start,
__native_ON_EXIT_tasks_start,
__native_tasks_end
};
void (**fptr)(void);
for (fptr = native_pre_tasks[level]; fptr < native_pre_tasks[level+1];
fptr++) {
if (*fptr) { /* LCOV_EXCL_BR_LINE */
(*fptr)();
}
}
}
/**
* Clean up all memory allocated by the SOC and POSIX core
*
* This function can be called from both HW and SW threads
*/
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_core_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 */
}
/*
* 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
*
*/