#define SCCSID  "@(#)pgframe.c  6.32 92/03/29"

/*
 *      IBM/Microsoft Confidential
 *
 *      Copyright (c) IBM Corporation 1987, 1989
 *      Copyright (c) Microsoft Corporation 1987, 1989
 *
 *      All Rights Reserved
 *   3/10/92 SFB b734380 remove process counter present pages
 *   7/29/92 SFB 46691   move counter for reclaim to counter # of pages
 *                       placed on idle list
 *
 *   Date    Init Defect  flag        Abstract
 *   ================================================================
 *   9/9/92  CAR  51579   car01       Revamp memory counters
 *   9/9/92  GB   53526   DCR32500    see DCR 32500
 *  12/15/93 RAC  76699   PERFVIEW    Make Perfview optionly compiled
 *  11/16/94 ChgTm82359   82359       Roll r206 defect 82359 into r207 sparse tree
 */

#define FNO_NUM  FNO_PGFRAME_C
#include <fnomem.h>

#define INCL_DOSERRORS
#define INCL_OS2STD
#include <os2.h>

#define INCL_DOSP
#include <os2p.h>

#include <osmac.h>
#include <mi.h>
#include <dossymc.h>
#include <sigdef.h>
#include <except.h>
#include <pgexport.h>
#include <vmexport.h>
#include <tkexport.h>
#include <infoseg.h>
#include <curdir.h>
#include <dirent.h>
#include <find.h>
#include <screen.h>
#include <sem.h>
#include <tkptda.h>
#include <smexport.h>
#include <pglocal.h>
#include <kutil.h>
#include <vmowner.h>
#include <stdlib.h>
#include <string.h>
#include <mmph.h>

#ifdef PERFVIEW
#include <pvwxport.h>
#include <perfvw.h>
#endif

#include <strat2.h>                     /* must follow basedef.h */
#include <devcmd.h>
#include <pageio.h>
#include <smlocal.h>
#include <sched.h>    /* for CLASS_TIME_CRITICAL define */

#define PG_DISC_ONLY_AGING 0x1
extern void SMPrintFatalMsg(void);
extern void SMTestSwapFile(void);

/***LP  pgFrameToPF - Convert a page frame into a pf pointer
 *
 *      pgFrameToPF converts a page frame into a pf pointer.  This is used
 *      only in a strict kernel.
 *
 *          - validate frame
 *          - convert frame number to pf pointer
 *
 *      ENTRY   frame - physical page frame
 *      EXIT    pf pointer
 *
 *      Don't use the 'far' keyword (FS and GS must be preserved)
 *
 *      for (each entry in page range table)
 *          if (frame is within current range)
 *              return (pft + frame)
 *      panic()
 */

#ifdef  PGSTRICT

struct pf_s *
pgFrameToPF(frame)
frame_t frame;
{
    int i = pgPageablePAI.pai_nranges;
    register struct pagerange_s *ppr = pgPageablePAI.pai_pprt;

// DCR32500
    if ( PGGlobalFlags & PG_HIGH_MEM_USEABLE || pgHighPagesUsed == 0)
// end
        pgAssert(frame <= pgPhysMax);

    while (ppr->pr_lastframe < frame && --i > 0) {
        ppr++;
    }
// DCR 32500
    if ( PGGlobalFlags & PG_HIGH_MEM_USEABLE || pgHighPagesUsed == 0)
//  end
        pgAssert(i > 0 && (frame >= ppr->pr_firstframe ||
                       frame == pgIdleList.li_headframe ||
                       frame == pgFreeList.li_headframe));
    return(pft + frame);
}

#endif


/***LP  pgInsertPF - Insert a pf into the idle list
 *
 *      pgInsertPF inserts a page frame into the idle list after the
 *      specified element.  If the number of idle pages reaches one of
 *      the page ager's water marks, the ager is notified.  In addition,
 *      if there are any threads blocked waiting for idle pages, they
 *      are unblocked.
 *
 *          - insert fast free pf at free list tail, or
 *          - insert slow free pf at free list head, or
 *          - insert idle pf into idle list after specified element
 *          - increment idle list count
 *          - adjust page aging level
 *          - wakeup any threads waiting for idle pages
 *
 *      ENTRY   ppf - pointer to pf to insert into list
 *              ppfL - pointer to pf in idle list to precede pf
 *      EXIT    none
 *
 *      if (pf is free)
 *          if (pf is fast)
 *              insert pf on free list tail
 *          else
 *              insert pf on free list head
 *      else
 *          insert pf on idle list after pfL
 *
 *      if (pgContigNeeded page have been idled)
 *          wakeup threads blocked on pgContigNeeded
 *
 *      if (we crossed one of the ager's water marks)
 *          notify ager
 *
 *      wakeup any threads blocked on the idle list
 */
void
pgInsertPF(ppf, frame, ppfL)
register struct pf_s *ppf;
frame_t frame;
register struct pf_s *ppfL;
{
    int i;
    pgAssertPF(ppf);
    pgAssertPF(ppfL);
    pgAssert(pgFrameToPF(frame) == ppf);

#ifdef PERFVIEW
/***PV - Perfview measurement hook
 *
 *      TRACE MINOR = <PVWEI_EQ_mem_QFree_0>,
 *      TP    = @,
 *      TYPE    = (PVW),
 *      GROUP   = NOSTRICT
 */
    if (ppf->pf_free) {
        PvwEnterQ(mem,QFree,0);

/***PV - Perfview measurement hook                             //car01
 *                                                             //car01
 *      TRACE MINOR = <PVWEI_LQ_mem_QUsed_0>,                  //car01
 *      TP    = @,                                             //car01
 *      TYPE    = (PVW),                                       //car01
 *      GROUP   = NOSTRICT                                     //car01
 */                                                            //car01
    PvwLeaveQ(mem,QUsed,0);                                    //car01
    }                                                          //car01
    else // Increment Idled counter                                    //46691
    {                                                                  //46691
/***PV - Perfview measurement hook
 *
 *      TRACE MINOR = <PVWEI_Ct_mem_Idled_0>,
 *      TP    = @,
 *      TYPE    = (PVW),
 *      GROUP   = NOSTRICT
 */
            PvwCnt(mem,Idled,0);
    }                                                                  //46691
#endif

    /*
     * Insert ppf in list after ppfL.
     */
    FLINK(ppf) = FLINK(ppfL);
    BLINK(ppf) = BLINK(ppfFLINK(ppfL));
    BLINK(ppfFLINK(ppfL)) = frame;
    FLINK(ppfL) = frame;
    ++pgListCount;

    /*
     * Notify aging thread if a water mark has been passed and the ager is
     * initialized.
     */
    if (pgCurAgeRate->ar_maxidle < pgListCount && pgpAgerTCB) {
        i = 1;
        while ((pgCurAgeRate+i)->ar_maxidle < pgListCount) {
            ++i;
        }
        pgAgeRate(pgCurAgeRate + i);
    }

    /*
     * Wake up any threads that are waiting for a pf.
     */
    if (pgIdleList.li_flags & LI_BLOCKED) {
        pgIdleList.li_flags &= ~LI_BLOCKED;
        TKRun(&pgIdleList, 0);
    }

//b734380 PvwLeaveQ(prc,QPg,0);       //update process present pages counter

}


/***LP  pgDeletePF - Delete a pf from a list
 *
 *      pgDeletePF deletes a page frame from the idle or free list.  If the
 *      number of idle and free pages drops to one of the page ager's water
 *      marks, the ager is notified.  The pf's frame number is stored in the
 *      pf_blink field upon return so the caller does not have to compute it.
 *
 *          - remove pf from doubly linked list
 *          - decrement idle list count
 *          - adjust page aging level
 *
 *      ENTRY   ppf - pointer to pf to delete from list
 *      EXIT    none
 *
 *      Remove pf from list
 *      Decrement count of idle/free pfs
 *      if (we crossed one the ager's water marks)
 *          notify ager
 */

void
pgDeletePF(ppf)
register struct pf_s *ppf;
{
    frame_t frame;

    pgAssertPF(ppf);
    pgAssert(FLINK(ppf) != 0 && BLINK(ppf) != 0);


#ifdef PERFVIEW
    if (ppf->pf_free) {                                    //car01
/***PV - Perfview measurement hook                         //car01
 *                                                         //car01
 *      TRACE MINOR = <PVWEI_LQ_mem_QUsed_0>,              //car01
 *      TP    = @,                                         //car01
 *      TYPE    = (PVW),                                   //car01
 *      GROUP   = NOSTRICT                                 //car01
 */                                                        //car01
      PvwEnterQ(mem,QUsed,0);                              //car01
                                                           //car01
/***PV - Perfview measurement hook
 *
 *      TRACE MINOR = <PVWEI_LQ_mem_QFree_0>,
 *      TP    = @,
 *      TYPE    = (PVW),
 *      GROUP   = NOSTRICT
 */
      PvwLeaveQ(mem,QFree,0);
 }

 //car01
 //car01 Assume that this page is going back to a new memory object.  For most
 //car01 cases it will, except for the case when PGDeletePF is invoked from
 //car01 PFPageFault; in that case, ctReassigned will decremented be on return
 //car01 from this subroutine.

/***PV - Perfview measurement hook                         //car01
 *                                                         //car01
 *      TRACE MINOR = <PVWEI_Ct_mem_Reassigned_0>,         //car01
 *      TP    = @,                                         //car01
 *      TYPE    = (PVW),                                   //car01
 *      GROUP   = NOSTRICT                                 //car01
 */                                                        //car01
    PvwCnt(mem,Reassigned,0);                              //car01
#endif

    /*
     * Delete ppf from the list.  Also store this pf's frame number in the
     * pf_blink field so the caller won't have to compute it.
     */
    frame = BLINK(ppfFLINK(ppf));
    BLINK(ppfFLINK(ppf)) = BLINK(ppf);
    FLINK(ppfBLINK(ppf)) = FLINK(ppf);
    BLINK(ppf) = frame;
    pgAssert(pgFrameToPF(frame) == ppf);
    LOCKS(ppf) = 0;

    --pgListCount;

    /*
     * Notify aging thread if a water mark has been passed (assuming the
     * ager is initialized).
     */
    if (pgCurAgeRate->ar_minidle > pgListCount && pgpAgerTCB) {
        pgAgeRate(pgCurAgeRate - 1);
    }

//b734380  PvwEnterQ(prc,QPg,0);       //update process present pages counter


}


