/*static char *SCCSID = "@(#)cachmisc.c 6.11 92/03/02";*/

#define SCCSID "@(#)cachmisc.c  6.11 92/03/02"
/********************************************************/
/*                                                      */
/*       IBM/Microsoft Confidential                     */
/*                                                      */
/*       Copyright (c) IBM Corporation 1987, 1989       */
/*       Copyright (c) Microsoft Corporation 1987, 1989 */
/*                                                      */
/*       All Rights Reserved                            */
/********************************************************/

#define FNO_NUM FNO_CACHMISC_C
#define INCL_TYPES                      // for basedef.h typedefs
#include <osconst.h>
#include <ostypes.h>
#include <fnofs.h>
#include <osmac.h>
#include <basemac.h>
#include <basedef.h>
#include <mi.h>
#include <vmexport.h>
#include <selxport.h>
#include <pgexport.h>
#include <tkexport.h>
#include <smexport.h>
#include <sft.h>
#include <cache.h>
#include <strat2.h>
#include <sched.h>
#include <vmowner.h>
#include <string.h>
#include <seldesc.h>
#include <filesys.h>

#ifdef FSSTRICT
    extern void CheckFATSector(ULONG,PBYTE);
#endif

#define MAXLWREQS 16
int fLWIO;
//                    0    1    2    3
BYTE abMustRead[] = { 0xff,0x00,0x00,0x00,
//                    4    5    6    7
                      0x00,0xff,0x00,0x00,
//                    8    9    10   11
                      0x00,0xff,0xff,0xff,
//                    12   13   14   15
                      0x00,0xff,0x00,0x00
                    };
ushort_t LWAsyncSem = 0;

/*
 ***    msNow - Get current Millisecond since boot
 *
 *      msNow Get current Millisecond since boot
 *
 *      ENTRY   NONE
 *
 *      EXIT
 *
 *      HISTORY
 */


ulong_t msNow ()
{
   ulong_t ctime;

      _disable();
      ctime = SISData.SIS_MsCount;
      _enable();
      return (ctime);
}
/*
 ***    LWWakeUp - wake up lazy write thread
 *
 *      LWWakeUp is called when the cache first gets dirty.  This will allow
 *      the lazy write thread to ascertain the correct sleep time until lazy
 *      flushing needs to occur.
 *
 *      ENTRY   Global LWSem contains 16:16 address of ramsem
 *
 *      EXIT    Yields
 */

void LWWakeUp ()
{
    TKWakeup ((void *)&semLW,0,NULL);
}
/*
 ***    LWForceWakeUp - forcibly wake up lazy write thread
 *
 *      LWWakeUp is called when the cache first gets dirty.  This will allow
 *      the lazy write thread to ascertain the correct sleep time until lazy
 *      flushing needs to occur.
 *
 *      LWForceWakeUp will clear the semaphore and guarantee that the
 *      lazy write thread gets a crack at running
 *
 *      ENTRY   Global LWSem contains 16:16 address of ramsem
 *
 *      EXIT    Yields
 *
 *      BUGBUG - We block and may need to preserve some static vars like CurIOF
 *              and tdb_sbdir.
 */

void LWForceWakeUp()
{
    /*
     * Force the lazy writer to wake up and start flushing things
     *  by indicating that the disk is idle.
     */
    msLastIO = msNow () - LWData.msIdle;

    /* clear the semaphore that the lazy writer blocks on */
    TKWakeup ((void *)&semLW,0,NULL);
    TKYield();
}
/*
 ***     fBufWritable - Determine if buffer is writable
 *
 *       ENTRY   pBuf = address of buffer header
 *
 *       EXIT    TRUE  if buffer is not writable
 *               FALSE if buffer is writable
 *
 *
 */
int  fBufWritable(register PBUFHDR pBuf,ulong_t flags)
{
   BYTE bDirtySecs,bDirtyMsk;
   int i,fMustRead,rc;

   bDirtySecs = 0;
   bDirtyMsk = 0x01;
   fMustRead = FALSE;

   if (pBuf->BufFlag & BUF_LAZYREAD){
      for (i=0;i < SECPERBUF; ++i){
        rc = FindNonChar(&pBuf->BufDirtyBits[i*BYTEBITSPERSEC],
                          0xff,
                          (ushort_t)BYTEBITSPERSEC);
        if (flags & BWRITABLE_GAPS){
           if (!rc){
              bDirtySecs |= bDirtyMsk;
              if (abMustRead[bDirtySecs] == 0xff){
                 fMustRead = TRUE;
                 break;
              }
           }
           bDirtyMsk <<= 1;
        }
        if (flags & BWRITABLE_PARTIALSEC) {
           if (rc){
              if (rc = FindNonChar(&pBuf->BufDirtyBits[i*BYTEBITSPERSEC],
                               0,
                               (ushort_t)BYTEBITSPERSEC)){
                 fMustRead = TRUE;
                 break;
              }
           }
        }
      }
   }
   return(fMustRead);
}

