;********************************************************************** ; ; 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. ; ; ; RTS - Mainline of Modula-2/86(tm) Run Time System ; ; 8308.09 converted to PC-DOS 1.1 ; 8312.28 function 26H and trapping of interrupt 23H (break) ; 8401.17 default programname is COMINT; init interrupt mask in PD ; CGROUP group code DGROUP group data,stack,memory include RTS.INC data segment public public START_MEM, MEM_SIZE public CUR_PROCESS, RTS_PROCESS, CUR_P_PTR public BASE_PAGE_PTR public SAVED_DISK, RTS_DISK public FILE_SPEC, FILE_HANDLE BASE_PAGE db 100H dup (?) ; required for the Main-Module TOP_OF_MEMORY equ word ptr BASE_PAGE+2 ; last free paragraph, +1 TRANS_COM_SIZE equ 440H ; transient part of COMMAND.COM (in parag) START_MEM dw ? ; first free paragraph MEM_SIZE dw ? ; number of free paragraphs at START_MEM DOS dd ? ; jump vector to DOS START_ADDR dd ? ; start address of .LOD program ; - saved interrupt vectors - OLD_RTS_VECTOR dd ? OLD_DIV0_VECTOR dd ? OLD_INTO_VECTOR dd ? OLD_BREAK_VECTOR dd ? ; This is a copy of the descriptor of the current process: CUR_PROCESS ProcessDescriptor <> ; Workspace of the MAIN process, starting with RTS: RTS_PROCESS ProcessDescriptor <> CUR_P_PTR dd RTS_PROCESS ; pointer to current process descr. BASE_PAGE_PTR dd BASE_PAGE ; ptr to program segment prefix MAIN_SP dw ? MAIN_SS dw ? START_DISK db ? SAVED_DISK db ? RTS_DISK db ? DEFAULT_NAME db 'COMINT ' ; default file to load DEFAULT_TYPE db 'LOD' ; default filetype for loading DEFAULT_PATH db '\M2LOD\' ; secondary directory to search DEF_PATH_LENGTH equ 7H FILE_SPEC db 64H dup(?) FILE_MSG1 db ' ' FILE_MSG2 db ' not found in current directory or in \M2LOD$' FILE_HANDLE dw ? RES_FN db '?:????????.???$' ; for writing filespecs NO_FILE db 'File not found: $' NO_MEMORY db 'Insufficient Memory: $' SOME_ERROR DB ' --- $' NORMAL_MSG DB 'normal termination$' WARNED_MSG DB 'warning$' STOP_MSG DB 'stopped$' ASSERT_MSG DB 'wrong assertion$' HALT_MSG DB 'HALT called$' CASE_MSG DB 'case-tag error$' STACK_MSG DB 'stack overflow$' HEAP_MSG DB 'heap overflow$' FCT_ERR_MSG DB 'function return error$' ADDR_OVF_MSG DB 'address overflow$' REAL_OVF_MSG DB 'real overflov$' CARD_OVF_MSG DB 'cardinal overflow$' INTEGER_OVF_MSG DB 'integer overflow$' RANGE_ERR_MSG DB 'range error$' ZERO_DIV_MSG DB 'divison by zero$' PROC_END_MSG DB 'coroutine end$' LOAD_MSG DB 'cannot load$' CALL_MSG DB 'unsuccessfull program call$' NO_PROG_MSG DB 'program not found$' NO_MOD_MSG DB 'module not found$' INCOMPAT_MSG DB 'incompatible module keys$' BAD_FILE_MSG DB 'bad structure in file$' ILL_INSTR_MSG DB 'illegal instruction encountered$' ILL_FCT_MSG DB 'illegal RTS call$' NO_MORE_ISR DB 'too many concurrent IO-Processes$' even STATUS_MSG DW NORMAL_MSG, WARNED_MSG, STOP_MSG, ASSERT_MSG DW HALT_MSG, CASE_MSG, STACK_MSG, HEAP_MSG DW FCT_ERR_MSG, ADDR_OVF_MSG, REAL_OVF_MSG,CARD_OVF_MSG DW INTEGER_OVF_MSG, RANGE_ERR_MSG, ZERO_DIV_MSG DW PROC_END_MSG, LOAD_MSG, CALL_MSG DW NO_PROG_MSG, NO_MOD_MSG, INCOMPAT_MSG, BAD_FILE_MSG DW ILL_INSTR_MSG, ILL_FCT_MSG, NO_MORE_ISR data ends stack segment stack db 100h dup (?) ; loader will set up stack for us stack ends code segment public ; Upon entry, we assume CS, IP and DS to be set correctly. ; We return to DOS through a jump to location 0 of the Program Segment Prefix ; There is no explicit release of memory or stack reset. extrn LoadProg:NEAR ; resident loader extrn RTS_BRANCH:NEAR ; interrupt dispatcher extrn REST_I_V:NEAR ; restore interrupt vectors extrn STACK_OVF:NEAR ; stack overflow extrn DIV_BY_ZERO:NEAR ; divide by zero handler extrn STOPPED:NEAR ; break handler extrn GET_INTERRUPT_MASK:NEAR ; reads the current interrupt mask public AFTER_RESIDENT public RTS_DS assume CS:code public RTS_DS RTS_DS DW ? ; We need a way to set the DS later on main proc near RTS_START: push DS ; base of PSP mov ax,data mov ES,ax ; point to data segment mov RTS_DS,ax ; (make it easy to access later, in ISR's) mov di,offset BASE_PAGE mov si,0 mov cx,size BASE_PAGE cld rep movsb ; copy PSP into BASE_PAGE mov DS,ax ; now switch to RTS data segment assume DS:data,ES:data pop word ptr DOS+2 ; set up exit vector, which mov word ptr DOS,0 ; goes to PSP:0 ; STI ; Allow interruptions ;****************************************************** ; Initial Memory Allocation ;****************************************************** mov START_MEM,SS ; bottom of last segment .. mov ax,sp mov cl,4 shr ax,cl ; plus paragraphs of stack.. add ax,10 ; (plus fudge factor..) add START_MEM,ax ; ..gives first free paragraph mov ax,TOP_OF_MEMORY sub ax,START_MEM IF KEEP_COM sub ax, TRANS_COM_SIZE ENDIF cmp ax, MAX_MEM_FOR_M2 ; more than we need? jbe N2MUCH ; nope mov ax, MAX_MEM_FOR_M2 ; yes, just take what is needed N2MUCH: mov MEM_SIZE,ax ; compute free paragraphs ; ; Find the current disk, and fill in the Filespec of the program to run ; mov ah, 25 int OS ; get current default disk mov START_DISK, al ; save for Postmortem dump mov RTS_DISK, al ; => RESTRICTION: The user has to log in the disk on which reside ; both, the Run-Time-Support and the RESIDENT.CMD mov di,offset FILE_SPEC FN_COPY2: mov si,DEFAULT_DMA cld mov cx,0 mov cl,byte ptr[si] inc si jcxz FN_COPY5 ; no command tail, use default name FN_COPY2a: lodsb ; look for first non-blank cmp al,' ' jne FN_COPY6a ; that must be file name loop FN_COPY2a jmp FN_COPY5 ; all blanks, use default name FN_COPY6: ; copy in file name! lodsb cmp al,' ' je FN_COPY6b ; until blank FN_COPY6a: stosb loop FN_COPY6 ; or end of command line inc si ; pretend we saw a blank.. FN_COPY6b: dec si ; back up over terminating blank cmp byte ptr[si-1],":" ; was only the device there? je FN_COPY5 ; yes, so set the default name. FN_COPY9: dec si cmp byte ptr[si],"." je EXT_END ; extension already here. cmp si,DEFAULT_DMA ; at start of command tail? ja FN_COPY9 ; no: keep looking for '.' jmp FN_COPY3 ; yes: no extension, supply one. FN_COPY5: ; use default name mov si,offset DEFAULT_NAME mov cx,6 FN_COPY4: movsb dec cx jz FN_COPY3 cmp byte ptr[si]," " jne FN_COPY4 FN_COPY3: ; end of all the 'write filename' loops mov byte ptr[di],"." inc di mov si,offset DEFAULT_TYPE mov cx,3 FEXT_COPY1: cmp byte ptr[si]," " je EXT_END movsb dec cx jnz FEXT_COPY1 EXT_END: mov byte ptr[di],0 CALL OPEN_FILE ; open program file jnb FOUND mov si,offset FILE_SPEC FN_COPY11: cmp byte ptr[si],"\" je NOT_FOUND ; path speficied, so don't retry cmp byte ptr[si],0 je LOOK_AGAIN ; no path, so look in default path inc si jmp FN_COPY11 LOOK_AGAIN: mov cx,15 mov di,offset FILE_MSG1 mov si,offset FILE_SPEC cld rep movsb mov cx,64-DEF_PATH_LENGTH mov di,offset FILE_SPEC+63 mov si,offset FILE_SPEC+63-DEF_PATH_LENGTH std ; move filename down so path can rep movsb ; be inserted. mov di,offset FILE_SPEC+2 cmp byte ptr[di]-1,":" je INS_PATH mov di,offset FILE_SPEC INS_PATH: mov si,offset DEFAULT_PATH cld mov cx,DEF_PATH_LENGTH rep movsb ; insert path call OPEN_FILE ; check if file is there... jnb FOUND jmp N_FOUND1 ; nope. issue special message. NOT_FOUND: MOV DX, OFFSET NO_FILE ; nope CALL WRITE_MSG CALL WRITE_FILE_NAME jmp DOS N_FOUND1: mov dx,offset FILE_MSG1 call WRITE_MSG jmp DOS FOUND: mov FILE_HANDLE,ax mov bx,FILE_HANDLE mov ax,RTS_PROCESS.PD_PROG_ID ; AX = current prog id mov dx,RTS_PROCESS.PD_MOD_TABLE mov cx,RTS_PROCESS.PD_MOD_TABLE+2 ; CX:DX = old module table call LoadProg ; load Resident mov RTS_PROCESS.PD_MOD_TABLE,dx mov RTS_PROCESS.PD_MOD_TABLE+2,cx ; CX:DX = new module table mov word ptr START_ADDR,di mov word ptr START_ADDR+2,ES ; ES:DI = start address push bx call CLOSE_FILE pop bx test bx,bx ; load ok? jz LOADED ; yes dec bx shl bx,1 mov dx,LdErr[bx] CALL WRITE_MSG CALL WRITE_FILE_NAME jmp DOS data segment ;; - load error table and messages - badstr db '** Bad Structure - $' badver db '** Bad Version or Target system - $' badeof db '** Unexpected EOF - $' badmem db '** Insufficient Memory - $' badchk db '** Bad Checksum - $' baderr db '** LOAD error table fu - $' even LdErr dw badstr,badver,badeof,badmem,badchk,baderr data ends ; Alloc_Mem - called by LoadProg to allocate memory for the 'IPL' ; ; in: AX memory request size, in paragraphs ; out: AX first paragraph of allocated chunk ; BX =0 if ok, <>0 if memory not available ; public Alloc_Mem Alloc_Mem: mov bx,1 cmp ax, MEM_SIZE ; can request be satisfied? ja AllFU ; no sub MEM_SIZE,ax ; yes add ax, START_MEM ; compute next free paragraph.. xchg ax, START_MEM ; update start_mem, return old value xor bx,bx AllFU: ret LOADED: MEM_OK: ; switch to real run-time stack, at top of workspace: MOV AX, MEM_SIZE MOV BX, START_MEM CALL COMP_STACK ; BX becomes SS, AX becomes SP MOV MAIN_SS, BX MOV MAIN_SP, AX MOV SS, BX ; No need to disable Interrupts, MOV SP, AX ; the processor does it here ;****************************************************** ; Fill in the Default Process Descriptor: ;****************************************************** ; First we put the Return Address in RTS on the ; Stack. It will be used in case of an error ; in the Main program (RESIDENT) PUSHF PUSH CS MOV AX, OFFSET AFTER_RESIDENT PUSH AX PUSH DS PUSH BP ; Now put all the significant registers at ; their places in P.D: PUSHF POP RTS_PROCESS.PD_FLAGS MOV RTS_PROCESS.PD_SP, SP MOV RTS_PROCESS.PD_SS, SS MOV RTS_PROCESS.PD_DS, DS ; and the initial value for the stack test: MOV AX, SP SUB AX, SP_RESERVE+4 ; 4 stands for the CALLF to RESIDENT MOV RTS_PROCESS.PD_SP_LIM, AX ; Stack Limit is actual value of SP ; minus some reserve ; and the initial values for the heap managment: MOV AX, START_MEM ; Paragraph addr MOV RTS_PROCESS.PD_HEAP_BASE + 2, AX ; first para of heap MOV RTS_PROCESS.PD_HEAP_TOP + 2, AX ; top para. of heap ; Only the minimum is done here, to be as ; independant from the implementation of the ; heap manager as possible. See also NEW_PROCESS ; Set all the values needed for TRANSFER ; and error handling: MOV RTS_PROCESS.PD_RET_SP, SP MOV RTS_PROCESS.PD_PROG_END, SP MOV RTS_PROCESS.PD_PROG_END+2, SS ;****************************************************** ; Create the Main Process: ;****************************************************** ; The Default Process Descriptor becomes the ; current one. This is not a TRANSFER, the ; very first process has to be created ; simply by copying its descriptor into the ; current-one: push DS pop ES mov si, offset RTS_PROCESS mov di, offset CUR_PROCESS mov cx, size CUR_PROCESS rep movsb ;****************************************************** ; Prepare the interrupt system: ;****************************************************** CALL GET_INTERRUPT_MASK MOV CUR_PROCESS.PD_PRIO_MASK, AX ; Compute physical address of RTS vector: mov bx,RTS_INT*4 MOV AX, 0 MOV ES, AX ; Set entry point for RTS-interrupt-vector: MOV AX, ES: [BX] ; Save the old value MOV OLD_RTS_VECTOR, AX MOV AX, ES: 2[BX] MOV OLD_RTS_VECTOR + 2, AX MOV ES:word ptr [BX], offset RTS_BRANCH MOV ES:word ptr 2[BX], CS ; Set the new-one ; Interrupt vector 0, used for 'divide by zero': mov bx,0*4 mov ax, ES:[bx] ; Save the old value mov OLD_DIV0_VECTOR, ax mov ax, ES:2[bx] mov OLD_DIV0_VECTOR + 2, ax mov ES:word ptr 2[bx], CS ; Set the new-one mov ES:word ptr[bx], offset DIV_BY_ZERO ; Interrupt vector 4 (used for INTO) has also ; to point to the RTS: mov bx,4*4 MOV AX, ES:[bx] ; Save the old value MOV OLD_INTO_VECTOR, AX MOV AX, ES:2[bx] MOV OLD_INTO_VECTOR + 2, AX MOV ES:word ptr [bx], offset RTS_BRANCH MOV ES:word ptr 2[bx], CS ; Set the new-one ; Note, that there is no special entry for the ; interrupt on overflow. The calling program ; has to set the function code in AX as for the ; other RTS calls. Needed, because an overflow ; may occur in several error conditions ; (INTEGER, CARDINAL, SUBRANGE...) ; Interrupt vector 23H (used for BREAK) has also ; to point to the RTS: mov bx,4*23H MOV AX, ES:[bx] ; Save the old value MOV OLD_BREAK_VECTOR, AX MOV AX, ES:2[bx] MOV OLD_BREAK_VECTOR + 2, AX MOV ES:word ptr [bx], offset STOPPED MOV ES:word ptr 2[bx], CS ; Set the new-one ;****************************************************** ; Call the RESIDENT part: ;****************************************************** ; For debugger, to detect first ; procedure in calling sequence: MOV BP, 0 CALL_RESIDENT: CALL START_ADDR ; call loaded program ; We are back from the MODULA program. AFTER_RESIDENT: MOV DS, RTS_DS ; restore data segment MOV AX, CUR_PROCESS.PD_STATUS test ax,ax jz RTS_END ; 0 = No error MOV DX, OFFSET SOME_ERROR CALL WRITE_MSG MOV BX, CUR_PROCESS.PD_STATUS CALL WRITE_STATUS CALL WRITE_LN ; It is safer to return to DOS and possibly reload the ; RTS rather then restarting its execution, since the ; code might have been overwritten. RTS_END: ; Restore the modified interrupt vectors MOV AX, 0 MOV ES, AX MOV BX, RTS_INT*4 MOV AX, OLD_RTS_VECTOR ; The RTS entry MOV ES: [BX], AX MOV AX, OLD_RTS_VECTOR + 2 MOV ES: [BX]+2, AX MOV AX, OLD_DIV0_VECTOR ; The entry for DIV0 MOV ES:word ptr 0, AX MOV AX, OLD_DIV0_VECTOR + 2 MOV ES:word ptr 2, AX MOV AX, OLD_INTO_VECTOR ; The entry for INTO MOV ES:word ptr 16, AX MOV AX, OLD_INTO_VECTOR + 2 MOV ES:word ptr 18, AX MOV AX, OLD_BREAK_VECTOR ; The entry for BREAK MOV ES:word ptr 140, AX MOV AX, OLD_BREAK_VECTOR + 2 MOV ES:word ptr 142, AX ; Restore the old interrupt vectors for every IO-Process, ; waiting on an interrupt: mov CUR_PROCESS.PD_PROG_ID, 0 ; 0 as program id is a sort of a joker. CALL REST_I_V ; select the same drive that was selected at beginning: MOV DL, START_DISK CALL SELECT_DISK public SYS_RESET SYS_RESET: jmp DOS ; Back to DOS ;********************************************************** public TERMINATE ;********************************************************** ; We arrive here, when a program is terminated or if any error had ; occured. In the former case, status is 'normal', in the latter case ; the error-code is set in the Status-field of Current Process Descriptor: data segment SAVE_SP dw ? SAVE_SS dw ? PMD_STACK dw 160 dup (?) ; should be enough PMD_STACK_END label word data ends extrn P_M_DUMP:near TERMINATE: ;========= MOV DS, RTS_DS assume DS:data ; We produce a memory dump, if the status is not NORMAL or WARNED: MOV AX, CUR_PROCESS.PD_STATUS CMP AX, 2 JB TERMINATION ; lines added so bad function call will not cause parity check cmp ax,ILL_FCT_CODE je TERMINATION CMP AX, HIGHEST_ERR_CODE ; Test if err-code legal JBE TERM_DUMP MOV CUR_PROCESS.PD_STATUS, ILL_FCT_CODE ; line added to avoid parity check jmp TERMINATION TERM_DUMP: ; First, we're going to set (SS,SP) to the ; auxiliary stack: MOV SAVE_SS, SS MOV SAVE_SP, SP MOV AX, DS MOV SS, AX MOV SP, OFFSET PMD_STACK_END CALL P_M_DUMP ; Restore stack of user process: MOV SS, SAVE_SS MOV SP, SAVE_SP TERMINATION: ; Free the resources, managed by RTS: CALL REST_I_V ; only Interrupt Vectors MOV DS, CS:RTS_DS ; Prepare return: MOV SS, CUR_PROCESS.PD_PROG_END+2 MOV SP, CUR_PROCESS.PD_PROG_END POP BP ; BP and DS of Father Program POP DS IRET ;****************************************************** ; Some Utilities: ;****************************************************** public COMP_STACK COMP_STACK: ; Upon Entry: ; AX holds size of free memory (in paragraphs) ; BX holds (paragraph) start address of free memory ; Upon Exit: ; AX holds SP and BX holds SS ; Policy: ; Set STACK to the end of memory. Check if ; there is room for a minimal stack. CMP AX, 1000H JBE SMALL_MEM ; There is more than 64K of free memory: SUB AX, 1000H ADD BX, AX ; Set SS to end of memory - 64K MOV AX, 0 ; and SP to 0 RET SMALL_MEM: ; Less than 64K of free memory ; SS is start of free memory MOV CL, 4 SHL AX, CL ; SP is length * 16 CMP AX, SP_INI_SIZE + SP_RESERVE + 4 ; 4 is for the call of RESIDENT JAE LARGE_ENOUGH JMP STACK_OVF ; Not enough for initial stack and ; for some reserve! LARGE_ENOUGH: RET public WRITE WRITE: ; The character to be printed is in DL MOV AH, 2 ; Console Output INT OS RET public WRITE_MSG WRITE_MSG: ; The address of the message is in DX MOV AH, 9 ; Print String INT OS RET public WRITE_LN WRITE_LN: MOV DL, 0DH ; Print CR CALL WRITE MOV DL, 0AH ; Print LF CALL WRITE RET public WRITE_FILE_NAME WRITE_FILE_NAME: mov di,offset FILE_SPEC WFN1: cmp byte ptr[di],0 je WFN3 inc di jmp WFN1 WFN3: mov byte ptr[di],'$' mov dx,offset FILE_SPEC call WRITE_MSG ret public WRITE_STATUS WRITE_STATUS: ; prints on the screen the meaning of a ; program status (passed in BL): push bx MOV DL, ' ' CALL WRITE pop bx xor bh,bh ADD BX, BX MOV DX, STATUS_MSG [BX] CALL WRITE_MSG RET public SET_DEFAULT_DMA SET_DEFAULT_DMA: mov dx, DEFAULT_DMA mov ah, 01Ah int OS ret public SELECT_DISK SELECT_DISK: ; the drive to be selected is passed in DL mov ah, 14 int OS ret public OPEN_FILE OPEN_FILE: ; open file in FILE_SPEC: returns carry flag set if not found mov ax,3D02H ; open for read/write mov dx,offset FILE_SPEC int OS ret public CLOSE_FILE CLOSE_FILE: ; closes the file given in the FILE_HANDLE mov ah,3EH mov bx,FILE_HANDLE int OS ret public DELETE_FILE DELETE_FILE: ; deletes the file given in the FILE_SPEC mov ah,41H mov dx,offset FILE_SPEC int OS ret public SEQ_WRITE SEQ_WRITE: ; writes the next byte in the file given ; in the FILE_HANDLE. push ds mov ah,2FH ; get current dma (buffer address) int os push es push bx mov bx,FILE_HANDLE pop dx mov cx,80H mov ah,40H pop ds int OS pop ds ret public MAKE_FILE MAKE_FILE: ; creates the file given in the FILE_SPEC mov ah, 3CH mov cx,0 ; attribute of zero mov dx,offset FILE_SPEC int OS ret public GET_CURR_DISK GET_CURR_DISK: ; gets the currently logged in disk and stores ; the value in the variable 'SAVED_DISK' mov ah, 25 int OS mov SAVED_DISK, al ret public NORM_ADDR NORM_ADDR: ; To normalize a address with segment and offset, ; i.e the segment value is as large as possible ; and the offset is smaller than 16. ; Upon entry: ; BX holds the old segment and AX the old offset. ; Upon exit: ; BX holds the normalized segment and AX the offset. ; If an overflow occurs, the 'CF' flag is set. MOV DX, AX AND AX, 0FH MOV CL, 4 SHR DX, CL ADD BX, DX RET main endp code ends end RTS_START