/*
 * FreeBSD kernel unpacker.
 * 1993 by Serge Vakulenko
 * modified for FreeBSD 2.1 by Gary Jennejohn - 12FEB95
 */

/*
 * FreeBSD(98) port
 * 1995 by KATO T. of Nagoya University
 */

#include <sys/types.h>
#include <sys/reboot.h>		/* for RB_SERIAL */

#include <machine/cpufunc.h>	/* for inb/outb */

short *videomem;
int curs;
int cols;
int lines;
unsigned int port;

#ifdef PC98
unsigned char bios[0x200];
#else
unsigned char bios[0x100];
#endif

extern int end, edata;
void *storage;
void *inbuf;
void *outbuf;
void *window;

void decompress_kernel (void *dest);

int memcmp (const void *arg1, const void *arg2, unsigned len)
{
	unsigned char *a = (unsigned char*) arg1;
	unsigned char *b = (unsigned char*) arg2;

	for (; len-- > 0; ++a, ++b)
		if (*a < *b)
			return (-1);
		else if (*a > *b)
			return (1);
	return (0);
}

void *memcpy (void *to, const void *from, unsigned len)
{
	char *f = (char*) from;
	char *t = (char*) to;

	while (len-- > 0)
		*t++ = *f++;
	return (to);
}

void serial_putchar (unsigned char c)
{
	unsigned char stat;

	if (c == '\n')
		serial_putchar('\r');
#ifdef PC98
	do {
		 stat = inb (COMCONSOLE+2);
	} while (!(stat & 0x01));
#else
	do {
		 stat = inb (COMCONSOLE+5);
	} while (!(stat & 0x20));
#endif

	outb (COMCONSOLE, c);
}

void putchar (unsigned char c)
{
	switch (c) {
	case '\n':      curs = (curs + cols) / cols * cols;     break;
#ifdef PC98
	default:        videomem[curs++] = (c == 0x5c ? 0xfc : c);   break;
#else
	default:        videomem[curs++] = 0x0700 | c;          break;
#endif
	}
	while (curs >= cols*lines) {
		int col;

		memcpy (videomem, videomem+cols, (lines-1) * cols * 2);
		for (col = 0; col < cols; col++)
#ifdef PC98
			videomem[(lines - 1) * cols + col] = 0x20;
#else
			videomem[(lines - 1) * cols + col] = 0x720;
#endif
		curs -= cols;
	}
	/* set cursor position */
#ifdef PC98
	while ((inb(0x60) & 0x04) == 0) {}
	outb(0x62, 0x49);
	outb(0x60, (curs + 1) & 0xff);
	outb(0x60, (curs + 1) >> 8);
#else
	outb (port, 0x0e); outb (port+1, curs>>8);
	outb (port, 0x0f); outb (port+1, curs);
#endif
}

int use_serial;

void putstr (char *s)
{
	while (*s) {
		if (use_serial)
			serial_putchar (*s++);
		else
			putchar (*s++);
	}
}

void error (char *s)
{
	putstr ("\n\n");
	putstr (s);
	putstr ("\n\n -- System halted");
	while (1);                      /* Halt */
}

void boot (int howto)
{
	int l, c, *p;
#ifdef PC98
	unsigned short gdc_curaddr;
	int i;
#endif
	/* clear bss */
	for (p = &edata; p < &end; ++p)
		*p = 0;

	inbuf   = (void *)0x20000;
	outbuf  = (void *)0x30000;
	window  = (void *)0x40000;
	storage = (void *)0x50000;

	if (!(use_serial = (howto & RB_SERIAL))) {
#ifdef PC98
		videomem = (void*)0xa0000;
		cols = 80;
		lines = 25;

		/* Is FIFO buffer empty ? */
		while ((inb(0x60) & 0x04) == 0) {}

		outb(0x62, 0xe0);	/* CSRR command */
		/* T-GDC busy ? */
		while ((inb(0x60) & 0x01) == 0) {}
		/* read cursor address */
		gdc_curaddr = inb(0x62);
		gdc_curaddr += (inb(0x62) << 8);
		/* ignore rest of data */
		for (i = 0; i < 3; i++) {
			(void)inb(0x62);
		}

		l = gdc_curaddr / 80 + 1;
		c = 0;
#else
		/* Test for monochrome video adapter */
		if ((*((unsigned char*) 0x410) & 0x30) == 0x30)
			videomem = (void*) 0xb0000;     /* monochrome */
		else
			videomem = (void*) 0xb8000;     /* color */

		port = *(unsigned short*) 0x463;
		cols = *(unsigned short*) 0x44a;
		lines = 1 + *(unsigned char*) 0x484;
		c = *(unsigned char*) 0x450;
		l = *(unsigned char*) 0x451;
#endif

		if (lines < 25)
			lines = 25;
		curs = l*cols + c;
		if (curs > lines*cols)
			curs = (lines-1) * cols;
	}

	putstr ("Uncompressing kernel...");
	decompress_kernel ((void*) KADDR);
	putstr ("done\n");
	putstr ("Booting the kernel\n");
}
