/**************************************************************************
 *
 * SOURCE FILE NAME =  vpol.c
 *
 * DESCRIPTIVE NAME = VDM polling detection
 *
 * Copyright : COPYRIGHT IBM CORPORATION, 1991, 1992
 *             Copyright Microsoft Corporation, 1990
 *             LICENSED MATERIAL - PROGRAM PROPERTY OF IBM
 *             REFER TO COPYRIGHT INSTRUCTION FORM#G120-2083
 *             RESTRICTED MATERIALS OF IBM
 *             IBM CONFIDENTIAL
 *
 * VERSION = V2.0
 *
 * DATE
 *
 * DESCRIPTION This module contains polling detection code.
 *
 *
 * FUNCTIONS
 *
 * ENTRY POINTS:
 *
 * DEPENDENCIES:
 *
 * NOTES
 *
 *
 * STRUCTURES
 *
 * EXTERNAL REFERENCES
 *
 * EXTERNAL FUNCTIONS
 *
 * CHANGE ACTIVITY =
 *  DATE      FLAG        APAR     CHANGE DESCRIPTION
 *  --------  ----------  -----    --------------------------------------
 *  mm/dd/yy  @Vr.mpppxx  xxxxx    xxxxxxx
 *  08/21/89                       Wayne Carr Created
 ****************************************************************************/

#define INCL_MI                         /* for flags register bit mask equate*/
#include <mvdm.h>
#include "vkbdp.h"
#include "propname.h"
#define INCL_DOSERRORS
#include <bseerr.h>


/****************************************************************************
 *
 *   Design Notes
 *
 *   VPOL (VPL Idle detection)
 *
 *   DOS applications sometimes poll for input and waste CPU time
 *   in a multitasking system.  VPOL is designed to determine when
 *   DOS applications are idle and to prevent them from hogging
 *   the CPU.
 *
 *   VPOL can never know for sure when a DOS app is idle unless
 *   the application tells it (INT 2fh AX=1680h).  In other cases,
 *   VPOL must guess when an application is idle.
 *
 *   Goals:
 *      + Catch applications when they are idle
 *      + Avoid considering an application idle
 *        when it is doing useful work
 *
 *   Idle detection handles 2 cases:
 *
 *    1. Calls requesting a timeout.
 *
 *          Case 1: - INT 2fh AX=1680h idle call from application
 *
 *          Case 2: - DOS call when in its console read loop
 *
 *    2. Polling events that occur at a high rate.
 *
 *        VDD's notify VPOL when polling occurs by calling VDHReportPeek
 *        and passing a weight for the event.  VPOL keeps a sum
 *        of the weighted reports within a single infoseg timer tick.
 *
 *        Conditions for determining application is idle
 *
 *          + VDHReportPeek notification count exceeds a threshhold
 *          + No application "busy" activity for some time
 *
 *        Each time the infoseg clock is found to have changed,
 *        the peek count is reset and counting restarts.
 *
 *        Rules for determining an application is idle:
 *
 *          Case 1: - peek loop
 *
 *              Peeks > threshhold
 *              No recent busy activity
 *
 *          Case 2: - INT 28h's
 *
 *              More than 16 (non DOS) INT 28h's
 *              No recent busy activity
 *
 *          Case 3: - INT 21h service 0bh
 *
 *              More than half of the threshhold was due to
 *              service 0bh.
 *              No recent busy activity
 *              This service is slower than INT 16h due to numerous
 *              traps into ring 0 and so requires special handling.
 *
 *        When determined to be idle, the VDM is put on probation
 *        for a period.  During that period, the initial count
 *        for peeks is reset to a value close to the threshhold
 *        to catch continued polling quickly while still allowing
 *        the application some CPU time.  This allows the application
 *        time to demonstrate it is busy is it really is not idle.
 *
 *  When an application is judged idle.  It can either be made to
 *  sleep for a time or it can have its priority reduced to IDLE
 *  priority for a time.  In either case, interrupts are simulated
 *  to the VDM even while it is judged as idle.
 *
 *  When VDD's that detect "busy" activities report these events,
 *  a VDM judged as IDLE is immediately restored to the non-idle state.
 *  For example, if a keystroke occurs in a VDM, that VDM is no
 *  longer considered idle.
 *
 *  If a VDM is considered idle for a prolonged period of time,
 *  the amount of time it sleeps is increased gradually to give
 *  it less CPU time.
 *
 *
 *  INTERFACES
 *
 *  The principle interfaces to VPOL are:
 *
 *      VDHReportPeeks
 *      Receives reports of polling events.
 *
 *      VDHWakeIdle
 *      Notes that the application is busy.  The VDM is awakened
 *      if sleeping for being idle and has its priority restored
 *      if priority had been dropped.
 *
 *      VPLDosWaitIdle
 *      Accepts reports of INT 28H's and INT 2fh, AX=1680h.
 *
 *      VPLDemInit
 *      Accepts address for DOS "not busy" variable and
 *      coordinates INT 16h callibration with DOSKRNL.
 *
 *      VPLNoIdle
 *      Turns off idle detection for a period of time.
 *
 *      VPLDosReqProc
 *      Returns the entry points for VPLDemInit, VPLDosWaitIdle,
 *      VDHWakeIdle, and VPLNoIdle.  This allows calls from the
 *      kernel and from VDD's loaded before VKBD.
 *
 *
 *  ROLE OF DOSKRNL
 *
 *  DOSKRNL aids in idle detection in a number of ways.
 *  All of these activities could easily be moved out of DOS
 *  to reduce the amount of special DOS code needed for OS2.
 *
 *  DOSKRNL provides the following:
 *
 *      1. Calls VPOL from DOS console read loop.
 *         The application cannot be stopped pending
 *         input since INT 28h and INT 2Ah application
 *         hooks must still be called periodically by DOS
 *         and the console may be redirected to a device
 *         that does not report busy activity.  The VDM
 *         can be given less CPU time however.
 *         Another component could provide this service
 *         by prehooking INT 21h.  When a polling INT 21h
 *         call was entered, VPOL would need to change modes
 *         to judge the application idle more quickly.
 *
 *      2. Clears a variable ("not busy" variable) when a
 *         non-peek service request is made by an application.
 *         This allows DOS to signal when the application
 *         is doing useful work.  This variable is periodically
 *         reset by VPOL.  Another component could provide this
 *         service by prehooking INT 21h.
 *
 *      3. Increments the "not busy" when service 11 is requested.
 *         This console status service causes so many traps that
 *         polling cannot be detected by a high rate on int 16h's.
 *         DOSKRNL aids here by counting the calls.  This variable
 *         is the same one DOSKRNL clears when it receives any
 *         non-polling request.  Another component could provide
 *         this service by prehooking INT 21h.
 *
 *      4. Hooks INT 28h.
 *         When an application does 16 successive INT 28h's
 *         without DOS performing one, DOSKRNL reports this
 *         to VPOL.  DOS does INT 28h's on both console input
 *         and output, so DOS calls must be ignored.  Applications
 *         do INT 28h only when they are allowing idle time code
 *         (like spoolers) to run.  Another component could provide
 *         this service by prehooking INT 28h.  The service provider
 *         would need to be able to distinguish DOS's CS from application's
 *         CS.
 *
 *      5. Hooks INT 2fh.
 *         INT 2fh AX=1680h indicates the application is willing
 *         to give up its time slice.  DOSKRNL reports this call
 *         to VPOL.  This function should be shifted to MVDM
 *         which already handles other INT 2fh calls.
 *
 *      6. VPL callibration.
 *         At the end of DOSKRNL creation, DOS calls VPOL.
 *         DOS supplies the V86 address of the "not busy" variable
 *         discussed above.  A return value indicates the number
 *         of INT 16H peeks DOSKRNL should perform.  VPOL
 *         is then called again and the process is repeated
 *         until VPOL requests 0 peeks.  This function could
 *         be moved to a V86 mode stub in VKBD.  The stub
 *         would need to be able to make an SVC call to
 *         contact VPOL to find out how many INT 16h's to do.
 *
 *
 *  PROBATION
 *
 *  When an application is judged idle, it still must be allowed
 *  to run periodically to let it check for input and to let INT 28h
 *  handlers run.  Some input, like keystrokes cause the VDM to wake
 *  immediately because they are reported to idle detection, but other
 *  input is not reported.
 *
 *  When judged idle, the VDM will be placed on one of three kinds
 *  of probation.
 *
 *       DOS console read probation
 *       INT 28h/ INT 2fh yield probation
 *       VDHReportPeek probation
 *
 *  The longer a VDM has been on probation for being idle, the longer
 *  it will sleep (or run at idle priority) each time it is judged idle.
 *  A VDM is removed from probation when "busy" activity is reported,
 *  or when some period of time has passed since it has been judged
 *  idle.  It also restarts probation when the type of probation
 *  changes.  For VDHReportPeek probation, the counter for peek events
 *  is reinitialized to a value near the threshhold while on probation.
 *  This ensures the VDM will be cauught more quickly when in a loop.
 *  Periodically, while on VDHReportPeek probation, the counter will
 *  be reset to 0 giving the application a normal test period.
 *
 *  CALLIBRATION
 *
 *  Peek events are weighted so that when an application peeks
 *  in a tight loop, the sum of the reported weights
 *  exceeds 64K in an infoseg clock tick.  To determine the weight
 *  to use, VDD's that report must callibrate based on actual calls
 *  in a VDM.  This rate is highly dependent on the number of
 *  traps to ring 0.  This means the rates on different machines
 *  in real mode may have no relation to the rates in VDMs.
 *  VPOL arranges to callibrate INT 16H peeks for VKBD.
 *
 *  The threshhold used to judge an application as idle
 *  is a % of 64K.  The user is able to set this level
 *  with an advanced property.  A value of 75 means that
 *  the VDM is considered idle when it performs polling
 *  at a 75% of the maximum possible rate.  Applications
 *  simply waiting for input have loops with varying amounts
 *  of overhead.  Setting the value to 100% turns idle detection
 *  off.  A second advanced property allows users to set
 *  a delay for idle detection.  Idle detection will turn
 *  off for this number of seconds every time busy activity
 *  is detected.
 *
 *  The phrase "VPL Idle detection" appears in all idle detection
 *  code in MVDM or V86Code to make it easier to locate.
 ****************************************************************************/

