;       SCCSID = @(#)bmp.asm	6.2 92/03/02
SCCSID  equ     "@(#)bmp.asm	6.2 92/03/02"
        PAGE    ,132
TITLE   BMP     Block Management Package

;***    Block Management Package
;
;       IBM/Microsoft Confidential
;
;       Copyright (c) IBM Corporation 1987, 1989
;       Copyright (c) Microsoft Corporation 1987, 1989
;
;       All Rights Reserved
;
;       JGL, Microsoft, 06/86


;***    The Block Management Package is a set of routines to
;       manage a free list of equal sized memory blocks within a segment.
;       BMP only manages the allocation of free blocks off of the free
;       chain, or if necessary the creation of new free blocks (by segment
;       growth).  The caller must manage non-free blocks.  Since the free
;       list is a chain blocks never move within the segment, although
;       the segment, of course, may be movable.
;
;       Since the blocks must fit entirely within a segment
;       this limits the total amount of information managable via BMP
;       to 65K.  Naturally a client can
;       use multiple segments to avoid this limit but the client will
;       have to keep track of which segment contains which block and
;       insure that blocks are returned to the proper segment.
;
;       Blocks must be at least 2 bytes.  Bmp_Init doesn't check this.
;
;       BMP Functions:
;
;           Bmp_Init     -  Initialize a segment
;           Bmp_Get      -  Get a free block
;           Bmp_Free     -  Free a block
;           Bmp_SegSize  -  Initial segment size to allocate for Bmp_Init
;           Bmp_CheckOffset - Check that offset points to a valid record
;           Bmp_SetGrowFunction - Change the function used to grow a segment
;
;       BMP is very fast.  BMP's segments only grow; they never
;       shrink when blocks are returned.


;***    Segment Structure
;
;       BMP maintains segments in the form:
;
;
;  N:0  -------------------------
;       |                       |
;       |  bmp_blksiz           |       WORD
;       |     size of each block|
;       |                       |
;       |-----------------------|
;       |                       |
;       |  bmp_freh             |       WORD
;       |     offset of first   |
;       |     free segment      |
;       |                       |
;       |-----------------------|
;       |                       |
;       |  bmp_size             |       WORD
;       |     current size of   |
;       |     this segment      |
;       |                       |
;       |-----------------------|
;       |                       |
;       |  bmp_handle           |
;       |     memory object     |       WORD
;       |     handle            |
;       |                       |
;       |-----------------------|
;       |                       |
;       |  bmp_grodelt          |
;       |     bmp_blksiz * N    |
;       |     where "N" = the   |       WORD
;       |     number of blocks  |
;       |     to grow each time |
;       |                       |
;       |-----------------------|
;       |                       |
;       |  bmp_sem              |
;       |     used to protect   |
;       |     the realloc of    |       DWORD
;       |     bmp segment from  |
;       |     other threads     |
;       |                       |
;       |-----------------------|
;       |                       |
;       |  bmp_growfun          |
;       |     16-bit function   |
;       |     called when the   |       DWORD
;       |     bmp segment must  |
;       |     be grown          |
;       |                       |
;       -------------------------
;


.xlist
include dosseg.inc
include basemaca.inc
include osmaca.inc
include fnomem.inc
include osconst.inc
include curdir.inc
include error.inc
include seldesc.inc
include vmowner.inc
include pgexport.inc
include vmexport.inc
include selxport.inc
include virtsym.inc
include mi.inc
include tkexport.inc
include semxport.inc
include mft.inc
.list

        ModName , FNO_BMP_ASM
        CpuMode 386

        PUBLIC  Bmp_SegSize

        ReferFlat   _MaxThreads,WORD
        ReferFlat   _pVMTempBuf,DWORD

        extrn   SegRealloc:near
        EXTRN   _BMPInit:NEAR
        EXTRN   _BMPGet:NEAR
        EXTRN   _BMPFree:NEAR
        EXTRN   _TKSleep:NEAR
        EXTRN   _TKWakeup:NEAR
        EXTRN   _SELAllocGDT:NEAR
        EXTRN   _SELSetDescInfo:NEAR
        EXTRN   _SELVirtToLin:NEAR

        EXTRNFAR SemRequest
        EXTRNFAR SemClear


;**     Block Management Package Definitions
;

BmpSeg  struc

bmp_blksiz      dw      ?               ; size of each block
bmp_freh        dw      ?               ; offset to first on free chain
                                        ;    0 if free chain empty
bmp_size        dw      ?               ; total size of segment
bmp_handle      dw      ?               ; segment handle
bmp_grodelt     dw      ?               ; amount to grow segment
bmp_sem         dd      ?               ; realloc protection semaphore
bmp_growfun     dd      ?               ; callback function for reallocation

BmpSeg  ends

Bmp_SegSize     =       SIZE BmpSeg


;   Temporary Buffers definitions

; Temporary buffers are held in a BMP32 object in Cruiser. Successive
;   chunks of this object is mapped with GDT selectors. Those that want
;   to use a FLAT address should use VM[Get/Free]TempBuffer, or call
;   _BMPGet/Free directly.

MAXTEMPBUFS         equ  (SEL_64K/TEMPBUFLEN)
                            ; max. no of temporary buffers to fit in 64K
SELTEMPBUFSIZE      equ  (MAXTEMPBUFS * TEMPBUFLEN)
                            ; Size of GDT sels mapping TempBuffer BMP32 object
MAXBUFGDTSELS       equ  ((TKCTCBMAX/MAXTEMPBUFS) + 1)
                            ; Max. no of GDT selectors needed for TempBuffers
;
;   The size of the temporary buffers must be at least the size of
;   mft_hvpb (for hVPB in front of path) PLUS the max of
;     the maximum length of an asciiz string (260) and
;     the size of a CDS (w_FsCtl uses buffer for CDS).

IF (TEMPBUFLEN LT (size mft_hvpb + size curdir))
        %out    ERROR - temp buffers too small to hold a CDS
        .ERR
ENDIF
IF (TEMPBUFLEN LT (size mft_hvpb + PMPATHLEN))
        %out    ERROR - temp buffers too small to hold full path strings
        .ERR
ENDIF


_DATA segment
        Public  _TempBufSels

        _TempBufScratch  dd  ?          ; scratch word used by _TKWakeup

        _TempBufSels  LABEL  byte

        dw      MAXBUFGDTSELS DUP (0)       ; zero init GDT sels

_DATA ends




         FARCODE HIGH3
HIGH3CODE SEGMENT


BREAK <Bmp_SetGrowFunction - Change the function used to grow a Bmp segment>

;***    Bmp_SetGrowFunction - Change the function used to grow a Bmp segment
;
;       Bmp_SetGrowFunction is called after Bmp_Init for segments which
;       require special handling when being resized.  For such segments, it
;       should be called before any calls to Bmp_Get.  By default, a segment
;       is grown by just calling SegRealloc.
;
;       The callback function should be FAR 16-bit code and should expect
;       at entry (ES)=selector of Bmp segment and (AX)=desired new size.
;       The function should return the carry flag clear if the grow attempt
;       was successful, and the carry flag set if it was unsuccessful.
;       The function may modify only AX and the flags.
;
;       ENTRY   (ES) = selector for managed segment
;               (DS:DX) = FAR address of 16-bit reallocation function
;       EXIT    NONE
;
;       USES    FLAGS
;
        ASSUME DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure Bmp_SetGrowFunction,HYBRID
        TaskTime
        mov     word ptr es:[bmp_growfun],dx
        mov     word ptr es:[bmp_growfun+2],ds
        ret
EndProc   Bmp_SetGrowFunction

BREAK <Bmp_DefaultGrowFunction - Default function used to grow a Bmp segment>

;***    Bmp_DefaultGrowFunction - Default function used to grow a Bmp segment
;
;       Bmp_DefaultGrowFunction is called to grow Bmp segments by default.
;       It simply calls SegRealloc.
;
;       ENTRY:  (ES) = selector of Bmp segment
;               (AX) = desired new size.
;       EXIT:   C clear if successful
;               C set if unsuccessful
;       USES    AX, FLAGS
;
        ASSUME DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure Bmp_DefaultGrowFunction,far
        TaskTime
        SaveReg <BX,CX,DX,SI,DI,DS,ES>

        mov     bx,ax
        xor     ax,ax                   ; (ax:bx) = size we need
        mov     di,es                   ; (di) = selector for segment
        xor     dx,dx                   ; (dx) = ptda handle
        call    SegRealloc              ; sets or clears carry flag

        RestoreReg <ES,DS,DI,SI,DX,CX,BX>
        ret
EndProc   Bmp_DefaultGrowFunction

BREAK   <Bmp_Get - Get a Free Block>

;***    Bmp_Get - Get a Free Block
;
;       Bmp_Get gets a free block from the managed segment.  A block is
;       taken from the free list.  If none are available, the segment
;       is grown to create some.  If the segment can't be grown then
;       an error is returned.
;
;       Note that we're speed sensitive when we're getting something
;       off the freelist.  When we get into the "grow segment" code
;       we're space sensitive.
;
;       ENTRY   (ES) = selector for managed segment.
;
;       EXIT    'C' clear if OK
;                 (DI) = offset of block in linked list
;               'C' set if no blocks are available
;
;       USES    DI, FLAGS
;
        ASSUME DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure  Bmp_Get,HYBRID
        TaskTime

bgt1:   mov     di,es:bmp_freh          ; (di) = next free link
        or      di,di                   ; check for end of linked list
        jz      bgt2
        mov     di,es:[di]              ; (di) = next link
        xchg    di,es:bmp_freh          ; remove block from free list

IFDEF MISCSTRICT                        ; validate offset

; make sure offset is not too large

        SaveReg <AX,DX>
        mov     ax,di                   ; ax = offset
        add     ax,es:bmp_blksiz        ; es:ax -> end of block
        cmp     ax,es:bmp_size          ; end of block > end of segment?
        ja      bgie1                   ;   yes, go panic

; make sure offset lies on block boundary

        mov     ax,di                   ; ax = offset
        sub     ax,size BmpSeg          ; ax = offset minus header size
        jb      bgie1                   ; if offset too small, go panic

        xor     dx,dx                   ; dx:ax = offset minus header size
        div     es:bmp_blksiz           ; dx = (offset-header size) mod blksiz
        or      dx,dx                   ; is offset on block boundary?
        jnz     bgie1                   ;   no, go panic

        RestoreReg <DX,AX>              ; no error, continue
ENDIF
        clc
        ret                             ; exit with 'C' clear


ifdef MISCSTRICT
bgie1:  InternalError <Bmp_Get: bad offset returned>
endif

;       There isn't a free block; we'll have to grow.  See if
;       we can grow a full "bmp_grodelt" amount without reaching
;       the segment size limit.
;
;       (ES) = managed segment.

bgt2:   AssertNoBuf                     ; assure no buffers owned, in case
                                            ;   segment needs to be resized
        push    ds                      ; save caller's regs
        pushad                           ;

bgt3:   push    es
        mov     ax,es
        mov     bx,offset bmp_sem       ; (AX:BX) = semaphore address
        mov     cx,-1                   ; no time out
        mov     di,cx
        CALLFAR SemRequest              ; grab semaphore
        pop     es
        jc      bgt3

;       We may have blocked so we need to check if another
;       thread allocated enough memory for this request or
;       freed a block.

        cmp     es:bmp_freh,0           ; check next free link
        jne     bgt7                    ; jump if there is a free block

        mov     ax,es:bmp_grodelt
        mov     dx,ax
        add     dx,es:bmp_size          ; (dx) = new size
        jnc     bgt4                    ; we can grow the requested amount

;       We can't grow the full requested amount.  Compute how many
;       bytes we can grow by

        mov     ax,es:bmp_size
        not     ax                      ; (ax) = total bytes we can grow
        sub     dx,dx
        push    ax
        div     es:bmp_blksiz
        pop     ax
        sub     ax,dx                   ; round down to N*bmp_blksiz
        jz      bgt8                    ; sorry - we're all out of space

;       There's room in the segment for more.  Grow the segment
;
;       (es) = segment selector
;       (ax) = bytes to grow by

bgt4:   mov     dx,ax                   ; (dx) = growth amount
        add     ax,es:bmp_size
        push    ax                      ; save new size
        call    es:[bmp_growfun]        ; use callback function to grow seg
        pop     ax                      ; (ax) = new size, (dx) = amount grown
        jc      bgt8                    ; we couldn't get it
        xchg    ax,es:bmp_size          ; set new size, (es:ax) = 1st free ptr
        mov     bx,ax
        xchg    bx,es:bmp_freh          ; set new freelist, (bx) = old free ptr

;       see if we got more than one block allocated.  If so, chain it up.
;
;       (dx) = amount grown
;       (es:ax) = address of first new free block
;       (bx) = previous free list

bgt5:   mov     cx,es:bmp_blksiz        ; (cx) = bytes per block
        mov     di,ax                   ; (es:di) = addr of this block
        add     ax,cx                   ; (es:ax) = addr of next block
        sub     dx,cx
        jz      bgt6                    ; no more blocks allocated
        stosw                           ; current free points to new free
        jmp     bgt5

;       Have chained new blocks together.  Save Old free list in last
;       block.
;
;       (es:di) = address of last new free block
;       (bx) = previous free list

bgt6:   xor     ax,ax
        stosw

bgt7:   push    es
        mov     ax,es
        mov     bx,offset bmp_sem       ; (AX:BX) = semaphore address
        CALLFAR SemClear
        jc      bgt9
        pop     es
        popad                           ; restore caller's regs
        pop     ds                      ;
        jmp     bgt1

;       Have error.  Restore registers and exit.

bgt8:   push    es
        mov     ax,es
        mov     bx,offset bmp_sem       ; (AX:BX) = semaphore address
        CALLFAR SemClear
        jc      bgt9
        pop     es
        popad                           ; restore caller's regs
        pop     ds                      ;
        stc
        ret

bgt9:   InternalError <Bmp_Get - SemClear failed>

EndProc   Bmp_Get

BREAK   <Bmp_CheckOffset - Check that offset points to a valid record>
;***    Bmp_CheckOffset - Check that offset points to a valid record
;
;       NOTE    This routine is only for the 3xbox search routines.
;
;       ENTRY   (ES) = selector for managed segment.
;               (DI) = offset of block
;
;       EXIT    'NC' offset is valid
;               'CY' offset is not valid
;
;       USES    flags
;
Procedure Bmp_CheckOffset,FARONLY

        SaveReg <ax,dx>
        cmp     di,es:bmp_size          ; beyond end of segment?
        ja      bco1                    ; yes, go return error

        xor     dx,dx
        mov     ax,di                   ; (DI) = offset
        sub     ax,Bmp_SegSize
        div     es:bmp_blksiz
        or      dx,dx                   ; remainder?
        jnz     bco1                    ; yes, go return error
bcox:   RestoreReg <dx,ax>
        ret

bco1:   stc
        jmp short bcox

EndProc Bmp_CheckOffset


BREAK   <Bmp_Free - Free a Block>

;***    Bmp_Free - Free a Block
;
;       Bmp_Free returns a block to the free list.
;
;       ENTRY   (ES) = selector for managed segment.
;               (DI) = offset of block in linked list.
;
;       EXIT    NONE
;
;       USES    AX, DI, FLAGS
;
        ASSUME DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure Bmp_Free,<HYBRID,FAR16>
        TaskTime

ifdef MISCSTRICT                        ; validate offset

; make sure offset is not too large

        mov     ax,di                   ; ax = offset
        add     ax,es:bmp_blksiz        ; es:ax -> end of block
        cmp     ax,es:bmp_size          ; end of block > end of segment?
        ja      bfie1                   ;   yes, go panic

; make sure offset lies on block boundary

        SaveReg <dx>
        mov     ax,di                   ; ax = offset
        sub     ax,size BmpSeg          ; ax = offset minus header size
        jb      bfie1                   ; if offset too small, go panic

        xor     dx,dx                   ; dx:ax = offset minus header size
        div     es:bmp_blksiz           ; dx = (offset-header size) mod blksiz
        or      dx,dx                   ; is offset on block boundary?
        jnz     bfie1                   ;   no, go panic

        RestoreReg <dx>                 ; no error, continue
endif

        mov     ax,di
        xchg    ax,es:bmp_freh          ; put new guy on front of chain
        stosw
        ret

ifdef MISCSTRICT
bfie1:  InternalError <Bmp_Free: bad offset>
endif
EndProc   Bmp_Free

BREAK   <Bmp_QueryFree - Query Free Block>

;***    Bmp_QueryFree - Query if there is a Free Block
;
;       Bmp_QueryFree returns TRUE if there is a free block or false if there
;       is not a free block
;
;       ENTRY   (ES) = selector for managed segment.
;
;       EXIT    NC  = free block
;               CY  = no free block
;
;       USES    AX, DI, FLAGS
;
        ASSUME DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure Bmp_QueryFree,<HYBRID,FAR16>
        TaskTime


        cmp     es:bmp_freh,0           ; free blocks ?
        clc
        jne     bqf1
        stc

bqf1:   ret
EndProc   Bmp_QueryFree


BREAK   <GetTempBuffer - get a temporary buffer>

;***    GetTempBuffer - get a temporary buffer
;
;       This routine will return the address of a free TEMPBUFLEN byte
;       buffer that can be used for copying user asciiz strings
;       from user space, etc. If there isn't enough memory to get
;       another buffer or the segment is 64k and there isn't any
;       buffers left, then this routine blocks on the address of
;       the "TempBufferSel" variable under a buffer is freed.
;
;       Those who need a FLAT address only, should call VMGetTempBuf(pp)
;       directly.
;
;       WARNING - There is a new hVPB field at the beginning of the temp
;       buffer for the MFT search code so that the hVPB and the path can
;       be one contiguous bit string to be entered into a tree data
;       structure (Patricia Tree).  However, most callers just want the
;       name portion of the buffer so this routine will return the pointer
;       to the name portion of the buffer.  In order to access the hVPB
;       part, the caller must use a negative offset.
;
;       ENTRY   NONE
;
;       EXIT    (ES:DI) -> temporary buffer (name part, NOT hVPB)
;
;       USES    DI, ES, FLAGS
;
        ASSUME DS:NOTHING,ES:NOTHING,SS:NOTHING,FS:NOTHING,GS:NOTHING

Procedure GetTempBuffer,<near,faronly,far16>
    LocalVar gtb_ds,word
    LocalVar gtb_BufFlat,DWORD
    LocalVar gtb_BufSelOff,DWORD

        EnterProc <ds>,,alignesp                ; gtb_ds
        TaskTime

; Save registers

        SaveReg <eax, ecx, edx>

        FlatContext <ds, es>, ax

;   pp = _VMGetTempBuffer()

gtp20:  SSToDS  eax,gtb_BufFlat
        SSToDS  edx,gtb_BufSelOff
        CCALL32 _VMGetTempBuffer, <eax,edx>

;   We can't return error from this routine (because we didn't in previous
;   versions), so we have to ignore errors (specifically ERROR_INTERRUPT).

        or      eax,eax
        jnz     short gtp20

