;***************************************************************** ; ; Copyrigth (C) 1984 Logitech. All Rights Reserved. ; ; Permission is hereby granted to registered users to use or ; abstract the following program in the implementation of ; customized versions. This permission does not include the ; right to redistribute the source code of this program. ; ; LOGITECH SA. CH-1143 Apples, Switzerland ; ; Modula-2/86 Run Time Support package ; ; Module: SERVICES.ASM ; Provides most of the functions to execute ; Modula-2/86 programs. ; ; Version: 8086, RAM-based, MS-DOS 1.1 and MS-DOS 2.0 ; Release: 1.1 - Dec 84 ; ;***************************************************************** CGROUP group code DGROUP group data assume CS: CGROUP assume DS: DGROUP assume ES: NOTHING assume SS: NOTHING include RTS.INC ;***************************************************************** ; ; EXPORT QUALIFIED public RTS_BRANCH public SAVE_CPU_INFO public STACK_OVF public DIV_BY_ZERO public NMI_server ; ;***************************************************************** ;***************************************************************** code segment public 'code' ; FROM RTS IMPORT extrn RTS_DS:word ; yes, this goes in CODE segment! extrn SYS_RESET:near extrn TERMINATE:near extrn COMP_STACK:near extrn NORM_ADDR:near extrn WRITE_STATUS:near ; FROM TRANSFER IMPORT extrn SET_INTERRUPT_MASK:near extrn TRANSFER:near extrn IOTRANSFER:near extrn NEWPROCESS:near extrn MON_ENTRY:near extrn MON_EXIT:near extrn LISTEN:near extrn FREE_INT_VECT:near extrn get_device_status:near extrn set_device_status:near ; FROM DBUG IMPORT extrn RTD_AFTER_LOAD : near extrn INSTALL_DEBUG:near code ends ;***************************************************************************** ;***************************************************************************** data segment public 'data' ; FROM RTS IMPORT extrn cur_process:byte ; :ProcessDescriptor extrn CUR_PROC_addr:dword ; (ptr to current ProcessDesc) extrn BASE_PAGE_PTR:dword ; pointer to prog seg prefix extrn term_proc_addr:dword ; pointer to termination proc extrn old_NMI_vector:dword ; ISR to be ex. if no 8087 data ends ;***************************************************************************** ;***************************************************************************** data segment public 'data' even TempWord dw ? ; temporary word storage.. TEMP_W dw ? ; another temporary word.. OldIP dw ? ; interrupt frame, saved and OldCS dw ? ; restored by DYN_PAR_COPY OldFlags dw ? ; data ends ;***************************************************************************** ;***************************************************************************** code segment public 'code' ; Run Time Support system JUMP TABLE ; ; The entries in this table cannot be changed without ; corresponding changes to the Modula-2/86 Compiler! ; It is suggested that extensions to the RTS be implemented ; with a different interrupt than the standard RTS interrupt. ; RTS_JMP_TBL dw SYS_RESET ; 00h slow dw M2_HALT ; 01h slow dw TRANSFER ; 02h slow, but saves itself dw IOTRANSFER ; 03h slow, but saves itself dw NEWPROCESS ; 04h slow dw MON_ENTRY ; 05h slow dw MON_EXIT ; 06h slow dw LISTEN ; 07h slow dw GET_RETURN_POINT ; 08h slow dw SET_RETURN_POINT ; 09h slow dw RUN_PROGRAM ; 0Ah slow dw slow_TERMINATE ; 0Bh slow dw COM_CASE ; 0Ch dw COM_CASE ; 0Dh dw COM_CASE ; 0Eh dw COM_CASE ; 0Fh dw COM_CASE ; 10h dw CASE_ERROR ; 11h slow dw PAR_COPY ; 12h dw DYN_PAR_COPY ; 13h slow dw STACK_CHECK ; 14h dw FREE_INT_VECT ; 15h slow dw WRITE_STAT ; 16h slow dw GET_PD_ADDR ; 17h slow dw ALLOC_HEAP ; 18h dw FCT_RET_ERR ; 19h slow dw NORM_ADDRESS ; 1Ah dw ADD_ADDR ; 1Bh dw ADD_A_C ; 1Ch dw SUB_ADDR ; 1Dh dw SUB_A_C ; 1Eh dw EQ_ADDR ; 1Fh dw GT_EQ_ADDR ; 20h dw GT_EQ_ADDR ; 21h dw CONV_A_C ; 22h dw CARD_OVF ; 23h slow dw INTEGER_OVF ; 24h slow dw RANGE_ERROR ; 25h slow dw PSP_POINTER ; 26H slow dw INSTALL_DEBUG ; 27H dw addr_ovf ; 28H slow dw install_termP ; 29H slow dw real_ovf ; 2AH slow dw real_udf ; 2BH slow dw get_device_status ; 2CH slow dw set_device_status ; 2DH slow ; public RTS_BRANCH RTS_BRANCH: ;========== ; This is the entry point for all the functions ; of RTS. During execution of these functions, ; interrupts are disabled. ; Upon entry: ; AL contains the function code. Parameters ; for the functions are on stack or in registers. ; SI and ES must not be used for parameters, ; they are overwritten here. ; We don't need to save all the registers, since we ; come here on explicite demande (SWI 228) and not ; through a hardware interrupt. ; Set the ES to data segment of RTS: ; Note: in the current release, the compiler ; does not assume the DS to hold the ; value of the current data segment. ; It is however safer not just to destroy it. MOV ES,RTS_DS xor ah,ah ; AL contains the function code, test if legal CMP AL, NBR_FCT JB VALID_FCT mov si,ES:word ptr cur_proc_addr + 2 mov es,si MOV ES:CUR_PROCESS.PD_STATUS,ILL_FCT_CODE mov es,rts_ds call save_cpu_info JMP TERMINATE VALID_FCT: MOV SI,AX SHL SI,1 ; At this point: ; AL holds the RTS-Function-Code, SI = 2 * AX. ; ES holds the Data Segment of RTS, ; nothing saved up to now JMP RTS_JMP_TBL [SI] ;*********************** That's the branch ;----------------------------------------------------------- ; public SAVE_CPU_INFO SAVE_CPU_INFO PROC NEAR ;============ ; Utility routine to save registers in the process descr: ; Upon entry: ES is data segment of RTS ; Upon exit: DS:0 point to current process mov si,ES:word ptr cur_proc_addr + 2 mov es,si MOV ES:CUR_PROCESS.PD_DS, DS ; save process' DS mov si, es mov ds, si ; We have to save some more information ; (used for the P_M_DUMP and for TRANSFER): MOV CUR_PROCESS.PD_SP,SP MOV CUR_PROCESS.PD_BP,BP MOV CUR_PROCESS.PD_SS,SS mov bp,sp mov si,[bp]+2 mov CUR_PROCESS.PD_IP,si ; offset of return address mov si,[bp]+4 mov CUR_PROCESS.PD_CS,si ; segment of return address mov si,[bp]+6 mov CUR_PROCESS.PD_FLAGS,si mov bp,cur_process.pd_bp ret SAVE_CPU_INFO ENDP ;----------------------------------------------------------- slow_TERMINATE: ;============== ; This procedure had to be introduced to make sure that a call ; to SAVE_CPU_INFO is executed when coming via System.Terminate call save_cpu_info jmp terminate ;----------------------------------------------------------- install_termP: ;============= ; input: ; cx:bx contains address of term proc to be installed ; IF bx=MAXCARD THEN just get current value ; ELSE exchange old and new value ; output: ; cx:bx contains former/current value of term_proc_addr ; this allows chaining to be done in System inc bx ; if bx=MAXCARD ==> bx=0 jnz install_and_get get: mov bx,ES:word ptr term_proc_addr mov cx,ES:word ptr term_proc_addr + 2 iret install_and_get: dec bx ; restore old value xchg bx,ES:word ptr term_proc_addr xchg cx,ES:word ptr term_proc_addr + 2 iret ;----------------------------------------------------------- STACK_CHECK: ;=========== ; BX = stack clearance requested, in bytes ; we first have to check, if the current stack is the one ; of the modula program. If we are interrupted inside MS-DOS, ; the stack points to an area inside MS-DOS and the test for ; stack-overflow we make here has no sense. mov dx,ds ; save ds mov ax,ES:word ptr cur_proc_addr + 2 mov ds,ax mov ax, ss cmp ax,rts_ds jb SP_OK add bx, sp_reserve ; BX is space required on stack mov ax, sp sub ax, bx ; compute new limit jb STACK_BOO ; oops, wrap thru 0 mov bx, ss mov cl,4 shr ax,cl add bx,ax ; convert SP to paragraph pointer mov ax,word ptr CUR_PROCESS.PD_HEAP_TOP shr ax,cl add ax,word ptr CUR_PROCESS.PD_HEAP_TOP+2 ;ditto with heap top ptr sub bx,ax ; stack below HeapTop? jbe STACK_BOO ; yup SP_OK: mov ds,dx ; restore ds IRET STACK_BOO: mov ds,dx ; restore ds mov es,rts_ds CALL SAVE_CPU_INFO ;;; JMP SHORT STACK_OVF ;----------------------------------------------------------- ; public STACK_OVF STACK_OVF: ;========= ; This is the entry through RTS_BRANCH for treatment ; of a stack overflow: mov ds,rts_ds ; in case we come from somewhere lds si,cur_proc_addr MOV CUR_PROCESS.PD_STATUS,STACK_OVF_CODE JMP TERMINATE ; No return! page ;----------------------------------------------------------- ; public NMI_server NMI_server: ;========== invalid_op_bit equ 1 ; 2**0 denorm_op_bit equ 2 ; 2**1 zero_divide_bit equ 4 ; 2**2 real_ovf_bits equ 11 ; 2**3 + 2**1 + 2**0 real_udf_bit equ 16 ; 2**4 unmasked_bits equ 31 ; sum of above values !!!!! ; wait ; wait til 8087 is ready, 8087 is present !!!!! ; the WAIT resulted in an endless wait push ax ; save value of AX push ds ; save value of ds mov ds,rts_ds ; to address TempWord AND old_NMI_vec esc 2FH,TempWord ; FNSTSW: get status word of 8087 mov ax,TempWord and ax,unmasked_bits ; erase all uninteresting bits jnz from8087 ; none of the expected 8087 interrupts, call old server inc sp ; restore stack, DS was only saved for inc sp ; those interrupts from the 8087 pop ax ; restore value of AX jmp old_NMI_vector from8087: pop ds ; restore value of ds inc sp ; restore stack, AX was only saved for inc sp ; those interrupts not from the 8087 ; clear lines etc for next time !!!!!!!!!!!!!!!!!!!!!!!!!!!! ; it is absolutely necessary that this statement is executed ; any further access to 8087 via WAIT results in endless wait, ; if the interrupt lines are not cleared esc 1CH,dx ; this generates 0DBE2H = FNCLEX for 8087 test ax,zero_divide_bit jnz div_by_zero test ax,real_ovf_bits jnz real_ovf test ax,real_udf_bit jnz real_udf ;----------------------------------------------------------- ; public DIV_BY_ZERO DIV_BY_ZERO: ;=========== ; We arrive here NOT through RTS_BRANCH, but directly ; from the interrupt, that the CPU performs in case ; of a division by zero. So, we have to save the registers ; that are relevant for the dump and the debugger: MOV ES, RTS_DS CALL SAVE_CPU_INFO ; Set the function code to some reasonable value: MOV CUR_PROCESS.PD_STATUS,ZERO_DIVIDE_CODE JMP TERMINATE ;----------------------------------------------------------- real_ovf: ;======== MOV ES, RTS_DS CALL SAVE_CPU_INFO MOV CUR_PROCESS.PD_STATUS,real_ovf_CODE JMP TERMINATE ;----------------------------------------------------------- real_udf: ;======== MOV ES, RTS_DS CALL SAVE_CPU_INFO MOV CUR_PROCESS.PD_STATUS,real_udf_code JMP TERMINATE ;----------------------------------------------------------- M2_HALT: ;======= ; The following registers are destroyed: SI, ES. ; (DS is already saved) call save_cpu_info MOV CUR_PROCESS.PD_STATUS,HALT_CODE JMP TERMINATE ;----------------------------------------------------------- GET_RETURN_POINT: ;================ SET_RETURN_POINT: ;================ JMP NOT_YET ; Reserved entries for use in connection with ; separate program loading and execution. ;----------------------------------------------------------------- GET_OLD_PROGRAM: ; We arrive here after termination of an ; overlay and - more precisely - after ; execution of TERMINATE. Stack is already set ; to top-of-stack of father program. ; BP and DS are restored for father program. MOV ES, RTS_DS mov ax,ES:word ptr cur_proc_addr + 2 mov ds,ax ; swap to the main process of the terminating program ; if father_proc <> NIL ( process ended ) mov ax,word ptr cur_process.pd_father_proc+2 cmp ax,nil_seg je rest_old_pd mov cx,cur_process.pd_status mov si,word ptr cur_process.pd_father_proc + 2 mov ds,si mov cur_process.pd_status,cx mov ES: word ptr cur_proc_addr+2,ds REST_OLD_PD: ; Restore old P.D.: POP word ptr CUR_PROCESS.PD_FATHER_PROC POP word ptr CUR_PROCESS.PD_FATHER_PROC + 2 POP word ptr CUR_PROCESS.PD_PROG_END POP word ptr CUR_PROCESS.PD_PROG_END + 2 POP AX MOV CUR_PROCESS.PD_PRIO_MASK, AX ; reset priority CALL SET_INTERRUPT_MASK POP DS ; has to be restored last ; Return to the father program: IRET data segment public 'data' NEW_PROG_START DW ? ; variable for 'RUN_PROGRAM' NEW_PROG_ENTRY DW ?,? ; the same data ends RUN_PROGRAM: ;=========== ; This function prepares the stack and ; starts a new program. Parameters: ; BX= segment addr of program area ; (used to prepare the new stack). ; DX:CX segment:offset of program entry point, ; Save the parameters: call save_cpu_info mov es,rts_ds MOV ES:NEW_PROG_START, BX MOV ES:NEW_PROG_ENTRY, CX MOV ES:NEW_PROG_ENTRY + 2, DX ; The old stack (current-one) still holds ; the return block, to go back to the father ; program upon termination. ; Save some values of the P.D. on the old stack: PUSH CUR_PROCESS.PD_DS ; has to be first PUSH CUR_PROCESS.PD_PRIO_MASK PUSH word ptr CUR_PROCESS.PD_PROG_END + 2 PUSH word ptr CUR_PROCESS.PD_PROG_END PUSH word ptr CUR_PROCESS.PD_FATHER_PROC + 2 PUSH word ptr CUR_PROCESS.PD_FATHER_PROC ; Now, we push the entry of the termination ; routine and set the new values for PROG_END: MOV AX, 0 ; interrupt disable PUSH AX ; flags PUSH CS MOV AX, OFFSET GET_OLD_PROGRAM PUSH AX PUSH CUR_PROCESS.PD_BP MOV word ptr CUR_PROCESS.PD_PROG_END + 2, SS MOV word ptr CUR_PROCESS.PD_PROG_END, SP ; New value for father process. It becomes ; NIL, because the current process will be ; the main of the new program: MOV AX, NIL_OFF MOV BX, NIL_SEG MOV word ptr CUR_PROCESS.PD_FATHER_PROC + 2, BX MOV word ptr CUR_PROCESS.PD_FATHER_PROC, AX ; Now, we create the new stack: MOV AX,word ptr CUR_PROCESS.PD_HEAP_TOP MOV BX,word ptr CUR_PROCESS.PD_HEAP_TOP + 2 CALL NORM_ADDR INC BX ; BX= seg of free memory MOV AX,ES:NEW_PROG_START SUB AX, BX JA NEW_PROG_OK MOV CUR_PROCESS.PD_STATUS,CALL_ERR_CODE POP BP IRET ; To return in this case (error), we ; execute the termination routine NEW_PROG_OK: ; Set the new stack: CALL COMP_STACK MOV SS, BX MOV SP, AX ; the old value is stored in PROG_END ; Put the address of the termination routine ; on the new stack. In case of normal termination, ; a RETF will be executed by the program and ; we will arrive in TERMINATE with status=normal. PUSH CS MOV AX, OFFSET TERMINATE PUSH AX ; Now push the entry address of ; the new program: PUSH CUR_PROCESS.PD_FLAGS PUSH ES:word ptr NEW_PROG_ENTRY + 2 PUSH ES:word ptr NEW_PROG_ENTRY ; BP is set to 0FFFFH, so the debugger ; can recognize the beginning ; of a new overlay: MOV BP, 0FFFFH ; it will be pushed in new program ; make sure ES=rts_ds and DS:[SI]='current process' CALL RTD_AFTER_LOAD ;...and call the new program: IRET ;--------------------------------------------------------------------------- COM_CASE: ;======== ; Common Entry Point for all kind of CASE evaluations ; The actual value of the tag is in BX. ; The parameters are in the code segment, right after the INT instr. ; First fetch the return addr, to get the addr of the parameters: POP DI POP ES PUSH ES ; Restore it, used for IRET ; Get the first parameter: MOV CX, ES: [DI] ; Set DI to the next parameter: INC DI INC DI ; Now select the corresponding routine: CMP AL, CASE_3_CARD_FCT JAE CASE_3 CMP AL, CASE_2_CARD_FCT JAE CASE_2 ; otherwise, it must be CASE_1: CASE_1: MOV DX, CX ; just to save it INC CX ; Search 1 more than the actual number of value. This is needed ; distinguish the case where the last element matches from the case ; where no element matches. MOV AX, BX cld REPNE SCASW ; Search the actual tag in the list ; DI points now to the element after the one that matches the actual ; tag. If no value matches, DI points to the word 2 positions after ; the last one in the list. DI is now used as the index in the table ; with the entry points: SHL DX, 1 ; Size of list to skip ADD DI, DX PUSH ES: WORD PTR [DI] - 2 ; Entry point for actual tag. The '-2' corrects for the incrementation ; of DI after the search. If no element had matched, we will find the ; address of the ELSE part. IRET CASE_2: MOV DX, ES: [DI] ; Lowest value ; CX holds the highest value, DX the lowest one. ; Set DI to poin to the jumptable: INC DI INC DI INC CX ; highest value + 1 CMP AL, CASE_2_CARD_FCT JNE CASE_2_INT CASE_2_CARD: ; The tag is a CARDINAL CMP BX, DX ; Test if lower than lowest value JAE CASE_2_1 MOV BX, CX ; actual tag was below lowest value CASE_2_1: ; The tag is above or equal to lowest value CMP BX, CX ; Test if higher than highest value JB CASE_2_OK MOV BX, CX JMP SHORT CASE_2_OK CASE_2_INT: ; The tag is an INTEGER CMP BX, DX ; Test for lowest value JGE CASE_2_2 MOV BX, CX CASE_2_2: ; Tag is greater or equal to lowest value CMP BX, CX ; Test for highest value JL CASE_2_OK MOV BX, CX CASE_2_OK: SUB BX, DX ; Tag - Lowest Value SHL BX, 1 PUSH ES: WORD PTR [BX + DI] IRET CASE_3: PUSH DI ; just to save it MOV DX, 0 ; Counter CASE_3_NEXT: INC DX CMP DX, CX JA CASE_3_FOUND ; The tag value was not found: proceed with the counter (DX) ; pointing to the ELSE part. MOV SI, ES: [DI] ; low limit of next intervall ; Set DI to the next high limit: INC DI INC DI CMP AL, CASE_3_CARD_FCT JNE CASE_3_INT CASE_3_CARD: ; Tag is a CARDINAL CMP BX, SI JB CASE_3_BELOW MOV SI, ES: [DI] ; high limit CMP BX, SI JBE CASE_3_FOUND JMP SHORT CASE_3_ABOVE ; It's not this one CASE_3_INT: ; Tag is an INTEGER CMP BX, SI JL CASE_3_BELOW MOV SI, ES: [DI] ; high limit CMP BX, SI JLE CASE_3_FOUND JMP SHORT CASE_3_ABOVE ; It's not this one CASE_3_BELOW: INC DI INC DI CASE_3_ABOVE: ; Set DI to the low limit of next intervall INC DI INC DI JMP SHORT CASE_3_NEXT CASE_3_FOUND: ; DX is the index in the jumptable ; CX is the number of listed intervalls SHL CX, 1 SHL CX, 1 ; CX is now size of list POP DI INC DI INC DI ; DI is the addr of the list ADD DI, CX SHL DX, 1 ADD DI, DX PUSH ES: WORD PTR [DI] IRET CASE_ERROR: ;========== call save_cpu_info MOV CUR_PROCESS.PD_STATUS, CASE_ERR_CODE JMP TERMINATE ; END CASE ;--------------------------------------------------------------------------- PAR_COPY: ;======== ; Used to copy a fix size value-parameter from its actual argument ; into the place inside the local variables of a procedure, reserved ; for that copy: ; Upon entry: CX = size of parameter, ; BX = offset, relativ to BP, where the addr of argument is ; DI = offset, relativ to BP, where to copy it. MOV SI, BX LDS SI, DWORD PTR [BP+SI] ; (DS,SI) hold source addr MOV AX, SS MOV ES, AX ADD DI, BP ; (ES,DI) hold dest addr MOV AX, CX ; save the counter SHR CX, 1 ; number of words to copy REP MOVSW AND AX, 1 ; check if odd JZ PAR_COPY_1 MOVSB ; move the last byte, if any PAR_COPY_1: mov ds,rts_ds mov si,word ptr cur_proc_addr + 2 mov ds,si MOV DS,CUR_PROCESS.PD_DS IRET DYN_PAR_COPY: ;============ ; Used to copy a value-parameter of type ARRAY OF T from the actual ; argument on the stack of the called procedure. The copy is placed ; topstack and its address (SS and offset) is put in the procedure ; interface. ; Upon entry: CX holds size of the element of the array. ; DI holds offset, relativ to BP, where the address and ; the high index stand (Offset, Segment, High). ; The low index is assumed to be zero. ; Upon exit: The address of the copy replaces the address of the ; original ([BP+DI] upon entry). call save_cpu_info mov es,rts_ds MOV AX, [BP+DI] + 4 ; High index value INC AX ; # of elements = high+1 CMP CX, 1 JE SIZE_IN_AX ; no multiplication needed CMP CX, 4 JA MUL_NEEDED SHL AX, 1 CMP CX, 2 JBE SIZE_IN_AX ; NOTE: in case the size was an odd ; number, we still have to multiply ; by the next higher even number. SHL AX, 1 JMP SHORT SIZE_IN_AX MUL_NEEDED: MUL CX SIZE_IN_AX: ; Save the return block from the stack: POP ES:OldIP POP ES:OldCS POP ES:OldFlags ; Check, if there is enough room on the stack: MOV BX, AX PUSH AX ; just to save it PUSH DI ; just to save it CALL SP_TEST ; returns AX<>0, if error CMP AX, 0 JZ STACK_GOOD CALL STACK_OVF ; no room for the copy STACK_GOOD: POP DI ; restore it POP CX ; restore it SUB SP, CX AND SP, 0FFFEH ; Mask out last bit, to ; ensure an even address. MOV DX, es ; save es (don't use stack) LDS SI, DWORD PTR [BP+DI] ; Source address MOV [BP+DI], SP ; Store the destination addr MOV [BP+DI] + 2, SS MOV DI, SP MOV AX, SS MOV ES, AX ; (ES,DI) = Dest addr INC CX ; number of bytes SHR CX, 1 ; CX = number of words REP MOVSW ; Restore the return block: MOV es, DX ; restore es PUSH ES:OldFlags PUSH ES:OldCS PUSH ES:OldIP lds si,ES:cur_proc_addr MOV DS, CUR_PROCESS.PD_DS IRET ; END PARAMETER_COPY ;--------------------------------------------------------------------------- SP_TEST PROC near ; only called by DYN_PAR_COPY ; Used registers: AX, BX, CX, DX, SI, DI ; BX holds the required size. SP is checked for room to grow by ; the required number of bytes + some reserve. AX returns 0 if ok ; and 0FFH if overflow occurs. mov ax,ss cmp ax,rts_ds jb stack_ok ADD BX, SP_RESERVE ; first check, if SP does not go through zero: CMP BX, SP JA STACK_BAD MOV AX, SP SUB AX, BX ; that's the new stack limit MOV BX, SS CALL NORM_ADDR ; Returns: BX=seg, AX=offset (<16) MOV cx, AX MOV DI, BX MOV AX,word ptr CUR_PROCESS.PD_HEAP_TOP ; DS: --> P.D. MOV BX,word ptr CUR_PROCESS.PD_HEAP_TOP + 2 CALL NORM_ADDR ; Returns: BX=seg, AX=offset (<16) CMP DI, BX ; test segment JA STACK_OK JB STACK_BAD CMP cx, AX ; test offset JA STACK_OK STACK_BAD: MOV AX, 0FFH ; means: error RET STACK_OK: MOV AX, 0 ; means: no error RET SP_TEST ENDP WRITE_STAT: ;========== ; BX holds the status value to be interpreted call save_cpu_info mov ds,rts_ds ; expected by WRITE_STATUS CALL WRITE_STATUS lds si,cur_proc_addr MOV DS, CUR_PROCESS.PD_DS IRET ;--------------------------------------------------------------------------- GET_PD_ADDR: ;=========== ; Upon entry: (DX,BX) hold address, where to put the addr of CUR_PROCESS call save_cpu_info MOV ES, DX MOV ES:WORD PTR [BX], OFFSET CUR_PROC_addr mov ax,rts_ds MOV ES:WORD PTR 2[BX],ax ; DS of RTS MOV DS, CUR_PROCESS.PD_DS IRET ;--------------------------------------------------------------------------- ALLOC_HEAP: ;========== ; Increases the Heap by the requested size ; (in register BX). Checks for collision ; Heap - Stack. ;;;;;;;;; Fast procedure mov ax,es:word ptr cur_proc_addr + 2 mov es,ax MOV ES: CUR_PROCESS.PD_DS, DS MOV AX,ES:word ptr CUR_PROCESS.PD_HEAP_TOP + 2 ADD BX,ES:word ptr CUR_PROCESS.PD_HEAP_TOP JC FIX_OFFSET ; save the new heap_top: PUSH AX ; segment PUSH BX ; offset JMP SHORT NORM_HEAP_TOP FIX_OFFSET: ; there was an overflow of the offset: ADD AX, 1000H PUSH AX ; new segment PUSH BX ; and old offset JC HEAP_BAD ; we ask for too much NORM_HEAP_TOP: MOV CL, 4 SHR BX, CL INC BX ADD BX, AX ; normalized new segment JC HEAP_BAD ; BX is the segment value just above the new ; Heap_Top. On the stack we have saved that ; new Heap_Top. Now we have to normalize the stack: MOV AX, SP MOV CL, 4 SHR AX, CL MOV DX, SS ADD AX, DX ; norm. stack segment CMP AX, BX ; compare segments only JB HEAP_BAD HEAP_OK: POP word ptr CUR_PROCESS.PD_HEAP_TOP POP word ptr CUR_PROCESS.PD_HEAP_TOP + 2 HEAP_RET: MOV DS, CUR_PROCESS.PD_DS IRET HEAP_BAD: MOV CUR_PROCESS.PD_STATUS, HEAP_OVF_CODE POP AX ; dummy POP AX JMP SHORT HEAP_RET ;--------------------------------------------------------------------------- FCT_RET_ERR: ;=========== ; This error will occur, if a function terminates without an ; explicite RETURN statement. call save_cpu_info MOV CUR_PROCESS.PD_STATUS, FCT_RET_ERR_CODE JMP TERMINATE ; No return! ;--------------------------------------------------------------------------- NORM_ADDRESS: ;============ ; GOAL: brings an address variable in its normalized form, ; i.e. segment as large as possible, offset = [0..15]. ; The program is terminated in case of overflow. ; INPUT: the address in (DS,BX) ; OUTPUT: same as input MOV AX, BX MOV BX, DS CALL NORM_ADDR MOV DS, BX MOV BX, AX JC ADDR_OVF ; address larger than 20 Bits! IRET ;--------------------------------------------------------------------------- ADD_ADDR: ;======== ; GOAL: Adds two addresses and checks the result for overflow. ; INPUT: the 2 addresses to add are in (DX,DI) and (DS,BX). ; OUTPUT: the resulting address in (DS,BX). MOV AX, DI ; add the offsets: ADD AX, BX JNC OFF_OK_1 ; IF CARRY means: the sum of the offsets gives an overflow, ; we have to add 1000H to the segment values: ADD DX, 1000H JC ADDR_OVF OFF_OK_1: MOV BX, DS ; add the segments: ADD BX, DX JC ADDR_OVF ;;; Don't make the following shortcut! ;;; It returns a non-normalized address and ;;; therefore the address comparison is slower! ;;; CMP BX, 0F000H ;;; JB ADD_ADDR_DONE ; overflow not possible ; check for overflow: CALL NORM_ADDR JC ADDR_OVF ADD_ADDR_DONE: MOV DS, BX MOV BX, AX IRET ADD_A_C: ;======= ; GOAL: Adds an ADDRESS and a CARDINAL and checks the result ; for overflow. ; INPUT: the ADDRESS is in (DS,BX) and the CARDINAL in (DX) ; OUTPUT: the resulting ADDRESS in (DS,BX). MOV AX, DX ; add the offsets: ADD AX, BX MOV BX, DS JNC OFF_OK_2 ; IF CARRY means: the sum of the offsets gives an overflow, ; so we have to add 1000H to the segment values: ADD BX, 1000H JC ADDR_OVF OFF_OK_2: ;;; Don't make the following shortcut! ;;; It returns a non-normalized address and ;;; therefore the address comparison is slower! ;;; CMP BX, 0F000H ;;; JB ADD_A_C_DONE ; overflow not possible ; check for overflow: CALL NORM_ADDR JC ADDR_OVF ADD_A_C_DONE: MOV DS, BX MOV BX, AX IRET ;--------------------------------------------------------------------------- ADDR_OVF: ; This is the treatment of the overflow ; of an address variable. CALL SAVE_CPU_INFO MOV CUR_PROCESS.PD_STATUS, ADDR_OVF_CODE JMP TERMINATE ; No return! ;--------------------------------------------------------------------------- COMM_SUB_ADDR PROC NEAR ; performs (BX,AX) - DX, and returns result in BX (seg) and AX (off): CMP AX, DX ; to check, which offset is larger JAE SUB_OFFSET ; IF BELOW means: the offset to subtract is larger then the offset ; of the address, so we have to borrow as much as we need from the ; segment: SUB DX, AX MOV AX, DX ; DX saves the difference ADD AX, 0FH ; AX := (AX + 15) MOD 16 MOV CL, 4 SHR AX, CL ; AX = number of paragraphs to borrow SUB BX, AX ; BX = corrected segment value JB ADDR_OVF AND DX, 0FH ; normalized offset to subtract MOV AX, 0 JZ OFF_OK_3 ; the resulting offset is zero MOV AX, 10H SUB_OFFSET: SUB AX, DX OFF_OK_3: RET COMM_SUB_ADDR ENDP SUB_ADDR: ;======== ; GOAL: Subtracts the ADDRESS in (DX,DI) from the ; ADDRESS in (DS,BX). The result is checked ; for overflow and returned in (DS,BX). MOV AX, BX MOV BX, DS MOV DS, DX MOV DX, DI CALL COMM_SUB_ADDR ; subtract the segments: CALL NORM_ONE ; result in (BX,AX) MOV CX, DS SUB BX, CX JB ADDR_OVF ; check for overflow: CALL NORM_ADDR JC ADDR_OVF MOV DS, BX MOV BX, AX IRET SUB_A_C: ;======= ; GOAL: Subtracts the CARDINAL in (DX) from the ; ADDRESS in (DS,BX). The result is checked ; for overflow and returned in (DS,BX). MOV AX, BX MOV BX, DS CALL COMM_SUB_ADDR ; check for overflow: CALL NORM_ADDR JC ADDR_OVF MOV DS, BX MOV BX, AX IRET ;--------------------------------------------------------------------------- NORM_ONE PROC NEAR ; Normalizes 'partially' ADDRESS in (BX,AX), result in (BX,AX). ; 'Partially' means: segment is as large as possible, offset ; as small as possible, but the offset might be larger than 15 ; (in case of addresses out of range). MOV DX, AX AND AX, 0FH MOV CL, 4 SHR DX, CL ADD BX, DX ; that's the regular normalization JC TOO_LARGE_ADDR RET TOO_LARGE_ADDR: MOV CL, 4 INC BX ; add 1 paragraph, since the maximum ; value for the segment is 0FFFFH. SHL BX, CL ; transform remaining paragraphs in offset ADD AX, BX ; complete the offset MOV BX, 0FFFFH ; the highest value for the segment RET NORM_ONE ENDP EQ_ADDR: ;======= ; Compares two ADDRESSes for equality. The values are passed ; in (DS,BX) and (DX,DI) and the result is in BL (0 = FALSE, ; 1 = TRUE). The input values are allowed to be out of the ; legal ADDRESS-range. ; FAST routine. We optimize the path, where the addresses are ; not equal, but have the same segment value. This is the most ; frequent case, when searching elements allocated in the same heap. ; first check, if they differ in the last 4 bits: MOV AX, BX AND BL, 0FH ; mask out the last 4 bits MOV CX, DI AND CL, 0FH CMP BL, CL JNE THEY_ARE_DIFF ; next we check if 1 part of addr is equal: MOV BX, DS CMP AX, DI ; compare offsets JE SAME_OFFSET CMP BX, DX ; compare segments JNE DO_NORMALIZE THEY_ARE_DIFF: MOV BL, 0 ; value for NOT EQUAL IRET SAME_OFFSET: CMP BX, DX ; compare segments JNE THEY_ARE_DIFF THEY_ARE_EQUAL: MOV BL, 1 ; value for EQUAL IRET DO_NORMALIZE: MOV SI, DX ; second par in (SI,DI) CALL NORM_ONE XCHG BX, SI XCHG AX, DI CALL NORM_ONE ; now, compare the 2 norm. addresses CMP BX, SI JNE THEY_ARE_DIFF CMP AX, DI JNE THEY_ARE_DIFF JMP SHORT THEY_ARE_EQUAL GT_EQ_ADDR: ;========== ; This routine performs both comparisons, GREATER and ; GREATER or EQUAL of two ADDRESSes (a1 > a2, a1 >= a2). ; The ADDRESS a1 is passed in (DX,DI), a2 in (DS,BX). ; They are allowed to be out of the legal ADDRESS-range. ; The result is in BL (0 = FALSE, 1 = TRUE). ; FAST routine. We optimize the path, where the addresses ; have same segment value, but different offset. MOV SI, AX ; the function code MOV AX, BX MOV BX, DS ; check if segments are equal: CMP BX, DX JNE DIFF_SEGMENTS ; segments are equal: CMP AX, DI ; compare offsets JA COND_FALSE ; its LESS THAN JB COND_TRUE ; its GREATER ; they are equal: CMP SI, GT_ADDR_FCT JE COND_FALSE COND_TRUE: MOV BL, 1 IRET COND_FALSE: MOV BL, 0 IRET DIFF_SEGMENTS: MOV DS, SI MOV SI, DX ; a1 is in (SI,DI) CALL NORM_ONE ; normalize a2 XCHG BX, SI XCHG AX, DI CALL NORM_ONE ; normalize a1 ; now compare the 2 normalized addresses: CMP BX, SI ; compare segments JA COND_TRUE JB COND_FALSE ; the segments are equal, now we compare the offsets: ; Here we have to distinguish between the comp. GT / GT_EQ: MOV SI, DS CMP SI, GT_ADDR_FCT JNE GT_EQ_TEST GT_TEST: CMP AX, DI JA COND_TRUE JMP SHORT COND_FALSE GT_EQ_TEST: CMP AX, DI JAE COND_TRUE JMP SHORT COND_FALSE ;--------------------------------------------------------------------------- CONV_A_C: ;======== ; Converts an address in (DS,BX) into a CARDINAL and returns ; it in DX. The result is checked for overflow: MOV DX, DS MOV CL, 4 SHL DX, CL ; base * 16 JC BAD_CONVERT ADD DX, BX ; result = (base * 16) + offset JC BAD_CONVERT IRET BAD_CONVERT: ;;; JMP SHORT CARD_OVF ;--------------------------------------------------------------------------- CARD_OVF: ;======== ; Treats the CARDINAL overflow: generate a P_M_DUMP, set the process ; status to CARD_OVF_CODE and terminates the current program: CALL SAVE_CPU_INFO MOV CUR_PROCESS.PD_STATUS, CARD_OVF_CODE JMP TERMINATE ; No return! ;--------------------------------------------------------------------------- INTEGER_OVF: ;=========== ; Treats the INTEGER overflow: generate a P_M_DUMP, set the process ; status to INTEGER_OVF_CODE and terminates the current program: CALL SAVE_CPU_INFO MOV CUR_PROCESS.PD_STATUS, INTEGER_OVF_CODE JMP TERMINATE ; No return! ;--------------------------------------------------------------------------- RANGE_ERROR: ;=========== ; Treats the RANGE ERROR: generate a P_M_DUMP, set the process ; status to RANGE_ERR_CODE and terminates the current program: CALL SAVE_CPU_INFO MOV CUR_PROCESS.PD_STATUS, RANGE_ERR_CODE JMP TERMINATE ; No return! ;------------------------------------------------------------------------ PSP_POINTER: ;========== ; Returns a pointer to a static copy of the program segment ; prefix (PSP) for the RTS. ; The address is returned in (CX:BX) CALL SAVE_CPU_INFO mov es, rts_ds LeS BX,ES: BASE_PAGE_PTR MOV CX,eS MOV DS,CUR_PROCESS.PD_DS IRET ;------------------------------------------------------------------------ ;data segment public 'data' ;NYI DB 'RTS-function not yet implemented: $' ;data ends NOT_YET: ;======= ; This function can be called by RTS-functions ; that are not yet implemented: call save_cpu_info MOV CUR_PROCESS.PD_STATUS, ILL_FCT_CODE JMP TERMINATE ; No Return! ;***************************************************************************** code ends end