;****************************************************************************
;*
;*                  SciTech OS Portability Manager Library
;*
;*  ========================================================================
;*
;*    The contents of this file are subject to the SciTech MGL Public
;*    License Version 1.0 (the "License"); you may not use this file
;*    except in compliance with the License. You may obtain a copy of
;*    the License at http://www.scitechsoft.com/mgl-license.txt
;*
;*    Software distributed under the License is distributed on an
;*    "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
;*    implied. See the License for the specific language governing
;*    rights and limitations under the License.
;*
;*    The Original Code is Copyright (C) 1991-1998 SciTech Software, Inc.
;*
;*    The Initial Developer of the Original Code is SciTech Software, Inc.
;*    All Rights Reserved.
;*
;*  ========================================================================
;*
;* Language:    80386 Assembler, TASM 4.0 or NASM
;* Environment: IBM PC Real mode and 16/32 bit protected mode
;*
;* Description: Low level assembly support for the PM library specific to
;*              MSDOS interrupt handling.
;*
;****************************************************************************

        IDEAL

include "scitech.mac"           ; Memory model macros

header      _pmdos              ; Set up memory model

; Define the size of our local stacks. For real mode code they cant be
; that big, but for 32 bit protected mode code we can make them nice and
; large so that complex C functions can be used.

ifdef flatmodel
MOUSE_STACK EQU 4096
TIMER_STACK EQU 4096
KEY_STACK   EQU 1024
INT10_STACK EQU 1024
IRQ_STACK   EQU 1024
else
MOUSE_STACK EQU 1024
TIMER_STACK EQU 512
KEY_STACK   EQU 256
INT10_STACK EQU 256
IRQ_STACK   EQU 256
endif

ifdef   USE_NASM

; Macro to load DS and ES registers with correct value.

%imacro   LOAD_DS 0
%ifdef  flatmodel
        mov     ds,[cs:_PM_savedDS]
        mov     es,[cs:_PM_savedDS]
%else
        push    ax
        mov     ax,_DATA
        mov     ds,ax
        pop     ax
%endif
%endmacro

; Note that interrupts we disable interrupts during the following stack
; %imacro for correct operation, but we do not enable them again. Normally
; these %imacros are used within interrupt handlers so interrupts should
; already be off. We turn them back on explicitly later if the user code
; needs them to be back on.

; Macro to switch to a new local stack.

%imacro NEWSTK  1
        cli
        mov     [seg_%1],ss
        mov     [ptr_%1],_sp
        mov     [TempSeg],ds
        mov     ss,[TempSeg]
        mov     _sp,offset %1
%endmacro

; %imacro to switch back to the old stack.

%imacro   RESTSTK   1
        cli
        mov     ss,[seg_%1]
        mov     _sp,[ptr_%1]
%endmacro

; %imacro to swap the current stack with the one saved away.

%imacro SWAPSTK 1
        cli
        mov     ax,ss
        xchg    ax,[seg_%1]
        mov     ss,ax
        xchg    _sp,[ptr_%1]
%endmacro

else

; Macro to load DS and ES registers with correct value.

MACRO   LOAD_DS
ifdef   flatmodel
        mov     ds,[cs:_PM_savedDS]
        mov     es,[cs:_PM_savedDS]
else
        push    ax
        mov     ax,_DATA
        mov     ds,ax
        pop     ax
endif
ENDM

; Note that interrupts we disable interrupts during the following stack
; macro for correct operation, but we do not enable them again. Normally
; these macros are used within interrupt handlers so interrupts should
; already be off. We turn them back on explicitly later if the user code
; needs them to be back on.

; Macro to switch to a new local stack.

MACRO   NEWSTK  stkname
        cli
        mov     [seg_&stkname&],ss
        mov     [ptr_&stkname&],_sp
        mov     [TempSeg],ds
        mov     ss,[TempSeg]
        mov     _sp,offset stkname
ENDM

; Macro to switch back to the old stack.

MACRO   RESTSTK stkname
        cli
        mov     ss,[seg_&stkname&]
        mov     _sp,[ptr_&stkname&]
ENDM

; Macro to swap the current stack with the one saved away.

MACRO   SWAPSTK stkname
        cli
        mov     ax,ss
        xchg    ax,[seg_&stkname&]
        mov     ss,ax
        xchg    _sp,[ptr_&stkname&]
