/**********************************************************************
 
	Copyright (C) 2003 Hirohisa MORI <joshua@nichibun.ac.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	This program is distributed in the hope that it will be 
	useful, but WITHOUT ANY WARRANTY; without even the 
	implied warranty of MERCHANTABILITY or FITNESS FOR A 
	PARTICULAR PURPOSE.

**********************************************************************/

/************************************

	machine dependent
	task routines

*************************************/

#include	<sys/wait.h>
#include	<sys/time.h>
#include	<sys/types.h>
#include	<unistd.h>
#include	<stdlib.h>
#include	<stdio.h>
#include	<signal.h>
#include	"init.h"
#include	"memory_debug.h"
#include	"machine/err.h"
#include	"task.h"
#include	"lock_level.h"

typedef void * _TKEY;

#define DTI_LENGTH		10000


typedef struct task_block {
	struct task_block *	next;
	int			cmd;
#define TBC_EXEC		1
#define TBC_FINISH		2
	int			hard_tid;
	int			soft_tid;
	int			data;
	void 			(*func)();
	unsigned int		finish_time;
} TASK_BLOCK;

typedef struct dti {
	TASK_BLOCK *		tb;
	void			(*func)();
	int			msg;
	int			msg2;
} DTI;

void setup_main_thread();
int _create_task(void (*func)(),int data,int pri);
int task_init;
int (*int_handler)();
void _task_timeout();

DTI * debug_task_info;

#define TASK_IDLE_LIMIT		3600
#define TASK_BLOCK_HASH		23
int task_cnt;

extern int pri_table[PRI_MAX];

#define MAX_TEST 10
int test_tid[MAX_TEST];
int test_tno[MAX_TEST];

TASK_BLOCK *	task_hash[TASK_BLOCK_HASH];
SEM		task_lock;
int		soft_tid;

void
test_no(int no)
{
int tid;
int i;
	tid = get_tid();
	for ( i = 0 ; i < MAX_TEST ; i ++ )
		if ( test_tid[i] == tid ) {
			test_tno[i] = no;
			return;
		}
	for ( i = 0 ; i < MAX_TEST ; i ++ )
		if ( test_tid[i] == 0 ) {
			test_tid[i] = tid;
			test_tno[i] = no;
			return;
		}
}

void
print_tno(char * str)
{
int i;
	printf("%s ",str);
	for ( i = 0 ; i < MAX_TEST ; i ++ ) {
		if ( test_tid[i] == 0 )
			continue;
		printf("(%i-%i)",test_tid[i],test_tno[i]);
	}
	printf("\n");
}


void
sleep_sec(int x)
{
	sleep(x);
}

void
sig_empty_handler()
{
}


void
all_signal_mask(int from,int to)
{
struct sigaction sa;
int sig;
	for ( sig = from ; sig <= to ; sig ++ ) {
		switch ( sig ) {
		case SIGILL:
		case SIGKILL:
		case SIGBUS:
		case SIGSEGV:
		case SIGSYS:
		case SIGTERM:
			continue;
		}
		if ( sig == 9 || sig == 10 )
			continue;
		sa.sa_handler = sig_empty_handler;
		sigemptyset(&sa.sa_mask);
		sa.sa_flags = 0;
		sigaction(sig,&sa,0);
	}
}


#ifdef SIGLWP
void
siglwp_handler()
{
	signal(SIGLWP,siglwp_handler);
}
#endif



void
sigalarm_handler()
{
	signal(SIGALRM,sigalarm_handler);
}


void
sigpipe_handler()
{
	signal(SIGPIPE,sigpipe_handler);
}

void sigint_handler()
{
	if ( int_handler ) {
		if ( (*int_handler)() < 0 )
			goto end;
		signal(SIGINT,sigint_handler);
		return;
	}
end:
	printf("Interrupted\n");
	exit(1);
}

void sigchild_handler()
{
	signal(SIGCHLD,sigchild_handler);
}

int task_flags;

#ifdef DEADLOCK_CHECK
SEM sem_list;

void
lock_check_task()
{
SEM s;
	sleep_sec(20);
	for ( ; ; ) {
		printf("sem");
		for ( s = sem_list ; s ; s = s->next ) {
			if ( s->tid == 0 )
				continue;
			printf("(%x-%i)",s,s->tid);
		}
		printf("\n");
		sleep_sec(10);
	}
}
#endif

