þ a‹R þ w Qþ m^9     þ hý	 oP    þ nSystem-wide$PAGEWIDTH(150)

    NAME CpMemmgr

; This is cp.memmgr.asm~text~
; This contains all the memory manager routines

$NOLIST
$INCLUDE (cp.constant.asm.inc~text~)
;$LIST

DGROUP GROUP DATA
CGROUP GROUP CODE

    PUBLIC CpFree, CpGetSize, InitMemMgr
    PUBLIC IntAllocate, CpAllocate, CpMemInit, CpFreeTaskMem
    PUBLIC CpGetMemStatus

    EXTRN OsVerifyCheckSum:FAR, OsRemoveFromQ:FAR, OsInsertIntoQ:FAR
    EXTRN OsInitQcb:FAR, OsElementCheckSum:FAR, OsElementInQ:FAR
    EXTRN CpWait:FAR, CpSignal:FAR, CpCreateSemaphore:FAR
    EXTRN LQ_DWORD_MUL:FAR
    EXTRN Exception:FAR

    EXTRN OsCurrentPid:WORD  ; current process ID


; constants

bytesPerBlk EQU 16         ; bytes per memory control block


; variables

DATA SEGMENT PUBLIC 'DATA'

EXTRN OsFreeMemQ : BYTE        ; Free memory list
EXTRN OsAllocMemQ : BYTE       ; Alocate memory list
; These were moved to cp.task.asm

dummyError DW ?              ; dummy error location

DATA ENDS


; This must be on a paragraph boundary

MemMgrSemaphore SEGMENT PARA 'DATA'

MemMgrScb ScbType <>         ; Memory manager semaphore

MemMgrSemaphore ENDS


CODE SEGMENT PUBLIC 'CODE'
     ASSUME CS:CGROUP, DS:DGROUP


DataFrame DW DATA            ; segment of data group

$EJ

;    AtOsFreeMemQ
;
;    This will leave the address of OsFreeMemQ on the stack
;    It changes AX and DI

AtOsFreeMemQ PROC NEAR
    POP DI                         ; save return address

    PUSH CS:DataFrame              ; push segment
    MOV AX, OFFSET OsFreeMemQ
    PUSH AX                        ; push offset

    JMP DI                         ; return to caller
AtOsFreeMemQ ENDP


;    AtOsAllocMemQ
;
;    This will leave the address of OsAllocMemQ on the stack
;    It changes AX and DI

AtOsAllocMemQ PROC NEAR
    POP DI                         ; save return address

    PUSH CS:DataFrame              ; push segment
    MOV AX, OFFSET OsAllocMemQ
    PUSH AX                        ; push offset

    JMP DI                         ; return to caller
AtOsAllocMemQ ENDP

$EJ

;    CheckCheckSum : PROCEDURE (scbSeg)
;        DCL scbSeg SELECTOR;
;
;    This will verify the checksum of this scbSeg.  If
;    it is okay, then this returns normally.  If it is not
;    okay, than an exception is raised and this never returns.

scbSeg  EQU WORD PTR [BP+4]

