#define SCCSID  "@(#)pgage.c    6.7 92/03/24"

/*
 *      IBM/Microsoft Confidential
 *
 *      Copyright (c) IBM Corporation 1987, 1989
 *      Copyright (c) Microsoft Corporation 1987, 1989
 *
 *      All Rights Reserved
 */

#define FNO_NUM  FNO_PGAGE_C
#include <fnomem.h>

#define INCL_ERRORS
#include <bseerr.h>
#include <osconst.h>
#include <ostypes.h>
#include <osmac.h>
#include <mi.h>
#include <dossymc.h>
#include <sigdef.h>
#include <except.h>
#include <seldesc.h>
#include <pgexport.h>
#include <vmexport.h>
#include <selxport.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 <sched.h>
#define INCL_TYPES
#include <basedef.h>
#include <mvdmexp.h>
#include <pglocal.h>
#include <mmph.h>
#include <vmowner.h>
#include <kmode.h>

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

#include <string.h>

// bugbug copied from pgframe.c should be in pgl*.h
/***  pgIsMustSwap(pvp)
 *    1 if pvp must be swapped out else 0 (discardable)
 */
#define pgIsMustSwap(pvpM) ((pvpM->vp_flags & (VP_DF|VP_DISCARDABLE)) == 0 || \
            ((pvpM->vp_flags & (VP_SOW|VP_DIRTY)) == (VP_SOW|VP_DIRTY)))

extern struct infoseg_s Sis2;
extern struct InfoSegGDT SISData;

extern void SMPrintFatalMsg(void);

#ifdef PGVERYSTRICT
void pgcompute_discardableinmem();
#endif

extern ulong_t hyberStat;

// swapper compression - local pg routines
void pgSMFreeDF(register ulong_t idf);
void pgTestSMCompress(struct pf_s *ppf, struct vp_s *pvp);
// end compression

/***EP  PGAgeLoop - age all the pages in the system forever
 *
 *      ENTRY:  NONE
 *      EXIT:   Does not exit
 *
 *      for (ever)
 *          for (each process in the system)
 *              set the aging needed flag in its ptda
 *
 *          for (each process in the system)
 *              if (this is the ager's ptda)
 *                  age all of system memory
 *              else
 *                  age this process' memory
 *              clear aging needed flag in ptda
 */
