/* tag: hosted forth environment, executable code
 *
 * Copyright (C) 2003-2005 Patrick Mauritz, Stefan Reinauer
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#define __USE_LARGEFILE64
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdarg.h>

#ifdef __GLIBC__
#define _GNU_SOURCE
#include <getopt.h>
#endif

#include "sysinclude.h"
#include "mconfig.h"
#include "config.h"
#include "kernel/kernel.h"
#include "dict.h"
#include "kernel/stack.h"
#include "arch/unix/plugins.h"
#include "libopenbios/bindings.h"
#include "libopenbios/console.h"
#include "libopenbios/openbios.h"
#include "openbios-version.h"

#include "blk.h"
#include "libopenbios/ofmem.h"

#define MEMORY_SIZE	(4*1024*1024)	/* 4M ram for hosted system */
#define DICTIONARY_SIZE	(256*1024)	/* 256k for the dictionary   */

#if defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS==64)
#define lseek lseek64
#define __LFS O_LARGEFILE
#else
#define __LFS 0
#endif

/* prototypes */
static void exit_terminal(void);
void boot(void);

unsigned long virt_offset = 0;

/* local variables */

static ucell *memory;

static int diskemu;

static int segfault = 0;
static int verbose = 0;

#if defined(CONFIG_PPC) || defined(CONFIG_SPARC64)
unsigned long isa_io_base;
#endif

#if defined(__APPLE__)
int errno_int;	/* implement for fs drivers, needed to build on Mac OS X */
#endif

ucell ofmem_claim(ucell addr, ucell size, ucell align)
{
    return 0;
}

#ifdef CONFIG_PPC
extern void flush_icache_range(char *start, char *stop);

void flush_icache_range(char *start, char *stop)
{
}
#endif

#ifdef CONFIG_PPC
/* Expose system level is_machine helpers to make generic code easier */

#include "drivers/drivers.h"
int is_apple(void)
{
    return 0;
}

int is_oldworld(void)
{
    return 0;
}

int is_newworld(void)
{
    return 0;
}
#endif

#if 0
static void write_dictionary(char *filename)
{
	FILE *f;
	xt_t initxt;

	initxt = findword("initialize-of");
	if (!initxt)
		printk("warning: dictionary needs word called initialize-of\n");

	f = fopen(filename, "w");
	if (!f) {
		printk("panic: can't open dictionary.\n");
		exit_terminal();
		exit(1);
	}

	fwrite(DICTID, 16, 1, f);
	fwrite(dict, dicthead, 1, f);

	/* Write start address and last to relocate on load */
	fwrite(&dict, sizeof(ucell), 1, f);
	fwrite(&last, sizeof(ucell), 1, f);

	fclose(f);

#ifdef CONFIG_DEBUG_DICTIONARY
	printk("wrote dictionary to file %s.\n", filename);
#endif
}
#endif

static ucell read_dictionary(char *fil)
{
	int ilen;
	ucell ret;
	char *mem;
	FILE *f;
	struct stat finfo;

	if (stat(fil, &finfo))
		return 0;

	ilen = finfo.st_size;

	if ((mem = malloc(ilen)) == NULL) {
		printk("panic: not enough memory.\n");
		exit_terminal();
		exit(1);
	}

	f = fopen(fil, "r");
	if (!f) {
		printk("panic: can't open dictionary.\n");
		exit_terminal();
		exit(1);
	}

	if (fread(mem, ilen, 1, f) != 1) {
		printk("panic: can't read dictionary.\n");
		fclose(f);
		exit_terminal();
		exit(1);
	}
	fclose(f);

	ret = load_dictionary(mem, ilen);

	free(mem);
	return ret;
}


/*
 * functions used by primitives
 */

static int unix_availchar(void)
{
	int tmp = getc(stdin);
	if (tmp != EOF) {
		ungetc(tmp, stdin);
		return -1;
	}
	return 0;
}

static int unix_putchar(int c)
{
	putc(c, stdout);
	return c;
}

static int unix_getchar(void)
{
	return getc(stdin);
}

static struct _console_ops unix_console_ops = {
	.putchar = unix_putchar,
	.availchar = unix_availchar,
	.getchar = unix_getchar
};

u8 inb(u32 reg)
{
#ifdef CONFIG_PLUGINS
	io_ops_t *ior = find_iorange(reg);
	if (ior)
		return ior->inb(reg);
#endif

	printk("TRAP: io byte read @0x%x", reg);
	return 0xff;
}

u16 inw(u32 reg)
{
#ifdef CONFIG_PLUGINS
	io_ops_t *ior = find_iorange(reg);
	if (ior)
		return ior->inw(reg);
#endif

	printk("TRAP: io word read @0x%x", reg);
	return 0xffff;
}

