/*
 * \brief  Libc kernel for main and pthreads user contexts
 * \author Christian Helmuth
 * \author Emery Hemingway
 * \author Norman Feske
 * \date   2016-01-22
 */

/*
 * Copyright (C) 2016-2019 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU Affero General Public License version 3.
 */

#ifndef _LIBC__INTERNAL__KERNEL_H_
#define _LIBC__INTERNAL__KERNEL_H_

/* base-internal includes */
#include <internal/call_func.h>

/* libc includes */
#include <libc/select.h>

/* libc-internal includes */
#include <internal/types.h>
#include <internal/malloc_ram_allocator.h>
#include <internal/cloned_malloc_heap_range.h>
#include <internal/timer.h>
#include <internal/pthread_pool.h>
#include <internal/init.h>
#include <internal/env.h>
#include <internal/vfs_plugin.h>
#include <internal/suspend.h>
#include <internal/resume.h>
#include <internal/select.h>
#include <internal/kernel_routine.h>
#include <internal/current_time.h>
#include <internal/kernel_timer_accessor.h>
#include <internal/watch.h>
#include <internal/signal.h>

namespace Libc { class Kernel; }


/**
 * Libc "kernel"
 *
 * This class represents the "kernel" of the libc-based application
 * Blocking and deblocking happens here on libc functions like read() or
 * select(). This combines blocking of the VFS backend and other signal sources
 * (e.g., timers). The libc task runs on the component thread and allocates a
 * secondary stack for the application task. Context switching uses
 * setjmp/longjmp.
 */