;   Move the buffer address into es:di

        les     di,[gtb_BufSelOff]

;   Restore registers

        RestoreReg <edx, ecx, eax>
        mov     ds,[gtb_ds]
        LeaveProc
        ret
EndProc   GetTempBuffer


BREAK   <FreeTempBuffer - free a temporary buffer>

;***    FreeTempBuffer - free a temporary buffer
;
;       This routine will free a previously allocated buffer
;       using GetTempBuffer.
;
;       WARNING - There is a new hVPB field at the beginning of the temp
;       buffer for the MFT search code so that the hVPB and the path can
;       be one contiguous bit string to be entered into a tree data
;       structure (Patricia Tree).  However, most callers use just the
;       name portion of the buffer so this routine takes the pointer
;       to the name portion of the buffer.
;
;       ENTRY   (ES:DI) -> temporary buffer (name part, NOT hVPB)
;
;       EXIT    NONE
;
;       USES    FLAGS

Procedure FreeTempBuffer,<near,faronly,far16>
        ASSUME DS:NOTHING,ES:NOTHING,SS:NOTHING,FS:NOTHING,GS:NOTHING
        TaskTime
        EnterProc ,,alignesp
        SaveReg <ds, es, eax, ecx, edx, esi>

        xor     esi, esi
        mov     si, es
        FlatContext <ds, es>, ax
        movzx   edi, di
        CCALL32 _SELVirtToLin <edi, esi>

