;       SCCSID = @(#)rmp.asm	6.2 91/11/11
SCCSID  equ     "@(#)rmp.asm	6.2 91/11/11"
        PAGE    ,132
TITLE   RMP     Record Management Package

;        IBM/Microsoft Confidential
;
;        Copyright (c) IBM Corporation 1987, 1989
;        Copyright (c) Microsoft Corporation 1987, 1989
;
;        All Rights Reserved

;***    Record Management Package
;
;
;       JGL, Microsoft, 11/84
;       DLM, Microsoft, 06/88


;***    The Record Management Package is a set of routines to
;       manage a collection of variable sized records.  These records
;       are kept in a movable segment.  RMP does all its work within
;       the "record segment"; different uses of RMP can, and probably
;       should, have their own record segments.
;
;       Since the record structure must fit entirely within a segment
;       this limits the total amount of information managable via RMP
;       to 64K (minus one word per record and the header overhead).
;       Naturally a client can use multiple segments to avoid this limit but
;       the client will have to keep track of which segment contains which
;       record (or search all the segments for the record).
;
;       Records
;
;           A record is a variable length structure, limited to a maximum
;           of 1534 bytes.  The limit allows all current users to succeed,
;           and prevents random new users from using huge records.  Each
;           record stored in a record segment can have a different length.
;
;       RMP Functions:
;
;           Rmp_Alloc -  allocate an RMP segment
;           Rmp_Add   -  add an entry to a record segment
;           Rmp_Rem   -  remove an entry from a record segment
;           Rmp_Find  -  search for an entry in a record segment
;           Rmp_FindN -  search for the next entry in a record segment
;           Rmp_Free  -  free an RMP segment
;
;       RMP Limitations:
;
;           speed:
;
;               RMP has been honed to provide a trade off between speed
;               and space efficiency. Freed records are marked as invalid
;               and can then be reused. Note new records occupying a 'free'
;               space must either be the same size or sufficiently smaller
;               to allow a free block link to be installed in the end section
;               of the old 'free'. Growth of the segment is done in quantums
;               of RMPGRAN, so the smaller the added records the less the
;               likely hood of a growth.
;               Rmp_Find serially searches the contents of the record
;               segment from the front.  If there are a lot of records
;               and the record in question is missing or near the end this
;               could involve some time.
;
;               In conclusion, RMP is sufficiently efficient for activities
;               related to infrequent system calls such as OPEN, CLOSE,
;               SHARMEM, etc., but is not a good idea for calls like
;               READ and WRITE.
;
;           Memory Limitations
;
;               Individual records are limited to 1534 bytes each.
;               A record segment is limited to 65522 bytes.  An additional
;               overhead of 1 word per record is consumed.
;
;           Usage Caveats
;
;               The caller need only assure that the record segment is
;               swapped in.  It need not be locked because RMP will not
;               block the calling process during a search operation.
;               Note however that the calling process may be blocked  during
;               record addition, however this will be prior to the actual
;               record entry.
;
;               A user must be very careful of context switches (ProcBlocks):
;
;                       After any ProcBlock some other process may have issued
;                       an Rmp_Rem which may change the offsets within
;                       the segment.  This can be prevented by making all
;                       users of the particular RMP segment enter a
;                       critical section.
;


;***    Record Segment Structure
;
;       RMP opperates on "record segments".
;
;       Their format is:
;
;  N:0  -------------------------
;       |  rb_size              |       WORD
;       |     total size of seg |
;       |-----------------------|
;       |  rb_free_size         |       WORD
;       |     total free space  |
;       |     in segment        |
;       |-----------------------|
;       |  rb_1st_free          |       WORD
;       |     offset to 1st     |
;       |     free block        |
;       |-----------------------|
;       |  rb_last_free         |       WORD
;       |     offset to last    |
;       |     free block        |
;       |-----------------------|
;       |  rb_hobowner          |       WORD
;       |-----------------------|
;       |  rb_hobmte            |       WORD
;       |-----------------------|
;       |  length+2 of record 1 |       WORD
;       |-----------------------|
;       |     record 1          |
;       |-----------------------|
;       |  length+2 of record 2 |       WORD
;       |-----------------------|
;       |     record 2          |
;       |-----------------------|
;               .
;               .
;               .
;       |-----------------------|
;       |  length+2 of last rec |       WORD
;       |-----------------------|
;       |     record last       |
;       |-----------------------|

.xlist
include dosseg.inc
include basemaca.inc
include fnomem.inc
include osmaca.inc
include osconst.inc
include mi.inc
include pgexport.inc
include rmp.inc
include seldesc.inc
include selxport.inc
include virtsym.inc
include vmexport.inc
.list

        ModName , FNO_RMP_ASM

        EXTRNFAR ProcBlock
        EXTRNFAR ProcRun
        EXTRNFAR TCYield

        EXTRN32 _SELAllocKHB
        EXTRN32 _SELReallocKHB
        EXTRN32 _SELFreeKHB


REPMOVSD macro
        push    cx
        shr     cx,2
    .386p
        rep     movsd                   ; move dwords
    CpuMode reset
        pop     cx
        and     cx,3
        rep     movsb                   ; move last 0, 1, 2 or 3 bytes
endm



        FARCODE HIGH3
HIGH3CODE segment



;***    Rmp_Wait - Wait for RMP semaphore to be cleared
;
;       This subroutine blocks the current thread until
;       the specified RMP handle is available.  Ownership
;       of an RMP handle amounts to ownership of the
;       associated RMP segment.
;
;       WARNING: This subroutine blocks UNINTERRUPTIBLY.
;
;       ENTRY   (ES:DI) = pointer to RMP handle structure
;               (DL) = 0 - don't wait for semaphore, return error
;                      1 - block until semaphore available
;       EXIT    Carry set
;                   Semaphore busy
;               Carry clear
;                   Semaphore available
;       USES    AX, BX, CX, DX, Flags

Procedure Rmp_Wait,hybrid
        ASSUME  DS:NOTHING, ES:NOTHING, SS:NOTHING

;       PMONLY  TASKTIME
        TaskTime
rw00:   test    es:[di].rp_flags,RPF_BUSY ; Is semaphore busy? (clears carry)
        jz      rw10                    ; no, return carry clear

        cmp     dl,1                    ; Will we wait?
        jc      rw10                    ; no, return error

        or      es:[di].rp_flags,RPF_WAITING ; We are waiting
        mov     ax,es                   ; (AX:BX) = event i.d. = handle address
        mov     bx,di
        mov     di,-1                   ; (DI:CX) = -1 = no timeout
        mov     cx,di
        mov     dh,1                    ; Sleep uninterruptibly
        pushf                           ; save interrupt state
        cli
        CALLFAR ProcBlock               ; Block until awakened
        popf                            ; restore int state (for FSD RIPL)
        mov     di,bx                   ; (DI) = handle offset
        jmp     short rw00              ; Loop if normal awakening

rw10:   ret

EndProc Rmp_Wait




;***    Rmp_Req - Request an RMP semaphore
;
;       This function obtains an RMP semaphore, blocking
;       if necessary.
;
;       NOTE: This function relies on the fact that the
;       kernel is non-preemptive.  Thus, interrupts may
;       be enabled between the time we determine the
;       semaphore is not busy and the time we set the
;       semaphore.
;
;       ENTRY   (ES:DI) = pointer to RMP handle
;               (DL) = 0 - don't wait for semaphore, return error
;                      1 - block until semaphore available or interrupted
;       EXIT    Carry clear
;                   Semaphore available
;               Carry set
;                   Semaphore busy
;       USES    AX, BX, CX, DX, Flags

Procedure Rmp_Req,near,local
        ASSUME  DS:NOTHING, ES:NOTHING, SS:NOTHING

;       PMONLY  TASKTIME
        TaskTime
        call    Rmp_Wait                ; Wait for semaphore to be available
        jc      rr0                     ;  return carry if abnormal wakeup
        or      es:[di].rp_flags,RPF_BUSY ; Set the semaphore
rr0:    ret
EndProc Rmp_Req



;***    Rmp_Clr - Clear an RMP semaphore, wake any waiting threads
;
;       This function releases an RMP semaphore and wakes
;       any threads waiting on that semaphore.
;
;       ENTRY   (DS:SI) = pointer to RMP handle
;       EXIT    NONE
;       USES    NONE

Procedure Rmp_Clr,near,local
        ASSUME  DS:NOTHING, ES:NOTHING, SS:NOTHING

;       PMONLY  TASKTIME
        TaskTime
        push    ax
        pushf
        mov     al,[si].rp_flags        ; (AL) = old flags
        and     [si].rp_flags,NOT (RPF_BUSY + RPF_WAITING) ; Turn off flags
        test    al,RPF_WAITING          ; Anybody waiting?
        jz      rc0                     ;  no, we're done
        push    bx
        mov     ax,ds                   ; (AX:BX) = event i.d. = handle address
        mov     bx,si
        CALLFAR ProcRun                 ; Sleepers awake
        pop     bx
rc0:    popf
        pop     ax
        ret
EndProc Rmp_Clr


;***    Rmp_Alloc - Initialize Record Managed Segment
;
;       Rmp_Alloc allocates an RMP segment and initializes
;       the structure allocated by the caller.
;
;       ENTRY   (AX)    = segment owner/hobowner
;               (BX)    = segment origin/hobmte
;               (ESI)   = allocation flags - allowed combinations are:
;                         PG_SWAPPABLE
;                         PG_RESIDENT
;                         PG_RESIDENT or PG_CONTIG
;               (ES:DI) = pointer to RMP handle
;       EXIT    Carry Flag clear on success
;               Carry Flag set on failure
;       USES    EAX, EBX, ECX, EDX, ESI, DS, ES, FLAGS

CpuMode 386

Procedure Rmp_Alloc,<faronly,far16>
    assume ds:nothing, es:nothing, ss:TASKAREA
    LocalVar ral_hobowner,word          ; parameters
    LocalVar ral_RmpHand,dword
    LocalVar ral_vmkhbi,<size vmkhbi_s>

;       PMONLY  TASKTIME
        TaskTime
        EnterProc <ax,es,di>            ; hobowner,RmpHand

;       (AX)    = hobowner
;       (BX)    = hobmte
;       (SI)    = handle flags
;
;       rc = SELAllocKHB(hkh, cb, fl, flSelMgr, hobOwner, pkhbi)

        test    esi,not (PG_SWAPPABLE or PG_RESIDENT or PG_CONTIG)
        jz      short ral00
        Panic <'Rmp_Alloc: flags'>

ral00:  FlatContext <ds,es>,cx

;       BUGBUG start:
;       Use hobmte for hobowner if non-null, until heap supports hobmte field
;       Can also delete the two fields from the rbheadr_s structure
        or      bx,bx
        jz      short ral10
        xchg    ax,bx           ; switch hobowner, hobmte
ral10:
;       BUGBUG end

        movzx   eax,ax
        mov     ecx,RMPINITSIZE
        mov     edi,VM_HKH_PUB_RESRW
        test    esi,PG_RESIDENT
        jnz     short ral20
        mov     edi,VM_HKH_PUB_SWAPRW
ral20:  SSToDS  edx,[ral_vmkhbi]
        push    cx
        CCALL32 _SELAllocKHB,<edi, ecx, esi, SELAL_W, eax, edx>
        pop     cx
        or      eax,eax
        stc
        jnz     short ralx              ; return error
        mov     edx,edi                 ; (edx) = heap handle

;       (BX)  = hobmte
;       (CX)  = initial size
;       (EDX) = heap handle
;       (ESI) = PG alloc/realloc flags

        les     di,[ral_RmpHand]
    assume es:nothing
        mov     es:[di].rp_flags,RPF_ALLOC ; Segment is allocated
        mov     ax,word ptr [ral_vmkhbi].khbi_sel
        mov     es:[di].rp_selector,ax
        mov     ds,ax                   ; (ds) = RMP segment selector
    assume ds:nothing
        mov     ds:[rb_size],cx         ; set size
        mov     ds:[rb_hkh],edx         ; set heap handle
        mov     ds:[rb_flags],esi       ; set flags
        mov     ax,[ral_hobowner]       ; set hobowner
        mov     ds:[rb_hobowner],ax
        mov     ds:[rb_hobmte],bx       ; set hobmte
        mov     ax,offset rb_first      ; point to free area
        sub     cx,ax                   ; calculate free area size
        mov     ds:[rb_free_size],cx    ; set free size (used for records)
        mov     ds:[rb_1st_free],ax     ; set 1st free block ptr
        mov     ds:[rb_last_free],ax    ; set last free block ptr
        mov     si,ax
        mov     ds:[si].rf_size,cx      ; fill in free header info
        or      ds:[si].rf_size,RPF_FREE; clears Carry for return
        mov     ds:[si].rf_prev_free,offset rb_size
        mov     ds:[si].rf_next_free,si
ralx:   LeaveProc
        ret
EndProc Rmp_Alloc


;***    rmprealloc - function used to resize an Rmp segment
;
;       rmprealloc is called to resize Rmp segments.
;       It simply calls SELReallocKHB.
;
;       ENTRY   (BX) = desired new size.
;               (DS) = selector of Rmp segment
;       EXIT    C clear if successful
;               C set if unsuccessful
;       USES    EAX, EBX.hiword, ECX, EDX, ESI, ES, FLAGS

        ASSUME DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure rmprealloc,,local
    LocalVar rel_vmkhbi,<size vmkhbi_s>
        TaskTime
        EnterProc

;       rc = SELReallocKHB(hkh, sel, cb, fl, pkhbi)

        mov     eax,ds:[rb_hkh]         ; (eax) = heap handle
        mov     si,ds                   ; (si) = RMP selector
        movzx   ebx,bx                  ; (ebx) = new size
        mov     ecx,ds:[rb_flags]       ; (ecx) = alloc/realloc flags
        push    ds
        FlatContext <ds,es>,dx
        SSToDS  edx,[rel_vmkhbi]        ; (edx) = &khbi
        CCALL32 _SELReallocKHB <eax, esi, ebx, ecx, edx>
        pop     ds
        or      eax,eax
        jz      short rrex
        stc
rrex:   LeaveProc
        ret
EndProc rmprealloc


;***    Rmp_Free - Terminate Record Managed Segment
;
;       Rmp_Free frees an RMP segment and clears
;       the structure allocated by the caller.
;
;       ENTRY   (ES:DI) = pointer to RMP handle
;       EXIT    Carry Flag clear on success
;               Carry Flag set on failure
;       USES    EAX, ECX, EDX, DS, ES, FLAGS

Procedure Rmp_Free,faronly
    assume ds:nothing, es:nothing, ss:TASKAREA
;       PMONLY  TASKTIME
        TaskTime
        xor     cx,cx
        mov     es:[di].rp_flags,cl
        xchg    es:[di].rp_selector,cx

;       rc = SELFreeKHB(hkh, fl, sel) - fl should be 0

        mov     ds,cx
        mov     eax,ds:[rb_hkh]         ; (eax) = heap handle
        FlatContext <ds,es>,dx
        CCALL32 _SELFreeKHB <eax, 0, ecx>
        or      eax,eax
        jz      short rfrx
        stc
rfrx:   ret
EndProc Rmp_Free

CpuMode 286


;***    Rmp_Add - Add Record to Managed Segment
;
;       Rmp_Add adds the supplied record to the managed segment.
;       Rmp_Add searches the free records for a suitable sized free slot
;       and fills in the new record (carving the free slot up if neccessary).
;       If not enough free space exist the segment is grown.
;       If the handle to the managed segment is zero a segment is
;       allocated and initialized.
;
;       If the user intends to "replace" an existing record he should
;       first remove the old copy via Rmp_Rem.
;
;       WARNING - Rmp_Add may context switch (ProcBlock), but only before
;                 the new record is added.  If a caller doesn't want to
;                 build the new record in memory so that Rmp_Add can
;                 copy it in, he may provide Rmp_Add with the address of
;                 sufficient garbage bytes to make up the record, and then
;                 store the correct values in when Rmp_Add returns.  This
;                 is safe providing that the caller corrects the record
;                 before any context switches, or if the caller wraps the
;                 RMP segment in a caller-maintained critical section.
;
;       ENTRY   (DS:SI) = address of structure to add
;               (CX) = length of item to add  (LIMIT of 1534)
;               (ES:DI) = address of handle to managed record segment,
;                         followed by selector to be used.
;
;       EXIT    Carry clear
;                   (ES:DI) = address of item in managed segment
;               Carry set
;                   Add failed
;
;       USES    AX, BX, CX, DX, SI, DI, DS, ES, FLAGS
;

Procedure Rmp_Add,<hybrid,far16>
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

        LocalVar ra_NewData,DWORD       ; parameters
        LocalVar ra_RmpHand,DWORD
        LocalVar ra_DataLen,WORD

        LocalVar ra_size,WORD           ; local variables

;       PMONLY  TASKTIME
        TaskTime
        EnterProc <ds,si,es,di,RMPMINSIZE>      ; (ra_NewData) = (DS:SI)
                                        ; (ra_RmpHand) = (ES:DI)
                                        ; min request size
        add     cx,rb_sz_field          ; Add in record size field
        jz      radie0

        cmp     cx,RMPGRAN              ; Size must be less than 1536
        jb      rad10                   ; requested size is ok

radie0: Panic <'Rmp_Add: bad request'>
radie1: Panic <'Rmp_Add: not allocated'>

rad10:  cmp     cx,RMPMINSIZE           ; Size must be > size rbfree_s
        jb      rad15                   ; jmp if below minimum size

        mov     [ra_DataLen],cx         ; save data length
rad15:  mov     dl,1                    ; Block until semaphore available
        call    Rmp_Req                 ; Get semaphore

        test    es:[di].rp_flags,RPF_ALLOC ; Segment allocated yet?
        jz      short radie1            ;  no, go Panic

;       The segment exists.

        mov     ds,es:[di].rp_selector  ; (DS) = RMP segment
    assume ds:nothing
        mov     ax,ds:[rb_free_size]    ; (AX) = amount of free space
        sub     ax,size rbfree_s        ; must always be space for end free
        mov     cx,[ra_DataLen]
        cmp     ax,cx                   ; Can space satisfy request?
        jb      rad40                   ; No!, seg needs to be grown

rad35:  call    Rmp_Srch                ; ds:di=RMP_Srch(ax=rec size,ds=rmp seg)
        jnc     rad50                   ; jmp - found free space

rad40:  mov     bx,[ra_DataLen]         ; (BX) = Requested record len
        add     bx,ds:[rb_size]         ; is this within the segment size?
        jc      rad60                   ; segment would exceed 64K

;       Segment is too small.
;       Try to grow it to the next multiple of RMPGRAN
;
;       (DS) = rmp segment
;       (BX) = new minimum size needed

        add     bx,RMPGRAN-1
        jc      rad60                   ; segment would exceed 64K

;       OK to grow segment
;
;       (DS) = rmp segment
;       (BX) = new segment size (needs to be rounded down)

        and     bx,RMPMASK              ; (BX) = new segment size
        call    rmprealloc              ; (ds) = rmp sel, (bx) = newsize
        jc      rad60                   ; jump if realloc fails

        mov     ax,bx                   ; (ax) = new size
        sub     ax,ds:[rb_size]         ; (AX) = new - old size
        add     ds:[rb_size],ax         ; Update new seg size
        add     ds:[rb_free_size],ax    ; Update new free size
        mov     bx,ds:[rb_last_free]
        add     [bx].rf_size,ax         ; Update size of last free block
        mov     cx,[ra_DataLen]
        jmp     short rad35

rad50:  mov     ax,[ra_DataLen]
        sub     ds:[rb_free_size],ax    ; update "total free" amount
        push    ds
        pop     es                      ; (ES) = rmp segment
        cld
        stosw                           ; set length+size field of record
        mov     cx,ax
        sub     cx,rb_sz_field          ; (CX) = length of record (only)
        lds     si,[ra_NewData]
        mov     ax,di                   ; Save offset
        REPMOVSD
        mov     di,ax                   ; Restore offset
        clc

;       Exit releasing semaphore.

rad60:  lds     si,[ra_RmpHand]         ; (DS:SI) = pointer to RMP handle
        call    Rmp_Clr                 ; Clear the semaphore

        LeaveProc
        ret

EndProc Rmp_Add




;**     Rmp_Srch - Search for free record
;
;       Rmp_Srch runs the linked list of free records looking for a record
;       large enough to satisfy the new record request.
;       The free block must either be the same size as the request or at
;       least 'size rbfree_s' larger so that a new smaller free block can be
;       carved out at the top of the old free block.
;
;       There are four cases to consider when searching for a large enough
;       free block
;
;       1) Free block is too small                      Skip
;       2) Free block is exact size                     Use
;       3) Free block is at least as large as           Carve into record and
;          the requested size plus the minimum          new free block
;          free block size
;       4) Free block is larger than request size       Skip
;          but not large enough to carve
;
;       Case 2 cannot use the last free block but case 3 can because
;       it creates a new last free block
;
;       ENTRY   DS = managed record segment
;               CX = requested record length (including Size field)
;
;       EXIT    'C'- clear : OK found free space, (DS:DI) = free space
;               'C'- set : Not enough free space, need to grow segment
;
;       USES    AX, BX, CX, DX, SI, DI, FLAGS
;

