; SW_FPSAVE_ASM
; on H38
; 20th May 1997
;
;
*********************************************************************
* This program should be LRESPR'd to set QDOS to save the FPU state *
*                  when this is needed.                             *
*                                                                   *
* Stage 1. Set type of FPU in SYS_VARS + $D0.                (byte) *
*          Set type of MMU in SYS_VARS + $D1                 (byte) *
*          Set maximum size of FSAVE area in SYS_VARS + $D2. (word) *
*                                                                   *
* Stage 2. Load the appropriate FPSP (Library version).             *
*          Set a new VBR with entries from the FPSP.                *
*          Set the FPU to signal first use.                         *
*                                                                   *
* Stage3.  Link FP_EX into the scheduler list.                      *
*          Clear Caches                                             *
*                                                                   *
*********************************************************************
; Stage 1.
;
VERSION   EQU       ?"1.18"
;
          MOVEQ     #MT_INF,D0
          TRAP      #1
          TST.L     SYS_CLFP(A0)
          BEQ       BEG1      FPSP not already loaded
;
	MOVEA.L	SYS_CLFP(A0),A5
	MOVE.L	(A5),D0
	LEA	MES3,A1	message for 'already loaded'
	CMP.L	CALL_FPSP,D0	this FPSAVE? . .
	BEQ	OUT1	. . yes - print 'loaded'
	LEA	MES4,A1	. . no - print "can't load"
	BRA	OUT1
;
BEG_ERR	CLR.L	SYS_CLFP(A5)
	MOVE	#0,SR
	LEA	MES5,A1
	BRA	OUT1	---->
;
BEG1      MOVEA.L   A0,A5
          LEA       CALL_FPSP,A0
          MOVE.L    A0,SYS_CLFP(A5)     set address of CALL_FPSP in sys vars
          LEA       FP_CLOSE,A0
          LEA       CLOSE,A1
          MOVE.L    A1,(A0)             set CLOSE address in FP_CLOSE
          TRAP      #0
          ORI       #$700,SR
          EXG       A6,A5     BASIC pointer -> A5
          BSR       PROC      get processor type to D7 and sys vars
          EXG       A6,A5     replace BASIC pointer
          BNE       BEG_ERR   SYS_FPU or SYS_MMU already set - wrongly
          LEA       M_TYP,A0
          MOVE.B    D7,(A0)   keep proc type (as in $A1 of sys vars)
          CMPI.W    #12,D0    68060? . . .
          BLT       BEG2      . . . no
;
; The 68060 FPU needs special treatment because the FSAVE frames
; have a different format from those of other FPUs. The following
; code alters 'S_FRAME' to 'S_FRAME+2' in two places.
; NB 'CLR_CACH' flushes and invalidates caches just before FPSAVE
; exits. This should enable the following changes to work.
;
          LEA       2+INST1_40,A0
          ADDQ.W    #2,(A0)
          LEA       4+INST2_40,A0
          ADDQ.W    #2,(A0)
;
; Get space for header
;
BEG2
          MOVE      #0,SR     back to user mode
          MOVEQ     #$40,D1   space for header
          MOVEQ     #0,D2     BASIC
          MOVEQ     #MT_ALCHP,D0
          TRAP      #1
          TST.L     D0
          BNE       OUT       ---->
          LEA       HEAD_AD,A1
          MOVE.L    A0,(A1)   keep place of header
