// 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" //----------------------------------------------------------------------------- // 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 ); } }