/*static char *SCCSID = "@(#)pgvp.c     6.1 90/11/17";*/


#define SCCSID  "@(#)pgvp.c    13.3 90/07/26"

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

#define FNO_NUM  FNO_PGVP_C
#include <fnomem.h>

#define INCL_DOSP
#include <os2p.h>
#define INCL_ERRORS
#include <bseerr.h>
#define INCL_TYPES
#include <basedef.h>
#include <osmac.h>
#include <smexport.h>
#include <pgexport.h>
#include <vmexport.h>
#include <tkexport.h>           /* for definition of KSEM */
#include <pglocal.h>
#include <vmowner.h>
#include <bseerr.h>


/***    VP free list
 *
 *      The vp free list is a circular, ordered, doubly linked list of vp
 *      bundles.  A vp bundle is composed of one or more free vp structures,
 *      with bundle information being stored in the first and last vp.  (The
 *      bundle information is nothing more than a set of links.)  The head
 *      vp in a bundle contains a pointer to the next bundle, and a pointer
 *      to the tail vp in the bundle.  The tail vp in a bundle holds only a
 *      pointer to the head vp of the previous bundle.  Thus to traverse
 *      backward in the list, we must go "through" the tail before reaching
 *      the previous bundle, and to traverse forward we simply walk from one
 *      bundle head to the next.
 *
 *      A bundle of size one is an exception--instead of the pointer to the
 *      tail vp, the head vp contains a pointer to the previous bundle.
 *
 *
 *         +------+        +------+     +------+     +------+        +------+
 *         |      +------->|      +---->|      +---->|      +------->|      |
 *      +--+      |<-+  +--+      |<----+      |<----+      |<-+  +--+      |
 *      |  |      |  |  |  |      |     |      |     |      |  |  |  |      |
 *      |  +------+  |  |  +------+     +------+     +------+  |  |  +------+
 *      |  |      |  |  |  |      |                            |  |  |      |
 *      |  |      |  |  |  |      |                            |  |  |      |
 *      |  |      |  |  |  |      |                            |  |  |      |
 *      |  +------+  |  |  +------+                            |  |  +------+
 *      |  |      |  |  +->|      |                            |  +->|      |
 *      |  |      |  +-----+      |                            +-----+      |
 *      |  |      |        |      |                                  |      |
 *      |  +------+        +------+                                  +------+
 *      +->|      |
 *         |      |
 *         |      |
 *         +------+
 *
 */

#pragma code_seg()

/***EP  pgAllocVP - allocate a vp
 *
 *      Returns a pointer to a vp structure.  The vp will be taken from
 *      the free list.  If the list is empty we panic.
 *
 *      - extract a frame from the free list
 *      - mark the frame as in-use
 *
 *      ENTRY   none
 *      EXIT    pointer to vp structure
 */

struct vp_s *
pgAllocVP(void)
{
    register struct vp_s *pvp;

    /*
     * Get a pointer to first bundle head.
     */
    pvp = pgVPHead.vp_flink;

    if (pvp->vp_blink == &pgVPHead) {
        /*
         * The bundle contains only a single vp.  Take it and reset the list
         * head to point to the next bundle.
         */
        pgVPHead.vp_flink = pvp->vp_flink;

        /*
         * Set the next bundle's back link to point to the list head.
         * The back link is in the head if the bundle is only a single vp,
         * otherwise the back link is in the tail.
         */
        if (pvp->vp_flink->vp_blink == pvp) {
            pvp->vp_flink->vp_blink = &pgVPHead;
        } else {
            pvp->vp_flink->vp_blink->vp_blink = &pgVPHead;
        }
    } else {
        /*
         * The bundle contains two or more vp's.  Take the bundle's tail vp.
         */
        ++pvp->vp_blink;
        if (pvp->vp_blink < pvp) {
            /*
             * The resulting bundle will contain two or more vp's.  Point to
             * the new tail and mark it as such.
             */
            pvp = pvp->vp_blink;
            pvp->vp_flink = 0;
            pvp->vp_refcount = 0;
        }
        pvp->vp_blink = &pgVPHead;
        --pvp;
    }
    pgAssert(pvp < pgpVPEnd - 1);       /* don't take either of last two vps */

#ifdef PGSTRICT
    pvp->vp_semowner = 0;
#endif
    pvp->vp_refcount = 1;                       /* mark the vp "in-use" */
    return(pvp);
}


