
#include "unix.h"
#include "extern.h"

/* int dummy;  /* For testing Hiesenbug */

init2()
{
    register char *j;
    pageaddr *k;
    static char bootline[2];
    static char arg[] =
	 { '/', 'i', 'n', 'i', 't', 0, 0, 0x01, 0x01, 0, 0, 0, 0};
    inoptr i_open(), n_open();
    ptptr ptab_alloc();

    bufinit();

    /* Create the context for the first process */
    newproc(udata.u_ptab = initproc = ptab_alloc());
    initproc->p_status = P_RUNNING;

    /* User's file table */
    for (j=udata.u_files; j < (udata.u_files+UFTSIZE); ++j)
	*j = -1;

    /* Turn on the clock */
    out(02,0xf1);
    ei();

    /* Wait until the clock has interrupted, to set tod */
    /*    while (!tod.t_date) ;  /* Loop */ 

    /* Open the console tty device (establishing the init process'
	controlling tty */

    if (d_open(TTYDEV) != 0)
	panic("no tty");

    kprintf("\nboot: ");
    udata.u_base = bootline;
    udata.u_sysio = 1;
    udata.u_count = 2;
    cdread(TTYDEV);
    ROOTDEV = *bootline - '0';

    /* 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);

    /* This embrionic process has only its first page allocated */
    for (k=udata.u_page; k < (udata.u_page+16); ++k)
	*k = NOPAGE;
    udata.u_page[0] = VIRGPAGE;
    ldmmu(udata.u_page);

    /* Poke the execve arguments into user space */
    uput(arg, PROGBASE, sizeof(arg));

    udata.u_argn2 = (int16)PROGBASE;
    udata.u_argn1 = 0x107;    /* Arguments (just "init") */
    udata.u_argn = 0x10b;     /* Environment (none) */
    _execve();

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

psleep(event)
char *event;
{
    register dummy;  /* Force saving of registers */

    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;
    udata.u_ptab->p_waitno = ++waitno;

    ei();

    swapout();		/* Swap us out, and start another process */

    /* Swapout doesn't return until we have been swapped back in */
}


/* wakeup() looks for any process waiting on the event,
and make them runnable */

wakeup(event)
char *event;
{
    register 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()
{
    register 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() */
int *stkptr;


/* 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 */
swapout()
{
    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;
	return (runticks = 0);
    }

#ifdef MEMDEBUG
    kprintf("Swapping out process %d\n", udata.u_ptab->p_pid);
#endif

    /* Read the MMU registers back into udata.u_page */
    rdmmu(udata.u_page);

    /* Save the stack pointer and critical registers */
#asm
	LD	HL,01	;this will return 1 if swapped.
	PUSH	HL	;will be return value
	PUSH	BC
	PUSH	IX
	LD	HL,0
	ADD	HL,SP	;get sp into hl
	LD	(stkptr?),HL
#endasm
    udata.u_ssp = stkptr;
    udata.u_usp = usp();

    swrite();
    /* Read the new process in, and return into its context. */
    swapin(newp);

    /* We should never get here. */
    panic("swapin failed");
}


/* This saves the user area */
swrite()
{
    di();
    bcopy(&udata, udata.u_ptab->p_ublk, 512);
    ei();
}


/* No automatics can be used past tempstack(); */
swapin(pp)
ptptr pp;
{
    static blkno_t blk;
    static ptptr newp;
    static char *bp;
    static char *base;
    static int cnt1, cnt2;

    newp = pp;

    di();
    tempstack();

    /* Reload the user area */
    bcopy(newp->p_ublk, &udata, 512);

    /* Reload the MMU */
    ldmmu(udata.u_page);

    ei();
    if (newp != udata.u_ptab)
	panic("mangled swapin");

#ifdef MEMDEBUG
    kprintf("Swapped in process %d\n", newp->p_pid);
#endif

    di();
    newp->p_status = P_RUNNING;
    runticks = 0;
    ei();
    /* Restore the registers */

    stkptr = udata.u_usp;
#asm
	LD	HL,(stkptr?)
	LDUSP
#endasm

    stkptr = udata.u_ssp;
#asm
	LD	HL,(stkptr?)
	LD	SP,HL
	POP	IX
	POP	BC
	POP	HL
	LD	A,H
	OR	L
	RET 		;return into the context of the swapped-in process 
#endasm

}


/* Temp storage for dofork */
int16 newid;

/* dofork implements forking.  */
/* This function can have no arguments or auto variables */

dofork()
{
    static ptptr p;
    ptptr ptab_alloc();
    static int j;

    ifnot (p = ptab_alloc())
    {
	udata.u_error = EAGAIN;
	return(-1);
    }
    di();

#ifdef MEMDEBUG
    kprintf("forking process %d\n", udata.u_ptab->p_pid);
#endif

    /* Read the MMU registers back into udata.u_page */
    rdmmu(udata.u_page);

    /* Mark all pages as copy-on-write (read-only bit), and increment
	reference count in master page table */
    for (j=0; j<16; ++j)
    {
	/* To deal with swapped out pages, swap them in first.
	   Do this by touching the page. */
	if (udata.u_page[j] == OUTPAGE)
	{
#ifdef MEMDEBUG
	    kprintf("Dofork touching swapped out page %d\n", j);
#endif
	    ugetc(0x1000 * j);
	}



	if (realpage(udata.u_page[j]))
	{
	    udata.u_page[j]->pg_addr |= 0x05;  /* Mark as modified */
	    pg_realloc(udata.u_page[j]);
	}
    }

    /* Reload the MMU with the modified entries */
    ldmmu(udata.u_page);

    udata.u_ptab->p_status = P_READY; /* Parent is READY */
    newid = p->p_pid;
    ei();

    /* Save the stack pointer and critical registers */
    /* When the process is swapped back in, it will be as if
    it returns with the value of the childs pid. */

#asm
	LD	HL,(newid?)
	PUSH	HL
	PUSH	BC
	PUSH	IX
	LD	HL,0
	ADD	HL,SP	;get sp into hl
	LD	(stkptr?),HL
#endasm

    udata.u_ssp = stkptr;
    udata.u_usp = usp();

    swrite();

#asm
	POP	HL		;repair stack pointer
	POP	HL
	POP	HL
#endasm

    /* Make a new process table entry, etc. */
    newproc(p);

    di();
    runticks = 0;
    p->p_status = P_RUNNING;
    ei();

#ifdef MEMDEBUG
    kprintf("resuming child process %d\n", udata.u_ptab->p_pid);
#endif

    return (0);  /* Return to child */
}


/* Newproc fixes up the tables for the child of a fork */
/* The u_page array and the MMU are unchanged */

newproc(p)
ptptr p;    /* New process table entry */
{
    register char *j;

    /* Note that ptab_alloc clears most of the entry */
    di();
    p->p_swap = (p - ptab) * 129  + 1;  /* Allow 129 blocks per process */
    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;
    p->p_tty = udata.u_ptab->p_tty;
    udata.u_ptab = p;
    bzero(&udata.u_utime,4*sizeof(time_t)); /* Clear tick counters */
    ei();

    rdtime(&udata.u_time);
    if (udata.u_cwd)
	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()
{
    register ptptr p;
    register 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();

    /* Allocate storage for udata when not running */
    ifnot (p->p_ublk = tmpbuf())
	return (NULL);

    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.
The user arg is true if we are in user mode */

clkint2(user)
int user;
{
    ptptr p;

    inint = 1;

    ifnot (in(0xf0))	/* See if clock actually interrupted, and turn it off */
	warning("Bad clock");

    /* Increment processes and global tick counters */
    if (udata.u_ptab->p_status == P_RUNNING)
        incrtick(user ? &udata.u_utime : &udata.u_stime);

    incrtick(&ticks);

    /* Do once-per-second things */

    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)
		    ssig(p,SIGALRM);
	}
    }


    /* Check run time of current process */
    if (++runticks >= MAXTICKS && user)    /* Time to swap out */
    {
	udata.u_insys = 1;
	inint = 0;
	udata.u_ptab->p_status = P_READY;
	swapout();
	di();
	udata.u_insys = 0;	/* We have swapped back in */ 
    }

    inint = 0;
}



/* In data.c */
extern int (*disp_tab[])();
extern int ncalls;

unix()
{
#asm 8080
	PUSH	PSW
	PUSH	B
	PUSH	D
	PUSH	H
.Z80
	PUSH	IX
	PUSH	IY
.8080

	;call unix2()
	LDHLSX	12
	SHLD	(ub? + ?OCALL)		;Store in udata.u_callno
	CALL	unix2?

	;shove the return value where it will eventually get popped
	;into HL
	STHLSX	4

	PUSH	PSW
	POP	H	;get flags
	STHLSX	10	;shove in right place for return

	JMP	IRET##  ;jump to common interrupt return routine
#endasm
}


extern int stopflag;

/* No auto vars here, so carry flag will be preserved */
unix2()
{

    udata.u_insys = 1;
    udata.u_usp = usp();
    udata.u_error = 0;

    /* Copy args from user data space, checking for access violation */
    udata.u_argn3 = ugetw(udata.u_usp+4);
    udata.u_argn2 = ugetw(udata.u_usp+3);
    udata.u_argn1 = ugetw(udata.u_usp+2);
    udata.u_argn = ugetw(udata.u_usp+1);

    udata.u_retloc = ugetw(udata.u_usp);

    if (udata.u_callno >= ncalls)
	udata.u_error = EINVAL;

    ei();

#ifdef DEBUG
    while (stopflag)
	;
    kprintf ("\t\t\t\t\tcall %d (%x, %x, %x)\n", udata.u_callno,
	udata.u_argn2, udata.u_argn1, udata.u_argn);
#endif

    /* Branch to correct routine */

    ifnot (udata.u_error)  /* Could be set by ugetw() */
	udata.u_retval = (*disp_tab[udata.u_callno])();

#ifdef DEBUG
    kprintf("\t\t\t\t\t\tcall %d ret %x err %d\n",
	udata.u_callno,udata.u_retval, udata.u_error);
#endif


    chksigs();
    di();
    if (runticks >= MAXTICKS)
    {
	udata.u_ptab->p_status = P_READY;
	swapout();
    }
    ei();

    udata.u_insys = 0;

    /* If an error, return errno with carry set */

    if (udata.u_error)
    {
	;
#asm
	LD	HL, (ub? + ?OERR)
	LD	A,H
	OR	L
	SCF
	RET
#endasm
	;
    }

    return(udata.u_retval);
}




/* This sees if the current process has any signals set, and deals with them */
chksigs()
{
    register 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();
#ifdef MEMDEBUG
	    kprintf("process terminated by signal: ");
#endif
	    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();
}


sgrpsig(tty,sig)
int tty;
int16 sig;
{
    register ptptr p;

    for (p=ptab; p < ptab+PTABSIZE; ++p)
	if (p->p_status && p->p_tty == tty)
            ssig(p,sig);
	
}

ssig(proc,sig)
register ptptr proc;
int16 sig;
{    
    register 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();
}


