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.

697 lines
16 KiB

  1. /*************************************************************************
  2. *
  3. * timer.c
  4. *
  5. * This module contains the ICA timer routines.
  6. *
  7. * Copyright 1998, Microsoft.
  8. *
  9. *************************************************************************/
  10. /*
  11. * Includes
  12. */
  13. #include <precomp.h>
  14. #pragma hdrstop
  15. #include <ntddkbd.h>
  16. #include <ntddmou.h>
  17. /*
  18. * Local structures
  19. */
  20. typedef VOID (*PICATIMERFUNC)( PVOID, PVOID );
  21. typedef struct _ICA_WORK_ITEM {
  22. LIST_ENTRY Links;
  23. WORK_QUEUE_ITEM WorkItem;
  24. PICATIMERFUNC pFunc;
  25. PVOID pParam;
  26. PSDLINK pSdLink;
  27. ULONG LockFlags;
  28. ULONG fCanceled: 1;
  29. } ICA_WORK_ITEM, *PICA_WORK_ITEM;
  30. /*
  31. * Timer structure
  32. */
  33. typedef struct _ICA_TIMER {
  34. LONG RefCount;
  35. KTIMER kTimer;
  36. KDPC TimerDpc;
  37. PSDLINK pSdLink;
  38. LIST_ENTRY WorkItemListHead;
  39. } ICA_TIMER, * PICA_TIMER;
  40. /*
  41. * Local procedure prototypes
  42. */
  43. VOID
  44. _IcaTimerDpc(
  45. IN struct _KDPC *Dpc,
  46. IN PVOID DeferredContext,
  47. IN PVOID SystemArgument1,
  48. IN PVOID SystemArgument2
  49. );
  50. VOID
  51. _IcaDelayedWorker(
  52. IN PVOID WorkerContext
  53. );
  54. BOOLEAN
  55. _IcaCancelTimer(
  56. PICA_TIMER pTimer,
  57. PICA_WORK_ITEM *ppWorkItem
  58. );
  59. VOID
  60. _IcaReferenceTimer(
  61. PICA_TIMER pTimer
  62. );
  63. VOID
  64. _IcaDereferenceTimer(
  65. PICA_TIMER pTimer
  66. );
  67. NTSTATUS
  68. IcaExceptionFilter(
  69. IN PWSTR OutputString,
  70. IN PEXCEPTION_POINTERS pexi
  71. );
  72. /*******************************************************************************
  73. *
  74. * IcaTimerCreate
  75. *
  76. * Create a timer
  77. *
  78. *
  79. * ENTRY:
  80. * pContext (input)
  81. * Pointer to SDCONTEXT of caller
  82. * phTimer (output)
  83. * address to return timer handle
  84. *
  85. * EXIT:
  86. * STATUS_SUCCESS - no error
  87. *
  88. ******************************************************************************/
  89. NTSTATUS
  90. IcaTimerCreate(
  91. IN PSDCONTEXT pContext,
  92. OUT PVOID * phTimer
  93. )
  94. {
  95. PICA_TIMER pTimer;
  96. NTSTATUS Status;
  97. /*
  98. * Allocate timer object and initialize it
  99. */
  100. pTimer = ICA_ALLOCATE_POOL( NonPagedPool, sizeof(ICA_TIMER) );
  101. if ( pTimer == NULL )
  102. return( STATUS_NO_MEMORY );
  103. RtlZeroMemory( pTimer, sizeof(ICA_TIMER) );
  104. pTimer->RefCount = 1;
  105. KeInitializeTimer( &pTimer->kTimer );
  106. KeInitializeDpc( &pTimer->TimerDpc, _IcaTimerDpc, pTimer );
  107. pTimer->pSdLink = CONTAINING_RECORD( pContext, SDLINK, SdContext );
  108. InitializeListHead( &pTimer->WorkItemListHead );
  109. TRACESTACK(( pTimer->pSdLink->pStack, TC_ICADD, TT_API3, "ICADD: TimerCreate: %08x\n", pTimer ));
  110. *phTimer = (PVOID) pTimer;
  111. return( STATUS_SUCCESS );
  112. }
  113. /*******************************************************************************
  114. *
  115. * IcaTimerStart
  116. *
  117. * Start a timer
  118. *
  119. *
  120. * ENTRY:
  121. * TimerHandle (input)
  122. * timer handle
  123. * pFunc (input)
  124. * address of procedure to call when timer expires
  125. * pParam (input)
  126. * parameter to pass to procedure
  127. * TimeLeft (input)
  128. * relative time until timer expires (1/1000 seconds)
  129. * LockFlags (input)
  130. * Bit flags to specify which (if any) stack locks to obtain
  131. *
  132. * EXIT:
  133. * TRUE : timer was already armed and had to be canceled
  134. * FALSE : timer was not armed
  135. *
  136. ******************************************************************************/
  137. BOOLEAN
  138. IcaTimerStart(
  139. IN PVOID TimerHandle,
  140. IN PVOID pFunc,
  141. IN PVOID pParam,
  142. IN ULONG TimeLeft,
  143. IN ULONG LockFlags )
  144. {
  145. PICA_TIMER pTimer = (PICA_TIMER)TimerHandle;
  146. KIRQL oldIrql;
  147. PICA_WORK_ITEM pWorkItem;
  148. LARGE_INTEGER DueTime;
  149. BOOLEAN bCanceled, bSet;
  150. TRACESTACK(( pTimer->pSdLink->pStack, TC_ICADD, TT_API3,
  151. "ICADD: TimerStart: %08x, Time %08x, pFunc %08x (%08x)\n",
  152. TimerHandle, TimeLeft, pFunc, pParam ));
  153. ASSERT( ExIsResourceAcquiredExclusiveLite( &pTimer->pSdLink->pStack->Resource ) );
  154. /*
  155. * Cancel the timer if it currently armed,
  156. * and get the current workitem and reuse it if there was one.
  157. */
  158. bCanceled = _IcaCancelTimer( pTimer, &pWorkItem );
  159. /*
  160. * Initialize the ICA work item (allocate one first if there isn't one).
  161. */
  162. if ( pWorkItem == NULL ) {
  163. pWorkItem = ICA_ALLOCATE_POOL( NonPagedPool, sizeof(ICA_WORK_ITEM) );
  164. if ( pWorkItem == NULL ) {
  165. return( FALSE );
  166. }
  167. }
  168. pWorkItem->pFunc = pFunc;
  169. pWorkItem->pParam = pParam;
  170. pWorkItem->pSdLink = pTimer->pSdLink;
  171. pWorkItem->LockFlags = LockFlags;
  172. pWorkItem->fCanceled = FALSE;
  173. ExInitializeWorkItem( &pWorkItem->WorkItem, _IcaDelayedWorker, pWorkItem );
  174. /*
  175. * If the timer was NOT canceled above (we are setting it for
  176. * the first time), then reference the SDLINK object on behalf
  177. * of the timer thread.
  178. */
  179. if ( !bCanceled )
  180. IcaReferenceSdLink( pTimer->pSdLink );
  181. /*
  182. * If timer should run immediately, then just queue the
  183. * workitem to an ExWorker thread now.
  184. */
  185. if ( TimeLeft == 0 ) {
  186. ExQueueWorkItem( &pWorkItem->WorkItem, CriticalWorkQueue );
  187. } else {
  188. /*
  189. * Convert timer time from milliseconds to system relative time
  190. */
  191. DueTime = RtlEnlargedIntegerMultiply( TimeLeft, -10000 );
  192. /*
  193. * Increment the timer reference count,
  194. * insert the workitem onto the workitem list,
  195. * and arm the timer.
  196. */
  197. _IcaReferenceTimer( pTimer );
  198. IcaAcquireSpinLock( &IcaSpinLock, &oldIrql );
  199. InsertTailList( &pTimer->WorkItemListHead, &pWorkItem->Links );
  200. IcaReleaseSpinLock( &IcaSpinLock, oldIrql );
  201. bSet = KeSetTimer( &pTimer->kTimer, DueTime, &pTimer->TimerDpc );
  202. ASSERT( !bSet );
  203. }
  204. return( bCanceled );
  205. }
  206. /*******************************************************************************
  207. *
  208. * IcaTimerCancel
  209. *
  210. * cancel the specified timer
  211. *
  212. *
  213. * ENTRY:
  214. * TimerHandle (input)
  215. * timer handle
  216. *
  217. * EXIT:
  218. * TRUE : timer was actually canceled
  219. * FALSE : timer was not armed
  220. *
  221. ******************************************************************************/
  222. BOOLEAN
  223. IcaTimerCancel( IN PVOID TimerHandle )
  224. {
  225. PICA_TIMER pTimer = (PICA_TIMER)TimerHandle;
  226. BOOLEAN bCanceled;
  227. TRACESTACK(( pTimer->pSdLink->pStack, TC_ICADD, TT_API3,
  228. "ICADD: TimerCancel: %08x\n", pTimer ));
  229. ASSERT( ExIsResourceAcquiredExclusiveLite( &pTimer->pSdLink->pStack->Resource ) );
  230. /*
  231. * Cancel timer if it is enabled
  232. */
  233. bCanceled = _IcaCancelTimer( pTimer, NULL );
  234. if ( bCanceled )
  235. IcaDereferenceSdLink( pTimer->pSdLink );
  236. return( bCanceled );
  237. }
  238. /*******************************************************************************
  239. *
  240. * IcaTimerClose
  241. *
  242. * cancel the specified timer
  243. *
  244. *
  245. * ENTRY:
  246. * TimerHandle (input)
  247. * timer handle
  248. *
  249. * EXIT:
  250. * TRUE : timer was actually canceled
  251. * FALSE : timer was not armed
  252. *
  253. ******************************************************************************/
  254. BOOLEAN
  255. IcaTimerClose( IN PVOID TimerHandle )
  256. {
  257. PICA_TIMER pTimer = (PICA_TIMER)TimerHandle;
  258. BOOLEAN bCanceled;
  259. TRACESTACK(( pTimer->pSdLink->pStack, TC_ICADD, TT_API3,
  260. "ICADD: TimerClose: %08x\n", pTimer ));
  261. ASSERT( ExIsResourceAcquiredExclusiveLite( &pTimer->pSdLink->pStack->Resource ) );
  262. /*
  263. * Cancel timer if it is enabled
  264. */
  265. bCanceled = IcaTimerCancel( TimerHandle );
  266. /*
  267. * Decrement timer reference
  268. * (the last reference will free the object)
  269. */
  270. //ASSERT( pTimer->RefCount == 1 );
  271. //ASSERT( IsListEmpty( &pTimer->WorkItemListHead ) );
  272. _IcaDereferenceTimer( pTimer );
  273. return( bCanceled );
  274. }
  275. /*******************************************************************************
  276. *
  277. * IcaQueueWorkItemEx, IcaQueueWorkItem.
  278. *
  279. * Queue a work item for async execution
  280. *
  281. * REM: IcaQueueWorkItemEx is the new API. It allows the caller to preallocate
  282. * the ICA_WORK_ITEM. IcaQueueWorkItem is left there for lecacy drivers that have not
  283. * been compiled with the new library not to crash the system.
  284. *
  285. * ENTRY:
  286. * pContext (input)
  287. * Pointer to SDCONTEXT of caller
  288. * pFunc (input)
  289. * address of procedure to call when timer expires
  290. * pParam (input)
  291. * parameter to pass to procedure
  292. * LockFlags (input)
  293. * Bit flags to specify which (if any) stack locks to obtain
  294. *
  295. * EXIT:
  296. * STATUS_SUCCESS - no error
  297. *
  298. ******************************************************************************/
  299. NTSTATUS
  300. IcaQueueWorkItem(
  301. IN PSDCONTEXT pContext,
  302. IN PVOID pFunc,
  303. IN PVOID pParam,
  304. IN ULONG LockFlags )
  305. {
  306. PSDLINK pSdLink;
  307. PICA_WORK_ITEM pWorkItem;
  308. NTSTATUS Status;
  309. Status = IcaQueueWorkItemEx( pContext, pFunc, pParam, LockFlags, NULL );
  310. return Status;
  311. }
  312. NTSTATUS
  313. IcaQueueWorkItemEx(
  314. IN PSDCONTEXT pContext,
  315. IN PVOID pFunc,
  316. IN PVOID pParam,
  317. IN ULONG LockFlags,
  318. IN PVOID pIcaWorkItem )
  319. {
  320. PSDLINK pSdLink;
  321. PICA_WORK_ITEM pWorkItem = (PICA_WORK_ITEM) pIcaWorkItem;
  322. pSdLink = CONTAINING_RECORD( pContext, SDLINK, SdContext );
  323. /*
  324. * Allocate the ICA work item if not yet allocated and initialize it.
  325. */
  326. if (pWorkItem == NULL) {
  327. pWorkItem = ICA_ALLOCATE_POOL( NonPagedPool, sizeof(ICA_WORK_ITEM) );
  328. if ( pWorkItem == NULL )
  329. return( STATUS_NO_MEMORY );
  330. }
  331. pWorkItem->pFunc = pFunc;
  332. pWorkItem->pParam = pParam;
  333. pWorkItem->pSdLink = pSdLink;
  334. pWorkItem->LockFlags = LockFlags;
  335. ExInitializeWorkItem( &pWorkItem->WorkItem, _IcaDelayedWorker, pWorkItem );
  336. /*
  337. * Reference the SDLINK object on behalf of the delayed worker routine.
  338. */
  339. IcaReferenceSdLink( pSdLink );
  340. /*
  341. * Queue work item to an ExWorker thread.
  342. */
  343. ExQueueWorkItem( &pWorkItem->WorkItem, CriticalWorkQueue );
  344. return( STATUS_SUCCESS );
  345. }
  346. /*******************************************************************************
  347. *
  348. * IcaAllocateWorkItem.
  349. *
  350. * Allocate ICA_WORK_ITEM structure to queue a workitem.
  351. *
  352. * REM: The main reason to allocate this in termdd (instead of doing it
  353. * in the caller is to keep ICA_WORK_ITEM an internal termdd structure that is
  354. * opaque for protocol drivers. There is no need for an IcaFreeWorkItem() API in
  355. * termdd since the deallocation is transparently done in termdd once the workitem
  356. * has been delivered.
  357. *
  358. * ENTRY:
  359. * pParam (output) : pointer to return allocated workitem
  360. *
  361. * EXIT:
  362. * STATUS_SUCCESS - no error
  363. *
  364. ******************************************************************************/
  365. NTSTATUS
  366. IcaAllocateWorkItem(
  367. OUT PVOID *pParam )
  368. {
  369. PICA_WORK_ITEM pWorkItem;
  370. *pParam = ICA_ALLOCATE_POOL( NonPagedPool, sizeof(ICA_WORK_ITEM) );
  371. if ( *pParam == NULL ){
  372. return( STATUS_NO_MEMORY );
  373. }
  374. return STATUS_SUCCESS;
  375. }
  376. /*******************************************************************************
  377. *
  378. * _IcaTimerDpc
  379. *
  380. * Ica timer DPC routine.
  381. *
  382. *
  383. * ENTRY:
  384. * Dpc (input)
  385. * Unused
  386. *
  387. * DeferredContext (input)
  388. * Pointer to ICA_TIMER object.
  389. *
  390. * SystemArgument1 (input)
  391. * Unused
  392. *
  393. * SystemArgument2 (input)
  394. * Unused
  395. *
  396. * EXIT:
  397. * nothing
  398. *
  399. ******************************************************************************/
  400. VOID
  401. _IcaTimerDpc(
  402. IN struct _KDPC *Dpc,
  403. IN PVOID DeferredContext,
  404. IN PVOID SystemArgument1,
  405. IN PVOID SystemArgument2
  406. )
  407. {
  408. PICA_TIMER pTimer = (PICA_TIMER)DeferredContext;
  409. KIRQL oldIrql;
  410. PLIST_ENTRY Head;
  411. PICA_WORK_ITEM pWorkItem;
  412. /*
  413. * Acquire spinlock and remove the first workitem from the list
  414. */
  415. IcaAcquireSpinLock( &IcaSpinLock, &oldIrql );
  416. Head = RemoveHeadList( &pTimer->WorkItemListHead );
  417. pWorkItem = CONTAINING_RECORD( Head, ICA_WORK_ITEM, Links );
  418. IcaReleaseSpinLock( &IcaSpinLock, oldIrql );
  419. /*
  420. * If workitem has been canceled, just free the memory now.
  421. */
  422. if ( pWorkItem->fCanceled ) {
  423. ICA_FREE_POOL( pWorkItem );
  424. /*
  425. * Otherwise, queue workitem to an ExWorker thread.
  426. */
  427. } else {
  428. ExQueueWorkItem( &pWorkItem->WorkItem, CriticalWorkQueue );
  429. }
  430. _IcaDereferenceTimer( pTimer );
  431. }
  432. /*******************************************************************************
  433. *
  434. * _IcaDelayedWorker
  435. *
  436. * Ica delayed worker routine.
  437. *
  438. *
  439. * ENTRY:
  440. * WorkerContext (input)
  441. * Pointer to ICA_WORK_ITEM object.
  442. *
  443. * EXIT:
  444. * nothing
  445. *
  446. ******************************************************************************/
  447. VOID
  448. _IcaDelayedWorker(
  449. IN PVOID WorkerContext
  450. )
  451. {
  452. PICA_CONNECTION pConnect;
  453. PICA_WORK_ITEM pWorkItem = (PICA_WORK_ITEM)WorkerContext;
  454. PICA_STACK pStack = pWorkItem->pSdLink->pStack;
  455. NTSTATUS Status;
  456. /*
  457. * Obtain any required locks before calling the worker routine.
  458. */
  459. if ( pWorkItem->LockFlags & ICALOCK_IO ) {
  460. pConnect = IcaLockConnectionForStack( pStack );
  461. }
  462. if ( pWorkItem->LockFlags & ICALOCK_DRIVER ) {
  463. IcaLockStack( pStack );
  464. }
  465. /*
  466. * Call the worker routine.
  467. */
  468. try {
  469. (*pWorkItem->pFunc)( pWorkItem->pSdLink->SdContext.pContext,
  470. pWorkItem->pParam );
  471. } except( IcaExceptionFilter( L"_IcaDelayedWorker TRAPPED!!",
  472. GetExceptionInformation() ) ) {
  473. Status = GetExceptionCode();
  474. }
  475. /*
  476. * Release any locks acquired above.
  477. */
  478. if ( pWorkItem->LockFlags & ICALOCK_DRIVER ) {
  479. IcaUnlockStack( pStack );
  480. }
  481. if ( pWorkItem->LockFlags & ICALOCK_IO ) {
  482. IcaUnlockConnection( pConnect );
  483. }
  484. /*
  485. * Dereference the SDLINK object now.
  486. * This undoes the reference that was made on our behalf in the
  487. * IcaTimerStart or IcaQueueWorkItem routine.
  488. */
  489. IcaDereferenceSdLink( pWorkItem->pSdLink );
  490. /*
  491. * Free the ICA_WORK_ITEM memory block.
  492. */
  493. ICA_FREE_POOL( pWorkItem );
  494. }
  495. BOOLEAN
  496. _IcaCancelTimer(
  497. PICA_TIMER pTimer,
  498. PICA_WORK_ITEM *ppWorkItem
  499. )
  500. {
  501. KIRQL oldIrql;
  502. PLIST_ENTRY Tail;
  503. PICA_WORK_ITEM pWorkItem;
  504. BOOLEAN bCanceled;
  505. /*
  506. * Get IcaSpinLock to in order to cancel any previous timer
  507. */
  508. IcaAcquireSpinLock( &IcaSpinLock, &oldIrql );
  509. /*
  510. * See if the timer is currently armed.
  511. * The timer is armed if the workitem list is non-empty and
  512. * the tail entry is not marked canceled.
  513. */
  514. if ( !IsListEmpty( &pTimer->WorkItemListHead ) &&
  515. (Tail = pTimer->WorkItemListHead.Blink) &&
  516. (pWorkItem = CONTAINING_RECORD( Tail, ICA_WORK_ITEM, Links )) &&
  517. !pWorkItem->fCanceled ) {
  518. /*
  519. * If the timer can be canceled, remove the workitem from the list
  520. * and decrement the reference count for the timer.
  521. */
  522. if ( KeCancelTimer( &pTimer->kTimer ) ) {
  523. RemoveEntryList( &pWorkItem->Links );
  524. pTimer->RefCount--;
  525. ASSERT( pTimer->RefCount > 0 );
  526. /*
  527. * The timer was armed but could not be canceled.
  528. * On a MP system, its possible for this to happen and the timer
  529. * DPC can be executing on another CPU in parallel with this code.
  530. *
  531. * Mark the workitem as canceled,
  532. * but leave it for the timer DPC routine to cleanup.
  533. */
  534. } else {
  535. pWorkItem->fCanceled = TRUE;
  536. pWorkItem = NULL;
  537. }
  538. /*
  539. * Indicate we (effectively) canceled the timer
  540. */
  541. bCanceled = TRUE;
  542. /*
  543. * No timer is armed
  544. */
  545. } else {
  546. pWorkItem = NULL;
  547. bCanceled = FALSE;
  548. }
  549. /*
  550. * Release IcaSpinLock now
  551. */
  552. IcaReleaseSpinLock( &IcaSpinLock, oldIrql );
  553. if ( ppWorkItem ) {
  554. *ppWorkItem = pWorkItem;
  555. } else if ( pWorkItem ) {
  556. ICA_FREE_POOL( pWorkItem );
  557. }
  558. return( bCanceled );
  559. }
  560. VOID
  561. _IcaReferenceTimer(
  562. PICA_TIMER pTimer
  563. )
  564. {
  565. ASSERT( pTimer->RefCount >= 0 );
  566. /*
  567. * Increment the reference count
  568. */
  569. if ( InterlockedIncrement( &pTimer->RefCount) <= 0 ) {
  570. ASSERT( FALSE );
  571. }
  572. }
  573. VOID
  574. _IcaDereferenceTimer(
  575. PICA_TIMER pTimer
  576. )
  577. {
  578. ASSERT( pTimer->RefCount > 0 );
  579. /*
  580. * Decrement the reference count
  581. * If it is 0 then free the timer now.
  582. */
  583. if ( InterlockedDecrement( &pTimer->RefCount) == 0 ) {
  584. ASSERT( IsListEmpty( &pTimer->WorkItemListHead ) );
  585. ICA_FREE_POOL( pTimer );
  586. }
  587. }