//                              -*- Mode: CFA -*-
//
// Cforall Version 1.0.0 Copyright (C) 2016 University of Waterloo
//
// The contents of this file are covered under the licence agreement in the
// file "LICENCE" distributed with Cforall.
//
// signal.c --
//
// Author           : Thierry Delisle
// Created On       : Mon Jun 5 14:20:42 2017
// Last Modified By : Thierry Delisle
// Last Modified On : --
// Update Count     : 0
//

#include "preemption.h"

extern "C" {
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
}

#include "libhdr.h"

#define __CFA_DEFAULT_PREEMPTION__ 10000

__attribute__((weak)) unsigned int default_preemption() {
	return __CFA_DEFAULT_PREEMPTION__;
}

#define __CFA_SIGCXT__ ucontext_t *
#define __CFA_SIGPARMS__ __attribute__((unused)) int sig, __attribute__((unused)) siginfo_t *sfp, __attribute__((unused)) __CFA_SIGCXT__ cxt

static void preempt( processor   * this );
static void timeout( thread_desc * this );

void sigHandler_ctxSwitch( __CFA_SIGPARMS__ );
void sigHandler_alarm    ( __CFA_SIGPARMS__ );

static void __kernel_sigaction( int sig, void (*handler)(__CFA_SIGPARMS__), int flags );

//=============================================================================================
// Kernel Preemption logic
//=============================================================================================

void kernel_start_preemption() {
	LIB_DEBUG_PRINT_SAFE("Kernel : Starting preemption\n");
	__kernel_sigaction( SIGUSR1, sigHandler_ctxSwitch, SA_SIGINFO );
	__kernel_sigaction( SIGALRM, sigHandler_alarm    , SA_SIGINFO );
}

void kernel_stop_preemption() {
	//Block all signals, we are no longer in a position to handle them
	sigset_t mask;
	sigfillset( &mask );
	sigprocmask( SIG_BLOCK, &mask, NULL );
	LIB_DEBUG_PRINT_SAFE("Kernel : Preemption stopped\n");
}

void tick_preemption() {
	LIB_DEBUG_DO(
		char text[256];
		__attribute__((unused)) int len = snprintf( text, 256, "Ticking preemption\n" );
		LIB_DEBUG_WRITE( STDERR_FILENO, text, len );
	);

	alarm_list_t * alarms = &systemProcessor->alarms;
	__cfa_time_t currtime = __kernel_get_time();
	while( alarms->head && alarms->head->alarm < currtime ) {
		alarm_node_t * node = pop(alarms);
		LIB_DEBUG_DO(
			len = snprintf( text, 256, "Ticking %p\n", node );
			LIB_DEBUG_WRITE( STDERR_FILENO, text, len );
		);
		if( node->kernel_alarm ) {
			preempt( node->proc );
		}
		else {
			timeout( node->thrd );
		}

		if( node->period > 0 ) {
			node->alarm = currtime + node->period;
			insert( alarms, node );
		}
		else {
			node->set = false;
		}
	}

	if( alarms->head ) {
		__kernel_set_timer( alarms->head->alarm - currtime );
	}

	LIB_DEBUG_DO(
		len = snprintf( text, 256, "Ticking preemption done\n" );
		LIB_DEBUG_WRITE( STDERR_FILENO, text, len );
	);
}

void update_preemption( processor * this, __cfa_time_t duration ) {
	LIB_DEBUG_DO(
		char text[256];
		__attribute__((unused)) int len = snprintf( text, 256, "Processor : updating preemption to %lu\n", duration );
		LIB_DEBUG_WRITE( STDERR_FILENO, text, len );
	);

	alarm_node_t * alarm = this->preemption_alarm;
	duration *= 1000;

	// Alarms need to be enabled
	if ( duration > 0 && !alarm->set ) {
		alarm->alarm = __kernel_get_time() + duration;
		alarm->period = duration;
		register_self( alarm );
	}
	// Zero duraction but alarm is set
	else if ( duration == 0 && alarm->set ) {
		unregister_self( alarm );
		alarm->alarm = 0;
		alarm->period = 0;
	}
	// If alarm is different from previous, change it
	else if ( duration > 0 && alarm->period != duration ) {
		unregister_self( alarm );
		alarm->alarm = __kernel_get_time() + duration;
		alarm->period = duration;
		register_self( alarm );
	}
}

void ?{}( preemption_scope * this, processor * proc ) {
	(&this->alarm){ proc };
	this->proc = proc;
	this->proc->preemption_alarm = &this->alarm;
	update_preemption( this->proc, this->proc->preemption );

	// enable_interrupts();
}

void ^?{}( preemption_scope * this ) {
	disable_interrupts();

	update_preemption( this->proc, 0 );
}

//=============================================================================================
// Kernel Signal logic
//=============================================================================================

