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.

374 lines
14 KiB

  1. #pragma hdrstop
  2. #include <ntddk.h>
  3. #include "TSQ.h"
  4. #include "TSQPublic.h"
  5. #define TSQ_TAG 'QST '
  6. //=================================================================================
  7. //
  8. // TSInitQueue
  9. // This function initializes the TS queue with the specified parameters.
  10. //
  11. // Inputs:
  12. // Flags: Properties of the TS Queue.
  13. // MaxThreads: Max number of worker threads to process the item.
  14. //
  15. // Return value:
  16. // Pointer to the TS Queue. NULL if init failed for any reason.
  17. // BUGBUG: Better to have a status:
  18. // Access denied, Invalid parameters, memory failure or success.
  19. //
  20. //=================================================================================
  21. PVOID TSInitQueue(
  22. IN ULONG Flags,
  23. IN ULONG MaxThreads,
  24. IN PDEVICE_OBJECT pDeviceObject
  25. )
  26. {
  27. PTSQUEUE pTsQueue = NULL;
  28. // Validate the inputs.
  29. if ( ( Flags & TSQUEUE_BEING_DELETED ) ||
  30. ( MaxThreads > MAX_WORKITEMS ) ||
  31. ( pDeviceObject == NULL ) ) {
  32. // BUGBUG: Ideally should check all the bits in the flags using mask.
  33. goto Exit;
  34. }
  35. // If the caller wants TS Queue to use its own thread, then caller must be running at PASSIVE_LEVEL.
  36. if ( ( KeGetCurrentIrql() != PASSIVE_LEVEL ) &&
  37. ( Flags & TSQUEUE_OWN_THREAD ) ) {
  38. goto Exit;
  39. }
  40. // Allocate space for the new TS Queue.
  41. pTsQueue = (PTSQUEUE) ExAllocatePoolWithTag( NonPagedPool, sizeof( TSQUEUE ), TSQ_TAG );
  42. if (pTsQueue == NULL) {
  43. goto Exit;
  44. }
  45. // Initialize the terminate event.
  46. KeInitializeEvent( &pTsQueue->TerminateEvent, NotificationEvent, FALSE );
  47. // Initialize the TS Queue spin lock.
  48. KeInitializeSpinLock( &pTsQueue->TsqSpinLock );
  49. // Initialize the list of work items and number of items being processed.
  50. InitializeListHead( &pTsQueue->WorkItemsHead );
  51. pTsQueue->ThreadsCount = 0;
  52. // Initialize the rest of the TS Queue fields as specified in the inputs.
  53. pTsQueue->Flags = Flags;
  54. pTsQueue->MaxThreads = MaxThreads;
  55. pTsQueue->pDeviceObject = pDeviceObject;
  56. Exit:
  57. return ( PVOID ) pTsQueue;
  58. }
  59. //=================================================================================
  60. //
  61. // TSAddWorkItemToQueue
  62. // This function allocates a work item (TSQ type) and adds it to the queue
  63. // from where it is processed by either system queue thread or TS queue
  64. // worker thread.
  65. //
  66. // Inputs:
  67. // TS Queue: To which the work item is to be added.
  68. // pContext: Caller context.
  69. // CallBack: The user's callback routine
  70. //
  71. // Return value:
  72. // Status of the operation:
  73. // STATUS_INVALID_PARAMETER: Incorrect TS Queue pointer, OR
  74. // STATUS_ACCESS_DENIED: The queue is being deleted, OR
  75. // STATUS_NO_MEMORY: Insufficient resources OR
  76. // STATUS_SUCCESS: Operation successful.
  77. //
  78. //=================================================================================
  79. NTSTATUS TSAddWorkItemToQueue(
  80. IN PTSQUEUE pTsQueue, // Pointer to the TS Queue.
  81. IN PVOID pContext, // Context.
  82. IN PTSQ_CALLBACK pCallBack // Callback function.
  83. )
  84. {
  85. KIRQL Irql;
  86. NTSTATUS Status;
  87. PTSQUEUE_WORK_ITEM pWorkItem = NULL;
  88. HANDLE ThreadHandle;
  89. // Check if the Input TS Queue pointer is valid.
  90. // May be we need a better error check on TS Queue pointer here (Like use of signature)
  91. // I don't need to care about validity of the other parameters.
  92. if ( pTsQueue == NULL ) {
  93. return STATUS_INVALID_PARAMETER;
  94. }
  95. // Allocate space for the new work item (TSQ type).
  96. // NOTE: Allocation is done before validation, because this is a costly operation and we
  97. // don't want to do it with spin-lock held for perf reasons.
  98. pWorkItem = (PTSQUEUE_WORK_ITEM) ExAllocatePoolWithTag( NonPagedPool, sizeof( TSQUEUE_WORK_ITEM ), TSQ_TAG );
  99. if ( pWorkItem == NULL ) {
  100. return STATUS_NO_MEMORY;
  101. }
  102. // Initialize the TSQ work item.
  103. pWorkItem->pContext = pContext;
  104. pWorkItem->pCallBack = pCallBack;
  105. // Acquire the Queue spin lock first.
  106. KeAcquireSpinLock( &pTsQueue->TsqSpinLock, &Irql );
  107. // Check if this queue is being deleted. If so, return error.
  108. if ( pTsQueue->Flags & TSQUEUE_BEING_DELETED ) {
  109. KeReleaseSpinLock( &pTsQueue->TsqSpinLock, Irql );
  110. if ( pWorkItem ) {
  111. ExFreePool( pWorkItem );
  112. }
  113. return STATUS_ACCESS_DENIED;
  114. }
  115. // All right, insert the work item in TS queue.
  116. InsertTailList( &pTsQueue->WorkItemsHead, &pWorkItem->Links );
  117. // NOTE: Once the work item is queued, we are going to return status success anyway.
  118. // Failed cases, if any, will be handled either by already running worker threads or
  119. // later when the queue is deleted.
  120. // Check if we need to start another worker thread.
  121. if ( pTsQueue->ThreadsCount >= pTsQueue->MaxThreads ) {
  122. // Do nothing else, when there are already enough number of worker threads serving the queue.
  123. KeReleaseSpinLock( &pTsQueue->TsqSpinLock, Irql );
  124. return STATUS_SUCCESS;
  125. }
  126. // We are about to start a new thread (own thread or system thread).
  127. // So, increment the thread count and release the spin lock.
  128. pTsQueue->ThreadsCount ++;
  129. KeReleaseSpinLock( &pTsQueue->TsqSpinLock, Irql );
  130. // Check if we are allowed to start a worker thread.
  131. if ( ( pTsQueue->Flags & TSQUEUE_OWN_THREAD ) &&
  132. ( KeGetCurrentIrql() == PASSIVE_LEVEL ) ) {
  133. // We can create our own thread for processing work item.
  134. Status = PsCreateSystemThread( &ThreadHandle,
  135. THREAD_ALL_ACCESS,
  136. NULL,
  137. NULL,
  138. NULL,
  139. (PKSTART_ROUTINE) TSQueueWorker,
  140. (PVOID) pTsQueue );
  141. if ( Status != STATUS_SUCCESS ) {
  142. goto QueueError;
  143. }
  144. ZwClose( ThreadHandle );
  145. }
  146. else { // Means, we can not create thread. Then call IOQueueWorkItem.
  147. PIO_WORKITEM pIoWorkItem = NULL;
  148. PTSQ_CONTEXT pTsqContext = NULL;
  149. WORK_QUEUE_TYPE QueueType = ( pTsQueue->Flags & TSQUEUE_CRITICAL ) ? CriticalWorkQueue : DelayedWorkQueue;
  150. // Allocate space for TSQ context.
  151. pTsqContext = (PTSQ_CONTEXT) ExAllocatePoolWithTag( NonPagedPool, sizeof( TSQ_CONTEXT ), TSQ_TAG );
  152. if ( pTsqContext == NULL ) {
  153. goto QueueError;
  154. }
  155. // Allocate the IO work item.
  156. pIoWorkItem = IoAllocateWorkItem( pTsQueue->pDeviceObject );
  157. if ( pIoWorkItem == NULL ) {
  158. ExFreePool( pTsqContext );
  159. goto QueueError;
  160. }
  161. // Initialize the TSQ context and queue the work item in the system queue.
  162. pTsqContext->pTsQueue = pTsQueue;
  163. pTsqContext->pWorkItem = pIoWorkItem; // This is IO work item.
  164. IoQueueWorkItem( pIoWorkItem, ( PIO_WORKITEM_ROUTINE )TSQueueCallback, QueueType, (PVOID) pTsqContext );
  165. }
  166. return STATUS_SUCCESS;
  167. QueueError:
  168. KeAcquireSpinLock( &pTsQueue->TsqSpinLock, &Irql );
  169. pTsQueue->ThreadsCount --;
  170. // If the thread count is zero, we are the last one who finished processing work items.
  171. // Now if the queue is marked "being deleted", we should set the Terminate event.
  172. if ( ( pTsQueue->Flags & TSQUEUE_BEING_DELETED ) &&
  173. ( pTsQueue->ThreadsCount == 0 ) ) {
  174. KeSetEvent( &pTsQueue->TerminateEvent, 0, FALSE );
  175. }
  176. KeReleaseSpinLock( &pTsQueue->TsqSpinLock, Irql );
  177. return STATUS_SUCCESS;
  178. }
  179. //=================================================================================
  180. //
  181. // TSDeleteQueue
  182. // This function deletes the specified TS Queue. It first processes all the
  183. // pending work items before deleting.
  184. //
  185. // Inputs:
  186. // TS Queue: To be deleted.
  187. //
  188. // Return value:
  189. // STATUS_SUCCESS: Operation successful.
  190. // BUGBUG: Wondering why we have this.
  191. //
  192. //=================================================================================
  193. NTSTATUS TSDeleteQueue(PVOID pTsq)
  194. {
  195. KIRQL Irql;
  196. PTSQUEUE pTsQueue = (PTSQUEUE) pTsq;
  197. NTSTATUS Status;
  198. // BUGBUG: There should be better way of checking TS Queue pointer (Use of signature or alike).
  199. if ( pTsQueue == NULL ) {
  200. return STATUS_SUCCESS;
  201. }
  202. KeAcquireSpinLock( &pTsQueue->TsqSpinLock, &Irql );
  203. // Check if the queue is already being deleted.
  204. // It should not happen, but just in case, if the driver is not good.
  205. if ( pTsQueue->Flags & TSQUEUE_BEING_DELETED ) {
  206. ASSERT( FALSE );
  207. return STATUS_ACCESS_DENIED;
  208. }
  209. // Mark the queue "being deleted", so that it won't accept any new work items.
  210. pTsQueue->Flags |= TSQUEUE_BEING_DELETED;
  211. // Now help other worker threads in processing the pending work items on the queue.
  212. // So, increment the thread count, which will be decremented in the TSQueueWorker routine.
  213. pTsQueue->ThreadsCount ++;
  214. KeReleaseSpinLock( &pTsQueue->TsqSpinLock, Irql );
  215. // NOTE: This will also clean up the queue, if there are work items in the queue and no
  216. // worker threads to process them.
  217. TSQueueWorker( pTsQueue );
  218. // It is still possible that other threads are still working with their work items.
  219. // So, we can not just go and free the queue. So wait for the termination event.
  220. KeWaitForSingleObject( &pTsQueue->TerminateEvent, Executive, KernelMode, TRUE, NULL );
  221. // BUGBUG: Now the worker threads have set the event, but they have done that while holding
  222. // the spin lock. If we free the TS queue right away, they will access NULL pointer and
  223. // bug-check. So, acquire spin-lock, so that the thread which set the event will release it.
  224. // We know that there will be only one such thread at a give time. So, we don't need ref-count
  225. // now. But using ref-count is more eligent solution here.
  226. KeAcquireSpinLock( &pTsQueue->TsqSpinLock, &Irql );
  227. KeReleaseSpinLock( &pTsQueue->TsqSpinLock, Irql );
  228. // We are all done.
  229. // Clean up the space allocated for the TS queue.
  230. ExFreePool( pTsQueue );
  231. pTsQueue = NULL;
  232. return STATUS_SUCCESS;
  233. }
  234. //=================================================================================
  235. //
  236. // TSQueueWorker
  237. // This is a worker thread for the TS Queue, which goes through the queue and
  238. // processes the pending work items (TSQ type) one by one.
  239. //
  240. //=================================================================================
  241. void TSQueueWorker(PTSQUEUE pTsQueue)
  242. {
  243. PLIST_ENTRY Item;
  244. PTSQUEUE_WORK_ITEM pWorkItem;
  245. KIRQL Irql;
  246. // Acquire the Queue spin lock first.
  247. KeAcquireSpinLock( &pTsQueue->TsqSpinLock, &Irql );
  248. // Process the work items on the queue, while the queue is not empty
  249. while( !IsListEmpty( &pTsQueue->WorkItemsHead ) ) {
  250. // Get the next TSQ work item from the queue.
  251. Item = RemoveHeadList( &pTsQueue->WorkItemsHead );
  252. pWorkItem = CONTAINING_RECORD( Item, TSQUEUE_WORK_ITEM, Links );
  253. // Release the Queue spin lock.
  254. KeReleaseSpinLock( &pTsQueue->TsqSpinLock, Irql );
  255. // Call the callback routine specified in the work item.
  256. if ( pWorkItem->pCallBack ) {
  257. ( *pWorkItem->pCallBack ) ( pTsQueue->pDeviceObject, pWorkItem->pContext );
  258. }
  259. // Free the TSQ work item.
  260. ExFreePool( pWorkItem );
  261. // Acquire the Queue spin lock again.
  262. KeAcquireSpinLock( &pTsQueue->TsqSpinLock, &Irql );
  263. }
  264. // We are done processing the work items. This thread is going to exit now.
  265. // Decrememnt the thread count so that next work item gets processed by a new thread
  266. // or queued in the system queue.
  267. pTsQueue->ThreadsCount --;
  268. // If the thread count is zero, we are the last one who finished processing work items.
  269. // Now if the queue is marked "being deleted", we should set the Terminate event.
  270. if ( ( pTsQueue->Flags & TSQUEUE_BEING_DELETED ) &&
  271. ( pTsQueue->ThreadsCount == 0 ) ) {
  272. KeSetEvent( &pTsQueue->TerminateEvent, 0, FALSE );
  273. }
  274. KeReleaseSpinLock( &pTsQueue->TsqSpinLock, Irql );
  275. }
  276. //=================================================================================
  277. //
  278. // TSQueueCallback
  279. // This is the callback routine we specify, when we use system queue for
  280. // processing the TSQ work item. This will in turn call the routine that
  281. // TS Queue worker thread executes. And that routine will process all the
  282. // pending work items from the queue. We use this callback routine just for
  283. // cleaning up the IO work item that we allocated for using system queue.
  284. //
  285. //=================================================================================
  286. void TSQueueCallback(PDEVICE_OBJECT pDeviceObject, PVOID pContext)
  287. {
  288. PTSQ_CONTEXT pTsqContext = (PTSQ_CONTEXT) pContext;
  289. // BUGBUG: It's better to have a check on pDeviceObject.
  290. // If input context here is NULL, then we sure have a big problem in system worker queue implementation.
  291. ASSERT( pTsqContext != NULL );
  292. // Process the TSQ work items on the queue.
  293. TSQueueWorker( pTsqContext->pTsQueue );
  294. // Cleanup the IO work Item.
  295. if ( pTsqContext->pWorkItem ) {
  296. IoFreeWorkItem( pTsqContext->pWorkItem );
  297. pTsqContext->pWorkItem = NULL;
  298. }
  299. // Free TSQ context.
  300. ExFreePool( pTsqContext );
  301. }