typedef ULONG VPLTIME;  /* tm */

BOOL  NEAR PASCAL VPLCheckIdle(VPLTIME);
VOID  NEAR PASCAL VPLCheckInt28Loop(VPLTIME, ULONG);
VOID  NEAR PASCAL VPLDosSleep(VPLTIME,ULONG);
LONG  EXPENTRY    VPLDosReqProc(HVDM,ULONG,PVOID,PVOID);
VOID  NEAR PASCAL VPLDosWaitIdle(ULONG);
VOID  HOOKENTRY   VPLIdleHook(PVOID,PCRF);
VOID  HOOKENTRY   VPLIdleTimeOut(PVOID);
VOID  NEAR PASCAL VPLMakeIdle(ULONG);
VOID  NEAR PASCAL VPLMakeSleep(ULONG);
VOID  NEAR PASCAL VPLResetProbation(VPLTIME);
VOID  NEAR PASCAL VPLResetTime(VPLTIME);
VOID  HOOKENTRY   VPLRetWaitIdle(PVOID,PCRF);
VOID  NEAR PASCAL VPLWaitIdle(VPLTIME,ULONG,ULONG);
VOID  NEAR PASCAL VPLWakeIdle(HVDM);

/*
** property routines
*/
ULONG EXPENTRY    fnSetPoll(ULONG,HVDM,ULONG,CHAR *);
ULONG EXPENTRY    fnSetPollDelay(ULONG,HVDM,ULONG,CHAR *);

/*
** initialization routines
*/
ULONG NEAR PASCAL VPLDemInit(PULONG);
ULONG NEAR PASCAL VPLEndInit(VOID);
ULONG NEAR PASCAL VPLStartInit(PULONG);
BOOL  NEAR PASCAL VPLUpdateKeyWeight(VOID);

#define VPL_IDLE_THRESHHOLD  0x10000
#define BUSY_TIMEOUT              60
#define DEFAULT_PEEKS_MIN         50
#define DEFAULT_PEEKS            100
#define DEFAULT_LEVEL             75
#define DEFAULT_DELAY              0
#define DELAY_MAX                 60
#define DEM_SERVICE_MAX            2
#define DOS_SLEEP_TIME           120
#define EXCLUSIVE_TIMEOUT        120
#define IDLE_SLEEP_MIN             5
#define INT28_BUSY_TIMEOUT        30
#define INT28_SLEEP_TIME         250
#define INT28_MAX                  2
#define PEEK_SLEEP_TIME         2000
#define IDLE_TEST_TIME            31
#define INIT_PEEKS_COUNT         900
#define LEVEL_MAX                100
#define NOT_CAUGHT_MAX            10
#define NOT_CAUGHT_TIMEOUT     15000
#define PEEK_PROBATION_TIME    15000
#define PROBATION_PEEK_MAX        16
#define VPL_INIT_ATTEMPTS          4
#define VPL_INT28_CALL             1
#define VPL_PEEK_PROBATION         1
#define VPL_DOS_PROBATION          2
#define VPL_28H_PROBATION          4
#define VPL_PEEK_TRIAL             8
#define VPL_USING_SLEEP            1
#define VPL_USING_IDLE             2
#define VPL_INT_SIMULATION         4

#pragma BEGIN_INIT_DATA

/*
** LD  vpboundPoll - bounds for polling sensitivity
*/
VPBOUND vpboundPoll = {1, LEVEL_MAX, 1};
VPBOUND vpboundDelay = {0, DELAY_MAX, 1};

#pragma END_INIT_DATA


#pragma BEGIN_INSTANCE_DATA

HHOOK   hhookWakeIdle = 0;          /* VDHWakeVIRRs context hook             */
VPLTIME tmStartPause = 0;           /* start of idle detection pause         */
VPLTIME tmPauseDelay = 0;           /* time for pause delay                  */
VPLTIME tmStartProbation = 0;       /* start of probation                    */
ULONG   flSleepState = 0;           /* VDM judged idle?                      */
ULONG   flVplProbation = 0;         /* probation type                        */
ULONG   ulVplDosIdle = 1;           /* replaced by DOS address               */
BOOL    fIdleTimerSet = FALSE;      /* timeout timer set?                    */
BOOL    fVDMBusy = TRUE;            /* VDD busy notification                 */
BOOL    fVPLExclusive = FALSE;      /* video peek notification shut off      */
BOOL    fVplWakeup = 0;             /* need wakeup call?                     */
BOOL    fVplHookPending = 0;        /* delivering context hook?              */

#pragma END_INSTANCE_DATA

#pragma BEGIN_SWAP_INSTANCE

VPLTIME tmStartSleep = 0;           /* start of idle sleep                    */
VPLTIME tmSleepWanted = 0;          /* sleep time requested                   */
VPLTIME tmIdleDelay = 0;            /* time to delay detection after busy     */
VPLTIME tmIdleLastCaught = 0;       /* time last caught idle                  */
VPLTIME tmLastBusy = 0;             /* last time busy                         */
VPLTIME tmLastCaught = 0;           /* last judged idle                       */
VPLTIME tmLastExclusive = 0;        /* last time video shut out               */
VPLTIME tmPeekProbation = 0;        /* time of last full peek test            */
VPLTIME tmStartIdleTest = 0;        /* start of idle test period              */

ULONG   cIdleMax = 0xffffffff;      /* idle threshhold for VDM                */
ULONG   cInt21Max = 0xffffffff;     /* Threshhold for slow int 21h 0bh peeks  */
ULONG   cInt28Calls = 0;            /* number of int 28h notifications        */
ULONG   cInt28Max = 0xffffffff;     /* sleep after this many notifications    */
ULONG   cPeeks = 0;                 /* count of peek events in test period    */
ULONG   cPeeksWarn = 0xffffffff;    /* Warning level for peeks                */
ULONG   cPeeksMax = 0;              /* max peek event total for callibration  */
ULONG   cProbationPeeks = 0;        /* peek reset value for peek probation    */
ULONG   cVplLocalInit = 0;          /* number of local initialization attempts*/
ULONG   ulIdleLevel = DEFAULT_LEVEL;/* detection sensitivity                  */


HHOOK   hhookIdleTimeOut;           /* hook handle for idle sleep timeout     */
HHOOK   hhookIdleWaitVIRR;          /* hook handle for waiting for interrupt  */

BOOL    fCountPeeks = FALSE;        /* track max peeks in any period?         */
BOOL    fFirstInit = TRUE;          /* waiting for VDM initialization?        */
BOOL    fIdleOff = TRUE;            /* idle detection off?                    */
BOOL    fVplCallibrating = FALSE;   /* callibrating?                          */
BOOL    fVplPriorityHigh = FALSE;   /* priority boosted for callibration?     */

PUSHORT puscDOSIdle = (PUSHORT)&ulVplDosIdle; /* DOS resets to V86 address    */

BOOL    fVMBoot = FALSE;            /* no DOSKRNL support                     */

#pragma END_SWAP_INSTANCE

#pragma BEGIN_SWAP_DATA

