PAGE 60,132 TITLE INDEINI - 386 XMA EMULATOR - Initialization COMMENT # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * MODULE NAME : INDEINI * * * * 5669-196 (C) COPYRIGHT 1988 Microsoft Corp. * * * * DESCRIPTIVE NAME: 80386 XMA EMULATOR INITIALIZATION * * * * STATUS (LEVEL) : VERSION (0) LEVEL (2.0) * * * * FUNCTION : Do all the initialization needed for the 386 XMA emulator.* * * * The 386 XMA emulator is installed by putting the follow- * * ing command in the CONFIG.SYS file: * * * * DEVICE=\386XMAEM.SYS bbb * * * * where "bbb" is the number of K reserved for the MOVEBLOCK * * function. If EMS is used, this command must appear * * before the command to load EMS. * * * * This module first of all does all the stuff to set up * * the device driver linkage to DOS. The driver is a * * character device. The only command it recognizes is P3C* * "initialize". When it receives the initialize command * * it does all the set up for the emulator. For information * * on device drivers see the DOS Technical Reference. * * * * Then it checks to see if we're on a model_80 and the * * emulator has not been previously installed. If this is * * the case, then it procedes to do the following: * * Get the MOVEBLOCK buffer size from the parameter list * * Save the maximum XMA block number in the header * * Relocate to high memory * * Initialize the page directory and page tables * * Call INDEIDT to initialize the IDT * * Call INDEGDT to initialize the GDT * * Switch to virtual mode * * Initialize the TSS for the virtual 8086 task * * Initialize the XMA page tables * * Enable paging * * * * This module also contains code to handle the Generic D2A* * IOCTL call which is used to query the highest valid D2A* * XMA block number. This code is left resident. D2A* * * * MODULE TYPE : ASM * * * * REGISTER USAGE : 80386 STANDARD * * * * RESTRICTIONS : None * * * * DEPENDENCIES : None * * * * LINKAGE : Invoked as a DOS device driver * * * * INPUT PARMS : The number of 1K blocks reserved for the MOVE BLOCK * * service can be specified after the DEVICE command in the * * CONFIG.SYS file. 0K is the default. * * * * RETURN PARMS : A return code is returned to DOS in the device header * * at offset 3. * * * * OTHER EFFECTS : None * * * * EXIT NORMAL : Return to DOS after device driver is loaded * * * * EXIT ERROR : Return to DOS after putting up error messages * * * * EXTERNAL * * REFERENCES : SIDT_BLD - Entry point for INDEIDT to build the IDT * * GDT_BLD - Entry point for INDEGDT to build the GDT * * WELCOME - The welcome message * * GOODLOAD - Message saying we loaded OK * * NO_80386 - Error message for not running on a model_80 * * WAS_INST - Error message for protect mode in use * * SP_INIT - Initial protect mode SP * * REAL_CS - Place to save our real mode CS * * REAL_SS - Place to save our real mode SS * * REAL_SP - Place to save our real mode SP * * PGTBLOFF - Offset of the page tables * * SGTBLOFF - Offest of the page directory * * NORMPAGE - Normal page directory entry * * XMAPAGE - Page directory entry for the first XMA page D1A* * BUFF_SIZE- Size of the MOVEBLOCK buffer * * MAXMEM - Maximum amount of memory on the box * * CRT_SELECTOR - Selector for the display buffer * * * * SUB-ROUTINES : GATE_A20 - Gate on or off address bit 20 * * GET_PARMS - Get the MOVEBLOCK buffer size specified on * * the command in CONFIG.SYS and convert to * * binary. * * * * MACROS : DATAOV - Add prefix for the next instruction so that it * * accesses data as 32 bits wide * * ADDROV - Add prefix for the next instruction so that it * * uses addresses that are 32 bits wide * * CMOV - Move to and from control registers * * JUMPFAR - Build an instruction that will jump to the * * offset and segment specified * * * * CONTROL BLOCKS : INDEDAT.INC * * * * CHANGE ACTIVITY : * * * * $MOD(INDEINI) COMP(LOAD) PROD(3270PC) : * * * * $D0=D0004700 410 870521 D : NEW FOR RELEASE 1.1. CHANGES TO THE ORIGINAL * * CODE ARE MARKED WITH D0A. * * $P1=P0000281 410 870730 D : SAVE 32 BIT REGISTERS ON model_80 * * $P2=P0000312 410 870804 D : CHANGE COMPONENT FROM MISC TO LOAD * * $P3=P0000335 410 870811 D : HEADER INFORMATION ALL SCREWED UP * * $D1=D0007100 410 870810 D : CHANGE TO EMULATE XMA 2 * * CHANGE ID STRING TO "386XMAEMULATOR10" * * $P4=P0000649 411 880125 D : A20 NOT ENABLED WHEN PASSWORD SET * * $P5=P0000650 411 880128 D : COPROCESSOR APPLICATIONS FAIL * * $P6=P0000740 411 880129 D : IDSS CAPTURED DCR 87 CODE. REMOVE IT. * * $D2=D0008700 120 880206 D : SUPPORT DOS 3.4 IOCTL CALL * * $P7=P0000xxx 120 880331 D : FIX INT 15. LOAD AS V86 MODE HANDLER. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * # .286P ; Enable recognition of 286 privileged instructs. .XLIST ; Turn off the listing INCLUDE INDEDAT.INC IF1 ; Only include the macros in the first pass INCLUDE INDEOVP.MAC ; of the assembler INCLUDE INDEINS.MAC ENDIF .LIST ; Turn on the listing ; Let these variables be known to external procedures PUBLIC POST PUBLIC INDEINI PROG SEGMENT PARA PUBLIC 'PROG' ASSUME CS:PROG ASSUME SS:NOTHING ASSUME DS:PROG ASSUME ES:NOTHING INDEINI LABEL NEAR ; These variables are located in INDEI15 EXTRN SP_INIT:WORD ; Initial protect mode SP EXTRN REAL_CS:WORD ; Place to save our real mode CS EXTRN REAL_SS:WORD ; Place to save our real mode SS EXTRN REAL_SP:WORD ; Place to save our real mode SP EXTRN PGTBLOFF:WORD ; Offset of the page tables EXTRN SGTBLOFF:WORD ; Offest of the page directory EXTRN NORMPAGE:WORD ; Normal page directory entry. Points to the ; page table that maps the real 0 to 4M. EXTRN XMAPAGE:WORD ; Page directory entry for the first XMA ; page table (page table for bank 0) @D1A EXTRN BUFF_SIZE:WORD ; Size of the MOVEBLOCK buffer EXTRN MAXMEM:WORD ; Maximum amount of memory on the box EXTRN CRT_SELECTOR:WORD ; Selector for the display buffer ; These are the messages EXTRN WELCOME:BYTE ; The welcome message EXTRN GOODLOAD:BYTE ; Message saying we loaded OK EXTRN NO_80386:BYTE ; Error message for not running on a model_80 EXTRN WAS_INST:BYTE ; Error message for protect mode in use Extrn Small_Parm:Byte ; Parm value < 64 and > 0 ;an000; dms; Extrn No_Mem:Byte ; Parm value > memory available ;an000; dms; ; These entries are located in external procedures EXTRN SIDT_BLD:NEAR ; Build the interrupt descriptor table (IDT) EXTRN GDT_BLD:NEAR ; Build the global descriptor table (GDT) ; General equates DDSIZE EQU GDT_LOC ; Size of the device driver HIGH_SEG EQU 0FFF0H ; The segment we relocate to MEG_SUPPORTED EQU 24 ; Must be a multiple of 4 XMA_PAGES_SEL EQU RSDA_PTR ; Selector for XMA pages DISPSTRG EQU 09H ; DOS display string function number D0A GET_VECT EQU 35H ; DOS get vector function number P7A SET_VECT EQU 25H ; DOS set vector function number P7A model_80 EQU 0F8H ; Model byte for the Wangler D0A XMA640KSTRT EQU 580H ; Start address of XMA block for D0A ; 640K (16000:0 / 1K) D0A P3C ; ASCII character equates TAB EQU 09H ; ASCII tab LF EQU 0AH ; ASCII line feed CR EQU 0DH ; ASCII carriage return SUBTTL Structure Definitions PAGE ;------------------------------------------------------------------------------; ; Request Header (Common portion) ; ;------------------------------------------------------------------------------; RH EQU DS:[BX] ; The Request Header structure is based off ; of DS:[BX] RHC STRUC ; Fields common to all request types DB ? ; Length of Request Header (including data) DB ? ; Unit code (subunit) RHC_CMD DB ? ; Command code RHC_STA DW ? ; Status DQ ? ; Reserved for DOS RHC ENDS ; End of common portion ; Status values for RHC_STA STAT_DONE EQU 0100H ; Function complete status (high order byte)@P3C STAT_CMDERR EQU 8003H ; Invalid command code error STAT_GEN EQU 800CH ; General error code D0A ;------------------------------------------------------------------------------; ; Request Header for INIT command ; ;------------------------------------------------------------------------------; RH0 STRUC DB (TYPE RHC) DUP (?) ; Reserve space for the header RH0_NUN DB ? ; Number of units ; Set to 1 if installation succeeds, ; Set to 0 to cause installation failure RH0_ENDO DW ? ; Offset of ending address RH0_ENDS DW ? ; Segment of ending address RH0_BPBO DW ? ; Offset of BPB array address RH0_BPBS DW ? ; Segment of BPB array address RH0_DRIV DB ? ; Drive code (DOS 3 only) RH0 ENDS RH0_BPBA EQU DWORD PTR RH0_BPBO ; Offset & segment of BPB array address. ; On the INIT command the BPB points to ; the characters following the "DEVICE=" ; in the CONFIG.SYS file. ;---------------------------------------------------------------------------D2A; ; Request Header for Generic IOCTL Request D2A; ;---------------------------------------------------------------------------D2A; RH19 STRUC DB (TYPE RHC) DUP (?) ; Reserve space for the header @D2A RH19_MAJF DB ? ; Major function @D2A RH19_MINF DB ? ; Minor function @D2A RH19_SI DW ? ; Contents of SI @D2A RH19_DI DW ? ; Contents of DI @D2A RH19_RQPK DD ? ; Pointer to Generic IOCTL request packet @D2A RH19 ENDS SUBTTL Device Driver Header PAGE POST PROC NEAR ; Declare the device driver header ORG 0 ; Device header must the very first thing in the ; device driver DD -1 ; Becomes pointer to next device header DW 0C040H ; Character device, does IOCTL @P3C @D2C DW OFFSET STRATEGY ; Pointer to device "strategy" routine DW OFFSET IRPT ; Pointer to device "interrupt handler" DB "386XMAEM" ; Device name @D0C ; End of device driver header ;------------------------------------------------------------------------------; ; Request Header (RH) address, saved here by "strategy" routine ; ;------------------------------------------------------------------------------; RH_PTRA LABEL DWORD RH_PTRO DW ? ; Offset of the request header RH_PTRS DW ? ; Segment of the request header ; Character ID "386XMAEMULATOR10" deleted 2@D2D HI_XMA_BLK DW ? ; The highest XMA block number @D0A EXT_MEM DW ? ; Number of K of extended memory @P7A ; D0A RBX DW ? ; Temporary save area for register BX @P1A ISmodel_80 DB -1 ; model_80 flag. Set to 1 if on a model_80 @P1A ; Set to 0 if not on a model_80 @D1C SUBTTL Device Strategy PAGE ;------------------------------------------------------------------------------; ; Device "strategy" entry point ; ; ; ; Retain the Request Header address for use by Interrupt routine ; ;------------------------------------------------------------------------------; STRATEGY PROC FAR MOV CS:RH_PTRO,BX ; Save the offset of the request header MOV CS:RH_PTRS,ES ; Save the segment of the request header RET STRATEGY ENDP SUBTTL Device Interrupt Intry Point PAGE ;------------------------------------------------------------------------------; ; Table of command processing routine entry points ; ;------------------------------------------------------------------------------; CMD_TABLE LABEL WORD DW OFFSET INIT_P1 ; 0 - Initialization DW OFFSET MEDIA_CHECK ; 1 - Media check DW OFFSET BLD_BPB ; 2 - Build BPB DW OFFSET INPUT_IOCTL ; 3 - IOCTL input DW OFFSET INPUT ; 4 - Input DW OFFSET INPUT_NOWAIT ; 5 - Non destructive input no wait DW OFFSET INPUT_STATUS ; 6 - Input status DW OFFSET INPUT_FLUSH ; 7 - Input flush DW OFFSET OUTPUT ; 8 - Output DW OFFSET OUTPUT_VERIFY ; 9 - Output with verify DW OFFSET OUTPUT_STATUS ;10 - Output status DW OFFSET OUTPUT_FLUSH ;11 - Output flush DW OFFSET OUTPUT_IOCTL ;12 - IOCTL output DW OFFSET DEVICE_OPEN ;13 - Device OPEN DW OFFSET DEVICE_CLOSE ;14 - Device CLOSE DW OFFSET REMOVABLE_MEDIA ;15 - Removable media DW OFFSET INVALID_FCN ;16 - Invalid IOCTL function @D2A DW OFFSET INVALID_FCN ;17 - Invalid IOCTL function @D2A DW OFFSET INVALID_FCN ;18 - Invalid IOCTL function @D2A DW OFFSET GENERIC_IOCTL ;19 - Generic IOCTL function @D2A DW OFFSET INVALID_FCN ;20 - Invalid IOCTL function @D2A DW OFFSET INVALID_FCN ;21 - Invalid IOCTL function @D2A DW OFFSET INVALID_FCN ;22 - Invalid IOCTL function @D2A DW OFFSET GET_LOG_DEVICE ;23 - Get Logical Device @D2A MAX_CMD EQU ($-CMD_TABLE)/2 ; Highest valid command follows DW OFFSET SET_LOG_DEVICE ;24 - Set Logical Device @D2A ;------------------------------------------------------------------------------; ; Device "interrupt" entry point ; ;------------------------------------------------------------------------------; IRPT PROC FAR ; Device interrupt entry point ; First we must save all the registers that we use so that when we return to ; DOS the registers are not changed. PUSH DS ; Save the segment registers modified PUSH ES CMP CS:ISmodel_80,-1; Did we already check what machine we are @D2A JNE DID_CHECK ; running on? @D2A MOV CS:RBX,BX ; Save BX @P1A MOV BX,0FFFFH ; Check the model byte at FFFF:000E @D0A @P1M MOV ES,BX ; to see if we're running on a @D0A @P1M MOV BX,0EH ; model_80 (PS/2 model 80). @D0A @P1M CMP BYTE PTR ES:[BX],model_80 ; @P1A MOV BX,CS:RBX ; Restore BX @P1A @D2M JNE NO_model_80 MOV CS:ISmodel_80,1 ; Set the flag saying we're on a @P1A @D2M JMP DID_CHECK ; model_80 @D2A NO_model_80: MOV CS:ISmodel_80,0 ; Set the flag saying we're not on a @D2M ; model_80 DID_CHECK: ; D2A CMP ISmodel_80,1 ; Are we on a model_80? @D2A JE PUSH32 ; If so, go save the 32 bit registers @P1A ; Push 16 bit registers onto the stack. PUSH AX PUSH BX PUSH CX PUSH DX PUSH DI PUSH SI PUSH BP JMP PUSHED ; @P1A ; Push 32 bit registers onto the stack P1A ; @D2D PUSH32: DATAOV ; Save all the 32 bit registers. The @P1A PUSHA ; model_80's BIOS uses 32 bit registers, @P1A ; so we must not trash the high order P1A ; words as well as the low order words. P1A PUSHED: CLD ; All moves go forward LDS BX,CS:RH_PTRA ; Get the request header address passed to the ; "strategy" routine into DS:BX MOV AL,RH.RHC_CMD ; Get the command code from the Request Header CBW ; Zero AH (if AL > 7FH, next compare will ; catch that error) CMP AL,MAX_CMD ; If command code is too high JA IRPT_CMD_HIGH ; Then jump to error routine ADD AX,AX ; Double command code for table offset since ; table entries are words MOV DI,AX ; Put into index register for CALL ; @D2D ; ; At entry to command processing routine: ; ; DS:BX = Request Header address ; CS = 386XMAEM code segment address ; AX = 0 ; CALL CS:CMD_TABLE[DI] ; Call routine to handle the command JMP IRPT_CMD_EXIT IRPT_CMD_HIGH: ; JMPed to if RHC_CMD > MAX_CMD MOV AX,STAT_CMDERR ; Return "Invalid Command" error code OR AX,STAT_DONE ; Add "done" bit to status word @P3C MOV RH.RHC_STA,AX ; Store status into request header IRPT_CMD_EXIT: ; Return from command routine ; Restore the registers before returning to DOS. CMP CS:ISmodel_80,1 ; Are we on a model_80? @P1A JE POP32 ; Yes. Then pop the 32 bit registers. @P1A ; Pop 16 bit registers off of the stack. POP BP POP SI POP DI POP DX POP CX POP BX POP AX JMP POPPED ; @P1A ; Pop 32 bit registers off of the stack. P1A ; P1A POP32: DATAOV ; @P1A POPA ; @P1A ; Pop the segment registers off of the stack. POPPED: POP ES POP DS RET IRPT ENDP SUBTTL Command Routines PAGE MEDIA_CHECK: ; BLD_BPB: ; INPUT_IOCTL: ; IOCTL input INPUT: ; INPUT_NOWAIT: ; Non-destructive input no wait INPUT_STATUS: ; Input status INPUT_FLUSH: ; Input flush OUTPUT: ; OUTPUT_VERIFY: ; OUTPUT_IOCTL: ; IOCTL output OUTPUT_STATUS: ; Output status OUTPUT_FLUSH: ; Output flush DEVICE_OPEN: ; DEVICE_CLOSE: ; REMOVABLE_MEDIA: ; INVALID_FCN: ; @D2A GET_LOG_DEVICE: ; @D2A SET_LOG_DEVICE: ; @D2A MOV AX,STAT_GEN ; Return general error code @D2A OR AX,STAT_DONE ; Add "done" bit to status word @D2A MOV RH.RHC_STA,AX ; Store status into request header @D2A RET SUBTTL Generic IOCTL Service Routine PAGE ;------------------------------------------------------------------------------; ; This routine handles the Generic IOCTL call. The Emulator provides an D2A; ; interface through the Generic IOCTL call to query the number of XMA D2A; ; blocks available. When the function code in the parameter list is 0 the D2A; ; Emulator will return the number of XMA blocks available. There are no D2A; ; other functions uspported at this time. D2A; ;------------------------------------------------------------------------------; GIP EQU ES:[DI] ; @D2A GEN_IOCTL_PARM STRUC ; @D2A GIOPLEN DW ? ; Length of the parameter list @D2A GIOPFCN DW ? ; Function code @D2A GIOPBLK DW ? ; Number of XMA blocks available @D2A GEN_IOCTL_PARM ENDS ; @D2A MAXFCN EQU 0 ; Highest function number allowed @D2A ; Return codes D2A GOODRET EQU 0 ; Good return code @D2A BADLEN EQU 1 ; Bad parameter list length @D2A BADFCN EQU 2 ; Bad function number @D2A GENERIC_IOCTL: ; D2A LES DI,RH.RH19_RQPK ; Point ES:DI to the Generic IOCTL @D2A ; request packet D2A ; First check to make sure the parameter list is long enough to return the D2A ; number of XMA blocks. D2A CMP GIP.GIOPLEN,4 ; Do we have at least four bytes? @D2A JAE GIP_CHKFCN ; Yup. Go to check function number. @D2A MOV GIP.GIOPFCN,BADLEN ; Nope. Sorry. Return the error @D2A JMP GIP_DONE ; code and go to the end. @D2A ; Check if the function number in the parameter list is a valid function. D2A GIP_CHKFCN: ; D2A CMP GIP.GIOPFCN,MAXFCN ; Is the function code less than or @D2A ; equal to the maximum supported? D2A JLE GIP_CONT ; Yes. Good boy. You get to continue. @D2A MOV GIP.GIOPFCN,BADFCN ; No. Shamey, shamey. Set the bad @D2A JMP GIP_DONE ; return code and go to the end. @D2A ; Parameter list is OK. Let's return the number of XMA blocks. D2A GIP_CONT: ; D2A MOV GIP.GIOPFCN,GOODRET ; Set a good return code @D2A MOV AX,CS:HI_XMA_BLK ; Get the number of XMA blox @D2A MOV GIP.GIOPBLK,AX ; Put it in the paramter list @D2A GIP_DONE: ; D2A MOV RH.RHC_STA,STAT_DONE ; Store done status and good return @D2A ; code into request header D2A RET ; @D2A INT15F88 PROC FAR ; P7A ; The following is the interrupt chaining structure specified in the PC AT P7A ; Technical Reference. P7A JMP SHORT BEGIN ; P7A CHAINOFF DW 0 ; Offest of the previous INT 15 vect. @P7A CHAINSEG DW 0 ; Segment of the previous INT 15 vect.@P7A SIGNATURE DW 424BH ; Says we're doing chaining @P7A FLAGS DB 0 ; @P7A FIRST EQU 80H ; @P7A JMP SHORT RESET ; @P7A RESERVED DB 7 DUP (0) ; @P7A ; OK. Let's see if the user asked for function 88H, query memory size. P7A ; The function number is specified in the AL register. If it's P7A ; function88h, then put the memory size in AX and IRET to the caller. P7A ; Else, just pass the interrupt on to the guy who was installed in the INT P7A ; 15 vector before us. P7A BEGIN: CMP AH,88H ; Is it function 88H? @P7A JNE NOT_MINE ; It's not ours to handle @P7A MOV AX,CS:EXT_MEM ; Put the number of K into AX @P7A IRET ; Return to the caller @P7A NOT_MINE: JMP CS:DWORD PTR CHAINOFF ; Pass the interrupt on to the @P7A ; previously installed vector @P7A RESET: RET ; This, too, is part of the interrupt@P7A ; chaining structure. We will just@P7A ; return on a call to reset. Note @P7A ; that this is a far return. @P7A INT15F88 ENDP ; @P7A LEAVE_RES LABEL NEAR ; Leave code up to here resident.@D0A @D2M SUBTTL Initialize Routine PAGE INIT_P1: PUSH ES ; Save our code segment at the @D0A MOV DI,0 ; fixed location 0:4F4. This @P3C MOV ES,DI ; gives us a quick way to find CS @P3C MOV DI,4F4H ; and also enables us to break on @P3C MOV ES:[DI],CS ; a write to 0:4F4 which helps us @P3C POP ES ; find the code on the ICE386. @D0A MOV AH,DISPSTRG ; Display the welcome message. @D0A MOV DX,OFFSET WELCOME ; @D0A PUSH DS ; Save DS since DS:BX points to the @D0A PUSH CS ; request header @D0A POP DS ; DS:DX points to the message @D0A INT 21H ; Display the message @D0A POP DS ; Restore DS @D0A ; @P3D MOV RH.RH0_ENDS,CS ; Set the segment and offset of the end MOV RH.RH0_ENDO,OFFSET LEAVE_RES ; of code to leave resident MOV RH.RHC_STA,STAT_DONE ; Store "done" status into request ; header CMP CS:ISmodel_80,1 ; Check if we're on a model_80 @D0A @P1C JE CONT ; If so, then continue @D0A ; D0A MOV RH.RH0_ENDO,0 ; Leave nothing resident @D0A MOV AX,STAT_GEN ; Return general error code @D0A OR AX,STAT_DONE ; Add "done" bit to status word@D0A @P3C MOV RH.RHC_STA,AX ; Store status into request header @D0A ; D0A MOV AH,DISPSTRG ; Display the message that we are @D0A MOV DX,OFFSET NO_80386 ; not on a model_80 @D0A PUSH CS ; @D0A POP DS ; @D0A INT 21H ; @D0A ; D0A RET ; D0A ; D0A CONT: ; @D0M SMSW AX ; Get machine status register TEST AL,1 ; Check if the processor is already in ; protect mode. If so, then someone ; else (maybe us) has already taken ; over protect mode. JZ STILLOK ; If not, keep going @D0C MOV RH.RH0_ENDO,0 ; Leave nothing resident @D0A MOV AX,STAT_GEN ; Return general error code @D0A OR AX,STAT_DONE ; Add "done" bit to status word@D0A @P3C MOV RH.RHC_STA,AX ; Store status into request header @D0A ; D0A MOV AH,DISPSTRG ; Display the message that protect @D0A MOV DX,OFFSET WAS_INST ; mode is taken. @D0A PUSH CS ; DS:DX points to the message @D0A POP DS ; @D0A INT 21H ; @D0A RET ; STILLOK: ; D0A PUSH 0DEADH ; Push stack delimiter ; Don't have to set character ID @D2D CALL GET_PARMS ; Get the MOVEBLOCK buffer size if jnc StillOK1 MOV RH.RH0_ENDO,0 ; Leave nothing resident @D0A MOV AX,STAT_GEN ; Return general error code @D0A OR AX,STAT_DONE ; Add "done" bit to status word@D0A @P3C MOV RH.RHC_STA,AX ; Store status into request header @D0A ; D0A pop ax ret ; exit program StillOK1: ; one was specified CLI ; Disable interrupts PUSH CS ; Now we can point DS to our own @P3A POP DS ; code segment @P3A ; 2@D2D MOV AX,CS MOV REAL_CS,AX ; Save real CS for when we @P3C MOV AX,SS ; switch to protect mode MOV REAL_SS,AX ; Save real SS @P3C MOV AX,SP MOV REAL_SP,AX ; Save real SP @P3C ;------------------------------------------------------------------------------; ; Enable address line A20 ; ;------------------------------------------------------------------------------; ; 3@P4D CALL GATE_A20 INT 11H ; Get the BIOS equipment flags AND AL,30H ; Bits 5 and 6 on means it's a mono CMP AL,30H JE LEAVEBW MOV CRT_SELECTOR,C_CCRT_PTR ; Set the CRT selector to color display LEAVEBW: MOV AH,88H ; Get number of 1k blocks above 1M INT 15H ADD AX,1024 ; Add 640K for the memory below 640K @P7C MOV MAXMEM,AX ; Save for later ; Get the maximum XMA block number and save it in the header up front D0A ; All memory is treated as XMA memory. D1C ; D0A SUB AX,BUFF_SIZE ; Can't use the MOVEBLOCK buffer for @D0A ; XMA memory. AX = number of K D0A ; available. D0A SUB AX,(1024-640) +128 ; Subtract 128k for the Emulator code ;an000; dms; SHR AX,2 ; Divide by four to get the number of @D0A ; 4K blocks D0A DEC AX ; Subtract 1. This converts the @P3A ; number of blocks available to the P3A ; highest block number available. P3A ; Block numbers are zero based. P3A MOV HI_XMA_BLK,AX ; Save it in the header D0A ;------------------------------------------------------------------------------; ; Now lets relocate ourselves to high memory. ; ;------------------------------------------------------------------------------; MOV AX,HIGH_SEG ; Set ES to the highest segment value MOV ES,AX MOV DI,0 ; ES:DI points to the place to relocate to MOV AX,CS MOV DS,AX MOV SI,0 ; DS:SI points to our code to be moved MOV CX,DDSIZE/2 ; Length of code / 2 since moving words CLD REP MOVSW ; Copy myself to high memory JUMPFAR NEXT,HIGH_SEG ; Jump to my relocated code NEXT: MOV AX,HIGH_SEG ; Set DS to be the same as CS MOV DS,AX ;------------------------------------------------------------------------------; ; The machine is still in real mode. Zero out GDT and IDT ram. ; ;------------------------------------------------------------------------------; MOV DI,GDT_LOC ; DI points to GDT location MOV CX,(GDT_LEN+SIDT_LEN)/2 ; Set GDT and IDT to zero MOV AX,0 ; Store zeroes for now REP STOSW ;------------------------------------------------------------------------------; ; Use good-old real-mode selectors to set up the page tables. The ; ; page directory is a 4K block that is placed just before the ; ; beginning of the GDT and on a 4K boundary. Note that the DATAOV ; ; macro creates a prefix for the following instruction so that its ; ; data references are 32 bits wide. ; ;------------------------------------------------------------------------------; DATAOV SUB AX,AX ; Clear EAX (32 bit AX reg.) MOV AX,HIGH_SEG ; Get the current code segment DATAOV SUB BX,BX ; Clear EBX (32 bit BX reg.) MOV BX,GDT_LOC/16 ; Load the offset of the GDT, converted ; to paragraphs DATAOV ; Add it on to the current code segment ADD AX,BX ; to get the segment address of the GDT. ; This will be over 1M, so use 32 bits. AND AX,0FF00H ; Round down to nice 4k boundary DATAOV SUB BX,BX ; Clear EBX MOV BX,4096/16 ; Load with the size of the page directory ; converted to paragraphs DATAOV ; Subtract the number of paragraphs needed SUB AX,BX ; for the page directory DATAOV SHL AX,4 ; Convert from paragraphs to bytes CMOV CR3,EAX ; Load the address of the page directory ; into CR3 DATAOV SUB BX,BX ; Clear EBX MOV BX,HIGH_SEG ; Load our current code segment DATAOV SHL BX,4 ; Convert from paragraphs to bytes DATAOV SUB AX,BX ; Subtract from the address of the page ; directory to get the offset of the ; directory in our code segment MOV SGTBLOFF,AX ; Save for later ; Now let's clear the page directory MOV CX,2048 ; Length is 4K/2 since storing words DATAOV MOV DI,AX ; ES:EDI points to beginning of directory MOV AX,0 REP STOSW ; Clear the page directory! ;------------------------------------------------------------------------------; ; Initialize the first directory entries to our page tables ; ;------------------------------------------------------------------------------; CMOV EAX,CR3 ; Get back CR3 - the address of the page dir. DATAOV MOV DI,SGTBLOFF ; Point ES:EDI to first entry in directory DATAOV SUB BX,BX ; Clear EBX MOV BX,MEG_SUPPORTED/4*4096 ; Load the size of the page tables. ; Each page table maps 4M of memory, so divide ; the number of Meg supported by 4 to get the ; number of page tables. Each page table is ; 4K in size, so multiply by 4K. DATAOV SUB AX,BX ; Subtract the size needed for the page tables ; from the address of the page directory to ; get the address of the first page table. ADD AX,7 ; Set the present bit and access rights. ; This converts the address to a valid entry ; for the page directory. DATAOV MOV NORMPAGE,AX ; Save for later MOV CX,MEG_SUPPORTED/4 ; Load the number of page tables into CX DATAOV SUB BX,BX ; Clear EBX MOV BX,1000H ; Set up 4k increment ; ; Now we load the page directory. EAX contains the address of the first ; page table, EBX contains 4K, CX contains the number of page tables, and ; ES:EDI (32 bit DI reg.) points to the first page directory entry. Now what ; we do is stuff EAX into the 32bits pointed to by EDI. EDI is then auto- ; incremented by four bytes, because of the 32 bit stuff, and points to the ; next page directory entry. (Page directory and page table entries are four ; bytes long.) Then we add the 4K in EBX to the address in EAX making EAX ; the address of the next page table. This is done for the number of page ; table entries in CX. Pretty slick, huh? ; LPT: DATAOV ; Stuff the page table address into the STOSW ; page directory DATAOV ; Add 4K to the page table address in EAX ADD AX,BX ; so that it contains the address of the ; next page table LOOP LPT ; Do it again ; Now calcuate the offset from our code segment of the page tables DATAOV SUB BX,BX ; Clear EBX MOV BX,HIGH_SEG ; Load our current code segment DATAOV SHL BX,4 ; Convert paragraphs to bytes DATAOV ; Load EAX with the address of the first MOV AX,NORMPAGE ; page table DATAOV SUB AX,BX ; Convert EAX to an offset AND AL,0F8H ; AND off the access rights MOV PGTBLOFF,AX ; Save for later ;------------------------------------------------------------------------------; ; Initialize the page tables ; ;------------------------------------------------------------------------------; MOV DI,PGTBLOFF ; ES:DI points to the first page table DATAOV SUB AX,AX ; Zero EAX ADD AX,7 ; Set the present and access rights MOV CX,MEG_SUPPORTED/4*1024 ; Load CX with the number of page table ; entries to initialize. As mentioned ; above, the number of page tables = ; number of Meg / 4. There are 1K ; entries per table so multiply by 1K DATAOV SUB BX,BX ; Clear EBX MOV BX,1000H ; Set up 4k increment ; ; As with the page directory, we use a tight loop to initialize the page tables. ; EAX contains the address of the first page frame, which is 0000, plus the ; access rights. EBX contains a 4K increment. ES:DI points to the first entry ; in the first page table. CX contains the number of page table entries to ; initialize. The stuff and increment works the same as for the page directory ; with an added touch. Note that this does all the page tables in one fell ; swoop. When we finish stuffing the last address into the first page table ; the next place we stuff is into the first entry in the second page table. ; Since our page tables are back to back we can just zoom up the page tables ; incrementing by 4K as we go and thus initialize all the page tables in one ; fell swoop. ; BPT: DATAOV ; Stuff the page frame address into the STOSW ; page table DATAOV ADD AX,BX ; Next 4k page frame LOOP BPT ;------------------------------------------------------------------------------; ; Now set up the first 64K over 1M to point to point to the first 64K ; ; in low memory to simulate the segment wrap over 1M. ; ; For now will set it up to point to itself and try to get DOS to load ; ; the device driver up there. Will find out if anyone tries to alter ; ; it because it will be marked for system use only. ; ;------------------------------------------------------------------------------; MOV DI,1024 ; 1M offset into page table ADD DI,PGTBLOFF ; Page table offset MOV AX,10H ; Set EAX to contain 1M address by loading DATAOV ; it with 10H and shifting it 16 bits to SHL AX,16 ; get 00100000. (Same as 10000:0) ADD AX,5 ; Present, system use, read only MOV CX,16 ; 16 entries = 64k BPT2: DATAOV STOSW ; Stuff the address in the page table DATAOV ADD AX,BX ; Next 4k page frame LOOP BPT2 PAGE ;------------------------------------------------------------------------------; ; Build the Global Descriptor Table and load the GDT register. ; ;------------------------------------------------------------------------------; CALL GDT_BLD MOV DI,GDT_PTR ; Get the offset of the GDT descriptor ADD DI,GDT_LOC ; located in the GDT MOV BP,DI ; Transfer the offset to BP LGDT ES:FWORD PTR[BP] ; Put the descriptor for the GDT into ; the GDT register PAGE ;------------------------------------------------------------------------------; ; Build and initialize the system Interrupt Descriptor Table, ; ; then load the IDT register. ; ;------------------------------------------------------------------------------; CALL SIDT_BLD MOV DI,MON_IDT_PTR ; Get the offset of the IDT descriptor ADD DI,GDT_LOC ; located in the GDT MOV BP,DI ; Transfer the offset to BP LIDT ES:FWORD PTR[BP] ; Put the descriptor for the IDT into ; the IDT register PAGE ;------------------------------------------------------------------------------; ; At this point we prepare to switch to virtual mode. The first ; ; instruction after the LMSW that causes the switch must be a ; ; jump far to set a protected mode segment selector into CS. ; ;------------------------------------------------------------------------------; MOV AX,VIRTUAL_ENABLE ; Machine status word needed to LMSW AX ; switch to virtual mode JUMPFAR DONE,SYS_PATCH_CS ; Must purge pre-fetch queue ; and set selector into CS DONE: PAGE ;------------------------------------------------------------------------------; ; Initialize all the segment registers ; ;------------------------------------------------------------------------------; MOV AX,SYS_PATCH_DS ; Load DS, ES, and SS with the selector MOV DS,AX ; for our data area. This is the same MOV ES,AX ; as our code area but has read/write ; access. MOV SS,AX MOV SP,OFFSET SP_INIT PUSH 0002H ; Clean up our flags. Turn off all bits POPF ; except the one that is always on. ;------------------------------------------------------------------------------; ; Load the LDTR to avoid faults ; ;------------------------------------------------------------------------------; MOV AX,SCRUBBER.TSS_PTR ; Load DS with the data descriptor for MOV DS,AX ; the virtual machine's TSS MOV AX,SCRUBBER.VM_LDTR ; Get the LDTR for virtual machine MOV DS:VM_LDT,AX ; Set LDTR in TSS LLDT AX ; Set the LDTR. Temporary for now. ; Have to always have space allocated for the dispatch task TSS MOV AX,SCRUBBER.VM_TR ; Low mem gets clobbered without this @P5C LTR AX ; Set current Task Register ; This TSS is located right after the IDT PAGE ;------------------------------------------------------------------------------; ; Now we initialize the TSS (Task State Segment) for the one and only ; ; virtual 8086 task. This task encompasses everything that runs in real ; ; mode. First we clear the TSS and its I/O bit map. Then we initialize ; ; the bit map for all the I/O ports we want to trap. Then we set up the ; ; registers for the V86 task. These registers are given the same values ; ; as we got on entry. IP is set to point to TEST_EXIT. ; ;------------------------------------------------------------------------------; MOV AX,SCRUBBER.TSS_PTR ; Load ES and DS with the descriptor MOV DS,AX ; for the VM's TSS with read/write MOV ES,AX ; access rights CLD MOV DI,0 ; Point ES:DI to the beginning of the TSS MOV AX,0 ; Clear AX MOV BX,0 ; Clear BX MOV CX,TSS_386_LEN ; Load CX with the length of the TSS REP STOSB ; Clear the TSS MOV CX,TSS_BM_LEN ; Load CX with the length of the I/O bit ; map. The bit map immediately follows ; the TSS and is in the TSS segment. REP STOSB ; Clear the bit map MOV AL,0FFH ; Intel requires this byte STOSB ; ; Now set up the bit map. Turn on bits for I/O ports that we want to trap. ; MOV DI,0+TSS_386_LEN ; Set bits 0,2,4,6 to 1 - DMA ports MOV AL,055H STOSB MOV DI,1+TSS_386_LEN ; Set C to 1 - DMA port MOV AL,010H STOSB MOV DI,3+TSS_386_LEN ; Set 18,1A to 1 - DMA ports MOV AL,005H STOSB MOV DI,16+TSS_386_LEN ; Set 80-8f to 1s - DMA page ports MOV AL,0FFH ; + manufacturing port for ctl-alt-del STOSB STOSB MOV DI,0680H/8+TSS_386_LEN ; Set Roundup manuf. port to 1 MOV AL,001H STOSB MOV DI,31A0H/8+TSS_386_LEN ; Set 31a0-31a7 to 1s (XMA) MOV AL,0FFH STOSB MOV WORD PTR [BX].ETSS_BM_OFFSET,TSS_386_LEN ; Put the bit map offset in the TSS MOV WORD PTR [BX].ETSS_SP0,OFFSET SP_INIT ; Put our SP as the SP for privilege ; level 0 MOV WORD PTR [BX].ETSS_SS0,SYS_PATCH_DS ; Put our SS as the SS for privilege ; level 0 ; Next we set up the segment registers MOV WORD PTR [BX].ETSS_GS,SEG PROG ; GS - our code segment MOV WORD PTR [BX].ETSS_FS,SEG PROG ; FS - our code segment MOV WORD PTR [BX].ETSS_DS,SEG PROG ; DS - our code segment MOV WORD PTR [BX].ETSS_ES,SEG PROG ; ES - our code segment ; Next the SS,SP MOV AX,CS:REAL_SS ; Set the real mode SS as the SS for the task MOV WORD PTR [BX].ETSS_SS,AX MOV AX,CS:REAL_SP ; Set the real mode SP as the SP for the task MOV WORD PTR [BX].ETSS_SP,AX ; The flags register MOV WORD PTR [BX].ETSS_FL2,2 ; Set the VM flag. Task is a V86 task. MOV WORD PTR [BX].ETSS_FL,0202H ; Set interrupts enabled ; Set up CS and IP MOV AX,CS:REAL_CS ; Set the real mode CS as the CS for the task MOV WORD PTR [BX].ETSS_CS,AX ; This is the CS we got when we loaded ; in low memory, before relocating MOV AX,OFFSET PROG:TEST_EXIT ; Set IP to the label TEST_EXIT below. MOV WORD PTR [BX].ETSS_IP,AX ; The LDTR MOV WORD PTR [BX].ETSS_LDT,SCRUBBER.VM_LDTR ; And finally, CR3, the page directory base register CMOV EAX,CR3 ; Get CR3 DATAOV MOV WORD PTR [BX].ETSS_CR3,AX ; Save it in the TSS PAGE ;------------------------------------------------------------------------------; ; Now initialize our wonderful XMA page tables. Each table maps 4M. ; ; There is one table for each XMA bank since 4M is enough to map the ; ; 1M address space. All the XMA tables are initialized to point to ; ; the real memory at 0 to 4M. This is done by just copying the page ; ; table entry for 0 to 4M that was initialized above. ; ;------------------------------------------------------------------------------; MOV AX,SYS_PATCH_DS ; Load DS with the selector for our data MOV DS,AX MOV SI,PGTBLOFF ; DS:SI point to the real page table for 0-4M MOV AX,XMA_PAGES_SEL ; Load ES with the selector for the XMA pages MOV ES,AX SUB DI,DI ; ES:DI point to the first XMA page table MOV CX,2048 ; Copy 4K / 2 since we're copying words REP MOVSW ; Copy the first XMA page table ; ; Now ES:DI points to the second XMA page table. Set DS:SI to point to the ; first XMA page table as the source for the copy. Now we can put a count ; of 15 page tables in CX. After each page is copied it is used as the source ; for the next page. This method lets us zip up the page tables initializing ; them all to be the same as the original page table for 0 - 4M. ; MOV AX,XMA_PAGES_SEL ; Load DS with the selector for the XMA page MOV DS,AX ; tables SUB SI,SI ; DS:SI points to the first XMA page table MOV CX,2048*15 ; Copy 15 more page tables REP MOVSW ; Copy to the other 15 XMA ID'S page tables ; D1A ; Set the first page directory entry to point to the page table for bank 0. D1A ; This is another way of saying, "Let's make bank 0 the active bank." We D1A ; are now emulating the XMA 2 card along with its initialization device D1A ; driver, INDXMAA.SYS. When the device driver exits, it leaves the XMA 2 D1A ; card enabled and set to bank 0. Therefore, we must do the same. D1A ; D1A ; D1A MOV AX,SYS_PATCH_DS ; Load DS and ES with our data segment @D1A MOV DS,AX ; selector @D1A MOV ES,AX ; @D1A MOV DI,SGTBLOFF ; Point ES:DI to the first page @D1A ; directory entry D1A DATAOV ; Load AX with the page directory entry @D1A MOV AX,XMAPAGE ; for the first XMA page table @D1A DATAOV ; Stuff the address of the page table @D1A STOSW ; for bank 0 into the page directory @D1A PAGE ;------------------------------------------------------------------------------; ; And now, the moment you've all been waiting for -- TURN ON THE PAGING ; ; MECHANISM!!! ; ;------------------------------------------------------------------------------; CMOV EAX,CR0 ; Get CR0 @P5A MOV BX,8000H ; Set up BX to OR on the Paging Enable bit @P5C DATAOV SHL BX,16 ; It's the one all the way on the left @P5C DATAOV ; @P5A OR AX,BX ; Set the paging enabled bit @P5A OR AL,02H ; Set co-processor bit on @P5A AND AL,0F7H ; Turn off Task Switch bit @P5C CMOV CR0,EAX ; Here we go... ; Make sure high order bits of ESP are zero - a1 errata MOV AX,SP ; Save SP in AX 'cause it changes when we do... PUSH 0 ; this PUSH. Push 0 for high 16 bits of ESP PUSH AX ; Push low 16 bits of SP DATAOV POP SP ; Pop 32 bit ESP! PAGE ;------------------------------------------------------------------------------; ; Now we give control back to the V86 task by setting up the stack P5C; ; for an IRET back to the V86 task. This requires putting the V86 P5C; ; task's segment registers, SS and ESP, and the EFLAGS, CS and IP on P5C; ; the stack. The 80386 puts all these values on the stack when it P5C; ; interrupts out of V86 mode, so it expects them there on an IRET P5C; ; back to V86 mode. But really we are giving control back to ; ; ourself. The CS:IP on the stack point to the label TEST_EXIT ; ; below, but it is in the copy of the emulator that was originally ; ; loaded, not the copy that was relocated to high memory and is now ; ; running in protect mode. This clever trick will result in the ; ; original copy of the emulator returning to DOS which will continue ; ; to load the rest of the system. The system will come up completely ; ; unaware that it is running in a small universe of a V86 task which ; ; is being monitored by the XMA emulator. ; ;------------------------------------------------------------------------------; MOV AX,SCRUBBER.TSS_PTR ; Load DS with the descriptor for the @P5A MOV DS,AX ; VM's TSS with read/write access @P5A MOV BX,0 ; VM's TSS with read/write access @P5A ; P5A ; Set up our stack for an IRET to the V86 task. This is an inter-level P5A ; IRET to a V86 task so we need the V86 task's SS, ESP, ES, DS, FS and GS P5A ; as well as his EFLAGS, EIP and CS. P5A ; P5A DATAOV ; @P5A PUSH WORD PTR [BX].ETSS_GS ; Put V86 task's GS on the stack @P5A DATAOV ; @P5A PUSH WORD PTR [BX].ETSS_FS ; Put V86 task's FS on the stack @P5A DATAOV ; @P5A PUSH WORD PTR [BX].ETSS_DS ; Put V86 task's DS on the stack @P5A DATAOV ; @P5A PUSH WORD PTR [BX].ETSS_ES ; Put V86 task's ES on the stack @P5A DATAOV ; @P5A PUSH WORD PTR [BX].ETSS_SS ; Put V86 task's SS on the stack @P5A DATAOV ; @P5A PUSH WORD PTR [BX].ETSS_SP ; Put V86 task's ESP on the stack @P5A DATAOV ; @P5A PUSH WORD PTR [BX].ETSS_FL ; Put V86 task's EFLAGS on the stack @P5A DATAOV ; @P5A PUSH WORD PTR [BX].ETSS_CS ; Put V86 task's CS on the stack @P5A DATAOV ; @P5A PUSH WORD PTR [BX].ETSS_IP ; Put V86 task's EIP on the stack @P5A DATAOV ; @P5A IRET ; @P5A ; @P5D TEST_EXIT: ; We are now running in V86 mode POP AX ; Pop the stack until our DEAD delimiter is CMP AX,0DEADH ; found JNE TEST_EXIT ; Replace the interrupt 15 vector with our handler (INT15F88). P7A MOV AH,GET_VECT ; Get the current vector at interrupt 15H @P7A MOV AL,15H ; @P7A INT 21H ; @P7A MOV CS:CHAINSEG,ES ; Save it in the chaining header in @P7A MOV CS:CHAINOFF,BX ; INT15F88 @P7A MOV AH,SET_VECT ; Set the entry point of INT15F88 as the @P7A MOV AL,15H ; new interrupt 15 vector @P7A PUSH CS ; @P7A POP DS ; @P7A MOV DX,OFFSET INT15F88 ; @P7A INT 21H ; @P7A ; Copy the number of K for extended memory from BUFF_SIZE to EXT_MEM. This P7A ; is needed because BUFF_SIZE does not stay resident, EXT_MEM does. P7A MOV AX,BUFF_SIZE ; @P7A MOV EXT_MEM,AX ; @P7A ; Issue the message that says we installed successfully MOV AH,DISPSTRG ; Set AH to DOS display string function @D0A MOV DX,OFFSET GOODLOAD ; @D0A PUSH CS ; @D0A POP DS ; DS:DX points to the message @D0A INT 21H ; Display the message @D0A RET ; Return to IRPT which called INIT_P1 SUBTTL Gate A20 PAGE ;------------------------------------------------------------------------------; ; GATE_A20 ; ; This routine controls a signal which gates address bit 20. ; ; Bit 2 of port 92H controls the enabling of A20. If bit 2 is on, P4C; ; then A20 is enabled. Conversely, if bit 2 is off, A20 is disabled. P4C; ; ; ;------------------------------------------------------------------------------; ; Equates for the Gate A20 enable ENABLE_A20 EQU 02H ; Bit 2 of port 92H turns on A20 @P4C GATE_A20 PROC IN AL,92H ; Get the current value of port 92 @P4A OR AL,ENABLE_A20 ; Turn on the bit to enable A20 @P4A OUT 92H,AL ; Send it back out to port 92 @P4A RET ; 15@P4D GATE_A20 ENDP SUBTTL GET_PARMS parameter line scan PAGE ;------------------------------------------------------------------------------; ; GET_PARMS ; ; This procedure converts the numeric parameter following the DEVICE statement ; ; in the CONFIG.SYS file to a binary number and saves it in BUFF_SIZE. The ; ; number is rounded up to the nearest 16K boundary. ; ; ; ; Register usage: ; ; DS:SI indexes parameter string ; ; AL contains character from parameter string ; ; CX value from GET_NUMBER ; ; ; ;------------------------------------------------------------------------------; ASSUME DS:NOTHING ; DS:BX point to Request Header GET_PARMS PROC PUSH DS ; Save DS push bx ; save bx ;an000; dms; LDS SI,RH.RH0_BPBA ; DS:SI point to all text after "DEVICE=" ; in CONFIG.SYS XOR AL,AL ; Start with a null character in AL. ;------------------------------------------------------------------------------; ; Skip until first delimiter is found. There may be digits in the path string.; ; ; ; DS:SI points to \pathstring\386XMAEM.SYS nn nn nn ; ; The character following 386XMAEM.SYS may have been changed to a null (00H). ; ; All letters have been changed to uppercase. ; ;------------------------------------------------------------------------------; GET_PARMS_A: CALL GET_PCHAR ; Get a character from the parameter string JZ Get_Parms_Null ; The zero flag is set if the end of the line ; is found. If so, then exit. ; Check for various delimeters OR AL,AL ; Null JZ GET_PARMS_B CMP AL,' ' ; Blank JE GET_PARMS_B CMP AL,',' ; Comma JE GET_PARMS_B CMP AL,';' ; Semi-colon JE GET_PARMS_B CMP AL,'+' ; Plus sign JE GET_PARMS_B CMP AL,'=' ; Equals JE GET_PARMS_B CMP AL,TAB ; Tab JNE GET_PARMS_A ; Skip until delimiter or CR is found GET_PARMS_B: ; Now pointing to first delimiter CALL SKIP_TO_DIGIT ; Skip to first digit JZ Get_Parms_C ; Found EOL, no digits remain CALL GET_NUMBER ; Extract the digits and convert to binary jmp Get_Parms_Found ; Parm found Get_Parms_Null: xor cx,cx ; set cx to 0 ;an000; dms; Get_Parms_Found: mov bx,cx ; put cx value in bx ;an000; dms; cmp cx,0 ; 0 pages requested? ;an000; dms; jne Get_Parm_Max ; allocate maximum number ;an000; dms; MOV CS:BUFF_SIZE,0; Store buffer size jmp Get_Parms_C Get_Parm_Max: cmp bx,64 ; >= 64 pages requested? ;an000; dms; jnb Get_Parms_64_Pg ; yes - continue ;an000; dms; mov dx,offset Small_Parm ; Parm < 64 and > 0 ;an000; dms; mov ah,Dispstrg ; Display the welcome message. ;an000; dms; push ds ; Save DS ;an000; dms; push cs ; ;an000; dms; pop ds ; DS:DX points to the message ;an000; dms; int 21h ; Display the message ;an000; dms; pop ds ; Restore DS ;an000; dms; stc ; flag an error occurred ;an000; dms; jmp Get_Parms_C ; exit routine ;an000; dms; Get_Parms_64_Pg: mov ax,bx ; prepare to adjust to Kb value ;an000; dms; mov cx,10h ; 16Kb per page ;an000; dms; xor dx,dx ; clear high word ;an000; dms; mul cx ; get Kb value ;an000; dms; mov bx,ax ; store page Kb value in bx ;an000; dms; add bx,128 ; adjust for emulator code mov ah,88h ; get number of 1k blocks above 1Mb ;an000; dms; int 15h ; sub ax,bx ; get number of blocks to allocate for extended ;an000; dms; jnc Get_Parms_Ext ; set extended memory value in buff size ;an000; dms; mov dx,offset No_Mem ; not enough memory for parm mov ah,Dispstrg ; Display the welcome message. ;an000; dms; push ds ; Save DS ;an000; dms; push cs ; ;an000; dms; pop ds ; DS:DX points to the message ;an000; dms; int 21h ; Display the message ;an000; dms; pop ds ; Restore DS ;an000; dms; stc ; flag an error ;an000; dms; jmp Get_Parms_C ; exit routine ;an000; dms; Get_Parms_Ext: MOV CS:BUFF_SIZE,ax ; Store buffer size clc GET_PARMS_C: pop bx ; restore bx ;an000; dms; POP DS ; Restore DS RET ;------------------------------------------------------------------------------; ; GET_PCHAR -- Get a character from the parameter string into AL ; ;------------------------------------------------------------------------------; GET_PCHAR PROC CMP AL,CR ; Carriage return already encountered? JE GET_PCHAR_X ; Don't read past end of line LODSB ; Get character from DS:SI, increment SI CMP AL,CR ; Is the character a carriage return? JE GET_PCHAR_X ; Yes, leave the zero flag set to signal end ; of line CMP AL,LF ; No, is it a line feed? This will leave the ; zero flag set if a line feed was found. GET_PCHAR_X: RET GET_PCHAR ENDP ;------------------------------------------------------------------------------; ; CHECK_NUM -- Check if the character in AL is a numeric digit, ASCII for ; ; 0 - 9. The zero flag is set if the character is a digit, ; ; otherwise it is reset. ; ;------------------------------------------------------------------------------; CHECK_NUM PROC CMP AL,'0' ; If character is less than a "0" then it is not JB CHECK_NUM_X ; a number, so exit CMP AL,'9' ; If character is greater than a "9" then it is JA CHECK_NUM_X ; not a number, so exit CMP AL,AL ; Set the zero flag to show it is a number CHECK_NUM_X: RET ; Zero flag is left reset if character is not CHECK_NUM ENDP ; a number ;------------------------------------------------------------------------------; ; SKIP_TO_DIGIT -- Scan the parameter string until a numeric character is ; ; found or the end of the line is encountered. If a numeric ; ; character is not found then the zero flag is set. Else if ; ; a character was found then the zero flag is reset. ; ;------------------------------------------------------------------------------; SKIP_TO_DIGIT PROC CALL CHECK_NUM ; Is the current character a digit? JZ SKIP_TO_DIGIT_X ; If zero flag is set then it is a number CALL GET_PCHAR ; Get the next character from the line JNZ SKIP_TO_DIGIT ; Loop until first digit or CR or LF is found RET ; Fall through to here if digit not found SKIP_TO_DIGIT_X: CMP AL,0 ; Digit found, reset the zero flag to show digit RET ; was found SKIP_TO_DIGIT ENDP ;------------------------------------------------------------------------------; ; GET_NUMBER -- Convert the character digits in the parameter string to a ; ; binary value. The value is returned in CX, unless the ; ; calculation overflows, in which case return a 0. The next ; ; character after the digits is left in AL. ; ;------------------------------------------------------------------------------; C10 DW 10 GN_ERR DB ? ; Zero if no overflow in accumulation GET_NUMBER PROC ; Convert string of digits to binary value XOR CX,CX ; Clear CX, the resulting number MOV CS:GN_ERR,CL ; No overflow yet GET_NUMBER_A: SUB AL,'0' ; Convert the ASCII character in AL to binary CBW ; Clear AH XCHG AX,CX ; Previous accumulation in AX, new digit in CL MUL CS:C10 ; DX:AX = AX*10 OR CS:GN_ERR,DL ; Any overflow from AX goes into DX. Any non- ; zero value in DL will signal an error ADD AX,CX ; Add the new digit to ten times the previous ; digits XCHG AX,CX ; New number now in CX CALL GET_PCHAR ; Get the next character CALL CHECK_NUM ; Check if it is numeric JZ GET_NUMBER_A ; If so, then go back and add this digit to the ; result CMP CS:GN_ERR,0 ; Did we overflow? JE GET_NUMBER_B ; If not, we're done XOR CX,CX ; Return a zero result if overflow GET_NUMBER_B: RET GET_NUMBER ENDP GET_PARMS ENDP POST ENDP PROG ENDS END