void
PGAgeLoop ()
{
    ulong_t i,j;
    register struct tcb_s **pptcb;
    register struct pgdata_s *ppd;
    register ulong_t pdi;
    ulong_t cidled;
    ulong_t cfutilesweeps = 0;

    /*
     *  Sleep until we're needed.
     */
    pgAgerAsleep = 1;
    TKSleep(&pgAgerAsleep, pgCurAgeRate->ar_sleepms, 1, 0);
    pgAgerAsleep = 0;

    /*
     *  This is the main aging loop.  Sweep through all the page tables
     *  of all the processes forever.
     */
    for(;TRUE != FALSE;) {  /* loop until Big Brother takes over */

        /*
         *  Reset count of pages idled in this sweep.
         */
        cidled = 0;

        /*
         *  Set the aging needed flag in each ptda in the system
         *
         *  BUGBUG(?) - bs - Note that here and in the next for loop
         *  we only scan for ThreadCount-1 threads.  Currently when
         *  someone says MAXTHREADS=300 tasking is giving them 300
         *  _slots_ not threads, and since slot 0 is invalid, we get
         *  one less thread than we would like.  TK could well change
         *  in the future, and if so these two loops must also change
         *  or else we may miss a process in our aging loop.
         */
        pptcb = papTCBSlots+1; /* skip always empty slot 0 */

        for (i = 1; i < ThreadCount; i++, pptcb++) {
            if (*pptcb) {
              ((struct pgdata_s *)
               &((*pptcb)->TCBpPTDA->ptda_pgdata))->pd_flags |= PD_AGINGNEEDED;
            }
        }

        /*
         *  Loop though all the processes and age all the page tables of
         *  those that have their PD_AGINGNEEDED.
         *  If the ager comes across it's own ptda, it ages system memory.
         */
        pptcb = papTCBSlots+1; /* skip always empty slot 0 */

        for (i = 1; i < ThreadCount; i++, pptcb++) {
            if (*pptcb) {  /* slot is valid */
                ppd = (struct pgdata_s *) &((*pptcb)->TCBpPTDA->ptda_pgdata);
                if (ppd->pd_flags & PD_AGINGNEEDED) {
                    if (*pptcb == pTCBCur) {
                        /*
                         *  Age system memory
                         */
                        /*
                         *  Don't age the system arena if it's being monitored
                         *  by Dos32QueryAppPageUsage.
                         */
                        if (MonSys.pptda != NULL) {
                            continue;
                        }
                        pdi = 0;
                        ppd = &pgData; /* don't clear sysinit's aging bit */
                        for (; pdi < MAXSYSSIZE >> PAGEDIRSHIFT;
                               pdi++) {
                            if (pgcPtePresent[pdi] != 0 &&
                                pgcPtePresent[pdi] > pgcPteResident[pdi] && // perf
                                pgcPteAllocated[pdi] != (ushort_t)VDMALIASSIG){

                                pgAgePt(pdi, pptcb, &pgData, SSToDS(&cidled));
                            }
                        }
                        // GSR start
                        //  age the global shared region
                        pdi = 0;
                        ppd = &pgShrData;
                        for (; pdi < MAXGLOBALSHRPDE ; pdi++) {
                            if (pgcShrPtePresent[pdi] != 0 &&
                                pgcShrPteAllocated[pdi] != (ushort_t)VDMALIASSIG){
                                pgAgePt(pdi, pptcb, &pgShrData, SSToDS(&cidled));
                            }
                        }
                        // GSR end

                    } else {
                        /*
                         *  Age this process' memory.  Loop over only those
                         *  page tables at the top and bottom of user space
                         *  that are known to be valid by the counts used
                         *  at context switch time.  If pgAgePt returns
                         *  error, the process died during a block, so stop
                         *  aging him.
                         */
                        /*
                         *  Don't age the app being monitored by
                         *   Dos32QueryAppPageUsage. Don't age any apps
                         *  if all are being monitored.
                         */

                        if ( (*pptcb)->TCBpPTDA == MonApp.pptda
                            || MonApp.pptda == QPU_ALLAPPS )
                            continue ;
                        pdi = 0;
                        for (j = ppd->pd_cpdelow; j > 0; --j, ++pdi) {
                            if (ppd->pd_pcpresent[pdi]) {
                                if (pgAgePt(pdi, pptcb, ppd, SSToDS(&cidled))){
                                    goto processdead;
                                }
                            }
                        }
                        pdi = ppd->pd_base + ppd->pd_maxpde - 1;
                        for (j = ppd->pd_cpdehigh; j > 0; --j, --pdi) {
                            if (ppd->pd_pcpresent[pdi]) {
                                if (pgAgePt(pdi, pptcb, ppd, SSToDS(&cidled))){
                                    goto processdead;
                                }
                            }
                        }
                    }
                    ppd->pd_flags &= ~PD_AGINGNEEDED;
  processdead:      ;
                }
            }
        }
#ifdef PGVERYSTRICT
         pgcompute_discardableinmem();
#endif
        /*
         *  At this point in the code we have finished a sweep through
         *  all of the processes that are running (and system space).
         *  If nothing was idled during the sweep, increment the count
         *  of futile sweeps (cfutilesweeps).  If the count passes a
         *  magic number, assume that more aging will not help things,
         *  so the ager will lower himself to idle time priority to
         *  let someone else run.
         */
        if (cidled == 0) {
            if (++cfutilesweeps > 4) {
                cfutilesweeps = 0;

                /*
                 *  If pusage is keeping us from aging something, turn him
                 *  off and try aging again.
                 */
                if (MonApp.pptda != 0 || MonSys.pptda != 0) {
                    MonApp.pptda = MonSys.pptda = 0;

                /*
                 *  Otherwise, turn off the ager
                 */
                } else {
                    pgAgeRate(&pgAgeRateTable[NRATES- 1]);

		    /* If entering hybernation announce that we've
 		       completed here. */
		    if (hyberStat) {
			hyberStat++;
			TKYield();
		    }
                    /*
                     * Wake up any threads blocked on the idle list or for
                     * contiguous memory.
                     */
                    TKRun(&pgIdleList, 0);
                }
            }
        } else {
            cidled = 0;
            cfutilesweeps = 0;
        }

    }
}


