#define SCCSID  "@(#)pglock.c   6.6 92/02/22"

/*
 *      IBM/Microsoft Confidential
 *
 *      Copyright (c) IBM Corporation 1987, 1989
 *      Copyright (c) Microsoft Corporation 1987, 1989
 *
 *      All Rights Reserved
 *  6/9/92 sfb fixed perfview counter for resident queue
 *  Change History (Change Team):
 *
 *  Defect  flag  Date     Author         Abstract
 *  ========================================================================
 *  51579   car01 09/09/92 Carlos Romero  Revamp Perfview memory hooks
 *  76699   PERFVIEW 12/15/93 RAC Make Perfview optionly compiled with flag
 *                                 PERFVIEW
 *  104052  83438 11/16/94 Change Team    Change PGlock to not zero out
 *           error code returned from pgLockPF for long locks and to undo
 *           counts when error is hit (even if unlocked != 0).
 */

#define FNO_NUM  FNO_PGLOCK_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 <smexport.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 <pglocal.h>
#define  INCL_TYPES
#include <basedef.h>
#include <except32.h>

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

/***EP  PGLock - Lock a range of pages, optionally forcing contiguity.
 *
 *      PGLock locks down a range of pages after performing overcommit
 *      accounting, possibly forcing contiguity.  If the lock would
 *      conflict with an existing lock or would reduce pageable memory
 *      too much an error is returned.
 *
 *      The flags parameter indicates long-term or short-term, contiguous
 *      or non-contiguous, read or write (to cause the accessed or accessed
 *      and dirty bits to be set), whether the physical memory should
 *      reside below 16 Meg, and whether the call is allowed to block or yield.
 *
 *      ENTRY   page - virt page
 *              pptda - pointer to ptda
 *              npages - number of pages to be locked
 *              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_TRY_CONTIG - same as PG_CONTIG except if waiting for
 *                                      short term locks is required, don't
 *                                      wait and lock non-contiguously
 *                      PG_NOBLOCK - don't give up cpu
 *                      PG_TRUSTED - don't check for write permission
 *      EXIT    0 on success, else error code
 *
 *      pin page tables
 *      take page semaphores
 *      if (short-term lock)
 *          request overcommit for whole range
 *          call pgLockPF
 *      else
 *          count pages that need overcommit
 *          increment pt resident counts
 *          request overcommit
 *          call pgLockPF
 *      unpin page tables
 */
