********************************************************************
*
*                                 Bovs.asm
*                        Bryan's Overlay Supervisor
*                       Copyright (C) 1991 Bryan Ford
*
********************************************************************
* $Id$
* Assemble with A68k version >2.71

* If OVERLAY is 0, the overlay system is disabled.  (This is how Bst.o is assembled.)
OVERLAY set     1

        include "exec/types.i"
        include "exec/tasks.i"
        include "exec/libraries.i"
        include "exec/alerts.i"
        include "exec/memory.i"
        include "exec/funcdef.i"
        include "exec/exec_lib.i"
        include "dos/dos.i"
        include "dos/dosextens.i"
        include "dos/rdargs.i"
        include "dos/dos_lib.i"
        include "workbench/startup.i"
        include "bry/macros.i"
        include "bry/memman.i"

* Main program must define these:
        xref    _stack                  ; Stack size for subprocess when run from CLI
        xref    _procname               ; Pointer to process name string
        xref    _priority               ; Priority for process detached from CLI

        xref    _argtemplate            ; ReadArgs template for 2.0 startup
        xref    _argarray               ; ReadArgs array of longwords to fill

        IFNE    OVERLAY
        xdef    _ovlyMgr
        ENDC
        xdef    LockOverlay,UnlockOverlay,ResCall
        xdef    @LockOverlay,@UnlockOverlay,_ResCall
        xdef    BExit,_BExit,@BExit

* Structure used to keep track of loaded overlays
        IFNE    OVERLAY
 STRUCTURE      OvNode,mmn_SIZEOF       ; First a MemMan node
        BPTR    on_SegList              ; SegList for node if currently loaded
        UWORD   on_LockCount            ; Number of times this node is locked
        LABEL   on_SIZEOF

* Offsets within the overlay table.
 STRUCTURE      OvTabEntry,0
        ULONG   ot_SeekOfs              ; Load file position
        APTR    ot_OvNode               ; Pointer to OvNode structure
        LONG    ot_dummy2
        ULONG   ot_Level                ; Overlay level - always 1
        ULONG   ot_Ordinate             ; Ordiinate number of node
        ULONG   ot_FirstHunk            ; Position to load into in hunk table
        ULONG   ot_SymbolHunk           ; Where symbol is found
        ULONG   ot_SymbolOfs            ; Where in that hunk
        LABEL   ot_SIZEOF
        ENDC

        code    NTRYHUNK

        xref    _LinkerDB,__BSSBAS,__BSSLEN
        xref    MMInit
        xref    @Main

first:
        IFNE    OVERLAY
        bra     startup
        cnop    0,4

* This next word serves to identify the overlay
* supervisor to 'unloader'.

                dc.l    $ABCD           ; Magic unloader cookie
loaddat:
loadstream      ds.l    1               ; Overlay input stream
loadovtab       ds.l    1               ; Overlay table (Machine address)
loadhtab        ds.l    1               ; Hunk table    (BCPL address)
loadglobvec     ds.l    1               ; Global vector (Machine address)
        ENDC

*** Startup code to initialize the overlay system and detach CLI processes
startup:
        lea     _LinkerDB,a4            ; Find data segment
        move.l  4,a6                    ; Find exec.library

        lea     __BSSBAS,a2             ; Clear undefined data
        moveq   #0,d1
        move.l  #__BSSLEN,d2
        bra.s   12$
11$     move.l  d1,(a2)+
12$     dbf     d2,11$

        move.l  a6,_SysBase(a4)         ; Store SysBase for everyone else

        movem.l d0/a0,cliargs(a4)       ; Store CLI argument line

        lea     dosname(pc),a1          ; Open dos.library
        moveq   #34,d0                  ; Requires at least Kickstart 1.2
        jsr     _LVOOpenLibrary(a6)
        move.l  d0,_DOSBase(a4)
        bz      \fatalerr

        IFNE    OVERLAY
        lea     loadstream(pc),a2       ; Steal the file handle
        move.l  (a2),stream(a4)
        clr.l   (a2)+

        move.l  (a2)+,a5                ; Overlay data table (a5)
        move.l  (a2),a3                 ; Hunk table (a3)
        add.l   a3,a3
        add.l   a3,a3

        move.l  -(a3),d0                ; Allocate room for a copy of both tables
        add.l   -(a5),d0
        subq.l  #4,d0
        move.l  d0,d2
        moveq   #0,d1
        jsr     _LVOAllocMem(a6)
        tst.l   d0
        bz      \fatalerr

        move.l  d0,a0                   ; Copy hunk table
        move.l  d2,(a0)+
        move.l  a0,htab(a4)
        move.l  (a3)+,d0
        lsr.l   #2,d0
        subq.l  #1+1,d0