ifdef VMSTRICT
        cmp     eax, -1
        jnz     short ftb10

        InternalError <FreeTempBuffer: Bad address>
ftb10:
endif

        CCALL32 _VMFreeTempBuffer <eax>

        RestoreReg <esi, edx, ecx, eax, es, ds>
        LeaveProc                       ; Restore ESP
        ret
EndProc   FreeTempBuffer


BREAK <Bmp_Init - Initialize Bmp Segment>

;***    Bmp_Init - Initialize Bmp Segment
;
;       Bmp_init is called to initialize the structure in a segment.
;       The segment should be sizeof (BmpSeg); Bmp will setup the
;       various header fields but won't allocate any blocks until
;       the first request. "extrn BmpSegSize:ABS" can be used to
;       size the initial segment.
;
;       Bmp_Init takes an "allocation increment" value.  This allows
;       the caller to specify that Bmp_Get should create several
;       free blocks when it runs out.  This prevents memory thrashing
;       by a series of calls resulting in a corresponding series of
;       memory management realloc calls.
;
;       Blocks must be at least 2 bytes.  Bmp_Init doesn't check this.
;
;       ENTRY   (AX) = segment handle
;               (BX) = selector of segment to initialize
;               (CX) = size of blocks  (must be >= 2)
;               (DX) = # of free blocks to create each time that we
;                        run out
;       EXIT    NONE
;
;       USES    AX, BX, CX, DX, DI, ES, FLAGS
;
        ASSUME DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure Bmp_Init,HYBRID
        TaskTime
        mov     es,bx
        xor     di,di                   ; (ES:DI) = bmp_blksize

        mov     bx,ax                   ; (BX) = bmp segment handle
        mov     ax,cx                   ; (AX) = block size
        .ERRNZ  bmp_blksiz
        stosw                           ; bmp_blksiz = (AX)

        xor     ax,ax
        .ERRNZ  bmp_freh-bmp_blksiz-2
        stosw                           ; bmp_freh = 0

        mov     ax,Bmp_SegSize
        .ERRNZ  bmp_size-bmp_freh-2
        stosw                           ; bmp_size = size of BmpSeg struc

        xchg    ax,bx                   ; (AX) = bmp segment handle
        .ERRNZ  bmp_handle-bmp_size-2
        stosw                           ; bmp_handle = (AX)

        xchg    ax,cx                   ; (AX) = block size
        mul     dx                      ; compute #bytes to grow when needed
        jnc     bmi1                    ; no overflow
        mov     ax,0ffffh               ; grow to the max