int
PGLock(page, pptda, npages, flags)
page_t page;
struct ptda_s *pptda;
int npages;
int flags;
{
    register struct pgdata_s *ppd;
    struct pgdata_s *ppdX;
    register struct pte_s *ppte;
    struct pte_s *pptebase;
    int unlocked;
    int undocount;                                                      //83438
    int i;
    int gpage;
    int err;
    int pdi;
//  ulong_t aulExceptionInfo[2];

    flags |= (flags & PG_TRY_CONTIG) ? PG_CONTIG : 0;
    flags |= (flags & PG_LONG) ? PG_RESIDENT : 0;

    flags &= pgFlagMask;        /* clear unneeded flags */
    if (err = pgPinPt(page, pptda, npages, flags, SSToDS(&ppdX))){
        return(err);
    }
    ppd = ppdX;
    ppte = pgPagePdTopPte(page, ppd);
    if (err = pgTakeSem(ppte, npages, flags)){
        goto unpin;
    }
    pptebase = ppte;

    /*
     *  For short term locks, just get the overcommit and call pgLockPF
     *  to increment the lock counts and check for possible write permission
     *  problems.
     */
    if ((flags & PG_LONG) == 0) {

        /*
         *  As an optimization, don't count number of pages that need new
         *  overcommit, assume everyone does
         */
        if (err = pgShortLockCommit(npages + pgNPagesToNPT(page, npages),
                                    flags)) {
            goto clearsem;
        }
        err = pgLockPF(pptebase, page, pptda, npages, flags, SSToDS(&gpage));

//      while ((err = pgLockPF(pptebase, page, pptda, npages, flags,
//          SSToDS(&gpage))) == ERROR_GUARDPAGE) {
//          /*
//           * We hit a guard page.  Release the semaphores before calling
//           * the exception manager to avoid deadlock when the page manager
//           * is reentered.
//           */
//          pgClearSem(pptebase, npages);
//
//          aulExceptionInfo[0] = (flags & PG_W) ? XCPT_WRITE_ACCESS : XCPT_READ_ACCESS;
//          aulExceptionInfo[1] = gpage << PAGESHIFT;   // FaultAddr
//
//          XCPTBuildR3DispatcherStack(EXCSW,                     // Trap #
//                                     XCPT_GUARD_PAGE_VIOLATION, // Xcpt #
//                                     2,                         // Param Ct.
//                                     SSToDS(aulExceptionInfo),  // Params
//                                     FALSE,                     // fContinue
//                                     NULL);                     // pERepRec
//
//          if (err = pgTakeSem(pptebase, npages, flags)){
//              break;
//          }
//      }

        if (err) {
            pgShortLockCommitFree(npages + pgNPagesToNPT(page, npages));
            goto clearsem;
        }

        /*
         *  BUGBUG - briansm (8-4-89) short-term locks of UVIRT pages
         *      is not currently doing anything to keep the page tables
         *      in core.
         *  BUGBUG briansm (8-4-89) - don't need overcommit for uvirts
         */

    /*
     *  Long term lock...
     */
    } else {
        pgAssert(flags & PG_RESIDENT);
        /*
         *  Count currently unlocked pages for overcommit
         */
//83438        for (unlocked = 0, i = 0; i < npages ; ppte++, i++) {
        for (unlocked = 0, i = 0, undocount = 0; i < npages ; ppte++, i++) {
            pdi = pgPagePdToPDI(page+i, ppd);
            if (ppte->pt_uvirt == 0) {

                ppd->pd_pcresident[pdi]++;                           //d25306
                undocount++;                                         //83438

                /*
                 *  We need aditional overcommit only if page is not
                 *  resident and not already locked.
                 */
                if (ppte->pt_present == 0 || (ppte->pt_resident == 0 &&
                    pgFrameToPF(ppte->pt_frame)->pf_llock == 0)) {

//d25306            ppd->pd_pcresident[pdi]++;
                    unlocked++;
                }
            /*
             *  For user uvirt pages, "lock" by incrementing page table
             *  resident/locked page count which will keep the page table from
             *  being swapped.  System uvirt pages have their resident
             *  count incremented at allocation time.
             */
            } else if (page < MINSYSPAGE) {
                ppd->pd_pcresident[pdi]++;
                undocount++;                                          //83438
            }
        }

        /*
         *  Request needed overcommit.  Block for it if needbe and the NOBLOCK
         *  flag isn't set and the request is for a short-term lock.
         */
        if (unlocked != 0) {
            if (err = pgCommit(unlocked, flags)) {
                goto undo_counts;
            }
        }

        /*
         *  Call pgLockPF to increment the counts,
         *  force pages present, and make the pages contiguous if requested.
         */
        err = pgLockPF(pptebase, page, pptda, npages, flags, SSToDS(&gpage));

//      while ((err = pgLockPF(pptebase, page, pptda, npages, flags,
//          SSToDS(&gpage))) == ERROR_GUARDPAGE) {
//          /*
//           * We hit a guard page.  Release the semaphores before calling
//           * the exception manager to avoid deadlock when the page manager
//           * is reentered.
//           */
//          pgClearSem(pptebase, npages);
//
//          aulExceptionInfo[0] = (flags & PG_W) ? XCPT_WRITE_ACCESS : XCPT_READ_ACCESS;
//          aulExceptionInfo[1] = gpage << PAGESHIFT;   // FaultAddr
//
//          XCPTBuildR3DispatcherStack(EXCSW,                     // Trap #
//                                     XCPT_GUARD_PAGE_VIOLATION, // Xcpt #
//                                     2,                         // Param Ct.
//                                     SSToDS(aulExceptionInfo),  // Params
//                                     FALSE,                     // fContinue
//                                     NULL);                     // pERepRec
//
//          if (err = pgTakeSem(pptebase, npages, flags)) {
//              break;
//          }
//      }

//83438        if (err && unlocked != 0) {
//83438            pgCommitFree(unlocked, PG_RESIDENT);
//83438            goto undo_counts;
//83438        }

       if (err) {
          if (unlocked != 0) {                                         //83438
             pgCommitFree(unlocked, PG_RESIDENT);                      //83438
             goto undo_counts;                                         //83438
          }                                                            //83438
          else {                                                       //83438
             if (undocount != 0) {                                     //83438
                goto undo_counts;                                      //83438
             }                                                         //83438
             else {                                                    //83438
                goto clearsem;                                         //83438
             }                                                         //83438
          }                                                            //83438
       }  /* END IF (err) */                                           //83438

    }

//83438    err = 0;

  unpin:
    pgUnpinPt(page, ppd, npages, flags);
    return(err);

    /*
     *  If the lock fails after incrementing the page table resident/locked
     *  counts, we have to loop back over the range and put them back
     *  they way they were.
     */
  undo_counts:
    for (ppte = pptebase, i = 0; i < npages; i++, ppte++) {
        pdi = pgPagePdToPDI(page+i, ppd);
        if (ppte->pt_uvirt == 0) {
            ppd->pd_pcresident[pdi]--;                               //d25306
//d25306
//          if (ppte->pt_present == 0 || (ppte->pt_resident == 0 &&
//              pgFrameToPF(ppte->pt_frame)->pf_llock == 0)) {
//              ppd->pd_pcresident[pdi]--;
//          }
        } else if (page < MINSYSPAGE) {
            --(ppd->pd_pcresident[pdi]);
        }
    }
  clearsem:
    pgClearSem(pptebase, npages);
    goto unpin;                      /* exit through common exit */
}