/***LP  pgAgePt - age the pages of a present page table
 *
 *      For the passed page table, pgAgePt examines all of the pages
 *      described therein.  If a page is has been accessed since the
 *      last visitation, the pte accessed bit is cleared.  The page
 *      is unaccessed (and not locked nor busy nor system uvirt)
 *      it is idled.
 *
 *      After a certain number of page tables have been aged (the value
 *      of pgCurAgeRate->ar_ntables) this routine will block for the
 *      number of milliseconds stored in pgCurAgeRate->ar_sleepms
 *      (or yield of the value is 0). If during the block the process
 *      being aged is terminated or is marked for termination, the caller
 *      is notified.
 *
 *      WARNING: This routine may block or yield, possibly forever.
 *
 *          - Get page data for this table
 *          - If table contains no pageable pages, return
 *          - For each page in table, clear accessed bit or idle if appropriate
 *          - If static count of tables aged => ar_ntables, then block or yield
 *
 *      ENTRY:  pdi - page director (table) index within this arena
 *              pptcb - pointer to slot in apTCBSlots of process to be aged
 *              ppd - pointer to pgdata structure for this arena
 *              pcidled - add the number of pages during this call onto the
 *                        variable pointed to by this
 *      EXIT:   0 if ptda is still valid, else ERROR_INVALID_PROCID
 *
 *      Pseudo-code:
 *      ppd = pgPageToPd()
 *      if (there are no present page in the page table)
 *          return(0)
 *
 *      ; idle all inactive pages in table
 *      ppte = pgPageToPte()
 *      for (i = NPTEPPT; i--; ppte++)
 *          if (pte is present)
 *              ppf = pointer to this page's pf
 *              if (pte is accessed)
 *                  clear accessed bit
 *              else if (pte is pageable and pf is not busy  nor locked)
 *                  if (pgIdle() == ERROR_INVALID_PROCID)
 *                      return(ERROR_INVALID_PROCID)
 *      ; sleep if we have had the cpu long enough
 *      if (ctablesaged++ => pgCurAgeRate->ar_ntables)
 *          ctablesaged=0
 *          flush TLB
 *          if (pgCurAgeRate->ar_sleepms == 0) ; just a yield
 *              TKYield()
 *          else ; sleep wanted
 *              pgAgerAsleep = 1
 *              TKSleep(&pgAgerAsleep, pgCurAgeRate->ar_sleepms, 1, 0)
 *              pgAgerAsleep = 0
 *          if (slot is now zero || does not have its aging needed bit set)
 *              return ERROR_INVALID_PROCID ; our ptda is gone
 *      return 0
 */
