#define SCCSID  "@(#)vmlock.c	6.16 92/03/26"

/*
 *       IBM/Microsoft Confidential
 *
 *       Copyright (c) IBM Corporation 1987, 1989
 *       Copyright (c) Microsoft Corporation 1987, 1989
 *
 *       All Rights Reserved
 *
 *  Modifications:
 *     Defect 104998 11/15/94 ChangeTeam Roll r206 defect 79144 into r207s.
 *     Defect 82818 2/7/95 ChangeTeam Moving r206 debug .mk command to r207.
 */

#define INCL_DOSERRORS
#define INCL_OS2STD
#include <os2.h>
#define INCL_DOSP
#include <os2p.h>
#define FNO_NUM         FNO_VMSHARED_C
#include <fnomem.h>
#include <osmac.h>
#include <mi.h>
#include <dossymc.h>
#include <sigdef.h>
#include <except.h>
#include <pgexport.h>
#include <vmexport.h>
#include <vmlocal.h>
#include <vmowner.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 <string.h>
#include <vmkh.h>
#include <mmph.h>

extern VMKH     apkh[hkh_MAX + 1];      /* Array of heap header pointers */

#ifdef PMDEBUG               /* 82818 */
VMLI   *pVMLIHead = 0;       /* 82818 */
uchar_t LogLockInfo = 1;     /* 82818 */
#endif                       /* 82818 */

/***EP  VMLockMem - Lock a region of an object
 *
 *      This function locks a region of a memory object.  If the VML_VERIFY
 *      flag was specified, a "verify lock" is performed: the vm lock counts
 *      are incremented to prevent the object from being freed or shrunk
 *      for the duration of the verify lock.  Permission flags may also be
 *      passed in during a verify lock.  The page level permissions of the
 *      memory region are compared to those passed in to verify that access to
 *      the region is allowed.
 *
 *      Non-verify locks force the pages in the region resident for
 *      the duration of the lock, in addition to blocking any
 *      frees/resizes.  The caller may request that the pages also be
 *      physically contiguous.  Such locks are limited to a maximum
 *      size of 64K.
 *
 *      int VMLockMem(ulong_t pregion, ulong_t cbregion, ulong_t fl,
 *                    struct vmlh_s *phlock,
 *                    struct pagelist_s *ppl,
 *                    ulong_t *pel)
 *
 *      ENTRY           pregion         - Virtual address of region to lock
 *                      cbregion        - Size of region to lock in bytes
 *                      fl              - Lock flags (VML_*)
 *                      phlock          - Rlace to put lock handle
 *                      ppl             - Pagelist_s array return pointer
 *                                        If set to VM_NO_ADDR no array is
 *                                        returned
 *                      pel             - Points to count of pagelist elements
 *      RETURN          int             - Result code (NO_ERROR if successful)
 *
 *
 *      CALLS           VMRegionSize, VMRegionBase, VMGetSem, VMClearSem,
 *                      VMGetOd, vmVDMTask, vmParToHar, PGLock, PGUnlock,
 *                      PGVerifyPermission, PGLinToPageList, TKSleep.
 *
 *      WARNING         A lock may be applied only to the current context.
 *
 *      EFFECTS         Changes lock counts in affected object records.
 */

