/*
** boot_atari.c -- This program loads the Atari Linux kernel and launches it.
**
** Copyright 1994 by Bj”rn Brauel and Roman Hodek
**
**
** This file is subject to the terms and conditions of the GNU General Public
** License.  See the file README.legal in the main directory of this archive
** for more details.
**
*/

#include <osbind.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <file.h>
#include <types.h>
#include <unistd.h>

#define m68k
#define PORTAR

/* #define	DEBUG    */


/* Atari bootstrap include file */
#include "bootstra.h"

/* required Atari linux include files */
# include "a.out.h"

#include "bootinfo.h"

/* temporary stack size */
#define TEMP_STACKSIZE	1024

#define	FAST_RAM_START	0x1000000

long *fastram_top = (long *)0x5a4,
	 *fastram_valid = (long *)0x5a8;

/* The following variables are global, because they have to be accessed
 * after the stack has been changed.
 */

struct bootinfo bi;

long  KSTART_MEM;
long  KMEM_SIZE;     
long  kbi_offset;

char  *memptr;
struct exec kexec;
long   rd_size;

void   *copy_addr;
long   copy_len;



static void get_default_args( int *argc, char ***argv );
static void waitkey( void );


void usage(void)
{
	fprintf (stderr, "Usage:\n"
		 "\tbootstrap [-d] [-k kernel_executable] [-r ramdisk_file]"
		 " [option...]\n");
	waitkey();
	exit (EXIT_FAILURE);
}