/***EP  PGPhysAvail - Query amount of free physical memory
 *
 *      PGPhysAvail returns the number of bytes of free physical memory in
 *      the system.  This value is highly volatile, and should be treated
 *      with care.
 *
 *          - count number of free pages on free list
 *
 *      ENTRY   none
 *      EXIT    number of bytes of free memory in system
 *
 *      for (each pf on free list)
 *          increment count
 *      return count
 */

int
PGPhysAvail()
{
    register struct pf_s *ppf = pgFreeList.li_headppf;
    int i = -1;

    do {
        ++i;
        ppf = ppfFLINK(ppf);
    } while (ppf != pgFreeList.li_headppf);

    return(i << PAGESHIFT);
}


/***EP  PGPhysPresent - return the number of in-use page frames
 *
 *      PGPhysPresent returns the number of page frames that are currently
 *      being referenced by pte's.  This function is needed to initialize
 *      a PerfView queue.
 *
 *      ENTRY   NONE
 *      EXIT    count of in-use PFs
 *
 *      return pgPhysPages - pgListCount
 */

ulong_t
PGPhysPresent()
{
    return(pgPhysPages - pgListCount);
}

//b734380/***EP  PGPhysPresentInProcess - return the number of in-use page frames
//b734380 *                               in any given process
//b734380 *
//b734380 *      PGPhysPresentInProcess returns the number of page frames that are
//b734380 *      currently being referenced by pte's in a process. This function is
//b734380 *      needed to initialize a PerfView queue.
//b734380 *
//b734380 *      ENTRY   pptda - pointer to ptda
//b734380 *      EXIT    count of in-use PFs in the specified process
//b734380 *
//b734380 */
//b734380
//b734380ulong_t
//b734380PGPhysPresentInProcess(pptda)
//b734380struct ptda_s *pptda;
//b734380{
//b734380   register ushort_t *pcpresent =
//b734380      ((struct pgdata_s *)(&pptda->ptda_pgdata))->pd_pcpresent;
//b734380   int i;
//b734380   ulong_t count = 0;
//b734380
//b734380   for (i = 0; i < MAXCRPDE; i++, pcpresent++)
//b734380       count += *pcpresent;
//b734380   return(count);
//b734380}

/***LP  pgAllocPF - allocate physical pages
 *
 *      pgAllocPF allocates contiguous or non-contiguous page frames.
 *      All pages will be marked RESIDENT upon exit.  If not enough pages
 *      are available to satisfy the request, the requesting thread blocks
 *      until more pages become available.  The page semaphores must be
 *      clear.
 *
 *          - find a suitable contiguous physical region, if appropriate
 *          - move non-free pages out of contiguous region, if appropriate
 *          - remove pages from idle list
 *          - update frame fields of ptes
 *
 *      ENTRY   ppte - first pte of linear region to allocate
 *              page - page number of first page in linear region
 *              npages - number of pages to allocate
 *              flags - PG_CONTIG         pages must be contiguous
 *                      PG_1M             all pages must reside below 1 Meg
 *                      PG_RESIDENT       pages must be in resident region
 *                      PG_16M            all pages must reside below 16 Meg
 *                      PG_ZFILL          zero fill pages
 *              boundary - physical page frame boundary, or 0 if specific
 *                         boundary not required.  Must be a power of 2.
 *                         Eg. 16 will result in frames starting on a 64K
 *                         boundary.
 *      EXIT    0 on success,
 *              else error code and no pages allocated
 *
 *      if (request is for contiguous memory)
 *          call pgFindContig to find an available block of contiguous memory
 *          for (each frame in contiguous region)
 *              if (frame is not free)
 *                  allocate another and copy contents to it
 *              if (zero fill requested)
 *                  zero fill frame
 *              initialize pte and pf
 *      else
 *          for (each page in linear range)
 *              allocate a frame
 *              if (zero fill requested)
 *                  zero fill frame
 *              initialize pte and pf
 */

#define MAXYIELD        4               /* required yield threshold */

int
pgAllocPF(ppte, page, npages, flags, boundary)
register struct pte_s *ppte;
page_t page;
int npages;
int flags;
page_t boundary;
{
    register struct vp_s *pvp;
    register struct pf_s *ppf;
    struct pte_s *pptebase = ppte;
    struct pf_s *ppfT = 0;                      /* temp pf */
    frame_t frame;
    int i;
    int error;
    int contig = FALSE;
    int yield = 0;
    ULONG ulFlags;                              /* az09219 */

    /*
     * Determine if we need to worry about the chip errata.
     */
    if (page < 16 && (page != 0 || npages > 1)) {
        flags |= pg64Kerrata;
    }
    flags &= pgFlagMask;        /* clear unneeded flags */
    if ((flags & PG_CONTIG) && npages > 1) {
        /*
         * Get a contiguous block of frames.
         */
        if ((error = pgFindContig(page, npages, flags, SSToDS(&ppfT),
            boundary)) != 0 && error != ERROR_BUSY) {
            goto Error;
        }
        ppf = ppfT;
        contig = TRUE;

        /*
         * Set all busy flags in the region and take the semaphores
         * of any in use pages.  (Note that the busy flags will be
         * cleared later when the frames are attached to the ptes.)
         */
        for (i = npages; i > 0; --i, ++ppf) {
            ppf->pf_busy = 1;
            if (ppf->pf_free == 0) {
                pgSetSem(ppf->pf_pvp);
            }
        }
        ppf -= npages;

        if (error == ERROR_BUSY) {
            /*
             * The region contains some in use frames.  Move them out.
             * All frames in the contiguous region will be placed on the
             * free list.
             */
            for (i = npages; i > 0; --i, ++ppf) {
                if (ppf->pf_free == 0) {
                    /*
                     * This frame is in use.  Move its contents to another.
                     */
                    pvp = ppf->pf_pvp;
                    if (error = pgMoveFrame(NULL, 0, pvp, 0, NULL)) {
                        goto Error;
                    }
                    pgClearSemVP(pvp);
                }
            }
            ppf -= npages;
        }

        frame = pgPFToFrame(ppf);
    }

    /*
     * Loop over the range attaching frames to the pages.
     */
    for (i = npages; i > 0; --i, ++ppte, ++page, ++ppf, ++frame) {
        if (contig) {
            pgDeletePF(ppf);
            ppf->pf_flags &= ~(PF_FREE|PF_BUSY);
        } else {
            if (error = pgGetPF(page, flags, SSToDS(&ppfT))) {
                goto Error;
            }
            ppf = ppfT;
            frame = BLINK(ppf);
        }

        /*
         * Zero initialize frame if required.
         */
        if (flags & PG_ZEROFILL) {
            memset((void *) pgAliasTemp(frame, 0), 0, PAGESIZE);
            ++yield;
        }

        /*
         * Connect the vp, pf, and pte.
         */
        pvp = pgVPITopVP(ppte->pt_frame);
        ppf->pf_pvp = pvp;

#ifdef PGSTRICT
        ppf->pf_block = pvp->vp_block;
#endif

        pvp->vp_frame = frame;
        ppf->pf_refcount = 1;
        pvp->vp_pf = 1;
        if ((pvp->vp_flags & VP_DISCARDABLE) &&
            ((pvp->vp_flags & VP_SOW) == 0) )
            pgDiscardableInmem++;
        pgAssert(pvp->vp_pfidle == 0);
        ppte->pt_frame = frame;
        ppte->pt_present = 1;
        pgClearSemVP(pvp);

        /*
         * Yield if we have exceeded the threshold.
         */
        if (yield > MAXYIELD) {
            yield = 0;
            ulFlags = TKDisable();        /*  az09219       */
            TKTCYield();
            TKEnable(ulFlags);            /*  az09219       */
        }
    }

    return(0);

  Error:
    /*
     * Release frames already attached to ptes, and clear pf busy flags and
     * semaphores if we have a contiguous block of frames.
     */
    ppte = pptebase;
    ppf = ppfT;                         /* first pf in contiguous range */
    for (i = npages; i > 0; --i, ++ppte, ++ppf) {
        if (ppte->pt_present) {
            ppf = pgFrameToPF(ppte->pt_frame);
            pvp = ppf->pf_pvp;
            ppf->pf_busy = 0;
            pgFreePF(ppf, ppte->pt_frame);
        } else {
            if (contig) {
                pgAssert(ppf->pf_free);
                ppf->pf_busy = 0;
            }
            pvp = pgFrameToVP(ppte->pt_frame);
        }

        pgAssert(pvp->vp_pfidle == 0 && pvp->vp_refcount == 1);
        pgClearSemVP(pvp);
        if (pvp->vp_hob != pgAllocBufHob) {
            pgFreeVP(pvp);
        }
        ppte->pt_long = 0;
    }
    return(error);
}


