/*static char *SCCSID = "@(#)vdmctrl.c  6.1 90/11/17";*/
#define SCCSID "@(#)vdmctrl.c   13.63 90/07/29"

/***    VDMCTRL.C
 *
 *      IBM/Microsoft Confidential
 *
 *      Copyright (c) IBM Corporation 1987, 1989
 *      Copyright (c) Microsoft Corporation 1987, 1989
 *
 *      All Rights Reserved
 *
 *
 *      Title:
 *
 *      VDMCTRL  Exported Procedures
 *
 *      Modification History:
 *      14-Oct-1988 sudeepb Initial version
 *      08-Feb-1989 bens    Updated function headers for review
 */
#define FNO_NUM  FNO_VDMCTRL_C
#include <fnomvdm.h>

#define         INCL_NONE
#define         INCL_VDHALL
#define         INCL_VDDHELPERS

#ifdef MMIOPH
  #include <mmph.h>
#endif
#include        <vdmvdh.h>
#include        <sched.h>

extern  ULONG   vdmid_ulFreezeCnt;
extern  ULONG   vdmid_sig;

extern  VOID            VDMCleanup();
extern  VOID PRIVENTRY  vdmmSetState(PTCB, BOOL);

#pragma BEGIN_MVDM_INSTANCE_DATA

/***    VDHSetPriority data structures
 *
 *      ulCurrentClass is the current priority class this VDM
 *      is running at.
 *
 *      acPriorityReference is the number of VDDs that has started
 *      using a priority class.
 *
 *      alPriorityLevel is an array of priority level by class
 *      number.  The "lDelta" is added to this value.  Note that
 *      the level value set outside of VDHSetPriority is lost.
 *
 *      aulPriorityClassOrder is the actual order to search for
 *      a class with a non-zero reference count.  The last entry
 *      (VDHSP_NUM_CLASSES) is the "default" class and is used
 *      if no VDD has "used" a class.
 *
 *      In all these arrays the zero (0) index is not used, but
 *      reserved so when the class number is used as an index
 *      it doesn't need to be decremented.
 *
 *      This data can not be swappable because preempting in the
 *      middle of VDHSetPriority could leave these data structures
 *      inconsistent and VDHSetPriority can be called out of context.
 */

ULONG   ulCurrentClass = VDHSP_REGULAR;
ULONG   acPriorityReference[VDHSP_NUM_CLASSES + 1] = { 0, 0, 0, 0, 0, 0 };
LONG    alPriorityLevel[VDHSP_NUM_CLASSES + 1]  = { 0, 0, 0, 0, 0, 0 };
ULONG   aulPriorityClassOrder[VDHSP_NUM_CLASSES + 1] = {
            VDHSP_TIME_CRITICAL,
            VDHSP_SIMULATED_INTERRUPT,
            VDHSP_SERVER,
            VDHSP_REGULAR,
            VDHSP_IDLE,
            VDHSP_REGULAR,                      // Default Class
};

#pragma END_MVDM_INSTANCE_DATA

#pragma BEGIN_MVDM_SWAP_CODE

/***EP+ VDHKillVDM - Terminate a VDM
 *
 *      The VDM will be terminated at the earliest opportunity.
 *      No more V86 code will be executed in the context of the VDM.
 *
 *      Note that this call just sets a flag and returns.  If the calling
 *      VDD is in the context of the VDM to be killed, then it must return
 *      to its caller to cause the kill to occur.
 *
 *      ENTRY
 *          hvdm -  VDM handle to which the object belongs
 *              0 => current VDM
 *
 *      EXIT-SUCCESS
 *          None
 *
 *      EXIT-FAILURE
 *          System Halt.
 *              If hvdm is not a valid HVDM.
 *
 *      CONTEXT
 *          Task
 *
 *      VDM TERMINATION IMPLICATIONS
 *          When a VDM is dying, all VDDs that registered a VDM_TERMINATE
 *          hook (with VDHInstallUserHook) will be called.
 *
 *      PCODE
 *          Mark the process to die
 *          return
 */

