*************************************************************************
*
* devent.a -- device startup code.
*
*	   Based on ramdev.device.asm
*	   12/16/94 Svend Daugaard Pedersen
*
* Bugs: If RTF_AUTOINIT fails, library base still left in memory.
*
*************************************************************************

   SECTION firstsection

   include "exec/types.i"
   include "exec/memory.i"
   include "exec/devices.i"
   include "exec/initializers.i"
   include "exec/resident.i"
   include "exec/io.i"
   include "exec/errors.i"
   include "exec/tasks.i"
   include "exec/execbase.i"
   include "libraries/dosextens.i"

;   include "asmsupp.i"  ;standard asmsupp.i, same as used for library

CALLSYS MACRO             ; call a library via A6 without using _LVO
          JSR	_LVO\1(A6)
        ENDM

XLIB	  MACRO             ; define a library reference without the _LVO
          XREF  _LVO\1
        ENDM

    STRUCTURE DEVBASE,LIB_SIZE
   UBYTE   db_Flags
   UBYTE   db_Pad1
   ;now longword aligned
   ULONG   db_SegList
   LABEL   DEVBASE_Sizeof


MEMFLAGS	  EQU	  MEMF_CLEAR+MEMF_PUBLIC
ABSEXECBASE equ 4   ;Absolute location of the pointer to exec.library base

   XDEF   _TaskStartup    ; small code to startup a new task
   XDEF   __XCEXIT        ; exit(code) is standard way to leave C.

   ;------ debuggers to have them globally visible
   XREF   _DevInit
   XREF   _DevOpen
   XREF   _DevClose
   XREF   _DevExpunge
   XREF   _DevBeginIO
   XREF   _DevAbortIO
   XREF   _NewTask
   XREF   _DevIdString
   XREF   _DevName

   XREF    _LinkerDB
   XREF    _RESLEN
   XREF    _RESBASE
   XREF    _NEWDATAL
   XREF    ___fpinit              ; initialize floating point
   XREF    ___fpterm              ; terminate floating point

   ;Pull these _LVOs in from amiga.lib
   XLIB   OpenLibrary
   XLIB   CloseLibrary
   XLIB   AllocMem
   XLIB   FreeMem
   XLIB   Remove
   XLIB   AddPort
   XLIB   PutMsg
   XLIB   ReplyMsg
   XLIB   GetMsg
   XLIB   Wait
   XLIB   WaitPort
   XLIB   SetTaskPri
   XLIB   Forbid
   XLIB   SetSignal

;-----------------------------------------------------------------------
; The first executable location.  This should return an error
; in case someone tried to run you as a program (instead of
; loading you as a device).

        section text,code

FirstAddress:
		moveq	#-1,d0
		rts

;-----------------------------------------------------------------------
; A romtag structure.  After your driver is brought in from disk, the
; disk image will be scanned for this structure to discover magic constants
; about you (such as where to start running you from...).
;-----------------------------------------------------------------------

   ; Most people will not need a priority and should leave it at zero.
   ; the RT_PRI field is used for configuring the roms.  Use "mods" from
   ; wack to look at the other romtags in the system
MYPRI	EQU   0

initDDescrip:
				;STRUCTURE RT,0
     DC.W    RTC_MATCHWORD	; UWORD RT_MATCHWORD (Magic cookie)
     DC.L    initDDescrip	  ; APTR	RT_MATCHTAG  (Back pointer)
     DC.L    EndCode		    ; APTR	RT_ENDSKIP   (To end of this hunk)
     DC.B    RTF_AUTOINIT	  ; UBYTE RT_FLAGS     (magic-see "Init:")
     DC.B    VERSION		    ; UBYTE RT_VERSION
     DC.B    NT_DEVICE		  ; UBYTE RT_TYPE      (must be correct)
     DC.B    MYPRI		      ; BYTE	RT_PRI
     DC.L    _DevName		    ; APTR	RT_NAME      (exec name)
     DC.L    _DevIdString		; APTR	RT_IDSTRING  (text string)
     DC.L    Init		        ; APTR	RT_INIT
	       ; LABEL RT_SIZE

   ; a major version number.