/***LP  pgLockPF - force all pages in a region present
 *
 *      pgLockPF forces all pages in a linear region present and
 *      optionally forces contiguity.  If the PG_DIRTY flag is passed
 *      in, the pages are checked for write permission and the dirty
 *      bit is set in the PF.  Copy-on-write
 *      pages are converted to swappable to avoid possible conflicts
 *      with subsequent attachments when the region is locked, and
 *      swap-on-write pages are converted to swappable only if the
 *      pages are to be marked dirty.  The PG_NOBLOCK flag can be
 *      specified only on non-contiguous or single page locks.
 *
 *          - reclaim reclaimable pages and remove from idle list
 *          - validate and check for existing contiguity, if appropriate
 *          - find a suitable contiguous physical region, if appropriate
 *          - move pages into contiguous region, if appropriate
 *          - load swapped and discarded pages
 *
 *      ENTRY   ppte - first pte in linear region
 *              page - page number of first page in linear region
 *              pptda - pointer to ptda
 *              npages - page count
 *              flags - PG_CONTIG - lock region contiguously
 *                      PG_1M - entire region must be below 1 Meg
 *                      PG_16M - entire region must be below 16 Meg
 *                      PG_W - pages will be dirtied
 *                      PG_LONG - 1=long term lock; 0=short term lock
 *                      PG_NOBLOCK - don't block
 *                      PG_TRUSTED - don't check for write permission
 *              pgpage - pointer to page_t variable to receive page number
 *                       of a guard page if one is encountered.
 *      EXIT    0 on success, else error code
 *
 *      if (contiguity requested)
 *          call pgValidateContig to determine if it's possible
 *          if (impossible)
 *              return error
 *          if (not already contiguous)
 *              call pgFindContig to find a contiguous block of pages
 *              call pgMovePF to move the pages into place
 *
 *      for (each linear page in the range)
 *          force the page present
 *          if (page is copy-on-write)
 *              split the page and make it swappable
 *          if (page is not in correct location (ie. errata, 1M, 16M, resident))
 *              allocate another frame in correct location
 *              copy frame contents
 *          increment lock count
 *          if (page will be written to)
 *              set the dirty bit
 */
int
pgLockPF(ppte, page, pptda, npages, flags, pgpage)
register struct pte_s *ppte;
page_t page;
struct ptda_s *pptda;
int npages;
int flags;
page_t *pgpage;
{
    register struct pf_s *ppf;
    struct pf_s *ppfTemp;
    struct vp_s *pvp;
    struct pgdata_s *ppd;
    int i;
    int error = 0;
    frame_t LoBound;
    frame_t HiBound;

    /*
     * Initialize LoBound and HiBound so that all frames between the two
     * will be considered invalid.
     */
    flags &= pgFlagMask;        /* clear unneeded flags */
    LoBound = HiBound = pgPageablePAI.pai_end.pb_frame;
    if (flags & (PG_LONG|PG_1M|PG_16M)) {
        if (flags & PG_LONG) {
            LoBound = pgLoResBoundfr;
            HiBound = pgHiResBoundfr;
        }
        if (flags & PG_16M) {
            LoBound = min(LoBound, pgPageablePAI.pai_16M.pb_frame);
            HiBound = pgPageablePAI.pai_end.pb_frame;
        }
        if (flags & PG_1M) {
            LoBound = min(LoBound, pgPageablePAI.pai_1M.pb_frame);
            HiBound = pgPageablePAI.pai_end.pb_frame;
        }
    }

    /*
     * Determine if we need to worry about the chip errata.
     */
    if (page < 16 && (page != 0 || npages > 1)) {
        flags |= pg64Kerrata;
    }

    pgPageToPd(page, pptda, SSToDS(&ppd));

    if (npages > 1 && (flags & PG_CONTIG)) {
        /*
         * Contiguous lock.  First see if it's possible.
         */
        error = pgValidateContig(ppte, page, npages, flags, SSToDS(&ppfTemp));
        ppf = ppfTemp;

        if (error == ERROR_ALIAS) {
            /*
             * The pages are not already contiguous.  If there is an existing
             * lock, pgValidateContig will have set ppf to point to the target
             * region.  Otherwise, find a contiguous region...
             */
            if (ppf == NULL) {
                error = pgFindContig(page, npages, flags, SSToDS(&ppfTemp), 0);
                ppf = ppfTemp;
                if (error != 0 && error != ERROR_BUSY) {
                    /*
                     * ERROR_BUSY is acceptable.  The region merely contains
                     * some in use frames.
                     */
                    if (flags & PG_TRY_CONTIG) {
                       flags &= ~(PG_TRY_CONTIG | PG_CONTIG);
                       goto Skip_Contig;
                    } else {
                       goto Error;
                    }
                }
            }

            /*
             * ...and move the pages into it.
             */
            if (error = pgMovePF(ppte, page, pptda, ppf, npages, flags,
                pgpage)) {
                goto Error;
            }
        } else if (error) {
            /*
             * There must be a conflicting lock.
             */
            if (flags & PG_TRY_CONTIG) {
               flags &= ~(PG_TRY_CONTIG | PG_CONTIG);
               // goto Skip_Contig;
            } else {
               goto Error;
            }
        }
    }
Skip_Contig:;
    /*
     * Loop over the pages and make them present, increment the lock counts,
     * and check for access violation.
     */
    for (i = npages; i > 0; --i, ++ppte, ++page) {
        /*
         * Make the page present.
         */
        if (ppte->pt_present == 0) {
            if (ppte->pt_uvirt == 0) {
                pvp = pgVPITopVP(ppte->pt_frame);
            }
            if (error = pgForcePresent(ppte, page, pptda, pvp, flags,
                LoBound, HiBound)) {
                *pgpage = page;
                goto undo;
            }
        } else if (ppte->pt_frame > LoBound && ppte->pt_frame < HiBound) {
            /*
             * Page must be moved.  Make sure it's possible.
             */
            if (ppte->pt_uvirt) {
                error = ERROR_UVIRTLOCK;
                goto undo;
            } else {
                ppf = pgFrameToPF(ppte->pt_frame);
                if (ppf->pf_pvp->vp_pfidle == 0) {
                    if (ppte->pt_resident) {
                        error = ERROR_LONGLOCK;
                        goto undo;
                    } else {
                        if (ppf->pf_llock) {
                            error = ERROR_LONGLOCK;
                            goto undo;
                        } else  if (ppf->pf_slock) {
                            error = ERROR_SHORTLOCK;
                            goto undo;
                        }
                    }
                }
            }

            if (error = pgMoveFrame(ppte, page, ppf->pf_pvp, flags, NULL)) {
                goto undo;
            }
        }

        /*
         * Set the dirty flag if the frame will be written (dma).
         */
        if (flags & PG_W) {
            /*
             * The loader calls us to dirty lock read-only pages.  This is
             * OK, so just whack to vp dirty bit.  We know he only does this
             * on swappable pages, so we don't have to worry about converting
             * SOWs.
             */
            if (flags & PG_TRUSTED) {
                pgAssert(ppte->pt_uvirt == 0); /* no one uses this way */
                ppf = pgFrameToPF(ppte->pt_frame);
                pvp = ppf->pf_pvp;
                pgAssert((pvp->vp_flags &
                          (VP_RESIDENT | VP_SOW | VP_DISCARDABLE)) == 0);
                pvp->vp_dirty = 1;
                if (pvp->vp_df) {
                    /*
                     *  Don't keep dfs if they have no valid data
                     */
                    pvp->vp_df = 0;
                    SMFreeDF(ppf->pf_block);
#ifdef PGSTRICT
                    ppf->pf_block = -7;
#endif
                }
                goto notuvirt;

            /*
             * Normal write request.  Make sure the memory it writable
             */
            } else {
                if (ppte->pt_ring3write == 0) {
                    error = ERROR_ACCESS_DENIED;
                    goto undo;
                }
                ppte->pt_dirty = 1;
            }
        }

        if (ppte->pt_uvirt == 0) {
            /*
             * Bump the lock count.
             */
            ppf = pgFrameToPF(ppte->pt_frame);

          notuvirt:

            if (flags & PG_LONG) {
#ifdef PERFVIEW
                if (++ppf->pf_llock == 1 && ppte->pt_resident == 0) {
//car01 Don't count long term locked pages as resident
//car01   /***PV - Perfview measurement hook
//car01   *
//car01   *   TRACE MINOR = <PVWEI_EQ_mem_QResident_0>,
//car01   *   TP    = @,
//car01   *   TYPE    = (PVW),
//car01   *   GROUP   = NOSTRICT
//car01   */
//car01                  PvwEnterQ(mem,QResident,0);
                }
#else
                ++ppf->pf_llock;
#endif
            } else {
                ++ppf->pf_slock;
            }

            /*
             *  On success, PGLock expects all the semaphores to be free
             */
            pgClearSemVP(ppf->pf_pvp);
        }
    }
    return(0);

    /*
     *  If we encounter an error part way through the pages,
     *  we have to undo the lock counts incremented so far.
     *  The current page we are sitting on (ppte) does not need any
     *  undoing, just those processed before (if any).
     */
  undo:
    while (i++ < npages) {
        --ppte;
        if (ppte->pt_uvirt == 0) {
            ppf = pgFrameToPF(ppte->pt_frame);
            if (flags & PG_LONG) {
                ppf->pf_llock--;
            } else {
                ppf->pf_slock--;
            }
        }
    }
  Error:
    return(error);
}


