/*
 * machine-dependent init stuff;
 * specific to VAX but general to all models
 */

#include "sys/param.h"
#include "sys/systm.h"
#include "sys/user.h"
#include "sys/proc.h"
#include "sys/map.h"
#include "sys/reg.h"
#include "sys/mtpr.h"
#include "sys/clock.h"
#include "sys/pte.h"
#include "sys/vm.h"
#include "sys/psl.h"
#include "sys/buf.h"
#include "sys/reboot.h"
#include "sys/conf.h"
#include "sys/text.h"
#include "sys/cmap.h"
#include "sys/msgbuf.h"

/*
 * table of little bits of page table needed
 * by various bits of code
 * the right way to do this would be
 * to have a pool, and hand space out dynamically
 */

extern char *CADDR1, *CADDR2, *CADDR3;
extern struct pte *CMAP1, *CMAP2, *CMAP3, *mmap;
extern char *vmmap;
extern char *priobuf;
extern struct pte *Prbufmap;

struct {
	int npages;
	struct pte **pmap;
	caddr_t *pmem;
} mmlist[] = {
	UPAGES, &Swapmap, (caddr_t *)&swaputl,
	UPAGES, &Forkmap, (caddr_t *)&forkutl,
	UPAGES, &Xswapmap, (caddr_t *)&xswaputl,
	UPAGES, &Xswap2map, (caddr_t *)&xswap2utl,
	UPAGES, &Pushmap, (caddr_t *)&pushutl,
	UPAGES, &Prusrmap, (caddr_t *)&prusrutl,
	CLSIZE, &Prbufmap, (caddr_t *)&priobuf,
	CLSIZE, &msgbufmap, (caddr_t *)&msgbuf,
	USRPTSIZE, &Usrptmap, (caddr_t *)&usrpt,
	1,	&CMAP1, (caddr_t *)&CADDR1,
	1,	&CMAP2, (caddr_t *)&CADDR2,
	1,	&CMAP3, (caddr_t *)&CADDR3,
	1,	&mmap, (caddr_t *)&vmmap,
	-1
};

struct pte *physspt;
struct pte *Sysmap;
struct pte *Usrptmap;
struct pte *usrpt;

/*
 * init the memory map
 * called early on, well before main
 * memory management is off
 *
 * the system page table is allocated here, out of free memory
 * the theory is:
 *	map the static kernel first
 *	then any pseudo-dynamic data structures
 *	then the buffers
 *	then lay down the system page table
 *	then map io space
 *
 * subtlety: we generally want end and etext to be physical addresses.
 * (long)&end & ~KSTART seems to be the only expression that is constant
 * with and without the c optimizer.
 */

mmapinit(hiaddr)
long hiaddr;
{
	extern int end, etext;
	extern char *iospace;
	register int i;
	register long *p;	/* pun; really struct pte * */
	register long mtop;	/* highest real memory address used */
	register long ptop;	/* highest virtual address used */
	register long v;

	maxmem = physmem = btoc(hiaddr);
	mtop = (long)&end & ~KSTART;
	/*
	 * dynamic things: add to memtop, set addrs along the way
	 */
	if (nbuf == 0) {
		nbuf = 32 * physmem / btoc(1024*1024);
		if (nbuf < 32)
			nbuf = 32;
	}
	buf = (struct buf *)(KSTART+mtop);
	mtop += nbuf * sizeof(struct buf);
	mtop = (mtop + NBPG - 1) & ~PGOFSET;
	buffers = (char *)(KSTART+mtop);
	mtop += nbuf * BUFSIZE;
	/*
	 * cmaps; we'll slightly overestimate; too bad
	 */
	cmap = (struct cmap *)(KSTART+mtop);
	ncmap = (physmem * NBPG - mtop)/(CLSIZE*NBPG + sizeof(struct cmap));
	mtop += ncmap * sizeof(struct cmap);
	ecmap = (struct cmap *)(KSTART+mtop);
	/*
	 * clear all the memory we just handed out
	 */
	p = (long *)((long)&end & ~KSTART);
	while (p < (long *)mtop)
		*p++ = 0;
	/*
	 * now the things that aren't memory
	 * maps are relative to beginning of spt
	 */
	mtop = (mtop + NBPG - 1) & ~PGOFSET;
	ptop = mtop;
	for (i = 0; mmlist[i].npages >= 0; i++) {
		*mmlist[i].pmem = (caddr_t)(ptop + KSTART);
		*mmlist[i].pmap = (struct pte *)(btoc(ptop) * sizeof(struct pte));
		ptop += ctob(mmlist[i].npages);
	}
	iospace = (char *)(ptop + KSTART);
	ptop += mchiopsize();
	Sysmap = (struct pte *)(ptop + KSTART);
	physspt = (struct pte *)mtop;
	p = (long *)mtop;
	i = (int)&etext & ~KSTART;	/* not - KSTART; optimizer gets it wrong */
	for (v = 0; v < mtop; v += NBPG)	/* kernel text + data */
		if (v < i)
			*p++ = PG_V|PG_KR|btoc(v);
		else
			*p++ = PG_V|PG_KW|btoc(v);
	for (; v < ptop; v += NBPG)		/* mystery maps */
		*p++ = 0;
	ptop += (btoc(ptop) + btoc(btoc(ptop)*sizeof(struct pte)))*sizeof(struct pte);
	for (i = btoc(mtop); v < ptop; v += NBPG, i++)		/* spt */
		*p++ = PG_V|PG_KW|i;
	firstfree = i + 1;
	/*
	 * fix the random maps
	 */
	for (i = 0; mmlist[i].npages >= 0; i++)
		*(long *)mmlist[i].pmap += (long)Sysmap;
	mchiopinit(physspt + btoc(iospace - KSTART));
	p = (long *)(physspt + (msgbufmap - Sysmap));
	for (i = 0; i < btoc(sizeof(struct msgbuf)); i++)
		*p++ = PG_V|PG_KW|(physmem - btoc(sizeof(struct msgbuf)) + i);
	maxmem -= CLSIZE;
	mtpr(SBR, mtop);
	mtpr(SLR, btoc(ptop));
}