ULONG   cVKBDPeekWeight = 1;        /* Weight VKBD uses for reporting         */
ULONG   cVKBDPeekMax = 0;           /* Maximum callibration peek rate         */
ULONG   cVplInitDone = 0;           /* Number of callibrations done           */
HVDHSEM hvdhsemVPL = (HVDHSEM)0;    /* protection for globals                 */
BOOL    fVplInitOK = FALSE;         /* initialization results                 */
CHAR    szPropPoll[] = PROP_NAME_POLL;  /* sensitivity property               */
CHAR    szPropDelay[] = PROP_NAME_DELAY;/* delay property                     */
SZ      szVMBoot = {'#', VDMP_ORD_VMBOOT+'0', 0};

#pragma END_SWAP_DATA

#pragma BEGIN_INIT_CODE

/****************************************************************************
 *
 * FUNCTION NAME = VPLInit - Idle detection initialization
 *
 * DESCRIPTION   =
 *
 *      Initialize idle detection.  This routine
 *      sets exports entry points for use by the
 *      kernel and VDD's.  It also registers
 *      advanced properties to allow the user
 *      to control the idle threshhold and the
 *      delay in starting idle detection after
 *      "busy" activity was reported.
 *
 * ENTRY
 *     None.
 * EXIT
 *     No return value.
 *
 * PCODE
 * allocate semaphore for protecting globals - VDHCreateSem
 * register entry point for use in SVC calls - VDHRegisterVDD
 * register property for level - VDHRegisterProperty
 * register property for delay - VDHRegisterProperty
 ****************************************************************************/

VOID NEAR PASCAL VPLInit()
{
    PSZ pszVDDname = VPL_NAME;

    fVplInitOK = VDHCreateSem(&hvdhsemVPL, VDH_MUTEXSEM)

              && VDHRegisterVDD(pszVDDname, NULL, VPLDosReqProc)

              && VDHRegisterProperty(
                        szPropPoll,             /* property name             */
                        NULL,                   /* no help file              */
                        0L,                     /*  no help id               */
                        VDMP_INT,               /*  type                     */
                        VDMP_ORD_OTHER,         /*  no ordinal               */
                        0L,                     /*                           */
                        (VOID*)DEFAULT_LEVEL,   /*  default value            */
                        &vpboundPoll,           /*  validation info          */
                        fnSetPoll)              /*  level update             */

              && VDHRegisterProperty(
                        szPropDelay,            /*  property name            */
                        NULL,                   /*  no help file             */
                        0L,                     /*  no help id               */
                        VDMP_INT,               /*  type                     */
                        VDMP_ORD_OTHER,         /*  no ordinal               */
                        0L,                     /*                           */
                        (VOID*)DEFAULT_DELAY,   /*  default value            */
                        &vpboundDelay,          /*  validation info          */
                        fnSetPollDelay)   ;     /*  level update             */

}  /* end VPLInit */

#pragma END_INIT_CODE


#pragma BEGIN_GLOBAL_CODE

/****************************************************************************
 *
 * FUNCTION NAME = VDHWakeIdle - Wake up VDM if doing Idle sleep
 *
 * DESCRIPTION   =
 *
 *      This routine notes that the VDM is doing useful work.
 *      If the VDM is currently sleeping or running at a lower
 *      priority because of polling activity, it is awakened
 *      and is no longer considered idle.
 *
 * ENTRY
 *     hvdm - hvdm to wake up
 *
 * EXIT
 *     No return code
 *
 * VDM TERMINATION IMPLICATIONS
 *     None.
 *
 * CONTEXT
 *     Task
 *     Interrupt
 *
 * PCODE
 * set the Busy flag
 * wake up VDM if sleeping or at idle priority (VPLWakeIdle)
 ****************************************************************************/

VOID VDHENTRY VDHWakeIdle(HVDM hvdm)
{
    CHECKHVDM(hvdm);
    REFHVDM(hvdm, BOOL, fVDMBusy) = TRUE;   /*  Note that VDM is busy        */

    if (REFHVDM(hvdm, BOOL, fVplWakeup))
        VPLWakeIdle(hvdm);                  /*  Wake idle VDM                */

}  /* end VDHWakeIdle */


/****************************************************************************
 *
 * FUNCTION NAME = VDHGetBusyFlagPtr
 *
 * DESCRIPTION   =
 *
 *
 *      Return pointer to busy flag.  This routine is used by other
 *      VDD's at init time to obtain a pointer to the busy flag for
 *      the current VDM.  It's faster for them to simply set the variable
 *      to TRUE later, rather than calling VDHWakeIdle(CURRENT_VDM).
 *      Measurements showed that a call to VDHWakeIdle() caused a degradation
 *      of 1-2%, whereas this scheme reduces the cost to virtually nothing.
 *
 * ENTRY
 *     none
 *
 * EXIT
 *     Pointer to VDM busy flag for (CURRENT_VDM)
 *
 * VDM TERMINATION IMPLICATIONS
 *     None.
 *
 * CONTEXT
 *     Task
 *     Interrupt
 *
 * PCODE
 * return &busyflag
 ****************************************************************************/

BOOL *VDHENTRY VDHGetBusyFlagPtr(void)
{
    return &fVDMBusy;

}  /* end VDHGetBusyFlagPtr */


/****************************************************************************
 *
 * FUNCTION NAME = VPLWakeIdle - Wake up VDM if doing Idle sleep
 *
 * DESCRIPTION   =
 *
 *      If the VDM is currently sleeping or has had its
 *      priority lowered because of polling activity,
 *      wake it up.
 *
 * ENTRY
 *     hvdm - hvdm to wake up
 *
 * EXIT
 *     No return code
 *
 * VDM TERMINATION IMPLICATIONS
 *     None.
 *
 * CONTEXT
 *     Task
 *     Interrupt
 *
 * PCODE
 * disable interrupts
 * if wakeup is required (fVplWakeup)
 *     if (hvdm is a VDM in polling sleep)
 *         wake the VDM up
 *         reset fVplWakeup and flSleepState
 *     else if (hvdm is a VDM set to IDLE priority)
 *         if hook has not already been set
 *             note that a hook is pending
 *             arrange context hook to reset priority
 *         endif
 *     endif
 * endif
 * enable interrupts
 ****************************************************************************/

VOID NEAR PASCAL VPLWakeIdle(HVDM hvdm)
{

    register PULONG pflSleepState;
    register PBOOL pfVplWakeup;
    ULONG ulFlags;

    ulFlags = SAVEFLAGS();
    DISABLE();
    pfVplWakeup = &REFHVDM(hvdm, BOOL, fVplWakeup);
    pflSleepState = &REFHVDM(hvdm, ULONG, flSleepState);

    if (*pfVplWakeup)
    {

        if (*pflSleepState & VPL_USING_SLEEP)
        {

            VDHWakeVIRRs(hvdm);     /*  Wake up VDM                          */
            *pflSleepState = 0;     /*  Note that it is no longer sleeping   */
            *pfVplWakeup = 0;       /*  Note that a wakeup is not needed     */

        }
        else if (*pflSleepState & VPL_USING_IDLE)
        {

            if (!REFHVDM(hvdm, BOOL, fVplHookPending))
            {

                REFHVDM(hvdm, BOOL, fVplHookPending) = TRUE;
                VDHArmContextHook(REFHVDM(hvdm, HHOOK, hhookWakeIdle),
                  GLOBAL_CONTEXT_HOOK);

            }  /* endif */

        }  /* endif */

    }  /* endif */

    RESTOREFLAGS(ulFlags);

}  /* end VPLWakeIdle */


/****************************************************************************
 *
 * FUNCTION NAME = VPLIdleTimeOut - Return from sleep timeout
 *
 * DESCRIPTION   =
 *
 *      This routine is called when the time that a VDM
 *      is to sleep or run at idle priority expires.
 *      The VDM is restored to the normal non-idle state.
 *
 * ENTRY
 *     p     - pointer to hvdm of VDM to wake up
 *
 * EXIT
 *     No return code
 *
 * CONTEXT
 *     Interrupt
 *
 * PCODE
 * set fIdleTimerSet to false to signal timeout occurred
 * use VPLWakeIdle to wake idle VDM
 ****************************************************************************/

VOID HOOKENTRY VPLIdleTimeOut(PVOID p, PVOID q)
{
    HVDM hvdm;

    hvdm = *(PHVDM)p;
    REFHVDM(hvdm, BOOL, fIdleTimerSet) = FALSE;
    VPLWakeIdle(hvdm);

}  /* end VPLIdleTimeOut */


/****************************************************************************
 *
 * FUNCTION NAME = VPLIdleHook - End use of idle priority
 *
 * DESCRIPTION   =
 *
 *      Global hook routine to end use of Idle priority.
 *
 * ENTRY
 *     p     - pointer to hvdm
 *     pcrf  - unused
 *
 * EXIT
 *     No return code
 *
 * CONTEXT
 *     Task
 *
 * PCODE
 * if flSleepState is set to "using idle"
 *     use VDHSetPriority to end use of idle priority
 *     clear fVplWakeup to signal done
 *     disarm timer if still pending
 * clear flSleepState so VDM is allowed to be caught again
 * reset hook pending flag
 ****************************************************************************/

