// the queue and spinlock should be part of each device context
LIST_ENTRY irpQueue;
KSPIN_LOCK irpQueueSpinLock;
VOID InitIrpQueue()
{
InitializeListHead(&irpQueue);
KeInitializeSpinLock(&irpQueueSpinLock);
}
VOID EnqueueIrp(PIRP Irp)
{
KIRQL oldIrql;
something is missing here!
KeAcquireSpinLock(&irpQueueSpinLock, &oldIrql);
InsertTailList( &irpQueue, &Irp->Tail.Overlay.ListEntry);
KeReleaseSpinLock(&irpQueueSpinLock, oldIrql);
}
PIRP DequeueIrp()
{
KIRQL oldIrql;
PLIST_ENTRY listEntry;
PIRP nextIrp = NULL;
KeAcquireSpinLock(&irpQueueSpinLock, &oldIrql);
listEntry = RemoveHeadList(&irpQueue);
KeReleaseSpinLock(&irpQueueSpinLock, oldIrql);
if (listEntry){
nextIrp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
}
// something is missing here!
return nextIrp;
}
Figure 3 IRP Queuing with Cancellation
// the queue and spinlock should be part of each device context
LIST_ENTRY irpQueue;
KSPIN_LOCK irpQueueSpinLock;
VOID InitIrpQueue()
{
InitializeListHead(&irpQueue);
KeInitializeSpinLock(&irpQueueSpinLock);
}
NTSTATUS EnqueueIrp(PIRP Irp)
{
PDRIVER_CANCEL oldCancelRoutine;
KIRQL oldIrql;
NTSTATUS status;
KeAcquireSpinLock(&irpQueueSpinLock, &oldIrql);
// must set a cancel routine before checking the Cancel flag
oldCancelRoutine = IoSetCancelRoutine(Irp, IrpCancelRoutine);
ASSERT(!oldCancelRoutine);
if (Irp->Cancel){
// This IRP has already been cancelled, so complete it now.
// We must clear the cancel routine before completing the IRP.
// We must release the spinlock before calling out of the driver.
IoSetCancelRoutine(Irp, NULL);
KeReleaseSpinLock(&irpQueueSpinLock, oldIrql);
status = Irp->IoStatus.Status = STATUS_CANCELLED;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
}
else {
// This macro sets a bit in the current stack location to indicate that
// the IRP may complete on a different thread.
IoMarkIrpPending(Irp);
InsertTailList( &irpQueue, &Irp->Tail.Overlay.ListEntry);
KeReleaseSpinLock(&irpQueueSpinLock, oldIrql);
status = STATUS_SUCCESS;
}
return status;
}
Figure 4 IRP Dequeuing with Cancellation
PIRP DequeueIrp()
{
KIRQL oldIrql;
PIRP nextIrp = NULL;
KeAcquireSpinLock(&irpQueueSpinLock, &oldIrql);
while (!nextIrp && !IsListEmpty(&irpQueue)){
PLIST_ENTRY listEntry = RemoveHeadList(&irpQueue);
nextIrp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
IoSetCancelRoutine(nextIrp, NULL);
if (nextIrp->Cancel){
// This IRP was just cancelled.
// The cancel routine may or may not have been called,
// but it doesn't matter because it will not find the IRP in the
// list.
// Must release the spinlock when calling outside the driver to
// complete the IRP.
KeReleaseSpinLock(&irpQueueSpinLock, oldIrql);
nextIrp->IoStatus.Status = STATUS_CANCELLED;
IoCompleteRequest(nextIrp, IO_NO_INCREMENT);
KeAcquireSpinLock(&irpQueueSpinLock, &oldIrql);
nextIrp = NULL;
}
}
KeReleaseSpinLock(&irpQueueSpinLock, oldIrql);
return nextIrp;
}
Figure 5 IrpCancelRoutine
VOID IrpCancelRoutine(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
KIRQL oldIrql;
PIRP firstIrp = NULL, irpToComplete = NULL;
// In this implementation, I don't assume that the IRP being cancelled is
// in the queue;
// I only complete the IRP if it IS in the queue.
KeAcquireSpinLock(&irpQueueSpinLock, &oldIrql);
while (!IsListEmpty(&irpQueue)){
PLIST_ENTRY listEntry;
PIRP thisIrp;
listEntry = RemoveHeadList(&irpQueue);
thisIrp = CONTAINING_RECORD(listEntry, IRP, Tail.Overlay.ListEntry);
if (thisIrp == Irp){
// This is the IRP being cancelled; we'll complete it after we
// release the spinlock
ASSERT(thisIrp->Cancel);
irpToComplete = thisIrp;
// keep looping so that order of the remaining IRPs is preserved
}
else {
// This is not the IRP being cancelled, so put it back
if (thisIrp == firstIrp){
// finished going through the list
InsertHeadList(&irpQueue, listEntry);
break;
}
else {
InsertTailList(&irpQueue, listEntry);
if (!firstIrp){
firstIrp = thisIrp;
}
}
}
}
KeReleaseSpinLock(&irpQueueSpinLock, oldIrql);
// Finally, release the global cancel spinlock whether or not we are
// completing this IRP
IoReleaseCancelSpinLock(Irp->CancelIrql);
if (irpToComplete){
// complete this cancelled IRP only if it was in the list
irpToComplete ->IoStatus.Status = STATUS_CANCELLED;
IoCompleteRequest(irpToComplete, IO_NO_INCREMENT);
}
}