ENDM

endif

begdataseg  _pmdos

ifdef flatmodel
    cextern _PM_savedDS,USHORT
endif
    cextern _PM_critHandler,CPTR
    cextern _PM_breakHandler,CPTR
    cextern _PM_timerHandler,CPTR
    cextern _PM_rtcHandler,CPTR
    cextern _PM_keyHandler,CPTR
    cextern _PM_key15Handler,CPTR
    cextern _PM_mouseHandler,CPTR
    cextern _PM_int10Handler,CPTR

    cextern _PM_ctrlCPtr,DPTR
    cextern _PM_ctrlBPtr,DPTR
    cextern _PM_critPtr,DPTR

    cextern _PM_prevTimer,FCPTR
    cextern _PM_prevRTC,FCPTR
    cextern _PM_prevKey,FCPTR
    cextern _PM_prevKey15,FCPTR
    cextern _PM_prevBreak,FCPTR
    cextern _PM_prevCtrlC,FCPTR
    cextern _PM_prevCritical,FCPTR
    cextern _PM_prevRealTimer,ULONG
    cextern _PM_prevRealRTC,ULONG
    cextern _PM_prevRealKey,ULONG
    cextern _PM_prevRealKey15,ULONG
    cextern _PM_prevRealInt10,ULONG

cpublic _PM_pmdosDataStart

; Allocate space for all of the local stacks that we need. These stacks
; are not very large, but should be large enough for most purposes
; (generally you want to handle these interrupts quickly, simply storing
; the information for later and then returning). If you need bigger
; stacks then change the appropriate value in here.

            ALIGN   4
            dclb MOUSE_STACK    ; Space for local stack (small)
MsStack:                        ; Stack starts at end!
ptr_MsStack DUINT   0           ; Place to store old stack offset
seg_MsStack dw      0           ; Place to store old stack segment

            ALIGN   4
            dclb INT10_STACK    ; Space for local stack (small)
Int10Stack:                     ; Stack starts at end!
ptr_Int10Stack  DUINT   0       ; Place to store old stack offset
seg_Int10Stack  dw      0       ; Place to store old stack segment

            ALIGN   4
            dclb TIMER_STACK    ; Space for local stack (small)
TmStack:                        ; Stack starts at end!
ptr_TmStack DUINT   0           ; Place to store old stack offset
seg_TmStack dw      0           ; Place to store old stack segment

            ALIGN   4
            dclb TIMER_STACK    ; Space for local stack (small)
RtcStack:                       ; Stack starts at end!
ptr_RtcStack DUINT  0           ; Place to store old stack offset
seg_RtcStack dw     0           ; Place to store old stack segment
RtcInside   dw      0           ; Are we still handling current interrupt

            ALIGN   4
            dclb KEY_STACK      ; Space for local stack (small)
KyStack:                        ; Stack starts at end!
ptr_KyStack DUINT   0           ; Place to store old stack offset
seg_KyStack dw      0           ; Place to store old stack segment
KyInside    dw      0           ; Are we still handling current interrupt

            ALIGN   4
            dclb KEY_STACK      ; Space for local stack (small)
Ky15Stack:                      ; Stack starts at end!
ptr_Ky15Stack   DUINT   0       ; Place to store old stack offset
seg_Ky15Stack   dw      0       ; Place to store old stack segment

TempSeg     dw      0           ; Place to store stack segment

cpublic _PM_pmdosDataEnd

enddataseg  _pmdos

begcodeseg  _pmdos              ; Start of code segment

cpublic _PM_pmdosCodeStart

;----------------------------------------------------------------------------
; PM_mouseISR - Mouse interrupt subroutine dispatcher
;----------------------------------------------------------------------------
; Interrupt subroutine called by the mouse driver upon interrupts, to
; dispatch control to high level C based subroutines. Interrupts are on
; when we call the user code.
;
; It is _extremely_ important to save the state of the extended registers
; as these may well be trashed by the routines called from here and not
; restored correctly by the mouse interface module.
;
; NOTE: This routine switches to a local stack before calling any C code,
;       and hence is _not_ re-entrant. For mouse handlers this is not a
;       problem, as the mouse driver arbitrates calls to the user mouse
;       handler for us.
;
; Entry:    AX  - Condition mask giving reason for call
;           BX  - Mouse button state
;           CX  - Horizontal cursor coordinate
;           DX  - Vertical cursor coordinate
;           SI  - Horizontal mickey value
;           DI  - Vertical mickey value
;
;----------------------------------------------------------------------------
ifdef   DJGPP
cprocstart  _PM_mouseISR
else
cprocfar    _PM_mouseISR
endif

        push    ds              ; Save value of DS
        push    es
        pushad                  ; Save _all_ extended registers
        cld                     ; Clear direction flag

        LOAD_DS                 ; Load DS register
        NEWSTK  MsStack         ; Switch to local stack