extern "C" {
	void disable_interrupts() {
		__attribute__((unused)) unsigned short prev = __atomic_fetch_add_2( &this_processor->disable_preempt_count, 1, __ATOMIC_SEQ_CST );
		assert( prev != (unsigned short) -1 );
	}

	void enable_interrupts_noRF() {
		unsigned short prev = __atomic_fetch_add_2( &this_processor->disable_preempt_count, -1, __ATOMIC_SEQ_CST );
		assert( prev != (unsigned short) 0 );
	}

	void enable_interrupts() {
		unsigned short prev = __atomic_fetch_add_2( &this_processor->disable_preempt_count, -1, __ATOMIC_SEQ_CST );
		assert( prev != (unsigned short) 0 );
		if( prev == 1 && this_processor->pending_preemption ) {
			this_processor->pending_preemption = false;
			LIB_DEBUG_DO(
				char text[256];
				__attribute__((unused)) int len = snprintf( text, 256, "Executing deferred CtxSwitch\n" );
				LIB_DEBUG_WRITE( STDERR_FILENO, text, len );
			);
			BlockInternal( this_processor->current_thread );
		}
	}
}

static inline void signal_unblock( bool alarm ) {
	sigset_t mask;
	sigemptyset( &mask );
	sigaddset( &mask, SIGUSR1 );

	if( alarm ) sigaddset( &mask, SIGALRM );

	if ( sigprocmask( SIG_UNBLOCK, &mask, NULL ) == -1 ) {
	    abortf( "internal error, sigprocmask" );
	} // if
}

static inline bool preemption_ready() {
	return this_processor->disable_preempt_count == 0;
}

static inline void defer_ctxSwitch() {
	this_processor->pending_preemption = true;
}

static inline void defer_alarm() {
	systemProcessor->pending_alarm = true;
}

void sigHandler_ctxSwitch( __CFA_SIGPARMS__ ) {

	LIB_DEBUG_DO(
		char text[256];
		__attribute__((unused)) int len = snprintf( text, 256, "Ctx Switch IRH\n" );
		LIB_DEBUG_WRITE( STDERR_FILENO, text, len );
	);

	signal_unblock( false );
	if( preemption_ready() ) {
		LIB_DEBUG_DO(
			len = snprintf( text, 256, "Ctx Switch IRH : Blocking thread\n" );
			LIB_DEBUG_WRITE( STDERR_FILENO, text, len );
		);
		BlockInternal( this_processor->current_thread );
	}
	else {
		LIB_DEBUG_DO(
			len = snprintf( text, 256, "Ctx Switch IRH : Defering\n" );
			LIB_DEBUG_WRITE( STDERR_FILENO, text, len );
		);
		defer_ctxSwitch();
	}
}

void sigHandler_alarm( __CFA_SIGPARMS__ ) {

	LIB_DEBUG_DO(
		char text[256];
		__attribute__((unused)) int len = snprintf( text, 256, "\nAlarm IRH\n" );
		LIB_DEBUG_WRITE( STDERR_FILENO, text, len );
	);

	signal_unblock( true );
	if( try_lock( &systemProcessor->alarm_lock ) ) {
		tick_preemption();
		unlock( &systemProcessor->alarm_lock );
	}
	else {
		defer_alarm();
	}

	if( preemption_ready() && this_processor->pending_preemption ) {
		LIB_DEBUG_DO(
			len = snprintf( text, 256, "Alarm IRH : Blocking thread\n" );
			LIB_DEBUG_WRITE( STDERR_FILENO, text, len );
		);
		this_processor->pending_preemption = false;
		BlockInternal( this_processor->current_thread );
	}
}

static void preempt( processor * this ) {
	LIB_DEBUG_DO(
		char text[256];
		__attribute__((unused)) int len = snprintf( text, 256, "Processor : signalling %p\n", this );
		LIB_DEBUG_WRITE( STDERR_FILENO, text, len );
	);

	if( this != systemProcessor ) {
		pthread_kill( this->kernel_thread, SIGUSR1 );
	}
	else {
		defer_ctxSwitch();
	}
}

static void timeout( thread_desc * this ) {
	//TODO : implement waking threads
}

static void __kernel_sigaction( int sig, void (*handler)(__CFA_SIGPARMS__), int flags ) {
	struct sigaction act;

	act.sa_sigaction = (void (*)(int, siginfo_t *, void *))handler;
	sigemptyset( &act.sa_mask );
	sigaddset( &act.sa_mask, SIGALRM );		// disabled during signal handler
	sigaddset( &act.sa_mask, SIGUSR1 );

	act.sa_flags = flags;

	if ( sigaction( sig, &act, NULL ) == -1 ) {
		// THE KERNEL IS NOT STARTED SO CALL NO uC++ ROUTINES!
		char helpText[256];
		__attribute__((unused)) int len = snprintf( helpText, 256, " __kernel_sigaction( sig:%d, handler:%p, flags:%d ), problem installing signal handler, error(%d) %s.\n",
				sig, handler, flags, errno, strerror( errno ) );
		LIB_DEBUG_WRITE( STDERR_FILENO, helpText, len );
		_exit( EXIT_FAILURE );
	} // if
}