/*
 * \brief  User-level scheduling
 * \author Sebastian Sumpf
 * \author Josef Soentgen
 * \author Christian Helmuth
 * \date   2012-04-25
 *
 * We use a pseudo-thread implementation based on setjmp/longjmp.
 */

/*
 * Copyright (C) 2012-2017 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.
 */

/* Genode includes */
#include <base/log.h>
#include <base/sleep.h>
#include <base/allocator.h>

/* local includes */
#include <bsd.h>
#include <scheduler.h>


/**********
 ** Task **
 **********/

bool Bsd::Task::_runnable() const
{
	switch (_state) {
	case STATE_INIT:          return true;
	case STATE_RUNNING:       return true;
	case STATE_BLOCKED:       return false;
	}

	Genode::error("state ", (unsigned)_state, " not handled by switch");
	Genode::sleep_forever();
}


bool Bsd::Task::run()
{
	if (!_runnable())
		return false;

	/*
	 * Save the execution environment. The scheduled task returns to this point
	 * after execution, i.e., at the next preemption point.
	 */
	if (_setjmp(_saved_env))
		return true;

	if (_state == STATE_INIT) {
		/* setup execution environment and call task's function */
		_state = STATE_RUNNING;
		Genode::Thread *th = Genode::Thread::myself();
		Genode::Thread::Stack_size const stack_size { _stack_size };

		_stack = th->alloc_secondary_stack(_name, stack_size).convert<void *>(
			[&] (void *sp) { return sp; },
			[&] (Genode::Thread::Stack_error) {
				Genode::error("failed to allocate secondary stack");
				return nullptr;
			});

		/* switch stack and call '_func(_arg)' */
		platform_execute(_stack, (void *)_func, _arg);
	} else {
		/* restore execution environment */
		_longjmp(_env, 1);
	}

	/* never reached */
	Genode::error("Unexpected return of Task");
	Genode::sleep_forever();
}


void Bsd::Task::schedule()
{
	/*
	 * Save the execution environment. The task will resume from here on next
	 * schedule.
	 */
	if (_setjmp(_env)) {
		return;
	}

	/* return to thread calling run() */
	_longjmp(_saved_env, 1);
}


Bsd::Task::Task(void (*func)(void*), void *arg, char const *name,
               Priority priority, Scheduler &scheduler, int stack_size)
:
	_priority(priority), _scheduler(scheduler),
	_func(func), _arg(arg), _name(name), _stack_size(stack_size)
{
	scheduler.add(this);
}


Bsd::Task::~Task()
{
	if (_stack)
		Genode::Thread::myself()->free_secondary_stack(_stack);
}


/***************
 ** Scheduler **
 ***************/

Bsd::Scheduler & Bsd::scheduler()
{
	static Bsd::Scheduler inst;
	return inst;
}


Bsd::Task *Bsd::Scheduler::current()
{
	if (!_current) {
		Genode::error("BUG: _current is zero!");
		Genode::sleep_forever();
	}

	return _current;
}


void Bsd::Scheduler::add(Task *task)
{
	Bsd::Task *p = _present_list.first();
	for ( ; p; p = p->next()) {
		if (p->priority() <= task->priority()) {
			_present_list.insert_before(task, p);
			break;
		}
	}
	if (!p)
		_present_list.append(task);
}


void Bsd::Scheduler::schedule()
{
	bool at_least_one = false;

	/*
	 * Iterate over all tasks and run first runnable.
	 *
	 * (1) If one runnable tasks was run start over from beginning of
	 *     list.
	 *
	 * (2) If no task is runnable quit scheduling (break endless
	 *     loop).
	 */
	while (true) {
		/* update current time before running task */
		Bsd::update_time();

		bool was_run = false;
		for (Task *t = _present_list.first(); t; t = t->next()) {
			/* update current before running task */
			_current = t;

			if ((was_run = t->run())) {
				at_least_one = true;
				break;
			}
		}
		if (!was_run)
			break;
	}

	if (!at_least_one) {
		Genode::warning("schedule() called without runnable tasks");
	}

	/* clear current as no task is running */
	_current = nullptr;
}


Bsd::Scheduler::Scheduler() { }


Bsd::Scheduler::~Scheduler() { }
