##
## startup.s
##
## VBCC startup code for PowerPC programs using the PowerUp kernel.
## (c)1997-98 by Frank Wille
##
## This startup code is intended to work with vbcc's PPCRun,
## SAS/C's RunELF, P5's PPCLoad and P5's ELF-LoadSeg-Patch.
## If it doesn't get stdin/stdout/stderr from PPCRun or from the
## LoadSeg-patch, they will be initialized here. The CLI command line
## or WB startup message is parsed and finally _main(int argc,char **argv)
## will be called.
##
## argc=0 indicates a WBStartup and the WB-message pointer can
## be found in argv, *if* argv is not zero!!
## If argv is zero too, then we have no arguments at all! Maybe
## we are called by PPCLoad, or something like that...
##
## WB-startup relies on the calling M68k process for receiving and
## replying the startup message.
##
##
## Global Functions:
## _exit:	Private! Will be called by vbcc's legal exit() function.
## exit:	MINSTART-only exit()-function.
##
## Global Data:
## SysBase:	The usual pointer to the ExecBase structure.
## _stdin, _stdout, _stderr : Private! Will be used by vbcc's _main()
##		startup routine to initialize ANSI-stdio.
##		Undefined for MINSTART.
## WBenchMsg:	Pointer to WB-startup message, when started from WB.
##		Undefined, if WBSTART was not set.
##
##
## V1.0 15-Feb-98 phx
##	Supports WBStartup and Ralph Schmidt's ELFLoadSegPatch.
##	On WB-startup, the global variable WBenchMsg contains a pointer
##	to the WB-startup message (also via argv). stdin,stdout and
##	stderr will contain a handle of the WB-ToolWindow or, if this
##	fails, from "NIL:".
##	Floating point registers are no longer saved.
##	Minimal startup code can be generated by defining MINSTART.
##	Some minor optimizations and fixes.
## V0.9 23-Dec-97 phx
##	No longer expect argc/argv from ppcrun/elfrun/etc.. A command
##	line pointer is received in r3 and must be parsed first. If no
##	problems occur, _main() will receive argc/argv as usual. Other-
##	wise it will get argc=argv=0.
##	Still no WB-startup support...
##	Fixed a bug with stderr (Enforcer-hit/IsInteractive()) in
##	"Non-StartupMessage mode".
##	Successfully tested with ppc.library V45.2.
## V0.8 21-Nov-97 phx
##	CallOS()-fix for ppc.library V44.29 with old libamiga.a.
## V0.7 02-Nov-97 phx
##	Shorter startup code by using stmw/lmw.
## V0.6 18-Oct-97 phx
##	The ppc.library executes ELF objects at .text+4, so the first
##	instruction was always skipped... :(
##	SysBase is exported. DOSBase must be initialized by _main().
## V0.5 15-Oct-97 phx
##	Also works with R.Schmidt's ppcload.
##	Supports blr in _main(), although libvc.a jumps to _exit directly.
##      No longer use r13 for debugging output. It's reserved for
##	Small Data pointer in V.4. :P
## V0.4 13-Oct-97 phx
##	No longer touches r2. r2 is reserved in System V.4 ABI!
## V0.3 07-Oct-97 phx
##	Rewritten in pure PowerPC assembler (pasm).
## V0.2 04-Oct-97 phx
##      Use "CONSOLE:" instead "*" (PowerPC will definitely not run
##      under Kickstart 1.x :)
##      argc and argv are passed via startup message. argc and argv are
##      also passed in GPR3 and GPR4 but I will better read them from the
##      startup message...
##      When the program terminates, the result code is returned in
##      the startup message, which is replied then.
## V0.1 03-Oct-97 phx
##      created


# Version
.set	VERSION,1
.set	REVISION,00

# Startup Code Configuration
#.set	DEBUG,1		# enables serial debugging
#.set	WAITBUTTON,1	# wait for left mouse button, after debugging
#			#  output (must be used together with DEBUG)
#.set	CALLOSFIX,1	# ppc.library V44.29 has PPCCallOS(), but
#			#  old libamiga.a stil uses CallOS().
#.set	WBSTART,1	# enables WB-startup support code
#.set	MINSTART,1	# create a minimal startup-code, without
#			#  argument parsing. main() is called directly.


