	INCLUDE	TITLE.MAC
	.TITLE	<INT_PKG -- Interrupt Vector Interface Routines>
	.SBTTL	History

; int_pkg.asm  17 Nov 83  Craig Milo Rogers at USC/ISI
;	Debugged in non-DOS, DOS 1, and DOS 2 configurations.
;	Set to non-DOS for actual use.
;	Added int_prev().
; int_pkg.asm  15 Nov 83  Craig Milo Rogers at USC/ISI
;	Created this package to change and restore interrupt vectors
; on the IBM PC.

	.SBHED	Overview

;	This package manages interrupt vectors on the IBM PC.
;
; 1)	The package may be compiled to use the DOS calls to get and
;	set interrupt vectors, or it may be compiled to do these
;	tasks directly.
;
; 2)	The basic services that this module provides to clients are
;	to set and restore interrupt vectors.  When an interrupt vector
;	is set, its old contents are saved.  The old contents may be
;	restored upon demand by a client, or when a package termination
;	(clean-up) call is issued.
;
; 3)	Nested calls to set and restore the same interrupt vector are
;	not supported.
;
; 4)	The old interrupt vectors are saved in a local storage area.
;	The length of the storage area is an assembly parameter.  A
;	fixed area was selected over a linked list because a linked
;	list is more likely to be damaged by a runaway program.

;	Entry points (Lattice C 1.05 calling conventions):

; void
; int_ini()			/* Initializes the saved interrupt database.*/

; bool				/* FALSE ==> vector in use or out of mem. */
; int_setup(vec, newip, newcs)	/* Setup an interrupt vector. */
; int vec;			/* Interrupt vector number. */
; int newip;			/* Instruction pointer for interrupt code. */
; int newcs;			/* Code segment for interrupt code. */
;				/* Together, newip and newcs are the same
;				 * as a Large model pointer-to-function.
;				 * However, C routines may not be directly
;				 * called this way, since lots of registers
;				 * must be saved and setup first before C
;				 * code can be used. */

; long				/* Actually, a pointer to a "function". */
;				/* 0L ==> vector not saved, or was empty. */
; int_prev(vec)			/* Returns prev. contents of the vector. */
; int vec;			/* Interrupt vector number. */

; bool				/* FALSE ==> vector not in use. */
; int_restore(vec)		/* Restore an interrupt vector. */
; int vec;			/* Interrupt vector number. */

; void
; int_trm()			/* Terminate, ie restore all vectors. */

	.SBHED	Declarations

IF1
	INCLUDE	DOS.MAC	; C segments.
	INCLUDE	BMAC.MAC	; C calling conventions.
ENDIF


;	Here are the two configuration parameters.  Ideally these
; should come from an include file, not the source file.

NODOS	EQU	0		; 0 ==> Use DOS calls, 1 ==> Avoid DOS.
NSAVVEC	EQU	10		; Nomber of vectors to handle.


;	The following structure saves old interrupt vector contents:

SVEC	STRUC			; Saves old vectors.
SAVVEC	DW	?		; Saved vector number.
SAVIP	DW	?		; Saved Instruction Pointer.
SAVCS	DW	?		; Saved Code Segment.
SVEC	ENDS

NOVEC	EQU	0FFFFH		; Indicates an unused entry.


;	Return Codes:

TRUE	EQU	1		; Truth.
FALSE	EQU	0		; Falsehood.

	.SBHED	<Data Storage>

ABS0	SEGMENT	AT 0H		; Segment with interrupt vectors.
ABS0	ENDS

	DSEG

SAVAREA	SVEC	NSAVVEC DUP(<>)	; Saved interrupt vector blocks.

IFE NODOS			; Are we using DOS?
DOSTYPE	DW	?		; Gets the DOS level.
ENDIF

	ENDDS

	PSEG
	DB	'(C) Copyright Inner Loop Software, 1983'

	.SBHED	<FINDVEC -- Internal Routine to Find a Vector>