bmi1:   .ERRNZ  bmp_grodelt-bmp_handle-2
        stosw                           ; set bmp_grodelt
        xor     ax,ax
        .ERRNZ  bmp_sem-bmp_grodelt-2
        stosw                           ; clear bmp_sem
        stosw
        .ERRNZ  bmp_growfun-bmp_sem-4
        mov     ax,offset Bmp_DefaultGrowFunction   ; set bmp_growfun
        stosw
        mov     ax,cs
        stosw
        ret
EndProc   Bmp_Init

HIGH3CODE ENDS


        FARCODE HIGH32
HIGH32CODE segment

;***EP  _VMGetTempBuffer - Obtain a temporary buffer
;
;       _VMGetTempBuffer obtains a temporary buffer from the Temporary
;       Buffers BMP32 object. If there are no free records left, and we can't
;       grow the temporary buffers object, this routine will block until
;       a buffer is freed.
;
;       WARNING!  This code depends on the fact that BMP32 does not do
;                 sparse decommit, i.e. all pages of a BMP32 object remain
;                 committed while there is at least one busy BMP32 block
;                 beyond them
;
;       WARNING!  There is a new hVPB field at the beginning of the temp
;       buffer for the MFT search code so that the hVPB and the path can
;       be one contiguous bit string to be entered into a tree data
;       structure (Patricia Tree).  However, most callers just want the
;       name portion of the buffer so this routine will return the pointer
;       to the name portion of the buffer.  In order to access the hVPB
;       part, the caller must use a negative offset.
;
;       ENTRY   vgt_ppBufFlat - pointer to flat address of temp buf
;               vgt_ppBufSelOff - pointer to 16:16 address of temp buf or NULL
;       RETURN  (EAX) = NO_ERROR or ERROR_INTERRUPT
;                   vgt_pBufFlat and vgt_pBufSelOff filled
;                   in to point to temp buffer (name part)
;       USES    EAX, ECX, EDX, EFlags

