/*
 *	Occam entry point
 *	Copyright (C) 1996-1999 Jim Moores
 *	Based on the KRoC/sparc kernel Copyright (C) 1994-2000 D.C. Wood and P.H. Welch
 *	Modifications copyright (C) 1999-2005 Fred Barnes  <frmb@kent.ac.uk>
 *
 *	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; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	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.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/*{{{  includes*/
#ifdef OOS_BUILD
	#include <oos_funcs.h>
#else	/* !OOS_BUILD */
	#include <stdlib.h>
	#include <stdio.h>
	#include <setjmp.h>
#endif	/* !OOS_BUILD */

#include <sched_consts.h>
#include <arch/sched_types.h>
#include <arch/sched.h>
#include <arch/sched_asm_inserts.h>
#include <arch/occam_io.h>
#include <queue_defs.h>
#include <pgroup.h>

#if !defined(OOS_BUILD)
	#ifdef USE_PTHREADS
		#include <pthread.h>
		#include <semaphore.h>
	#else
		#include <sched.h>
		#include <sys/ipc.h>
		#include <sys/sem.h>
	#endif
#endif
#include <arch/atomics.h>

#include <arch/deadlock.h>

#ifndef OOS_BUILD
#ifdef BLOCKING_SYSCALLS
	#include <setjmp.h>
	#include <bsyscalls_if.h>
#endif

#ifdef LINUX_CSPDRIVER
	#include <arch/cspdriver.h>
#endif
#endif	/* !OOS_BUILD */

#include <dmem_if.h>
/*}}}*/
/*{{{  local/global vars*/
extern void _occam_start(void); /* occam code entry point */
extern word _wsbytes; /* occam workspace size */
extern word _vsbytes; /* occam vectorspace size */
#ifdef MOBILES
extern word _msbytes; /* occam mobilespace size */
#endif
#ifndef OOS_BUILD
extern int _occam_tlp_iface;	/* translator added description of the top-level process */
#endif
extern int _occam_errormode;	/* non-zero for STOP errormode */

#if !defined(OOS_BUILD)
extern FILE *kroc_out;
extern FILE *kroc_err;

extern jmp_buf kroc_out_jump_buffer;
#endif

static char *ws, *vs;
#ifdef MOBILES
static char *ms;
#endif
extern int occam_finished;

static word gforkbarrier[2];	/* global FORK barrier */

#if !defined(OOS_BUILD)
static word kse_ptrs[3];	/* indirect top-level params for top-level SUSPEND */
#endif

int do_print_memstats;

#if defined(BLOCKING_SYSCALLS) && !defined(OOS_BUILD)
static int using_keyboard = 0;
static int using_screen = 0;
static int using_error = 0;
#endif
/*}}}*/

#if defined(BLOCKING_SYSCALLS) && !defined(OOS_BUILD)
/*{{{  bsyscalls keyboard handling*/
static int stdin_is_tty;

/*{{{  void init_kbdio (int is_a_tty)*/
/*
 *	initialises keyboard (or input) stuff
 */
void init_kbdio (int is_a_tty)
{
	stdin_is_tty = is_a_tty;
	return;
}
/*}}}*/
/*{{{  int kill_kbdio (void)*/
/*
 *	kills the "keyboard process" (which might be blocked in terminal read)
 */
int kill_kbdio (void)
{
	int status;
	int ws_arry[2];

	if (using_keyboard) {
		ws_arry[0] = (int)&kbd_termchan;
		ws_arry[1] = (int)&status;
		_killcall (ws_arry);
	} else {
		status = 0;
	}
	return status;
}
/*}}}*/
/*{{{  void _read_keyboard (int *wsptr)*/
/*
 *	reads the keyboard and places a byte at (char *)wsptr[0]
 */
void _read_keyboard (int *wsptr)
{
	int *ch = (int *)(wsptr[0]);
	int c_read, n;
	unsigned char tty_char;

	if (occam_finished) {
		*ch = -1;
		return;
	}
	if (!stdin_is_tty) {
		c_read = getc (stdin);
		n = (c_read == EOF) ? 0 : 1;
		tty_char = (unsigned char)c_read;
	} else {
		n = read (0, &tty_char, 1);
	}
	if (n < 1) {
		*ch = -1;
	} else {
		*ch = (int)((unsigned int)tty_char);
	}
	return;
}
/*}}}*/
/*}}}*/
#endif	/* BLOCKING_SYSCALLS && !OOS_BUILD */

/*{{{  output handling*/
#define KROC_OUTPUT_BUFFER_SIZE 256

