;
;        "PCSupport" !PC Support module
;
; Provides keyboard event trapping and anything else we come up with
;
;   13-02-92  NvS   Started, loads of code ripped off from Ian and Borris
;   30-02-92  INH   Now disables ctrl-break
;   13-04-92        saves/restores LED state
;   15-05-92        Connect mouse SWI's added
;   29-05-92        Tidied up for 0.86
;   11-06-92        Puts mouse limits to full-screen
;   28-06-92  NvS   Added screen mode handling bits
;   18-07-92  INH   Supports 360*480*8
;   22-12-92  INH   Release 1.01N2 - for network interrupts
;   02-03-93  INH   Serial & Parallel interrupt handlers
;   05-03-93  INH   (1.09) MultiIO code moved to separate module
;   12-05-93        (1.14) Saves & restores esc/break state
;   25-10-93        (1.22) SWI base changed to proper value
;   05-01-95        (1.69) Generate-Interrupt SWI & related things
;   10-02-95        (1.71) Date format changed(!) Initial Gemini release
;   17-03-95        (1.74) Callbacks added, big revamp
;   11-05-95        (1.77) 386PC_ModuleInfo added
;   25-01-95        (1.993) Renamed to PCSupport
;   28-02-96        (1.994) SYS data moved into module space.
;   08-05-96        (2.00) Now v2.00, LockCard no longer takes slot number
; 1997.04.14 W      (2.04) SWI to get addresses of Irq & DMARequest calls
; 1997.07.01 W      (2.08) GetInternalAddr SWI now returns R12 address
; 1997.10.14 RJW    (2.15) GetInternalAddr SWI gives callback fns too
; 1998.01.30 MB            Added MC146818 periodic interrupt support
;             *** NB Doesn't co-operate well with other Timer1 clients ***
; 1998.05.12               Added VRAM-update-on-vsync code
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
        GET     Module.hdr.listopts
        GET     Module.hdr.macros
        GET     Module.hdr.system
        GET     Module.hdr.proc
        GET     Module.hdr.modhand
        GET     Module.hdr.services

PC_STATIC_SPACE_SIZE EQU 7168
; Size of SYS_State plus SYS_FEState structures. 7K will do for now

PCSWI_Chunk     * &44680
Key_BufferSize  * 1 :SHL: 4       ; Ensure it is a power of two

        MACRO
$label  DisableInts $rtmp            ; Only works in non-user mode
$label  MOV      $rtmp, #I_bit
        ORR      $rtmp, $rtmp, PC
        TEQP     $rtmp, #0
        MEND

        LEADR   Module_LoadAddr

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Module_BaseAddr

        DCD     0
        DCD     Init    - Module_BaseAddr
        DCD     Die     - Module_BaseAddr
        DCD     Service - Module_BaseAddr
        DCD     Title   - Module_BaseAddr
        DCD     Help    - Module_BaseAddr
        DCD     0

        DCD     PCSWI_Chunk
        DCD     SWI     - Module_BaseAddr       ; Handler code
        DCD     SWI_Decode      - Module_BaseAddr       ; Decoding table
        DCD     0

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;
; Purpose:              Initialisation entry point.
;
; Entry:        r10     Pointer to environment string. This is the parameters
;               supplied to OS_Module.
;       r11     I/O base or the instantiation number
;       r12     Pointer to private word associated with this (the
;               preferred) instantiation. Zero implies cold init, and
;               non zero implies warm init.
;
; Exit:         Return with error if initialisation is not possible.
;
; Description:  Perform either an initialisation or a reinitialisation
;               for the indicated instantiation. Called as a result of
;               *rmtidy and OS_Module with reason codes Run, Load,
;               Reinit and Tidy.
;
; Preservation: r7, r8, r9, r10, r11, SP (note only V is noted on exit)
;
; Processor mode:       SVC mode, IRQs (probably) enabled
;

Init    ENTRY
        LDR     r2, [r12]
        TEQ     r2, #0
        MOVNE   r12, r2
        BLNE    WarmInit
        BNE     %FT50
        LDR     r3, =256        ; size, word aligned
        MOV     r0, #ModHandReason_Claim
        SWI     XOS_Module
        EXIT    VS
        STR     r2, [r12]
        MOV     r0, #0
        MOV     r14, r2
00      STR     r0, [r14], #4
        SUBS    r3, r3, #4
        BNE     %BT00
        MOV     r12, r2
        BL      ColdInit
50      BL      CommonInit
        EXITS

ColdInit   ENTRY
           EXITS

WarmInit   ENTRY
           EXITS

; General 'Init' routine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

CommonInit ENTRY

        MOV     R0, #0
        STR     R0, Key_Active
        STR     R0, Mou_Active

        MOV     R0, # MouseV
        ADRL    R1, Mou_MouseVHandler
        MOV     R2, # 0
        SWI     XOS_Claim

        MOV     R0, # EventV
        ADRL    R1, Key_EventHandler
        MOV     R2, # 0
        SWI     XOS_Claim

        MOV     R0, # TickerV
        ADRL    R1, Tmr_TickHandler
        MOV     R2, # 0
        SWI     XOS_Claim

        MOV     R0, # 14          ; OSByte: enable an event
        MOV     R1, # Event_Keyboard
        SWI     XOS_Byte

        MOV     R0, # 14
        MOV     R1, # Event_VSync
        SWI     XOS_Byte