VOID HOOKENTRY VPLIdleHook(PVOID p, PCRF pcrf)
{
    HVDM hvdm;
    PULONG pflSleepState;

    pflSleepState = &REFHVDM(hvdm, ULONG, flSleepState);

    hvdm = *(PHVDM)p;

    if (*pflSleepState & VPL_USING_IDLE)
    {

        /*
        **  end use of idle priority
        */

        VDHSetPriority(hvdm, VDHSP_IDLE | VDHSP_END_USE, 0);
        REFHVDM(hvdm, BOOL, fVplWakeup) = FALSE;

        /*
        ** disarm timer if still set
        */
        if (fIdleTimerSet)
        {
            VDHDisarmTimerHook(hhookIdleTimeOut);
            fIdleTimerSet = FALSE;
        }  /* endif */

    }  /* endif */

    REFHVDM(hvdm, BOOL, fVplHookPending) = FALSE;  /*  no hook pending       */
    *pflSleepState &= (~VPL_USING_IDLE);           /*  not idle priority     */

}  /* end VPLIdleHook */

#pragma END_GLOBAL_CODE


#pragma BEGIN_SWAP_CODE

/*
** VDH services
*/

/****************************************************************************
 *
 * FUNCTION NAME = VDHReportPeek - Report VDM polling activity
 *
 * DESCRIPTION   =
 *
 *      Report VDM polling activity.  A counter of idle polling
 *      activity is incremented.  If the count exceeds a threshhold,
 *      the current VDM is put to sleep for a period.
 *
 *      Any VDD that can detect idle polling activity can call
 *      this service to report it.
 *
 *      If the sum of peek weights exceeds 64K in a single
 *      VDHGSV_MSECSBOOT clock tick (see VDHQuerySysValue),
 *      the VDM will be considered idle. It is the VDDs responsibility
 *      to callibrate its peek weight.  This depends on machine speed
 *      and the ring0 trap overhead time, and so cannot be callibrated
 *      from measuring peek rate under DOS.
 *
 * ENTRY
 *     cPeekWeight - value to add to the Idle counter.
 *
 * EXIT-SUCCESS
 *     No return code
 *
 * EXIT-FAILURE
 *     System Halt.
 *         if current process is not a VDM.
 *
 * VDM TERMINATION IMPLICATIONS
 *     None.
 *
 * CONTEXT
 *     VDM Task
 *
 * NOTE: Testing the time before the count ensures
 * that new tests begin at the first peek in a time
 * slice.  This allows use of a very short test period.
 *
 * PCODE
 * if callibrating
 *     change the input peek weight to 1
 * else if idle is turned off
 *     check to see if it is time to turn it back on
 *     return
 *
 * if still in current test period
 *     VPLCheckIdle checks busy flags
 * else
 *     VPLResetTime resets counters
 *
 * add cPeekWeight to cPeeks
 *
 * if (cPeeks >= cIdleMax)
 *    AND not already sleeping
 *      (avoids idle priority or simulated interrupt reentry)
 *    AND (at least  BUSY_TIMEOUT msecs since last busy)
 *     put on probation if not already on it
 *     use VPLWaitIdle to make VDM sleep
 * endif
 ****************************************************************************/

VOID VDHENTRY VDHReportPeek(ULONG cPeekWeight)
{
    VPLTIME tmCur;

    if (fVplCallibrating)
        cPeekWeight = 1;            /*  callibration weight is always 1      */

    else if (fIdleOff)
    {                              /*  skipped if callibrating              */
        if (tmPauseDelay)
        {
            tmCur = VDHQuerySysValue(0, VDHGSV_MSECSBOOT);
            if (tmCur - tmStartPause >= tmPauseDelay)
            {
                tmPauseDelay = 0;
                fIdleOff = (ulIdleLevel == LEVEL_MAX);
            } /*  endif                                                      */

        }  /*  endif                                                         */
        return;
    }  /*  endif                                                             */

    tmCur = VDHQuerySysValue(0, VDHGSV_MSECSBOOT);

    /*
    ** check if still in test period
    */
    if (tmCur - tmStartIdleTest < IDLE_TEST_TIME)
        VPLCheckIdle(tmCur);        /*  check and reset busy flags           */
    else
        VPLResetTime(tmCur);        /*  reset for new test period            */

    cPeeks += cPeekWeight;          /*  increment peek count                 */

    /*
    ** check threshhold
    */
    if ( cPeeks > cIdleMax
         && tmCur - tmLastBusy > BUSY_TIMEOUT
         && !flSleepState )  {

        if (flVplProbation == VPL_PEEK_TRIAL)
        {
            flVplProbation  = VPL_PEEK_PROBATION;
            tmPeekProbation = tmCur;   /*  time of last full peek test       */
        }
        else if (flVplProbation != VPL_PEEK_PROBATION)
        {
            flVplProbation  = VPL_PEEK_PROBATION;
            tmStartProbation = tmCur;  /*  start of idle warning period      */
            tmPeekProbation = tmCur;   /*  time of last full peek test       */
        }  /*  endif                                                         */

        /*
        ** put idle VDM to sleep
        */
        VPLWaitIdle(tmCur, PEEK_SLEEP_TIME, VPL_USING_SLEEP);

    }  /*  endif                                                             */

}  /*  end VDHReportPeek                                                     */


/*
**  @IBM FUNCTION NAME = VDHNotIdle - Report that VDM is not idle
**  @IBM
**  @IBM DESCRIPTION   =
**  @IBM    BUGBUG
**  @IBM    NOTE: THIS SERVICE WILL BE REMOVED.
**  @IBM    USE VDHWAITIDLE INSTEAD.
*/

VOID VDHENTRY VDHNotIdle()
{
    /* @IBM to be removed as part of DCR */

    fVDMBusy = TRUE;
    VPLWakeIdle(0);

}  /*  end VDHNotIdle                                                        */



/*
** Low level routines for peek loop detection
*/


/****************************************************************************
 *
 * FUNCTION NAME = VDHNoIdle - Stop idle detection for a time
 *
 * DESCRIPTION   =
 *
 *      If the input is nonzero, turn idle detection off, else reset
 *      it to its previous value.
 *
 * ENTRY
 *     hvdm - hvdm to change idle detection state for.
 *     tm   - time in milliseconds idle detection should be off
 *            0 turns it back on
 *
 * EXIT
 *     No return code
 *
 * VDM TERMINATION IMPLICATIONS
 *     None.
 *
 * CONTEXT
 *     Task
 *
 * PCODE
 * if turning off
 *     set time started and length of pause
 *     set fIdleOff to true;
 * else
 *     set fIdleOff to (ulIdleLevel == 100);
 * endif
 * Wake up if sleeping (VDHWakeIdle)
 ****************************************************************************/

 VOID VDHENTRY VDHNoIdle(HVDM hvdm, ULONG tm)
{
    if (tm)
    {
        REFHVDM(hvdm, ULONG, tmStartPause)
            = VDHQuerySysValue(0, VDHGSV_MSECSBOOT);
        REFHVDM(hvdm, ULONG, tmPauseDelay) = tm;
        REFHVDM(hvdm, ULONG, fIdleOff) = TRUE;
    }
    else
    {
        REFHVDM(hvdm, ULONG, tmStartPause) = 0;
        REFHVDM(hvdm, ULONG, tmPauseDelay) = 0;
        REFHVDM(hvdm, ULONG, fIdleOff)
            = (REFHVDM(hvdm, ULONG, ulIdleLevel) == LEVEL_MAX);
    }  /*  endif                                                             */

    VDHWakeIdle(hvdm);

}  /*  end VDHNoIdle                                                         */


/****************************************************************************
 *
 * FUNCTION NAME = VPLResetTime - Reset test time
 *
 * DESCRIPTION   =
 *
 *      Start a new test period.  Counters and time are reset.
 *      If probation time has not expired, the polling count
 *      is set to a high value.
 *
 * ENTRY
 *     tmCur - current time
 *
 * EXIT-SUCCESS
 *     Returns True
 *
 * EXIT-FAILURE
 *     Returns False
 *
 * PCODE
 * set cPeeksTmp to 0
 * use VPLCheckIdle to wakeup if necessary and reset busy variables
 * set fVPLExclusive to True if callibrating, else False
 * if counting peeks for callibration
 *     update cPeeksMax if cPeeks is greater
 * if cPeeks is > half the the fixed threshhold
 *     if DOS idle variable > half the max int 16h count
 *         (high int 21h 0b rate)
 *         set cPeeksTmp to a high initial value
 *     if > EXCLUSIVE_TIMEOUT since last stopped optional video call
 *         set fVPLExclusive to block video notification for a period
 * endif
 * if on probation
 *     use VplResetProbation to reset counters
 * reset *puscDOSIdle, tmStartIdleTest, cInt28Calls
 * cPeeks = cPeekTmp;
 ****************************************************************************/