int                     VMLockMem(pregion,cbregion,fl,phlock,ppl,pel)
register ulong_t        pregion;        /* Virtual address of region to lock */
ulong_t                 cbregion;       /* Size of region to lock */
ulong_t                 fl;             /* Lock flags */
struct vmlh_s           *phlock;        /* Place to return lock handle */
struct pagelist_s       *ppl;           /* Location to return PageList array */
ulong_t                 *pel;           /* Location to return number of
                                           elements in array*/
{
  ulong_t               fgs;            /* VMGetSem flags */
  ulong_t               ipg;            /* First page to lock */
  ulong_t               cpg;            /* Count of pages to lock */
  uchar_t               *pchLock;       /* Pointer to lock count */
  VMKHBI                khbi;           /* Kernel Heap block info */
  VMOD                  od;             /* Object data */
  VMHOB                 hob;            /* Handle to object record */
  int                   cloops = 0;     /* number of time through main loop */
  register int          rc;             /* Return code */
#ifdef VMSTRICT
  ulong_t               RetAddr;        // function return address
#endif
  struct  seldesc_s     desc;           /* LDT descriptor info structure */
  int                   numsel = 0;     /* num of selectors in packed object */
  int                   i;              /* local var */
  ulong_t               cbsize;         /* commit size of packed object */
  SEL                   sel;            /* selector */
  ulong_t               cpgTmp;         // aa04450
  ulong_t               pgflags;        // aa04450

  MMPHPreVMLock()                       /* Performance Hook (20/13) */

  vmAssert(cbregion != 0);              /* Cannot have zero-length region */

  /*
   *  Round region base address and size to page granularity and convert
   *  to page counts.  Set flags for VMGetSem.
   */

  cpg = (VMRegionSize(pregion, cbregion) >> PAGESHIFT);
  ipg = (VMRegionBase(pregion) >> PAGESHIFT);
  fgs = ((fl & VML_NOBLOCK)? VMGS_TAKESEM | VMGS_NOBLOCK : VMGS_TAKESEM);

  /*
   *  If this is a long-term lock request, disallow it if the region to be
   *  locked is on a kernel resident heap.  This allows decommit of free
   *  heap block pages to work without running into the problem that the
   *  decommit is not allowed because the heap has long locks.  Later, this
   *  restriction will be extended to the swappable heaps as well.
   */

  if(fl & VML_LONG)
  {
      khbi.khbi_pbData = (char *)(ipg << PAGESHIFT);
      if(vmPossWithinKH(khbi.khbi_pbData) &&
         VMGetKHBI(VMKHBI_HEAP, SSToDS(&khbi)) == NO_ERROR)
      {
          if(khbi.khbi_fl & PG_RESIDENT)
          {
              /* return(ERROR_ACCESS_DENIED); */

              /*
               *  BUGBUG - IDB 03/29/90 - We should be able to disallow
               *  long locks on the resident heap, but this currently is not
               *  possible because pmshell.exe locks pmdd.sys and netwksta.exe
               *  does long locks during net start rdr etc.  The rc = NO_ERROR
               *  serves as a place to set a breakpoint to catch the lockers.
               */

              rc = NO_ERROR;
          }
      }
      /* fgs |= VMGS_SLOCKWAIT; */
  }

  /*
   *  Main loop in which we try to lock the region.  Note that we
   *  only repeat the loop if we hit a short-term lock conflict when
   *  making a contiguous or long-term lock request (because page movement
   *  may be needed to satisfy the request).  The for loop enables us to
   *  conveniently go to error in cases where we need to release the
   *  object handle semaphore, by using break.
   */

  for(;;cloops++)                               /* Loop to lock region */
  {
      /*
       *  Get VM object data for the object instance or alias containing
       *  the given region, taking the object's semaphore at the same
       *  time.
       */

      od.od_laddr = ipg << PAGESHIFT;
      od.od_pPTDA = pPTDACur;
      if((rc = VMGetOd(fgs, SSToDS(&od))) != NO_ERROR)
      {
          MMPHPostVMLock()              /* Performance Hook (20/93) */
          return(rc);
      }

      hob = vmPobToHob(od.od_pob);

      /*
       *  If the caller can block, and some thread is waiting for short-term
       *  locks to clear, then postpone the lock for a bit.
       *  We only wait for a few seconds because devhlp locks cannot be
       *  trusted to go away quickly and the lock may have been placed by
       *  this very thread, in which case it will never go away!
       *
       *  "cloops" is the number of times we have gone through this code.  If
       *  it is greater than 0, we have already waited and there is no reason
       *  to do so again.
       */

      if( cloops == 0 && !(fl & VML_NOBLOCK) )                         //b790643
      {
          if (fl & VML_LONG)
          {
              if (((od.od_pob)->ob_xflags & VMOB_LLOCK_WAIT) == 0)
              {
                  goto no_waiters;
              }
          }
          else
          {
              if (((od.od_pob)->ob_xflags & VMOB_SLOCK_WAIT) == 0)
              {
                  goto no_waiters;
              }
          }

          VMResClearSem(od.od_pob);
          rc = TKSleep(&((od.od_pob)->ob_cslock),VMOB_CSLOCK_TIMEOUT, 0, 0);
          vmAssert(rc == NO_ERROR ||
                   rc == ERROR_TIMEOUT ||
                   rc == ERROR_INTERRUPT);
          continue;
      }

no_waiters:;
      /*
       *  If not a VDM, check that the specified region resides entirely
       *  within one object.  VDM's are allowed to lock regions that
       *  cross object boundaries.
       */

      if(!vmVDMTask(pPTDACur) && ipg + cpg >
        (od.od_par)->ar_ipg + (od.od_par)->ar_cpg)
      {
          rc = ERROR_CROSSES_OBJECT_BOUNDARY;
          break;                        /* free object sem4 and return */
      }

      /*
       *  Set pointers to short or long-term lock counts and devhlp
       *  lock counts in object record.
       */

      if(fl & VML_LONG)
      {
          pchLock = &((od.od_pob)->ob_cllock);
      }
      else
      {
          pchLock = &((od.od_pob)->ob_cslock);
      }

      /*
       *  Check if short or long-term lock count overflow would occur.
       */

      if(*pchLock == VMOB_CLOCK_MAX)
      {
          rc = ERROR_BUSY;
          break;                        /* free object sem4 and return */
      }

      /*
       *  Try to lock the pages.
       *  For "hard" locks, call PGLock().  For "verify" ("soft") locks,
       *  call PGVerifyPermission().
       */

      if(fl & VML_VERIFY)
      {
          rc = PGVerifyPermission(ipg, pPTDACur, cpg, fl);
      }
      else
      {
      /*
       * KDH - If the region to be locked (long-term) is in the PACKSHR
       *       region, we should lock down the entire packed object.
       */
          if((fl & VML_LONG) && ipg >= vmPackedLBDRY >> PAGESHIFT &&
                                ipg < parPackedSearchStart->ar_ipg)
          {
              ipg = (od.od_par)->ar_ipg;
              sel = LaToSelTiled ((od.od_par)->ar_ipg << PAGESHIFT);
              numsel = (od.od_par)->ar_cpg >> TILEDSEGSHIFT;
              cbsize = 0;
              for ( i = 1; i <= numsel; i++ )
              {
                  rc = SELGetDescInfo(sel, pPTDACur->ptda_handle, SSToDS(&desc));
                  vmAssert(rc == NO_ERROR);
                  cbsize += desc.desc_cb;
                  sel += SEL_INCR;
              }
              cpg = (VMRegionSize(ipg << PAGESHIFT, cbsize) >> PAGESHIFT);
          }
retrystack:;
          rc = PGLock(ipg, pPTDACur, cpg, fl);
          // start aa04450
          if ( ((rc == ERROR_ACCESS_DENIED) || (rc == ERROR_GUARDPAGE)) &&
                 (pTCBCur->TCBpStack <= pregion) &&
                 ((pTCBCur->TCBpStack+pTCBCur->TCBcbStackMax) > pregion) )
          {
             /* if the rc is Access_Denied and the region falls within the
              *  threads stack, commit the stack on behalf of the caller
              *  1) query the address to find out how much  of the stack is
              *  committed, can't trust the TCBcbStackCur var. since its only
              *  updated with the original stack allocation
              */
             if (rc == ERROR_GUARDPAGE)  {
                 ipg++;
                 cpg--;
                 goto retrystack;
             }
             cpgTmp = cpg;
             rc = PGQueryAccess(ipg, pPTDACur,SSToDS(&cpgTmp),SSToDS(&pgflags));
             if (rc == 0 && pgflags == 0) {
                 /* allocate the pages on the stack that have
                  *    not been committed
                  */
                 vmAssert(cpgTmp>0);
                 rc = PGAlloc(ipg, od.od_pPTDA, cpgTmp, -1,hob,
                                      ipg - (od.od_laddr >> PAGESHIFT),
                                     PG_W | PG_U | PG_R | PG_ZEROFILL);
                 if (rc == 0) {
                    /* change the setting on the guard page
                     */
                     rc = PGSetAccess(ipg+cpgTmp, pPTDACur, 1,
                                   PG_W | PG_U | PG_R | PG_ZEROFILL);
                     if ( (rc==0) &&
                          (ipg > (pTCBCur->TCBpStack >> PAGESHIFT)))
                     {                                               // D79144
                        /* if the entire stack has not been committed
                        we must setup the next guard page */

                        ulong_t  pg1page = 1;     // check 1 page for guard  D79144
                        ulong_t  pgguardflags = PG_GUARD| PG_W| PG_U| PG_R| PG_ZEROFILL; // guard flags D79144

                        /*we cannot assume that the next page is unallocated  D79144 */

                        rc = PGQueryAccess(ipg, pPTDACur,SSToDS(&pg1page),SSToDS(&pgguardflags));

                        /* D79144: If next page is not guard aready make it */
                        /* guard, then goto retrystack. Else if it is guard */
                        /* already, goto retrystack without calling pgalloc.*/

                        if (rc != 0)                                 // D79144
                        {                                            // D79144
                           rc = PGAlloc(ipg-1, od.od_pPTDA, cpgTmp, -1,hob,
                                   (ipg-1) - (od.od_laddr >> PAGESHIFT),
                                   PG_GUARD| PG_W| PG_U| PG_R| PG_ZEROFILL);
                        }

                     }                                               // D79144
                 }
             }
             if (rc == 0) goto retrystack;
          }
          // end aa04450
      }

      /*
       *  If the PG worker succeeded, fill in the lock handle and
       *  break out of the loop so we can exit.
       */

      if(rc == NO_ERROR)
      {
          phlock->lh_hptda = (ushort_t) pPTDACur->ptda_handle;
          phlock->lh_hob = hob;
          phlock->lh_page = ipg;
          phlock->lh_npages = (ushort_t) cpg;
          phlock->lh_flags = (fl & VML_FLAGMASK) >> VML_FLAGSHIFT;
          phlock->lh_sig = vmLockHandleSig(phlock);

          (*pchLock)++;

/*** Start 82818 ******/
#ifdef PMDEBUG
          if (LogLockInfo) {
             VMLI *pvmli;
             ulong_t tmp;

             if(VMAllocKHB(VM_HKH_PUB_RESRW,
                           sizeof(struct vmli_s),
                           (VMHOB) VMLLOCKOWNER,
                           (ulong_t)  NA,
                           (VMKHBI *) SSToDS(&pvmli)) == NO_ERROR) {

                if (pTCBCur->TCBpDHRetAddr) {
                   pvmli->eip = *(pTCBCur->TCBpDHRetAddr);
                   pvmli->cs = *(pTCBCur->TCBpDHRetAddr+1);
                }
                else {
                   pvmli->eip = vmRetAddr();
                   _asm {
                      mov   ax,cs
                      movzx eax,ax
                      mov   tmp,eax
                   }
                   pvmli->cs = tmp;
                };
                pvmli->phlock = phlock;
                pvmli->lh_page = phlock->lh_page;
                pvmli->lh_flags = phlock->lh_flags;
                pvmli->lh_hptda = phlock->lh_hptda;
                pvmli->lh_npages = phlock->lh_npages;
                pvmli->lh_hob = phlock->lh_hob;
                pvmli->lh_sig = phlock->lh_sig;
                pvmli->next = pVMLIHead;
                pVMLIHead = pvmli;
             }
             else
               panic("Error allocating lock info");

          }
#endif

/*** End 82818 **************/

#ifdef VMSTRICT
          if(vmLogFlags & vmLogLocks)
          {

              RetAddr = vmRetAddr();
              printf("L base %lx  size %x flags %x  hob %x hptda %x ret %lx\n",
                          phlock->lh_page, phlock->lh_npages, phlock->lh_flags,
                          phlock->lh_hob, phlock->lh_hptda, RetAddr);
          }
#endif

          /*
           *   Return PageList array if requested
           */

          if(!(fl & VML_VERIFY) && ((ulong_t) ppl != VM_NO_ADDR))
          {
              *pel = PGLinToPageList(pregion, pPTDACur, cbregion, ppl);
              vmAssert(*pel != -1);
          }

          break;                        /* free object sem4 and return */
      }

      /*
       *  Error from PGLock or PGVerifyPermission.  Check for conflicting lock.
       */

      if(rc == ERROR_SHORTLOCK && !(fl & VML_NOBLOCK))
      {
          int cTimeOut;

          /*
           *  The lock request failed because of short-term locks conflicting
           *  with a request for contiguous pages or a long-term lock request
           *  and we are allowed to wait.  Set flag to wait for any unlock to
           *  occur, release the object handle semaphore, and try again.  Note
           *  that object may still be locked, but the conflicting lock(s) may
           *  have gone away.  Limit the wait if only short-term locks are
           *  "untrusted" devhlp locks.
           */

          (od.od_pob)->ob_fs |= OB_LOCKWAIT;
          cTimeOut = ((od.od_pob)->ob_xflags & VMOB_SLOCK_WAIT)
                     ? VMOB_CSLOCK_TIMEOUT : -1L;
          VMResClearSem(od.od_pob);
          if((rc = TKSleep(&((od.od_pob)->ob_fs),cTimeOut,0,0)) == NO_ERROR)
          {
              continue;                 /* repeat from beginning */
          }

          if(rc == ERROR_TIMEOUT)
          {
              #ifdef VMSTRICT
                printf("VMLOCK: Short Term Lock for > 10 secs: hob=%lx\n",hob);
              #endif
              rc = ERROR_LOCKED;
          }
          else
          {
              vmAssert(rc == ERROR_INTERRUPT);
          }

          /*
           *  We've already freed the object semaphore, and hence can't go
           *  through the normal exit cleanup code, so return an error
           *  straight from here.
           */

          MMPHPostVMLock()              /* Performance Hook (20/93) */
          return(rc);
      }

      /*
       *  Error from PGLock or TKSleep.  Break out of loop to clear handle
       *  semaphore and return.
       */

      break;
  }

  /*
   *  If a long-term lock request, record the long-term lock info.  Undo
   *  the lock if unable to record the info, and decrement lock counts.
   */

  if((rc == NO_ERROR) && (fl & VML_LONG))
  {
      if((rc = vmAccessLongLockInfo(vmParToHar(od.od_par),
                                          pPTDACur->ptda_handle,
                                          vmlt_RECORD)) != NO_ERROR)
      {

#ifdef VMSTRICT
          printf("L Access Long Lock Info Failed\n");
#endif
          if(!(fl & VML_VERIFY))
          {
              PGUnlock(ipg, pPTDACur, cpg, fl);
          }

          (*pchLock)--;
      }
  }

  /*
   *  The lock request succeeded, or failed after the object handle
   *  semaphore was taken. Free the semaphore and return.
   */

  VMResClearSem(od.od_pob);
  MMPHPostVMLock()              /* Performance Hook (20/93) */
  return(rc);
}


