|
|
// 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 "l2tpp.h"
#include "timer.tmh"
//-----------------------------------------------------------------------------
// 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 ); } }
|