        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