u32 inl(u32 reg)
{
#ifdef CONFIG_PLUGINS
	io_ops_t *ior = find_iorange(reg);
	if (ior)
		return ior->inl(reg);
#endif

	printk("TRAP: io long read @0x%x", reg);
	return 0xffffffff;
}

void outb(u32 reg, u8 val)
{
#ifdef CONFIG_PLUGINS
	io_ops_t *ior = find_iorange(reg);
	if (ior) {
		ior->outb(reg, val);
		return;
	}
#endif

	printk("TRAP: io byte write 0x%x -> 0x%x", val, reg);
}

void outw(u32 reg, u16 val)
{
#ifdef CONFIG_PLUGINS
	io_ops_t *ior = find_iorange(reg);
	if (ior) {
		ior->outw(reg, val);
		return;
	}
#endif
	printk("TRAP: io word write 0x%x -> 0x%x", val, reg);
}

void outl(u32 reg, u32 val)
{
#ifdef CONFIG_PLUGINS
	io_ops_t *ior = find_iorange(reg);
	if (ior) {
		ior->outl(reg, val);
		return;
	}
#endif
	printk("TRAP: io long write 0x%x -> 0x%x", val, reg);
}

/*
 * terminal initialization and cleanup.
 */

static struct termios saved_termios;

static void init_terminal(void)
{
	struct termios termios;

	tcgetattr(0, &saved_termios);
	tcgetattr(0, &termios);
	termios.c_lflag &= ~(ICANON | ECHO);
        termios.c_cc[VMIN] = 1;
        termios.c_cc[VTIME] = 3; // 300 ms
	tcsetattr(0, 0, &termios);
}

static void exit_terminal(void)
{
	tcsetattr(0, 0, &saved_termios);
}

/*
 *  segmentation fault handler. linux specific?
 */

static void
segv_handler(int signo __attribute__ ((unused)),
	     siginfo_t * si, void *context __attribute__ ((unused)))
{
	static int count = 0;
	ucell addr = 0xdeadbeef;

	if (count) {
		printk("Died while dumping forth dictionary core.\n");
		goto out;
	}

	count++;

	if (PC >= (ucell) dict && PC <= (ucell) dict + dicthead)
		addr = *(ucell *) PC;

	printk("panic: segmentation violation at %x\n", (ucell)si->si_addr);
	printk("dict=0x%x here=0x%x(dict+0x%x) pc=0x%x(dict+0x%x)\n",
	       (ucell)dict, (ucell)dict + dicthead, dicthead, PC, PC - (ucell) dict);
	printk("dstackcnt=%d rstackcnt=%d instruction=%x\n",
	       dstackcnt, rstackcnt, addr);

#ifdef CONFIG_DEBUG_DSTACK
	printdstack();
#endif
#ifdef CONFIG_DEBUG_RSTACK
	printrstack();
#endif
#if 0
	printk("Writing dictionary core file\n");
	write_dictionary("forth.dict.core");
#endif

      out:
	exit_terminal();
	exit(1);
}

/*
 *  Interrupt handler. linux specific?
 *  Restore terminal state on ctrl-C.
 */

static void
int_handler(int signo __attribute__ ((unused)),
            siginfo_t * si __attribute__ ((unused)),
            void *context __attribute__ ((unused)))
{
    printk("\n");
    exit_terminal();
    exit(1);
}

/*
 * allocate memory and prepare engine for memory management.
 */

static void init_memory(void)
{
	memory = malloc(MEMORY_SIZE);
	if (!memory) {
		printk("panic: not enough memory on host system.\n");
		exit_terminal();
		exit(1);
	}

	memset (memory, 0, MEMORY_SIZE);
	/* we push start and end of memory to the stack
	 * so that it can be used by the forth word QUIT
	 * to initialize the memory allocator
	 */

	PUSH((ucell) memory);
	PUSH((ucell) memory + MEMORY_SIZE);
}

void exception(__attribute__((unused)) cell no)
{
	/*
	 * this is a noop since the dictionary has to take care
	 * itself of errors it generates outside of the bootstrap
	 */
}

static void
arch_init( void )
{
	openbios_init();
	modules_init();
	if(diskemu!=-1)
		blk_init();

	device_end();
        bind_func("platform-boot", boot);
}

int
read_from_disk( int channel, int unit, int blk, unsigned long mphys, int size )
{
	// channels and units not supported yet.
	unsigned char *buf=(unsigned char *)mphys;

	if(diskemu==-1)
		return -1;

	//printk("read: ch=%d, unit=%d, blk=%ld, phys=%lx, size=%d\n",
	//		channel, unit, blk, mphys, size);

	lseek(diskemu, (ducell)blk*512, SEEK_SET);
	read(diskemu, buf, size);

	return 0;
}