; Call the installed high level C code routine

        clrhi   dx              ; Clear out high order values
        clrhi   cx
        clrhi   bx
        clrhi   ax
        sgnhi   si
        sgnhi   di

        push    _di
        push    _si
        push    _dx
        push    _cx
        push    _bx
        push    _ax
        sti                     ; Enable interrupts
        call    [CPTR _PM_mouseHandler]
        _add    sp,12,24

        RESTSTK MsStack         ; Restore previous stack

        popad                   ; Restore all extended registers
        pop     es
        pop     ds
        ret                     ; We are done!!

cprocend

;----------------------------------------------------------------------------
; PM_timerISR - Timer interrupt subroutine dispatcher
;----------------------------------------------------------------------------
; Hardware interrupt handler for the timer interrupt, to dispatch control
; to high level C based subroutines. We save the state of all registers
; in this routine, and switch to a local stack. Interrupts are *off*
; when we call the user code.
;
; NOTE: This routine switches to a local stack before calling any C code,
;       and hence is _not_ re-entrant. Make sure your C code executes as
;       quickly as possible, since a timer overrun will simply hang the
;       system.
;----------------------------------------------------------------------------
cprocfar    _PM_timerISR

        push    ds              ; Save value of DS
        push    es
        pushad                  ; Save _all_ extended registers
        cld                     ; Clear direction flag

        LOAD_DS                 ; Load DS register

        NEWSTK  TmStack         ; Switch to local stack
        call    [CPTR _PM_timerHandler]
        RESTSTK TmStack         ; Restore previous stack

        popad                   ; Restore all extended registers
        pop     es
        pop     ds
        iret                    ; Return from interrupt

cprocend

;----------------------------------------------------------------------------
; PM_chainPrevTimer - Chain to previous timer interrupt and return
;----------------------------------------------------------------------------
; Chains to the previous timer interrupt routine and returns control
; back to the high level interrupt handler.
;----------------------------------------------------------------------------
cprocstart  PM_chainPrevTimer

ifdef   TNT
        push    eax
        push    ebx
        push    ecx
        pushfd                  ; Push flags on stack to simulate interrupt
        mov     ax,250Eh        ; Call real mode procedure function
        mov     ebx,[_PM_prevRealTimer]
        mov     ecx,1           ; Copy real mode flags to real mode stack
        int     21h             ; Call the real mode code
        popfd
        pop     ecx
        pop     ebx
        pop     eax
        ret
else
        SWAPSTK TmStack         ; Swap back to previous stack
        pushf                   ; Save state of interrupt flag
        pushf                   ; Push flags on stack to simulate interrupt
ifdef   USE_NASM
        call far dword [_PM_prevTimer]
else
        call    [_PM_prevTimer]
endif
        popf                    ; Restore state of interrupt flag
        SWAPSTK TmStack         ; Swap back to C stack again
        ret
endif

cprocend

; Macro to delay briefly to ensure that enough time has elapsed between
; successive I/O accesses so that the device being accessed can respond
; to both accesses even on a very fast PC.

ifdef   USE_NASM
%macro  DELAY 0
        jmp     short $+2
        jmp     short $+2
        jmp     short $+2
%endmacro
%macro  IODELAYN 1
%rep    %1
        DELAY
%endrep
%endmacro
else
macro   DELAY
        jmp     short $+2
        jmp     short $+2
        jmp     short $+2
endm
macro   IODELAYN    N
    rept    N
        DELAY
    endm
endm
endif