VERSION:    EQU   37

;DevName:      DC.B  "fysik.device",0
;DevIdString:  DC.B  "fysikdevice",0

   ; A particular revision.  This should uniquely identify the bits in the
   ; device.  I use a script that advances the revision number each time
   ; I recompile.  That way there is never a question of which device
   ; that really is.
REVISION:   EQU   1

   ; force word alignment
   ds.w   0


   ; The romtag specified that we were "RTF_AUTOINIT".  This means
   ; that the RT_INIT structure member points to one of these
   ; tables below.  If the AUTOINIT bit was not set then RT_INIT
   ; would point to a routine to run.

Init:
   DC.L   DEVBASE_Sizeof	; data space size
   DC.L   funcTable	      ; pointer to function initializers
   DC.L   dataTable	      ; pointer to data initializers
   DC.L   initRoutine	    ; routine to run


funcTable:
   ;------ standard system routines
   dc.l   Open
   dc.l   Close
   dc.l   Expunge
   dc.l   Null	    ;Reserved for future use!

   ;------ my device definitions
   dc.l   BeginIO
   dc.l   AbortIO

   ;------ function table end marker
   dc.l   -1


   ;The data table initializes static data structures. The format is
   ;specified in exec/InitStruct routine's manual pages.  The
   ;INITBYTE/INITWORD/INITLONG macros are in the file "exec/initializers.i".
   ;The first argument is the offset from the device base for this
   ;byte/word/long. The second argument is the value to put in that cell.
   ;The table is null terminated
   ;
dataTable:
   INITBYTE   LN_TYPE,NT_DEVICE       ;Must be LN_TYPE!
   INITLONG   LN_NAME,_DevName
   INITBYTE   LIB_FLAGS,LIBF_SUMUSED!LIBF_CHANGED
   INITWORD   LIB_VERSION,VERSION
   INITWORD   LIB_REVISION,REVISION
   INITLONG   LIB_IDSTRING,_DevIdString
   DC.W   0   ;terminate list


;-------- initRoutine -------------------------------------------------------
;
; FOR RTF_AUTOINIT:
;   This routine gets called after the device has been allocated.
;   The device pointer is in D0.  The AmigaDOS segment list is in a0.
;   If it returns the device pointer, then the device will be linked
;   into the device list.  If it returns NULL, then the device
;   will be unloaded.
;
; IMPORTANT:
;   If you don't use the "RTF_AUTOINIT" feature, there is an additional
;   caveat.  If you allocate memory in your Open function, remember that
;   allocating memory can cause an Expunge... including an expunge of your
;   device.  This must not be fatal.  The easy solution is don't add your
;   device to the list until after it is ready for action.
;
;   Routine calls C-routine
;
;     struct Library *DevInit(struct Library *libbase)
;
; This call is single-threaded by exec; please read the description for
; "Open" below.
;
; Register Usage
; ==============
; a5 -- device pointer
; a6 -- Exec base
;----------------------------------------------------------------------
initRoutine:
      ;------ get the device pointer into a convenient A register
      MOVEM.L  D1/A0-A1/A4-A5,-(A7)   ; Preserve ALL modified registers
      MOVE.L   D0,A5

      ;------ save pointer to our loaded code (the SegList)
      MOVE.L   A0,db_SegList(A5)
      BSR      SetUp                   ; Set up new data area etc.
      TST.L    D0
      
      BEQ.S    InitRet
      MOVE.L   A4,dbase                ; Save data base for future use

;---------------------------------------------
; Call device init routine
      MOVE.L   A5,-(A7)
      BSR      _DevInit
      ADDQ.L   #4,A7

InitRet:
      MOVEM.L  (A7)+,D1/A0-A1/A4-A5
      RTS

dbase: dc.l  0