VOID VDHENTRY VDHKillVDM(hvdm)
HVDM          hvdm;
/*end*/
{
#ifdef MMIOPH
    MMPHPreVDHKillVDM()
#endif
    // Validate calling context
    ASSERTTASKONLY;
    CHECKHVDM(hvdm);
    TKDieSoonPTDA(QUERYPPTDA(hvdm), 0, TC_HARDERR,0);
#ifdef MMIOPH
    MMPHPostVDHKillVDM()
#endif
    return;
}

/***EP+ VDHHaltSystem - MVDM System Halt
 *
 *      System Halt.
 *
 *      ENTRY
 *          None.
 *
 *      EXIT-SUCCESS
 *          None.
 *
 *      EXIT-FAILURE
 *          None.
 *
 *      NOTES
 *          VDHPopup/VDHKillVDM is the preferred method for identifying
 *          a problem.  This service should be used only if the VDD is
 *          certain that all of MVDM is inoperable.  In general, this
 *          call should only be made by a "base" VDD, whose absence would
 *          render MVDM unusable, e.g., the video VDD or keyboard VDD.
 *
 *          VDHPopup should be called prior to VDHHaltSystem to tell the
 *          user what happened.
 *
 *      CONTEXT
 *          Task
 *
 *      PCODE
 *          Call Panic to do System Halt
 */

VOID VDHENTRY VDHHaltSystem(VOID)
/*end*/
{
#ifdef MMIOPH
    MMPHPreVDHHaltSystem()
#endif
    // Validate calling context
    ASSERTTASKONLY;

    Panic(("VDHHaltSystem"));
#ifdef MMIOPH
    MMPHPostVDHHaltSystem()
#endif
}

/***EP+ VDHPanic - DEVELOPMENT KERNEL ONLY
 *
 *      Used when bad parameters or other problems in internal consistency
 *      have been detected by a VDD.  This service displays the given message
 *      on the debugging terminal, if one is available, optionally passing
 *      control to the kernel debugger.  If the kernel debugger is not
 *      available, then the system is halted, after displaying the given
 *      message on the primary display.
 *
 *      By removing this service from retail kernels, it will not be possible
 *      to load VDDs that make use of it.  All panic-related support should
 *      be included for development builds only.
 *
 *      ENTRY
 *          pszMessage - warning/error message
 *
 *      EXIT-SUCCESS
 *          None.
 *
 *      EXIT-FAILURE
 *          None.
 *
 *      VDM TERMINATION IMPLICATIONS
 *          None.
 *
 *      CONTEXT
 *          Any
 */

/***EP+ VDHYield - yield the processor
 *
 *      Yield the processor to any other thread of equal or higher priority.
 *
 *      ENTRY
 *          flOptions - yield options
 *              VDH_YIELD_TIME_CRITICAL - yield only to time critical threads
 *
 *      EXIT-SUCCESS
 *          None.
 *
 *      EXIT-FAILURE
 *          None.
 *
 *      CONTEXT
 *          Task
 *
 *      VDM TERMINATION IMPLICATIONS
 *          None.
 *
 *      NOTE
 *          If flOptions is VDH_YIELD_TIME_CRITICAL and no time
 *          critical thread is runnable, the caller will keep the CPU
 *
 *      PCODE
 *          call TCYield
 */

VOID VDHENTRY VDHYield(flOptions)
BOOL          flOptions;
/*end*/
{
#ifdef MMIOPH
    MMPHPreVDHYield()
#endif

    // Validate calling context
    ASSERTTASKONLY;

    if(flOptions == VDH_YIELD_TIME_CRITICAL)
        TKTCYield();
    else
        TKYieldTimeslice();       // 59378

#ifdef MMIOPH
    MMPHPostVDHYield()
#endif

    return;
}

#pragma END_MVDM_SWAP_CODE

#pragma BEGIN_MVDM_GLOBAL_CODE

