/**********************************************************************
 
	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.

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


#include	<unistd.h>
#include	"utils.h"
#include	"machine/include.h"
#include	"task.h"
#include	"pri_level.h"

extern D_SEM task_lock;
WD_ENTRY * wd_list;
WD_ENTRY wd_dummy;
int _create_task(void (*func)(),int data,int pri);

int wd_pipe[2];

void
init_wd();
int _wait_data_unlock(WD_ENTRY * e);
void wd_task();

void
init_wd()
{
	for ( ; pipe(wd_pipe) < 0 ; );
	create_task(wd_task,0,5);
}

void
wd_task()
{
struct pollfd * p_ptr;
int fd_nos;
WD_ENTRY * e,* e2;
int i;
char ch;
int er;
	for ( ; ; ) {
		_lock_task(task_lock,__FILE__,__LINE__);
		for ( ; wd_list == 0 ; ) {
			t_sleep_task((int)&wd_list,
				task_lock,0);
			_lock_task(task_lock,__FILE__,__LINE__);
		}
		fd_nos = 0;
		for ( e = wd_list ; e ; e = e->next )
			if ( e->state == WDS_POLLING )
				fd_nos ++;
		fd_nos ++;
		p_ptr = malloc(sizeof(p_ptr[0])*(fd_nos));
		memset(p_ptr,0,sizeof(p_ptr[0])*(fd_nos));
		e = wd_list;
		p_ptr[0].fd = wd_pipe[0];
		p_ptr[0].events = POLLIN;
		p_ptr[0].revents = 0;
		for ( i = 1 ; i < fd_nos ; e = e->next ) {
			if ( e->state != WDS_POLLING )
				continue;
			p_ptr[i].fd = e->fd;
			if ( e->type & WDT_READ )
				p_ptr[i].events |= POLLIN;
			if ( e->type & WDT_WRITE )
				p_ptr[i].events |= POLLOUT;
			i ++;
		}
		_unlock_task(task_lock,"wdl",__FILE__,__LINE__);
	retry:
		if ( poll(p_ptr,fd_nos,-1) < 0 ) {
			switch( errno ) {
			case EAGAIN:
				sleep_sec(1);
				goto retry;
			case EINTR:
				goto retry;
			default:
				er_panic("polling");
			}
		}

		if ( p_ptr[0].revents ) {
			for ( ; (er=read(wd_pipe[0],&ch,1)) == 0 ; );
			if ( er < 0 )
				er_panic("wdl");
			if ( ch == 'r' ) {
				free(p_ptr);
				continue;
			}
		}
		_lock_task(task_lock,__FILE__,__LINE__);
		e = wd_list;
		for ( i = 1 ; i < fd_nos ; i ++ ) {
			if ( p_ptr[i].revents == 0 )
				continue;
			for ( ; e && e->fd != p_ptr[i].fd ;
					e = e->next ) {
				if ( e->state == WDS_SIGNAL )
					t_wakeup_task((int)e,0);
			}
			if ( e == 0 )
				er_panic("wdl");
			for ( e2 = wd_list ; e2 != e ; e2 = e2->next ) {
				if ( e2->fd == e->fd )
					goto next;
			}
			for ( e2 = e->next ; e2 ; e2 = e2->next ) {
				if ( e2->fd == e->fd &&
						e2->state != WDS_POLLING )
					goto next;
			}
			e->state = WDS_LOCKED;
			e->p_events = p_ptr[i].events;
			e->p_revents = p_ptr[i].revents;
			t_wakeup_task((int)e,0);
		next:	;
		}
		for ( ; e ; e = e->next ) {
			if ( e->state == WDS_SIGNAL )
				t_wakeup_task((int)e,0);
		}
		free(p_ptr);
		_unlock_task(task_lock,"wdl",__FILE__,__LINE__);
	}
}

WD_ENTRY *
wait_data_lock(int fd,int type)
{
WD_ENTRY * e;
WD_ENTRY * ret;
char ch;
int er;

	if ( fd >= 0x10000 )
		return &wd_dummy;
	if ( type == WDT_WRITE )
		return &wd_dummy;
	_lock_task(task_lock,__FILE__,__LINE__);
	e = malloc(sizeof(*e));
	memset(e,0,sizeof(*e));
	e->fd = fd;
	e->type = type;
	e->htid = _get_tid();
	if ( type & WDT_CLOSE ) {
		e->state = WDS_CP_LOCKED;
		e->next = wd_list;
		wd_list = e;
		_unlock_task(task_lock,"wdl",__FILE__,__LINE__);
		return e;
	}
	e->next = wd_list;
	wd_list = e;

retry_polling:
	e->state = WDS_POLLING;
	ch = 'r';
	for ( ; (er=write(wd_pipe[1],&ch,1)) == 0 ; );
	if ( er < 0 )
		er_panic("wait_data_lock");
	t_wakeup_task((int)&wd_list,0);

	t_sleep_task((int)e,task_lock,0);

	_lock_task(task_lock,__FILE__,__LINE__);
	switch ( e->state ) {
	case WDS_LOCKED:
		ret = e;
		break;
	case WDS_SIGNAL:
		ret = 0;
		break;
	case WDS_POLLING:
		goto retry_polling;
	default:
		er_panic("wdl");
	}
	if ( ret == 0 )
		_wait_data_unlock(e);
	_unlock_task(task_lock,"wdl",__FILE__,__LINE__);
	return ret;
}

int
_wait_data_unlock(WD_ENTRY * e)
{
WD_ENTRY ** rp;
int ret;
	ret = 0;
	if ( e == &wd_dummy )
		return 0;
	for ( rp = &wd_list ; *rp && *rp != e ; rp = &(*rp)->next );
	if ( *rp == 0 )
		er_panic("wait_data_unlock");
	*rp = e->next;

	if ( e->state == WDS_CP_SIGNAL )
		ret = 1;
	free(e);
	t_wakeup_task((int)&wd_list,0);
	return ret;
}

int
wait_data_unlock(WD_ENTRY * e)
{
int ret;
	_lock_task(task_lock,__FILE__,__LINE__);
	ret = _wait_data_unlock(e);
	_unlock_task(task_lock,"wdl",__FILE__,__LINE__);
	return ret;
}


void
throw_signal_close_task(TKEY d)
{
int id;
int er;
	id = (int)GET_TKEY(d);

c_retry:
	er = close(id);
	if ( er < 0 && errno == EINTR )
		goto c_retry;
}



void
throw_signal(int tid,int sig)
{
HTID _tid;
WD_ENTRY * e;
char ch;
int er;
	_tid = get_hard_tid(tid);
	_lock_task(task_lock,__FILE__,__LINE__);
	for ( e = wd_list ; e ; e = e->next )
		if ( cmp_htid(e->htid,_tid) == 0 )
			break;

	if ( e ) {

		switch ( e->state ) {
		case WDS_POLLING:
			e->state = WDS_SIGNAL;
			ch = 'a';
			for ( ; (er=write(wd_pipe[1],&ch,1)) == 0; );
			if ( er < 0 )
				er_panic("throw_signal");
			break;
		case WDS_CP_LOCKED:
			e->state = WDS_CP_SIGNAL;
			_create_task(throw_signal_close_task,
					e->fd,PRI_FETCH);
			break;
		case WDS_LOCKED:
		case WDS_SIGNAL:
		case WDS_CP_SIGNAL:
			break;
		default:
			er_panic("throw_signal");
		}
	}
	_unlock_task(task_lock,"throw_signal",__FILE__,__LINE__);
}


