        PAGE    ,132
        TITLE   DXMMGR    - Dos Extender Memory Management Routines

; Copyright (c) Microsoft Corporation 1988-1991. All Rights Reserved.

;****************************************************************
;*                                                              *
;*      DXMMGR.ASM      -   Dos Extender Memory Manager         *
;*                                                              *
;****************************************************************
;*                                                              *
;*  Module Description:                                         *
;*                                                              *
;*  This module contains routines for maintaining a dynamic     *
;*  memory heap in extended memory for use in the Dos Extender. *
;*                                                              *
;*  There are two kinds of objects in the memory heap.  There   *
;*  are block headers and data blocks.  The block headers are   *
;*  each 16 bytes long, and are organized in a doubly linked    *
;*  list.  There is a segment descriptor in the global          *
;*  descriptor table for each block header, with each header    *
;*  containing the selectors for the next and previous block    *
;*  to form the list.  Block headers can either control a free  *
;*  block or they can control a data block.  In either case,    *
;*  the block immediately follows the header in memory, and     *
;*  the header contains a field that gives its size.  Free      *
;*  blocks do not have a descriptor in the global descriptor    *
;*  table, but in use data blocks do.  In the case where the    *
;*  in use data block is larger than 64k, there will be a       *
;*  contiguous range of selectors for the block.  The block     *
;*  header contains a field giving the initial selector for     *
;*  the data block for in use blocks.                           *
;*                                                              *
;*  A special type of free block serve as sentinels. They mark  *
;*  start and end of each physical block of memory available to *
;*  the memory manager. They are identified by the reserved     *
;*  value of 0FFFE (start sentinel) and 0FFFF (end sentinel) in *
;*  the selBlkOwner field. Unlike other blocks, they may not be *
;*  moved without appeal to some higher level of memory         *
;*  allocation.                                                 *
;*                                                              *
;*  Except for a pair of sentinels, a pair of blocks are        *
;*  contiguous in memory if and only if their headers are       *
;*  adjacent in the header chain.                               *
;*                                                              *
;*  The blocks at the start and end of the header chain are     *
;*  sentinals. Their selectors are stored in global variables.  *
;*  The start and end of the block header chain are identified  *
;*  by NULL (0) pointers in the Next and Previous pointer fields*
;*  respectively.                                               *
;*                                                              *
;****************************************************************
;*  Naming Conventions Used:                                    *
;*                                                              *
;*  The following hungarian prefixes are used in this module:   *
;*      lp      - far pointer (selector:offset)                 *
;*      bp      - linear byte pointer (32 bit byte offset from  *
;*                  the beginning of memory)                    *
;*      p       - near pointer (16 bit offset)                  *
;*      sel     - protected mode segment selector               *
;*      seg     - real mode paragraph address                   *
;*      cb      - count of bytes                                *
;*      id      - generic byte that contains an ID code         *
;*      hmem    - handle to XMS extended memory block           *
;*                                                              *
;****************************************************************
;*  Revision History:                                           *
;*                                                              *
;*  11/28/90 amitc    IntLowHeap allocates memory from Switcher *
;*                    if it can.                                *
;*                                                              *
;*  08/08/90 earleh   DOSX and client privilege ring determined *
;*                    by equate in pmdefs.inc                   *
;*  05/09/90 jimmat   Started VCPI changes.                     *
;*  05/09/90 jimmat   Incorporated bug fix from CodeView folks, *
;*                    changes marked with [01]                  *
;*  06/23/89 jimmat:  Added init of variable with heap handle   *
;*  04/17/89 jimmat:  Added routines for special low memory     *
;*                    heap for use by network mapping           *
;*  03/28/89 jimmat:  Incorporated bug fixes from GeneA         *
;*  02/10/89 (GeneA): changed Dos Extender from small model to  *
;*          medium model                                        *
;*  12/07/88 (GeneA): moved SetupHimemDriver to dxinit.asm      *
;*  08/30/88 (GeneA):   created                                 *
;*                                                              *
;****************************************************************

.286p

; -------------------------------------------------------
;           INCLUDE FILE DEFINITIONS
; -------------------------------------------------------

include gendefs.inc
include segdefs.inc
include pmdefs.inc
include woaswapi.inc
if VCPI
include dxvcpi.inc
endif
include dpmi.inc

; -------------------------------------------------------
;           GENERAL SYMBOL DEFINITIONS
; -------------------------------------------------------



; -------------------------------------------------------
;           EXTERNAL SYMBOL DEFINITIONS
; -------------------------------------------------------

        extrn   XMScontrol:FAR

pmxmssvc macro   fcn
        ifnb    <fcn>
        mov     ah, fcn
        endif
        call    XMScontrol
endm

        extrn   FreeSelector:NEAR
        extrn   FreeSelectorBlock:NEAR
        extrn   AllocateSelector:NEAR
        extrn   AllocateSelectorBlock:NEAR
        extrn   GetSegmentAddress:NEAR
        extrn   SetSegmentAddress:NEAR
        extrn   DupSegmentDscr:NEAR
externFP        NSetSegmentDscr
        extrn   RemoveFreeDescriptor:NEAR
        extrn   EnterProtectedMode:NEAR
        extrn   EnterRealMode:NEAR
        extrn   MoveMemBlock:NEAR
ifdef WOW
externNP        NSetSegmentAccess
externNP        NSetSegmentBase
externNP        NSetSegmentLimit
endif

DXDATA  segment

        extrn   selPSPChild:WORD
        extrn   selGDT:WORD
        extrn   bpGDT:FWORD
        extrn   rglpfnRmISR:DWORD
if VCPI
        extrn   fVCPI:byte
endif

ifdef WOW
        extrn   FastBop:fword
endif
DXDATA  ends

; -------------------------------------------------------
;           DATA SEGMENT DECLARATIONS
; -------------------------------------------------------

DXDATA  segment

; Minimum xmem allocation in K
;       Note: This value MUST be a power of 2!!!
;       Note: We will allocate a smaller block if only smaller blocks
;             are available from xms
XMEM_MIN_ALLOC equ 256

        extrn   lpfnXmsFunc:DWORD
;
; These variables control access to the extended memory heap.

        public  cKbInitialHeapSize
cKbInitialHeapSize dw   -1      ; Optional specification for first block
                                ; of extended memory in KB.

        public  dsegCurrent
dsegCurrent     dw      0       ; Paragraph count if the current block comes from DOS

        public  hmemHeap
hmemHeap        dw      ?       ;XMS memory manager handle to the memory
                                ; block containing the heap
bpHeapStart     dd      ?       ;byte address of start of heap
                                ; (points to block header of first heap block)
bpHeapEnd       dd      ?       ;byte address of end of heap
                                ; (points to block header of last heap block)
cbHeapMove      dd      ?       ;number of bytes by which a subheap moved

        public  cbHeapSize,selLowHeapStart,selHiHeapStart,selHeapStart
cbHeapSize      dd      ?       ;number of bytes in the heap

selHeapStart    dw      ?       ;selector for the sentinel block at the
                                ; start of the heap list
bpCurTop        dd      0       ;current top of compacted heap
                                ; (used during heap compaction)

cbHeapBlkMax    dd      ?       ;size of largest free block seen while
                                ; searching the heap

cbHeapFreeSpace dd      ?       ;total free space in the heap

selHiHeapStart  dw      ?       ;selector for the sentinel block at the start
                                ; of the higm memory heap
selLowHeapStart dw      ?       ;selector for the sentinel block at the start
                                ; of the special low heap
segLowHeap      dw      ?       ;segment address of low memory heap
selLowHeapEnd   dw      ?       ;selector for the sentinel block at the end
                                ; of the special low heap
fDosErr         db      ?

        public  hmem_XMS_Table,hmem_XMS_Count

hmem_XMS_Table          dw      CXMSBLOCKSDX    DUP(0)
hmem_XMS_Count          dw      0

        public  NoAsyncSwitching

NoAsyncSwitching        db      0       ;0=> async switching allowed

        public LowMemAllocFn,LowMemFreeFn

LowMemAllocFn   dd 0
LowMemFreeFn    dd 0

DXDATA  ends

; -------------------------------------------------------
;       MISCELLANEOUS DATA STRUCTURE DECLARATIONS
; -------------------------------------------------------

; ****** also in dxvcpi.asm ******

.ERRE CB_MEMHDR EQ 10h          ;size of memory block header

; ****** also in dxvcpi.asm ******

; This segment declaration describes the format of the memory
; block header at the beginning of each object in the heap.

MEMHDR  segment at 0    ;Analogous to MS-DOS arenas

fBlkStatus  db      ?       ;Status bits about the arena block
cselBlkData db      ?       ;number of selectors used by the data block
                            ; that this arena header controls
selBlkData  dw      ?       ;initial selector for the data block that this
                            ; arena header controls.
                            ; (if the block is >64k, this is the first of the
                            ;  range of selectors assigned to the block)
selBlkOwner dw      ?       ;PSP selector of owner.  0 if free.
;
; !!!!! There is code in ModifyXmemBlock which depends on the previous
; !!!!! four fields (and only those) being at lower addresses than selBlkHdr.
;
selBlkHdr   dw      ?       ;the selector of the block header.  This points
                            ; back at itself, so that we can find the selector
                            ; to the header from its address
selBlkNext  dw      ?       ; next block header selector
selBlkPrev  dw      ?       ; previous block header selector
cbBlkLen    LABEL DWORD     ; Block size in bytes
hBlockHandle dw     ?       ; The handle returned by DOS or HIMEM
dsegBlock   dw      ?       ; 0 => HIMEM ELSE DOS paragraph count

MEMHDR  ends


; -------------------------------------------------------
        subttl  Initialization Routines
        page

DXPMCODE  segment
        assume  cs:DXPMCODE
if VCPI
externNP        FreeVCPIHeap
externNP        VCPISpace
externNP        AddVCPIHeap
endif

; -------------------------------------------------------
;               INITIALIZATION ROUTINES
; -------------------------------------------------------
;
;   InitLowHeap -- Allocate and initialize the special low
;                  memory heap for use by network buffers
;                  and the like.
;
;   Input:  AX      - number of K bytes of LOW heap needed
;                     (assumed to be < 1 Meg)
;   Output: none
;   Errors: CY set if error occurs
;   Uses:   All registers preserved
;
;   Note:   This routine must be called in protected mode
;           and it assumes that interrupts should be enabled
;           while it runs.

        assume  ds:DGROUP,es:DGROUP,ss:NOTHING
        public  InitLowHeap

InitLowHeap     proc    near

        ret

InitLowHeap     endp

; -------------------------------------------------------
;   AddToXmemHeap    -- This routine will add a block of memory
;       to the extended memory heap.  It creates the sentinel header
;       blocks and a header block for a free object containing
;       the new memory.
;
;   Input:  CX  -   Least significant part of minimum block length required
;           DX  -   Most significant part of minimum block length required
;   Output: None
;   Errors: None
;   Uses:
;   NOTES:   This routine must be called in protected mode.
;            With XMS-only providing memory, all XMS memory is allocated
;            the first time this routine is called.
;
;            If VCPI is active, and VCPI memory is being used, then
;            enough memory is allocated from the server to satisfy
;            the request.  The allocation is handled in dxvcpi.asm.
;

        assume  ds:DGROUP,es:NOTHING,ss:NOTHING

cProc AddToXmemHeap,<NEAR,PUBLIC>,<ax,bx,cx,dx,si,es>
cBegin

if VCPI
        cmp     fVCPI, 0                ;running under a VCPI server?
        jz      atxh100
        cCall   AddVCPIHeap             ;yes, alloc memory from server
        jc      atxhDone                ;no luck from vcpi
        jmp     atxh300                 ;rejoin common code

        ; should have 32 bit address in bx:dx when coming out
        ;  of AddVCPIHeap
@@:
endif

;
; Add to requested size the size of our header overhead
;
	add	cx,3*CB_MEMHDR	; Adjust for header overhead
	adc	dx,0