;
; This code allows for the existence of four different FPSP's with
; filenames
;
;  Filename
;  FPSP_81
;  FPSP_82
;  FPSP_40
;  FPSP_60
;
; Each FPSP has an embedded version number <a.b> from <1.1> to <255.255>.
; FPSAVE checks that <a.b> is not less than the version number recorded in
; FPSAVE. Later versions of FPSAVE may thus disallow some versions of
; the FPSP to be loaded.
;
; The files have the following format:
; A 64 byte header
;
;   i.  Relative longword offset to the set of branches
;  ii.  A longword pointer to the real_fline exception
; iii.  A spare longword
;  iv.  A 2 byte version number
;   v.  A 2 byte identifier
;  vi.  40 bytes of pointers to the 10 exception entries
; vii.  A spare longword
;viii.  A set of branches to the entries in the FPSP itself
;
; Followed by the FPSP itself
;
; Some details of the header items are:
;  ii.  One long word offset to the real_fline routine for insertion of
;       the original F_line vector.
;
;  vi.  Up to ten longword pointers giving the position of
;       the exception routine in the FPSP relative to top of
;       the header in the following order. A zero pointer
;       indicates that the vector should not be set.
;
;       Type of vector     No    Offset (in VBR)
;                                   (Hex)
;            F-line        11         2C
;             BSUN         48         C0
;             INEX         49         C4
;               DZ         50         C8
;             UNFL         51         CC
;            OPERR         52         D0
;             OVFL         53         D4
;             SNAN         54         D8
;  Unimplemented data type 55         DC  (not for 68881/2)
;            EFFADD        60         F0  (only 68060)
;
;
;viii.  Each branch to the routine in the FPSP starts on an 8 byte
;       boundary (relative to the start) although the branches themselves
;       can be byte, word or long. The functions and the position of the
;       branch relative to the start of the list are:
;
;   Rel. address (hex)     Function
;          0               FMOVECR
;          8               FACOSS
;         10               FACOSD
;         18               FACOSX
;         20               FASINS
;         28               FASIND
;         30               FASINX
;         38               FATANS
;         40               FATAND
;         48               FATANX
;         50               FATANHS
;         58               FATANHD
;         60               FATANHX
;         68               FCOSS
;         70               FCOSD
;         78               FCOSX
;         80               FCOSHS
;         88               FCOSHD
;         90               FCOSHX
;         98               FETOXS
;         A0               FETOXD
;         A8               FETOXX
;         B0               FETOXM1S
;         B8               FETOXM1D
;         C0               FETOXM1X
;         C8               FGETEXPS
;         D0               FGETEXPD
;         D8               FGETEXPX
;         E0               FGETMANS
;         E8               FGETMAND
;         F0               FGETMANX
;         F8               FLOG10S
;        100               FLOG10D
;        108               FLOG10X
;        110               FLOG2S
;        118               FLOG2D
;        120               FLOG2X
;        128               FLOGNS
;        130               FLOGND
;        138               FLOGNX
;        140               FLOGNP1S
;        148               FLOGNP1D
;        150               FLOGNP1X
;        158               FMODS
;        160               FMODD
;        168               FMODX
;        170               FREMS
;        178               FREMD
;        180               FREMX
;        188               FSCALES
;        190               FSCALED
;        198               FSCALEX
;        1A0               FSINS
;        1A8               FSIND
;        1B0               FSINX
;        1B8               FSINCOSS
;        1C0               FSINCOSD
;        1C8               FSINCOSX
;        1D0               FSINHS
;        1D8               FSINHD
;        1E0               FSINHX
;        1E8               FTANS
;        1F0               FTAND
;        1F8               FTANX
;        200               FTANHS
;        208               FTANHD
;        210               FTANHX
;        218               FTENTOXS
;        220               FTENTOXD
;        228               FTENTOXX
;        230               FTWOTOXS
;        238               FTWOTOXD
;        240               FTWOTOXX
;        248               FABSS
;        250               FABSD
;        258               FABSX
;        260               FADDS
;        268               FADDD
;        270               FADDX
;        278               FDIVS
;        280               FDIVD
;        288               FDIVX
;        290               FINTS
;        298               FINTD
;        2A0               FINTX
;        2A8               FINTRZS
;        2B0               FINTRZD
;        2B8               FINTRZX
;        2C0               FMULS
;        2C8               FMULD
;        2D0               FMULX
;        2D8               FNEGS
;        2E0               FNEGD
;        2E8               FNEGX
;        2F0               FSQRTS
;        2F8               FSQRTD
;        300               FSQRTX
;        308               FSUBS
;        310               FSUBD
;        318               FSUBX
;
; I_TO_Q and Q_TO_I are only on the later versions of FPSP
;
;	 320		   I_TO_Q converts IEEE to QDOS fp
;	 328		   Q_TO_I converts QDOS fp to IEEE
;
; When the FPSP is loaded its header is dropped so that the
; first entry is the branch to FMOVECR.
;
; A program wishing to use the FPSP should set A0 either zero or to the
; address of a table of user entries to enabled FP exceptions and then branch
; to the subroutine whose address is at $D8(sys vars).
;
; The table of user entries if it exists must consist of a set of word offsets
; of each routine from the start of the table. The order of exceptions is DZ,
; INEX, OVFL, UNFL, SNAN, OPERR, BSUN, F-line, Unsupported data type and TRACE.
;
; If the subroutine call is successful, a save area is set up if needed and the
; address of the start of the list of branches of the FPSP will be returned in
; A1. Otherwise D0 contains the error code. All other registers are preserved.
;
S_A0        EQU        4      Address of saved A0 relative to LINK of save area
S_PC        EQU        8                       PC
S_ID        EQU        -8                  program ID
S_RE        EQU        $E     Exception table address
S_FRAME     EQU        $7E    Address of FSAVE frame
S_FPcr      EQU        $12               FPcr
S_FPn       EQU        $1E               FPn
SYS_FPU     EQU        $D0    Address of FPU status byte
SYS_FPSZ    EQU        $D2    Address of maximum size of FSAVE frame
SV_FX       EQU        $8E    Additional size of save area
SYS_FPSL    EQU        $D4    Start of save area list
SYS_CLFP    EQU        $D8    Address to access the FPSP
H_L       EQU       64        Length of FPSP header
H_VER     EQU       12        position of version in header
H_ID      EQU       14        position of ID in header
H_FL      EQU       4         position of real-fline pointer in header
H_VS      EQU       16        position of start of 10 vectors
;
        SWAP       D7
        MOVE.B     D7,D0
;
; D0 bits 0 to 2:                  Action
;
;      -1 for no FPU            -> 'Unimplemented' error
;       0     don't know        -> 'Unimplemented' error
;       1     68881             ->  load FPSP_81
;       2     68882             ->  load FPSP_82
;       3      ???              -> 'Unimplemented' error
;       4     68040             ->  load FPSP_40
;       5      ???              ->       "
;       6     68060             ->  load FPSP_60
;       7      ???              ->       "
;
; Codes 3, 5 and 7 are not set by PROC and so should not occur.
;
; D0 bit 3 is set to indicate an FPSP_xx (library version) loaded.
;
        BMI        ERROR5     No FPU
        BEQ        ERROR5     This shouldn't happen!
;
; Stage 2. (It is assumed that we have a 68020+)
;
        ANDI.W     #7,D0
        MOVE.W     (T_SIZE-2,D0.W*2),SYS_FPSZ(A5)  set max size of fsave area
          MOVE.W    MIN_FP-2(D0.W*2),D6   minimum FPSP version
          LEA       MESD,A0
          MOVE.W    MN1-2(D0.W*2),(A0)  set '81' etc in message
        LEA        FILE_TB,A0
        ADDA.W     -2(A0,D0.W*2),A0        set filename
        MOVEQ      #1,D3        OPEN_IN
        BSR        OPENFILE
        BNE        ERROR1       ---->
        MOVEA.L    A0,A4        keep file ID in A4
        MOVEA.L    HEAD_AD,A0
        MOVEA.L    A0,A5        keep base address in A5
        MOVEQ      #$40,D2      buffer size
        MOVEQ      #-1,D3
        MOVEA.L    A0,A1
        MOVEA.L    A4,A0
        MOVEQ      #FS_HEADR,D0
        TRAP       #3           header to base
        TST.L      D0
        BNE        ERROR2       ----> (release space & close file)
        CMPI.B     #1,5(A5)
        BNE        ERROR2       ----> wrong type of file
        MOVE.L     (A5),D7      length of file
        MOVEA.L    A5,A1        reset base address
        MOVEQ      #H_L,D2       to read the FPSP header
        MOVEQ      #IO_FSTRG,D0
        TRAP       #3
        TST.L      D0
        BNE        ERROR2       ---->
