/*
 * \brief  Kernel backend for execution contexts in userland
 * \author Martin Stein
 * \date   2013-09-15
 */

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

/* Genode includes */
#include <base/thread_state.h>

/* base-hw includes */
#include <placement_new.h>

/* core includes */
#include <kernel/kernel.h>
#include <kernel/thread.h>
#include <kernel/vm.h>
#include <platform_pd.h>

using namespace Kernel;

typedef Genode::Thread_state Thread_state;

unsigned Thread::pd_id() const { return _pd ? _pd->id() : 0; }

bool Thread::_core() const { return pd_id() == core_id(); }


void Thread::_signal_context_kill_pending()
{
	assert(_state == SCHEDULED);
	_state = AWAITS_SIGNAL_CONTEXT_KILL;
	cpu_scheduler()->remove(this);
}


void Thread::_signal_context_kill_done()
{
	assert(_state == AWAITS_SIGNAL_CONTEXT_KILL);
	user_arg_0(0);
	_schedule();
}


void Thread::_signal_context_kill_failed()
{
	assert(_state == AWAITS_SIGNAL_CONTEXT_KILL);
	user_arg_0(-1);
	_schedule();
}


void Thread::_await_signal(Signal_receiver * const receiver)
{
	cpu_scheduler()->remove(this);
	_state = AWAITS_SIGNAL;
	_signal_receiver = receiver;
}


void Thread::_receive_signal(void * const base, size_t const size)
{
	assert(_state == AWAITS_SIGNAL && size <= _utcb_phys->size());
	Genode::memcpy(_utcb_phys->base(), base, size);
	_schedule();
}


void Thread::_received_ipc_request(size_t const s)
{
	switch (_state) {
	case SCHEDULED:
		user_arg_0(0);
		return;
	default:
		PERR("wrong thread state to receive IPC");
		_stop();
		return;
	}
}


void Thread::_await_ipc()
{
	switch (_state) {
	case SCHEDULED:
		cpu_scheduler()->remove(this);
		_state = AWAITS_IPC;
		return;
	default:
		PERR("wrong thread state to await IPC");
		_stop();
		return;
	}
}


void Thread::_await_ipc_succeeded(size_t const s)
{
	switch (_state) {
	case AWAITS_IPC:
		user_arg_0(0);
		_schedule();
		return;
	default:
		PERR("wrong thread state to receive IPC");
		_stop();
		return;
	}
}


void Thread::_await_ipc_failed()
{
	switch (_state) {
	case AWAITS_IPC:
		user_arg_0(-1);
		_schedule();
		return;
	default:
		PERR("wrong thread state to cancel IPC");
		_stop();
		return;
	}
}


int Thread::_resume()
{
	switch (_state) {
	case AWAITS_RESUME:
		_schedule();
		return 0;
	case SCHEDULED:
		return 1;
	case AWAITS_IPC:
		Ipc_node::cancel_waiting();
		return 0;
	case AWAITS_SIGNAL:
		Signal_handler::cancel_waiting();
		return 0;
	case AWAITS_SIGNAL_CONTEXT_KILL:
		Signal_context_killer::cancel_waiting();
		return 0;
	case AWAITS_START:
	case STOPPED:;
	}
	PERR("failed to resume thread");
	return -1;
}


void Thread::_pause()
{
	assert(_state == AWAITS_RESUME || _state == SCHEDULED);
	cpu_scheduler()->remove(this);
	_state = AWAITS_RESUME;
}


void Thread::_schedule()
{
	cpu_scheduler()->insert(this);
	_state = SCHEDULED;
}


Thread::Thread(unsigned const priority, char const * const label)
:
	Execution_context(priority),
	Thread_cpu_support(this),
	_state(AWAITS_START),
	_pd(0),
	_utcb_phys(0),
	_signal_receiver(0),
	_label(label)
{ }


void
Thread::init(unsigned const cpu_id, unsigned const pd_id_arg,
             Native_utcb * const utcb_phys, bool const start)
{
	assert(_state == AWAITS_START)

	/* FIXME: support SMP */
	if (cpu_id) { PERR("multicore processing not supported"); }

	/* store thread parameters */
	_utcb_phys = utcb_phys;

	/* join protection domain */
	_pd = Pd::pool()->object(pd_id_arg);
	assert(_pd);
	addr_t const tlb = _pd->tlb()->base();
	User_context::init_thread(tlb, pd_id());

	/* print log message */
	if (START_VERBOSE) {
		PINF("in program %u '%s' start thread %u '%s'",
		     pd_id(), pd_label(), id(), label());
	}
	/* start execution */
	if (start) { _schedule(); }
}