int
pgAgePt (pdi, pptcb, ppd, pcidled)
ulong_t pdi;
struct tcb_s **pptcb;
struct pgdata_s *ppd;
ulong_t *pcidled;
{
    static ushort_t ctablesaged;
    register struct pf_s *ppf;
    register struct vp_s *pvp;
    register struct pte_s *ppte;
    struct pte_s *ppteend;
    ushort_t *pcpresent;
//  ulong_t *pcresident;  //d25306
    ushort_t *pcresident;  //d25306 - perf back to the old way

    ulong_t cidled = 0;

    /*
     *  If some present pages exist, examine them all.  Present accessed pages
     *  just have their accessed bits cleared.  Unaccessed pages that
     *  are present, pageable, not system uvirt, not busy and not locked
     *  are idled.
     */
    pcpresent = &ppd->pd_pcpresent[pdi];
    pgAssert(*pcpresent != 0);
    pcresident = &ppd->pd_pcresident[pdi];
    ppte = ppd->pd_ppte + (pdi << (PAGEDIRSHIFT - PAGESHIFT));
    ppteend = ppte + NPTEPPT;

    TKBeginNoBlock();
    for (; ppte < ppteend; ++ppte) {
        if (ppte->pt_present) {
            if (ppte->pt_long & (PT_RESIDENT | PT_ACCESSED)) {
                /*
                 *  Turn off the accessed bit of accessed pages and also
                 *  use this clause to skip the aging of resident pages.
                 */
                ppte->pt_accessed = 0;
            } else if (ppte->pt_uvirt == 0) {

                /*
                 *  Here we have a non-uvirt, present, unaccessed page.
                 *  Idle it if it is not resident, locked, or busy.
                 *  Long-term locked pages may be idled if the page table
                 *  resident/locked count is 0, because if so the lock is
                 *  not through this context.
                 *
                 */
                ppf = pgFrameToPF(ppte->pt_frame);
                pgAssertPF(ppf);
                pvp = ppf->pf_pvp;

/* 08/17/94 - 94489
   Lan Server group had a problem in which a locked page was being idled.
   They were allocating and locking a page in process space and then sending
   it to the device driver shich was doing a VMProcessToGlobal on it. The
   page in the shared arena inherited the lock but was not recorded as
   resident (*pcresident == 0). At interrupt time they were taking a page
   fault.

   THIS FIX CAUSES A 4 MEG SYSTEM TO THRASH SO MUCH THAT BOOTING
   TAKES OVER 20 MIN'S

   08/32/94 = 96074
   Ok, let's try this again. Just test for long locks as by testing against
   the count of resident pages for the page table we are not allowing any
   aging to take place.

   NOTE:
      - we originally had;
                    (ppf->pf_llock == 0 || *pcresident == 0)) {
      - 94489 changed this to (following SMP change)
                    ppf->pf_llock == 0 && *pcresident == 0) {
      - and this defect now uses
                    ppf->pf_llock == 0) {
*/
                if (pvp->vp_busy == 0 && ppf->pf_slock == 0 &&
                    ppf->pf_llock == 0) {

                    /*
                     *  The semaphore is clear, so we are able to do
                     *  sanity checks here.
                     */
#ifdef PGSTRICT
                    pgAssert(pvp->vp_refcount > 0 && ppf->pf_refcount > 0);
                    pgAssert(ppte->pt_frame == pvp->vp_frame);
                    pgAssert((pvp->vp_flags & (VP_PF | VP_PFIDLE)) == VP_PF);
                    if (pvp->vp_discardable) {
                        pgAssert(pvp->vp_df == 0);
                    } else if (pvp->vp_sow) {
                        pgAssert(FALSE); /* should have both bits set */
                    }
#endif
                    if (PGGlobalFlags & PG_DISC_ONLY_AGING) {
                        // here were in a low resource condition, the swap file
                        //  is full and were stealing df's from in memory pf's
                        //  so age only discardable memory until the in memory
                        //  discardable count drops below 1/2 the mininum
                        //  in memory discardable count
                        if (pgDiscardableInmem > PG_MIN_DISC_IN_MEM)  {
                            if ((pvp->vp_discardable) && ((pvp->vp_sow) == 0)) {
                            } else {
//                             continue;
                               goto skip_pte;
                            }
                        } else {
// gebcommit
                            if (PGGlobalFlags & PG_DO_OVERCOMMIT) {
                                PGGlobalFlags &= ~PG_DISC_ONLY_AGING;
                            } else {
                                if (SMSwapSpace >=
                                        (SMcDFInuse+(ulong_t)SMMaxPageCmds) ) {
                                    PGGlobalFlags &= ~PG_DISC_ONLY_AGING;
                                } else {
                                   /*  Kill the system due to Swap File full
                                    *    condition
                                    */
                                    SMPrintFatalMsg();
                                }
                            }
// gebcommit
                        }
                    }
                    /*
                     *  to avoid reentering the swapper (and possible deadlock)
                     *  don't age TSDs that are in the act of swapping
                     *  or that own a file buffer
                     */
                    if (((ppte >= pgTSDStart) && ppte < pgTSDEnd) &&
                        (((*pptcb)->TCBfSwapping &
                              (SM_TCB_SWAPPING | SM_TCB_RESIZING)) ||
                        ((*pptcb)->TCBForcedActions & TK_FF_BUF))) {
//                        continue;
                          goto skip_pte;
                    }
                    /*
                     *  If the present count is going to hit 0 with this
                     *  idle, call pgReleasePt to mark the pt swappable.
                     *  Don't cause a deadlock by waiting for the page table's
                     *  semaphore.
                     */
                    if (--(*pcpresent) == 0) {
                        if (pgReleasePt(ppd, pdi, PG_NOBLOCK)) {
                            *pcpresent = 1;
                            break;
                        }
                    }

                    /*
                     *  propogate the pte dirty bit into the vp
                     */
                    if (ppte->pt_dirty) {
                        if (ppte->pt_ring3write == 0) {
                            pgIllegalWrite(pvp);
                        }
                        pvp->vp_flags |= (VP_DIRTY | VP_SHDIRTY);
                        if (pvp->vp_df) {
                            /*
                             *  Don't keep dfs if they have no valid data
                             */
                            pvp->vp_df = 0;

                            // when freeing the compressed page from
                            // the resident heap, we could block so
                            // delay freeing by setting the hook
                            pgSMFreeDF(ppf->pf_block);
#ifdef PGSTRICT
                            ppf->pf_block = 0xfff0;
#endif
                        }
                    }

                    /*
                     *  Do the idle.  If this is a TSD, inform the scheduler.
                     */
                    if (ppte >= pgTSDStart && ppte < pgTSDEnd) {
                        TKIdleTSD(pageTKTSDs + (ppte - pgTSDStart));
                    }
                    ppte->pt_long &= ~(PT_PRESENT | PT_FRAME | PT_DIRTY);
                    ppte->pt_long |= pgpVPToVPI(pvp) << PT_FRAMESHIFT;
                    if (--ppf->pf_refcount == 0) {
                        pvp->vp_pfidle = 1;
                        pgIdlePF(ppf, pvp->vp_frame);
                        /****** swapper compression start *******/
                        if (pgIsMustSwap(pvp)) {
                            pgTestSMCompress(pvp,ppf);
                        }
                        /******* swapper compression end ***********/
                    }
                    cidled++;

//46691             /***PV - Perfview measurement hook
//46691              *
//46691              *      TRACE MINOR = <PVWEI_EQ_mem_QReclaim_0>,
//46691              *      TP    = @,
//46691              *      TYPE    = (PVW),
//46691              *      GROUP   = NOSTRICT
//46691              */
//46691                 PvwEnterQ(mem,QReclaim,0);

                    if (*pcpresent == 0) { /* stop if last page in pt*/
                        break;
                    }
                }
            } else if (*pcresident == 0) {
                /*
                 *  This uvirt must be in user space since its pt_resident
                 *  bit is off.  That makes it a candidate for aging.
                 *  Skip it, however, if the pt resident count is non-0,
                 *  since that might mean the page is locked.
                 */
                pgAssert(ppte->pt_uvirt && ppte->pt_resident == 0);
                ppte->pt_present = 0;
                if (--(*pcpresent) == 0) {
                    if (pgReleasePt(ppd, pdi, PG_NOBLOCK)) {
                        ppte->pt_present = 1;
                        *pcpresent = 1;
                        break;
                    }
                }
            }
        }
skip_pte:;
     }
     TKEndNoBlock();

    /*
     *  Here we are done aging one full page table.  See how many we have
     *  aged since last blocking or yielding (by incrementing ctablesaged)
     *  and block or yield if we have passed the value in the current
     *  aging rate parameters.
     */
    if (++ctablesaged >= pgCurAgeRate->ar_ntables) {
        ctablesaged = 0;
        /*
         *  CAVEAT - briansm - 11/89 - a block of 0 should probably be the same
         *  as a yield, but the tasking code does not implement it as such.
         */
        if (pgCurAgeRate->ar_sleepms == 0) {    /* just a yield */
            TKYield();
        } else {                                /* need to block */
            pgAgerAsleep = 1;
            TKSleep(&pgAgerAsleep, pgCurAgeRate->ar_sleepms, 1, 0);
            pgAgerAsleep = 0;
        }

        /*
         *  Validate process after coming back from block.
         *  Abandon aging this process by returning error if any of these
         *  conditions are true (which indicate that the process we were aging
         *  may have died):
         *      The slot is empty
         *      The ptda for the thread in this slot has a different address
         *              than it did before we blocked
         *      The ptda's PD_AGINGNEEDED bit is clear
         */
        if (*pptcb == 0 ||
            ppd != (struct pgdata_s *) &((*pptcb)->TCBpPTDA->ptda_pgdata) ||
            (ppd->pd_flags & PD_AGINGNEEDED) == 0) {

            return(ERROR_INVALID_PROCID);
        }
    }
    *pcidled += cidled;
    return(0);
}