/*
 ***     LazyRead - Read data into buffers
 *
 *       ENTRY  pBuf       - address of buffer header
 *              flags      - LazyWrite vs Non-LazyWrite
 *              pfMustRead - Pointer to fMustread
 *
 *       EXIT   rc = NO_ERROR or ERROR_CODE otherwise
 *
 *
 */
int LazyRead(register PBUFHDR pbuf,ulong_t flags,int *pfMustRead)
{
   ulong_t count,BufOff,ReqListSel,ReqListOff;
   ulong_t tmprba;
   int fMustRead = FALSE,rc = 0;
   PBUFHDR pbuftmp;
   PBYTE PhysBuf,LinBuf;

    if ((flags & LREAD_LWRITE) && (pbuf->BufFlag & BUF_LOCK)) {
       *pfMustRead = TRUE;
       return (0);
    }
    *pfMustRead = FALSE;
    BufLock(pbuf);
    if (pbuf->BufFlag & BUF_LAZYREAD){
       if (flags & LREAD_LWRITE){
          fMustRead = fBufWritable(pbuf,BWRITABLE_GAPS | BWRITABLE_PARTIALSEC);
       }
       else
          fMustRead = TRUE;

       if (fMustRead){

          *pfMustRead = TRUE;

          if (flags & LREAD_LWRITE){
             if ((rc = fReqListFree()) == 0){
                BufUnLock(pbuf);
                return (0);
             }
          }
          MMPHPreLazyRead(); /* performance hook (3208) */

          count = 1;
          SetRBA_Count(SSToDS(&tmprba),
                       pbuf->BufRBA,
                       SSToDS(&count),
                       pbuf->BufhVPB);

          PhysBuf = pPhysLRBuf;

          // BUGBUG 04/09/91 MACON Need to allocate memory from the heap
          // instead of using a cache buffer

          if (!(flags & LREAD_LWRITE)){
             pbuftmp = GFB(GFB_BLOCK);
             PhysBuf = pbuftmp->BufPhysAddr;
             LinBuf = pbuftmp->BufLinAddr;
          }
          BufOff = BUFOFFSET(tmprba);

          ReqListAlloc(SSToDS(&ReqListSel),
                       SSToDS(&ReqListOff));
          ReqListInit(ReqListSel,
                      ReqListOff,
                      pbuf->BufhVPB,
                      RLCB_KEEP_REQ);
          rc = ReqListAdd_REQ(tmprba,
                              count,
                              PB_READ_X,
                              ReqListSel,
                              ReqListOff,
                                                                //#83284
//                              (flags & LREAD_LWRITE) ? PRIO_PREFETCH:
//                                                       PRIO_FOREGROUND_USER,
                               PRIO_BACKGROUND_USER,
                              pbuf->BufhVPB);
          BufAssert (!rc);
          ReqListAdd_SG(count * SECSIZE,
                        ReqListSel,
                        ReqListOff,
                        (PBYTE)PhysBuf + BufOff);

          if (flags & LREAD_LWRITE){
             ReqListLnkPtr(pbuf,
                           &pbuf->BufReadLink,
                           ReqListSel,
                           ReqListOff);

             QueueAsyncRead(ReqListSel,ReqListOff);
          }
          else {
             if (rc = ReqListExecute(ReqListSel,ReqListOff) != 0){
                BufUnLock(pbuf);
                if (pbuf->BufDirtyLru.pbufFwd != NULL) {
                   DBCREM (pbuf);
                }
                BufUnlink(pbuf);
                BufFree(pbuf);
                MMPHPostLazyRead(); /* performance hook (3288) */
                return (rc);
             }
             else {
                LazyReadIODone(pbuf,LinBuf);
             }
             BufFree(pbuftmp);
             FreeReqList(ReqListSel,ReqListOff);
          }
       }

    }
    if (!fMustRead){
       BufUnLock(pbuf);
    }
    return (0);
}