rsie00: Panic <'Rmp_Srch : busy record'>

Procedure Rmp_Srch,near,local
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

;       PMONLY  TASKTIME
        TaskTime

        mov     dx,cx                   ; (CX) = (DX) = request size
        add     dx,size rbfree_s
        mov     bx,ds:[rb_1st_free]     ; (BX) = 1st free record
rs00:   mov     di,bx                   ; (BX) = (DI) = free record
        mov     bx,ds:[di].rf_next_free ; (BX) = next free record
        mov     ax,[di].rf_size         ; (AX) = size/free field
        test    ax,RPF_FREE
        jz      rsie00                  ; jmp if is busy record

        and     ax,RPF_USED             ; clear the free flag

;       Check that the free block is at least 3 words larger than the
;       requested size so that a new free block can also be carved out

        cmp     ax,dx                   ; can a new free also be carved?
        jae     rs20                    ; Yes - go do carve

        cmp     bx,di                   ; last free block?                      ; B728969
        jna     rs10                    ; Yes - could not successfully allocate ; B728969

        cmp     ax,cx                   ; is is an exact fit?                   ; B728969
        je      rs20                    ; Yes - use record                      ; B728969

        jmp     rs00                    ; Otherwise - try next record           ; B728969

;       We get here when free space granularity is too small to allow
;       a successful allocation, or an exact fit has been found on the
;       last_free. So we  need to indicate that the segment must be grown.