void Thread::_stop()
{
	if (_state == SCHEDULED) { cpu_scheduler()->remove(this); }
	_state = STOPPED;
}


void Thread::handle_exception()
{
	switch (cpu_exception) {
	case SUPERVISOR_CALL:
		_call();
		return;
	case PREFETCH_ABORT:
		_mmu_exception();
		return;
	case DATA_ABORT:
		_mmu_exception();
		return;
	case INTERRUPT_REQUEST:
		handle_interrupt();
		return;
	case FAST_INTERRUPT_REQUEST:
		handle_interrupt();
		return;
	default:
		PERR("unknown exception");
		_stop();
		reset_lap_time();
	}
}


void Thread::_receive_yielded_cpu()
{
	if (_state == AWAITS_RESUME) { _schedule(); }
	else { PERR("failed to receive yielded CPU"); }
}


void Thread::proceed()
{
	mtc()->continue_user(static_cast<Cpu::Context *>(this));
}


char const * Kernel::Thread::pd_label() const
{
	if (_core()) { return "core"; }
	if (!_pd) { return "?"; }
	return _pd->platform_pd()->label();
}


void Thread::_call_new_pd()
{
	/* check permissions */
	if (!_core()) {
		PERR("not entitled to create protection domain");
		user_arg_0(0);
		return;
	}
	/* create translation lookaside buffer and protection domain */
	void * p = (void *)user_arg_1();
	Tlb * const tlb = new (p) Tlb();
	p = (void *)((addr_t)p + sizeof(Tlb));
	Pd * const pd = new (p) Pd(tlb, (Platform_pd *)user_arg_2());
	user_arg_0(pd->id());
}


void Thread::_call_bin_pd()
{
	/* check permissions */
	if (!_core()) {
		PERR("not entitled to destruct protection domain");
		user_arg_0(-1);
		return;
	}
	/* lookup protection domain */
	unsigned id = user_arg_1();
	Pd * const pd = Pd::pool()->object(id);
	if (!pd) {
		PERR("unknown protection domain");
		user_arg_0(-1);
		return;
	}
	/* destruct translation lookaside buffer and protection domain */
	Tlb * const tlb = pd->tlb();
	pd->~Pd();
	tlb->~Tlb();

	/* clean up buffers of memory management */
	Cpu::flush_tlb_by_pid(pd->id());
	user_arg_0(0);
}


void Thread::_call_new_thread()
{
	/* check permissions */
	if (!_core()) {
		PERR("not entitled to create thread");
		user_arg_0(0);
		return;
	}
	/* create new thread */
	void * const p = (void *)user_arg_1();
	unsigned const priority = user_arg_2();
	char const * const label = (char *)user_arg_3();
	Thread * const t = new (p) Thread(priority, label);
	user_arg_0(t->id());
}


void Thread::_call_bin_thread()
{
	/* check permissions */
	assert(_core());

	/* get targeted thread */
	unsigned thread_id = (unsigned)user_arg_1();
	Thread * const thread = Thread::pool()->object(thread_id);
	assert(thread);

	/* destroy thread */
	thread->~Thread();
}


void Thread::_call_start_thread()
{
	/* check permissions */
	if (!_core()) {
		PERR("not entitled to start thread");
		user_arg_0(0);
		return;
	}
	/* dispatch arguments */
	unsigned const thread_id = user_arg_1();
	unsigned const cpu_id    = user_arg_2();
	unsigned const pd_id     = user_arg_3();
	Native_utcb * const utcb = (Native_utcb *)user_arg_4();

	/* lookup targeted thread */
	Thread * const t = Thread::pool()->object(thread_id);
	if (!t) {
		PERR("unknown thread");
		user_arg_0(0);
		return;
	}
	/* start thread */
	t->init(cpu_id, pd_id, utcb, 1);
	user_arg_0((Call_ret)t->_pd->tlb());
}


