dos_compilers/Microsoft QuickBASIC v4/INTRPT.ASM

359 lines
14 KiB
NASM
Raw Normal View History

2024-07-02 01:13:22 +02:00
TITLE INTERRUPT - BASCOM software interrupt calling routine
PAGE 56,132
;***
; INTERRUPT - BASCOM software interrupt calling routine
;
; Copyright <C> 1986, 1987 Microsoft Corporation
;
;Purpose:
; Allows a BASIC program to invoke an interrupt through a CALL statement.
;
; INTERRUPT allows BASIC to set AX,BX,CX,DX,BP,SI,DI, and the flags
; before the call. INTERRUPTX also allows DS and ES to be set.
; Both routines will return the values of the registers upon the
; completion of a successful call. If the interrupt could not
; be generated (due to a bad interrupt number or an illegal array)
; then the interrupt number will be set to -1 to indicate an error.
;
;******************************************************************************
;
;Note:
; The DOSSEG, .MODEL, .CODE, and .DATA? directives used in this program
; are part of the simplified segment system of MASM 5.0. If you have
; an earlier version of MASM, you must modify the source to define
; the segments required by Microsoft high-level languages. These
; segments are discussed in Appendix C of "Learning and Using QuickBASIC."
;
DOSSEG ;requires MASM 5.0 or higher
.MODEL medium
; Define all publicly accessible routines.
PUBLIC INTERRUPT, INTERRUPTX
; Frame structure definition
ARG1 = 0AH ;pointer to first of three arguments
ARG2 = 08H ;pointer to second of three arguments
ARG3 = 06H ;pointer to third of three arguments
; Frame temp variables
UCODE_FLGS = -02H ;user code flag register value
UCODE_DS = -04H ;user code DS register value
REG_NUM = -06H ;number of regs used (INTERRUPT=8, INTERRUPTX=10)
INT_ES = -08H ;INT ES register value
INT_DS = -0AH ;INT DS register value
INT_FLGS = -0CH ;INT flags register value
INT_DI = -1EH ;INT DI register value
INT_SI = -10H ;INT SI register value
INT_BP = -12H ;INT BP register value
INT_DX = -14H ;INT DX register value
INT_CX = -16H ;INT CX register value
INT_BX = -18H ;INT BX register value
INT_AX = -1AH ;INT AX register value
OLD_SI = -1CH ;save old SI for interpreter
OLD_DI = -1EH ;save old DI for interpreter
FRM_SIZ = -1EH ;negative size of frame temporaries
; Locations past frame allocation used to recover post-INT BP value.
INT_BP_TMP = -22H ;temp location for INT BP register value
;***
; INTERRUPT, and INTERRUPTX - BASCOM software interrupt calling interface
;
; Purpose:
; To allow a BASIC Compiler program to perform any software
; interrupt. The interrupt is executed with the registers
; set to values specified in a register variable. The post-
; interrupt values of the registers are then stored in
; another register variable.
;
; CALL INTERRUPT[X] (int_no AS INTEGER,
; inreg AS RegType[X],
; outreg AS RegType[X])
;
; Inputs:
; int_no = interrupt number (range 0 to 255) to execute
; inreg and outreg are register variables of type RegType[X]
; defined as follows;
;
; TYPE RegType
; ax AS INTEGER
; bx AS INTEGER
; cx AS INTEGER
; dx AS INTEGER
; bp AS INTEGER
; si AS INTEGER
; di AS INTEGER
; flags AS INTEGER
; END TYPE
;
;
; TYPE RegTypeX
; ax AS INTEGER
; bx AS INTEGER
; cx AS INTEGER
; dx AS INTEGER
; bp AS INTEGER
; si AS INTEGER
; di AS INTEGER
; flags AS INTEGER
; ds AS INTEGER
; es AS INTEGER
; END TYPE
;
; Outputs:
; If no error:
; int_no = unchanged (range 0 to 255)
; outreg: This array will be set to the post-interrupt
; register values. It has the same structure
; as inreg.
; If error:
; int_no = -1
; outreg unchanged. INT call is not performed.
; error occurs:
; first argument not 0 to 255 (2^8-1)
; second or third arguments not 0 to 1048575 (2^20-1)
; (VARPTR will always be in this range)
;
; Modifies:
; All, except BP, DS, and flags.
; Also, possible side effects of INT call.
;
; Exceptions:
; INT 24H call may result from some INT 21H MS-DOS calls.
;
;******************************************************************************
.CODE
INTERRUPT PROC FAR
PUSH BP ;save BASCOM frame pointer on stack
MOV BP,SP ;establish program frame reference
ADD SP,FRM_SIZ ;allocate working space for frame
MOV WORD PTR [BP].REG_NUM,08H ;eight regs used (not DS or ES)
JMP SHORT INTERRUPT_COMMON ;jump to common code
INTERRUPT ENDP
INTERRUPTX PROC FAR
PUSH BP ;save BASCOM frame pointer on stack
MOV BP,SP ;establish program frame reference
ADD SP,FRM_SIZ ;allocate working space for frame
MOV WORD PTR [BP].REG_NUM,0AH ;ten regs used (including DS and ES)
; Save a copy of the processor flags, SI, DI, and DS in the stack frame.
INTERRUPT_COMMON:
MOV [BP].OLD_SI,SI ;save old SI for interpreter
MOV [BP].OLD_DI,DI ;save old DI for interpreter
MOV [BP].UCODE_DS,DS;save DS for interpreter
PUSHF ;push the flags on the stack
POP [BP].UCODE_FLGS ;put value in the stack frame
; Move eight or ten words (depending if executing INTERRUPT or INTERRUPTX)
; of the integer input array from the far pointer computed to the frame.
MOV SI,[BP].ARG2 ;and array offset - pointer in DS:SI
LEA DI,[BP].INT_AX ;get start of temporary register storage.
MOV CX,[BP].REG_NUM ;eight or ten words to move
CLD ;movement is to higher memory
PUSH SS
POP ES
REP MOVSW ;move the array into the stack frame
; Save stack frame pointer to recover its value after the INT call.
PUSH BP ;saved to first word past the stack frame
; Create a two-instruction program on the stack to execute the
; INT call requested and return with stack cleanup.
;
; INT XX (hex: CD XX)
; RETF 06 (hex: CA 06 00)
;
; In the case of INT 25 and 26 (which leave a word of flags on the stack)
; We generate:
;
; INT XX (hex: CD XX)
; ADD SP,2 (hex: 83 C4 02)
; RETF 08 (hex: CA 08 00)
;
MOV SI,[BP].ARG1 ;[SI] = ptr to first CALL arg - interrupt #
MOV BX,[SI] ;[BL] = get integer value of INT type
OR BH,BH ;test if in range, 00 to FFH is legal
JZ NO_INT_ERROR ;if not, then error - jump
JMP INT_ERROR ;long jump to error routine
NO_INT_ERROR:
CMP BL,25H ;Interrupt 25 request?
JZ Int2526 ;Jump if so
CMP BL,26H ;Interrupt 26 request?
JNZ IntNorm ;Jump if other, "normal" interrupt
Int2526:
MOV AX,8 ;[AX] = argument of RETF instruction
PUSH AX
MOV AX,0CA02H ;[AX] = RETF opcode, & arg to ADD SP
PUSH AX
MOV AX,0C483H ;[AX] = ADD SP, opcode
PUSH AX
JMP SHORT IntInstruct
IntNorm:
XOR AX,AX ;value of second word past frame
PUSH AX ;put on stack - 00 byte of RETF and filler
MOV AX,06CAH ;value of third word past frame
PUSH AX ;put on stack - CA 06 bytes of RETF
IntInstruct:
MOV AH,BL ;move interrupt number to upper byte of AX
MOV AL,0CDH ;value of fourth word past frame
PUSH AX ;put on stack - CD XX bytes of INT XX
; Push far pointer of return address after the stack program
; executes, which is INT_RET in this code segment.
PUSH CS ;push current code segment for return segment
MOV AX,OFFSET INT_RET ;offset just after stack program call
PUSH AX ;push value for return offset
; Push far pointer pointer to the start of the stack program.
; The stack program will be entered by executing a RETF after the
; registers are set up.
PUSH SS ;push current stack segment for starting ptr
MOV AX,SP ;get current stack offset
ADD AX,6 ;move past the last three stack entries
PUSH AX ;push offset for starting ptr of stack program
; Move the input array values from the stack to their actual registers.
MOV AX,[BP].INT_FLGS ;get input flag register value
AND AX,0000111111010101B ;mask out undefined 8086 flags
PUSH AX ;push masked flag register value
MOV AX,[BP].INT_AX ;set up input AX value
MOV BX,[BP].INT_BX ;set up input BX value
MOV CX,[BP].INT_CX ;set up input CX value
MOV DX,[BP].INT_DX ;set up input DX value
MOV SI,[BP].INT_SI ;set up input SI value
MOV DI,[BP].INT_DI ;set up input DI value
; For DS and ES, leave in the compiler data segment values if:
; executing INTERRUPT; or executing INTERRUPTX with array values of -1.
CMP WORD PTR [BP].REG_NUM,08H ;test if executing INTERRUPT
JE INT_ES_DEF ;if so, then use both default values
CMP [BP].INT_DS,0FFFFH ;test if default DS to be used
JE INT_DS_DEF ;if so, then leave it unchanged
MOV DS,[BP].INT_DS ;set up input DS value
INT_DS_DEF:
CMP [BP].INT_ES,0FFFFH ;test if default ES to be used
JE INT_ES_DEF ;if so, then leave it unchanged
MOV ES,[BP].INT_ES ;set up input ES value
INT_ES_DEF:
MOV BP,[BP].INT_BP ;set up input BP value
;must be last move using BP
POPF ;set up input flag register value
; With all registers set according to the input array, execute the
; stack program.
;
; The following RETF pops the last two stack entries, which are
; interpreted as a far pointer to the stack program.
;
; The stack program executes the INT XX call which changes the
; registers (flags included) to the values to be put into the
; output array.
;
; The stack program then executes the RETF 06 instruction which
; does two operations. First, the next two entries on stack are
; popped and interpreted as a far ptr return address, which points
; the code at INT_RET in this code segment. Second, the stack
; pointer is then adjusted by six bytes to remove the six-byte
; program from the stack.
RET ;far return to execute stack program, etc.
INT_RET:
; The stack should now contain only the first entry past the
; frame, the value of the stack frame pointer itself. First
; save the BP value from the INT call, then get the old value
; to reference the frame.
PUSH BP ;save post-INT value of BP
MOV BP,SP ;temporary frame is second word past frame
MOV BP,[BP+02H] ;get real frame reference value
; Put post-INT value of all registers into the frame variables
; to be subsequently written into the output array.
PUSHF ;put flags on the stack
POP [BP].INT_FLGS ;put in post-INT flag register value
PUSH [BP].UCODE_FLGS ;get old copy of flags from frame
POPF ;and restore the old flag values
MOV [BP].INT_AX,AX ;put in post-INT AX value
MOV [BP].INT_BX,BX ;put in post-INT BX value
MOV [BP].INT_CX,CX ;put in post-INT CX value
MOV [BP].INT_DX,DX ;put in post-INT DX value
MOV AX,[BP].INT_BP_TMP ;get post-INT BP value (one entry past frame)
MOV [BP].INT_BP,AX ;put in post-INT BP value
MOV [BP].INT_SI,SI ;put in post-INT SI value
MOV [BP].INT_DI,DI ;put in post-INT DI value
MOV [BP].INT_DS,DS ;put in post-INT DS value
MOV [BP].INT_ES,ES ;put in post-INT ES value
; Move frame register values to the output array whose
; far pointer is in the frame.
MOV DS,[BP].UCODE_DS;replace original DS value
LEA SI,[BP].INT_AX ;get start of register area in frame
PUSH DS
POP ES
MOV DI,[BP].ARG3 ;get output array offset
MOV CX,[BP].REG_NUM ;eight or ten words to move
CLD ;movement is toward upper memory
REP MOVSW ;perform the transfer
; Clean up stack to remove frame. Remove CALL arguments with RETF.
MOV SI,[BP].OLD_SI ;replace old SI for interpreter
MOV DI,[BP].OLD_DI ;replace old DI for interpreter
MOV SP,BP ;deallocate temporary frame variables
POP BP ;return compiler frame pointer
RET 06 ;remove three CALL arguments and far return
; If error, then restore DS, set int_no to -1 to report error,
; clean up, and exit.
INT_ERROR:
MOV SI,[BP].ARG1 ;ptr to first CALL arg - interrupt number
MOV [SI],0FFFFH ;set interrupt number to -1 for error
MOV SI,[BP].OLD_SI ;replace old SI for interpreter
MOV DI,[BP].OLD_DI ;replace old DI for interpreter
MOV DS,[BP].UCODE_DS;replace original DS value
MOV SP,BP ;deallocate temporary frame variables
POP BP ;return compiler frame pointer
RET 06 ;remove three CALL arguments and far return
INTERRUPTX ENDP
END