;
; The VBR is to be set after the FPSP
; To ensure that this starts at a long word boundary
; D4.L is set to the number of padding bytes needed ('bit')
;
        BFEXTU     D7{30:2},D1  last two bits
        SUBQ.L     #8,D1
        NEG.L      D1
        BFEXTU     D1{30:2},D4  D4.L = 0 to 3
        MOVE.L     D4,D1        'bit'
        ADD.L      D7,D1        +file length
        ADDI.L     #$400-H_L,D1  space for FPSP & VBR
        MOVEQ      #0,D2        BASIC
        MOVEQ      #MT_ALCHP,D0
        TRAP       #1
        TST.L      D0
        BNE        ERROR2       ---->
          LEA       FPSP_AD,A3
          MOVE.L    A0,(A3)   keep FPSP address
        MOVEA.L    A0,A3
        MOVEQ      #-H_L,D2
        ADD.L      D7,D2        length of remainder of file
        MOVEQ      #-1,D3       timeout
        MOVEA.L    A4,A0        file ID
          MOVEA.L   A3,A1
          MOVE.W    D6,-(A7)  keep minimum FPSP version
        MOVE.L     D2,D6
        ANDI.W     #$3FFF,D2    remainder after division by 2^15
          BEQ       LP9         no remainder
        BSR        G_MORE       get remainder bytes in
        BNE        ERROR3       ---->
LP9     MOVE.W     #$4000,D2
        BFEXTU     D6{1:17},D5  count of 2^15's
        BFEXTU     D5{0:16},D6  top count should be 0!
        BRA        LP4
LP5     BSR        G_MORE
LP4     BNE        ERROR3       ---->
        DBF        D5,LP5
        MOVEQ      #IO_CLOSE,D0
        TRAP       #2
;
; A4 is now free
;
; check that FPSP version is acceptable
;
          CMPI.W    #'gw',H_ID(A5)      check validity of FPSP
          BNE       ERROR9    ---->     not an FPSP
          MOVE.W    H_VER(A5),D0        version number
          CMP.W     (A7)+,D0
          BCS       ERROR4    ---->     version too early
;
; FPSP OK
;
; The version number is a.b which is put in the message
; A3 is the address of the FPSP
; A5 is the address of the FPSP header
; D0.W contains 'a' and 'b' in its two bytes.
;
          LEA       MESF,A2             set to the end of 'a'
          BSR       PUT_V               sets <a.b> in the message
;
        TRAP       #0
        ORI        #$700,SR     stop interrupts
        MOVEC      VBR,A2
        LEA        -H_L(A3,D4.L),A1
        LEA        (A1,D7.L),A1 -> new VBR
        MOVEA.L    A1,A4
        MOVE.W     #$400/4-1,D0 count-1
LP1     MOVE.L     (A2)+,(A1)+
        DBF        D0,LP1       copy old VBR info to new place
        MOVEA.L    A4,A1        reset VBR address
        MOVEC      A1,VBR
        LEA        H_VS(A5),A2          start address of vector offsets
        MOVE.L     (A2)+,D1     offset for Vector 11 . .
        LEA        -H_L(A3),A0  . . from FPSP-H_L
        ADD.L      A0,D1
          MOVE.L    $2C(A1),D0          old F-line vector (11)
        MOVE.L     D1,$2C(A1)   set new Vector 11
          MOVE.L    H_FL(A5),D1 offset to real_fline1
          ADD.L     A0,D1     absolute address
          MOVE.L    D0,(D1.L) now 'real_fline' -> the original vector
        LEA        $C0(A1),A1
        MOVEQ      #7,D0
LP2     MOVE.L     (A2)+,D1     set Vectors
          BNE       LP7       there is a vector to set
          ADDQ.L    #4,A1     go to next item - no vector
          BRA       LP6
LP7     ADD.L      A0,D1            48
        MOVE.L     D1,(A1)+         to
LP6     DBF        D0,LP2           55
          MOVE.L    (A2),D1   last vector
          BEQ       LP8      nothing to set
          ADD.L     A0,D1     get address
          MOVE.L    D1,$10(A1)  set vector @ $F0
LP8     LEA        DZ_AD,A1
        MOVE.L     $C8(A4),(A1) set address of DZ
        BSR        SET_TRAP     prime the FPU to trap 1st use
        MOVE       #0,SR        back to user mode
;
; Now release the header space
;
; A5 -> Space for header
;
          MOVEA.L   A5,A0
          MOVEQ     #MT_RECHP,D0
          TRAP      #1
;
; Stage 3.
; Link in FP_EX to the scheduler list
;
LP10    TRAP       #0
        ORI        #$700,SR
        LEA        8+FP_EX,A0   address of routine ...
        MOVE.L     A0,-4(A0)    ... in link
        LEA        -8(A0),A0    address of link
        MOVEQ      #MT_LSCHD,D0
        TRAP       #1
          BSR       CLR_CACH
        MOVE       #0,SR
;
; Print message
;
	LEA	MES,A1
OUT1	SUBA.L	A0,A0	channel 0
	MOVEA.W	UT_MTEXT,A2
	JSR	(A2)
;
; Exit
;
        MOVEQ      #0,D0
          RTS
;
MES       DC.W      MESE-MES-2
          DC.B      "George Gwilt's FPSAVE v",VERSION," with FPSP_"
