dos_compilers/Microsoft QuickBASIC v3/INT86.ASM
2024-07-01 13:00:14 -07:00

511 lines
18 KiB
NASM
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

TITLE INT86 - BASCOM software interrupt calling routine
;-----------------------------------------------------------------------
; R E V I S I O N H I S T O R Y
; 11-Mar-87 [1] RDK Since VARPTR returns negative values for DGROUP
; offsets 32K to 64K, add 64K to these values.
;-----------------------------------------------------------------------
;
; Microsoft Math Version
;
;
; Frame structure definition
; The following is used in INT86, INT86X, and PTR86.
ARG1 = 0AH ;pointer to first of three arguments
ARG2 = 08H ;pointer to second of three arguments
ARG3 = 06H ;pointer to third of three arguments
UCODE_SEG = 04H ;user code return pointer - segment
UCODE_OFF = 02H ;user code return pointer - offset
UCODE_BP = 00H ;user code BP register value (FRAME base)
; The following frame temp variables are used in INT86 and INT86X.
UCODE_FLGS = -02H ;user code flag register value
OUTARY_SEG = -04H ;output array pointer - segment
OUTARY_OFF = -06H ;output array pointer - offset
REG_NUM = -08H ;number of regs used (INT86=8, INT86X=10)
INT_ES = -0AH ;INT ES register value
INT_DS = -0CH ;INT DS register value
INT_FLGS = -0EH ;INT flags register value
INT_DI = -10H ;INT DI register value
INT_SI = -12H ;INT SI register value
INT_BP = -14H ;INT BP register value
INT_DX = -16H ;INT DX register value
INT_CX = -18H ;INT CX register value
INT_BX = -1AH ;INT BX register value
INT_AX = -1CH ;INT AX register value
FRM_SIZ = -1CH ;negative size of frame temporaries
; Locations past frame allocation used to recover post-INT BP value.
FRM_BP = -1EH ;frame BP saved for post-INT recovery
INT_BP_TMP = -20H ;temp location for INT BP register value
;***
; INT86, INT86X - 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 an integer array. The post-
; interrupt values of the registers are then stored in
; another integer array.
;
; CALL INT86[X] (int_no%,VARPTR(in_ary%(x)),VARPTR(out_ary%(y)))
;
; Inputs:
; int_no% = interrupt number (range 0 to 255) to execute
; in_ary%(x) to in_ary%(x+7) = input array. (INT86)
; in_ary%(x) to in_ary%(x+9) = input array. (INT86X)
; This array specifies the register values at the INT as
; follows (INT86 uses DGROUP to set DS and ES, not array
; elements 8 and 9.):
; in_ary%(x) = AX
; in_ary%(x+1) = BX
; in_ary%(x+2) = CX
; in_ary%(x+3) = DX
; in_ary%(x+4) = BP
; in_ary%(x+5) = SI
; in_ary%(x+6) = DI
; in_ary%(x+7) = flags
; in_ary%(x+8) = DS (if -1, then use DGROUP value) (INT86X only)
; in_ary%(x+9) = ES (if -1, then use DGROUP value) (INT86X only)
; Outputs:
; If no error:
; int_no% = unchanged (range 0 to 255)
; out_ary%(y) to out_ary%(y+9) = output array.
; This array will be set to the post-interrupt
; register values. It has the same structure
; as in_ary%.
; If error:
; int_no% = -1
; out_ary% unchanged. INT call is not performed.
; error occurs:
; first argument not 0 to 255 (2^8-1)
;[1] second or third arguments not in VARPTR range
;[1] -32767 (-2^15+1) to 1048575 (2^20-1)
; 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.
;***
DATA SEGMENT WORD PUBLIC 'DATA'
DATA ENDS
DGROUP GROUP DATA
CODE SEGMENT BYTE PUBLIC 'CODE'
ASSUME CS:CODE,DS:DGROUP,ES:DGROUP,SS:DGROUP
PUBLIC INT86
INT86 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 INT86_COMMON ;jump to common code
PUBLIC INT86X
INT86X 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 in the stack frame.
INT86_COMMON:
PUSHF ;push the flags on the stack
POP [BP].UCODE_FLGS ;put value in the stack frame
; From the third CALL argument on the stack, get the pointer to the
; VARPTR value of the output array and compute a far pointer to
; it. Then save the far pointer segment and offset in the frame.
MOV SI,[BP].ARG3 ;get pointer to s.p. VARPTR value
CALL SP_TO_PTR ;convert to far pointer in DX:AX
JC INT_ERROR_JUMP ;if error, then jump
MOV [BP].OUTARY_SEG,DX ;save far pointer segment in frame
MOV [BP].OUTARY_OFF,AX ;save far pointer offset in frame
; From the second CALL argument on the stack, obtain the far
; pointer to the input array in the same manner as above.
MOV SI,[BP].ARG2 ;get pointer to s.p. VARPTR value
CALL SP_TO_PTR ;convert to far pointer in DX:AX
JNC NO_INT_ERROR ;if no error, then jump
INT_ERROR_JUMP:
JMP INT_ERROR ;long jump to error routine
NO_INT_ERROR:
; Move eight or ten words (depending if executing INT86 or INT86X)
; of the integer input array from the far pointer computed to the frame.
MOV DS,DX ;move array pointer segment
MOV SI,AX ;and array offset - far pointer in DS:SI
LEA DI,[BP].FRM_SIZ ;get frame offset - ES = SS = DGROUP
MOV CX,[BP].REG_NUM ;eight or ten words to move
CLD ;movement is to higher memory
REP MOVSW ;move the array into the stack frame
PUSH ES ;get compiler data segment value on stack
POP DS ;restore so DS = ES = SS = compiler data seg
; 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) <--- fourth word past stack frame
; RETF 06 (hex: CA 06 00) <--- third and second word
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
MOV SI,[BP].ARG1 ;ptr to first CALL arg - interrupt number
MOV AX,[SI] ;from pointer, get integer value of INT type
OR AH,AH ;test if in range, 00 to FFH is legal
JNZ INT_ERROR_JUMP ;if not, then error - jump
MOV AH,AL ;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 CODE: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 INT86; or executing INT86X with array values of -1.
CMP WORD PTR [BP].REG_NUM,08H ;test if executing INT86
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
; Restore DS to SS. Move frame register values to the output
; array whose far pointer is in the frame.
PUSH SS ;put compiler data segment on stack
POP DS ;and restore DS register to it
LEA SI,[BP].FRM_SIZ ;get start of register area in frame
MOV ES,[BP].OUTARY_SEG ;get output array segment
MOV DI,[BP].OUTARY_OFF ;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 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:
PUSH SS ;put compiler data segment value on stack
POP DS ;and restore DS to its original value
MOV SI,[BP].ARG1 ;ptr to first CALL arg - interrupt number
MOV [SI],0FFFFH ;set interrupt number to -1 for error
MOV SP,BP ;deallocate temporary frame variables
POP BP ;return compiler frame pointer
RET 06 ;remove three CALL arguments and far return
INT86X ENDP
INT86 ENDP
;***
; PTR86 - Compute segment/offset from variable VARPTR value.
; Purpose:
; From a s.p. VARPTR of a compiler data variable, compute an
; equivalent segment and offset integer values. These variables
; are used to set INT86X register input array values.
;
; CALL PTR86 (varseg%,varoff%,VARPTR(var))
;
; Inputs:
; var = data variable (any type)
; Outputs:
; if no error, varseg% = segment part of far pointer to var
; varoff% = offset part of far pointer to var
; if error, varseg% = -1
; Modifies:
; AX, DX, and SI.
; Exceptions:
; None.
;***
PUBLIC PTR86
PTR86 PROC FAR
PUSH BP ;save BASCOM frame pointer on stack
MOV BP,SP ;establish program frame reference
MOV SI,[BP].ARG3 ;ptr to third CALL arg - VARPTR of variable
CALL SP_TO_PTR ;compute segment:offset in DX:AX
MOV SI,[BP].ARG1 ;ptr to first CALL arg - segment result
JC PTR86_ERROR ;if error, then jump
MOV [SI],DX ;put segment value into argument
MOV SI,[BP].ARG2 ;ptr to second CALL arg - offset result
MOV [SI],AX ;put offset value into argument
POP BP ;restore old frame pointer
RET 06H ;far return to caller - remove three stack args
PTR86_ERROR:
MOV [SI],0FFFFH ;put -1 in first arg for error report
POP BP ;restore old frame pointer
RET 06H ;far return to caller - remove three stack args
PTR86 ENDP
;***
; SP_TO_PTR - converts s.p. VARPTR value to segment/offset values
; Purpose:
; From the s.p. value pointed by DS:SI, convert to an integer
;[1] value. Report an error if not in the range -2^15 to 2^20-1.
;[1] Negative values are adjusted by 2^16 (65535) since VARPTR
;[1] maps the values 32768...65535 to -32768...-1 for interpreter
;[1] compatibility. Convert 20-bit address to segment and offset
; integer values using the standard 8086/8088 address computation:
;
; (16-bit segment)*16 + (16-bit offset) = 20-bit address
;
; Inputs:
; SI = pointer to s.p. address value
;
; The value is represented in Microsoft binary format, consisting
; of four bytes starting at the pointer given. The first three
; bytes are the low, middle, and high bytes of the number's
; mantissa while the fourth is the exponent. Sign-magnitude
; representation is used with the sign being the MSB of the high
; mantissa byte. All values are stored normalized with the
; mantissa MSB hidden with the sign bit. The binary point of the
; mantissa is before its MSB. The exponent is biased by 80H.
; A zero exponent implies a zero value independent of the mantissa's
; contents.
; Outputs:
; If no error:
; CF = 0 (carry cleared)
; DX = pointer segment integer result
; AX = pointer offset integer result
; If error:
; CF = 1 (carry set)
; DX,AX = undefined
; Modifies:
; None.
; Exceptions:
; None.
;***
SP_TO_PTR PROC NEAR
; Load s.p. number into DX:AX.
MOV AX,[SI] ;AH=middle mantissa - AL=low mantissa
MOV DX,[SI+2] ;DH=exponent - DL=sign/high mantissa
; If exponent is zero, then number value is zero.
OR DH,DH ;test if exponent is zero
JZ SP_ZERO ;if so, then jump
; Put in hidden MSB of mantissa.
OR DL,80H ;set hidden high-order mantissa bit
; Move data so DX:AX has 24-bit mantissa right-justified in 32 bits;
; and CX has 8-bit exponent right-justified in 16 bits.
PUSH CX ;save register...
XOR CX,CX ;clear both CH and CL
XCHG CL,DH ;DX:AX=24-bit mantissa - CX=exponent
; Remove 80H bias from exponent. Also subtract 12 (0CH) to move
; the binary from left of the 24th bit to left of the 12th bit of
; DX:AX. The resulting value is the number of left shifts of DX:AX
; resulting in a binary value with 20 bits left of the binary point
; and 12 bits right of it.
SUB CX,8CH ;remove 80H bias and 0CH to move b.pt. 12 bits
JE SP_NO_SHIFT ;if no shifting needed, then jump
JB SP_RIGHT_SHIFT ;if negative, then right shifting needed
SP_LEFT_SHIFT_LOOP:
SHL AX,1 ;shift low-order word left once
RCL DX,1 ;shift carry into high-order word
LOOP SP_LEFT_SHIFT_LOOP ;shift until done
JMP SHORT SP_NO_SHIFT ;shifting left done - jump
; A negative shift count is the inverse of the number of right
; shifts needed.
SP_RIGHT_SHIFT:
NEG CX ;compute shift count of DX:AX to the right
SP_RIGHT_SHIFT_LOOP:
SHR DX,1 ;shift high-order word right once
RCR AX,1 ;shift carry into low-order word
LOOP SP_RIGHT_SHIFT_LOOP ;loop until done...
;[1] If input value was negative, test magnitude for legal range
;[1] of 1 to 32768 (1 to 8000H). First decrement the value, so the
;[1] range is now 0 to 7FFFH. Test for the range and, if legal,
;[1] invert the significant bits.
SP_NO_SHIFT:
TEST BYTE PTR [SI+2],80H ;[1]test if value was negative
JZ SP_NOT_NEG ;[1]if not, then jump
SUB AH,10H ;[1]decrement value in high nybble of AH
SBB DX,0 ;[1]propagate if borrow was needed
TEST DX,NOT 7FFH ;[1]test if in legal range
JNZ SP_ERROR ;[1]jump to return error
XOR AH,0F0H ;[1]invert high nybble of AH
XOR DX,0FFFH ;[1]invert rest of value in DX
; The lower 4 bits of the 20-bit integer value are in the upper
; nybble of AX. Rotate and mask to move to lower nybble which
; will be the offset returned.
SP_NOT_NEG: ;[1]
ROL AX,1 ;rotate leftmost nybble in AX to rightmost...
ROL AX,1 ;second bit...
ROL AX,1 ;third bit...
ROL AX,1 ;fourth bit - done
AND AX,000FH ;leave only rightmost nybble left
; The upper 16 bits of the 20-bit integer value are in DX.
; This value is the integer divided by 16 and is therefore the
; paragraph value of the pointer. Since VARPTR values are
; relative to the compiler data segment, add the data segment
; value to compute the pointer segment.
MOV CX,DS ;get compiler data segment value
ADD DX,CX ;add to get pointer (permit wraparound)
POP CX ;restore register
CLC ;clear carry for success
RET ;near return to caller
; If zero, report 0 as the offset and the compiler data segment.
SP_ZERO:
XOR AX,AX ;clear offset result
MOV DX,DS ;set segment result to compiler DS
CLC ;clear carry for success
RET ;near return to caller
; If error, then set carry to report it.
SP_ERROR: ;[1]
POP CX ;[1]
STC ;set carry for failure
RET ;near return to caller
SP_TO_PTR ENDP
CODE ENDS
END