21$     move.l  (a3)+,(a0)+
        dbra    d0,21$
        move.l  (a5)+,d0
        move.l  (a5),d1                 ; Find first overlay data table node
        lsl.l   #2,d1
        add.l   a0,d1
        move.l  d1,ovdtab(a4)
        lsr.l   #2,d0                   ; Copy overlay table
        subq.l  #1+1,d0
22$     move.l  (a5)+,(a0)+
        dbra    d0,22$
        move.l  a0,ovdtabend(a4)
        ENDC

        jsr     MMInit                  ; Initialize the memory manager
        tst.l   d0
        bz      \fatalerr

        sub.l   a1,a1                   ; Which way did we come?
        jsr     _LVOFindTask(a6)
        move.l  d0,a2
        tst.l   pr_CLI(a2)
        bnz.s   \fromcli

\fromwb:
        lea     pr_MsgPort(a2),a0       ; Get the startup message
        move.l  a0,d2
        jsr     _LVOWaitPort(a6)
        move.l  d2,a0
        jsr     _LVOGetMsg(a6)
        move.l  d0,a3
        move.l  a3,_WBenchMsg(a4)

        move.l  sm_ArgList(a3),a1       ; Use program's directory
        move.l  wa_Lock(a1),d1
        move.l  _DOSBase(a4),a6
        jsr     _LVOCurrentDir(a6)

        move.l  sp,origsp(a4)           ; Set things up for exit
        pea     _BExit

        moveq   #0,d0                   ; Call the main program
        move.l  a3,a0
        jmp     @Main

\fromcli:
        move.l  a2,parenttask(a4)       ; Make sure child knows where to find us

        move.l  #SIGBREAKF_CTRL_F,d7    ; Make sure CTRL-F flag is cleared
        move.l  d7,d1
        moveq   #0,d0
        jsr     _LVOSetSignal(a6)

        move.l  _DOSBase(a4),a6

        cmp.w   #36,LIB_VERSION(a6)     ; Use 2.0's ReadArgs if possible
        blo.s   79$
        move.l  #_argtemplate,d1
        move.l  #_argarray,d2
        moveq   #0,d3
        jsr     _LVOReadArgs(a6)
        move.l  d0,rdargs(a4)
        bz      \doserr
79$
        move.l  pr_CurrentDir(a2),d1    ; Get an initial directory for the subprocess
        jsr     _LVODupLock(a6)
        move.l  d0,dupdir(a4)

        move.l  _procname,d1            ; Start the child process
        move.l  _priority,d2
        move.l  #cliseg,d3
        lsr.l   #2,d3
        move.l  _stack,d4
        jsr     _LVOCreateProc(a6)
        tst.l   d0
        bz      \doserr
        move.l  d0,a2
        lea     -pr_MsgPort(a2),a2

        move.l  d7,d0                   ; Wait for the go-ahead from the child
        move.l  _SysBase(a4),a6
        jsr     _LVOWait(a6)

        lea     first-4(pc),a0          ; Detach the segment list
        move.l  (a0),splitseg(a4)
        clr.l   (a0)

        moveq   #0,d0                   ; Return success code immediately
        rts

\doserr:
        cmp.w   #36,LIB_VERSION(a6)     ; Display an appropriate error message
        blo.b   \fatalerr
        jsr     _LVOIoErr(a6)
        move.l  d0,d1
        move.l  _procname,d2
        jsr     _LVOPrintFault(a6)

\fatalerr:
        jmp     errexit

dosname dc.b    "dos.library",0

        code    text

        xref    MMFinish,MMAddNode,MMRemNode

        IFNE    OVERLAY
*** Entrypoint called by the linker when overlaid functions are called
* d0 = Entrypoint number
* a0/a1 = Parameters from caller
_ovlyMgr:
        movem.l d2-d3/a0-a3/a5-a6,-(sp) ; 8 longwords

        move.l  ovdtab,a3               ; Find overlay data table node (a3)
        move.l  a3,a2
        lsl.w   #2,d0
        adda.w  d0,a3

        move.l  ot_OvNode(a3),d0        ; See if we already know its OvNode
        bnz.s   \alreadyfound

        move.l  ot_SeekOfs(a3),d0       ; Find the first entry in this node (a2)
        moveq   #ot_SIZEOF,d1
        bra.s   \findfirst_a
\findfirst:
        add.l   d1,a2
\findfirst_a:
        cmp.l   ot_SeekOfs(a2),d0
        bne.s   \findfirst

        move.l  ot_OvNode(a2),d0        ; See if the OvNode is already allocated
        bnz.s   \alreadyallocated