int main(int argc, char *argv[])
{
    long i,memreq;
    int kfd,rfd;
    int debugflag=0;
    long offs;
	char *kernel_name = "vmlinux";
	char *ramdisk_name = NULL;
    void *stack;
	long savessp;

	rfd=-1;
	/* print the greet message */
	puts("Atari Linux Bootstrap version 0.6");
	puts("Copyright 1994 by Bj”rn Brauel, Roman Hodek\n");

	if (argc == 1)
		get_default_args( &argc, &argv );

	/* ++roman: get kernel and ramdisk name from the command line */
	while( argc > 1 && argv[1][0] == '-' ) {
		switch( argv[1][1] ) {
		    case 'k':
				if (argv[1][2]) {
					kernel_name = argv[1]+2;
					--argc; ++argv;
				}
				else if (argc > 2 && argv[2][0]) {
					kernel_name = argv[2];
					argc -= 2; argv += 2;
				}
				else
					usage();
				break;
		    case 'r':
				if (argv[1][2]) {
					ramdisk_name = argv[1]+2;
					--argc; ++argv;
				}
				else if (argc > 2 && argv[2][0]) {
					ramdisk_name = argv[2];
					argc -= 2; argv += 2;
				}
				else
					usage();
				break;
			case 'd':
				debugflag = 1;
				--argc; ++argv;
				break;
		    case '?':
	    	default:
				usage();
		}
	}
	--argc; ++argv;

	/* machine is Atari */
	bi.machtype = MACH_ATARI;
	KSTART_MEM = 0x000000L;                              
	KMEM_SIZE = 0x400000L;

	bi.cputype = CPU_68030;
	bi.cputype |= FPU_68881;
	bi.num_memory = 1;
	bi.memory[0].addr=KSTART_MEM;
	bi.memory[0].size=KMEM_SIZE;

	/* Test for TT Ram: I rely on undocumented system variables here,
	 * hope the compatibility gods won't damn me forever...
	 */
	savessp = Super( 0L );
	if (*fastram_valid == 0x1357bd13 && *fastram_top != 0) {
		bi.memory[bi.num_memory].addr = FAST_RAM_START;
		bi.memory[bi.num_memory].size = *fastram_top - FAST_RAM_START /* - PAGE_SIZE */;
		bi.num_memory++;
	}
	Super( savessp );

	/* Copy remaining arguments into the boot info */
	i = 0;
	bi.command_line[0] = 0;
	while (argc--) {
		if ((i+strlen(*argv)+1) < CL_SIZE) {
			i += strlen(*argv) + 1;
			if (bi.command_line[0])
				strcat (bi.command_line, " ");
			strcat (bi.command_line, *argv++);
		}
	}

	printf( "Command line is \"%s\"\n", bi.command_line );

	/* tell us where the kernel will go */
	printf("\nThe kernel will be located at %08lx\n", KSTART_MEM);

	/* open kernel executable and read exec header */
	if ((kfd = open (kernel_name, O_RDONLY)) == -1) {
		fprintf (stderr, "Unable to open kernel file %s\n", kernel_name);
		waitkey();
		exit (EXIT_FAILURE);
	}

	if (read (kfd, (void *)&kexec, sizeof(kexec)) != sizeof(kexec)) {
		fprintf (stderr, "Unable to read exec header from %s\n",
			 kernel_name);
		waitkey();
		exit (EXIT_FAILURE);
	}

	if (ramdisk_name) {
		if ((rfd = open (ramdisk_name, O_RDONLY)) == -1) {
			fprintf (stderr, "Unable to open ramdisk file %s\n",
				 ramdisk_name);
			waitkey();
			exit (EXIT_FAILURE);
		}
		/* record ramdisk size */
		bi.ramdisk_size = (lseek (rfd, 0, SEEK_END)) >> 10;
	} else
		bi.ramdisk_size = 0;

	/* Ramdisk goes at the end of the last memory block */
	rd_size = bi.ramdisk_size << 10;
	bi.ramdisk_addr = bi.memory[bi.num_memory-1].addr +
	                  bi.memory[bi.num_memory-1].size - rd_size ;

	printf( "ramdisk size = %lx\n", (long)rd_size );
	printf( "ramdisk addr = %lx\n", (long)bi.ramdisk_addr );

	/* find offset to boot_info structure */
    offs = get_nlist (kernel_name, "_boot_info");
	if (offs==0) {
		perror ("get_nlist");
		waitkey();
		exit (EXIT_FAILURE);
	} else {
		kbi_offset = offs - kexec.a_entry;
	}

	memreq = kexec.a_text + kexec.a_data + rd_size;
    memptr = (char *)Malloc (memreq);
	if (!memptr) {
		fprintf (stderr, "Unable to allocate memory\n");
		waitkey();
		exit (EXIT_FAILURE);
	}
	printf( "Temporary load area is 0x%lx...0x%lx\n", (long)memptr, (long)(memptr+memreq-1) );

	{	long	load_st = (long)memptr,
				load_end = (long)memptr + memreq,
				dest_st = KSTART_MEM ,
	            dest_end = KSTART_MEM + kexec.a_text + kexec.a_data +
				                        kexec.a_bss + 8;
	
		if ((load_end >= dest_st && load_end < dest_end) ||
			(dest_end >= load_st && dest_end < load_end)) {
			fprintf( stderr, "\nOVERLAPPING OF LOAD AND BOOT AREA!!!\n\n" );
			waitkey();
			exit( -1 );
		}
		if (bi.ramdisk_addr < load_end) {
			fprintf( stderr, "\nOVERLAPPING OF RAMDISK AND LOAD AREA!!!\n\n" );
			waitkey();
			exit( -1 );
		}
	}
	
	if (lseek (kfd, N_TXTOFF(kexec), SEEK_SET) == -1) {
		fprintf (stderr, "Failed to seek to text\n");
		Mfree ((void *)memptr);
		waitkey();
		exit (EXIT_FAILURE);
	}
	if (read (kfd, memptr + rd_size, kexec.a_text) != kexec.a_text) {
		fprintf (stderr, "Failed to read text\n");
		Mfree ((void *)memptr);
		waitkey();
		exit (EXIT_FAILURE);
	}
	if (lseek (kfd, N_DATOFF(kexec), SEEK_SET) == -1) {
		fprintf (stderr, "Failed to seek to data\n");
		Mfree ((void *)memptr);
		waitkey();
		exit (EXIT_FAILURE);
	}
	if (read (kfd, memptr + kexec.a_text + rd_size, kexec.a_data) != kexec.a_data) {
		fprintf (stderr, "Failed to read data\n");
		Mfree ((void *)memptr);
		waitkey();
		exit (EXIT_FAILURE);
	}
	close (kfd);

	if (rfd != -1) {
		if (lseek (rfd, 0, SEEK_SET) == -1) {
			fprintf (stderr, "Failed to seek to beginning of ramdisk file\n");
			Mfree ((void *)memptr);
			waitkey();
			exit (EXIT_FAILURE);
		}
		if (read (rfd, memptr, rd_size) != rd_size) {
			fprintf (stderr, "Failed to read ramdisk file\n");
			Mfree ((void *)memptr);
			waitkey();
			exit (EXIT_FAILURE);
		}
		close (rfd);
	}

	/* allocate temporary stack */
	stack = (void *)Malloc((long)TEMP_STACKSIZE);
	if (!stack) {
		fprintf (stderr, "Unable to allocate memory for stack\n");
		Mfree ((void *)memptr);
		waitkey();
		exit (EXIT_FAILURE);
	}
	printf( "Temporary stack is 0x%lx..0x%lx\n", (long)stack, (long)(stack + TEMP_STACKSIZE-1) );

	/* Allocate memory for copy routine. This must lie within the 
	 * destination address ranges (kernel mem and ramdisk mem).
	 */
	copy_len = copy_kernel_end - copy_kernel;
	do {
		if (!(copy_addr = (void *)Malloc( copy_len ))) {
			fprintf( stderr, "Unable to allocate memory for copy routine.\n" );
			waitkey();
			exit (EXIT_FAILURE);
		}
	} while( (long)copy_addr < KSTART_MEM+kexec.a_text+kexec.a_data+kexec.a_bss+8 ||
		     (long)copy_addr + copy_len > bi.ramdisk_addr );
	
	printf( "Copy routine will be located at %08lx.\n", (unsigned long)copy_addr );
	memcpy( copy_addr, copy_kernel, copy_len );

	if (debugflag) {
		if (bi.ramdisk_size)
			printf ("RAM disk at %#lx, size is %ldK\n",	bi.ramdisk_addr,
			        bi.ramdisk_size);

		printf ("\nKernel text at %#lx, code size %ld\n",
			KSTART_MEM + N_TXTADDR(kexec), (long)kexec.a_text);
		printf ("Kernel data at %#lx, data size %ld\n",
			KSTART_MEM + N_DATADDR(kexec), (long)kexec.a_data );
		printf ("Kernel bss  at %#lx, bss  size %ld\n",
			KSTART_MEM + N_BSSADDR(kexec), (long)kexec.a_bss );

		printf ("\nKernel boot_info is at offset %#lx, physical %#lx, virtual %#lx\n",
			kbi_offset, KSTART_MEM + kbi_offset,	kbi_offset + kexec.a_entry );

		printf ("\nKernel entry is %#lx\n", (unsigned long)kexec.a_entry );

		printf ("ramdisk dest top is %#lx\n", KSTART_MEM);
		printf ("ramdisk lower limit is %#lx\n",
			(unsigned long)(memptr));
		printf ("ramdisk src top is %#lx\n",
			(unsigned long)(memptr + rd_size));

		printf( "Load area is 0x%lx .. 0x%lx\n", (long)(KSTART_MEM - rd_size),
		        (long)(KSTART_MEM + kexec.a_text + kexec.a_data + kexec.a_bss) );

		printf( "\nFound %d memory blocks:\n", bi.num_memory );
		for( i = 0; i < bi.num_memory; ++i )
			printf( "  0x%08lx .. 0x%08lx (0x%lx bytes)\n", 
			        bi.memory[i].addr,
					bi.memory[i].addr + bi.memory[i].size - 1,
					bi.memory[i].size );

		printf ("\nType a key to continue the Linux boot...");
		fflush (stdout);
		getchar();
	}

	/* copy boot info into the kernel */
	memcpy( memptr + rd_size + kbi_offset, &bi, sizeof(bi) );

	/* wait for things to settle down */
	sleep(2);
 
	/* Go into supervisor state and change stack*/
	Super(0L);
	change_stack(stack + TEMP_STACKSIZE - sizeof(long)); 
	/* turn off caches */
	cache_off(); 

	/* turn off any mmu translation */
	disable_mmu ();  

	/* Turn off Interrupts */
	raise_int();  

	__asm__ __volatile__ (
		"movel	%0,a6\n\t"
		"movel	%1,a5\n\t"
		"movel	%2,a4\n\t"
		"movel	%3,d7\n\t"
		"movel	%4,d6\n\t"
		"movel	%5,d5\n\t"
		"movel	%6,a0\n\t"
		"jmp	a0@"
		: /* no outputs */
		: "g" (memptr), "g" (KSTART_MEM+8), "g" (bi.ramdisk_addr),
		  "g" (kexec.a_text+kexec.a_data), "g" (kexec.a_bss),
		  "g" (rd_size), "g" (copy_addr)
	);

#if 0
	{
		/*
		 * there may be problems here if the compiler optimizer
		 * doesn't put these variables in registers, and the
		 * stack space was allocated at the beginning of the function
		 * and we've changed the stack.
		 */
		unsigned char *csrc, *cdest, *climit;
		unsigned long *src, *dest, *limit;

		/*
		 * copy the kernel text and data to their final resting place.
		 * The text is padded out (in the a.out file) to a multiple of
		 * the page size.
		 */
       
		src = (unsigned long *)(memptr + rd_size);
		dest = (unsigned long*)(KSTART_MEM + 8L);  /* first 8 bytes are ROM!! */
		limit = (unsigned long *)(memptr + rd_size + kexec.a_text +
								  kexec.a_data + 8L);
		while (src < limit)
			*dest++ = *src++;

		/* clear kernel bss */
		dest = (unsigned long *)(KSTART_MEM + kexec.a_text + kexec.a_data + 8L);
		limit = dest + kexec.a_bss / sizeof(unsigned long);
		while (dest < limit)
			*dest++ = 0;

		/* copy the boot_info struct to the correct location */
		cdest = (unsigned char *)KSTART_MEM + 8L + kbi_offset;
		climit = cdest + sizeof (bi);
		csrc = (unsigned char *)&bi;
		while (cdest < climit)
			*cdest++ = *csrc++;

		/* copy the ramdisk at end of mem */
		dest = (unsigned long *)((u_long)bi.ramdisk_addr+(u_long)rd_size);
		limit = (unsigned long *)((u_long)memptr);
		src = (unsigned long *)((u_long)memptr + (u_long)rd_size);
		while (src > limit)
			*--dest = *--src; 

	}
	
	/* jump to start of kernel */
	jump_to (KSTART_MEM + 8L); 
#endif

	/* NOTREACHED */
    return(0);

}