Init_Exit
        EXITS

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;
; Purpose:              Terminate a module
;
; Entry:        r10     Fatality indicator. 0 is non-fatal, 1 is fatal
;       r11     Instantiation number
;       r12     Private word for the Instantiation
;
; Exit:         Returning an error leaves the RM in the RMA
;
; Description:  Causes the indicated instantiation to either terminate
;               permantently, or to pause during RMA tidying. Used by
;               OS_Module with reason codes Reinit, Delete, Tidy and
;               Clear, and reloading modules of the same name.
;
; Preservation: r7, r8, r9, r10, r11, SP
;
; Processor mode:       SVC mode
;

Die     ENTRY
        LDR     r12, [r12]
        BL      CommonDie
        TEQ     r10, #0
        BLEQ    TempDie
        BLNE    PermDie
        EXIT    VS
        EXITS   EQ
        MOV     r0, #ModHandReason_Free
        MOV     r2, r12
        SWI     XOS_Module
        EXIT

PermDie ENTRY
        EXITS

TempDie ENTRY
        EXITS

; General 'Terminate' routine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

CommonDie  ENTRY

        MOV     R0, # 13          ; OSByte: disable an event
        MOV     R1, # Event_Keyboard
        SWI     XOS_Byte

        MOV     R0, # 13          ; OSByte: disable an event
        MOV     R1, # Event_VSync
        SWI     XOS_Byte

	BL	Timer1_Disable	; Turn timer off if we've claimed it

        MOV     R0, # EventV
        ADR     R1, Key_EventHandler
        MOV     R2, # 0
        SWI     XOS_Release

        MOV     R0, # MouseV
        ADR     R1, Mou_MouseVHandler
        MOV     R2, # 0
        SWI     XOS_Release

        MOV     R0, # TickerV
        ADRL    R1, Tmr_TickHandler
        MOV     R2, # 0
        SWI     XOS_Release

        EXITS

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;
; Purpose:              Service call handler
;
; Entry:        r1      Service number
;       r12     Private word of preferred instantiation
;
; Exit: r1      Zero if claimed
;
; Description:  Despatch the indicated service call. Errors are not
;               supported in general, although certain calls handle
;               them.
;
; Preservation: r9, r10, r11, SP. r0-r8 unless results.
;
; Processor mode:       SVC or IRQ. IRQs undefined
;

Service ROUT
        LDR     r12, [r12]
        CMP     r1, #Service_ModeExtension
        BEQ     ModeExtender

        MOVS    pc, r14

ModeExtender
        CMP     r3, #0                          ; Can't do this on lo-res
        CMPNE   r3, #2                          ; or very high res.
        MOVEQS  pc, r14

        LDR     r1, ModeToSpot
        CMP     r2, r1
        MOV     r1, #Service_ModeExtension
        MOVNES  pc, r14

        MOV     r1, #0
        ADR     r3, MX_IHModeTable
        ADR     r4, MX_ModeTable
        MOVS    pc, r14

ModeToSpot
        DCD     65

; ----------------

       MACRO
$lbl   DVID    $reg, $data
$lbl   DCD     ($reg << 24) + ($data << 14)
       MEND


MX_IHModeTable  DCD  0
       DCD     28
       DVID    &80, 265            ;383
       DVID    &84, 31             ;35
       DVID    &88, 63             ;55
       DVID    &8C, 61             ;106
       DVID    &90, 241            ;286
       DVID    &94, 243            ;379
       DVID    &A0, 524
       DVID    &A4, 1
       DVID    &A8, 32
       DVID    &AC, 34
       DVID    &B0, 514
       DVID    &B4, 516
       DCD     (&E0 << 24) + &0E
       DCD     -1

; ----------------


MX_ModeTable
        DCD     0
MX_MT_BaseMode
        DCD     28

        DCD     1
MX_MT_ScrRCol
        DCD     44

        DCD     2
MX_MT_ScrBRow
        DCD     59

        DCD     5
MX_MT_YEigFactor
        DCD     1

        DCD     6
MX_MT_LineLength
        DCD     360

        DCD     7
MX_MT_ScreenSize
        DCD     360*480

        DCD     11
MX_MT_XWindLimit
        DCD     359

        DCD     12
MX_MT_YWindLimit
        DCD     479

        DCD     -1

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; SetupNewMode: Fills in the mode extention bits.
;               R0 = mode number
;               R1 = X Pixels wide
;               R2 = Y Pixels high
;               R3 = log2 bpp

PCSWI_Mode_Setup
        STR     r0, ModeToSpot
        MOV     pc, r14

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;
; Purpose:              Name by which module is referred
;
; Description:  This is the name by which the module is identified,
;               and also the name printed by a *modules call. The
;               name is NULL terminated,

Title   DCB     "PCSupport", 0
        ALIGN

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;
; Purpose:              Name that describes module
;
; Description:  This is the name printed by *help modules, and is
;               intended to be a descriptive name. It includes TAB
;               characters to column sixteen, then a version and date
;               in a common format.
;

Help    DCB     "PC Support", 9, "2.23 (01 Jun 1998)", 0
        ALIGN

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;
; Purpose:              Associate SWI number to code
;
; Entry:        r11     SWI number within chunk
;       r12     Private word of preferred instantiation
;       r14     Flags of the caller, except V bit clear
;
; Exit: r0      Error indicator if applicable. In particular, an
;               error for an unknown SWI should use an error message
;               quoting the module's title, but use an error number
;               of &1E6. IRQs may be enabled with:
;
;               MVN     Rn, #I_bit
;               TSTP    Rn, pc
        MACRO
$lbl    IRQEnable    $rn
        MVN   $rn, #I_bit
        TSTP  $rn, PC
        MEND
;
;               IRQs may be disabled with:
;
;               MOV     Rn, pc
;               ORR     Rn, Rn, #I_bit
;               TEQP    Rn, #0
        MACRO