/***EP+ VDHSetPriority - Set VDM's Scheduler priority
 *
 *      Set the priority of the specified VDM.  Priority levels
 *      at each priority class are in the range 0-31.
 *
 *      A count of current users is maintained at each class.  The
 *      actual priority class in effect at any time is always the
 *      highest priority class with current declared users.  When
 *      there are no declared users, the DEFAULT class is used.
 *      Changes to a class other than the effective class are
 *      made to saved values.  These changes take effect when
 *      the class becomes the effective class.
 *
 *      The caller must signal when it begins use of the class, when
 *      it makes changes within the class, and when it finshes with
 *      the class.  The typical user would make one or more changes
 *      to priority at a particular class and finally finish the series
 *      of changes at the class.  The VDD uses the begin and end flags
 *      at the first and last calls at the class and the continue flag
 *      for changes in between.
 *
 *      The DEFAULT class is normally REGULAR unless the class/delta
 *      is changed without declaring yourself a user (i.e. without
 *      specifing a start, continue or end action flag).  This allows
 *      for a user interface to adjust the default priority of a VDM.
 *
 *      ENTRY
 *          hvdm - VDM handle
 *              0 => current VDM
 *
 *          flActionClass - VDHSP_TIME_CRITICAL
 *                    VDHSP_SIMULATED_INTERRUPT
 *                    VDHSP_SERVER
 *                    VDHSP_REGULAR
 *                    VDHSP_IDLE
 *                    VDHSP_NO_CHANGE
 *              Action flags:
 *                    VDHSP_START_USE
 *                    VDHSP_CONTINUE_USE
 *                    VDHSP_END_USE
 *              If no action flag is specified, the DEFAULT class is changed.
 *
 *          lPriority - delta from the current priority
 *
 *      EXIT-SUCCESS
 *          None
 *
 *      EXIT-FAILURE
 *          System Halt.
 *              If hvdm is not a valid HVDM, or if priority class is invalid.
 *
 *      CONTEXT
 *          Task
 *          Interrupt
 *
 *      VDM TERMINATION IMPLICATIONS
 *          None.
 *
 *      PCODE
 *          adjust the saved delta at the class specified
 *          switch (action)
 *              default use:
 *                  set DEFAULT class to ulActionClass
 *              start use:
 *                  increment class use count
 *              end use:
 *                  decrement class use count
 *              continue use:
 *                  do nothing
 *          get highest class with > 0 use count
 *          if no class with > 0 use count
 *              class = DEFAULT class
 *          get the level for highest class
 *          force level into 0 to 31 range
 *          call TKUpdatePriority(ptcb)
 */

VOID VDHENTRY VDHSetPriority(hvdm, ulActionClass, lDelta)
register HVDM hvdm;
ULONG ulActionClass;
LONG lDelta;
/*end*/
{
    register ULONG ulClass;
    LONG lLevel, ulFlags;
    PTCB ptcb;
    int i;

#ifdef MMIOPH
    MMPHPreVDHSetPriority()
#endif

    // save and disable flags
    ulFlags = TKDisable();

    // extract class number from action/class parameter
    ulClass = ulActionClass & VDHSP_CLASS_MASK;

#ifdef VDHSTRICT
    // Validate calling context
    ASSERTTASKONLY;
    CHECKHVDM(hvdm);

    // Validate parameters
    if (ulClass > VDHSP_NUM_CLASSES) {
        Panic(("VDHSetPriority: invalid class"));
    }
#endif

    ptcb = QUERYPTCB(hvdm);
    if(ulClass == VDHSP_NO_CHANGE) {
        ulClass = REFHVDM(hvdm,ULONG,ulCurrentClass);
    }

    // adjust the saved delta at the class specified
    REFHVDM(hvdm,LONG,alPriorityLevel[ulClass]) += lDelta;

    // decode action flag
    switch(ulActionClass & VDHSP_ACTION_MASK) {
        case VDHSP_DEFAULT_ACTION:
            // The last entry in the aulPriorityClassOrder array is the
            // "default" class.  This array is used to search in priority
            // class order for the first class with a non-zero reference
            // count and if all the classes are zero, this last entry is used.
            REFHVDM(hvdm,ULONG,aulPriorityClassOrder[VDHSP_NUM_CLASSES])
              = ulClass;
            break;
        case VDHSP_START_USE:
            ++REFHVDM(hvdm,ULONG,acPriorityReference[ulClass]);
            break;
        case VDHSP_CONTINUE_USE:
#ifdef VDHSTRICT
            if(REFHVDM(hvdm,ULONG,acPriorityReference[ulClass]) == 0) {
                Panic(("VDHSetPriority: continue without start action"));
            }
#endif
            break;
        case VDHSP_END_USE:
#ifdef VDHSTRICT
            if(REFHVDM(hvdm,ULONG,acPriorityReference[ulClass]) == 0) {
                Panic(("VDHSetPriority: reference count underflow"));
            }
#endif
            --REFHVDM(hvdm,ULONG,acPriorityReference[ulClass]);
            break;
        default:
            Panic(("VDHSetPriority: invalid action"));
    }

    // search for highest priority with a non-zero reference count
    for(i = 0; i < VDHSP_NUM_CLASSES + 1; i++) {
        ulClass = REFHVDM(hvdm,ULONG,aulPriorityClassOrder[i]);
        lLevel = REFHVDM(hvdm,ULONG,alPriorityLevel[ulClass]);
        if(REFHVDM(hvdm,ULONG,acPriorityReference[ulClass]) != 0) {
            break;
        }
    }
    // update the current class and calcalate new delta
    REFHVDM(hvdm,ULONG,ulCurrentClass) = ulClass;

    // force level into 0 to 31 range
    if(lLevel > LEVEL_MAX) {
        lLevel = LEVEL_MAX;
    } else if(lLevel < LEVEL_MIN) {
        lLevel = LEVEL_MIN;
    }

    // set up priority TCB variables
    ptcb->TCBPriClass = (BYTE)ulClass;
    ptcb->TCBPriLevel = (BYTE)lLevel;
    ptcb->TCBPriClassMod &= ~CLASSMOD_VDM_INTERRUPT;

    // set simulated interrupt priority modifier
    if(ulClass == VDHSP_SIMULATED_INTERRUPT) {
        ptcb->TCBPriClassMod |= CLASSMOD_VDM_INTERRUPT;
        ptcb->TCBPriClass = CLASS_REGULAR;
    }
    TKEnable(ulFlags);
    TKUpdatePriority(ptcb);
#ifdef MMIOPH
    MMPHPostVDHSetPriority()
#endif

}

