; *********** EVENTHDW.ASM *******************************************

        .model medium, basic
        .data

IRQLevel        dw      -1      ; the requested IRQ level
OrigMask        db      0       ; how the 8259 IMR was on entry
Chained         db      0       ; did we chain in or take over?

        .code

PreviousVector  LABEL DWORD     ; placed in the code segment to...
PVOffset        dw      -1      ; ...facilitate chaining if req'd
PVSegment       dw      77      ; a signature to avoid multi-installs

EXTRN   SetUEvent:FAR           ; this is in the BC runtime
EXTRN   B_OnExit:FAR            ;          ditto

InstallHandler  PROC irqnum, chnd       ; both passed BYVAL

; This takes care of wrapping our metaphorical tentacles around the
; PC.  We take over the interrupt vector associated with the IRQ level
; passed, saving the previous contents so that we can put things back
; the way they were when we exit, and we enable the interrupt level in
; the PC hardware.

        cmp     PVSegment, 77   ; insure this is first time
        jne     iherr
        mov     ax, irqnum      ; test for correct range
        cmp     ax, 2
        jb      iherr           ; vector too low, < 2
        cmp     ax, 7
        ja      iherr           ; vector too high, > 7
        mov     dx, offset RemoveInt
        push    cs              ; push far address of RemoveInt
        push    dx              ; to register the exit routine
        call    B_OnExit        ; so that we don't hang machine
        or      ax, ax          ; registered OK?
        jz      iherr           ; error: too many registered routines
        mov     ax, chnd        ; will we chain or not?
        mov     chained, al     ; keep local copy
        mov     ax, irqnum      ; get this again
        mov     IRQLevel, ax    ; save for removal routine
        mov     cx, ax          ; hold value a moment as shift count
        add     al, 8           ; add 8 to turn low IRQ into INT
        mov     ah, 35H         ; use DOS to get current...
        INT     21H             ; ...holder of the vector
        mov     PVSegment, es   ; save for chaining, removal
        mov     PVOffset, bx
        test    chained, -1     ; who does the 8259?
        jnz     @f              ; ...not me...

        mov     ah, 1           ; turn IRQ # into bit mask
        shl     ah, cl
        push    ax              ; preserve mask
        cli                     ; do this with all interrupts off
        in      al, 21H         ; get PC's Interrupt Mask Register
        mov     OrigMask, al    ; save for later removal
        or      al, ah          ; set this IRQ level OFF...
        out     21H, al         ; ...in the IMR
        sti                     ; interrupts back on
        mov     ax, cx          ; get IRQ number again
        add     al, 8           ; turn into INT number
@@:
        mov     ah, 25H         ; tell DOS we're taking this vector
        push    ds              ; hold DS a moment
        mov     bx, cs          ; transfer CS to DS
        mov     ds, bx
        mov     dx, offset IntHandler; get address of our MASM handler
        INT     21H             ; grab the vector, Victor
        pop     ds              ; restore DS
        test    chained, -1     ; who's doing the 8259?
        jnz     @f              ; ..the other guy is

        pop     ax              ; get mask back
        not     ax              ; invert mask bits
        cli                     ; all interrupts off
        in      al, 21H         ; get IMR
        and     al, ah          ; set this IRQ level ON
        out     21H, al         ; set IMR
        sti                     ; interrupts back on again
@@:
        xor     ax, ax          ; return zero: all OK
@@:     ret

iherr:  mov     ax, -1          ; couldn't install: return TRUE
        jmp     @b

InstallHandler  ENDP

RemoveInt       PROC

; RemoveInt will undo the InstallHandler by restoring the PC to the
; state it was in before we came along.  This is vital, because
; otherwise when the program terminates the interrupt vector will
; point into volatile space, guaranteeing an eventual machine hang.

; Because we "register" this routine with B_OnExit, restoring the
; machine state becomes a part of BASIC's normal shutdown process...
; we don't need to call this routine explicitly.

        test    chained, -1     ; who's job is the 8259?
        jnz     @f              ; not ours, skip mask stuff
        mov     cx, IRQLevel    ; level we're using
        mov     ax, 1           ; turn into bit mask
        shl     ax, cl          ; by shifting
        mov     ah, al          ; protect it
        push    ax              ; save AH for later
        cli                     ; do this with interrupts off
        in      al, 21H         ; get IMR
        or      al, ah          ; this IRQ level OFF
        out     21H, al         ; set IMR
        sti                     ; interrupts on again
@@:     mov     ax, IRQLevel    ; IRQ number
        add     al, 8           ; becomes INT number
        mov     ah, 25H         ; SET VECTOR call
        push    ds              ; protect DS
        lds     dx, PreviousVector; get original vector
        INT     21H             ; restore it
        pop     ds              ; so we can read OrigMask
        test    chained, -1     ; who's job is the 8259?
        jnz     @f              ; not ours, skip mask stuff
        pop     ax              ; recover mask in AH
        test    OrigMask, ah    ; the state before we interfered
        jnz     @f              ; originally off, leave off
        not     ah              ; bit clear = ON
        cli                     ; all interrupts off
        in      al, 21H         ; get IMR
        and     al, ah          ; this IRQ level ON
        out     21H, al         ; set IMR
        sti                     ; interrupts back on
@@:     ret

RemoveInt       ENDP

IntHandler      PROC FAR  ;note that we don't just say "PROC" here

; This routine notifies the BASIC runtime that an event has occurred.
; You MUST save and restore any registers you use.  In this example
; we're overly pessimistic, assuming that BC will destroy everything.

        assume  cs:@code, ds:nothing, es:nothing, ss:nothing

        sti                     ; allow more important interrupts
        push    ax
        push    ds              ; only save the registers you really need to
        push    di
        push    es              ; here, we assume BC can destroy anything
        push    si
        push    dx
        push    cx
        push    bx
        call    SetUEvent       ; notify BC/QB that the event occurred
;
;  If there are hardware-specific duties, do them here
;
        pop     bx
        pop     cx
        pop     dx
        pop     si
        pop     es
        pop     di
        mov     ax, @data
        mov     ds, ax
        assume  ds:@data
        test    chained, -1     ; is someone else doing the 8259?
        pop     ds
        assume  ds:nothing
        jnz     @f              ; yes, skip the following
        mov     al, 20H         ; clear PC's interrupt by
        out     20H, al         ; using a non-specific EOI
        pop     ax
        iret                    ; resume program in progress
@@:
        pop     ax
        jmp     [PreviousVector]

IntHandler      ENDP

        END