/*
 * main loop
 */

#define BANNER	"OpenBIOS core. (C) 2003-2006 Patrick Mauritz, Stefan Reinauer\n"\
		"This software comes with absolutely no warranty. "\
		"All rights reserved.\n\n"


#define USAGE   "usage: %s [options] [dictionary file|source file]\n\n"

int main(int argc, char *argv[])
{
	struct sigaction sa;
#if 0
	unsigned char *dictname = NULL;
#endif
	int c;

	const char *optstring = "VvhsD:P:p:f:?";

	while (1) {
#ifdef __GLIBC__
		int option_index = 0;
		static struct option long_options[] = {
			{"version", 0, NULL, 'V'},
			{"verbose", 0, NULL, 'v'},
			{"help", 0, NULL, 'h'},
//			{"dictionary", 1, NULL, 'D'},
			{"segfault", 0, NULL, 's'},
#ifdef CONFIG_PLUGINS
			{"plugin-path", 1, NULL, 'P'},
			{"plugin", 1, NULL, 'p'},
#endif
			{"file", 1, NULL, 'f'}
		};

		c = getopt_long(argc, argv, optstring, long_options,
				&option_index);
#else
		c = getopt(argc, argv, optstring);
#endif
		if (c == -1)
			break;

		switch (c) {
		case 'V':
                        printk(BANNER "Version " OPENBIOS_VERSION_STR "\n");
			return 0;
		case 'h':
		case '?':
                        printk(BANNER "Version " OPENBIOS_VERSION_STR "\n"
                               USAGE, argv[0]);
			return 0;
		case 'v':
			verbose = 1;
			break;
		case 's':
			segfault = 1;
			break;
#if 0
		case 'D':
			printk("Dumping final dictionary to '%s'\n", optarg);
			dictname = optarg;
			break;
#endif
#ifdef CONFIG_PLUGINS
		case 'P':
			printk("Plugin search path is now '%s'\n", optarg);
			plugindir = optarg;
			break;
		case 'p':
			printk("Loading plugin %s\n", optarg);
			load_plugin(optarg);
			break;
#endif
		case 'f':
			diskemu=open(optarg, O_RDONLY|__LFS);
			if(diskemu!=-1)
				printk("Using %s as harddisk.\n", optarg);
			else
				printk("%s not found. no harddisk node.\n",
						optarg);
			break;
		default:
			return 1;
		}
	}

	if (argc < optind + 1) {
		printk(USAGE, argv[0]);
		return 1;
	}

	/* Initialise console */
	init_console(unix_console_ops);

	if ((dict = (unsigned char *) malloc(DICTIONARY_SIZE)) == NULL) {
		printk("panic: not enough memory.\n");
		return 1;
	}

	dictlimit = DICTIONARY_SIZE;
	memset(dict, 0, DICTIONARY_SIZE);

	if (!segfault) {
		if (verbose)
			printk("Installing SIGSEGV handler...");

		sa.sa_sigaction = segv_handler;
		sigemptyset(&sa.sa_mask);
		sa.sa_flags = SA_SIGINFO | SA_NODEFER;
		sigaction(SIGSEGV, &sa, NULL);

		if (verbose)
			printk("done.\n");
	}

	/* set terminal to do non blocking reads */
	init_terminal();

        if (verbose)
            printk("Installing SIGINT handler...");

        sa.sa_sigaction = int_handler;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_SIGINFO | SA_NODEFER;
        sigaction(SIGINT, &sa, NULL);

        if (verbose)
            printk("done.\n");

	read_dictionary(argv[optind]);
	forth_init();

	PUSH_xt( bind_noname_func(arch_init) );
	fword("PREPOST-initializer");

	PC = (cell)findword("initialize-of");
	if (PC) {
		if (verbose) {
			if (optind + 1 != argc)
				printk("Warning: only first dictionary used.\n");

			printk("dictionary loaded (%d bytes).\n", dicthead);
			printk("Initializing memory...");
		}
		init_memory();

		if (verbose) {
			printk("done\n");

			printk("Jumping to dictionary...");
		}

		enterforth((xt_t)PC);
#if 0
		if (dictname != NULL)
			write_dictionary(dictname);
#endif

		free(memory);

	} else {		/* input file is not a dictionary */
		printk("not supported.\n");
	}

	exit_terminal();
	if (diskemu!=-1)
		close(diskemu);

	free(dict);
	return 0;
}

#undef printk
int
printk( const char *fmt, ... )
{
	int i;

	va_list args;
	va_start( args, fmt );
	i = vprintf(fmt, args );
	va_end( args );
	return i;
}