void Thread::_call_pause_thread()
{
	unsigned const tid = user_arg_1();

	/* shortcut for a thread to pause itself */
	if (!tid) {
		_pause();
		user_arg_0(0);
		return;
	}

	/* get targeted thread and check permissions */
	Thread * const t = Thread::pool()->object(tid);
	assert(t && (_core() || this == t));

	/* pause targeted thread */
	t->_pause();
	user_arg_0(0);
}


void Thread::_call_resume_thread()
{
	/* lookup thread */
	Thread * const t = Thread::pool()->object(user_arg_1());
	if (!t) {
		PERR("unknown thread");
		user_arg_0(-1);
		return;
	}
	/* check permissions */
	if (!_core() && pd_id() != t->pd_id()) {
		PERR("not entitled to resume thread");
		user_arg_0(-1);
		return;
	}
	/* resume targeted thread */
	user_arg_0(t->_resume());
}


void Thread_event::_signal_acknowledged()
{
	Cpu::tlb_insertions();
	_thread->_resume();
}


Thread_event::Thread_event(Thread * const t)
:
	_thread(t), _signal_context(0)
{ }


void Thread_event::submit()
{
	if (_signal_context && !_signal_context->submit(1)) { return; }
	PERR("failed to communicate thread event");
}


void Thread::_call_yield_thread()
{
	Thread * const t = Thread::pool()->object(user_arg_1());
	if (t) { t->_receive_yielded_cpu(); }
	cpu_scheduler()->yield();
}


void Thread::_call_await_request_msg()
{
	void * buf_base;
	size_t buf_size;
	_utcb_phys->message()->info_about_await_request(buf_base, buf_size);
	Ipc_node::await_request(buf_base, buf_size);
}


void Thread::_call_send_request_msg()
{
	Thread * const dst = Thread::pool()->object(user_arg_1());
	if (!dst) {
		PERR("unknown recipient");
		_await_ipc();
		return;
	}
	void * msg_base;
	size_t msg_size;
	void * buf_base;
	size_t buf_size;
	_utcb_phys->message()->info_about_send_request(msg_base, msg_size,
	                                               buf_base, buf_size);
	Ipc_node::send_request_await_reply(dst, msg_base, msg_size,
	                                   buf_base, buf_size);
}


void Thread::_call_send_reply_msg()
{
	void * msg_base;
	size_t msg_size;
	_utcb_phys->message()->info_about_send_reply(msg_base, msg_size);
	Ipc_node::send_reply(msg_base, msg_size);
	bool const await_request_msg = user_arg_1();
	if (await_request_msg) { _call_await_request_msg(); }
	else { user_arg_0(0); }
}


void Thread::_call_route_thread_event()
{
	/* check permissions */
	if (!_core()) {
		PERR("not entitled to route thread event");
		user_arg_0(-1);
		return;
	}
	/* get targeted thread */
	unsigned const thread_id = user_arg_1();
	Thread * const t = Thread::pool()->object(thread_id);
	if (!t) {
		PERR("unknown thread");
		user_arg_0(-1);
		return;
	}

	/* override event route */
	unsigned const event_id = user_arg_2();
	unsigned const signal_context_id = user_arg_3();
	if (t->_route_event(event_id, signal_context_id)) { user_arg_0(-1); }
	else { user_arg_0(0); }
	return;
}


int Thread::_route_event(unsigned const event_id,
                         unsigned const signal_context_id)
{
	/* lookup signal context */
	Signal_context * c;
	if (signal_context_id) {
		c = Signal_context::pool()->object(signal_context_id);
		if (!c) {
			PERR("unknown signal context");
			return -1;
		}
	} else { c = 0; }

	/* lookup event and assign signal context */
	Thread_event Thread::* e = _event(event_id);
	if (!e) { return -1; }
	(this->*e).signal_context(c);
	return 0;
}


void Thread_event::signal_context(Signal_context * const c)
{
	_signal_context = c;
	if (_signal_context) { _signal_context->ack_handler(this); }
}


unsigned Thread_event::signal_context_id() const
{
	if (_signal_context) { return _signal_context->id(); }
	return 0;
}