/***     LazyReadIODone - Process Lazy Read data
 *
 *       ENTRY   pBuf   = address of buffer header
 *               LinBuf = source address of lazy read data
 *
 *       EXIT    NONE
 *
 *       NOTE:   This code should be performance tuned (ie. Yields and a faster
 *               loop.
 *
 */
void LazyReadIODone(PBUFHDR pBuf,register PBYTE LinBuf)
{
   register int i;
   int rc;
   register PBYTE pDestAddr;

    BufAssert (pBuf->BufFlag & BUF_LAZYREAD);

    MMPHPostLazyRead(); /* performance hook (3288) */

    pDestAddr = pBuf->BufLinAddr;
    for (i=0;i < SECPERBUF; ++i){

       rc = FindNonChar(&pBuf->BufDirtyBits[i*BYTEBITSPERSEC],
                        0xff,
                        (ushort_t)BYTEBITSPERSEC);
       if (rc) {
          rc = FindNonChar(&pBuf->BufDirtyBits[i*BYTEBITSPERSEC],
                           0,
                           (ushort_t)BYTEBITSPERSEC);
          if (!rc) {
             memcpy ((void *)(pDestAddr),(void *)(LinBuf),SECSIZE);
          }
          else {
             BufFillFromBitMap(&(pBuf->BufDirtyBits[i * BYTEBITSPERSEC]),
                               LinBuf,
                               pDestAddr);
          }
       }
       pDestAddr += SECSIZE;
       LinBuf += SECSIZE;
    }
    memset((void *)&pBuf->BufDirtyBits[0],
           0,
           BYTEBITSPERSEC * SECPERBUF);
    pBuf->BufFlag &= ~BUF_LAZYREAD;
    BufUnLock(pBuf);
}

/***     LWAsync - Lazy Write Buffers to Disk.
 *
 *       ENTRY   pBuf        = address of buffer header to start with
 *               fAddImmune  = Immune flag
 *               ReqListSel  = Selector of request list
 *               ReqListOff  = Offset of request list
 *
 *       EXIT    NONE
 *
 *       NOTE:   This code should be performance tuned (ie. Yields and a faster
 *               loop.
 *
 *       HISTORY
 *        @DB77030 - Debug check for FAT sector should not check
 *                   sectors in the system area of the disk.
 */