\retryalloc:
        moveq   #on_SIZEOF,d0           ; Allocate the OvNode
        move.l  #MEMF_PUBLIC!MEMF_CLEAR,d1
        move.l  4,a6
        jsr     _LVOAllocMem(a6)
        move.l  d0,ot_OvNode(a2)        ; Store the OvNode in the first OvTabEntry
        bz.s    \retryalloc             ; FIXME: Shouldn't there be a better way?

        move.l  d0,a0                   ; Initialize the node
        lea     getridfunc(pc),a1
        move.l  a1,mmn_GetRidFunc(a0)
        move.l  a0,mmn_GetRidData(a0)

\alreadyallocated:
        move.l  d0,ot_OvNode(a3)        ; Store OvNode into current OvTabEntry

\alreadyfound:
        move.l  d0,a5                   ; OvNode into (a5)

        move.l  a5,a1                   ; Lock this node in memory during the call
        bsr     lockoverlay

        move.l  on_SegList(a5),d0       ; See if the code is loaded
        bnz     \alreadyloaded

        lea     rescallret(pc),a0       ; Don't load if the call is coming through ResCall
        cmp.l   8*4(sp),a0
        beq     \rescallquit            ; Note: D0 = 0 from above test

* Now seek to the segment and load it.  If we fail (probably because
* the user removed the disk), then retry until successful.  This has
* the effect of continually reprompting the user to insert the disk
* until he/she obeys.
        move.l  _DOSBase,a6
\retryload:
        move.l  stream,d1               ; Find hunk position in load file
        move.l  ot_SeekOfs(a3),d2
        moveq   #OFFSET_BEGINNING,d3
        jsr     _LVOSeek(a6)
        tst.l   d0
        bmi.s   \retryload

        moveq   #0,d1                   ; Special calling format - NULL for name
        move.l  htab,d2                 ; Hunk table
        lsr.l   #2,d2
        move.l  stream,d3               ; File handle
        jsr     _LVOLoadSeg(a6)
        move.l  d0,on_SegList(a5)
        ble.s   \retryload

\alreadyloaded:
        move.w  ot_SymbolHunk+2(a3),d1  ; Find hunk function is in
        sub.w   ot_FirstHunk+2(a3),d1
        bra.b   \hunkhuntin
\hunkhunt
        move.l  (a2),d0
\hunkhuntin
        lsl.l   #2,d0
        move.l  d0,a2
        dbra    d1,\hunkhunt

        add.l   ot_SymbolOfs(a3),a2     ; Call function (d0=OvNode, a0-a1=Passed)
        move.l  a5,d0
        movem.l 8(sp),a0-a1
        jsr     (a2)

\rescallquit
        move.l  d0,d2                   ; Unlock the node
        move.l  a5,a1
        bsr     unlockoverlay
        move.l  d2,d0

        movem.l (sp)+,d2-d3/a0-a3/a5-a6
        rts

*** Lock an OvNode
LockOverlay:
@LockOverlay:
        move.l  d0,a1
lockoverlay:
        addq.w  #1,on_LockCount(a1)     ; Increment nesting counter

        bra     MMRemNode               ; Take the node off the dead-list

*** Unlock an OvNode
UnlockOverlay:
@UnlockOverlay:
        move.l  d0,a1
unlockoverlay:
        subq.w  #1,on_LockCount(a1)     ; Decrement nesting counter
        bz      MMAddNode               ; If zero, add to the dead-list
        rts