;----------------------------------------------------------------------
;
; Here begins the system interface commands.  When the user calls
; OpenDevice/CloseDevice/RemDevice, this eventually gets translated
; into a call to the following routines (Open/Close/Expunge).
; Exec has already put our device pointer in a6 for us.
;
; IMPORTANT:
;   These calls are guaranteed to be single-threaded; only one task
;   will execute your Open/Close/Expunge at a time.
;
;   For Kickstart V33/34, the single-threading method involves "Forbid".
;   There is a good chance this will change.  Anything inside your
;   Open/Close/Expunge that causes a direct or indirect Wait() will break
;   the Forbid().  If the Forbid() is broken, some other task might
;   manage to enter your Open/Close/Expunge code at the same time.
;   Take care!
;
; Since exec has turned off task switching while in these routines
; (via Forbid/Permit), we should not take too long in them.
;
;----------------------------------------------------------------------


; Open will set up A4 and call C-Open routine:
;
;   int DevOpen(struct Library *libbase,long unit,struct IORequest *ior,long flags)
;
; and then set the IO_ERROR field to the return value on an error.
; If it was successfull, we should also set up the IO_UNIT and LN_TYPE
; fields.
; Exec takes care of setting up IO_DEVICE.
;
; Called with
;   A6 = device base (struct Library)
;   A1 = IORequest
;   D1 = flags
;   D0 = unit number
;
Open: ADDQ.W   #1,LIB_OPENCNT(A6)  ;Fake an opener for duration of call <|>

      MOVEM.L  A2/A4,-(A7)

      MOVE.L   A1,A2               ; save the IORequest
      MOVE.L   dbase(PC),A4        ; Get C-code data base

      MOVE.L   D1,-(A7)
      MOVE.L   A1,-(A7)
      MOVE.L   D0,-(A7)
      MOVE.L   A6,-(A7)
      BSR      _DevOpen
      LEA      16(A7),A7
      TST.L    D0
      BEQ.S    OpenOk
      MOVEQ.L  #-1,D1
      MOVE.L   D1,IO_DEVICE(A2)    ; IMPORTANT: trash IO_DEVICE on open failure
      SUBQ.W   #1,LIB_OPENCNT(A6)  ; End of expunge protection <|>
      BRA.S    OpenEnd

OpenOk:
      ;------ prevent delayed expunges
      BCLR     #LIBB_DELEXP,db_Flags(A6)
      MOVE.B   #NT_REPLYMSG,LN_TYPE(A2) ;IMPORTANT: Mark IORequest as "complete"

OpenEnd:
      MOVE.B   D0,IO_ERROR(A2)
      MOVEM.L  (A7)+,A2/A4
      RTS