VOID NEAR PASCAL VPLResetTime(VPLTIME tmCur)
{
    ULONG cPeeksTmp = 0;

    VPLCheckIdle(tmCur);                        /*  reset busy variables     */
    fVPLExclusive = fVplCallibrating;           /*  exclusive when callibrati*/

    /*
    ** save peek rate during callibration
    */
    if (fCountPeeks)
    {
        if (cPeeksMax < cPeeks)
        {
            cPeeksMax = cPeeks;

        }  /*  endif                                                         */
    }  /*  endif                                                             */

    /*
    ** check for special cases
    */
    if (!flSleepState
        && tmCur - tmLastBusy > EXCLUSIVE_TIMEOUT
        && cPeeks > cPeeksWarn)  {

        /*
        ** special handling for int 21h 0bh
        */
        if ((ULONG)*puscDOSIdle  > cInt21Max)
        {
                cPeeksTmp = cProbationPeeks;
        }
        else if (tmCur - tmLastExclusive > EXCLUSIVE_TIMEOUT)
        {

            /*
            ** block out video for time slice to see if really idle
            */
            tmLastExclusive = tmCur;
            fVPLExclusive = TRUE;
        }  /*  endif                                                         */

    }  /*  endif                                                             */

    cPeeks = cPeeksTmp;

    if (flVplProbation)
        VPLResetProbation(tmCur);

    *puscDOSIdle = 1;                   /*  not busy + int 21h 0b count      */
    tmStartIdleTest = tmCur;            /*  record time for new period       */
    cInt28Calls = 0;                    /*  number of int 28hs in period     */

}  /*  end VPLResetTime                                                      */

/****************************************************************************
 *
 * FUNCTION NAME = VPLResetProbation - reset probation variables
 *
 * DESCRIPTION   =
 *
 *      If probation time has not expired the polling count is started
 *      at a high value.  If a number of test periods have passed
 *      without finding the VDM idle or if a good deal of time has
 *      passed without being caught, probation is ended.
 *
 * ENTRY
 *     tmCur - current time
 *
 * EXIT-SUCCESS
 *     Returns True
 *
 * EXIT-FAILURE
 *     Returns False
 *
 * PCODE
 * if not caught idle for a while
 *     end probatoin
 * else
 *     if on peek probation a
 *         if peek probation time has not elapsed
 *             set initial peek count high
 *         else
 *             end peek probation
 *     endif
 * endif
 ****************************************************************************/

VOID NEAR PASCAL VPLResetProbation(VPLTIME tmCur)
{
    /*
    ** remove from probation if not being caught often
    */
    if (tmCur - tmLastCaught > NOT_CAUGHT_TIMEOUT)
    {
            flVplProbation = 0;
            tmStartProbation = 0;

    }
    else if (flVplProbation & VPL_PEEK_PROBATION
             && tmCur - tmStartProbation > tmIdleDelay)
        {

        /*
        ** peek probation, set cPeeks high to catch peeks quickly
        */
        if (tmCur - tmPeekProbation < tmIdleDelay + PEEK_PROBATION_TIME)
            cPeeks = cProbationPeeks;
        else
            flVplProbation = VPL_PEEK_TRIAL;
    }  /*  endif                                                             */

}  /*  end VPLResetProbation                                                 */


/****************************************************************************
 *
 * FUNCTION NAME = VPLRetWaitIdle - Return from Idle sleep
 *
 * DESCRIPTION   =
 *
 *      Go back to sleep if still idle after returning from a simulated
 *      interrupt.
 *
 * ENTRY
 *     p     - unused
 *     pcrf  - unused
 *
 * EXIT
 *     No return code
 *
 * CONTEXT
 *     VDM Task
 *
 * PCODE
 * reset fVplWakeup and flSleepState since sleep is over
 * if (idle sleep time has not expired and no busy reports)
 *     go back to sleep using VPLWaitIdle
 ****************************************************************************/

VOID HOOKENTRY VPLRetWaitIdle(PVOID p, PCRF pcrf)
{
    VPLTIME tmCur, tmSlept, tmLeft;

    fVplWakeup = FALSE;
    flSleepState = 0;

    if(flVplProbation && !fVDMBusy && *puscDOSIdle)  {

        tmCur = VDHQuerySysValue(0, VDHGSV_MSECSBOOT);
        tmSlept = tmCur - tmStartSleep;

        if (tmSlept + IDLE_SLEEP_MIN < tmSleepWanted)
            VPLWaitIdle(tmCur, tmSleepWanted - tmSlept, VPL_USING_SLEEP);

    } /*  endif                                                              */

}  /*  end VPLRetWaitIdle                                                    */


/****************************************************************************
 *
 * FUNCTION NAME = VPLWaitIdle - sleep while idle
 *
 * DESCRIPTION   =
 *
 *      Sleep until either a timeout expires or until a virtual interrupt
 *      is ready to be simulated.
 *
 * ENTRY
 *     tmCur     - current time
 *     ulTimeout - timout in milliseconds
 *     flSleep   - type of sleep desired
 *
 * EXIT-SUCCESS
 *     Returns True
 *
 * EXIT-FAILURE
 *     Returns False
 *
 * VDM TERMINATION IMPLICATIONS
 *     None.
 *
 * CONTEXT
 *     VDM Task
 *
 * PCODE
 * set tmLastCaught and clear cNotCaught (used in probation logic)
 * if VDM is not already sleeping
 * and idle detection is not off
 * and VDM has been on probation minimum time required
 * and timeout timer is not set
 * and sleep request is nonzero
 * and VDMs interrupts are on
 *
 *     save time for start of sleep and duration
 *     if VDHWakeVIRR sleep requested
 *         use VPLMakeSleep
 *     else if idle priority requested
 *         use VPLMakeIdle
 * endif
 * clear count of peeks, cpeeks
 ****************************************************************************/

VOID NEAR PASCAL VPLWaitIdle(VPLTIME tmCur, ULONG ulTimeOut, ULONG flSleep)
{
    PCRF pcrf;

    /*
    ** get pcrf pointer
    */
    pcrf = (HVDM)VDHQuerySysValue(0, VDHLSV_PCRF);

    /*
    ** track when last caught for determining if probation should end
    */
    tmLastCaught = tmCur;

    if (!flSleepState
        && !fIdleOff
        && tmCur - tmStartProbation >= tmIdleDelay
        && !fIdleTimerSet
        && ulTimeOut
        && (FL(pcrf) & F_INTERRUPT))              {

        /*
        ** make VDM sleep
        */
        tmStartSleep = tmCur;
        tmSleepWanted = ulTimeOut;

        if (flSleep == VPL_USING_SLEEP)
            VPLMakeSleep(ulTimeOut);
        else if (flSleep == VPL_USING_IDLE)
            VPLMakeIdle(ulTimeOut);

        cPeeks = 0;
    }  /*  endif                                                             */

}  /*  end VPLWaitIdle                                                       */


/****************************************************************************
 *
 * FUNCTION NAME = VPLMakeSleep - make VDM sleep
 *
 * DESCRIPTION   =
 *
 *      Use VDHWaitVIRRs to sleep.  When VDHWakeVIRRs returns
 *      true VPLRetWaitIdle will be called after simulated
 *      interrupts.  This is noted to prevent putting the
 *      VDM to sleep again during interrupt simulation.
 *
 * ENTRY
 *     None
 *
 * EXIT
 *     None
 *
 * VDM TERMINATION IMPLICATIONS
 *     None.
 *
 * CONTEXT
 *     VDM Task
 *
 * NOTE: Do not change this procedure without carefully
 * considering race conditions
 *
 * PCODE
 * set flSleepState, fVplWakeup and fIdleTimerSet
 * call VDHArmTimerHook to set up wakeup call
 * if busy flag is not set
 *     if VDHWaitVIRRs returns true
 *         flSleepState set to pending VPLRetWaitIdle call
 *     else
 *         clear flSleepState since sleep is done
 *     endif
 * clear fVplWakeup since awake
 * if timout timer is still set
 *     disarm timer
 * endif
 ****************************************************************************/