/***LP  pgFindContig - find an available contiguous region of page frames
 *
 *      pgFindContig scans the page frame array for the first available
 *      contiguous region of idle pages.  The region can optionally be
 *      specified to be in the first 1M or 16M of memory, and start at a
 *      physical address with bits 12-15 equal to bits 12-15 of the linear
 *      address.  If a region is not found, the thread blocks until more
 *      pages are added to the idle list.  When a suitable region is found,
 *      a pointer to the first pf in the region is returned.  The frames are
 *      NOT removed from the idle list, nor are their semaphores taken.
 *
 *          - scan the pf array for a best-fit region of non-busy, idle pages
 *
 *      ENTRY   npages - page count
 *              page - page number of first page to allocate
 *              flags - PG_1M       all pages must reside below 1 Meg
 *                      PG_16M      all pages must reside below 16 Meg
 *                      PG_RESIDENT all pages must reside in resident region
 *              pppf - pointer to pointer to pf structure
 *              boundary - physical page frame boundary, or 0 if specific
 *                         boundary not required.  Must be a power of 2.
 *                         Eg. 16 will result in frames starting on a 64K
 *                         boundary.
 *      EXIT    0 on success
 *                      *pppf valid
 *              else error
 *
 */

int
pgFindContig(page, npages, flags, pppf, boundary)
page_t page;
int npages;
int flags;
struct pf_s **pppf;
page_t boundary;
{
    register struct pf_s *ppf;
    struct pagerange_s *ppr;
    struct physboundary_s *pbound;
    int frame;
    register int cpages;
    int offset = 0;
    int locked;
    int ShortLock = FALSE;
    register unsigned inuse;
    unsigned best = -1;
    int error = 0;
    struct physarena_s *ppai;
    struct pf_s *ppftsd = pgFrameToPF(pTCBCur->TCBpteKStackTSD>>PT_FRAMESHIFT);
    struct pf_s *freeppf;
    struct pf_s *bestppf;
    struct pf_s *firstppf;


#ifdef PGSTRICT
    /*
     *  The caller should mask off the below 16meg flag if this machine has
     *  no memory above 16meg.  (unless we are early in init).
     */
      if (flags & PG_16M) {
        pgAssert(pgHighInitPending || pgPhysMax > (_16MEG >> PAGESHIFT));
    }
#endif

    /*
     * Select the appropriate physical arena structure.
     */
    if (flags & (PG_RESIDENT|PG_LONG)) {
        ppai = &pgResidentPAI;
    } else {
        ppai = &pgPageablePAI;
    }

    /*
     * Determine the starting point in the search.
     */
    pbound = &ppai->pai_end;
    if (flags & PG_16M) {
        pbound = &ppai->pai_16M;
    }
    if (flags & PG_1M) {
        pbound = &ppai->pai_1M;
    }

    /*
     * If we must handle the errata workaround, setup appropriate variables.
     */
    if (flags & PG_ERRATA) {
        boundary = 16;
        offset = page;
    }
    boundary = ~(boundary - 1);                 /* 0 will still be 0 */

    /*
     * Outer loop: scan entire range of physical memory (1 Meg to 0, or
     * 16 Meg to 0, or end of memory to 0).  If we reach the end of the
     * loop, we were unable to find a valid free region.  We may have found
     * a valid idle region which we can use, and if not, we must wait for
     * more pages to be idled/unlocked.
     */
    for (;;) {
        /*
         * Set ppr to the first region to scan, and ppf to the last frame
         * we can use in the region.
         */


        ppr = pbound->pb_ppr;                   /* first range to scan */
        ppf = pbound->pb_ppf;
        firstppf = pgFrameToPF(ppr->pr_firstframe);

        for (;;) {
            /*
             * Middle loop: Scan all page ranges from high to low.
             */
            ppf -= npages - 1;
            if (boundary) {
                /*
                 * Round frame down so that it starts at the requested
                 * boundary + offset.
                 */
                cpages = frame = pgPFToFrame(ppf);              /* temp */
                frame = ((frame - offset) & boundary) + offset;
                ppf -= cpages - frame;
            }

            while (ppf >= firstppf) {
                /*
                 * Inner loop:  Scan the pages from low to high within a
                 * possible region checking for all pages idle or free.
                 * Keep a use count of non-free pages so we can identify
                 * the "cheapest" region to steal in case we don't find
                 * a free region.
                 */
                inuse = 0;
                cpages = npages;
                locked = FALSE;
                freeppf = ppf - 1;

                do {
                    /*
                     * Page must not be busy...
                     */
                    if ((ppf->pf_flags & PF_BUSY) == 0) {
                        if ((ppf->pf_flags & PF_FREE) == 0) {
                            /*
                             * nor resident...
                             */
                            if ((ppf->pf_pvp->vp_flags &
                                (VP_BUSY|VP_RESIDENT)) == 0) {

                                if (ppf->pf_pvp->vp_pfidle == 0) {
                                    /*
                                     * nor long term locked, nor contain
                                     * our own tsd...
                                     */
                                    if (ppf == ppftsd || ppf->pf_llock > 0) {
                                        break;
                                    }

                                    if (ppf->pf_slock) {
                                        /*
                                         * and if it's short term locked, we
                                         * won't be able to use it but we can
                                         * wait for a lock to go away and try
                                         * again later.
                                         */
                                        locked = TRUE;
                                    }
                                }
                                ++inuse;
                            } else {
                                break;
                            }
                        } else if (inuse == 0) {
                            /*
                             * Record the last page in the contiguous block (if
                             * one exists) of free pages at the beginning of
                             * the region.
                             */
                            freeppf = ppf;
                        }
                    } else {
                        break;
                    }
                    ++ppf;
                } while (--cpages > 0);

                if (cpages == 0) {
                    /*
                     * We found a potential region.  If all of its pages
                     * are free, take it.  Otherwise record it for potential
                     * use and keep looking.
                     */
                    if (locked) {
                        /*
                         * At least one page was short term locked.  Record
                         * that fact and continue.
                         */
                        ShortLock = TRUE;
                    } else if (inuse < best) {
                        bestppf = ppf;
                        best = inuse;
                        if (inuse == 0) {
                            /*
                             * We found a completely free region.
                             */
                            goto exit;
                        }
                    }
                    /*
                     * Start the next scan so it includes the free pf's that
                     * were at the front of the block we just scanned.
                     */
                    ppf = freeppf + 1;
                }

                /*
                 * Bump ppf down to the next block to look at.
                 */
                ppf -= npages;

                if (boundary) {
                    /*
                     * Round frame down so that it starts at the requested
                     * boundary + offset.
                     */
                    cpages = frame = pgPFToFrame(ppf);          /* temp */
                    frame = ((frame - offset) & boundary) + offset;
                    ppf -= cpages - frame;
                }
            }

            /*
             * Move to the next range.  Break out of search if we've scanned
             * all ranges.
             */
            if (--ppr < ppai->pai_pprt) {
                break;
            }

            firstppf = pgFrameToPF(ppr->pr_firstframe);
            ppf = pgFrameToPF(ppr->pr_lastframe);
        }

        if (best != -1) {
            /*
             * We didn't find a completely free region, but we did find one
             * that we can steal.
             */
            error = ERROR_BUSY;
            break;
        } else if (!ShortLock) {
            /*
             * Nothing was available.
             */
            error = ERROR_NOT_ENOUGH_MEMORY;
            break;
        }

        /*
         * The only thing available was short term locked.  Wait for a lock
         * to be released and try again. Don't wait for TRY_CONTIG.
         */
        if (flags & PG_TRY_CONTIG) {
            error = ERROR_NOT_ENOUGH_MEMORY;
            break;
        } else {
            if (error = TKSleep(&pgShortLockPages, TIMEOUT_SHORTLOCK, 0, 0)) {
                if (error == ERROR_TIMEOUT) {
                    error = ERROR_NOT_ENOUGH_MEMORY;
                }
                break;
            }
        }
    }

  exit:

    *pppf = bestppf - npages;
    return(error);
}



/***LP  pgGetPF - get one physical page frame
 *
 *      pgGetPF removes one page from either the free list or the idle list.
 *      If a free page is not
 *      available, a reclaimable page is taken and its data discarded or
 *      swapped out as appropriate. If both the free and the idle list
 *      are empty, the thread
 *      blocks until more pages are made available.  The page can optionally
 *      be specified to reside in the first 1M or 16M of memory.
 *
 *      When allocating frames for pages in the first 64K of linear memory,
 *      bits 12-15 of the physical address must equal bits 12-15 of the
 *      linear address (386 errata workaround).
 *
 *          - scan the free list from tail to head, if no pf found then
 *            scan the idle list from the lru end for the first non-busy page
 *          - if a page is not found, block
 *          - remove the page from the free or idle list
 *          - swap out the page contents if necessary
 *
 *      ENTRY   page - page number
 *              flags - PG_1M       all pages must reside below 1 Meg
 *                      PG_RESIDENT all pages must reside below res boundary
 *                      PG_16M all pages must reside below 16 Meg
 *              pppf - pointer to pointer to pf structure
 *      EXIT    0 on success,
 *                      *pppf - page frame pointer
 *              else error code
 *
 */

int
pgGetPF(page, flags, pppf)
page_t page;
int flags;
struct pf_s **pppf;
{
    register struct pf_s *ppf;
    struct vp_s *pvp;
    int error;
    int ctries;
    struct pf_s *ppfTmp;

    MMPHPrePGGetPF();           /* Performance Hook */