/***LP  vmAccessLongLockInfo - Access Long Lock Info
 *
 *      This function manages information held by VM for long-term locks.
 *      There are 3 subfunctions; insert lock record, erase lock record
 *      and check for existence of lock record.
 *      WARNING: This function must remain in resident code.
 *
 *      int vmAccessLongLockInfo ( har, hobptda, fl )
 *
 *      ENTRY   VMHAR   har             - arena record for object
 *              VMHOB   hobptda         - PTDA handle
 *              ulong_t fl              - flags
 *      RETURN  int                     - return code (NO_ERROR if successful)
 */

struct vmlt_s {
                  VMHAR lt_har;
                  VMHOB lt_hobptda;
              };

struct vmlt_s  *pvmlt;          // pointer to table of long-term lock handles
ulong_t        cvmltMax = 0;    // long-term lock handle available
ulong_t        cvmltUsed = 0;   // long-term lock handles currently in use

int vmAccessLongLockInfo(har, hobptda, fl)
VMHAR           har;
VMHOB           hobptda;
ulong_t         fl;
{
  ulong_t               cvmltIndex;
  ulong_t               cvmltNewMax;
  int                   rc = 0;
  VMKRH                 *pkrh;

  struct vmlt_s *pvmltEntry, *pvmltNew, *pvmltOld;

  /*
   *  For system arena long-term locks, hobPTDA is recorded as HOBNULL.
   *  This allows system arena long-term locks to be removed by any context.
   */

  if(VMSysArenaPg((vmArMap(har))->ar_ipg))
  {
      hobptda = HOBNULL;
  }

  switch(fl)
  {
    case vmlt_RECORD:
      if(cvmltUsed == cvmltMax)
      {
          /*
           *  No room in the long lock table. Need to grow the table.
           *  Get the object semaphore for the long lock table. Return
           *  if interrupted wait error.
           */

          pkrh = apkh[VM_HKH_PUB_RESRW].kh_pkrh;

          if ((rc = KSEMRequestMutex((HKSEM)&(pkrh->krh_ksem), -1))
                            != NO_ERROR)
          {
              return(rc);             /* return if ERROR_INTERRUPT */
          }

          /*
           *  Now have the object semaphore. The state of the table may have
           *  changed if we had to wait on the semaphore. If there still
           *  are no entries available then allocate a larger table, copy
           *  the old table to the new table and free the old table.
           */

          if(cvmltUsed == cvmltMax)
          {
              cvmltNewMax = cvmltMax + vmlt_DELTA;

              if((rc = VMAllocKHB(VM_HKH_PUB_RESRW,
                                      cvmltNewMax * sizeof(struct vmlt_s),
                                      (VMHOB)    VMLLOCKOWNER,
                                      (ulong_t)  NA,
                                      (VMKHBI *) SSToDS(&pvmltNew)))
                                          == NO_ERROR)
              {
                  /*
                   *  Copy the old lock table to the new one.  Free the old.
                   *  Fill remainder of new block with zeros.
                   */

                  if(cvmltUsed > 0)
                  {
                      memcpy(pvmltNew, pvmlt, cvmltMax*sizeof(struct vmlt_s));
                      memset((((char *)(pvmltNew))+
                                       cvmltMax*sizeof(struct vmlt_s)), 0,
                                       (cvmltNewMax - cvmltMax) *
                                         sizeof(struct vmlt_s));
                  }

                  pvmltOld = pvmlt;
                  pvmlt = pvmltNew;
                  cvmltMax = cvmltNewMax;

                  if (cvmltUsed > 0)
                  {
                      VMFreeKHB(VM_HKH_PUB_RESRW, pvmltOld);
                  }
              }

              /*
               *  Clear semaphore and make waiters ready to run.  Note that
               *  they won't actually run until we're done updating the lock
               *  table.
               */

          }
          KSEMReleaseMutex((HKSEM)&(pkrh->krh_ksem));  // b700000
          /*
           *  Return if an error occurred while trying to grow the table.
           */

          if(rc != NO_ERROR)
          {
              return(rc);
          }
      }

      /*
       *  Scan the lock table for a free slot.  Note that we will succeed
       *  since we have not yielded since we released the semaphore.
       */

      for(cvmltIndex=0,pvmltEntry=pvmlt;
          cvmltIndex < cvmltUsed && pvmltEntry->lt_har != HARNULL;
          cvmltIndex++,pvmltEntry++);

      /*
       *  Record the entry and update table used
       */

      pvmltEntry->lt_har = har;
      pvmltEntry->lt_hobptda = hobptda;
      cvmltUsed++;
      break;

    case vmlt_ERASE:
    case vmlt_CHECK:

      /*
       *  Locate the entry to be erased
       */

      vmAssert(cvmltUsed > 0);
      for(cvmltIndex=0,pvmltEntry=pvmlt;
          cvmltIndex < cvmltMax &&
            (pvmltEntry->lt_har != har || pvmltEntry->lt_hobptda != hobptda);
        cvmltIndex++,pvmltEntry++);

      if(fl & vmlt_ERASE)
      {
          /*
           *  Return an error if there is no long-term lock for
           *  the specified (arena record handle, ptda pseudo-handle)
           */

          if(cvmltIndex == cvmltMax)
          {
              vmAssert(0);                     // No Long-Lock for handle
              return(ERROR_INVALID_HANDLE);
          }
          pvmltEntry->lt_har = HARNULL;
          cvmltUsed--;
      }
      else
      {
          if(cvmltIndex < cvmltMax)
          {
              rc = ERROR_LONGLOCK;
          }
      }
  }

  return(rc);
}


