/*
 * 9boot - load next 386 or amd64 kernel from disk and start it
 *	and
 * 9load - load next 386 or amd64 kernel via pxe (bootp, tftp) and start it
 *
 * intel says that pxe can only load into the bottom 640K, and
 * intel's pxe boot agent takes 128K, leaving only 512K for 9boot.
 */
#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"io.h"
#include	"ureg.h"
#include	"pool.h"
#include	"reboot.h"
#include	"ip.h"		/* for eipfmt */
#include	<tos.h>

enum {
	Datamagic = 0xbabeabed,
};

Mach *m;

ulong* mach0pdb;
Mach* mach0m;
Segdesc* mach0gdt;
u32int memstart;
u32int memend;
int noclock;

extern int pcivga;
extern char hellomsg[];

/*
 * Where configuration info is left for the loaded programme.
 */
char bootdisk[KNAMELEN];
Conf conf;

uchar *sp;	/* user stack of init proc */
int delaylink;
int debug;
int v_flag;

static void
sanity(void)
{
	uintptr cr3;

	cr3 = (uintptr)KADDR(getcr3());
	if (cr3 == 0)
		panic("zero cr3");
	if ((uintptr)m->pdb != cr3 || (uintptr)mach0pdb != cr3)
		panic("not all same: cr3 %#p m->pdb %#p mach0pdb %#p",
			cr3, m->pdb, mach0pdb);
	if (m != mach0m)
		panic("m %#p != mach0m %#p", m, mach0m);
	if (m->gdt != mach0gdt)
		panic("m->gdt %#p != mach0gdt %#p", m->gdt, mach0gdt);
	if (0)
		iprint("m->pdb %#p m %#p sp %#p m->gdt %#p\n",
			m->pdb, m, &cr3, m->gdt);
}

enum {
	/* system control port a */
	Sysctla=	0x92,
	 Sysctlreset=	1<<0,
	 Sysctla20ena=	1<<1,
};

static int
isa20on(void)
{
	int r;
	ulong o;
	ulong *zp, *mb1p;

	zp = 0;
	mb1p = (ulong *)MB;
	o = *zp;

	*zp = 0x1234;
	*mb1p = 0x8765;
	mb586();
	wbinvd();
	r = *zp != *mb1p;

	*zp = o;
	return r;
}

void
a20init(void)
{
	int b;

	if (isa20on())
		return;

	i8042a20();			/* original method, via kbd ctlr */
	if (isa20on())
		return;

	/* newer method, last resort */
	b = inb(Sysctla);
	if (!(b & Sysctla20ena))
		outb(Sysctla, (b & ~Sysctlreset) | Sysctla20ena);
	if (!isa20on()){
		iprint("a20 didn't come on!\n");
		for(;;)
			;
	}
}

void
main(void)
{
	Proc *savup;
	static ulong vfy = Datamagic;
	static char novga[] = "\nno vga; serial console only\n";

	savup = up;
	up = nil;
	/* m has been set by l32v.s */

	/*
	 * disable address wraps at 1MB boundaries.
	 * if we're 9boot, ldecomp.s already did this.
	 */
	a20init();

	mach0init();
//	options();		/* we don't get options passed to us */
	ioinit();
	/* we later call i8250console after plan9.ini has been read */
	i8250config("0");	/* configure serial port 0 with defaults */
	quotefmtinstall();
 	fmtinstall('i', eipfmt);
 	fmtinstall('I', eipfmt);
 	fmtinstall('E', eipfmt);
 	fmtinstall('V', eipfmt);
 	fmtinstall('M', eipfmt);
	screeninit();			/* cga setup */
	cgapost(0xc);

	trapinit0();
	mmuinit0();

	kbdinit();
	i8253init();
	cpuidentify();
	readlsconf();
	meminit();
	confinit();
	archinit();
	xinit();
	if(i8237alloc != nil)
		i8237alloc();		/* dma (for floppy) init */
	trapinit();
	printinit();
	sanity();
	cgapost(1);

	/*
	 * soekris servers have no built-in video but each has a serial port.
	 * they must see serial output, if any, before cga output because
	 * otherwise the soekris bios will translate cga output to serial
	 * output, which will garble serial console output.
	 */
	pcimatch(nil, 0, 0);		/* force scan of pci table */
	if (!pcivga) {
		screenputs = nil;
		uartputs(novga, sizeof novga - 1);
	}
	print(" %s\n\n", hellomsg);

	if (vfy != Datamagic)
		panic("data segment incorrectly aligned or loaded");
	if (savup)
		print("up was non-nil (%#p) upon entry to main; bss wasn't zeroed!\n",
			savup);

//	xsummary();
	cpuidprint();
	mmuinit();
	if(arch->intrinit)	/* launches other processors on an mp */
		arch->intrinit();
	timersinit();
	mathinit();
	kbdenable();
	/*
	 * 9loadusb runs much faster if we don't use the clock.
	 * perhaps we're competing with the bios for the use of it?
	 */
	if(!noclock && arch->clockenable)
		arch->clockenable();
	procinit0();
	initseg();
	if(delaylink){
		bootlinks();
		pcimatch(0, 0, 0);
	}else
		links();
	conf.monitor = 1;
	cgapost(0xcd);
	chandevreset();
	cgapost(2);
	pageinit();	/* must follow xinit, and conf.mem must be populated */
	i8253link();
	userinit();

	active.thunderbirdsarego = 1;
	cgapost(0xb0);
	schedinit();
}

