Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

523 lines
14 KiB

  1. // Copyright (c) 1997, Microsoft Corporation, all rights reserved
  2. //
  3. // timer.c
  4. // RAS L2TP WAN mini-port/call-manager driver
  5. // Timer management routines
  6. //
  7. // 01/07/97 Steve Cobb
  8. #include "l2tpp.h"
  9. //-----------------------------------------------------------------------------
  10. // Local prototypes (alphabetically)
  11. //-----------------------------------------------------------------------------
  12. BOOLEAN
  13. RemoveTqi(
  14. IN TIMERQ* pTimerQ,
  15. IN TIMERQITEM* pItem,
  16. IN TIMERQEVENT event );
  17. VOID
  18. SetTimer(
  19. IN TIMERQ* pTimerQ,
  20. IN LONGLONG llCurrentTime );
  21. VOID
  22. TimerEvent(
  23. IN PVOID SystemSpecific1,
  24. IN PVOID FunctionContext,
  25. IN PVOID SystemSpecific2,
  26. IN PVOID SystemSpecific3 );
  27. //-----------------------------------------------------------------------------
  28. // Interface routines
  29. //-----------------------------------------------------------------------------
  30. VOID
  31. TimerQInitialize(
  32. IN TIMERQ* pTimerQ )
  33. // Initializes caller's timer queue context 'pTimerQ'.
  34. //
  35. {
  36. TRACE( TL_N, TM_Time, ( "TqInit" ) );
  37. InitializeListHead( &pTimerQ->listItems );
  38. NdisAllocateSpinLock( &pTimerQ->lock );
  39. NdisInitializeTimer( &pTimerQ->timer, TimerEvent, pTimerQ );
  40. pTimerQ->pHandler = NULL;
  41. pTimerQ->fTerminating = FALSE;
  42. pTimerQ->ulTag = MTAG_TIMERQ;
  43. }
  44. VOID
  45. TimerQInitializeItem(
  46. IN TIMERQITEM* pItem )
  47. // Initializes caller's timer queue item, 'pItem'. This should be called
  48. // before passing 'pItem' to any other TimerQ routine.
  49. //
  50. {
  51. InitializeListHead( &pItem->linkItems );
  52. }
  53. VOID
  54. TimerQTerminate(
  55. IN TIMERQ* pTimerQ,
  56. IN PTIMERQTERMINATECOMPLETE pHandler,
  57. IN VOID* pContext )
  58. // Terminates timer queue 'pTimerQ'. Each scheduled item is called back
  59. // with TE_Terminate. Caller's 'pHandler' is called with 'pTimerQ' and
  60. // 'pContext' so the 'pTimerQ' can be freed, if necessary. Caller's
  61. // 'pTimerQ' must remain accessible until the 'pHandler' callback occurs,
  62. // which might be after this routine returns.
  63. //
  64. {
  65. BOOLEAN fCancelled;
  66. LIST_ENTRY list;
  67. LIST_ENTRY* pLink;
  68. TRACE( TL_N, TM_Time, ( "TqTerm" ) );
  69. InitializeListHead( &list );
  70. NdisAcquireSpinLock( &pTimerQ->lock );
  71. {
  72. pTimerQ->fTerminating = TRUE;
  73. // Stop the timer.
  74. //
  75. NdisCancelTimer( &pTimerQ->timer, &fCancelled );
  76. TRACE( TL_N, TM_Time, ( "NdisCancelTimer" ) );
  77. if (!fCancelled && !IsListEmpty( &pTimerQ->listItems ))
  78. {
  79. // No event was cancelled but the list of events is not empty.
  80. // This means the timer has fired, but our internal handler has
  81. // not yet been called to process it, though it eventually will
  82. // be. The internal handler must be the one to call the terminate
  83. // complete in this case, because there is no way for it to know
  84. // it cannot reference 'pTimerQ'. Indicate this to the handler by
  85. // filling in the termination handler.
  86. //
  87. TRACE( TL_A, TM_Time, ( "Mid-expire Q" ) );
  88. pTimerQ->pHandler = pHandler;
  89. pTimerQ->pContext = pContext;
  90. pHandler = NULL;
  91. }
  92. // Move the scheduled events to a temporary list, marking them all
  93. // "not on queue" so any attempt by user to cancel the item will be
  94. // ignored.
  95. //
  96. while (!IsListEmpty( &pTimerQ->listItems ))
  97. {
  98. pLink = RemoveHeadList( &pTimerQ->listItems );
  99. InsertTailList( &list, pLink );
  100. }
  101. }
  102. NdisReleaseSpinLock( &pTimerQ->lock );
  103. // Must be careful here. If 'pHandler' was set NULL above, 'pTimerQ' must
  104. // not be referenced in the rest of this routine.
  105. //
  106. // Call user's "terminate" event handler for each removed item.
  107. //
  108. while (!IsListEmpty( &list ))
  109. {
  110. TIMERQITEM* pItem;
  111. pLink = RemoveHeadList( &list );
  112. InitializeListHead( pLink );
  113. pItem = CONTAINING_RECORD( pLink, TIMERQITEM, linkItems );
  114. TRACE( TL_I, TM_Time,
  115. ( "Flush TQI=$%p, handler=$%p", pItem, pItem->pHandler ) );
  116. pItem->pHandler( pItem, pItem->pContext, TE_Terminate );
  117. }
  118. // Call user's "terminate complete" handler, if it's still our job.
  119. //
  120. if (pHandler)
  121. {
  122. pTimerQ->ulTag = MTAG_FREED;
  123. pHandler( pTimerQ, pContext );
  124. }
  125. }
  126. VOID
  127. TimerQScheduleItem(
  128. IN TIMERQ* pTimerQ,
  129. IN OUT TIMERQITEM* pNewItem,
  130. IN ULONG ulTimeoutMs,
  131. IN PTIMERQEVENT pHandler,
  132. IN VOID* pContext )
  133. // Schedule new timer event 'pNewItem' on timer queue 'pTimerQ'. When the
  134. // event occurs in 'ulTimeoutMs' milliseconds, the 'pHandler' routine is
  135. // called with arguments 'pNewItem', 'pContext', and TE_Expired. If the
  136. // item is cancelled or the queue terminated 'pHandler' is called as above
  137. // but with TE_Cancel or TE_Terminate as appropriate.
  138. //
  139. {
  140. TRACE( TL_N, TM_Time, ( "TqSchedItem(ms=%d)", ulTimeoutMs ) );
  141. pNewItem->pHandler = pHandler;
  142. pNewItem->pContext = pContext;
  143. NdisAcquireSpinLock( &pTimerQ->lock );
  144. {
  145. LIST_ENTRY* pLink;
  146. LARGE_INTEGER lrgTime;
  147. ASSERT( pNewItem->linkItems.Flink == &pNewItem->linkItems );
  148. // The system time at which the timeout will occur is stored.
  149. //
  150. NdisGetCurrentSystemTime( &lrgTime );
  151. pNewItem->llExpireTime =
  152. lrgTime.QuadPart + (((LONGLONG )ulTimeoutMs) * 10000);
  153. // Walk the list of timer items looking for the first item that will
  154. // expire before the new item. Do it backwards so the likely case of
  155. // many timeouts with roughly the same interval is handled
  156. // efficiently.
  157. //
  158. for (pLink = pTimerQ->listItems.Blink;
  159. pLink != &pTimerQ->listItems;
  160. pLink = pLink->Blink )
  161. {
  162. TIMERQITEM* pItem;
  163. pItem = CONTAINING_RECORD( pLink, TIMERQITEM, linkItems );
  164. if (pItem->llExpireTime < pNewItem->llExpireTime)
  165. {
  166. break;
  167. }
  168. }
  169. // Link the new item into the timer queue after the found item (or
  170. // after the head if none was found).
  171. //
  172. InsertAfter( &pNewItem->linkItems, pLink );
  173. if (pTimerQ->listItems.Flink == &pNewItem->linkItems)
  174. {
  175. // The new item expires before all other items so need to re-set
  176. // the NDIS timer.
  177. //
  178. SetTimer( pTimerQ, lrgTime.QuadPart );
  179. }
  180. }
  181. NdisReleaseSpinLock( &pTimerQ->lock );
  182. }
  183. BOOLEAN
  184. TimerQCancelItem(
  185. IN TIMERQ* pTimerQ,
  186. IN TIMERQITEM* pItem )
  187. // Remove scheduled timer event 'pItem' from timer queue 'pTimerQ' and
  188. // call user's handler with event 'TE_Cancel', or nothing if 'pItem' is
  189. // NULL.
  190. //
  191. // Returns true if the timer was cancelled, false if it not, i.e. it was
  192. // not on the queue, possibly because it expired already.
  193. //
  194. {
  195. TRACE( TL_N, TM_Time, ( "TqCancelItem" ) );
  196. return RemoveTqi( pTimerQ, pItem, TE_Cancel );
  197. }
  198. BOOLEAN
  199. TimerQExpireItem(
  200. IN TIMERQ* pTimerQ,
  201. IN TIMERQITEM* pItem )
  202. // Remove scheduled timer event 'pItem' from timer queue 'pTimerQ' and
  203. // call user's handler with event 'TE_Expire', or do nothing if 'pItem' is
  204. // NULL.
  205. //
  206. // Returns true if the timer was expired, false if it not, i.e. it was not
  207. // on the queue, possibly because it expired already.
  208. //
  209. {
  210. TRACE( TL_N, TM_Time, ( "TqExpireItem" ) );
  211. return RemoveTqi( pTimerQ, pItem, TE_Expire );
  212. }
  213. BOOLEAN
  214. TimerQTerminateItem(
  215. IN TIMERQ* pTimerQ,
  216. IN TIMERQITEM* pItem )
  217. // Remove scheduled timer event 'pItem' from timer queue 'pTimerQ', or do
  218. // nothing if 'pItem' is NULL.
  219. //
  220. // Returns true if the timer was terminated, false if it not, i.e. it was not
  221. // on the queue, possibly because it expired already.
  222. //
  223. {
  224. TRACE( TL_N, TM_Time, ( "TqTermItem" ) );
  225. return RemoveTqi( pTimerQ, pItem, TE_Terminate );
  226. }
  227. #if DBG
  228. CHAR*
  229. TimerQPszFromEvent(
  230. IN TIMERQEVENT event )
  231. // Debug utility to convert timer event coode 'event' to a corresponding
  232. // display string.
  233. //
  234. {
  235. static CHAR* aszEvent[ 3 ] =
  236. {
  237. "expire",
  238. "cancel",
  239. "terminate"
  240. };
  241. return aszEvent[ (ULONG )event ];
  242. }
  243. #endif
  244. //-----------------------------------------------------------------------------
  245. // Timer utility routines (alphabetically)
  246. //-----------------------------------------------------------------------------
  247. BOOLEAN
  248. RemoveTqi(
  249. IN TIMERQ* pTimerQ,
  250. IN TIMERQITEM* pItem,
  251. IN TIMERQEVENT event )
  252. // Remove scheduled timer event 'pItem' from timer queue 'pTimerQ' and
  253. // call user's handler with event 'event'. The 'TE_Expire' event handler
  254. // is not called directly, but rescheduled with a 0 timeout so it occurs
  255. // immediately, but at DPC when no locks are held just like the original
  256. // timer had fired..
  257. //
  258. // Returns true if the item was on the queue, false otherwise.
  259. //
  260. {
  261. BOOLEAN fFirst;
  262. LIST_ENTRY* pLink;
  263. if (!pItem)
  264. {
  265. TRACE( TL_N, TM_Time, ( "NULL pTqi" ) );
  266. return FALSE;
  267. }
  268. pLink = &pItem->linkItems;
  269. NdisAcquireSpinLock( &pTimerQ->lock );
  270. {
  271. if (pItem->linkItems.Flink == &pItem->linkItems
  272. || pTimerQ->fTerminating)
  273. {
  274. // The item is not on the queue. Another operation may have
  275. // already dequeued it, but may not yet have called user's
  276. // handler.
  277. //
  278. TRACE( TL_N, TM_Time, ( "Not scheduled" ) );
  279. NdisReleaseSpinLock( &pTimerQ->lock );
  280. return FALSE;
  281. }
  282. fFirst = (pLink == pTimerQ->listItems.Flink);
  283. if (fFirst)
  284. {
  285. BOOLEAN fCancelled;
  286. // Cancelling first item on list, so cancel the NDIS timer.
  287. //
  288. NdisCancelTimer( &pTimerQ->timer, &fCancelled );
  289. TRACE( TL_N, TM_Time, ( "NdisCancelTimer" ) );
  290. if (!fCancelled)
  291. {
  292. // Too late. The item has expired already but has not yet
  293. // been removed from the list by the internal handler.
  294. //
  295. TRACE( TL_A, TM_Time, ( "Mid-expire e=%d $%p($%p)",
  296. event, pItem->pHandler, pItem->pContext ) );
  297. NdisReleaseSpinLock( &pTimerQ->lock );
  298. return FALSE;
  299. }
  300. }
  301. // Un-schedule the event and mark the item descriptor "off queue", so
  302. // any later attempt to cancel will do nothing.
  303. //
  304. RemoveEntryList( pLink );
  305. InitializeListHead( pLink );
  306. if (fFirst)
  307. {
  308. // Re-set the NDIS timer to reflect the timeout of the new first
  309. // item, if any.
  310. //
  311. SetTimer( pTimerQ, 0 );
  312. }
  313. }
  314. NdisReleaseSpinLock( &pTimerQ->lock );
  315. if (event == TE_Expire)
  316. {
  317. TimerQScheduleItem(
  318. pTimerQ, pItem, 0, pItem->pHandler, pItem->pContext );
  319. }
  320. else
  321. {
  322. // Call user's event handler.
  323. //
  324. pItem->pHandler( pItem, pItem->pContext, event );
  325. }
  326. return TRUE;
  327. }
  328. VOID
  329. SetTimer(
  330. IN TIMERQ* pTimerQ,
  331. IN LONGLONG llCurrentTime )
  332. // Sets the NDIS timer to expire when the timeout of the first link, if
  333. // any, in the timer queue 'pTimerQ' occurs. Any previously set timeout
  334. // is "overwritten". 'LlCurrentTime' is the current system time, if
  335. // known, or 0 if not.
  336. //
  337. // IMPORTANT: Caller must hold the TIMERQ lock.
  338. //
  339. {
  340. LIST_ENTRY* pFirstLink;
  341. TIMERQITEM* pFirstItem;
  342. LONGLONG llTimeoutMs;
  343. ULONG ulTimeoutMs;
  344. if (IsListEmpty( &pTimerQ->listItems ))
  345. {
  346. return;
  347. }
  348. pFirstLink = pTimerQ->listItems.Flink;
  349. pFirstItem = CONTAINING_RECORD( pFirstLink, TIMERQITEM, linkItems );
  350. if (llCurrentTime == 0)
  351. {
  352. LARGE_INTEGER lrgTime;
  353. NdisGetCurrentSystemTime( &lrgTime );
  354. llCurrentTime = lrgTime.QuadPart;
  355. }
  356. llTimeoutMs = (pFirstItem->llExpireTime - llCurrentTime) / 10000;
  357. if (llTimeoutMs <= 0)
  358. {
  359. // The timeout interval is negative, i.e. it's already passed. Set it
  360. // to zero so it is triggered immediately.
  361. //
  362. ulTimeoutMs = 0;
  363. }
  364. else
  365. {
  366. // The timeout interval is in the future.
  367. //
  368. ASSERT( ((LARGE_INTEGER* )&llTimeoutMs)->HighPart == 0 );
  369. ulTimeoutMs = ((LARGE_INTEGER* )&llTimeoutMs)->LowPart;
  370. }
  371. NdisSetTimer( &pTimerQ->timer, ulTimeoutMs );
  372. TRACE( TL_N, TM_Time, ( "NdisSetTimer(%dms)", ulTimeoutMs ) );
  373. }
  374. VOID
  375. TimerEvent(
  376. IN PVOID SystemSpecific1,
  377. IN PVOID FunctionContext,
  378. IN PVOID SystemSpecific2,
  379. IN PVOID SystemSpecific3 )
  380. // NDIS_TIMER_FUNCTION called when a timer expires.
  381. //
  382. {
  383. TIMERQ* pTimerQ;
  384. LIST_ENTRY* pLink;
  385. TIMERQITEM* pItem;
  386. PTIMERQTERMINATECOMPLETE pHandler;
  387. TRACE( TL_N, TM_Time, ( "TimerEvent" ) );
  388. pTimerQ = (TIMERQ* )FunctionContext;
  389. if (!pTimerQ || pTimerQ->ulTag != MTAG_TIMERQ)
  390. {
  391. // Should not happen.
  392. //
  393. TRACE( TL_A, TM_Time, ( "Not TIMERQ?" ) );
  394. return;
  395. }
  396. NdisAcquireSpinLock( &pTimerQ->lock );
  397. {
  398. pHandler = pTimerQ->pHandler;
  399. if (!pHandler)
  400. {
  401. // The termination handler is not set, so proceed normally.
  402. // Remove the first event item, make it un-cancel-able, and re-set
  403. // the timer for the next event.
  404. //
  405. if (IsListEmpty( &pTimerQ->listItems ))
  406. {
  407. // Should not happen (but does sometimes on MP Alpha?).
  408. //
  409. TRACE( TL_A, TM_Time, ( "No item queued?" ) );
  410. pItem = NULL;
  411. }
  412. else
  413. {
  414. pLink = RemoveHeadList( &pTimerQ->listItems );
  415. InitializeListHead( pLink );
  416. pItem = CONTAINING_RECORD( pLink, TIMERQITEM, linkItems );
  417. SetTimer( pTimerQ, 0 );
  418. }
  419. }
  420. }
  421. NdisReleaseSpinLock( &pTimerQ->lock );
  422. if (pHandler)
  423. {
  424. // The termination handler was set meaning the timer queue has been
  425. // terminated between this event firing and this handler being called.
  426. // That means we are the one who calls user's termination handler.
  427. // 'pTimerQ' must not be referenced after that call.
  428. //
  429. TRACE( TL_A, TM_Time, ( "Mid-event case handled" ) );
  430. pTimerQ->ulTag = MTAG_FREED;
  431. pHandler( pTimerQ, pTimerQ->pContext );
  432. return;
  433. }
  434. if (pItem)
  435. {
  436. // Call user's "expire" event handler.
  437. //
  438. pItem->pHandler( pItem, pItem->pContext, TE_Expire );
  439. }
  440. }