void LWAsync(register PBUFHDR pBuf,
         BOOL fAddImmune,               /* Used by SkipBecauseImmune(). */
         ulong_t ReqListSel,
         ulong_t ReqListOff)
{

#define IsImmune(pbufAny)                                               \
          ((msSinceBoot - (pbufAny)->BufmsLastDirtied) <= LWData.msIdleBuf)

#define SkipBecauseImmune(pbufAny)                                      \
       ( (fAddImmune)                                                  \
         ? FALSE                                                       \
         : IsImmune(pbufAny) )

    BOOL fChainEmpty = TRUE;
    BOOL fNeedIONow = FALSE;
    ulong_t rba,PrevRBA = NULL;
    ulong_t hVPB,cbReqList = sizeof(Req_List_Header) + sizeof(struct RLCB_Header);
    BYTE bDirt;                         /*  location to save old dirty bits     */
    ulong_t cSec,LWreqs = 0,PrevSecCnt;
    int rc,fMustRead;
    ULONG msSinceBoot = msNow();        /* Used by SkipBecauseImmune().
                                         * This must be updated each time we
                                         * block.
                                         */
    tcb_t *ptcb = pTCBCur;
#ifdef FSSTRICT
    if (pBuf->BufFlag & BUF_LAZYREAD){
       BufAssert (!(pBuf->BufFlag & BUF_LOCK));
       fMustRead = fBufWritable(pBuf,BWRITABLE_GAPS | BWRITABLE_PARTIALSEC);
       BufAssert (!fMustRead);
    }
#endif

    MMPHPreLWWrite(); /* performance hook (320C) */

    do {        /* until fNeedIONow, loop for each sector. */

        /*
         * pbuf should point to a dirty, writable cache block.
         */
        PBUFLINK_CK(pBuf);      /* debug: is it really on dirty chain? */

        /*
         * Scan back, trying to make a contiguous block.  Ignore blocks which
         * have already been marked as locked by the lazy writer (because we've
         * already put them in this command chain).
         */
        {
            register PBUFHDR pbufCandidate;
            PBUFHDR pbufReturn = pBuf;
            rba = pBuf->BufRBA;
            hVPB = pBuf->BufhVPB;

            while (((pbufCandidate = HashFind(rba,hVPB)) != NULL) &&
                   (pbufCandidate->BufDirtyLru.pbufFwd != NULL) &&
                   ( ! SkipBecauseImmune(pbufCandidate) ) &&
                   ( ! (pbufCandidate->BufFlag & BUF_LWLOCK) ) &&
                   ( ! (pbufCandidate->BufFlag & BUF_LOCK) ) &&
                   (pbufCandidate->BufDirtyMsk != 0) &&
                   (pbufCandidate->BufhVPB == hVPB) &&
                   (!(fBufWritable(pbufCandidate,
                                   BWRITABLE_GAPS | BWRITABLE_PARTIALSEC)))){
                pbufReturn = pbufCandidate;
                rba -= SECPERBUF;
            }
            pBuf = pbufReturn;
            PBUFLINK_CK(pBuf);  /* debug: is it really on dirty chain? */
        }

        /*
         * Arrange for this block to be written, if possible.  (The command
         * chain may be full.)
         */
        {
            BufAssert (!(pBuf->BufFlag & BUF_LWLOCK));
            pBuf->BufFlag |= BUF_LWLOCK;  /* Mark so we won't rewrite it. */
            ++cbufLWLock;
            DBCREM (pBuf);

            /*
             * Queue up a request to write this block.
             */

            bDirt = pBuf->BufDirtyMsk;
            pBuf->BufDirtyMsk = 0;
            cSec = aBufa[bDirt].cSecToWrite;

#ifdef FSSTRICT
      {

      ULONG i,TmpRba,BufOff;

      i = cSec;
      TmpRba = pBuf->BufRBA + aBufa[bDirt].offus;
      while (i){
         rc = IsRBAForFAT(TmpRba,pBuf->BufhVPB)
              && !IsRBAInSysArea(TmpRba,pBuf->BufhVPB);           /* @DB77030 */
         if (rc){
            BufOff = BUFOFFSET(TmpRba);
            CheckFATSector(pBuf->BufhVPB,pBuf->BufLinAddr + BufOff);
         }
         ++TmpRba;
         --i;
      }
      }
#endif
            BufAssert (pBuf->BufFlag & BUF_LWLOCK);
            BufAssert (pBuf->BufDirtyLru.pbufFwd == NULL);

            rc = ReqListAdd_REQ(pBuf->BufRBA + (ULONG)(aBufa[bDirt].offus),
                                cSec,
                                (ushort_t)PB_WRITE_X,
                                ReqListSel,
                                ReqListOff,
                                (ushort_t)PRIO_LAZY_WRITE,
                                pBuf->BufhVPB);
            BufAssert (!rc);
            if (PrevRBA != NULL && (PrevRBA + PrevSecCnt != pBuf->BufRBA)){
               cbReqList += sizeof(PB_Read_Write);
            }
            PrevRBA = pBuf->BufRBA;
            PrevSecCnt = cSec;
            ReqListAdd_SG(cSec * SECSIZE,
                          ReqListSel,
                          ReqListOff,
                          pBuf->BufPhysAddr + (aBufa[bDirt].offus * SECSIZE));
            cbReqList += sizeof(SG_Descriptor);
            ReqListLnkPtr(pBuf,
                          &pBuf->BufWriteLink,
                          ReqListSel,
                          ReqListOff);
            msSinceBoot = msNow();
            fChainEmpty = FALSE;
            ++LWreqs;

            if (LWreqs >= MAXLWREQS){

               /* List full, arrange to do I/O we've already set up. */
               fNeedIONow = TRUE;
               break;      /* Stop trying to find more data to write. */
            }
        }
        /* Look for more work to do.  First, look for a contiguous block. */
        {
            BOOL fNeedNonContig;
            PBUFHDR pbufNext;
            ulong_t rbaNext = pBuf->BufRBA + SECPERBUF;
            ulong_t hVPBNext = pBuf->BufhVPB;

            if ( (pbufNext = HashFind(rbaNext,hVPBNext)) != NULL) {
                pBuf = pbufNext;
                /*
                 * Use pbuf we just found, unless it meets any of the following
                 * criteria:
                 *      (a) it's immune and we're skipping immune buffers for
                 *          the time being,
                 *      (b) it's already marked as being written,
                 *      (c) it's clean,
                 *      (d) it isn't on the dirty chain, or
                 *      (e) it isn't on the same volume.
                 */
                fNeedNonContig = SkipBecauseImmune(pBuf) ||
                                 (pBuf->BufFlag & BUF_LWLOCK) ||
                                 (pBuf->BufFlag & BUF_LOCK) ||
                                 (pBuf->BufDirtyMsk == 0) ||
                                 (pBuf->BufDirtyLru.pbufFwd == NULL) ||
                                 (pBuf->BufhVPB != hVPB) ||
                                 (fBufWritable(pBuf,
                                               BWRITABLE_GAPS | BWRITABLE_PARTIALSEC));
            } else
                fNeedNonContig = TRUE;          /* Couldn't find contig. */
            if (fNeedNonContig) {
                /*
                 * No more contiguous disk blocks - look for noncontig.  Scan
                 * the (modified) dirty buffer chain (from bufLWHead) and find
                 * another dirty buffer for this volume.
                 */
                BOOL fNothingLeft = FALSE;
                pBuf = BufDirtyHead.BufDirtyLru.pbufFwd;
                while (pBuf != &BufDirtyHead) {
                    if ((pBuf->BufhVPB == hVPB) &&
                         ( ! (pBuf->BufFlag & BUF_LWLOCK)) &&
                         ( ! (pBuf->BufFlag & BUF_LOCK)) &&
                         ( ! (pBuf->BufDirtyMsk == 0) ) &&
                           ( ! SkipBecauseImmune(pBuf)) &&
                           (!(fBufWritable(pBuf,
                                           BWRITABLE_GAPS | BWRITABLE_PARTIALSEC)))){

                        /* debug: is it really on dirty chain? */
                        PBUFLINK_CK(pBuf);

                        break;  /* Same vol, not being lazy written; use it! */
                    }
                    if (SkipBecauseImmune(pBuf)){
                        fNothingLeft = TRUE;
                        break;
                    }
                    pBuf = pBuf->BufDirtyLru.pbufFwd;
                }

                if ( fNothingLeft || (pBuf == &BufDirtyHead) ) {
                    /* No more dirty bufs on this vol. */
                    if (fChainEmpty)
                        break;          /* done vol, no I/O to do, so exit. */
                    else
                        fNeedIONow = TRUE;  /* chain not empty, so do this! */
                } /* if (done volume) */
            } /* if fNeedNonContig (scope of fNothingLeft) */
        } /* scope of fNeedNonContig, pbufNext, vlsnNextBlock */

    } while (!fNeedIONow);

    /*
     * Do the I/O, block until it's done, and handle errors.
     */
    if (!fChainEmpty) {
       rc = ReqListExecute(ReqListSel,ReqListOff);
    }
    else
       FreeReqList(ReqListSel,ReqListOff);

    MMPHPostLWWrite(); /* performance hook (328C) */

    /* Return, to give LazyWriter a chance to look at other volumes again. */
}