/***LP  pgIllegalWrite - called when we detect a write to a read-only page
 *
 *      This routine is called when we detect a write to a read-only page
 *      by a ring 2 or ring 0 thread after the fact (on a 386 cpu).
 *      We iterate through all processes attached to this page and idle
 *      the ptes (killing the process if we have an illegal write in that
 *      context) and if we can idle all pte and the page is discardable,
 *      we will discard it so the callers can have a clean copy.
 *
 *      The caller must either own the page semaphore or it must be clear.
 *
 *      ENTRY: pvp - pointer to the vp of page that was illegally written to
 *      EXIT:  none
 *
 *      mark all pages not present that we can
 *      if (we got reference count to 0 and the page can be discarded)
 *          discard it
 */
void
pgIllegalWrite(pvp)
struct vp_s *pvp;
{
    /*
     *  If the global info seg was written to, get a fresh copy of it
     *  from the master.
     */
    if (pvp->vp_hob == Sis2.is_hob) {
        memcpy(&Sis2.is_va, &SISData, SIS_LEN);
    }

    /*
     *  Mark all references to the page not present and free the dirty
     *  page if it is discardable
     */
    VMScanMaps(&PGMarkNP, 0, pvp->vp_obpg, pvp->vp_hob);
    if (pvp->vp_pfidle && pvp->vp_discardable == 1) {
        pgFreePF(pgFrameToPF(pvp->vp_frame), pvp->vp_frame);
        if ((pvp->vp_flags & VP_DISCARDABLE)  &&
          ((pvp->vp_flags & VP_SOW) == 0) )
             pgDiscardableInmem--;
        pvp->vp_flags &= ~(VP_PFIDLE | VP_PF);
    }
}