/***EP  PGUnlock - Unlock a range of pages.
 *
 *      PGUnlock releases a previous lock over the memory region
 *      imposed by PGLock.
 *
 *      ENTRY   page - starting virtual page number of region to unlock
 *              pptda - context
 *              npages - number of pages to unlock
 *              flags - PG_LONG if long-term lock, else 0
 *      EXIT    0 on success, else error code
 *
 *      if (long-term lock)
 *          for (each page in range)
 *              if (not uvirt)
 *                  decrement lock count
 *                  if (page is totally unlocked)
 *                      decrement page table resident count
 *                      increment count of local var "unlocked"
 *                  release resident overcommit for "unlocked" pages
 *              else if (user space uvirt)
 *                  decrement page table resident count
 *
 *          release resident overcommit for "unlocked" pages
 *
 *      else ; short term lock
 *          for (each page in range)
 *              if (not uvirt)
 *                  decrement lock count
 *          release short term overcommit
 */

int
PGUnlock(page, pptda, npages, flags)
page_t page;
struct ptda_s *pptda;
int npages;
int flags;
{
    register struct pte_s *ppte;
    register struct pf_s *ppf;
    int unlocked;
    struct pgdata_s *ppd;
    int pdi;
    struct pte_s *ppteend;
    int err;

    /*
     *  There should be no need to pin the page tables or take the semaphores
     *  of these pages if they are locked.
     */
    err = pgPageToPd(page, pptda, SSToDS(&ppd));
    pgAssert(err == 0);
    ppte = pgPagePdTopPte(page, ppd);
    ppteend = ppte+npages;

    /*
     *  Unlock Long-term...
     */
    if (flags & PG_LONG) {
        unlocked = 0;
        for (; ppte < ppteend; ++page, ++ppte) {
            pgAssert(ppte->pt_present);

            pdi = pgPagePdToPDI(page, ppd);

            if (ppte->pt_uvirt == 0) {
                ppf = pgFrameToPF(ppte->pt_frame);

                pgAssert((int) ppf->pf_llock > 0);

                if (--ppf->pf_llock == 0 && ppte->pt_resident == 0) {
                    unlocked++;
//d25306
//                  if (--(ppd->pd_pcresident[pdi]) == 0) {
//                      pgReleasePt(ppd, pdi, PG_RESIDENT);
//                  }
                }

                pgAssert (ppd->pd_pcresident[pdi] > 0);              //d25306
                if (--(ppd->pd_pcresident[pdi]) == 0) {              //d25306
                    pgReleasePt(ppd, pdi, PG_RESIDENT);              //d25306
                }                                                    //d25306
            /*
             *  User-space UVirt pages are long-term locked by incrementing
             *  their page table's resident count.  So you can guess how we
             *  unlock them.  System Uvirts are always resident, so we don't
             *  have to do anything for them.
             */
            } else if (page < MINSYSPAGE && --(ppd->pd_pcresident[pdi]) == 0) {
                pgReleasePt(ppd, pdi, PG_RESIDENT);
            }
        }

        /*
         *  Release resident overcommit, if there were any pages that
         *  lost their last long term lock.
         */
        if (unlocked) {
            pgCommitFree(unlocked, PG_RESIDENT);

//car01 Remove QResident counter
//car01/***PV - Perfview measurement hook
//car01 *
//car01 *      TRACE MINOR = <PVWEI_LQ_mem_QResident_1>,
//car01 *      TP    = @,
//car01 *      TYPE    = (PVW),
//car01 *      GROUP   = NOSTRICT
//car01 */
//car01      PvwLeaveQ(mem,QTResident,1); // bulk(npages) 51579
        }
    /*
     *  Unlock short-term...
     */
    } else {
        for (; ppte < ppteend; ++ppte) {
            pgAssert(ppte->pt_present);

            if (ppte->pt_uvirt == 0) {
                ppf = pgFrameToPF(ppte->pt_frame);
                pgAssert((int) ppf->pf_slock > 0);
                ppf->pf_slock--;
            }
            /*
             *  BUGBUG briansm (8-4-89) - nothing is being done to short-term
             *  lock uvirt pages.
             */
        }
        /*
         *  BUGBUG briansm (8-4-89) - don't need overcommit for uvirts
         */
        pgShortLockCommitFree(npages + pgNPagesToNPT(page, npages));
    }
    return(0);
}