/***    LazyWriter - block and flush as required
 *
 *      LazyWriter is used by the lazy write thread to wait until the cache
 *      contains old data or until there is dirty data and the disk
 *      appears to be idle.  After each block, we go back to the top to
 *      see if we can return.  We do this as blocking may change previously
 *      ascertained state.
 *
 *      Once we ascertain that there is data to flush, we write it out and then
 *      rescan the cache for either more writes or sleep time.
 *
 *      Entry   none
 *
 *      Exit    Returns error code, never returns if no error
 *              Yields
 *
 *      HISTORY
 */

void LazyWriter ()
{
    BOOL fAddImmune;
    register PBUFHDR pBuf;
    ULONG msTmp;
    ULONG msSinceBoot;  /* current time */
    register ULONG msBlock;      /* amount of time to block */
    int rc,fMustRead;
    ulong_t ReqListSel = NULL,ReqListOff = NULL;
    VMAC    ac;

    /* Set the LazyWriter to Time-Critical priority forever. */

    SCHSetPriority(pTCBCur, (CLASS_TIME_CRITICAL << 8) + LEVEL_MIN);
    if ((rc = VMAllocKHB(VM_HKH_PUB_RESRW,
              (ushort_t)SECPERBUF * (ushort_t)SECSIZE,FSBUFOWNER,PG_CONTIG,
              (void *)&pLinLRBuf)) != 0)
       panic ("LazyWriter: Cannot allocate 2k contiguous memory");

    if (rc = VMLinToPhys((ulong_t)pLinLRBuf,(ulong_t *)(&pPhysLRBuf)))
       panic ("LazyWriter: Cannot convert memory to physical address");

    ReqListAlloc(SSToDS(&ReqListSel),
                 SSToDS(&ReqListOff));

    while (TRUE) { /* For each time we can write stuff... */
        while (TRUE) { /* Scan for something to write, or wait for it. */

            /* get the current time */

            msSinceBoot = msNow ();

            /* start at the top of the lw list */
            pBuf = BufDirtyHead.BufDirtyLru.pbufFwd;
            while (pBuf != &BufDirtyHead){
                  rc = LazyRead(pBuf,
                                LREAD_LWRITE,
                                SSToDS(&fMustRead));
                  if (fMustRead ||
                      (pBuf->BufFlag & BUF_LWLOCK)){
                     pBuf = pBuf->BufDirtyLru.pbufFwd;
                  }
                  else
                     break;
            }

            /* if there are no bufs on the lazy write queue go back to sleep */
            if (pBuf == &BufDirtyHead) {
                if (BufDirtyHead.BufDirtyLru.pbufFwd == &BufDirtyHead)
                   msBlock = -1l;
                else
                   msBlock = 100l;
            }

            /* if there are more bufs dirty than we allow then write it */
            else if (cbufDirty > LWData.cbufDirtyMax) {
                fAddImmune = TRUE;
                break;
            }

            /* if age of buf is greater than we allow then write it */
            else if ((msBlock = msSinceBoot - pBuf->BufmsFirstDirtied) >=
                                                        LWData.msDataAge) {
                fAddImmune = TRUE;
                break;  /* goto lwb9 */
            }
            else {

                /* block time is ms til buff times out */
                msBlock = LWData.msDataAge - msBlock;

                /*
                 * if disk is not idle..., msTmp gets the time that the
                 * disk has been idle.
                 */
                if ((msTmp = msSinceBoot - msLastIO) < LWData.msIdle) {
                    msTmp = LWData.msIdle - msTmp; /* ms until idle time out */

                    /*
                     * block until either the disk is idle or the
                     * buffer times out
                     */
                    msBlock = MIN (msBlock, msTmp);
                } else {
                    /*
                     * Disk is idle.  Walk the cache to find minimum time until
                     * not-recently touched.
                     */
                    while (pBuf != &BufDirtyHead){
                        rc = LazyRead(pBuf,
                                      LREAD_LWRITE,
                                      SSToDS(&fMustRead));
                        if (!fMustRead &&
                            (!(pBuf->BufFlag & BUF_LWLOCK)) &&

                        /* if buf not recently touched, go write it */

                           (msTmp = msSinceBoot - pBuf->BufmsLastDirtied) >=
                                                        LWData.msIdleBuf) {
                            fAddImmune = FALSE;
                            goto lwb9;
                        } else {
                            /*
                             * msTmp gets time left for buffer to remain
                             * untouched before it will time out.
                             */
                            msTmp = LWData.msIdleBuf - msTmp;
                            msBlock = MIN (msBlock, msTmp);
                            pBuf = pBuf->BufDirtyLru.pbufFwd;
                        }
                    } /* while... */
                } /* else (disk idle) */
            } /* else */

              FreeReqList(ReqListSel,ReqListOff);
              TKSleep ((void *)&(semLW),msBlock,0,0);

              ReqListAlloc(SSToDS(&ReqListSel),
                           SSToDS(&ReqListOff));

        } /* Loop scanning for stuff to write, or wait for it. */

lwb9:
        fLWIO = TRUE;

        /* Write pbuf and other buffers for this volume. */

        ReqListInit(ReqListSel,
                    ReqListOff,
                    pBuf->BufhVPB,
                    RLCB_HOTFIX);
        MMPHPreLazyWriter(); /* performance hook (3207) */
        LWAsync(pBuf,                                      /* may block */
                fAddImmune,
                ReqListSel,
                ReqListOff);

        MMPHPostLazyWriter(); /* performance hook (3287) */
        ReqListAlloc(SSToDS(&ReqListSel),
                     SSToDS(&ReqListOff));

        fLWIO = FALSE;
    } /* Loop for each time we can write... */


} /* LazyWriter() */

