text: /*
   * Copyright (c) 2017 Oticon A/S
   *
   * SPDX-License-Identifier: Apache-2.0
   */
  
  /**
   * For all purposes, Zephyr threads see a CPU running at an infinitly high
   * clock.
   *
   * Therefore, the code will always run until completion after each interrupt,
   * after which k_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
   * poisix_interrupt_raised() which will transfer control to the irq handler,
   * which will run inside SW/Zephyr contenxt. After which a __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 "posix_soc_if.h"
  #include "posix_soc.h"
  #include "posix_board_if.h"
  #include "posix_core.h"
  #include "nano_internal.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.
   */
  static void posix_change_cpu_state_and_wait(bool halted)
  {
  	if (pthread_mutex_lock(&mtx_cpu)) {
  		posix_print_error_and_exit(ERPREFIX"pthread_mutex_lock()\n");
  	}
  
  	PS_DEBUG("Going to halted = %d\n", halted);
  
  	cpu_halted = halted;
  
  	/* We let the other side know the CPU has changed state */
  	if (pthread_cond_broadcast(&cond_cpu)) {
  		posix_print_error_and_exit(
  				ERPREFIX"pthread_cond_broadcast()\n");
  	}
  
  	/* 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);
  
  	if (pthread_mutex_unlock(&mtx_cpu)) {
  		posix_print_error_and_exit(ERPREFIX"pthread_mutex_unlock()\n");
  	}
  }
  
  /**
   * 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 hateld 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) {
  		main_clean_up(0);
  	}
  }
  
  
  /**
   * Called from k_cpu_idle(), the idle loop will call this function to set the
   * CPU to "sleep".
   * Interrupts should be unlocked before calling
   */
  void posix_halt_cpu(void)
  {
  	/* We change the CPU to halted state, and block this thread until it is
  	 * set running again
  	 */
  	posix_change_cpu_state_and_wait(true);
  
  	/* We are awaken when some interrupt comes => let the "irq handler"
  	 * check what interrupt was raised and call the appropriate irq handler
  	 * That may trigger a __swap() to another Zephyr thread
  	 */
  	posix_irq_handler();
  
  	/*
  	 * When the interrupt handler is back we go back to the idle loop (which
  	 * will just call us back)
  	 * Note that when we are coming back from the irq_handler the Zephyr
  	 * kernel has swapped back to the idle thread
  	 */
  }
  
  
  /**
   * Implementation of k_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 _Cstart()
   * called from posix_boot_cpu()
   */
  static void *zephyr_wrapper(void *a)
  {
  	/* Ensure posix_boot_cpu has reached the cond loop */
  	if (pthread_mutex_lock(&mtx_cpu)) {
  		posix_print_error_and_exit(ERPREFIX"pthread_mutex_lock()\n");
  	}
  	if (pthread_mutex_unlock(&mtx_cpu)) {
  		posix_print_error_and_exit(ERPREFIX"pthread_mutex_unlock()\n");
  	}
  
  #if (POSIX_ARCH_SOC_DEBUG_PRINTS)
  		pthread_t zephyr_thread = pthread_self();
  
  		PS_DEBUG("Zephyr init started (%lu)\n",
  			zephyr_thread);
  #endif
  
  	/* Start Zephyr: */
  	_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)
  {
  	if (pthread_mutex_lock(&mtx_cpu)) {
  		posix_print_error_and_exit(ERPREFIX"pthread_mutex_lock()\n");
  	}
  
  	cpu_halted = false;
  
  	pthread_t zephyr_thread;
  
  	/* Create a thread for Zephyr init: */
  	if (pthread_create(&zephyr_thread, NULL, zephyr_wrapper, NULL)) {
  		posix_print_error_and_exit(ERPREFIX"pthread_create\n");
  	}
  
  	/* And we wait until Zephyr has run til completion (has gone to idle) */
  	while (cpu_halted == false) {
  		pthread_cond_wait(&cond_cpu, &mtx_cpu);
  	}
  	if (pthread_mutex_unlock(&mtx_cpu)) {
  		posix_print_error_and_exit(ERPREFIX"pthread_mutex_unlock()\n");
  	}
  
  	if (soc_terminate) {
  		main_clean_up(0);
  	}
  }
  
  /**
   * 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)
  {
  	/*
  	 * 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();
  
  	} else if (soc_terminate == false) {
  
  		soc_terminate = true;
  
  		if (pthread_mutex_lock(&mtx_cpu)) {
  			posix_print_error_and_exit(
  					ERPREFIX"pthread_mutex_lock()\n");
  		}
  
  		cpu_halted = true;
  
  		if (pthread_cond_broadcast(&cond_cpu)) {
  			posix_print_error_and_exit(
  					ERPREFIX"pthread_cond_broadcast()\n");
  		}
  		if (pthread_mutex_unlock(&mtx_cpu)) {
  			posix_print_error_and_exit(
  					ERPREFIX"pthread_mutex_unlock()\n");
  		}
  		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
  			 */
  		}
  	}
  }
  
  