/**************************************************
UZI (Unix Z80 Implementation) Kernel:  process.c
***************************************************/


#include <sys/unix.h>
#include <sys/extern.h>
#include <sys/hardware.h>

extern void *current_term;

init2()
{
	char		*j;
	static char	bootchar;
	static char	*arg[2] = { "init", NULL };
	inoptr		i_open(), n_open();
	ptptr		ptab_alloc();
	int		pnum;

	bufinit();

	/* Create the context for the first process */
	newproc( udata.u_ptab = initproc = ptab_alloc() );
	pnum = ((int)initproc-(int)ptab)/sizeof(p_tab);
	initproc->p_status = P_RUNNING;
	initproc->p_isp = ((char*)(isps[pnum+1]))-4;
	initproc->p_memlst.next = (memblk*)0;

	/* Don't bother to try swtching yet */
	udata.u_insys = 1;

	di();

	/* Turn on ticker */
	*INTENA = 0x8020;

	/* User's file table */
	for( j=udata.u_files; j < (udata.u_files+UFTSIZE); ++j )
		*j = -1;

	ei();

	/* Wait until the clock has interrupted, to set tod
	while (!tod.t_date) ;  /* Loop */

	/* Open the console tty device */
	if( d_open(TTYDEV ) != 0 )
		panic("no tty");

	kprintf( "boot: " );
	udata.u_base = &bootchar;
	udata.u_count = 1;
	cdread( TTYDEV );
	ROOTDEV = bootchar - '0';

	/* Get rid of that bloody CR in the buffer */
	udata.u_base = &bootchar;
	udata.u_count = 1;
	cdread( TTYDEV );
	
	/* Mount the root device */
	if( fmount(ROOTDEV,NULLINODE) )
		panic( "no filesys" );

	ifnot( root = i_open(ROOTDEV,ROOTINODE) )
		panic( "no root" );

	i_ref( udata.u_cwd = root );
	rdtime( &udata.u_time );

	udata.u_argn  = (int)("/init");
	udata.u_argn1 = (int)(&arg[0]);
	udata.u_argn2  = (int)(&arg[1]);
	_execve();
/*
    _execve("/init",&arg[0],&arg[1]);
*/
	panic( "no /init" );
}


 /*
 |  psleep() puts a process to sleep on the given event.
 |  If another process is runnable, it swaps out the current one
 |  and starts the new one.
 |  Normally when psleep is called, the interrupts have already been
 |  disabled.   An event of 0 means a pause(), while an event equal
 |  to the process's own ptab address is a wait().
*/
int psleep( event )
char	*event;
{
	di();
	if( udata.u_ptab->p_status != P_RUNNING )
		panic( "psleep: voodoo" );
	if( !event )
		udata.u_ptab->p_status = P_PAUSE;
	else if( event == (char *)udata.u_ptab )
		udata.u_ptab->p_status = P_WAIT;
	else
		udata.u_ptab->p_status = P_SLEEP;

	udata.u_ptab->p_wait = event;

	ei();

	/* Saves enough info to be able to return to our calling funtction */
	save_isp();

	swapout( 0 );  /* Swap us out, and start another process */

	/* The returned process is now leaving the kernel */
	leave_kernel( udata.u_sp, udata.u_swaptype==SYSCALL );
}

/* Called by unix() before swapping out */
int isleep()
{
	udata.u_ptab->p_status = P_READY;
	save_isp();	/* Will return to unix() */
	swapout( 0 );
	leave_kernel( udata.u_sp, udata.u_swaptype==SYSCALL );
}


 /*
 |  wakeup() looks for any process waiting on the event,
 |  and make them runnable
*/
int wakeup( event )
char	*event;
{
	ptptr p;

	di();
	for( p=ptab; p < ptab+PTABSIZE; ++p )
	{
		if( (p->p_status>P_RUNNING) && (p->p_wait==event) )
		{
			p->p_status = P_READY;
			p->p_wait = (char *)NULL;
		}
	}
	ei();
}


 /*
 |  Getproc returns the process table pointer of a runnable process.
 |  It is actually the scheduler.
 |  If there are none, it loops.  This is the only time-wasting loop in the
 |  system.
*/
ptptr getproc()
{
	int		status;
	static ptptr	pp = ptab;    /* Pointer for round-robin scheduling */

	for( ;; )
	{
		if( ++pp >= ptab + PTABSIZE )
			pp = ptab;

		di();
		status = pp->p_status;
		ei();

		if (status == P_RUNNING)
			panic( "getproc: extra running" );
		if (status == P_READY)
			return( pp );
	}
}


