OpenCBM
queue.c
Go to the documentation of this file.
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version
5  * 2 of the License, or (at your option) any later version.
6  *
7  * Copyright 2004 Spiro Trikaliotis
8  *
9  */
10 
19 #include <wdm.h>
20 #include "cbm_driver.h"
21 
22 /*
23  This file contains the quite tricky part of the drivers.
24 */
25 
46 static VOID
47 InsertIrp(IN PIO_CSQ Csq, IN PIRP Irp)
48 {
49  PQUEUE queue;
50 
51  FUNC_ENTER();
52 
53  PERF_EVENT_VERBOSE(0x3000, (ULONG)Irp);
54 
55  // Get the QUEUE object which contains this CSQ
56 
57  // no IRQL restrictions apply
58  queue = CONTAINING_RECORD(Csq, QUEUE, IrpQueue);
59 
60  // Insert the IRP into the QUEUE
61 
62  // no IRQL restrictions apply
63  InsertTailList(&queue->IrpListHead, &Irp->Tail.Overlay.ListEntry);
64 
65  PERF_EVENT_VERBOSE(0x3001, 0);
66 
67  FUNC_LEAVE();
68 }
69 
90 static VOID
91 RemoveIrp(IN PIO_CSQ Csq, IN PIRP Irp)
92 {
93  FUNC_ENTER();
94 
95  UNREFERENCED_PARAMETER(Csq);
96 
97  PERF_EVENT_VERBOSE(0x3010, (ULONG)Irp);
98 
99  // Remove the IRP from the CSQ/QUEUE
100 
101  // no IRQL restrictions apply
102  RemoveEntryList(&Irp->Tail.Overlay.ListEntry);
103 
104  PERF_EVENT_VERBOSE(0x3011, 0);
105 
106  FUNC_LEAVE();
107 }
108 
148 static PIRP
149 PeekNextIrp(IN PIO_CSQ Csq, IN PIRP Irp, IN PVOID FileObject)
150 {
151  PLIST_ENTRY currentListEntry;
152  PQUEUE queue;
153  PIRP currentIrp;
154 
155  FUNC_ENTER();
156 
157  // Get the QUEUE object which contains this CSQ
158 
159  // no IRQL restrictions apply
160  queue = CONTAINING_RECORD(Csq, QUEUE, IrpQueue);
161 
162  // Get the list entry of the first IRP to be checked
163  // into currentListEntry
164 
165  if (Irp == NULL)
166  {
167  // No previous IRP was given, thus, start at the
168  // beginning of the queue
169 
170  currentListEntry = queue->IrpListHead.Flink;
171  }
172  else
173  {
174  // As an IRP was given, go to its successor.
175 
176  currentListEntry = Irp->Tail.Overlay.ListEntry.Flink;
177  }
178 
179  // only process the list if it is not empty!
180 
181  if (currentListEntry != &queue->IrpListHead)
182  {
183  // get the containing IRP
184 
185  // no IRQL restrictions apply
186  currentIrp = CONTAINING_RECORD(currentListEntry, IRP, Tail.Overlay.ListEntry);
187 
188  // If we were not given any file object, we have finished.
189  // If we were given a file object, check if the IRP is
190  // associated with that file object, thus, perform some
191  // additional steps:
192 
193  if (FileObject)
194  {
195  BOOLEAN found;
196 
197  // We have not found any matching IRP yet
198 
199  found = FALSE;
200 
201  // For every IRP in the list, check if the condition
202  // holds. If we found a matching IRP, quit this loop.
203 
204  while ((currentListEntry != &queue->IrpListHead) && !found)
205  {
206  PIO_STACK_LOCATION irpStack;
207 
208  // get the containing IRP
209 
210  // no IRQL restrictions apply
211  irpStack = IoGetCurrentIrpStackLocation(currentIrp);
212 
213  if (irpStack->FileObject == (PFILE_OBJECT) FileObject)
214  {
215  found = TRUE;
216  }
217  else
218  {
219  // Proceed to next list entry (and IRP)
220 
221  currentListEntry = currentListEntry->Flink;
222 
223  // no IRQL restrictions apply
224  currentIrp = CONTAINING_RECORD(currentListEntry, IRP,
225  Tail.Overlay.ListEntry);
226  }
227  }
228 
229  if (!found)
230  {
231  // If we are to quit the loop now, we have not
232  // found a matching IRP
233 
234  currentIrp = NULL;
235  }
236  }
237  }
238  else
239  {
240  // We have not found any IRP
241 
242  currentIrp = NULL;
243  }
244 
245 
246  FUNC_LEAVE_PTR(currentIrp, PIRP);
247 }
248 
268 static VOID
269 AcquireLock(IN PIO_CSQ Csq, OUT PKIRQL Irql)
270 {
271  PQUEUE queue;
272  KIRQL irql; // do not use Irql directly, but only indirectly,
273  // as suggested by Doron Holan at
274  // http://blogs.msdn.com/doronh/archive/2006/03/08/546934.aspx
275 
276  FUNC_ENTER();
277 
278  // Get the containing QUEUE object
279 
280  // no IRQL restrictions apply
281  queue = CONTAINING_RECORD(Csq, QUEUE, IrpQueue);
282 
283  // As we are using a spin lock, acquire that one
284 
285  DBG_IRQL( <= DISPATCH_LEVEL);
286  KeAcquireSpinLock(&queue->IrpListSpinlock, &irql);
287 
288  *Irql = irql;
289 
290  FUNC_LEAVE();
291 }
292 
313 static VOID
314 ReleaseLock(IN PIO_CSQ Csq, IN KIRQL Irql)
315 {
316  PQUEUE queue;
317 
318  FUNC_ENTER();
319 
320  // Get the containing QUEUE object
321 
322  // no IRQL restrictions apply
323  queue = CONTAINING_RECORD(Csq, QUEUE, IrpQueue);
324 
325  // As we are using a spin lock, release that one
326 
327  DBG_IRQL( == DISPATCH_LEVEL);
328  KeReleaseSpinLock(&queue->IrpListSpinlock, Irql);
329 
330  FUNC_LEAVE();
331 }
332 
355 static VOID
356 CompleteCanceledIrp(IN PIO_CSQ Csq, IN PIRP Irp)
357 {
358  PQUEUE queue;
359 
360  FUNC_ENTER();
361 
363 
364  // Get the containing QUEUE object
365 
366  // no IRQL restrictions apply
367  queue = CONTAINING_RECORD(Csq, QUEUE, IrpQueue);
368 
369  // Complete the IRP
370 
371  QueueCompleteIrp(queue, Irp, STATUS_CANCELLED, 0);
372 
373  FUNC_LEAVE();
374 }
375 
387 VOID
388 QueueInit(PQUEUE Queue, PCBMDRIVER_STARTIO DriverStartIo)
389 {
390  FUNC_ENTER();
391 
392  // can be run at arbitrary IRQL
393  KeInitializeSpinLock(&Queue->IrpListSpinlock);
394 
395  // can be run at arbitrary IRQL
396  InitializeListHead(&Queue->IrpListHead);
397 
398  // Initialize the event which is used to wake up the thread
399  DBG_IRQL( == PASSIVE_LEVEL);
400  KeInitializeEvent(&Queue->NotEmptyEvent, SynchronizationEvent, FALSE);
401 
402 #ifdef USE_FAST_START_THREAD
403 
404  // Initialize the event which is used to wake up "the caller of the thread"
405  DBG_IRQL( == PASSIVE_LEVEL);
406  KeInitializeEvent(&Queue->BackSignalEvent, SynchronizationEvent, FALSE);
407 
408 #endif // #ifdef USE_FAST_START_THREAD
409 
410  // Initialize the cancel-safe queue
411  // no IRQL restrictions apply!
412  IoCsqInitialize(&Queue->IrpQueue,
413  InsertIrp,
414  RemoveIrp,
415  PeekNextIrp,
416  AcquireLock,
417  ReleaseLock,
418  CompleteCanceledIrp);
419 
420  // remember the function pointers
421  Queue->DriverStartIo = DriverStartIo;
422 
423  // start in stalled state
424  Queue->IsStalled = TRUE;
425 
426  // do not start with dropping IRPs
427  Queue->IsDropping = FALSE;
428 
429  FUNC_LEAVE();
430 }
431 
466 NTSTATUS
467 QueueStartPacket(PQUEUE Queue, PIRP Irp, BOOLEAN FastStart, PDEVICE_OBJECT Fdo)
468 {
469  NTSTATUS ntStatus = STATUS_SUCCESS;
470 
471  FUNC_ENTER();
472 
473  // Are we currently dropping IRPs?
474  if (!Queue->IsDropping)
475  {
476  // No: Process the IRP
477 
478  PERF_EVENT_VERBOSE(0x3020, (ULONG)Irp);
479 
480  if (FastStart)
481  {
482  DBG_ASSERT(Fdo != NULL);
483 
484  PERF_EVENT_VERBOSE(0x3021, (ULONG)Irp);
485 
486  // The caller has chosen FastStart.
487  // This means that he wants to IRP to be executed
488  // immediately (and not be queue) if there is not
489  // IRP currently being executed in the thread.
490 
491  // is there already an IRP in progress?
492 
493  if (Queue->CurrentIrp == NULL)
494  {
495  // no, thus, we can do a fast start
496 
497  // Mark this IRP as the current one
498 
499  Queue->CurrentIrp = Irp;
500 
501  // Immediately execute this IRP
502 
503  PERF_EVENT_VERBOSE(0x3022, (ULONG)Irp);
504 
505  ntStatus = Queue->DriverStartIo(Fdo, Irp);
506 
507  PERF_EVENT_VERBOSE(0x3023, 0);
508  }
509  else
510  {
511  // There is an IRP being processed.
512  // Unfortunately, this means that a fast
513  // start is not possible.
514 
515  FastStart = FALSE;
516  }
517  }
518 
519  // Has this IRP already been fast started?
520 
521  if (!FastStart)
522  {
523  // No: Add the IRP to the Queue
524 
525  PERF_EVENT_VERBOSE(0x3024, (ULONG)Irp);
526 
527  // First of all, since we will pend the IRP later,
528  // mark it pending to prevent the race condition
529 
530  DBG_IRQL( <= DISPATCH_LEVEL);
531  IoMarkIrpPending(Irp);
532 
533  // Insert the IRP into the queue.
534 
535  // no IRQL restrictions apply!
536  IoCsqInsertIrp(&Queue->IrpQueue, Irp, NULL);
537 
538  PERF_EVENT_VERBOSE(0x3025, 0);
539 
540  // Wake up the waiting thread which can execute
541  // the queued IRP
542 
543  QueueSignal(Queue);
544 
545  PERF_EVENT_VERBOSE(0x3026, 0);
546 
547  // Return to the caller that the IRP has been pended.
548  // This tells the caller that it has to wait until the
549  // IRP is completed by another means.
550 
551  ntStatus = STATUS_PENDING;
552  }
553  }
554  else
555  {
556  // We are currently dropping the IRP, thus,
557  // just return the value set before, and complete the IRP
558  // without doing anything.
559 
560  ntStatus = QueueCompleteIrp(NULL, Irp, Queue->DroppingReturnStatus, 0);
561  }
562 
563  FUNC_LEAVE_NTSTATUS(ntStatus);
564 }
565 
585 static PIRP
586 QueueRemoveNextIrp(PQUEUE Queue)
587 {
588  PIRP irp;
589 
590  FUNC_ENTER();
591 
592  // Just let the CSQ perform all the necessary steps
593 
594  irp = IoCsqRemoveNextIrp(&Queue->IrpQueue, NULL);
595 
596  PERF_EVENT_VERBOSE(0x3030, (ULONG)irp);
597 
598  FUNC_LEAVE_PTR(irp, PIRP);
599 }
600 
614 VOID
616 {
617  LONG ret;
618 
619  FUNC_ENTER();
620 
621  // Increment the number of stall requests
622 
623  ret = InterlockedIncrement(&Queue->IsStalled);
624 
625  DBG_ASSERT(ret == 0);
626 
627  FUNC_LEAVE();
628 }
629 
643 VOID
645 {
646  LONG ret;
647 
648  FUNC_ENTER();
649 
650  // Decrement the number of stall requests
651 
652  ret = InterlockedDecrement(&Queue->IsStalled);
653 
654  DBG_ASSERT(ret == 1);
655 
656  FUNC_LEAVE();
657 }
658 
659 #if DBG
660 
678 BOOLEAN
680 {
681  BOOLEAN Ret;
682 
683  FUNC_ENTER();
684 
685  Ret = Queue->IsStalled ? TRUE : FALSE;
686 
687  FUNC_LEAVE_BOOLEAN(Ret);
688 }
689 
717 BOOLEAN
719 {
720  BOOLEAN Ret;
721 
722  FUNC_ENTER();
723 
724  Ret = Queue->IsDropping ? TRUE : FALSE;
725 
726  FUNC_LEAVE_BOOLEAN(Ret);
727 }
728 
729 #endif // #if DBG
730 
755 NTSTATUS
756 QueueCompleteIrp(PQUEUE Queue, PIRP Irp, NTSTATUS NtStatus, ULONG_PTR Information)
757 {
758  FUNC_ENTER();
759 
760  // If there was a Queue given, the IRP can be the current
761  // executing IRP of the Queue. Because of this, make sure
762  // to unset it from executing state ("CurrentIrp") if it
763  // happens to be the current IRP.
764 
765  if (Queue)
766  {
767  DBG_ASSERT(Queue->CurrentIrp != NULL);
768  DBG_ASSERT(Queue->CurrentIrp == Irp);
769 
770  if (Queue->CurrentIrp == Irp)
771  {
772  Queue->CurrentIrp = NULL;
773  }
774  }
775 
776  // Set the return values into the IRP
777 
778  Irp->IoStatus.Information = Information;
779  Irp->IoStatus.Status = NtStatus;
780 
782 
783  // Now, complete the IRP.
784 
785  DBG_IRQL( <= DISPATCH_LEVEL);
786  IoCompleteRequest(Irp, IO_NO_INCREMENT);
787 
788  // For convenience, return the NTSTATUS which was used to
789  // complete the IRP.
790 
791  FUNC_LEAVE_NTSTATUS(NtStatus);
792 }
793 
815 NTSTATUS
816 QueuePoll(PQUEUE Queue, PDEVICE_OBJECT Fdo)
817 {
818  PIRP irp;
819 
820  FUNC_ENTER();
821 
822  DBG_IRQL( == PASSIVE_LEVEL);
823 
824  DBG_ASSERT(Queue->CurrentIrp == NULL);
825 
826  // First, check if there is an IRP in the QUEUE
827 
828  PERF_EVENT_VERBOSE(0x3040, 0);
829 
830  irp = QueueRemoveNextIrp(Queue);
831 
832  PERF_EVENT_VERBOSE(0x3041, (ULONG)irp);
833 
834  if (!irp)
835  {
836  // There is no IRP in the QUEUE, wait for a new one
837 
838  PERF_EVENT_VERBOSE(0x3042, (ULONG)irp);
839 
840  KeWaitForSingleObject(&Queue->NotEmptyEvent,
841  Executive,
842  KernelMode,
843  FALSE,
844  NULL);
845 
846  PERF_EVENT_VERBOSE(0x3043, (ULONG)irp);
847 
848 #ifdef USE_FAST_START_THREAD
849 
850  // Signal to the caller that it can continue
851 
852  DBG_IRQL( <= DISPATCH_LEVEL);
853  KeSetEvent(&Queue->BackSignalEvent, IO_NO_INCREMENT, FALSE);
854 
855  PERF_EVENT_VERBOSE(0x3044, (ULONG)irp);
856 
857 #endif USE_FAST_START_THREAD
858 
859  // Get the next IRP from the QUEUE
860  // Remember: There might not be any IRP on the
861  // QUEUE. The caller has to handle this state
862  // properly.
863 
864  irp = QueueRemoveNextIrp(Queue);
865 
866  PERF_EVENT_VERBOSE(0x3045, (ULONG)irp);
867  }
868 
869  // If there is an IRP, execute and complete that
870 
871  if (irp)
872  {
873  DBG_ASSERT(Queue->CurrentIrp == NULL);
874 
875  // Set the IRP we just got as the current one
876 
877  Queue->CurrentIrp = irp;
878 
879  // Execute the IRP by calling DriverStartIo()
880 
881 // KIRQL oldIrql;
882 // KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);
883  Queue->DriverStartIo(Fdo, irp);
884 // KeLowerIrql(oldIrql);
885  }
886 
887  FUNC_LEAVE_NTSTATUS_CONST(STATUS_SUCCESS);
888 }
889 
899 NTSTATUS
901 {
902 #ifdef USE_FAST_START_THREAD
903 
904  LARGE_INTEGER timeout;
905 
906 #endif // #ifdef USE_FAST_START_THREAD
907 
908  FUNC_ENTER();
909 
910  // Wake up the waiting thread
911 
912 #ifdef USE_FAST_START_THREAD
913 
914  // The fast start of the thread is only allowed if we are running
915  // at PASSIVE_LEVEL.
916 
917  PERF_EVENT_VERBOSE(0x3050, 0);
918 
919  if (KeGetCurrentIrql() == PASSIVE_LEVEL)
920  {
921  // Make sure the "backsignal event" is not set before we start the thread
922 
923  DBG_IRQL( <= DISPATCH_LEVEL);
924  KeClearEvent(&Queue->BackSignalEvent);
925 
926  PERF_EVENT_VERBOSE(0x3051, 0);
927 
928  // Initialize the timeout value
929 
930  timeout.QuadPart = 1;
931 
932  // Signal the other thread. Make sure no-one else can disturb us
933  // by setting the last argument (Wait) to TRUE
934 
935  DBG_IRQL( == PASSIVE_LEVEL);
936  KeSetEvent(&Queue->NotEmptyEvent, IO_NO_INCREMENT, TRUE);
937 
938  PERF_EVENT_VERBOSE(0x3052, 0);
939 
940  // Make sure the other thread is scheduled by waiting for the
941  // "back event"
942 
943  // This IRQL restriction "normally" applies. Anyway, as the above
944  // KeSetEvent() was called with Wait == TRUE, in fact, we *are* at
945  // DISPATCH_LEVEL, so this tests does not make sense.
946 
947  // DBG_IRQL( < DISPATCH_LEVEL);
948  KeWaitForSingleObject(&Queue->BackSignalEvent,
949  Executive,
950  KernelMode,
951  FALSE,
952  &timeout);
953 
954  PERF_EVENT_VERBOSE(0x3053, 0);
955  }
956  else
957  {
958  // As no fast start is allowed, just signal the thread
959 
960  PERF_EVENT_VERBOSE(0x3054, 0);
961 
962  DBG_IRQL( <= DISPATCH_LEVEL);
963  KeSetEvent(&Queue->NotEmptyEvent, IO_NO_INCREMENT, FALSE);
964 
965  PERF_EVENT_VERBOSE(0x3055, 0);
966  }
967 
968 #else // #ifdef USE_FAST_START_THREAD
969 
970  DBG_IRQL( <= DISPATCH_LEVEL);
971  KeSetEvent(&Queue->NotEmptyEvent, IO_NO_INCREMENT, FALSE);
972 
973 #endif // #ifdef USE_FAST_START_THREAD
974 
975 
976  FUNC_LEAVE_NTSTATUS_CONST(STATUS_SUCCESS);
977 }
978 
997 NTSTATUS
998 QueueCleanup(PQUEUE Queue, PFILE_OBJECT FileObject)
999 {
1000  PIRP irp;
1001 
1002  FUNC_ENTER();
1003 
1004  // Check if there is an IRP associated with that FILE_OBJECT
1005 
1006  // No IRQL restrictions apply
1007  irp = IoCsqRemoveNextIrp(&Queue->IrpQueue, FileObject);
1008 
1009  // While there are still IRPs which are associated with the
1010  // given FILE_OBJECT, take them out of the QUEUE and complete them.
1011 
1012  while (irp)
1013  {
1014  QueueCompleteIrp(NULL, irp, STATUS_CANCELLED, 0);
1015 
1016  // Check for next IRP associated with that FILE_OBJECT
1017 
1018  // No IRQL restrictions apply
1019  irp = IoCsqRemoveNextIrp(&Queue->IrpQueue, FileObject);
1020  }
1021 
1023 
1024  FUNC_LEAVE_NTSTATUS_CONST(STATUS_SUCCESS);
1025 }
1026 
1040 BOOLEAN
1042 {
1043  FUNC_ENTER();
1044 
1045  // Check if the Cancel field of the current IRP has been set
1046 
1047  FUNC_LEAVE_BOOLEAN(Queue->CurrentIrp->Cancel ? TRUE : FALSE);
1048 }
NTSTATUS QueueCompleteIrp(PQUEUE Queue, PIRP Irp, NTSTATUS NtStatus, ULONG_PTR Information)
Complete an IRP which is on a QUEUE.
Definition: queue.c:756
BOOLEAN QueueShouldCancelCurrentIrp(PQUEUE Queue)
Should the current IRP be cancelled?
Definition: queue.c:1041
KEVENT NotEmptyEvent
signal that the queue is not empty
Definition: queue.h:53
#define FUNC_LEAVE_BOOLEAN(_xxx)
Definition: debug.h:356
NTSTATUS(* PCBMDRIVER_STARTIO)(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
Definition: queue.h:28
VOID QueueInit(PQUEUE Queue, PCBMDRIVER_STARTIO DriverStartIo)
Initialize a QUEUE object.
Definition: queue.c:388
#define FUNC_LEAVE()
Definition: debug.h:349
A QUEUE object A QUEUE object is an object which can be used to queue IRPs for processing. This QUEUE object is optimized for being polled from an own worker thread. Anyway, a concept called FastStart is also implemented, which allows some IRPs to be completed immediately, without being queued, if no IRP is executed yet and the caller has stated that he wants this IRP to be started fast if possible.
Definition: queue.h:40
VOID QueueUnstall(PQUEUE Queue)
Sets a QUEUE into unstalled state.
Definition: queue.c:644
#define FUNC_LEAVE_PTR(_xxx, _TYPE)
Definition: debug.h:376
LIST_ENTRY IrpListHead
the list head for the IRP list
Definition: queue.h:47
VOID QueueStall(PQUEUE Queue)
Sets a QUEUE into stalled state.
Definition: queue.c:615
BOOLEAN QueueIsStalled(PQUEUE Queue)
Check if a QUEUE is in stalled state.
Definition: queue.c:679
KSPIN_LOCK IrpListSpinlock
the spin lock to protect the IRP list
Definition: queue.h:50
#define DBG_ASSERT(_xxx)
Definition: debug.h:401
#define PERF_EVENT_COMPLETEIRP(_x_)
Definition: cbm_driver.h:42
NTSTATUS QueueStartPacket(PQUEUE Queue, PIRP Irp, BOOLEAN FastStart, PDEVICE_OBJECT Fdo)
Insert an IRP into a QUEUE object.
Definition: queue.c:467
PIRP CurrentIrp
Pointer to the IRP which is currently processed.
Definition: queue.h:80
NTSTATUS QueueCleanup(PQUEUE Queue, PFILE_OBJECT FileObject)
Process an IRP_MJ_CLEANUP on the QUEUE.
Definition: queue.c:998
#define FUNC_ENTER()
Definition: debug.h:347
PCBMDRIVER_STARTIO DriverStartIo
pointer to the StartIo function to be called
Definition: queue.h:64
Definitions for the opencbm driver.
LONG IsStalled
counter; if != 0, this queue is stalled, that is, no entries are dequeued.
Definition: queue.h:68
NTSTATUS QueuePoll(PQUEUE Queue, PDEVICE_OBJECT Fdo)
Poll the QUEUE.
Definition: queue.c:816
#define PERF_EVENT_CANCELIRP(_x_)
Definition: cbm_driver.h:44
IO_CSQ IrpQueue
the structure for the cancel-safe queue
Definition: queue.h:44
LONG IsDropping
counter; if != 0, this queue is dropping, that is, no new entries are queued into the queue; instead...
Definition: queue.h:73
BOOLEAN QueueIsDropping(PQUEUE Queue)
Check if a QUEUE is in DROPPING state.
Definition: queue.c:718
NTSTATUS DroppingReturnStatus
The NTSTATUS return code with which the IRP are completed if we are dropping IRPs.
Definition: queue.h:77
NTSTATUS QueueSignal(PQUEUE Queue)
Signal to the QUEUE need for processing.
Definition: queue.c:900