/*static char *SCCSID = "@(#)vdmtime.c  6.3 92/02/27";*/

#define SCCSID "@(#)vdmtime.c   6.3 92/02/27"

/***    VDMTIME.C
 *
 *      IBM/Microsoft Confidential
 *
 *      Copyright (c) IBM Corporation 1987, 1989
 *      Copyright (c) Microsoft Corporation 1987, 1989
 *
 *      All Rights Reserved
 *
 *      Title:
 *
 *      VDMTIME  Exported Procedures
 *
 *      Description:
 *
 *      Exported VDH services to support timer operations
 *
 *      Modification History:
 *
 *      6.3 02/27/92 B731836 S. Barnes  vdmmproctick change
 *          03/20/93  063248 K. Luo     Added MMIO perf hooks
 *          10/08/94 @101671 D.Azzarito  Remove PANIC from retail kernel
 */
#define FNO_NUM  FNO_VDMTIME_C
#include <fnomvdm.h>

#define INCL_NONE
#define INCL_TYPES              // include base types
#define INCL_MVDMTYPES          // include mvdm types
#define INCL_VDHALL             // include VDH services

#include <vdmvdh.h>
#include <kmode.h>
#include <em86.h>

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

// Internal timer services flag - means the timer hook has been armed
// as a context hook now.  Disarm needs to know this.
#define VDH_TIMER_CONTEXT_HOOK_SET      ((PVOID)2)

extern HVDM vdmid_HVDM;                 // current HVDM

VOID PRIVENTRY vdmmDisarmTimerHooks(VOID);

#pragma BEGIN_MVDM_GLOBAL_DATA

TIMH timhTail;                          // dummy tail timer node
HOOKHEAD hookheadTIMH;                  // head of armed timer list
BOOL fIsMarkOn=FALSE;                   // 6.3 timer hook semaphore

#pragma END_MVDM_GLOBAL_DATA

#pragma BEGIN_MVDM_INIT_CODE

/***LP  VDMMInitTimer - initialize vdmm timer services
 *
 *      ENTRY
 *          None
 *
 *      EXIT
 *          None
 *
 *      PCODE
 *          initialize the timer block list head
 *          add the dummy tail node to the timer block head
 */

VOID EXPENTRY VDMMInitTimer()
{
    KMInitHookHead(&hookheadTIMH);
    KMAddHookAfter(&timhTail.timh_hook, (PHOOK)&hookheadTIMH);
    em86AssertCreate(HOOK, &timhTail.timh_hook);
    em86AssertCreate(TIMH, &timhTail);
    timhTail.timh_ulmsDelta = 0;
}

#pragma END_MVDM_INIT_CODE


//#pragma BEGIN_MVDM_SWAP_CODE
#pragma code_seg ("SWAP32_27","CODE")
/***LP  vdmmDisarmTimerHooks - clean up timer hooks
 *
 *      This function disarms all the timer hooks during VDM
 *      termination.
 *
 *      ENTRY   NONE
 *
 *      EXIT    NONE
 *
 *      CONTEXT Task
 *
 *      PSEUDO-CODE
 *
 *      for each entry in the TCB_phookOwnerHead list
 *          if timer hook
 *             disarm timer hook
 */

VOID PRIVENTRY vdmmDisarmTimerHooks()
{
    register PHOOK phook;

    phook = pTCBCur->TCB_phookOwnerHead;
    while(phook != NULL) {
        kmAssertP(HOOK, phook);
        if(KMQueryHookType(phook) == VDH_TIMER_HOOK) {
            VDHDisarmTimerHook(phook);
        }
        phook = phook->hook_phookOwner;
    }
}
#pragma code_seg()
//#pragma END_MVDM_SWAP_CODE

#pragma BEGIN_MVDM_GLOBAL_CODE

/***EP+ VDHArmTimerHook - Set a timer
 *
 *      Set a timer handler to be called after a specified
 *      duration has elapsed. The handler is called only once.
 *
 *      ENTRY
 *          hhook - hook handle, this hook should be allocated
 *                  as a VDH_TIMER_HOOK regardless of what you
 *                  provide in hvdm.
 *          cMillisecond - timer duration in milliseconds
 *          hvdm - hvdm or flag
 *              hvdm or 0
 *                  A local context hook is automaticly armed when
 *                  the timer pops.
 *              VDH_TIMER_GLOBAL_CONTEXT
 *                  A global context hook is automaticly armed when
 *                  the timer pops.
 *              VDH_TIMER_INTERRUPT_HOOK
 *                  The timer handler is called at interrupt time.
 *
 *      EXIT-SUCCESS
 *          None.
 *
 *      EXIT-FAILURE
 *          System Halt.
 *              If cMillisecond or hhook is invalid, or the hook is already
 *              armed.
 *
 *      CONTEXT
 *          Init
 *          Task
 *          Interrupt
 *
 *      VDM TERMINATION IMPLICATIONS
 *          Any timer hooks allocated in the context of the terminating
 *          VDM are deallocated.
 *
 *      NOTES
 *          (1) The timer handler may not block, since it executes at
 *              physical interrupt time if VDH_TIMER_INTERRUPT_HOOK
 *              type.
 *
 *          (2) Hook routine interface:
 *                  VOID HOOKENTRY pfn(pRefData)
 *                      ENTRY   pRefData - pointer to reference data
 *
 *                      EXIT    none
 *
 *                      CONTEXT Interrupt
 *
 *      PCODE
 *          validate parameters
 *          save and disable interrupt flag
 *          for each entry on timer linked list
 *              if the requested time < the total so far + current timh's delta
 *                 break
 *              add the current timh's delta to the total
 *              next timh entry
 *          the new hook entry's delta value is (requested time - the total)
 *          subtract the new hook's delta from the next entry in the list
 *          add the new hook entry before the current timh (KMArmHook)
 *          restore interrupt flag
 *          return
 *
 */