MESD      DC.B      "   v"
MESF      DC.B      "       ",10
MESE      DS.B      0
MES1      DC.W      MES1E-MES1-2
          DC.B      'Aborted - FPSP version '
MES1D     DC.B      '       too early',10
MES1E     DS.B      0
MES2      DC.W      MES2E-MES2-2
          DC.B      'Aborted - invalid FPSP',10
MES2E     DS.B      0
MES3	DC.W	MES3E-MES3-2
	DC.B	'FPSAVE already loaded',10
MES3E	DS.B	0
MES4	DC.W	MES4E-MES4-2
	DC.B	'FPSAVE not loaded - SYS_FPU in use',10
MES4E	DS.B	0
MES5	DC.W	MES5E-MES5-2
	DC.B	'Aborted - SYS_FPU or SYS_MMU set wrongly',10
MES5E	DS.B	0
;
ERROR5    MOVEQ     #-1,D0              no FPU or bad FPSP
ERROR1    MOVE.L    D0,D4
ERROR6    MOVEQ     #MT_INF,D0
          TRAP      #1
          CLR.L     SYS_CLFP(A0)
          CMPI.B    #-1,SYS_FPU(A0)
          BEQ       ERROR8              leave '-1 = no FPU'
          BCLR      #3,SYS_FPU(A0)      mark FPSP not loaded
ERROR8    MOVEA.L   HEAD_AD,A0          release HEADER space
          MOVEQ     #MT_RECHP,D0
          TRAP      #1
          MOVE.L    D4,D0
OUT       RTS                           back to BASIC
;
ERROR3    MOVE.L	D0,D4
	MOVEQ	#IO_CLOSE,D0
	TRAP	#2
	ADDQ      #2,A7
ERROR7	MOVEA.L   FPSP_AD,A0          release FPSP/VBR space
          MOVEQ     #MT_RECHP,D0
          TRAP      #1
          BRA       ERROR6
;
; D0.W contains the faulty version number
;
ERROR4    LEA       MES1D,A2  end of 'a'
          BSR       PUT_V
          LEA       MES1,A1
ERROR10   SUBA.L    A0,A0
          MOVEA.W   UT_MTEXT,A2
          JSR       (A2)
          MOVEQ     #0,D4
          BRA       ERROR7
;
ERROR2  MOVEA.L    A4,A0
        MOVEQ      #IO_CLOSE,D0
        TRAP       #2
        BRA        ERROR5
;
ERROR9    ADDQ.L    #2,A7      - invalid FPSP -
          LEA       MES2,A1
          BRA       ERROR10
;
G_MORE  MOVEQ      #IO_FSTRG,D0
        TRAP       #3
        TST.L      D0
        RTS
;
E1        MOVEA.L   A4,A0     ID to A0
E2        MOVEQ     #IO_CLOSE,D0
          TRAP      #2
E3        MOVEQ     #-19,D0   unimplemented
E4        RTS
;
;
HEAD_AD  DS.L      1
FPSP_AD   DS.L      1
;
; D0.W contains 'a' and 'b' in its two bytes.
; A2 -> the start of the answer
;
PUT_V     MOVE.W    D0,D2
          BFEXTU    D2{16:8},D0         D0.L has 'a'
          LINK      A4,#-16             space to get 'b'
          BSR       P2UT
          MOVE.B    #'.',(A2)+
          MOVE.B    D2,D0               'b' is in D0.L
          BSR       P2UT
          UNLK      A4
          RTS
;
P2UT      MOVEA.L   A4,A0               end of space
          BSR       LTOD
          MOVE.L    A4,D1
          SUB.L     A0,D1               length of 'b'
          ANDI.W    #3,D1               (an unnecessary belt & braces!?)
          BRA       PUT_V1
PUT_V2    MOVE.B    (A0)+,(A2)+
PUT_V1    DBF       D1,PUT_V2
          RTS
;
; LTOD sets ASCII decimal for D0.L in
; a buffer indicated by A0 (and uses D1).
;
; At entry:
;   D0.L contains the (positive) number.
;   A0 points to the end of the area to
;   contain the ASCII decimal.
;
; At exit:
;   D0 = 0
;   A0 points to the start of the number.
;
LTOD      SWAP      D0
          BEQ.S     LTODW2
          MOVE.W    D0,D1
          BEQ.S     LTODW
          EXT.L     D1
          DIVU      #10,D1
          SWAP      D1
          MOVE.W    D1,D0
          SWAP      D0
          DIVU      #10,D0
          SWAP      D0
          ADDI.W    #48,D0
          MOVE.B    D0,-(A0)
          SWAP      D0
          MOVE.W    D0,D1
          MOVE.L    D1,D0
          BRA.S     LTOD
LTODW1    DIVU      #10,D0
          SWAP      D0
LTODW2    ADDI.W    #48,D0
          MOVE.B    D0,-(A0)
          CLR.W     D0
LTODW     SWAP      D0
          BNE.S     LTODW1
          RTS
;
; This clears the cache depending on the processor
;
CLR_CACH  BFEXTU    M_TYP{1:3},D0
          LEA       CL_T,A0             jump table
          ADDA.W    (A0,D0.W*2),A0
          JMP       (A0)
;
CL_T      DC.W      CL_0-CL_T           68000/8
          DC.W      CL_0-CL_T           68010
          DC.W      CL_20-CL_T          68020
          DC.W      CL_20-CL_T          68030
          DC.W      CL_40-CL_T          68040
          DC.W      CL_0-CL_T             -
          DC.W      CL_40-CL_T          68060
          DC.W      CL_0-CL_T             -
;
CL_20     MOVEC     CACR,D0
          MOVEQ     #8,D1
          MOVEC     D1,CACR   clear cache
          MOVEC     D0,CACR   replace CACR
CL_0      RTS
;
CL_40     CPUSHA    BC        clear both caches
          CINVA     IC        invalidate instruction cache (for 68060)
          RTS
