;TSRUTIL.ASM

;Define segment names used by C
;
_TEXT   segment byte public 'CODE'
_TEXT   ends

CONST   segment word public 'CONST'
CONST   ends

_BSS    segment word public 'BSS'
_BSS    ends

_DATA   segment word public 'DATA'
_DATA   ends

DGROUP  GROUP   CONST, _BSS, _DATA

    assume  CS:_TEXT, DS:DGROUP  

    public  _new_int13, _init_intr
IFDEF MULTI
    public  _timer_int_chain
    public  _new_int10, _new_int25, _new_int26
ELSE
    public  _deinstall
ENDIF

    extrn   _ss_save:near       ;save foreground SS
    extrn   _sp_save:near       ;save foreground SP
    extrn   _unsafe_flag:near   ;if true, don't interrupt
    extrn   _old_int13:near     
IFDEF MULTI
    extrn   _old_int8:near      
    extrn   _old_int10:near     
    extrn   _old_int25:near     ; note difference between
    extrn   _old_int26:near     ; old_int25 and _old_int25!
ELSE
    extrn   _multiplex_id:near  ;our int 2f id byte
ENDIF

_TEXT   segment

IFNDEF MULTI
;*****
;void far deinstall(void)
;function to use int 2f to ask TSR to deinstall itself
;the registers are probably all changed when our tsr exits
;so we save then and perform the INT 2f. The TSR exit will
;eventually bring us back here. Then the registers are restored
;This function is called from the foreground, not the TSR 

DEINSTALL   equ     1

_deinstall       proc    far
    push si
    push di
    push bp
    mov word ptr _ss_save,ss         ;save our stack frame
    mov word ptr _sp_save,sp

    mov cs:_ds_save,ds      ; save DS for later restore

    mov bx,cs
    mov dx,offset TerminateAddr ;bx:dx points to terminate address
    mov ah, byte ptr _multiplex_id
    mov al, DEINSTALL
    int 2fh                 ;call our TSR
;
;if TSR terminates ok, we'll skip this code and return to Terminate Addr
;
    jmp short NoTerminate

TerminateAddr:
;Restore DS and stack
        mov     ax,cs:_ds_save  ;bring back our data segment
        mov     ds,ax           ;destroyed by int 2f

        mov al,2            ;Set value for success

        mov     ss, word ptr _ss_save   ;restore our stack        
        mov     sp, word ptr _sp_save   ;destroyed by int 2f

NoTerminate:
        cbw             ;Extend return value to word
        pop bp
        pop di
        pop si
        ret
_deinstall endp
ENDIF

;*****
;void inc_unsafe_flag(void) - increment unsafe flag
;*****
inc_unsafe_flag proc    far
    push    ax
    push    ds
    mov ax,DGROUP       ;make DS = to our TSR C data segment
    mov ds,ax

    inc word ptr _unsafe_flag

    pop ds              ;put DS back to whatever it was
    pop ax
    ret
inc_unsafe_flag endp

;*****
;void dec_unsafe_flag(void) - decrement unsafe flag
;*****
dec_unsafe_flag proc    far
    push    ax
    push    ds
    mov ax,DGROUP       ;make DS = to our TSR 'C' data segment
    mov ds,ax

    dec word ptr _unsafe_flag 

    pop ds              ;put DS back to whatever it was
    pop ax
    ret
dec_unsafe_flag endp

;we can't trap the following interrupts in C for a number of
;reasons
;   INT 13 returns info in the FLAGS, but a normal IRET
;   restores the flags
;
;   INT 25 & 26 leave the flags on the stack.  The user
;   must pop the off after performing an INT 25 or 26
;
;   These interrupts pass information via registers such
;   as DS. We don't want to change DS.
;
;   Since DS is unknown, we must call the old interrupts
;   via variables in the code segment. The _init_intr routine
;   sets up these CS variables from ones with nearly-identical
;   names in the C data segment in TSREXAMP.C.

;*****
;void far init_intr(void)
;move interrupt pointer saved in the C program to our CS data area
;*****
_init_intr proc far
        push    es
        push    bx

IFDEF MULTI
        les     bx,dword ptr _old_int10
        mov     word ptr cs:old_int10,bx
        mov     word ptr cs:old_int10+2,es

        les     bx,dword ptr _old_int25
        mov     word ptr cs:old_int25,bx
        mov     word ptr cs:old_int25+2,es

        les     bx,dword ptr _old_int26
        mov     word ptr cs:old_int26,bx
        mov     word ptr cs:old_int26+2,es
ENDIF
        ; note incredibly confusing distinction 
        ; between e.g. _old_int13 and old_int13
        les     bx,dword ptr _old_int13
        mov     word ptr cs:old_int13,bx
        mov     word ptr cs:old_int13+2,es

        pop     bx
        pop     es
        ret
_init_intr endp

;*****
;void far new_int13(void) - disk interrupt
;*****
_new_int13 proc far             
    call    inc_unsafe_flag
    pushf   ;simulate interrupt call    
    call    cs:old_int13
    call    dec_unsafe_flag        
    ret 2   ; leave flags intact
_new_int13 endp

IFDEF MULTI
;*****
;void far new_int10(void) - video interrupt
;*****
_new_int10 proc far             
    call    inc_unsafe_flag
    pushf   ;simulate interrupt call    
    call    cs:old_int10
    call    dec_unsafe_flag        
    iret
_new_int10 endp

;*****
;void far new_int25(void) - MS-DOS absolute sector read
;*****
_new_int25  proc far                
    call    inc_unsafe_flag
    call    cs:old_int25
    call    dec_unsafe_flag 
    ret ; user must pop flags - MS-DOS convention
        ; so leave them on the stack
_new_int25 endp

;*****
;void far new_int26(void) - MS-DOS absolute sector write
;*****
_new_int26 proc far                 
    call    inc_unsafe_flag
    call    cs:old_int26    
    call    dec_unsafe_flag 
    ret ; user must pop flags - MS-DOS convention
        ; so leave them on the stack
_new_int26 endp

;*****
;void far timer_int_chain(void) - jump to next timer ISR
;we need to clean up the stack because of this call
;*****
_timer_int_chain proc far
    mov _ax_save,ax
    pop ax
    pop ax
    mov ax,_ax_save
    jmp dword ptr _old_int8 
_timer_int_chain    endp
ENDIF

;
;save areas for original interrupt vectors
;
IFDEF MULTI
old_int10   dd  0       ;video
old_int25   dd  0       ;sector read
old_int26   dd  0       ;sector write
ENDIF
old_int13   dd  0       ;disk

_ds_save    dw  0
_TEXT   ends

_DATA   segment 
IFDEF MULTI
_ax_save dw 0
ENDIF
_DATA   ends

    end
