;
;        "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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

        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
        ADR     R1, Mou_MouseVHandler
        MOV     R2, # 0
        SWI     XOS_Claim

        MOV     R0, # EventV
        ADR     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

        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, # 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.08 (1 Jul 1997)", 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   ; 

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     0       ; Terminate list
        ALIGN

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

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

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

        ; Entered with
        ; R0 = 11 (dec) for keyboard event
        ; 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_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}
        ADR     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")

        ADR     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

        ADR     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
      ADR      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
      ADR      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, R12 = Address of
        ; module's private workspace (already looked-up)
        
        ADDR    R0, PCSWI_IrqRequest
        ADDR    R1, PCSWI_DmaRequest
        MOV     R2, R12
        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, # 204
	ADR       R1, PC_Static_Space
	MOV	  R2, # PC_STATIC_SPACE_SIZE
	MOVS      PC, LR

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

Tmr_TimerWord   DCD 0

Tmr_TickHandler ; Called every 10ms on interrupt
        STMFD   R13!, {R0, LR}  ; Save regs & return addr
        LDR     R0, Tmr_TimerWord
        CMP     R0, #0             ; Is it zero?
        LDMEQFD R13!, {R0, PC} ^   ; If so, exit

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

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

; 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 1991-1996 & Wookey 1997", 0

        END