Procedure  _VMGetTempBuffer
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
    ArgVar      vgt_ppBufFlat,DWORD     ; where to store buffer laddr
    ArgVar      vgt_ppBufSelOff,DWORD   ; where to store 16:16 buffer address
    LocalVar    vgt_p,DWORD             ; address of temporary buffer
    LocalVar    vgt_pbOldEndBlocks,DWORD
    LocalVar    vgt_pbNewEndBlocks,DWORD

        EnterProc
        TaskTime
        push    esi
        push    edi
        push    ebx

;  Get the old end of valid blocks

vgt10:  mov     ebx,[_pVMTempBuf]
        sub     ebx, size vmbh_s
        
        mov     eax,[ebx].bh_pbEndBlocks
        mov     vgt_pbOldEndBlocks,eax
                
        SSToDS  eax, vgt_p
        CCALL32 _BMPGet <[_pVMTempBuf], eax>
        or      eax, eax
        jz      short vgt80

;   _BMPGet returned error, we don't return error, except ERROR_INTERRUPT

vgt40:  cmp     eax, ERROR_INTERRUPT
        je      vgtx

;   We will ignore the error code returned, and block until we get one.
;   _TKSleep(block id, block time, interruptibility, 0)

        mov     ecx, offset FLAT:_TempBufSels
        CCALL32 _TKSleep <ecx, -1, NULL, NULL>
        or      eax, eax
        jnz     short vgt40
        jmp     short vgt10                 ; go back to calling BMP