;	Called with the vector in AX.
;	Returns with a pointer to the vector save block in SI.
;		Carry clear -- found the vector save entry.
;		Carry set -- didn't find the vector save entry.
;	Modifies:  CX, SI, CF

FINDVEC	PROC	NEAR			; Internal routine.

	MOV	SI,OFFSET SAVAREA	; Address of save area.
	MOV	CX,NSAVVEC		; Number of vector save entries.

FINDLOOP:
	CMP	[SI].SAVVEC,AX		; Is this the vector?
	 JE	FOUNDIT			;   (yup, too bad)
	ADD	SI,TYPE SVEC		; Advance SI to next save entry.
	LOOP	FINDLOOP		; Loop for number of save entries.

	STC				; Set the carry flag -- not found.
	RET				; Return with error set.

FOUNDIT:
	CLC				; Clear carry - found entry.
	RET				; Return, no error.

FINDVEC	ENDP

	.SBHED	<INT_INI -- Initialize Saved Interrupt Database>

; void
; int_ini()			/* Initializes the saved interrupt database.*/

	BENTRY	INT_INI

	MOV	SI,OFFSET SAVAREA	; Address of save area.
	MOV	CX,NSAVVEC		; Number of vector save entries.
	XOR	AX,AX			; Clear AX for a handy source of 0.

INILOOP:
	MOV	[SI].SAVVEC,NOVEC	; Indicate no vector present.
	MOV	[SI].SAVIP,AX		; Clear saved Instruction Pointer.
	MOV	[SI].SAVCS,AX		; Clear saved Code Segment.
	ADD	SI,TYPE SVEC		; Advance SI to next save entry.
	LOOP	INILOOP			; Loop for number of save entries.

IFE NODOS				; Are we using DOS?
	MOV	AH,30H			; Get DOS Version function number.
	INT	21H			; DOS Function Call.
	MOV	DOSTYPE,AX		; Save the returned value.
ENDIF

	BEND	INT_INI			; That's all for initialization.

	.SBHED	<INT_SETUP -- Setup an Interrupt Vector>

; bool				/* FALSE ==> vector in use or out of mem. */
; int_setup(vec, newip, newcs)	/* Setup an interrupt vector. */
; int vec;			/* Interrupt vector number. */
; int newip;			/* Instruction pointer for interrupt code. */
; int newcs;			/* Code segment for interrupt code. */
;				/* Together, newip and newcs are the same
;				 * as a Large model pointer-to-function.
;				 * However, C routines may not be directly
;				 * called this way, since lots of registers
;				 * must be saved and setup first before C
;				 * code can be used. */

	BENTRY	INT_SETU <VEC,NEWIP,NEWCS>

	MOV	AX,VEC			; Vector for which we're looking.
	CALL	FINDVEC			; See if already allocated
	 JNC	SETBAD			;   (yup, too bad)

	MOV	SI,OFFSET SAVAREA	; Address of save area.
	MOV	CX,NSAVVEC		; Number of vector save entries.

SETFREE:
	CMP	[SI].SAVVEC,NOVEC	; Is this a free entry?
	 JE	SETVEC			;   (yup)
	ADD	SI,TYPE SVEC		; Advance SI to next save entry.
	LOOP	SETFREE			; Loop for number of save entries.

SETBAD:
	MOV	AX,FALSE		; Vector in use, or no free space.
	JMP SHORT SETDONE		; Go return to the user.

SETVEC:					; Let's set the interrupt vector.
IF NODOS