/***EP  PGMarkNP - VMScanMaps worker to mark a page idle
 *
 *      If a context being scanned is dirty but read-only, that process
 *      is terminated.
 *
 *      ENTRY:  pdata - ignored
 *              page - virtual page number of page to idle
 *              pptda - context
 *      EXIT:   none
 */
void
PGMarkNP(pdata, page, pptda)
void *pdata;
page_t page;
struct ptda_s *pptda;
{
    struct pgdata_s *ppd;
    struct pte_s *ppte;
    ulong_t pdi;

    /*
     * Get the pgdata pointer and page directory index.
     */
    if (pgPageToPd(page, pptda, ppd) == 0) {
        pdi = pgPagePdToPDI(page, ppd);
        if ((int)ppd->pd_pcpresent[pdi] > 0) {
            /*
             * The page table is present.
             */
            ppte = pgPagePdTopPte(page, ppd);
            if (ppte->pt_present) {

                /*
                 *  The page is present, so idle it.
                 */
                pgAssert((ppte->pt_long & (PT_UVIRT | PT_RESIDENT)) == 0);

                if (pgIdle(ppte, pgFrameToPF(ppte->pt_frame), pdi, ppd)) {

                    /* BUGBUG-TKRing2WriteError doesn't exist, panic for now. */

                    Panic(("Read-only page written on by ring 0 or 2 thread.  \
Address=%x, pptda=%x", page << PAGESHIFT,
                            (ulong_t) pptda));
                    /*
                     *  NOTE: If you hit this panic, it is because someone
                     *  wrote to a read-only page at some time in the past and
                     *  we didn't catch it at the time (because of a 386 design
                     * "feature")
                     */
                }
            }
        }
    } else {
        pgAssert(FALSE);        /* bad arg from VM */
    }
}


/***LP  pgIdle - mark a single page not present
 *
 *      ENTRY:  ppte - pointer to target pte
 *              ppf - pointer to its pf
 *              pdi = its page directory index
 *              ppd - pointer to its pgData structure
 *      EXIT:   0 or ERROR_WRITE_FAULT if the page was dirty and read-only
 *              or ERROR_LOCKED
 */
int
pgIdle(ppte, ppf, pdi, ppd)
struct pte_s *ppte;
struct pf_s *ppf;
ulong_t pdi;
struct pgdata_s *ppd;
{
    register struct vp_s *pvp = ppf->pf_pvp;
    int err;

    MMPHPrePGIdle();                    // Performance Hook

    pgAssert(ppte->pt_present);

    if (pvp->vp_pfidle == 0 && LOCKS(ppf) != 0) {
        err = ERROR_LOCKED;
    } else {
        /*
         *  propogate the pte dirty bit into the vp
         */
        if (ppte->pt_dirty) {
            if (ppte->pt_ring3write == 0) {
                err= ERROR_WRITE_FAULT;
            }
            pvp->vp_flags |= (VP_DIRTY | VP_SHDIRTY);
            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 = -1;
#endif
            }
        }