    flags &= pgFlagMask;        /* clear unneeded flags */
    if (flags & (PG_1M|PG_16M|PG_RESIDENT|PG_ERRATA)) {
        /*
         * This is a special memory request.  Go find an appropriate frame.
         */
        if (error = pgFindFrame(page, flags, pppf)) {
            goto Error;
        }
        ppf = *pppf;
    } else {
        /*
         * Take the free page on the fast end of the free list, or the
         * idle page on the lru end of the idle list.
         */
        error = 0;
        for (ctries = 0;;ctries++) {
            ppf = pgFreeList.li_headppf;                /* head of free list */
            do {
                ppf = ppfBLINK(ppf);
            } while (ppf->pf_busy);

            if (ppf != pgFreeList.li_headppf) {
                break;
            }

            /*
             * Free list is empty.  Grab one from the idle list.
             */
            ppf = pgIdleList.li_headppf;                /* head of idle list */
            do {
FindDiscPage:
                ppf = ppfBLINK(ppf);
            } while (ppf->pf_busy || ppf->pf_pvp->vp_busy);

            if (ppf != pgIdleList.li_headppf) {
                break;
            }

            /*
             * Idle list is empty.  Wait for the ager to idle some unless
             * we tried that already and it didn't work.  If so,
             * sleep for a half second so someone might release a
             * semaphore on one of the buys pages and try again.
             */
            if (ctries > 1) {
                if ((error = TKBlock(&pgIdleList, 500, 0)) != ERROR_TIMEOUT) {
                    if (error) {
                        goto Error;
                    }
                }
            }
            if (pgCurAgeRate != &pgAgeRateTable[0]) {
                pgAgeRate(&pgAgeRateTable[0]);
            }
            pgIdleList.li_flags |= LI_BLOCKED;
            if ((error = TKBlock(&pgIdleList, -1, 0))) {
                goto Error;
            }
        }
    }

    pgAssert((ppf->pf_free || ppf->pf_pvp->vp_pfidle) && ppf->pf_busy == 0);

    if (ppf->pf_free == 0) {
        /*
         * Swap out or discard page as appropriate.
         */
        if (PGGlobalFlags & PG_DISC_ONLY_AGING &&
            pgDiscardableInmem <= PG_MIN_DISC_IN_MEM)
                     PGGlobalFlags &= ~PG_DISC_ONLY_AGING;

        pvp = ppf->pf_pvp;
        if (pgIsMustSwap(pvp)) {
            /*
             * Page is swappable and doesn't have a disk frame, or
             * page is dirty and is swap-on-write.
             * Swap it out.
             * NOTE: this "if" statement is duplicated in PGBuildGrpIOList
             *       and if one is updated, the other one should be too.
             */
//          if (PGfSwapFull & PG_DISC_ONLY_AGING) {
//              if (pgDiscardableInmem > PG_MIN_DISC_IN_MEM)
//                   goto FindDiscPage;
//              else
//                   PGfSwapFull &= ~PG_DISC_ONLY_AGING;
//          } else {
            /*
             * if the current thread is time critical and this is not a special
             *   request try to find a discardable page on the idle list
             */
            if ( (pTCBCur->TCBPriority >= 0x804) && // BUGBUG
                 (!(flags & (PG_1M|PG_16M|PG_RESIDENT|PG_ERRATA))) )
            {
                ppfTmp=ppf;
                for (ppf=ppfBLINK(ppf) ; ppf != pgIdleList.li_headppf ;
                                         ppf=ppfBLINK(ppf) )
                {
                    if ( ppf->pf_busy ||
                         ppf->pf_pvp->vp_busy ||
                        ((ppf->pf_pvp->vp_flags &
                              (VP_DF|VP_DISCARDABLE))== 0) ||
                        ((ppf->pf_pvp->vp_flags &
                              (VP_SOW|VP_DIRTY)) == (VP_SOW|VP_DIRTY)))
                    {        /* found swappable or busy page */
                             /* do nothing and try next page  */
                    } else { /* found discardable page */
                        pvp = ppf->pf_pvp;
                        goto found_disc;
                    }
                }
                /* couldn't find discardable page, use the one that was
                 * found originally
                 */
                ppf = ppfTmp;
                pvp = ppf->pf_pvp;
            }
//          }
            pvp->vp_flags &= ~(VP_SOW|VP_DISCARDABLE);
            pgSetSem(pvp);
// overcommit
            if (PGGlobalFlags & PG_DO_OVERCOMMIT) {
                error = SMSwapOut(ppf);
            } else {
                if ((error = SMSwapOut(ppf)) == NO_ERROR)  {
                    SMTestSwapFile();
                }
            }
// end overcommit
            pgClearSemVP(pvp);
            if (error) {
                if (error == ERROR_SWAP_FILE_FULL) {
                    if (PGGlobalFlags & PG_DISC_ONLY_AGING) {
                       if (pgDiscardableInmem > PG_MIN_DISC_IN_MEM)  {
                           goto FindDiscPage;
                       } else {
                           SMPrintFatalMsg();
                       } /* endif */
                    }
                }
                goto Error;
            }
        } else {
found_disc:;
            /*
             * Page is discardable.
             */

#ifdef PERFVIEW
/***PV - Perfview measurement hook
 *
 *      TRACE MINOR = <PVWEI_Ct_mem_Discard_0>,
 *      TP    = @,
 *      TYPE    = (PVW),
 *      GROUP   = NOSTRICT
 */
            PvwCnt(mem,Discard,0);
#endif
        }
                                                                       //b727647
//46691 PvwCnt(mem,Reclaim,0);       //increment counter for # of reclaims
        /*
         * Break the cross link between the vp and pf, and clear the dirty bit.
         */
        pvp->vp_flags &= ~(VP_PF|VP_PFIDLE|VP_DIRTY);
        if ((pvp->vp_flags & VP_DISCARDABLE) &&
            ((pvp->vp_flags & VP_SOW) == 0) )
            pgDiscardableInmem--;
        pvp->vp_block = ppf->pf_block;
    }

    /*
     * Remove the pf from the idle or free list.
     */
    pgDeletePF(ppf);    /* clears lock counts */
    ppf->pf_free = 0;
    *pppf = ppf;
  Error:
    MMPHPostPGGetPF();                  /* Performance Hook */

    return(error);
}


/***LP  pgValidateContig - validate contiguitibility
 *
 *      ptValidateContig determines if a linear region is contiguous, and,
 *      if not, whether it can be made contiguous.  It is not necessary
 *      that the region be completely present in memory, nor that the
 *      reclaimable pages be reclaimed prior to calling.
 *
 *          - check for existing contiguity
 *          - check for low 64K linear = physical
 *          - check for conflicting locks
 *
 *      ENTRY   ppte - pte pointer
 *              page - page number
 *              pptda - pointer to ptda
 *              npages - page count
 *              flags - PG_1M       All frames must lie below  1 Meg
 *                    - PG_RESIDENT All frames must lie in resident region
 *                    - PG_16M      All frames must lie below 16 Meg
 *              pppf - pointer to pointer to pf structure
 *                      (set to NULL unless there is an existing lock, in which
 *                      case it receives the pointer to the first pf in the
 *                      target region.)
 *      EXIT    0 if range is already contiguous
 *              ERROR_ALIAS if range is not contiguous but can be
 *              else error code
 *
 */

int
pgValidateContig(ppte, page, npages, flags, pppf)
register struct pte_s *ppte;
page_t page;
int npages;
int flags;
struct pf_s **pppf;
{
    frame_t ContigFrame;
    frame_t LoBound;
    frame_t HiBound;
    frame_t frame;
    register struct pf_s *ppf;
    struct vp_s *pvp;
    struct pagerange_s *ppr;
    int LockFrame;
    int Contiguous = TRUE;
    int ExistingLock = FALSE;
    int Locked = FALSE;
    int LockType = 0;
    int error = 0;
    int i;

#ifdef PGSTRICT
    /*
     *  The caller should mask off the below 16meg flag if this machine has
     *  no memory above 16meg.  (unless we are early in init).
     */
    if (flags & PG_16M) {
        pgAssert(pgHighInitPending || pgPhysMax > (_16MEG >> PAGESHIFT));
    }
#endif

    /*
     * Initialize LoBound and HiBound so that all frames between the two
     * will be considered invalid.
     */
    LoBound = HiBound = pgPageablePAI.pai_end.pb_frame;
    if (flags & (PG_LONG|PG_1M|PG_16M)) {
        if (flags & PG_LONG) {
            LoBound = pgLoResBoundfr;
            HiBound = pgHiResBoundfr;
        }
        if (flags & PG_16M) {
            LoBound = min(LoBound, pgPageablePAI.pai_16M.pb_frame);
            HiBound = pgPageablePAI.pai_end.pb_frame;
        }
        if (flags & PG_1M) {
            LoBound = min(LoBound, pgPageablePAI.pai_1M.pb_frame);
            HiBound = pgPageablePAI.pai_end.pb_frame;
        }
    }

    /*
     * Initialize ContigFrame.  Note that if the frame is not in core, the
     * value of ContigFrame won't matter as the region will be detected as
     * being non-contiguous inside the loop.
     *
     * Also check for bits 12-15 linear==physical (errata workaround).
     */
    ContigFrame = ppte->pt_long & (PT_UVIRT|PT_PRESENT) ? ppte->pt_frame :
                  pgVPITopVP(ppte->pt_frame)->vp_frame;

    if ((flags & PG_ERRATA) && page != (ContigFrame & 0xF)) {
        Contiguous = FALSE;
    }