void Thread::_call_access_thread_regs()
{
	/* check permissions */
	if (!_core()) {
		PERR("not entitled to access thread regs");
		user_arg_0(-1);
		return;
	}
	/* get targeted thread */
	unsigned const thread_id = user_arg_1();
	Thread * const t = Thread::pool()->object(thread_id);
	if (!t) {
		PERR("unknown thread");
		user_arg_0(-1);
		return;
	}
	/* execute read operations */
	unsigned const reads = user_arg_2();
	unsigned const writes = user_arg_3();
	addr_t * const utcb = (addr_t *)_utcb_phys->base();
	addr_t * const read_ids = &utcb[0];
	addr_t * const read_values = (addr_t *)user_arg_4();
	for (unsigned i = 0; i < reads; i++) {
		if (t->_read_reg(read_ids[i], read_values[i])) {
			user_arg_0(reads + writes - i);
			return;
		}
	}
	/* execute write operations */
	addr_t * const write_ids = &utcb[reads];
	addr_t * const write_values = (addr_t *)user_arg_5();
	for (unsigned i = 0; i < writes; i++) {
		if (t->_write_reg(write_ids[i], write_values[i])) {
			user_arg_0(writes - i);
			return;
		}
	}
	user_arg_0(0);
	return;
}


void Thread::_call_update_pd()
{
	assert(_core());
	Cpu::flush_tlb_by_pid(user_arg_1());
}


void Thread::_call_update_region()
{
	assert(_core());

	/* FIXME we don't handle instruction caches by now */
	Cpu::flush_data_cache_by_virt_region((addr_t)user_arg_1(),
	                                     (size_t)user_arg_2());
}


void Thread::_print_activity_table()
{
	for (unsigned id = 0; id < MAX_THREADS; id++) {
		Thread * const t = Thread::pool()->object(id);
		if (!t) { continue; }
		t->_print_activity(t == this);
	}
	return;
}


void Thread::_print_activity(bool const printing_thread)
{
	static Thread * idle = dynamic_cast<Thread *>(cpu_scheduler()->idle());
	Genode::printf("\033[33m[%u] %s", pd_id(), pd_label());
	Genode::printf(" (%u) %s:\033[0m", id(), label());
	if (id() == idle->id()) {
		Genode::printf("\033[32m run\033[0m");
		_print_common_activity();
		return;
	}
	switch (_state) {
	case AWAITS_START: {
		Genode::printf("\033[32m init\033[0m");
		break; }
	case SCHEDULED: {
		if (!printing_thread) { Genode::printf("\033[32m run\033[0m"); }
		else { Genode::printf("\033[32m debug\033[0m"); }
		break; }
	case AWAITS_IPC: {
		_print_activity_when_awaits_ipc();
		break; }
	case AWAITS_RESUME: {
		Genode::printf("\033[32m await RES\033[0m");
		break; }
	case AWAITS_SIGNAL: {
		unsigned const receiver_id = Signal_handler::receiver()->id();
		Genode::printf("\033[32m await SIG %u\033[0m", receiver_id);
		break; }
	case AWAITS_SIGNAL_CONTEXT_KILL: {
		unsigned const context_id = Signal_context_killer::context()->id();
		Genode::printf("\033[32m await SCK %u\033[0m", context_id);
		break; }
	case STOPPED: {
		Genode::printf("\033[32m stop\033[0m");
		break; }
	}
	_print_common_activity();
}


void Thread::_print_common_activity()
{
	Genode::printf(" ip %lx sp %lx\n", ip, sp);
}


void Thread::_print_activity_when_awaits_ipc()
{
	switch (Ipc_node::state()) {
	case AWAIT_REPLY: {
		Thread * const server = dynamic_cast<Thread *>(Ipc_node::outbuf_dst());
		Genode::printf("\033[32m await RPL %u\033[0m", server->id());
		break; }
	case AWAIT_REQUEST: {
		Genode::printf("\033[32m await REQ\033[0m");
		break; }
	case PREPARE_AND_AWAIT_REPLY: {
		Thread * const server = dynamic_cast<Thread *>(Ipc_node::outbuf_dst());
		Genode::printf("\033[32m prep RPL await RPL %u\033[0m", server->id());
		break; }
	default: break;
	}
}


void Thread::_call_print_char()
{
	char const c = user_arg_1();
	if (!c) { _print_activity_table(); }
	Genode::printf("%c", (char)user_arg_1());
}