;
; CALL_FPSP is the routine for accessing the FPSP.
;
; +If a save area does not exist for this Job it sets one up.
; +If A0.L is non zero it sets its value in the save area.
; +It sets A1 to point to the start of the FPSP.
;
CALL_FPSP MOVEM.L   D1-2/D4-6/A0/A6,-(A7)
          TRAP      #0
          MOVE       SR,D4
        ORI        #$700,SR
        MOVE.L     A0,D5        user exception address
          MOVEQ     #MT_INF,D0
          TRAP      #1
          MOVE.L    D1,D6       ID
          MOVEA.L   A0,A6       sys vars
;
; Now see if save area is needed
;
        MOVE.L     SYS_FPSL(A6),D0        pointer to save blocks
CF_7      BEQ        CF_5                 no area - set one up
        CMP.L      -8(D0.L),D6            this Job? ...
        BEQ        CF_6                ... yes - don't set
        MOVE.L     (D0.L),D0              next save area
        BRA        CF_7
;
; set up an area
;
CF_5      MOVE.L     D6,D1              ID
        BSR        FP_SSVE              sets D0 = save area link or error
        BMI        CF_4                 ---->
;
CF_6      TST.L     D5
          BEQ       CF_8
          MOVE.L    D5,S_RE(D0.L)       set user exception table
CF_8      MOVEQ     #0,D0
          MOVEA.L   FPSP_AD,A1          set pointer to FPSP in A1
CF_4      MOVE      D4,SR
          ANDI      #$DFFF,SR
        MOVEM.L     (A7)+,D1-2/D4-6/A0/A6
        RTS
;
; FP_SSVE 1. sets up FPU save area
;         2. resets DZ vector to normal
;         3. clears FPU
;         4. turns off Trap
;
*********************************************************
* SAVE AREA FORMAT                                      *
*                                                       *
* Relative address    Item                   Length     *
*     (hex)                                             *
*       0             Length of area           L        *
*       4             Pointer to CLOSE - $C    L        *
*       8             Owner job ID             L        *
*       C                 -                    L        *
*      10             Link to next area        L        *
*      14             A0 of owner job          L        *
*      18             PC of owner job          L        *
*      1C                 -                             *
*      1E             Pointer to user . . .             *
*                     . . . exception table    L        *
*      22             FPCR, FPSR and FFIAR    3L        *
*      2E             FP0 - FP7              96B        *
*      8E             FSAVE frame             ?         *
*                                                       *
*    The length of FSAVE frame depends on the FPU       *
*    type and version as well as the type of frame.     *
*    For the 68060 all frames are 12 bytes long. For    *
*    other FPUs the length can be determined from the   *
*    first word of the frame. If this is zero the frame *
*    is 4 bytes long (a NULL frame). Otherwise the      *
*    length-4 is given in the second byte of the frame. *
*                                                       *
*    The maximum size of frame is set in word $D2 of    *
*    System Variables.                                  *
*********************************************************
;
; On entry to FP_SSVE:
;  D1 = Job ID
;  A6 -> System Variables
;
; On exit:
;  A0 = start of ALCHP'd area
;  D0 = start of save area (A0 + $10) or error (OM)
;
; Uses D1
;
FP_SSVE MOVEM.L    D3/D6/A1-3,-(A7)
        MOVE.L     D1,D6
        MOVE.W     #SV_FX,D1        fixed amount of save area
        ADD.W      SYS_FPSZ(A6),D1  maximum size of FSAVE area
        EXT.L      D1
        MOVEA.W    MM_ALCHP,A2
        JSR        (A2)
        BMI        FP_SSVE2         ---->
        LEA        $10(A0),A1       start of usable area
        MOVE.L     SYS_FPSL(A6),(A1)        link in
        MOVE.L     A1,SYS_FPSL(A6)
        MOVE.L     A1,D0
        MOVE.L     D6,8(A0)        ID
        LEA        FP_CLOSE-$C,A2
        MOVE.L     A2,4(A0)        set address of CLOSE
        MOVEC      VBR,A1
        MOVE.L     DZ_AD,$C8(A1)   reset DZ vector
          FRESTORE  NULL           reset FPU
        LEA        FP_TRAP,A1
        BCLR       #0,(A1)         turn off trap indicator
FP_SSVE1:
        MOVE.L     D6,D1           replace ID in D1
        TST.L      D0              set codes
        MOVEM.L    (A7)+,D3/D6/A1-3
        RTS
FP_SSVE2:
        MOVEQ      #-3,D0
        BRA        FP_SSVE1
;
; FP_REST is called just after a program using the FPU is
; restarted by the scheduler. It restores the FPU state
; from the program's save area and switches off the FP Trap.
;
; At entry A0 -> save area
;
FP_REST:
          TRAP      #0
          MOVEM.L   A1,-(A7)  (does not alter CCR)
          MOVE      SR,-(A7)
          ORI       #$700,SR
          FRESTORE  NULL
          MOVEC     VBR,A1
          MOVE.L    DZ_AD,$C8(A1)       Reset the true DZ vector routine
INST1_40  TST.B     S_FRAME(A0)         * Altered to S_FRAME+2 if 68060 *
          BEQ       FP_REST1
          FMOVEM.L  S_FPcr(A0),FPIAR/FPSR/FPCR
          FMOVEM.X  S_FPn(A0),FP0-7
          FRESTORE  S_FRAME(A0)
FP_REST1:
          LEA       FP_TRAP,A1
          BCLR      #0,(A1)             turn off the Trap indicator
          MOVEC     USP,A1
          MOVE.L    S_PC(A0),-(A1)      put true PC on user stack
          MOVE.W    (A7)+,-(A1)         put CCR on user stack
          MOVEA.L   S_A0(A0),A0         reset the true A0
          MOVEC     A1,USP              set the user stack
          MOVE      (A1),SR             (possibly allows interrupts)
          MOVEA.L   (A7)+,A1
          ANDI      #$DFFF,SR           back to user mode
          RTR                           go to the true PC and set CCR