# powerup includes
.set	PPCTASKTAG_STARTUP_MSG,0x80020015
.set	PPCTASKTAG_STARTUP_MSGDATA,0x80020016


# workbench includes

# struct WBStartup
.set	sm_Message,0
.set	sm_Process,20
.set	sm_Segment,24
.set	sm_NumArgs,28
.set	sm_ToolWindow,32
.set	sm_ArgList,36
.set	sm_SIZEOF,40

# struct WBArg
.set	wa_Lock,0
.set	wa_Name,4
.set	wa_SIZEOF,8


# dos includes
.set	MODE_OLDFILE,1005
.set	MODE_NEWFILE,1006


# struct StartupData
.set	sd_M68kPort,0	# the PowerPC task can send messages to this port
.set	sd_std_in,4	# standard input handle
.set	sd_std_out,8	# standard output handle
.set	sd_std_err,12	# standard error handle
.set	sd_retCode,16	# return code of PPC program
.set	sd_flags,20	# additional flags (currently unused)


.set	IOERROR,50	# can't open stdin/stdout/stderr


# serial debugging
.ifdef	DEBUG
	.extern	PPCRawDoFmt

.macro	_debug		# _debug <format string>[,<gpr>, ...]
	.data		# ... works with all GPRs, except r12
.db\@:	.string	\1
	.text
	stwu	r12,-4(r1)
	lis	r12,.regsave@ha
	addi	r12,r12,.regsave@l
	stw	r0,0(r12)
	stw	r3,4(r12)
	stw	r4,8(r12)
	stw	r5,12(r12)
	stw	r6,16(r12)
	stw	r7,20(r12)
	stw	r8,24(r12)
	stw	r9,28(r12)
	stw	r10,32(r12)
	stw	r11,36(r12)
	lwz	r3,0(r1)
	addi	r1,r1,4
	stw	r3,40(r12)
	lwz	r3,4(r12)
	.ifge	$NARG-2
	lis	r12,.datastream@ha
	addi	r12,r12,.datastream@l
	stw	\2,0(r12)
	.endif
	.ifge	$NARG-3
	stw	\3,4(r12)
	.endif
	.ifge	$NARG-4
	stw	\4,8(r12)
	.endif
	.ifge	$NARG-5
	stw	\5,12(r12)
	.endif
	.iflt	$NARG-2
	li	r4,0
	.else
	mr	r4,r12
	.endif
	lis	r3,(.db\@)@ha
	addi	r3,r3,(.db\@)@l
	li	r5,1
	li	r6,0
	bl	PPCRawDoFmt		# output to serial line
.ifdef	WAITBUTTON
	lis	r12,0xbfe001@h		# wait for left mouse button
	ori	r12,r12,0xbfe001@l
.dbwait1\@:
	lbz	r3,0(r12)
	andi.	r3,r3,0x40
	bne	.dbwait1\@
.dbwait2\@:
	lbz	r3,0(r12)
	andi.	r3,r3,0x40
	beq	.dbwait2\@
.endif
	lis	r12,.regsave@ha
	addi	r12,r12,.regsave@l
	lwz	r0,0(r12)
	lwz	r3,4(r12)
	lwz	r4,8(r12)
	lwz	r5,12(r12)
	lwz	r6,16(r12)
	lwz	r7,20(r12)
	lwz	r8,24(r12)
	lwz	r9,28(r12)
	lwz	r10,32(r12)
	lwz	r11,36(r12)
	lwz	r12,40(r12)
.endm

.else
.macro	_debug
.endm
.endif


	.text

	.extern	PPCGetTaskAttr
	.extern	PPCOpen
	.extern	PPCClose
	.extern	PPCAllocVec
	.extern	PPCFreeVec
.ifdef	MINSTART
	.extern	main
.else
	.extern	_main
.endif


# This is the first function in a program. Execution starts here!

__start:
# CLI-start:
#  r3 = char *CommandLine
#  r4 = NULL
# WB-start:
#  r3 = NULL
#  r4 = struct WBStartup *WBMsg
	.word	0x56424343		# ppc.library calls __start+4 !
	mflr	r0
	stw	r0,4(r1)
	stwu	r1,-80(r1)
	stmw	r14,8(r1)		# save all non-volatile GP-registers
	_debug	"\n-------\nstartup\n-------\nstack = 0x%08lx\n",r1
	lis	r11,_init_stk@ha	# save initial stack pointer
	stw	r1,_init_stk@l(r11)