    /*
     * Scan ptes for locked pages and existing contiguity.
     */
    for (i = npages; i > 0; --i, ++ppte, ++LockFrame, ++ContigFrame) {
        if (ppte->pt_uvirt) {
            /*
             * Page is uvirt.  The pte is the only data structure.
             */
            frame = ppte->pt_frame;
        } else if (ppte->pt_present) {
            /*
             * Page is present.
             */
            frame = ppte->pt_frame;
            ppf = pgFrameToPF(frame);
        } else {
            /*
             * Page is not-present.  If a pf is not cross linked to the vp,
             * then the range isn't contiguous.
             */
            pvp = pgVPITopVP(ppte->pt_frame);
            if (pvp->vp_pf == 0) {
                Contiguous = FALSE;
                continue;
            }
            frame = pvp->vp_frame;
            ppf = pgFrameToPF(frame);
        }

        /*
         * Check for existing contiguity and frames outside the specified
         * region (0-1M, 0-16M, pageable).
         */
        if (frame != ContigFrame || (frame > LoBound && frame < HiBound)) {
            Contiguous = FALSE;
        }

        /*
         * Check for conflicting locks.  Set LockType to the error
         * we should return if we later find a conflicting lock.
         *
         * UVIRT, RESIDENT and long term locks override short term.
         */
        if (ppte->pt_uvirt) {
            LockType = ERROR_UVIRTLOCK;
            Locked = TRUE;
        } else if (ppf->pf_pvp->vp_pfidle) {
            continue;
        } else if (ppte->pt_resident || ppf->pf_llock) {
            LockType = ERROR_LONGLOCK;
            Locked = TRUE;
        } else if (ppf->pf_slock) {
            if (LockType == 0) {
                LockType = ERROR_SHORTLOCK;
            }
            Locked = TRUE;
        }

        if (Locked) {
            if (ExistingLock) {
                if (frame != LockFrame) {
#ifdef PGSTRICT
                    if ( (LockType == ERROR_LONGLOCK)  &&
                         ((flags & PG_TRY_CONTIG)==0)  ) {
    printf("Attempt to lock non-contiguous resident memory contiguously");
                    }
#endif
                    error = LockType;           /* conflicting lock */
                    break;
                }
            } else {
                ExistingLock = TRUE;
                LockFrame = frame;              /* initialize 1st locked page */
            }
            Locked = FALSE;
        }
    }

    if (error == 0) {
        *pppf = NULL;
        if (!Contiguous) {
            error = ERROR_ALIAS;
            if (ExistingLock) {
// Begin 82359
                if ( LockFrame <= npages) {
                   error = LockType;
                   goto Error;
                }
// End 82359
                LockFrame -= npages;
                ppte -= npages;

                /*
                 * Make sure all frames are within the specified region and
                 * linear(12-15) == physical(12-15) in the first 64K of linear
                 * memory (386 chip errata work around).
                 */
                if (LockFrame + npages - 1 > LoBound || LockFrame < HiBound ||
                    ((flags & PG_ERRATA) && page != (LockFrame & 0xF))) {
                    error = LockType;
                    goto Error;
                }

                /*
                 * See if the lock would straddle a hole in physical memory.
                 */
                for (ppr = pgPageablePAI.pai_pprt;
                    ppr->pr_lastframe < LockFrame; ++ppr)
                    ;

                if (LockFrame < ppr->pr_firstframe ||
                    LockFrame + npages - 1 > ppr->pr_lastframe) {
                    error = LockType;
                    goto Error;
                }

                /*
                 * Scan the frames we need to steal.
                 */
                for (i = npages; i > 0; --i, ++LockFrame, ++ppte) {
                    ppf = pgFrameToPF(LockFrame);
                    if (ppf->pf_busy ||
                        (ppf->pf_free == 0 &&
                        ((ppf->pf_pvp->vp_flags & (VP_RESIDENT|VP_BUSY)) ||
                        (ppf->pf_pvp->vp_pfidle == 0 &&
                        (ppf->pf_llock|ppf->pf_slock))))) {
                        /*
                         * The frame we need is busy, resident or locked.
                         */
                        error = LockType;
                        goto Error;
                    }
                }

                *pppf = pgFrameToPF(LockFrame - npages);
            }
        }
    }
  Error:
    return(error);
}


/***LP  pgMovePF - Move page frames into a physically contiguous region
 *
 *      pgMovePF moves page frames referenced through a specified
 *      linear region (the source) into a specified contiguous physical
 *      region (the target).  All page frames in the target region must
 *      be idle or free.  Source pages that are not present are loaded into
 *      the appropriate frames.
 *
 *      If the target page contains valid data, it is copied to the free
 *      page.  If the source page is not to be marked dirty (ie. it will
 *      not be overwritten) or it is a boundary page, its contents are
 *      copied to the target page.  Otherwise, the copy operation is
 *      omitted.  Source pages marked copy-on-write with reference counts
 *      greater than 1 are split regardless of whether the page will be
 *      dirtied (we cannot enumerate all ptes associated with cows,
 *      therefore if we move the page w/o splitting it, we won't be able
 *      to update all ptes).  Copy-on-write pages are always changed to
 *      swappable; swap-on-write pages only if they are to be marked dirty.
 *      When the move is complete, the semaphores for the pages mapped to
 *      what used to be the source frames are clear, and pages' semaphores
 *      for the now contiguous range of pages are still owned.
 *
 *          - get a free page to use as a worker
 *          - if target page is not free, copy target to free page
 *          - if source page has valid data, copy source to target page
 *
 *      ENTRY   ppte - pointer to first pte in linear region
 *              page - page number
 *              pptda - pointer to ptda
 *              ppf - pointer to first pf in physical region
 *              npages - page count
 *              flags - PG_DIRTY  - frames will be dirtied after lock
 *                      PR_RELOAD - reload page contents
 *      EXIT    0 on success, else error code
 *
 */

int
pgMovePF(ppte, page, pptda, ppf, npages, flags, pgpage)
register struct pte_s *ppte;
page_t page;
struct ptda_s *pptda;
register struct pf_s *ppf;
int npages;
int flags;
page_t *pgpage;
{
    struct vp_s *pvp;
    struct pgdata_s *ppd;
    frame_t frame = pgPFToFrame(ppf);
    int i;
    int error;
    int yield = 0;
    struct vp_s *pvptarget;

    /*
     * Set all busy flags in the region and take the semaphores
     * of any in use pages.  The semaphores will be freed as each page
     * is moved out of the range.
     */
    for (i = npages; i > 0; --i, ++ppf) {
        ppf->pf_busy = 1;
        if (ppf->pf_free == 0) {
            pgSetSem(ppf->pf_pvp);
        }
    }
    ppf -= npages;

    for (i = npages; i > 0; --i, ++page, ++ppte, ++ppf, ++frame) {
        pgAssert(ppte->pt_uvirt == 0);

        /*
         * Get the vp pointer.
         */
        if (ppte->pt_present) {
            pvp = pgFrameToPF(ppte->pt_frame)->pf_pvp;
        } else {
            pvp = pgVPITopVP(ppte->pt_frame);
        }

        if (pvp->vp_pf == 0 || pvp->vp_frame != frame) {
            /*
             * Page is not in place.  Move it or load it.
             */
            if ((ppf->pf_flags & PF_FREE) == 0) {
                /*
                 * The target frame is not free.  Move its contents into
                 * another frame.
                 */
                pvptarget = ppf->pf_pvp;
                if (error = pgMoveFrame(NULL, 0, pvptarget, 0, NULL)) {
                    break;
                }
                pgClearSemVP(pvptarget);
                yield += 2;
            }

            /*
             * The target frame is free.
             */
            if (pvp->vp_pf == 0) {
                /*
                 * The page isn't in core.  Reload it.
                 */
                if (error = pgReloadFrame(page, pvp, pptda, flags, ppf)) {
                    break;
                }
            }
            if (pvp->vp_frame != frame) {
                /*
                 * Move the frame into place.
                 *
                 * If the page is already locked then it must be aliased
                 * within the region we are locking, and contiguity is
                 * impossible.
                 */
                if (pvp->vp_pfidle == 0 && LOCKS(pgFrameToPF(pvp->vp_frame))) {
                    error = ERROR_ALIASLOCK;
                    break;
                }

                if (error = pgMoveFrame(ppte, page, pvp, flags, ppf)) {
                    break;
                }
                yield += 2;
            }
        }
        pgAssert(pvp->vp_pf && pvp->vp_frame == frame);

        if (ppte->pt_present == 0) {
            /*
             * Reclaim the frame.  We know it's in the proper location.
             */
            if (pvp->vp_pfidle) {
                /*
                 * The pf is on the idle list.  Remove it.
                 */
                pgDeletePF(ppf);
                pvp->vp_pfidle = 0;
                ppf->pf_refcount = 0;
            }

            /*
             * Attach the frame to the pte.
             */
            ++ppf->pf_refcount;
            ppte->pt_frame = frame;
            ppte->pt_present = 1;
            pgPageToPd(page, pptda, SSToDS(&ppd));
            ++ppd->pd_pcpresent[pgPagePdToPDI(page, ppd)];

            /*
             * Check for a guard page.
             */
            if (ppte->pt_dirty) {
                ppte->pt_dirty = 0;
                *pgpage = page;
                error = ERROR_GUARDPAGE;
                break;
            }
        }

        /*
         * We don't need the busy flag protection now that the pte and pf
         * are linked together.
         */
        ppf->pf_busy = 0;

        pgFlushTLB();
        if (yield > MAXYIELD) {
            yield = 0;
            TKTCYield();
        }
    }

    /*
     * Handle an early bailout on error by clearing all pf and vp busy flags.
     */
    while (i-- > 0) {
        if (ppf->pf_free == 0) {
            pgClearSemVP(ppf->pf_pvp);
        }
        (ppf++)->pf_busy = 0;
    }

    return(error);
}


/***LP  pgFindFrame - find a special frame
 *
 *      pgFindFrame searches the physical arena for a frame that must reside
 *      in the resident region, or below a specified boundary (16M or 1M), or
 *      satisfy a 386 errata workaround requirement.
 *
 *      ENTRY   page -  linear page number (needed for errata workaround)
 *              flags - PG_1M       frame must reside below 1 Meg
 *                      PG_16M      frame must reside below 16 Meg
 *                      PG_RESIDENT frame must reside within resident region
 *                      PG_ERRATA   frame must satisfy 386 B1 errata workaround
 *              pppf  - pointer to pointer to pf to receive result
 *      EXIT    0 if success  (pppf has pointer to pf)
 *              else error code
 *
 */