;
; Routine called when the job owning the save area is removed
;
CLOSE   LEA        SYS_FPSL(A6),A1
        LEA        $10(A0),A0
        MOVEA.W    UT_UNLNK,A2
        JSR        (A2)
        LEA        -$10(A0),A0
        MOVEA.W    MM_RECHP,A2
        JSR        (A2)
          TST.L     SYS_FPSL(A6)        any save blocks? . .
          BEQ       CLOSE_1            . . no - see that trap is set
          RTS
CLOSE_1   LEA       FP_TRAP,A0
          BSET      #0,(A0)             mark 'trap on'
          BEQ       SET_TRAP            it must be set . .
          RTS                           . . it was on - don't set
;
; Routine linked in to the scheduler list
;
; It sets the PC and A0 of the current job (if FPU user)
;  PC -> RESTORE and A0 -> save area
;
; A3 = address of link - $10
; A6 -> System Variables
;
FP_EX   DC.L       0,0
;
        TST.L      SYS_FPSL(A6)
        BEQ        FP_EX1          nothing to do
          MOVEM.L   D0-1/D4/A0-2,-(A7)
        MOVE       SR,D4           keep SR
        ORI        #$700,SR        stop interrupts
        LEA        FP_TRAP,A0
        BSET       #0,(A0)         set Trap on . .
        BNE        FP_EX3          . . it was already
        MOVE.L     SV_JBPNT(A6),D1 current job
          BEQ       FP_EX2              SMSQE 2nd time round