# initialize SysBase
	li	r11,4
	lwz	r0,0(r11)
	lis	r11,SysBase@ha
	stw	r0,SysBase@l(r11)
.ifndef	MINSTART
	mr	r30,r3			# r30 CommandLine
.ifdef	WBSTART
	mr	r31,r4			# r31 WBMsg
.endif

# evaluate startup message from M68k
	lis	r3,PPCTASKTAG_STARTUP_MSG@h
	ori	r3,r3,PPCTASKTAG_STARTUP_MSG@l
	bl	PPCGetTaskAttr
	_debug	"PPCTASKTAG_STARTUP_MSG = 0x%08lx\n",r3
	cmpwi	r3,0
	beq	.1
	lis	r3,PPCTASKTAG_STARTUP_MSGDATA@h
	ori	r3,r3,PPCTASKTAG_STARTUP_MSGDATA@l
	bl	PPCGetTaskAttr
	_debug	"PPCTASKTAG_STARTUP_MSGDATA = 0x%08lx\n",r3
	lwz	r4,sd_std_in(r3)	# get _stdin
	lis	r11,_stdin@ha
	stw	r4,_stdin@l(r11)
	lwz	r5,sd_std_out(r3)	# get _stdout
	lis	r11,_stdout@ha
	stw	r5,_stdout@l(r11)
	lwz	r6,sd_std_err(r3)	# get _stderr
	lis	r11,_stderr@ha
	stw	r6,_stderr@l(r11)
	lis	r11,_startup_data@ha
	stw	r3,_startup_data@l(r11)
	_debug	"stdin=0x%08lx stdout=0x%08lx stderr=0x%08lx\n",r4,r5,r6
	b	.parse_args

# get _stdin, _stdout, _stderr
.1:	lis	r16,.constring@ha	# r16 = "CONSOLE:"
	addi	r16,r16,.constring@l
	li	r4,MODE_OLDFILE
	mr	r3,r16
	bl	PPCOpen			# PPCOpen("CONSOLE:",MODE_OLDFILE)
	_debug	"PPCOpen(\"%s\",MODE_OLDFILE) = 0x%08lx\n",r16,r3
	cmpwi	r3,0
	beq	__error
	lis	r11,_stdin@ha
	stw	r3,_stdin@l(r11)

	li	r4,MODE_NEWFILE
	mr	r3,r16
	bl	PPCOpen			# PPCOpen("CONSOLE:",MODE_NEWFILE)
	_debug	"PPCOpen(\"%s\",MODE_NEWFILE) = 0x%08lx\n",r16,r3
	cmpwi	r3,0
	beq	__error
	lis	r11,_stdout@ha
	stw	r3,_stdout@l(r11)
	lis	r12,_stderr@ha
	stw	r3,_stderr@l(r12)	# _stderr = _stdout

# parse cli or wb arguments
.parse_args:
	li	r14,0			# r14: argc = 0
	mr.	r15,r30			# r15: command line/argv
	beq	.wbmsg
	bl	parse_cli_args		# parse command line
	b	.startup
.wbmsg:
.ifdef	WBSTART
	mr.	r15,r31			# WBMsg?
	bnel	parse_wb_args
.endif

# call _main in libvc.a
.startup:
	mr	r3,r14
	mr	r4,r15
	_debug	"calling _main(argc=%ld,argv=0x%08lx)\n",r3,r4
	bl	_main			# _main(argc,argv)
	li	r3,0
	b	_exit
.else	# MINSTART
	bl	main
	li	r3,0
.endif

	.type	__start,@function
	.size	__start,$-__start


.ifndef	MINSTART
__error:
	li	r3,IOERROR		# error: can't open stdin/stdout


	.global	_exit
_exit:
# r3 = return code
	mr	r14,r3
	_debug	"\n-----\n_exit\n-----\nstack = 0x%08lx\nrc = %ld\n",r1,r14

.ifdef	WBSTART
# If we ran from WB, then close _wbOutput.
# There's no need to reply the WBMsg, because the calling M68k
# task (or LoadSeg-patch) does it for us.
	lis	r11,_wbOutput@ha
	lwz	r3,_wbOutput@l(r11)
	cmpwi	r3,0
	beq	.5
	_debug	"PPCClose(wbout=0x%08lx)\n",r3
	bl	PPCClose