int
pgFindFrame(page, flags, pppf)
page_t page;
int flags;
struct pf_s **pppf;
{
    register struct pf_s *ppf;
    struct pf_s *ppfstop;
    struct pf_s *ppfstart;
    struct pf_s *ppftsd = pgFrameToPF(pTCBCur->TCBpteKStackTSD>>PT_FRAMESHIFT);
    struct pagerange_s *ppr;
    struct pagerange_s *pprstart;
    struct physarena_s *ppai;
    int shortlock = FALSE;
    int pfdelta = 1;
    int frame;
    int error = 0;
    unsigned int bestref = -1;
    struct pf_s *bestpf;

#ifdef PGSTRICT
    /*
     *  The caller should mask off the below 16meg flag if this machine has
     *  no memory above 16meg.  (unless we are early in init).
     */
      if (flags & PG_16M) {
        pgAssert(pgHighInitPending || pgPhysMax > (_16MEG >> PAGESHIFT));
    }
#endif

    /*
     * Select the appropriate physical arena structure.
     */
    if (flags & (PG_RESIDENT|PG_LONG)) {
        ppai = &pgResidentPAI;
    } else {
        ppai = &pgPageablePAI;
    }

    /*
     * Determine the starting point in the search.
     */
    ppf = ppai->pai_end.pb_ppf;
    ppr = ppai->pai_end.pb_ppr;

    if (flags & PG_16M) {
        ppf = ppai->pai_16M.pb_ppf;
        ppr = ppai->pai_16M.pb_ppr;
    }
    if (flags & PG_1M) {
        ppf = ppai->pai_1M.pb_ppf;
        ppr = ppai->pai_1M.pb_ppr;
    }

    ppfstart = ppf;
    pprstart = ppr;

    if (flags & PG_ERRATA) {
        pfdelta = _64K / PAGESIZE;
    }

    /*
     * Scan every range.
     */
    for (;;) {
        if (flags & PG_ERRATA) {
            /*
             * Frame must satisfy errata workaround.  Bits 12-15 of the physical
             * address must be identical to bits 12-15 of the linear address.
             */
            frame = pgPFToFrame(ppf);
            ppf -= frame - (((frame - page) & ~0xF) + page);
        }

        /*
         * Scan the frames within one range.
         */
        ppfstop = pgFrameToPF(ppr->pr_firstframe);
        while (ppf >= ppfstop) {
            /*
             * The frame must not be busy.
             */
            if ((ppf->pf_flags & PF_BUSY) == 0) {
                if (ppf->pf_free) {
                    goto Found;
                }

                /*
                 * The page's semaphore must be free, and the page must not
                 * be resident nor long-term locked.
                 */
                if ((ppf->pf_pvp->vp_flags & (VP_BUSY|VP_RESIDENT)) == 0) {
                    if (ppf->pf_pvp->vp_pfidle) {
                        if (bestref > 0 && ppf != ppftsd) {
//                          if ((PGGlobalFlags==1) &&
                            if ((PGGlobalFlags & PG_DISC_ONLY_AGING) &&
                                (pgIsMustSwap(ppf->pf_pvp))) {
                                 goto DiscOnly;
                            }
                            /*
                             * We found a better frame, and it's not our tsd.
                             */
                            bestref = 0;
                            bestpf = ppf;
                        }
                    } else if (ppf->pf_llock == 0) {
                        if (ppf->pf_slock) {
//                          if ((PGGlobalFlags==1) &&
                            if ((PGGlobalFlags & PG_DISC_ONLY_AGING) &&
                                (pgIsMustSwap(ppf->pf_pvp))) {
                                goto DiscOnly;
                            }
                            shortlock = TRUE;
                        } else if (ppf->pf_refcount < bestref &&
                            ppf != ppftsd) {
//                          if ((PGGlobalFlags==1) &&
                            if ((PGGlobalFlags & PG_DISC_ONLY_AGING) &&
                                (pgIsMustSwap(ppf->pf_pvp))) {
                                goto DiscOnly;
                            }
                            /*
                             * We found a better frame, and it's not our tsd.
                             */
                            bestref = ppf->pf_refcount;
                            bestpf = ppf;
                        }
                    }
                }
            }
DiscOnly:
            ppf -= pfdelta;
        }

        /*
         * Move down to the next range.
         */
        if (--ppr < ppai->pai_pprt) {
            /*
             * We've scanned everything.
             */
            if (bestref != -1) {
                /*
                 * We found something -- take it.
                 */
                ppf = bestpf;
                break;
            }

            /*
             * We didn't find an available frame.  If we found a short term
             * locked frame that would have been acceptable, wait for a short
             * term lock to be released and try again.
             */
            error = ERROR_NOT_ENOUGH_MEMORY;
            if (!shortlock ||
                (error = TKSleep(&pgShortLockPages, TIMEOUT_SHORTLOCK,0,0))) {
                return(error == ERROR_TIMEOUT ? ERROR_NOT_ENOUGH_MEMORY :error);
            }

            ppf = ppfstart;
            ppr = pprstart;
        }

        ppf = pgFrameToPF(ppr->pr_lastframe);
    }

    /*
     * We found an idle or in-use frame.  Move its contents into another frame.
     */
    pgSetSem(ppf->pf_pvp);
    error = pgMoveFrame(NULL, 0, ppf->pf_pvp, 0, NULL);
    pgClearSemVP(ppf->pf_pvp);

  Found:
    *pppf = ppf;
    return(error);
}


/***LP  pgMoveFrame - Move a page from one frame to another
 *
 *      pgMoveFrame accepts a page and an optional destination frame and
 *      copies the page contents into the destination frame and updates
 *      all appropriate data structures, including all ptes.
 *
 *      Note that ppte is not required.  However supplying ppte will save a
 *      call to VMScanMaps when the page frame has a reference count of one,
 *      and ppte is referencing the frame.  If the destination frame is not
 *      supplied, one will be allocated.  Page is needed only for B1 errata
 *      workaround, and is also optional but should be given if known.  (Any
 *      ptes that are remapped will be marked not-present if the destination
 *      frame is not appropriate.)
 *
 *      ENTRY   ppte    - pointer to page's pte (optional)
 *              page    - page number (optional)
 *              pvp     - pointer to vp structure
 *              flags   - PG_1M         page must reside below 1 Meg
 *                        PG_16M        page must reside below 16 Meg
 *                        PG_RESIDENT   page must reside in resident region
 *              ppfcaller - pointer to destination frame's pf (optional)
 *      EXIT    0 on success, else error code
 *
 */

int
pgMoveFrame(ppte, page, pvp, flags, ppfcaller)
struct pte_s *ppte;
page_t page;
struct vp_s *pvp;
int flags;
struct pf_s *ppfcaller;
{
    register struct pf_s *ppfdes = ppfcaller;
    struct pf_s *ppfsrc;
    struct pgmappte_s pgmp;
    int pdi;
    int error = 0;

    pgAssert(pvp->vp_pf);
    ppfsrc = pgFrameToPF(pvp->vp_frame);

    if (ppfdes == NULL) {
        /*
         * We must allocate the target frame.
         */
        if (error = pgGetPF(page, flags, SSToDS(&pgmp.mp_ppfdes))) {
            goto Error;
        }
        ppfdes = pgmp.mp_ppfdes;
        pgmp.mp_framedes = BLINK(ppfdes);
    } else {
        /*
         * The caller supplied a free target frame.  Remove it from the free
         * list.
         */
        pgAssert(ppfdes->pf_free);
        pgDeletePF(ppfdes);
        ppfdes->pf_free = 0;
        pgmp.mp_framedes = BLINK(ppfdes);
    }
    pgmp.mp_framesrc = pvp->vp_frame;

    /*
     * Copy the frame contents.
     */
    memcpy((void *)pgAliasTemp(pgmp.mp_framedes, 0),
           (void *)pgAliasTemp(pgmp.mp_framesrc, 1), PAGESIZE);

    /*
     * Cross link the destination frame with the vp, and update all pte's
     * to reference the destination frame.
     */
    pvp->vp_frame = pgmp.mp_framedes;
    ppfdes->pf_pvp = pvp;
    ppfdes->pf_block = ppfsrc->pf_block;