;   Store the flat addr in the callers buffer

vgt80:  mov     eax,[ebx].bh_pbEndBlocks   ; Save new end of valid blocks
        mov     vgt_pbNewEndBlocks,eax          
        mov     eax, [vgt_p]
        add     eax,size mft_hvpb       ; (eax) -> name part of buffer
        mov     edi,[vgt_ppBufFlat]     ; (edi) -> flat pointer to buffer
        mov     [edi],eax               ; store the flat address

;   Now determine which GDT sel to use

        mov     edi,[vgt_ppBufSelOff]   ; (edi) -> 16:16 pointer to buffer
        or      edi,edi
        jz      vgt99                   ; null, done

        CCALL32 _vmTempBuffToSelOff
        mov     [edi],eax               ; store the 16:16 address

;  Check if the selector limit is adequate to cover the given buffer
        
        mov     eax,vgt_pbOldEndBlocks  ; Get new end of valid blocks
        cmp     eax,vgt_pbNewEndBlocks  ; Is it the same as the old end?
        jge     vgt99                   ; J if same, selector limit is OK
        
;  One or more additional pages have been committed to the BMP32 object in
;  order to do the allocation (possibly by some other thread).  All the
;  selectors that map part or all of the newly committed range need to have
;  their selector limits increased at this time.

        mov     ebx,eax                 ; EBX = laddr of old end
        CCALL32 _vmTempBuffToSelOff
        mov     ecx,eax                 ; ECX = sel:offset
        movzx   ecx,cx                  ; ECX = offset of buffer
        sub     ebx,ecx                 ; EBX = selector base           

vgt95:  mov     edx,vgt_pbNewEndBlocks      ; EDX = end of valid blocks
        cmp     ebx,edx                     ; Past end of valid region?
        jae     vgt99                       ; Jump if so, selectors unchanged
        mov     edi,SELTEMPBUFSIZE          ; Assume to set limit = max+1
        mov     eax,ebx                     ; EAX = base laddr for next SEL
        add     eax,edi                     ; EAX = End of region for next SEL
        cmp     eax,edx                     ; Sel maps past end of region?
        jbe     vgt96                       ; Jump if not
        mov     edi,edx                     ; EDI = end of region
        sub     edi,ebx                     ; minus base addr for sel
vgt96:  mov     ax, [esi]
        CCALL32 _SELSetDescInfo <SELSD_LIMIT, NULL, eax, edi, \
                    NULL, NULL, NULL, NULL>
        add     ebx,SELTEMPBUFSIZE          ; address for next GDT sel
        add     esi,2                       ; address of next sel.
        jmp     vgt95
vgt99:  xor     eax,eax                 ; success path
vgtx:   pop     ebx     
        pop     edi
        pop     esi
        LeaveProc
        RETC

EndProc _VMGetTempBuffer