/***LP  pgLockTSD - short-term lock a TSD that is in the swapper
 *
 *      ENTRY:  NONE
 *      EXIT:   NONE
 *
 *      get a page of overcommit
 *      wait for page semaphore
 *      increment lock count
 */
void
pgLockTSD(void)
{
    struct pf_s *ppf;

    pgAssert(TSDCBCOMMIT <= PAGESIZE);

    /*
     *  We can't wait for commit here without deadlocking, so just increment
     *  the variable.  This is OK because our pageable pad is bigger than
     *  MAXSIMULSWAP.
     */
    pgAssert(PADPAGES >= MAXSIMULSWAP);
    pgShortLockPages++;

    for (;;) {
        ppf = pgFrameToPF(pgPagePdTopPte((((page_t)pTSDCur) + TSDSTART) >>
                                           PAGESHIFT, &pgData)->pt_frame);
        if (ppf->pf_pvp->vp_busy == 0) {
            break;
        } else {
            ppf->pf_pvp->vp_wanted = 1;
            TKSleep(ppf->pf_pvp, -1, 1, 0);
        }
    }
    ppf->pf_slock++;
}


/***LP  pgTakeSem - Take page semaphores for a range of linear pages.
 *
 *      pgTakeSem takes the page semaphores for a range of linear pages.
 *      The appropriate page tables are assumed to be present.
 *
 *          - if busy, set wanted bit, block, and restart
 *          - if not busy, set busy bit
 *
 *      ENTRY   pptebase - base pte pointer
 *              npages - number of pages
 *              flags - PG_NOBLOCK      don't give up the cpu
 *                      PG_IGNOREINTS   ignore interrupts
 *                      PG_NOTIFYBLOCK  wait for sem,return err if blocked
 *                                      (must retry TakeSem)
 *      EXIT    0 on success, else error code
 *
 */