        ppte->pt_long &= ~(PT_PRESENT | PT_FRAME | PT_DIRTY);
        ppte->pt_long |= pgpVPToVPI(pvp) << PT_FRAMESHIFT;
        if (--ppf->pf_refcount == 0) {
            pvp->vp_pfidle = 1;
            pgIdlePF(ppf, pvp->vp_frame);
        }
        if (--(ppd->pd_pcpresent[pdi]) == 0) {
            pgReleasePt(ppd, pdi, 0);
        }
        err = 0;
    }

    MMPHPostPGIdle();           // Performance Hook
    return(err);
}


/***LP  pgAgeRate - change the aging rate
 *
 *      pgAgeRate changes the aging rate to the parameters in the
 *      pgAgeRateTable pointed to by the caller.  The priority of
 *      the aging thread is set to the requested rate.
 *
 *      If swapping is off, all requests will cause the lowest aging
 *      rate to be selected, except for requests for the highest aging
 *      rate, which will be honored (this is to support contiguity).
 *
 *          - Set pointer to current params
 *          - Set aging thread priority to new value
 *
 *      ENTRY:  part - pointer to entry in pgAgeRateTable of parameters
 *                      for requested rate.
 *      EXIT:   NONE
 *
 *      Pseudo-code:
 *      if (pgAgerAsleep && request is an increase in aging rate)
 *          TKWakeup(pgAgerAsleep)
 *      pgCurAgeRate = part
 *      SCHSetPriority()
 */
void
pgAgeRate (par)
struct agerate_s *par;
{
    pgAssert(pgpAgerTCB);    /* if called before initialization */

    if (!PGSwapEnabled) {
        if (par != pgAgeRateTable) {
            par = &pgAgeRateTable[NRATES-1];
        }
    }

    if (pgAgerAsleep && par < pgCurAgeRate) {
        TKWakeup(&pgAgerAsleep,0,0);
        pgAgerAsleep = 0;
    }
    pgCurAgeRate = par;
    SCHSetPriority(pgpAgerTCB, (par->ar_prtyclass << 8) + par->ar_prtydelta);
}

#ifdef PGVERYSTRICT

void pgcompute_discardableinmem()
{
   ulong_t  dsc1,dsc2,dsc3;
   ulong_t i;
   register struct vp_s *pvp;
   register struct pf_s *ppf;

   i=0;
   ppf=pft;

   dsc1 = dsc2 = dsc3 = 0l;

   for (i=0; i<=pgPhysMax; i++, ppf++) {
      pvp = ppf->pf_pvp;
      if ( ((ppf->pf_flags & PF_FREE)==0) &&
           (pvp != 0l)                    &&
           ((ulong_t)pvp != 0xfffff000l)) {
/*       printf("ppf=%lx  pvp=%lx\n",ppf,pvp);   */
         if ( ((pvp->vp_flags & VP_SOW) == 0)  &&
               (pvp->vp_flags & VP_DISCARDABLE) )
         {
               dsc1++;
         }
         if ( ((pvp->vp_flags & VP_SOW) == 0)  &&
              ((pvp->vp_flags & VP_RESIDENT) == 0)  &&
               (pvp->vp_flags & VP_DISCARDABLE) )
         {
               dsc2++;
         }
         if ( ((pvp->vp_flags & VP_PF) == 0) &&
              ((pvp->vp_flags & VP_PFIDLE) == 1) )
              printf("ERROR: ppf=%lx  pvp=%lx\n",ppf,pvp);
/*******
         if (pvp->vp_flags & VP_DISCARDABLE)
         {
               dsc3++;
         }
*******/
      } /* endif */
   } /* endfor */
   pgAssert(dsc2 == dsc1);
     if (pgDiscardableInmem>dsc1)
          if ((pgDiscardableInmem-dsc1) > 8)
              pgAssert(0);
     else
          if ((dsc1 - pgDiscardableInmem) > 8)
              pgAssert(0);

     if ((pgDiscardableInmem-dsc1)!=0)
        printf("%lx %lx %lx %lx\n", pgDiscardablePages,
                                    dsc1,
                                    pgDiscardableInmem,
                                    (dsc1-pgDiscardableInmem));
}
#endif





/***LP  pgTestSMCompress - test if the page can be compressed
 *
 *      Call SMCompress to compress the page, if the page is completely
 *      full of zeros mark the vp AOD the put the pf on the free list
 *      if the page can be compressed, allocate memory from the KRH
 *      (using the NO_BLOCK option) and move the compressed data to it
 *      and the pf on the idle list
 *
 *      added for defect 98488
 *
 *      ENTRY   pvp - pointer to the vp
 *              ppf - pointer to the pf
 *
 *      EXIT:   NONE
 */