/*
 ***     AddLW - Add to LazyWrite List
 *
 *       ENTRY   pBuf = address of buffer header
 *
 *       EXIT    added to lazywrite list
 *
 *
 */
void AddLW (pBuf)
register PBUFHDR   pBuf;  /* address of buffer header */
{
   MMPHPreAddLW(); /* performance hook (3205) */
   /* set most recent dirty time */
   pBuf->BufmsLastDirtied = msNow ();

   /* if buffer not already in dirty list, add it */
   if (pBuf->BufDirtyLru.pbufFwd == NULL) {

       /* Set first dirty time */
       pBuf->BufmsFirstDirtied = pBuf->BufmsLastDirtied;

       /* (add buffer to LazyWrite chain)                     */

       BufAssert (pBuf->BufFileDirtyMsk != 0);
       BufAssert (pBuf->BufDirtyMsk != 0);
       DCADDB (pBuf, BufDirtyLru, BufDirtyHead);

       /* increment the buffer count (for the dirty buffer chain) */
       cbufDirty++;

       /* If no other buffers in the lazy write chain, wake LazyWriter */
       if (BufDirtyHead.BufDirtyLru.pbufFwd == pBuf){
           LWWakeUp ();
       }
       else
       /* if too many buffers in chain, wake LazyWriter */
       if (cbufDirty > LWData.cbufDirtyMax){
           LWWakeUp ();
       }
   }
   MMPHPostAddLW(); /* performance hook (3285) */
}