/* Temp storage for swapout() */
static char inswapout;


 /*
 |  Swapout swaps out the current process, finds another that is READY,
 |  possibly the same process, and swaps it in.
 |  When a process is restarted after calling swapout,
 |  it thinks it has just returned from swapout().
*/
/* This function can have no arguments or auto variables */
/* In MAPUX it can - mcy */
int swapout( fromintr )
void	*fromintr;
{
	static ptptr	newp;
	ptptr		getproc();

	/* See if any signals are pending */
	chksigs();

	/* Get a new process */
	newp = getproc();

	/* If there is nothing else to run, just return */
	if( newp == udata.u_ptab )
	{
		udata.u_ptab->p_status = P_RUNNING;
		runticks = 0;
		/* This only happens when starting processes that were psleep()ed */
		if( udata.u_insys )
		{
			if( fromintr )
				lunsleep(fromintr);
			unsleep();
		}
		return( 0 );
	}

	swrite();
	
	/* Read the new process in, and return into its context. */
	if( swapin(newp,fromintr) == -1 )
		panic( "swapin failed" );
}


 /*
 |  This actually writes out the image
*/
int swrite()
{
	int pnum = ((int)udata.u_ptab-(int)ptab)/sizeof(p_tab);
	/* Write out the user data. */
	bcopy( &udata, &uds[pnum], sizeof(u_data) );
}

int swapin( pp, fromintr )
ptptr	pp;
void	*fromintr;
{
	static void	*blk;
	static ptptr	newp;
	int		pnum = ((int)pp-(int)ptab)/sizeof(p_tab);

	di();
	newp = pp;
	ei();

	memmanValidate( &(pp->p_memlst) );

	di();
	bcopy( &uds[pnum], &udata, sizeof(u_data) );

	if (newp != udata.u_ptab)
		panic( "mangled swapin" );

	newp->p_status = P_RUNNING;
	runticks = 0;
	ei();

	/* This only happens when starting processes that were psleep()ed */
	if( udata.u_insys )
	{
		if( fromintr )
			lunsleep(fromintr);
		unsleep();
	}

	return( 0 );
}


/* Temp storage for dofork */
int16 newid;


/* This has to be a function because the code that restarts us expects
   to be able to unlk us (as if from a psleep()) */
