;       SCCSID = @(#)buf.asm    6.23 92/03/06
SCCSID  equ     "@(#)buf.asm    6.23 92/03/06"
        PAGE    ,132
TITLE   BUF - I/O Buffer Management
NAME    BUF
;
;***    Buffer management for MS-DOS 5.0
;
;  Title:       Buf - Manage disk sector buffers.
;
;  Author:      Others
;               Benjamin W. Slivka
;               (c) Microsoft Corporation
;               1986,1987
;
;  History:     11 Nov 1986 - BWS
;                   Major rewrite of existing code.  Put buffers in single
;                   segment, added backward links, free list.
;               01-Jun-1987 - BWS
;                   IFDEF'd out old buffer profiling code, due to redesign
;                   of profiling support.  Could be modified for new profiler.
;               02-Jun-1987 - BMW
;                   Deserialization of buffer cache.
;               28-Jun-1987 - BWS
;                   Yield CPU after getting buffer to reduce dispatch latency.
;               17-Aug-1987 - BWS,RJS
;                   Remove free list, do PlaceBufLRU on free buffers.
;               06-Aug-1991 - SFB
;                   Add perfview counters for FAT Disk Cache pb722131
;               15-Nov-1991 - SFB
;                   Remove perfview tmwrt qlQwrt from FBC group aa04447
;               03-Mar-1992 - SFB
;                   reduce perfview overhead sfb
;               14-Nov-1992 - ACW defect 57219
;                   Add check for ((#Buffers * SizeofBuffers) > 64K) in BufInit
;               15-Dec-1993 - RAC defect 76699
;                   Allow optional assembly of Perfview Code
;
;               12-Sept-1994 - FxM defect 97886
;                   Make BUFFERS holding FAT less likely for reallocation.
;                   Extend time slice if cache hit in BUFFERS.
;
;               19-Oct-95 - scott garfinkle defect 140726
;                   Serialize access to VPBs.
;
;               27-Nov-1995 - MLM defect 72417
;                   Made FlushBufND,BufInvalidate a hybrid entry point
;
;       Global          Local
;
;       BufAssert_FAIL_Busy
;       BufAssert_FAIL_Clean
;       BufAssert_FAIL_hVPB
;       BufAssert_FAIL_IsBuf
;       BufAssert_FAIL_NotBusy
;       AssertNoBufProc
;       w_DiskReset
;       BufInvalidate
;       BufInvalidate2
;       BufFree
;       BufIncRef
;       BufDecRef
;       BufNext
;       ScanPlace
;                       PlaceBufMRU
;       PlaceBufLRU
;                       UnlinkBuf
;       GetBuffrBP
;       GetBuffr
;       GetBuffrB
;                       ChkBufCache
;                       ChkABuf
;       ReCheckWriteRange
;       ReCheckReadRange
;       ChkBufPtr
;                       GetFreeBuffr
;       RlseBuffr
;                       LockBuf
;       UnlockBuf
;       FlushBuf
;       FlushBuf2
;       FSD_FlushBuf
;                       FlushFsdBuf
;       PointComp
;                       WaitBuf
;                       BufWrite
;       RemoveBufVPB
;                       ChkNoBuf
;       GetOverlapBuf
;       BufInit
;                       BufAlloc
;                       BufSize
;                       BufLinkUp
;
;  Design:
;
;       The Buffer Segment has the following structure:
;
;
;    +--------------------+
;    |                    |
;    |      +---------+---------+----------+------------------------+ Buffer
;    |   +--| MRUHead | MRUTail | FreeHead |                        | Segment
;    |   |  +---------+---------+----------+------------------------+ Header
;    |   |
;    |   |            =
;    |   |           ===
;    |   |            |
;    |   |  +------+--|---+----------+-----+--------+-------+-------+
;    |   +->| next | prev | freeLink | vpb | sector | flags |       | Most
;    |      +--|---+------+----------+-----+--------+-------+-------+ Recently
;    |      |  |                                                    | Used
;    |      |  |                                                    | Buffer
;    |      +--|----------------------------------------------------+
;    |         |      A
;    |         |      |
;    |         ...   ...
;    |
;    |         |      |
;    |         V      |
;    |      +------+--|---+----------+-----+--------+-------+-------+
;    +---->| next | prev | freeLink | vpb | sector | flags |        | Least
;           +--|---+------+----------+-----+--------+-------+-------+ Recently
;           |  |                                                    | Used
;           |  |                                                    | Buffer
;           +--|----------------------------------------------------+
;              |
;             ===
;              =
;
;
;       The buffer segment is allocated and initialized by the system boot
;       code, the size of the number of buffers and size of the segment are
;       computed from the buffers= parameter in config.sys.
;
;       The buffer segment begins with a header record which contains the
;       "MRUHead", "MRUTail", and "FreeHead" pointers.  These are offsets
;       into the buffer segment that point to, respectively, the
;       Most Recently Used (MRU), the Least Recently Used (LRU), and the
;       first Free buffer.
;
;       A "buffer" consists of a "buffer header" immediately followed by a
;       "data area" that is large enough to hold the largest disk sector
;       among all devices detected at boot-time.  The buffer header has the
;       following fields: flags, VPB, sector, prev, next, freeLink.
;
;       The flags field contains bit flags to indicate the type of sector in
;       the buffer (FAT, Directory, or data) and the status of the buffer
;       (busy, wanted, and/or dirty).
;
;       The VPB and sector fields uniquely identify which sector of which
;       volume is contained in the buffer.  If the VPB is zero, then the
;       buffer is "free" and contains no valid data.
;
;       The "next" and "prev" fields link up all of the buffers in the buffer
;       segment.  The buffers are maintained in an MRU order through the "next"
;       field starting with the MRUHead pointer.  Hence, starting with the
;       MRUTail pointer and following the "prev" pointer results in an LRU
;       traversal of the buffers.
;
;       The "freeLink" field is used to maintain a list of free buffers, i.e.,
;       buffers which are not currently associated with a sector.  A buffer
;       is always on the MRUHead/MRUTail lists, but is placed on the free list
;       only when it no longer contains valid data (which is indicated by a
;       0 VPB field).  RemoveBufVPB is the routine that puts a buffer on the
;       free list.  We cannot take a buffer off the MRU list when it is free
;       because this operation may confuse a buffer scan in progress.
;
;       Except as noted in the next paragraph, each thread may have only one
;       buffer allocated at a time, and the far pointer to this buffer is
;       contained in the CurBuf field of the TCB.
;
;       The exceptions to the single buffer rule occur during multi-sector
;       reads and writes that overlap buffers in the buffer cache.  For
;       writes, the writer calls BufFree to scan the cache.  BufFree will
;       find and invalidate all buffers which overlap the write region.
;       For reads, the reader calls BufIncRef, which will mark a collection
;       of buffers "busy" by incrmenting their buf_refcnt fields.  When a
;       buffer is "busy" in this way, we allow other readers to use it
;       for multi-sector transfers, but treat it as busy for all other
;       purposes, including single sector reads to deblock heads and tails.
;
;       The primary interface is the GetBuffr routine.  It returns a buffer
;       filled in with the data for a requested <volume,sector> pair.  GetBuffr
;       first scans the buffer segment in MRU order looking for a match.  If
;       no match is found, GetFreeBuffr is called to get a free buffer.
;       GetFreeBuffr scans the buffer list in LRU order looking for a "clean"
;       buffer, i.e., one that is not dirty, busy, or wanted.
;
;
;  ToDo?:
;
;       Make GetBuffr's search algorithm faster.  One possibility is to
;       maintain an array of sector numbers at the head of the buffer segment.
;       Use SCASW to search for match.  Check VPB to complete test.
;
;       Write protect errors in buffer flushes are not
;       handled very well.  Some recognition of the
;       condition should be made to avoid multiple
;       hard errors.
;
;       The current implementation of FlushBuf can lead
;       to starvation as one process is forced to do the
;       work for one or more processes which are adding
;       dirty buffers to the list.
;
;       Asynchronous buffer writes would be nice.  We
;       would need an I/O request header per buffer.
;       Error handling will become even more complicated
;
;  NOTE ON SWAPPER INTERACTION:
;
;       We no longer do funky things to the buffer cache on behalf of
;       the swapper.  However, GetBuffrBP does do a Bypass read, rather
;       than a normal read, to fill a buffer.  This should be used when
;       fetching partial buffers for the swapper.  Since the swapper always
;       writes full sectors, we'll never write a swapper buffer, so BufWrite
;       will ALWAYS do normal (not bypass) output.
;       (See BufRd.)
;
;       In GetFreeBuffr, when scanning the buffer chain for a buffer to free,
;       we do not get buffers that contain data for removable media if we are
;       doing this on behalf of the swapper. This minimizes the chances of
;       deadlock between the swapper and the hard_error daemon. (see
;       GetFreeBuffr). The only exception is when we are swapping to a
;       removable media.
;
;  NOTE ON BUFFER CACHE ASSERTIONS:
;
;       Make with "-DBUFSTRICT" to enable buffer cache consistency checking.


.xlist
.xcref
include dosseg.inc
include defdbug.inc
include basemaca.inc
include osmaca.inc
include oldmaca.inc
include osconst.inc
include fnofs.inc
include bpb.inc
include dpb.inc
include buffer.inc
include dossymc.inc
include harderr.inc
include error.inc
include filesys.inc
include fsc.inc
include vmowner.inc
include mi.inc
include protmode.inc
include virtsym.inc
include seldesc.inc
include proc.inc
;include tcbdata.inc
include tkexport.inc
include mft.inc
include timer.inc
include vpb.inc
include pgexport.inc
include sched.inc
include vmexport.inc
include selxport.inc
include filemacs.inc
include sft.inc
include filemode.inc

ifdef PERFVIEW
include perfvw.inc                                                      ;b722131
endif

.cref
.list
        CpuMode 286
        ModName buf,FNO_BUF_ASM

;; BUFFERCHK = 1                        ; define this to turn on checking

ifdef OLD_PROFILE
        ReferGlobal Ptr_BufCache,DWORD
        ReferGlobal Len_BufCache,WORD
        ReferGlobal ProfState,BYTE
endif ; OLD_PROFILE

        ReferFlat       _pvheVDM,DWORD

ifdef PERFVIEW
        ReferFlat       _fPerfview                                     ;sfb
endif

        ReferGlobal     hVPBFirst,WORD
        ReferGlobal     MaxSec,WORD
        ReferFlat       ksemVPB,BYTE    ; sem for vpb list 140726
        ReferFlat       TCResched,BYTE
        ReferFlat       _pPTDACur,dword
        ReferFlat       _pTCBCur,dword
        ReferFlat       _pTSDCur,dword

        ReferTask       CurrTCB,WORD
        ReferTask       AutoFail,BYTE
        ReferTask       ProcType,BYTE
        ReferTask       ProcFlag,WORD
        ReferGlobal     FATCache,BYTE

        ReferGDT        GDT_VPB,WORD
        ReferGDT        GDT_SFT,WORD

        EXTRNFAR        ProcBlock
        EXTRNFAR        ProcRun
        EXTRNFAR        TCYield
        EXTRNFAR        VirtToPhys
        EXTRNFAR        SFFromSFN
        EXTRNFAR        UseCache
        EXTRNFAR        Init_VPB_Cache

        extrn           IncVPBRef:near
        extrn           FatSecRd:near
        extrn           DRead:near
        extrn           DWrite:near
        extrn           DecVPBRef:near

        EXTRN           _SELAllocKHB:near
        EXTRN32          _KSEMRequestMutex                      ;140726
        EXTRN32          _KSEMReleaseMutex                      ;140726
        EXTRN32         _BufRead
        EXTRN32         _BufWrite
        EXTRN32         FreeVPBCacheData
        EXTRN32         ioRestoreTCB
        EXTRN32         ioSaveTCB
        EXTRN32         _BufFlushAll
        EXTRN32         _BufFlushAll2
        EXTRN32         _BufDiscard

;***    BufAsserthVPB - fail assertion if hVPB is invalid
;
;       ENTRY:  (DS) = buffer segment
;               off  = buffer offset
;       EXIT:   none
;       USES:   none

BufAsserthVPB   macro   off
        local   a
ifdef   BUFSTRICT
    ifdif   <off>,<di>
        push    di
        mov     di,off
    endif
        pushf
        cmp     ds:[di].buf_hVPB,BUF_FREE
        jne     a                       ; hVPB is valid

        call    BufAssert_FAIL_hVPB
a:      popf
    ifdif   <off>,<di>
        pop     di
    endif

endif ; BUFSTRICT
        endm


;***    BufAssertIsBuf - fail assertion if offset is not valid
;
;       ENTRY:  (DS) = buffer segment
;               off  = buffer offset
;       EXIT:   none
;       USES:   none

BufAssertIsBuf  macro   off
        local   a,b
ifdef BUFSTRICT
        pushf
        SaveReg <ax,dx>
        mov     ax,off                  ; (ax) = buffer offset
        cmp     ax,ds:[bs_seglimit]     ; Is offset too large?
        ja      a                       ;   Yes, error

        sub     ax,size BUFSEG          ; (AX) = offset - buffer seg header
        xor     dx,dx                   ; (DX:AX) = full offset
        div     ds:[bs_buffsize]        ; (AX) = quotient; (DX) = remainder
        or      dx,dx                   ; Is there a remainder
        jz      b                       ;   No, pointer is fine

a:      call    BufAssert_FAIL_IsBuf
b:      RestoreReg <dx,ax>
        popf
endif;BUFSTRICT
        endm


;***    BufAssertBITS - test assertion of buf_flags bit settings
;
;       ENTRY:  (DS)  = buffer segment
;               off   = buffer offset
;               relop = Jump relational operation for success
;               bits  = buf_flags bits to test
;               lbl   = label to CALL if assertion fails
;       EXIT:   none
;       USES:   none

BufAssertBITS   macro   off,relop,bits,lbl
        local   a
ifdef   BUFSTRICT
    ifdif   <off>,<di>
        push    di
        mov     di,off
    endif
        pushf
        test    ds:[di].buf_flags,bits
        j&relop a                       ; Assertion true
        call    lbl                     ; Assertion false
a:      popf
    ifdif   <off>,<di>
        pop     di
    endif
endif ; BUFSTRICT
        endm


;***    BufAssertBusy - fail assertion if buffer is NOT busy
;
;       ENTRY:  (DS) = buffer segment
;               off  = buffer offset
;       EXIT:   none
;       USES:   none

BufAssertBusy   macro   off
        BufAssertBITS   off,NZ,BUF_BUSY,BufAssert_FAIL_Busy
        endm


;***    BufAssertClean - fail assertion if buffer is BUSY, WANT, or DIRTY
;
;       ENTRY:  (DS) = buffer segment
;               off  = buffer offset
;       EXIT:   none
;       USES:   none

BufAssertClean  macro   off
        BufAssertBITS   off,Z,BUF_BUSY+BUF_WANT+BUF_DIRTY,BufAssert_FAIL_Clean
        endm


;***    BufAssertNotBusy - fail assertion if buffer is busy
;
;       ENTRY:  (DS) = buffer segment
;               off  = buffer offset
;       EXIT:   none
;       USES:   none

BufAssertNotBusy    macro   off
        BufAssertBITS   off,Z,BUF_BUSY,BufAssert_FAIL_NotBusy
        endm


;***    CmpBuf - Check if buffer = (volume, sector)
;
;       The buffer header pointed to by DS:DI is checked to see
;       if it matches the disk and sector number passed.
;
;       ENTRY:  (AX)    = Desired volume
;               (CX:DX) = Desired sector number
;               (DS:DI) = buffer header
;
;               CmpBuf op,lbl
;
;               op  - comparison operator; E  => Equality
;                                          NE => Non-Equality
;               lbl - Transfer control here if <op> relation holds
;
;       EXIT:   none
;
;       USES:   Flags

CmpBuf  MACRO   op,lbl
        local   a
    IFIDN   <op>,<NE>
        CMP     DX,[DI].buf_sector.loword       ; check sector number
        JNE     lbl                             ; Sectors don't match
        CMP     CX,[DI].buf_sector.hiword       ; check sector number
        JNE     lbl
        CMP     AX,[DI].buf_hVPB        ; check volume number
        DLJNE   lbl                     ; Volumes don't match
    ELSE
      IFIDN <op>,<E>
        CMP     DX,[DI].buf_sector.loword       ; check sector number
        JNE     a
        CMP     CX,[DI].buf_sector.hiword       ; check sector number
        JNE     a
        CMP     AX,[DI].buf_hVPB        ; check volume number
        DLJE    lbl
a:
      ELSE
        .ERR    ; Invalid comparison operator
      ENDIF
    ENDIF

ENDM ; CmpBuf


;***    LockBuf - Lock a buffer
;
;       The passed buffer is checked to see if it is busy. If so, the process
;       is suspended until it is available.
;
;       ENTRY:  (DS:DI) = buffer to lock
;
;       EXIT:   Buffer header is marked BUSY
;               ZF set, did not block (buffer cache unchanged)
;                   (JZ NotBlocked)
;               ZF clear, blocked on buffer (buffer may have changed)
;                   (JNZ Blocked)
;
;       USES:   flags
;
;       EFFECTS Switches Context (ProcBlock)

LockBuf macro
        local   lkb0

        TEST    [DI].buf_flags,BUF_VISIT or BUF_ATTEMPTING_READ or BUF_BUSY
        pushf
        jz      lkb0                    ; Busy, wait for it
        call    LockBuf_
lkb0:   push    BP
        MOV     BP,CurrTCB
        mov     BP,[BP].TCBNumber       ; (BP) = thread ID
        MOV     [DI].buf_tid,BP         ; Set thread ID
        pop     BP
        OR      [DI].buf_flags,BUF_BUSY
        popf

ENDM ; LockBuf


        FARCODE HIGH2
HIGH2CODE    SEGMENT
        ASSUME  SS:TASKAREA

ifdef   BUFSTRICT
Entry   BufAssert_FAIL_Busy
        int     3
Entry   BufAssert_FAIL_Clean
        int     3
Entry   BufAssert_FAIL_hVPB
        int     3
Entry   BufAssert_FAIL_IsBuf
        int     3
Entry   BufAssert_FAIL_NotBusy
        int     3
endif ; BUFSTRICT


;***    AssertNoBufProc - Detect case where current thread is holding a buffer
;
;       AssertNoBufProc is called by the AssertNoBuf macro to detect
;       whether the current thread is holding a buffer.  AssertNoBufProc
;       will call an InternalError if it finds the current thread is
;       holding a buffer.  It's not 100% fool-proof, but should detect
;       most cases.
;
;       AssertNoBufProc is assembled in by the FSSTRICT flag.
;
;       ENTRY   (SS) = TASKAREA
;       EXIT    None if OK, InternalError if error found
;       USES    None, including flags
;

ifdef FSSTRICT
Procedure AssertNoBufProc,HYBRID
        ASSUME  DS:NOTHING,ES:NOTHING,SS:TASKAREA

        pushf
        push    bp
        push    ds
        mov     bp,SS:CurrTCB

;       Do we have a buffer?

        test    SS:[bp].TCBForce_Flg,FF_BUF
        jz      anb1                    ; No force flag, go return OK.

;       Is the buffer busy.  (We shouldn't ever have FF_BUF set and then
;       not be pointing to a busy buffer, but RlseBuffr doesn't enforce
;       this, so we can't either.)

        lds     bp,SS:[bp].TCBCurBuf    ; (DS:BP) -> buffer
        test    DS:[BP].buf_flags,BUF_BUSY
        jnz     anbie1                  ; Busy, go do internalerror.

anb1:   pop     ds
        pop     bp
        popf                            ; Use safe one, we might be called
                                        ; with interrupts off.
        ret

;       We have a buffer, and it's busy.  Report error.
;       (FSSTRICTGER: (TOS) = ds, bp, flags, far-ret)

anbie1: Panic   <"AssertNoBufProc">

EndProc   AssertNoBufProc
endif ;FSSTRICT

ifdef FSSTRICT
        Procedure   checkfat
        SaveReg <ds,es>
        pusha
        BufferContext <DS>
        mov     SI,DS:[bs_MRUHead]      ; (SI) = old MRU buffer; set MRU head
cf0:    cmp     si,-1
        je      cf1

        mov     bx,[si].buf_hVPB
        CMP     bx,BUF_FREE
        je      cfn

        test    [SI].buf_flags,BUF_ISFAT
        jz      cfn                     ; Not a FAT sector

        VPBContext ES,BX
        cmp     es:[bx].vpb_fsd.vpdFAT_FATentrysize,16                             ;@@
        jne     cfn
        lea     di,[si].BUFINSIZ        ; Point at buffer
        mov     ax,ds:[di]
        add     di,2
        mov     di,ds:[di]
        cmp     ax,0fff8h
        je      cfn
        cmp     ax,0ffffh
        je      cfn
        cmp     ax,0fff7h
        je      cfn
        CMP     ax,ES:[bx].vpb_fsd.vpdFAT_max_cluster
;        ja      cfx
        ja      cfn
        cmp     di,0fff8h
        je      cfn
        cmp     di,0ffffh
        je      cfn
        cmp     di,0fff7h
        je      cfn
        CMP     di,ES:[bx].vpb_fsd.vpdFAT_max_cluster
        jbe     cfn
; cfx:    int     3
cfn:
        MOV     SI,[si].buf_next        ; (SI) = next buffer to scan
        jmp     cf0
cf1:    popa
        RestoreReg <es,ds>
        ret
        EndProc     checkfat
endif

;***    w_DiskReset - Flush and invalidate all non-busy buffers for all volumes
;
;       ENTRY:  none
;
;       EXIT:   All dirty buffers written to disk, clean buffers put on
;               free list.
;
;       USES:   all

Procedure   w_DiskReset,<near,faronly,far16>
    assume  ds:nothing,es:nothing

        mov     ax,BUF_FREE             ; (AX) = flush for ALL volumes
        mov     dx,FLU_DISCARD          ; dx = invalidate buffers after flush
        call    FSD_FlushBuf
        ret

EndProc     w_DiskReset


BREAK <BufInvalidate - Invalidate clean buffers for a volume>
;***    BufInvalidate - Invalidate clean buffers for a volume
;
;       Invalidate clean buffers and place on free list.
;
;       ENTRY:  (AX) = VPB for which to invalidate buffers
;                    = BUF_FREE, invalidate for ALL volumes
;
;       EXIT:   Clean buffers for specified volume(s) are invalidated.
;
;       USES:   DI,DS,ES,flags

Procedure   BufInvalidate,hybrid

    ASSUME  DS:NOTHING,ES:NOTHING
;       PMONLY

ifdef BUFFERCHK
        call    ChkBufCache
endif ;BUFFERCHK
        BufferContext <DS>

        SaveReg <BX,CX>

        MOV     BX,DS:[bs_buffsize]     ; (BX) = size of buffer + header
        MOV     CX,DS:[bs_nBuffers]     ; (CX) = number of buffers
        MOV     DI,BUFBASE              ; (DS:DI) -> first buffer

;*  Invalidate loop.
;   DS:DI -> buffer
;   AX    =  hVPB of buffers of interest, or 0 if all are of interest.
;   BX    =  size of a buffer+header
;   CX    =  number of buffers left to examine

bi1:    CMP     AX,BUF_FREE             ; Invalidating for all volumes?
        JE      bi2                     ; yes all volumes, go check flags.

        CMP     [DI].buf_hVPB,AX
        JNE     bi3                     ; Wrong volume, go advance to next buf.

bi2:    TEST    [DI].buf_flags,BUF_BUSY+BUF_DIRTY+BUF_WANT
        JNZ     bi3                     ; cannot free, go advance to next buf.

        and     [di].buf_flags,NOT BUF_BUSY+BUF_DIRTY+BUF_WANT ;buffer not valid

        cmp     [di].buf_hVPB,BUF_FREE  ; Buffer free?
        je      bi3                     ;   YES, already invalid

        CALL    RemoveBufVPB            ; Invalidate buffer

bi3:    ADD     DI,BX                   ; Advance to next buf.
        LOOP    bi1                     ; If any left, go do new one.

        RestoreReg <CX,BX>
        ret

EndProc     BufInvalidate

BREAK <BufInvalidate2 - Invalidate clean buffers for a volume>
;***    BufInvalidate2 - Invalidate clean buffers for a volume
;
;       Invalidate clean buffers and place on free list.
;
;       ENTRY:  (AX) = VPB for which to invalidate buffers
;                    = BUF_FREE, invalidate for ALL volumes
;               (BX) = Type of discard buffer check
;
;       EXIT:   Clean buffers for specified volume(s) are invalidated.
;               If error CY set
;
;       USES:   DI,DS,ES,flags

Procedure   BufInvalidate2,near

    ASSUME  DS:NOTHING,ES:NOTHING
;       PMONLY

ifdef BUFFERCHK
        call    ChkBufCache
endif ;BUFFERCHK
        BufferContext <DS>

        SaveReg <BX,CX>

        MOV     BX,DS:[bs_buffsize]     ; (BX) = size of buffer + header
        MOV     CX,DS:[bs_nBuffers]     ; (CX) = number of buffers
        MOV     DI,BUFBASE              ; (DS:DI) -> first buffer

;*  Invalidate loop.
;   DS:DI -> buffer
;   AX    =  hVPB of buffers of interest, or 0 if all are of interest.
;   BX    =  size of a buffer+header
;   CX    =  number of buffers left to examine

bi21:   CMP     AX,BUF_FREE             ; Invalidating for all volumes?
        JE      bi22                     ; yes all volumes, go check flags.

        CMP     [DI].buf_hVPB,AX
        JNE     bi23                     ; Wrong volume, go advance to next buf.

bi22:   TEST    [DI].buf_flags,BUF_BUSY+BUF_DIRTY+BUF_WANT
        JNZ     bi24                     ; cannot free, bail out

        and     [di].buf_flags,NOT BUF_BUSY+BUF_DIRTY+BUF_WANT ;buffer not valid

        cmp     [di].buf_hVPB,BUF_FREE  ; Buffer free?
        je      bi23                     ;   YES, already invalid

        CALL    RemoveBufVPB            ; Invalidate buffer

bi23:   ADD     DI,BX                   ; Advance to next buf.
        LOOP    bi21                    ; If any left, go do new one.
        clc
        jmp short bi25

bi24:   stc
bi25:
        RestoreReg <CX,BX>
        ret

EndProc     BufInvalidate2


;***    BufFree - Free/Invalidate buffers in range.
;
;       BufFree finds all the buffers in a specified range, gains control
;       of them, and frees them.  Both clean and dirty buffers are made
;       stale by a write, so both clean and dirty buffers are invalidated
;       by this routine.
;
;       NOTE    Because of the way the MFT works, a given sector/buffer
;               can only belong to one file, and only 1 write to a given
;               file is possible at any one time.  Therfore, we will only
;               need to wait for a buffer if it's being used to READ the
;               disk via DASD I/O.  (File reads must wait for write.)
;               DASD I/O writes shouldn't ever be going on concurrently
;               with file system writes.
;
;               This means that the only way for a new stale buffer to
;               appear in the cache while we're blocked is for a DASD
;               read to bring it in.  No new dirty buffers will appear
;               in our range.
;
;       ALGOR.  Scan buffer cache in array order.  If a buffer holds a
;               sector, such that DX <= sector < BX, and hVPB == BP,
;               lock that buffer.  After lock, if buffer is still valid,
;               invalidate it.  If we blocked, set a mark.  We must make
;               a second pass if we block, because a DASD read could
;               introduce new stale buffers above us.
;
;               However, no new dirty buffers will appear in our range,
;               and DASD reads don't happen very often, so we rarely have
;               to take more than two passes.  (We'll NEVER block for a
;               buffer that's out of our range.)
;
;       ENTRY   (BP) = hVPB
;               (ESI) = low sector of range
;               (EAX) = high sector+1 of range
;
;       EXIT    NONE
;
;       USES    bx, ecx, dx, DS, flags
;
;       EFFECTS May block (via LockBuf)

Procedure BufFree,<hybrid,far16>

CpuMode 386

    ASSUME  DS:NOTHING,ES:NOTHING

;       PMONLY
        chkvselhd <GSEL GDT_VPB>,bp

        BufferContext   <DS>

        mov     dx,ds:[bs_buffsize]     ; (dx) = size of buffer + header

;       We come to bf10 to make a pass over the entire cache.

bf10:   movzx   ecx,ds:[bs_nBuffers]    ; (ecx) = number of buffers
        xor     bx,bx                   ; (bx) = 0 didn't block, !0 did, repeat
        mov     di,BUFBASE              ; (DS:DI) -> first buffer

; iterate through the buffers in the cache

bf20:   cmp     [di].buf_sector,esi     ; Is the buffer below our range?
        jb      bf30                    ;   yes, skip it

        cmp     [di].buf_sector,eax     ; Is the buffer in our range?
        jb      bf40                    ;   yes, we need to go deal with it

bf30:   add     di,dx                   ; (DS:DI) -> next buffer
        loop    bf20                    ; loop if more buffers

        or      bx,bx                   ; Did we block?
        jnz     bf10                    ; yes we blocked, go have another pass

;       When we get here, we have passed over the entire cache and invalidated
;       any buffers in our range without blocking.

ifdef BUFFERCHK
        CALL    ReCheckWriteRange       ; Check the state of the cache.
endif ;BUFFERCHK

bfx:    ret

; we have a sector that is in range

bf40:   cmp     [di].buf_hVPB,bp        ; Is the buffer on our volume?
        jne     bf30                    ;   no, not our volume, skip it

;       The buffer is in our range, so lock it, unlock it, and invalidate it.
;       (We don't block between the lock and unlock)
;       If the lock blocks, set a mark, so we know to take another pass.
;       (We jump to bf20 to reconsider the buffer we're looking at.  We'll
;        start from the beginning if we ever block.)

        LockBuf                         ; ZF ? 0:we blocked, 1:we didn't block
                                        ; Busy bit set.  Visit bit clear.

        pushf                           ; Save LockBuf flags
        call    UnlockBuf               ; We ALWAYS unlock for BufFree
        popf                            ; Restore LockBuf flags

        jz      bf60                    ; We did NOT block, go do our thing.

        mov     bx,1                    ; We DID block, remember this.
        jmp     short bf20              ; Go see if buffer in place after block
                                        ; is interesting to us.

;       If we come here, buffer is (still) in our range and valid.

bf60:   call    RemoveBufVPB            ; Invalidate.
        jmp     short bf30

CpuMode reset

EndProc   BufFree


;***    BufIncRef - Increment read reference count for buffers in range.
;
;       BufIncRef finds all the buffers in a specified range, gains control
;       of them, and increments their read reference count.
;
;       NOTE    Because of the way the MFT works, a given sector/buffer
;               can only belong to one file, and only 1 write to a given
;               file is possible at any one time.  Multiple reads may
;               overlap, because read is not destructive.  We will only
;               have to wait for a buffer it's being used to read a head
;               or tail.  No write (or other edit) will dirty a buffer
;               in our range.  Therefore, no new buffer of interest will
;               appear above us in the buffer cache, even accross a block.
;
;       NOTE    BufIncRef applies to a range, BufDecRef applies to a single
;               buffer.
;
;       ALGOR.  Scan buffer cache in array order.  If a buffer holds a
;               sector, such that DX <= sector < BX, and hVPB == BP, and
;               the buffer is dirty, lock that buffer.  After lock, make
;               sure buffer is same buffer.  If not, it doesn't interest
;               us, since no new buffer of interest may appear, therefore,
;               advance to next buffer.
;
;       ENTRY   (BP) = hVPB
;               (ESI) = low sector of range
;               (EAX) = high sector+1 of range
;
;       EXIT    NONE
;
;       USES    ebx, ecx, dx, di, ds, flags
;
;       EFFECTS May block (via LockBuf)

Procedure BufIncRef,faronly

CpuMode 386

    ASSUME  DS:NOTHING,ES:NOTHING
;       PMONLY

        chkvselhd <GSEL GDT_VPB>,bp
        BufferContext   <DS>

        movzx   ecx,ds:[bs_nBuffers]    ; (ecx) = number of buffers
        mov     dx,ds:[bs_buffsize]     ; (dx) = size of buffer + header
        mov     di,BUFBASE              ; (DS:DI) -> first buffer

bir10:  cmp     [di].buf_sector,esi     ; Is the sector before our range?
        jb      bir20                   ;   yes, no need to consider it

        cmp     [di].buf_sector,eax     ; Is the sector in our range?
        jb      bir30                   ;   yes, go mark it busy

bir20:  add     di,dx                   ; ds:di -> next buffer
        loop    bir10                   ; Loop if there are any more buffers

ifdef BUFFERCHK
        CALL    ReCheckReadRange        ; Check the state of the cache.
endif ;BUFFERCHK

birx:   ret                             ; exit

; sector is in range, go check it out

bir30:  cmp     [di].buf_hVPB,BP        ; Is it for the same volume?
        jne     bir20                   ;   no, not our volume, skip it

; buffer is in range, go see if we need to do anything with it

;       This is tricky.
;       The buffer could be
;       NOT BUSY and NOT DIRTY.
;           We'll decide it's clean and skip it.
;       BUSY and NOT DIRTY.
;           We'll decide it's clean and skip it.  This IS safe because
;           no other process will make a buffer DIRTY, even if it's busy,
;           while we're doing a read.  (MFT protects file I/O, locking
;           protects against DASD write.)
;       BUSY and DIRTY and VISIT
;           It's already in use for a read like us, just increment
;           the ref. count.
;       NOT BUSY and DIRTY and VISIT
;           Internal Error.
;       BUSY and DIRTY and NOT VISIT
;       NOT BUSY and DIRTY and VISIT
;           We'll decide it's dirty, and wait for it.  (We wait because it
;           might become clean.)  If it's still of interest after the wait,
;           we mark it VISIT, BUSY and set the ref count to 1.

        test    [di].buf_flags,BUF_DIRTY
        jz      bir20                   ; clean, go to next buffer

        test    [di].buf_flags,BUF_VISIT
        jnz     bir45                   ; already in read use, inc ref count.

        mov     ebx,[di].buf_sector     ; (ebx) = sector in current buffer

        LockBuf                         ; ZF ? 0:we blocked, 1:we didn't block
                                        ; Busy bit set.  Visit bit clear.

        JZ      bir40                   ; We did NOT block, go do our thing.

        cmp     ebx,[di].buf_sector     ; Did the sector change?
        jne     bir50                   ;   yes, a different sector
                                        ; It CANNOT be one of ours, unless it's
                                        ; clean, in which case we don't care.
                                        ; Go unlock buffer and advance.

;       It is the same sector.  Is it dirty and still valid?

        cmp     [di].buf_hVPB,BP        ; Is it on the same volume?
        jnz     bir50                   ;   no, not on our volume, skip it

        test    [di].buf_flags,BUF_DIRTY; Is it still dirty?
        jz      bir50                   ;   no, not dirty anymore, skip it

;       If we get here, buffer is (still) for us, dirty, and busy.
;       BUT VISIT is not set, and buf_refcnt is random.

bir40:  OR      [DI].buf_flags,BUF_VISIT    ; Mark the buffer as visited.
        MOV     [DI].buf_refcnt,0           ; prepare for inc.
    BufAsserthVPB <di>

bir45:  inc     [di].buf_refcnt         ; we have read the buffer
        jmp     bir20

;       Buffer is VISIT and BUSY and DIRTY and OURS and refcnt has a good
;       value, so inc refcnt and add ourselves the user population.

bir50:  CALL    UnlockBuf               ; Unlock buffers we don't care about.
        jmp     bir20

CpuMode reset

EndProc   BufIncRef


;***    BufDecRef - Decrement reference count, and perhaps unlock, buffer
;
;       BufDecRef decrements the refcnt on the buffer.  If the refcnt goes
;       to 0, the VISIT bit will be cleared, and Unlock will be called to
;       clear the busy bit and run any waiting threads.
;
;       NOTE    BufIncRef applies to a range, BufDecRef applies to a single
;               buffer.
;
;       ENTRY   (DS:DI) -> Buffer
;
;       USES    flags

Procedure BufDecRef,faronly

    ASSUME  DS:NOTHING,ES:NOTHING
;       PMONLY

    BufAssertIsBuf <di>
ifdef BUFFERCHK
        TEST    [di].buf_flags,BUF_VISIT
        JZ      bdfie                   ; not visit, go complain
        TEST    [di].buf_flags,BUF_BUSY
        JZ      bdfie                   ; not busy, go complain
        CMP     [di].buf_refcnt,0
        JBE     bdfie                   ; refcnt !> 0, go complain
        JMP     bdfok

bdfie:  InternalError <BufDecRef: Unvisited buffer being released.>
bdfok:
endif ;BUFFERCHK

        dec     [di].buf_refcnt         ; Decrement reader reference count
        jnz     bdr1                    ; not 0, go return

;       Reference count is now 0, clear visit bit and call Unlockbuf, which
;       will clear the busy bit and run any waiting threads.

        and     [di].buf_flags,NOT BUF_VISIT
        call    UnlockBuf

bdr1:   ret

EndProc   BufDecRef


;***    BufNext - Advance to next buffer in read range, so it can be released.
;
;       BufNext finds the next buffer in the specified range that contains
;       new data to copy over a read.  We do this by finding buffers in the
;       range which are VISIT, (A buffer which is VISIT but NOT DIRTY or
;       which has a 0 refcnt indicates an internal error.)
;       BufNext starts looking at the buffer pointed to by DI.  The caller
;       starts the search with DI = 0, and continues it by using the last
;       DI returned from BufNext.
;
;       Buffers are of interest if:
;           ECX <= sector < EAX and hVPB = BP and VISIT.
;
;       ENTRY   (DS:SI) -> buffer to continue scan from.  SI = 0 to start.
;               (BP)    =  hVPB
;               (ECX)   =  low sector of range
;               (EDX)   =  high sector of range + 1
;
;       EXIT    Carry clear
;                   DS:SI -> next buffer of interest
;               Carry set
;                   No more buffers of interest (NOT AN ERROR)
;
;       USES    bx, dx, si, flags

Procedure BufNext,faronly

    ASSUME  DS:NOTHING,ES:NOTHING
;       PMONLY

        chkvselhd <GSEL GDT_VPB>,bp

CpuMode 386

        mov     ax,ds:[bs_seglimit]     ; (dx) = segment limit
        or      si,si                   ; Caller want to start at beginning?
        jnz     bn10                    ; no, go use value caller gave us.

        mov     si,BUFBASE              ; yes, start with first buffer
bn10:   mov     bx,ds:[bs_buffsize]     ; (bx) = size of buffer + header

bn20:   cmp     [si].buf_sector,ecx     ; Is the sector before our range?
        jb      bn30                    ;   yes, no need to consider it

        cmp     [si].buf_sector,edx     ; Is the sector in our range?
        jb      bn40                    ;   yes, go see if the match is complete

bn30:   add     si,bx                   ; ds:si -> next buffer
        cmp     si,ax                   ; Have we reached the end of segment?
        jbe     bn20                    ;   no, go check the next buffer

; NOTE:   We allow SI==AX to continue, even though it will fault.
;           Why?  Because the GP fault will catch the bug that this represents.
;           (DI should = AX+1 in the normal case.)

        stc                             ; We're at end, signal user.
bnx:    ret                             ; exit


; ds:di -> buffer that may be the next on we are interested in

bn40:   cmp     [si].buf_hVPB,bp        ; Is this the volume we want?
        jne     bn30                    ;   no, not our volume, skip it

        test    [si].buf_flags,BUF_VISIT; Is the buffer VISIT?  (clears carry)
        jz      bn30                    ;   no, not visit, skip it

        jmp     short bnx               ; ds:di -> next buffer of interest, exit

CpuMode reset

EndProc   BufNext


BREAK <ScanPlace/PlaceBufMRU>
;***    ScanPlace - Return next buffer, place current buffer MRU
;
;       ENTRY:  (DS:DI) = buffer to place at MRU position
;
;       EXIT:   (DS:DI) = next MRU buffer following original buffer
;
;       USES:   SI, flags

Procedure   ScanPlace,near

    ASSUME  DS:NOTHING,ES:NOTHING
;       PMONLY

    BufAssertIsBuf <di>
        MOV     SI,[DI].buf_next        ; (SI) = next buffer to scan
        CALL    PlaceBufMRU
        MOV     DI,SI
        ret
EndProc ScanPlace


;***    PlaceBufMRU - Place buffer at MRU end of list.
;
;       Unlink the buffer from its current position in the MRU list and place
;       it at the MRU position.
;
;       This routine is called when it is expected that the buffer will be
;       needed again, e.g., for FAT and directory sectors.
;
;       ENTRY:  (DS:DI) = buffer to place at MRU position.
;
;       EXIT:   Buffer placed at MRU position.
;
;       USES:   flags

Procedure   PlaceBufMRU,NEAR

    ASSUME  DS:NOTHING,ES:NOTHING
;       PMONLY

ifdef BUFFERCHK
        call    ChkBufCache
endif ;BUFFERCHK
        Assert  ISBUF,<DS,DI>,"PlaceBufMRU entry"

    BufAssertBusy <di>
        CMP     DI,DS:[bs_MRUHead]
        JZ      pbmx                    ; Buffer is already head

        SaveReg <SI>
        CALL    UnlinkBuf
        MOV     SI,DI                   ; (SI) = new MRU buffer
        XCHG    SI,DS:[bs_MRUHead]      ; (SI) = old MRU buffer; set MRU head
        MOV     [DI].buf_next,SI        ; Set forward link
        MOV     [DI].buf_prev,BUF_NIL   ; End of back list
        MOV     [SI].buf_prev,DI        ; Set back link
        RestoreReg <SI>

pbmx:   ret

EndProc PlaceBufMRU


;***    PlaceBufLRU - Place buffer at LRU end of list.
;
;       Unlink the buffer from its current position in the LRU list and place
;       it at the LRU position.
;
;       This routine is called when it is expected that the buffer won't be
;       needed again, e.g., when the last byte of the sector has been read or
;       written.
;
;       ENTRY:  (DS:DI) = buffer to place at LRU position
;
;       EXIT:   Buffer placed at LRU position.
;
;       USES:   flags

Procedure   PlaceBufLRU,NEAR

    ASSUME  DS:NOTHING,ES:NOTHING
;       PMONLY

ifdef BUFFERCHK
        call    ChkBufCache
endif ;BUFFERCHK
        Assert  ISBUF,<DS,DI>,"PlaceBufLRU entry"

        CMP     DI,DS:[bs_MRUTail]
        JE      pblx                    ; Buffer already at LRU position

        SaveReg <SI>
        CALL    UnlinkBuf               ; Unlink buffer from MRU list
        MOV     SI,DI                   ; (SI) = new LRU buffer
        XCHG    SI,DS:[bs_MRUTail]      ; (SI) = old LRU buffer; Set MRU tail
        MOV     [DI].buf_prev,SI        ; Set back link
        MOV     [DI].buf_next,BUF_NIL   ; End of forward list
        MOV     [SI].buf_next,DI        ; Set forward link
        RestoreReg <SI>

pblx:   ret

EndProc PlaceBufLRU

;***    GetMRUBufSec - Get the Most Recent Buffers' Sector Number
;
;       Get the most recently used buffers' sector number.
;
;       ENTRY:  NONE
;
;       EXIT:   AX
;
;       WARNING: This only works for FAT sectors since only 1 word is returned
;                The worst that could happen is that we may flush a buffer
;                too soon which will result a minmal performance loss.
;
;       USES:   AX,flags

Procedure   GetMRUBufSec,<hybrid,far16>

    ASSUME  DS:NOTHING,ES:NOTHING
;       PMONLY

        SaveReg <ds,di>
        BufferContext <DS>
        mov     di,DS:[bs_MRUHead]      ; (DI) = MRU buffer;
        mov     ax,word ptr ds:[di].buf_sector
        RestoreReg <di,ds>

        ret
EndProc GetMRUBufSec


;***    UnlinkBuf - remove a buffer from the MRU buffer list
;
;       WARNING: Buffer must be on MRU list!
;               This routine assumes there are at least TWO buffers on
;               the MRU list at entry.  This assumption implies that MRUHead
;               and MRUTail NEVER point to the same buffer.  Hence, when we
;               remove OUR buffer, there is still at least one buffer left
;               in the list.
;
;       ENTRY:  (DS:DI) = pointer to buffer to be removed from list
;
;       EXIT:   buffer unlinked from list
;               bs_MRUHead/bs_MRUTail updated if necessary
;
;       USES:   SI,flags

Procedure   UnlinkBuf,near,local

    ASSUME  DS:NOTHING,ES:NOTHING
;       PMONLY

ifdef BUFFERCHK
        call    ChkBufCache
endif ;BUFFERCHK
        SaveReg <AX>
        CMP     DI,DS:[bs_MRUHead]
        JNE     ub1                     ; Not at head of MRU list

;*  Remove buffer from head of list

        MOV     SI,[DI].buf_next        ; (SI) = new head of list
        MOV     DS:[bs_MRUHead],SI      ; New head of list
        MOV     [SI].buf_prev,BUF_NIL   ; End of back list
        JMP     SHORT ub4               ; Go to exit

;*  Not at head of list, adjust next pointer for prev buffer

ub1:    MOV     SI,[DI].buf_prev        ; (SI) = preceding buffer
        MOV     AX,[DI].buf_next        ; (AX) = following buffer
        MOV     [SI].buf_next,AX        ; Preceding points to following

        CMP     DI,DS:[bs_MRUTail]
        JNE     ub3                     ; Not at tail of MRU list

;*  Remove buffer from tail of list

        MOV     DS:[bs_MRUTail],SI      ; New tail of list
        MOV     [SI].buf_next,BUF_NIL   ; End of forward list
        JMP     SHORT ub4

;*  Not at tail, adjust prev pointer for next buffer

ub3:    XCHG    SI,AX                   ; (SI) = buf_next; (AX) = buf_prev
        MOV     [SI].buf_prev,AX        ; Next points to previous

ub4:    RestoreReg <AX>
ubx:    ret

EndProc UnlinkBuf


BREAK   <GetBuffr -- GET A SECTOR INTO A BUFFER>

;***    GetBuffr - Get a sector into a buffer.
;
;       Get the specified local sector into one of the I/O buffers and
;       place that buffer at the MRU position.
;
;***    GetBuffrB - Get a sector into an I/O buffer.
;
;       As above, but entered with SI non-zero to read a FAT sector.
;
;***    GetBuffrBP - Get a swapper sector into an I/O buffer.
;
;       Like GetBuffr, but with DI = ReadBypassAdj if swapper-bypass input
;       is needed, DI = 0 for all other cases.
;       GetBuffrBP should be called with DI != 0 ONLY for DATA transfers
;       done on behalf of the swapper, NEVER for non-data or non-swapper I/O.
;       (See note on swapper interaction at top of file.)
;
;       ENTRY:  (AL)    = if the desired sector is not in a buffer, then
;                          0 => sector WILL be read from disk
;                         !0 => sector WILL NOT be read from disk
;               (CX:DX) = Desired physical sector number
;               (ES:BP) = points to VPB
;               [Allowed] set in case of INT 24
;
;       EXIT:   [CurBuf] Points to the Buffer for the sector
;                       The buffer header will be marked BUSY
;                       and the drive and sector numbers stored
;                       into it.  The buffer type field of
;                       buf_flags = 0, caller must set it
;               (DS) = TASKAREA
;               Carry set if error (currently user FAILed to INT 24)
;
;       USES:   AX,BX,CX,SI,DI,DS,flags
;
;       EFFECTS Switches Context

    ASSUME  DS:NOTHING,ES:NOTHING

Procedure GetBuffrBP,<hybrid,far16>
        XOR     SI,SI
        JMP     short gb0

Entry   GetBuffr,HYBRID
        PMONLY

        gb_VPB equ  [bp]                    ; Caller's BP
ifdef OLD_PROFILE
        LocalVar    profile_seccnt  ; count of buffers checked for profiling
endif ;OLD_PROFILE
        LocalVar    gb_PreRead,BYTE         ; 0 => do disk read if necessary
        LocalVar    gb_BypassAdj,WORD       ; 0 for normal reads, 1 for Bypass.
        LocalVar    gb_physBufAddr,DWORD    ; Buffer physical address

        XOR     SI,SI

Entry   GetBuffrB,HYBRID
        XOR     DI,DI

ifdef BUFFERCHK
        call    ChkBufCache
endif ;BUFFERCHK
gb0:    chkvselhd es,bp

        Assert  ISVPB,<ES,BP>,"GetBuffr"
        CALL    ChkNoBuf
        TimerOn TIM_BUF_GET

        SaveReg <es,di,ax>
        les     di,es:[bp].vpb_fsi.vpi_pDPB     ; es:di -> DPB ptr
        mov     ax,es:[di].dpb_cbSector         ; ax = bytes/sector
        BufferContext <DS>
        cmp     ax,ds:[bs_MaxSec]       ; Is sector to buffer too big?
        RestoreReg      <ax,di,es>
        jbe     gb00
        stc
        jmp     gbx3
gb00:
ifdef FSSTRICT
        or      al,al                   ; pre-read?
        jz      gb0a                    ; yes
        cmp     al,1                    ; no pre-read?
        jz      gb0a                    ; yes
    InternalError <GetBuffr - pre-read flag not set>
gb0a:
endif
        EnterProc

        MOV     gb_PreRead,AL           ; Save pre-read flag
        MOV     gb_BypassAdj,DI         ; Argument for calling DRead.

        SaveReg <ax,bx>
        mov     bx,gb_VPB               ; (BX) = VPB (caller BP after EnterProc)
        CALLFAR UseCache                ; Can we use the cache for this vol ?
        RestoreReg <bx,ax>
        jc      gb0b                    ; No, skip initializing cache

        SaveReg <si>
        mov     si,gb_VPB               ; SI = hVPB
        CALLFAR Init_VPB_Cache          ; Initialize cache if necessary
        RestoreReg <si>                 ;
        LJC     gb7a
gb0b:

;       mov     bx,gb_VPB               ; (BX) = VPB (caller BP after EnterProc)
;       mov     ax,es:[bx.vpb_fsi.vpi_cbSector] ; (AX) = size of sector to buffer
;       cmp     ax,ds:[bs_MaxSec]       ; Is sector to buffer too big?
;       LJA     gb7a                    ; yes, branch to error exit
;       mov     ax,bx                   ; (AX) = VPB

        mov     ax,gb_VPB               ; (AX) = VPB
gb1:
        mov     bx,TASKAREA:[CurrTCB]
        mov     TASKAREA:[bx].TCBBufHE,BufHE_Percolate ; We handle retry

; Search for our buffer

        MOV     DI,DS:[bs_MRUHead]      ; (DI) = MRU buffer
        MOV     BX,BUF_NIL              ; (BX) = nil ptr
ifdef OLD_PROFILE
        MOV     [profile_seccnt],0      ; Count of buffers checked
endif ;OLD_PROFILE

        EVEN

gb2:    Assert  ISBUF,<DS,DI>,"GetBuffr Scan"
ifdef OLD_PROFILE
        INC     [profile_seccnt]        ; Looking at next buffer
endif ;OLD_PROFILE

        CmpBuf  NE,gb4                  ; IF not desired buffer THEN gb4

        DBPRT   40h,10h,<found >,<>
        CALL    WaitBuf                 ; wait for buffer, set BUSY
        JZ      gb5                     ; Didn't block, so we got buffer
        CmpBuf  E,gb5                   ; IF same buffer THEN gb5

gb3:
    BufAssertBusy <di>
        CALL    RlseBuffr               ; Buffer changed, try again
        JMP     gb1                     ; Scan from top of chain

gb5:
; set timeslice extension               ; ** FXM #97886
        push    di
        mov     di,CurrTCB              ; extend time slice
        or      TASKAREA:[di].TCBSpareFlags,SPFLAGS_XTIME_SLICE
        pop     di
        JMP     gb10

gb4:    MOV     DI,[DI].buf_next        ; (DI) = next MRU buffer
        CMP     DI,BX
        JNE     gb2
        mov     bx,gb_VPB               ; BX is VPB for volume we want buffer
                                        ; free
        CALL    GetFreeBuffr            ; get a free buffer
        JC      gb1                     ; State of list may have changed
    BufAssertBusy <di>
        MOV     word ptr [DI].buf_sector,DX
        MOV     word ptr [DI].buf_sector+2,CX    ; reassign the buffer
        SaveReg <BP>
        MOV     BP,gb_VPB               ; (BP) = VPB
        MOV     [DI].buf_hVPB,BP
        call    IncVPBRef
        RestoreReg <BP>
        AND     [DI].buf_flags,BUF_TYPE_0  ; clear out type bits
        OR      [DI].buf_flags,BUF_ATTEMPTING_READ
        test    gb_PreRead,-1
        JNZ     gb8b                    ; Do not read new sector

;* Read in the new sector

        SaveReg <DX,DI,SI,BP,CX>
        LEA     BX,[DI].BUFINSIZ        ; Point at buffer
        SaveReg <ax,bx>
        mov     bx,gb_VPB               ; (BX) = VPB (caller BP after EnterProc)
        CALLFAR UseCache                ; Can we use the cache for this vol ?
        RestoreReg <bx,ax>
        jc      gb4_1                   ; No, skip cache read
        SaveReg <ds,es>
        mov     di,bx
        sub     di,BUFINSIZ             ; DS:[DI]->Buffer
        mov     bx,gb_VPB               ; BX = hVPB
        VPBContext ES,BX                ; ES:[BX]->VPB
        call    BufCacheRead            ; Read data through cache
        RestoreReg <es,ds>
        ASSUME ES:NOTHING
        pushf
        MOV     AH,0                    ; Set buf_flags to no type, DO NOT XOR!
        OR      SI,SI
        JZ      gb4_0                   ; Not reading a FAT sector
;test
        MOV     AH,BUF_ISFAT            ; Set buf_flags
;;        MOV     AH,BUF_ISFAT+BUF_WANT   ; Set buf_flags  ;** FXM #97886
gb4_0:  popf
        JMP     SHORT gb7
gb4_1:

ifdef PERFVIEW

        pushf                                                           ;sfb
        CpuMode 386                                                     ;sfb
        SaveReg <es>                                                    ;sfb
        FlatContext <es>                                                ;sfb
        cmp     es:_fPerfview,0         ; check if perfview is up       ;sfb
        je      short gb4_2                                             ;sfb
        PvwCnt fbc,Bypass,0            ; counter for bypass cache      ;b722131
gb4_2:                                                                  ;sfb
        CpuMode Reset                                                   ;sfb
        ASSUME  ES:NOTHING                                              ;sfb
        RestoreReg <es>                                                 ;sfb
        popf                                                            ;sfb

endif ;PERFVIEW

        mov     ax,gb_BypassAdj         ; (ax) = 0 OR ReadBypassAdj
        XCHG    DI,CX                   ; DI:DX = starting sector number
        MOV     CX,1
        OR      SI,SI
        JZ      gb6                     ; Not reading a FAT sector
        MOV     BP,gb_VPB               ; (BP) = VPB
        call    FatSecRd
        MOV     AH,BUF_ISFAT            ; Set buf_flags
;;        MOV     AH,BUF_ISFAT+BUF_WANT   ; Set buf_flags ; ***FXM #97886
        JMP     SHORT gb7

gb6:    DBPRT   40h,10h,< reading $x >,<dx>
CpuMode 386
        push    eax
        mov     eax,ds:[bs_physBufSeg]  ; Buffer segment physical address
        mov     [gb_physBufAddr],eax
        movzx   eax,bx                  ; Buffer offset in segment
        add     [gb_physBufAddr],eax    ; Buffer physical address
        pop     eax
CpuMode Reset
        push    ss
        pop     ds
        lea     bx,[gb_physBufAddr]     ; ds:bx point to dword phys address
        or      ah,1                    ; Tell DRead we pass physical address
        MOV     BP,gb_VPB               ; (BP) = VPB
        call    DRead                   ; DRead(al=0 or ReadBypassAdj)
        BufferContext <DS>
        MOV     AH,0                    ; Set buf_flags to no type, DO NOT XOR!

gb7:    RestoreReg <CX,BP,SI,DI,DX>
        jnc     gb8                     ; Read succeeded

;*  Read failed, so we do not have a buffer.

        mov     bx,CurrTCB
        cmp     TASKAREA:[bx].TCBactBufHE,HDERR_RETRY
        LJE     gb1                     ; Start all over

;BUGBUG 01-Sep-1987 bws Map IGNORE to FAIL for GetBuffr
;
;   We no longer have the buffer, so we cannot return it (with arbitrary data)
;   to the caller.  Our only recourse is to scan the buffer cache again, and,
;   upon not finding the data, ask for a free buffer again.  Now, we can fill
;   this buffer with goop (or leave it alone) and return "success" to the
;   caller.  For now, that is too much work, so we punt!

gb7a:   stc                             ; Indicate failure
        jmp     gbx                     ; Go to exit


gb8:    DBPRT   40h,10h,< CurBuf = $x:$x  >,<ds,di>
    BufAssertBusy <di>
        OR      [DI].buf_flags,AH       ; set buffer type
gb8b:   MOV     AX,1                    ; Default to not a FAT sector

; if no pre-read is set, it is the caller's responsibility to not release
; the buffer with garbage in it.

        AND     [DI].buf_flags,NOT BUF_ATTEMPTING_READ
        MOV     WORD PTR [DI].buf_wrtcntinc,0
        OR      SI,SI
        JZ      gb9
        MOV     BX,gb_VPB               ; (BX) = VPB
        MOV     AX,ES:[BX].vpb_fsd.vpdFAT_FAT_size
        MOV     WORD PTR [DI].buf_wrtcntinc,AX  ; Set FAT size from VPB
        MOV     AL,ES:[BX].vpb_fsd.vpdFAT_FAT_count

gb9:    MOV     [DI].buf_wrtcnt,AL
ifdef OLD_PROFILE
        mov     [profile_seccnt],0      ; didn't find buffer in cache
endif ;OLD_PROFILE

gb10:   CALL    PlaceBufMRU
    BufAssertBusy <di>
ifdef OLD_PROFILE
        SaveReg <ES>
        DosContext <ES>
        TEST    ProfState,-1            ; Check profiling state
        JZ      gbPrf                   ; Not profiling, don't collect stats

        mov     si,[profile_seccnt]
        shl     si,1                    ; (SI) = offset in stats block
        ADD     SI,DS:[bs_pStats]       ; (SI) = counter to increment
        INC     WORD PTR DS:[SI]        ; Increment counter

gbPrf:  RestoreReg <ES>
endif ;OLD_PROFILE
IF Debug
        MOV     AX,2[BP]                ; get offset of caller
        MOV     [DI].buf_fill,AX        ; save for use by ChkNoBuf
ENDIF

        CLC

        .386p
gbx:    FlatContext ds
        pushf
        test    TCResched,-1            ; Anyone want the CPU?
        jz      gbx1                    ;   No
        CpuMode reset

        CALLFAR TCYield                 ; Give up CPU

gbx1:   popf
gbx2:   LeaveProc
gbx3:   TaskContext <DS>                ; Callers expect this!
        ret

EndProc GetBuffrBP


;***    BufCacheRead - Read buffer from FAT cache
;
;       BufCacheRead saves the current file system TCB variables and
;       initializes all of the TCB variables required for a direct ccess I/O
;       call, and calls _BufRead to read the required sector into a FAT buffer.
;
;       ENTRY:  DS:DI -> Buffer to read into
;               ES:BX -> VPB
;                  SI =  0 NonFAT Sector
;                  SI != 0 FAT Sector
;       EXIT:   Carry Set if error
;       USES:   none
ASSUME DS:NOTHING,ES:NOTHING,SS:TASKAREA
Procedure   BufCacheRead,near

LocalVar SectorSize,DWORD                        ; Sector size for volume
LocalVar hVPB,WORD                               ; handle to volume
LocalVar BufPtr,DWORD                            ; Current buffer ptr
LocalVar tcbsavebuf,TCBIOCopySize                ; TCB save area

        EnterProc <0,0,bx,ds,di>
CpuMode 386
        SaveReg <esi,edi,ebx,ecx,edx>

; save TCB variables

        SaveReg <es,ds,di,bx,si>
        FlatContext <ds,es>,bx
        SSToDS  edi,tcbsavebuf                   ; EDI = pointer TCB save area
        CCALL32 ioSaveTCB,,nocall                ; Save TCB variables
        RestoreReg <si,bx,di,ds,es>
    ASSUME  DS:NOTHING,ES:NOTHING

        mov     dx,es:[bx].vpb_fsd.vpdFAT_FAT_size
        xor     ch,ch                            ; DX = number of sectors in FAT
        mov     cl,es:[bx].vpb_fsd.vpdFAT_FAT_count

        SaveReg <bx>
        mov     bx,CurrTCB
        movzx   eax,ds:[di].buf_sector.HighWord
        shl     eax,16
        mov     ax,ds:[di].buf_sector.LowWord
        mov     TASKAREA:[bx].TCBSecPos,eax      ; Sector # for direct access
        RestoreReg <bx>
                                                 ; CX = count of FAT sectors
bcr1:   SaveReg <dx,cx,si>
        SaveReg <bx>
        mov     bx,CurrTCB
        xor     ax,ax
        mov     TASKAREA:[bx].TCBBytSecPos,0     ; No position within first sector
        mov     TASKAREA:[bx].TCBfIOLock,0       ; No lock taken
        mov     TASKAREA:[bx].TCBBytCnt1,ax      ; No partial head
        mov     TASKAREA:[bx].TCBBytCnt2,ax      ; No partial tail
        mov     TASKAREA:[bx].TCBSecCnt,1        ; 1 full sector
        or      si,si                            ; FAT Sector read ?
        jz      bcr2                             ; No,
        mov     al,JFN_FAIL                      ; Fail request if error
        cmp     cx,1                             ; Are we on the Last FAT ?
        jne     bcr2                             ; No, Fail is correct.
        xor     al,al                            ; Yes, put up HardError PopUp.
bcr2:   mov     TASKAREA:[bx].TCBJfnFlag,al
        mov     ax,hVPB
        mov     TASKAREA:[bx].TCBThishVPB,ax     ; Store current VPB
        lea     ax,[DI].BUFINSIZ
        mov     TASKAREA:[bx].TCBDMAAdd.Segmt,ds ; Segment of buffer
        mov     TASKAREA:[bx].TCBDMAAdd.Offst,ax ; offset of buffer
        mov     TASKAREA:[bx].TCBNextAdd,ax      ; offset of buffer
        RestoreReg <bx>
        mov     ax,es:[bx.vpb_fsi.vpi_cbSector]  ; (AX) = size of sector
        mov     SectorSize.LowWord,ax

        movzx   eax,[hVPB]
        SaveReg <ds,es>
        FlatContext <ds,es>
        CCALL32 _BufRead,<dword ptr [SectorSize],0,1,eax>
        RestoreReg <es,ds>
        RestoreReg <si,cx,dx>

    ASSUME  DS:NOTHING,ES:NOTHING
        or      ax,ax                            ; Error ?
        jz      bcr3                             ; No, do not check for FAT sector

        or      si,si                            ; FAT Sector read ?
        jz      bcr3                             ; No, go return error

        SaveReg <si>
        mov     si,CurrTCB
        add     TASKAREA:[si].TCBSecPos.LowWord,dx ; increment to next FAT
        adc     TASKAREA:[si].TCBSecPos.HighWord,0
        RestoreReg <si>
        dec     cx
        jz      bcr3                             ; all FATs done, return
        jmp     bcr1                             ; try next FAT

bcr3:
        SaveReg <es,ds,di,bx,ax>
        FlatContext <ds,es>,bx
        SSToDS  esi,tcbsavebuf                   ; ESI = ptr to TCB area
        CCALL32 ioRestoreTCB,,nocall             ; Restore TCB variables
        RestoreReg <ax,bx,di,ds,es>
    ASSUME  DS:NOTHING,ES:NOTHING
        or      ax,ax                            ; Was there an error ?
        jz      bcr6                             ; No, carry clear
        stc                                      ; yes, set carry
bcr6:
        lds     di,BufPtr                        ; ds:[di]->buffer
        RestoreReg <edx,ecx,ebx,edi,esi>
        LeaveProc
        ret
CpuMode 286
EndProc BufCacheRead

ifdef BUFFERCHK
;***    ChkBufCache - Check buffer cache pointers
;
;       ChkBufCache is a debugging routine.  It does a consistency
;       check on the state of the buffer cache.  If a problem
;       is found an internal error is generated.  The code is
;       enabled by adding a "BUFFERCHK equ 1" to buffer.inc.
;
;       ENTRY:  none
;       EXIT:   Internal Error if buffer cache inconsistent
;       USES:   none

Procedure   ChkBufCache,near,local
        SaveReg <ds>
        PUSHA
        PUSHF

        BufferContext <ds>
        MOV     CX,DS                   ; (CX) = buffer segment
        LSL     CX,CX                   ; (CX) = limit of buffer segment
        CMP     CX,ds:[bs_seglimit]     ; Is seglimit rational?
        JNZ     cbcie6                  ; bad seglimit field, go complain
        MOV     BX,ds:[bs_buffsize]     ; (BX) = size of buffer

;*  Check MRU head offset
        MOV     AX,ds:[bs_MRUHead]
        CALL    ChkABuf                 ; Check buffer offset
        JNZ     cbcie1

;*  Check MRU tail offset
        MOV     AX,ds:[bs_MRUTail]
        CALL    ChkABuf                 ; Check buffer offset
        JNZ     cbcie2

;*  Check Free head offset
        MOV     AX,ds:[bs_FreeHead]
        CALL    ChkABuf                 ; Check buffer offset
        JNZ     cbcie3

;* Check free list to check if ALL buffers on it are free
        CALL    CheckFreeList

;*  Check headers of all buffers
        MOV     DI,BUFBASE              ; (di) = first buffer

cbc1:   mov     ax,ds:[di].buf_next     ; (ax) = next buffer
        call    ChkABuf
        jnz     cbcie4

        mov     ax,ds:[di].buf_prev     ; (ax) = next buffer
        call    ChkABuf
        jnz     cbcie5

        ADD     DI,BX                   ; (DI) = next buffer
        CMP     DI,CX                   ; Check against limit
        JB      cbc1                    ; Check next buffer

        POPF
        POPA
        RestoreReg <dS>
        ret

cbcie1: InternalError <ChkBufCache: MRU Head offset invalid>
cbcie2: InternalError <ChkBufCache: MRU Tail offset invalid>
cbcie3: InternalError <ChkBufCache: FREE Head offset invalid>
cbcie4: InternalError <ChkBufCache: next offset invalid>
cbcie5: InternalError <ChkBufCache: prev offset invalid>
cbcie6: InternalError <ChkBufCache: seglimit does not match selector>
EndProc     ChkBufCache

;***    ChkABuf - Check a buffer offset
;
;       ENTRY:  (AX) = buffer offset to check
;               (BX) = size of buffer+header
;               (CX) = limit of buffer segment
;       EXIT:   ZF set, offset valid
;               ZF clear, offset invalid
;       USES:   AX,flags

Procedure   ChkABuf,near,local

        cmp     ax,BUF_NIL              ; NIL ptr?
        je      cabx                    ;   Yes, okay

        CMP     AX,CX
        JA      cabx                    ; Offset past end of segment!

        PUSH    DX
        SUB     AX,size BUFSEG          ; (AX) = offset - buffer seg header
        XOR     DX,DX                   ; (DX:AX) = full offset
        DIV     BX                      ; (AX) = quotient; (DX) = remainder
        OR      DX,DX                   ; Set ZF from remainder.
        POP     DX
cabx:   ret

EndProc     ChkABuf

;***    ReCheckWriteRange - check that no valid dirty buffers are left in range
;
;       ReCheckWriteRange is called at the end of BufFree, by which time any
;       buffers left in the cache which are valid and in the range should
;       be long gone.
;
;       ENTRY   (BP) = hVPB
;               (DX) = low sector of range
;               (BX) = high sector of range + 1
;
Procedure ReCheckWriteRange,near
    ASSUME  DS:NOTHING,ES:NOTHING

        chkvselhd <GSEL GDT_VPB>,bp

        MOV     CX,DS:[bs_nBuffers]     ; (CX) = number of buffers
        MOV     SI,DS:[bs_buffsize]     ; (SI) = size of buffer + header
        MOV     DI,BUFBASE              ; (DS:DI) -> first buffer

rcwr1:  CMP     [DI].buf_sector,DX
        JB      rcwr2                   ; sector < DX, not for us, go to next
        CMP     [DI].buf_sector,BX
        JAE     rcwr2                   ; sector >= BX, not for us, go to next
        CMP     [DI].buf_hVPB,BP
        JNE     rcwr2                   ; not our volume, go to next

;       We have an in-range, valid buffer.  This shouldn't be.

        InternalError <ReCheckWriteRange: Dirty buffer left in range>

rcwr2:  ADD     DI,SI
        LOOP    rcwr1
        ret

EndProc   ReCheckWriteRange


;***    ReCheckReadRange - check that no valid dirty buffers are left in range
;
;       ReCheckReadRange is called at the end of BufIncRef, by which time any
;       buffers left in the cache which are dirty and valid and in the range
;       should be marked VISIT, BUSY and have !0 refcounts.
;
;       ENTRY   (BP) = hVPB
;               (DX) = low sector of range
;               (BX) = high sector of range + 1
;
Procedure ReCheckReadRange,near
    ASSUME  DS:NOTHING,ES:NOTHING

        chkvselhd <GSEL GDT_VPB>,bp

        MOV     CX,DS:[bs_nBuffers]     ; (CX) = number of buffers
        MOV     SI,DS:[bs_buffsize]     ; (SI) = size of buffer + header
        MOV     DI,BUFBASE              ; (DS:DI) -> first buffer

rcrr1:  CMP     [DI].buf_sector,DX
        JB      rcrr2                   ; sector < DX, not for us, go to next
        CMP     [DI].buf_sector,BX
        JAE     rcrr2                   ; sector >= BX, not for us, go to next
        CMP     [DI].buf_hVPB,BP
        JNE     rcrr2                   ; not our volume, go to next
        TEST    [DI].buf_flags,BUF_DIRTY
        JZ      rcrr2                   ; not dirty, it's ok.

;       We have an in-range, valid, dirty buffer.  It MUST be VISIT, BUSY,
;       and have a refcnt > 0.

        TEST    [DI].buf_flags,BUF_VISIT
        JZ      rcrrie                  ; not visit, go complain
        TEST    [DI].buf_flags,BUF_BUSY
        JZ      rcrrie                  ; not busy, go complain
        CMP     [DI].buf_refcnt,0
        JBE     rcrrie                  ; refcnt <= 0, go complain

rcrr2:  ADD     DI,SI
        LOOP    rcrr1
        ret

rcrrie: InternalError <ReCheckReadRange: Dirty buffer left unvisited in range>

EndProc   ReCheckReadRange

endif ;BUFFERCHK


;***    ChkBufPtr - verify that a buffercache pointer is valid
;
;       This simply checks the segment and offset but not the content of
;       the buffer itself.
;
;       ENTRY   ds:si = buffer pointer to be checked
;
;       EXIT    if 'NC' - pointer is valid
;               if 'C' - pointer is invalid
;
;       USES    ax,bx,dx,es,flags
;
;       PSEUDO CODE
;
;       if (ds != buffer segment)
;         return 'C'
;       if (offset < offset of first buffer)
;         return 'C'
;       if (offset > offset of last buffer)
;         return 'C'
;       if (offset not on buffer boundary)
;         return 'C'
;       return 'NC'

Procedure ChkBufPtr,near
    assume ds:nothing,es:nothing,ss:TASKAREA

        BufferContext <es>,ax           ; ax,es = buffer segment
        mov     bx,ds                   ; bx = caller's buffer segment
        cmp     ax,bx                   ; is segment valid?
        jne     cbp10                   ; no, error

        cmp     si,BUFBASE              ; offset >= first valid offset?
        jb      cbp10                   ; no, error

        mov     ax,es:[bs_seglimit]     ; ax = last valid byte of segment
        inc     ax                      ; ax-1 = last valid byte of segment
        mov     bx,es:[bs_buffsize]     ; bx = size of one buffer
        sub     ax,bx                   ; ax = last valid buffer offset
        cmp     si,ax                   ; offset <= last valid offset?
        ja      cbp10                   ; no, error

        mov     ax,si                   ; ax = offset from segment
        sub     ax,BUFBASE              ; ax = offset from first buffer
        xor     dx,dx                   ; dx:ax = offset
        div     bx                      ; dx = remainder
        or      dx,dx                   ; is offset on buffer boundary?
        jnz     cbp10                   ; no, error

        ret                             ; pointer ok, carry already cleared

cbp10:  stc                             ; error
        ret

EndProc ChkBufPtr


BREAK <GetFreeBuffr - Get a free buffer>
;***    GetFreeBuffr - Get a free buffer
;
;       The buffer list is searched for a free buffer.  If one
;       is found it is returned.  If a free buffer is not found,
;       an assigned buffer is selected and made into a free
;       buffer.  An attempt is made to avoid taking a buffer
;       which is in use by someone else, but to otherwise pick
;       the LRU buffer.
;
;       NOTES on Swapper Interaction
;           We reserve the first few buffers (NBUF_NONREMMEDIA) in the
;           buffer cache exclusively for non-removable media.
;           This ensures that if we are swapping to non-removable
;           media, then we will be able to always have buffers
;           that we can flush out (if dirty) without having to invoke the
;           hard-error daemon (which might cause deadlocks if it needs to
;           be swapped in).
;
;           candidate buffer, flushing it out to disk if it was dirty.
;           The "best" depends on whether the volume we are buffering is
;           on a removable or non-removable drive. If it is non-removable,
;           then all the buffers are game; if it is removable, then we only
;           select from the set that is not reserved for non-removable
;           media.
;           If we are the swapper, then we only pick buffers currently in
;           use for non-removable media, UNLESS we are swapping to a
;           removable medium.
;
;           We have two possible scenarios:
;               - Swapping to a non-removable medium
;                   Request buffer for:
;                       - removable medium
;                       - non-removable medium
;                       - swapper (non-removable medium)
;               - Swapping to a removable medium
;                   Request buffer for:
;                       - removable medium
;                       - non-removable medium
;                       - swapper (removable medium)
;
;           The first NBUF_NONREMMEDIA buffers are used exclusively for
;           non-removable media. If we are swapping to a removable medium,
;           then the swapper cannot use these reserved buffers at all.
;
;           The swapper will:
;               - NOT reuse buffers that are being used for removable
;                 media if we are swapping to a non-removable medium
;               - CAN reuse buffers that are being used for removable
;                 media if we are swapping to a removable medium
;               - NOT use the non-removable buffers if swapping to a
;                 removable medium.
;
;           We also treat the SHELL screen switching process in the same
;           way since it cannot handle hard-error pop-ups.
;
;       ENTRY:  (DS)    = buffer segment
;               (BX)    = hVPB for which we want a buffer
;
;       EXIT:   Carry Clear, buffer obtained
;                   DS:DI   Points to the Buffer.  The buffer header
;                           will be marked BUSY and FREE.
;                   [TCBCurBuf] = DS:DI
;               Carry Set, no buffer obtained
;                   Caller (GetBuffr) should rescan buffer cache before
;                   calling us again.
;
;       USES:   BX,DI,DS
;
;       EFFECTS Switches Context
;
;       ALGORITHM
;           if (floppy)
;               Limit = be_OffRemMed
;           else
;               Limit = SIZE BUFSEG
;           Get LRU BUffer
;           BestBuf = NULL
;           BestFlags = 0xFF    /* Worst possible */
;           While (! End of LRU chain) {
;               if (BufOffset >= Limit) {
;                   if (ProcFlag ==1 ) {
;                       if (Wanting buffer for non-removable medium) /* i.e. Limit == size BUFSEG */
;                           if (Current buffer is used for removable medium)
;                               goto gfb3:
;                   }
;                   if (buffer is free)
;                       if (dirty)
;                           goto gfb4
;                       else
;                           goto gfb6
;                   Update BestBuf based on BestFlags and buf_flags
;                   Update BestFlags
;               }
;   gfb3:       Get next buf in LRU chain
;           }
;   gfb4:   PlaceBufMRU()
;           BufWrite()
;           goto GotBuf
;
;   gfb6:   if (buffer is free)
;               RemoveBufVPB()
;   GotBuf: Mark buffer as BUSY
;           Update TCB variables
;           return()
;
    ASSUME  DS:NOTHING,ES:NOTHING

Procedure   GetFreeBuffr,NEAR,local
;       PMONLY

ifdef BUFFERCHK
        call    ChkBufCache
endif ;BUFFERCHK
        DBPRT   40h,10h,<GetFreeBuffr: >,<>
        TimerOn TIM_BUF_FREE
        SaveReg <AX,SI,BP,CX,ES>
; The first NBUF_NONREMMEDIA buffers are not given for buffering data for
; removable media. This assures that there will always be buffers that we
; can flush out for the swapper if we get to that stage
;
; we determine where the cut-off is for the offset of the buffer that we
; return. This ensures that a certain number of buffers is never used for
; removable media.
;
        VPBContext      es,bx   ; ES:BX -> VPB
        SaveReg <ds>
        lds     bx,es:[bx].vpb_fsi.vpi_pDPB     ; es:bx ->DPB
        test    [bx].dpb_flags,DPBNONREMOV
        RestoreReg      <ds>            ; Ds is selector for buffer segment
        ASSUME DS:NOTHING
        mov     ax,SIZE BUFSEG          ; assume it is for non_rem. media
                                        ; for the swapper
        jnz     gfb1                    ; assumption was correct
        mov     ax,ds:[bs_offRemMed]    ;
;*  Scan list in LRU order.  Take first "clean" buffer.  If no clean buffers,
;   we have remembered the best candidate, so wait for it to be clean and then
;   grab it.

; AX = min offset of buffer that we can allow for this volume
;
gfb1:   MOV     BX,BUF_NIL              ; (BX) = nil ptr
        MOV     SI,BX                   ; (SI) = no best buffer, yet
        MOV     CH,0ffh                 ; (CH) = worst flags possible
        MOV     DI,DS:[bs_MRUTail]      ; (DI) = LRU buffer

;*  (bx) = BUF_NIL (for performance)
;   (ch) = best buf_flags seen so far
;   (si) = best buffer seen so far (corresponds to best flags)
;   (di) = buffer to inspect
;   (ax) = min. offset of buffer we should return for this drive
;          ( = SIZE BUFSEG if we want buffer for non_removable media)
;          ( = bs_offRemMed if we are getting buffer for a rem. media)
;   (es) = VPB segment

        EVEN

gfb2:   cmp     di,ax                   ; we are not allowed to select
        jb      gfb3                    ; this one because it is reserved for
                                        ; non_rem. media
; if this is for the swapper, and we are swapping to non-rem. medium,
; then we skip over any buffer that is for a rem. medium. This helps in
; preventing deadlocks between the swapper and the hard-error daemon.
; If we are swapping to rem. medium, then it is ok to select a buffer that
; is being used for a non-rem medium. Actually it isn't necessary but it
; will do for now since swapping to rem. media is a PAIN anyway, especially
; if someone changes the diskette.
;
        test    [ProcFlag],1            ; Is this for swapper/screen switcher?
        jnz      swapscr                 ; If yes, no rem-media buffers

        mov     bp,CurrTCB              ; (SS:BP) = current TCB
        test    TASKAREA:[bp].TCBfSwapping,SM_TCB_SWAPPING ;
        jz      gfb2_5                  ; If no, any buffer is ok.


; This is for swapper/screen switcher; we should not give a rem-media buffer
; to this process
swapscr:
        cmp     [di].buf_hVPB,BUF_FREE  ; Is this a free buffer?
        je      gfb2_5                  ; Yes. This is probably the one we want.
                                        ; Skip over DPB check below and check.
        cmp     ax,SIZE BUFSEG          ; Swapping to non-removable medium?
        jne     gfb2_5                  ; No. It is OK to use buffers being
                                        ; used for removable media
; We want this for a non-removable medium. So, in order to avoid deadlocks,
; we do not reuse any buffer that is being used for a removable medium.
;
        SaveReg <si>                    ; Save current "best" choice
        mov     si,[di].buf_hVPB
        VPBContext es,si                ; ES:SI -> VPB
        les     si,es:[si].vpb_fsi.vpi_pDPB     ; ES:SI -> DPB
        test    es:[si].dpb_flags,DPBNONREMOV
        RestoreReg <si>                 ; SI -> Best buffer so far
        jz      gfb3                    ; Data in buffer is for a removable medium.
                                        ; Skip it because we are the swapper
gfb2_5: MOV     CL,[DI].buf_flags       ; (CL) = buffer flags
        AND     CL,BUF_WANT+BUF_BUSY    ; (CL) = pertinent flags
        JZ      gfb6                    ; Got LRU unused buffer; CLC!

;==============================
;make fix here to preserve CL   FxM
        MOV     CL,[DI].buf_flags       ; (CL) = buffer flags
;==============================
        CMP     CH,CL                   ; Compare with best buffers so far
        JBE     gfb3                    ; Buffer is not better

        MOV     CH,CL                   ; (CH) = best flags yet
        MOV     SI,DI                   ; (SI) = best buffer yet

gfb3:   MOV     DI,[DI].buf_prev        ; (DI) = next LRU buffer
        CMP     DI,BX                   ; end of list?
        JNE     gfb2                    ; More buffers to check

        MOV     DI,SI                   ; (DI) = best buffer

; Place buffer MRU so that other GetFreeBuffr callers don't select the same
; buffer while we are blocked in BufWrite

gfb4:
    BufAssertIsBuf <di>
        call    PlaceBufMRU
        SaveReg <DX,ES>
        mov     bx,fFB_FAILDISCARD      ;
        CALL    BufWrite                ; Make buffer clean
        RestoreReg <ES,DX>

;*  Now, if carry is clear, we managed to write out the buffer.  If carry is
;   set, we had a harderror, and the state of the buffer is uncertain.  In
;   either case, we tell the caller we did not get a buffer.

        stc                             ; Tell caller we failed
        jmp     gfbx                    ; go to exit

gfb6:   test    [di].buf_flags,BUF_DIRTY ; Is buffer dirty?
        jnz     gfb4                    ;   yes, go write it out

        cmp     [di].buf_hVPB,BUF_FREE  ; did buffer become free?
        je      gfb7                    ;   Yes, no need to move it again

        CALL    RemoveBufVPB            ; this buffer is free

gfb7:   MOV     [DI].buf_flags,BUF_BUSY ; Grab buffer, clear other flags
        MOV     BP,CurrTCB              ; (SS:BP) = current TCB
        mov     ax,[bp].TCBNumber       ; (AX) = thread ID
        MOV     [DI].buf_tid,AX         ; Set thread ID
        OR      TASKAREA:[BP].TCBForce_Flg,FF_BUF ; We own a buffer
        MOV     TASKAREA:[BP].TCBCurBuf.Offst,DI  ; Save buffer address
        MOV     TASKAREA:[BP].TCBCurBuf.Segmt,DS
        clc                             ; We got a buffer without blocking

gfbx:   RestoreReg <ES,CX,BP,SI,AX>
        TimerOff TIM_BUF_FREE
        ret

ifdef MISCSTRICT
gfbie:  Panic   "GetFreeBuffr: thread number above 255"
endif

EndProc GetFreeBuffr



BREAK <RlseBuffr - Release a buffer>
;***    RlseBuffr - Release a buffer
;
;       The buffer pointed to by CurBuf is released.  If any processes are
;       waiting for the buffer, the are reawakened. The buffer is marked not
;       busy and the current buffer pointer is zeroed.  In order to simplify
;       the code which calls this routine, it is forgiving of being called
;       when no buffer is assigned.
;
;
;       ENTRY:  [CurBuf] != 0 -> [CurBuf] -> buffer
;               [CurBuf] == 0 -> No current buffer to release (this is OK)
;
;       EXIT:   The buffer is marked not BUSY
;               [CurBuf] = ??:BUF_NIL (will cause GP-Fault if used)
;
;       USES:   flags

    ASSUME  DS:NOTHING,ES:NOTHING

Procedure   RlseBuffr,<hybrid, far16>
;       PMONLY

ifdef BUFFERCHK
        call    ChkBufCache
endif ;BUFFERCHK
        SaveReg <BP>
        MOV     BP,CurrTCB
        test    [bp].TCBForce_Flg,FF_BUF        ; buffer assigned?
        JZ      rlsbx                           ; no, just return

        SaveReg <AX,DI,DS>
        LDS     DI,TASKAREA:[BP].TCBCurBuf      ; get buffer pointer
        TEST    [DI].buf_flags,BUF_BUSY
        JZ      rlsb2
        MOV     AX,[DI].buf_tid
        CMP     [bp].TCBNumber,ax
        JNZ     rlsbe1

        CALL    UnlockBuf
rlsb2:  MOV     TASKAREA:[BP].TCBCurBuf.Segmt,0 ; Cause fault if referenced
        AND     TASKAREA:[BP].TCBForce_Flg,NOT FF_BUF

        RestoreReg <DS,DI,AX>

rlsbx:  RestoreReg <BP>
        ret

rlsbe1: InternalError   <RlseBuffr: attempt to free other thread''s buffer>
EndProc RlseBuffr

;***    BufCacheWrite - Write buffer to cache
;
;       BufCacheWrite saves the current file system TCB variables and
;       initializes all of the TCB variables required for a direct access I/O
;       call, and calls _BufWrite to write the FAT buffer.
;
;
;       ENTRY:  DS:DI -> Buffer to read into
;               ES:BX -> VPB
;               SI:DX -> Sector to write
;       EXIT:   Carry Set if error
;       USES:   none
ASSUME DS:NOTHING,ES:NOTHING,SS:TASKAREA
Procedure   BufCacheWrite,near

LocalVar SectorSize,DWORD                        ; Sector size of volume
LocalVar SectorNum,DWORD                         ; Sector number we are writing
LocalVar hVPB,WORD                               ; handle to volume
LocalVar BufPtr,DWORD                            ; Buffer pointer
LocalVar tcbsavebuf,TCBIOCopySize                ; TCB save area

        EnterProc <0,0,si,dx,bx,ds,di>
CpuMode 386
        SaveReg <esi,edi,ebx,ecx,edx>

; save TCB variables
        SaveReg <es,ds,di,bx>
        FlatContext <ds,es>,bx
        SSToDS  edi,tcbsavebuf                   ; EDI = ptr to TCB save area
        CCALL32 ioSaveTCB,,nocall                ; save TCB variables
        RestoreReg <bx,di,ds,es>
    ASSUME  DS:NOTHING,ES:NOTHING

        VPBContext es,bx                         ; ES:[BX]->VPB
        lds     di,BufPtr                        ; DS:[DI]->Buffer
        mov     si,CurrTCB
        xor     ax,ax
        mov     TASKAREA:[si].TCBfIOLock,0       ; No lock done yet
        mov     TASKAREA:[si].TCBBytSecPos,0     ; No pos. within first sector
        mov     TASKAREA:[si].TCBBytCnt1,ax      ; No, partial head sector
        mov     TASKAREA:[si].TCBBytCnt2,ax      ; No, partial tail sector
        mov     TASKAREA:[si].TCBSecCnt,1        ; 1 full sector
        mov     TASKAREA:[si].TCBJfnFlag,al      ; No, special JFN flags
        mov     ax,hVPB
        mov     TASKAREA:[si].TCBThishVPB,ax     ; Save hVPB
        lea     ax,[DI].BUFINSIZ
        mov     TASKAREA:[si].TCBDMAAdd.Segmt,ds ; Selector of Buffers
        mov     TASKAREA:[si].TCBDMAAdd.Offst,ax ; Offset of buffer
        mov     TASKAREA:[si].TCBNextAdd,ax      ; offset to begin transfer
        mov     eax,SectorNum
        mov     TASKAREA:[si].TCBSecPos,eax      ; RBA for Direct Access

        mov     ax,es:[bx.vpb_fsi.vpi_cbSector]  ; (AX) = sector size of vol.
        mov     SectorSize.LowWord,ax
        movzx   eax,[hVPB]                       ; EAX = hVPB
;       PvwStartTm fbc,Wrt,0           ; start counter for write time ;aa04447
        FlatContext <ds,es>
        CCALL32 _BufWrite,<0,0,dword ptr [SectorSize],0,1,0,eax>

;       PvwEndTm fbc,Wrt,0              ; end counter for write time   ;aa04447
    ASSUME  DS:NOTHING,ES:NOTHING
        SaveReg <es,ds,di,bx,ax>
        FlatContext <ds,es>,bx
        SSToDS  esi,tcbsavebuf                   ; ESI = ptr to TCB save area
        CCALL32 ioRestoreTCB,,nocall             ; restore TCB variables
        RestoreReg <ax,bx,di,ds,es>
    ASSUME  DS:NOTHING,ES:NOTHING
        or      ax,ax                            ; Was there an Error
        jz      wcr6                             ; No, carry clear
        stc                                      ; Yes, set carry
wcr6:
        lds     di,BufPtr                        ; DS:[DI]->Buffer
        RestoreReg <edx,ecx,ebx,edi,esi>
        LeaveProc
        ret
CpuMode 286
EndProc BufCacheWrite

BREAK <LockBuf_ - Lock a buffer>
;***    LockBuf_ - Lock a buffer
;
;       The passed buffer is checked to see if it is busy. If so, the process
;       is suspended until it is available.
;
;       ENTRY:  (DS:DI) = buffer to lock
;
;       EXIT:
;       USES: flags
;
;       EFFECTS Switches Context (ProcBlock)

    ASSUME  DS:NOTHING,ES:NOTHING

Procedure   LockBuf_,NEAR,local
;       PMONLY

        push    AX
        SaveReg <BP>
        MOV     BP,CurrTCB
        mov     AX,[bp].TCBNumber       ; (AX) = thread ID
        RestoreReg <BP>

lkbuf1: TEST    [DI].buf_flags,BUF_VISIT
        JNZ     lkbuf2                  ; VISIT -> Busy, go block

        TEST    [DI].buf_flags,BUF_ATTEMPTING_READ
        JNZ     lkbuf2                  ; getbuffr reading into buffer

        TEST    [DI].buf_flags,BUF_BUSY
        JZ      lkbuf3                  ; not busy, go claim buffer

        CMP     [DI].buf_tid,AX         ; Do we own buffer?
        JNE     lkbuf2                  ; no, it's okay
        InternalError   <Process waiting for his own buffer>

lkbuf2: OR      [DI].buf_flags,BUF_WANT

        PUSHA                           ; SaveReg <AX,BX,CX,DX>
        MOV     AX,DS
        MOV     BX,DI
        MOV     CX,-1
        MOV     DI,CX
        MOV     DH,1
        CALLFAR ProcBlock
        POPA                            ; RestoreReg <DX,CX,BX,AX>
        JMP     lkbuf1

lkbuf3: pop     AX
        ret

EndProc LockBuf_


BREAK <UnlockBuf - Unlock a buffer>
;***    UnlockBuf - Unlock a buffer
;
;       The passed buffer is marked not busy.  If any processes are waiting
;       for the buffer, they are reawakened.
;
;       ENTRY   (DS:DI) = buffer to unlock
;
;       EXIT    The buffer is marked not BUSY
;
;       USES    flags

Procedure   UnlockBuf,NEAR
    ASSUME  DS:NOTHING,ES:NOTHING
;       PMONLY

        DBPRT   40h,10h,<UnlockBuf: $x:$x\n>,<ds,di>
        TEST    [DI].buf_flags,BUF_ATTEMPTING_READ ; buffer released during attempted read?
        JZ      rlsb20                 ; no
        AND     [DI].buf_flags,NOT (BUF_ATTEMPTING_READ)
        mov     [di].buf_sector.loword,0
        mov     [di].buf_sector.hiword,0
        SaveReg <bp>
        xor     bp,bp
    .errnz NULLVPB
        xchg    [di].buf_hVPB,bp                ; buffer contents invalid
;
; If buf_hVPB was NULLVPB, then the VPB had been freed earlier. So, we don't
; need to decrement any reference count in this case.
;
        or      bp,bp                           ; Was there an hVPB?
        jz      rlsb10                          ; No. Skip DEC
        call    DecVPBRef                       ; Remove VPB referent put by GetBuffr
rlsb10: RestoreReg <bp>

rlsb20: TEST    [DI].buf_flags,BUF_WANT         ; anyone waiting?
        JZ      rlsb40                          ; no

        PUSHA                           ; SaveReg <AX,BX,CX,DX>
        MOV     AX,DS
        MOV     BX,DI
        CALLFAR ProcRun
        POPA                            ; RestoreReg <DX,CX,BX,AX>

rlsb40: AND     [DI].buf_flags,NOT (BUF_WANT+BUF_BUSY)
        MOV     [DI].buf_tid,0                  ; No owner
        ret

EndProc UnlockBuf


BREAK <FlushBuf - Write out dirty buffer and discard even when write fails>
BREAK <FlushBufND - Write out dirty buffer >
;***    FlushBuf - Write out dirty buffer
;***    FlushBufND
;
;       Write out all dirty buffers for volume and flag them as clean.
;
;       FlushBuf will keep on writing out buffers, even after it hits an
;       error.  Thus, carry clear means ALL buffers got written OK, carry set
;       means that AT LEAST ONE did not.
;
;       The difference bet. FlushBuf and FlushBufND is that FlushBuf will
;       'dirty' and throw the buffers if write fails, whereas FlushBufND takes
;       this option as an arg.
;
;       NOTE    Because FlushBuf is called very often, we split it into two
;               separate loops, one for volume specific flushes, and one
;               for all volume flushes.
;
;       ENTRY   AX = hVPB for flush
;                  = BUF_FREE for all volumes
;
;       EXIT    Carry clear
;                       it worked
;               Carry set
;                       at least one buffer write failed
;                       (but some may have worked)
;                       (BX) = number of write errors we encountered
;
;       USES    ax,bx,cx,dx,si,di,es,flags
;
;       EFFECTS Switches Context

    ASSUME  DS:NOTHING,ES:NOTHING

Procedure   FlushBuf,hybrid
;       PMONLY
LocalVar        vFB_keepbuf,WORD        ; == 0 ; discard buf if write fails
                                        ; == 1 ; don't discard if write fails
LocalVar        vFB_HARDERR,WORD        ; ### Japan

        mov     bx,fFB_FAILDISCARD      ; discard the buffer when write fails

Entry   FlushBufND,hybrid               ; 72417 added hybrid, fshelper will farcall
;       AX = hVPB       for one volume
;          = BUF_FREE   for all volumes
;       BX = 0 if discard even when write fails
;          = 1 if don't  discard if write fails
fb0:    EnterProc <bx>
ifdef BUFFERCHK
        call    ChkBufCache
endif ;BUFFERCHK
        SaveReg <DS>

        CALL    ChkNoBuf                ; We will likely hang if CurBuf is set
        BufferContext <DS>

        XOR     BX,BX                   ; (BX) = number of writes that failed.
        mov     vFB_HARDERR, bx         ; ### Japan
        MOV     CX,DS:[bs_nBuffers]     ; (CX) = number of buffers
        MOV     DX,DS:[bs_buffsize]     ; (DX) = size of buffer + header
        MOV     DI,BUFBASE              ; (DS:DI) -> first buffer

        CMP     AX,BUF_FREE             ; Invalidating for all volumes?
        JE      fb4                     ; yes all volumes, go do all volume loop.

;*  Volume specific flush loop.
;   DS:DI -> buffer
;   AX    =  hVPB of buffers of interest
;   BX    =  number of writes that failed
;   CX    =  number of buffers left to examine
;   DX    =  size of a buffer+header

        chkvselhd <GSEL GDT_VPB>,ax

fb1:    TEST    [DI].buf_flags,BUF_DIRTY
        JNZ     fb3                     ; Buffer dirty, go check its volume
fb2:    ADD     DI,DX                   ; (DS:DI) -> next buffer
        LOOP    fb1                     ; If any left, go do new one.
        JMP     fbx                     ; we're done, go clean up

fb3:    CMP     AX,[DI].buf_hVPB        ; Our volume?
        JNZ     fb2                     ; wrong volume, go advance buffer
                                        ; Should consider add&loop here as well.
        SaveReg <AX,BX,CX,DX>
        mov     bx,vFB_keepbuf          ;
        CALL    BufWrite                ; Carry = 0:success, 1:failure
;       mov     vFB_HARDERR,ax          ; ### Japan
        RestoreReg <DX,CX,BX,AX>
        ADC     BX,0                    ; Increment BX iff Carry is set

;       Duplicate next three lines rather than take 2 jumps

        ADD     DI,DX                   ; (DS:DI) -> next buffer
        LOOP    fb1                     ; If any left, go do new one
        JMP     fbx                     ; we're done, go clean up

;*  All volume flush loop.
;   DS:DI -> buffer
;   BX    =  number of writes that failed
;   CX    =  number of buffers left to examine
;   DX    =  size of a buffer+header

fb4:    TEST    [DI].buf_flags,BUF_DIRTY
        JNZ     fb5                     ; Buffer dirty, go check its volume
        ADD     DI,DX                   ; (DS:DI) -> next buffer
        LOOP    fb4                     ; If any left, go do new one.
        JMP     short fb6               ; we're done, go clean up

fb5:    SaveReg <AX,BX,CX,DX>
        mov     bx,vFB_keepbuf          ;
        CALL    BufWrite                ; Carry = 0:success, 1:failure
;       mov     vFB_HARDERR,ax          ; ### Japan
        RestoreReg <DX,CX,BX,AX>
        ADC     BX,0                    ; Increment BX iff Carry is set

;       Duplicate next two lines rather than take 2 jumps

        ADD     DI,DX                   ; (DS:DI) -> next buffer
        LOOP    fb4                     ; If any left, go do new one

fb6:    SaveReg <es>
        DosContext es
        cmp     es:[FATCache],0         ; FAT Cache present ?
        ASSUME ES:NOTHING
        RestoreReg <es>
        je      fbx                     ; No, go return
        SaveReg <ds,es>
        pusha
CpuMode 386
        xor     eax,eax
        FlatContext <ds,es>
        CCALL32  _BufFlushAll,<eax>     ; Flush all FAT cache buffers
        popa
        RestoreReg <es,ds>
CpuMode reset
    ASSUME  DS:NOTHING,ES:NOTHING

;       At end, report results

fbx:    XOR     AX,AX
        SUB     AX,BX                   ; 0 - 0 = NC, 0 - !0 = CY
;       mov     ax, vFB_HARDERR         ; ### Japan
        RestoreReg <DS>
        LeaveProc
        ret

EndProc FlushBuf

BREAK <FlushBuf2 - Write out dirty buffer if not buffer is not busy>
;***    FlushBuf2 - Write out dirty buffer if not buffer is not busy
;
;       Write out all dirty buffers for volume and flag them as clean.
;
;       FlushBuf2 will keep on writing out buffers, even after it hits an
;       error.  Thus, carry clear means ALL buffers got written OK, carry set
;       means that AT LEAST ONE did not.
;
;
;       NOTE    FlushBuf2 is split it into two separate loops,
;               one for volume specific flushes, and one
;               for all volume flushes.
;
;       ENTRY   AX = hVPB for flush
;                  = BUF_FREE for all volumes
;               CX = Discard type
;
;       EXIT    Carry clear
;                       it worked
;               Carry set
;                       at least one buffer write failed
;                       (but some may have worked)
;                       (BX) = number of write errors we encountered
;
;       USES    ax,bx,cx,dx,si,di,es,flags
;
;       EFFECTS Switches Context

    ASSUME  DS:NOTHING,ES:NOTHING

Procedure   FlushBuf2,hybrid
;       PMONLY
LocalVar        vFB2_keepbuf,WORD        ; == 0 ; discard buf if write fails
                                        ; == 1 ; don't discard if write fails
LocalVar        vFB2_retcode,WORD

        mov     bx,fFB_FAILDISCARD      ; discard the buffer when write fails

fbh0:   EnterProc <bx>

ifdef BUFFERCHK
        call    ChkBufCache
endif ;BUFFERCHK
        SaveReg <DS>

        CALL    ChkNoBuf                ; We will likely hang if CurBuf is set
        BufferContext <DS>

        XOR     BX,BX                   ; (BX) = number of writes that failed.
        MOV     CX,DS:[bs_nBuffers]     ; (CX) = number of buffers
        MOV     DX,DS:[bs_buffsize]     ; (DX) = size of buffer + header
        MOV     DI,BUFBASE              ; (DS:DI) -> first buffer

        CMP     AX,BUF_FREE             ; Invalidating for all volumes?
        JE      fbh4                     ; yes all volumes, go do all volume loop.

;*  Volume specific flush loop.
;   DS:DI -> buffer
;   AX    =  hVPB of buffers of interest
;   BX    =  number of writes that failed
;   CX    =  number of buffers left to examine
;   DX    =  size of a buffer+header

        chkvselhd <GSEL GDT_VPB>,ax

fbh1:   TEST    [DI].buf_flags,BUF_DIRTY
        JNZ     fbh3                     ; Buffer dirty, go check its volume

fbh2:   ADD     DI,DX                   ; (DS:DI) -> next buffer
        LOOP    fbh1                     ; If any left, go do new one.
        JMP     fbhx                     ; we're done, go clean up

fbh3:   CMP     AX,[DI].buf_hVPB        ; Our volume?
        JNZ     fbh2                     ; wrong volume, go advance buffer
                                        ; Should consider add&loop here as well.

        TEST    [DI].buf_flags,BUF_VISIT+BUF_ATTEMPTING_READ+BUF_BUSY+BUF_WANT
        stc
        jnz     fbh3a

        SaveReg <AX,BX,CX,DX>
        mov     bx,vFB2_keepbuf          ;
        CALL    BufWrite                ; Carry = 0:success, 1:failure
        RestoreReg <DX,CX,BX,AX>
fbh3a:
        ADC     BX,0                    ; Increment BX iff Carry is set

;       Duplicate next three lines rather than take 2 jumps

        ADD     DI,DX                   ; (DS:DI) -> next buffer
        LOOP    fbh1                     ; If any left, go do new one
        JMP     fbhx                     ; we're done, go clean up

;*  All volume flush loop.
;   DS:DI -> buffer
;   BX    =  number of writes that failed
;   CX    =  number of buffers left to examine
;   DX    =  size of a buffer+header


fbh4:   TEST    [DI].buf_flags,BUF_DIRTY
        JNZ     fbh4a                     ; Buffer dirty, check busy status

        ADD     DI,DX                   ; (DS:DI) -> next buffer
        LOOP    fbh4                     ; If any left, go do new one.
        JMP     short fbh6               ; we're done, go clean up

fbh4a:  TEST    [DI].buf_flags,BUF_VISIT+BUF_ATTEMPTING_READ+BUF_BUSY+BUF_WANT
        stc
        jnz     fbh5a

fbh5:   SaveReg <AX,BX,CX,DX>
        mov     bx,vFB2_keepbuf          ;
        CALL    BufWrite                ; Carry = 0:success, 1:failure
        RestoreReg <DX,CX,BX,AX>
fbh5a:
        ADC     BX,0                    ; Increment BX iff Carry is set

;       Duplicate next two lines rather than take 2 jumps

        ADD     DI,DX                   ; (DS:DI) -> next buffer
        LOOP    fbh4                     ; If any left, go do new one

fbh6:   SaveReg <es>
        DosContext es
        cmp     es:[FATCache],0         ; FAT Cache present ?
        ASSUME ES:NOTHING
        RestoreReg <es>
        je      fbhx                     ; No, go return
        SaveReg <ds,es>
        pusha
CpuMode 386
        xor     eax,eax
        FlatContext <ds,es>
        CCALL32  _BufFlushAll2,<eax>     ; Flush all non-busy FAT cache buffers
        mov     vFB2_retcode,ax          ; save return code
        popa
        RestoreReg <es,ds>
        cmp     vFB2_retcode,0
        je      fbhx
        inc     bx
CpuMode reset
    ASSUME  DS:NOTHING,ES:NOTHING

;       At end, report results

fbhx:   XOR     AX,AX
        SUB     AX,BX                   ; 0 - 0 = NC, 0 - !0 = CY
        RestoreReg <DS>
        LeaveProc
        ret

EndProc FlushBuf2

;**     FSD_FlushBuf - flush both kernel and FSDs managed buffers
;
;       ENTRY   ax = hVPB of volume to flush
;                  = BUF_FREE to flush all volumes
;               dx = FLU_KEEP    - retain data in buffers after flushing
;                  = FLU_DISCARD - discard data in buffers after flushing
;                  = FLU_HDISCARD - discard data in buffers after flushing
;                                   for hybernation
;
;       EXIT    if 'NC' - success
;               if 'C' - error in at least one flush
;                 bx = number of write errors
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
;       NOTE    switches context
;
;       PSEUDO CODE
;
;       FlushBuf  /* flush kernel managed buffers */
;       if (dx = FLU_HDISCARD)
;         BufInvalidate2
;       if (dx = FLU_DISCARD)
;         BufInvalidate
;       if (ax = hVPB && vpb_FSC -> FSC)
;         FlushFsdBuf  /* flush FSD managed buffers */

Procedure FSD_FlushBuf,hybrid
    assume ds:nothing,es:nothing,ss:TASKAREA

LocalVar fsb_hVPB,WORD                  ; hVPB or BUF_FREE
LocalVar fsb_fKeep,WORD                 ; FLU_KEEP or FLU_DISCARD
LocalVar fsb_cError,WORD                ; number of errors

        EnterProc <ax,dx,0>             ; hVPB=ax, fKeep=dx, cError=0

ifdef FSSTRICT                          ; check flag for valid values
        .errnz  FLU_KEEP
        cmp     dx,FLU_HDISCARD
        jbe     fsb5
        InternalError <FSD_FlushBuf: bad flag>
endif

fsb5:
        cmp     fsb_fKeep,FLU_HDISCARD2 ; discard buffers for hybernate?
        jb      fsb6                    ; no, do normal flush buffers

        call    FlushBuf2               ; bx = number of write errors if carry
        jnc     fsb10a                  ; no error, go discard buffers
        mov     fsb_cError,bx           ; cError = number of errors
        jmp short fsb10a

fsb6:   call    FlushBuf                ; bx = number of write errors if carry
        jnc     fsb10                   ; no error, go discard buffers
        mov     fsb_cError,bx           ; cError = number of errors

fsb10:  cmp     fsb_fKeep,FLU_HDISCARD2  ; discard buffers for hybernate?
        jb      fsb11                   ; no, go flush FSD buffers

fsb10a:
        push    bx
        mov     ax,fsb_hVPB             ; ax = hVPB or BUF_FREE
        mov     bx,fsb_fKeep            ; type of discard
        call    BufInvalidate2          ; discard buffers
        pop     bx
        jnc     fsb13                   ;
        jmp short fsb35

fsb11:  cmp     fsb_fKeep,FLU_DISCARD   ; should we discard buffers?
        jne     fsb20                   ; no, go flush FSD buffers

        mov     ax,fsb_hVPB             ; ax = hVPB or BUF_FREE
        call    BufInvalidate           ; discard buffers

;       cmp     fsb_hVPB,BUF_FREE       ;### b731143
;       je      fsb15                   ;### b731143
fsb13:
        SaveReg <ax,bx>
        mov     bx,fsb_hVPB             ; bx = hVPB
        CALLFAR UseCache                ; Can we use the cache for this vol. ?
        RestoreReg <bx,ax>
        jc      fsb20                   ; No, skip BufDiscard
;fsb15:                                 ;### b731143
        SaveReg <ds,es>
        pusha
CpuMode 386
        mov     ax,fsb_hVPB
        movzx   eax,ax                  ; EAX = hVPB
        FlatContext <ds,es>
        CCALL32  _BufDiscard,<eax>      ; Discard all clean Cache buffers
CpuMode 286
        popa
        RestoreReg <es,ds>
    ASSUME  DS:NOTHING,ES:NOTHING

; call FlushFsdBuf if necessary

fsb20:  mov     ax,fsb_hVPB             ; ax = hVPB or BUF_FREE
        cmp     ax,BUF_FREE             ; flush all volumes?
        je      fsb25                   ; yes, go call FlushFsdBuf
        mov     si,ax                   ; si = hVPB
        VPBContext ds,si                ; ds:si -> vpb
        cmp     ds:[si].vpb_FSC.Offst,FSC_LOCAL ; FSD volume?
        je      fsb30                   ; no (FAT), go exit

fsb25:  mov     dx,fsb_fKeep            ; dx = FLU_KEEP or FLU_DISCARD
        call    FlushFsdBuf             ; flush fsd managed buffers
        jnc     fsb30                   ; no error, go exit

        add     fsb_cError,bx           ; sum errors

fsb30:  mov     bx,fsb_cError           ; bx = cError
        xor     ax,ax
        cmp     ax,bx                   ; set carry if bx != 0
fsb35:
        LeaveProc
        ret

EndProc FSD_FlushBuf

;**     w16_FSD_FlushBuf - flush both kernel and FSDs managed buffers
;
;       ENTRY   ax = hVPB of volume to flush
;                  = BUF_FREE to flush all volumes
;               dx = FLU_KEEP    - retain data in buffers after flushing
;                  = FLU_DISCARD - discard data in buffers after flushing
;
;       EXIT    if 'NC' - success
;               if 'C' - error in at least one flush
;                 bx = number of write errors
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
;       NOTE    switches context
;
;       PSEUDO CODE
;
;       FlushBuf  /* flush kernel managed buffers */
;       if (dx = FLU_DISCARD)
;         BufInvalidate
;       if (ax = hVPB && vpb_FSC -> FSC)
;         FlushFsdBuf  /* flush FSD managed buffers */

Procedure w16_FSD_FlushBuf,<far16>
    assume ds:nothing,es:nothing,ss:TASKAREA

LocalVar fsdfb_fKeep,WORD                 ; FLU_KEEP or FLU_DISCARD

        EnterProc <dx>

        SaveReg <ds,es,ax,dx>           ; save ds,es
        call FSD_FlushBuf
        RestoreReg <dx,ax,es,ds>        ; restore es,ds
        jnc     fsdfb1
        jmp     fsdfberrexit2           ; error exit
fsdfb1:
        SaveReg <ds,es>                 ; save ds,es

        cmp     dx, FLU_HDISCARD2       ; hibernate discard mode?
        jae     fsdfbDiscard            ; yes
        jmp     fsdfbexit               ; no, go exit

fsdfbDiscard:
;;      ax = hVPB or BUF_FREE

        cmp     ax,BUF_FREE             ; was this a flush all volumes?
        je      fsdfb5                  ; yes, run the VPB chain
        DosContext <ds>
        mov     si,ax                   ; si = hVPB
        push    GSEL GDT_VPB
        pop     es
        cmp     es:[si].vpb_FSC.Offst,FSC_LOCAL ; FSD volume?
        je      fsdfbLocal
        jmp     fsdfbexit                 ; not a FAT volume, exit

fsdfbLocal:
        test    es:[si].vpb_flags,VPBINITCACHE ; VPB cache data being init'd?
        jz      fsdfb3                         ; no
        stc                                    ; yes, can't free right now
        jmp     fsdfberrexit1                  ; error exit

fsdfb3:
        cmp     fsdfb_fKeep, FLU_HDISCARD2 ; hibernate discard pre-check mode?
        je      fsdfbCHKSFT                ; yes, do not modify VPB cache data

CpuMode 386
        and     es:[si].vpb_fMisc, not VPB_FM_INITCACHE_DONE
        mov     di,si                      ; di=si=hVPB
        push    es                         ; es = SEL VPB
        FlatContext <ds,es>
        CCALL32  FreeVPBCacheData,,nocall  ; free VPB's cluster bit map

        pop     es                         ; es = SEL VPB
        MOV     es:[si].vpb_fsd.vpdFAT_free_cnt,-1 ; invalidate free count
CpuMode reset
        jmp     fsdfbCHKSFT

fsdfb5: DosContext <ds>
        mov     si,[hVPBFirst]          ; si = first hVPB
        or      si,si                   ; any VPB's
        jz      fsdfbCHKSFT             ; no, go check SFTs

        push    GSEL GDT_VPB
        pop     es

; es:si -> vpb

fsdfb10:
        or      si,si                   ; end of vpb list?
        jz      fsdfbCHKSFT             ; yes, go check SFTs

        cmp     es:[si].vpb_FSC.Offst,FSC_LOCAL ; FAT volume?
        jne     fsdfb20               ; no, check next volume

        test    es:[si].vpb_flags,VPBINITCACHE ; VPB cache in initialization?
        jz      fsdfb12                        ; no
        stc                                    ; yes, can't free right now
        jmp     fsdfberrexit1                  ; error exit

fsdfb12:
        cmp     fsdfb_fKeep, FLU_HDISCARD2 ; hibernate discard pre-check mode?
        je      fsdfb20                    ; yes, do not free VPB cluster map

CpuMode 386
        and     es:[si].vpb_fMisc, not VPB_FM_INITCACHE_DONE
        mov     di,si                      ; di=si=hVPB
        push    es                         ; es = SEL VPB
        FlatContext <ds,es>
        CCALL32  FreeVPBCacheData,,nocall  ; free VPB's cluster bit map

        pop     es                         ; es = SEL VPB
        MOV     es:[si].vpb_fsd.vpdFAT_free_cnt,-1 ; invalidate free count
CpuMode reset

fsdfb20:
        mov     si, es:[si].vpb_flink   ; next vpb on list
        jmp short fsdfb10               ;

fsdfbCHKSFT:
        xor     bx,bx           ; start with the first SFT
fas10:
        mov     di,GSEL GDT_SFT
        mov     es,di           ; es = GDT_SFT
        cmp     bx,es:CALLSFN
        jae     fsdfbexit       ; invalid SFN, exit

        CALLFAR SFFromSFN       ; Did we get an SFT?
        jc      short fas20     ;   No, do not attempt to flush it.
                                ;   es:di -> SFT
        test    es:[di].sf_flags2,sff2_isfree   ; Is the SFT on the free list?
        jnz     short fas20     ;   Yes, do not attempt to flush it.
        test    es:[di].sf_fsi.sfi_mode2,open2_phys_disk ; physical disk?
        jnz     short fas20     ;   Yes, don't flush it
;
; If sf_FSC has not been set up, then this SFT is still "in transition" and
; we do not need to flush it out.
;

        cmp     es:[di].sf_FSC.Segmt,FSC_BAD
        je      short fas20     ; Don't flush it

        cmp     es:[di].sf_MFT,SF_MFT_NULL ; MFT assigned ?
        je      fas20                   ; no, get next SFT

CpuMode 386
        push    edi
        mov     edi,es:[di].sf_MFT  ;
        FlatContext <ds,es>,,noassume   ; (ds:edi) -> MFT, ds = es = FlatContext

        mov     esi, es:[edi].mft_pCMap  ; (esi) -> file's cluster map

        or      esi,esi                 ; Cluster map allocated yet?
        jz      SHORT fas15             ; no, get next SFT

        mov     ax, es:[esi].cm_iValid   ; force page into memory

;        cmp     fsdfb_fKeep, FLU_HDISCARD2 ; hybernate pre-check mode?
;        je      fas15                      ; yes, do not modify MFT

;        xor     ax,ax
;        mov     es:[esi].cm_iValid,ax   ;  yes, iValid = caller requested ord
fas15:
        pop     edi                      ;  ds:di -> SFT

CpuMode reset

fas20:
        DosContext <ds>
        assume  ds:nothing, es:nothing
        inc     bx
        jmp     short fas10

fsdfbexit:
        clc

fsdfberrexit1:
        RestoreReg <es,ds>            ; restore es,ds

fsdfberrexit2:
        LeaveProc
        RETP    ?aframe

EndProc w16_FSD_FlushBuf


;**     FlushFsdBuf - flush buffers managed by FSDs
;
;       ENTRY   ax = hVPB of volume to flush
;                  = BUF_FREE to flush all volumes
;               dx = FLU_KEEP    - retain data in buffers after flushing
;                  = FLU_DISCARD - discard data in buffers after flushing
;
;       EXIT    if 'NC' - success
;               if 'C' - at least one flush failed
;                 bx = number of flush errors
;
;       USES    ax,bx,cx,dx,si,di,ds,es,flags
;
;       PSEUDO CODE
;
;       for all hVPB in use
;         if (vpb_FSC points to an FSD)
;           call FS_FLUSHBUF(hVPB, flag)

Procedure FlushFsdBuf,near,local
    assume ds:nothing,es:nothing,ss:TASKAREA

LocalVar ffb_fKeep,WORD                 ; 0 = keep data, 1 = discard data
LocalVar ffb_cError,WORD                ; error count

        EnterProc <dx,0>                ; fKeep = dx, cError=0

        FlatContext <ds,es>                                     ;140726
        CCALL32 _KSEMRequestMutex,<<offset FLAT:ksemVPB>,-1>    ;140726

        cmp     ax,BUF_FREE             ; flush one or flush all?
        je      ffb5                    ;   go flush all

; flush one vpb

        mov     si,ax                   ; si = hVPB
        VPBContext ds,si                ; ds:si -> vpb
        push    ax                      ; push hVPB
        push    dx                      ; push flag
        les     di,ds:[si].vpb_FSC      ; es:di -> fsc
        call    es:[di].FS_FLUSHBUF     ; ax = 0 or error code

; increment cError if error

        or      ax,ax                   ; error?
        jz      ffbx                    ;   no, go exit

        inc     ffb_cError
        jmp     short ffb25             ; go exit

ffb5:   DosContext <ds>
        mov     si,[hVPBFirst]          ; si = first hVPB
        or      si,si                   ; any VPB's
        jz      ffb25                   ; no
        VPBContext ds,si                ; ds:si -> vpb

; ds:si -> vpb

ffb10:  or      si,si                   ; end of vpb list?
        jz      ffb25                   ;   yes, go exit

        cmp     ds:[si].vpb_FSC.Offst,FSC_LOCAL ; what file system?
        je      ffb20                   ; fat, skip it

        SaveReg <ds,si>                 ; save pointer to vpb
        push    si                      ; push hVPB
        push    ffb_fKeep               ; push flag
        les     di,ds:[si].vpb_FSC      ; es:di -> fsc
        call    es:[di].FS_FLUSHBUF     ; ax = 0 or error code
        RestoreReg <si,ds>              ; ds:si -> vpb

; increment cError if error

        or      ax,ax                   ; error?
        jz      ffb20                   ;   no, don't count it

        inc     ffb_cError              ; count error

ffb20:  mov     si,ds:[si].vpb_flink    ; ds:si -> next vpb
        jmp     short ffb10

ffb25:  mov     bx,ffb_cError           ; bx = number of errors
        xor     ax,ax
        cmp     ax,bx                   ; set carry if bx != 0

ffbx:
        FlatContext <ds,es>                                     ;140726
        CCALL32 _KSEMReleaseMutex,<offset FLAT:ksemVPB>         ;140726
        LeaveProc
        ret

EndProc FlushFsdBuf


BREAK <PointComp - Compare pointers for equality>
;***    PointComp - Compare pointers for equality
;
;       Compare DS:SI to ES:DI (or DS:DI to ES:SI) for equality
;       DO NOT USE FOR < or >
;
;       ENTRY   ES:DI, DS:DI    Pointers to be compared
;       EXIT    ZF set, pointers are equal
;               ZF clear, pointers differ
;       USES    Flags
;

    ASSUME  DS:NOTHING,ES:NOTHING

Procedure   PointComp,hybrid
        ASSUME  DS:NOTHING,ES:NOTHING

        CMP     SI,DI
        jnz     short pcx
        PUSH    CX
        PUSH    DX
        MOV     CX,DS
        MOV     DX,ES
        CMP     CX,DX
        POP     DX
        POP     CX
pcx:    ret
EndProc PointComp


BREAK <WaitBuf - Wait for a buffer to be available>
;***    WaitBuf - Wait for a buffer to be available
;
;       The passed buffer is checked to see if it is busy.
;       If so, the process is suspended until it is available.
;
;       ENTRY:  DS:DI   Pointer to buffer header
;
;       EXIT:   Buffer header is marked BUSY
;               [CurBuf] set to point to buffer header
;               ZF set, did not block (buffer cache unchanged)
;                   (JZ NotBlocked)
;               ZF clear, blocked on buffer (buffer may have changed)
;                   (JNZ Blocked)
;
;       USES:   flags
;
;       EFFECTS Switches Context (ProcBlock)
;

    ASSUME  DS:NOTHING,ES:NOTHING

Procedure   WaitBuf,NEAR,local
;       PMONLY

        call    ChkNoBuf
        LockBuf
        PUSHF                           ; Save indication of whether we blocked
        SaveReg <BP>
        MOV     BP,CurrTCB
        MOV     TASKAREA:[bp].TCBCurBuf.Offst,DI
        MOV     TASKAREA:[bp].TCBCurBuf.Segmt,DS
        OR      [BP].TCBForce_Flg,FF_BUF
        RestoreReg <BP>
        POPF
        ret
EndProc WaitBuf


BREAK <BufWrite - Wait for and write a buffer if dirty>
;***    BufWrite - Wait for and write a buffer if dirty
;
;       Write out the buffer if dirty.  The buffer is waited on if it is
;       busy.  The buffer is marked free if the write fails.  If the buffer
;       contains a FAT sector, the buf_wrtcnt and buf_wrtcntinc fields in
;       the buffer header cause us to update all copies of the FAT (usually
;       2).
;
;       ENTRY:  (DS:DI) = buffer to wait for and write
;                BX = 0 -> if buffer can be discarded when write fails
;                   = 1 -> if buffer should not be discarded even when
;                          write fails ( used by FSH_FLUSHBUF)
;
;       EXIT:   Carry Clear, buffer written to disk successfully
;                   Buffer marked clean
;               Carry set, buffer changed or hard error was FAILed
;                   Buffer is in unknown state
;
;       USES:   AX,BX,CX,DX,SI,ES,flags
;
;       EFFECTS:Switches Context

    ASSUME  DS:NOTHING,ES:NOTHING

Procedure   BufWrite,hybrid
;       PMONLY

        LocalVar    vBW_keepbuf,WORD    ; discard/no discard when write fails
        LocalVar    bw_hVPB,WORD        ; hVPB of buffer
        LocalVar    bw_sector,DWORD     ; sector of buffer
        LocalVar    bw_cntWrt,WORD      ; count of successful writes
        LocalVar    bw_HDERR,WORD       ; Harderr ### Japan
        EnterProc       <bx>

        DBPRT   40h,10h,<BufWrite: $x:$x\n>,<ds,di>
        mov     bw_HDERR, 0             ;### Japan
bw10:   BufAssertIsBuf  <di>
        CALL    WaitBuf                 ; check for, set BUSY
        clc                             ; indicate no write error
        pushf
        CMP     [DI].buf_hVPB,BUF_FREE  ; buffer free?
        jz      bw20                    ; yes

        TEST    [DI].buf_flags,BUF_DIRTY

bw20:   LJZ     bw80                    ; Buffer is clean.
        popf
        mov     bw_cntWrt,0             ; no writes, yet

        mov     bx,[di].buf_hVPB        ; Save hVPB
        mov     bw_hVPB,bx

        mov     bx,word ptr [di].buf_sector     ; Save sector
        mov     word ptr bw_sector,bx
        mov     bx,word ptr [di].buf_sector+2
        mov     word ptr bw_sector+2,bx

        mov     si,CurrTCB
        mov     TASKAREA:[si].TCBAllowed,ALLOWED_RETRY + ALLOWED_FAIL

        LEA     BX,[DI.BUFINSIZ]        ; (DS:BX) = data area of buffer
        MOV     DX,word ptr [DI].buf_sector
        MOV     SI,word ptr [DI].buf_sector+2
        XOR     CH,CH
        MOV     CL,[DI].buf_wrtcnt
        MOV     AX,WORD PTR [DI].buf_wrtcntinc

;*  (CX) = number of times to write buffer
;   (AX) = increment between successive sectors for write
;   (SI:DX) = sector number of buffer
;
;   This supports updating multiple FATs and writing single data sectors.

;BUGBUG 01-Sep-1987 bws Do not allow IGNORE response in BufWrite
;*
;*  We cannot handle the IGNORE case of hard errors for dirty buffers,
;*  so don't allow it.  IGNORE is too dangerous anyway!
;;;;
;;;;
;;;;
;;;;    TEST    [DI].buf_flags,BUF_ISDATA
;;;;    JZ      bw40
;;;;    or      TASKAREA:[si].TCBAllowed,ALLOWED_IGNORE

        XCHG    DI,SI           ; DI:DX = starting sector number
                                ; DS:SI -> buffer
;
; DI:DX = sector number to write out
; SI -> buffer
;
bw40:

ifdef PERFVIEW
        pushf                                                           ;sfb
        CpuMode 386                                                     ;sfb
        SaveReg <es>                                                    ;sfb
        FlatContext <es>                                                ;sfb
        cmp     es:_fPerfview,0         ; check if perfview is up       ;sfb
        je      short bw41                                              ;sfb
        PvwCnt fbc,WrtActiv,0          ; counter for # of writes       ;b722131
bw41:                                                                   ;sfb
        CpuMode Reset                                                   ;sfb
        ASSUME  ES:NOTHING                                              ;sfb
        RestoreReg <es>                                                 ;sfb
        popf                                                            ;sfb
endif ;PERFVIEW

        pusha
        mov     bx,[si].buf_hVPB
        CALLFAR UseCache                ; Can we use the cache ?
        popa
        jc      bw45                    ; No, bypass cache

;       PvwEnterQ fbc,QWrt,0           ; counter for entering write ;aa04447
        SaveReg <ds,es>
        pusha
        xchg    di,si                   ; SI:DX = starting sector number
        mov     bx,TASKAREA:[CurrTCB]
        mov     TASKAREA:[bx].TCBBufHE,BufHE_Percolate ; We handle retry
        mov     bx,ds:[di].buf_hVPB
        VPBContext ES,BX
        call    BufCacheWrite           ; write data through cache
        popa
        RestoreReg <es,ds>

;       PvwLeaveQ fbc,QWrt,0             ; counter for leaving write    ;aa04447
    ASSUME  DS:NOTHING,ES:NOTHING
        jmp     short bw46

bw45:

ifdef PERFVIEW
        pushf                                                           ;sfb
        CpuMode 386                                                     ;sfb
        SaveReg <es>                                                    ;sfb
        FlatContext <es>                                                ;sfb
        cmp     es:_fPerfview,0         ; check if perfview is up       ;sfb
        je      short bw45a                                             ;sfb
        PvwCnt fbc,Bypass,0            ; counter for bypass cache      ;b722131
bw45a:                                                                  ;sfb
        CpuMode Reset                                                   ;sfb
        ASSUME  ES:NOTHING                                              ;sfb
        RestoreReg <es>                                                 ;sfb
        popf                                                            ;sfb
endif ;PERFVIEW

;       PUSHA                           ;### Japan
;       PUSH    DS                      ;### Japan
        push    ax                      ;### Japan
        SaveReg <bx,cx,dx,bp,si,di,ds>  ;### Japan
        XOR     AX,AX                   ; tell DWrite we do NOT want ByPass
        MOV     CX,1
        mov     si,TASKAREA:[CurrTCB]
        mov     TASKAREA:[si].TCBBufHE,BufHE_Percolate ; We handle retry
        mov     bp,bw_hVPB              ; (bp) = hVPB of buffer
        call    DWrite                  ; Write out the dirty buffer
        RestoreReg <ds,di,si,bp,dx,cx,bx>  ;### Japan
;        mov     bw_HDERR, ax            ; Harderr ### Japan
        pop     ax                      ;### Japan
;       POP     DS                      ;### Japan
;       POPA                            ;### Japan
bw46:   JC      bw50                    ; Hard error occured
        inc     bw_cntWrt               ; Write successful
        jmp     bw60

;*  RETRY
;       The hard error caused the buffer to be marked not busy.  Hence, the
;       buffer may have changed.

bw50:   xchg    di,si                   ; DS:DI -> buffer
        mov     si,TASKAREA:[CurrTCB]
        cmp     TASKAREA:[si].TCBactBufHE,HDERR_RETRY
        LJE     bw10                    ; Go do retry

;*  FAIL or ABORT
;       DiskHE has unlocked the buffer.  If we can get the buffer back, and
;       if it is the same buffer, we continue the write at the next sector.
;
;       ALL THIS TROUBLE FOR REDUNDANT FAT SECTORS!

        call    WaitBuf                 ; check for, set BUSY
        cmp     [di].buf_hVPB,BUF_FREE  ; buffer free?
        jz      bw65                    ;   YES, not our buffer

        SaveReg <ax>
        mov     ax,word ptr bw_sector   ; (ax) = low word of saved sector
        cmp     ax,word ptr [di].buf_sector ; Is sector still the same?
        RestoreReg <ax>
        jne     bw65                    ;   NO, not our buffer

        SaveReg <ax>
        mov     ax,word ptr bw_sector+2 ; (ax) = high word of saved sector
        cmp     ax,word ptr [di].buf_sector+2   ; Is sector still the same?
        RestoreReg <ax>
        jne     bw65                    ;   NO, not our buffer

        SaveReg <ax>
        mov     ax,bw_hVPB              ; (bx) = saved hVPB
        cmp     ax,[di].buf_hVPB        ; Is hVPB still the same?
        RestoreReg <ax>
        jne     bw65                    ;   NO, not our buffer
;
; Buffer may have changed during harderror.  If so, release it and pass
; indication of failure to caller.  Note that we will not change the buffer
; because it is no longer the one we are interested in.
;
        test    [di].buf_flags,BUF_DIRTY ; Is it still dirty?
        mov     si,word ptr [di].buf_sector+2   ; Reload DI with high word of sector
        jz      bw65
        xchg    si,di                   ; SI -> buffer, DI:DX = sector number
; Yes, it is our buffer!
bw60:   ADD     DX,AX
        ADC     DI,0
        dec     cx
        LJNZ    bw40                    ; write again if more FAT sectors
        xchg    si,di                   ; DI -> buffer
        cmp     bw_cntWrt,0             ; Clears carry
        jne     bw70                    ; At least one write worked

;       test    AutoFail,DOSERR_HARDERR_FLAG  ; AutoFail set?     PB723642
;       jnz     short bw65              ; YES, don't trash buffer PB723642

        cmp     ProcType,PT_REALMODE    ; 3xbox?
        jnz     short bw66              ; NO, okay to trash buffer

        .386p
        SaveReg <esi,ds>                ; YES, see if we can trash buffer
        FlatContext <ds>,ax
        mov     esi,[_pvheVDM]          ; (ESI) = ptr to v86 hard error struc
        or      esi,esi                 ; is the vhe address initialized?
        jz      short bw66              ;  NO, go trash buffer
        test    [esi].vhe_fbMisc,VHE_FLAG_POPUP ; Did we popup?
        RestoreReg <ds,esi>
    ASSUME  DS:NOTHING
        jnz     short bw66              ; YES, go trash buffer
        CpuMode reset
;
; We don't want to free the buffer if AutoFail is in effect, because such
; apps may want to retry the original operation, without having to rewrite
; the data.  They'll have trouble if this BufWrite was on behalf of a
; DOSCLOSE, since the handle will be freed, but in that case they should
; have called DOSBUFRESET first...
;
; PB723642.. Toss the buffers even if AUTOFAIL is on.  The caller of
; a dos api has no way to retry the operation.  They could flush the
; buffers. but this doesn't fix a 63K write that failed.  They will
; just have to start from scratch.
;
; Also, we do not know what an app is planning to do in the 3xBox (ie,
; whether it is a FAPI app trying to simulate DOSERROR or not), so we don't
; free the buffer if we're the 3xBox AND there was no actual pop-up.
;
; If we want to preserve the buffer, we do not reset the DIRTY bit because
; the data is STILL valid AND did not get to the disk.
;
bw65:   call    RlseBuffr
        stc                     ; indicate failure
        jmp short bwx           ; go straight to exit

bw66:   cmp     vBW_keepbuf,fFB_FAILDISCARD     ; can we discard this buffer ?
        stc
        jne     bwx             ; no, don't 'dirty' the buffer.
        call    RemoveBufVPB   ; free buffer
        stc

bw70:   pushf
bw80:   and     [di].buf_flags,NOT BUF_DIRTY    ; mark clean
        CALL    RlseBuffr               ; release the buffer
        popf
bwx:
;        mov     ax, bw_HDERR            ; Harderr ### Japan
        LeaveProc
        ret

EndProc BufWrite,NoCheck


Break <RemoveBufVPB - clear out the VPB for a buffer>
;***    RemoveBufVPB - clear out the VPB for a buffer>
;
;       Removes the association between a buffer and a volume.
;
;       ENTRY:  (DS:DI) = buffer to free
;               (DS:DI).buf_hVPB != BUF_FREE
;
;       EXIT:   (DS:DI).buf_hVPB  = BUF_FREE
;               (DS:DI).buf_flags  BUF_DIRTY bit is reset
;               Buffer placed at LRU position, so that GetFreeBuffr will
;               find it quickly.
;
;       USES:   ES, flags

    ASSUME  DS:NOTHING,ES:NOTHING

Procedure   RemoveBufVPB,hybrid
;       PMONLY

    BufAsserthVPB <di>
        SaveReg <BP>
        xor     bp,bp                   ; (BP) = null hVPB
.errnz  BUF_FREE
        mov     word ptr [di].buf_sector,bp
        mov     word ptr [di].buf_sector+2,bp
        xchg    bp,[DI].buf_hVPB        ; (BP) = buffer volume, free buffer
        call    DecVPBRef               ; Reduce ref count on VPB
        call    PlaceBufLRU             ; Let GetFreeBuffr find it quickly
        and     [di].buf_flags,NOT BUF_DIRTY ; Buffer no longer has valid data

rbvx:   RestoreReg <BP>
        ret

EndProc RemoveBufVPB


BREAK <ChkNoBuf - Check that no buffer is allocated.>
;***    ChkNoBuf - Check that no buffer is allocated.
;
;       A consistency check is made that the CurBuf pointer is zero.
;
;       ENTRY:  none
;
;       EXIT:   none
;
;       USES:   none

    ASSUME  DS:NOTHING,ES:NOTHING

Procedure   ChkNoBuf,NEAR,local
    ASSUME      DS:NOTHING,ES:NOTHING
;       PMONLY

    IF NOT Debug
        JMP     RlseBuffr

    ELSE
        PUSH    DS
        PUSH    DI
        mov     DI,CurrTCB
        test    ss:[di].TCBForce_Flg,FF_BUF ; buffer assigned?
        JZ      chnb1                   ; no
    ENDIF

    DBBEG       40h,30h
        LDS     DI,TASKAREA:[DI].TCBCurBuf              ; get buffer pointer
        DBPRT   <>,0,<ChkNoBuf:  Buffer assigned at $x  >,<[di.buf_fill]>
    DBEND
    ENDIF
    IF  Debug
        DBPRT   <>,0,<ChkNoBuf: Buffer allocated\n>
        CALL    RlseBuffr
        mov     DI,CurrTCB
        MOV     TASKAREA:WORD PTR [di].TCBCurBuf,0
chnb1:
        POP     DI
        POP     DS
        ret
    ENDIF                       ; IF NOT Debug

EndProc ChkNoBuf

;***    GetOverlapBuf - Locates buffer overlapping range
;
;       Locates the buffer containing the lowest sector in the range.
;
;       ENTRY   (SI) = hVPB
;               (CX:DX) = first sector in range
;               (AX:BX) = last sector in range
;
;       EXIT    CY - no buffer found
;               NC - buffer found
;                    (DS:DI) -> buffer data
;                    (CX:DX) = sector number
;
;       USES    ax,bx,cx,dx,di,si,ds,flags
;
;       PSEUDOCODE
;
;       pBuf = 0
;       for (each buffer in cache)
;         if (correct volume and in range)
;           highbound = buffer.sector
;           pBuf = this buffer
;       if (pBuf == 0)
;         return CY
;       WaitBuf
;       if (buffer didn't change)
;         PlaceBufMRU
;         TCYield
;         return no error
;       else
;         RlseBuffr
;         start over from beginning of cache

Procedure GetOverlapBuf,NEAR
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

LocalVar gob_hVPB,WORD
LocalVar gob_low,DWORD                  ; low bound
LocalVar gob_high,DWORD                 ; high bound
LocalVar gob_pBuf,WORD                  ; points to last candidate buffer

        EnterProc <si, cx,dx, ax,bx>    ; save parameters

        BufferContext <ds>
gob1:   mov     di,BUFBASE              ; (DS:DI) -> first buffer
        mov     gob_pBuf,0              ; no buffer found yet

gob2:   cmp     [di].buf_hVPB,si
        jne     gob5                    ; not our volume

; check low bound
; the 32-bit comparison used here can only indicate >= or < based on
; the carry flag. The zero flag is useless.

        cmp     [di].buf_sector.loword,dx
        mov     si,[di].buf_sector.hiword
        sbb     si,cx                   ; current buf >= low bound?
        jb      gob4                    ; no, skip it

; check high bound

        cmp     bx,[di].buf_sector.loword
        mov     si,[di].buf_sector.hiword
        sbb     ax,si                   ; high bound >= current buf?
        jb      gob4                    ; no, skip it

; found a candidate; set high bound to this buffer's sector

        mov     ax,[di].buf_sector.hiword
        mov     bx,[di].buf_sector.loword
        mov     gob_pBuf,di             ; remember this buffer

gob4:   mov     si,gob_hVPB             ; si = hVPB again

gob5:   add     di,ds:[bs_buffsize]     ; Advance to next buffer
        cmp     di,ds:[bs_seglimit]     ; Pointing pass end of segment?
        jb      gob2                    ; NO, go work on new buffer

; All buffers are scanned. See if we found any in range.

        mov     di,gob_pBuf             ; 0 or points to buffer
        or      di,di                   ; did we find any buffer in range?
        jnz     gob6                    ; yes, use it
        stc                             ; no, return error
        jmp     short gob8

gob6:   mov     cx,[di].buf_sector.loword
        mov     dx,[di].buf_sector.hiword ; cx:dx = buffer's sector
        call    WaitBuf                 ; get possession of buffer, uses flags
        jz      gob7                    ; didn't block, so we got buffer
        mov     ax,si                   ; (AX) = volume
        CmpBuf  E,gob7                  ; buffer didn't change, grab it
        call    RlseBuffr

; restore parameters and start over

        mov     cx,gob_low.hiword
        mov     dx,gob_low.loword
        mov     ax,gob_high.hiword
        mov     bx,gob_high.loword
        mov     si,gob_hVPB
        jmp     short gob1              ; go start over

gob7:   call    PlaceBufMRU
;
; We assume that since the buffer is MRU, it won't get used during the TCYield
;
        mov     cx,[di].buf_sector.hiword
        mov     dx,[di].buf_sector.loword ; (CX:DX) = sector
        lea     di,[di].BUFINSIZ        ; (DS:DI) -> buffer data
        CALLFAR TCYield
        clc

gob8:   LeaveProc
        ret
EndProc GetOverlapBuf

HIGH2CODE    ENDS


        FARCODE INIT
INITCODE segment

BREAK <BufInit - Allocate and initialize permanent disk buffer pool>
;***    BufInit - Allocate and initialize permanent disk buffer pool
;
;       Allocate file system buffer pool, and initialize it.
;
;       ENTRY:  (CX) = number of buffers to allocate
;
;       EXIT:   Carry Clear, Allocation successfull
;                   DOSGROUP:BufferSel -> buffer segment
;                   Permanent buffer segment allocated and linked up.
;               Carry Set, Allocation failed
;                   (AX) = ERROR_NOT_ENOUGH_MEMORY
;                   Caller (SysInit) should request fewer buffers.
;
;       USES:   All but BP

Procedure   BufInit,near

        cmp     cx,NBUFDFLT             ; no of buffers must be atleast 6
        jae     bi10                    ;
        mov     cx,NBUFDFLT             ;
bi10:   SaveReg <BX,CX>
        CALL    BufSize                 ; (AX:BX) = size of buffer segment

        ; If we have DASD with > 512byte sectors (2K for example), ;d57219
        ; then we have to make sure (#buffers * sizeofbuffers)     ;d57219
        ; does not exceed 64K.                                     ;d57219
                                                                   ;d57219
        or     ax,ax                    ; segment under 64K?       ;d57219
        jz     bi11                     ;  yes, continue           ;d57219
                                                                   ;d57219
        pop    cx                       ; Segment over 64K, so     ;d57219
        pop    bx                       ;  restore BX and CX,      ;d57219
        dec    cx                       ;  dec the # of buffers,   ;d57219
        jmp    bi10                     ; and try again.           ;d57219
                                                                   ;d57219
bi11:                                                              ;d57219
                                                                   ;d57219


; Add Big Buffers memory size
; Currently we allocate 4 buffers, each one size of two sectors.
; We don't allocate any big buffers if it will make the buffer segment
; to exceed 64K

        DosContext <DS>
        mov     CX,MaxSec
        assume DS:nothing
        shl     CX,3
        add     CX,BX
        pushf                           ; Save carry
        push    BX                      ; Save seg size excluding Big Buffers
        jc      bi15                    ; Total size exceeds 64K.
        mov     BX,CX
bi15:
        CALL    BufAlloc                ; (DS) = buffer segment
        JC      bie                     ; Allocation failed
        pop     BX
        popf
; Assume no Big Buffers.
        mov     DS:[bs_BigBufMap],0
        mov     DS:[bs_BigBufBase],-1
        jc      bi20                    ; No Big Buffers
        mov     DS:[bs_BigBufBase],BX   ; Offset of 1st Big Buffer.
        mov     DS:[bs_BigBufMap],0Fh   ; All Big buffers unused.
bi20:
        RestoreReg <CX,BX>
        CALL    BufLinkUp               ; Link up buffers
        ret

bie:
        pop     BX
        popf
        pop     CX
        pop     BX
        STC                             ; Indicate error
        MOV     AX,ERROR_NOT_ENOUGH_MEMORY
        ret

EndProc     BufInit


BREAK <BufAlloc - Allocate disk buffer segment>
;***    BufAlloc - Allocate disk buffer segment
;
;       Allocate buffer segment and map GDT_Buffers selector to it.
;
;       ENTRY:  (AX:BX) = size of segment to allocate
;
;       EXIT:   (DS:SI) = buffer segment
;               GDT_Buffers selector points to buffer segment
;
;       USES:   All but BP

Procedure   BufAlloc,near,local

CpuMode 386

    LocalVar bfa_ac,<size vmac_s>       ; Return info from SelAllocKHB

;       reserve system memory and selector for filesystem buffers
;   _SELAllocKHB(hkh, cb, fl, flsel, hobowner, pac)

        EnterProc

        xor     si,si                   ; offset of buffer segment = 0
        SaveReg <ecx,es,esi>
        push    ax
        push    bx
        pop     esi                     ; esi = size of segment
        mov     ax,GSEL GDT_Buffers
        mov     [bfa_ac].ac_sel,ax      ;

        FlatContext <ds, es>, cx
        SSToDS  ecx, bfa_ac             ; Flat address of wgl_ac
        CCALL32 _SELAllocKHB <VM_HKH_PUB_RESRW, esi, PG_CONTIG OR PG_16M , \
                    SELAL_ALLOCATED , FSBUFOWNER, ecx>
        cmp     eax, NO_ERROR
        jnz     short baEx              ; Internal Error if failed

        mov     ds,[bfa_ac].ac_sel
        mov     ax,[bfa_ac].ac_hob      ;
        MOV     DS:[bs_Handle],AX       ; Save handle to buffer segment

    ASSUME  DS:NOTHING
bax:    RestoreReg      <esi,es,ecx>
        LeaveProc
        ret

baEx:   stc
        jmp     short bax

CpuMode reset
EndProc     BufAlloc

BREAK <BufSize - Compute size of buffer segment>
;***    BufSize - Compute size of buffer segment
;
;       Compute size of buffer segment bytes to hold *CX* buffers.
;
;       ENTRY:  (CX) = number of buffers in segment
;
;       EXIT:   (AX:BX) = required size of buffer segment
;
;       USES:   AX,BX,DX,DS

Procedure   BufSize,near,local

        DosContext <DS>
        MOV     AX,MaxSec               ; (AX) = maximum sector size
        ADD     AX,BUFINSIZ             ; (AX) = size of buffer + header
        MUL     CX                      ; (DX:AX) = size of buffers
        ADD     AX,SIZE BUFSEG          ; Add size of fixed segment header
        ADC     DX,0                    ; (DX:AX) = size of buffer segment

ifdef OLD_PROFILE
;*  Add size of statistics block
        MOV     BX,CX                   ; (BX) = number of buffers
        INC     BX                      ; (BX) = 1 + number of buffers
        SHL     BX,1                    ; (BX) = number of bytes in stats block
        ADD     BX,0Fh                  ; Round up size to...
        AND     BX,0FFF0h               ; ...multiple of paragraphs
        ADD     AX,BX                   ; (AX) = buffer segment size
        ADC     DX,0                    ; (DX:AX) = size of buffer segment
endif ;OLD_PROFILE

        XCHG    BX,AX                   ; (AX:BX) = size of buffer segment
        XCHG    AX,DX

        ret

EndProc     BufSize

BREAK <BufLinkUp - Link up buffer segment into chain of buffers>
;***    BufLinkUp - Link up buffer segment into chain of buffers
;
;       Create buffer segment header, buffer headers, link buffers on MRU
;       list.  Also link all buffers on free list, since none of them contain
;       valid data.
;
;       we reserve the first NBUF_NONREMMEDIA buffers for use exclusively for
;       non rem media. This ensures that we will have some buffers that we
;       can use for the swapper, and do not need to invoke the harderror daemon
;       to ask the user to swap the correct floppy in
;
;       ENTRY:  (DS) = buffer segment
;               (CX) = number of buffers
;
;       EXIT:   none
;
;       USES:   AX,CX,DX,DI,SI,ES

        ASSUME  DS:NOTHING,ES:NOTHING

Procedure   BufLinkUp,near,local

        DosContext <ES>
        MOV     DX,MaxSec               ; (DX) = buffer size
        MOV     DS:[bs_MaxSec],DX       ; save maximum bufferable sector size

        ADD     DX,BUFINSIZ             ; (DX) = size of buffer + header


        MOV     DS:[bs_nBuffers],CX     ; Save number of buffers

        push    bx
        push    ds
        xor     si,si                   ; ds:si = Buffer segment virt address
        CALLFAR VirtToPhys              ; ax:bx = Buffer segment phys address
        pop     ds
        mov     ds:[bs_physBufSeg].hiword,ax
        mov     ds:[bs_physBufSeg].loword,bx
        pop     bx

        MOV     SI,SIZE BUFSEG          ; (DS:SI) = first buffer


ifdef OLD_PROFILE
;*  We need a block of n+1 words.  These count the outcomes of GetBuffr:
;
;   block[0] = number of times GetBuffr didn't get hit in buffer cache
;   block[1] = number of times GetBuffr got hit on first buffer
;   block[2] = number of times GetBuffr got hit on second buffer
;   ...

        SaveReg <CX>
        MOV     Ptr_BufCache.Segmt,DS   ; Set pointer for w_DBPROFILE
        MOV     Ptr_BufCache.Offst,SI   ;
        MOV     DS:[bs_pStats],SI       ; Set pointer for GetBuffr
        INC     CX                      ; (CX) = 1 + number of buffers
        SHL     CX,1                    ; (CX) = number of bytes in block
        MOV     Len_BufCache,CX         ; Set length for w_DBPROFILE
        ADD     SI,CX                   ; (SI) = next free word in buf seg
        ADD     SI,0Fh                  ; Round (SI) up to...
        AND     SI,0FFF0h               ; ...next paragraph boundary
        RestoreReg <CX>
endif ; OLD_PROFILE

        MOV     DI,BUF_NIL              ; (DI) = nil ptr
        MOV     DS:[bs_MRUHead],SI      ; Set head of MRU list
        MOV     DS:[bs_buffsize],DX     ; [bs_buffsize] = size of buffer+header
        PUSH    DX
        MOV     AX,DX                   ; (AX) = size of buffer+header
        MUL     DS:[bs_nBuffers]        ; (AX) = size of buffer and headers
        ADD     AX,BUFBASE              ; (AX) = size bufs, hdrs, and segptrs
        DEC     AX                      ; (AX) = buffer segment limit
        MOV     DS:[bs_seglimit],AX     ; [bs_seglimit] = buffer segment limit
        POP     DX                      ; (DX) = size of buffer + header

;*  (SI) = current buffer
;   (DI) = previous buffer

blu1:   MOV     AX,SI                   ; (AX) = current buffer
        ADD     AX,DX                   ; (AX) = next buffer
        MOV     [SI].buf_next,AX        ; Set next pointer
        MOV     [SI].buf_prev,DI        ; Set prev pointer
        MOV     [SI].buf_hVPB,BUF_FREE  ; Mark buffer as free
        MOV     [SI].buf_flags,0        ; Clear flags
        XCHG    SI,AX                   ; (AX) = next buffer
        XCHG    DI,AX                   ; (DI) = current buffer
        LOOP    blu1                    ; Link next buffer

;*  Terminate list
;
        MOV     [DI].buf_next,BUF_NIL   ; Set end of list
        MOV     DS:[bs_MRUTail],DI      ; Set tail of MRU list (LRU head)
;
;*  Set up value that we use to determine if we should give the buffer
;   to removable media or not. This will be used to ensure that we always
;   have a buffer that we can use for the Swapper that is not being used
;   by a removable volume.
;
        MOV     AX,MaxSec               ; (AX) = buffer size
        ADD     AX,BUFINSIZ             ; (AX) = size of buffer + header
        mov     cx,NBUF_NONREMMEDIA
        mul     cx                      ; (AX) = size reserved for non-rem.media
        or      dx,dx
        jnz     bluie                   ; (DX must be zero)
        add     ax,SIZE BUFSEG          ; AX = min. offset of buffer for
                                        ;   rem media
        mov     DS:[bs_offRemMed],AX    ; Save it away for use in GetFreeBuffr
        ret

bluie:  Panic <'BufLinkUp: bs_offRemMed too big'>

EndProc     BufLinkUp

INITCODE ends
        END