/*
 ***    BufFree - Free Buffer
 *
 *      BufFree - Free Buffer
 *
 *      ENTRY   pBuf = buffer header address
 *
 *      EXIT    NONE
 *
 */


void BufFree (register PBUFHDR pBuf)
{
   register PBUFHDR pBuftmp;
      pBuftmp = HashFind(pBuf->BufRBA,pBuf->BufhVPB);
      if (pBuftmp == pBuf)
         panic ("BufFree: Found free buffer on hash chain");
      pBuf->BufFlag = 0;
      DCADDF (pBuf, BufFreeLru, BufFreeList);
      pBuf->BufLru.pbufFwd = pBuf;
      pBuf->BufLru.pbufBak = pBuf;
      ++FreeBufs;
      if (GFB_Sem) {
         TKWakeup ((void *)&GFB_Sem,0,NULL);
         GFB_Sem = FALSE;
      }
}

/*
 ***    BufLock - Lock Buffer
 *
 *      BufLock - Lock Buffer
 *
 *      ENTRY   pBuf = buffer header address
 *
 *      EXIT    NONE
 *
 */
void BufLock (PBUFHDR pBuf)
{
   _disable();
   while (pBuf->BufFlag & BUF_LOCK){
      pBuf->BufFlag |= BUF_WAITING;
      TKSleep ((void *)pBuf,-1,0,0);
      _disable();
   }
   pBuf->BufFlag |= BUF_LOCK;
   pBuf->BufOwner = pTCBCur->TCBNumber;
   _enable();
}
/*
 ***    BufWaitUnLock - Wait Until Buffer UnLocked
 *
 *      BufWaitUnLock - Wait Until Buffer UnLocked
 *
 *      ENTRY   pBuf = buffer header address
 *
 *      EXIT    NONE
 *
 */

void BufWaitUnLock (PBUFHDR pBuf)
{
   pBuf->BufFlag |= BUF_WAITING;
   TKSleep ((void *)pBuf,-1,0,0);
}

/*
 ***    BufUnLock - UnLock Buffer
 *
 *      BufUnLock - UnLock Buffer
 *
 *      ENTRY   pBuf = buffer header address
 *
 *      EXIT    NONE
 *
 */


void BufUnLock (PBUFHDR pBuf)
{
   BufAssert (pBuf->BufFlag & BUF_LOCK);
   pBuf->BufFlag &= ~BUF_LOCK;
   if (pBuf->BufFlag & BUF_WAITING){
      pBuf->BufFlag &= ~BUF_WAITING;
      TKWakeup ((void *)pBuf,0,NULL);
   }
   pBuf->BufOwner = 0;
}

/***LP  BufIODone - Release Cache Pages
 *
 *
 *      ENTRY  ReqListPage    - First Buffer header on chain
 *
 *      EXIT:  NONE
 *
 *
 */