*** Special call routine - calls a function ONLY if it's already resident
*** (if it's not resident, it returns immediately without loading the function.)
* a2 = Pointer to routine to call
* a0-a1 = Parameters to routine
* d0 = Return value from routine if resident, zero if the routine wasn't resident.
ResCall:
_ResCall:
        jsr     (a2)
rescallret                              ; ovlyMgr watches for this return address...
        rts
        ENDC

*** Fake the LockOverlay and UnlockOverlay functions for the non-overlaid versions
        IFEQ    OVERLAY
LockOverlay:
@LockOverlay:
UnlockOverlay:
@UnlockOverlay:
        rts

ResCall:
_ResCall:
        jmp     (a2)
        ENDC

*** Fake SegList for the CLI subprocess to start at
        cnop    0,4
        dc.l    0                       ; Segment size
cliseg: dc.l    0                       ; Link to next segment

        lea     _LinkerDB,a4            ; Find the data segment
        move.l  sp,origsp(a4)           ; Save the stack pointer

        movem.l cliargs(a4),d2/a0       ; Copy the arguments
        move.l  d2,d0
        addq.l  #4-1,d0
        andi.b  #$fc,d0
        sub.l   d0,sp
        move.l  sp,a1
        move.l  d2,d0
        bra.s   12$
11$     move.b  (a0)+,(a1)+
12$     dbra    d0,11$

        move.l  _SysBase(a4),a6         ; Signal parent task to get lost
        move.l  parenttask(a4),a1
        move.l  #SIGBREAKF_CTRL_F,d0
        jsr     _LVOSignal(a6)

        move.l  _DOSBase(a4),a6         ; Set the initial directory
        move.l  dupdir(a4),d1
        jsr     _LVOCurrentDir(a6)

        move.l  d2,d0                   ; Call the main program
        move.l  sp,a0
        jsr     @Main

        ; fall through...

*** Exit code for the sub-process
BExit:
_BExit:
@BExit:
        lea     _LinkerDB,a4            ; Restore data segment
        move.l  origsp(a4),sp           ; Restore SP

errexit:
        move.l  _DOSBase(a4),a6

        move.l  rdargs(a4),d1           ; Free all command-line arguments
        bz.s    79$
        jsr     _LVOFreeArgs(a6)
79$
        IFNE    OVERLAY
        move.l  stream(a4),d1           ; Close load file
        bz.s    49$
        jsr     _LVOClose(a6)
        ENDC
49$
        move.l  dupdir(a4),d1           ; Free the initial directory
        bz.s    59$
        jsr     _LVOUnLock(a6)
59$
        move.l  _SysBase(a4),a6

        IFNE    OVERLAY
        move.l  ovdtab(a4),d0           ; Free all overlays
        bz.s    19$
        move.l  d0,a2
        move.l  ovdtabend(a4),a3
        moveq   #0,d2
1$      move.l  ot_OvNode(a2),d0
        bz.s    18$
        move.l  d0,a5
        move.l  ot_SeekOfs(a2),d0       ; Only one OvNode per SeekOfs
        cmp.l   d0,d2
        beq.s   18$
        move.l  d0,d2
        move.l  a5,a0                   ; Unload the SegList
        bsr     getridfunc
        move.l  a5,a1
        moveq   #on_SIZEOF,d0
        jsr     _LVOFreeMem(a6)
18$     lea     ot_SIZEOF(a2),a2
        cmp.l   a2,a3
        bne.s   1$
19$
        move.l  htab(a4),d0             ; Free the hunk and overlay tables
        bz.s    29$
        move.l  d0,a1
        move.l  -(a1),d0
        jsr     _LVOFreeMem(a6)
        ENDC
29$
        move.l  _WBenchMsg(a4),d2       ; Return the Workbench message
        bz.s    39$
        jsr     _LVOForbid(a6)          ; Don't let Workbench unload us yet!
        move.l  d2,a1
        jsr     _LVOReplyMsg(a6)
39$
        move.l  _DOSBase(a4),d2         ; Close dos.library
        bz.s    69$
        move.l  d2,a1
        jsr     _LVOCloseLibrary(a6)
69$
        bsr     MMFinish                ; Close down the memory manager

        move.l  splitseg(a4),d1         ; Unload the program code
        bz.s    99$
        move.l  d2,a6
        jmp     _LVOUnLoadSeg(a6)

99$     moveq   #10,d0                  ; Always return an error code
        rts                             ; (If anybody sees it, something went wrong.)

*** Get rid of an OvNode's SegList
* a0 = OvNode
* Returns:
* d0 = Nonzero if we freed anything
        IFNE    OVERLAY
getridfunc:
        movem.l a2/a6,-(sp)
        move.l  a0,a2

        move.l  a0,a1                   ; No need to keep it on the dead-list
        bsr     MMRemNode

        move.l  on_SegList(a2),d0       ; Un-load the SegList
        bz.b    \out
        move.l  d0,d1
        move.l  _DOSBase,a6
        jsr     _LVOUnLoadSeg(a6)
        clr.l   on_SegList(a2)

        moveq   #1,d0
\out    movem.l (sp)+,a2/a6
        rts
        ENDC

        bss     __MERGED

        xdef    _SysBase,_DOSBase,_WBenchMsg

_SysBase        ds.l    1               ; System libraries
_DOSBase        ds.l    1

_WBenchMsg      ds.l    1               ; If we came from Workbench

                IFNE    OVERLAY
stream          ds.l    1               ; Load file handle
htab            ds.l    1               ; Fake hunk table
ovdtab          ds.l    1               ; Overlay data table
ovdtabend       ds.l    1               ; End of overlay data table
                ENDC

splitseg        ds.l    1               ; First segment after NTRYHUNK

origsp          ds.l    1               ; Original sub-process SP

cliargs:                                ; Command-line arguments
cliarglen       ds.l    1
cliargstr       ds.l    1

dupdir          ds.l    1               ; DupLock'd directory we need to free

parenttask      ds.l    1               ; Original CLI process

rdargs          ds.l    1               ; RDArgs structure returned by ReadArgs

        end
