Leaked source code of windows server 2003
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.

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