void
init_task(int flags)
{
int i;
	if ( task_init )
		return;
	task_init = 1;

	debug_task_info = malloc(sizeof(DTI)*DTI_LENGTH);
	for ( i = 0 ; i < DTI_LENGTH ; i ++ ) {
		debug_task_info[i].tb = 0;
		debug_task_info[i].func = 0;
		debug_task_info[i].msg = 0;
		debug_task_info[i].msg2 = 0;
	}

	set_alarm_mask(flags);
	init_thread_block();
	sw_init();
/*
	_create_task(lock_check_task,0,1);
*/

	task_lock = new_lock(LL_TASK);
	_create_task(_task_timeout,0,1);

	setup_main_thread();
	task_init = 2;
}


void
set_t_msg(int msg)
{
int tid;
	tid = get_tid();
	debug_task_info[tid % DTI_LENGTH].msg = msg;
}


void
set_t_msg2(int msg)
{
int tid;
	tid = get_tid();
	debug_task_info[tid % DTI_LENGTH].msg2 = msg;
}



int
get_pri(int tid)
{
struct sched_param sp;
int i;
int ret;
int p;
	if ( tid == 0 )
		tid = _get_tid();
	else	tid = get_hard_tid(tid);
	errno = 0;
	ret = pthread_getschedparam(tid,&p,&sp);
	switch ( ret ) {
	case 0:
		break;
	default:
		printf("cange_priority(1)%i\n",ret);
	}
	if ( errno )
	  perror("get_pri");
	for ( i = 0; i < PRI_MAX ; i ++ )
		if ( pri_table[i] == sp.sched_priority )
			return i;
	er_panic("invalid pri\n");
	return 0;
}

void
_change_pri(int hard_tid,int pri)
{
struct sched_param sp;
int ret;
int p;
	if ( hard_tid == 0 )
		hard_tid = _get_tid();
	if ( pri < 0 )
		pri = 0;
	if ( pri >= PRI_MAX )
		pri = PRI_MAX-1;
	if ( SCHED_POLICY != SCHED_OTHER ) {
		ret = pthread_getschedparam(hard_tid,&p,&sp);
		switch ( ret ) {
		case 0:
			break;
		default:
			printf("cange_priority(1)%i\n",ret);
		}
		sp.sched_priority = pri_table[pri];
		ret = pthread_setschedparam(hard_tid,SCHED_POLICY,&sp);
		switch ( ret ) {
		case 0:
			return;
		default:
			printf("cange_priority(2)%i\n",ret);
		}
	}
}

void
change_pri(int tid,int pri)
{
int hard_tid;
	if ( tid ) {
		hard_tid = get_hard_tid(tid);
		if ( hard_tid < 0 )
			return;
	}
	else	hard_tid = 0;
	lock_task(task_lock);
	_change_pri(hard_tid,pri);
	unlock_task(task_lock,"change_pri");
}

int
_create_task(void (*func)(),int data,int pri)
{
pthread_t tid;
int ret;
void *(*f)();
int * d;
pthread_attr_t tattr;
struct sched_param param;
int policy;
	f = (void *(*)())func;
	d = d_alloc(sizeof(int),140);
	*d = data;

	ret = pthread_attr_init(&tattr);

	policy = SCHED_POLICY;
	ret = pthread_attr_setschedpolicy(&tattr,policy);
	if ( SCHED_POLICY != SCHED_OTHER ) {
		pthread_attr_getschedparam(&tattr,&param);
		if ( pri >= PRI_MAX )
			pri = PRI_MAX-1;
		if ( pri < 0 )
			pri = 0;
		param.sched_priority = pri_table[pri];
		ret = pthread_attr_setschedparam(&tattr,&param);
		if ( ret )
			er_panic("create_task(set_schedparam)");
	}
	ret = pthread_attr_setdetachstate(&tattr,PTHREAD_CREATE_DETACHED);
	if ( ret )
		er_panic("create_task(detatch)");
	ret = pthread_create(&tid,&tattr,f,d);
/*
	fprintf(stderr,"tid = %i\n",tid);
*/
	switch ( ret ) {
	case 0:
/*
		fprintf(stderr,"OK\n");
*/
		break;
	case ESYS_AGAIN:
		fprintf(stdout,"EAGAIN %i\n",getpid());
		break;
	case ESYS_INVAL:
		fprintf(stdout,"EINVAL %i\n",getpid());
		break;
	default:
		fprintf(stdout,"default %i %i\n",getpid());
		break;
	}
	return ret;
}