;----------------------------------------------------------------------------
; PM_rtcISR - Real time clock interrupt subroutine dispatcher
;----------------------------------------------------------------------------
; Hardware interrupt handler for the timer interrupt, to dispatch control
; to high level C based subroutines. We save the state of all registers
; in this routine, and switch to a local stack. Interrupts are *off*
; when we call the user code.
;
; NOTE: This routine switches to a local stack before calling any C code,
;       and hence is _not_ re-entrant. Make sure your C code executes as
;       quickly as possible, since a timer overrun will simply hang the
;       system.
;----------------------------------------------------------------------------
cprocfar    _PM_rtcISR

        push    ds                  ; Save value of DS
        push    es
        pushad                      ; Save _all_ extended registers
        cld                         ; Clear direction flag

; Clear priority interrupt controller and re-enable interrupts so we
; dont lock things up for long.

        mov     al,20h
        out     0A0h,al
        out     020h,al

; Clear real-time clock timeout

        in      al,70h              ; Read CMOS index register
        push    _ax                 ;  and save for later
        IODELAYN 3
        mov     al,0Ch
        out     70h,al
        IODELAYN 5
        in      al,71h

; Call the C interrupt handler function

        LOAD_DS                     ; Load DS register
        cmp     [BYTE RtcInside],1  ; Check for mutual exclusion
        je      @@Exit
        mov     [BYTE RtcInside],1
        NEWSTK  RtcStack            ; Switch to local stack
        sti                         ; Re-enable interrupts
        call    [CPTR _PM_rtcHandler]
        RESTSTK RtcStack            ; Restore previous stack
        mov     [BYTE RtcInside],0

@@Exit: pop     _ax
        out     70h,al              ; Restore CMOS index register
        popad                       ; Restore all extended registers
        pop     es
        pop     ds
        iret                        ; Return from interrupt

cprocend

ifdef flatmodel
;----------------------------------------------------------------------------
; PM_irqISRTemplate - Hardware interrupt handler IRQ template
;----------------------------------------------------------------------------
; Hardware interrupt handler for any interrupt, to dispatch control
; to high level C based subroutines. We save the state of all registers
; in this routine, and switch to a local stack. Interrupts are *off*
; when we call the user code.
;
; NOTE: This routine switches to a local stack before calling any C code,
;       and hence is _not_ re-entrant. Make sure your C code executes as
;       quickly as possible.
;----------------------------------------------------------------------------
cprocfar    _PM_irqISRTemplate

        push    ebx
        mov     ebx,0           ; Relocation adjustment factor
        jmp     __IRQEntry

; Global variables stored in the IRQ thunk code segment

_CHandler       dd      0       ; Pointer to C interrupt handler
_PrevIRQ        dd      0       ; Previous IRQ handler
                dd      0
_IRQ            dd      0       ; IRQ we are hooked for
ptr_IRQStack    DUINT   0       ; Place to store old stack offset
seg_IRQStack    dw      0       ; Place to store old stack segment
_Inside         db      0       ; Mutual exclusion flag
        ALIGN   4
        dclb IRQ_STACK          ; Space for local stack
_IRQStack:                      ; Stack starts at end!

; Check for and reject spurious IRQ 7 signals

__IRQEntry:
        cmp     [BYTE cs:ebx+_IRQ],7    ; Spurious IRQs occur only on IRQ 7
        jmp     @@ValidIRQ
        push    eax
        mov     al,1011b            ; OCW3: read ISR
        out     20h,al              ; (Intel Peripheral Components, 1991,
        in      al,20h              ; p. 3-188)
        shl     al,1                ; Set C = bit 7 (IRQ 7) of ISR register
        pop     eax
        jc      @@ValidIRQ
        iret                        ; Return from interrupt

; Save all registers for duration of IRQ handler

@@ValidIRQ:
        push    ds                  ; Save value of DS
        push    es
        pushad                      ; Save _all_ extended registers
        cld                         ; Clear direction flag
        LOAD_DS                     ; Load DS register

; Send an EOI to the PIC

        mov     al,20h              ; Send EOI to PIC
        cmp     [BYTE ebx+_IRQ],8   ; Clear PIC1 first if IRQ >= 8
        jb      @@1
        out     0A0h,al
@@1:    out     20h,al

; Check for mutual exclusion

        cmp     [BYTE ebx+_Inside],1
        je      @@ChainOldHandler
        mov     [BYTE ebx+_Inside],1