    if (pvp->vp_pfidle) {
        /*
         * Put the new pf on the idle list in place of the current pf.
         */
        BLINK(ppfFLINK(ppfsrc)) = pgmp.mp_framedes;
        FLINK(ppfBLINK(ppfsrc)) = pgmp.mp_framedes;
        FLINK(ppfdes) = FLINK(ppfsrc);
        BLINK(ppfdes) = BLINK(ppfsrc);
    } else {
        ppfdes->pf_refcount = 0;
        pgAssert(LOCKS(ppfdes) == 0);

        pgmp.mp_pvp = pvp;
        pgmp.mp_kill = FALSE;
        pgmp.mp_ppfsrc = ppfsrc;
        pgmp.mp_ppfdes = ppfdes;

        /*
         * Use VMScanMaps to find all pte's that need to be updated.
         */
        VMScanMaps(pgMapPte, SSToDS(&pgmp), pvp->vp_obpg, pvp->vp_hob);

        if (pvp->vp_pfidle == 0 &&
            ppfdes->pf_refcount != ppfsrc->pf_refcount) {
            panic("pgMoveFrame: bad reference count");
        }

        if (pgmp.mp_kill) {
            /*
             * An illegal write was caught.  Kill somebody.
             */
            pgIllegalWrite(pvp);
        }

        /*
         * See if we just moved a page table.  If so, then we may need to
         * update the page directory, too.
         */
        if (pvp->vp_hob == pgSysPtHOB) {
            pgAssert(pvp->vp_refcount == 1);
            pgAssert(pgpPageDir[(MINSYSLADDR >> PAGEDIRSHIFT) +
                     pvp->vp_obpg].pt_frame = pgmp.mp_framesrc);
            pgpPageDir[(MINSYSLADDR >> PAGEDIRSHIFT) + pvp->vp_obpg].pt_frame =
                pgmp.mp_framedes;
// GSR start
        } else if (pvp->vp_hob == pgShrCRPtHOB) {
//            pgAssert(pvp->vp_refcount == 1);
//            pgAssert(pgpPageDir[(MINSYSLADDR >> PAGEDIRSHIFT) +
//                     pvp->vp_obpg].pt_frame = pgmp.mp_framesrc);
            pgpPageDir[(MINGLOBALSHRLADDR  >> PAGEDIRSHIFT) + pvp->vp_obpg].pt_frame =
                pgmp.mp_framedes;

// GSR end
        } else if (pvp->vp_hob == pgCRPtHOB) {
            pgAssert(pvp->vp_refcount == 1);
// GSR start
//          pdi = (pvp->vp_obpg & (MAXCRPDE - 1)) + (MINCRLADDR>>PAGEDIRSHIFT);
            pdi = (pvp->vp_obpg % MAXCR_PDE) + (MINCR_LADDR>>PAGEDIRSHIFT);
// GSR end

            /*
             * See if it's from the current context.
             */
            if ((pgpPageDir[pdi].pt_long & (PT_FRAME|PT_PRESENT)) ==
                ((pgmp.mp_framesrc << PT_FRAMESHIFT) | PT_PRESENT)) {
                /*
                 * It is, update the page directory.
                 */
                pgpPageDir[pdi].pt_frame = pgmp.mp_framedes;
            }
        }
    }

    /*
     * Put the source frame on the free list.
     */
    pgFreePF(ppfsrc, pgmp.mp_framesrc);

  Error:
    pgFlushTLB();
    return(error);
}


#pragma code_seg("SMLOCK_TEXT", "CODE")
/***EP  PGBuildGrpIOList - builds a PageCmd list for a group swapout
 *
 *      PGBuildGrpIOList - worker routine to take a single pf that is tobe
 *                          swapped out and searchs the idle list for
 *                          SM_MAX_CMDS-1 other swappable pfs tobe grouped
 *                          together in an unrelated group swap out
 *
 *      ENTRY   ppf            - pointer to pf that must be swapped out
 *              pSMPageCmdList - pointer to a page command list buffer
 *      EXIT    0 on success
 *              else error code
 *
 *      note: an error will only be returned if the first PGAllocBuf fails
 *
 *      if ((rc=PGAllocBuf()) != 0)
 *          return(rc)
 *      pgBuildCmd()
 *      for (ppftmp=ppf->pf_blink; ntry=0;
 *           ncmds<nmaxcmds && (ppftmp != &pgIdleHead) ;i++ )
 *          while (ppftmp != &pgIdleHead &&
 *             ((ppftmp->pf_flags & PF_STATEMASK) != PF_SWAPPED ||
 *              (ppftmp->pf_flags & PF_DIRTY) == 0 ||
 *              (ppftmp->pf_flags & PF_SEM)))
 *              ppftmp = ppftmp->pf_blink
 *              if (++ntry > PGMAXLOOKAHEAD)
 *                  goto earlyexit;
 *          if (ppftmp != &pgIdleHead)
 *              ppftmp->pf_flags |= PF_BUSY
 *              if ((PGAllocBuf() != 0)
 *                  goto earlyexit
 *              pgBuildCmd();
 *              ncmds++
 *              ppftmp=ppftmp->pf_blink
 *
 *  earlyexit:
 *      pSMPageCmdList->Hdr.OpCount = ncmds
 *      return(rc)
 */

int
PGBuildGrpIOList(ppf,pSMPageCmdList)
struct pf_s *ppf;
register struct SMPageCmdList_s *pSMPageCmdList;
{
    register struct pf_s *ppftmp;
    register struct vp_s *pvp;
    uchar_t ncmds;
    ulong_t vaddr;
    ulong_t laddr;
    int error;
    int ntry;

    /*
     *  Get a transfer buffer
     */
    if (error = PGAllocBuf(1, PG_SWAPPABLE, pgPFToFrame(ppf), SSToDS(&laddr),
                           SSToDS(&vaddr), 0)) {
        return(error);
    }
    pgBuildCmd(pSMPageCmdList, 0, ppf, laddr, vaddr);
    ncmds = 1;

    for (ppftmp = ppfBLINK(pgIdleList.li_headppf), ntry=0;
        ncmds < SMMaxPageCmds && (ppftmp != pgIdleList.li_headppf) ; ) {
        /*
         *  Walk the idle list till we hit the end or find a candidate for
         *  swapping out or walk farther than PGMAXLOOKAHEAD
         */
        for (;ppftmp != pgIdleList.li_headppf; ppftmp = ppfBLINK(ppftmp)) {

            pvp = ppftmp->pf_pvp;

            /*
             *  If the page is swappable and doesn't have a DF or it
             *  is dirty and swap-on-write, it is a candidate for swapping.
             *  This code is stolen from pgGetPF and should be maintained
             *  in parallel.
             */
            if (pvp->vp_busy == 0 &&
                ((pvp->vp_flags & (VP_DF|VP_DISCARDABLE)) == 0 ||
                ((pvp->vp_flags & (VP_SOW|VP_DIRTY)) == (VP_SOW|VP_DIRTY)))) {

                /*
                 * Build a command packet for this page.
                 */
                pgSetSem(pvp);
                pvp->vp_flags &= ~(VP_SOW | VP_DISCARDABLE);
                if ((PGAllocBuf(1, PG_SWAPPABLE, pgPFToFrame(ppftmp),
                                SSToDS(&laddr),
                                SSToDS(&vaddr), 0)) != 0)  {
                    goto earlyexit;
                }
                pgBuildCmd(pSMPageCmdList,ncmds,ppftmp,laddr,vaddr);
                ncmds++;
                ppftmp=ppfBLINK(ppftmp);
                break;

            /*
             *  If we didn't find a good page, make sure we are not looking
             *  too far.
             */
            } else  if (++ntry > PGMAXLOOKAHEAD)  {
                goto earlyexit;
            }
        }
    }

earlyexit:
    pSMPageCmdList->Hdr.pgcmdh_OpCount = ncmds;
    return(0);
}


/***LP  pgFindDF - scan VP array for a DF
 *
 *      ENTRY   iDF - DF number to scan for
 *      EXIT    pointer to the VP holding the DF or NULL if it wasn't found
 */
struct vp_s *
pgFindDF(iDF)
ulong_t iDF;
{
    register struct vp_s *pvp;

    for (pvp = pgpVPBase;; pvp++) {
        /*
         *  If the vp is free, skip to the next allocated vp on the other
         *  end of the free bundle or break out if at the end of the array.
         */
        if (pvp->vp_refcount == 0) {
            pvp = pvp->vp_blink->vp_flink;
            if (pvp == pgpVPEnd) {
                /*
                 *  We hit the end of the array without finding our man.
                 */
                return(NULL);
            }
            pgAssert(pvp->vp_refcount == 0);
            pvp++;
            pgAssert(pvp->vp_refcount > 0);
        }

        /*
         *  pvp points to a valid vp.  Is it our man?
         */
        if (pvp->vp_df) {
            if (pvp->vp_pf) {
                if (pgFrameToPF(pvp->vp_frame)->pf_block == iDF) {
                    break;      /* found it */
                }
            } else if (pvp->vp_block == iDF) {
                break;          /* found it */
            }
        }
    }
    return(pvp);
}

#ifdef OMIT

/***    pgCountDFs - compare number of DFs in DF array with vp's with VP_DF set
 *
 *      NOTE:  just because there is a mis-compare, it does not mean there is
 *             a bug.  There are some places (swap file compaction, group DF
 *             allocation) in which the numbers will not match, but that is
 *             a temporary state.  It is leaks that we are really worried
 *             about.
 *
 *      ENTRY   none
 *      EXIT    none
 */
int
pgCountDFs(void)
{
    int vpcount = 0;
    int dfcount = 0;
    register struct vp_s *pvp;
    register char *p;
    char *pend;
    int delta;
    unsigned char c;
    static lastd = 0;


    for (pvp = pgpVPBase;; pvp++) {
        /*
         *  If the vp is free, skip to the next allocated vp on the other
         *  end of the free bundle or break out if at the end of the array.
         */
        if (pvp->vp_refcount == 0) {
            pvp = pvp->vp_blink->vp_flink;
            if (pvp == pgpVPEnd) {
                /*
                 *  We hit the end of the array
                 */
                break;
            }
            pgAssert(pvp->vp_refcount == 0);
            pvp++;
            pgAssert(pvp->vp_refcount > 0);
        }

        /*
         *  pvp points to a valid vp.  Is it our man?
         */
        if (pvp->vp_df) {
            vpcount++;
        }
    }

    /*
     * vpcount == number of vp_df bits
     */
    p = smbmDF;
    pend = p + ((smDFMac + 7) / 8);
    while (p <= pend) {
        if (*p == -1) {
            dfcount += 8;
        } else {
            for (c = 0x80; c != 0; c >>= 1) {
                if (*p & c) {
                    dfcount++;
                }
            }
        }
        p++;
    }
    delta = dfcount - vpcount;
    pgAssert(lastd == delta);
    lastd = delta;
}
#endif


#pragma code_seg()