/*{{{  void _write_screen (int *wsptr)*/
/*
 *	krocif.s calls this to print something on the screen channel
 *	PROC C.write.screen (VAL []BYTE buffer)
 */
void _write_screen (int *wsptr)
{
	const char *buffer = (char *)(wsptr[0]);

#if defined(OOS_BUILD)
	oos_printk ("%s", buffer);
#else
	fputs (buffer, kroc_out);
	fflush (kroc_out);
#endif
	return;
}
/*}}}*/
/*{{{  void _write_error (int *wsptr)*/
/*
 *	krocif.s calls this to print something on the error channel
 *	PROC C.write.error (VAL BYTE ch)
 */
void _write_error (int *wsptr)
{
#if defined(OOS_BUILD)
	oos_printk ("%c", (int)(wsptr[0]));
#else
	fputc ((int)(wsptr[0]), kroc_err);
	fflush (kroc_err);
#endif
	return;
}
/*}}}*/
/*{{{  void _out_stderr (int *wsptr)*/
/*
 *	this is available for debugging
 *	PROC C.out.stderr (VAL []BYTE str)
 */
void _out_stderr (int *wsptr)
{
	const char *buf = (char*)(wsptr[0]);
	int slen = (int)(wsptr[1]);

#if defined(OOS_BUILD)
	oos_printk ("%*s", slen, buf);
#else
	fflush (kroc_err);
	write (fileno (kroc_err), buf, slen);
#endif
	return;
}
/*}}}*/
/*{{{  void _out_stderr_int (int *wsptr)*/
/*
 *	this is available for debugging
 *	PROC C.out.stderr.int (VAL INT n)
 */
void _out_stderr_int (int *wsptr)
{
	const int n = (int)(wsptr[0]);

#if defined(OOS_BUILD)
	oos_printk ("%d", n);
#else
	fprintf (kroc_err, "%d", n);
	fflush (kroc_err);
#endif
	return;
}
/*}}}*/
/*}}}*/

#if defined(OOS_BUILD)
/*{{{  void _occ_enter (void)*/
/*
 *	called to initialise and run the occam program
 *	this one is for OOS
 */
void _occ_enter (void)
{
	#ifdef DEBUG_KERNEL
		fprintf (stderr, "in _enter()\n");
	#endif
	dmem_init ();
	init_occam_io ();
	gforkbarrier[0] = 0;
	gforkbarrier[1] = 0;

	ws = (char *)dmem_alloc ((_wsbytes + 32 + _vsbytes + 256));
	if (!ws) {
		oos_printk ("KRoC: fatal: unable to allocate workspace/vectorspace (%d bytes)\n", _wsbytes + 24 + _vsbytes + 256);
		user_bad_exit ();
	}

	{
		int i;
		/* initialise the workspace/vectorspace with (MOSTNEG INT) == 0x80000000 */

		for (i=0; i<((_wsbytes + 32 + _vsbytes + 256) >> 2); i++) {
			((unsigned int *)ws)[i] = 0x80000000;
		}
	}
	Wptr = (word *)ws;

	#ifdef DEBUG_KERNEL
	  	oos_printk ("allocated %d bytes for workspace and vectorspace.\n", _wsbytes+24+_vsbytes+256 );
	#endif
	  	oos_printk ("allocated %d bytes for workspace and vectorspace.\n", _wsbytes+24+_vsbytes+256 );

	if (!Wptr) {
		oos_printk ("KRoC: fatal.  Unable to allocate workspace. (%d bytes)\n", _wsbytes + 24 + _vsbytes + 256 );
		user_bad_exit ();
	}

	Wptr = (word *)((byte *)Wptr + _wsbytes);
	if (_vsbytes) {
		vs = ws + (_wsbytes + 24 + 128);
		Wptr[1] = (word)vs;
	}
	#ifdef MOBILES
		if (_msbytes) {
			int i;

			ms = (char *)dmem_alloc (_msbytes);
			if (ms == NULL) {
				oos_printk ("KRoC: fatal.  Unable to allocate mobilespace. (%d bytes)\n", _msbytes);
				user_bad_exit ();
			}
			/* initialise mobile-space to MOSTNEG INT */
			for (i=0; i<(_msbytes >> 2); i++) {
				((int *)ms)[i] = 0x80000000;
			}
			if (_vsbytes) {
				Wptr[2] = (word)ms;
			} else {
				/* occupy the vectorspace-slot (this is what the compiler expects) */
				Wptr[1] = (word)ms;
			}
		} else {
			ms = NULL;
		}
#if 0
fprintf (stderr, "KRoC: passing mobilespace pointer to application (%p for %d words)\n", ms, _msbytes >> 2);
#endif
	#endif
	#ifdef DEBUG_KERNEL
		oos_printk ("Wptr=%p  ws=%p  vs=%p  _wsbytes=%d  _vsbytes=%d\n", Wptr, ws, vs, _wsbytes, _vsbytes );
	#endif
	/* give deadlock stuff a handle on ws/vs */
	give_initial_ws_code (ws, _wsbytes, (unsigned char *)_occam_start);

	#ifdef PROCESS_PRIORITY
	{
		int k;

		for (k=0; k<MAX_PRIORITY_LEVELS; k++) {
			Fptr_q[k] = NotProcess_p;
			Bptr_q[k] = NotProcess_p;
		}
		PPriority = 0;
		PState = 0x1;
		#if defined(PROCESS_GROUPS)
		/*{{{  do setup of process groups*/
			/* nothing to do for RMoX, at the moment */
		/*}}}*/
		#endif	/* defined(PROCESS_GROUPS) */
	}
	#endif
	Fptr = Bptr = NotProcess_p;
	Tptr = NotProcess_p;

	do_print_memstats = 0;

	mdparam1 = 0xffffffff;
	mdparam2 = 0xffffffff;
	mdparam3 = 0xffffffff;
	mdparam4 = 0xffffffff;

	/* we call the occam program like a C function, but it never returns.  Instead the scheduler
	 * is invoked when the occam program finishes (and it sets a global flag before doing so)
	 * which the scheduler checks */
	/* The ASM on the other side of this first loads from Wptr, Fptr and Bptr */
	oos_printk ("about to call _occam_start()\n");
	_occam_start ();
}
/*}}}*/
#else	/* !OOS_BUILD */
/*{{{  int occam_uses_keyboard (void)*/
/*
 *	returns non-zero if the keyboard channel was specified at the top-level
 */