parent_stays()
{
	save_pisp();
}



 /*
 |  dofork implements forking.
*/
dofork()
{
	memblk		*olist, *nlist;
	static ptptr	p;
	ptptr		ptab_alloc();
	ptptr		optab;
	char		*ousp;
	int		i;

	ifnot( p = ptab_alloc() )
	{
		udata.u_error = EAGAIN;
		return( -1 );
	}

	di();
	udata.u_ptab->p_status = P_READY; /* Parent is READY */
	newid = p->p_pid;

	udata.u_retval = newid;

	/* Parent will return to this point */
	save_pisp();

	if( udata.u_ptab->p_status == P_RUNNING )
		leave_kernel( udata.u_sp, 1 );
	
	olist = udata.u_ptab->p_memlst.next;
	memmanDupList( olist, &(udata.u_ptab->p_memlst) );
	swrite();
	/* The above saves the parent.  We are now the child. */

	/* Make a new process table entry, etc. */
	optab = udata.u_ptab;
	newproc( p );
	p->p_memlst.next = olist;
	olist->prev = &(p->p_memlst.next);
	while( olist )
	{
		olist->home = &(p->p_memlst);
		olist = olist->next;
	}

	/* Make a copy of the interrupt stack for the child */
	qcopy( optab->p_isp-1020, p->p_isp-1020, 256 );

	p->p_status = P_RUNNING;
	runticks = 0;

	udata.u_retval = 0;

	/* The child's exit stack contains only one absolute pointer, the
	   pseudo-link address for discarding parameters to unix().  We must
	   now diddle that pointer */
	ousp = udata.u_sp;
	udata.u_sp =  p->p_isp-(optab->p_isp-udata.u_sp);

	ei();
	
	/* Leave using the new stack for child */
	leave_kernel( udata.u_sp, 1 );  /* Return to child */
}


 /*
 |  Newproc fixes up the tables for the child of a fork
 |  Note: interrupts should be disabled on entry
*/
int newproc(p)
ptptr	p;    /* New process table entry */
{
	char	*j;
	int	pnum = ((int)p-(int)ptab)/sizeof(p_tab);

	/* Note that ptab_alloc clears most of the entry */
	p->p_isp = ((char*)(isps[pnum+1]))-4;
	p->p_status = P_RUNNING;
	p->p_pptr = udata.u_ptab;
	p->p_ignored = udata.u_ptab->p_ignored;
	p->p_uid = udata.u_ptab->p_uid;
	udata.u_ptab = p;
	bzero( &udata.u_utime, 4*sizeof(time__t) ); /* Clear tick counters */

	rdtime( &udata.u_time );
	i_ref( udata.u_cwd );
	udata.u_cursig = udata.u_error = 0;

	for( j=udata.u_files; j < (udata.u_files+UFTSIZE); ++j )
		if( *j >= 0 )
			++of_tab[*j].o_refs;
}



 /*
 |  This allocates a new process table slot, and fills
 |  in its p_pid field with a unique number
*/
ptptr ptab_alloc()
{
	ptptr	p;
	ptptr	pp;
	static int nextpid = 0;

	di();
	for( p=ptab; p < ptab+PTABSIZE; ++p )
	{
		if( p->p_status == P_EMPTY )
			goto found;
	}
	ei();
	return( NULL );

found:
	/* See if next pid number is unique */
nogood:
	if( nextpid++ > 32000 )
		nextpid = 1;
	for( pp=ptab; pp < ptab+PTABSIZE; ++pp )
	{
		if( (pp->p_status != P_EMPTY) && (pp->p_pid == nextpid) )
			goto nogood;
	}

	bzero( p, sizeof(struct p_tab) );
	p->p_pid = nextpid;
	p->p_status = P_FORKING;
	ei();
	return( p );
}



 /*
 |  This is the clock interrupt routine.   Its job is to
 |  increment the clock counters, increment the tick count of the
 |  running process, and either swap it out if it has been in long enough
 |  and is in user space or mark it to be swapped out if in system space.
 |  Also it decrements the alarm clock of processes.
*/
/* This must have no automatic or register variables */
/* Actually, because we have an interrupt stack, it can - mcy (I think) */
clk_int( context )
char	*context;
{
	static ptptr p;
	int	olev = ((*(int16*)(context+64))&0x700)>>8;
	static int dflag = 0;

	/* Increment processes and global tick counters */
	if( udata.u_ptab->p_status == P_RUNNING )
		incrtick( udata.u_insys ? &udata.u_stime : &udata.u_utime );

	incrtick( &ticks );

	/* For slower events */
	sec++;

	/* These are things we don't do if interrupting an interrupts */
	if( olev == 0 )
	{
		/* Keyboard repeat is tunable, so this is called rather often */
		if( (sec&1) == 0 )
			termRepInt();

		/* This is a twice-per-second thing */
		if( (sec==TICKSPERSEC) || (sec==TICKSPERSEC/2) )
			termFlashInt( current_term );

		/* Services timeout() events */
		/* Timeouts are not precise because they are only updated
		   in certain circumstances */
		toutcheck();
	}

	/* Do once-per-second things */
	/* Is sendsig safe with nested interrupts?  Must check - mcy */
	if( sec == TICKSPERSEC )
	{
		/* Update global time counters */
		sec = 0;

		rdtod();  /* Update time-of-day */

		/* Update process alarm clocks */
		for( p=ptab; p<ptab+PTABSIZE; ++p )
		{
			if( p->p_alarm )
				ifnot( --p->p_alarm )
					sendsig( p,SIGALRM );
		}
	}

	/* Check run time of current process */
	/* This test is beginning to look like overkill... */
	runticks++;
	if( (runticks>=MAXTICKS) )
	{
		if( (udata.u_insys==0) )
		{
			kprintf("<I-%x-",get_ccr());
			if( ((get_ccr()&0x2000)==0) )
			{
				kprintf("C");
				if( (olev==0) )
					kprintf("O");
			}
			kprintf(">");
		}
	}

	/*if( (udata.u_insys==1) && ((get_ccr()&0x7000)!=0x2000) )
		kprintf("ccr=%x  ",get_ccr());*/
	
	if( (runticks>=MAXTICKS) && (udata.u_insys==0) && ((get_ccr()&0x2000)==0) && (olev==0) )    /* Time to swap out? */
	{
		inint = 0;
		udata.u_ptab->p_status = P_READY;
		/* If the current process is switched out, we need to know what
		   to do when switching it in again */
		udata.u_swaptype = INTERRUPT;
		/* This is necessary only if we're going to switch */
		udata.u_sp = (char*)save_int( context );
		swapout( udata.u_sp );
		leave_kernel( udata.u_sp, udata.u_swaptype==SYSCALL );
	}

	/* No switch, so simply leave */
	leave_kernel( context, 0 );
}


