##
## 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.
##
##
## V2.1 03-Nov-98 phx
##      Put SysBase, WBenchMsg, etc. into .sdata instead .bss, to avoid
##      problems with small data.
## V2.0 24-Oct-98 phx
##      Small data support. r13 will be initialized with the linker
##      symbol _LinkerDB, which will be supplied by vlink 0.6 in
##      elf32powerup-mode.
##      When SDATA2 is set, r2 will be initialized with the linker symbol
##      _SDA2_BASE. This is a 2nd small data region, normally used
##      under EABI only, consisting of the sections .sdata2 and .sbss2.
## V1.1 14-Apr-98 phx
##      Inserted a 'nop' at __start again, because newer releases of
##      ppc.library don't skip the first word.
## 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,2
.set    REVISION,10
.macro  VBCC_VER
        .byte   "VBCC 0.7"
.endm

# 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.
#.set   SDATA2,1        # initialize r2 with linker symbol _SDA2_BASE_


# 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

# Linker symbols (requires vlink 0.6)
        .extern _LinkerDB
.ifdef  SDATA2
        .extern _SDA2_BASE_
.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
        nop
# ppc.library will call __start+0 or __start+4 (newer releases) ... :(
        b       .start
        VBCC_VER
.start:
        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 small data
        lis     r13,_LinkerDB@ha
        addi    r13,r13,_LinkerDB@l
.ifdef  SDATA2
        lis     r2,_SDA2_BASE_@ha
        addi    r2,r2,_SDA2_BASE_@l
.endif

# 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, placed into .sdata for Small Data compatibility
        .sdata

        .global SysBase
SysBase:
        .space  4
        .type   SysBase,@object
        .size   SysBase,4

.ifndef MINSTART
        .global _stdin
_stdin:
        .space  4
        .type   _stdin,@object
        .size   _stdin,4

        .global _stdout
_stdout:
        .space  4
        .type   _stdout,@object
        .size   _stdout,4

        .global _stderr
_stderr:
        .space  4
        .type   _stderr,@object
        .size   _stderr,4

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

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