NT4/private/mvdm/dpmi/486/dxutil.asm
2020-09-30 17:12:29 +02:00

2981 lines
84 KiB
NASM

PAGE ,132
TITLE DXUTIL.ASM -- Dos Extender Miscellaneous Routines
; Copyright (c) Microsoft Corporation 1988-1991. All Rights Reserved.
;****************************************************************
;* *
;* DXUTIL.ASM - Dos Extender Miscellaneous *
;* *
;****************************************************************
;* *
;* Module Description: *
;* *
;* This module contains miscellaneous routines for the Dos *
;* Extender. *
;* *
;****************************************************************
;* Revision History: *
;* *
;* 08/08/90 earleh DOSX and client privilege ring determined *
;* by equate in pmdefs.inc *
;* 04/09/90 jimmat If 286 with 287, put 287 into pMode too. *
;* 08/20/89 jimmat Removed local A20 code since HIMEM 2.07 *
;* works properly across processor resets *
;* 07/28/89 jimmat Added A20 check/set routines, added *
;* SelOff2SegOff & Lma2SegOff routines. *
;* 06/19/89 jimmat Set direction flag before REP MOVS *
;* 05/25/89 jimmat Added GetSegmentAccess routine *
;* 03/30/89 jimmat Set IOPL = 3 when entering protect mode *
;* 03/16/89 jimmat Added more debug sanity checks *
;* 03/15/89 jimmat Minor changes to run child in ring 1 *
;* 03/13/89 jimmat Added support for LDT & TSS *
;* 02/10/89 (GeneA): changed Dos Extender from small model to *
;* medium model. Also added MoveMemBlock function. *
;* 01/25/89 (GeneA): changed initialization of real mode code *
;* segment address in EnterRealMode. caused by adding *
;* new method of relocationg dos extender for PM operation *
;* 12/13/88 (GeneA): moved EnterProtectedMode and EnterReal- *
;* Mode here from dxinit.asm *
;* 09/16/88 (GeneA): created by extracting code from the *
;* SST debugger modules DOSXTND.ASM, VIRTMD.ASM, *
;* VRTUTIL.ASM, and INTERRPT.ASM *
;* 18-Dec-1992 sudeepb Changed cli/sti to faster FCLI/FSTI *
;* 24-Jan-1992 v-simonf Added WOW callout when INT 8 hooked *
;* *
;****************************************************************
.286p
.287
; -------------------------------------------------------
; INCLUDE FILE DEFINITIONS
; -------------------------------------------------------
; .sall
; .xlist
include segdefs.inc
include gendefs.inc
include pmdefs.inc
include dpmi.inc
if VCPI
include dxvcpi.inc
endif
IFDEF ROM
include dxrom.inc
ENDIF
include intmac.inc
.list
; -------------------------------------------------------
; GENERAL SYMBOL DEFINITIONS
; -------------------------------------------------------
SHUT_DOWN = 8Fh ;address in CMOS ram of the shutdown code
CMOS_ADDR = 70h ;i/o address of the cmos ram address register
CMOS_DATA = 71h ;i/o address of the cmos ram data register
DMAServiceSegment equ 040h ;40:7B bit 5 indicates DMA services
DMAServiceByte equ 07Bh ; are currently required
DMAServiceBit equ 020h
; -------------------------------------------------------
; EXTERNAL SYMBOL DEFINITIONS
; -------------------------------------------------------
; -------------------------------------------------------
; DATA SEGMENT DEFINITIONS
; -------------------------------------------------------
DXDATA segment
extrn segGDT:WORD
extrn segIDT:WORD
extrn selGDT:WORD
extrn selIDT:WORD
extrn selGDTFree:WORD
extrn selEHStack:WORD, npEHStackLimit:WORD
extrn bpGDT:FWORD
extrn bpIDT:FWORD
extrn bpRmIVT:FWORD
extrn idCpuType:WORD
extrn rgbXfrBuf1:BYTE
extrn i31HWReset:BYTE
ifdef NOT_NTVDM_NOT
extrn fHPVectra:BYTE
endif
extrn PMFaultVector:DWORD
extrn lpfnXMSFunc:DWORD
extrn PMInt24Handler:DWORD
if VCPI
extrn fVCPI:BYTE
endif
IFDEF ROM
extrn segDXCode:WORD
extrn segDXData:WORD
ENDIF
extrn pbReflStack:WORD
bIntMask db 0
bpBogusIDT df 0 ;This is loaded into the IDT register to
; force a bogus IDT to be defined. When we
; then do an interrupt a triple fault will
; occur forcing the processor to reset. This
; is when doing a mode switch to real mode.
IDTSaveArea dw 3 DUP (?) ;save area for IDT during mode switch
public A20EnableCount
A20EnableCount dw 0
ShutDownSP dw 0 ;stack pointer during 286 reset
public f286_287
f286_287 db 0 ;NZ if this is a 286 with 287 coprocessor
EXTRN FasterModeSwitch:WORD
if DEBUG ;------------------------------------------------------------
extrn fTraceA20:WORD
extrn fTraceMode:WORD
public fA20
fA20 db 0
endif ;DEBUG --------------------------------------------------------
selPmodeFS dw 0
selPmodeGS dw 0
public HighestSel
HighestSel dw 0
ifndef WOW_x86
public IretBopTable
IretBopTable label byte
irp x,<0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>
db 0c4h, 0c4h, 05dh, x
endm
else
public FastBop
FastBop df 0
IretBopTable label byte
irp x,<0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>
db 02eh, 066h, 0FFh, 01eh, 00h, 00h, 05dh, x
endm
NullSel dd 0
dd 0
endif
extrn DpmiFlags:WORD
DXDATA ends
ifndef WOW_x86
DXSTACK segment
extrn rgw0Stack:WORD
DXSTACK ends
endif
; -------------------------------------------------------
; CODE SEGMENT VARIABLES
; -------------------------------------------------------
DXCODE segment
IFNDEF ROM
extrn segDXData:WORD
extrn segDXCode:WORD
extrn selDgroup:WORD
ENDIF
if VCPI
extrn fnVCPI:FWORD
EXTRN laVTP:DWORD
endif
DXCODE ends
DXPMCODE segment
extrn selDgroupPM:WORD
DXPMCODE ends
; -------------------------------------------------------
subttl Real/Protected Mode Switch Routines
page
; -------------------------------------------------------
DXCODE segment
assume cs:DXCODE
; -------------------------------------------------------
; REAL/PROTECTED MODE SWITCH ROUTINES
; -------------------------------------------------------
;
; EnterProtectedMode -- This routine will switch the processor
; into protected mode. It will return with the processor
; in protected mode and all of the segment registers loaded
; with the selectors for the protected mode segments.
; (CS with the selector for DXCODE and DS,ES,SS with the
; selector for DXDATA)
; It will also switch mode dependent memory variables.
; It assumes that InitGlobalDscrTable and InitIntrDscrTable
; have been called to set up the descriptor tables appropriately.
;
; Note: Except for a very brief time in this routine and in
; EnterRealMode, the DOS Extender runs in the same ring along
; with it's child app. This has the benefit of eliminating
; ring transitions on hardware and software interrupts.
; It also makes it possible for the child to hook their
; own interrupt routine into the IDT.
;
; Input: none
; Output: none
; Errors: none
; Uses: AX, DS, ES, SS, CS modified, all others preserved
;
; NOTE: This routine turns interrupts of and does not turn them
; back on.
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public EnterProtectedMode
EnterProtectedMode proc near
FCLI
IFNDEF ROM
; Update the mode dependent variables.
mov ax,SEL_DXDATA or STD_RING
mov selDgroup,ax
ENDIF
; Set the DMA services required bit for pMode users.
mov ax,DMAServiceSegment
mov es,ax
or byte ptr es:[DMAServiceByte],DMAServiceBit
if VCPI
cmp fVCPI,0 ;if vcpi, don't touch
jne epmVCPIstart ; the a20 line, et al
endif
; 'local enable' the A20 line via HIMEM before switching to pMode.
; This is more complicated than you might think. Some real mode code
; (like old versions of SMARTDRV.SYS) may diddle with A20 on their own.
; These programs may not want us to change A20 on them. RMIntrReflector
; may do a XMS 'local enable' to turn A20 back on for one of these pgms.
; Also, on a 386 where we actually do the mode switch, we try to leave
; A20 enabled so as to not waste time diddling for nothing. The
; A20EnabledCount variable tracks if we've 'local enabled' A20 or not.
; Since we can't really trust real mode to leave A20 alone, we double
; check that it's really really on when we think it should be.
push bx ;save bx around XMS calls
cmp A20EnableCount,0 ;should A20 already be enabled?
jz enpm10 ; no, (normal 4 286) just go enable it
xmssvc 7 ; yes, is it really enabled?
or ax,ax
jnz enpm15 ; yes, we be done!
if DEBUG ;------------------------------------------------------------
or fA20,1 ; somebody done us wrong
endif ;---------------------------------------------------------------
xmssvc 6 ;keep enable/disable calls balanced
dec A20EnableCount
enpm10:
xmssvc 5 ;local enable A20
inc A20EnableCount
if DEBUG ;------------------------------------------------------------
or ax,ax
jnz @f
or fA20,2 ;enable failed!
@@:
cmp fTraceA20,0
jz @f
xmssvc 7 ;in debug mode, make double sure
or ax,ax ; A20 was enabled. Slows things
jnz @f ; down, but it's better to know.
or fA20,2
@@:
endif ;DEBUG --------------------------------------------------------
enpm15: pop bx
ifndef WOW_x86
; Make sure that the nested task flag is clear
pushf
pop ax
and ax,NOT 4000h
push ax
npopf
; Make sure that we have the appropriate descriptor tables in effect,
; and switch the machine into protected mode
enpr20: smsw ax ;get current machine state
or ax,1 ;set the protected mode bit
lgdt bpGDT
lidt bpIDT
lmsw ax ;and away we go
; Flush the instruction queue and load the code segment selector
; by doing a far jump.
db 0EAh ;jump far opcode
dw offset enpm40 ;offset of far pointer
dw SEL_DXCODE0 ;selector part of PM far pointer (ring 0)
if VCPI
; Switch when we are under VCPI
epmVCPIstart:
.386
push esi ;save regs modified by VCPI server
push eax ; (saving high word of EAX)
push bp ;save bp on stack & sp in bp
mov bp,sp
mov esi, laVTP ;linear address of structure
RMvcpi vcpiSWITCHTOPM ;go for it
; Entry point (PM) when running under vcpi
PUBLIC epmVCPI
epmVCPI:
mov ax,SEL_DXDATA0 ;stack has gotta be ring 0
mov ss,ax
mov sp,bp ;restore sp
pop bp ;restore old bp
pop eax ;restore regs that VCPI server may have zapped
pop esi
.286p
mov ax,SEL_DXDATA or STD_RING
mov ds,ax ;ds to our DGROUP
mov es,ax ;es too
jmp short enpmSwitchRing ;rejoin common code
endif
; Load the other segment registers with valid selectors (not under VCPI)
enpm40: mov ax,SEL_DXDATA0 ;stack has gotta be ring 0 also
mov ss,ax
; Load the LDT register and the Task Register
mov ax,SEL_LDT
lldt ax ;load the LDT register
mov ax,SEL_DXDATA or STD_RING
mov ds,ax ;ds to our DGROUP
mov ax,SEL_GDT
mov es,ax ;es to GDT
push si ;make sure busy bit is off
mov si,SEL_TSS ; in the TSS descriptor
mov es:[si].arbSegAccess,STD_TSS ; before trying to load it
ltr si ;now load the task register
pop si
else
.386p
push ebp
if 0
movzx ebp,sp
else
mov ebp,esp
endif
push SEL_DXCODE or STD_RING ; new cs
push 0 ; high half eip
push offset epmwow ; new eip
push SEL_DXDATA or STD_RING ; new ss
push ebp
push SEL_DXDATA or STD_RING ; new ds
DPMIBOP DPMISwitchToProtectedMode
epmwow:
pop ebp
.286p
endif
push ds ;point es to DGROUP
pop es
; If this is a 286 machine with a 287 math coprocessor, put the coprocessor
; into protected mode also.
cmp f286_287,0 ;286 and 287?
jz @f
xor al,al ; yup, clear co-processor busy line
out 0F0h,al
fsetpm ; and put it in pMode
@@:
; We're currently running in ring 0. Setup an interlevel iret frame
; to switch to our normal ring, and also force IOPL=3. I spent 1+ day
; debugging on a 286 system (with no debugger!) because the 286 seemed
; switch into protected mode with IOPL=0, and once we got to an outer
; ring, we would fault on things like CLI instructions.
enpmSwitchRing:
ifndef WOW_x86
mov ax,sp ;still points to return address
push SEL_DXDATA or STD_RING ;new ss
push ax ;new sp
pushf
pop ax
or ah,30h
push ax ;new flags, with IOPL=3
push SEL_DXCODE or STD_RING ;new cs
push offset DXCODE:epm_ret ;new ip
iret
endif
; When we get here, we are now in an outer ring.
epm_ret:
cmp idCpuType, 3
jc SegRegsOK
.386
mov ax, selPmodeFS
mov fs, ax
mov ax, selPmodeGS
mov gs, ax
.286p
SegRegsOK:
if DEBUG ;------------------------------------------------------------
cmp fTraceMode,0
jz @f
Trace_Out "^",x
@@:
if VCPI
cmp fVCPI,0
jz @f
jmp a20okay
@@:
endif ; VCPI
cmp A20EnableCount,1
jz @f
Debug_Out "EnterProtectedMode: A20EnableCount != 1"
@@:
cmp fTraceA20,0
jz a20okay
test fA20,0FFh
jz a20okay
test fA20,01h
jz @f
Trace_Out "EPM: A20 was wrong!"
@@:
test fA20,02h
jz @f
Trace_Out "EPM: A20 enable failed!"
@@:
test fA20,04h
jz @f
Trace_Out "rM2pMInt: A20 was on"
@@:
mov fA20,0
a20okay:
endif ;DEBUG --------------------------------------------------------
ret ;near return to caller in pMode
EnterProtectedMode endp
; -------------------------------------------------------
; EnterRealMode -- This routine will switch the processor
; from protected mode back into real mode. It will also
; reset the various mode dependent variables to their
; real mode values and load the segment registers with
; the real mode segment addresses.
;
; Input: none
; Output: none
; Errors: none
; Uses: AX, DS, ES, SS, CS modified
;
; NOTE: This routine must be called with the stack segment set
; to the Dos Extender data segment, as it resets the stack
; segment register to the Dos Extender real mode data segment
; but does not modify the stack pointer.
; NOTE: This routine turns interrupts off and and does not turn
; them back on.
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public EnterRealMode
EnterRealMode proc near
if DEBUG ;--------------------------------------------------------
cmp fTraceMode,0
jz @f
Trace_Out "v",x
@@:
endif ;DEBUG ---------------------------------------------------------
cmp idCpuType,3
jc RegsOkay
.386
mov ax,fs
mov selPmodeFS, ax
mov ax,gs
mov selPmodeGS, ax
RegsOkay:
.286p
FCLI
IFDEF ROM
push ds
pop es
ELSE
mov es,selDgroup
ENDIF
IFNDEF WOW_x86
; If we are running on an 80386, we can do the switch more efficiently.
cmp idCpuType,3 ;80386?
jc enrm20
; We're on an 386 (or better)--do a faster mode switch. Call a ring 0 proc
; to actually do the switch. The call gate has been setup to select either
; the roll-our-own 386 switch, or the VCPI switch.
enrm10:
db 9Ah ;call far SEL_RESET:0
dw 0,SEL_RESET or STD_RING
; The Reset386 routine does a near return (cause the far CS on the stack
; is for protected mode, and it returns in real mode). Discard the PM
; CS value.
pop ax ;discard old PM CS from stack
; The transition to the ring 0 procedure caused a stack switch. The return
; back to here (in real mode) didn't restore the old stack, so do it now...
pop sp ;restore previous sp offset
jmp enrm70 ;jmp to common exit code
; On the 80286, it is a lot more complicated. The PC BIOS start up code
; will check some special locations in the BIOS data segment to see if the
; machine was reset because it wants to do a mode switch. If this is the
; case, it will restore the machine state from these variables and return
; to the original process. We need to set up the state so that the BIOS
; will return control to us, and then generate a reset. The reset is
; generated by causing the machine to triple fault. This is an undocumented
; feature (i.e. bug) in the '286.
enrm20:
; Set up the stack for the BIOS to use after the reset.
cmp [FasterModeSwitch],0
jne enrm21
pushf ;Push an IRET type return address of where
; we want to come back to after the reset
push segDXCode
push offset enrm50
enrm21:
mov ax,segDXData ;Our real mode data segment address
IFNDEF ROM
ifdef NOT_NTVDM_NOT
test fHPVectra,HP_CLASSIC ;HP Vectra A & A+ systems have a bug
jz enrm25 ; in their rom that requires a
; different stack setup
push 0 ;HP Vectra A & A+ trashes this
push ax ;real mode data seg
pusha
push ax ;es?
jmp short enrm30
enrm25:
endif
ENDIF
pusha ;The BIOS will do a POPA, so put all of the
; registers on the stack for it.
push ax ;It will also pop these into ES and DS
push ax
enrm30:
push SEL_BIOSDATA ;BIOS data segment selector
pop es
assume es:BIOS_DATA
cmp [FasterModeSwitch],0
jne enrm31
; Tell the BIOS where the stack is.
mov IO_ROM_SEG,ax ;Set up the address of the stack for the
mov IO_ROM_INIT,sp ; BIOS to use after the reset
jmp enrm32
enrm31:
; Tell the BIOS where to transfer control.
mov bx,segDXCode
mov IO_ROM_SEG,bx
mov IO_ROM_INIT,offset DXCODE:enrm45
mov ShutDownSP,sp ;save stack pointer in data seg
enrm32:
; IDT save/restore taken from OS/2 mode switching code...
;
; Now preserve three words of the vector table at 03FAh
; so we can restore it after the mode switch. The ROM
; trashes the top three words of the real mode IDT by
; putting a stack at 30:100.
push ds
pop es
push SEL_RMIVT or STD_RING ;address real mode IDT
pop ds
assume ds:NOTHING,es:DGROUP
MOV SI,03FAH ;BIOS will pop regs, so
MOV DI,OFFSET DGROUP:IDTSaveArea ; we don't need to save
CLD ; them here
MOVSW
MOVSW
MOVSW
push es
pop ds
assume ds:DGROUP
; Write the shutdown type code into the CMOS ram. Code 9 causes the BIOS
; to load SS:SP from IO_ROM_SEG:IO_ROM_INIT, restore the registers and then
; do an IRET. Code 0Ah loads a CS:IP and jumps to it.
mov al,SHUT_DOWN
out CMOS_ADDR,al
mov al,9
cmp [FasterModeSwitch],0
je enrm33
mov al,0ah
enrm33:
out CMOS_DATA,al
; Shut out all interrupts while we are resetting the processor.
in al,INTA01
mov bIntMask,al
mov al,0FFh
out INTA01,al
; Now, force a reset by causing a triple fault. We do this via a far call
; to a call gate. The ring 0 procedure (Reset286) then loads the IDT
; register with a bogus IDT and does an INT 3. This causes the infamous
; "triple fault" which resets the processor.
db 9Ah ;call far SEL_RESET:0
dw 0,SEL_RESET or STD_RING
; After the BIOS has finished its reset processing, control will come back
; to here in real mode. We will be using the same stack as before, all
; regular registers have been preserved, and DS and ES contain the real mode
; address of DXDATA.
enrm45:
FCLI
IFDEF ROM
%OUT What stack is in use here?
GetRMDataSeg
mov ss,ax
ELSE
mov ss,segDXData ;restore our stack
ENDIF
mov sp,ss:ShutDownSP
pop es ;restore registers from stack
pop ds
popa
; Reset processing is almost finished. We are using the same stack as
; before, all regular registers have been preserved, and DS and ES
; contain the real mode seg of DXDATA.
assume ds:DGROUP,es:DGROUP,SS:DGROUP
enrm50:
FCLI
if DEBUG
lgdt bpGDT ;We need to do this so that DEB386 can still
; dump things after we switch to real mode.
;When we went through the reset for '286 we
; have trashed the GDTR in the cpu.
endif
; Restore trashed interrupt vector area
xor ax,ax ;address real mode IDT
mov es,ax
push si ;restore the three IDT words
push di
mov si,offset DGROUP:IDTSaveArea
mov di,03FAh
cld
movsw
movsw
movsw
pop di
pop si
push ds ;resotre es -> DXDATA
pop es
; Restore the state of the interrupt mask register
mov al,bIntMask
out INTA01,al
cmp [FasterModeSwitch],0
jne enrm51
; Back in real mode, 'local disable' the A20 line via HIMEM. We only do
; this on a 286 'cause the BIOS has already turned off A20. The 'local
; disable' lets the XMS driver know A20 is off, and possibly causes it to
; actually enable A20 if someone loaded prior to DOSX (like a TSR) wanted
; A20 on.
push bx
xmssvc 6
dec A20EnableCount
pop bx
jmp enrm70
enrm51:
; Enable NMIs
mov al,0Dh
out CMOS_ADDR,al
; This is common exit code that is performed for both '386 and '286
; processors.
else ; WOW_x86
push SegDxCode
push offset DXCODE:enrmwow
push SegDxData
push sp
push SegDxData
.386p
FBOP BOP_SWITCHTOREALMODE,,FastBop
.286p
enrmwow: add sp,6 ; remove rest of parameters
push ds
pop es ; es not set by mode switch
endif ; WOW_x86
enrm70:
push es ;clear DMA services required
mov ax,DMAServiceSegment ; bit for real mode
mov es,ax
and byte ptr es:[DMAServiceByte],not DMAServiceBit
pop es
IFNDEF ROM
mov ax,segDXData
mov selDgroup,ax
ENDIF
ret
EnterRealMode endp
; -------------------------------------------------------
; Reset286 -- This procedure is called via a gate as
; part of the switch to real mode. Most of
; the DOS Extender runs in the child application's
; ring. The lidt instruction has to execute in ring 0.
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public Reset286
Reset286 proc far
ifndef KBD_RESET ;----------------------------------------
lidt bpBogusIDT ;Load up IDTR with 0 length IDT
int 3 ;Interrupt thru interrupt table with 0 length
;Causes "triple fault" which resets the 286
else ;----------------------------------------
call Sync8042
mov al,0feh ; Send 0feh - system reset command
out 64h,al
@@:
hlt
jmp short @b
endif ;----------------------------------------
Reset286 endp
ifdef KBD_RESET ;----------------------------------------
Sync8042 proc near
xor cx,cx
S8InSync:
in al,64h
and al,2
loopnz S8InSync
ret
Sync8042 endp
endif ;----------------------------------------
; -------------------------------------------------------
; Reset386 -- This procedure is called via a gate as
; part of the switch to real mode. Most of
; the DOS Extender runs in ring 1. This
; routine has a few ring 0 instructions.
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public Reset386
Reset386 proc far
.386p
push dx
push eax ;save high word of eax
mov dx,segDXData ;get the real mode data segment
mov eax,cr0 ;turn off protected mode bit
and al,0FEh
lidt fword ptr bpRmIVT ;Load real-mode IDT
mov cr0,eax
IFDEF ROM ;--------------------------------------------------------
extrn segRomDXCODE:ABS
db 0EAh ;jmp far to purge prefetch queue
dw r386_10
public lCodeSegLoc
lCodeSegLoc label word
dw segRomDXCODE
ELSE ;ROM ---------------------------------------------------------
; Note, the following far jmp has special dispensation to use a SEG DXCODE
; operand which would normally require a fixup. DOSX used to relocate its
; code for protected mode and plug the segment value in at lCodeSegLoc. It
; doesn't perform the relocation any longer, but debug only code still
; checks for fix ups that might require fix ups. It knows lCodeSegLoc is ok.
db 0EAh ;jmp far to purge prefetch queue
dw r386_10
public lCodeSegLoc
lCodeSegLoc label word
dw seg DXCODE
ENDIF ;ROM ---------------------------------------------------------
r386_10:
mov ss,dx ;real mode data segment address
mov ds,dx
mov es,dx
pop eax
pop dx
.286p
; We entered this routine via a far call, even though it was from code in
; the same segment. However, the CS value on the stack is a protected
; mode selector, not a real mode segment. Do a near return, the caller
; will pop off the PM CS value.
retn ;NOTE: near return even though far call!
Reset386 endp
if VCPI ;----------------------------------------------------------------
; -------------------------------------------------------
; ResetVCPI -- This procedure is called via a gate as
; part of the switch to real (V86) mode when
; running under a VCPI server. Most of
; the DOS Extender runs in the user code ring. This
; routine runs in ring 0.
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public ResetVCPI
ResetVCPI proc far
.386p
push eax ;save eax & edx for after switch
push edx
IFDEF ROM
GetPMDataSeg
movzx edx, ax
ELSE
movzx edx,segDXData ;real mode DGROUP segment
ENDIF
mov eax, esp ;save esp for stack frame of dosext
push 0 ;gs begin frame for VCPI
push 0 ;gs
push 0 ;fs
push 0 ;fs
push edx ;ds
push edx ;es
push edx ;ss
push eax ;esp
push eax ;eflags reserved
push 0 ;cs
push cs:[lCodeSegLoc] ;cs
push 0 ;eip (high)
push offset DXCODE:retVCPI ;eip (low)
mov ax, SEL_VCPIALLMEM ;setup ds to be
mov ds, ax ; selector for all memory
mov ax, vcpiSWITCHTOV86
call [fnVCPI] ;call the VCPI server
; Return point for pMode to V86 switch. The VCPI server sets up
; segment registers & stack pointer.
retVCPI:
pop edx ;restore eax & edx
pop eax
.286p
; We entered this routine via a far call, even though it was from code in
; the same segment. However, the CS value on the stack is a protected
; mode selector, not a real mode segment. Do a near return, the caller
; will pop off the PM CS value.
retn ;NOTE: near return even though far call!
ResetVCPI endp
; -------------------------------------------------------
; CallVCPI -- This procedure is invoked via a call gate in
; order to call the VCPI protected mode entry
; point in ring 0. Most of the DOS Extender
; runs in the user code ring, this routine runs in ring 0.
;
; Note: This routine requires DS to point to the DOSX
; DGROUP, not a parameter to VCPI!
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public CallVCPI
CallVCPI proc far
.386p
call [fnVCPI] ;call the VCPI server
.286p
ret
CallVCPI endp
endif ;VCPI ---------------------------------------------------------
public RmUnsimulateProc
RmUnsimulateProc proc far
BOP BOP_UNSIMULATE
RmUnsimulateProc endp
; -------------------------------------------------------
DXCODE ends
DXPMCODE segment
assume cs:DXPMCODE
; -------------------------------------------------------
; RAW MODE SWITCH ROUTINES
; -------------------------------------------------------
; ------------------------------------------------------
; PmRawModeSwitch -- This routine performs a raw mode switch from
; protected mode to real mode. NOTE: applications will JUMP at this
; routine
;
; Input: ax - new DS
; cx - new ES
; dx - new SS
; bx - new sp
; si - new CS
; di - new ip
; Output: DS, ES, SS, sp, CS, ip contain new values
; Errors: none
; Uses:
;
;
;
assume ds:nothing, ss:nothing, es:nothing
public PmRawModeSwitch
PmRawModeSwitch proc far
push ss
pop ds
push bx
.386p
mov bx,ss
movzx ebx,bx
lar ebx,ebx
test ebx,(AB_BIG SHL 8)
mov ebx,esp
jnz prms10
movzx ebx,bx
prms10:
.286p
; Switch to dosx stack (since switch to real mode will do that to us anyway
; NOTE: no-one can call EnterIntHandler or ExitIntHandler until we switch to
; the user's new stack. If they do, they will use the area we stored
; the parameters for this call for a stack frame
rpushf
FCLI
push SEL_DXDATA OR STD_RING
pop ss
assume ss:DGROUP
.386p
movzx esp,word ptr pbReflStack
.286p
; Save user registers
push dx ; ss
.386p
push word ptr [ebx]
push word ptr [ebx - 2]; flags pushed before cli
.286p
push si ; cs
push di ; ip
push ax ; ds
push cx ; es
; switch modes
mov ax,SEL_DXDATA OR STD_RING
mov ds,ax
SwitchToRealMode
; set the registers, switch stacks, and return to the user
pop es
pop ds
pop ax ; ip
pop bx ; cs
pop cx ; flags
pop si ; sp
pop ss
assume ss:nothing
mov sp,si
push cx
popf
push bx
push ax
ret
PmRawModeSwitch endp
; NOTE: this is now the DXCODE segment, NOT the DXPMCODE segment (courtesy
; of SwitchToRealMode
; ------------------------------------------------------
; RmRawModeSwitch -- This routine performs a raw mode switch from
; protected mode to real mode. NOTE: applications will JUMP at this
; routine
;
; Input: ax - new DS
; cx - new ES
; dx - new SS
; bx - new sp
; si - new CS
; di - new ip
; Output: DS, ES, SS, sp, CS, ip contain new values
; Errors: none
; Uses:
;
;
;
assume ds:nothing, ss:nothing, es:nothing
public RmRawModeSwitch
RmRawModeSwitch proc far
push ss
pop ds
push bx
mov bx,sp
; Switch to dosx stack (since switch to real mode will do that to us anyway
; NOTE: no-one can call EnterIntHandler or ExitIntHandler until we switch to
; the user's new stack. If they do, they will use the area we stored
; the parameters for this call for a stack frame
pushf
FCLI
push segDxData
pop ss
assume ss:DGROUP
mov sp,pbReflStack
; Save user registers
push dx ; ss
push word ptr [bx] ; sp
push word ptr [bx - 2] ; flags from before cli
push si ; cs
push di ; ip
push ax ; ds
push cx ; es
; switch modes
mov ax,segDxData
mov ds,ax
SwitchToProtectedMode
; set the registers, switch stacks, and return to the user
pop es
pop ds
.386p
test DpmiFlags,DPMI_32BIT
jnz rrms10
xor eax,eax ; clear high 16 bits
xor edi,edi ; clear high 16 bits
.286p
rrms10: pop di ; ip
pop ax ; cs
pop cx ; flags from before cli
pop bx ; sp
assume ss:nothing
pop ss
.386p
mov esp,ebx
.286p
push cx
rpopf
.386p
push eax
push edi
db 066h
retf
.286p
RmRawModeSwitch endp
DXPMCODE ENDS
DXCODE SEGMENT
; -------------------------------------------------------
; STATE SAVE/RESTORE ROUTINES
; -------------------------------------------------------
; -------------------------------------------------------
; RmSaveRestoreState -- This routine exists as a placeholder. It
; is not currently necessary to perform any state saving/restoring
; for raw mode switch. The DPMI spec states that the user can call
; this routine with no adverse effect.
;
; Input: none
; Output: none
; Errors: none
; Uses: none
;
assume ds:nothing, ss:nothing, es:nothing
public RmSaveRestoreState
RmSaveRestoreState proc far
ret
RmSaveRestoreState endp
DXCODE ends
; -------------------------------------------------------
DXPMCODE segment
assume cs:DXPMCODE
; -------------------------------------------------------
; RmSaveRestoreState -- This routine exists as a placeholder. It
; is not currently necessary to perform any state saving/restoring
; for raw mode switch. The DPMI spec states that the user can call
; this routine with no adverse effect.
;
; Input: none
; Output: none
; Errors: none
; Uses: none
;
assume ds:DGROUP, ss:nothing, es:nothing
public PmSaveRestoreState
PmSaveRestoreState proc far
push ax
push ds
mov ax, SEL_DXDATA or STD_RING
mov ds, ax
test DpmiFlags,DPMI_32BIT
pop ds
pop ax
jnz short @f ; 32-bit return
ret
@@:
db 66h
ret
PmSaveRestoreState endp
; -------------------------------------------------------
; GetIntrVector -- This routine will return the interrupt
; vector from the Interrupt Descriptor Table for the
; specified interrupt.
;
; Input: AX - interrupt number of interrupt to return
; Output: CX - selector of the interrupt service routine
; DX - offset of the interrupt service routine
; Errors: none
; Uses: CX, DX modified, all else preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public GetIntrVector
GetIntrVector proc near
push si
mov si,ax
push es
shl si,3 ;otherwise it's in the IDT
mov es,selIDT
test DpmiFlags,DPMI_32BIT
je giv20
; Get upper 16 bits of ip
.386p
mov dx,es:[si + 6]
shl edx,16
.286p
giv20: mov dx,es:[si].offDest
mov cx,es:[si].selDest
pop es
pop si
return
GetIntrVector endp
; -------------------------------------------------------
; PutIntrVector -- This routine will place the specified
; interrupt vector address into the Interrupt Descriptor
; Table entry for the specified interrupt.
;
; Input: AX - interrupt number
; CX - selector of interrupt routine
; DX - offset of interrupt routine
; Output: none
; Errors: none
; Uses: all registers preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public PutIntrVector
PutIntrVector proc near
.386p
push di
push ax
push ebx
push edx
test DpmiFlags,DPMI_32BIT
jz piv10
mov ebx,VDM_INT_32
jmp piv20
piv10: mov ebx,VDM_INT_16
movzx edx,dx
piv20: mov di,ax
push es
shl di,3 ;otherwise it goes directly in the IDT
mov es,selIDT
FCLI
; Put upper 16 bits of ip
push edx
shr edx,16
mov es:[di + 6],dx
pop edx
mov es:[di].offDest,dx
mov es:[di].selDest,cx
FSTI
pop es
; If setting the Critical Error Handler, store the routine's address for
; easy access later.
cmp al,24h
jnz piv23
mov word ptr PMInt24Handler+4,cx
mov dword ptr PMInt24Handler,edx
;
; set the handler in the actual "ivt", so it will get called on interrupts
;
piv23:
cmp ax,8
jb piv30
cmp ax,0fh
jbe piv24
cmp ax,70h
jb piv30
cmp ax,77h
ja piv30
piv24:
;
; Hardware interrupts get interrupt gates
;
or ebx,VDM_INT_INT_GATE
jmp piv40
;
; Everyone else gets trap gates
;
piv30: or ebx,VDM_INT_TRAP_GATE
piv40: push bx
push ax
push cx
push edx
DPMIBOP SetProtectedModeInterrupt
add sp,10
piv90:
pop edx
pop ebx
pop ax
pop di
ret
PutIntrVector endp
.286p
; -------------------------------------------------------
; GetFaultVector -- This routine will return the fault
; handler address from the fault handler vector.
;
; Input: AX - fault number of fault handler to return
; Output: CX - selector of the fault handler routine
; DX - offset of the fault handler routine
; Errors: none
; Uses: CX, DX modified, all else preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public GetFaultVector
GetFaultVector proc near
push si
mov si,ax ;if the Int # is below CRESERVED
cmp ax,CRESERVED ; then we do it
jnb @f
shl si,3
.386p
mov edx,dword ptr PMFaultVector[si]
.286p
mov cx,word ptr PMFaultVector[si+4]
@@:
pop si
return
GetFaultVector endp
; -------------------------------------------------------
; PutFaultVector -- This routine will place the specified
; fault handler address into the fault handler vector.
;
; Input: AX - fault number
; CX - selector of fault handler routine
; DX - offset of fault handler routine
; Output: none
; Errors: none
; Uses: all registers preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public PutFaultVector
PutFaultVector proc near
push di
mov di,ax ;if the fault # is below CRESERVED
cmp ax,CRESERVED ; then we do it
jnb short pfv_exit
shl di,3
.386p
mov dword ptr PMFaultVector[di],edx
.286p
mov word ptr PMFaultVector[di+4],cx
.386p
push ebx
mov ebx,VDM_INT_16
test DpmiFlags,DPMI_32BIT
jz short @f
mov ebx,VDM_INT_32
@@:
or ebx, VDM_INT_INT_GATE
push ebx
push ax
push cx
push edx
push 0
push 0
push 0
DPMIBOP SetFaultHandler
add sp,18
pop ebx
.286p
pfv_exit:
pop di
ret
PutFaultVector endp
; -------------------------------------------------------
; GTPARA -- This routine will return the real mode paragraph
; address of the specified protected mode memory segment.
;
; Input: SI - selector of the segment
; Output: returns ZR true if segment is in lower 1MB range
; AX - real mode paragraph address
; returns ZR false if segment is in extended memory
; Errors: returns CY true if selector isn't valid
; Uses: AX modified, all else preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public gtpara
gtpara:
push cx
push es
push si
push bx
mov bx,selGDT ;selector for the GDT segment
mov es,bx
lsl bx,bx
and bx,SELECTOR_INDEX
and si,SELECTOR_INDEX
cmp si,bx ;check the given selector against
; the GDT segment limit
pop bx
jc gtpr20
; The given selector is beyond the end of the GDT, so return error.
or sp,sp
stc
Debug_Out "gtpara: invalid selector (#si)"
jmp short gtpr90
; The selector specified is inside the range of defined descriptors in
; the GDT. Get the address from the descriptor.
gtpr20: mov cl,es:[si].adrBaseHigh
test cl,0F0h
jnz gtpr90
shl cl,4
mov ax,es:[si].adrBaseLow
if DEBUG ;------------------------------------------------------------
test al,0Fh
jz @f
Debug_Out "gtpara: segment not on para boundry, sel #si at #cl#ax"
@@:
endif ;DEBUG --------------------------------------------------------
shr ax,4
or ah,cl
cmp ax,ax
;
gtpr90:
pop si
pop es
pop cx
ret
; -------------------------------------------------------
; SelOff2SegOff -- This routine will return will translate a
; protected mode selector:offset address to the corresponding
; real mode segment:offset address.
;
; Input: AX - PM selector
; DX - PM offset
; Output: if Z set:
; AX - RM segment
; DX - RM offset
; if NZ set, address is not in conventional memory, and
; cannot be translated
;
; Errors: none
; Uses: AX, DX; all else preserved
;
; Note: This routine is very similar to gtpara, and could replace it!
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public SelOff2SegOff
SelOff2SegOff proc near
push bx
push cx
push dx
call GetSegmentAddress ;bx:dx = lma of segment
pop cx ;cx = offset
test bl,0f0h ;above 1 Meg line?
jnz @f ; yes, cut out now
add dx,cx
adc bx,0 ;bx:dx = lma of segment:offset
call Lma2SegOff ;bx:dx = seg:off
mov ax,bx ;dx:ax = seg:off
cmp ax,ax ;under 1 Meg, set Z flag
@@:
pop cx
pop bx
ret
SelOff2SegOff endp
; ------------------------------------------------------
; Lma2SegOff -- This routine converts a linear memory address
; in BX:DX to a normalized SEG:OFF in BX:DX.
;
; Input: BX:DX = lma
; Output: BX:DX = normalized SEG:OFF
; Uses: none
public Lma2SegOff
Lma2SegOff proc near
push dx
shl bx,12
shr dx,4
or bx,dx
pop dx
and dx,0fh
ret
Lma2SegOff endp
; -------------------------------------------------------
; GetSegmentAddress -- This routine will return with
; the linear address of the specified segment.
;
; Input: AX - segment selector
; Output: DX - low word of segment address
; BX - high word of segment address
; Errors: none
; Uses: BX, DX, all else preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public GetSegmentAddress
GetSegmentAddress:
push es
push di
mov es,selGDT
mov di,ax
and di,SELECTOR_INDEX
mov dx,es:[di].adrBaseLow
mov bl,es:[di].adrBaseHigh
mov bh,es:[di].adrbBaseHi386
pop di
pop es
ret
; -------------------------------------------------------
; SetSegmentAddress -- This routine will modify the
; segment base address of the specified segment.
;
; Input: AX - segment selector
; Output: DX - low word of segment address
; BX - high word of segment address
; Errors: None
; Uses: All registers preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public SetSegmentAddress
SetSegmentAddress:
push si
push es
mov es,selGDT
mov si,ax
and si,SELECTOR_INDEX
mov es:[si].adrBaseLow,dx
mov es:[si].adrBaseHigh,bl
mov es:[si].adrbBaseHi386,bh
push ax
push bx
push cx
mov ax,si
mov cx,1
mov bx,si
.386p
FBOP BOP_DPMI,<SetDescriptorTableEntries>,FastBop
.286p
pop cx
pop bx
pop ax
pop es
pop si
ret
; -------------------------------------------------------
; NSetSegmentAccess -- This routine will modify the
; access rights byte of a specified segment.
;
; Input: Selector - segment selector
; Access - Access rights byte value
; Output: none
; Errors: Carry set, AX = error code
; Uses: All registers preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
cProc NSetSegmentAccess,<PUBLIC,NEAR>,<es,si>
parmW Selector
parmW Access
cBegin
mov es,selGDT
mov si,Selector
and si,SELECTOR_INDEX
mov ax,Access
mov es:[si].arbSegAccess,al ; Set access byte.
and ah,0F0h ; Mask off reserved bits.
and es:[si].cbLimitHi386,0fh ; Clear old extended bits.
or es:[si].cbLimitHi386,ah ; Set new extended bits.
IFDEF WOW_x86
push ax
push bx
push cx
mov ax,si
mov cx,1
mov bx,si
.386p
FBOP BOP_DPMI,<SetDescriptorTableEntries>,FastBop
.286p
pop cx
pop bx
pop ax
ENDIF
cEnd
; -------------------------------------------------------
; NSetSegmentLimit -- This routine will modify the
; limit of a specified segment.
;
; Input: Selector - segment selector
; Limit - Limit value
; Output: none
; Errors: Carry set, AX = error code
; Uses: All registers preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
cProc NSetSegmentLimit,<PUBLIC,NEAR>,<es,si>
parmW Selector
cBegin
mov es,selGDT
mov si,Selector
and si,SELECTOR_INDEX
push ax
push bx
push cx
mov ax,si
mov cx,1
mov bx,si
.386p
FBOP BOP_DPMI,<SetDescriptorTableEntries>,FastBop
.286p
pop cx
pop bx
pop ax
cEnd
; -------------------------------------------------------
; NSetSegmentBase -- This routine will modify the
; base address of a specified segment.
;
; Input: Selector - segment selector
; Base - base address
; Output: none
; Errors: Carry set, AX = error code
; Uses: All registers preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
cProc NSetSegmentBase,<PUBLIC,NEAR>,<es,si>
parmW Selector
parmD Base
cBegin
mov es,selGDT
mov si,Selector
and si,SELECTOR_INDEX
mov ax,word ptr Base
mov es:[si].adrBaseLow,ax
mov ax,word ptr Base + 2
mov es:[si].adrbBaseMid386,al
mov es:[si].adrbBaseHi386,ah
clc
push ax
push bx
push cx
mov ax,si
mov cx,1
mov bx,si
.386p
FBOP BOP_DPMI,<SetDescriptorTableEntries>,FastBop
.286p
pop cx
pop bx
pop ax
cEnd
; -------------------------------------------------------
; NMoveDescriptor -- This routine copy a descriptor from
; the source address to the destination address. This
; can be to or from the descriptor table. If it copied
; to the descriptor table, the system will be notified as
; appropriate.
;
; Input: Source -- address of source descriptor
; Dest -- address of destination descriptor
; Output: none
; Errors: Carry set, AX = error code
; Uses: All registers preserved
assume ds:NOTHING,es:NOTHING,ss:NOTHING
.386p
cProc NMoveDescriptor,<PUBLIC,NEAR>,<es,esi,ds,edi,ebx,ecx>
ParmW SourceSel
ParmD SourceOff
ParmW DestSel
ParmD DestOff
cBegin
.386p
xor ecx,ecx
mov cx,SourceSel
mov ds,cx
mov esi,SourceOff
mov cx,DestSel
mov es,cx
mov edi,DestOff
mov cx,8
rep movs byte ptr [esi], byte ptr [edi]
mov ds,selDgroupPM
assume ds:DGROUP
mov cx,es
cmp cx,ds:selGdt
jne nm20
mov cx,1
mov ebx,DestOff
mov ax,bx
FBOP BOP_DPMI,<SetDescriptorTableEntries>,FastBop
.386p
nm20:
cEnd
.286p
; -------------------------------------------------------
; NWOWSetDescriptor --
; The Descriptors are set on the system side.
;
; Input: Source -- address of source descriptors
; Count -- count of descriptors to set
; Output: none
; Errors: Carry set, AX = error code
; Uses: All registers preserved
assume ds:NOTHING,es:NOTHING,ss:NOTHING
cProc NWOWSetDescriptor,<PUBLIC,NEAR>,<es,ds,bx,cx>
ParmW Count
ParmD Source
cBegin
les bx, Source
mov cx, Count
mov ax, bx
mov ds,selDgroupPM
assume ds:DGROUP
.386p
FBOP BOP_DPMI,<SetDescriptorTableEntries>,FastBop
.286p
cEnd
; -------------------------------------------------------
; ParaToLDTSelector -- This routine will convert a segment
; address relative to the start of the exe file into the
; corresponding selector for the segment. It searches the
; LDT to see if a segment is already defined at that address.
; If so, its selector is returned. If not, a new segment
; descriptor will be defined.
;
; Note: The LDT and GDT are currently one and the same.
;
; Input: AX - paragraph aaddress of real mode segment
; BX - access rights byte for the segment
; Output: AX - selector for the segment
; Errors: returns CY set if unable to define a new segment
; Uses: AX, all other registers preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public ParaToLDTSelector
ParaToLDTSelector proc near
push bx
push cx
push dx
; Convert the paragraph address to a linear address and see if there
; is a segment defined at that address.
mov dx,ax
call FindLowSelector
jnc @f ;if so, we don't need to make one
; This segment isn't defined, so we need to create a descriptor for it.
mov ax,dx
call MakeLowSegment
if DEBUG ;------------------------------------------------------------
jnc ptos80
Debug_Out "ParaToLDTSelector: can't make selector!"
ptos80:
endif ;DEBUG --------------------------------------------------------
jc ptos90
@@: or al,SELECTOR_TI or STD_RING ;look like LDT selector
; All done
ptos90: pop dx
pop cx
pop bx
ret
ParaToLDTSelector endp
public FarParaToLDTSelector
FarParaToLDTSelector proc far
call ParaToLDTSelector
ret
FarParaToLDTSelector endp
; -------------------------------------------------------
page
; -------------------------------------------------------
; DESCRIPTOR TABLE MANIPULATION ROUTINES
; -------------------------------------------------------
;
; AllocateLDTSelector -- This function will obtain the
; next free descriptor in the local descriptor table.
;
; Note: Currently the LDT and GDT are in the same table!
;
; Note: The function InitGlobalDscrTable must have been
; called before calling this function.
;
; Input: none
; Output: AX - selector if one is available
; Errors: CY clear if successful, AX=0 and CY set if not free selectors
; Uses: AX, all else preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public AllocateLDTSelector
AllocateLDTSelector proc near
call AllocateSelector ;get a GDT selector
jc @f
or al,SELECTOR_TI or STD_RING ;say it's in the LDT
@@: ret
AllocateLDTSelector endp
; -------------------------------------------------------
; AllocateSelector -- This function will obtain the
; next free descriptor in the global descriptor table.
; The descriptors in the GDT are stored on a linked list
; of free descriptors. The cbLimit field of the descriptor
; is used as the link to the next element of the list. In
; addition, free descriptors have the access rights byte
; set to 0.
;
; Note: The function InitGlobalDscrTable must have been
; called before calling this function.
;
; Input: none
; Output: AX - selector if one is available
; Errors: CY clear if successful, AX=0 and CY set if not free selectors
; Uses: AX, all else preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public AllocateSelector
AllocateSelector proc near
; Get the next free descriptor on the list.
mov ax,selGDTFree ;get head of free list
cmp ax,-1 ;is the list empty?
jnz alsl20 ;if so, report error.
stc ;set error flag
Debug_Out "AllocateSelector: out of selectors!"
jmp short alsl90 ;and get out
; We now need to update the new head of list to point to the
; following one on the list.
; Whenever we allocate a descriptor, the access rights byte is set
; to 0Fh. This marks it as a '386 task gate, which is not legal to
; have in the GDT. We need to stick something in this byte, because
; having the access rights byte be 0 means that it is free, which is
; no longer the case.
alsl20:
if DEBUG ;------------------------------------------------------------
test al,not SELECTOR_INDEX
jz @f
Debug_Out "AllocateSelector: selGDTFree invalid! #AX"
and al,SELECTOR_INDEX
@@:
endif ;DEBUG --------------------------------------------------------
push bx
push es
mov bx,ax ;BX points to the descriptor
mov es,selGDT ;ES points to the GDT segment
push es:[bx].cbLimit ;push the link field of the descriptor
pop selGDTFree ;make it be the new head of the list
mov es:[bx].adrbBaseHi386,0
mov es:[bx].arbSegAccess,0Fh
pop es
pop bx
or al,SELECTOR_TI
;
; Remember the highest selector alloced, so we can reinit the ldt
;
cmp ax,HighestSel
jbe alsl50
mov HighestSel,ax
alsl50: clc
;
; All done
alsl90: ret
AllocateSelector endp
; -------------------------------------------------------
; AllocateSelectorBlock -- This function will allocate
; a set of contiguous descriptors from the global
; descriptor table. It will search the GDT from the
; beginning looking for a sufficiently large contiguous
; set of descriptors. When if finds a set, it will
; then remove each one from the free descriptor list.
;
; Input: AX - Number of selectors needed
; CX - non-zero if allocation should be started from higher range
; first.
; Output: AX - starting selector of the block
; Errors: return CY set if error occurs
; Uses: AX modified, all other registers preserved
; modifies global descriptor table free list
;
; *******************************************************
; The strategy of allocating selectors has been modified to
; give preference to selector values above 1000h. This is an
; attempt to emulate typical values that are returned by win31.
; -neilsa
;
; Some DPMI DOS applications demand that all selectors(no matter it comes
; from AllocateLDTSelector or this function) be contiguous, so
; the strategy for WOW doesn't work for DPMI DOS applications.
; For this reason, a new parameter is added so the caller can control
; where to start searching for free selectors.
; -williamh
;
SEL_START_HI equ 1000h
.erre SEL_START_HI GE SEL_USER ;make sure SEL_START_HI really
;is larger than SEL_USER
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public AllocateSelectorBlock
AllocateSelectorBlock proc near
push cx
push dx
push si
push di
push es
mov es,selGDT
mov dx,ax ;remember descriptor count in DX
lsl di,selGDT ;stop search at table limit
; Start at the first user selector and search until we find a
; sufficiently large block of contiguous selectors.
mov si,SEL_USER
jcxz alsb10
mov si, SEL_START_HI ;no branch on WOW, so it is faster
alsb10:
push cx ; save the starting selector indicator
alsb20:
mov ax,si ;remember the starting one
mov cx,dx ;number of descriptors to check
if 0
alsb30: cmp es:[si].arbSegAccess,0 ;is this one free
else
alsb30: cmp word ptr es:[si].arbSegAccess,0 ;is this one free
endif
jnz alsb40 ;if not, we have to continue looking
add si,8 ;bump to next descriptor
cmp si,di ;check if at end of table
jae alsb80 ;if so, get out with error
loop alsb30 ;repeat for the number of selectors requested
jmp short alsb50;we found the block
; This one wasn't free, try the next one
alsb40: add si,8 ;bump to next descriptor
cmp si,di ;are we at the end of the table.
jc alsb20 ;and repeat if not
jmp short alsb80;we didn't find it, so report error
; AX has the starting selector of the block of descriptors. We need
; to remove each of them from the free list.
alsb50: push ax ;remember the starting point
mov cx,dx ;get descriptor count
mov dx,ax ;remember current selector number
alsb52: mov ax,dx
call RemoveFreeDescriptor
add dx,8
loop alsb52
;
; Remember the highest number
;
or dl,SELECTOR_TI
cmp dx,HighestSel
jbe alsb60
mov HighestSel,dx
alsb60: pop ax ;restore starting selector
or al,SELECTOR_TI
add sp, 2 ; throw away starting selector indicator
clc
jmp short alsb90
; Couldn't find them, so return error.
alsb80:
pop cx ; get back starting selector indicator
jcxz alsb81 ; we started at SEL_USER, no more search
cmp si, SEL_START_HI+SEL_USER+8 ;is this really the end?
jbe alsb81 ;yes, now failure
mov di, SEL_START_HI+SEL_USER ;new end location
mov si,SEL_USER ;restart low
jmp alsb10 ;try again
alsb81:
stc
Debug_Out "AllocSelectorBlock: Failed!"
alsb90: pop es
pop di
pop si
pop dx
pop cx
ret
AllocateSelectorBlock endp
; -------------------------------------------------------
; RemoveFreeDescriptor -- This routine will remove the
; specified descriptor from the free descriptor list
; of the GDT.
;
; Input: AX - selector of the descriptor to remove
; ES - segment address of GDT
; Output: none
; Errors: none
; Uses: AX used, all other registers preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public RemoveFreeDescriptor
RemoveFreeDescriptor proc near
push si
push di
and ax,SELECTOR_INDEX ;clear table/pl bits
; Check that the segment is really free.
mov si,ax ;Segment index
if 0
cmp es:[si].arbSegAccess,0 ; Should be 0 if free
else
cmp word ptr es:[si].arbSegAccess,0 ; Should be 0 if free
endif
jnz rmfd80 ;Error if segment is not free!
;
xor di,di
mov si,selGDTFree ;start at the head of the list.
; Look for a selector matching the one to free
rmfd20: cmp si,-1 ;check for end of list.
jz rmfd90 ;and get out if so
cmp ax,si ;is this the one we are looking for
jz rmfd40
mov di,si
mov si,es:[si].cbLimit ;point SI at next one in the list
jmp rmfd20 ;and repeat
; We found the one we want, so now remove it from the list.
rmfd40: mov es:[si].adrbBaseHi386,0
mov es:[si].arbSegAccess,0Fh
mov ax,es:[si].cbLimit
or di,di ;is it the head of the list
jz rmfd50
; The one we have isn't the head of the list, so make the previous
; list element point at the one beyond the one being removed.
mov es:[di].cbLimit,ax
jmp short rmfd90
; The one we have is the head of the list. Make the head of the list
; point to the one following the one being removed.
rmfd50:
mov selGDTFree,ax
jmp short rmfd90
rmfd80: stc ;Flag allocation error
Debug_Out "RemoveFreeDescriptor: Failed!"
rmfd90: pop di
pop si
ret
RemoveFreeDescriptor endp
; -------------------------------------------------------
; IsSelectorFree -- This routine determines if the specified
; selector is on the free list. It is used to prevent
; apps from corrupting the free list by doing things like
; set selector on a descriptor in the free list.
;
; Input: BX - Descriptor index
; Output: CY - set if decriptor is not free
; clear otherwise
;
assume ds:dgroup,es:nothing,ss:nothing
public IsSelectorFree
IsSelectorFree proc near
push es
push si
mov si,selGdt
mov es,si
test byte ptr es:[bx].adrbBaseHi386,080h
jnz isf30
stc
jmp isf40
isf30: clc
isf40: pop si
pop es
ret
IsSelectorFree endp
; -------------------------------------------------------
; FreeSelector -- This routine will mark the segment
; descriptor for the specified selector as free. This
; is used to release a temporary selector when no longer
; needed. The descriptor is marked as free by setting the
; access rights byte to 0 and placing it on the free list.
;
; Note: This routine can only be called in protected mode.
;
; Input: AX - selector to free
; Output: none
; Errors: CY clear if no error, set if selector is invalid
; Uses: AX used, all other registers preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public FreeSelector
FreeSelector proc near
push bx
push es
; Check for this being a valid selector.
cmp ax,SEL_USER ;make sure it is a user selector
jb frsl80
mov bx,SEL_LDT_ALIAS
mov es,selGDT
mov bx,es
lsl bx,bx
cmp ax,bx ;make sure it is in the range of the table
jnc frsl80
; We have a legitimate selector, so set the access rights byte to 0, and
; place it at the head of the free list.
mov bx,ax
and bx,SELECTOR_INDEX ;clear unwanted bits
if 0
cmp es:[bx].arbSegAccess,0 ;already marked as free?
else
cmp word ptr es:[bx].arbSegAccess,0 ;already marked as free?
endif
jz frsl80 ; yes, don't free it again!
if 0
mov es:[bx].arbSegAccess,0
else
mov word ptr es:[bx].arbSegAccess,0
mov byte ptr es:[bx].adrbBaseHi386,080h
endif
mov ax,selGDTFree ;pointer to current head of list
mov es:[bx].cbLimit,ax ;store in link field of this dscr.
mov selGDTFree,bx ;make this one be the head of list.
if 0
BUGBUG fix this
IF 1 ; DaveHart was IFDEF WOW_x86, need it for MIPS too
int 3; debugbug
push ax
push bx
push cx
mov ax,bx
mov bx,offset NullSel
push ds
pop es
mov cx,1
IFDEF WOW_x86
.386p
FBOP BOP_DPMI,<SetDescriptorTableEntries>,FastBop
.286p
ELSE
DPMIBOP SetDescriptorTableEntries
ENDIF
pop cx
pop bx
pop ax
ENDIF
endif
clc
jmp short frsl90
; Bogus selector given. Return error.
frsl80: stc
Debug_Out "FreeSelector failed, #AX invalid or not used!?"
frsl90: pop es
pop bx
frsl99: ret
FreeSelector endp
; -------------------------------------------------------
; FreeSelectorBlock -- This routine will free the specified
; range of segment descriptors in the global descriptor
; table.
;
; Input: AX - starting selector in the range
; CX - number of selectors to free
; Output: none
; Errors: returns CY set if error occurs
; Uses: AX, all else preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public FreeSelectorBlock
FreeSelectorBlock proc near
jcxz frsb99
push cx
frsb20: push ax
call FreeSelector
pop ax
jc frsb90
add ax,8
loop frsb20
frsb90: pop cx
frsb99: ret
FreeSelectorBlock endp
; -------------------------------------------------------
; FindLowSelector -- This function will search the global
; descriptor table for a descriptor matching the given
; address.
;
; Input: AX - real mode paragraph to search for
; BX - access rights byte for the segment
; Output: AX - selector corresponding to input paragraph address
; Errors: returns CY set if specified descriptor not found
; Uses: AX, all else preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public FindLowSelector
FindLowSelector:
push bx
push dx
;
mov dx,ax
push bx
call ParaToLinear
pop ax
mov bh,al
call FindSelector
;
pop dx
pop bx
ret
; -------------------------------------------------------
; FindSelector -- This function will search the global
; descriptor table for a segment descriptor matching
; the specified linear byte address.
;
; Note that this routine cannot be used to find
; selectors pointing to addresses above 16 Megabytes.
; This is not really a problem, since the routine
; is used to find selectors in real mode DOS land
; most of the time.
;
; Input: DX - low word of linear byte address
; BL - high byte of linear address
; BH - access rights byte for the segment
; Output: AX - selector of corresponding segment
; Errors: returns CY set if specified descriptor not found
; Uses: AX used, all other registers preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public FindSelector
FindSelector proc near
push si
push di
push es
push cx
; Get segment limit of the GDT to use as a limit on the search.
lsl di,selGDT
mov es,selGDT
; Look for a descriptor matching the address in BL:AX
mov si,SEL_USER ;search starting here
if 0
fnds20: cmp es:[si].arbSegAccess,0
else
fnds20: cmp word ptr es:[si].arbSegAccess,0
endif
jz fnds28 ;skip if unused descriptor
cmp bl,es:[si].adrBaseHigh
jnz fnds28
cmp dx,es:[si].adrBaseLow
jnz fnds28
if 0
cmp es:[si].cbLimit,0
jz fnds28 ;skip if dscr has 0 limit
else
cmp es:[si].cbLimit,0ffffh
jnz fnds28 ;skip unless dscr has 64k limit
endif
mov cl,bh
xor cl,es:[si].arbSegAccess
and cl,NOT AB_ACCESSED
jz fnds90
fnds28: add si,8 ;bump to next descriptor
jc fnds80
cmp si,di ;check against end of GDT
jc fnds20 ;if still less, continue on.
; Hit the end of the GDT and didn't find one. So return error.
fnds80: stc
jmp short fnds99
; We found it, so return the selector
fnds90: mov ax,si
fnds99: pop cx
pop es
pop di
pop si
ret
FindSelector endp
; -------------------------------------------------------
; DupSegmentDscr -- This function will duplicate the specified
; segment descriptor into the specified destination descriptor. The
; end result is a second segment descriptor pointing to the same place
; in memory as the first.
;
; Input: AX - selector of segment descriptor to duplicate
; BX - selector of the segment descriptor to receive duplicate
; Output: none
; Errors: none
; Uses: All registers preserved. Modifies the segment
; descriptor for the specified segment. If this selector happens
; to be in a segment register when this routine is called, that
; segment register may end up pointing to the new location.
assume ds:DGROUP,es:NOTHING
public DupSegmentDscr
DupSegmentDscr proc near
push cx
push si
push di
push ds
push es
mov si,ax
mov di,bx
and si,SELECTOR_INDEX
and di,SELECTOR_INDEX
mov es,selGDT
mov ds,selGDT
assume ds:NOTHING
mov cx,4
cld
rep movs word ptr [di],word ptr [si]
pop es
pop ds
pop di
pop si
pop cx
ret
DupSegmentDscr endp
IFDEF ROM
; -------------------------------------------------------
DXPMCODE ends
; -------------------------------------------------------
DXCODE segment
assume cs:DXCODE
ENDIF
; -------------------------------------------------------
; NSetSegmentDscr -- This function will initialize the
; specified descriptor table entry with the specified data.
;
; This function can be called in real mode or protected mode.
;
; Input:
; Param1 - WORD segment selector
; Param2 - DWORD 32-bit segment base address
; Param3 - DWORD 32-bit segment limit
; param4 - WORD segment access/type
; Output: returns selector for the segment
; Errors: none
; Uses: Flags
assume ds:DGROUP,es:NOTHING,ss:NOTHING
cProc NSetSegmentDscr,<PUBLIC,FAR>,<es,di,ax,bx>
parmW Selector
parmD Base
parmD Limit
parmW Access
cBegin
mov es,selGDT
mov di,Selector
and di,SELECTOR_INDEX
mov ax,off_Base ; Set segment base
mov es:[di].adrBaseLow,ax
mov ax,seg_Base
mov es:[di].adrBaseHigh,al
mov es:[di].adrbBaseHi386,ah
mov ax,word ptr Access
and ax,070ffh ; clear 'G' bit and
; extended limit bits
mov word ptr es:[di].arbSegAccess,ax
; set access
mov ax,seg_Limit
mov bx,off_Limit ; AX:BX = segment limit
test ax,0fff0h ; big?
jz ssd_0 ; No
shr bx,12d ; Yes, make it page granular.
shl ax,4d
or bx,ax
mov ax,seg_Limit
shr ax,12d
or al,080h ; set 'G' bit
ssd_0:
or es:[di].cbLimitHi386,al ; set high limit
mov es:[di].cbLimit,bx ; set low limit
push ax
push bx
push cx
mov ax,di
mov cx,1
mov bx,di
.386p
FBOP BOP_DPMI,<SetDescriptorTableEntries>,FastBop
.286p
pop cx
pop bx
pop ax
cEnd
ifndef WOW_x86
; -------------------------------------------------------
; NSetGDTSegmentDscr -- This function will initialize the
; specified descriptor table entry with the specified data.
;
; This function can be called in real mode or protected mode.
;
; Input:
; Param1 - WORD segment selector
; Param2 - DWORD 32-bit segment base address
; Param3 - DWORD 32-bit segment limit
; param4 - WORD segment access/type
; Output: returns selector for the segment
; Errors: none
; Uses: Flags
assume ds:DGROUP,es:NOTHING,ss:NOTHING
cProc NSetGDTSegmentDscr,<PUBLIC,FAR>,<es,di,ax,bx>
parmW Selector
parmD Base
parmD Limit
parmW Access
cBegin
mov ax,SEL_GDT
mov es,ax
mov di,Selector
and di,SELECTOR_INDEX
mov ax,off_Base ; Set segment base
mov es:[di].adrBaseLow,ax
mov ax,seg_Base
mov es:[di].adrBaseHigh,al
mov es:[di].adrbBaseHi386,ah
mov ax,word ptr Access
and ax,070ffh ; clear 'G' bit and
; extended limit bits
mov word ptr es:[di].arbSegAccess,ax
; set access
mov ax,seg_Limit
mov bx,off_Limit ; AX:BX = segment limit
test ax,0fff0h ; big?
jz @f ; No
shr bx,12d ; Yes, make it page granular.
shl ax,4d
or bx,ax
mov ax,seg_Limit
shr ax,12d
or al,080h ; set 'G' bit
@@:
or es:[di].cbLimitHi386,al ; set high limit
mov es:[di].cbLimit,bx ; set low limit
cEnd
endif ; WOW_x86
IFDEF ROM
; -------------------------------------------------------
DXCODE ends
; -------------------------------------------------------
DXPMCODE segment
assume cs:DXPMCODE
ENDIF
; -------------------------------------------------------
; MakeLowSegment -- This function will create a segment
; descriptor for the specified low memory paragraph address.
; The segment length will be set to 64k. The difference
; between this and MakeScratchSelector is that this function
; allocates a new segment descriptor in the user area of
; the global descriptor table, thus creating a more or less
; permanent selector. MakeScratchSelector always uses the
; same descriptor location in the descriptor table, thus
; creating a very temporary selector.
;
; Input: AX - paragraph address in low memory
; BX - access rights word for the segment
; Output: AX - selector to use to access the memory
; Errors: returns CY clear if no error, CY set if unable to
; allocate a segment descriptor
; Uses: AX used, all else preserved
assume ds:DGROUP,es:NOTHING,ss:NOTHING
public MakeLowSegment
MakeLowSegment proc near
; We need to allocate a segment descriptor, convert the paragraph address
; to a linear byte address, and then initialize the allocated segment
; descriptor.
push dx
push cx
mov cx,bx
mov dx,ax ;paragraph address to DX
call AllocateSelector ;get a segment descriptor to use
jc mksl90 ;get out if error
call ParaToLinear
cCall NSetSegmentDscr,<ax,bx,dx,0,0FFFFh,cx>
clc
mksl90:
pop cx
pop dx
ret
MakeLowSegment endp
; -------------------------------------------------------
; ParaToLinear
;
; This function will convert a paragraph address in the lower
; megabyte of memory space into a linear address for use in
; a descriptor table.
;
; Input: DX - paragraph address
; Output: DX - lower word of linear address
; BX - high word of linear address
; Errors: none
; Uses: DX, BL used, all else preserved
assume ds:NOTHING,es:NOTHING,ss:NOTHING
public ParaToLinear
ParaToLinear proc near
mov bl,dh
shr bl,4
shl dx,4
xor bh,bh
ret
ParaToLinear endp
; -------------------------------------------------------
; MoveMemBlock -- This routine will copy a block
; from one place to another. It copies from the bottom
; up, so if the address ranges overlap it can only be
; used to copy down.
;
; Input: BX - selector of GDT
; CX - low word of block length
; DX - high word of block length
; DS - selector pointing to source address
; ES - selector pointing to destination address
; Output: none
; Errors: none
; Uses: modifies segment descriptors selected by ES and DS
; AX used, other registers preserved
assume ds:NOTHING,es:NOTHING,ss:NOTHING
public MoveMemBlock
MoveMemBlock:
cld
push cx
push dx
push si
push di
;
mvsd30: xor si,si
mov di,si
or dx,dx ;is there more than 64k left to move
jz mvsd32 ;if not, move the amount left
dec dx
push cx
mov cx,8000h ;move 64k bytes this time
rep movs word ptr [di],word ptr [si]
pop cx
jmp short mvsd36
mvsd32: jcxz mvsd90
shr cx,1
rep movs word ptr [di],word ptr [si]
jnc mvsd90
movs byte ptr [di],byte ptr [si]
jmp short mvsd90
;
; There is still something to move, so bump the segment descriptors to
; point to the next chunk of memory and repeat.
mvsd36:
mov di,es
push di
and di,SELECTOR_INDEX
mov es,bx
inc es:[di].adrBaseHigh ;bump the destination segment to
pop di ; the next 64k boundary
mov si,ds
push si
and si,SELECTOR_INDEX
inc es:[si].adrBaseHigh ;bump the source segment to the
pop si ; next 64k boundary
mov ds,si
mov es,di
jmp mvsd30
;
; All done
mvsd90: pop di
pop si
pop dx
pop cx
ret
; -------------------------------------------------------
subttl Utility Routines
page
; -------------------------------------------------------
; UTILITY ROUTINES
; -------------------------------------------------------
BeginLowSegment
; -------------------------------------------------------
if DEBUG
; MemCopy -- This routine is for use in a debugger to
; copy a block of extended memory down to real mode
; memory so that it can be looked at. The data is
; copied to rgbXfrBuf1. (This buffer is 4k bytes in
; size, so don't copy more than this amount)
;
; Input: CX - Number of bytes to copy
; DS:SI - protected mode address of start of copy
; Output; none
; Errors: none
; Uses: all registers trashed.
assume ds:NOTHING,es:NOTHING,ss:NOTHING
public MemCopy
MemCopy:
in al,INTA01
mov dl,al
mov al,0FFh
out INTA01,al
push ds
IFDEF ROM
SetRMDataSeg
ELSE
mov ds,segDXData
ENDIF
call EnterProtectedMode
pop ds
push SEL_DXDATA
pop es
mov di,offset DGROUP:rgbXfrBuf1
cld
rep movsb
IFDEF ROM
push SEL_DXDATA OR STD_RING
pop ds
ELSE
mov ds,selDgroup
ENDIF
call EnterRealMode
mov al,dl
out INTA01,al
int 3
endif
; -------------------------------------------------------
EndLowSegment
;
; -------------------------------------------------------
; -------------------------------------------------------
; -------------------------------------------------------
; -------------------------------------------------------
; -------------------------------------------------------
; -------------------------------------------------------
; -------------------------------------------------------
; -------------------------------------------------------
DXPMCODE ends
;
;****************************************************************
end