.endif

# free argv
.5:	lis	r11,_argv@ha
	lwz	r3,_argv@l(r11)
	cmpwi	r3,0
	beq	.6
	_debug	"PPCFreeVec(argv=0x%08lx)\n",r3
	bl	PPCFreeVec

.6:	lis	r15,_startup_data@ha
	lwz	r15,_startup_data@l(r15)
	cmpwi	r15,0			# did we receive a startup message?
	bne	.3

# close _stdout and _stdin, when started without startup message
	lis	r11,_stdout@ha
	lwz	r3,_stdout@l(r11)
	cmpwi	r3,0
	beq	.2
	_debug	"PPCClose(stdout=0x%08lx)\n",r3
	bl	PPCClose
.2:	lis	r11,_stdin@ha
	lwz	r3,_stdin@l(r11)
	cmpwi	r3,0
	beq	.4
	_debug	"PPCClose(stdin=0x%08lx)\n",r3
	bl	PPCClose
	b	.4

# write return code to startup message
.3:	_debug	"store rc=%ld in startup data (0x%08lx)\n",r14,r15
	stw	r14,sd_retCode(r15)

# restore initial stack frame
.4:	mr	r3,r14
.else	# MINSTART
	.global	exit
exit:
# r3 = return code
.endif
	lis	r11,_init_stk@ha
	lwz	r1,_init_stk@l(r11)
	_debug	"restored stack = 0x%08lx\n",r1
	lmw	r14,8(r1)
	addi	r1,r1,80
	lwz	r0,4(r1)
	mtlr	r0
	blr

.ifdef	MINSTART
	.type	exit,@function
	.size	exit,$-exit
.else
	.type	_exit,@function
	.size	_exit,$-_exit
.endif


.ifdef	CALLOSFIX
	.extern	PPCCallOS
	.global	CallOS
CallOS:
	b	PPCCallOS

	.type	CallOS,@function
	.size	CallOS,$-CallOS
.endif


.ifndef	MINSTART
.ifdef	WBSTART
parse_wb_args:
# Parse WBStartup message, init some global variables
# r14: argc=0
# r15: argv = WBStartup message
	_debug	"WB-Startup 0x%08lx\n",r15
	mflr	r16
	lis	r11,WBenchMsg@ha
	stw	r15,WBenchMsg@l(r11)	# store global WBenchMsg
	lwz	r3,sm_ToolWindow(r15)
	cmpwi	r3,0
	bne	.wb1			# window argument?
	lis	r3,.nilstring@ha	# otherwise, open "NIL:"
	addi	r3,r3,.nilstring@l
.wb1:	li	r4,MODE_OLDFILE
	bl	PPCOpen
	lis	r11,_wbOutput@ha
	stw	r3,_wbOutput@l(r11)
	cmpwi	r3,0
	beq	__error
	lis	r11,_stdin@ha		# set stdin, stdout, stderr
	stw	r3,_stdin@l(r11)
	lis	r11,_stdout@ha
	stw	r3,_stdout@l(r11)
	lis	r11,_stderr@ha
	stw	r3,_stderr@l(r11)
	mtlr	r16
	blr

	.type	parse_wb_args,@function
	.size	parse_wb_args,$-parse_wb_args
.endif


parse_cli_args:
# Parse command line and generate argc/argv.
# r14: argc=0
# r15: cmdline (0-terminated, includes program name), will become argv
	_debug	"CLI-Startup 0x%08lx\n",r15
	mflr	r17
	mr	r10,r15
	li	r16,0
.pa1:	bl	nextarg
	cmpwi	r3,0
	beq	.pa2
	addi	r16,r16,4		# another argv slot
	bl	skiparg
	cmpwi	r3,0
	bne	.pa1
.pa2:	cmpwi	r16,0
	beq	.pa_nomem		# no arguments found?
	addi	r16,r16,4		# + one termination slot
	sub	r3,r10,r15
	add	r3,r3,r16
	li	r4,0			# MEMF_ANY
	bl	PPCAllocVec		# memory for argv slots and buffer
	mr	r10,r15
	mr.	r15,r3
	beq	.pa_nomem
	lis	r9,_argv@ha
	stw	r15,_argv@l(r9)
	subi	r9,r15,4		# r9 argv slots
	add	r16,r15,r16		# r16 argv buffer