int
_GET_TKEY(_TKEY d)
{
int ret;
int * ptr;
	ptr = d;
	ret = *ptr;
	d_f_ree(d);
	return ret;
}


void
rw_lock_init(RW_LOCK * rwl,int level)
{
	rwl->lock = new_lock(level);
	rwl->read_cnt = 0;
}

void
read_lock(RW_LOCK * rwl)
{
struct timeval tm;
	lock_task(rwl->lock);
	for ( ; rwl->read_cnt < 0 ; ) {
		unlock_task(rwl->lock,"read_lock(1)");
		tm.tv_sec = 0;
		tm.tv_usec = 10000;
		select(0,0,0,0,&tm);
		lock_task(rwl->lock);
	}
	rwl->read_cnt ++;
	unlock_task(rwl->lock,"read_lock(2)");
}

void
write_lock(RW_LOCK * rwl)
{
struct timeval tm;
	lock_task(rwl->lock);
	for ( ; rwl->read_cnt != 0 ; ) {
		unlock_task(rwl->lock,"write_lock(1)");
		tm.tv_sec = 0;
		tm.tv_usec = 10000;
		select(0,0,0,0,&tm);
		lock_task(rwl->lock);
	}
	rwl->read_cnt = -1;
	unlock_task(rwl->lock,"write_lock(2)");
}

void
rwl_unlock(RW_LOCK * rwl)
{
	lock_task(rwl->lock);
	if ( rwl->read_cnt > 0 )
		rwl->read_cnt --;
	else if ( rwl->read_cnt < 0 )
		rwl->read_cnt = 0;
	else	er_panic("rwl_unlock(1)");
	unlock_task(rwl->lock,"rwl_unlock(1)");
}

/*
void
init_fifo(FIFO * f)
{
	f->head = f->tail = 0;
	f->lock = new_lock();
	f->length = 0;
}
*/

void
insert_fifo(FIFO * f,FIFO_EL * e)
{
	lock_task(f->lock);
	e->next = 0;
	if ( f->head == 0 )
		f->head = f->tail = e;
	else {
		f->tail->next = e;
		f->tail = e;
	}
	f->length ++;
	unlock_task(f->lock,"insert_fifo");
}


FIFO_EL *
delete_fifo(FIFO * f)
{
FIFO_EL * ret;
	lock_task(f->lock);
	if ( f->head == 0 ) {
		ret = 0;
	}
	else {
		ret = f->head;
		f->head = ret->next;
		if ( f->head == 0 )
			f->tail = 0;
		f->length --;
	}
	unlock_task(f->lock,"delete_fifo");
	return ret;
}

int
_get_tid()
{
	return pthread_self();
}


void
_insert_task_hash(TASK_BLOCK * b)
{
unsigned int key;
	key = b->hard_tid % TASK_BLOCK_HASH;
	b->next = task_hash[key];
	task_hash[key] = b;

	if ( debug_task_info ) {
	DTI * d;
		d = &debug_task_info[b->soft_tid % DTI_LENGTH];
		d->tb = b;
		d->func = b->func;
	}
}

void
_delete_task_hash(TASK_BLOCK * b)
{
unsigned int key;
TASK_BLOCK ** bp;
	key = b->hard_tid % TASK_BLOCK_HASH;
	bp = &task_hash[key];
	for ( ; *bp ; bp = &(*bp)->next )
		if ( *bp == b ) {
			*bp = b->next;
			break;
		}
}

int
_get_new_tid()
{
int tid;
TASK_BLOCK * b;
int key;

next:
	tid = soft_tid + 1;
	if ( tid <= 1 )
		tid = 2;
	soft_tid = tid;
	for ( key = 0 ; key < TASK_BLOCK_HASH ; key ++ )
		for ( b = task_hash[key] ; b ; b = b->next )
			if ( b->soft_tid == tid )
				goto next;
	return tid;
}

TASK_BLOCK *
_get_free_block()
{
int key;
TASK_BLOCK * b;
	for ( key = 0 ; key < TASK_BLOCK_HASH ; key ++ )
		for ( b = task_hash[key] ; b ; b = b->next )
			if ( b->soft_tid == 0 )
				return b;
	return 0;
}