VOID VDHENTRY VDHArmTimerHook(hhook, ulms, hvdm)
HHOOK hhook;
ULONG ulms;
HVDM hvdm;
/*end*/
{
    register ULONG ulmsTotal = 0;
    register PTIMH ptimhCur;
    ULONG ulFlags;

#ifdef MMIOPH
    MMPHPreVDHArmTimerHook()
#endif

    // validate parameters
    em86AssertP(HOOK, (PHOOK)hhook);
    em86AssertP(TIMH, (PTIMH)hhook);
    VDHAssertTRUE(KMQueryHookType(hhook) == VDH_TIMER_HOOK);
    VDHAssertTRUE(hvdm != VDH_TIMER_CONTEXT_HOOK_SET);
    VDHAssertTRUE(ulms != 0);
    ulFlags = TKDisable();
    ptimhCur = KMNextHookLink((PHOOK)&hookheadTIMH);
    while(ptimhCur != &timhTail) {
        em86AssertP(HOOK, &ptimhCur->timh_hook);
        em86AssertP(TIMH, ptimhCur);
        if(ulms < ulmsTotal + ptimhCur->timh_ulmsDelta) {
            break;
        }
        ulmsTotal += ptimhCur->timh_ulmsDelta;
        ptimhCur = KMNextHookLink(&ptimhCur->timh_hook);
    }
    em86AssertP(TIMH, ptimhCur);
#ifdef EM86STRICT
    if(hvdm == CURRENT_VDM) {
        ASSERTVDMTASKONLY;
    }
#endif
    if(hvdm == VDH_TIMER_INTERRUPT_HOOK || hvdm == VDH_TIMER_GLOBAL_CONTEXT) {
        ((PTIMH)hhook)->timh_ptcb = hvdm;
    }
    else {
        CHECKHVDM(hvdm);
        ((PTIMH)hhook)->timh_ptcb = QUERYPTCB(hvdm);
    }
    ((PTIMH)hhook)->timh_ulmsDelta = ulms - ulmsTotal;
    ptimhCur->timh_ulmsDelta -= ((PTIMH)hhook)->timh_ulmsDelta;
    if(KMArmHook(hhook, (PHOOKHEAD)&ptimhCur->timh_hook)) {
#ifdef VDHSTRICT     // @101671
        Panic(("VDHArmTimerHook: already armed"));
#endif
    }
    TKEnable(ulFlags);
#ifdef MMIOPH
    MMPHPostVDHArmTimerHook()
#endif
    return;
}

/***EP+ VDHDisarmTimerHook - Stops a timer
 *
 *      Cancel a timer installed by VDHArmTimerHook before
 *      the handler has been called.
 *
 *      ENTRY
 *          hhook - hook handle
 *
 *      EXIT-SUCCESS
 *          returns TRUE
 *
 *      EXIT-FAILURE
 *          returns FALSE
 *          VDHGetError may return:
 *              ERROR_ALREADY_POSTED
 *                  The timer was already fired.
 *          System Halt.
 *              If hhook is invalid.
 *
 *      VDM TERMINATION IMPLICATIONS
 *          Any timer hooks allocated in the context of the terminating
 *          VDM are deallocated.
 *
 *      CONTEXT
 *          Init
 *          Task
 *          Interrupt
 *
 *      PCODE
 *          save and disable interrupt flag
 *          add this hook's delta to the next timh entry's delta
 *          remove the hook entry from the timer list
 *          if already unarmed
 *              restore interrupt flag
 *              return timer fired (FALSE)
 *          restore interrupt flag
 *          return hook not fired (TRUE)
 */