;
; Round allocation up to next MIN_ALLOC size by first rounding up to the
; nearest 1K, then converting to K and rounding
;

        add     cx,1023
        adc     dx,0
        shr     cx,10
        push    dx
        shl     dx,6
        or      cx,dx                   ; cx is now size rounded to K
        pop     dx
        add     cx,XMEM_MIN_ALLOC - 1
        adc     dx,0
        and     cx,NOT (XMEM_MIN_ALLOC - 1) ; rounded to next higher MIN_ALLOC


; See How much memory is available from the HIMEM driver

atxh100:
        pmxmssvc 8                      ; query freespace available
        cmp     bl,0                    ; Test for error
        jnz     atxhDone                 ; No luck - Try DOS
        cmp     ax,0
        je      atxhDone

;        cmp     ax,cx           ; is largest block > allocation desired?
;        jb      atxh110         ; no, use largest block

;        mov     ax,cx           ; yes, use desired alloc
atxh110:
        mov     dx,ax           ; Load reported size of largest free block
        mov     bx,ax           ; Calculate number of bytes available
        shl     bx,10
        shr     ax,6            ; AX:BX = available bytes

        sub     bx,3*CB_MEMHDR  ; Adjust for header overhead
        sbb     ax,0
        mov     word ptr cbHeapSize,bx     ; Save lower order bytes available
        mov     word ptr [cbHeapSize+2],ax ; Save higher order bytes available

        pmxmssvc 9              ; Allocate the largest possible block
        cmp     ax,0            ; Check for error
        jz      atxhDone        ; HIMEM driver speak with forked tongue
        mov     hmemHeap,dx     ; Save the handle

        pmxmssvc 0Ch            ; Lock and query address
        xchg    bx,dx
        mov     dsegCurrent,0   ; Flag this allocate was not from DOS
        cmp     ax,0            ; Test result
        jnz     atxh300         ; Rejoin common code

        Trace_Out "AddToXmemHeap: Lock of XMS handle #DX failed."

        jmp     atxhDone        ; Jump on error

atxh300:

        call    StructureHeap   ; Build sentinels and free block

        mov     ax,selHeapStart ; Remember the high heap start selector
        mov     selHiHeapStart,ax

if VCPI
        cmp     fVCPI, 0        ;running under a VCPI server?
        jnz     atxhDone        ;Yes, AddVCPIHeap should have got it all
endif
;        jmp     short atxh100

; Done allocating memory.

atxhDone:

cEnd

; -------------------------------------------------------
;   InitXmemHeap    -- This routine will initialize the
;                      extended memory heap.
;
;   Input:
;   Output:
;   Errors:
;   Uses:
;

        assume  ds:DGROUP,es:DGROUP,ss:NOTHING
        public  InitXmemHeap

InitXmemHeap:
        ret

IFNDEF WOW
; -------------------------------------------------------
;    FreeXmemHeap -- This routine gives the xms memory back to
;       the xms driver.  The selectors for the heap are not
;       freed.  Shortly after we release the heap, the LDT will
;       be reinitialized, and that will free the selectors
;
        public FreeXmemHeap
FreeXmemHeap:
        ret ; bugbug
ENDIF
; -------------------------------------------------------
;   StructureHeap -- This routine creates the sentinel header
;       blocks and a header block for a free object containing
;       the new memory.
;
;   Input:  BX  -   Most significant part of heap block address
;           DX  -   Least significant part of heap block address
;
;   Output: None
;   Errors: Carry flag set if there was not enough memory available
;   Uses:   BX, DX
;
;   NOTE:   This routine must be called in protected mode.
;

        assume  ds:DGROUP,es:NOTHING,ss:NOTHING
        public  StructureHeap

StructureHeap   proc    near

        push    ax
        push    cx
        push    es

        push    bx              ; most significant part of address
        push    dx              ; least significant part of address

; Allocate selectors for the two sentinel blocks, and the initial free
; block and link them at the start of the chain.
;
; Create the end sentinel

        add     dx,word ptr cbHeapSize       ;Calculate address of end sentinel
        adc     bx,word ptr [cbHeapSize + 2]
        add     dx,2*CB_MEMHDR
        adc     bx,0
        call    AddSelectorToChain           ;Allocate/link the end sentinel

; Initialize the ending sentinel block.

        assume  es:MEMHDR
        xor     ax,ax
        mov     fBlkStatus,al
        mov     cselBlkData,al
        mov     selBlkData,ax
        mov     selBlkOwner,0FFFFh
        mov     cx,hmemHeap         ; Save handle
        mov     hBlockHandle,cx
        mov     cx,dsegCurrent      ; Save paragraph count
        mov     dsegBlock,cx

; Create the free block

        pop     dx                  ; least significant part of address
        pop     bx                  ; most significant part of address
        add     dx,CB_MEMHDR        ; Calculate address of Free block
        adc     bx,0
        call    AddSelectorToChain  ; Allocate and link the Free Block

; Initialize the header for the free data block.

        mov     fBlkStatus,al
        mov     cselBlkData,al
        mov     selBlkData,ax
        mov     selBlkOwner,ax
        mov     cx,word ptr [cbHeapSize]
        mov     word ptr [cbBlkLen],cx
        mov     cx,word ptr [cbHeapSize+2]
        mov     word ptr [cbBlkLen+2],cx

; Create the starting sentinel

        sub     dx,CB_MEMHDR        ; Calculate address of start sentinel
        sbb     bx,0
        call    AddSelectorToChain  ; Allocate and link the start sentinel

; Initialize the starting sentinel block.

        mov     fBlkStatus,al
        mov     cselBlkData,al
        mov     selBlkData,ax
        mov     selBlkOwner,0FFFEh  ;mark it as in use
        mov     cx,hmemHeap         ; Save handle
        mov     hBlockHandle,cx
        mov     cx,dsegCurrent      ; Save paragraph count
        mov     dsegBlock,cx

        pop     es
        pop     cx
        pop     ax

        ret

StructureHeap   endp


; -------------------------------------------------------
; AddSelectorToChain    -- This function will create a header block at a
;                          specified address and link it to the head of the
;                          header chain. It is the caller's responsibility
;                          to initialize all fields in the header other
;                          than the chain link pointers themselves.
;
;   This function can only be called in protected mode.
;
;   Input:  BX      - Most significant part of header address
;           DX      - Least significant part of header address
;   Output: ES      - selector for the new header
;   Errors: Carry flag set on error
;   Uses:   all registers except ES preserved

        assume  ds:DGROUP,es:NOTHING,ss:NOTHING
        public  AddSelectorToChain

AddSelectorToChain:
        push    ax                  ; Save callers regs
        push    bx                  ;
        push    cx                  ;
        call    AllocateSelector    ; Get a free selector
        jc      astcFail            ; Jump on error
        mov     cx,CB_MEMHDR - 1    ;
        cCall   NSetSegmentDscr,<ax,bx,dx,0,cx,STD_DATA>

        mov     cx,selHeapStart     ; Load old start of chain
        jcxz    @f
        mov     es,cx               ; Point to old start of chain
        assume  es:MEMHDR           ;
        mov     selBlkPrev,ax       ; Link to new start of chain
@@:
        mov     es,ax               ; Point to new head of chain
        mov     selBlkNext,cx       ; Link it to old head
        mov     selBlkPrev,0        ; NULL back pointer
        mov     selBlkHdr,ax        ; Block header points to itself
        mov     selHeapStart,ax     ; Store new head of chain
        clc                         ; Flag no error
astcFail:
        pop     cx                  ; Restore Users regs
        pop     bx                  ;
        pop     ax                  ;
        ret                         ; AddSelectorToChain


; -------------------------------------------------------
; AllocateXmem32        -- This function will return a 32-bit address and
;                          handle of a block of memory allocated in extended
;                          memory.
;
;   Input:  BX:CX   - Size of block desired
;           DX      - Owner of block
;   Output: BX:CX   - Address of memory
;           SI:DI   - handle of memory
;   Errors: Carry flag set on error
;   Uses:
; -------------------------------------------------------

        public  AllocateXmem32
        assume  ds:DGROUP,es:NOTHING,ss:NOTHING
AllocateXmem32  proc    near

        FBOP    BOP_DPMI, AllocXmem, FastBop
        ret

AllocateXmem32  endp


; -------------------------------------------------------
; FreeXmem32            -- This function will free a block of extended memory
;                          allocated by AllocateXmem.
;
;   Input:  SI:DI   - Handle of memory
;   Errors: Carry flag set on error
;   Uses:
; -------------------------------------------------------

        public  FreeXmem32
        assume  ds:DGROUP,es:NOTHING,ss:NOTHING
FreeXmem32      proc    near

        FBOP    BOP_DPMI, FreeXmem, FastBop
        ret

FreeXmem32      endp


DXPMCODE    ends

DXCODE  segment
        assume  cs:DXCODE

; -------------------------------------------------------
;   ReleaseLowHeap      -- This routine will release the
;       special low memory heap.  After this function is
;       called, InitLowHeap must be called again before
;       any other low heap functions can be used.
;
;       Currently this routine doesn't bother to release
;       selectors used by the blocks in the low heap (like
;       ReleaseXmemHeap does) under the assumption that
;       the DOS extender is about to terminate.  If you are
;       really going reinitialize the low heap with InitLowHeap,
;       then you should do more clean up here.
;
;   Input:  none
;   Output: none
;   Errors:
;   Uses:   All preserved
;
;   Note:   This routine must be called in real mode!

        assume  ds:DGROUP,es:DGROUP,ss:NOTHING
        public  ReleaseLowHeap

ReleaseLowHeap  proc    near

        push    ax
        push    es

        mov     ax,segLowHeap           ;make sure there really is a low heap
        or      ax,ax
        jz      rlh90

        mov     es,ax                   ;now use DOS to get rid of it
        mov     ah,49h
        pushf
        sub     sp,8                    ; make room for stack frame
        push    bp
        mov     bp,sp
        push    es
        push    ax

        xor     ax,ax
        mov     es,ax
        mov     [bp + 8],cs
        mov     [bp + 6],word ptr (offset rlh10)
        mov     ax,es:[21h*4]
        mov     [bp + 2],ax
        mov     ax,es:[21h*4 + 2]
        mov     [bp + 4],ax
        pop     ax
        pop     es
        pop     bp
        retf

rlh10:  xor     ax,ax                   ;just to be tidy
        mov     segLowHeap,ax
        mov     selLowHeapStart,ax

rlh90:
        pop     es
        pop     ax
        ret

ReleaseLowHeap  endp


; -------------------------------------------------------
;   ReleaseXmemHeap     -- This routine will release memory
;       used by the extended memory heap.  After this function
;       is called, no extended memory manager routines can
;       be called except InitXmemHeap.
;
;   Input:
;   Output:
;   Errors:
;   Uses:
;
;   Note:    Do NOT enable interrupts while in protected mode!

        assume  ds:DGROUP,es:DGROUP,ss:NOTHING
        public  ReleaseXmemHeap

ReleaseXmemHeap:
        push    ax
        push    bx
        push    cx
        push    dx
        push    ds
        push    es

        SwitchToProtectedMode   ; Needed for selector juggling

ifdef MD
        call    CheckXmemHeap
endif
        mov     bx,selHeapStart ; Point to the start of the block chain
        mov     es,bx           ;
        assume  es:MEMHDR       ;
;
; This is the start of a loop on all blocks. ES and BX both contain the
; selector for the current block, or zero if at the end of the chain.
rxh100:
        or      bx,bx           ; End of chain?
        jz      rxhEnd          ; Yes - Jump
        mov     cl,cselBlkData  ; Load number of data segments
        xor     ch,ch           ;
        jcxz    rxh200          ; Jump if nothing to do
        mov     ax,selBlkData   ; Load first data segment
        and     ax,SELECTOR_INDEX ; Treat like GDT entry for FreeSelector
rxh110:
        push    ax              ; Since it is destroyed by FreeSelector
        call    FreeSelector    ; Free each of the data selectors
        pop     ax              ;
        add     ax,8            ; Point to the next data descriptor
        loop    rxh110          ;