$lbl    IRQDisable    $rn
$lbl    MOV   $rn, PC
        ORR   $rn, $rn, #I_bit
        TEQP  $rn, #0
        MEND
;
; Description:  Entry point to decode the handling of SWIs within the
;               module's SWI chunk.
;
; Preservation: SP
;
; Processor mode:       SVC, IRQs from caller
;

SWI     ROUT
        LDR     r12, [r12]
        CMP     r11, # (%FT01 - %FT00) / 4
        ADDCC   pc, pc, r11, LSL #2
        B       %FT01
00
        B       PCSWI_Key_Install       ; +0
        B       PCSWI_Key_Remove
        B       PCSWI_Key_Count
        B       PCSWI_Key_Get

        B       PCSWI_Key_Flush         ; +4
        B       PCSWI_Key_SetLEDs
        B       PCSWI_Mou_Install
        B       PCSWI_Mou_Remove

        B       PCSWI_Mou_GetState      ; +8
        B       PCSWI_Mode_Setup
        B       PCSWI_LockCard
        B       PCSWI_CallbackSetup

        B       PCSWI_SetCallback       ; +C
        B       PCSWI_GetCallback
        B       PCSWI_IrqRequest
        B       PCSWI_DmaRequest

        B       PCSWI_ModuleInfo        ; Return module version no.
        B       PCSWI_GetInternalAddr   ;
	B	PCSWI_Timer1Int
	B	PCSWI_VSyncCopy

	B	PCSWI_FastCopy

01      ADR     r0, %FT02
        ORRS    pc, r14, #V_bit
02
        DCD     &1E6
        DCB     "Unknown PCSupport operation", 0
        ALIGN

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;
; Purpose:      Permit generation of the name of a SWI from a number
;
; Description:  When RISC OS generates a SWI name from a SWI number
;               it attempts to use this table to generate the name.
;               It will use the next entry if it cannot generate a
;               name by using this entry.
;

SWI_Decode      DCB     "PCSupport", 0          ; Prefix to all SWI names
        DCB     "KeyInstall", 0
        DCB     "KeyRelease", 0
        DCB     "KeyCount", 0
        DCB     "KeyGet", 0

        DCB     "KeyFlush", 0
        DCB     "KeySetLEDs", 0
        DCB     "MouInstall", 0
        DCB     "MouRemove", 0

        DCB     "MouGetState", 0
        DCB     "ModeSetup", 0
        DCB     "LockCard", 0
        DCB     "CallbackSetup", 0

        DCB     "SetCallback", 0
        DCB     "GetCallback", 0
        DCB     "IrqRequest", 0
        DCB     "DmaRequest", 0

        DCB     "ModuleInfo", 0
        DCB     "GetInternalAddr", 0
	DCB	"Timer1Int", 0
	DCB	"VSyncCopy", 0

	DCB	"FastCopy", 0

        DCB     0       ; Terminate list
        ALIGN

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

        ; Event handlers ======================================