void pgTestSMCompress(pvp,ppf)
struct vp_s *pvp;
struct pf_s *ppf;
{
    uchar_t *p;           // for swapper compression
    int smcompc;          // for swapper compression
    int err;


    p = (uchar_t *)pgAliasTemp(pgPFToFrame(ppf), 0);

    // the last 4 bytes MUST be zero the SMCompress routine to work
    if ( (*(ulong_t *)(p+0xffc) == 0L) &&
          *(ulong_t *)(p+0x100 ) == 0L  ) {

        if (SMCompNextIndex != -1) {

            smcompc = smCompress(p, SMpCompBase);

            if ( smcompc != 0) {

                if (smcompc == 4)  {
                    pgDeletePF(ppf);
                    ppf->pf_free = 1;
                    pgFreePF(ppf, pvp->vp_block);
                    pvp->vp_flags = 0;
                    pvp->vp_block = VPB_ZEROFILL;
                } else {

                    err = VMAllocKHB(VM_HKH_PUB_RESRW,
                          smcompc,
                          SMDFHOWNER,       /* owner */
                          PG_NOBLOCK,
                          SMpCompTbl+SMCompNextIndex);

                    if (err == 0) {
                        memcpy (*(SMpCompTbl+SMCompNextIndex),
                             SMpCompBase, smcompc);
                        pvp->vp_flags &= ~(VP_SOW | VP_DISCARDABLE | VP_DIRTY);
                        pvp->vp_df    = 1;
                        ppf->pf_block = SMCompNextIndex | SM_COMPRESSION_FLAG;
                        SMCompNextIndex =
                            AllocBit((ulong_t *)&smbmComp[0],
                            SM_MAX_COMPRESSED_ENTRIES);
                    }
                }
            }
        }
    }
}


/***LP  pgSMFreeDF - free the DF
 *
 *      with swapper compression getting its space from the resident
 *      heap, there is a possibility of having the freekhb call
 *      (in SMFreeDF) block, so since the ager run under the assumption that
 *      that it will not block, we must NOT call SMFreeDF with
 *      DFs that have been compressed. When a compressed DF is found
 *      we set the flGlobalAction flag, and put the address of the KRHB
 *      that is tobe freed in the SMpFreeCompDF variable and free the
 *      bit from the smapper compression bitmap (smbmComp).  If the
 *      df is not compressed, just call SMFreeDF as usual.
 *
 *      added for defect 98488
 *
 *      ENTRY   iDF - disk frame number to free
 *
 *      EXIT:   NONE
 */
void pgSMFreeDF(idf)
register ulong_t idf;
{

   // check to see if the df is compressed or not
   if (idf & SM_COMPRESSION_FLAG) {

      // if the df is compressed, see if NextIndex has a valid index
      if (SMCompNextIndex == -1) {
          SMCompNextIndex = idf & SM_COMPRESSION_FLAG_MSK;
      } else {
          FreeBit((ulong_t*)&smbmComp[0],
                   (int)(idf & SM_COMPRESSION_FLAG_MSK));
      }

      // SMpFreeCompDFs is the head pointer to the chain of KRH blocks
      // that need to be freed by SMFreeCompDFs

      if (SMpFreeCompDFs == 0) {   // setup  the header for the chain

          flGlobalActions |= KM_FF_FREECOMPDFS;
          SMpFreeCompDFs = *(SMpCompTbl+ (idf & SM_COMPRESSION_FLAG_MSK));
          *(SMpFreeCompDFs) = 0;

      } else {  // insert address of new KRHB into the chain
                // note that the address of the next KRHB is stored in
                // the first dword of this KRHB

          *(ulong_t *)*(SMpCompTbl+(idf & SM_COMPRESSION_FLAG_MSK))=
                                                            SMpFreeCompDFs;
          SMpFreeCompDFs = *(SMpCompTbl+ (idf & SM_COMPRESSION_FLAG_MSK));

      }
      #ifdef PGSTRICT
         *(SMpCompTbl+ (idf & SM_COMPRESSION_FLAG_MSK)) = 0;
      #endif
   } else {
      // the df is not compressed, so call SMFreeDF
      SMFreeDF(idf);
   }
   return;
}