void
mach0init(void)
{
	conf.nmach = 1;
	MACHP(0) = mach0m;
	m->machno = 0;
	m->pdb = mach0pdb;
	m->gdt = mach0gdt;

	machinit();

	active.machs = 1;
	active.exiting = 0;
}

void
machinit(void)
{
	int machno;
	ulong *pdb;
	Segdesc *gdt;

	machno = m->machno;
	pdb = m->pdb;
	gdt = m->gdt;
	memset(m, 0, sizeof(Mach));
	m->machno = machno;
	m->pdb = pdb;
	m->gdt = gdt;
	m->perf.period = 1;

	/*
	 * For polled uart output at boot, need
	 * a default delay constant. 100000 should
	 * be enough for a while. Cpuidentify will
	 * calculate the real value later.
	 */
	m->loopconst = 100000;
}

void
init0(void)
{
	int i;
	char buf[2*KNAMELEN];

	up->nerrlab = 0;

	spllo();

	/*
	 * These are o.k. because rootinit is null.
	 * Then early kproc's will have a root and dot.
	 */
	up->slash = namec("#/", Atodir, 0, 0);
	pathclose(up->slash->path);
	up->slash->path = newpath("/");
	up->dot = cclone(up->slash);

	chandevinit();

	if(0 && !waserror()){			/* not needed by boot */
		snprint(buf, sizeof(buf), "%s %s", arch->id, conffile);
		ksetenv("terminal", buf, 0);
		ksetenv("cputype", "386", 0);
		if(cpuserver)
			ksetenv("service", "cpu", 0);
		else
			ksetenv("service", "terminal", 0);
		for(i = 0; i < nconf; i++){
			if(confname[i][0] != '*')
				ksetenv(confname[i], confval[i], 0);
			ksetenv(confname[i], confval[i], 1);
		}
		poperror();
	}
	kproc("alarm", alarmkproc, 0);

	conschan = enamecopen("#c/cons", ORDWR);
	bootloadproc(0);
	panic("bootloadproc returned");
}

void
userinit(void)
{
	Proc *p;

	p = newproc();
	p->pgrp = newpgrp();
	p->egrp = smalloc(sizeof(Egrp));
	p->egrp->ref = 1;
	p->fgrp = dupfgrp(nil);
	p->rgrp = newrgrp();
	p->procmode = 0640;

	kstrdup(&eve, "");
	kstrdup(&p->text, "*init*");
	kstrdup(&p->user, eve);

	p->fpstate = FPinit;
	fpoff();

	/*
	 * Kernel Stack
	 *
	 * N.B. make sure there's enough space for syscall to check
	 *	for valid args and 
	 *	4 bytes for gotolabel's return PC
	 */
	p->sched.pc = (ulong)init0;
	p->sched.sp = (ulong)p->kstack+KSTACK-(sizeof(Sargs)+BY2WD);

	/* NB: no user stack nor text segments are set up */

	ready(p);
}