#if VDHSP_TIME_CRITICAL != CLASS_TIME_CRITICAL
#error Flags should match: VDHSP_TIME_CRITICAL == CLASS_TIME_CRITICAL
#endif

#if VDHSP_SERVER != CLASS_SERVER
#error Flags should match: VDHSP_SERVER == CLASS_SERVER
#endif

#if VDHSP_REGULAR != CLASS_REGULAR
#error Flags should match: VDHSP_REGULAR == CLASS_REGULAR
#endif

#if VDHSP_IDLE != CLASS_IDLE_TIME
#error Flags should match: VDHSP_IDLE == CLASS_IDLE_IDLE
#endif

#if VDHSP_NO_CHANGE != CLASS_NO_CHANGE
#error Flags should match: VDHSP_NO_CHANGE == CLASS_NO_CHANGE
#endif

/***EP+ VDHFreezeVDM - Freeze a VDM
 *
 *      The specified VDM is not be allowed to execute any V86-mode
 *      code until VDHThawVDM is called.  The freeze occurs when
 *      the specified VDM leaves "kernel mode".  The VDM is guaranteed
 *      not to execute an V86-mode code from the time the call is
 *      made until the matching VDHThawVDM call is made.
 *
 *      Each VDM has a "freeze count".  This count starts at zero
 *      when the VDM is created.  Each VDHFreezeVDM call adds one
 *      to this count.  Each VDHThawVDM call subtracts one from
 *      this count.
 *
 *      The VDM is frozen when the freeze count is one or greater.
 *      The VDM is thawed when the freeze count is zero.
 *
 *      This allows multiple VDDs to perform freeze thaw operations
 *      without inadvertently making the VDM runnable.
 *
 *      If this count exceeds MAX_FREEZE_COUNT, then VDHFreezeVDM will
 *      return ERROR_BUFFER_OVERFLOW.
 *
 *      ENTRY
 *          hvdm - VDM handle
 *
 *      EXIT-SUCCESS
 *          returns TRUE
 *
 *      EXIT-FAILURE
 *          returns FALSE
 *          VDHGetError may return:
 *              ERROR_BUFFER_OVERFLOW
 *          System Halt.
 *              If hvdm is not a valid HVDM.
 *
 *
 *      CONTEXT
 *          Task
 *          Interrupt
 *
 *      VDM TERMINATION IMPLICATIONS
 *          The VDM Manager will thaw a frozen VDM prior to calling
 *          any VDM_TERMINATE user hooks.
 *
 *      PCODE
 *          if freeze count is less than MAX_FREEZE_COUNT
 *              if freeze count is zero
 *                  set VDM state to FROZEN
 *              increment the freeze count
 *              mark success
 *          else
 *              mark failure
 *          return result
 */