; Call the C interrupt handler function

        mov     [ebx+seg_IRQStack],ss   ; Switch to local stack
        mov     [ebx+ptr_IRQStack],esp
        mov     [TempSeg],ds
        mov     ss,[TempSeg]
        lea     esp,[ebx+_IRQStack]
        sti                             ; Re-enable interrupts
        push    ebx
        call    [DWORD ebx+_CHandler]
        pop     ebx
        cli
        mov     ss,[ebx+seg_IRQStack]   ; Restore previous stack
        mov     esp,[ebx+ptr_IRQStack]
        or      eax,eax
        jz      @@ChainOldHandler   ; Chain if not handled for shared IRQ

@@Exit: mov     [BYTE ebx+_Inside],0
        popad                       ; Restore all extended registers
        pop     es
        pop     ds
        pop     ebx
        iret                        ; Return from interrupt

@@ChainOldHandler:
        cmp     [DWORD ebx+_PrevIRQ],0
        jz      @@Exit
        mov     [BYTE ebx+_Inside],0
        mov     eax,[DWORD ebx+_PrevIRQ]
        mov     ebx,[DWORD ebx+_PrevIRQ+4]
        mov     [DWORD _PrevIRQ],eax
        mov     [DWORD _PrevIRQ+4],ebx
        popad                       ; Restore all extended registers
        pop     es
        pop     ds
        pop     ebx
        jmp     [cs:_PrevIRQ]       ; Chain to previous IRQ handler

cprocend
cpublic _PM_irqISRTemplateEnd
endif

;----------------------------------------------------------------------------
; PM_keyISR - keyboard interrupt subroutine dispatcher
;----------------------------------------------------------------------------
; Hardware interrupt handler for the keyboard interrupt, to dispatch control
; to high level C based subroutines. We save the state of all registers
; in this routine, and switch to a local stack. Interrupts are *off*
; when we call the user code.
;
; NOTE: This routine switches to a local stack before calling any C code,
;       and hence is _not_ re-entrant. However we ensure within this routine
;       mutual exclusion to the keyboard handling routine.
;----------------------------------------------------------------------------
cprocfar    _PM_keyISR

        push    ds              ; Save value of DS
        push    es
        pushad                  ; Save _all_ extended registers
        cld                     ; Clear direction flag

        LOAD_DS                 ; Load DS register

        cmp     [BYTE KyInside],1   ; Check for mutual exclusion
        je      @@Reissued

        mov     [BYTE KyInside],1
        NEWSTK  KyStack         ; Switch to local stack
        call    [CPTR _PM_keyHandler]   ; Call C code
        RESTSTK KyStack         ; Restore previous stack
        mov     [BYTE KyInside],0

@@Exit: popad                   ; Restore all extended registers
        pop     es
        pop     ds
        iret                    ; Return from interrupt

; When the BIOS keyboard handler needs to change the SHIFT status lights
; on the keyboard, in the process of doing this the keyboard controller
; re-issues another interrupt, while the current handler is still executing.
; If we recieve another interrupt while still handling the current one,
; then simply chain directly to the previous handler.
;
; Note that for most DOS extenders, the real mode interrupt handler that we
; install takes care of this for us.

@@Reissued:
ifdef   TNT
        push    eax
        push    ebx
        push    ecx
        pushfd                  ; Push flags on stack to simulate interrupt
        mov     ax,250Eh        ; Call real mode procedure function
        mov     ebx,[_PM_prevRealKey]
        mov     ecx,1           ; Copy real mode flags to real mode stack
        int     21h             ; Call the real mode code
        popfd
        pop     ecx
        pop     ebx
        pop     eax
else
        pushf
ifdef   USE_NASM
        call far dword [_PM_prevKey]
else
        call    [_PM_prevKey]
endif
endif
        jmp     @@Exit

cprocend

;----------------------------------------------------------------------------
; PM_chainPrevkey - Chain to previous key interrupt and return
;----------------------------------------------------------------------------
; Chains to the previous key interrupt routine and returns control
; back to the high level interrupt handler.
;----------------------------------------------------------------------------
cprocstart  PM_chainPrevKey

ifdef   TNT
        push    eax
        push    ebx
        push    ecx
        pushfd                  ; Push flags on stack to simulate interrupt
        mov     ax,250Eh        ; Call real mode procedure function
        mov     ebx,[_PM_prevRealKey]
        mov     ecx,1           ; Copy real mode flags to real mode stack
        int     21h             ; Call the real mode code
        popfd
        pop     ecx
        pop     ebx
        pop     eax
        ret