/***EP  vmLockHandleSig - Lock Handle Signature
 *
 *      Return the signature for a lock handle.
 *
 *      ushort_t VMLockHandleSig(VMLH *phlock)
 *
 *      ENTRY           phlock          - Pointer to lock handle
 *      RETURN          ushort_t        - Lock Handle Signature
 *
 *      CALLS           VMLockMem, VMUnlock
 *
 *      WARNING         NONE
 *
 *      EFFECTS         Returns lock handle signature.
 */

ushort_t                vmLockHandleSig(phlock)
register VMLH *phlock;                          // Pointer to Lock handle
{
  return((ushort_t) (phlock->lh_hptda +
                     phlock->lh_npages +
                     phlock->lh_hob +
                     (ushort_t) (phlock->lh_page / _64K) +
                     (ushort_t) (phlock->lh_page % _64K) +
                     (ushort_t) phlock->lh_flags));
}

#define vmLockWaiters      -1

/***EP  VMUnlock - Unlock a Region of an Object
 *
 *      This function unlocks a locked region of a memory object.
 *      Except for VDM's, the locked region will reside entirely within
 *      one object.
 *
 *      int VMUnlock(VMLH *phlock)
 *
 *      ENTRY           phlock          - Pointer to lock handle
 *      RETURN          int             - Result code (NO_ERROR if successful)
 *
 *
 *      CALLS           VMGetSem, VMClearSem, vmAccessLongLockInfo,
 *                      vmPobToHob, vmParToHar, PGUnlock, TKWakeup
 *
 *      WARNING         NONE
 *
 *      EFFECTS         Changes the lock counts in the object records.
 *                      Wakes waiting threads.
 */