struct Libc::Kernel final : Vfs::Io_response_handler,
                            Entrypoint::Io_progress_handler,
                            Reset_malloc_heap,
                            Resume,
                            Suspend,
                            Select,
                            Kernel_routine_scheduler,
                            Current_time,
                            Watch
{
	private:

		/**
		 * Pointer to singleton instance
		 */
		static Kernel *_kernel_ptr;

		Genode::Env &_env;

		/**
		 * Allocator for libc-internal data
		 *
		 * Not mirrored to forked processes. Preserved across 'execve' calls.
		 */
		Allocator &_heap;

		/**
		 * Allocator for application-owned data
		 *
		 * Mirrored to forked processes. Not preserved across 'execve' calls.
		 */
		Reconstructible<Malloc_ram_allocator> _malloc_ram { _heap, _env.ram() };

		Constructible<Heap> _malloc_heap { };

		Registry<Registered<Cloned_malloc_heap_range> > _cloned_heap_ranges { };

		/**
		 * Reset_malloc_heap interface used by execve
		 */
		void reset_malloc_heap() override;

		Env_implementation _libc_env { _env, _heap };

		bool const _update_mtime = _libc_env.libc_config().attribute_value("update_mtime", true);

		Vfs_plugin _vfs { _libc_env, _libc_env.vfs_env(), _heap, *this,
		                  _update_mtime ? Vfs_plugin::Update_mtime::YES
		                                : Vfs_plugin::Update_mtime::NO,
		                  _libc_env.config() };

		bool  const _cloned = _libc_env.libc_config().attribute_value("cloned", false);
		pid_t const _pid    = _libc_env.libc_config().attribute_value("pid", 0U);

		Xml_node _passwd_config()
		{
			return _libc_env.libc_config().has_sub_node("passwd")
			     ? _libc_env.libc_config().sub_node("passwd")
			     : Xml_node("<empty/>");
		}

		typedef String<Vfs::MAX_PATH_LEN> Config_attr;

		Config_attr const _rtc_path = _libc_env.libc_config().attribute_value("rtc", Config_attr());

		/* handler for watching the stdout's info pseudo file */
		Constructible<Watch_handler<Kernel>> _terminal_resize_handler { };

		void _handle_terminal_resize();

		/* handler for watching user interrupts (control-c) */
		Constructible<Watch_handler<Kernel>> _user_interrupt_handler { };

		void _handle_user_interrupt();

		Signal _signal { _pid };

		Reconstructible<Io_signal_handler<Kernel>> _resume_main_handler {
			_env.ep(), *this, &Kernel::_resume_main };

		jmp_buf _kernel_context;
		jmp_buf _user_context;
		bool    _valid_user_context          = false;
		bool    _dispatch_pending_io_signals = false;

		/* io_progress_handler marker */
		bool _io_ready { false };

		Thread &_myself { *Thread::myself() };

		addr_t _kernel_stack = Thread::mystack().top;

		size_t _user_stack_size();

		void *_user_stack = {
			_myself.alloc_secondary_stack(_myself.name().string(),
			                              _user_stack_size()) };

		void (*_original_suspended_callback)() = nullptr;

		enum State { KERNEL, USER };

		State _state = KERNEL;

		Application_code *_nested_app_code = nullptr;

		Application_code *_app_code     = nullptr;
		bool              _app_returned = false;

		bool _resume_main_once  = false;
		bool _suspend_scheduled = false;

		Select_handler_base *_scheduled_select_handler = nullptr;

		Kernel_routine *_kernel_routine = nullptr;

		void _resume_main() { _resume_main_once = true; }

		Kernel_timer_accessor _timer_accessor { _env };

		struct Main_timeout : Timeout_handler
		{
			Timer_accessor        &_timer_accessor;
			Constructible<Timeout> _timeout;
			Kernel                &_kernel;

			void _construct_timeout_once()
			{
				if (!_timeout.constructed())
					_timeout.construct(_timer_accessor, *this);
			}

			Main_timeout(Timer_accessor &timer_accessor, Kernel &kernel)
			: _timer_accessor(timer_accessor), _kernel(kernel)
			{ }

			void timeout(uint64_t timeout_ms)
			{
				_construct_timeout_once();
				_timeout->start(timeout_ms);
			}

			uint64_t duration_left()
			{
				_construct_timeout_once();
				return _timeout->duration_left();
			}

			void handle_timeout() override
			{
				_kernel._resume_main();
			}
		};

		Main_timeout _main_timeout { _timer_accessor, *this };

		Pthread_pool _pthreads { _timer_accessor };

		Constructible<Clone_connection> _clone_connection { };

		struct Resumer
		{
			GENODE_RPC(Rpc_resume, void, resume);
			GENODE_RPC_INTERFACE(Rpc_resume);
		};

		struct Resumer_component : Rpc_object<Resumer, Resumer_component>
		{
			Kernel &_kernel;

			Resumer_component(Kernel &kernel) : _kernel(kernel) { }

			void resume() { _kernel.run_after_resume(); }
		};

		/**
		 * Trampoline to application (user) code
		 *
		 * This function is called by the main thread.
		 */
		static void _user_entry(Kernel *kernel)
		{
			struct Check : Suspend_functor {
				bool suspend() override { return true; }
			} check;

			kernel->_app_code->execute();
			kernel->_app_returned = true;
			kernel->_suspend_main(check, 0);
		}

		bool _main_context() const { return &_myself == Thread::myself(); }

		/**
		 * Utility to switch main context to kernel
		 *
		 * User context must be saved explicitly before this function is called
		 * to enable _switch_to_user() later.
		 */
		void _switch_to_kernel()
		{
			_state = KERNEL;
			_longjmp(_kernel_context, 1);
		}

		/**
		 * Utility to switch main context to user
		 *
		 * Kernel context must be saved explicitly before this function is called
		 * to enable _switch_to_kernel() later.
		 */
		void _switch_to_user()
		{
			if (!_valid_user_context)
				error("switching to invalid user context");

			_resume_main_once = false;
			_state = USER;
			_longjmp(_user_context, 1);
		}

		uint64_t _suspend_main(Suspend_functor &check, uint64_t timeout_ms)
		{
			/* check that we're not running on libc kernel context */
			if (Thread::mystack().top == _kernel_stack) {
				error("libc suspend() called from non-user context (",
				      __builtin_return_address(0), ") - aborting");
				exit(1);
			}

			if (!check.suspend() && !_kernel_routine)
				return 0;

			if (timeout_ms > 0)
				_main_timeout.timeout(timeout_ms);

			if (!_setjmp(_user_context)) {
				_valid_user_context = true;
				_switch_to_kernel();
			} else {
				_valid_user_context = false;
				_signal.execute_signal_handlers();
			}

			/*
			 * During the suspension of the application code a nested
			 * Libc::with_libc() call took place, which will be executed
			 * before returning to the first Libc::with_libc() call.
			 */
			if (_nested_app_code) {

				/*
				 * We have to explicitly set the user context back to true
				 * because we are borrowing it to execute our nested application
				 * code.
				 */
				_valid_user_context = true;

				_nested_app_code->execute();
				_nested_app_code = nullptr;
				_longjmp(_kernel_context, 1);
			}

			return timeout_ms > 0 ? _main_timeout.duration_left() : 0;
		}

		void _init_file_descriptors();

		void _clone_state_from_parent();

	public:

		Kernel(Genode::Env &env, Allocator &heap);

		~Kernel() { error(__PRETTY_FUNCTION__, " should not be executed!"); }

		Libc::Env & libc_env() { return _libc_env; }

		/**
		 * Setup kernel context and run libc application main context
		 *
		 * This function is called by the component thread on with_libc().
		 */
		void run(Application_code &app_code)
		{
			if (!_main_context() || _state != KERNEL) {
				error(__PRETTY_FUNCTION__, " called from non-kernel context");
				return;
			}

			_resume_main_once = false;
			_app_returned     = false;
			_app_code         = &app_code;

			/* save continuation of libc kernel (incl. current stack) */
			if (!_setjmp(_kernel_context)) {
				/* _setjmp() returned directly -> switch to user stack and call application code */

				if (_cloned) {
					_switch_to_user();
				} else {
					_state = USER;
					call_func(_user_stack, (void *)_user_entry, (void *)this);
				}

				/* never reached */
			}

			/* _setjmp() returned after _longjmp() - user context suspended */

			while ((!_app_returned) && (!_suspend_scheduled)) {

				if (_kernel_routine) {
					Kernel_routine &routine = *_kernel_routine;

					/* the 'kernel_routine' may install another kernel routine */
					_kernel_routine = nullptr;
					routine.execute_in_kernel();
					if (!_kernel_routine)
						_switch_to_user();
				}

				if (_dispatch_pending_io_signals) {
					/* dispatch pending signals but don't block */
					while (_env.ep().dispatch_pending_io_signal()) ;
				} else {
					/* block for signals */
					_env.ep().wait_and_dispatch_one_io_signal();
				}

				if (!_kernel_routine && _resume_main_once && !_setjmp(_kernel_context))
					_switch_to_user();
			}

			_suspend_scheduled = false;
		}

		/*
		 * Run libc application main context after suspend and resume
		 */
		void run_after_resume()
		{
			if (!_setjmp(_kernel_context))
				_switch_to_user();

			while ((!_app_returned) && (!_suspend_scheduled)) {
				_env.ep().wait_and_dispatch_one_io_signal();
				if (_resume_main_once && !_setjmp(_kernel_context))
					_switch_to_user();
			}

			_suspend_scheduled = false;
		}

		/**
		 * Resume interface
		 */
		void resume_all() override
		{
			if (_app_returned) {
				if (_scheduled_select_handler)
					_scheduled_select_handler->dispatch_select();
			} else {
				if (_main_context())
					_resume_main();
				else
					Signal_transmitter(*_resume_main_handler).submit();
			}

			_pthreads.resume_all();
		}

		/**
		 * Suspend interface
		 */
		uint64_t suspend(Suspend_functor &check, uint64_t timeout_ms) override
		{
			if (timeout_ms > 0
			 && timeout_ms > _timer_accessor.timer().max_timeout()) {
				warning("libc: limiting exceeding timeout of ",
				        timeout_ms, " ms to maximum of ",
				        _timer_accessor.timer().max_timeout(), " ms");

				timeout_ms = min(timeout_ms, _timer_accessor.timer().max_timeout());
			}

			return _main_context() ? _suspend_main(check, timeout_ms)
			                       : _pthreads.suspend_myself(check, timeout_ms);
		}

		void dispatch_pending_io_signals()
		{
			if (!_main_context()) return;

			if (!_setjmp(_user_context)) {
				_valid_user_context          = true;
				_dispatch_pending_io_signals = true;
				_resume_main_once            = true; /* afterwards resume main */
				_switch_to_kernel();
			} else {
				_valid_user_context          = false;
				_dispatch_pending_io_signals = false;
				_signal.execute_signal_handlers();
			}
		}

		/**
		 * Current_time interface
		 */
		Duration current_time() override
		{
			return _timer_accessor.timer().curr_time();
		}

		/**
		 * Called from the main context (by fork)
		 */
		void schedule_suspend(void(*original_suspended_callback) ());

		/**
		 * Select interface
		 */
		void schedule_select(Select_handler_base &h) override
		{
			_scheduled_select_handler = &h;
		}

		/**
		 * Select interface
		 */
		void deschedule_select() override
		{
			_scheduled_select_handler = nullptr;
		}

		/**
		 * Called from the context of the initial thread (on fork)
		 */
		void entrypoint_suspended()
		{
			_resume_main_handler.destruct();

			_original_suspended_callback();
		}

		/**
		 * Called from the context of the initial thread (after fork)
		 */
		void entrypoint_resumed()
		{
			_resume_main_handler.construct(_env.ep(), *this, &Kernel::_resume_main);

			Resumer_component resumer { *this };

			Capability<Resumer> resumer_cap =
				_env.ep().rpc_ep().manage(&resumer);

			resumer_cap.call<Resumer::Rpc_resume>();

			_env.ep().rpc_ep().dissolve(&resumer);
		}

		/**
		 * Return if main is currently suspended
		 */
		bool main_suspended() { return _state == KERNEL; }

		/**
		 * Public alias for _main_context()
		 */
		bool main_context() const { return _main_context(); }

		/**
		 * Execute application code while already executing in run()
		 */
		void nested_execution(Application_code &app_code)
		{
			_nested_app_code = &app_code;

			if (!_setjmp(_kernel_context)) {
				_switch_to_user();
			}
		}

		/**
		 * Watch interface
		 */
		Vfs::Vfs_watch_handle *alloc_watch_handle(char const *path) override
		{
			Vfs::Vfs_watch_handle *watch_handle { nullptr };
			typedef Vfs::Directory_service::Watch_result Result;
			return _libc_env.vfs().watch(path, &watch_handle, _heap) == Result::WATCH_OK
				? watch_handle : nullptr;
		}


		/****************************************
		 ** Vfs::Io_response_handler interface **
		 ****************************************/

		void read_ready_response() override {
			_io_ready = true; }

		void io_progress_response() override {
			_io_ready = true; }


		/**********************************************
		 * Entrypoint::Io_progress_handler interface **
		 **********************************************/

		void handle_io_progress() override;

		/**
		 * Kernel_routine_scheduler interface
		 */
		void register_kernel_routine(Kernel_routine &kernel_routine) override
		{
			_kernel_routine = &kernel_routine;
		}


		/********************************
		 ** Access to kernel singleton **
		 ********************************/

		struct Kernel_called_prior_initialization : Exception { };

		static Kernel &kernel()
		{
			if (!_kernel_ptr)
				throw Kernel_called_prior_initialization();

			return *_kernel_ptr;
		}
};

#endif /* _LIBC__INTERNAL__KERNEL_H_ */