else

; YIKES! For some strange reason, when execution returns from the
; previous keyboard handler, interrupts are re-enabled!! Since we expect
; interrupts to remain off during the duration of our handler, this can
; cause havoc. However our stack macros always turn off interrupts, so they
; will be off when we exit this routine. Obviously there is a tiny weeny
; window when interrupts will be enabled, but there is nothing we can
; do about this.

        SWAPSTK KyStack         ; Swap back to previous stack
        pushf                   ; Push flags on stack to simulate interrupt
ifdef   USE_NASM
        call far dword [_PM_prevKey]
else
        call    [_PM_prevKey]
endif
        SWAPSTK KyStack         ; Swap back to C stack again
        ret
endif

cprocend

;----------------------------------------------------------------------------
; PM_key15ISR - Int 15h keyboard interrupt subroutine dispatcher
;----------------------------------------------------------------------------
; This routine gets called if we have been called to handle the Int 15h
; keyboard interrupt callout from real mode.
;
;   Entry:  AX  - Hardware scan code to process
;   Exit:   AX  - Hardware scan code to process (0 to ignore)
;----------------------------------------------------------------------------
cprocfar    _PM_key15ISR

        push    ds
        push    es
        LOAD_DS
        cmp     ah,4Fh
        jnz     @@NotOurs       ; Quit if not keyboard callout

        pushad
        cld                     ; Clear direction flag
        xor     ah,ah           ; AX := scan code
        NEWSTK  Ky15Stack       ; Switch to local stack
        push    _ax
        call    [CPTR _PM_key15Handler] ; Call C code
        _add    sp,2,4
        RESTSTK Ky15Stack       ; Restore previous stack
        test    ax,ax
        jz      @@1
        stc                     ; Set carry to process as normal
        jmp     @@2
@@1:    clc                     ; Clear carry to ignore scan code
@@2:    popad
        jmp     @@Exit          ; We are done

@@NotOurs:
ifdef   TNT
        push    eax
        push    ebx
        push    ecx
        pushfd                  ; Push flags on stack to simulate interrupt
        mov     ax,250Eh        ; Call real mode procedure function
        mov     ebx,[_PM_prevRealKey15]
        mov     ecx,1           ; Copy real mode flags to real mode stack
        int     21h             ; Call the real mode code
        popfd
        pop     ecx
        pop     ebx
        pop     eax
else
        pushf
ifdef   USE_NASM
        call far dword [_PM_prevKey15]
else
        call    [_PM_prevKey15]
endif
endif
@@Exit: pop     es
        pop     ds
ifdef flatmodel
        retf    4
else
        retf    2
endif

cprocend

;----------------------------------------------------------------------------
; PM_breakISR - Control Break interrupt subroutine dispatcher
;----------------------------------------------------------------------------
; Hardware interrupt handler for the Ctrl-Break interrupt. We simply set
; the Ctrl-Break flag to a 1 and leave (note that this is accessed through
; a far pointer, as it may well be located in conventional memory).
;----------------------------------------------------------------------------
cprocfar    _PM_breakISR

        sti
        push    ds              ; Save value of DS
        push    es
        push    _bx

        LOAD_DS                 ; Load DS register
ifdef flatmodel
        mov     ebx,[_PM_ctrlBPtr]
else
        les     bx,[_PM_ctrlBPtr]
endif
        mov     [UINT _ES _bx],1

; Run alternate break handler code if installed

        cmp     [CPTR _PM_breakHandler],0
        je      @@Exit

        pushad
        mov     _ax,1
        push    _ax
        call    [CPTR _PM_breakHandler] ; Call C code
        pop     _ax
        popad

@@Exit: pop     _bx
        pop     es
        pop     ds
        iret                    ; Return from interrupt

cprocend

;----------------------------------------------------------------------------
; int PM_ctrlBreakHit(int clearFlag)
;----------------------------------------------------------------------------
; Returns the current state of the Ctrl-Break flag and possibly clears it.
;----------------------------------------------------------------------------
cprocstart  PM_ctrlBreakHit

        ARG     clearFlag:UINT

        enter_c
        pushf                   ; Save interrupt status
        push    es