int
pgTakeSem(pptebase, npages, flags)
struct pte_s *pptebase;
int npages;
int flags;
{
    register struct pte_s *ppte;
    struct pte_s *ppteend;
    register struct vp_s *pvp;
    int err;

    ppte = pptebase;
    ppteend = ppte+npages;
    pgAssert((flags & PG_WAITSEM) == 0); /* not supported any more */

    for (; ppte < ppteend; ++ppte) {

        /*
         *  Quickly skip unallocated pages and uvirt pages
         */
  retrysem:
        if (ppte->pt_long) {
            if (ppte->pt_uvirt == 0) {
                /*
                 *  Find the vp
                 */
                if (ppte->pt_present) {
                    pvp = pgFrameToPF(ppte->pt_frame)->pf_pvp;
                } else {
                    pvp = pgFrameToVP(ppte->pt_frame);
                }

                /*
                 *  If the semaphore is currently clear, just take it and go on
                 */
                if (pvp->vp_busy == 0) {
                    pgSetSem(pvp);

                /*
                 *  If the semaphore is busy, block for it or return error if
                 *  requested.
                 */
                } else if (flags & PG_NOBLOCK) {
                    err = ERROR_NOBLOCK;
                    goto undo;

                } else {
                    pvp->vp_wanted = 1;
                    if (pvp == (struct vp_s *)SMPageSemHeld) {
                        TKDeclareInversion(SMCompactSlot);
                    }
                    if (err=TKSleep(pvp, -1, (flags & PG_IGNOREINTS) ? 1 : 0, 0)) {
                        goto undo;
                    }
                    if (flags & PG_NOTIFYBLOCK) {
                        err = ERROR_BLOCKED;
                        goto undo;
                    }
                    goto retrysem;
                }
            }
        /*
         *  The page is unallocated.  return error.
         */
        } else {
            err = ERROR_ACCESS_DENIED;
            goto undo;
        }
    }
    return(0);

    /*
     *  If we get an error part way through taking the semaphores, clear
     *  the sems we already took.
     */
  undo:
    npages = ppte - pptebase;
    if (npages) {
        pgClearSem(pptebase, npages);
    }
    return(err);
}


/***LP  pgClearSem - Clear page semaphores for a range of linear pages.
 *
 *      pgClearSem clears the page semaphores for a range of linear pages.
 *      The appropriate page tables are assumed to be present.
 *      Clearing an already clear semaphore is a nop and not an error.
 *
 *      WARNING: Do not clear a subset of the semaphores taken in a single
 *      pgTakeSem call.  This could result in a shared page mistakenly losing
 *      its semaphore.
 *
 *          - if wanted, call TKRun()
 *          - clear busy bit
 *
 *      ENTRY   ppte - pte pointer
 *              npages - number of pages
 *      EXIT    NONE
 */

void
pgClearSem(ppte, npages)
register struct pte_s *ppte;
int npages;
{
    struct pte_s *ppteend;
    register struct vp_s *pvp;

    for (ppteend = ppte+npages; ppte < ppteend; ++ppte) {

        /*
         *  Quickly skip unallocated pages and uvirt pages
         */
        if (ppte->pt_long && ppte->pt_uvirt == 0) {
            /*
             *  Find the vp
             */
            if (ppte->pt_present) {
                pvp = pgFrameToPF(ppte->pt_frame)->pf_pvp;
            } else {
                pvp = pgFrameToVP(ppte->pt_frame);
            }

            /*
             *  If the semaphore is busy, wakeup sleepers
             */
            pgClearSemVP(pvp);
        }
    }
}

