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.

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