ifdef flatmodel
        mov     ebx,[_PM_ctrlBPtr]
else
        les     bx,[_PM_ctrlBPtr]
endif
        cli                     ; No interrupts thanks!
        mov     _ax,[_ES _bx]
        test    [BYTE clearFlag],1
        jz      @@Done
        mov     [UINT _ES _bx],0

@@Done: pop     es
        popf                    ; Restore interrupt status
        leave_c
        ret

cprocend

;----------------------------------------------------------------------------
; PM_ctrlCISR - Control Break interrupt subroutine dispatcher
;----------------------------------------------------------------------------
; Hardware interrupt handler for the Ctrl-C interrupt. We simply set
; the Ctrl-C flag to a 1 and leave (note that this is accessed through
; a far pointer, as it may well be located in conventional memory).
;----------------------------------------------------------------------------
cprocfar    _PM_ctrlCISR

        sti
        push    ds              ; Save value of DS
        push    es
        push    _bx

        LOAD_DS                 ; Load DS register
ifdef flatmodel
        mov     ebx,[_PM_ctrlCPtr]
else
        les     bx,[_PM_ctrlCPtr]
endif
        mov     [UINT _ES _bx],1

; Run alternate break handler code if installed

        cmp     [CPTR _PM_breakHandler],0
        je      @@Exit

        pushad
        mov     _ax,0
        push    _ax
        call    [CPTR _PM_breakHandler] ; Call C code
        pop     _ax
        popad

@@Exit: pop     _bx
        pop     es
        pop     ds
        iret                    ; Return from interrupt
        iretd

cprocend

;----------------------------------------------------------------------------
; int PM_ctrlCHit(int clearFlag)
;----------------------------------------------------------------------------
; Returns the current state of the Ctrl-C flag and possibly clears it.
;----------------------------------------------------------------------------
cprocstart  PM_ctrlCHit

        ARG     clearFlag:UINT

        enter_c
        pushf                   ; Save interrupt status
        push    es
ifdef flatmodel
        mov     ebx,[_PM_ctrlCPtr]
else
        les     bx,[_PM_ctrlCPtr]
endif
        cli                     ; No interrupts thanks!
        mov     _ax,[_ES _bx]
        test    [BYTE clearFlag],1
        jz      @@Done
        mov     [UINT _ES _bx],0

@@Done:
        pop     es
        popf                    ; Restore interrupt status
        leave_c
        ret

cprocend

;----------------------------------------------------------------------------
; PM_criticalISR - Control Error handler interrupt subroutine dispatcher
;----------------------------------------------------------------------------
; Interrupt handler for the MSDOS Critical Error interrupt, to dispatch
; control to high level C based subroutines. We save the state of all
; registers in this routine, and switch to a local stack. We also pass
; the values of the AX and DI registers to the as pointers, so that the
; values can be modified before returning to MSDOS.
;----------------------------------------------------------------------------
cprocfar    _PM_criticalISR

        sti
        push    ds              ; Save value of DS
        push    es
        push    _bx             ; Save register values changed
        cld                     ; Clear direction flag

        LOAD_DS                 ; Load DS register
ifdef flatmodel
        mov     ebx,[_PM_critPtr]
else
        les     bx,[_PM_critPtr]
endif
        mov     [_ES _bx],ax
        mov     [_ES _bx+2],di

; Run alternate critical handler code if installed

        cmp     [CPTR _PM_critHandler],0
        je      @@NoAltHandler

        pushad
        push    _di
        push    _ax
        call    [CPTR _PM_critHandler]  ; Call C code
        _add    sp,4,8
        popad

        pop     _bx
        pop     es
        pop     ds
        iret                    ; Return from interrupt

@@NoAltHandler:
        mov     ax,3            ; Tell MSDOS to fail the operation
        pop     _bx
        pop     es
        pop     ds
        iret                    ; Return from interrupt

cprocend

;----------------------------------------------------------------------------
; int PM_criticalError(int *axVal,int *diVal,int clearFlag)
;----------------------------------------------------------------------------
; Returns the current state of the critical error flags, and the values that
; MSDOS passed in the AX and DI registers to our handler.
;----------------------------------------------------------------------------
cprocstart  PM_criticalError

        ARG     axVal:DPTR, diVal:DPTR, clearFlag:UINT

        enter_c
        pushf                   ; Save interrupt status
        push    es
