/* 
 *   Creation Date: <2000/07/25 00:22:18 samuel>
 *   Time-stamp: <2003/09/02 23:06:34 samuel>
 *   
 *	<async.c>
 *	
 *	Asynchronous I/O support
 *   
 *   Copyright (C) 2000, 2001, 2002, 2003 Samuel Rydh (samuel@ibrium.se)
 *   
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public License
 *   as published by the Free Software Foundation
 *   
 */

#include "mol_config.h"
#include <signal.h>
#ifdef __linux__
#include <linux/version.h>
#endif
#include "poll.h"
#include <sys/time.h>
#include <sys/resource.h>
#include <sched.h>
#include "async.h"
#include "debugger.h"
#include "thread.h"
#include "timer.h"
#include "molcpu.h"

#define MAX_NUM_HANDLERS	16
#define MAX_NUM_AEVENTS		8

typedef struct {
	int		handler_id;
	async_handler_t	proc;
} handler_t;

typedef struct {
	int		 token;
	aevent_handler_t proc;
} aevent_t;

volatile int	 	__io_is_pending;

static handler_t 	handlers[ MAX_NUM_HANDLERS ];
static struct pollfd	ufds[ MAX_NUM_HANDLERS ];
static int 		num_handlers;
static int		num_poll;
static int		num_pending;
static int 		next_handler_id;
static int    		handlers_modified;

static aevent_t		aevents[ MAX_NUM_AEVENTS ];
static int		num_aevents;
static int		next_aevent_token;

static volatile int	cancel_poll_thread;

static int	 	pipefds[2];
static int	 	ackpipe[2];

static int		initialized;


/* only to be called from the main thread */	
static void
wait_safe( void )
{
	if( initialized && !__io_is_pending ) {
		send_aevent(0);
		while( !__io_is_pending )
			sched_yield();
	}
}


/* WARNING: handler pointers are invalid after this call! */
static void
permute_inactive( void )
{
	struct pollfd tmp_p;
	handler_t tmp_h;
	int i, j;

	for( i=0; i<num_handlers; i++ ) {
		if( ufds[i].events )
			continue;
		tmp_p = ufds[i];
		tmp_h = handlers[i];

		for( j=i+1; j<num_handlers; j++ ) {
			if( ufds[j].events ) {
				ufds[i] = ufds[j];
				handlers[i] = handlers[j];
				ufds[j] = tmp_p;
				handlers[j] = tmp_h;
				break;
			}
		}
	}

	num_poll = 0;
	for( i=0; i<num_handlers && ufds[i].events; i++ )
		num_poll++;

	handlers_modified = 1;
}

int
add_async_handler( int fd, int events, async_handler_t proc, int sigio_capable )
{
	handler_t *h;
	int id, flags;

	if( num_handlers >= MAX_NUM_HANDLERS || !proc ) {
		printm("async error: %s\n", proc ? "MAX_NUM_HANDLERS exceeded\n" : "NULL proc" );
		return -1;
	}

	wait_safe();

	/* doesn't hurt to make sure it is close-on-exec */
	fcntl( fd, F_SETFD, FD_CLOEXEC );

	/* set O_NONBLOCK */
	if( (flags=fcntl(fd, F_GETFL)) == -1 )
		return -1;
	flags |= O_NONBLOCK;
	if( fcntl(fd, F_SETFL, flags) == -1 )
		return -1;

	/* add to table */
	ufds[num_handlers].fd = fd;
	ufds[num_handlers].events = events;
	ufds[num_handlers].revents = 0;
	h = &handlers[num_handlers];
	id = h->handler_id = next_handler_id++;
	h->proc = proc;
	num_handlers++;

	/* h is invalid after this call! */
	permute_inactive();
	return id;
}

void
delete_async_handler( int handler_id )
{
	int i;

	wait_safe();

	for( i=0; i<num_handlers; i++ )
		if( handlers[i].handler_id == handler_id ){
			memmove( &handlers[i], &handlers[i+1], (num_handlers-i-1)*sizeof(handlers[0]) );
			memmove( &ufds[i], &ufds[i+1], (num_handlers-i-1)*sizeof(ufds[0]) );
			break;
		}
	if( i == num_handlers ) {
		printm("delete_async_handler: bad id (%d)!\n", handler_id );
		return;
	}
	num_handlers--;
	permute_inactive();
}

