text: /*++
  
  Copyright (c) 1989-1993  Microsoft Corporation
  
  Module Name:
  
      Timer.c
  
  Abstract:
  
  
      This file contains the code to implement timer functions.
  
  
  Author:
  
      Jim Stewart (Jimst)    10-2-92
  
  Revision History:
  
  --*/
  
  #include "precomp.h"
  #include "timer.h"
  #include "ntddndis.h"
  
  // the timer Q
  tTIMERQ TimerQ;
  
  
  //*******************  Pageable Routine Declarations ****************
  #ifdef ALLOC_PRAGMA
  #pragma CTEMakePageable(PAGE, InitTimerQ)
  #pragma CTEMakePageable(PAGE, DestroyTimerQ)
  #pragma CTEMakePageable(PAGE, DelayedNbtStopWakeupTimer)
  #endif
  // #pragma CTEMakePageable(PAGE, WakeupTimerExpiry)
  //*******************  Pageable Routine Declarations ****************
  
  
  //----------------------------------------------------------------------------
  NTSTATUS
  InterlockedCallCompletion(
      IN  tTIMERQENTRY    *pTimer,
      IN  NTSTATUS        status
      )
  /*++
  
  Routine Description:
  
      This routine calls the completion routine if it hasn't been called
      yet, by first getting the JointLock spin lock and then getting the
      Completion routine ptr. If the ptr is null then the completion routine
      has already been called. Holding the Spin lock interlocks this
      with the timer expiry routine to prevent more than one call to the
      completion routine.
  
  Arguments:
  
  Return Value:
  
      there is no return value
  
  
  --*/
  {
      CTELockHandle       OldIrq;
      COMPLETIONCLIENT    pClientCompletion;
  
      // to synch. with the the Timer completion routines, Null the client completion
      // routine so it gets called just once, either from here or from the
      // timer completion routine setup when the timer was started.(in namesrv.c)
      //
      CTESpinLock(&NbtConfig.JointLock,OldIrq);
  
      pClientCompletion = pTimer->ClientCompletion;
      pTimer->ClientCompletion = NULL;
      if (pClientCompletion)
      {
          // remove the link from the name table to this timer block
          CHECK_PTR(((tNAMEADDR *)pTimer->pCacheEntry));
          ((tNAMEADDR *)pTimer->pCacheEntry)->pTimer = NULL;
  
          CTESpinFree(&NbtConfig.JointLock,OldIrq);
          (*pClientCompletion) (pTimer->ClientContext, status);
          return(STATUS_SUCCESS);
      }
      else
      {
          CTESpinFree(&NbtConfig.JointLock,OldIrq);
      }
  
      return(STATUS_UNSUCCESSFUL);
  }
  
  //----------------------------------------------------------------------------
  NTSTATUS
  InitTimerQ(
      VOID
      )
  /*++
  
  Routine Description:
  
      This routine sets up the timer Q.
  
  Arguments:
  
  
  Return Value:
  
      The function value is the status of the operation.
  
  --*/
  {
      tTIMERQENTRY    *pTimerEntry;
  
      CTEPagedCode();
  
      InitializeListHead(&TimerQ.ActiveHead);
      ExInitializeNPagedLookasideList(
                      &TimerQ.LookasideList,
                      NULL,
                      NULL,
                      0,
                      sizeof(tTIMERQENTRY),
                      NBT_TAG2('16'),
                      0
                      );
      return(STATUS_SUCCESS);
  }
  
  //----------------------------------------------------------------------------
  NTSTATUS
  DestroyTimerQ(
      )
  /*++
  
  Routine Description:
  
      This routine clears up the TimerQEntry structures allocated
  
  Arguments:
  
  
  Return Value:
  
      The function value is the status of the operation.
  
  --*/
  {
      tTIMERQENTRY    *pTimer;
      PLIST_ENTRY     pEntry;
  
      CTEPagedCode();
      ExDeleteNPagedLookasideList(&TimerQ.LookasideList);
  
      return(STATUS_SUCCESS);
  }
  
  //----------------------------------------------------------------------------
  NTSTATUS
  GetTimerEntry(
      OUT tTIMERQENTRY    **ppTimerEntry
      )
  /*++
  
  Routine Description:
  
      This routine gets a free block&TimerQ.
      NOTE: this function is called with the JointLock held.
  
  Arguments:
  
  Return Value:
  
      The function value is the status of the operation.
  
  
  --*/
  {
      PLIST_ENTRY     pEntry;
      tTIMERQENTRY    *pTimerEntry;
  
      pTimerEntry = (tTIMERQENTRY *)ExAllocateFromNPagedLookasideList(&TimerQ.LookasideList);
      if (NULL == pTimerEntry) {
          KdPrint(("Unable to allocate memory!! - for the timer Q\n"));
          return (STATUS_INSUFFICIENT_RESOURCES);
      }
  
      *ppTimerEntry = pTimerEntry;
      pTimerEntry->Verify = NBT_VERIFY_TIMER_ACTIVE;
      NbtConfig.lNumTimersRunning++;
  
      return(STATUS_SUCCESS);
  }
  
  
  //----------------------------------------------------------------------------
  VOID
  ReturnTimerToFreeQ(
      tTIMERQENTRY    *pTimerEntry,
      BOOLEAN         fLocked
      )
  {
      CTELockHandle   OldIrq;
  
      if (!fLocked)
      {
          CTESpinLock(&NbtConfig.JointLock,OldIrq);
      }
  
      // return the timer block
      ASSERT (pTimerEntry->Verify == NBT_VERIFY_TIMER_ACTIVE);
      pTimerEntry->Verify = NBT_VERIFY_TIMER_DOWN;
  
      ExFreeToNPagedLookasideList(&TimerQ.LookasideList, pTimerEntry);
      if (!--NbtConfig.lNumTimersRunning)
      {
          KeSetEvent(&NbtConfig.TimerQLastEvent, 0, FALSE);
      }
  
      if (!fLocked)
      {
          CTESpinFree(&NbtConfig.JointLock,OldIrq);
      }
  }
  
  
  //----------------------------------------------------------------------------
  NTSTATUS
  CleanupCTETimer(
      IN  tTIMERQENTRY     *pTimerEntry
      )
  /*++
  
  Routine Description:
  
      This routine cleans up the timer. Called with the JointLock held.
  
  Arguments:
  
  
  Return Value:
  
      returns the reference count after the decrement
  
  --*/
  {
      COMPLETIONROUTINE   TimeoutRoutine;
      PVOID               Context;
      PVOID               Context2;
  
      pTimerEntry->RefCount = 0;
  
      // the expiry routine is not currently running so we can call the
      // completion routine and remove the timer from the active timer Q
  
      TimeoutRoutine = (COMPLETIONROUTINE)pTimerEntry->TimeoutRoutine;
      pTimerEntry->TimeoutRoutine = NULL;
      Context = pTimerEntry->Context;
      Context2 = pTimerEntry->Context2;
  
      if (pTimerEntry->pDeviceContext)
      {
          NBT_DEREFERENCE_DEVICE ((tDEVICECONTEXT *) pTimerEntry->pDeviceContext, REF_DEV_TIMER, TRUE);
          pTimerEntry->pDeviceContext = NULL;
      }
  
      // release any tracker block hooked to the timer entry.. This could
      // be modified to not call the completion routine if there was
      // no context value... ie. for those timers that do not have anything
      // to cleanup ...however, for now we require all completion routines
      // to have a if (pTimerQEntry) if around the code so when it gets hit
      // from this call it does not access any part of pTimerQEntry.
      //
      if (TimeoutRoutine)
      {
          // call the completion routine so it can clean up its own buffers
          // the routine that called this one will call the client's completion
          // routine.  A NULL timerEntry value indicates to the routine that
          // cleanup should be done.
  
          (VOID)(*TimeoutRoutine) (Context, Context2, NULL);
      }
  
      // move to the free list
      RemoveEntryList(&pTimerEntry->Linkage);
      ReturnTimerToFreeQ (pTimerEntry, TRUE);
  
      return(STATUS_SUCCESS);
  }
  
  
  
  //----------------------------------------------------------------------------
  NTSTATUS
  StartTimer(
      IN  PVOID           TimeoutRoutine,
      IN  ULONG           DeltaTime,
      IN  PVOID           Context,
      IN  PVOID           Context2,
      IN  PVOID           ContextClient,
      IN  PVOID           CompletionClient,
      IN  tDEVICECONTEXT  *pDeviceContext,
      OUT tTIMERQENTRY    **ppTimerEntry,
      IN  USHORT          Retries,
      BOOLEAN             fLocked)
  /*++
  
  Routine Description:
  
      This routine starts a timer.
      It has to be called with the JointLock held!
  
  Arguments:
  
      The value passed in is in milliseconds - must be converted to 100ns
      so multiply to 10,000
  
  Return Value:
      The function value is the status of the operation.
  
  
  --*/
  {
      tTIMERQENTRY    *pTimerEntry;
      NTSTATUS        status;
      CTELockHandle   OldIrq;
  
      //
      // Do not allow any timers to be started if we are currently
      // Unloading!
      //
      if (NbtConfig.Unloading)
      {
          return STATUS_UNSUCCESSFUL;
      }
  
      if (!fLocked)
      {
          CTESpinLock(&NbtConfig.JointLock,OldIrq);
      }
  
      if ((!pDeviceContext) ||
          (NBT_REFERENCE_DEVICE (pDeviceContext, REF_DEV_TIMER, TRUE)))
      {
          // get a free timer block
          status = GetTimerEntry (&pTimerEntry);
          if (NT_SUCCESS(status))
          {
              pTimerEntry->DeltaTime = DeltaTime;
              pTimerEntry->RefCount = 1;
              //
              // this is the context value and routine called when the timer expires,
              // called by TimerExpiry below.
              //
              pTimerEntry->Context = Context;
              pTimerEntry->Context2 = Context2;
              pTimerEntry->TimeoutRoutine = TimeoutRoutine;
              pTimerEntry->Flags = 0; // no flags
  
              // now fill in the client's completion routines that ultimately get called
              // after one or more timeouts...
              pTimerEntry->ClientContext = (PVOID)ContextClient;
              pTimerEntry->ClientCompletion = (COMPLETIONCLIENT)CompletionClient;
              pTimerEntry->Retries = Retries;
  
              pTimerEntry->pDeviceContext = (PVOID) pDeviceContext;
  
              CTEInitTimer(&pTimerEntry->VxdTimer);
              CTEStartTimer(&pTimerEntry->VxdTimer,
                             pTimerEntry->DeltaTime,
                             (CTEEventRtn)TimerExpiry,
                             (PVOID)pTimerEntry);
  
              // check if there is a ptr to return
              if (ppTimerEntry)
              {
                  *ppTimerEntry = pTimerEntry;
              }
  
              // put on list
              InsertHeadList(&TimerQ.ActiveHead, &pTimerEntry->Linkage);
          }
          else if (pDeviceContext)
          {
              NBT_DEREFERENCE_DEVICE (pDeviceContext, REF_DEV_TIMER, TRUE);
          }
      }
      else
      {
          status = STATUS_INVALID_DEVICE_STATE;
      }
  
      if (!fLocked)
      {
          CTESpinFree(&NbtConfig.JointLock,OldIrq);
      }
  
      return(status);
  }
  
  //----------------------------------------------------------------------------
  NTSTATUS
  StopTimer(
      IN  tTIMERQENTRY     *pTimerEntry,
      OUT COMPLETIONCLIENT *ppClientCompletion,
      OUT PVOID            *ppContext)
  /*++
  
  Routine Description:
  
      This routine stops a timer.  Must be called with the Joint lock held.
  
  Arguments:
  
  
  Return Value:
  
      The function value is the status of the operation.
  
  --*/
  {
      NTSTATUS            status;
      COMPLETIONROUTINE   TimeoutRoutine;
  
      // null the client completion routine and Context so that it can't be called again
      // accidently
      if (ppClientCompletion)
      {
          *ppClientCompletion = NULL;
      }
      if (ppContext)
      {
          *ppContext = NULL;
      }
  
      // it is possible that the timer expiry routine has just run and the timer
      // has not been restarted, so check the refcount, it will be zero if the
      // timer was not restarted and 2 if the timer expiry is currently running.
      if (pTimerEntry->RefCount == 1)
      {
          // this allows the caller to call the client's completion routine with
          // the context value.
          if (ppClientCompletion)
          {
              *ppClientCompletion = pTimerEntry->ClientCompletion;
          }
          if (ppContext)
          {
              *ppContext = pTimerEntry->ClientContext;
          }
  
          pTimerEntry->ClientCompletion = NULL;
  
          if (!(pTimerEntry->Flags & TIMER_NOT_STARTED))
          {
              if (!CTEStopTimer((CTETimer *)&pTimerEntry->VxdTimer ))
              {
                  //
                  // It means the TimerExpiry routine is waiting to run,
                  // so let it return this timer block to the free Q
                  // Bug # 229535
                  //
                  // Before returning from here, we should do the cleanup since
                  // the CompletionRoutine (if any) can result in some data
                  // that is required for cleanup to be cleaned up (Bug # 398730)
                  //
                  if (TimeoutRoutine = (COMPLETIONROUTINE)pTimerEntry->TimeoutRoutine)
                  {
                      // call the completion routine so it can clean up its own buffers
                      // the routine that called this one will call the client's completion
                      // routine.  A NULL timerEntry value indicates to the routine that
                      // cleanup should be done.
              
                      pTimerEntry->TimeoutRoutine = NULL;
                      (VOID)(*TimeoutRoutine) (pTimerEntry->Context, pTimerEntry->Context2, NULL);
                  }
                  pTimerEntry->RefCount = 2;
                  return (STATUS_SUCCESS);
              }
          }
  
          status = STATUS_SUCCESS;
          status = CleanupCTETimer(pTimerEntry);
      }
      else if (pTimerEntry->RefCount == 2)
      {
          // the timer expiry completion routines must set this routine to
          // null with the spin lock held to synchronize with this stop timer
          // routine.  Likewise that routine checks this value too, to synchronize
          // with this routine.
          //
          if (pTimerEntry->ClientCompletion)
          {
              // this allows the caller to call the client's completion routine with
              // the context value.
              if (ppClientCompletion)
              {
                  *ppClientCompletion = pTimerEntry->ClientCompletion;
              }
              if (ppContext)
              {
                  *ppContext = pTimerEntry->ClientContext;
              }
              // so that the timer completion routine cannot also call the client
              // completion routine.
              pTimerEntry->ClientCompletion = NULL;
  
          }
  
          // signal the TimerExpiry routine that the timer has been cancelled
          //
          pTimerEntry->RefCount++;
          status = STATUS_UNSUCCESSFUL;
      }
      else
      {
          status = STATUS_UNSUCCESSFUL;
      }
  
      return(status);
  }
  
  
  //----------------------------------------------------------------------------
  VOID
  TimerExpiry(
  #ifndef VXD
      IN  PKDPC   Dpc,
      IN  PVOID   DeferredContext,
      IN  PVOID   SystemArg1,
      IN  PVOID   SystemArg2
  #else
      IN  CTEEvent * pCTEEvent,
      IN  PVOID   DeferredContext
  #endif
      )
  /*++
  
  Routine Description:
  
      This routine is the timer expiry completion routine.  It is called by the
      kernel when the timer expires.
  
  Arguments:
  
  
  Return Value:
  
      The function value is the status of the operation.
  
  --*/
  {
      tTIMERQENTRY    *pTimerEntry;
      CTELockHandle   OldIrq1;
  
      CTESpinLock(&NbtConfig.JointLock,OldIrq1);
  
      // get the timer Q list entry from the context passed in
      pTimerEntry = (tTIMERQENTRY *)DeferredContext;
  
      if (pTimerEntry->RefCount == 0)
      {
          // the timer has been cancelled already!
          CTESpinFree(&NbtConfig.JointLock,OldIrq1);
          return;
      }
      else if (pTimerEntry->RefCount >= 2)    // Bug #: 229535
      {
          // the timer has been cancelled already!
          // Bug # 324655
          // If the Timer has been cancelled, we still need to do cleanup,
          // so do not NULL the TimeoutRoutine!
          //
  //        pTimerEntry->TimeoutRoutine = NULL;
          ASSERT ((pTimerEntry->RefCount == 2) || (pTimerEntry->TimeoutRoutine == NULL));
          CleanupCTETimer (pTimerEntry);
          CTESpinFree(&NbtConfig.JointLock,OldIrq1);
          return;
      }
  
      // increment the reference count because we are handling a timer completion
      // now
      pTimerEntry->RefCount++;
  
      CTESpinFree(&NbtConfig.JointLock,OldIrq1);
  
      // call the completion routine passing the context value
      pTimerEntry->Flags &= ~TIMER_RESTART;   // incase the clients wants to restart the timer
      (*(COMPLETIONROUTINE)pTimerEntry->TimeoutRoutine)(
                  pTimerEntry->Context,
                  pTimerEntry->Context2,
                  pTimerEntry);
  
      CTESpinLock(&NbtConfig.JointLock,OldIrq1);
  
      pTimerEntry->RefCount--;
      if (pTimerEntry->Flags & TIMER_RESTART)
      {
          if (pTimerEntry->RefCount == 2)
          {
              // the timer was stopped during the expiry processing, so call the
              // deference routine
              //
              CleanupCTETimer(pTimerEntry);
              CTESpinFree(&NbtConfig.JointLock,OldIrq1);
  
              return;
          }
          else
          {
              CTEStartTimer (&pTimerEntry->VxdTimer,
                             pTimerEntry->DeltaTime,
                             (CTEEventRtn)TimerExpiry,
                             (PVOID)pTimerEntry);
          }
  
          CTESpinFree(&NbtConfig.JointLock,OldIrq1);
          return;
      }
      else
      {
          // move to the free list after setting the reference count to zero
          // since this timer block is no longer active.
          //
          pTimerEntry->TimeoutRoutine = NULL;
          CleanupCTETimer (pTimerEntry);
          CTESpinFree(&NbtConfig.JointLock,OldIrq1);
      }
  }
  
  //----------------------------------------------------------------------------
  VOID
  ExpireTimer(
      IN  tTIMERQENTRY    *pTimerEntry,
      IN  CTELockHandle   *OldIrq1
      )
  /*++
  
  Routine Description:
  
      This routine causes the timer to be stopped (if it hasn't already)
      and if successful, calls the Completion routine.
  
  Arguments:
  
  
  Return Value:
  
      The function value is the status of the operation.
  
  --*/
  {
      //
      // Reset the Number of retries
      //
      pTimerEntry->Retries = 1;
  
      //
      // RefCount == 0    =>  Timer was stopped, but not restarted
      // RefCount == 1    =>  Timer is still running                  *
      // RefCount == 2    =>  TimerExpiry is currently running
      //
      if ((pTimerEntry->RefCount == 1) &&
          (!(pTimerEntry->Flags & TIMER_NOT_STARTED)) &&
          (CTEStopTimer( (CTETimer *)&pTimerEntry->VxdTimer)))
      {
          // increment the reference count because we are handling a timer completion
          // now
          pTimerEntry->RefCount++;
  
          CTESpinFree(&NbtConfig.JointLock, *OldIrq1);
  
          // call the completion routine passing the context value
          pTimerEntry->Flags &= ~TIMER_RESTART;   // incase the clients wants to restart the timer
          (*(COMPLETIONROUTINE)pTimerEntry->TimeoutRoutine) (pTimerEntry->Context,
                                                             pTimerEntry->Context2,
                                                             pTimerEntry);
  
          CTESpinLock(&NbtConfig.JointLock, *OldIrq1);
  
          pTimerEntry->RefCount--;
          if (pTimerEntry->Flags & TIMER_RESTART)
          {
              if (pTimerEntry->RefCount == 2)
              {
                  // the timer was stopped during the expiry processing, so call the
                  // deference routine
                  //
                  CleanupCTETimer(pTimerEntry);
              }
              else
              {
                  CTEStartTimer(&pTimerEntry->VxdTimer,
                                 pTimerEntry->DeltaTime,
                                 (CTEEventRtn)TimerExpiry,
                                 (PVOID)pTimerEntry);
              }
          }
          else
          {
              // move to the free list after setting the reference count to zero
              // since this tierm block is no longer active.
              //
              pTimerEntry->TimeoutRoutine = NULL;
              CleanupCTETimer (pTimerEntry);
          }
      }
  
      CTESpinFree(&NbtConfig.JointLock, *OldIrq1);
  }
  
  
  
  //----------------------------------------------------------------------------
  //
  // Wakeup Timer routines
  //
  //----------------------------------------------------------------------------
  
  
  VOID
  WakeupTimerExpiry(
      PVOID           DeferredContext,
      ULONG           LowTime,
      LONG            HighTime
      )
  {
      BOOLEAN         fAttached = FALSE;
      CTELockHandle   OldIrq;
      tTIMERQENTRY    *pTimerEntry = (tTIMERQENTRY *)DeferredContext;
  
      //
      // Call the TimerExpiry function while holding the NbtConfig.Resource
      //
      CTEExAcquireResourceExclusive (&NbtConfig.Resource,TRUE);
      if (pTimerEntry->RefCount > 1)
      {
          //
          // The timer is waiting to be cleaned up on a Worker thread,
          // so let it cleanup!
          //
          CTEExReleaseResource (&NbtConfig.Resource);
          return;
      }
  
      //
      // The Timeout routine has to ensure that it cleans up
      // properly since this pTimerEntry + handle will not be valid
      // at the end of this routine!
      //
      (*(COMPLETIONROUTINE) pTimerEntry->TimeoutRoutine) (pTimerEntry->Context,
                                                          pTimerEntry->Context2,
                                                          pTimerEntry);
  
      //
      // Close the timer handle
      //
      CTEAttachFsp(&fAttached, REF_FSP_WAKEUP_TIMER_EXPIRY);
      //
      // The Expiry routine should always be called in the context
      // of the system process
      //
      ASSERT (fAttached == FALSE);
      ZwClose (pTimerEntry->WakeupTimerHandle);
      CTEDetachFsp(fAttached, REF_FSP_WAKEUP_TIMER_EXPIRY);
      CTEExReleaseResource (&NbtConfig.Resource);
  
      ReturnTimerToFreeQ (pTimerEntry, FALSE);
  }
  
  
  //----------------------------------------------------------------------------
  VOID
  DelayedNbtStopWakeupTimer(
      IN  tDGRAM_SEND_TRACKING    *pUnused1,
      IN  PVOID                   pClientContext,
      IN  PVOID                   Unused2,
      IN  tDEVICECONTEXT          *Unused3
      )
  /*++
  Routine Description:
  
      This Routine stops a timer.
      This function has to be called after ensuring that the TimerExpiry has not
      yet cleaned up (while holding the NbtConfig.Resource)
      The NbtConfig.Resource has to be held while this routine is called
  
  Arguments:
  
      Timer       - Timer structure
  
  Return Value:
  
      PVOID - a pointer to the memory or NULL if a failure
  
  --*/
  {
      NTSTATUS        Status;
      BOOLEAN         CurrentState = FALSE;
      BOOLEAN         fAttached = FALSE;
      tTIMERQENTRY    *pTimerEntry = (tTIMERQENTRY *) pClientContext;
  
      CTEPagedCode();
  
      ASSERT (pTimerEntry->fIsWakeupTimer);
  
      CTEAttachFsp(&fAttached, REF_FSP_STOP_WAKEUP_TIMER);
      Status = ZwCancelTimer (pTimerEntry->WakeupTimerHandle, &CurrentState);
      ZwClose (pTimerEntry->WakeupTimerHandle);
      CTEDetachFsp(fAttached, REF_FSP_STOP_WAKEUP_TIMER);
  
      ReturnTimerToFreeQ (pTimerEntry, FALSE);
  
      return;
  }
  
  
  //----------------------------------------------------------------------------
  VOID
  DelayedNbtStartWakeupTimer(
      IN  tDGRAM_SEND_TRACKING    *pUnused1,
      IN  PVOID                   Unused2,
      IN  PVOID                   Unused3,
      IN  tDEVICECONTEXT          *Unused4
      )
  /*++
  
  Routine Description:
  
      This routine starts a Wakeup timer.
      NbtConfig.Resource may be held on entry into this routine!
  
  Arguments:
  
      The value passed in is in milliseconds - must be converted to 100ns
      so multiply to 10,000
  Return Value:
  
      The function value is the status of the operation.
  
  
  --*/
  {
      NTSTATUS            Status = STATUS_UNSUCCESSFUL;
      OBJECT_ATTRIBUTES   ObjectAttributes;
      BOOLEAN             fAttached = FALSE;
      LARGE_INTEGER       Time;
      tTIMERQENTRY        *pTimerEntry;
      CTELockHandle       OldIrq;
      ULONG               TimerInterval = 0;
      ULONG               MilliSecsLeftInTtl = 0;
      LIST_ENTRY          *pEntry;
      LIST_ENTRY          *pHead;
      tDEVICECONTEXT      *pDeviceContext;
      BOOLEAN             fValidDevice = FALSE;
  
      CTEAttachFsp(&fAttached, REF_FSP_START_WAKEUP_TIMER);
      CTEExAcquireResourceExclusive(&NbtConfig.Resource,TRUE);
      CTESpinLock(&NbtConfig.JointLock,OldIrq);
  
      ASSERT (!NbtConfig.pWakeupRefreshTimer);
  
      //  
      // Verify that at least 1 WOL-enabled device has an Ip + Wins address!
      //
      pHead = pEntry = &NbtConfig.DeviceContexts;
      while ((pEntry = pEntry->Flink) != pHead)
      {
          pDeviceContext = CONTAINING_RECORD (pEntry,tDEVICECONTEXT,Linkage);
          if ((pDeviceContext->WOLProperties & NDIS_DEVICE_WAKE_UP_ENABLE) &&
              (pDeviceContext->IpAddress) &&
              (pDeviceContext->lNameServerAddress != LOOP_BACK))
          {
              fValidDevice = TRUE;
              break;
          }
      }
  
      if ((NbtConfig.Unloading) ||                                    // Problem!!!
          (!fValidDevice) ||                                          // No valid device ?
          !(NbtConfig.GlobalRefreshState & NBT_G_REFRESH_SLEEPING))   // check if request was cancelled!
      {
          CTESpinFree(&NbtConfig.JointLock,OldIrq);
          KdPrint (("Nbt.NbtStartWakeupTimer: FAIL: Either: Unloading=<%x>, fValidDevice=<%x>, RefreshState=<%x>\n",
              NbtConfig.Unloading, fValidDevice, NbtConfig.GlobalRefreshState));
      }
      else if (!NT_SUCCESS (Status = GetTimerEntry (&pTimerEntry)))    // get a free timer block
      {
          CTESpinFree(&NbtConfig.JointLock,OldIrq);
          KdPrint (("Nbt.NbtStartWakeupTimer: ERROR: GetTimerEntry returned <%x>\n", Status));
      }
      else
      {
          pTimerEntry->RefCount = 1;
          pTimerEntry->TimeoutRoutine = WakeupRefreshTimeout;
          pTimerEntry->Context = NULL;
          pTimerEntry->Context2 = NULL;
  
          CTESpinFree(&NbtConfig.JointLock,OldIrq);
  
  #ifdef HDL_FIX
          InitializeObjectAttributes (&ObjectAttributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
  #else
          InitializeObjectAttributes (&ObjectAttributes, NULL, 0, NULL, NULL);
  #endif  // HDL_FIX
          Status = ZwCreateTimer (&pTimerEntry->WakeupTimerHandle,
                                  TIMER_ALL_ACCESS,
                                  &ObjectAttributes,
                                  NotificationTimer);
  
          if (NT_SUCCESS (Status))
          {
              //
              // Set the machine to Wakeup at 1/2 the time between now and Ttl
              // This should not be less than the configured MinimumRefreshSleepTimeout
              // (default = 6 hrs)
              //
              MilliSecsLeftInTtl = NbtConfig.MinimumTtl
                                   - (ULONG) (((ULONGLONG) NbtConfig.sTimeoutCount * NbtConfig.MinimumTtl)
                                              / NbtConfig.RefreshDivisor);
  
              if ((MilliSecsLeftInTtl/2) < NbtConfig.MinimumRefreshSleepTimeout)
              {
                  TimerInterval = NbtConfig.MinimumRefreshSleepTimeout;
              }
              else
              {
                  TimerInterval = MilliSecsLeftInTtl/2;
              }
              pTimerEntry->DeltaTime = TimerInterval;
  
              IF_DBG(NBT_DEBUG_PNP_POWER)
                  KdPrint(("Nbt.DelayedNbtStartWakeupTimer: TimerInterval=<%d:%d> (h:m), Currently: <%d/%d>\n",
                      TimerInterval/(3600000), ((TimerInterval/60000)%60),
                      NbtConfig.sTimeoutCount, NbtConfig.RefreshDivisor));
  
              //
              // convert to 100 ns units by multiplying by 10,000
              //
              Time.QuadPart = UInt32x32To64(pTimerEntry->DeltaTime,(LONG)MILLISEC_TO_100NS);
              Time.QuadPart = -(Time.QuadPart);   // to make a delta time, negate the time
              pTimerEntry->fIsWakeupTimer = TRUE;
              ASSERT(Time.QuadPart < 0);
      
              Status = ZwSetTimer(pTimerEntry->WakeupTimerHandle,
                                  &Time,
                                  (PTIMER_APC_ROUTINE) WakeupTimerExpiry,
                                  pTimerEntry,
                                  TRUE,
                                  0,
                                  NULL);
      
              if (!NT_SUCCESS (Status))
              {
                  KdPrint (("Nbt.NbtStartWakeupTimer: ERROR: ZwSetTimer returned <%x>, TimerHandle=<%x>\n",
                      Status, pTimerEntry->WakeupTimerHandle));
                  ZwClose (pTimerEntry->WakeupTimerHandle);
              }
          }
          else
          {
              KdPrint (("Nbt.NbtStartWakeupTimer: ERROR: ZwCreateTimer returned <%x>\n", Status));
          }
  
          if (NT_SUCCESS (Status))
          {
              NbtConfig.pWakeupRefreshTimer = pTimerEntry;
          }
          else
          {
              ReturnTimerToFreeQ (pTimerEntry, FALSE);
          }
      }
  
      CTEExReleaseResource(&NbtConfig.Resource);
      CTEDetachFsp(fAttached, REF_FSP_START_WAKEUP_TIMER);
  
      KeSetEvent (&NbtConfig.WakeupTimerStartedEvent, 0, FALSE);
      return;
  }
  
  
  
  