rs10:                                   ; B728969
        stc
        jmp     short rs30

;       (CX) = requested size (including size field)
;       (DI) = free record
;       (DS) = RMP segment

;       Go take this block out of the free block links

rs20:   call    Rmp_Link                ; Rmp_link(cx=req_size,ds:di=free blk)
        clc
rs30:   ret

EndProc Rmp_Srch




;**     Rmp_Comp - Compact the RMP segment
;
;       Rmp_Comp compacts the RMP segment sliding down the valid records
;       over the freed up records. If after the compaction too much free
;       space exists then part of it is released.
;
;       ENTRY   DS = managed record segment
;
;       EXIT    NONE
;
;       USES    AX, BX, CX, DX, SI, DI, FLAGS
;


Procedure Rmp_Comp,near,local
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

;       PMONLY  TASKTIME
        TaskTime

        push    es
        mov     bx,ds
        mov     es,bx                   ; (ES) = (DS) = manage record seg
        xor     bx,bx                   ; (BX) = moved size for yield check
        mov     di,ds:[rb_1st_free]
        mov     si,di                   ; (SI) = (DI) = 1st free record
rc00:   mov     cx,[si].rf_next_free
        cmp     cx,si                   ; Have we got to the last free
        je      rc10                    ; Yes!

        mov     ax,[si].rf_size         ; AX = size of free record
        and     ax,RPF_USED             ; clear the free flag
        add     si,ax
        sub     cx,si                   ; CX = size to be moved