VOID NEAR PASCAL VPLMakeSleep(ULONG ulTimeOut)
{
    flSleepState = VPL_USING_SLEEP;
    fVplWakeup = TRUE;
    fIdleTimerSet = TRUE;
    VDHArmTimerHook(hhookIdleTimeOut, ulTimeOut, VDH_TIMER_GLOBAL_CONTEXT);

    if (!fVDMBusy && VDHWaitVIRRs(hhookIdleWaitVIRR))
    {
        flSleepState = VPL_INT_SIMULATION; /*  do not reenter                */
    }
    else
    {
        flSleepState = 0;          /*  finished sleeping                     */
    }  /*  endif                                                             */

    fVplWakeup = 0;                /*  wakeup not needed                     */

    /*
    ** disarm timer if still set
    */
    if (fIdleTimerSet)
    {
        VDHDisarmTimerHook(hhookIdleTimeOut);
        fIdleTimerSet = FALSE;
    }  /*  endif                                                             */

}  /*  end VPLMakeSleep                                                      */


/****************************************************************************
 *
 * FUNCTION NAME = VPLMakeIdle - set to idle priority
 *
 * DESCRIPTION   =
 *
 *      Set VDM to idle priority.
 *
 * ENTRY
 *     None
 *
 * EXIT
 *     None
 *
 * VDM TERMINATION IMPLICATIONS
 *     None.
 *
 * CONTEXT
 *     VDM Task
 *
 * NOTE: Do not change this procedure without carefully
 * considering race conditions
 *
 * PCODE
 * use regular high priority (VDHSetPriority)
 * set idle priority (VDHSetPriority)
 * set flSleepState, fVplWakeup, and fIdleTimerSet
 * set timeout timer
 * if interrupt has caused VDM to be marked busy
 *     use vplwakeidle to wake it back up
 * end use of regular class
 * (regular class use is to keep idle from taking effect)
 ****************************************************************************/

VOID NEAR PASCAL VPLMakeIdle(ULONG ulTimeOut)
{
    /*
    ** use regular class to  avoid slowdown while setting to idle
    */
    VDHSetPriority(0,VDHSP_REGULAR | VDHSP_START_USE,+31);

    /*
    ** set priority to idle
    */
    VDHSetPriority(0,VDHSP_IDLE | VDHSP_START_USE,0);
    flSleepState = VPL_USING_IDLE;
    fVplWakeup = TRUE;
    fIdleTimerSet = TRUE;
    VDHArmTimerHook(hhookIdleTimeOut, ulTimeOut,VDH_TIMER_INTERRUPT_HOOK);

    if (fVDMBusy)
         VPLWakeIdle(0);

    /*
    ** end use of regular class
    */
    VDHSetPriority(0,VDHSP_REGULAR | VDHSP_END_USE,-31);

}  /*  VPLMakeIdle                                                           */


/****************************************************************************
 *
 * FUNCTION NAME = VPLCheckIdle - check busy flags
 *
 * DESCRIPTION   =
 *
 *      Check busy flags and wake up if necessary
 *
 * ENTRY
 *     tmCur   - current time
 *
 * EXIT
 *     Returns True if not busy.
 *     Returns False if busy.
 *
 * VDM TERMINATION IMPLICATIONS
 *     None.
 *
 * CONTEXT
 *     VDM Task
 *
 * PCODE
 *     if either busy flag is set
 *         note the time in tmLastBusy
 *         take off probation
 *         reset busy flags
 *         if sleeping or set to idle
 *             wake up using VPLWakeIdle
 *         endif
 *     endif
 ****************************************************************************/

BOOL NEAR PASCAL VPLCheckIdle(VPLTIME tmCur)
{
    BOOL fIdle = !fVDMBusy && *puscDOSIdle;

    if (!fIdle)
    {

        tmLastBusy = tmCur;
        flVplProbation = 0;
        tmStartProbation = 0;
        tmPeekProbation = 0;
        fVDMBusy = 0;
        *puscDOSIdle = 1;

        if (fVplWakeup)
            VPLWakeIdle(0);

    }  /*  endif                                                             */

    return(fIdle);

}  /*  end VPLCheckIdle                                                      */



/****************************************************************************
 *
 * FUNCTION NAME = VPLDosWaitIdle - handle yield requests from DOSKRNL
 *
 * DESCRIPTION   =
 *
 *      DOS passes a sleep request time greater than 1
 *      when in its idle loop.
 *
 *      DOS passes 1 when int 28h is called 16 times by an
 *      application without an intervening int 28h call from
 *      DOS, and passes 0 when an application issues
 *      INT 2fh, AX=1680h.
 *
 * ENTRY
 *     ulTimeout - timout in milliseconds
 *                 0 INT 2fh call
 *                 1 indicates int 28h call
 *
 * EXIT
 *     None.
 *
 * VDM TERMINATION IMPLICATIONS
 *     None.
 *
 * CONTEXT
 *     VDM Task
 *
 * PCODE
 * if idle is turned off
 *     return
 *
 * if current test period has ended
 *     VPLResetTime resets counters
 * else
 *     VPLCheckIdle checks busy flags
 * endif
 *
 * if VDM's priority is already set to idle
 *     yield
 * else
 *     if timeout is greater than 1 (in DOS input loop)
 *         call VPLDosSleep
 *     else  (int 2fh yield or int 28h)
 *         call VPLCheckInt28Loop
 * endif
 ****************************************************************************/

VOID NEAR PASCAL VPLDosWaitIdle(ULONG ulTimeout)
{
    VPLTIME tmCur;

    if (fIdleOff)
    {
        if (tmPauseDelay)
        {
            tmCur = VDHQuerySysValue(0, VDHGSV_MSECSBOOT);
            if (tmCur - tmStartPause >= tmPauseDelay)
            {
                tmPauseDelay = 0;
                fIdleOff = (ulIdleLevel == LEVEL_MAX);
            } /*  endif                                                      */

        }  /*  endif                                                         */

        return;

    }  /*  endif                                                             */

    tmCur = VDHQuerySysValue(0, VDHGSV_MSECSBOOT);

    if (tmCur == tmStartIdleTest)
        VPLCheckIdle(tmCur);        /*  check and reset busy flags           */
    else
        VPLResetTime(tmCur);        /*  reset for new test period            */

    if (flSleepState)
    {

        if (flSleepState & VPL_USING_IDLE)
            VDHYield(0);

    }
    else
    {
        if (ulTimeout > VPL_INT28_CALL)
            VPLDosSleep(tmCur, ulTimeout);

        else
            VPLCheckInt28Loop(tmCur, !ulTimeout);

    }  /*  endif                                                             */

}  /*  end VPLDosWaitIdle                                                    */


/****************************************************************************
 *
 * FUNCTION NAME = VPLDosSleep - Sleep for time DOS requested
 *
 * DESCRIPTION   =
 *
 *      Sleep for time DOS requested.  As the time slept
 *      since the last Busy work was detected increases,
 *      extra time is added to the sleep.  This reduces
 *      the time DOS apps spend polling in the DOS stdin
 *      peek loop when polling has gone on a long time.
 *      VDM is made to yield (VPLWaitIdle).
 *
 * ENTRY
 *     tmCur     - current time
 *     ulTimeout - time to sleep
 *
 * EXIT
 *     None.
 *
 * VDM TERMINATION IMPLICATIONS
 *     None.
 *
 * CONTEXT
 *     VDM Task
 *
 * PCODE
 * if not on DOS loop probation
 *     set tmStartProbation to current time
 *     note that it is DOS loop probation
 *       (cancels any previous type of probation)
 * endif
 * Add extra time to sleep time if on DOS probation for a while
 * use VPLWaitIdle to sleep.
 ****************************************************************************/

VOID NEAR PASCAL VPLDosSleep(VPLTIME tmCur, ULONG ulTimeout)
{
    VPLTIME tmInDosLoop;

    if (flVplProbation != VPL_DOS_PROBATION)
    {
        flVplProbation = VPL_DOS_PROBATION;
        tmStartProbation = tmCur;
    }  /*  endif                                                             */

    if (ulTimeout < DOS_SLEEP_TIME)
        ulTimeout = DOS_SLEEP_TIME;

    if ((tmInDosLoop = tmCur - tmStartProbation) > 60000)
    {

        if ((tmInDosLoop) > 300000)

            if ((tmInDosLoop) > 900000)
                ulTimeout += 2000;
            else
                ulTimeout += 1000;

        else
            ulTimeout += 150;


    }  /*  endif                                                             */

    VPLWaitIdle(tmCur, ulTimeout, VPL_USING_SLEEP);

}  /*  end VPLDosSleep                                                       */