;	This code copies the old vector contents into the vector save
; block and sets the new vector contents, as an atomic operation.  This
; eliminates the possibility of an interrupt changing the vector's
; contents while the vector is being saved.

	MOV	BX,ABS0			; Get start of interrupt vectors.
	MOV	ES,BX			; ES is vector segment.
	ASSUME	ES:ABS0

					; AX still has target vector.
	MOV	BX,AX			; Transfer vector number.
	ADD	BX,BX			; Convert to vector address.
	ADD	BX,BX
	MOV	CX,NEWIP		; Get the new vector offset.
	MOV	DX,NEWCS		; Get the new vector section.

	PUSHF				; Save current interrupt setting.
	CLI				; ******* Disable Interrupts *******
	XCHG	CX,ES:[BX]		;;; Set vector's Instruction Ptr.
	XCHG	DX,ES:[BX+2]		;;; Set vector's Segment Number.
	MOV	[SI].SAVVEC,AX		;;; Mark this save block used.
	MOV	[SI].SAVIP,CX		;;; Store the old vector offset.
	MOV	[SI].SAVCS,DX		;;; Store the old vector segment.
	POPF				;;; ******* Restore Interrupts *******
					;;; (Next instruction still disabled)
	ASSUME	ES:NOTHING
ELSE

;	This code saves the old contents of the vector, then sets the new
; contents.  The operations are not atomic, so there is an implicit
; assumption that the interrupt vector will not be changed (by an
; interrupt routine) which this routine is running.

	CMP	BYTE PTR DOSTYPE,2	; Is this DOS 2 or greater?
	 JGE	SETDOS2			;   (yup)

;	This system is pre-DOS 2.  Save the old vector without DOS.

	MOV	BX,ABS0			; Get start of interrupt vectors.
	MOV	ES,BX			; ES is vector segment.
	ASSUME	ES:ABS0
					; AX still has target vector.
	MOV	BX,AX			; Transfer vector number.
	ADD	BX,BX			; Convert to vector address.
	ADD	BX,BX

	MOV	CX,ES:[BX]		; Get vector's Instruction Ptr.
	MOV	DX,ES:[BX+2]		; Get vector's Segment Number.
	MOV	[SI].SAVIP,CX		; Store the old vector offset.
	MOV	[SI].SAVCS,DX		; Store the old vector segment.
	MOV	[SI].SAVVEC,AX		; Mark this save block used.
	ASSUME	ES:NOTHING

	JMP SHORT SETDOSVEC		; Go set the vector.

SETDOS2:

;	Use the DOS 2 call to get old vec.  Store the vector number
; into the save block last.  This interlocks the save block contents
; so it can be referenced at interrupt level.

					; AX still has target vector.
	MOV	AH,35H			; DOS Get Vector function.
	INT	21H			; DOS Function Call.
	MOV	[SI].SAVIP,BX		; Store the old vector offset.
	PUSH	ES			; Store the old vector segment
	POP	[SI].SAVCS		;   via the stack.
	MOV	AX,VEC			; Get the vector number again.
	MOV	[SI].SAVVEC,AX		; Mark this save block used.
					; Fall into SETDOSVEC...

SETDOSVEC:

;	 Call DOS to set the interrupt vector.

					; Vector number already in AL.
	MOV	DX,NEWIP		; Fetch the new vector offset.
	PUSH	DS			; Save our data segment.
	PUSH	NEWCS			; Fetch the new vector segment.
	POP	DS			; Transfer new segment to DS.
	MOV	AH,25H			; DOS Set Interrupt Vector function.
	INT	21H			; DOS Function Call.
	POP	DS			; Restore our data segment.
ENDIF

	MOV	AX,TRUE			; Return TRUE to caller.
					; Fall into SETDONE...
SETDONE:
	BEND	INT_SETU

	.SBHED	<INT_PREV -- Return Previous Vector Contents>

; long				/* Actually, a pointer to a "function". */
;				/* 0L ==> vector not saved, or was empty. */
; int_prev(vec)			/* Returns prev. contents of the vector. */
; int vec;			/* Interrupt vector number. */

