/*static char *SCCSID = "@(#)gfb.c      6.6 91/11/23";*/

#define SCCSID "@(#)gfb.c       6.6 91/11/23"
/********************************************************/
/*                                                      */
/*       IBM/Microsoft Confidential                     */
/*                                                      */
/*       Copyright (c) IBM Corporation 1987, 1989       */
/*       Copyright (c) Microsoft Corporation 1987, 1989 */
/*                                                      */
/*       All Rights Reserved                            */
/********************************************************/

#define FNO_NUM FNO_GFB_C
#define INCL_TYPES                      // for basedef.h typedefs
#include <osconst.h>
#include <ostypes.h>
#include <basemac.h>
#include <basedef.h>
#include <fnofs.h>
#include <osmac.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>

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


/***    BufWrt - Write a Buffer
 *
 *      ENTRY   pBuf
 *
 *      EXIT
 *
 *
 *      HISTORY
 *        @DB77030 - Debug check for FAT sector should not check
 *                   sectors in the system area of the disk.
 *
 */

int BufWrt (register PBUFHDR pBuf,ulong_t ReqListSel)
{
   int rc,fMustRead;
   BYTE bOldDirt;                   /*  location to save old dirty bits     */
   register ulong_t cSecWritten;

      if (pBuf->BufDirtyLru.pbufFwd != NULL) {
         DBCREM (pBuf);
      }
      BufAssert (pBuf->BufDirtyMsk != 0);
      BufAssert (pBuf->BufDirtyLru.pbufFwd == NULL);
      BufAssert (!(pBuf->BufFlag & BUF_LWLOCK));
      BufAssert (!(pBuf->BufFlag & BUF_LOCK));
#ifdef FSSTRICT
      fMustRead = fBufWritable(pBuf,
                               BWRITABLE_GAPS | BWRITABLE_PARTIALSEC);
      BufAssert (!fMustRead);
#endif
      /*  Increment the hold count, to prevent the buffer from
       *  being ripped out from under us.
       *
       */
      BufIncRef(pBuf);

      /*  While we're off writing these sectors, somebody else may
       *  dirty the buffer.  Therefore, we need to clear the dirty
       *  bits before the write, so we'll notice any new dirt.
       *
       */

      bOldDirt = pBuf->BufDirtyMsk;
      /* clean out dirty bits */

      pBuf->BufDirtyMsk = 0;
      pBuf->BufFileDirtyMsk = 0;

      cSecWritten = aBufa[bOldDirt].cSecToWrite;
#ifdef FSSTRICT
      {

      ulong_t i,TmpRba,BufOff;

      i = cSecWritten;
      TmpRba = pBuf->BufRBA + aBufa[bOldDirt].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
      rc = DWrite(pBuf->BufRBA + aBufa[bOldDirt].offus,
                  cSecWritten,pBuf->BufPhysAddr +
                  (aBufa[bOldDirt].offus * SECSIZE),
                  pBuf->BufhVPB,
                  ReqListSel);

      /*  Decrement the hold count;
       *
       */

      BufDecRef(pBuf);
      return(rc);
}

/*
 ***    GFBWait - Wait for GFB to finish locking
 *
 *      GFB sets an interlock on cache failures, this routine is
 *      called to wait for the interlock to clear.  See GFB
 *
 *      ENTRY   none
 *      EXIT    fGFBActive is set to 0
 *
 */

void GFBWait ()
{
    /*  Blocks until GFB is not active.  */
    while (fGFBActive) {
        _disable();
        ++pGFBWaitChain;
        TKSleep ((void *)(&pGFBWaitChain),-1,0,0);
        _enable();
    }
}
/***    GFB - Get Free Buffer
 *
 *      ENTRY   None
 *
 *      EXIT    Returns address of buffer header (buffer is not
 *                      on any LRU chain or hash chain)
 *
 *      HISTORY
 *
 */


PBUFHDR GFB (ulong_t gfb_flags)
{
        register PBUFHDR pbufResult;    /* Result.  Returned at end of function  */
        BOOL fFound = FALSE;
        BOOL fFlushedOnce = FALSE;

        pbufResult = BufLRUHead.BufLru.pbufBak;
        while (!fFound) {

           if (BufFreeList.BufFreeLru.pbufBak != &BufFreeList) {
              pbufResult = BufFreeList.BufFreeLru.pbufBak;
              DCREM (pbufResult, BufFreeLru);
              --FreeBufs;
              return (pbufResult);
           }

           if (BufLRUHead.BufLru.pbufBak == &BufLRUHead){

             if (gfb_flags & GFB_NOBLOCK)
                return(NULL);

             /*
              * All buffers are being used!  Wait for one
              */
              GFB_Sem = TRUE;
              /* Out of free buffers, wait for one */
              fGFBActive += 1;
#ifdef FSSTRICT
              GFBActiveThread = pTCBCur->TCBNumber;
#endif
              TKSleep ((void *)&(GFB_Sem),-1,0,0);
              fGFBActive -= 1;
              /*  If necessary, Wakeup on pGFBWaitChain */
              if (pGFBWaitChain != 0) {
                  TKWakeup (&(pGFBWaitChain),0,NULL);
                  pGFBWaitChain = 0;
              }
              pbufResult = BufLRUHead.BufLru.pbufBak;

              /*
               * Since new buffers have been put on the chain,
               * we need to look at all of them again
               */
              fFlushedOnce = FALSE;
           }
           else if (pbufResult == &BufLRUHead){

                   /*  If pbufResult is pointing at the head of the
                    *  LRU list, then we're out of clean buffers.
                    *  This means we need to flush the lazy write
                    *  queue, reset pbufResult to the tail of the
                    *  LRU list, and continue our search from there.
                    */

                   if (gfb_flags & GFB_NOBLOCK)
                      return(NULL);

                   if (!fFlushedOnce) {
                      fGFBActive += 1;
#ifdef FSSTRICT
                      GFBActiveThread = pTCBCur->TCBNumber;
#endif
                      FLW();
                      fGFBActive -= 1;
                      pbufResult = BufLRUHead.BufLru.pbufBak;

                      /*  If necessary, Wakeup on pGFBWaitChain.
                       */
                      if (pGFBWaitChain != 0) {
                         TKWakeup (&(pGFBWaitChain),0,NULL);
                         pGFBWaitChain = 0;
                      }

                      /* rember we have flushed once already */
                      fFlushedOnce = TRUE;
                   } else {
                       /*
                        * We have been around the chain twice, and
                        * still have not found a candidate buffer.
                        * Yield to all threads (Not just Hi Priority ones)
                        * and then try again
                        */
                       fGFBActive += 1;
#ifdef FSSTRICT
                       GFBActiveThread = pTCBCur->TCBNumber;
#endif
                       TKSleep (SSToDS(&fFound),1,0,0);   // yield to other threads
                       fGFBActive -= 1;
                       /*  If necessary, Wakeup on pGFBWaitChain */
                       if (pGFBWaitChain != 0) {
                           TKWakeup (&(pGFBWaitChain),0,NULL);
                           pGFBWaitChain = 0;
                       }
                       fFlushedOnce = FALSE;
                       pbufResult = BufLRUHead.BufLru.pbufBak;
                   }
           }
           else if ((pbufResult != &BufLRUHead) &&
                    (pbufResult->BufDirtyMsk == 0) &&
                    (pbufResult->BufDirtyLru.pbufFwd == NULL) &&
                    (!(pbufResult->BufFlag & BUF_LWLOCK)) &&
                    (!(pbufResult->BufFlag & BUF_LOCK)) &&
                    (pbufResult->BufRefCnt == 0)) {

                    /*  if the dirty bits are all zero, the buffer is
                     *  not on a dirty buffer chain, the buffer is not
                     *  being lazily written, the hold count is zero,
                     *  and the lock count is zero (whew!) then the
                     *  buffer is free.  Set fFound to TRUE to end
                     *  the search.
                     */

                    fFound = TRUE;
           }
           else {
               /*  Move pbufResult up towards the head of the
                *  LRU list.
                */
               pbufResult = pbufResult->BufLru.pbufBak;
           }
        }

        /*  Take *pbufResult off the LRU chain.  The buffer's LRU pointers
         *  point at the buffer itself, so the casual observer can tell
         *  that it's not on the LRU chain.
         */
        DCREM (pbufResult, BufLru);

        pbufResult->BufLru.pbufFwd = pbufResult;
        pbufResult->BufLru.pbufBak = pbufResult;

        /*  Take *pbufResult off its hash chain  */
        if (pbufResult->BufHashLru.pbufFwd == pbufResult) {

            /*  If it points at itself, it's the only member in its
             *  hash chain.  All we need to do is set the hash
             *  table entry to NULL.
             */
            *(pbufResult->ppBufHTA) = NULL;
        }
        else {
            /* There are other buffers on the hash chain;  the buffer
             * we want has to be detached from the chain.
             */

            if (*(pbufResult->ppBufHTA) == pbufResult) {

                /* if it's at the head of the chain, we need to
                 * update the Hash Table entry.
                 */

                *(pbufResult->ppBufHTA) = pbufResult->BufHashLru.pbufFwd;
            }

            /*  Detach the hash chain connection  */
            DCREM (pbufResult, BufHashLru);
        }

        pbufResult->BufRBA = -1;
        BufAssert (!(pbufResult->BufFlag & BUF_LWLOCK));
        BufAssert (!(pbufResult->BufFlag & BUF_LOCK));

        return (pbufResult);
}