;----------------------------------------------------------------------------
; There are two different things that might be returned from the Close
; routine.  If the device wishes to be unloaded, then Close must return
; the segment list (as given to Init).  Otherwise close MUST return NULL.
;
; Open will set up A4 and call C-Open routine:
;
;   void DevClose(struct Library *libbase,struct IORequest *ior)
;
; Called with
;   A6 = device base (struct Library)
;   A1 = IORequest
;
Close:
      MOVEM.L  D1/A2-A4,-(A7)
      MOVE.L   A1,A2               ; save the IORequest
      MOVE.L   dbase(PC),A4        ; Get C-code data base

      MOVE.L   A1,-(A7)
      MOVE.L   A6,-(A7)
      BSR      _DevClose
      LEA      8(A7),A7

      ;------ IMPORTANT: make sure the IORequest is not used again
      ;------ with a -1 in IO_DEVICE, any BeginIO() attempt will
      ;------ immediatly halt (which is better than a subtle corruption
      ;------ that will lead to hard-to-trace crashes!!!!!!!!!!!!!!!!!!
      MOVEQ.L  #-1,D0
      MOVE.L   D0,IO_UNIT(A2)   ; We're closed...
      MOVE.L   D0,IO_DEVICE(A2) ; customers not welcome at this IORequest!!

      MOVEQ.L  #0,D0
      ;------ mark us as having one fewer openers
      SUBQ.W  #1,LIB_OPENCNT(A6)

      ;------ see if there is anyone left with us open
      BNE.S   Close_End

      ;------ see if we have a delayed expunge pending
      BTST    #LIBB_DELEXP,db_Flags(A6)
      BEQ.S   Close_End

      ;------ do the expunge
      BSR	    Expunge

Close_End:
      MOVEM.L   (A7)+,D1/A2-A4
      RTS				; MUST return either zero or the SegList!!!


;------- Expunge -----------------------------------------------------------
;
; Expunge is called by the memory allocator when the system is low on
; memory.
;
; There are two different things that might be returned from the Expunge
; routine.  If the device is no longer open then Expunge may return the
; segment list (as given to Init).  Otherwise Expunge may set the
; delayed expunge flag and return NULL.
;
; One other important note: because Expunge is called from the memory
; allocator, it may NEVER Wait() or otherwise take long time to complete.
;
; If ready to be expunged calls C-routine:
;
;   BOOL DevExpunge(struct MyLibrary *libbase)
;
; Registers at call
;	  A6	    - library base (scratch)
;	  D0-D1/A0-A1 - scratch
;
Expunge:
      MOVEM.L  D1/D2/A4-A6,-(A7)   ; Save ALL modified registers
      MOVE.L   A6,A5
      MOVE.L   ABSEXECBASE.W,A6

      ;------ see if anyone has us open
      TST.W    LIB_OPENCNT(A5)
      BEQ.S    CleanUp

      ;------ it is still open.  set the delayed expunge flag
      BSET     #LIBB_DELEXP,db_Flags(A5)
      MOVEQ.L  #0,D0
      BRA.S    Expunge_End

CleanUp:
      MOVE.L   dbase(PC),A4        ; Get C-code data base
      MOVE.L   A5,-(A7)
      BSR      _DevExpunge
      ADDQ.L   #4,A7
      EXT.L    D0
      BEQ.S    Expunge_End         ; Don't expunge !

      ;------ go ahead and get rid of us.
      BSR      FreeData            ; Free data area, close DOS,
                                   ; clean up any floating point etc.

      MOVE.L   db_SegList(A5),D2   ; Store our seglist in D2

      ;------ unlink from device list
      MOVE.L   A5,A1
      CALLSYS  Remove		; Remove first (before FreeMem)

      ;------ free our memory (must calculate from LIB_POSSIZE & LIB_NEGSIZE)
      MOVE.L   A5,A1		          ; Devicebase
      MOVEQ.L  #0,D0
      MOVE.W   LIB_NEGSIZE(A5),d0
      SUB.L    D0,A1		          ; Calculate base of functions
      ADD.W    LIB_POSSIZE(A5),D0 ; Calculate size of functions + data area
      CALLSYS  FreeMem

      ;------ set up our return value
      MOVE.L   D2,D0

Expunge_End:
      MOVEM.L  (A7)+,D1/D2/A4-A6
      RTS


;------- Null ---------------------------------------------------------------
;
Null: MOVEQ.L  #0,D0  	    ;The "Null" function MUST return NULL.
      RTS

;--------------------------------
; BeginIO starts all incoming io.  The IO is either queued up for the
; unit task or processed immediately.
;
;
; BeginIO often is given the responsibility of making devices single
; threaded... so two tasks sending commands at the same time don't cause
; a problem.  Once this has been done, the command is dispatched via
; PerformIO.
;
; There are many ways to do the threading.  This example uses the
; UNITB_ACTIVE bit.  Be sure this is good enough for your device before
; using!  Any method is ok.  If immediate access can not be obtained, the
; request is queued for later processing.
;
; Some IO requests do not need single threading, these can be performed
; immediatley.
;
; IMPORTANT:
;   The exec WaitIO() function uses the IORequest node type (LN_TYPE)
;   as a flag.	If set to NT_MESSAGE, it assumes the request is
;   still pending and will wait.  If set to NT_REPLYMSG, it assumes the
;   request is finished.  It's the responsibility of the device driver
;   to set the node type to NT_MESSAGE before returning to the user.
;
; Routine calls C-coded function:
;
;   void DevBeginIO(struct MyLibrary *libbase,struct IORequest *ior)
;
; Registers at entrance
;   A1 = IORequest
;   A6 = Device
;
BeginIO:
      MOVE.L   A4,-(A7)
      
      MOVE.L   dbase(PC),A4        ; Get C-code data base
      MOVE.L   A1,-(A7)
      MOVE.L   A6,-(A7)
      BSR      _DevBeginIO
      LEA      8(A7),A7

      MOVE.L  (A7)+,A4
      RTS


; AbortIO() is a REQUEST to "hurry up" processing of an IORequest.
; If the IORequest was already complete, nothing happens (if an IORequest
; is quick or LN_TYPE=NT_REPLYMSG, the IORequest is complete).
; The message must be replied with ReplyMsg(), as normal.
;
; Routine calls C-coded function:
;
;   void DevAbortIO(struct MyLibrary *libbase,struct IORequest *ior)
;
; Registers at entrance
;   A1 = IORequest
;   A6 = Device
;
AbortIO:
      MOVE.L   A4,-(A7)
      
      MOVE.L   dbase(PC),A4        ; Get C-code data base
      MOVE.L   A1,-(A7)
      MOVE.L   A6,-(A7)
      BSR      _DevAbortIO
      LEA      8(A7),A7

      MOVE.L  (A7)+,A4
      RTS

; some dos magic, useful for Processes.  A process is started at
; the first  executable address  after a segment list.	We hand craft a
; segment list here.  See the the DOS technical reference if you really
; need to know more about this.
; The next instruction after the segment list is the first executable address

      cnop    0,4     ; long word align
      DC.L    16	    ; segment length -- any number will do (this is 4
		    ; bytes back from the segment pointer)
proc_seglist:
      DC.L    0	    ; pointer to next segment

_TaskStartup:
      MOVEM.L D1-D6/A0-A6,-(A7)   ; First save all registers
      
;--------------------------------------------------------------
; Then get startup message
;
      MOVE.L  ABSEXECBASE.W,A6
      MOVE.L	ThisTask(A6),A3
      LEA     pr_MsgPort(A3),A0   ; Point to message port
      CALLSYS WaitPort
      LEA     pr_MsgPort(A3),A0
      CALLSYS GetMsg
      MOVE.L  D0,A2               ; Startup message to A2

      BSR.S   SetUp               ; Set up new data area etc.
      TST.L   D0
      BEQ.S   StartupFailed
      MOVE.L  A7,StackSave(A4)    ; Save stack pointer

      MOVE.L  A2,A1               ; Startup message to A1
      CALLSYS ReplyMsg            ; Reply startup message: Ok until now

;--------------------------------------------------------------
; Parameters on stack and call start of new task
;
      MOVE.L  MN_SIZE+8(A2),-(A7) ; device unit number
      MOVE.L  MN_SIZE+4(A2),-(A7) ; device name
      MOVE.L  MN_SIZE(A2),-(A7)   ; pointer to structure Unit
      BSR     _NewTask            ; Execute main program

__XCEXIT:
      MOVE.L  StackSave(A4),A7    ; Set up old stack
      BSR     FreeData
      BRA.S   exit

;--------------------------------------------------------------
; Process startup failed - not enough memory
;
StartupFailed:
      MOVE.L  A2,A1               ; Startup message to A1
      MOVE.L  D0,MN_SIZE(A1)      ; Signal error
      CALLSYS ReplyMsg

;-----------------------------------------------------------------
; Get terminating message
;
exit: MOVE.L  ABSEXECBASE.W,A6
      MOVE.L	ThisTask(A6),A3
      LEA     pr_MsgPort(A3),A0   ; Point to message port
      MOVE.L  A0,-(A7)
      CALLSYS WaitPort
      MOVE.L  (A7)+,A0
      CALLSYS GetMsg
      MOVE.L  D0,A2               ; Message to A2

      CALLSYS Forbid              ; Make a safe exit
      MOVE.L  A2,A1
      CALLSYS ReplyMsg
      
      MOVEM.L (A7)+,D1-D6/A0-A6   ; Get back saved registers
      MOVEQ.L #0,D0
      RTS                         ; ..and return


;------------------------------------------------------------------------
;------------------------------------------------------------------------
;
; Set up new data area, open DOS and initialize fp
;
; At call:   -
; At return: D0 <>0, if ok and D0=0, if fail
;            A4 = data base for new initialized data area (or zero)
;
; Regs used: D0-D1/A0-A1/A4
;
SetUp:          LEA     _LinkerDB,A4    ; Load base register
                MOVEM.L D2/A2/A6,-(A7)
                MOVE.L	#_RESLEN+4,D0   ; Get length of data area
                MOVE.L	#MEMFLAGS,D1
        	      MOVE.L  ABSEXECBASE.W,A6
	              CALLSYS AllocMem        ; Allocate memory
	              TST.L	  D0
	              BEQ.S	  SetUpRet

	              MOVE.L	D0,A0
	              MOVE.L	#_RESLEN+4,D0   ; Get length of data area
                MOVE.L  D0,(A0)+
                MOVE.L	A0,A1
	              MOVE.L	A0,A2

        	      MOVE.L	#_NEWDATAL,D0   ; Length of initialized data
	              SUB.L	  #_RESBASE,A4    ; Point to real start

; Copy initialized data over
1$:             MOVE.L	(A4)+,(A0)+
        	      SUBQ.L	#1,D0
        	      BNE	    1$

; A4 now points at number of relocs
	              MOVE.L	(A4)+,D0        ; Get the number
                BEQ.S	  NoReloc         ; Branch if no relocs
2$:	            MOVE.L	A1,A0           ; A0 = new data start
	              ADD.L	  (A4)+,A0        ; A0 has add of reloc
        	      ADD.L	  (A0),A2
        	      MOVE.L	A2,(A0) 
        	      MOVE.L	A1,A2		        ; Restore offset
	              SUBQ.L	#1,d0
                BNE	2$                  ; Continue until all done
	
NoReloc:        MOVE.L	A1,A4		        ; Set up new base register
	              ADD.L	  #_RESBASE,A4

                ADD.L   #_RESLEN,D2      ; Number of bytes allocated
        	      MOVE.L  A6,_SysBase(A4) ; Set ExecBase
                
*------ Set __base for stack checking
                MOVE.L  A7,D0
                SUB.L   D1,D0
                ADD.L   #256,D0         ; Safe low bound of stack
	              MOVE.L  D0,__base(A4)   ; Save for stack checking
        	
*-----  clear any pending signals
	              MOVEQ	  #0,d0
	              MOVE.L	#$00003000,d1
	              CALLSYS	SetSignal
*------ Open DOS library:
	              LEA     DOSName(PC),A1
	              MOVEQ   #0,D0
	              CALLSYS OpenLibrary
	              MOVE.L  D0,_DOSBase(A4)
                BNE.S   SetUpOk
                BSR.S   FreeData1
                MOVEQ   #0,D0
                BRA.S   SetUpRet

;---------------------------------------------
; Initialize floating point and constructors
;
SetUpOk:        MOVE.L  D0,-(A7)
                JSR      ___fpinit(PC)
                MOVE.L  (A7)+,D0
                
SetUpRet:       MOVEM.L (A7)+,D2/A2/A6
                RTS

; Free data area
;
; At call:      A4 = pointer to data base
; At return:    -
; Regs used:    D0-D1,A0-A1/A6
;
FreeData:	      JSR     ___fpterm(PC)    ; clean up any floating point etc.

                MOVE.L  ABSEXECBASE.W,A6
	              MOVE.L  _DOSBase(A4),A1
        	      CALLSYS CloseLibrary	   ; Close Dos library

FreeData1:      MOVE.L  A4,A1
	              SUB.L   #_RESBASE,A1
	              MOVE.L  -(A1),D0
	              MOVE.L  ABSEXECBASE.W,A6
	              CALLSYS FreeMem
                RTS

DOSName         dc.b        'dos.library',0

;----------------------------------------------------------------------
; EndCode is a marker that shows the end of your code.	Make sure it does not
; span hunks, and is not before the rom tag!  It is ok to put it right after
; the rom tag -- that way you are always safe.	I put it here because it
; happens to be the "right" thing to do, and I know that it is safe in this
; case (this program has only a single code hunk).
;----------------------------------------------------------------------
EndCode:  equ *

        csect        __MERGED,1,0,0,4

     	    XREF    _DOSBase

          xdef    _NULL,_SysBase

_SysBase:      DC.L        0
__base:        DC.L        0
_NULL          DC.L        0
StackSave:     DC.L        0

    END