;	A 0L return can meas two things:  either the wrong vector was
; supplied to int_prev(), or the vector's previous contents were in fact
; zero.  0L is not a significant value for any of the vectors at the
; present time, and isn't a reasonable address for a procedure.
; However, it is quite possible for a vector to contain 0L if it has
; never been used before.  So, don't treat 0L as a fatal error, but
; don't treat it as a valid procedure, either.

	BENTRY	INT_PREV <VEC>

	MOV	AX,VEC			; Vector for which we're looking.
	CALL	FINDVEC			; Try to find save block.
	 JC	PREVERR			;   (not found)
	MOV	AX,[SI].SAVCS		; AX gets segment.
	MOV	BX,[SI].SAVIP		; BX gets offset.
	JMP SHORT PREVDONE		; Go return to the user.

PREVERR:
	XOR	AX,AX			; Failed to find target vector.
	XOR	BX,BX

PREVDONE:
	BEND	INT_PREV		; That's all, return code in AX, BX.

	.SBHED	<INT_RESTORE -- Restore a Saved Vector>

; bool				/* FALSE ==> vector not in use. */
; int_restore(vec)		/* Restore an interrupt vector. */
; int vec;			/* Interrupt vector number. */

	BENTRY	INT_REST <VEC>

	MOV	AX,VEC			; Vector for which we're looking.
	CALL	FINDVEC			; Look for the saved vector.
	 JNC	RESTVEC			;   (found it)
	MOV	AX,FALSE		; Failed to find target vector.
	JMP SHORT RESTDONE		; Go return to the user.

RESTVEC:				; SI points to the save block.

IF NODOS
	MOV	BX,AX			; Transfer vector number.
	ADD	BX,BX			; Convert to vector address.
	ADD	BX,BX
	MOV	CX,[SI].SAVIP		; Fetch the saved vector offset.
	MOV	DX,[SI].SAVCS		; Fetch the saved vector segment.
	PUSH	DS			; Save our data segment.
	MOV	AX,ABS0			; Get start of interrupt vectors.
	MOV	DS,AX			; Now work in vector segment.

	PUSHF				; Save current interrupt setting.
	CLI				; ******* Disable Interrupts *******
	MOV	[BX],CX			;;; Restore vector's Instruction Ptr.
	MOV	[BX+2],DX		;;; Restore vector's Segment Number.
	POPF				;;; ******* Restore Interrupts *******
	POP	DS			;;; Restore our data segment.
ELSE
					; Use DOS call to restore the vector.
					; Vector number already in AL.
	MOV	DX,[SI].SAVIP		; Fetch the saved vector offset.
	PUSH	DS			; Save our data segment.
	PUSH	[SI].SAVCS		; Fetch the saved vector segment.
	POP	DS			; Transfer saved segment to DS.
	MOV	AH,25H			; DOS Set Interrupt Vector function.
	INT	21H			; DOS Function Call.
	POP	DS			; Restore our data segment.
ENDIF

	MOV	[SI].SAVVEC,NOVEC	; Free this saved vector block.
	MOV	AX,TRUE			; Return TRUE to caller.
					; Fall into RESTDONE...

RESTDONE:
	BEND	INT_REST		; That's all, return code in AX.

	.SBHED	<INT_TRM -- Terminate and Restore All Vectors>

; void
; int_trm()			/* Terminate, ie restore all vectors. */

	BENTRY	INT_TRM

	MOV	SI,OFFSET SAVAREA	; Address of save area.
	MOV	CX,NSAVVEC		; Number of vector save entries.

TRMLOOP:
	CMP	[SI].SAVVEC,NOVEC	; Is there a vector present?
	 JE	TRMNEXT			;   (nope)

	PUSH	SI			; Save SI
	PUSH	CX			;   and CX accross subroutine call.
	BCALL	INT_REST <[SI].SAVVEC>	; Call the code to restore the vector.
	POP	CX			; Restore saved registers.
	POP	SI

TRMNEXT:
	ADD	SI,TYPE SVEC		; Advance SI to next save entry.
	LOOP	TRMLOOP			; Loop for number of save entries.

	BEND	INT_TRM			; That's all.

	ENDPS
	END