void Thread::_call_new_signal_receiver()
{
	/* check permissions */
	if (!_core()) {
		PERR("not entitled to create signal receiver");
		user_arg_0(0);
		return;
	}
	/* create receiver */
	void * const p = (void *)user_arg_1();
	Signal_receiver * const r = new (p) Signal_receiver();
	user_arg_0(r->id());
}


void Thread::_call_new_signal_context()
{
	/* check permissions */
	if (!_core()) {
		PERR("not entitled to create signal context");
		user_arg_0(0);
		return;
	}
	/* lookup receiver */
	unsigned const id = user_arg_2();
	Signal_receiver * const r = Signal_receiver::pool()->object(id);
	if (!r) {
		PERR("unknown signal receiver");
		user_arg_0(0);
		return;
	}
	/* create and assign context*/
	void * const p = (void *)user_arg_1();
	unsigned const imprint = user_arg_3();
	Signal_context * const c = new (p) Signal_context(r, imprint);
	user_arg_0(c->id());
}


void Thread::_call_await_signal()
{
	/* check wether to acknowledge a context */
	unsigned const context_id = user_arg_2();
	if (context_id) {
		Signal_context * const c = Signal_context::pool()->object(context_id);
		if (c) { c->ack(); }
		else { PERR("failed to acknowledge signal context"); }
	}
	/* lookup receiver */
	unsigned const receiver_id = user_arg_1();
	Signal_receiver * const r = Signal_receiver::pool()->object(receiver_id);
	if (!r) {
		PERR("unknown signal receiver");
		user_arg_0(-1);
		return;
	}
	/* register handler at the receiver */
	if (r->add_handler(this)) {
		PERR("failed to register handler at signal receiver");
		user_arg_0(-1);
		return;
	}
	user_arg_0(0);
}


void Thread::_call_signal_pending()
{
	/* lookup signal receiver */
	unsigned const id = user_arg_1();
	Signal_receiver * const r = Signal_receiver::pool()->object(id);
	if (!r) {
		PERR("unknown signal receiver");
		user_arg_0(0);
		return;
	}
	/* get pending state */
	user_arg_0(r->deliverable());
}


void Thread::_call_submit_signal()
{
	/* lookup signal context */
	unsigned const id = user_arg_1();
	Signal_context * const c = Signal_context::pool()->object(id);
	if(!c) {
		PERR("unknown signal context");
		user_arg_0(-1);
		return;
	}
	/* trigger signal context */
	if (c->submit(user_arg_2())) {
		PERR("failed to submit signal context");
		user_arg_0(-1);
		return;
	}
	user_arg_0(0);
}


void Thread::_call_ack_signal()
{
	/* lookup signal context */
	unsigned const id = user_arg_1();
	Signal_context * const c = Signal_context::pool()->object(id);
	if (!c) {
		PERR("unknown signal context");
		return;
	}
	/* acknowledge */
	c->ack();
}


void Thread::_call_kill_signal_context()
{
	/* lookup signal context */
	unsigned const id = user_arg_1();
	Signal_context * const c = Signal_context::pool()->object(id);
	if (!c) {
		PERR("unknown signal context");
		user_arg_0(-1);
		return;
	}
	/* kill signal context */
	if (c->kill(this)) {
		PERR("failed to kill signal context");
		user_arg_0(-1);
		return;
	}
}


void Thread::_call_bin_signal_context()
{
	/* check permissions */
	if (!_core()) {
		PERR("not entitled to kill signal context");
		user_arg_0(-1);
		return;
	}
	/* lookup signal context */
	unsigned const id = user_arg_1();
	Signal_context * const c = Signal_context::pool()->object(id);
	if (!c) {
		PERR("unknown signal context");
		user_arg_0(0);
		return;
	}
	/* destruct signal context */
	c->~Signal_context();
	user_arg_0(0);
}


void Thread::_call_bin_signal_receiver()
{
	/* check permissions */
	if (!_core()) {
		PERR("not entitled to kill signal receiver");
		user_arg_0(-1);
		return;
	}
	/* lookup signal receiver */
	unsigned const id = user_arg_1();
	Signal_receiver * const r = Signal_receiver::pool()->object(id);
	if (!r) {
		PERR("unknown signal receiver");
		user_arg_0(0);
		return;
	}
	r->~Signal_receiver();
	user_arg_0(0);
}