Key_EventHandler ; *************************************

        ; Entered with
        ; R0 = 11 (dec) for keyboard event
	;      4 for vsync event (MB)
        ; R1 = 1 for key down, 0 for key up
        ; R2 = ARM key code
        ; R3 = keyboard handler ID (dunno about this)
        ;
        ; The data is packed into 1 word and stored in a
        ; circular buffer (RawKeyBuf) indexed by InPtr

	CMP	R0, #Event_VSync
	BEQ	VSync_event
        CMP     R0, #Event_Keyboard   ; Is it keyboard ?
        MOVNES  PC, LR                ; If not, pass on
        STMFD   SP!, {R0-R4, LR}      ; Otherwise, save regs

        LDR     R0, Key_Active        ; Is keyboard intercept active?
        CMP     R0, #0
        LDMEQFD SP!, {R0-R4, PC}^     ; If not, restore regs and put PC=LR

        AND     R1, R1, #1            ; Just for safety
        ORR     R2, R2, R1, LSL #8    ; Put up/down into bit 8
        ORR     R2, R2, R3, LSL #9    ; Key handler at 9..31

        ADRL    R0, Key_RawKeyBuf
        LDR     R1, Key_InPtr
        LDR     R3, Key_OutPtr
        ADD     R4, R1, #1
        BIC     R4, R4, # Key_BufferSize
        CMP     R3, R4                  ; Will we overrun ?

        STRNE   R2, [R0, R1, LSL #2]    ; Put data in (word) buffer if not

        STRNE   R4, Key_InPtr           ; update pointer

        LDMFD   SP!, {R0-R4, LR, PC}^ ; And exit

Mou_MouseVHandler ; *************************************

        STMFD   SP!, {R4, LR}
        LDR     R4, Mou_Active      ; Are we intercepting this?
        CMP     R4, #1
        LDMNEFD SP!, {R4, PC}^      ; If not, pass on


                                    ; If so, generate dummy values
        SWI     XOS_ReadMonotonicTime
        MOV     R3, R0

        MOV     R0, #640
        MOV     R1, #512
        MOV     R2, #0

        LDMFD   SP!, {R4, LR, PC}^  ; and claim event


        ; SWI service routines =================================

PCSWI_Key_Install ;(void) *************************************

        STMFD   SP!, {LR}

        MOV     R0, # 229        ; OSByte: disable escape key
        MOV     R1, #1
        MOV     R2, #0
        SWI     XOS_Byte
        STR     R1, Key_EscState

        MOV     R0, # 247        ; OSByte: set break-key actions
        MOV     R1, # &AA        ; 10101010b -all keys - no effect
        MOV     R2, #0
        SWI     XOS_Byte
        STR     R1, Key_BrkState

        MOV     R0, #0
        STR     R0, Key_InPtr
        STR     R0, Key_OutPtr

        ; Save keyboard status byte

        MOV     R0, # 202         ; 202 = read/write keyboard status
        MOV     R1, # 0
        MOV     R2, # &FF
        SWI     XOS_Byte
        STR     R1, Key_LEDstate

        MOV     R0, #1
        STR     R0, Key_Active

        LDMFD   SP!, {PC}^

PCSWI_Key_Remove ;(void) *************************************

        STMFD   SP!, {LR}

        MOV     R0, #0
        STR     R0, Key_Active

        MOV     R0, # 15          ; OSByte: flush input buffer
        MOV     R1, # 1

        SWI     XOS_Byte

        MOV     R0, # 229        ; OSByte: enable escape key
        LDR     R1, Key_EscState
        MOV     R2, #0
        SWI     XOS_Byte

        MOV     R0, # 247        ; OSByte: set break-key actions
        LDR     R1, Key_BrkState
        MOV     R2, #0
        SWI     XOS_Byte

        ; Restore keyboard LED state

        MOV     R0, # 202         ; 202 = read/write keyboard status
        LDR     R1, Key_LEDstate
        MOV     R2, # 0
        SWI     XOS_Byte

        MOV     R0, # 118        ; OSByte: Reflect keyboard state in LEDs
        SWI     XOS_Byte


        LDMFD   SP!, {PC}^


Key_Active       DCD       0
Key_LEDstate     DCD       &24
Key_EscState     DCD       0
Key_BrkState     DCD       1



PCSWI_Key_Count ; Returns the number in the buffer
        STMFD   SP!, {R1, LR}
        LDR     R0, Key_InPtr
        LDR     R1, Key_OutPtr
        SUB     R0, R0, R1
        AND     R0, R0, # Key_BufferSize - 1
        LDMFD   SP!, {R1, PC}^

PCSWI_Key_Get
        STMFD   SP!, {R1-R3, LR}
        ADRL    R1, Key_RawKeyBuf
        LDR     R2, Key_OutPtr
        LDR     R3, Key_InPtr
        CMP     R3, R2
        MVNEQ   R0, #0
        LDRNE   R0, [R1, R2, LSL #2]
        ADDNE   R2, R2, #1
        BICNE   R2, R2, # Key_BufferSize
        STRNE   R2, Key_OutPtr
        LDMFD   SP!, {R1-R3, PC}^

PCSWI_Key_Flush
        MOV     R0, #0
        STR     R0, Key_InPtr
        STR     R0, Key_OutPtr
; Returns zero to indicate buffer succesfully flushed :-)
        MOVS    PC, LR


PCSWI_Key_SetLEDs ;(int) *************************************

        STMFD   SP!, {LR}

        AND     R1, R0, # &16    ; Save new LEDs state in R1, allowing
                            ; only bits 1 (ScrollLock), 2 (NumLock)
                            ; and 4 (Capslock )

        MOV     R0, # 202        ; OSByte: alter keyboard state
        EOR     R1, R1, # 2_10100; bits 2 and 4 are wrong way up (!)
        MOV     R2, # 2_11101001 ; Preserve bits 0, 3, 5, 6, 7
        SWI     XOS_Byte

        MOV     R0, # 118        ; OSByte: Reflect keyboard state in LEDs
        SWI     XOS_Byte

        LDMFD   SP!, {PC}^




PCSWI_Mou_Install ;(void) *************************************

        STMFD   SP!, {LR}

        MOV     R0, #1
        STR     R0, Mou_Active

        ADR     R1, Mou_NoLimits_blk    ; Set limits to 'infinite'
        MOV     R0, #21
        SWI     XOS_Word

        LDMFD   SP!, {PC}^

PCSWI_Mou_Remove ;(void) *************************************

        STMFD   SP!, {LR}

        MOV     R0, #0            ; Disable mouse intercept
        STR     R0, Mou_Active

        ; Read size of screen & determine mouse limits *****

        MOV     R0, #-1
        MOV     R1, #4               ; Read XEigFactor
        SWI     XOS_ReadModeVariable
        MOV     R3, R2               ; Save in R3

        MOV     R0, #-1
        MOV     R1, #11              ; Read screen X total
        SWI     XOS_ReadModeVariable
        ADD     R2, R2, #1
        MOV     R3, R2, LSL R3       ; R3 = Xtotal << EigFactor
        SUB     R3, R3, #1

        ADR     R1, Mou_ScrLimits_blk
        STRB    R3, [R1, #5]
        MOV     R3, R3, LSR #8
        STRB    R3, [R1, #6]

        MOV     R0, #-1
        MOV     R1, #5               ; Read YEigFactor
        SWI     XOS_ReadModeVariable
        MOV     R3, R2               ; Save in R3

        MOV     R0, #-1
        MOV     R1, #12              ; Read screen Y total
        SWI     XOS_ReadModeVariable
        ADD     R2, R2, #1
        MOV     R3, R2, LSL R3       ; R3 = Xtotal << EigFactor
        SUB     R3, R3, #1

        ADR     R1, Mou_ScrLimits_blk
        STRB    R3, [R1, #7]
        MOV     R3, R3, LSR #8
        STRB    R3, [R1, #8]

                                  ; R1 still points to ScrLimits block
        MOV     R0, #21           ; OSWord 21, 1 = set mouse limits
        SWI     XOS_Word


        ; Set mouse to be where pointer currently is (so it doesn't "jump")

        ADRL    R1, Mou_OSWord_blk  ; OSWord 21, 6 = read pointer position
        MOV     R0, #6               ; (into OSWord_blk)
        STRB    R0, [R1]
        MOV     R0, #21
        SWI     XOS_Word

        ADRL    R1, Mou_OSWord_blk  ; OSWord 21, 3 = set mouse position
        MOV     R0, #3
        STRB    R0, [R1]
        MOV     R0, #21
        SWI     XOS_Word

        MOV     R0, #106          ; OSByte: Select pointer
        MOV     R1, # 1           ; normal pointer shape, linked
        SWI     XOS_Byte

        MOV     R0, #21           ; OSByte 21, 6 = flush mouse buffer
        MOV     R1, #9
        SWI     XOS_Byte

        LDMFD   SP!, {PC}^

PCSWI_Mou_GetState ;(void) *************************************

        ; On exit, R0=X, R1=Y and R2=buttons for the mouse


        LDR     R3, Mou_Active	; R3 = current active state
        STMFD   SP!, {R3,LR}    ; Save it

        MOV     R3, #0          ; Disable mouse interception
        STR     R3, Mou_Active

        SWI     XOS_Mouse       ; Get mouse position
        LDMFD   SP!, {R3,LR}    ; Get back old state & return addr

        STR     R3, Mou_Active  ; Store it
        MOVS	PC, LR          ; Return




 ; Data ************************************************

Mou_NoLimits_blk   DCB &01, &00, &80, &00, &80, &FF, &7F, &FF, &7F

     ALIGN

Mou_ScrLimits_blk  DCB &01, &00, &00, &00, &00, &00, &05, &00, &04

    ALIGN

PCSWI_LockCard ; ****************************************************

      ; Prevents !PC being loaded twice.
      ; Enter R0 = 1 to claim lock, R0 = 0 to release lock
      ; exit with R0 = previous lock state ( 1=> lock failed!)

        ; Disable interrupts?
        LDR	R1, CardLock
        STR	R0, CardLock
        MOV	R0, R1
        MOVS	PC, LR

CardLock DCD  0

      ALIGN

PCSWI_CallbackSetup ; ************************************************

      ; Used on initialisation to set up callback mechanism

      ; On entry, R0 = Address of Irq-generation callback fn,
      ;           R1 = address of DMAReq generation callback fn.
      ;
      ; On exit,  R0 = Address of 'callback set' flag: non zero
      ;                 when a callback has been set.
      ;           R1 = address of timer word
      ;
      ; The timer works as follows: it is set by writing a non-zero
      ; value into [R1]. This value is decremented every 10ms on the
      ; ticker timer. When it reaches zero, the callback flag is set.
      ; The [R1] word will not be decremented further.

      ; This allows the callback flag to be used as a flag for terminating
      ; the CPU emulation: it stops when either a callback occurs or
      ; the timer expires. The timer can be stopped by setting [R1] to
      ; zero.

        STMFD   R13!, {R2-R4, LR}

        ; Initialise Irq callback structures

        ADRL    R2, Irq_CBStructs
        MOV     R3, #0
        MOV     R4, #0
CBS_10
        STR     R4, [R2, #0]            ; Clear out tag
        STR     R0, [R2, #4]            ; Set function address
        STR     R3, [R2, #8]            ; R0 value = IRQ number
        STR     R4, [R2, #16]           ; Clear out link value
        ADD     R2, R2, #20             ; Next struct
        ADD     R3, R3, #1              ; Next IRQ number
        CMP     R3, #16
        BNE     CBS_10

        ; Initialise Dma callback structures

        ADRL    R2, Dma_CBStructs
        MOV     R3, #0
        MOV     R4, #0
CBS_20
        STR     R4, [R2, #0]            ; Clear out tag
        STR     R1, [R2, #4]            ; Set DMA function address
        STR     R3, [R2, #8]            ; R0 value = IRQ number
        STR     R4, [R2, #16]           ; Clear out link value
        ADD     R2, R2, #20             ; Next struct
        ADD     R3, R3, #1              ; Next IRQ number
        CMP     R3, #8
        BNE     CBS_20

      ; Now zero out all callback lists

        STR     R4, CallbackFlag
        STR     R4, CallbackList        ; Forget pending callbacks

        ADR     R0, CallbackFlag
        ADR     R1, Tmr_TimerWord
        LDMFD   R13!, {R2-R4, PC}^      ; That's it

CallbackList DCD 0      ; Head of list of callback structures
CallbackFlag DCD 0      ; Callback flag


        ALIGN

PCSWI_SetCallback ; ****************************************************

        ; On entry, R0 points to a callback structure
        ;  [R0+0] = tag: 0 on entry, A950317Bh if the callback has
        ;              already been put in the list
        ;  [R0+4] = address of function to call back
        ;  [R0+8] = value to put in R0 when calling function
        ;  [R0+12] = value to put in R12 when calling function
        ;  [R0+16] = pointer to next callback structure, or NULL

        DisableInts R1          ; IRQ state restored on exit

        LDR     R2, MagicTag
        LDR     R1, [R0, #0]    ; Get tag word
        CMP     R1, R2          ; Already in list?
        MOVEQS  PC, LR          ; If so, don't bother further

        STR     R2, [R0, #0]    ; Update tag
        LDR     R1, CallbackList
        STR     R1, [R0, #16]   ; R0->next = CallbackList
        STR     R0, CallbackList ; CallbackList = R0

        STR     R2, CallbackFlag ; Set flag to indicate callback set
        MOVS    PC, LR

MagicTag DCD &A950317B


        ALIGN

PCSWI_GetCallback ; ****************************************************

      ; Atomically pulls one entry off the callback list.

      ; On exit R0 = address of callback structure, R1 corrupted
      ;  R0 = NULL if no more entries in list

        DisableInts R1           ; Ints restored on exit
        LDR     R0, CallbackList
        CMP     R0, #0           ; Any entries?
        MOVEQS  PC, LR           ; If not, return R0=NULL

        LDR     R1, [R0, #16]    ; R1 = Link field
        STR     R1, CallbackList
        MOV     R1, #0
        STR     R1, [R0, #0]     ; Clear Tag field (show off-list)
        STR     R1, [R0, #16]    ; Clear link field (paranoia)
        MOVS    PC, LR


        ALIGN

PCSWI_IrqRequest ; *************************************************

      ; Generates an interrupt. On entry, R0 is the interrupt
      ; number to be generated. Corrupts R1

      ; To do this, a callback is set to the interrupt
      ;  generation function in CPU.C.CPU. We have 16 callback structures
      ;  for each of the 16 possible ints 0..15.

      CMP      R0, #16
      MOVHSS   PC, LR           ; Return if >= 16
      ADRL     R1, Irq_CBStructs
      ADD      R0, R0, R0, LSL #2       ; R0 = R0 * 5
      ADD      R0, R1, R0, LSL #2       ; R0 = Callback struct address
      B        PCSWI_SetCallback      ; And set callback

PCSWI_DmaRequest ; *************************************************

      ; Generates an DMA request. On entry, R0 is the interrupt
      ; number to be generated. Corrupts R1
      ; Assumes a callback struct is 20 bytes (5 words)

      ; This uses a callback mechanism like IrqRequest

      CMP      R0, #8
      MOVHSS   PC, LR           ; Return if >= 8
      ADRL     R1, Dma_CBStructs
      ADD      R0, R0, R0, LSL #2       ; R0 = R0 * 5
      ADD      R0, R1, R0, LSL #2       ; R0 = Callback struct address
      B        PCSWI_SetCallback      ; And set callback


PCSWI_GetInternalAddr ; -------------------------------------
        ; Returns addresses of !PC SWIs for calling directly
        ; without a CPU mode change. Can be called in all modes that
        ; will allow interrupts to be turned off.

        ; On return, R0 = IrqRequest, R1 = DMARequest, R2 = Address of
        ; module's private workspace (already looked-up), R3 = Set Callback
        ; R4 = GetCallback

        ADDR    R0, PCSWI_IrqRequest
        ADDR    R1, PCSWI_DmaRequest
        MOV     R2, R12
        ADDR    R3, PCSWI_SetCallback
        ADDR    R4, PCSWI_GetCallback
	ADDR	R5, TimedIntFlag
        MOVS    PC, LR

PCSWI_ModuleInfo ; -------------------------------------
	; Returns version number *100 decimal in R0
	; Returns address of !PC static space (used to keep
	; SYS_State and SYS_FEState) in R1, and the size of
	; this space in R2.


	MOV       R0, # 223
	ADRL      R1, PC_Static_Space
	MOV	  R2, # PC_STATIC_SPACE_SIZE
	MOVS      PC, LR

PCSWI_VSyncCopy ; -------------------------------------
	; Sets up the mechanism to update the screen memory with
	; whatever's at PC address 0xA0000 every vsync to speed up
	; games which don't swap the video banks
	;
	; R0 = source (typically CPU_MemoryBase + A0000h)
	; R1 = dest (i.e. sprite buffer or screen memory)
	; R2 = length (i.e. size of image)
	; R3 = frame skip (0 for every vsync)
	;
	; on exit, R4 points to a value which is set to 1 if an update
	; has happened (main code must reset after reading)
	;
	; pass -1 in any parameter to have the current value returned

	CMN	R0, #1
	LDREQ	R0, VSync_src
	STRNE	R0, VSync_src
	CMN	R1, #1
	LDREQ	R1, VSync_dest
	STRNE	R1, VSync_dest
	CMN	R2, #1
	LDREQ	R2, VSync_len
	STRNE	R2, VSync_len
	CMN	R3, #1
	LDREQ	R3, VSync_skip
	STRNE	R3, VSync_skip
	ADR	R4, VID_VSyncUpdated
	MOVS	PC, R14

VSync_src	DCD 0
VSync_dest	DCD 0
VSync_len	DCD 0
VSync_skip	DCD 0
VSync_skipcur	DCD 0
VSync_entered	DCD 0
VID_VSyncUpdated DCD 0

PCSWI_FastCopy ; -------------------------------------
	; R0 = src
	; R1 = dest
	; R2 = len
	;
	; just so we're not duplicating code in the main program

	LDR	R3, MagicTag
	STMFD	R13!, {R0-R12, R14}
	B	VSync_copy176	; short-cut into routine below

VSync_event	 ; Called every vsync
	STMFD	R13!, {R0, R14}
	LDR	R0, VSync_skip	; Are we feeling turned on?
	CMP	R0, #256
	LDMGEFD	R13!, {R0, PC}^

	LDR	R0, VSync_skipcur
	SUBS	R0, R0, #1
	LDRLT	R0, VSync_skip
	STR	R0, VSync_skipcur; Allow for vsync skipping value
	LDMGEFD	R13!, {R0, PC}^
	LDR	R0, VSync_entered

	CMP	R0, #1		; Check for re-entrancy
	LDMEQFD	R13!, {R0, PC}^
	MOV	R0, #1
	STR	R0, VSync_entered

	STMFD	R13!, {R8-R9}	; Drop into SVC mode so we can use R14_irq
	MOV	R9, PC
	ORR	R8, R9, #3
	TEQP	R8, #0
	STMFD	R13!, {R0-R12,R14}

	LDR	R0, VSync_src
	LDR	R1, VSync_dest
	LDR	R2, VSync_len

VSync_copy176	; Slightly unravelled copying loop
	CMP	R2, #176
	BLT	VSync_copy88
	LDMIA	R0!, {R3-R12, R14}
	STMIA	R1!, {R3-R12, R14}
	LDMIA	R0!, {R3-R12, R14}
	STMIA	R1!, {R3-R12, R14}
	LDMIA	R0!, {R3-R12, R14}
	STMIA	R1!, {R3-R12, R14}
	LDMIA	R0!, {R3-R12, R14}
	STMIA	R1!, {R3-R12, R14}
	SUB	R2, R2, #176
	B	VSync_copy176
VSync_copy88
	CMP	R2, #88
	BLT	VSync_copy44
	LDMIA	R0!, {R3-R12, R14}
	STMIA	R1!, {R3-R12, R14}
	LDMIA	R0!, {R3-R12, R14}
	STMIA	R1!, {R3-R12, R14}
	SUB	R2, R2, #88
	B	VSync_copy88
VSync_copy44
	CMP	R2, #44
	BLT	VSync_copywords
	LDMIA	R0!, {R3-R12, R14}
	STMIA	R1!, {R3-R12, R14}
	SUB	R2, R2, #44
	B	VSync_copy44
VSync_copywords
	CMP	R2, #4
	BLT	VSync_copyend
	LDR	R3, [R0], #4
	STR	R3, [R1], #4
	SUB	R2, R2, #4
	B	VSync_copywords

VSync_copyend	; No bytes to copy, since screen sizes are multiples of 4
	LDMFD	R13!, {R0-R12, R14}
	LDR	R8, MagicTag
	CMP	R3, R8
	MOVEQS	PC, R14 ; called from PCSupport_FastCopy, so no mode change
	TEQP	R9, #0
	MOVNV	R0, R0
	LDMFD	R13!, {R8-R9}

	MVN	R0, #0
	STR	R0, VID_VSyncUpdated
	MOV	R0, #0
	STR	R0, VSync_entered

	LDMFD	R13!, {R0, PC}^

SYS_BannerText_addr
	DCD	0
PCSWI_BannerText ; -------------------------------------
	; Used to set the banner text
	STMFD	R13!, {R0-R3, R14}
	CMP	R0, #1	                ; Main code telling us where
	STREQ	R1, SYS_BannerText_addr ;   the function is
	LDMEQFD	R13!, {R0-R3, PC}^
	LDR	R2, SYS_BannerText_addr
	CMP	R2, #0			; Don't know where the function is?
	LDMEQFD	R13!, {R0-R3, PC}^
	ADR	R14, PCSWI_BannerText_return
	MOV	PC, R2			; Otherwise call main code
PCSWI_BannerText_return
	LDMFD	R13!, {R0-R3, R14}
	MOVS	PC, R14

PCSWI_Timer1Int ; -------------------------------------
	; Called by !PC to set the frequency with which Timer1 sends
	; an IRQ 8 (i.e. for emulating the MC146818)
	;
	; on entry:
	;   frequency = 2MHz / R0 value (16-bit) or 0 to disable
	;
	; on exit:
	; R0 = address of poll word in module; this gets to 1 set every time
	;        an interrupt is generated, and should be set back to 0
	;        by the main (RTC) code when the StatusC flag is read.
	; most other registers toyed with
	;
	; N.B. Setting Timer1 to multiples of 65536 will probably result
	; in Bad Stuff happening, but !PC only uses 12 or so presets,
	; none of which fall into this category.

	STMFD	R13!,{R14}
	CMP	R0,# 0
	STREQ	R0,Timer1_TimerLatch
	BLEQ	Timer1_Disable
	LDMEQFD	R13!,{PC}	; Turn off timer & exit if 0 passed
	BL	Timer1_Enable	; Turn on otherwise

	MOV	R1,# 0		; R1 = Further loops around timer1
PCSWI_Timer1Int_CheckInRange
	CMP	R0,# 65536
	SUBGE	R0,R0,# 65536
	ADDGE	R1,R1,#1
	BGE	PCSWI_Timer1Int_CheckInRange
	STR	R0,Timer1_TimerLatch
	STR	R1,Timer1_LoopLatch
	STR	R1,Timer1_Loop

	CMP	R1,# 0
	MOVEQ	R0,# &FF00
	ORREQ	R0,R0,#&FF
	MOV	R2,# IOC
	STRB	R0,[R2,# Timer1LL]
	MOV	R0,R0,LSR #8
	STRB	R0,[R2,# Timer1LH]
	STRB	R0,[R2,# Timer1GO]

	ADR	R0, Timer1_PollWord
	LDMFD	R13!,{R14}

; Timer handlers -----------------------------------------

Tmr_TimerWord	DCD 0

TimedIntFlag	DCD -1
TimedIntTime    DCD 0

Tmr_TickHandler ; Called every 10ms on interrupt
        STMFD   R13!, {R0-R2, LR}  ; Save regs & return addr

	;LDR	R0, TimedIntTime
	;SUBS	R0, R0, #1
	;BLT	NoTimedInt
	;STR	R0, TimedIntTime
	;BNE	NoTimedInt
	;LDR	R0, TimedIntFlag
	;CMP	R0, #14
	;BLEQ	PCSWI_IrqRequest
NoTimedInt

        LDR     R0, Tmr_TimerWord
        CMP     R0, #0             ; Is it zero?
        LDMEQFD R13!, {R0-R2, PC} ^   ; If so, exit

        SUBS    R0, R0, #1
        STR     R0, Tmr_TimerWord       ; Dec timer
        LDMNEFD R13!, {R0-R2, PC} ^        ; If non-zero, exit

        MOV     R0, # -1
        STR     R0, CallbackFlag
        LDMFD   R13!, {R0-R2, PC} ^        ; And exit

;  MC146818 'periodic interrupt' emulation functions -------------------

Timer1_Enable
	; Makes sure the Timer1 interrupt is enabled and the device vector
	; claimed
	STMFD	R13!,{R0-R5,R14}

	LDR	R0, Timer1_Claimed	; Check *we* haven't claimed it
	CMP	R0,#0
	LDMNEFD	R13!,{R0-R5,PC}^

	MOV	R1, # IOC
	LDRB	R0, [R1,# IOCIRQMSKA]
	ANDS	R0, R0, # timer1_bit	; Check another task hasn't enabled
	PullEnv NE
	ADRNE	R0, Error_Init_Timer1	; timer1 for conflicting purposes
	LDMNEFD	R13!,{R0-R5,R14}
	ORRNES	PC, R14, #V_bit

	MOV	R0,#1
	STR	R0, Timer1_Claimed

	MOV	R0, # 6		; IOC timer1 device vector
	ADRL	R1, Timer1_Interrupt
	MOV	R2, # 0
	SWI	OS_ClaimDeviceVector
	LDMVSFD	R13!,{PC}

	MOV	R0,#&10000
	SUB	R0,R0,#1
	MOV	R2,# IOC
	STRB	R0,[R2,# Timer1LL]
	MOV	R0,R0,LSR #8
	STRB	R0,[R2,# Timer1LH]
	STRB	R0,[R2,# Timer1GO]

	MOV	R4, # PC
	ORR	R5, R4, #(I_bit + F_bit); Hush interrupts for a sec...
	TEQP	R5, # 0
	MOV	R3, # IOC
	LDRB	R0, [R3, #IOCIRQMSKA]
	ORR	R0, R0, # timer1_bit	; Enable timer1 IRQ
	STRB	R0, [R3, #IOCIRQMSKA]
	TEQP	R4, #0			; Re-enable interrupts
	MOV	R0, R0

	LDMFD	R13!,{R0-R5, PC}^

Timer1_Disable
	; Makes sure the Timer1 interrupt is disabled and the device vector
	; released
	STMFD	R13!,{R0-R5, R14}

	LDR	R0, Timer1_Claimed
	CMP	R0,#0
	LDMEQFD	R13!,{R0-R5, PC}^
	MOV	R0,#0
	STR	R0, Timer1_Claimed	; Check we have claimed it

	MOV	R4, # PC
	ORR	R5, R4, #(I_bit + F_bit); Hush interrupts for a sec...
	TEQP	R5, # 0
	MOV	R3, # IOC
	LDRB	R0, [R3, #IOCIRQMSKA]
	BIC	R0, R0, # timer1_bit	; Disable timer1
	STRB	R0, [R3, #IOCIRQMSKA]
	TEQP	R4, #0			; Re-enable interrupts
	MOV	R0, R0

	MOV	R0, # 6		; IOC timer1 device vector
	ADRL	R1, Timer1_Interrupt
	MOV	R2, # 0
	SWI	OS_ReleaseDeviceVector

	LDMFD	R13!,{R0-R5, PC}^

Timer1_Interrupt ; Called whenever Timer1 expires in order to prod IRQ 8
		 ; on PC.  We keep a separate loop counter so that we can
		 ; ask for times coarser than (2 / 65535)MHz.

	MOV	R3,# IOC
	MOV	R0,# timer1_bit
	STR	R0,[R3,# IOCIRQCLRA]	; Clear interrupt request

	LDR	R2,Timer1_TimerLatch
	CMP	R2,#0
	MOVEQS	PC,R14	; No effect if timer is turned off
	LDR	R0,Timer1_Loop
	LDR	R1,Timer1_LoopLatch

	SUBS	R0,R0,# 1		; This condition carries right down
	MOVLT	R0,R1
	MOVGT	R2,#65536
	SUBGT	R2,R2,#1
	STR	R0,Timer1_Loop
	STRB	R2,[R3,# Timer1LL]
	MOV	R2,R2,LSR#8
	STRB	R2,[R3,# Timer1LH]
	STRB	R2,[R3,# Timer1GO]

	MOVGTS	PC,R14			; Return if loop counter != 0

	MOV	R0,#1
	STR	R0,Timer1_PollWord	; Tell main code what we're up to
	MOV	R0,#8
	B	PCSWI_IrqRequest

		  DCD &ABCDEF12 ; Magic debugging word
Timer1_Loop	  & 0
Timer1_LoopLatch  & 0
Timer1_TimerLatch & 0
Timer1_PollWord   & 0
Timer1_Claimed	  & 0

Error_Init_Timer1
	DCD	0
	DCB	"Timer1 in use by another application-- PCSupport could not initialise",0
	ALIGN


; Data space ==========================================================

; Tack the data on the end of the module (can't be bothered to claim it)

     ALIGN

Diva_DataSpace
                ^       Diva_DataSpace

Key_RawKeyBuf    #       Key_BufferSize*4

Key_InPtr        #       4
Key_OutPtr       #       4


Mou_Active       #       4
Mou_OSWord_blk   #       12

Irq_CBStructs    #       320      ; 16 structs of 5 words each
Dma_CBStructs    #       160      ; 8 structs of 5 words each

PC_Static_Space	 #       PC_STATIC_SPACE_SIZE

Diva_DataEnd     #       0

Diva_Data        %       Diva_DataEnd - Diva_DataSpace

        DCB     "(c) IanH, Wookey, Mattbee 1998", 0

        END