int                     VMUnlock(phlock)
register VMLH *phlock;                  // Pointer to Lock handle
{
  ulong_t               fl = 0;         // flags set from lockhandle
  struct ptda_s         *pPTDA;         // Pointer to PTDA for lock's ctxt
  VMHOB                 hob;            // Object record handle
  VMOB                  *pob;           // Pointer to object record
  register uchar_t      *pchLock;       // Pointer to lock count
  VMHOB                 hobPTDA;        // PTDA of task that issued the lock
  ulong_t               ipg;            // Start virtual page of lock
  int                   rc;             // Return code
  VMOD                  od;             // VM Object Data
#ifdef VMSTRICT
  ulong_t               RetAddr;
#endif

  MMPHPreVMUnlock()                     // Performance Hook (20/14)

  /*
   *  Verify lock handle
   */

  rc = ERROR_INVALID_HANDLE;

  if((phlock->lh_sig == vmLockHandleSig(phlock)) ||
     (phlock->lh_npages == 0))
  {

      /*
       *  Take the semaphore of the object (handle is in the lock handle)
       *  and get a pointer to the object record.
       */

      if((rc = VMGetSem((hob = phlock->lh_hob),
                    VMGS_IGNINT+VMGS_TAKESEM+VMGS_VERIFY,
                    SSToDS(&pob))) == NO_ERROR)
      {
          /*
           *  Set pointer to short term lock count in object record.
           *  Change it later if this is an unlock of a long-term lock.
           */

          pchLock = &(pob->ob_cslock);
          ipg = phlock->lh_page;

          /*
           *  If not a system arena address range, get the context under which
           *  the lock was placed.  That context must still exist, because tasks
           *  wait for locks that they have placed on shared or private address
           *  space to clear before terminating.  For system arena locks, the
           *  context under which they were placed is not relevant.
           */

          if(!VMSysArenaPg(ipg))
          {
              hobPTDA = phlock->lh_hptda;
              pPTDA = VMPseudoHandleMap(hobPTDA);
          }
          else
          {
              hobPTDA = HOBNULL;
              pPTDA = NA;
          }

          /*
           *  Determine the type of lock request that we are trying to undo from
           *  the Lock Handle.  Note that we need to remember this information
           *  before we call the PGUnlock routine since the Page Manager will
           *  zeroise the lock handle.
           */

          if(phlock->lh_flags & VMLH_LONG)
          {
              fl |= VML_LONG;                       /* Was long term lock */
              pchLock = &(pob->ob_cllock);

              od.od_laddr = ipg << PAGESHIFT;
              od.od_pPTDA = pPTDA;
              if((rc = VMGetOd(NA, SSToDS(&od))) == NO_ERROR)
              {
                  rc = vmAccessLongLockInfo(vmParToHar(od.od_par),
                                            hobPTDA, vmlt_ERASE);
              }
          }


          if  (rc == NO_ERROR)                       // no errors
          {

              /*
               *  Check that object is locked.
               */

              if(*pchLock > 0)
              {
                  /*
                   *  If this lock was a full lock, not just a verify lock,
                   *  call Page manager to unlock the pages.
                   */

                  if(!(phlock->lh_flags & VMLH_VERIFY))
                  {
                      rc = PGUnlock(ipg, pPTDA, phlock->lh_npages, fl);
                      vmAssert(rc == NO_ERROR);
                  }

                  if(rc == NO_ERROR)
                  {
                      (*pchLock)--;

/*** Start 82818 ******/
#ifdef PMDEBUG
                      if (LogLockInfo) {
                         VMLI *pvmli, *pprevvmli;
                         int found = 0;

                         for (pprevvmli = (VMLI *)&pVMLIHead,
                              pvmli = pVMLIHead;
                              pvmli != NULL;
                              pprevvmli = pvmli,
                              pvmli = pvmli->next) {

// 80048                    if ((pvmli->phlock == phlock) &&
                            if ((pvmli->lh_page == phlock->lh_page) &&
                                (pvmli->lh_flags == phlock->lh_flags) &&
                                (pvmli->lh_hptda == phlock->lh_hptda) &&
                                (pvmli->lh_npages == phlock->lh_npages) &&
                                (pvmli->lh_hob == phlock->lh_hob) &&
                                (pvmli->lh_sig == phlock->lh_sig)) {

                               found++;
                               pprevvmli->next = pvmli->next;
                               VMFreeKHB(VM_HKH_PUB_RESRW, pvmli);
                               break;
                            }
                         }

                         if (!found)
                            panic ("VM Lock info not found");
                      }
#endif
/*** End 82818 *****/


#ifdef VMSTRICT
                      if(vmLogFlags & vmLogLocks)
                      {

                          RetAddr = vmRetAddr();
                          printf("U base %lx  size %x flags %x  hob %x hptda %x ret %lx\n",
                                  phlock->lh_page, phlock->lh_npages,
                                  phlock->lh_flags, phlock->lh_hob,
                                  phlock->lh_hptda, RetAddr);
                      }
#endif

                      if(*pchLock == 0)               // lock count 0???
                      {
                          /*
                           *  If someone is waiting for locks to clear, reset
                           *  waiting flag and awaken waiters.
                           */

                          if(pob->ob_fs & OB_LOCKWAIT)
                          {
                              pob->ob_fs &= ~OB_LOCKWAIT;
                              TKWakeup(&(pob->ob_fs),0,NULL);
                          }

                          /*
                           *  If there are threads waiting for any unlock
                           *  event set indicator to awaken them.
                           */

                          if(fl & VML_LONG)
                          {
                              if(pob->ob_xflags & VMOB_LLOCK_WAIT)
                              {
                                  pob->ob_xflags &= ~VMOB_LLOCK_WAIT;
                                  fl = vmLockWaiters;
                              }
                          }
                          else
                          {
                              if (pob->ob_xflags & VMOB_SLOCK_WAIT)
                              {
                                  pob->ob_xflags &= ~VMOB_SLOCK_WAIT;
                                  fl = vmLockWaiters;
                              }
                          }
                      }
                  }
              }
              else                                // object not locked
              {
                  vmAssert(0);
                  rc = ERROR_NOT_LOCKED;
              }
          }

          VMClearSem(hob);

          /*
           *  if waiters for unlock event, awaken them and then sleep to
           *  give them a chance to get the object. (this thread could lock
           *  the object again before yielding).
           */

          if(fl == vmLockWaiters)                                   //b790643
          {                                                         //b790643
              TKWakeup(pchLock,0,NULL);                             //b790643
              TKSleep((void *)&(pob->ob_fs),                        //b790643
                          (ULONG)VMOB_CSLOCK_TIMEOUT >> 4,          //b790643
                          (ULONG) 0, (ULONG) 0);                    //b790643
          }                                                         //b790643
      }
  }

  vmAssert(rc == NO_ERROR);

  MMPHPostVMUnlock()                             // Performance Hook (20/94)

  return(rc);
}