BOOL VDHENTRY VDHDisarmTimerHook(hhook)
register HHOOK hhook;
/*end*/
{
    register PHOOK phookNext;
    ULONG ulFlags;

#ifdef MMIOPH
    MMPHPreVDHDisarmTimerHook()
#endif

    em86AssertP(HOOK, (PHOOK)hhook);
    em86AssertP(TIMH, (PTIMH)hhook);
    VDHAssertTRUE(KMQueryHookType(hhook) == VDH_TIMER_HOOK);
    ulFlags = TKDisable();
    if(KMIsHookLinked((PHOOK)hhook)) {
        if(((PTIMH)hhook)->timh_ptcb != VDH_TIMER_CONTEXT_HOOK_SET) {
            phookNext = KMNextHookLink((PHOOK)hhook);
            em86AssertP(HOOK, phookNext);
            em86AssertP(TIMH, (PTIMH)phookNext);
            ((PTIMH)phookNext)->timh_ulmsDelta += ((PTIMH)hhook)->timh_ulmsDelta;
        }
        KMDisarmHook(hhook);
    }
    else {
        VDHSetError(ERROR_ALREADY_POSTED);
        TKEnable(ulFlags);
#ifdef MMIOPH
        MMPHPostVDHDisarmTimerHook(FALSE)
#endif
        return(FALSE);
    }
    TKEnable(ulFlags);
#ifdef MMIOPH
    MMPHPostVDHDisarmTimerHook(TRUE)
#endif
    return(TRUE);
}

/***EP+ VDMMProcTick - Process the VDD timer hooks on clock tick
 *
 *      VDMMProcTick is called from SchedClock routine to process
 *      the VDD timer hooks.
 *
 *      ENTRY
 *          ulms - tick duration in millisecond since last tick
 *          ulContext
 *              0 - pre EOI  (Non re-entrant; So process the entries)
 *              1 - post EOI (re-entrant; So call the pending handlers)
 *
 *      EXIT-SUCCESS
 *          None.
 *
 *      EXIT-FAILURE
 *          None.
 *
 *      PCODE
 *          save and disable interrupts
 *          if ulContext == 0
 *              if the first entry's delta is > the tick duration
 *                  // we have to wait some more
 *                  subtract the tick duration from the first entry's delta
 *              else
 *                  // we have some handlers to call now (during post-EOI)
 *                  set the first entry's delta to zero
 *          else
 *              for each on timer list and while the delta fields are zero
 *                  remove the timh entry from timer linked list
 *                  call the VDD handler
 *          restore interrupts
 */

VOID EXPENTRY VDMMProcTick(ulms,ulContext)
ULONG ulms;
ULONG ulContext;
/*end*/
{
    register PTIMH ptimh;
    register PTIMH ptimhNext;
    ULONG ulFlags;

    ulFlags = TKDisable();
    if (!fIsMarkOn)                              // 6.3
    {                                            // 6.3
      fIsMarkOn = TRUE;                          // 6.3
      TKEnable(ulFlags);                         // 6.3

      ptimh = KMNextHookLink((PHOOK)&hookheadTIMH);
      em86AssertP(HOOK, &ptimh->timh_hook);
      em86AssertP(TIMH, ptimh);
      if(!ulContext) {            // pre-EOI case
          while(ptimh != &timhTail) {
              if(ptimh->timh_ulmsDelta >= ulms) {
                  ptimh->timh_ulmsDelta -= ulms;
                  break;
              }
              ulms -= ptimh->timh_ulmsDelta;
              ptimh->timh_ulmsDelta = 0;
              ptimh = KMNextHookLink(&ptimh->timh_hook);
              em86AssertP(HOOK, &ptimh->timh_hook);
              em86AssertP(TIMH, ptimh);
          }
      }
      else {                      // post-EOI case
          while(ptimh != &timhTail && ptimh->timh_ulmsDelta == 0) {
              ptimhNext = KMNextHookLink(&ptimh->timh_hook);
              TKDisable();
              KMDeleteHook(&ptimh->timh_hook);
              TKEnable(ulFlags);
              if(ptimh->timh_ptcb == VDH_TIMER_INTERRUPT_HOOK) {
                  KMCallHook(&ptimh->timh_hook, KMQueryHookData(&ptimh->timh_hook));
              }
              else {
                  KMArmContextHook(&ptimh->timh_hook, ptimh->timh_ptcb);
                  // tell disarm that the context hook has been armed
                  ptimh->timh_ptcb = VDH_TIMER_CONTEXT_HOOK_SET;
              }
              ptimh = ptimhNext;
              em86AssertP(HOOK, &ptimh->timh_hook);
              em86AssertP(TIMH, ptimh);
          }
      }
      ulFlags = TKDisable();                     // 6.3
      fIsMarkOn = FALSE;                         // 6.3
      TKEnable(ulFlags);                         // 6.3
    } else {                                     // 6.3
      TKEnable(ulFlags);                         // 6.3
    }                                            // 6.3
}

#pragma END_MVDM_GLOBAL_CODE