/***EP  pgFreeVP - free a vp structure
 *
 *      pgFreeVP frees a vp structure for re-use.
 *
 *      - free any associated DF
 *      - place vp on free list
 *
 *      ENTRY   pvp - pointer to vp to be freed
 *      EXIT    none
 *
 */

void
pgFreeVP(pvp)
register struct vp_s *pvp;
{
    register struct vp_s *pvpadj;
    struct vp_s *pvpend;
    struct pf_s *ppf;

    /*
     * If there is a DF associated with this page, free it.
     */
    if (pvp->vp_df) {
        SMFreeDF(pvp->vp_block);
    }

    if (pvp->vp_flags & VP_PRIVATIZED) {
        pgFindPF(pvp, TRUE);
    }

    pvp->vp_refcount = 0;                       /* mark the vp free */

    if ((pvp+1)->vp_refcount == 0) {
        /*
         * We can coalesce this vp with a following bundle.
         */
        if ((pvp-1)->vp_refcount == 0) {
            /*
             * We can also coalesce with a preceeding bundle.
             */
            pvpadj = pvp-1;

            /*
             * Coalesce the preceeding bundle with the following bundle.
             * Find the bundle preceeding the immediately preceeding bundle
             * and adjust its forward link, and then adjust the back link
             * in the following bundle.
             */
            if (pvpadj->vp_blink->vp_flink) {
                /*
                 * Preceeding bundle contains only one vp.
                 */
                pvpadj->vp_blink->vp_flink = pvpadj->vp_flink;
                pvpadj->vp_flink->vp_blink = pvpadj;
                pvpadj->vp_flink = 0;
            } else {
                /*
                 * The preceeding bundle contains two or more vp's.
                 */
                pvpadj->vp_blink->vp_blink->vp_flink = pvpadj->vp_flink;
                pvpadj->vp_flink->vp_blink = pvpadj->vp_blink;
            }
        } else {
            /*
             * Coalesce with the following bundle only.
             */
            pvpadj = pvp+1;

            /*
             * The immediately following vp is either the tail or head of a
             * bundle.  Make this vp the tail by setting its back link to the
             * head of the previous bundle, and setting the following bundle's
             * head's back link to this vp.
             */
            pvp->vp_blink = pvpadj->vp_blink;
            pvp->vp_flink = 0;
            if (pvpadj->vp_flink) {
                pvpadj->vp_blink = pvp;
            } else {
                pvp->vp_blink->vp_flink->vp_blink = pvp;
            }
        }
    } else if ((pvp-1)->vp_refcount == 0) {
        /*
         * Coalesce with a preceeding bundle only.
         */
        pvpadj = pvp-1;

        /*
         * The immediately preceeding vp is a bundle head.  Make this vp the
         * new bundle head.
         */
        pvp->vp_flink = pvpadj->vp_flink;

        /*
         * Adjust the back link of the following bundle to the new head.
         */
        if (pvp->vp_flink->vp_blink == pvpadj) {
            pvp->vp_flink->vp_blink = pvp;
        } else {
            pvp->vp_flink->vp_blink->vp_blink = pvp;
        }

        /*
         * Adjust the forward link of the bundle preceeding the bundle we
         * are coalescing with to the new head.  If the bundle we are
         * coalescing with contains only one vp, then make that vp the tail.
         */
        if (pvpadj->vp_blink->vp_flink) {
            /*
             * Bundle size is one.  The head points directly to the previous
             * bundle.
             */
            pvpadj->vp_blink->vp_flink = pvp;
            pvp->vp_blink = pvpadj;
            pvpadj->vp_flink = 0;
        } else {
            /*
             * Bundle size is greater than one.  Go through the tail to get
             * to the previous bundle.
             */
            pvpadj->vp_blink->vp_blink->vp_flink = pvp;
            pvp->vp_blink = pvpadj->vp_blink;
        }
    } else {
        /*
         * We couldn't coalesce at all.  Insert the vp the ordered list as a
         * new bundle of size one.
         */
        pvpadj = pgVPHead.vp_flink;

        /*
         * Scan for the right place to insert in the list.
         */
        while (pvpadj < pvp) {
            pvpadj = pvpadj->vp_flink;
        }

        /*
         * Insert pvp preceeding pvpadj.
         */
        pvp->vp_flink = pvpadj;

        if (pvpadj->vp_blink->vp_flink == 0) {
            /*
             * Set pvpadj to the tail of the following bundle.
             */
            pvpadj = pvpadj->vp_blink;
        }
        pvp->vp_blink = pvpadj->vp_blink;
        pvpadj->vp_blink = pvp;
        pvp->vp_blink->vp_flink = pvp;
    }
}