CheckCheckSum PROC NEAR
    PUSH BP
    MOV BP, SP

    CALL AtOsFreeMemQ              ; This assumes Free and Alloc are same size

    PUSH scbSeg                  ; scbSeg

    CALL OsVerifyCheckSum
    RCR AL, 1
    JC CheckDone                   ; IF NOT(OsVerifyCheckSum(@OsFreeMemQ, scbSeg) THEN DO

    PUSH scbSeg
    CALL MemoryCheckSum            ;     Raise Exception

CheckDone:
    MOV SP, BP
    POP BP
    RET 2
CheckCheckSum ENDP

PURGE scbSeg

$EJ

;    AddToFreeChain : PROCEDURE (scbSeg) 
;        DCL scbSeg SELECTOR;
;
;    This will add an scb to the free queue.  If possible
;    it will coallesce it with already freed scbs.  It will
;    always add to the free queue in order of ascending address

scbSeg EQU WORD PTR [BP+6]         ; first param

tempSeg EQU WORD PTR [BP-2]        ; local variable
curScbSeg EQU WORD PTR [BP-4]      ; designated register variable

AddToFreeChain PROC NEAR
    PUSH DS
    PUSH BP                      
    MOV BP, SP
    SUB SP, 4                      ; 2 local variables

    MOV DS, CS:DataFrame
    MOV AX, DS:OsFreeMemQ.headOfQ  ; curScbSeg := OsFreeMemQ.headOfQ
    MOV curScbSeg, AX             

    MOV DS, scbSeg
    MOV DS:mmcbPid, nullWord       ; scb.pid := nullWord;
    MOV tempSeg, nullWord          ; tempSeq := nullWord;

AddToTopOfLoop:
    CMP curScbSeg, nullWord        ; DO WHILE curScbSeg <> nullWord;
    JNE AddTo01
    JMP AddToLoopExit

AddTo01:
    PUSH curScbSeg                 ;     verify checkSum

    CALL CheckCheckSum

    MOV AX, curScbSeg
    MOV ES, AX
    ADD AX, ES:mmcbNumBlk
    INC AX                         ;     AX := curScbSeg + curScb.numBlk + 1

    CMP AX, scbSeg                 ;     IF (AX = scbSeg) THEN DO;
    JNE AddToSecondTest

    MOV DS, scbSeg
    MOV AX, DS:mmcbNumBlk
    INC AX
    ADD ES:mmcbNumBlk, AX          ;         curScb.numBlk := curScb.numBlk + scb.numBlk + 1;

    MOV AX, ES
    ADD AX, ES:mmcbNumBlk
    INC AX
    CMP AX, ES:next                ;         IF (curScbSeg + curScb.numBlk + 1) = curScb.next THEN DO;
    JNE AddTo20

    MOV DS, ES:next
    MOV AX, DS:mmcbNumBlk
    INC AX
    ADD ES:mmcbNumBlk, AX          ;             curScb.numBlk := curScb.numBlk + nextScb.numBlk + 1;

    CALL AtOsFreeMemQ              ;             @OsFreeMemQ

    PUSH DS                        ;             curScb.next

    CALL OsRemoveFromQ

    JMP SHORT AddToReturn

AddTo20:                           ;         ELSE
    CALL AtOsFreememQ              ;             @OsFreeMemQ
 
    PUSH ES                        ;             curScbSeg

    CALL OsElementCheckSum

    MOV ES, curScbSeg
    MOV ES:checkSum, AX            ;             curScb := CheckSum(@OsFreeMemQ, curScbSeg)
    JMP SHORT AddToReturn

AddToSecondTest:
    MOV DS, scbSeg
    MOV AX, DS
    ADD AX, DS:mmcbNumBlk
    INC AX
    CMP AX, curScbSeg              ;     IF (scbSeg + scb.numBlk + 1) = curScbSeg THEN DO;
    JNE AddToThirdTest

    MOV ES, curScbSeg
    MOV AX, ES:mmcbNumBlk
    INC AX
    ADD DS:mmcbNumBlk, AX          ;         scbSeg.numBlk := scbSeg.numBlk + curScbSeg.numBlk + 1;

    CALL AtOsFreeMemQ              ;         @OsFreeMemQ

    PUSH curScbSeg                 ;         curScbSeg

    CALL OsRemoveFromQ

    CALL AtOsFreeMemQ              ;         @OsFreeMemQ

    PUSH tempSeg                   ;         tempSeg

    PUSH scbSeg                    ;         scbSeg

    CALL OsInsertIntoQ

    JMP SHORT AddToReturn

AddToThirdTest:
    MOV AX, curScbSeg
    CMP AX, scbSeg                 ;     IF curScbSeg > scbSeg THEN GOTO LoopExit;
    JA AddToLoopExit

    MOV tempSeg, AX                ;     tempSeg := curScbSeg

    MOV ES, AX
    MOV AX, ES:next                ;     curScbSeg := curScb.next
    MOV curScbSeg, AX
    JMP AddToTopOfLoop

AddToLoopExit:
    CALL AtOsFreeMemQ              ; @OsFreeMemQ

    PUSH tempSeg                   ; tempSeg

    PUSH scbSeg                    ; scbSeg

    CALL OsInsertIntoQ

AddToReturn:
    MOV SP, BP
    POP BP
    POP DS
    RET 2
AddToFreeChain ENDP

PURGE tempSeg, scbSeg, curScbSeg

$EJ

;    Allocated : PROCEDURE (scbSeg) BOOLEAN
;        DCL scbSeg SELECTOR;
;
;    This will return TRUE if the segment is in OsAllocMemQ

scbSeg EQU WORD PTR [BP+4]

Allocated PROC NEAR
    PUSH BP
    MOV BP, SP

    CALL AtOsAllocMemQ             ; @OsAllocmemQ

    PUSH scbSeg                    ; scbSeg

    CALL OsElementInQ

    POP BP
    RET 2
Allocated ENDP

PURGE scbSeg

$EJ

;    EnterMemMgr : PROCEDURE;
;
;    This will just wait on the memory manager semaphore

EnterMemMgr PROC NEAR

    MOV AX, SEG MemmgrScb
    PUSH AX                        ; semaphoreId

    MOV AX, nullWord
    PUSH AX                        ; wait forever

    PUSH CS:DataFrame
    MOV AX, OFFSET dummyError      ; @dummyerror
    PUSH AX

    CALL CpWait

    RET
EnterMemMgr ENDP


;    LeaveMemMgr : PROCEDURE
;
;    This will signal the memory manager semaphore

LeaveMemMgr PROC NEAR

    MOV AX, SEG MemmgrScb
    PUSH AX                        ; semaphoreId

    MOV AX, signalNormal
    PUSH AX                        ; normal signal

    PUSH AX                        ; random note

    PUSH CS:DataFrame
    MOV AX, OFFSET dummyError      ; @dummyerror
    PUSH AX

    CALL CpSignal

    RET
LeaveMemMgr ENDP

$EJ
;    CpFree : PROCEDURE (pBlock, pError) CLEAN;
;
;    This will free a segment that must have already
;    been allocated.  All this does is retrun the segment
;    to the free queue.

pError EQU DWORD PTR [BP+8]        ; second param
pBlock EQU DWORD PTR [BP+12]       ; first param
pBlockSeg EQU WORD PTR [BP+14]     ; segment of pBlock6

; let DS be the scbSeg

CpFree PROC FAR
    PUSH DS
    PUSH BP
    MOV BP, SP

    CALL EnterMemMgr

    MOV AX, pBlockSeg
    DEC AX
    MOV DS, AX                     ; scbSeg := SELECTOR(pBlock) - 1;

    PUSH DS                        ; scbSeg

    CALL Allocated
    RCR AL, 1                      ; IF Allocate(scbSeg) THEN DO;
    JNC OsFree10

    LES BX, pError
    MOV WORD PTR ES:[BX], eOK      ;     error = eOK;

    CALL AtOsAllocMemQ             ;     @OsAllocMemQ

    PUSH DS                        ;     scbSeg

    CALL OsRemoveFromQ

    PUSH DS                        ;     scbSeg

    CALL AddToFreeChain
    JMP SHORT OsFreeDone

OsFree10:                          ; ELSE 
    LES BX, pError
    MOV WORD PTR ES:[BX], eInvMemBlock ;     error := eInvMemBlock

OsFreeDone:
    CALL LeaveMemMgr
    MOV SP, BP
    POP BP
    POP DS
    RET 8
CpFree ENDP

PURGE pError, pBlock, pBlockSeg

$EJ

;    CpGetSize : PROCEDURE (pBlock, pError) WORD CLEAN;
;
;    This will return the size (in bytes) of an allocated
;    block.  If the block hasn't been allocated, then an error
;    is returned.

pError EQU DWORD PTR [BP+8]         ; second param
pBlock EQU DWORD PTR [BP+12]        ; first param
pBlockSeg EQU WORD PTR [BP+14]      ; segment of pBlock6

; let DS be scbSeg

CpGetSize PROC FAR
    PUSH DS
    PUSH BP
    MOV BP, SP

    CALL EnterMemMgr

    MOV AX, pBlockSeg
    DEC AX
    MOV DS, AX                      ; scbSeg := SELECTOROF(pBlock) - 1;

    PUSH DS                         ; scbSeg

    CALL Allocated
    RCR AL, 1                       ; IF Allocated(scbSeg) THEN DO;
    JNC OsGsNotAllocated

    LES BX, pError
    MOV WORD PTR ES:[BX], eOK       ;     error := eOK;

    MOV AX, DS:mmcbNumBlk
    DEC AX
    MOV CL, 4                       ;     numBytes := ((scb.numBlk - 1) * 16) + scb.remainder
    SHL AX, CL
    ADD AX, DS:mmcbRemainder

    CMP DS:mmcbRemainder, 0
    JNE OsGsDone                    ;     IF scb.remainder = 0 THEN DO;

    ADD AX, bytesPerBlk             ;         numBytes := numBytes + bytesPerBlk;
    JMP SHORT OsGsDone

OsGsNotAllocated:                   ; ELSE DO;
    LES BX, pBlock
    MOV WORD PTR ES:[BX], eInvMemBlock ;    error = einvMemBlock;
    XOR AX, AX                      ;       numBytes := 0;

OsGsDone:
    PUSH AX
    CALL LeaveMemMgr
    POP AX                          ; return numBytes in AX

    MOV SP, BP
    POP BP
    POP DS
    RET 8
CpGetSize ENDP

PURGE pBlock, pBlockSeg, pError

$EJ

;    IntAllocate : PROCEDURE (pid, size, pError) PTR CLEAN;
;        DCL pid PidType;
;        DCL size WORD;
;        DCL pError PTR;
;
;    This will attempt to allocate a block of size bytes.
;    It will grab the first block that is >= the desired size.
;    Thus it uses a "first fit" algorithm.

pError EQU DWORD PTR [BP+8]         ; third param
siz EQU WORD PTR [BP+12]            ; second param
pid EQU WORD PTR [BP+14]            ; first param

numBlkReq EQU WORD PTR [BP-2]       ; local variable
remainder EQU WORD PTR [BP-4]       ; local variable

; let DS be curScbSeg and newScbSeg

IntAllocate PROC FAR
    PUSH DS
    PUSH BP
    MOV BP, SP
    SUB SP, 4                       ; 2 local variables

    CALL EnterMemMgr

    MOV ES, CS:DataFrame
    MOV BX, OFFSET OsFreeMemQ
    MOV DS, ES:[BX].tailOfQ         ; ds := curScbSeg

    MOV AX, siz
    DEC AX
    MOV CL, 4
    SHR AX, CL                      ; numBlkReq := ((size - 1) / 16) + 1;
    INC AX
    MOV numBlkReq, AX

    MOV AX, siz
    AND AX, 0000fh                  ; remainder := size MOD 16;
    MOV remainder, AX

IntAllTopOfLoop:
    MOV AX, DS
    CMP AX, nullWord                ; DO WHILE (curScbSeg <> nullWord);
    JNE IntAll05
    JMP IntAllNone

IntAll05:
    PUSH DS                         ;     curScbSeg

    CALL CheckCheckSum              ;     verify checksum

    MOV AX, DS:mmcbNumBlk
    CMP AX, numBlkReq               ;     IF curScb.numBlk >= numBlkReq THEN DO;
    JAE IntAll15
    JMP IntAllNoRoom

IntAll15:
    DEC AX
    CMP AX, numBlkReq               ;         IF (curScb.numBlk - 1) <= numBlkReq THEN DO;
    JA IntAllUsePart

    CALL AtOsFreeMemQ               ;             @OsFreeMemQ

    PUSH DS                         ;             curScbSeg

    CALL OsRemoveFromQ

    JMP SHORT IntAllFinish          ;             newScbSeg = curScbSeg

IntAllUsePart:                      ;         ELSE DO
    MOV AX, DS
    ADD AX, DS:mmcbNumBlk
    SUB AX, numBlkReq               ;             AX (newScbSeg) := curscbSeg + curScb.numBlk - numBlkReq;
    PUSH AX                         ;             save newScbSeg

    MOV AX, numBlkReq
    INC AX
    SUB DS:mmcbNumBlk, AX           ;             curScb.numBlk := curScb.numBlk - (numBlkReq + 1)

    CALL AtOsFreeMemQ               ;             @OsFreeMemQ

    PUSH DS                         ;             curScbSeg

    CALL OsElementCheckSum
    MOV DS:checkSum, AX             ;             curScb.checksum := OsElementCheckSum(@OsFreeMemQ, curScbSeg)

    POP DS                          ;             from previous PUSH AX

    MOV AX, numBlkReq
    MOV DS:mmcbNumBlk, AX           ;             newScb.numBlk := numBlkReq

    MOV AX, remainder
    MOV DS:mmcbRemainder, AX        ;             newScb.remainder := remainder

IntAllFinish:
    MOV AX, pid
    MOV DS:mmcbPid, AX              ;         newScb.pid := pid

    CALL AtOsAllocMemQ              ;         @OsAllocMemQ

    PUSH DS                         ;         newScbSeg

    CALL OsElementCheckSum
    MOV DS:checkSum, AX             ;         newScb.checkSum := OsElementChecksum(@OsAllocMemQ, newScbSeg)

    CALL AtOsAllocMemQ              ;         @OsAllocMemQ

    MOV AX, nullWord
    PUSH AX                         ;         add to front of q

    PUSH DS                         ;         newScbSeg

    CALL OsInsertIntoQ

    LES BX, pError
    MOV WORD PTR ES:[BX], eOK       ;         error := eOK

    MOV AX, DS
    INC AX
    MOV ES, AX                      ;         ES:BX := BUILDPTR(newScbSeg + 1, 0)
    XOR BX, BX

    JMP SHORT IntAllDone

IntAllNoRoom:
    MOV DS, DS:prev                 ;     Try previous element
    JMP IntAllTopOfLoop

IntAllNone:
    LDS BX, pError
    MOV WORD PTR DS:[BX], eOutOfMem ;         error := eOK

    MOV AX, nullWord
    MOV ES, AX                      ; no memory, return nullPtr
    MOV BX, 0000fh

IntAllDone:

    PUSH ES
    PUSH BX
    CALL LeaveMemMgr                ; save result, leave memmgr, restore result
    POP BX
    POP ES

    MOV SP, BP
    POP BP
    POP DS
    RET 8
IntAllocate ENDP

PURGE pError, siz, pid, numBlkReq, remainder

$EJ

;    CpAllocate : PROCEDURE (size, pError) PTR CLEAN;
;        DCL size WORD;
;        DCL pError PTR;
;
;    This is the user's version of allocate.  It will allocate the segment on
;    behalf of the current pid

pError EQU DWORD PTR [BP+6]          ; second param
siz EQU WORD PTR [BP+10]             ; first param

CpAllocate PROC FAR
    PUSH BP
    MOV BP, SP

    MOV AX, SEG OsCurrentPid
    MOV ES, AX
    MOV BX, OFFSET OsCurrentPid      ; OsCurrentPid
    PUSH WORD PTR ES:[BX]

    PUSH siz                         ; size

    LES BX, pError
    PUSH ES                          ; pError
    PUSH BX

    CALL IntAllocate

    MOV SP, BP
    POP BP
    RET 6
CpAllocate ENDP

PURGE pError, siz


;    MemoryCheckSum : PROCEDURE (scbSeg)
;        DCL scbSeg SELECTOR;
;
;    This will raise a memory exception

MemoryCheckSum PROC NEAR
    PUSH BP

    MOV AX, memException
    PUSH AX
    MOV AX, eCheckSum
    PUSH AX
    CALL Exception

    POP BP
    RET 2
MemoryCheckSum ENDP

$EJ

;    CpMemInit : PROCEDURE (initSeg, numBlk) CLEAN
;        DCL initSeg SELECTOR;
;        DCL numBlk WORD;
;
;    This will add another free block of memory to the free chain

numBlk EQU WORD PTR [BP+8]           ; second param
initSeg EQU WORD PTR [BP+10]         ; first param

CpMemInit PROC FAR
    PUSH DS
    PUSH BP
    MOV BP, SP

    MOV DS, initSeg                  ; DS ^ initial free segment

    MOV AX, numBlk
    DEC AX
    MOV DS:mmcbNumBlk, AX            ; initScb.numBlk := numBlk - 1

    MOV DS:mmcbRemainder, 0          ; initScb.remainder := 0

    PUSH DS
    CALL AddToFreeChain              ; Add this block to the free chain

    POP BP
    POP DS
    RET 4
CpMemInit ENDP

PURGE numBlk, initSeg

$EJ

;    InitMemMgr : PROCEDURE CLEAN;
;
;    This will initialize the memory manager
;    but leave all of the queues empty

InitMemMgr PROC FAR
    PUSH DS

    CALL AtOsAllocMemQ               ; @OsAllocMemQ
    
    MOV AL, TRUE
    PUSH AX                          ; FALSE
    
    MOV AX, SIZE MemCbType
    PUSH AX                          ; size of a memory control block

    CALL OsInitQcb

    CALL AtOsFreeMemQ                ; @OsFreeMemQ
    
    MOV AL, TRUE
    PUSH AX                          ; FALSE
    
    MOV AX, SIZE MemCbType
    PUSH AX                          ; size of a memory control block

    CALL OsInitQcb

    MOV AX, SEG MemMgrScb
    MOV DS, AX

    PUSH DS                          ; memmgr sid

    CALL CpCreateSemaphore
   
    MOV DS:scbBusy, signalled        ; signal this semaphore to prime it

    POP DS
    RET
InitMemMgr ENDP

$EJ

;    CpFreeTaskMem : PROCEDURE (pid) CLEAN;
;        DCL pid PidType;
;
;    This will search the allocate queue for any blocks
;    that have been allocated on behalf of this pid and free them.

pid EQU WORD PTR [BP+8]              ; first param

; let DS be curScbSegc

CpFreeTaskMem PROC FAR
    PUSH DS
    PUSH BP
    MOV BP, SP

    CALL EnterMemMgr

    MOV DS, CS:DataFrame
    MOV BX, OFFSET OsAllocMemQ
    MOV DS, DS:[BX].headOfQ

OsFtmTopOfLoop:
    MOV AX, DS
    CMP AX, nullWord                 ; DO WHILE (curScbSeg <> nullWord)
    JE OsFtmDone

                                
    PUSH DS                          ;     curScbSeg

    CALL CheckCheckSum               ;     verify checksum

    MOV AX, DS:next
    PUSH AX                          ;     save next element

    MOV AX, DS:mmcbPid
    CMP AX, pid                      ;     IF curScb.pid = pid THEN DO;
    JNE OsFtmNotEqual

    CALL AtOsAllocMemQ               ;         @OsAllocMemQ

    PUSH DS                          ;         curScbSeg

    CALL OsRemoveFromQ

    PUSH DS                          ;         curScbSeg

    CALL AddToFreeChain

OsFtmNotEqual:
    POP DS                           ;     Get next segment from previous push
    JMP SHORT OsFtmTopOfLoop

OsFtmDone:
    CALL LeaveMemMgr

    MOV SP, BP
    POP BP
    POP DS
    RET 2
CpFreeTaskMem ENDP

PURGE pid

$EJ

;    CpGetMemStatus : PROCEDURE (pid, pMemStatus, pError) CLEAN;
;        DCL pid PidType;
;        DCL (pMemStatus, pError) PTR;
;
;    This will return status information about the memory manager.
;    If pid is null then information is about all processes, else its about
;    that particular process.

pError EQU DWORD PTR [BP+8]          ; third param
pMemStatus EQU DWORD PTR [BP+12]     ; second param
pid EQU WORD PTR [BP+16]             ; first param

qToUse EQU DWORD PTR [BP-4]          ; which q
qToUseSeg EQU WORD PTR [BP-2]
qToUseOff EQU WORD PTR [BP-4]
pStatus EQU DWORD PTR [BP-8]         ; which part of status
pStatusSeg EQU WORD PTR [BP-6]
pStatusOff EQU WORD PTR [BP-8]
count EQU WORD PTR [BP-10]           ; counter
tempSize EQU DWORD PTR [BP-14]       ; temp 32 #
tempSizeHigh EQU WORD PTR [BP-12]
tempSizeLow EQU WORD PTR [BP-14]

; let DS be curScbSeg

CpGetMemStatus PROC FAR
    PUSH DS
    PUSH BP
    MOV BP, SP
    SUB SP, 14                       ; many local vars
    CALL EnterMemMgr

    LDS BX, pError
    MOV WORD PTR DS:[BX], eOK        ; error := eOK

    MOV AL, 0
    LES DI, pMemStatus
    MOV CX, SIZE MemStatusType       ; set status to zeroes
    CLD
    REP STOSB

    LDS BX, pMemStatus
    MOV pStatusSeg, DS               ; initialize status pointer
    MOV pStatusOff, BX

    MOV AX, CS:DataFrame
    MOV qToUseSeg, AX
    MOV AX, OFFSET OsFreeMemQ        ; first use freeQ
    MOV qToUseOff, AX

    MOV count, 2                     ; loop twice

OsGetTopLoop1:
    LDS BX, qToUse
    MOV DS, DS:[BX].headOfQ

OsGetTopLoop2:
    MOV AX, DS
    CMP AX, nullWord                 ; DO WHILE (curScbSeg <> nullWord);
    JNE OsGet05
    JMP SHORT OsGetLoop2

OsGet05:
    PUSH DS                          ;     curScbSeg

    CALL CheckcheckSum               ;     verify checksum
    
    CMP count, 1                     ;     IF second time THEN DO;
    JNZ OsGet07

    MOV AX, pid
    CMP AX, nullWord                 ;        IF (pid = nullWord)
    JZ OsGet07

    CMP AX, DS:mmcbPid               ;        OR (pid = cur.pid) THEN process this one
    JNZ OsGetNext

OsGet07:
    LES BX, pStatus

    INC ES:[BX].freeBlocks           ;     memStatus.freeBlocks += 1;

    MOV AX, DS:mmcbNumBlk
    MOV DX, 0
    MOV CX, 16                       ;     tempSize := DOUBLE(curScb.numBlk) * 16
    MOV DI, 0
    CALL LQ_DWORD_MUL
    MOV tempSizeLow, AX
    MOV tempSizeHigh, DX

    LES BX, pStatus
    MOV CX, ES:[BX+freeBytesLow]
    MOV DI, ES:[BX+freeBytesHigh]
    ADD CX, AX
    ADC DI, DX                       ;     status.freeBytes += tempSize
    MOV ES:[BX+freeBytesLow], CX
    MOV ES:[BX+freeBytesHigh], DI

    AND DI, DI                       ;     IF tempSize > 65536 THEN DO;
    LES BX, pStatus
    JZ OsGetLessThanBigNum

    MOV ES:[BX].largestFree, nullWord;         status.largestFree = 65535
    JMP SHORT OsGetNext

OsGetLessThanBigNum:                 ;     ELSE
    MOV AX, tempSizeLow              ;         IF status.largestFree < tempsize THEN DO
    CMP AX, ES:[BX].largestFree
    JB OsGetNext

    MOV ES:[BX].largestFree, AX      ;             status.largestFree := tempSize

OsGetNext:
    MOV DS, DS:next
    JMP SHORT OsGetTopLoop2

OsGetLoop2:

OsGetLoop1:
    MOV AX, OFFSET OsAllocMemQ       ; now use alloc q
    MOV qToUseOff, AX

    MOV AX, allocBytesOffset
    ADD pStatusOff, AX               ; now pStatus points to .allocBytes

    DEC count
    JZ OsGetDone
    JMP SHORT OsGetTopLoop1

OsGetDone:
    CALL LeaveMemMgr
    MOV SP, BP
    POP BP
    POP DS
    RET 10
CpGetMemStatus ENDP

PURGE pError, pMemStatus, pid, qToUse, qToUseSeg, qToUseOff
PURGE pStatus, pStatusSeg, pStatusOff, count
PURGE tempSize, tempSizeHigh, tempSizeLow

CODE ENDS

    END