.pa4:	bl	nextarg
	cmpwi	r3,0
	beq	.pa_quit
	stwu	r16,4(r9)		# store arg-pointer
	addi	r14,r14,1		# increment argc
	cmpwi	r3,0x22
	bne	.pa10			# quoted?
.pa5:	lbzu	r3,1(r10)
	cmpwi	r3,0x22
	bne	.pa6
	addi	r10,r10,1		# end quote
	li	r3,0
.pa6:	cmpwi	r3,'*'			# BCPL escape character?
	bne	.pa8
	lbzu	r3,1(r10)
	andi.	r4,r3,0xdf
	cmpwi	r4,'N'			# newline
	bne	.pa7
	li	r3,10
.pa7:	cmpwi	r4,'E'			# escape
	bne	.pa8
	li	r3,27
.pa8:	stb	r3,0(r16)
	addi	r16,r16,1
	cmpwi	r3,0
	bne	.pa5
	b	.pa4
.pa10:	stb	r3,0(r16)		# normal arg copy
	addi	r16,r16,1
	lbzu	r3,1(r10)
	cmplwi	r3,0x20
	bgt	.pa10
	li	r3,0
	b	.pa8

.pa_nomem:
	li	r15,0
	mtlr	r17
	blr
.pa_quit:
	stwu	r3,4(r9)		# r3=0 terminates the arguments
	mtlr	r17
	blr

	.type	parse_cli_args,@function
	.size	parse_cli_args,$-parse_cli_args


nextarg:
# move pointer to beginning of next argument
# r10: cmdline-pointer
# -> r10: new cmdline-pointer, r3: char
	lbz	r3,0(r10)
	cmplwi	r3,0x20
	bgtlr
	cmpwi	r3,0
	beqlr
	addi	r10,r10,1
	b	nextarg

	.type	nextarg,@function
	.size	nextarg,$-nextarg


skiparg:
# move pointer behind the current argument
# r10: cmdline-pointer
# -> r10: new cmdline-pointer, r3: char
	lbz	r3,0(r10)
	cmpwi	r3,0x22			# " ?
	beq	.skipquote
.sk1:	cmplwi	r3,0x20
	blelr
	cmpwi	r3,0
	beqlr
	lbzu	r3,1(r10)
	b	.sk1
.skipquote:
	lbzu	r3,1(r10)
	cmpwi	r3,0
	beqlr
	cmpwi	r3,'*'			# escape?
	bne	.sk2
	lbzu	r4,1(r10)
	cmpwi	r4,0
	beqlr
.sk2:	cmpwi	r3,0x22			# " ?
	bne	.skipquote
	lbzu	r3,1(r10)
	blr

	.type	skiparg,@function
	.size	skiparg,$-skiparg


.constring:
	.string	"CONSOLE:"
.ifdef	WBSTART
.nilstring:
	.string	"NIL:"
.endif
.endif	# MINSTART
	.byte	"startup V"
	.byte	'0'+VERSION,'.','0'+(REVISION/10),'0'+(REVISION%10),0
	.align	4


# local data

	.type	_init_stk,@object
	.size	_init_stk,4
	.bss	_init_stk,4

.ifndef	MINSTART
	.type	_startup_data,@object
	.size	_startup_data,4
	.bss	_startup_data,4

	.type	_argv,@object
	.size	_argv,4
	.bss	_argv,4

.ifdef	WBSTART
	.type	_wbOutput,@object
	.size	_wbOutput,4
	.bss	_wbOutput,4
.endif
.endif


# global data

	.global	SysBase
	.type	SysBase,@object
	.size	SysBase,4
	.comm	SysBase,4

.ifndef	MINSTART
	.global	_stdin
	.type	_stdin,@object
	.size	_stdin,4
	.comm	_stdin,4

	.global	_stdout
	.type	_stdout,@object
	.size	_stdout,4
	.comm	_stdout,4

	.global	_stderr
	.type	_stderr,@object
	.size	_stderr,4
	.comm	_stderr,4

.ifdef	WBSTART
	.global	WBenchMsg
	.type	WBenchMsg,@object
	.size	WBenchMsg,4
	.comm	WBenchMsg,4
.endif
.endif

.ifdef	DEBUG
	.bss	.regsave,11*4		# r0,r3,...,r12
	.bss	.datastream,4*4		# max. 4 arguments
.endif