#define	MAXARGS		30

static void get_default_args( int *argc, char ***argv )

{	FILE		*f;
	static char	*nargv[MAXARGS];
	char		arg[256], *p;
	int			c, quote, state;

	if (!(f = fopen( "bootargs", "r" )))
		return;
	
	*argc = 0;
	*argv = nargv;
	nargv[(*argc)++] = "bootstra.ttp";

	quote = state = 0;
	while( (c = fgetc(f)) != EOF ) {		

		if (state == 0) {
			/* outside args, skip whitespace */
			if (!isspace(c)) {
				state = 1;
				p = arg;
			}
		}
		
		if (state == 1) {
			/* inside an arg: copy it into 'arg', obeying quoting */
			if (!quote && (c == '\'' || c == '"'))
				quote = c;
			else if (quote && c == quote)
				quote = 0;
			else if (!quote && isspace(c)) {
				/* end of this arg */
				*p = 0;
				nargv[(*argc)++] = strdup(arg);
				state = 0;
			}
			else
				*p++ = c;
		}
	}
	if (state == 1) {
		/* last arg finished by EOF! */
		*p = 0;
		nargv[(*argc)++] = strdup(arg);
	}
	fclose( f );
	
	nargv[*argc] = 0;
}    

long get_nlist(const char *fname, const char *symname)
{
    int fd = open (fname, O_RDONLY);
    struct exec ex;
    struct nlist *nl, *syms;
    char *strs;
    long back;
    size_t filesize;
    long strsize, numsyms;

    if (fd == -1)
	return 0;

    read (fd, &ex, sizeof(ex));
    if (!ex.a_syms) {
	close (fd);
	return 0;
    }

#ifdef DEBUG
    printf ("%ld bytes of symbol data\n", ex.a_syms);
#endif

    syms = (struct nlist *)malloc (ex.a_syms);
    if (!syms) {
	close (fd);
	return 0;
    }
    filesize=lseek (fd, N_SYMOFF(ex), SEEK_SET);
    read (fd, syms, ex.a_syms);
    numsyms = ex.a_syms / sizeof (struct nlist);
    filesize = lseek (fd, 0L, SEEK_END);
    strsize = filesize - N_STROFF(ex);
    strs = (char *)malloc (strsize);
    if (!strs) {
	free (syms);
	close (fd);
	return 0;
    }
    lseek (fd, N_STROFF(ex), SEEK_SET);
    read (fd, strs, strsize);
    back=0;


    for (nl = syms; nl < syms + numsyms; nl++) {
#ifdef DEBUG
	printf ("checking symbol number %d, name %s\n",
		nl - syms, strs + nl->n_un.n_strx);
#endif
	if (strcmp (symname, strs + nl->n_un.n_strx) == 0
	    && nl->n_type == N_BSS | N_EXT) {
		back = nl->n_value;
		break;
	}
    }
    free (strs);
    free (syms);
    close (fd);
    return back;
}


static void waitkey( void )

{
	fprintf( stderr, "\nPress any key.\n" );
	while( !Bconstat(2) )
		;
	(void)Bconin(2);
}