/*
 * set up context for process 0:
 * first page of free memory becomes page table
 * next UPAGES become user block
 * init the process control block slightly
 * called before memory mapping is turned on
 */

uctinit(kpc)
long kpc;
{
	register long *p;	/* pun: struct pte * */
	register int i;
	register struct pcb *pp;

	p = (long *)ctob(firstfree);
	for (i = 0; i < (UPAGES+1)*NBPG; i += sizeof(long))
		*p++ = 0;
	p = (long *)(physspt + ((long)usrpt-KSTART)/NBPG);
	*p = PG_V|PG_KW|firstfree;	/* one page for first p0 page table */
	p = (long *)ctob(firstfree+1);
	for (i = UPAGES; i > 0; --i)
		*--p = PG_V|PG_URKW|firstfree+i;
	pp = (struct pcb *)ctob(firstfree+1);
	pp->pcb_p0br = usrpt;
	pp->pcb_p0lr = 0 + AST_NONE;	/* no p0 space mapped */
	pp->pcb_p1br = usrpt + NPTEPG - P1TOP;
	pp->pcb_p1lr = P1TOP - UPAGES;
	pp->pcb_ksp = KSTART;
	pp->pcb_esp = pp->pcb_ssp = -1;		/* something invalid */
	pp->pcb_usp = USRSTACK;
	pp->pcb_psl = 0;		/* mode (kernel,kernel), ipl 0 */
	pp->pcb_pc = kpc;
	firstfree += UPAGES + 1;
	mtpr(PCBB, pp);
}

/*
 * Machine-dependent startup code
 * some of this is excessively specific to the paging code
 */

int dmmin, dmmax, dmtext;

startup()
{
	register long maxsz;
	register unsigned i;
	register struct proc *p;
	extern struct map kernelmap[];
	extern int kernelcnt;
	extern char version[];

	setrestart();
	printf("%s\n", version);
	meminit(firstfree, maxmem);
	maxmem = freemem;
	printf("mem = %d\n", ctob(maxmem));
	rminit(kernelmap, kernelcnt, USRPTSIZE-1, 1);
	/*
	 * dmmin fixed for now
	 * dmmax == first power of 2 larger than max?size/(NDMAP-1)
	 */
	dmmin = DMMIN;
	maxsz = maxtsize / NXDAD;
	for (i = 0x80000000; i; i >>= 1)
		if (maxsz & i)
			break;
	i *= 2;
	if (i < dmmin)
		i = dmmin;
	dmtext = i;
	maxsz = maxdsize > maxssize ? maxdsize : maxssize;
	maxsz /= NDMAP-1;
	if (maxsz < dmtext)
		maxsz = dmtext;
	for (i = 0x80000000; i; i >>= 1)
		if (maxsz & i)
			break;
	i *= 2;
	if (i < dmmin)
		i = dmmin;
	dmmax = i;
	maxpgio *= nswdevt;
	/*
	 * set up vm aspects of first process
	 */
	p = &proc[0];
	p->p_p0br = (struct pte *)mfpr(P0BR);
	p->p_szpt = 1;
	p->p_addr = uaddr(p);
	setredzone(p->p_addr, (caddr_t)&u);
	u.u_pcb.pcb_szpt = CLSIZE;
}

/*
 * init unix time
 * base is some nearly correct number, e.g. from a filesystem.
 * try to get something better from the VAX time-of-year clock,
 * which contains the number of .01-second clicks in the current year
 */