;       DI = offset to destination of compaction slide
;       SI = offset to source of compaction slide
;       CX = size to move (in bytes)

        add     bx,cx                   ; Keep account of size moved
        call    Rmp_Move                ; do the motion
        mov     ax,si                   ; set size of tmp free block as
        sub     ax,di                   ; we may yield before next move
        or      ax,RPF_FREE             ; and we need FindN to succeed
        mov     ds:[di].rf_size,ax
        jmp     short rc00

;       Motion is complete so go set all the pointers

rc10:   mov     ds:[rb_1st_free],di     ; set the free pointers
        mov     ds:[rb_last_free],di
        mov     ds:[di].rf_prev_free,rb_size
        mov     ds:[di].rf_next_free,di
        mov     ax,ds:[rb_size]         ; setup last free size
        sub     ax,di                   ; "
        mov     dx,ax                   ; (AX) = (DX) = size last free block
        or      ax,RPF_FREE             ; mark as free
        mov     ds:[di].rf_size,ax
        cmp     dx,RMPGRAN * 2
        jb      rc20                    ; jmp if don't need to shrink seg

;       Need to shrink segment as we have too much free space at the end
;
;       (DS) = rmp segment
;       (BX) = new segment size (needs to be rounded down)

        mov     bx,di
        add     bx,(2 * RMPGRAN)-1
        and     bx,RMPMASK              ; (BX) = new segment size
        push    di                      ; save last free ptr
        call    rmprealloc              ; (ds) = rmp sel, (bx) = newsize
        pop     di                      ; restore last free ptr
        jc      rc20                    ; jump if realloc fails

        mov     ds:[rb_size],bx         ; set new seg size
        sub     bx,di                   ; setup last free size
        mov     ds:[rb_free_size],bx    ; set total free size
        or      bx,RPF_FREE             ; mark as free
        mov     ds:[di].rf_size,bx      ; "