;
; All data descriptors for this block now free (if there ever were any)
rxh200:
        push    selBlkNext      ; Push pointer to the next block in the chain
        push    es              ; Push the pointer to this one
        cmp     selBlkOwner,0FFFFh ; Is this an end sentinel ?
        jne     rxh300          ; No - jump
;
; Time to free a HIMEM allocated memory blcok
rxh210:
if VCPI
        cmp     fVCPI, 0
        jnz     rxh300
endif
        mov     dx,hBlockHandle ;
        push    dx              ; Since the data may move after the unlock
        ASSUME  es:NOTHING      ;
        pmxmssvc 0Dh,disable    ;unlock the memory block containing the heap
        pop     dx              ;
        pmxmssvc 0Ah,disable    ;free the block
;
; Time to free the header selector.
rxh300:
        pop     ax              ; Retrieve the selector for the current header
        pop     bx              ; Retrieve the selector for the next one
        mov     es,bx           ;
        call    FreeSelector    ; Free the selector for the current header
        jmp     rxh100          ; Loop for the next block
;
; All done
rxhEnd:
        mov     selHeapStart,0  ; Point to the start of the block chain
ifdef MD
        call    CheckXmemHeap
endif
if VCPI
        cmp     fVCPI, 0
        jz     @F
.386
        cCall   FreeVCPIHeap
.286p
@@:
endif
        SwitchToRealMode        ; Restore callers environment
        sti                     ;
        pop     es
        pop     ds
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        ret

; -------------------------------------------------------

DXCODE  ends

; -------------------------------------------------------
        subttl  Main Routines
        page
; -------------------------------------------------------

DXPMCODE    segment
        assume  cs:DXPMCODE

; -------------------------------------------------------
;           MAIN MEMORY MANAGEMENT ROUTINES
; -------------------------------------------------------
;
;   AllocateLowBlock -- This function will allocate a block
;       of memory from the special low memory heap.
;       If the requested block is larger than 64k, multiple segment
;       descriptors will be allocated, one for each full 64k and one
;       for whatever is left over.  When multiple segment descriptors
;       are created, the selectors will differ by 8 for each segment.
;       (i.e. the returned selector accesses the first 64k, add 8 to
;        get to the next 64k, etc.)
;
;   Input:  CX      - low word of requested block size
;           DX      - high word of requested block size
;   Output: AX      - initial selector for the memory block
;   Errors: returns CY set and size of largest free memory block in CX,DX.
;           This can occur either because there isn't a large enough
;           free block in the memory pool, or there isn't a contiguous
;           range of free segment descriptors that is large enough.
;   Uses:   AX, all else preserved.  Modifies the segment descriptors
;           for the SEL_SCR0 and SEL_SCR1 segments
;
;   Note:   This routine is _very_ similar to AllocateXmemBlock,
;           but just different enough that it's a separate routine.

        assume  ds:DGROUP,es:NOTHING,ss:NOTHING
        public  AllocateLowBlock

AllocateLowBlock  proc  near

        push    bp
        mov     bp,sp
        sub     sp,14
        push    es
        push    si
        push    bx
        push    cx
        push    dx

Segm    equ word ptr [bp - 2]
StartSel equ word ptr [bp - 4]
Largest equ word ptr [bp - 6]
MemSize    equ [bp - 10]
cSelector equ word ptr [bp - 12]
selHeader equ word ptr [bp - 14]

        mov     word ptr MemSize,cx
        mov     word ptr MemSize + 2,dx

;
; See if we need to use wow kernel to manage low memory
;
        mov     ax,word ptr [LowMemAllocFn]
        or      ax,word ptr [LowMemAllocFn + 2]
        je      alm3
        jmp     alm130
;
; Round up to the next paragraph
;
alm3:   add     cx,15
        adc     dx,0
        and     cx,0FFF0h

;
; Form a paragraph count
;
        mov     bx,cx
        shr     bx,4
        shl     dx,12
        or      bx,dx

;
; Add one to make room for the heap header
;
        cmp bx,0ffffh
        je      alm5                            ; won't succed, but get size

        inc bx
alm5:
;
; Switch to real mode and allocate the memory
;
        SwitchToRealMode

        mov     ax,4800h
        int     21h                             ; call dos

        jnc     alm10

        mov     Segm,ax
        mov     Largest,bx
        mov     cx,0
        jmp     alm20
;
; Remember the values returned
;

alm10:  mov     Segm,ax
        mov     cx,1

alm20:  SwitchToProtectedMode
        cmp     cx,0
        jne     alm40

        ;
        ; return an error
        ;

alm30:  mov     cx,bx
        mov     dx,bx
        shr     dx,12
        shl     cx,4                            ; form bytes available
        mov     ax,Segm                         ; actually error code for fail
        add     sp,4                            ; skip cx,dx
        stc
        jmp     alm110

alm40:  mov     ax,word ptr MemSize + 2
        mov     bx,word ptr MemSize

        or      bx, bx
        jnz     short alm44
        or      ax, ax
        jz      short alm45                     ; if zero then don't adjust

alm44:
        sub     bx,1
        sbb     ax,0                            ; go from size to limit
alm45:
        mov     word ptr MemSize + 2,ax
        mov     word ptr MemSize,bx
        inc     ax                              ; we always want 2 more
        inc     ax
        mov     cSelector,ax

        call    AllocateSelectorBlock
        jnc     alm50

        mov     ax,8                            ; insufficient memory
        mov     bx,Largest
        jmp     alm30

alm50:  or      ax,STD_RING
        mov     si,ax
        mov     StartSel,ax
        mov     ax,word ptr MemSize + 2
        mov     bx,word ptr MemSize             ; ax:bx - block size
        mov     cx,Segm
        mov     dx,cx
        shr     cx,12
        shl     dx,4                            ; cx:dx - block base
        ;
        ; Set up the first one to have the entire limit
        ;
        cCall   NSetSegmentDscr,<si,cx,dx,ax,bx,STD_DATA>

        ;
        ; Set up the rest to have 64K limits
        ;

        dec     ax                              ; already set one
        cmp     ax,0FFFFh
        je      alm80

        cmp     ax,0
        je      alm70

        mov     di,0FFFFh
alm60:  add     si,8
        inc     cx                              ; add 64K to base

        cCall   NSetSegmentDscr,<si,cx,dx,0,di,STD_DATA>

        dec     ax
        cmp     ax,0
        jne     alm60

        ;
        ; Just one selector left, so set the correct limit
        ;

alm70:  add     si,8
        inc     cx
        cCall   NSetSegmentDscr,<si,cx,dx,0,bx,STD_DATA>

        ;
        ; Set up header
        ;

alm80:  mov     ax,Segm
        mov     bx,ax
        shr     ax,12
        shl     bx,4                            ; ax:bx = base of header
        add     bx,word ptr MemSize
        adc     ax,word ptr MemSize + 2
        add     si,8
        cCall   NSetSegmentDscr,<si,ax,bx,0,CB_MEMHDR,STD_DATA>

        ;
        ; Set up values in header and add to chain
        ;
        mov     es,si
        mov     selHeader,si
        assume  es:MEMHDR
        mov     [selBlkHdr],si
        mov     ax,StartSel
        mov     [selBlkData],ax
        mov     ax,cSelector
        mov     [cselBlkData],al
        mov     ax,selPSPChild
        mov     [selBlkOwner],ax
        mov     ax,Segm
        mov     [hBlockHandle],ax
        mov     ax,selLowHeapStart
        mov     [selBlkNext],ax
        mov     bx,0
        mov     [selBlkPrev],bx
        or      ax,ax
        jz      alm100

        mov     bx,es
        mov     es,ax
        mov     [selBlkPrev],bx

        ;
        ; set up return values
        ;
alm100: mov     ax,selHeader
        mov     selLowHeapStart,ax
        mov     ax,StartSel
        clc
alm105: pop     dx
        pop     cx
alm110: pop     bx
        pop     si
        pop     es
        mov     sp,bp
        pop     bp
        ret

alm130: push    dx
        push    cx
        call    [LowMemAllocFn]
        or      ax,ax
        jz      alm140
        clc
        jmp     alm105

alm140: xor     cx,cx
        stc
        add     sp,4
        jmp     alm110

AllocateLowBlock  endp


; -------------------------------------------------------
;   FreeLowBlock   -- This function will free the low heap
;       memory block specified by the given selector.  It
;       will return the memory block to the free memory pool,
;       and release all selectors used by this block.
;
;   Input:  AX      - selector of the data block to free
;   Output: none
;   Errors: returns CY set if invalid selector
;   Uses:   AX, all other registers preserved
;           Modifies the descriptor for segment SEL_SCR0

        assume  ds:DGROUP,es:NOTHING,ss:NOTHING
        public  FreeLowBlock

FreeLowBlock    proc    near
        push    bp
        mov     bp,sp
        sub     sp,4
Segm    equ word ptr [bp - 2]
HeaderSel equ word ptr [bp - 4]
        push    es
        push    bx
        push    cx

        ;
        ; See if we need to use the wow kernel to manage memory
        ;
        mov     bx,word ptr [LowMemFreeFn]
        or      bx,word ptr [LowMemFreeFn + 2]
        jz      flm5
        jmp     flm130

        ;
        ; search for the block to free
        ;
flm5:   mov     bx,selLowHeapStart
flm10:  or      bx,bx                           ; any low blocks?
        jz      flm100

        mov     es,bx
        assume  es:MEMHDR

        cmp     [selBlkData],ax
        je      flm30                           ; found the correct block

        mov     bx,[selBlkNext]
        jmp     flm10

        ;
        ; Unlink the block from the list
        ;
flm30:  mov     ax,es
        mov     HeaderSel,ax

        mov     ax,[selBlkPrev]
        mov     bx,[selBlkNext]
        cmp     ax,0
        je      flm40

        mov     es,ax
        mov     [selBlkNext],bx
        jmp     flm50

flm40:  mov     SelLowHeapStart,bx
flm50:  cmp     bx,0
        je      flm60

        mov     es,bx
        mov     [selBlkPrev],ax

flm60:  mov     ax,HeaderSel
        mov     es,ax
        mov     ax,[hBlockHandle]
        mov     Segm,ax

        mov     ax,[selBlkData]
        xor     cx,cx
        mov     cl,[cselBlkData]
        push    0
        pop     es
        call    FreeSelectorBlock

        SwitchToRealMode
        mov     ax,Segm
        mov     es,ax
        mov     ax,4900h
        int     21h                             ; free the memory

        push    ax                              ; save return code
        jnc     flm70

        mov     cx,0                            ; error
        jmp     flm80

flm70:  mov     cx,1                            ; no error
flm80:  SwitchToProtectedMode
        pop     ax
        cmp     cx,0
        je      flm100                          ; error path

flm85:  clc
flm90:  pop     cx
        pop     bx
        pop     es
        mov     sp,bp
        pop     bp
        ret

flm100: stc
        jmp     flm90

flm130: push    ax
        call    [LowMemFreeFn]
        or      ax,ax
        jz      flm85

        mov     ax,9
        stc
        jmp     flm90

FreeLowBlock    endp

; -------------------------------------------------------
;
;   AllocateXmemBlock -- This function will allocate a block
;       of extended memory and return selectors for accessing it.
;       If the requested block is larger than 64k, multiple segment
;       descriptors will be allocated, one for each full 64k and one
;       for whatever is left over.  When multiple segment descriptors
;       are created, the selectors will differ by 8 for each segment.
;       (i.e. the returned selector accesses the first 64k, add 8 to
;        get to the next 64k, etc.)
;
;   Input:  BL      - flag controlling allocation of selectors,
;                     if 0 - a selector will be allocated for each 64k
;                     if != 0 - only one selector will be allocated to
;                     the block.
;           CX      - low word of requested block size
;           DX      - high word of requested block size
;   Output: AX      - initial selector for the memory block
;   Errors: returns CY set and size of largest free memory block in CX,DX.
;           This can occur either because there isn't a large enough
;           free block in the memory pool, or there isn't a contiguous
;           range of free segment descriptors that is large enough.
;   Uses:   AX, all else preserved.  Modifies the segment descriptors
;           for the SEL_SCR0 and SEL_SCR1 segments
;
;   Notes:  Hard coding an int 3 in this routine is fatal.  This routine
;           is used to allocate the stack used for exception handling.
;

        assume  ds:DGROUP,es:NOTHING,ss:NOTHING
        public  AllocateXmemBlock