void BufIODone(register PBUFHDR ReqListPage)
{
   int fLWrite;

      if (ReqListPage == (PBUFHDR)-1l)
         return;

      if (!ReqListPage){
         msLastIO = msNow();
         return;
      }
      while (ReqListPage){
         /* Unlock the buffer for this request. */

         fLWrite = FALSE;
         if (ReqListPage->BufFlag & BUF_LWLOCK) {
            fLWrite = TRUE;
            ReqListPage->BufFlag &= ~BUF_LWLOCK;   /* remove the lw lock */
            --cbufLWLock;
            if (LWBufWanted) {
               LWBufWanted = FALSE;
               TKWakeup ((void *)&LWBufWanted,0,NULL);
            }
            if (ReqListPage->BufFlag & BUF_LWWANTED){
               ReqListPage->BufFlag &= ~BUF_LWWANTED;
               TKWakeup ((void *)&ReqListPage->BufFlag,0,NULL);
            }
         }
         if (fLWrite)
            ReqListPage = ReqListPage->BufWriteLink;
         else
            ReqListPage = ReqListPage->BufReadLink;
      }
}

/*
 ***    FLW - Flush Lazy Writes
 *
 *      FLW is called to force lazy write operations to the disk.
 *      If the lazy write daemon is present, we pretend that we've gone
 *      idle and yield to him to let him start cleaning things up.
 *
 *      We also walk the cache flushing things too.
 *
 *      Otherwise, we manually do it ourselves.
 *
 *      ENTRY   none
 *
 *      EXIT    Yields CPU
 *
 *      PSEUDOCODE
 *              if (Daemon present)
 *                  Wake up/Yield to daemon
 *              for (all buffers in cache)
 *                  if (buffer not locked and buffer on diry chain) {
 *                      remove from dirty chain
 *                      decrement dirty chain count
 *                      if (buffer is dirty)
 *                          BufWrit (buffer)
 *                      }
 *
 */

void FLW ()
{
    register PBUFHDR pBuf;
    int rc;

    MMPHPreFLW(); /* performance hook (320B) */

    /*  The Lazy Write Daemon is present; force it to
     *  wake up and help us out.
     */
    LWForceWakeUp();

    while (KSEMRequestMutex ((HKSEM)&(FLWKsem),-1));

    pBuf = BufLRUHead.BufLru.pbufBak;
    while (pBuf != &BufLRUHead) {

        /*      If the buffer is not locked and not held, and if
         *      it's dirty chain forward pointer is not null,
         *      take it off the dirty chain, decrement the count
         *      of dirty buffers, and write the buffer out if it's
         *      dirty.
         */
        if ((pBuf->BufRefCnt == 0) &&
            (pBuf->BufDirtyLru.pbufFwd != NULL) &&
            (!(pBuf->BufFlag & BUF_LOCK)) &&
            (!(pBuf->BufFlag & BUF_LWLOCK))) {

            if (!(fBufWritable(pBuf,BWRITABLE_GAPS | BWRITABLE_PARTIALSEC))){
               BufWrt(pBuf,FLWReqListSel);
               MMPHPostFLW(); /* performance hook (328B) */
               KSEMReleaseMutex ((HKSEM)&(FLWKsem));
               return;             /*  Just write one */
            }
        }
        pBuf = pBuf->BufLru.pbufBak;
    }
    MMPHPostFLW(); /* performance hook (328B) */
    KSEMReleaseMutex ((HKSEM)&(FLWKsem));
}
/***EP  BufDiscard - Discard Buffers
 *
 *
 *      ENTRY  ULONG  hVPB
 *      EXIT:  NONE
 *
 *      WARNING:
 *
 */
void BufDiscard(register ulong_t hvpb)
{
register PBUFHDR pBuf;
    pBuf = BufLRUHead.BufLru.pbufBak;
    while (pBuf != &BufLRUHead) {

        /*      If the buffer is not locked and not held, and if
         *      it's dirty chain forward pointer is not null,
         *      take it off the dirty chain, decrement the count
         *      of dirty buffers, and write the buffer out if it's
         *      dirty.
         */
        if (hvpb == NULL || hvpb==pBuf->BufhVPB){
           if ((pBuf->BufDirtyLru.pbufFwd == NULL) &&
               (!(pBuf->BufFlag & BUF_LWLOCK)) &&
               (pBuf->BufRefCnt == 0) &&
               (!(pBuf->BufFlag & BUF_LOCK))) {
              BufUnlink(pBuf);
              BufFree(pBuf);
              pBuf = BufLRUHead.BufLru.pbufBak;
           }
           else
              pBuf = pBuf->BufLru.pbufBak;
        }
        else
           pBuf = pBuf->BufLru.pbufBak;
    }
}