BOOL VDHENTRY VDHFreezeVDM(HVDM hvdm)
/*end*/
{
    ULONG ulCnt;

#ifdef MMIOPH
    MMPHPreVDHFreezeVDM()
#endif

    // Validate parameters
    CHECKHVDM(hvdm);

    ulCnt = REFHVDM(hvdm,ULONG,vdmid_ulFreezeCnt);
    if(ulCnt++ < MAX_FREEZE_COUNT) {
        REFHVDM(hvdm,ULONG,vdmid_ulFreezeCnt) = ulCnt;
        if (ulCnt == 1)
            vdmmSetState(QUERYPTCB(hvdm), FALSE);
    }
    else {
        VDHSetError(ERROR_BUFFER_OVERFLOW);
        ulCnt = 0;
    }

#ifdef MMIOPH
    MMPHPostVDHFreezeVDM()
#endif

    return ulCnt;
}

/***EP+ VDHThawVDM - Thaw a VDM
 *
 *      Reverses the effect of VDHFreezeVDM.  The specified VDM will
 *      be made runnable when the "freeze count" becomes zero.
 *
 *      This function does nothing if called on behalf of a VDM
 *      that is not frozen.
 *
 *      See VDHFreezeVDM for a full discussion of "freeze counting".
 *
 *      ENTRY
 *          hvdm - VDM handle
 *
 *      EXIT-SUCCESS
 *          None.
 *
 *      EXIT-FAILURE
 *          System Halt.
 *              If hvdm is not a valid HVDM.
 *
 *      CONTEXT
 *          Task
 *          Interrupt
 *
 *      VDM TERMINATION IMPLICATIONS
 *          None.
 *
 *      PCODE
 *          if per-VDM "freeze count" is greater than ZERO
 *            decrement "freeze count"
 *            if "freeze count" is ZERO
 *              Change VDM state to ready to run
 *          return
 */

VOID VDHENTRY VDHThawVDM(HVDM hvdm)
/*end*/
{
    ULONG ulCnt;

#ifdef MMIOPH
    MMPHPreVDHThawVDM()
#endif

    // Validate parameters
    CHECKHVDM(hvdm);

    ulCnt = REFHVDM(hvdm,ULONG,vdmid_ulFreezeCnt);
    if (ulCnt-- > 0) {
        REFHVDM(hvdm,ULONG,vdmid_ulFreezeCnt) = ulCnt;
        if (ulCnt == 0)
            vdmmSetState(QUERYPTCB(hvdm), TRUE);
    }

#ifdef MMIOPH
    MMPHPostVDHThawVDM()
#endif

    return;
}

/***EP+ VDHIsVDMFrozen - Determine whether or not a VDM is frozen
 *
 *      Returns freeze state of a VDM.
 *
 *      ENTRY
 *          hvdm - VDM handle
 *
 *      EXIT-SUCCESS
 *          returns FALSE, VDM is not frozen;  non-zero otherwise
 *
 *      EXIT-FAILURE
 *          System Halt.
 *              If hvdm is not a valid HVDM.
 *
 *      VDM TERMINATION IMPLICATIONS
 *          None.
 *
 *      CONTEXT
 *          Task
 *          Interrupt
 *
 *      PCODE
 *          validate parameter
 *          return per-VDM "freeze count"
 */

BOOL VDHENTRY VDHIsVDMFrozen(HVDM hvdm)
/*end*/
{

#ifdef MMIOPH
    MMPHPreVDHIsVDMFrozen()
#endif

    // Validate hvdm
    CHECKHVDM(hvdm);

#ifdef MMIOPH
    MMPHPostVDHIsVDMFrozen()
#endif

    return REFHVDM(hvdm,ULONG,vdmid_ulFreezeCnt);
}

#pragma END_MVDM_GLOBAL_CODE
