511 lines
18 KiB
NASM
511 lines
18 KiB
NASM
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
|