windows-nt/Source/XPSP1/NT/net/rras/ndis/raspppoe/timer.c
2020-09-26 16:20:57 +08:00

539 lines
15 KiB
C

// Copyright (c) 1997, Microsoft Corporation, all rights reserved
//
// timer.c
// RAS L2TP WAN mini-port/call-manager driver
// Timer management routines
//
// 01/07/97 Steve Cobb
#include <ntddk.h>
#include <ntddndis.h>
#include <ndis.h>
#include <ndiswan.h>
#include <ndistapi.h>
#include <ntverp.h>
#include "debug.h"
#include "timer.h"
#include "bpool.h"
#include "ppool.h"
#include "util.h"
#include "packet.h"
#include "protocol.h"
#include "miniport.h"
#include "tapi.h"
//-----------------------------------------------------------------------------
// Local prototypes (alphabetically)
//-----------------------------------------------------------------------------
BOOLEAN
RemoveTqi(
IN TIMERQ* pTimerQ,
IN TIMERQITEM* pItem,
IN TIMERQEVENT event );
VOID
SetTimer(
IN TIMERQ* pTimerQ,
IN LONGLONG llCurrentTime );
VOID
TimerEvent(
IN PVOID SystemSpecific1,
IN PVOID FunctionContext,
IN PVOID SystemSpecific2,
IN PVOID SystemSpecific3 );
//-----------------------------------------------------------------------------
// Interface routines
//-----------------------------------------------------------------------------
VOID
TimerQInitialize(
IN TIMERQ* pTimerQ )
// Initializes caller's timer queue context 'pTimerQ'.
//
{
TRACE( TL_N, TM_Time, ( "TqInit" ) );
InitializeListHead( &pTimerQ->listItems );
NdisAllocateSpinLock( &pTimerQ->lock );
NdisInitializeTimer( &pTimerQ->timer, TimerEvent, pTimerQ );
pTimerQ->pHandler = NULL;
pTimerQ->fTerminating = FALSE;
pTimerQ->ulTag = MTAG_TIMERQ;
}
VOID
TimerQInitializeItem(
IN TIMERQITEM* pItem )
// Initializes caller's timer queue item, 'pItem'. This should be called
// before passing 'pItem' to any other TimerQ routine.
//
{
InitializeListHead( &pItem->linkItems );
}
VOID
TimerQTerminate(
IN TIMERQ* pTimerQ,
IN PTIMERQTERMINATECOMPLETE pHandler,
IN VOID* pContext )
// Terminates timer queue 'pTimerQ'. Each scheduled item is called back
// with TE_Terminate. Caller's 'pHandler' is called with 'pTimerQ' and
// 'pContext' so the 'pTimerQ' can be freed, if necessary. Caller's
// 'pTimerQ' must remain accessible until the 'pHandler' callback occurs,
// which might be after this routine returns.
//
{
BOOLEAN fCancelled;
LIST_ENTRY list;
LIST_ENTRY* pLink;
TRACE( TL_N, TM_Time, ( "TqTerm" ) );
InitializeListHead( &list );
NdisAcquireSpinLock( &pTimerQ->lock );
{
pTimerQ->fTerminating = TRUE;
// Stop the timer.
//
NdisCancelTimer( &pTimerQ->timer, &fCancelled );
TRACE( TL_N, TM_Time, ( "NdisCancelTimer" ) );
if (!fCancelled && !IsListEmpty( &pTimerQ->listItems ))
{
// No event was cancelled but the list of events is not empty.
// This means the timer has fired, but our internal handler has
// not yet been called to process it, though it eventually will
// be. The internal handler must be the one to call the terminate
// complete in this case, because there is no way for it to know
// it cannot reference 'pTimerQ'. Indicate this to the handler by
// filling in the termination handler.
//
TRACE( TL_A, TM_Time, ( "Mid-expire Q" ) );
pTimerQ->pHandler = pHandler;
pTimerQ->pContext = pContext;
pHandler = NULL;
}
// Move the scheduled events to a temporary list, marking them all
// "not on queue" so any attempt by user to cancel the item will be
// ignored.
//
while (!IsListEmpty( &pTimerQ->listItems ))
{
pLink = RemoveHeadList( &pTimerQ->listItems );
InsertTailList( &list, pLink );
}
}
NdisReleaseSpinLock( &pTimerQ->lock );
// Must be careful here. If 'pHandler' was set NULL above, 'pTimerQ' must
// not be referenced in the rest of this routine.
//
// Call user's "terminate" event handler for each removed item.
//
while (!IsListEmpty( &list ))
{
TIMERQITEM* pItem;
pLink = RemoveHeadList( &list );
InitializeListHead( pLink );
pItem = CONTAINING_RECORD( pLink, TIMERQITEM, linkItems );
TRACE( TL_I, TM_Time,
( "Flush TQI=$%p, handler=$%p", pItem, pItem->pHandler ) );
pItem->pHandler( pItem, pItem->pContext, TE_Terminate );
}
// Call user's "terminate complete" handler, if it's still our job.
//
if (pHandler)
{
pTimerQ->ulTag = MTAG_FREED;
pHandler( pTimerQ, pContext );
}
}
VOID
TimerQScheduleItem(
IN TIMERQ* pTimerQ,
IN OUT TIMERQITEM* pNewItem,
IN ULONG ulTimeoutMs,
IN PTIMERQEVENT pHandler,
IN VOID* pContext )
// Schedule new timer event 'pNewItem' on timer queue 'pTimerQ'. When the
// event occurs in 'ulTimeoutMs' milliseconds, the 'pHandler' routine is
// called with arguments 'pNewItem', 'pContext', and TE_Expired. If the
// item is cancelled or the queue terminated 'pHandler' is called as above
// but with TE_Cancel or TE_Terminate as appropriate.
//
{
TRACE( TL_N, TM_Time, ( "TqSchedItem(ms=%d)", ulTimeoutMs ) );
pNewItem->pHandler = pHandler;
pNewItem->pContext = pContext;
NdisAcquireSpinLock( &pTimerQ->lock );
{
LIST_ENTRY* pLink;
LARGE_INTEGER lrgTime;
ASSERT( pNewItem->linkItems.Flink == &pNewItem->linkItems );
// The system time at which the timeout will occur is stored.
//
NdisGetCurrentSystemTime( &lrgTime );
pNewItem->llExpireTime =
lrgTime.QuadPart + (((LONGLONG )ulTimeoutMs) * 10000);
// Walk the list of timer items looking for the first item that will
// expire before the new item. Do it backwards so the likely case of
// many timeouts with roughly the same interval is handled
// efficiently.
//
for (pLink = pTimerQ->listItems.Blink;
pLink != &pTimerQ->listItems;
pLink = pLink->Blink )
{
TIMERQITEM* pItem;
pItem = CONTAINING_RECORD( pLink, TIMERQITEM, linkItems );
if (pItem->llExpireTime < pNewItem->llExpireTime)
{
break;
}
}
// Link the new item into the timer queue after the found item (or
// after the head if none was found).
//
InsertAfter( &pNewItem->linkItems, pLink );
if (pTimerQ->listItems.Flink == &pNewItem->linkItems)
{
// The new item expires before all other items so need to re-set
// the NDIS timer.
//
SetTimer( pTimerQ, lrgTime.QuadPart );
}
}
NdisReleaseSpinLock( &pTimerQ->lock );
}
BOOLEAN
TimerQCancelItem(
IN TIMERQ* pTimerQ,
IN TIMERQITEM* pItem )
// Remove scheduled timer event 'pItem' from timer queue 'pTimerQ' and
// call user's handler with event 'TE_Cancel', or nothing if 'pItem' is
// NULL.
//
// Returns true if the timer was cancelled, false if it not, i.e. it was
// not on the queue, possibly because it expired already.
//
{
TRACE( TL_N, TM_Time, ( "TqCancelItem" ) );
return RemoveTqi( pTimerQ, pItem, TE_Cancel );
}
BOOLEAN
TimerQExpireItem(
IN TIMERQ* pTimerQ,
IN TIMERQITEM* pItem )
// Remove scheduled timer event 'pItem' from timer queue 'pTimerQ' and
// call user's handler with event 'TE_Expire', or do nothing if 'pItem' is
// NULL.
//
// Returns true if the timer was expired, false if it not, i.e. it was not
// on the queue, possibly because it expired already.
//
{
TRACE( TL_N, TM_Time, ( "TqExpireItem" ) );
return RemoveTqi( pTimerQ, pItem, TE_Expire );
}
BOOLEAN
TimerQTerminateItem(
IN TIMERQ* pTimerQ,
IN TIMERQITEM* pItem )
// Remove scheduled timer event 'pItem' from timer queue 'pTimerQ', or do
// nothing if 'pItem' is NULL.
//
// Returns true if the timer was terminated, false if it not, i.e. it was not
// on the queue, possibly because it expired already.
//
{
TRACE( TL_N, TM_Time, ( "TqTermItem" ) );
return RemoveTqi( pTimerQ, pItem, TE_Terminate );
}
#if DBG
CHAR*
TimerQPszFromEvent(
IN TIMERQEVENT event )
// Debug utility to convert timer event coode 'event' to a corresponding
// display string.
//
{
static CHAR* aszEvent[ 3 ] =
{
"expire",
"cancel",
"terminate"
};
return aszEvent[ (ULONG )event ];
}
#endif
//-----------------------------------------------------------------------------
// Timer utility routines (alphabetically)
//-----------------------------------------------------------------------------
BOOLEAN
RemoveTqi(
IN TIMERQ* pTimerQ,
IN TIMERQITEM* pItem,
IN TIMERQEVENT event )
// Remove scheduled timer event 'pItem' from timer queue 'pTimerQ' and
// call user's handler with event 'event'. The 'TE_Expire' event handler
// is not called directly, but rescheduled with a 0 timeout so it occurs
// immediately, but at DPC when no locks are held just like the original
// timer had fired..
//
// Returns true if the item was on the queue, false otherwise.
//
{
BOOLEAN fFirst;
LIST_ENTRY* pLink;
if (!pItem)
{
TRACE( TL_N, TM_Time, ( "NULL pTqi" ) );
return FALSE;
}
pLink = &pItem->linkItems;
NdisAcquireSpinLock( &pTimerQ->lock );
{
if (pItem->linkItems.Flink == &pItem->linkItems
|| pTimerQ->fTerminating)
{
// The item is not on the queue. Another operation may have
// already dequeued it, but may not yet have called user's
// handler.
//
TRACE( TL_N, TM_Time, ( "Not scheduled" ) );
NdisReleaseSpinLock( &pTimerQ->lock );
return FALSE;
}
fFirst = (pLink == pTimerQ->listItems.Flink);
if (fFirst)
{
BOOLEAN fCancelled;
// Cancelling first item on list, so cancel the NDIS timer.
//
NdisCancelTimer( &pTimerQ->timer, &fCancelled );
TRACE( TL_N, TM_Time, ( "NdisCancelTimer" ) );
if (!fCancelled)
{
// Too late. The item has expired already but has not yet
// been removed from the list by the internal handler.
//
TRACE( TL_A, TM_Time, ( "Mid-expire e=%d $%p($%p)",
event, pItem->pHandler, pItem->pContext ) );
NdisReleaseSpinLock( &pTimerQ->lock );
return FALSE;
}
}
// Un-schedule the event and mark the item descriptor "off queue", so
// any later attempt to cancel will do nothing.
//
RemoveEntryList( pLink );
InitializeListHead( pLink );
if (fFirst)
{
// Re-set the NDIS timer to reflect the timeout of the new first
// item, if any.
//
SetTimer( pTimerQ, 0 );
}
}
NdisReleaseSpinLock( &pTimerQ->lock );
if (event == TE_Expire)
{
TimerQScheduleItem(
pTimerQ, pItem, 0, pItem->pHandler, pItem->pContext );
}
else
{
// Call user's event handler.
//
pItem->pHandler( pItem, pItem->pContext, event );
}
return TRUE;
}
VOID
SetTimer(
IN TIMERQ* pTimerQ,
IN LONGLONG llCurrentTime )
// Sets the NDIS timer to expire when the timeout of the first link, if
// any, in the timer queue 'pTimerQ' occurs. Any previously set timeout
// is "overwritten". 'LlCurrentTime' is the current system time, if
// known, or 0 if not.
//
// IMPORTANT: Caller must hold the TIMERQ lock.
//
{
LIST_ENTRY* pFirstLink;
TIMERQITEM* pFirstItem;
LONGLONG llTimeoutMs;
ULONG ulTimeoutMs;
if (IsListEmpty( &pTimerQ->listItems ))
{
return;
}
pFirstLink = pTimerQ->listItems.Flink;
pFirstItem = CONTAINING_RECORD( pFirstLink, TIMERQITEM, linkItems );
if (llCurrentTime == 0)
{
LARGE_INTEGER lrgTime;
NdisGetCurrentSystemTime( &lrgTime );
llCurrentTime = lrgTime.QuadPart;
}
llTimeoutMs = (pFirstItem->llExpireTime - llCurrentTime) / 10000;
if (llTimeoutMs <= 0)
{
// The timeout interval is negative, i.e. it's already passed. Set it
// to zero so it is triggered immediately.
//
ulTimeoutMs = 0;
}
else
{
// The timeout interval is in the future.
//
ASSERT( ((LARGE_INTEGER* )&llTimeoutMs)->HighPart == 0 );
ulTimeoutMs = ((LARGE_INTEGER* )&llTimeoutMs)->LowPart;
}
NdisSetTimer( &pTimerQ->timer, ulTimeoutMs );
TRACE( TL_N, TM_Time, ( "NdisSetTimer(%dms)", ulTimeoutMs ) );
}
VOID
TimerEvent(
IN PVOID SystemSpecific1,
IN PVOID FunctionContext,
IN PVOID SystemSpecific2,
IN PVOID SystemSpecific3 )
// NDIS_TIMER_FUNCTION called when a timer expires.
//
{
TIMERQ* pTimerQ;
LIST_ENTRY* pLink;
TIMERQITEM* pItem;
PTIMERQTERMINATECOMPLETE pHandler;
TRACE( TL_N, TM_Time, ( "TimerEvent" ) );
pTimerQ = (TIMERQ* )FunctionContext;
if (!pTimerQ || pTimerQ->ulTag != MTAG_TIMERQ)
{
// Should not happen.
//
TRACE( TL_A, TM_Time, ( "Not TIMERQ?" ) );
return;
}
NdisAcquireSpinLock( &pTimerQ->lock );
{
pHandler = pTimerQ->pHandler;
if (!pHandler)
{
// The termination handler is not set, so proceed normally.
// Remove the first event item, make it un-cancel-able, and re-set
// the timer for the next event.
//
if (IsListEmpty( &pTimerQ->listItems ))
{
// Should not happen (but does sometimes on MP Alpha?).
//
TRACE( TL_A, TM_Time, ( "No item queued?" ) );
pItem = NULL;
}
else
{
pLink = RemoveHeadList( &pTimerQ->listItems );
InitializeListHead( pLink );
pItem = CONTAINING_RECORD( pLink, TIMERQITEM, linkItems );
SetTimer( pTimerQ, 0 );
}
}
}
NdisReleaseSpinLock( &pTimerQ->lock );
if (pHandler)
{
// The termination handler was set meaning the timer queue has been
// terminated between this event firing and this handler being called.
// That means we are the one who calls user's termination handler.
// 'pTimerQ' must not be referenced after that call.
//
TRACE( TL_A, TM_Time, ( "Mid-event case handled" ) );
pTimerQ->ulTag = MTAG_FREED;
pHandler( pTimerQ, pTimerQ->pContext );
return;
}
if (pItem)
{
// Call user's "expire" event handler.
//
pItem->pHandler( pItem, pItem->pContext, TE_Expire );
}
}