AllocateXmemBlock       proc    near

axb1:   push    bp
        mov     bp,sp
        sub     sp,20
        push    es
        push    bx
        push    si
        push    di
        push    cx
        push    dx

AllocFlag equ byte ptr [bp - 2]
cSelector equ word ptr [bp - 4]
MemHandle equ [bp - 8]
MemSize equ [bp - 12]
MemAddr equ [bp - 16]
StartSel equ word ptr [bp - 18]
HeaderSel equ word ptr [bp - 20]

        mov     AllocFlag,bl
        mov     word ptr MemSize,cx
        mov     word ptr MemSize + 2,dx
        mov     word ptr MemHandle,0
        mov     word ptr MemHandle + 2,0

        ;
        ; Save room for header
        ;
        add     cx,16
        adc     dx,0

        mov     bx,dx
        mov     dx, selPSPChild

        call    AllocateXmem32

        jnc     axb40
        jmp     axb130

axb40:  mov     word ptr MemAddr,cx
        mov     word ptr MemAddr + 2,bx
        mov     word ptr MemHandle,di
        mov     word ptr MemHandle + 2,si
        ;
        ; Change size to limit
        ;
        mov     ax,word ptr MemSize + 2
        mov     bx,word ptr MemSize
        sub     bx,1
        sbb     ax,0                    ; size -> limit
        mov     word ptr MemSize,bx
        mov     word ptr MemSize + 2,ax

        ;
        ; Figure out how many selectors to allocate
        ;
        cmp     AllocFlag,0
        jne     axb50

        inc     ax
        jmp     axb60

axb50:  mov     ax,1

        ;
        ; Add one additional for the header block
        ;
axb60:  inc     ax
        mov     cSelector,ax

        ;
        ; Allocate the selectors
        ;
        call    AllocateSelectorBlock
        jnc     axb65

        jmp     axb120

axb65:  or      ax,STD_RING
        mov     StartSel,ax

        ;
        ; Set up the first selector to have the entire limit
        ;
        mov     di,cSelector
        mov     si,ax
        mov     ax,word ptr MemSize + 2
        mov     bx,word ptr MemSize
        mov     cx,word ptr MemAddr + 2
        mov     dx,word ptr MemAddr
        add     dx,16                   ; room for header
        adc     cx,0
        cCall   NSetSegmentDscr,<si,cx,dx,ax,bx,STD_DATA>
        dec     di
        dec     ax
        add     si,8

        cmp     di,1
        je      axb90

        ;
        ; Set up the rest with 64K limits
        ;
axb70:  cmp     ax,0
        je      axb80

        inc     cx
        cCall   NSetSegmentDscr,<si,cx,dx,0,0FFFFh,STD_DATA>
        dec     ax
        add     si,8
        jmp     axb70

        ;
        ; Set up the last one with the remaining limit
        ;
axb80:  inc     cx
        cCall   NSetSegmentDscr,<si,cx,dx,0,bx,STD_DATA>
        add     si,8

        ;
        ; Set up Header selector
        ;
axb90:  mov     ax,word ptr MemAddr + 2
        mov     bx,word ptr MemAddr
        cCall   NSetSegmentDscr,<si,ax,bx,0,CB_MEMHDR,STD_DATA>
        mov     HeaderSel,si

        ;
        ; Set up header
        ;
        mov     es,si
        assume  es:MEMHDR
        mov     [selBlkHdr],si
        mov     ax,StartSel
        mov     [selBlkData],ax
        mov     ax,cSelector
        mov     [cselBlkData],al
        mov     ax,selPSPChild
        mov     [selBlkOwner],ax
        mov     ax,MemHandle
        mov     [hBlockHandle],ax
        mov     ax,word ptr MemHandle + 2
        mov     [dsegBlock],ax                  ; use for high half handle
        mov     ax,selHiHeapStart
        mov     [selBlkNext],ax
        mov     bx,0
        mov     [selBlkPrev],bx
        or      ax,ax
        jz      axb100

        mov     bx,es
        mov     es,ax
        mov     [selBlkPrev],bx
axb100: mov     ax,HeaderSel
        mov     selHiHeapStart,ax

        ;
        ; return information to the caller
        ;
        clc
        mov     ax,StartSel
        pop     dx
        pop     cx
axb110: pop     di
        pop     si
        pop     bx
        pop     es
        mov     sp,bp
        pop     bp
        ret

        ;
        ; Error
        ;

axb120: mov     dx,word ptr MemHandle
        or      dx,word ptr MemHandle + 2
        jz      axb130

        mov     di,word ptr MemHandle
        mov     si,word ptr MemHandle + 2

        FBOP    BOP_DPMI, FreeXmem, FastBop
        ;
        ; Get largest free block
        ;
axb130: pmxmssvc 08h
        mov     dx,ax
        mov     cx,ax
        shl     cx,10
        shr     dx,6
        add     sp,4                    ; skip cx,dx on stack
        stc
        jmp     axb110

AllocateXmemBlock       endp

; -------------------------------------------------------
;   FreeXmemBlock   -- This function will free the extended
;       memory block specified by the given selector.  It
;       will return the memory block to the free memory pool,
;       and release all selectors used by this block.
;
;   Input:  AX      - selector of the data block to free
;   Output: none
;   Errors: returns CY set if invalid selector
;   Uses:   AX, all other registers preserved
;           Modifies the descriptor for segment SEL_SCR0

        assume  ds:DGROUP,es:NOTHING,ss:NOTHING
        public  FreeXmemBlock


FreeXmemBlock:

fxb1:   push    bp
        mov     bp,sp
        sub     sp,6
HeaderSel equ word ptr [bp - 2]
MemHandle equ [bp - 6]
        push    es
        push    bx
        push    si
        push    di
        push    dx
        push    cx

        ;
        ; search for the block to free
        ;
        mov     bx,selHiHeapStart
fxb10:  or      bx,bx                           ; any hi blocks?
        jnz     fxb20
        jmp     fxb100

fxb20:  mov     es,bx
        assume  es:MEMHDR

        cmp     [selBlkData],ax
        je      fxb30                           ; found the correct block

        mov     bx,[selBlkNext]
        jmp     fxb10

        ;
        ; Unlink the block from the list
        ;
fxb30:  mov     ax,es
        mov     HeaderSel,ax

        mov     ax,[selBlkPrev]
        mov     bx,[selBlkNext]
        cmp     ax,0
        je      fxb40

        mov     es,ax
        mov     [selBlkNext],bx
        jmp     fxb50

fxb40:  mov     SelHiHeapStart,bx
fxb50:  cmp     bx,0
        je      fxb60

        mov     es,bx
        mov     [selBlkPrev],ax

fxb60:  mov     ax,HeaderSel

        mov     es,ax
        mov     dx,[dsegBlock]                  ; high half handle
        mov     word ptr MemHandle + 2,dx
        mov     dx,[hBlockHandle]
        mov     word ptr MemHandle,dx

        ;
        ; Free the selectors
        ;
        mov     ax,[selBlkData]
        xor     cx,cx
        mov     cl,[cselBlkData]
        push    0
        pop     es
        assume  es:nothing
        call    FreeSelectorBlock

        mov     si,word ptr MemHandle + 2
        mov     di,word ptr MemHandle

        call    FreeXmem32
        clc

fxb90:  pop     cx
        pop     dx
        pop     di
        pop     si
        pop     bx
        pop     es
        mov     sp,bp
        pop     bp
        ret

fxb100: stc
        jmp     fxb90

; -------------------------------------------------------
;   ModifyXmemBlock -- This function will modify the size
;       of the specified extended memory block to the new
;       size specified by CX,DX.  If the block is being
;       shrunk, the new size must be at least CB_HDRSIZ bytes
;       smaller than the old size, or nothing is done.If the
;       block grows beyond the next 64k boundary,
;       it may be necessary to allocate another segment
;       descriptor for it.  If this is not possible, the grow
;       will fail.
;
;   Input:  AX      - segment selector for the data block to modify
;           BL      - flag controlling allocation of selectors to block,
;                     if 0 - selectors will be allocated for each 64k
;                     if != 0 - only one selector is allocated to block
;           CX      - low word of new desired size
;           DX      - high word of new desired size
;   Output: none
;   Errors: CY set if unable to accomplish the request.
;           returns error code in AX.
;           CX,DX will be set to the largest possible size that the
;           block could be modified to have
;   Uses:   AX,CX,DX,Flags

        assume  ds:DGROUP,es:NOTHING
        public  ModifyXmemBlock

ModifyXmemBlock proc    near
mxb1:   push    bp
        mov     bp,sp
        sub     sp,20
HeaderSel equ word ptr [bp - 2]
AllocFlags equ byte ptr [bp - 4]
MemSize equ [bp - 8]
MemAddr equ [bp - 12]
Success equ word ptr [bp - 14]
cSelector equ word ptr [bp - 16]
BlockHandle equ word ptr [bp - 20]
        push    es
        push    bx
        push    cx
        push    dx

        mov     word ptr MemSize + 2,dx
        mov     word ptr MemSize,cx
        mov     AllocFlags,bl
        mov     Success,1

        ;
        ; Search for the block to resize
        ;
        mov     bx,selHiHeapStart
mxb10:  or      bx,bx                           ; any hi blocks?
        jnz     mxb15
        jmp     mxb140

mxb15:  mov     es,bx
        assume  es:MEMHDR

        cmp     [selBlkData],ax
        je      mxb30                           ; found the correct block

        mov     bx,[selBlkNext]
        jmp     mxb10

        ;
        ; Calculate the number of selectors needed
        ;
mxb30:  mov     HeaderSel,es
        test    AllocFlags,1
        jne     mxb40

        sub     cx, 1                           ; size -> limit
        sbb     dx,0
        inc     dl
        jmp     mxb50

mxb40:  mov     dl,1
mxb50:  inc     dl                              ; always need 1 more

        ;
        ; See if we have enough selectors
        ;
        cmp     dl,[cselBlkData]
        jna     mxb55
        jmp     mxb120

mxb55:  and     dl,0ffh
        mov     cSelector,dx
        mov     dx,[dsegBlock]
        mov     word ptr BlockHandle + 2,dx
        mov     dx,[hBlockHandle]
        mov     word ptr BlockHandle,dx

        mov     bx,word ptr MemSize + 2
        mov     cx,word ptr MemSize
        add     cx,010h
        adc     bx,0
        mov     si,word ptr BlockHandle + 2
        mov     di,word ptr BlockHandle

        FBOP    BOP_DPMI, ReallocXmem, FastBop
        jnc     mxb60

        mov     Success,0