void Thread::_call_new_vm()
{
	/* check permissions */
	assert(_core());

	/* dispatch arguments */
	void * const allocator = (void * const)user_arg_1();
	Genode::Cpu_state_modes * const state =
		(Genode::Cpu_state_modes * const)user_arg_2();
	Signal_context * const context =
		Signal_context::pool()->object(user_arg_3());
	assert(context);

	/* create vm */
	Vm * const vm = new (allocator) Vm(state, context);

	/* return vm id */
	user_arg_0((Call_ret)vm->id());
}


void Thread::_call_run_vm()
{
	/* check permissions */
	assert(_core());

	/* get targeted vm via its id */
	Vm * const vm = Vm::pool()->object(user_arg_1());
	assert(vm);

	/* run targeted vm */
	vm->run();
}


void Thread::_call_pause_vm()
{
	/* check permissions */
	assert(_core());

	/* get targeted vm via its id */
	Vm * const vm = Vm::pool()->object(user_arg_1());
	assert(vm);

	/* pause targeted vm */
	vm->pause();
}


int Thread::_read_reg(addr_t const id, addr_t & value) const
{
	addr_t Thread::* const reg = _reg(id);
	if (reg) {
		value = this->*reg;
		return 0;
	}
	PERR("unknown thread register");
	return -1;
}


int Thread::_write_reg(addr_t const id, addr_t const value)
{
	addr_t Thread::* const reg = _reg(id);
	if (reg) {
		this->*reg = value;
		return 0;
	}
	PERR("unknown thread register");
	return -1;
}


void Thread::_call()
{
	switch (user_arg_0()) {
	case Call_id::NEW_THREAD:           _call_new_thread(); return;
	case Call_id::BIN_THREAD:           _call_bin_thread(); return;
	case Call_id::START_THREAD:         _call_start_thread(); return;
	case Call_id::PAUSE_THREAD:         _call_pause_thread(); return;
	case Call_id::RESUME_THREAD:        _call_resume_thread(); return;
	case Call_id::YIELD_THREAD:         _call_yield_thread(); return;
	case Call_id::SEND_REQUEST_MSG:     _call_send_request_msg(); return;
	case Call_id::SEND_REPLY_MSG:       _call_send_reply_msg(); return;
	case Call_id::AWAIT_REQUEST_MSG:    _call_await_request_msg(); return;
	case Call_id::UPDATE_PD:            _call_update_pd(); return;
	case Call_id::UPDATE_REGION:        _call_update_region(); return;
	case Call_id::NEW_PD:               _call_new_pd(); return;
	case Call_id::PRINT_CHAR:           _call_print_char(); return;
	case Call_id::NEW_SIGNAL_RECEIVER:  _call_new_signal_receiver(); return;
	case Call_id::NEW_SIGNAL_CONTEXT:   _call_new_signal_context(); return;
	case Call_id::KILL_SIGNAL_CONTEXT:  _call_kill_signal_context(); return;
	case Call_id::BIN_SIGNAL_CONTEXT:   _call_bin_signal_context(); return;
	case Call_id::BIN_SIGNAL_RECEIVER:  _call_bin_signal_receiver(); return;
	case Call_id::AWAIT_SIGNAL:         _call_await_signal(); return;
	case Call_id::SUBMIT_SIGNAL:        _call_submit_signal(); return;
	case Call_id::SIGNAL_PENDING:       _call_signal_pending(); return;
	case Call_id::ACK_SIGNAL:           _call_ack_signal(); return;
	case Call_id::NEW_VM:               _call_new_vm(); return;
	case Call_id::RUN_VM:               _call_run_vm(); return;
	case Call_id::PAUSE_VM:             _call_pause_vm(); return;
	case Call_id::BIN_PD:               _call_bin_pd(); return;
	case Call_id::ACCESS_THREAD_REGS:   _call_access_thread_regs(); return;
	case Call_id::ROUTE_THREAD_EVENT:   _call_route_thread_event(); return;
	default:
		PERR("unknown kernel call");
		_stop();
		reset_lap_time();
	}
}