rc20:   pop     es
        ret

EndProc Rmp_Comp




;**     Rmp_Move - Perform the slide down of a RMP section
;
;       Rmp_Move performs the motion of a section of valid records in a
;       RMP segment. The motion is performed within a compaction scenario
;       whereby a free record is overwritten ie. slide towards lower addresses
;
;       ENTRY   (DS:SI) = Move Source
;               (ES:DI) = Move Destination
;               CX = bytes to be moved
;               BX = total number of bytes moved since last yield
;
;       ENTRY   (DS:SI) = End Move Source
;               (ES:DI) = End Move Destination
;
;       USES    AX, BX, CX, DX, SI, DI, FLAGS
;


Procedure Rmp_Move,near,local
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

;       PMONLY  TASKTIME
        TaskTime

        cmp     bx,RMPMAXMOVED
        jb      rp00                    ; jmp - don't yield

;       the compaction is taking too long so we must yield to give higher
;       priority threads a chance to run

        push    es
        push    ds
        pusha
        CALLFAR TCYield
        popa
        pop     ds
        pop     es
        xor     bx,bx                   ; clear yield counter

rp00:   cld
        REPMOVSD
        ret

EndProc Rmp_Move



;**     Rmp_Link - Link the free blocks
;
;       Rmp_Link resets the free block links when at free block large enough
;       to satisfy a request has been found.
;
;       ENTRY   DS = managed record segment
;               DI = free block to be removed from links --> busy block
;               CX = requested length (includes size field)
;
;       EXIT    DS:DI = free space to be used to hold new record
;
;       USES    AX, BX, CX, DX, SI, FLAGS
;