;***LP  _vmTempBuffToSelOff - Convert Temp Buffer Address to Selector:Offset
;
;       This function converts a linear temporary buffer address to a
;       selector:offset
;       
;       ENTRY   eax                     - Temporary buffer linear address
;       EXIT    eax                     - Selector:Offset
;               esi                     - Points to selector
;       USES    eax, ecx, edx, esi
        
Procedure  _vmTempBuffToSelOff
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing
        sub     eax, [_pVMTempBuf]      ; (eax) = offset into BMP32 obj.
        xor     edx, edx                ; (edx) = 0
        mov     ecx, SELTEMPBUFSIZE
        div     ecx                     ; remainder (16 bit offset) in edx
        shl     eax,1                   ; (eax) = offset in sel array
        add     eax, offset FLAT:_TempBufSels
        mov     esi, eax
        mov     ax, [esi]
        shl     eax,16
        mov     ax,dx                   ; (eax) = sel:off
        ret

EndProc _vmTempBuffToSelOff

;***EP  _VMFreeTempBuffer - Free temporary buffer
;
;       _VMFreeTempBuffer frees temporary buffer allocated via
;       _VMGetTempBuffer, and wakes up any threads blocked waiting for a
;       Temporary Buffer.
;
;       WARNING!  This code depends on the fact that BMP32 does not do
;                 sparse decommit, i.e. all pages of a BMP32 object remain
;                 committed while there is at least one busy BMP32 block
;                 beyond them
;
;       This routine, instead of _BMPFree should be called when freeing
;       temporary buffers, otherwise we may leave some threads blocked.
;
;       WARNING - There is a new hVPB field at the beginning of the temp
;       buffer for the MFT search code so that the hVPB and the path can
;       be one contiguous bit string to be entered into a tree data
;       structure (Patricia Tree).  However, most callers use just the
;       name portion of the buffer so this routine takes the pointer
;       to the name portion of the buffer.
;
;       ENTRY   vft_pBuf - Address of buffer to free (name part)
;       EXIT    NONE
;       USES    EAX, ECX, EDX, EFlags

Procedure _VMFreeTempBuffer
    assume ds:FLAT, es:FLAT, fs:nothing, gs:nothing, ss:nothing

    ArgVar      vft_pBuf,DWORD
    LocalVar    vft_pbOldEndBlocks,DWORD
    LocalVar    vft_pbNewEndBlocks,DWORD
        
        EnterProc

;  Get the old end of valid blocks from the BMP32 header.  The _pVMTempBuf
;  pointer points just after the BMP32 header.

        push    esi
        push    edi
        push    ebx
        mov     ebx,_pVMTempBuf
        sub     ebx,size vmbh_s
        mov     eax,[ebx].bh_pbEndBlocks
        mov     vft_pbOldEndBlocks,eax          

;   _BMPFree(pbh,p)

        mov     eax,[vft_pBuf]
        sub     eax,size mft_hvpb       ; (eax) -> real buffer

        CCALL32 _BMPFree <[_pVMTempBuf], eax>

;  Get the new end of valid blocks in the BMP32 object.  If it has decreased,
;  one or more pages have been decommitted (by this or some other thread).
;  We need to ensure that no temp buff selectors that map at least one
;  temp buffer are left mapping any decommitted memory in case a dh_lock is
;  attempted.

        mov     eax,[ebx].bh_pbEndBlocks                ; EAX = new end of valid blocks
        cmp     eax,vft_pbOldEndBlocks          ; Have pages been decommitted
        jge     vft20                           ; Jump if not

;  One or more pages have been decommitted from the BMP32 object

        mov     vft_pbNewEndBlocks,eax          ; Save new limit
        mov     ebx,eax

;  Get the base linear address of the lowest selector to be changed.
;  This is the selector that maps the new end of valid blocks.  The
;  base address = end of valid blocks - offset.

        CCALL32 _vmTempBuffToSelOff         ; EAX = sel:offset, ESI = sel index
        mov     ecx,eax                     ; ECX = sel:offset
        movzx   ecx,cx                      ; ECX = offset of buffer
        sub     ebx,ecx                     ; EDX = selector base               

;       EBX = base address of selector to be changed

vft15:  mov     edx,vft_pbOldEndBlocks      ; EDX = end of valid blocks
        cmp     ebx,edx                     ; Past end of old valid region?
        jae     vft20                       ; We're done if so
        mov     edi,vft_pbNewEndBlocks      ; EDI = new lower end of region
        cmp     ebx,edi                     ; Is selector completely invalid ?
        jae     vft16                       ; Jump if so
        sub     edi,ebx                     ; Subtract base addr for sel
        jmp     vft18
vft16:  mov     edi,1                       ; Reduce selector limit to 0
vft18:  mov     ax, [esi]
        CCALL32 _SELSetDescInfo <SELSD_LIMIT, NULL, eax, edi, \
                    NULL, NULL, NULL, NULL>
        add     ebx, SELTEMPBUFSIZE         ; address for next GDT sel
        add     esi, 2                      ; address of next sel.
        jmp     vft15