void
set_async_handler_events( int id, int new_events )
{
	int i;

	wait_safe();

	for( i=0; i<num_handlers; i++ )
		if( handlers[i].handler_id == id ) {
			ufds[i].events = new_events;
			break;
		}

	if( i >= num_handlers )
		printm("set_async_handler_events: Handler not found\n");

	permute_inactive();
}

static void
poll_thread_entry( void *dummy )
{
	char ack_char;

	/* Increase priority to reduce latency */
	setpriority( PRIO_PROCESS, getpid(), -17 );

	while( !cancel_poll_thread ) {
		if( (num_pending=poll(ufds, num_poll, -1)) < 0 ) {
			if( errno != EINTR )
				perrorm("poll");
			continue;
		}
		__io_is_pending = 1;
		interrupt_emulation();

		/* wait for ack */
		while( read(ackpipe[0], &ack_char, 1) != 1 && errno == EINTR )
			;
	}
	cancel_poll_thread = 0;
}

void
__do_async_io( void )
{
	const char ackchar=1;
	int i;

	for( i=0; i<num_poll && num_pending>0; i++ ) {
		int ev = ufds[i].revents & ufds[i].events;
		if( ev ) {
			num_pending--;
			ufds[i].revents = 0;
			handlers_modified = 0;

			(*handlers[i].proc)( ufds[i].fd, ev );

			/* the handler might change our table */
			if( handlers_modified ) {
				handlers_modified = 0;
				i=0;
			}
		}
	}
	__io_is_pending = 0;

	/* restart polling thread */
	while( write(ackpipe[1], &ackchar, 1) != 1 && errno == EINTR )
		;
}


/************************************************************************/
/*	Asynchronous event mechanism					*/
/************************************************************************/

static void 
aevent_rx( int fd, int dummy_events )
{
	char ev;
	int i;

	if( read( pipefds[0], &ev, 1 ) != 1 )
		return;
	if( !ev )
		return;
	
	for( i=0; i<num_aevents; i++ ){
		if( ev == aevents[i].token ) {
			(*aevents[i].proc)( (int)ev );
			return;
		}
	}
	printm("Aevent %d unhandled!\n", ev );
}

void
send_aevent( int token )
{
	char ev = token;

	if( TEMP_FAILURE_RETRY( write( pipefds[1], &ev, 1 ) ) != 1 )
		perrorm("send_event");
}

int
add_aevent_handler( aevent_handler_t proc )
{
	aevent_t *aev = &aevents[num_aevents];

	if( num_aevents >= MAX_NUM_AEVENTS ){
		printm("MAX_NUM_AEVENTS exceeded\n");
		return -1;
	}
	aev->token = next_aevent_token++;
	aev->proc = proc;

	if( aev->token > 255 ){
		printm("run out of aevent tokens!\n");
		return -1;
	}
	num_aevents++;
	return aev->token;
}

void
delete_aevent_handler( int token )
{
	int i;
	aevent_t *aev;

	for( aev=aevents, i=0; i<num_aevents; aev++, i++ ){
		if( aev->token == token ){
			memmove( aev, aev+1, (num_aevents-i-1)*sizeof(aevent_t) );
			break;
		}
	}
}

void
async_init( void )
{
	next_handler_id = 1;

	if( pipe(pipefds) < 0 || pipe(ackpipe) < 0 ) {
		perrorm("pipe");
		exit(1);
	}
	fcntl( pipefds[0], F_SETFD, FD_CLOEXEC );
	fcntl( pipefds[1], F_SETFD, FD_CLOEXEC );
	fcntl( ackpipe[0], F_SETFD, FD_CLOEXEC );
	fcntl( ackpipe[1], F_SETFD, FD_CLOEXEC );

	next_aevent_token = 1;	/* 0 is used by us */

	add_async_handler( pipefds[0], POLLIN, aevent_rx, 0 );
	create_thread( poll_thread_entry, NULL, "async-io thread");

	initialized = 1;
}

void
async_cleanup( void )
{
	const char dummy_ch = 0;

	if( !initialized )
		return;
	
	/* Make sure thread is not spinning in the poll loop */
	wait_safe();
	cancel_poll_thread = 1;
	write( ackpipe[1], &dummy_ch, 1 );
	while( cancel_poll_thread )
		;

	close( pipefds[0] );
	close( pipefds[1] );
	close( ackpipe[0] );
	close( ackpipe[1] );

	initialized = 0;
}