void
confinit(void)
{
	int i, userpcnt;
	ulong kpages;

	userpcnt = 0;			/* bootstrap; no user mode  */
	conf.npage = 0;
	for(i=0; i<nelem(conf.mem); i++)
		conf.npage += conf.mem[i].npage;

	conf.npage = MemMax / BY2PG;
	conf.nproc = 20;		/* need a few kprocs */
	if(cpuserver)
		conf.nproc *= 3;
	if(conf.nproc > 2000)
		conf.nproc = 2000;
	conf.nimage = 40;
	conf.nswap = conf.nproc*80;
	conf.nswppo = 4096;

	kpages = conf.npage - (conf.npage*userpcnt)/100;

	/*
	 * can't go past the end of virtual memory
	 * (ulong)-KZERO is 2^32 - KZERO
	 */
	if(kpages > ((ulong)-KZERO)/BY2PG)
		kpages = ((ulong)-KZERO)/BY2PG;

	conf.upages = conf.npage - kpages;
	conf.ialloc = (kpages/2)*BY2PG;

	/*
	 * Guess how much is taken by the large permanent
	 * datastructures. Mntcache and Mntrpc are not accounted for
	 * (probably ~300KB).
	 */
	kpages *= BY2PG;
	kpages -= conf.upages*sizeof(Page)
		+ conf.nproc*sizeof(Proc)
		+ conf.nimage*sizeof(Image)
		+ conf.nswap
		+ conf.nswppo*sizeof(Page*);
	mainmem->maxsize = kpages;
	if(!cpuserver){
		/*
		 * give terminals lots of image memory, too; the dynamic
		 * allocation will balance the load properly, hopefully.
		 * be careful with 32-bit overflow.
		 */
		imagmem->maxsize = kpages;
	}
}

/*
 *  math coprocessor segment overrun
 */
static void
mathover(Ureg*, void*)
{
	pexit("math overrun", 0);
}

void
mathinit(void)
{
}

/*
 *  set up floating point for a new process
 */
void
procsetup(Proc*p)
{
	p->fpstate = FPinit;
	fpoff();
}

void
procrestore(Proc *p)
{
	uvlong t;

	if(p->kp)
		return;
	cycles(&t);
	p->pcycles -= t;
}

/*
 *  Save the mach dependent part of the process state.
 */
void
procsave(Proc *p)
{
	uvlong t;

	cycles(&t);
	p->pcycles += t;

	/*
	 * While this processor is in the scheduler, the process could run
	 * on another processor and exit, returning the page tables to
	 * the free list where they could be reallocated and overwritten.
	 * When this processor eventually has to get an entry from the
	 * trashed page tables it will crash.
	 *
	 * If there's only one processor, this can't happen.
	 * You might think it would be a win not to do this in that case,
	 * especially on VMware, but it turns out not to matter.
	 */
	mmuflushtlb(PADDR(m->pdb));
}

static void
shutdown(int ispanic)
{
	int ms, once;

	lock(&active);
	if(ispanic)
		active.ispanic = ispanic;
	else if(m->machno == 0 && (active.machs & (1<<m->machno)) == 0)
		active.ispanic = 0;
	once = active.machs & (1<<m->machno);
	/*
	 * setting exiting will make hzclock() on each processor call exit(0),
	 * which calls shutdown(0) and arch->reset(), which on mp systems is
	 * mpshutdown, which idles non-bootstrap cpus and returns on bootstrap
	 * processors (to permit a reboot).  clearing our bit in machs avoids
	 * calling exit(0) from hzclock() on this processor.
	 */
	active.machs &= ~(1<<m->machno);
	active.exiting = 1;
	unlock(&active);

	if(once)
		iprint("cpu%d: exiting\n", m->machno);

	/* wait for any other processors to shutdown */
	spllo();
	for(ms = 5*1000; ms > 0; ms -= TK2MS(2)){
		delay(TK2MS(2));
		if(active.machs == 0 && consactive() == 0)
			break;
	}

	if(active.ispanic){
		if(!cpuserver)
			for(;;)
				halt();
		if(getconf("*debug"))
			delay(5*60*1000);
		else
			delay(10000);
	}else
		delay(1000);
}