/****************************************************************************
 *
 * FUNCTION NAME = VPLCheckInt28Loop - check for int 28 idle loop
 *
 * DESCRIPTION   =
 *
 *      Check for v86 app looping and doing int 28h's
 *      or int 2fh yield.
 *
 *      DOS uses sleep SVC with sleep time 0 when a number of
 *      int 28h's occur from an application without intervening
 *      int 28hs from DOS.  DOS also calls for int 2fh yield.
 *      If called twice in the same time slice, the
 *      VDM is made to yield (VPLWaitIdle).
 *
 * ENTRY
 *     tmCur   - current time
 *     flYield - if set, call is from int 2f yield request
 *
 * EXIT
 *     None.
 *
 * VDM TERMINATION IMPLICATIONS
 *     None.
 *
 * CONTEXT
 *     VDM Task
 *
 * PCODE
 * if not on int 28h probation already
 *     set tmStartProbation to current time
 *     note that it is int 28h probation
 *       (cancels any previous type of probation)
 * endif
 * increment count of calls in test period
 * if number of calls exceeds threshhold
 *     increase sleep time thee longer in the loop without busy
 *     use VPLWaitIdle to sleep
 *      (will change to use idle priority).
 ****************************************************************************/

VOID NEAR PASCAL VPLCheckInt28Loop(VPLTIME tmCur, ULONG flYield)
{
    ULONG ulTimeout = INT28_SLEEP_TIME;

    if (!flYield && tmCur - tmLastBusy < INT28_BUSY_TIMEOUT)
        return;


    if (flVplProbation != VPL_28H_PROBATION)
    {
        flVplProbation = VPL_28H_PROBATION;
        tmStartProbation = tmCur;
    }  /*  endif                                                             */

    if (++cInt28Calls >= cInt28Max || flYield)
    {

        if (tmCur - tmStartProbation > 5000)
        {

            if (cPeeks)
                ulTimeout += 250;

            if (tmCur - tmStartProbation > 120000)
            {
                if (tmCur - tmStartProbation > 300000)
                    ulTimeout += 1000;   /*  waiting a long time             */
                else
                    ulTimeout += 500;
            }  /*  endif                                                     */

        }  /*  endif                                                         */

        /*
        ** change to using idle class here
        */
        VPLWaitIdle(tmCur, ulTimeout, VPL_USING_SLEEP);

        cInt28Calls = 0;

    }  /*  endif                                                             */

}  /*  end VPLCheckInt28Loop                                                 */



/*
** Creation, callibration, reset functions
*/

/****************************************************************************
 *
 * FUNCTION NAME = VPLCreate - Polling detection creation routine
 *
 * DESCRIPTION   =
 *
 *      Allocate VDM hooks for polling detection
 *
 * ENTRY
 *     hvdm - hvdm being created
 *
 * EXIT-SUCCESS
 *     Returns True
 *
 * EXIT-FAILURE
 *     Returns False
 *
 * PCODE
 * Allocate wait_virrs hook, timer_hook and context hook
 * Get pointer to data for timer hook and context hook
 * Set hook data to hvdm
 * If any failures return false, else true
 ****************************************************************************/

RETCODE NEAR PASCAL VPLCreate(HVDM hvdm)
{

    PHVDM phvdmIdleTimeOut;          /*  hook return data                    */
    PHVDM phvdmContextHook;          /*  hook return data                    */
    PSZ pszVMBoot = NULL;            /*  ssloop                              */

    if ( !(hhookIdleWaitVIRR =
        VDHAllocHook(VDH_WAITVIRRS_HOOK,(PFNARM)VPLRetWaitIdle,0))
      || !(hhookIdleTimeOut =
        VDHAllocHook(VDH_TIMER_HOOK, (PFNARM)VPLIdleTimeOut, 4))
      || !(phvdmIdleTimeOut = (HVDM  *)VDHQueryHookData(hhookIdleTimeOut))
      || !(hhookWakeIdle =
        VDHAllocHook(VDH_CONTEXT_HOOK, (PFNARM)VPLIdleHook, 4))
      || !(phvdmContextHook = (HVDM  *)VDHQueryHookData(hhookWakeIdle)) )
    {

        return(FALSE);
    }  /*  endif                                                             */

    *phvdmContextHook = hvdm;
    *phvdmIdleTimeOut = hvdm;

    /*
    ** @IBM BUGBUG 19-Jun-90 JeffPar
    ** @IBM V86Box support requires different callibration method
    */

    pszVMBoot = (PSZ)VDHQueryProperty(szVMBoot);
    if (*pszVMBoot != 0)
    {
        fVMBoot = TRUE;
        VPLDemInit(NULL);
    }

    if (pszVMBoot)
    {
      VDHFreeMem(pszVMBoot);
    }

    return(TRUE);
}  /*  end VPLCreate                                                         */


/****************************************************************************
 *
 * FUNCTION NAME = VPLDemInit - Polling detection initialization
 *
 * DESCRIPTION   =
 *
 *      Set polling threshhold.
 *
 * ENTRY
 *     pDosAddrs   - Dos notification ulong
 * EXIT
 *     No return value.
 *
 * PCODE
 * set local cPeeksNeeded to 0
 * if first time called in this VDM and not a VM machine
 *     call VPLStartInit to determine cPeeksNeeded
 * endif
 * if cPeeksNeeded is 0
 *     call VPLEndInit to set values
 * endif
 * bump cVplLocalInit count of local init attempts
 ****************************************************************************/

ULONG NEAR PASCAL VPLDemInit(PULONG pDosAddrs)
{
    ULONG cPeeksNeeded = 0;

    if (!fVMBoot && !cVplLocalInit)
    {
        cPeeksNeeded = VPLStartInit(pDosAddrs);

    }  /*  endif                                                             */

    if (!cPeeksNeeded)
    {
        cPeeksNeeded = VPLEndInit();
    }  /*  endif                                                             */

    cVplLocalInit++;    /*  number of callibrations attempted                */

    return(cPeeksNeeded);

}  /*  end VPLDemInit                                                        */


/****************************************************************************
 *
 * FUNCTION NAME = VPLStartInit - Polling detection initialization
 *
 * DESCRIPTION   =
 *
 *      Set polling threshhold.
 *
 * ENTRY
 *     pDosAddrs   - Dos notification address
 * EXIT
 *     returns number of peeks to do
 *
 * PCODE
 * if DOS address was passed in
 *     store DOS Idle address
 * endif
 * if not VM machine and no more than 2 previous attempts
 *  in this VDM and no more than VPL_INIT_ATTEMPTS across VDMs
 *     set up for callibration
 *     if successful callibrations already done
 *         ask for 3 * highest rate so far
 *     else if first try in this VDM
 *         ask for a fixed large number of peeks
 *     else if second try (did not get good reading first time)
 *         do 8 times as many as first time
 *     boost priority
 * endif
 ****************************************************************************/

ULONG NEAR PASCAL VPLStartInit(PULONG pDosAddrs)
{
    ULONG cPeeksWanted = 0;

    if (pDosAddrs != NULL)
    {
        puscDOSIdle = (PUSHORT)pDosAddrs;
        *puscDOSIdle = 1;

    }  /*  endif                                                             */

    if (!fVMBoot && cVplLocalInit < 2 && cVplInitDone < VPL_INIT_ATTEMPTS)
    {
        fCountPeeks = TRUE;         /*  track max peeks in any period?       */
        fVplCallibrating = TRUE;    /*  don't allow video notification       */
        fVPLExclusive = TRUE;       /*  video peek notification off          */
        cInt28Max = 0xffffffff;
        cPeeksMax = 0;
        cIdleMax = 0xffffffff;
        VDHSetPriority(0, VDHSP_REGULAR | VDHSP_START_USE, 31);
        fVplPriorityHigh = TRUE;

        /*
        ** determine number of peeks to do
        */
        if (cVplInitDone)
            cPeeksWanted = 3*cVKBDPeekMax;
        else if (cVplLocalInit)
            cPeeksWanted = 2*INIT_PEEKS_COUNT;
        else
            cPeeksWanted = INIT_PEEKS_COUNT;

    }  /*  endif                                                             */

    return(cPeeksWanted);

}  /*  end VPLStartInit                                                      */


/****************************************************************************
 *
 * FUNCTION NAME = VPLUpdateKeyWeight - Update key weight
 *
 * DESCRIPTION   =
 *
 *      Set polling threshhold.
 *
 * ENTRY
 *     None
 * EXIT
 *     False if another callibration is needed.
 *
 * PCODE
 * claim semaphore to protect globals
 * if not a VM machine
 *   if new max peek count during callibration was
 *                                higher than cVKBDPeekMax
 *     set cVKBDPeekMax to new high value
 *   if observed max peeks was higher than min value
 *     increment count of successful callibrations
 *   else if only 1 attempt was made
 *     try again by feturning false
 * else if a VM machine and no previous callibration
 *     set cVKBDPeekWeight to a default value
 * free semaphore
 ****************************************************************************/