/***LP  pgResizeVP - grow or shrink the vp array
 *
 *      This routine is called to commit or decommit pages on the vp array.
 *
 *      ENTRY   dpg - signed number of pages
 *      EXIT    0 if success, else error code
 */
int
pgResizeVP(dpg)
int dpg;
{
    int err;
    int npages;
    ulong_t lastpg;

    /*
     *  If this is a request for more than a page of vps, first call
     *  ourselve recursively to allocate vps for the new pages of vps
     */
    if (dpg > 1) {
        if (err = pgResizeVP(1)) {
            return(err);
        }
        dpg--;
    }

    pgAssert(pgVPHead.vp_blink == pgpVPEnd &&
             pgVPHead.vp_blink->vp_blink->vp_blink->vp_flink == pgpVPEnd);

    npages = pgVPEndPg + 1 - pgVPBasePg;
    pgAssert(npages + dpg > 0 && dpg != 0);
    lastpg = pgVPEndPg + 1;

    /*
     *  For VP array grows, commit the one page first to handle the vps
     *  for the new vp array pages.  Then commit the rest (if there is more).
     */
    if (dpg > 0) {
        if (err = PGAlloc(lastpg, 0, dpg, 0, pgVPHob, npages,
                                  PG_RESIDENT | PG_W | PG_R)) {
            return(err);
        }
    }

    pgVPEndPg += dpg;
    pgpVPEnd = pgpVPBase + (((npages + dpg) << PAGESHIFT) /
                            sizeof(struct vp_s)) - 1;
    pgAssert((ulong_t) pgpVPEnd >> PAGESHIFT == pgVPEndPg);
    pgVPHead.vp_blink->vp_blink->vp_blink->vp_flink = pgpVPEnd;
    *pgpVPEnd = *pgVPHead.vp_blink;
    pgVPHead.vp_blink = pgpVPEnd;

    if (dpg < 0) {
        if (PGFree(lastpg + dpg, 0, -dpg, 0)) {
            pgAssert(0);
        }
    }
    return(0);
}


ulong_t pgSavePF(struct vp_s *pvp, struct pf_s *ppf) {
    ulong_t rc;
    struct savePF_s *pSavePF;

    if ((rc = VMAllocKHB(VM_HKH_PUB_RESRW,
                         sizeof(struct savePF_s),
                         (VMHOB)PGPFOWNER,
                         NA,
                         SSToDS(&pSavePF))) == NO_ERROR) {

       pSavePF->pvp = pvp;
       pSavePF->ppf = ppf;

       /*
        * Link into list
        */
       if (pgpSavePF == NULL)
           pSavePF->next = NULL;
       else
           pSavePF->next = pgpSavePF;

       pgpSavePF = pSavePF;
   }

   return (rc);
}


struct pf_s *pgFindPF(struct vp_s *pvp, ulong_t free) {
   struct savePF_s *pSavePF,
                   *pprevSavePF;
   struct pf_s *ppf = NULL;

   pSavePF = pgpSavePF;
   pprevSavePF = (struct savePF_s *)(((char *)&pgpSavePF) -
       ((char *)&(((struct savePF_s *)0)->next)));

   while (pSavePF) {

       if (pSavePF->pvp == pvp) {

           ppf = pSavePF->ppf;

           if (free) {
               /*
                * Free PF, unlink from list, and free savePF_s structure
                */

               pgAssert(ppf->pf_busy);
               ppf->pf_busy = 0;
               pgFreePF(ppf, pgPFToFrame(ppf));
               ppf = NULL;

               pprevSavePF->next = pSavePF->next;
               VMFreeKHB(VM_HKH_PUB_RESRW, (void *)pSavePF);
           }

           break;
       }
       pprevSavePF = pSavePF;
       pSavePF = pSavePF->next;
   }

   return (ppf);
}