void
_task_grue(_TKEY d)
{
TASK_BLOCK b;
TASK_BLOCK * ctb;
int er;


	if ( (task_flags&INI_THREAD) == INI_DONTWAITCHI )
		set_ignore_chld();
	ctb = (TASK_BLOCK*)_GET_TKEY(d);
	b = *ctb;
	free(ctb);
	b.hard_tid = _get_tid();
	b.finish_time = 0;
	lock_task(task_lock);
	_insert_task_hash(&b);
	task_cnt ++;
	unlock_task(task_lock,"_task_grue");
	for ( ; ; ) {

		if ( debug_task_info ) {
		DTI * d;
			d = &debug_task_info[b.soft_tid % DTI_LENGTH];
			d->tb = &b;
			d->func = b.func;
		}

		(*b.func)(b.data);

		lock_task(task_lock);
		b.soft_tid = 0;
		b.finish_time = get_xltime();
		sleep_task((int)&b,task_lock);

		lock_task(task_lock);
		switch ( b.cmd ) {
		case TBC_EXEC:
			break;
		case TBC_FINISH:
			_delete_task_hash(&b);
			task_cnt --;
			unlock_task(task_lock,"_task_grue");
			er = pthread_detach(b.hard_tid);
			pthread_exit(0);
			return;
		default:
			er_panic("_task_grue(1)");
		}
		unlock_task(task_lock,"_task_grue");
	}
}

int 
create_task(void (*func)(),int data,int pri)
{
TASK_BLOCK * b;
int ret;

retry:
	lock_task(task_lock);
	b = _get_free_block();
	if ( b ) {
		ret = b->soft_tid = _get_new_tid();
		b->data = data;
		b->func = func;
		b->finish_time = 0;
		b->cmd = TBC_EXEC;
		_change_pri(b->hard_tid,pri);
		wakeup_task((int)b);
	}
	else {
		b = malloc(sizeof(*b));
		ret = b->soft_tid = _get_new_tid();
		b->data = data;
		b->func = func;
		b->cmd = TBC_EXEC;
		_create_task(_task_grue,(int)b,pri);
	}
	unlock_task(task_lock,"create_task");
	return ret;
}

int
get_tid()
{
int hard_tid,soft_tid;
int key;
TASK_BLOCK * b;
	hard_tid = _get_tid();
	lock_task(task_lock);
	key = hard_tid % TASK_BLOCK_HASH;
	for ( b = task_hash[key] ; b ; b = b->next )
		if ( b->hard_tid == hard_tid ) {
			soft_tid = b->soft_tid;
			unlock_task(task_lock,"get_tid");
			return soft_tid;
		}
	er_panic("get_tid");
	return 0;
}

void
_task_timeout()
{
unsigned int n;
unsigned int key;
TASK_BLOCK * b;
int cnt;
	for ( ; ; ) {
		sleep_sec(20);
		n = get_xltime();
		cnt = 0;
		lock_task(task_lock);
		for ( key = 0 ; key < TASK_BLOCK_HASH ; key ++ )
			for ( b = task_hash[key] ; b ; b = b->next ) {
				cnt ++;
				if ( b->soft_tid )
					continue;
				if ( n - b->finish_time <
						TASK_IDLE_LIMIT )
					continue;
				b->cmd = TBC_FINISH;
				wakeup_task((int)b);
			}
		unlock_task(task_lock,"_task_timeout");
	}
}

int
get_hard_tid(int tid)
{
int key;
TASK_BLOCK * b;
int ret;
	lock_task(task_lock);
	for ( key = 0 ; key < TASK_BLOCK_HASH ; key ++ )
		for ( b = task_hash[key] ; b ; b = b->next )
			if ( b->soft_tid == tid ) {
				ret = b->hard_tid;
				unlock_task(task_lock,"get_hard_tid");
				return ret;
			}
	unlock_task(task_lock,"get_hard_tid");
	return -1;
}

void
setup_main_thread()
{
TASK_BLOCK * b;
	lock_task(task_lock);
	b = malloc(sizeof(*b));
	b->soft_tid = _get_new_tid();
	b->hard_tid = pthread_self();
	b->data = 0;
	b->func = 0;
	b->cmd = TBC_EXEC;
	_insert_task_hash(b);
	unlock_task(task_lock,"setup_main_thread");
}