BOOL NEAR PASCAL VPLUpdateKeyWeight()
{
    BOOL fOK = TRUE;

    VDHRequestMutexSem(hvdhsemVPL, SEM_INDEFINITE_WAIT);

    if (!fVMBoot)
    {

        if (cVKBDPeekMax < cPeeksMax)
        {
            cVKBDPeekMax = cPeeksMax;
            cVKBDPeekWeight = VPL_IDLE_THRESHHOLD / cPeeksMax;
            if (!cVKBDPeekWeight)
                cVKBDPeekWeight = 1;
        }  /*  endif                                                         */

        if (cPeeksMax > DEFAULT_PEEKS)
            cVplInitDone++;     /*  bump initialization attempts             */
        else if (cVplLocalInit == 1)
            fOK = FALSE;
        else if (cVplLocalInit > 1 && !cVplInitDone && cPeeksMax)
            cVKBDPeekWeight = VPL_IDLE_THRESHHOLD / cPeeksMax;

    }
    else if (!cVplInitDone)
    {
        cVKBDPeekWeight = VPL_IDLE_THRESHHOLD/DEFAULT_PEEKS;

    }  /*  endif                                                             */

    VDHReleaseMutexSem(hvdhsemVPL);

    return(fOK);

}  /*  end VPLUpdateKeyWeight                                                */


/****************************************************************************
 *
 * FUNCTION NAME = VPLEndInit - Polling detection initialization
 *
 * DESCRIPTION   =
 *
 *      Set polling threshhold.
 *
 * ENTRY
 *     None
 * EXIT
 *     returns number of callibration peeks wanted
 *
 * PCODE
 * reset priority if set high
 * if VplUpdateKeyWeight indicates another callibration needed
 *   cPeeksNeeded = VplStartInit(0);
 * if cPeeksNeeded is zero finish setting variables
 *   get delay property and level property
 *   initialize all the counts
 ****************************************************************************/

ULONG NEAR PASCAL VPLEndInit()
{
    ULONG cPeeksNeeded = 0, ul;

    if (fVplPriorityHigh)
    {
        VDHSetPriority(0, VDHSP_REGULAR | VDHSP_END_USE, -31);
        fVplPriorityHigh = FALSE;

    }  /*  endif                                                             */

    if (cVplLocalInit && !VPLUpdateKeyWeight())
        cPeeksNeeded = VPLStartInit(0);

    if (!cPeeksNeeded)
    {

        /*
        **  Set idle threshhold
        */
        if (!(ulIdleLevel = VDHQueryProperty(szPropPoll)))
            ulIdleLevel = 1;

        /*
        ** set delay threshhold
        */
        tmIdleDelay = VDHQueryProperty(szPropDelay);

        fCountPeeks = FALSE;        /*  track max peeks in any period?       */
        fVplCallibrating = FALSE;   /*  finished callibrating                */
        fVPLExclusive = FALSE;      /*  video peek notification allowed      */
        cInt28Max = INT28_MAX;      /*  sleep after this many calls from DOS */
        fVDMBusy = TRUE;            /*  start out busy                       */

        flVplProbation = 0;
        tmStartProbation = 0;
        cPeeksNeeded = 0;

        /*
        ** reset variables that depend on level
        */
        cIdleMax = VPL_IDLE_THRESHHOLD * ulIdleLevel / 100;
        cPeeksWarn = cIdleMax * 3 / 5 + 1;
        cInt21Max = cIdleMax/(3*cVKBDPeekWeight)+1;
        ul = cVKBDPeekWeight * PROBATION_PEEK_MAX;
        cProbationPeeks = (ul < cIdleMax) ? cIdleMax - ul : 0;
        fIdleOff = (ulIdleLevel == LEVEL_MAX);

    }  /*  endif                                                             */

    return(cPeeksNeeded);

}  /*  end VPLEndInit                                                        */


/****************************************************************************
 *
 * FUNCTION NAME = fnSetPoll - set polling level
 *
 * DESCRIPTION   =
 *
 * ENTRY
 *     op - operation to perform (set)
 *     hvdm - target VDM
 *     cb - length of value
 *     pch - ptr to CHAR is actually ULONG level
 *
 * EXIT
 *     returns 0 (no error)
 *
 * CONTEXT OS/2 task context (usually the Shield layer)
 *
 * PCODE
 * set ulIdleLevel to desired level
 * if (not callibrating now)
 *     reset cIdleMax threshhold
 *     if sleeping
 *         use VDHWakeIdle to wake up
 *     turn idle detection off if level == max
 * endif
 ****************************************************************************/

ULONG EXPENTRY fnSetPoll(ULONG op, HVDM hvdm, ULONG cb, CHAR *pch)
{
    ULONG   ulLevel, ulMax, ul;

    if (!(ulLevel = (ULONG)pch))
        ulLevel = 1;

    REFHVDM(hvdm, ULONG, ulIdleLevel) = ulLevel; /*  set new level           */

    if (!REFHVDM(hvdm, BOOL, fVplCallibrating))
    {
        ulMax = VPL_IDLE_THRESHHOLD * ulLevel / 100;
        REFHVDM(hvdm, ULONG, cIdleMax) = ulMax + 1;
        REFHVDM(hvdm, ULONG, cPeeksWarn) = ulMax * 3 / 5 + 1;
        REFHVDM(hvdm, ULONG, cInt21Max) = ulMax/(3*cVKBDPeekWeight)+1;
        ul = cVKBDPeekWeight * PROBATION_PEEK_MAX;
        REFHVDM(hvdm, ULONG, cProbationPeeks) = (ul < ulMax) ? ulMax-ul : 0;
        REFHVDM(hvdm, ULONG, tmStartProbation) = 0;
        REFHVDM(hvdm, ULONG, flVplProbation) = 0;
    }  /*  endif                                                             */

    /*
    ** turn detection on/off, as appropriate
    */
    if (!REFHVDM(hvdm, BOOL, fFirstInit))
    {

        if (REFHVDM(hvdm, BOOL, fVplWakeup))
            VDHWakeIdle(hvdm);

        REFHVDM(hvdm, ULONG, fIdleOff) = (ulLevel == LEVEL_MAX);

    }  /*  endif                                                             */

    return 0;
}


/****************************************************************************
 *
 * FUNCTION NAME = fnSetPollDelay - set polling level
 *
 * DESCRIPTION   =
 *
 * ENTRY
 *     op - operation to perform (set)
 *     hvdm - target VDM
 *     cb - length of value
 *     pch - ptr to CHAR is actually ULONG delay (seconds)
 *
 * EXIT
 *     returns 0 (no error)
 *
 * CONTEXT OS/2 task context (usually the Shield layer)
 *
 * PCODE
 * set delay to desired level
 * if not callibrating now and currently sleeping
 *     use VDHWakeIdle to wake up
 * endif
 *
 ****************************************************************************/

ULONG EXPENTRY fnSetPollDelay(ULONG op, HVDM hvdm, ULONG cb, CHAR *pch)
{
    REFHVDM(hvdm, ULONG, tmIdleDelay) = (ULONG)pch * 1000;

    /*
    ** wake up if asleep
    */
    if (!REFHVDM(hvdm, BOOL, fFirstInit) && REFHVDM(hvdm, BOOL, fVplWakeup))
        VDHWakeIdle(hvdm);

    return 0;
}


/****************************************************************************
 *
 * FUNCTION NAME = VPLDosReqProc - Polling requests from kernel
 *
 * DESCRIPTION   =
 *
 *      Kernel requests addresses for VPL serveces used by DEM.
 *
 * ENTRY
 *     hvdm    - not used (assume current context is VDM)
 *     ulFunct - not used
 *     pIn     - not used
 *     pOut    - filled with addresses for
 *                 VPLDemInit
 *                 VPLDosWaitIdle
 *
 * EXIT
 *     Always returns TRUE
 *
 * CONTEXT
 *     VDM Task.
 *
 * PCODE
 * return addresses for VPLDemInit, VPLDosWaitIdle
 *
 *   in buffer pointed to by pOut
 ****************************************************************************/

LONG EXPENTRY VPLDosReqProc(HVDM hvdm, ULONG ulFunc, PVOID pIn, PVOID pOut)
{
    ULONG ulRet = fVplInitOK && pOut;

    if (ulRet)
    {
        ((PFN *)pOut)[VDHVPL_BUSY_ADDR] = (PFN)VPLDemInit;
        ((PFN *)pOut)[VDHVPL_WAIT] = (PFN)VPLDosWaitIdle;

/*
** @IBM
** @IBM BUGBUG wayneca 10-5-90 enable to let vtimer turn off idle detection
** @IBM     ((PFN *)pOut)[VDHVPL_NOIDLE] = (PFN)VDHNoIdle;
** @IBM     ((PFN *)pOut)[VDHVPL_WAKE] = (PFN)VDHWakeIdle;
*/
    }  /*  endif                                                             */

    return(ulRet);

}  /*  end VPLReqProc                                                        */


#pragma END_SWAP_CODE