Procedure Rmp_Link,near,local
        ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

;       PMONLY  TASKTIME
        TaskTime

        mov     ax,di                   ; save ptr to new record
        mov     si,[di].rf_prev_free
        mov     bx,[di].rf_next_free
        .errnz (rf_next_free - rb_1st_free)
        and     [di].rf_size,RPF_USED   ; mark record as busy
        mov     dx,[di].rf_size         ; (DX) = size of current free
        sub     dx,cx                   ; (DX) = size of new free
        or      dx,dx
        jz      rl20                    ; jmp if don't need to carve new free

ifdef VMSTRICT
        cmp     dx,size rbfree_s
        jb      rlie                    ; jmp if is too small to carve free
endif
        add     cx,di                   ; compute start of the new free block
        cmp     bx,di
        ja      rl00                    ; jmp if is not to be last free record

        mov     bx,cx
        mov     ds:[rb_last_free],cx    ; (BX) = (CX) = new (last) free block
rl00:   or      dx,RPF_FREE             ; mark record as free
        mov     di,cx                   ; (DI) = new free
        mov     [di].rf_size,dx         ; set size of new free

        mov     [di].rf_next_free,bx    ; set up links to new free
        mov     [bx].rf_prev_free,di    ; "
        mov     [di].rf_prev_free,si    ; "
        mov     [si].rf_next_free,di    ; "
        jmp     short rl30

rl20:   mov     [bx].rf_prev_free,si    ; delink old free block
        mov     [si].rf_next_free,bx    ;  "
rl30:   mov     di,ax                   ;  "
        ret

ifdef VMSTRICT
rlie:   Panic <'Rmp_Link : too small to carve free'>
endif

EndProc Rmp_Link




;**     Rmp_Rem - Remove Managed Record
;
;       Rmp_Rem removes the managed record by marking the record as invalid
;       and linking it into the linked list of free space.
;
;       ENTRY   (ES:DI) = address of item to remove
;               (DS:BX) = address of handle to managed record segment
;
;       EXIT    NONE
;
;       USES    AX, BX, CX, DX, SI, DI, DS, ES, FLAGS
;

rrmie0: Panic <'Rmp_Rem: semaphore busy'>