mxb60:  mov     word ptr MemAddr,cx
        mov     word ptr MemAddr + 2,bx
        mov     word ptr BlockHandle,di
        mov     word ptr BlockHandle + 2,si
        ;
        ; Fix the base of the Header selector
        ;
        mov     bx,word ptr MemAddr + 2
        mov     dx,word ptr MemAddr
        mov     ax,HeaderSel
        call    SetSegmentAddress
        mov     es,ax

        ;
        ; Fix the handle (may have changed
        ;
        mov     dx,word ptr BlockHandle
        mov     [hBlockHandle],dx
        mov     dx,word ptr BlockHandle + 2
        mov     [dsegBlock],dx

        ;
        ; Change size to limit
        ;
        mov     ax,word ptr MemSize + 2
        mov     bx,word ptr MemSize
        sub     bx,1
        sbb     ax,0                    ; size -> limit
        mov     word ptr MemSize,bx
        mov     word ptr MemSize + 2,ax

        ;
        ; Set up the first selector to have the entire limit
        ;
        mov     di,cSelector
        mov     si,[selBlkData]
        mov     ax,word ptr MemSize + 2
        mov     bx,word ptr MemSize
        mov     cx,word ptr MemAddr + 2
        mov     dx,word ptr MemAddr
        add     dx,16                   ; room for header
        adc     cx,0
        cCall   NSetSegmentDscr,<si,cx,dx,ax,bx,STD_DATA>
        dec     di
        dec     ax
        add     si,8

        cmp     di,1
        je      mxb100

        ;
        ; Set up the rest with 64K limits
        ;
mxb80:  cmp     ax,0
        je      mxb90

        inc     cx
        cCall   NSetSegmentDscr,<si,cx,dx,0,0FFFFh,STD_DATA>
        dec     ax
        add     si,8
        jmp     mxb80

        ;
        ; Set up the last one with the remaining limit
        ;
mxb90:  inc     cx
        cCall   NSetSegmentDscr,<si,cx,dx,0,bx,STD_DATA>
        add     si,8

        ;
        ; Were we successfull?
        ;
mxb100: cmp     Success,1
        jne     mxb130

        clc
        pop     dx
        pop     cx
mxb110: pop     bx
        pop     es
        mov     sp,bp
        pop     bp
        ret

        ;
        ; We had an error, not enough selectors.  Figure out how large
        ; it could have been
        ;
mxb120: xor     dx,dx
        xor     cx,cx
        mov     dl,[cselBlkData]
        dec     dl                              ; don't count header sel
        mov     ax,8
        add     sp,4                            ; pop cx,dx
        stc
        jmp     mxb110

        ;
        ; We had an error calling xms.  Get the largest block
        ;
mxb130: pmxmssvc 08h
        mov     cx,ax                           ; convert to bytes
        mov     dx,ax
        shl     cx,10
        shr     dx,6
        mov     ax,8
        add     sp,4
        stc
        jmp     mxb110

        ;
        ; We had an error, invalid selector
        ;
mxb140: mov     ax,9
        add     sp,4
        stc
        jmp     mxb110

ModifyXmemBlock endp
        ret


;
; -------------------------------------------------------
        subttl  Utility Routines
        page
; -------------------------------------------------------
;               UTIITY ROUTINES
; -------------------------------------------------------

; -------------------------------------------------------
;   CalculateMaximumSegmentSpace -- This function will see if there
;                            are enough free segments available
;                            to expand a block of memory.
;                            If not it returns with carry set
;                            and the maximum space available.
;
;   Input;  ES      - segment selector for the data block to modify
;           CX      - low word of new desired size
;           DX      - high word of new desired size
;
;   Output  CX      - / If CY set: The maximum available
;           DX      - \ If CY not set: Unchanged
;   Errors: CY set if unable to accomplish the request.
;           CX,DX will be set to the largest possible size that the
;           block could be modified to have
;   Uses:   AX,CX,DX,Flags

        assume  ds:DGROUP,es:MEMHDR
        public  CalculateMaximumSegmentSpace

CalculateMaximumSegmentSpace:
        push    bx
        push    es
        xor     bx,bx
        mov     bl,cselBlkData      ; Allocated selector count
        shl     bx,3                ; Convert to byte offset
        add     bx,selBlkData       ; Add starting data selector offset
        and     bx,SELECTOR_INDEX   ; Treat it as a GDT selector
        mov     es,selGDT           ; Base the global descriptor table
        assume  es:NOTHING          ;

; Count through the immediately following selectors

cmssLoop:
        cmp     bx,word ptr bpgdt       ; Off the end of the GDT ?
        ja      cmssOutOfSelectors      ; Yes - Jump
if 0
        cmp     es:[bx].arbSegAccess,0  ; Is the next selector free ?
else
        cmp     word ptr es:[bx].arbSegAccess,0 ; Is the next selector free ?
endif
        jne     cmssOutOfSelectors      ; No - jump

; Point to the next selector

        add     bx,8                ; Increment to next selector
        jnc     cmssLoop            ; Try the next one

; BX points to the first following selector which is not free

cmssOutOfSelectors:
        pop     es                  ; Recover block header segment
        assume  es:MEMHDR
        push    dx                  ; Subtract base selector address
        mov     dx,selBlkData
        and     dx,SELECTOR_INDEX
        sub     bx,dx
        pop     dx
        shr     bx,3                ; Number of contiguous selectors available
        cmp     bx,dx               ; Is it enough ?
        jb      cmssNotEnough       ; No - jump
        jne     cmssOK              ; Yes - jump
        or      cx,cx               ; Don't know - Look at less significant part
        jnz     cmssNotEnough       ; Yes it will fit after all - jump

; Not enough free selectors

cmssNotEnough:
        mov     dx,bx               ; Put max available in CX&DX
        xor     cx,cx               ;
        stc                         ; Flag error
        jmp     short cmssExit      ; Leave
;
; There are enough selectors to satisfy the request
cmssOK:
        clc                         ; Reset the error flag
;
; All Done
cmssExit:
        pop     bx                  ; Restore registers
        ret                         ; CalculateMaximumSegmentSpace

; -------------------------------------------------------
;   CalculateMaximumSpace -- This function will see if there
;                            is room to expand a block of memory.
;                            If not it returns with carry set
;                            and the maximum space available.
;
;   Input:  AL      - check selectors yes/no flag - 0 = yes, !0 = no
;           ES      - segment selector for the data block to modify
;           CX      - low word of new desired size
;           DX      - high word of new desired size
;   Output: AX      - Strategy used: 0 = expand into same block
;                                    1 = expand into next block
;                                    2 = expand into next and previous blocks
;                                    3 = allocate a new block and transfer data
;                                    4 = move lower and higher blocks
;           CX      - / If CY set: The maximum available
;           DX      - \ If CY not set: Unchanged
;   Errors: CY set if unable to accomplish the request.
;           CX,DX will be set to the largest possible size that the
;           block could be modified to have
;   Uses:   AX,CX,DX,Flags

        assume  ds:DGROUP,es:MEMHDR
        public  CalculateMaximumSpace

CalculateMaximumSpace   proc    near

        push    bp
        mov     bp,sp
        push    es
        push    cx                      ; Save required length
        push    dx
        push    ax                      ; Save segment/selector flag

        mov     cx,word ptr [cbBlkLen]  ; Load present length
        mov     dx,word ptr [cbBlkLen+2] ;
        mov     ax,0                    ; Load strategy code
        cmp     dx,word ptr [bp - 6]    ; Compare available with needed
        jb      cms90                   ; Jump if it doesn't fit
        ja      cmsOKa                  ; Jump if it fits
        cmp     cx,word ptr [bp - 4]    ;
        jb      cms90                   ;
cmsOKa:
        jmp     cmsOK

; There is not enough room in the current block. See if the following
; one is free

cms90:
        mov     es,[selBlkNext]         ; Point to the following block
        cmp     [selBlkOwner],0         ; Is it in use?
        jnz     cmsFail                 ; Yes - cannot use it
        add     cx,CB_MEMHDR            ; Add the header of next block
        adc     dx,0
        add     cx,word ptr [cbBlkLen]  ; Add the body of the next block
        adc     dx,word ptr [cbBlkLen+2];
        mov     ax,1                    ; Load strategy code
        cmp     dx,word ptr [bp - 6]    ; Compare available with needed
        ja      cmsOK                   ; Jump if it fits
        cmp     cx,word ptr [bp - 4]    ;
        jae     cmsOK                   ;

; Cannot expand. The max available is in CX&DX

cmsFail:
        add     sp,6                    ; Throw away requested size, and strat
        pop     es                      ; Point to original header

        cmp     byte ptr [bp-8],0       ; Should we check the # of
        jz      @f                      ;   available selectors?
        stc
        jmp     short cmsExit
@@:
        call    CalculateMaximumSegmentSpace    ;Check for enough selectors
        stc                                     ;Flag error
        jmp     short cmsExit

; Expand will succeed. Strategy number is in ax

cmsOK:
        add     sp,2                    ; discard strategy
        pop     dx                      ; Restore requested size
        pop     cx                      ;
cms900:
        pop     es                      ; Point to original header

        cmp     byte ptr [bp-8],0       ; Should we check the # of
        jz      @f                      ;   available selectors?
        clc
        jmp     short cmsExit
@@:
        call    CalculateMaximumSegmentSpace    ;Check for enough selectors

cmsExit:
        pop     bp
        ret

CalculateMaximumSpace   endp


; -------------------------------------------------------
;
;
; CombineFreeBlocks -- This routine is used when freeing
;   an extended memory block.  It will examine the previous
;   and following blocks to see if they are free also, and
;   combine them into a single larger block if so.
;
;   Input:  AX      - selector of segment descriptor for the block
;   Output: AX      - selector for new free memory block
;   Errors: none
;   Uses:   AX, ES modified, all other registers preserved
;
;   NOTE:   This routine may free the segment descriptor for the
;           entry block as part of the process of combining blocks.
;           The original entry value may no longer be used after
;           calling this routine.  If access to the block header
;           for the free segment is still needed, you must use
;           the value returned in AX to access it.

        assume  ds:DGROUP,es:NOTHING,ss:NOTHING
        public  CombineFreeBlocks

CombineFreeBlocks:
        push    bx
        push    dx
;
        mov     bx,ax           ;save entry value in BX
;
; Look at the previous block and see if it is free.
        mov     es,bx           ;look at block header of entry block
        assume  es:MEMHDR
        mov     es,[selBlkPrev] ;look at block header of previous block
        cmp     [selBlkOwner],0 ;is it in use?
        jnz     cfrb30          ;if so, continue on
;
; The previous memory block is free.  We need to combine it with the
; current one.
        mov     ax,bx
        call    SpliceBlocks
        mov     bx,es
;
; Look at the following block and see if it is free.
cfrb30: mov     es,bx           ;look at the current lower free block
        assume  es:MEMHDR
        mov     es,[selBlkNext] ;look at the following block
        cmp     [selBlkOwner],0 ;is it in use?
        jnz     cfrb90
;
; The following memory block is free.  We need to combine it with the
; current one.
        mov     ax,es
        mov     es,bx
        call    SpliceBlocks
;
; All done
cfrb90: mov     ax,bx
        pop     dx
        pop     bx
        ret

; -------------------------------------------------------
;   SpliceBlocks    -- This routine is used by CombineFreeBlocks
;       to perform the actual combination of two adjacent free
;       blocks.  The space occupied by the upper block is assigned
;       to the lower block, and the upper block is then eliminated.
;
;   Input:  AX      - selector to the upper block
;           ES      - selector to the lower block
;   Output: none
;   Errors: none
;   Uses:   AX, DX modified

        assume  ds:DGROUP,es:NOTHING,ss:NOTHING

SpliceBlocks:
        push    ds
        mov     ds,ax
        assume  ds:NOTHING          ;DS points at upper block
                                    ;ES points at lower block
        push    ax
;
        mov     dx,word ptr ds:[cbBlkLen]
        mov     ax,word ptr ds:[cbBlkLen+2]
        add     dx,CB_MEMHDR
        adc     ax,0
        add     word ptr es:[cbBlkLen],dx
        adc     word ptr es:[cbBlkLen+2],ax
        mov     ax,ds:[selBlkNext]
        mov     es:[selBlkNext],ax
        mov     ds,ax           ;DS points at block following entry block
        mov     ds:[selBlkPrev],es
;
        pop     ax
        pop     ds

        call    FreeSelector        ;release the segment descriptor for the
                                    ; upper block
        ret

; -------------------------------------------------------
;   FindFreeBlock   -- This function will search the extended
;       memory heap looking for a free memory block of at
;       least the requested size.
;
;   Input:  CX      - low word of requested block size
;           DX      - high word or requested block size
;   Output: AX      - selector for the block header of the free block
;           cbHeapBlkMax    - size of largest free block found
;           cbHeapFreeSpace - total free space seen
;   Errors: Returns CY set if large enough free block not found.
;           If that happens, then cbHeapBlkMax contains the size of the
;           largest free block, and cbHeapFreeSpace contains the total
;           of all free blocks.
;   Uses:   AX modified, all other registers preserved.

        assume  ds:DGROUP,es:NOTHING,ss:NOTHING
        public  FindFreeBlock

FindFreeBlock:
        push    cx
        push    dx
        push    es
;
        mov     word ptr [cbHeapBlkMax],0
        mov     word ptr [cbHeapBlkMax+2],0
        mov     word ptr [cbHeapFreeSpace],0
        mov     word ptr [cbHeapFreeSpace+2],0
;
        cmp     selHeapStart,0      ;
        jz      ffrb80              ; No heap - no allocate - jump
        mov     es,selHeapStart     ; ES points at the beginning of the heap
        assume  es:MEMHDR
;
; Is the current memory block free?
ffrb20:
        cmp     selBlkOwner,0           ;if the block isn't free, try the next one
        jnz     ffrb26
;
; Accumulate size into free space count.
        mov     ax,word ptr [cbBlkLen]
        add     word ptr [cbHeapFreeSpace],ax
        mov     ax,word ptr [cbBlkLen+2]
        adc     word ptr [cbHeapFreeSpace+2],ax
;
; Update our view of what the largest free block is.
        mov     ax,word ptr [cbBlkLen+2]
        cmp     ax,word ptr [cbHeapBlkMax+2]
        jc      ffrb26
        jnz     ffrb22
        mov     ax,word ptr [cbBlkLen]
        cmp     ax,word ptr [cbHeapBlkMax]
        jc      ffrb26
ffrb22: mov     ax,word ptr [cbBlkLen+2]
        mov     word ptr [cbHeapBlkMax+2],ax
        mov     ax,word ptr [cbBlkLen]
        mov     word ptr [cbHeapBlkMax],ax
;
; Check the size of this memory block to see if it is large enough.
ffrb24: cmp     dx,word ptr [cbBlkLen+2]
        jc      ffrb40
        jnz     ffrb26
        cmp     cx,word ptr [cbBlkLen]
        jna     ffrb40
;
; Go to the next block in the heap
ffrb26:
        cmp     selBlkNext,0                ; End of chain?
        jz      ffrb80                      ; Yes - jump
        mov     es,[selBlkNext]
        jmp     ffrb20
;
; Found a free block that is large enough
ffrb40: mov     ax,es
        clc
        jmp     short ffrb90
;
; Didn't find one, return error.
ffrb80: stc
;
; All done
ffrb90: pop     es
        pop     dx
        pop     cx
        ret

; -------------------------------------------------------
;   FreeSpace   -- This function is used to find out how
;       much free space there is in the heap, plus that
;       which is potentially available from an XMS driver.
;
;   Input:  None
;   Output: AX:DX -- Total free space
;           BX:CX -- Largest free block
;   Errors: none
;   Uses:   AX, all else preserved
;
;   History:10/09/90 -- earleh wrote it.
;
cProc FreeSpace,<NEAR,PUBLIC>,
cBegin

        ;
        ; Call xms to get free memory space
        ;
        pmxmssvc 08h

        ;
        ; convert to bytes
        ;
        mov     bx,dx
        mov     dx,ax
        mov     cx,bx
        shl     dx,10
        shr     ax,6
        shl     cx,10
        shr     bx,6

        ;
        ; account for heap header
        ;
        sub     dx,CB_MEMHDR
        sbb     ax,0
        sub     cx,CB_MEMHDR
        sbb     bx,0

        ret
cEnd

; -------------------------------------------------------
;   SplitBlock  -- This function will split the specified
;       memory block into two blocks, with the first one
;       being the specified size, and the second one containing
;       the remaining space.
;
;   Input:  AX      - selector of block header for memory block to split
;           CX      - low word of requested size
;           DX      - high word of requested size
;   Output: none
;   Errors: none
;   Uses:   AX, all else preserved

        assume  ds:DGROUP,es:NOTHING,ss:NOTHING
        public  SplitBlock

SplitBlock:
        push    bx
        push    cx
        push    dx
        push    si
        push    es
;
        mov     es,ax
        assume  es:MEMHDR
;
; We are going to need a segment descriptor for the remainder block
; when we do the split.  Make sure that we can allocate the selector
; now, before we change anything.
        call    AllocateSelector
        jnc     spbl20              ;
        jmp     spbl90              ;get out if error
spbl20: mov     si,ax               ;save selector in SI for later
;
; Adjust the size of the current block and figure the size of the
; remainder.
        xchg    cx,word ptr [cbBlkLen]      ;store the new block length
        xchg    dx,word ptr [cbBlkLen+2]    ; and get the old block length
        push    cx                          ;Save the original size for recovery
        push    dx
        sub     cx,word ptr [cbBlkLen]      ;compute the amount of space
        sbb     dx,word ptr [cbBlkLen+2]    ; remaining in the block
        sub     cx,CB_MEMHDR        ;also account for the new block header
        sbb     dx,0
        jc      spbl25              ;Jump if not enough memory to split
        jnz     spbl30              ;if there is some remaining memory, then
        cmp     cx,0                ; we have additional work to do
        jnz     spbl30
;
; There is no remaining memory.  Free the selector that we allocated
; earlier and then get out.
spbl25: mov     ax,si
        pop     dx                  ;Recover old size
        pop     cx
        mov     word ptr [cbBlkLen],cx ;restore the old block length
        mov     word ptr [cbBlkLen+2],dx
        call    FreeSelector
        jmp     spbl90
;
; We need to create a segment descriptor for the new memory block header
; for the remainder block.
spbl30: add     sp,4                ;Dispose of unneeded recovery data
        push    cx                  ;save memory block length for later
        push    dx
        mov     ax,es               ;selector of current memory header
        call    GetSegmentAddress
        mov     cx,CB_MEMHDR - 1
        add     dx,word ptr [cbBlkLen]  ;bump by size of current block
        adc     bx,word ptr [cbBlkLen+2]
        add     dx,CB_MEMHDR        ;plus size of block header
        adc     bx,0
        mov     ax,si
        cCall   NSetSegmentDscr,<ax,bx,dx,0,cx,STD_DATA>
        pop     dx
        pop     cx
;
; Now, we need to build a new memory block header for the remainder of
; the original block.  CX,DX has the size of the new block.
        push    ds
;
        mov     ds,si
        assume  ds:NOTHING          ;DS points at new block header
        assume  es:NOTHING          ;ES points at old block header
        xor     ax,ax
        mov     ds:[fBlkStatus],al
        mov     ds:[cselBlkData],al
        mov     ds:[selBlkData],ax
        mov     ds:[selBlkOwner],ax
        mov     ds:[selBlkHdr],ds
        mov     word ptr ds:[cbBlkLen],cx
        mov     word ptr ds:[cbBlkLen+2],dx
        mov     ds:[selBlkPrev],es
        mov     ax,es:[selBlkNext]
        mov     ds:[selBlkNext],ax
        mov     es:[selBlkNext],si
        mov     ds,ax               ;DS points at following block
        mov     ds:[selBlkPrev],si
        cmp     ds:[selBlkOwner],0  ;is it in use?
;
        pop     ds
        assume  ds:DGROUP
        jnz     spbl90              ;Jump if the following block is not free
;
; The following memory block is free.  We need to combine it with the
; current one.
        mov     es,si
        call    SpliceBlocks
;
; All done
spbl90: pop     es
        pop     si
        pop     dx
        pop     cx
        pop     bx
        ret

; -------------------------------------------------------
;   GetBlockHeader  -- This function will return the selector
;       for the block header of the specified memory data
;       block.
;
;   Input:  AX      - selector of the data block
;   Output: AX      - selector of the block header
;   Errors: returns CY set if the entry selector doesn't point
;           to a valid data block
;   Uses:   AX, all other registers preserved
;           Modifies the descriptor for segment SEL_SCR0

        assume  ds:DGROUP,es:NOTHING,ss:NOTHING

GetBlockHeader:
        push    bx
        push    cx
        push    dx
        push    es

        push    ax                  ;save entry value for later
        mov     bx,SEL_SCR0 or STD_TBL_RING
        call    DupSegmentDscr      ;duplicate input descriptor to scratch
        mov     ax,bx
        call    GetSegmentAddress
        sub     dx,CB_MEMHDR        ;backup to supposed header
        sbb     bx,0
        mov     cx,CB_MEMHDR-1      ;  has the proper limit!
        cCall   NSetSegmentDscr,<ax,bx,dx,0,cx,STD_DATA>

        mov     es,ax
        assume  es:MEMHDR

        pop     cx                  ;recover data block selector
        cmp     cx,[selBlkData]     ;does the header point back to the
        jz      gtbh50              ; data block.

        stc                         ;if not, then what were given wasn't a
        jmp     short gtbh90        ; selector to a memory block

gtbh50: mov     ax,[selBlkHdr]      ;get the real selector to the header

gtbh90: pop     es
        pop     dx
        pop     cx
        pop     bx
        ret

; -------------------------------------------------------
;   DisposeBlock        -- This routine will free all segment
;       descriptors associated with the specified memory block,
;       and remove the block header from the heap list.
;
;   Input:  AX      - selector of block to dispose
;   Output: none
;   Errors: none
;   Uses:   All registers preserved
;
; NOTE: This routine frees selectors for the specified memory block.
;       If this selector is already in a segment register when this
;       routine is called a GP fault will occur it that segment
;       register is later saved or restored.  Also, any references to
;       memory using that segment register might destroy random data
;       in memory.

        assume  ds:DGROUP,es:NOTHING,ss:NOTHING
        public  DisposeBlock

DisposeBlock:
        push    cx
        push    si
        push    di
        push    es
;
; Remove the block header from the heap list.
        mov     es,ax               ;header selector to ES
        mov     si,es:[selBlkNext]  ;SI points to following block
        mov     di,es:[selBlkPrev]  ;DI points to previous block
        mov     es,di               ;ES points to previous block
        mov     es:[selBlkNext],si  ;previous block points to following block
        mov     es,si               ;ES points to following block
        mov     es:[selBLkPrev],di  ;following block points back to prev. block
;
; Free any data block descriptors associated with this memory object.
        mov     es,ax
        cmp     es:[selBlkOwner],0
        jz      dspb40
        xor     ch,ch
        mov     cl,es:[cselBlkData]
        mov     ax,es:[selBlkData]
        and     ax,SELECTOR_INDEX   ;treat as GDT FreeSelectorBlock
        call    FreeSelectorBlock
;
; Free the descriptor for the block header.
dspb40: mov     ax,es
        pop     es
        call    FreeSelector
;
; All done
dspb90: pop     di
        pop     si
        pop     cx
        ret

; -------------------------------------------------------
;   SetUpDataDescriptors -- This function will initialize
;       all of the data segment descriptors associated with
;       a block. Those that should point to data will be
;       initialized accordingly. Those corresponding to
;       addresses beyond the allocated data will be initialized
;       as not present. If insufficient data segment
;       descriptors have been allocated, an attempt
;       will be made to allocate them. If this fails an error
;       occurs.
;
;   Input:  AX  -   Selector of the header segment.
;
;   Output: None
;
;   Errors: Returns CY set if not enough data segment descriptors
;           are available.
;
;   Uses:   Segment descriptor table is modified. Possibly the
;           data segment count in the header is modified.
;           No registers affected.
;
    assume  ds:DGROUP,es:NOTHING,ss:NOTHING

    public  SetUpDataDescriptors

SetUpDataDescriptors:
        push    es
        push    si
        push    bx
        push    cx
        push    dx
        push    ax

        mov     es,ax
        mov     si,ax               ;save header segment for dup
        assume  es:MEMHDR
        mov     bx,selBlkData       ;Starting data selector index
        and     bx,SELECTOR_INDEX
        xor     cx,cx
        mov     cl,cselBlkData      ;Load segment count
        inc     cx
        mov     dx,word ptr es:[cbBlkLen+2] ;Full data segment count
        mov     es,selGDT
        assume  es:NOTHING
sudd30:
        or      dx,dx                   ;is this the last segment?
        jnz     sudd35
        pop     es                      ;Get the header segment
        push    es
        cmp     word ptr es:[cbBlkLen],0 ;Test partial length
        mov     es,selGDT               ;Rebase the global descriptor table
        jnz     sudd35                  ;Jump if the partial length is 0
        jmp     sudd60
sudd35: loop    short sudd40            ;Out of segments?
        xchg    bx,ax
        call    RemoveFreeDescriptor    ;Attempt to get segment
        xchg    bx,ax
        jnc     sudd37
        jmp     sudd80
sudd37: inc     cx
sudd40: mov     ax,si
        call    DupSegmentDscr          ; replicate the previous seg (in si)
        mov     si,bx

; Set the DPL of allocated blocks to user DPL

ifndef WOW
        mov     es:[si].arbSegAccess,STD_DATA
else
        push    ax
        xor     ax,ax
        mov     ah,es:[si].cbLimitHi386
        or      ax,STD_DATA
        cCall   NSetSegmentAccess,<si,ax>
        pop     ax
endif

; Increment the segment start address past the previous one

        cmp     es:[si].cbLimit,CB_MEMHDR-1  ; Is this the first block?
        jne     sudd41                       ; No - jump
ifndef WOW
        add     es:[si].adrBaseLow,CB_MEMHDR ; bump segment start to point
        adc     es:[si].adrBaseHigh,0        ;past the block header
else
        push    ax
        push    dx
        mov     ax,es:[si].adrBaseLow
        mov     dl,es:[si].adrBaseHigh
        mov     dh,es:[si].adrbBaseHi386
        add     ax,CB_MEMHDR
        adc     dx,0
        cCall   NSetSegmentBase,<si,dx,ax>
        pop     dx
        pop     ax
endif
        jmp     short sudd42
ifndef WOW
sudd41: add     es:[si].adrBaseHigh,1        ; bump segment start by 64k
else
sudd41: push    ax
        push    dx
        mov     ax, es:[si].adrBaseLow
        mov     dl, es:[si].adrBaseHigh
        mov     dh, es:[si].adrbBaseHi386
        inc     dx
        cCall   NSetSegmentBase,<si,dx,ax>
        pop     dx
        pop     ax
endif

; Set the segment length

sudd42: add     bx,8                    ; Offset of next segment descriptor
        or      dx,dx                   ; Was this a partial segment?
        jnz     sudd45                  ; jump if not
;
; We are at the partial (<64k) chunk at the end.
        pop     es                      ;Get the header segment
        push    es
        mov     dx,word ptr es:[cbBlkLen] ; Segment length
        dec     dx                      ; Last legal offset
        mov     es,selGDT
        mov     es:[si].cblimit,dx      ; Store in segment descriptor
ifdef WOW
        cCall   NSetSegmentLimit,<si>
endif
        jmp     sudd60                  ; All valid data segs now exist
sudd45:
        mov     es:[si].cbLimit,0FFFFh  ;set segment limit at 64k
ifdef WOW
        cCall   NSetSegmentLimit,<si>
endif
        dec     dx                      ; On to the next data segment
        jmp     sudd30
;
; Invalidate remaining segments
sudd50: mov     ax,si
        call    DupSegmentDscr          ; replicate the previous one and
        mov     si,bx
if 0
        mov     es:[si].arbSegAccess,ARB_DATA0NP ; Invalidate the segment
else
        mov     word ptr es:[si].arbSegAccess,0 ; Invalidate the segment
endif
        add     bx,8                    ; Offset of next segment descriptor
sudd60: loop    sudd50
;
; All done
        clc
        jmp     short sudd90
;
; Allocate of a segment descriptor failed.
sudd80: pop     ax                      ;Header Segment
        mov     es,ax                   ;
        mov     dx,es:selBlkData        ;Calculate # segments
        and     dx,SELECTOR_INDEX       ;  successfully allocated
        sub     bx,dx
        shr     bx,3                    ;Succesfully allocated
        mov     es:[cselBlkData],bl     ;Store new value
        mov     dx,bx                   ;Return max allocatable space
        mov     cx,0                    ;
        add     sp,4                    ;Pop redundant stack data
        stc                             ;Flag error
        jmp     short sudd100           ;Join common exit code
;
; Tidy up
sudd90: pop     ax                      ;Header segment
        mov     es,ax
        mov     dx,es:selBlkData        ;Calculate # segments
        and     dx,SELECTOR_INDEX
        sub     bx,dx
        shr     bx,3
        mov     es:[cselBlkData],bl     ;Store new value
        pop     dx
        pop     cx
sudd100:
        pop     bx
        pop     si
        pop     es
        ret
;
ifdef MD

; -------------------------------------------------------
;   CheckXmemHeap    -- This routine will check the integrity
;                       of the chain of block headers.
;
;   Input:  NONE
;   Output: None
;   Errors: INT3 If an error is found
;   Uses:   All registers preserved
;
;   NOTE:   This routine must be called in protected mode.

        assume  ds:DGROUP,es:NOTHING,ss:NOTHING
        public  CheckXmemHeap

CheckXmemHeap:
        push    bp
        mov     bp,sp
        pushf
        push    bx
        push    cx
        push    dx
        push    es
        xor     cx,cx
        mov     dx,cx
        mov     bx,selHeapStart ; Load start of chain
cxmh100:
        or      bx,bx           ;
        jnz     cxmh101
        jmp     cxmh200         ;
cxmh101:
        mov     es,bx           ;
        assume  es:MEMHDR       ;
        cmp     selBlkHdr,bx    ;
        je      cxmh110         ;
        int     3
        jmp     cxmh200
;;        Debug_Out "CheckXmemHeap: Block header does not point to block header!"
cxmh110:
        cmp     selBlkPrev,dx   ;
        je      cxmh120         ;
        int     3
        jmp     cxmh200
;;        Debug_Out "CheckXmemHeap: Previous block does not point to previous block!"
cxmh120:
        mov     dx,bx           ;
        mov     bx,selBlkNext   ;
        loop    cxmh100         ;
        int     3
;;        Debug_Out "CheckXmemHeap: Can't find end of arena chain!"
cxmh200:
        pop     es
        pop     dx
        pop     cx
        pop     bx
        popf
        pop     bp
        ret
endif

; -------------------------------------------------------
;   FreeMemByOwner -- This routine frees all of the mem
;       blocks that belong to the specified owner.
;
;   Input:  bx = owner
;   Output: none
;   Uses:   ax
;
;  Note:  We have to start at the beginning of the heap
;       after we free a block, because the free may
;       coalesce the heap, and the selector for the
;       next block may no longer be correct.  We know we
;       are done when we have traversed the entire heap,
;       and not found a block to free.

        assume ds:DGROUP, es:nothing, ss:nothing
        public FreeMemByOwner

FreeMemByOwner:
        push    es

        cmp     bx,0                    ; owner is free owner??
        je      fbo40

; First traverse xmem heap
        push    selHeapStart
        mov     ax,selHiHeapStart
        mov     selHeapStart,ax

        call    FreeHiMemByOwnerW
        call    FreeLowMemByOwnerW
        pop     selHeapStart


fbo40:  pop     es
        ret

FreeHiMemByOwnerW:
fhbow10: mov     ax,selHiHeapStart
fhbow20: cmp     ax,0
        jne     fhbow25
        ret

fhbow25: mov     es,ax
        assume es:MEMHDR
        cmp     selBlkOwner,bx
        jne     fhbow30

        mov     ax,selBlkDAta
        push    0
        pop     es
        call    FreeXmemBlock
        jmp     fhbow10                   ; check again.

fhbow30: mov     ax,selBlkNext           ; check next block
        jmp     fhbow20

FreeLowMemByOwnerW:
flbow10: mov     ax,selLowHeapStart
flbow20: cmp     ax,0
        jne     flbow25
        ret

flbow25: mov     es,ax
        assume es:MEMHDR
        cmp     selBlkOwner,bx
        jne     flbow30

        mov     ax,selBlkData
        push    0
        pop     es
        call    FreeLowBlock
        jmp     flbow10                   ; check again.

flbow30: mov     ax,selBlkNext           ; check next block
        jmp     flbow20
; -------------------------------------------------------
; -------------------------------------------------------
; -------------------------------------------------------
; -------------------------------------------------------
; -------------------------------------------------------


if 0

dead code section


AllocateLowBlock  proc  near

        push    bx
        push    si
        push    di
        push    es
        push    selHeapStart            ;save current heap start selector

        push    selLowHeapStart         ;  and select the low heap
        pop     selHeapStart

; We assume the low memory heap is for fixed objects (like network buffers)
; that should not be moved.  Therefore, we never compact the low heap, just
; combine free blocks when they're released.

        call    FindFreeBlock           ;try to find a large enuff free block
        jc      alb80

        mov     es,ax
        assume  es:MEMHDR

        mov     ax,dx                   ;set ax = # selectors needed for block
        jcxz    @f
        inc     ax
@@:
        mov     di,ax                   ;keep selector count for later
        call    AllocateSelectorBlock
        jc      alb80

        mov     si,ax                   ;save starting selector for later
        mov     ax,es
        call    SplitBlock              ;split the current block into 2 parts
        mov     ax,selPSPChild
        mov     selBlkOwner,ax
        mov     selBlkData,si           ;  the starting selector of this block

        or      selBlkData,SELECTOR_TI or SELECTOR_PL_DX
                                        ;mark sel as LDT/DOSX ring

        mov     ax,di                   ;set # selectors for this block
        mov     cselBlkData,al

        mov     ax,es                   ;initialize selector descriptors
        call    SetUpDataDescriptors
        jnc     alb70

; We can't do the allocate for some reason. Clean up

        mov     ax,si                   ;we can't continue, so free the
        mov     cx,di                   ;  set of selectors that we allocated
        call    FreeSelectorBlock       ;  but can't use

        xor     cx,cx                   ;return that there is no memory
        mov     dx,cx                   ;  available
        jmp     short alb88

; Normal completion, setup initial selector and exit

alb70:
        clc                                     ;Good return
        mov     ax,si                           ;Return first data selector
        or      al,SELECTOR_TI or SELECTOR_PL_DX;  as LDT and DOSX ring
        jmp     short alb90

; No block large enough, or not enough selectors, return max size block
; and null selector

alb80:
        mov     cx,word ptr [cbHeapBlkMax]
        mov     dx,word ptr [cbHeapBlkMax+2]
alb88:
        xor     ax,ax
        stc

alb90:
        pop     selHeapStart            ;restore current heap
        pop     es
        pop     di
        pop     si
        pop     bx

        ret

AllocateLowBlock  endp
endif

if 0
dead code
FreeLowBlock    proc    near

        push    selHeapStart            ;save current heap start selector
        push    selLowHeapStart         ;  and select the low heap
        pop     selHeapStart

        call    FreeXmemBlock           ;call this guy to really do it

        pop     selHeapStart            ;restore previous heap start

        ret

FreeLowBlock    endp
endif

if 0
InitLowHeap proc
Now we allocate directly from dos

        push    ax
        push    bx
        push    dx

; Allocate the memory from DOS, or fail

        mov     bx,ax                   ;convert K to paragraphs
        shl     bx,6
        add     bx,3                    ;add in overhead for sentinels & header

        .errnz  CB_MEMHDR - 16

        SwitchToRealMode
        sti


; check to see if there is a switcher around.

        mov     fDosErr,0               ;assume no allocation error
        push    ax
        push    bx
        push    es
        push    di                      ;save
        mov     ax,SWAPI_DETECT_SWITCHER;SWAPI function code
        xor     bx,bx                   ;requred by above function
        mov     es,bx
        mov     di,bx                   ;es:di set to 0:0
        int     2fh                     ;make swapi call
        mov     ax,es                   ;is there a switcher around ?
        or      ax,di                   ;es:di will be non 0 then
        pop     di
        pop     es
        pop     bx
        pop     ax                      ;restore
        jnz     ILH_AllocFromSwitcher   ;switcher is around

ILH_AllocFromDos:

; there is no switcher, allocate memory from DOS

        mov     ah,48h                  ;alloc memory function

        pushf                           ;invoke DOS to alloc
        sub     sp,8                    ; make room for stack frame
        push    bp
        mov     bp,sp
        push    es
        push    ax

        xor     ax,ax
        mov     es,ax
        mov     [bp + 8],cs
        mov     [bp + 6],word ptr (offset ILH_10)
        mov     ax,es:[21h*4]
        mov     [bp + 2],ax
        mov     ax,es:[21h*4 + 2]
        mov     [bp + 4],ax
        pop     ax
        pop     es
        pop     bp
        retf
ILH_10: jnc     ILH_BackToPM            ;go back to PM
        mov     fDosErr,1
        jmp     short ILH_BackToPM      ;go back to PM

ILH_AllocFromSwitcher:

        push    bp
        sub     sp,4                    ;space for a far address
        mov     bp,sp                   ; bp -> [addrlow][addrhi][bp]
        push    bx
        push    es                      ;save

        mov     ax,SWAPI_ALLOC_MEM_CALLIN;code to get global memory call in
        int     2fh                     ;make SWAPI call
        or      ax,ax                   ;was it successful ?
        jz      ILH_CallInObtained

;;This line has been added because, it seems that --- sub sp,4 ---
;;will not be compensated for if win.com doesn't have global memory
;;allocator. and dos alloc has to be used.

        add     sp,4                    ;EARLEH/AMITC should code review this.

;;

        stc                             ;no global memory support
        jmp     short ILH_BalanceStack

ILH_CallInObtained:

        jnz     ILH_AllocFromDos        ;global memory support not there
        mov     [bp],bx                 ;save low word of call in
        mov     [bp+2],es               ;hiword of call in
        pop     es
        pop     bx                      ;restore
        mov     ax,SWAPI_ALLOC_MEM      ;allocate memory
        call    dword ptr ss:[bp]

ILH_BalanceStack:

; we need to pop off the saved space, can't do 'add sp,4' as that would change
; the state of the carry flag.

        pop     bp                      ;discard space for addrlo
        pop     bp                      ;discard space for addrhi
        pop     bp                      ;restore saved value of bp
        jnc     ILH_BackToPM            ;allocation succeeded

; the switcher cannot allocate global memory, allocate it from DOS. In this
; case the DosX should allow a switch out when asynchronous calls are pending

        mov     [NoAsyncSwitching],1    ;cant switch with async calls pending
        jmp     short ILH_AllocFromDos  ;allocate memory from DOS

ILH_BackToPM:

        push    ax

        SwitchToProtectedMode

        pop     ax
        sti

        cmp     fDosErr,0               ;allocate go okay?
        jnz     ilh95                   ;  no, then exit

; Heap space allocated, build the sentinels and free block

        mov     segLowHeap,ax           ;save segment addr of entire low heap
        mov     hmemHeap,ax

        mov     dsegCurrent,bx          ;flag block as from DOS

        mov     dx,bx                   ;calculate heap size in bytes
        shl     bx,4
        shr     dx,12
        sub     bx,3*CB_MEMHDR          ;  don't count initial headers
        sbb     dx,0
        mov     word ptr cbHeapSize,bx
        mov     word ptr [cbHeapSize+2],dx

        mov     dx,ax                   ;BX:DX -> start of heap
        shl     dx,4
        mov     bx,ax
        shr     bx,12

        xor     ax,ax                   ;low heap is beinig started now
        mov     selHeapStart,ax

        call    StructureHeap           ;build sentinels & free block

; Setup global variables for later use

        mov     ax,selHeapStart
        mov     selLowHeapStart,ax

        mov     ax,selHiHeapStart       ;do some clean up for the next guy
        mov     selHeapStart,ax

        clc                             ;the sweat smell of success
        jmp     short ilh99

ilh95:  stc                             ;error occured, set carry & exit

ilh99:  pop     dx
        pop     bx
        pop     ax
endif

if 0
dead code
AllocateXmemBlock       proc    near

ifdef MD
        call    CheckXmemHeap
endif
        push    bx
        push    si
        push    di
        push    es

; Get a free memory block to use.

        push    bx                      ;save BL selector alloc flag

        call    FindFreeBlock
        jnc     alxm40
        cCall   AddToXmemHeap
        call    FindFreeBlock

alxm40:
        pop     bx                      ;restore BL alloc flag
        jnc     alxm41                  ;no carry == success
        jmp     alxm80                  ;  otherwise fail the allocation

; We have found a free heap block that is large enough.  Now, we need
; to make sure that we can get a block of selectors large enough to
; be able to handle the block size.

alxm41:
        mov     es,ax               ;save selector of free block
        assume  es:MEMHDR

        or      bl,bl           ;bl == 0 means a selector for each 64k,
        jz      @f              ;bl != 0 means 1 selector at start of blk
        mov     ax,1
        jmp     alxm42
@@:
        mov     ax,dx               ;get high word of block size, which
                                    ; gives us the number of complete
                                    ; 64k chunks in the block

        cmp     cx,0                ;is low word 0
        jz      alxm42              ;if so, AX has number of selectors needed
        inc     ax                  ;if not, we need one more for the partial
                                    ; (<64k) chunk left over.

alxm42: mov     di,ax               ;save count of selectors for later
        call    AllocateSelectorBlock
        jnc     alxm50
        jmp     alxm80

; We have the memory block and we have the segment descriptors allocated,
; so now we need to split the memory block and then initialize the
; segment descriptors to point to the allocated block.

alxm50: mov     si,ax               ;save starting selector for later
        mov     ax,es
        call    SplitBlock          ;split the current block into two parts
;
alxm52: mov     ax,selPSPChild
        mov     selBlkOwner,ax      ;mark the current block as being in use
        mov     selBlkData,si       ;and indicate the starting selector
                                    ; of this block

        or      selBlkData,SELECTOR_TI or SELECTOR_PL_DX
                                    ;mark sel as LDT/DOSX ring

        mov     ax,di
        mov     cselBlkData,al

        cmp     al,1            ;if there is only one selector for the block,
        jnz     alxm53          ;  initialize it here and now

        push    cx              ;save size in CX:DX
        push    dx

        dec     cx              ;set segment limit, assume size is < 64k
        or      dx,dx           ;  so limit is CX - 1
        jz      @f
        mov     cx,0ffffh       ;oops, limit is 64k
@@:
        mov     ax,es           ;get header address in BX:DX
        call    GetSegmentAddress
        add     dx,CB_MEMHDR    ;block starts just past header
        adc     bx,0
        mov     ax,si
        cCall   NSetSegmentDscr,<ax,bx,dx,0,cx,STD_DATA>

        pop     dx
        pop     cx
        jmp     short alxm70

alxm53:
        mov     ax,es               ;Initialize the GDT data block entries
        call    SetUpDataDescriptors
        jnc     alxm70              ;Jump on no error

; We can't do the allocate for some reason. Clean up

alxm51: mov     ax,si               ;we can't continue, so free the
        mov     cx,di               ; set of selectors that we allocated but
        call    FreeSelectorBlock   ; can't use
        xor     cx,cx               ;and return that there is no memory
        mov     dx,cx               ; available
        mov     ax,7
        jmp     short alxm88

; All done

alxm70: clc                                     ;Good return
        mov     ax,si                           ;Return first data selector
        or      al,SELECTOR_TI or SELECTOR_PL_DX;  as LDT and DOSX ring
        jmp     short alxm90

; Error occured, return the error code.

alxm80: mov     cx,word ptr [cbHeapBlkMax]
        mov     dx,word ptr [cbHeapBlkMax+2]
        mov     ax,8
alxm88: stc

; Cleanup

alxm90: pop     es
        pop     di
        pop     si
        pop     bx
ifdef MD
        call    CheckXmemHeap
endif
        ret

AllocateXmemBlock       endp
endif ; NEWHIGHMEM


if 0
dead code
ModifyXmemBlock proc    near
ifdef MD
        call    CheckXmemHeap
endif
        push    es
        push    bx
        push    si
        push    di

        push    bp              ;save BL selector flag on stack
        mov     bp,sp
        sub     sp,2
        mov     [bp-2],bx

; What we have is the selector to the data block.  We need a selector
; for the memory block header.

        call    GetBlockHeader      ;Find the header segment
        jnc     short mxmb14        ;Jump if all OK
        mov     ax,7                ;Set "Control Blocks Damaged" Error
        jmp     mxmb80              ;Jump to error exit

; See if we are shrinking or growing.

mxmb14: mov     es,ax
        assume  es:MEMHDR
        mov     al,byte ptr [bp-2]      ;selector flag to al for CalcMaxSpace
        call    CalculateMaximumSpace
        jnc     mxmb17
        jmp     mxmbNoRoom
mxmb17:
        cmp     ax,0                    ; Test expansion strategy
        je      mxmb50                  ; If strategy 0 - jump

; Strategy 1
; There is enough room to expand into the following block.
; The Header segment is in ES.

mxmb100:
        mov     ax,[selBlkNext]         ;AX = block to be combined
        push    dx                      ;Save register destroyed by following call
        call    spliceblocks            ;Expand into freespace following
        pop     dx                      ;Restore dx

; The block is large enough. The Header segment is in ES.
; It may need to be shrunk.

mxmb50:
        cmp     dx,word ptr es:[cbBlkLen +2]    ;Cmp required and present len
        jne     mxmb51
        cmp     cx,word ptr es:[cbBlkLen]
        je      mxmbGotBlock                    ;Size is correct already - jump

; The block needs to be shrunk. The Header segment is in ES.

mxmb51:
        mov     ax,es                   ; AX = Block header segment
        call    splitblock              ;Remove redundant space

; We have a big enough block. The header segment is in ES. Massage the GDT

mxmbGotBlock:
        mov     ax,es           ; AX = Block header segment

        cmp     cselBlkData,1   ;if there is only one selector for the block,
        jnz     mxmb53          ;  initialize it here and now

        push    cx              ;save size in CX:DX
        push    dx

        dec     cx              ;set segment limit, assume size is < 64k
        or      dx,dx           ;  so limit is CX - 1
        jz      @f
        mov     cx,0ffffh       ;oops, limit is 64k
@@:
        call    GetSegmentAddress       ;get header address in BL:DX
        add     dx,CB_MEMHDR            ;block starts just past header
        adc     bx,0
        mov     ax,selBlkData
        cCall   NSetSegmentDscr,<ax,bx,dx,0,cx,STD_DATA>

        pop     dx
        pop     cx

        jmp     short mxmb90

mxmb53:
        call    SetUpDataDescriptors    ;Map the new memory into
        jnc     mxmb90                  ;the users address space

; There is not enough room to expand. Max available is in CX&DX

mxmbNoRoom:
        mov     ax,8                    ;Memory allocation error
        jmp     short mxmb95

; Error

mxmb80:
        stc
        jmp     mxmb95

; All done

mxmb90: clc
        mov     ax,0                    ;Clear error flag and code

mxmb95: pop     bp                      ; throw away, but don't wreck flags
        pop     bp
        pop     di
        pop     si
        pop     bx
        pop     es

ifdef MD
        call    CheckXmemHeap
endif
ModifyXmemBlock endp
endif ; NEWHIGHMEM


if 0
FreeXmemBlock:
ifdef MD
        call    CheckXmemHeap
endif
        push    cx
        push    es
;
; What we have is the selector to the data block.  We need a selector
; for the memory block header.
        call    GetBlockHeader
        jc      frxb90              ;get out if error
        mov     es,ax
        assume  es:MEMHDR
;
; Free the data selectors used by this memory block.
        xor     ch,ch
        mov     cl,[cselBlkData]
        mov     ax,[selBlkData]
        and     ax,SELECTOR_INDEX   ;treat like GDT entry for FreeSelectorBlk
        call    FreeSelectorBlock
;
; Mark this block as free, and then combine any free blocks that are
; adjacent with it.
        mov     selBlkOwner,0
        mov     cselBlkData,0
        mov     selBlkData,0
        mov     ax,es
        call    CombineFreeBlocks
;
; All done
frxb90: pop     es
        pop     cx
ifdef MD
        call    CheckXmemHeap
endif
        ret
endif

if 0
initxmemheap
        xor     dx,dx
        mov     cx,dx
        mov     selHeapStart,cx
        mov     cx,XMEM_MIN_ALLOC
        shl     cx,10
        mov     dx,XMEM_MIN_ALLOC
        shr     dx,6

        cCall   AddToXmemHeap
endif
DXPMCODE  ends
;
;****************************************************************
        end