int occam_uses_keyboard (void)
{
	switch (_occam_tlp_iface & ~TLP_SHAREDMASK & ~TLP_EFLAGMASK) {		/* mask out shared -- and EFLAGs */
	case TLP_KYB:
	case TLP_KYBSCR:
	case TLP_KYBERR:
	case TLP_KYBSCRERR:
		using_keyboard = 1;
		return 1;
	}
	return 0;
}
/*}}}*/
/*{{{  void _occ_enter (void)*/
/*
 *	called to initialise and run the occam program
 *	(calls stuff to intiialise blocking system calls and dynamic memory)
 */
void _occ_enter (void)
{
	int wspptr;

	#ifdef DEBUG_KERNEL
		fprintf (stderr, "in _enter()\n");
	#endif
	dmem_init ();
	#ifndef OOS_BUILD
		#ifdef LINUX_CSPDRIVER
		initialise_cspdriver ();
		#endif
	#endif	/* !OOS_BUILD */
	init_occam_io (_occam_tlp_iface);
	#if defined(DM_DEBUG) && (DM_DEBUG == 1)
		extadd_elf_mem (kroc_argv[0]);
	#endif

	/* initialise global forking barrier */
	gforkbarrier[0] = 0;
	gforkbarrier[1] = 0;

	#if defined(DM_DEBUG) && (DM_DEBUG == 1)
		extadd_ord_mem (gforkbarrier, 2 * sizeof (int), MODE_READ | MODE_WRITE);
	#endif

	ws = (char *)malloc (_wsbytes + 32 + _vsbytes + 256);
	{
		int i;
		/* initialise the workspace/vectorspace with (MOSTNEG INT) == 0x80000000 */

		for (i=0; i<((_wsbytes + 32 + _vsbytes + 256) >> 2); i++) {
			((unsigned int *)ws)[i] = 0x80000000;
		}
		#if defined(DM_DEBUG) && (DM_DEBUG == 1)
			extadd_ord_mem (ws, _wsbytes + 32, MODE_READ | MODE_WRITE);
			if (_vsbytes) {
				extadd_ord_mem (ws + (_wsbytes + 24 + 128), _vsbytes, MODE_READ | MODE_WRITE);
			}
		#endif
	}
	Wptr = (word *)ws;
	#ifdef DEBUG_KERNEL
	  	fprintf( stderr, "allocated %d bytes for workspace and vectorspace.\n", _wsbytes+24+_vsbytes+256 );
	#else
		if (!Wptr) {
			fprintf( stderr, "KRoC: fatal: unable to allocate workspace. (%d bytes)\n", _wsbytes + 24 + _vsbytes + 256 );
			user_bad_exit ();
		}
	#endif
	Wptr = (word *)((byte *)Wptr + _wsbytes);
	wspptr = 1;
	/* check for any MPP barrier at the top-level */
	if (_occam_tlp_iface & TLP_MPP_BARRIER) {
		Wptr[wspptr++] = (word)NULL;
		kse_ptrs[0] = (word)&kbd_chan;
		kse_ptrs[1] = (word)&scr_chan;
		kse_ptrs[2] = (word)&err_chan;
		#if defined(DM_DEBUG) && (DM_DEBUG == 1)
			extadd_ord_mem (kse_ptrs, 3 * sizeof (int), MODE_READ | MODE_WRITE);
		#endif
	}
	do_print_memstats = (_occam_tlp_iface & TLP_PRINT_MEMSTATS);
	switch (_occam_tlp_iface & ~TLP_EFLAGMASK) {
	case TLP_FSTS:
		Wptr[wspptr++] = (word)NULL;		/* causes fault */
		Wptr[wspptr++] = (word)NULL;		/* causes fault */
		break;
	case TLP_FSTSMEM:
		Wptr[wspptr++] = (word)NULL;		/* causes fault */
		Wptr[wspptr++] = (word)NULL;		/* causes fault */
		Wptr[wspptr++] = (word)NULL;		/* causes fault */
		break;
	case TLP_FSTSSIZEDMEM:
		Wptr[wspptr++] = (word)NULL;		/* causes fault */
		Wptr[wspptr++] = (word)NULL;		/* causes fault */
		Wptr[wspptr++] = (word)NULL;		/* causes fault */
		Wptr[wspptr++] = 0;			/* dimemsion */
		break;
	default:
		switch (_occam_tlp_iface & ~TLP_SHAREDMASK & ~TLP_EFLAGMASK) {		/* mask out shared -- and EFLAGs */
		case TLP_NULL:
			break;
		case TLP_KYB:
			Wptr[wspptr++] = (_occam_tlp_iface & TLP_MPP_BARRIER) ? (word)&(kse_ptrs[0]) : (word)&kbd_chan;
			using_keyboard = 1;
			break;
		case TLP_SCR:
			Wptr[wspptr++] = (_occam_tlp_iface & TLP_MPP_BARRIER) ? (word)&(kse_ptrs[1]) : (word)&scr_chan;
			using_screen = 1;
			break;
		case TLP_ERR:
			Wptr[wspptr++] = (_occam_tlp_iface & TLP_MPP_BARRIER) ? (word)&(kse_ptrs[2]) : (word)&err_chan;
			using_error = 1;
			break;
		case TLP_KYBSCR:
			Wptr[wspptr++] = (_occam_tlp_iface & TLP_MPP_BARRIER) ? (word)&(kse_ptrs[0]) : (word)&kbd_chan;
			Wptr[wspptr++] = (_occam_tlp_iface & TLP_MPP_BARRIER) ? (word)&(kse_ptrs[1]) : (word)&scr_chan;
			using_keyboard = using_screen = 1;
			break;
		case TLP_KYBERR:
			Wptr[wspptr++] = (_occam_tlp_iface & TLP_MPP_BARRIER) ? (word)&(kse_ptrs[0]) : (word)&kbd_chan;
			Wptr[wspptr++] = (_occam_tlp_iface & TLP_MPP_BARRIER) ? (word)&(kse_ptrs[2]) : (word)&err_chan;
			using_keyboard = using_error = 1;
			break;
		case TLP_SCRERR:
			Wptr[wspptr++] = (_occam_tlp_iface & TLP_MPP_BARRIER) ? (word)&(kse_ptrs[1]) : (word)&scr_chan;
			Wptr[wspptr++] = (_occam_tlp_iface & TLP_MPP_BARRIER) ? (word)&(kse_ptrs[2]) : (word)&err_chan;
			using_screen = using_error = 1;
			break;
		case TLP_KYBSCRERR:
			Wptr[wspptr++] = (_occam_tlp_iface & TLP_MPP_BARRIER) ? (word)&(kse_ptrs[0]) : (word)&kbd_chan;
			Wptr[wspptr++] = (_occam_tlp_iface & TLP_MPP_BARRIER) ? (word)&(kse_ptrs[1]) : (word)&scr_chan;
			Wptr[wspptr++] = (_occam_tlp_iface & TLP_MPP_BARRIER) ? (word)&(kse_ptrs[2]) : (word)&err_chan;
			using_keyboard = using_screen = using_error = 1;
			break;
		default:
			fprintf (stderr, "KRoC: fatal: unknown interface code for top-level occam process (%d)\n", _occam_tlp_iface);
			user_bad_exit();
			break;
		}
		break;
	}
	vs = ws + (_wsbytes + 24 + 128);
	if (_vsbytes) {
		Wptr[wspptr++] = (word)vs;
	}
	#ifdef MOBILES
		if (_msbytes) {
			int i;

			ms = (char *)malloc (_msbytes);
			#if defined(DM_DEBUG) && (DM_DEBUG == 1)
				extadd_ord_mem (ms, _msbytes, MODE_READ | MODE_WRITE);
			#endif
			/* initialise mobile-space to MOSTNEG INT */
			for (i=0; i<(_msbytes >> 2); i++) {
				((int *)ms)[i] = 0x80000000;
			}
			Wptr[wspptr++] = (word)ms;
		} else {
			ms = NULL;
		}
#if 0
fprintf (stderr, "KRoC: passing mobilespace pointer to application (%p for %d words)\n", ms, _msbytes >> 2);
#endif
	#endif

	/* if the FORK barrier is required, add it */
	if (_occam_tlp_iface & TLP_FORK_BARRIER) {
		Wptr[wspptr++] = (word)gforkbarrier;
	}
	#ifdef DEBUG_KERNEL
		fprintf( stderr, "Wptr=%p  ws=%p  vs=%p  _wsbytes=%d  _vsbytes=%d\n", Wptr, ws, vs, _wsbytes, _vsbytes );
	#endif
	/* give deadlock stuff a handle on ws/vs */
	give_initial_ws_code (ws, _wsbytes, (unsigned char *)_occam_start);

	#ifdef PROCESS_PRIORITY
	{
		int k;

		for (k=0; k<MAX_PRIORITY_LEVELS; k++) {
			Fptr_q[k] = NotProcess_p;
			Bptr_q[k] = NotProcess_p;
		}
		PPriority = 0;
		PState = 0x1;
		#if defined(PROCESS_GROUPS)
		/*{{{  do initial setup of process groups*/
		/* keyboard, screen and error processes might be on the run-queue */
		PGroupOf (PPriority)->nprocs += (using_keyboard + using_screen + using_error);
		/*}}}*/
		#endif
	}
	#endif
	Fptr = Bptr = NotProcess_p;

	Tptr = NotProcess_p;
	mdparam1 = 0xffffffff;
	mdparam2 = 0xffffffff;
	mdparam3 = 0xffffffff;
	mdparam4 = 0xffffffff;
	#ifndef OOS_BUILD
		#ifdef BLOCKING_SYSCALLS
			if (bsyscalls_create_clones ()) {
				fprintf (stderr, "KRoC: Unable to create clones for blocking syscalls.\n");
				user_bad_exit ();
			}
		#endif	/* BLOCKING_SYSCALLS */
	#endif	/* !OOS_BUILD */

	/* put the screen and error processes on the run-queue.  init_occam_io makes sure
	 * that they are correctly setup (and will jump into the actual PROCs) */
	Fptr = scr_workspace;
	scr_workspace[Link] = (word)err_workspace;
	err_workspace[Link] = NotProcess_p;
	Bptr = err_workspace;

	#ifdef PROCESS_PRIORITY
	scr_workspace[Priority] = 0;
	err_workspace[Priority] = 0;
	#endif

	#ifndef OOS_BUILD
		#ifdef BLOCKING_SYSCALLS
			if (using_keyboard) {
				/* place keyboard process on the run queue */
				Bptr[Link] = (word)kbd_workspace;
				kbd_workspace[Link] = NotProcess_p;
				#ifdef PROCESS_PRIORITY
				kbd_workspace[Priority] = 0;
				#endif
				Bptr = kbd_workspace;
			}
		#endif	/* BLOCKING_SYSCALLS */
	#endif	/* !OOS_BUILD */

	/* we call the occam program like a C function, but it never returns.  Instead the scheduler
	 * is invoked when the occam program finishes (and it sets a global flag before doing so)
	 * which the scheduler checks */
	/* The ASM on the other side of this first loads from Wptr, Fptr and Bptr */
	barrier ();
	_occam_start ();
	barrier ();
}
/*}}}*/
#endif	/* !OOS_BUILD */

/*{{{  int _occ_exit (void)*/
/*
 *	cleans up after the occam program has finished
 */
int _occ_exit (void)
{
#if defined(OOS_BUILD)
	oos_kfree (ws);
#else
	free (ws);
#endif
	#ifdef MOBILES
		if (ms) {
#if defined(OOS_BUILD)
			oos_kfree (ms);
#else
			free (ms);
#endif
		}
	#endif
	ws = vs = NULL;
	#if !defined(OOS_BUILD)
		#if defined(LINUX_CSPDRIVER)
		shutdown_cspdriver ();
		#endif
	#endif	/* !OOS_BUILD */
	return 0;
}
/*}}}*/