extern int (*disp_tab[])();


/* No auto vars here, so carry flag will be preserved */
unix( context, argn3, argn2, argn1, argn, uret, callno )
int	argn3, argn2, argn1, argn;
char	*uret, *context;
int	callno;
{
	static int xxdone = 0;

	if( xxdone == 0 )
	{
		xxdone = 1;
		kprintf("mccr = %x\n",get_ccr());
	}

	udata.u_swaptype = SYSCALL;
	udata.u_argn3  = argn3;
	udata.u_argn2  = argn2;
	udata.u_argn1  = argn1;
	udata.u_argn   = argn;
	udata.u_callno = callno;

	udata.u_sp = context;

	udata.u_insys = 1;
	udata.u_error = 0;

	ei();

	/* Branch to correct routine */
	udata.u_retval = (*disp_tab[callno])();

	chksigs();

	if( runticks >= MAXTICKS )
		isleep();

	/* Call trap routine if necessary */
	calltrap( udata.u_sp, udata.u_swaptype==SYSCALL );

	/* If an error, return errno with carry set */
	leave_kernel( udata.u_sp, udata.u_swaptype==SYSCALL );
}



 /*
 |  This sees if the current process has any signals set, and deals with them
*/
chksigs()
{
	int	j;

	di();
	ifnot( udata.u_ptab->p_pending )
	{
		ei();
		return;
	}

	for( j=1; j<NSIGS; ++j )
	{
		ifnot( sigmask(j) & udata.u_ptab->p_pending )
			continue;

		if( udata.u_sigvec[j] == SIG_DFL )
		{
			ei();
			doexit( 0, j );
		}

		if( udata.u_sigvec[j] != SIG_IGN )
		{
			/* Arrange to call the user routine at return */
			udata.u_ptab->p_pending &= !sigmask(j);
			udata.u_cursig = j;
		}
	}
	ei();
}


int sendsig( proc, sig )
ptptr	proc;
int16	sig;
{
	ptptr p;

	if( proc )
		ssig( proc, sig );
	else
		for( p=ptab; p<ptab+PTABSIZE; ++p )
			if( p->p_status )
				ssig(p,sig);	
}


int ssig( proc, sig )
ptptr	proc;
int16	sig;
{
	int	stat;

	di();

	ifnot( proc->p_status )
		goto done;  /* Presumably was killed just now */

	if( proc->p_ignored & sigmask(sig) )
		goto done;

	stat = proc->p_status;
	if( stat == P_PAUSE || stat == P_WAIT || stat == P_SLEEP )
		proc->p_status = P_READY;

	proc->p_wait = (char *)NULL;
	proc->p_pending |= sigmask(sig);
done:
	ei();
}