void
reboot(void *entry, void *code, ulong size)
{
	int i;
	void (*f)(ulong, ulong, ulong);
	ulong *pdb;

	/* we do pass options to the kernel we loaded, however, at CONFADDR. */
	// writeconf();

	/*
	 * the boot processor is cpu0.  execute this function on it
	 * so that the new kernel has the same cpu0.  this only matters
	 * because the hardware has a notion of which processor was the
	 * boot processor and we look at it at start up.
	 */
	if (m->machno != 0) {
		procwired(up, 0);
		sched();
	}

	if(conf.nmach > 1) {
		/*
		 * the other cpus could be holding locks that will never get
		 * released (e.g., in the print path) if we put them into
		 * reset now, so force them to shutdown gracefully first.
		 */
		lock(&active);
		active.rebooting = 1;
		unlock(&active);
		shutdown(0);
		if(arch->resetothers)
			arch->resetothers();
		delay(20);
	}

	/*
	 * should be the only processor running now
	 */
	active.machs = 0;
	if (m->machno != 0)
		print("on cpu%d (not 0)!\n", m->machno);

	print("shutting down...\n");
	delay(200);

	splhi();

	/* turn off buffered serial console */
	serialoq = nil;

	/* shutdown devices */
	chandevshutdown();
	arch->introff();

	/*
	 * Modify the machine page table to directly map low memory
	 * This allows the reboot code to turn off the page mapping
	 */
	pdb = m->pdb;
	for (i = 0; i < LOWPTEPAGES; i++)
		pdb[PDX(i*4*MB)] = pdb[PDX(KZERO + i*4*MB)];
	mmuflushtlb(PADDR(pdb));

	/* setup reboot trampoline function */
	f = (void*)REBOOTADDR;
	memmove(f, rebootcode, sizeof(rebootcode));

	print("rebooting...\n");

	/* off we go - never to return */
	coherence();
	(*f)(PADDR(entry), PADDR(code), size);
}


void
exit(int ispanic)
{
	shutdown(ispanic);
	spllo();
	arch->reset();
}

int
isaconfig(char *class, int ctlrno, ISAConf *isa)
{
	char cc[32], *p;
	int i;

	snprint(cc, sizeof cc, "%s%d", class, ctlrno);
	p = getconf(cc);
	if(p == nil)
		return 0;

	isa->type = "";
	isa->nopt = tokenize(p, isa->opt, NISAOPT);
	for(i = 0; i < isa->nopt; i++){
		p = isa->opt[i];
		if(cistrncmp(p, "type=", 5) == 0)
			isa->type = p + 5;
		else if(cistrncmp(p, "port=", 5) == 0)
			isa->port = strtoul(p+5, &p, 0);
		else if(cistrncmp(p, "irq=", 4) == 0)
			isa->irq = strtoul(p+4, &p, 0);
		else if(cistrncmp(p, "dma=", 4) == 0)
			isa->dma = strtoul(p+4, &p, 0);
		else if(cistrncmp(p, "mem=", 4) == 0)
			isa->mem = strtoul(p+4, &p, 0);
		else if(cistrncmp(p, "size=", 5) == 0)
			isa->size = strtoul(p+5, &p, 0);
		else if(cistrncmp(p, "freq=", 5) == 0)
			isa->freq = strtoul(p+5, &p, 0);
	}
	return 1;
}

int less_power_slower;

/*
 *  put the processor in the halt state if we've no processes to run.
 *  an interrupt will get us going again.
 */
void
idlehands(void)
{
	/*
	 * we used to halt only on single-core setups. halting in an smp system 
	 * can result in a startup latency for processes that become ready.
	 * if less_power_slower is true, we care more about saving energy
	 * than reducing this latency.
	 */
	if(conf.nmach == 1 || less_power_slower)
		halt();
}

void
trimnl(char *s)
{
	char *nl;

	nl = strchr(s, '\n');
	if (nl != nil)
		*nl = '\0';
}