ifdef flatmodel
        mov     ebx,[_PM_critPtr]
else
        les     bx,[_PM_critPtr]
endif
        cli                     ; No interrupts thanks!
        xor     _ax,_ax
        xor     _di,_di
        mov     ax,[_ES _bx]
        mov     di,[_ES _bx+2]
        test    [BYTE clearFlag],1
        jz      @@NoClear
        mov     [ULONG _ES _bx],0
@@NoClear:
        _les    _bx,[axVal]
        mov     [_ES _bx],_ax
        _les    _bx,[diVal]
        mov     [_ES _bx],_di
        pop     es
        popf                    ; Restore interrupt status
        leave_c
        ret

cprocend

;----------------------------------------------------------------------------
; void PM_setMouseHandler(int mask, PM_mouseHandler mh)
;----------------------------------------------------------------------------
cprocstart  _PM_setMouseHandler

        ARG     mouseMask:UINT

        enter_c
        push    es

        mov     ax,0Ch          ; AX := Function 12 - install interrupt sub
        mov     _cx,[mouseMask] ; CX := mouse mask
        mov     _dx,offset _PM_mouseISR
        push    cs
        pop     es              ; ES:_DX -> mouse handler
        int     33h             ; Call mouse driver

        pop     es
        leave_c
        ret

cprocend

ifdef flatmodel

;----------------------------------------------------------------------------
; void PM_mousePMCB(void)
;----------------------------------------------------------------------------
; Mouse realmode callback routine. Upon entry to this routine, we recieve
; the following from the DPMI server:
;
;   Entry:  DS:_SI  -> Real mode stack at time of call
;           ES:_DI  -> Real mode register data structure
;           SS:_SP  -> Locked protected mode stack to use
;----------------------------------------------------------------------------
cprocfar    _PM_mousePMCB

        pushad
        mov     eax,[es:_di+1Ch]    ; Load register values from real mode
        mov     ebx,[es:_di+10h]
        mov     ecx,[es:_di+18h]
        mov     edx,[es:_di+14h]
        mov     esi,[es:_di+04h]
        mov     edi,[es:_di]
        call    _PM_mouseISR        ; Call the mouse handler
        popad

        mov     ax,[ds:_si]
        mov     [es:_di+2Ah],ax     ; Plug in return IP address
        mov     ax,[ds:_si+2]
        mov     [es:_di+2Ch],ax     ; Plug in return CS value
        add     [WORD es:_di+2Eh],4 ; Remove return address from stack
        iret                        ; Go back to real mode!

cprocend

;----------------------------------------------------------------------------
; void PM_int10PMCB(void)
;----------------------------------------------------------------------------
; int10 realmode callback routine. Upon entry to this routine, we recieve
; the following from the DPMI server:
;
;   Entry:  DS:ESI  -> Real mode stack at time of call
;           ES:EDI  -> Real mode register data structure
;           SS:ESP  -> Locked protected mode stack to use
;----------------------------------------------------------------------------
cprocfar        _PM_int10PMCB

        pushad
        push    ds
        push    es
        push    fs

        pushfd
        pop     eax
        mov     [es:edi+20h],ax     ; Save return flag status
        mov     ax,[ds:esi]
        mov     [es:edi+2Ah],ax     ; Plug in return IP address
        mov     ax,[ds:esi+2]
        mov     [es:edi+2Ch],ax     ; Plug in return CS value
        add     [WORD es:edi+2Eh],4 ; Remove return address from stack

; Call the install int10 handler in protected mode. This function gets called
; with DS set to the current data selector, and ES:EDI pointing the the
; real mode DPMI register structure at the time of the interrupt. The
; handle must be written in assembler to be able to extract the real mode
; register values from the structure

        push    es
        pop     fs                  ; FS:EDI -> real mode registers
        LOAD_DS
        NEWSTK  Int10Stack          ; Switch to local stack

        call    [_PM_int10Handler]

        RESTSTK Int10Stack          ; Restore previous stack
        pop     fs
        pop     es
        pop     ds
        popad
        iret                        ; Go back to real mode!

cprocend

endif

cpublic _PM_pmdosCodeEnd

endcodeseg  _pmdos

        END                     ; End of module
