/*
 * Copyright (c) 2017 Oticon A/S
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/*
 * The basic principle of operation is:
 *   No asynchronous behavior, no indeterminism.
 *   If you run the same thing 20 times, you get exactly the same result 20
 *   times.
 *   It does not matter if you are running from console, or in a debugger
 *   and you go for lunch in the middle of the debug session.
 *
 * This is achieved as follows:
 *  The execution of native_posix is decoupled from the underlying host and its
 *  peripherals (unless set otherwise).
 *  In general, time in native_posix is simulated.
 *
 * But, native_posix can also be linked if desired to the underlying host,
 * e.g.:You can use the provided Ethernet TAP driver, or a host BLE controller.
 *
 * In this case, the no-indeterminism principle is lost. Runs of native_posix
 * will depend on the host load and the interactions with those real host
 * peripherals.
 *
 */

#include <stdio.h>
#include <soc.h>
#include "hw_models_top.h"
#include <stdlib.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/time_units.h>
#include "cmdline.h"
#include "irq_ctrl.h"

void posix_exit(int exit_code)
{
	static int max_exit_code;

	max_exit_code = MAX(exit_code, max_exit_code);
	/*
	 * posix_soc_clean_up may not return if this is called from a SW thread,
	 * but instead it would get posix_exit() recalled again
	 * ASAP from the HW thread
	 */
	posix_soc_clean_up();
	hwm_cleanup();
	native_cleanup_cmd_line();
	exit(max_exit_code);
}

/**
 * Run all early native_posix initialization steps, including command
 * line parsing and CPU start, until we are ready to let the HW models
 * run via hwm_one_event()
 */
void posix_init(int argc, char *argv[])
{
	/*
	 * Let's ensure that even if we are redirecting to a file, we get stdout
	 * and stderr line buffered (default for console)
	 * Note that glibc ignores size. But just in case we set a reasonable
	 * number in case somebody tries to compile against a different library
	 */
	setvbuf(stdout, NULL, _IOLBF, 512);
	setvbuf(stderr, NULL, _IOLBF, 512);

	run_native_tasks(_NATIVE_PRE_BOOT_1_LEVEL);

	native_handle_cmd_line(argc, argv);

	run_native_tasks(_NATIVE_PRE_BOOT_2_LEVEL);

	hwm_init();

	run_native_tasks(_NATIVE_PRE_BOOT_3_LEVEL);

	posix_boot_cpu();

	run_native_tasks(_NATIVE_FIRST_SLEEP_LEVEL);
}

/**
 * Execute the simulator for at least the specified timeout, then
 * return.  Note that this does not affect event timing, so the "next
 * event" may be significantly after the request if the hardware has
 * not been configured to e.g. send an interrupt when expected.
 */
void posix_exec_for(uint64_t us)
{
	uint64_t start = hwm_get_time();

	do {
		hwm_one_event();
	} while (hwm_get_time() < (start + us));
}

#ifndef CONFIG_ARCH_POSIX_LIBFUZZER

/**
 * This is the actual host process main routine.  The Zephyr
 * application's main() is renamed via preprocessor trickery to avoid
 * collisions.
 *
 * Not used when building fuzz cases, as libfuzzer has its own main()
 * and calls the "OS" through a per-case fuzz test entry point.
 */
int main(int argc, char *argv[])
{
	posix_init(argc, argv);
	while (true) {
		hwm_one_event();
	}

	/* This line should be unreachable */
	return 1; /* LCOV_EXCL_LINE */
}

#else /* CONFIG_ARCH_POSIX_LIBFUZZER */

const uint8_t *posix_fuzz_buf;
size_t posix_fuzz_sz;

/**
 * Entry point for fuzzing (when enabled). Works by placing the data
 * into two known symbols, triggering an app-visible interrupt, and
 * then letting the OS run for a fixed amount of time (intended to be
 * "long enough" to handle the event and reach a quiescent state
 * again)
 */
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t sz)
{
	static bool posix_initialized;

	if (!posix_initialized) {
		posix_init(0, NULL);
		posix_initialized = true;
	}

	/* Provide the fuzz data to Zephyr as an interrupt, with
	 * "DMA-like" data placed into posix_fuzz_buf/sz
	 */
	posix_fuzz_buf = data;
	posix_fuzz_sz = sz;
	hw_irq_ctrl_set_irq(CONFIG_ARCH_POSIX_FUZZ_IRQ);

	/* Give the OS time to process whatever happened in that
	 * interrupt and reach an idle state.
	 */
	posix_exec_for(k_ticks_to_us_ceil64(CONFIG_ARCH_POSIX_FUZZ_TICKS));

	return 0;
}

#endif