;                                       (but we shouldn't get here!)
          BMI       FP_EX2              apparently this can happen!
          MOVE.L    (D1.L),D0
        BMI        FP_EX2          gone!!
        MOVEA.L    D0,A1           job base
        SUB.L      SV_JBBAS(A6),D1
        BMI         FP_EX2    faulty job pointer!
        LSR.L      #2,D1           job number
          CMP.W     SV_JBMAX(A6),D1
          BGT       FP_EX2    can't be a job
        BSR        FP_BLK
        TST.L      D0
        BEQ        FP_EX2          not FPU
        LEA        FP_REST,A2      new PC
        CMP.L      JB_PC(A1),A2
        BEQ        FP_EX2          already done
        MOVE.L     JB_A0(A1),S_A0(D0.L)
        MOVE.L     JB_PC(A1),S_PC(D0.L)
;       MOVE.W    JB_SR(A1),S_SR(D0.L)
        MOVE.L     D0,JB_A0(A1)
        MOVE.L     A2,JB_PC(A1)
;
; Now save FPU state
;
          FSAVE      S_FRAME(D0.L)
INST2_40  TST.B      S_FRAME(D0.L)   * Altered to S_FRAME+2 if 68060 *
          BEQ        FP_EX2          null frame
          FMOVEM.L   FPCR/FPSR/FPIAR,S_FPcr(D0.L)
          FMOVEM.X   FP0-7,S_FPn(D0.L)
;
; Set Trap
;
FP_EX2  BSR        SET_TRAP
FP_EX3  MOVE       D4,SR
          MOVEM.L   (A7)+,D0-1/D4/A0-2
FP_EX1  RTS
;
SET_TRAP:
        MOVEC      VBR,A0
        LEA        START,A1
        MOVE.L     A1,$C8(A0)
          FRESTORE  NULL      reset FPU
        FMOVE.L    #$400,FPCR      enable DZ
        FMOVE.B    #1,FP0
        FDIV.B     #0,FP0          set DZ
        RTS
;
NULL      DC.L      0,0,0     FRESTOREs on all 68xxx
;
DZ_AD   DS.L        1              space for real DZ address
FP_TRAP DC.B        0              1 = FP trap set
FP_CLOSE:
        DS.L        1              address of 'CLOSE' set @ start
;
; Trap sprung - set up a save area and turn off Trap
;
START   MOVEM.L    D0-3/A0-3/A6,-(A7)
        ORI        #$700,SR
        MOVEQ      #MT_INF,D0
        TRAP       #1
        MOVEA.L    A0,A6
        BSR        FP_SSVE
        BMI        START1          ----> FATAL!
;
START2
        MOVEM.L    (A7)+,D0-3/A0-3/A6
START_1 RTE                   this resets SR
;
E_MESS  DC.W       17
        DC.B       'No FPU save area',10
;
START1  LEA        E_MESS,A1
        SUBA.L     A0,A0
          MOVE.W    D1,-(A7)
        MOVEA.W    UT_MTEXT,A2
        JSR        (A2)
          MOVE.W    (A7)+,D1
          BEQ       START2              don't try to stop BASIC
        MOVEA.L    SV_JBBAS(A6),A0
        CLR.B      ([A0,D1.W*4],JB_PRINC)        attempt to suspend job
        BRA        START2
;
PROC      LIB       WIN1_ASS_DJW_PROCTYPE_X1_BIN
;
T_SIZE    DC.W      $B8,$D8,0,$64,$64,$C,$C
;
; minimum version numbers acceptable
;
MIN_FP    DC.W      $101      68881
          DC.W      $101      68882
          DC.W      0
          DC.W      $101      68040
          DC.W      0
          DC.W      $101      68060
          DC.W      0
;
MN1       DC.W      '81','82','  ','40','  ','60','  '
;
; Entry: D1.W = no of job
;        A1   = prog base
;        A6 -> sys vars
;
; Exit:  D0 = address of save area or 0 if none
;
FP_BLK    MOVE.L    SYS_FPSL(A6),D0
          BEQ       FP_BLK1        none
          SWAP      D1
          MOVE.W    JB_TAG(A1),D1
          SWAP      D1             job ID
FP_BLK2   CMP.L     S_ID(D0.L),D1
          BEQ       FP_BLK1        found
          MOVE.L    (D0.L),D0      to next block
          BNE       FP_BLK2        more to come
FP_BLK1   RTS
;
;                          FROM
;                       GW_DJW1_ASM
;                       ~~~~~~~~~~
;   Routines added to GWASS assembler by Dave Walker
;
;   AMENDMENT HISTORY
;   ~~~~~~~~~~~~~~~~~
;   14 Sep 1996     DJW   - First version.
;   24 Sep 1996     GDG   - Second version - TK2_BUFFER[X] in DATA SPACE
;                                          - More defaults
;
;======================================================================
;                       STRING HANDLING
;
;   Useful string handling routines.
;
;   - These are based on C68 C style routines adapted to handle QL strings.
;   - Parameters are passed on the stack.
;   - All registers are preserved.
;   - The calling routines must tidy the stack.
;----------------------------------------------------------------------

;   Copy a QL string
;       4(A7) = Target
;       8(A7) = Source

QSTRCPY
            MOVEM.L     D0/A0-A1,-(A7)              ; save regosters used
            MOVE.L      12+4(A7),A1                 ; Target address
            MOVE.L      12+8(A7),A0                 ; Source address
            MOVE.W      (A0),D0                     ; Length to copy
            MOVE.W      (A0)+,(A1)+                 ; Copy length field
            BRA         QSTRCPY2            ; Remove if NULL byte at end wanted
QSTRCPY1    MOVE.B      (A0)+,(A1)+                 ; Copy a byte
QSTRCPY2    DBRA        D0,QSTRCPY1                 ; loop until finished
            MOVEM.L     (A7)+,D0/A0-A1              ; restore saved registers
            RTS

;   Concatenate a QL string onto another one
;       4(A7) = Target
;       8(A7) = Source

QSTRCAT
            MOVEM.L     D0/A0-A1,-(A7)
            MOVE.L      12+4(A7),A1                 ; Target address
            MOVE.L      12+8(A7),A0                 ; Source address
            MOVE.W      (A1),D0                     ; get old length
            ADD.W       (A0),D0                     ; Calculate new length
            MOVE.W      D0,(A1)+                    ; Store new length
            SUB.W       (A0),D0                     ; Reset to old length
            ADDA.W      D0,A1                       ; skip over current data
            MOVE.W      (A0)+,D0                    ; get length to copy
            BRA         QSTRCPY2


;   Copy a QL string with maximum length check
;       4(A7).L  = Target
;       8(A7).L  = Source
;       12(A7).W = Maximum length

QSTRNCPY
            MOVEM.L     D0-D1/A0-A2,-(A7)
            MOVE.L      20+4(A7),A1             ; Target address
            MOVE.L      A1,A2                   ; ... copied to A2
            MOVE.L      20+8(A7),A0             ; Source address
            MOVE.W      20+12(A7),D1            ; Maximum length
            MOVE.W      (A0)+,D0                ; Length to copy
            CLR.W       (A1)+                   ; Clear target length
            BRA         QSTRNCPY2
QSTRNCPY1   CMP.W       (A2),D1                 ; Check max length  not reached
            BEQ         QSTRNCPY3               ; If so exit immediately
            ADDQ.W      #1,(A2)                 ; Update length
            MOVE.B      (A0)+,(A1)+             ; ... and copy byte
QSTRNCPY2   DBRA        D0,QSTRNCPY1            ; Loop until finished
QSTRNCPY3   MOVEM.L     (A7)+,D0-D1/A0-A2
            RTS

;   Concatenate a QL string onto another one with maximum length check
;       4(A7) = Target
;       8(A7) = Source

QSTRNCAT
            MOVEM.L     D0-D1/A0-A2,-(A7)
            MOVE.L      20+4(A7),A1             ; Target address
            MOVE.L      A1,A2                   ; copied to A2
            MOVE.L      20+8(A7),A0             ; Source address
            MOVE.W      20+12(A7),D1            ; Maximum length
            MOVE.W      (A1)+,D0                ; Existing length
            ADDA.W      D0,A1                   ; Skip over source length
            MOVE.W      (A0)+,D0                ; Length to copy
            BRA         QSTRNCPY2               ; Join QSTRNCPY code

;======================================================================
;                          FILE HANDLING
;
;   Friendly versions of file open/delete that will
;   allow use of TK2 directories in names.
;
;   These routines assume that the parameters have been set up
;   for the respective TRAP #2 call.  They can then be called
;   in place of the TRAP #2 routine.
;-----------------------------------------------------------------------

;   When we OPEN a file we want to try the open in
;   the following sequence of events:
;     - The name exactly as supplied
;     - If that fails, the name with the DATA_USE directory
;       added to the front.
;     - If that fails, and we are doing a read the name with
;       the PROG_USE added to the front.  We do not do this
;       with WRITE types of open as we only want those in the
;       current directory if a path is not explicitly specified.

OPENFILE
            MOVEQ    #IO_OPEN,D0
            MOVEQ    #-1,D1
            MOVEM.L  D0/A0,-(A7)                    ; Save name pointer

;       We start by trying the name exactly as passed

            TRAP    #2                          ; Try operation
            TST.L   D0                          ; OK?
            BEQ     OPENEXIT                    ; YES, jump

;       If we failed with the first try, we now want
;       to try with the DATA_USE directory added

            MOVEM.L 0(A7),D0/A0                 ; restore saved registers
            BSR     DATA_USE
            TRAP    #2
            TST.L   D0
            BEQ     OPENEXIT

;       If the DATA_USE did not work and we are
;       attempting to read a file, then try the
;       PROG_USE as well.

            CMPI.B  #1,D3                       ; OPEN_IN ?
            BGT     OPENEXIT                    ; ... NO, then give up
            MOVEM.L 0(A7),D0/A0                 ; restore saved registers
            BSR     PROG_USE
            TRAP    #2

OPENEXIT    ADDA.W  #2*4,A7                     ; remove saved values
            TST.L   D0                          ; set condition code
            RTS

;   When we DELETE a file we want to try the delete in
;   the following sequence of events:
;     - The name exactly as supplied
;     - If that fails, the name with the DATA_USE directory
;       added to the front.

DELETEFILE
            MOVEQ   #IO_DELET,D0
            MOVEQ   #-1,D1
            MOVEM.L D0/D1/A0,-(A7)              ; Save registers

;       We start by trying the name exactly as passed

            TRAP    #2                          ; Try operation
            TST.L   D0
            BEQ     DELEXIT

;       If we failed with the first try, we now want
;       to try with the DATA_USE directory added

            MOVEM.L 0(A7),D0/D1/A0              ; restore saved registers
            BSR     DATA_USE
            TRAP    #2

DELEXIT     ADDA.W  #3*4,A7                     ; remove saved values
            TST.L   D0                          ; set confition code
            RTS

TK2_BUFFER  DS.B    52                  ; Buffer to hold expanded filename

;       Subroutine to get the current system
;       variables address.  This is returned
;       in A1 (which would have been corrupted
;       in both the OPEN and DELETE calls anyway)
;       with all other registers being preserved.

TK2_VALUES
            MOVEM.L D0-D2/A0,-(A7)           ; save registers that get corrupted
            MOVEQ   #0,D0                    ; SMS.INFO
            TRAP    #1
            MOVE.L  A0,A1                    ; Move result to A1
            MOVEM.L (A7)+,D0-D2/A0           ; Restore registers
            RTS

;       Get the DATA_USE value, and build up a name
;       which has this at the front followed by the
;       users name and try that.

DATA_USE
            BSR     TK2_VALUES
            MOVE.L  $B0(A1),A1                  ; Get DATA_USE pointer
DATA_USE1
            CMPA.L  #0,A1                       ; Is value NULL?
            BEQ     DATA_USE9                   ; ... YES, exit immediately

            MOVE.L  A1,-(A7)                    ; Source
            PEA     TK2_BUFFER                  ; Target
            BSR     QSTRCPY
            ADDQ.L  #8,A7                       ; Remove parameters

            MOVE.W  #50,-(A7)                   ; Max length
            MOVE.L  A0,-(A7)                    ; Source
            PEA     TK2_BUFFER                  ; Target
            BSR     QSTRNCAT
            ADDA.W  #10,A7                      ; Remove parameters
            LEA     TK2_BUFFER,A0               ; set A0 to new filename
DATA_USE9   RTS

;       Get the PROG_USE value, and build up a name
;       which has this at the front followed by the
;       users name and try that.

PROG_USE
            BSR     TK2_VALUES
            MOVE.L  $AC(A1),A1
            BRA     DATA_USE1
;
FILE_TB DC.W        FP81-FILE_TB
        DC.W        FP82-FILE_TB
        DC.W        ERR-FILE_TB
        DC.W        FP40-FILE_TB
        DC.W        FP40-FILE_TB
        DC.W        FP60-FILE_TB
        DC.W        FP60-FILE_TB
;
ERR     DC.W        0,0
;
NAME    DC.W        4
        DC.B        'FPSP'
;
; CONFIG BLOCK
;
          DC.L      '<<QC','FX>>'
          DC.W      '01'
CNME      DC.W      6
          DC.B      'FPSAVE'
CVER      DC.W      CVERX-CVER-2
          DC.B      VERSION
CVERX     DS.B      0
;
          DS.W      0
ITEM1     DC.B      0,'A'     string, short key
          DC.W      FP81_MX-*
          DC.W      0,0
          DC.W      DES81-*
          DC.W      STRATT-*
;
ITEM2     DC.B      0,'B'     string, short key
          DC.W      FP82_MX-*
          DC.W      0,0
          DC.W      DES82-*
          DC.W      STRATT-*
;
ITEM3     DC.B      0,'C'     string, short key
          DC.W      FP40_MX-*
          DC.W      0,0
          DC.W      DES40-*
          DC.W      STRATT-*
;
ITEM4     DC.B      0,'D'     string, short key
          DC.W      FP60_MX-*
          DC.W      0,0
          DC.W      DES60-*
          DC.W      STRATT-*
;
          DC.W      -1        mark end
;
DES81     DC.W      DES81X-DES81-2
          DC.B      'Filename for FPSP81'
DES81X    DS.B      0
;
DES82     DC.W      DES82X-DES82-2
          DC.B      'Filename for FPSP82'
DES82X    DS.B      0
;
DES40     DC.W      DES40X-DES40-2
          DC.B      'Filename for FPSP40'
DES40X    DS.B      0
;
DES60     DC.W      DES60X-DES60-2
          DC.B      'Filename for FPSP60'
DES60X    DS.B      0
;
STRATT    DC.W      0
;
FP81_MX   DC.W      40
FP81      DC.W      FP81X-FP81-2
          DC.B      'FPSP_81'
FP81X     DC.B      0
          DC.L      0,0,0,0,0,0,0,0     (Use of DS can cause a spurious
;                                        <<QCFX>> to appear. This fouls
FP82_MX   DC.W      40                   up CONFIG.)
FP82      DC.W      FP82X-FP82-2
          DC.B      'FPSP_82'
FP82X     DS.B      1
          DC.L      0,0,0,0,0,0,0,0
;
FP40_MX   DC.W      40
FP40      DC.W      FP40X-FP40-2
          DC.B      'FPSP_40'
FP40X     DS.B      1
          DC.L      0,0,0,0,0,0,0,0
;
FP60_MX   DC.W      40
FP60      DC.W      FP60X-FP60-2
          DC.B      'FPSP_60'
FP60X     DS.B      1
          DC.L      0,0,0,0,0,0,0,0
;
M_TYP     DS.B      1         copy of byte @ $A1 of sys_vars