Procedure Rmp_Rem,<hybrid,far16>
        ASSUME  DS:NOTHING, ES:NOTHING, SS:TASKAREA

        LocalVar rrm_RmpHand,DWORD      ; Pointer to RMP handle

;       PMONLY  TASKTIME
        TaskTime
        EnterProc <ds,bx>               ; (rrm_RmpHand) = (DS:BX)

;       Get semaphore

        push    es                      ; Save record pointer
        push    di
        les     di,rrm_RmpHand          ; (ES:DI) = pointer to RMP handle
        xor     dl,dl                   ; Don't block, return error if busy
        call    Rmp_Req                 ; Get semaphore
        jc      rrmie0                  ; internal error if semaphore busy

        pop     di                      ; Restore record pointer
        pop     es

;       find record position in chain

        mov     bx,rb_size              ; because target may be below 1st_free
        mov     si,es:[rb_1st_free]
        sub     di,rb_sz_field          ; decrement to actual record start
rm00:   cmp     si,di
        jae     rm10                    ; jmp if link brackets record

        mov     bx,si                   ; keep record of prev_free
        mov     si,es:[si].rf_next_free ; get next free
        jmp     short rm00

;       Link current record into free links

rm10:   push    bp                      ; will use BP as temp free start index
        mov     bp,bx                   ;
        mov     dx,di                   ;
        xor     cx,cx                   ; clear size accumulator
        or      bx,bx                   ;
        jz      rm20                    ; jmp if prev_free is header

        mov     bp,es:[bx].rf_prev_free ; default to prev of prev
        mov     dx,bx                   ; default to concat with prev_free
        mov     ax,es:[bx].rf_size      ;
        and     ax,RPF_USED             ; clear free flag
        mov     cx,ax                   ;
        add     ax,bx                   ; (AX) = start of next rec
        cmp     ax,di                   ;
        je      rm20                    ; jmp if target adjacent to prev_free

        xor     cx,cx                   ; clear size accumulator
        mov     dx,di                   ; clear defaults (no concat prev_free)
        mov     bp,bx                   ;  "
rm20:   mov     ax,es:[di].rf_size      ;
        mov     bx,ax                   ;
        add     cx,ax                   ; size total (+prev free size?)
        add     bx,di                   ; (BX) = start of next rec
        cmp     bx,si                   ;
        jne     rm30                    ; jmp if not adjacent to next_free

        add     cx,es:[si].rf_size      ;
        and     cx,RPF_USED             ; size total (+prev free + next free)
        cmp     si,es:[rb_last_free]    ;
        mov     si,es:[si].rf_next_free
        jne     rm30                    ; jmp if not last_free

        mov     es:[rb_last_free],dx    ;
        mov     si,dx                   ; set up last free to point to itself
rm30:   mov     bx,bp                   ; set where prev_free is to start
        pop     bp                      ;
        add     es:[rb_free_size],ax    ; FreeTotal=FreeTotal+target size only
        mov     di,dx                   ; set where actual free is to start
        mov     es:[bx].rf_next_free,di
        mov     es:[si].rf_prev_free,di ; BUGBUG - MUST be before rm40
        mov     es:[di].rf_size,cx
rm40:   mov     es:[di].rf_prev_free,bx
        mov     es:[di].rf_next_free,si

;       mark record as invalid

        or      es:[di].rf_size,RPF_FREE

;       If free size is > 4k compact the records

        cmp     cx,RMPMAXFREE
        jb      rm50                    ; jmp if don't need compaction

        mov     ax,es
        mov     ds,ax
        call    Rmp_Comp                ; Rmp_Comp (DS=Rmp_Seg)

;       Release semaphore and return.

rm50:   lds     si,rrm_RmpHand          ; (DS:SI) = pointer to RMP handle
        call    Rmp_Clr                 ; Release semaphore
        LeaveProc
        ret

EndProc Rmp_Rem



;***    Rmp_Find - Find Record in Managed Segment
;
;       Rmp_Find serially scans the records in a segment until it
;       finds one which matches the caller's criteria or it reaches
;       the end.  There is no way for Rmp_Find to find a second or
;       third match for the criteria without first removing the first match.
;       Call Rmp_FindN to locate additional records meeting the same criteria.
;
;       ENTRY   (DS:SI) = address of match pattern
;               (CX) = length of match pattern
;               (BX) = offset into record to start match
;               (ES:DI) = address of handle to managed segment
;
;       EXIT    Carry clear
;                   (ES:DI) = start of found record (NOT start of matched area)
;                   (CX)    = size of record found
;               Carry set
;                   No match found
;
;       USES    AX, BX, DI, ES, FLAGS
;

Procedure Rmp_Find,<hybrid,far16>
        ASSUME  DS:NOTHING, ES:NOTHING, SS:TASKAREA

;       PMONLY  TASKTIME
        TaskTime
        SaveReg <dx,cx,bx>

;       Wait for semaphore.

        mov     dl,1                    ; Block if semaphore busy
        call    Rmp_Wait                ; Wait for semaphore

