/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 * NB: This file is used for both 16 and 32 bit code.
 * USE NO "int"s IN HERE!!!!
 *!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 *
 *
 * Crt0: C run-time initialization code.
 * Written by Eric R. Smith, and placed in the public domain.
 * Use at your own risk.
 *
 * _start(base): sets things up for the process whose basepage is
 *    base. In particular, it sets up a valid environment, shrinks the
 *    TPA to a reasonable value, and parses the command line.
 *
 * 01/03/89 ++jrb
 *	The (new) meaning of _stksize: (thanks to allan pratt for the feedback)
 *
 * 11/27/91 ++jrb
 *     More meanings for _stksize (thanks to eric and allan for the feedback)
 *
 *	_stksize			meaning
 *	  -4L	    keep 3/4, free 1/4, malloc from own heap
 *	  -3L	    keep 2/4 (1/2), free 1/2 malloc from own heap
 *	  -2L	    keep 1/4 of memory, free 3/4, malloc from own heap
 *
 *	NOTE: all of the following will do malloc from Malloc() first,
 *	when that fails, malloc() will do further mallocs from
 *	our own heap. This lets us use the maximum amount of
 *      memory in traditional ST's as well as newer split address
 *	STs.
 *
 *	  -1L	    keep all of memory (except MINFREE at top) and do
 *		    mallocs from own heap, with heap grown upwards towards
 *		    stack, and the stack growing down towards heap,
 *		    with a minimum slush between them so that they
 *		    dont meet (only checked while malloc'ing). With
 *		    this model, further spawning is not possible, but it is
 *		    well suited for programs such as gcc-cc1 etc.
 *		    Thanks to Piet van Oostrum & Atze Dijkstra for this idea
 *
 *	0L	    keep minimum amount of memory. this is also the
 *		    case when _stksize is undefined by the user.
 *	1L	    keep 1/4 of memory, free 3/4 ( as in Alcyon GEMSTART)
 *	2L	    keep 2/4 (1/2), free rest
 *	3L	    keep 3/4, free 1/4
 *	other	    keep that many bytes
 *	-other	    keep |other| bytes and malloc from own heap
 *
 * 02/14/90 ++jrb (thanks edgar)
 *	auto acc detect
 *	undump friendly
 *   Note: some of the stuff here may seem extraneous: these are in
 *	 prep for upcoming plug'n play device interface
 *	 (the moment the ol'boss lets me do some "real" work).
 *
 *
 * NOTE: dumping applications should use _initial_stack instead: if
 *	 !=0, then _stksize is initialized from _initial_stack, and
 *	 mallocs are always from internal heap. (TeX works much better now),
 *	 thanks edgar!
 *
 * Acc convention:
 *	user sets _heapbase to bottom of stack + heap area
 *	     sets _stksize to the size of this area
 *	     at startup, sp will be set to top of this area
 *	     (_heapbase  + _stksize ) and malloc()'s will happen from heap.
 *		(note malloc() and *not* Malloc())
 *     OR
 *	user sets only _stksize. then _heapbase is set to Malloc(_stksize)
 *	sp to _heapbase + _stksize and mallocs all happen from this heap.
 *
 * 02/16/90 ++jrb
 *  - bug fix: dont get screwed by desktop launch when fast bit is set
 *             convert env string to format usable
 *		(atari get your act together!!)
 */

#include <basepage.h>
#include <osbind.h>
#ifndef _COMPILER_H
#include <compiler.h>
#endif
#include <stddef.h>

#define isspace(c) ((c) == ' '||(c) == '\t')
#define BUFSIZ	((unsigned long)1024)	/* this must track the value */
					/* in stdio.h                */
#define MINFREE	(8L * 1024L)		/* free atleast this much mem */
					/* on top */
#define MINKEEP (8L * 1024L)		/* keep atleast this much mem */

/*
 * this little goodie is for emacs
 * edgar: note: it has grown two leading '_',
 *              adjust in emacs/src/sysdep.c:start_of_data()
 *
 * __data_start removed, as that can be determined from the basepage.
 */

BASEPAGE *_base;
char **environ;
static long argc;
static char **argv;

/*
 * initial stack is used primarily by dumping application,
 * if it is, malloc is always from heap, and _stksize is init
 * from initial_stack (to preserve the value in the undumped run)
 */
long _initial_stack;			/* .comm __initial_size, 4 */
extern long _stksize;			/* picked up from user or stksiz.c */
/* set to heap base addr when _stksize == -1L || _initial_stack || When DA */
void *_heapbase;

/* default sizeof stdio buffers */
size_t __DEFAULT_BUFSIZ__;	/* .comm             */

/* are we an app? */
short _app;

/* are we on a split addr mem ST */
short _split_mem = 0;

/* externs to pull in ident strings of all used libraries into the
   executable. if a library is not used, then the extern is satisfied
   by a dummy in the library
 */
asm("
	.globl ___Ident_libg
	.globl ___Ident_curses
	.globl ___Ident_widget
	.globl ___Ident_gem
	.globl ___Ident_pml
	.globl ___Ident_gnulib
     ");

static void _acc_main __PROTO((void));
static void _start1 __PROTO((BASEPAGE *bp));
static long parseargs __PROTO((BASEPAGE *bp));
static void setup_handlers __PROTO((void));
static void _start0 __PROTO((BASEPAGE *));
static void setup_handlers __PROTO((void));
__EXTERN void _main __PROTO((long, char **, char **));
__EXTERN void _init_signal __PROTO((void));
__EXTERN void _start __PROTO((BASEPAGE *));
#ifdef __GCRT0__
__EXTERN void monstartup __PROTO((void *lowpc, void *highpc));
__EXTERN void monitor __PROTO((void *lowpc, void *highpc, void *buffer, unsigned long bufsize, unsigned int nfunc));
__EXTERN void moncontrol __PROTO((long flag));
__EXTERN void _mcleanup __PROTO((void));
__EXTERN int profil __PROTO((void *buff, unsigned long bufsiz, unsigned long offset, int shift));
#endif

/*
 * From: kbad@atari.UUCP (Ken Badertscher)
 * Newsgroups: comp.sys.atari.st
 * Subject: Am I a DA? (long)
 *   .....
 * I mentioned before that a DA's registers are garbage on startup, well,
 * that's not entirely true.  When the DA gets control, register A0 always
 * points to its basepage.  When a program is started by a GEMDOS Pexec(),
 * register A0 is always cleared.  Using this fact, it is possible to
 * implement startup code which gets the basepage address from register A0
 * if the code is launched as a DA, or at 4(sp) if the code is launched by
 * Pexec().  Since it knows how it was launched, it can also do the stack
 * setup required of a DA, otherwise it can use the stack pointer it gets.
 *
 */
/*
 * revert back to testing A0 (instead of SP) after controversy on the net.
 * packers will just have to fix themselves.
 * in addition to testing A0 check long A0@(36) (parents basepage). for an
 * acc this should be NULL.
 */
__asm__("
 	.text				
 	.even				
 	.globl __start			
__start:
"
#ifdef __MBASE__
"	movl	a0,a1			/* Find basepage, data seg */
	cmpw	#0,a1
	jne	1f
	movl	sp@(4),a1
1:
	movl	a1@(16)," __MBASESTR__ "	/* Set base to data seg + 32K */
	subw	#32768," __MBASESTR__ "

"
#define	Base	__MBASESTR__ "@(__base)"
#define Heapbase __MBASESTR__ "@(__heapbase)"
#define Stksize	__MBASESTR__ "@(__stksize)"
#else
#define Base	"__base"
#define Heapbase "__heapbase"
#define Stksize "__stksize"
#endif
"	cmpw	#0,a0			 /* test acc or prog  */
 	jeq	__start0		 /* br if prog	      */
	tstl    a0@(36)			 /* tst parent basepage pointer */
	jne     __start0		 /* its a prog if != 0  */

 /* its an acc, set up a stck+heap */
	movl	a0," Base "		 /* sto basepage	*/
	tstl	" Heapbase "		 /* setup _heapbase and sp */
	jne	1f
	movl	" Stksize ",d3		 /* _heapbase not specified */
	addql	#3, d3
	andl	#0xfffffffc,d3
	movl	d3,sp@-			 /* _heapbase = Malloc(_stksize) */
        movw    #0x48,sp@-
	trap	#1
	addqw	#6,sp
	movl	d0," Heapbase "
	addl	d3,d0
	movl	d0, sp		 	 /* sp = _heapbase + _stksize */
	jra	__acc_main
1:					 /* heap base specified */
 	movl	" Heapbase ",sp		 /* setup sp		*/
 	addl	" Stksize ",sp		
 	jra	__acc_main");		   /* acc main          */
					   /* dont even think of */
					   /* dropping through	*/

static char	*acc_argv[] = {"", (char *) 0}; /* no name and no arguments */

static void _acc_main(void)
{
	_app = 0;				/* this is an accessory */
	_main(1L, acc_argv, acc_argv);
	/*NOTREACHED*/
}

void _start0(bp) /* bp is passed at sp@(4) */
	register BASEPAGE *bp;
{
	/* temporarily set sp at hitpa (rounded),then call the real
	   startup function. not doing this was blowing up the
	   stack provided by gemdos on a 1040, because _start1()
	   was pushing too many reggies on the stack
	   _start1() will finally set sp to its final value and
	   then do a mshrink().
	*/
	asm volatile("movl %0,sp" ::"r"((long)bp->p_hitpa & ~3L));
	_start1(bp);
}

static void _start1(bp)
	register BASEPAGE *bp __asm("a3");
{
	register long m __asm("d3");
	register long freemem __asm("d4");
	extern void etext();		/* fake-out if pcrel used */
	
	_app = 1;	/* its an application */
	_base = bp;
	if(!__DEFAULT_BUFSIZ__)
	    __DEFAULT_BUFSIZ__ = BUFSIZ;

	m = parseargs(bp);	/* m = # bytes used by environment + args */
/* make m the total number of bytes required by program sans stack/heap */
	m += (bp->p_tlen + bp->p_dlen + bp->p_blen + sizeof(BASEPAGE));
	m = (m + 3L) & (~3L);
/* freemem the amount of free mem accounting for MINFREE at top */
	if((freemem = (long)bp->p_hitpa - (long)bp - MINFREE - m) <= 0L)
	    goto notenough;
	
	if(_initial_stack)
	{
	    /* the primary use of _initial_stack will be in dumping */
	    /* applications where only a heap for malloc makes sense */
	    _heapbase = (void *) ((long)bp + m);
	    _stksize = _initial_stack;
	}
	
	if((_stksize <  -1L))
	{
	    _heapbase = (void *) ((long)bp + m);
	    _stksize = -_stksize - 1;
        }
        
	if((!_initial_stack) && (_stksize >= -1L))
        {   /* malloc from Malloc first, then from own heap */
	    _split_mem = 1;
        }

	switch(_stksize)
	{
	  case -1L:	/* keep all but MINFREE */
	    _stksize = freemem;
	    _heapbase = (void *) ((long)bp + m);
	    break;
	    
	  case 0L:	/* free all but MINKEEP */
	    _stksize = MINKEEP;
	    break;

	  case 1L:	/* keep 1/4, free 3/4 */
	    _stksize = freemem >> 2;
	    break;

	  case 2L:	/* keep 1/2, free 1/2 */
	    _stksize = freemem >> 1;
	    break;

	  case 3L:	/* keep 3/4, free 1/4 */
	    _stksize = freemem - (freemem >> 2); 
	    break;

	  default:
	    /* if _stksize > 0, keep that much */
	    break;
	}
	
/* make m the total number of bytes including stack */
	_stksize = _stksize & (~3L);
	m += _stksize;

/* make sure there's enough room for the stack */
	if (((long)bp + m) > ((long)bp->p_hitpa - MINFREE))
	    goto notenough;

/* set up the new stack to bp + m  */
	asm volatile("\
 		movl	%0, sp  | move base to sp
 		addal	%1, sp	| add total bytes"
		     :			/* outputs */
		     : "g"(bp), "g"(m)	/* inputs  */
		     );	 /* we dont tell gcc about clobbered reggies */
	
/* shrink the TPA - this is always correct, shared text or not -- hyc */
	(void)Mshrink(bp, m);
	asm volatile("subl a6,a6");	/* clear link reg for gdb */
	
/* establish handlers,  call the main routine */
	setup_handlers();
#ifdef __GCRT0__
	monstartup((void *)(bp->p_tbase), (void *)etext-1);
#endif
	_main(argc, argv, environ);
	/* not reached normally */

notenough:
	Cconws("Fatal error: insufficient memory\r\n");
		Pterm(-1);
}


/*
 * parseargs(bp): parse the environment and arguments pointed to by the
 * basepage. Return the number of bytes of environment and arguments
 * that have been appended to the bss area (the environ and argv arrays
 * are put here, as is a temporary buffer for the command line, if
 * necessary).
 *
 * The MWC extended argument passing scheme is assumed.
 *
 */

static long parseargs(bp)
	BASEPAGE *bp;
{
    	long count = 4;		/* compensate for aligning */
	long  i;
	char *from, *cmdln, *to;
	char **envp, **arg;
	
/* handle the environment first */

	environ = envp = (char **)(( (long)bp->p_bbase + bp->p_blen + 4) & (~3));
	from = bp->p_env;
	while (*from) {

/* if we find MWC arguments, tie off environment here */
		if (*from == 'A' && *(from+1) == 'R' && *(from+2) == 'G' &&
		    *(from+3) == 'V' && *(from+4) == '=') {
			*envp++ = (char *) 0; count += 4;
			*from++ = 0;
#ifdef STRICTLY_COMPATIBLE_WITH_STANDARD
			if (bp->p_cmdlin[0] != 127)
				goto old_cmdlin;
#endif
			while (*from++) ; /* skip ARGV= string */
			argv = arg = envp++;
			*arg++ = from; count+= 4;
			while (*from++) ; /* skip argv[0] */
			goto do_argc;
		}
		*envp++ = from;
		count += 4;
		while (*from++);

		/* if launched from desktop -- fix up env
                 * be careful while doing this, as the environment
		 * may have a variable whose value is the empty
		 * string. (make puts a MAKFLAGS=\0). The desktop
		 * typically has "PATH=\0C:\\0\0", so to distinguish
		 * the two cases, check for uppercase drive letter
		 * followed by ":\\"
		 */
#define ISDRV(x) (('A' <= (x)) && ((x) <= 'Z'))
 		if ( (from[ -2 ] == '=') && (ISDRV(*from)) && 
		     (from[1] == ':') && (from[2] == '\\') )
		{
		    char *p = &from[-1]; /* typically  "PATH=\0C:\\0\0" */
		    while(*from)
			*p++ = *from++;
		    *p = '\0'; from++;
		}
	}
	*envp++ = (char *)0;
	count += 4;

/* Allocate some room for the command line to be parsed */
	cmdln = bp->p_cmdlin;
	i = *cmdln++;
	from = to = (char *) envp;
	if (i > 0) {
		count += (i&(~3));
		envp = (char **) ( ((long) envp)  + (i&(~3)) );
	}
	envp += 2; count += 8;

/* Now parse the command line and put argv after the environment */

	argv = arg = envp;
	*arg++ = "";		/* argv[0] not available */
	count += 4;
	while(i > 0 && isspace(*cmdln) )
		cmdln++,--i;

	while (i > 0) {
		if (isspace(*cmdln)) {
			--i; cmdln++;
			while (i > 0 && isspace(*cmdln))
				--i,cmdln++;
			*to++ = 0;
		}
		else {
			if (!(*to++ = *cmdln++)) break;
			--i;
		}
	}
	*to++ = '\0';
	*to = '\0'; /* bug fix example:cmdln == '\3' 'a' ' ' 'b' '\0' */
	/* the loop below expects \0\0 at end to terminate! */
	/* the byte @ cmdln[i+2] != 0 when fast bit is set */
do_argc:
	argc = 1;		/* at this point argv[0] is done */
	while (*from) {
		*arg++ = from;
		argc++;
		count += 4;
		while(*from++) ;
	}
	*arg++ = (char *) 0;
	return count+4;
}

static void setup_handlers(void)
{
    /* more stuff to come */
    _init_signal();
}

void __exit(status)
	long status;
{
#ifdef __GCRT0__
    moncontrol(0L);
    _mcleanup();
#endif
    Pterm(status);

}