;   _TKWakeup(block id, 0, pcount)

vft20:  mov     ecx, offset FLAT:_TempBufSels
        mov     eax, offset FLAT:_TempBufScratch
        CCALL32 _TKWakeup <ecx, NULL, eax>
        pop     ebx
        pop     edi
        pop     esi
        LeaveProc
        RETC

EndProc _VMFreeTempBuffer

HIGH32CODE ends

         FARCODE INIT
INITCODE segment


BREAK   <InitTempBuffers - initialize the temporary buffer allocator>

;***    InitTempBuffers - initialize the temporary buffer allocator
;
;       This routine will initialize the Temporary Buffers BMP32 segment,
;       and maps GDT selectors to it.
;
;       WARNING!  This code depends on the fact that BMP32 does not do
;                 sparse decommit, i.e. all pages of a BMP32 object remain
;                 committed while there is at least one busy BMP32 block
;                 beyond them
;
;       ENTRY   NONE
;
;       EXIT    NONE
;
;       USES    EAX, EBX, ECX, EDX, ESI, EDI, DS, ES, EFLAGS
;

Procedure InitTempBuffers,NEAR
        ASSUME DS:NOTHING,ES:NOTHING,SS:NOTHING,FS:NOTHING,GS:NOTHING

        FlatContext <ds, es>, ax

;   Determine no of blocks needed. We want a minimum of 64Kb BMP32 object.

        movzx   esi, [_MaxThreads]          ; max no of threads
        cmp     si, MAXTEMPBUFS
        jae     short itb10

        mov     si, MAXTEMPBUFS             ; minimum 64Kb

;   _BMPInit(cblocks, cbblock, flswap, hobowner, pfbusy, pbh)
;   (esi) = max. no of blocks needed

itb10:  mov     ebx, offset FLAT:_pVMTempBuf
        CCALL32 _BMPInit <esi, TEMPBUFLEN, PG_RESIDENT, VMTMPBUFOWNER, \
                NULL, ebx>
        or      eax, eax
        jz      short itb20

        Panic <'InitTempBuffers: BMPInit'>

;   Calculate no. of selectors needed by dividing the total no of blocks by
;   the number that fits into 64Kb

itb20:  mov     eax, esi
        mov     ecx, MAXTEMPBUFS
        xor     edx, edx
        div     ecx                     ; divide (edx:eax) by ecx
        mov     cx, ax
        or      edx, edx
        jz      short itb25             ; no remainder

        inc     cx                      ; include 1 more (for remainder)
itb25:  cmp     cx, MAXBUFGDTSELS
        jbe     short itb30

        Panic <'InitTempBuffers: Too many sels'>
itbie1: Panic <'InitTempBuffers: SELAllocGDT'>

;   (ebx) = points to _pVMTempBufs
;   (cx) = no of GDT sels needed

itb30:  mov     ebx, [ebx]
        mov     esi, offset FLAT:_TempBufSels
        mov     edi, SELTEMPBUFSIZE
        mov     eax,ebx                     ; EAX = pointer to 1st buffer
        sub     eax,size vmbh_s             ; EAX points to BMP32 header

itb40:  push    cx                          ; save loop count
        push    eax                         ; Save BMP32 header pointer
        CCALL32 _SELAllocGDT <esi>
        or      eax, eax
        jnz     short itbie1

;       Calculate the number of bytes that the selector is to map.  All
;       selectors required to map the whole of the BMP32 object are
;       initialized, but the selector that maps the region containing the
;       end of the valid BMP32 blocks is truncated to that boundary and
;       succeeding selectors are mapped to a zero limit.

        pop     eax                         ; EAX = pointer to BMP32 header
        push    eax                         ; Keep on stack
        mov     edx,[eax].bh_pbEndBlocks    ; EDX = end of valid blocks
        cmp     ebx,edx                     ; Past end of valid region?
        jb      short itb41                 ; Jump if so

        mov     edi,1                       ; Set limit to 0
        jmp     short itb42

itb41:  mov     eax,ebx                     ; EAX = base laddr for next SEL
        add     eax, SELTEMPBUFSIZE         ; EAX = End of region for next SEL
        cmp     eax, edx                    ; Sel maps past end of region?
        jbe     itb42                       ; Jump if not
        mov     edi,edx                     ; EDI = end of committed region
        sub     edi,ebx                     ; EDI = # bytes selector is to map
itb42:  mov     ax, [esi]
        CCALL32 _SELSetDescInfo <SELSD_ALL, D_DATA0, eax, edi, \
                    ebx, NULL, NULL, NULL>
        add     ebx, SELTEMPBUFSIZE         ; address for next GDT sel
        add     esi, 2                      ; address of next sel.
        pop     eax                         ; EAX = pointer to BMP32 header
        pop     cx                          ; CX = loop count
        loop    itb40

        ret
EndProc InitTempBuffers

INITCODE ends

        END