clkinit(base)
	time_t base;
{
	register unsigned long todr;
	int year = YRREF;

	todr = gettodr();
	if (todr < TODRZERO) {	/* too small; TODR was restarted */
		time = base;
		clkset();	/* so fix it */
	}
	else {
		for (time = (todr-TODRZERO)/100; time < base-SECYR/2; time += SECYR) {
			if (LEAPYEAR(year))
				time += SECDAY;
			year++;
		}
	}
}

/*
 * once a day or so, set TOY clock to match system clock
 */

static time_t clknext;

clkcheck()
{

	if (clknext == 0)
		clknext = time + SECDAY;
	if (clknext > time)
		return;
	clknext = time + SECDAY;
	clkset();
}

/*
 * reset the TOY clock to match current unix time
 */

clkset()
{
	int year = YRREF;
	unsigned secyr;
	unsigned yrtime = time;

	for (;;) {
		secyr = SECYR;
		if (LEAPYEAR(year))
			secyr += SECDAY;
		if (yrtime < secyr)
			break;
		yrtime -= secyr;
		year++;
	}
	settodr(TODRZERO + yrtime*100);
}

/*
 * bias the clock to correct an error of `wrong' milliseconds
 * -- just tick slightly slowly or quickly for a while
 * MicroVAX has no NICR, but writing it is a no-op;
 * let the clock interrupt code do the work
 */

int clockbias;		/* number of ticks */
char clocksign;		/* direction to correct */

biasclock()
{
	register struct a {
		int wrong;
	} *uap;
	register long nticks;

	uap = (struct a *)u.u_ap;
	if (!suser(0))
		return;
	nticks = (long)uap->wrong * 1000;	/* microseconds */
	nticks /= CLKBIAS;			/* number of ticks to bias */
	spl7();
	if (nticks < 0) {
		mtpr(NICR, CLK60HZ-CLKBIAS);
		clockbias = -nticks;
		clocksign = -1;		/* -1 => clock must run slower */
	}
	else {
		mtpr(NICR, CLK60HZ+CLKBIAS);
		clockbias = nticks;
		clocksign = 1;
	}
	spl0();
}

/*
 * Return the difference (in microseconds)
 * between the  current time and a previous
 * time as represented  by the arguments.
 * If there is a pending clock interrupt
 * which has not been serviced due to high
 * ipl, return error code.
 */
vmtime(otime, olbolt, oicr)
	register int otime, olbolt, oicr;
{

	if (mfpr(ICCS)&ICCS_INT)
		return(-1);
	else
		return(((time-otime)*60 + lbolt-olbolt)*16667 + mfpr(ICR)-oicr);
}

/*
 * Send an interrupt to process
 *
 * SHOULD CHANGE THIS TO PASS ONE MORE WORK SO THAT ALL INFORMATION
 * PROVIDED BY HARDWARE IS AVAILABLE TO THE USER PROCESS.
 */
sendsig(p, n)
	int (*p)();
{
	register int *usp, *regs;

	regs = u.u_ar0;
	usp = (int *)regs[SP];
	usp -= 5;
	if ((int)usp <= USRSTACK - ctob(u.u_ssize))
		(void) grow((unsigned)usp);
	if (useracc((caddr_t)usp, 5*sizeof(int), B_WRITE) == 0)
		goto bad;
	*usp++ = n;
	if (n == SIGILL || n == SIGFPE) {
		*usp++ = u.u_code;
		u.u_code = 0;
	} else
		*usp++ = 0;
	*usp++ = (int)p;
	*usp++ = regs[PC];
	*usp++ = regs[PS];
	regs[SP] = (int)(usp - 5);
	regs[PS] &= ~(PSL_CM|PSL_FPD);
	regs[PC] = (int)u.u_pcb.pcb_sigc;
	return;

bad:
	/*
	 * Process has trashed its stack; give it an illegal
	 * instruction to halt it in its tracks.
	 */
	u.u_signal[SIGILL] = SIG_DFL;
	P_SETDFL(u.u_procp, SIGMASK(SIGILL));
	psignal(u.u_procp, SIGILL);
}

/*
 * Invalidate single all pte's in a cluster
 */
tbiscl(v)
	unsigned v;
{
	register caddr_t addr;
	register int i;

	addr = ptob(v);
	for (i = 0; i < CLSIZE; i++) {
		mtpr(TBIS, addr);
		addr += NBPG;
	}
}

/*
 * sys reboot
 * can this please go away?
 */

boot(howto)
	int howto;
{

	if (howto&RB_HALT)
		death();
	reboot(1);
}

/*
 * freeze.  can't just halt because
 * on some machines that would cause a boot
 */

death()
{
	splx(0x1f);
	printf("death\n");
	for (;;)
		;
}