;       Call Rmp_FindN to do the work.

        RestoreReg <bx,cx>
        mov     dx,rb_first+rb_sz_field ; start search at beginning
        sub     al,al                   ; consider all records
        call    Rmp_FindN
        RestoreReg <dx>

        ret

EndProc Rmp_Find





;***    Rmp_FindN - Find Next Record in Managed Segment
;
;       Rmp_FindN is just like Rmp_Find except that it starts at a specifed
;       offset rather than at the beginning.  Rmp_FindN is typically used
;       to locate the next matching record after a Rmp_Find or an Rmp_Rem.
;       In the case of following a previous Find, the (AL) flag should be 1
;       to keep from hanging up finding the same record over and over.
;       If a found record was deleted and we're looking for the next one,
;       (AL) should be 0.
;
;       CX may be set to zero to match on any record.  In this way Rmp_FindN
;       may be used to iterate through all records in a segment.
;
;       DX may be set to zero to indicate that the search should start at
;       the beginning of the segment.
;
;       Note that record offsets may change when Rmp_Add is called, so
;       the caller must be careful about context switches when dealing
;       with said offsets.
;
;       ENTRY   (DS:SI) = address of match pattern
;               (CX) = length of match pattern
;               (BX) = offset into record to start match
;               (DX) = offset of first record to consider (within RMP segment)
;               (AL) = number of records to skip from record at (DX)
;               (ES:DI) = address of handle to managed segment
;
;       EXIT    Carry clear if found
;                   (ES:DI) = start of found record (NOT start of matched area)
;                   (CX)    = size of record found
;               Carry set
;                   No match found
;
;       USES    AX, BX, DX, DI, ES, FLAGS
;

Procedure Rmp_FindN,<hybrid,far16>
        ASSUME  DS:NOTHING, ES:NOTHING, SS:TASKAREA

;       PMONLY  TASKTIME
        TaskTime
        SaveReg <bp>                    ; can't destroy BP
        cld
        xor     ah,ah
        mov     bp,ax                   ; (BP low bit) = skip record flag

;       Establish addressability to segment

        test    es:[di].rp_flags,RPF_ALLOC ; Has the segment been allocated?
        jz      rfn70                   ;       no, the record can't exist

        mov     es,es:[di].rp_selector  ; get managed segment's selector

;       We have addressability to RMP segment

        cmp     dx,size rbheadr_s       ; search from beginning?
        jae     rfn10                   ; no, offset is at or beyond 1st record

        mov     dx,size rbheadr_s       ; start search at beginning
        sub     bp,bp                   ; in this case, always look at 1st rec.
rfn10:  mov     di,dx
        sub     di,rb_sz_field          ; ES:DI = addr of len field of 1st
                                        ;        record to scan
        add     bx,rb_sz_field          ; adjust offset for record len field

;       see if more records to search
;
;       ES:DI = address of next record (len word), if any
;       DS:SI = address of match pattern
;       CX = length of match pattern
;       BX = offset from len word to match pattern
;       BP = number of busy records to skip

rfn20:  cmp     di,es:[rb_last_free]
        jae     rfn70                   ; jmp if at end

        mov     ax,es:[di].rf_size      ; AX = length of record
        test    ax,RPF_FREE
        jnz     rfn50                   ; jmp if is a free block

        or      bp,bp
        jz      rfn25                   ; jmp - don't have to skip records

        dec     bp                      ; decrement the skip count
        jmp     short rfn60             ; and go skip the record

rfn25:  jcxz    rfn80                   ; no match string, we're done

        mov     bp,cx                   ; save cx
        mov     dx,bx                   ;
        add     dx,cx                   ; (dx) = match offset + length
rfn30:  cmp     ax,dx                   ; will it overflow end of record?
        jb      rfn40                   ; jmp if pattern won't fit in record

        SaveReg <si, di>                ; do a compare string
        add     di,bx
        repe cmpsb
        RestoreReg <di, si>
        mov     cx,bp                   ; refresh cx
        je      rfn80                   ; found it!

rfn40:  add     di,ax                   ; get next record
        cmp     di,es:[rb_last_free]
        jae     rfn70                   ; jmp if at end

        mov     ax,es:[di].rf_size      ; AX = length of record
        test    ax,RPF_FREE
        jz      rfn30                   ; jmp if block in use

        and     ax,RPF_USED             ; clear the free flag
        jmp     rfn40

rfn50:  and     ax,RPF_USED             ; clear the free flag
rfn60:  add     di,ax                   ; get next record
        jmp     rfn20

;       Return with result.
;
;       'C' set as appropriate
;       ditto other registers

rfn70:  stc
        jmp     short rfn90             ; leave CX if not found

rfn80:  mov     cx,word ptr es:[di]     ; Get size of (record+size field)
        .errnz (rb_sz_field - 2)
        dec     cx
        dec     cx                      ; CX = size of record
        inc     di                      ; this only matters if record is found
        inc     di                      ; dec,inc used as carry not affected
rfn90:  RestoreReg <bp>                 ; restore BP
        ret

EndProc Rmp_FindN


HIGH3CODE ends
        end
