however testing the Irp->Cancel flag only makes sense if you also hold the global Cancel spinlock before the test and until after the IRP is queued. Otherwise you might as well look at any random memory location and decide based on the value of the 6th bit.
Now the aforementioned "Cancel Logic in Windows Drivers" document on page 9 states:
The interlocked sequence in IoSetCancelRoutine ensures that the value of Irp->Cancel is read before the Cancel routine is removed.
Is that why it's safe to read Irp->Cancel without holding the global Cancel spinlock?
Thanks Mark but I was really wondering whether it's safe to read the Irp->Cancel flag without holding the global Cancel spinlock. That OSR article I linked says no.
That article is ancient and wrong.
Anyway, the answer is no.
lock;
x = irp->cancel;
unlock;
and x = irp->cancel;
are equivalent. If you read the value of Irp->cancel with the cancel spinlock held, the value can change as soon as you release the cancel spinlock.
This is correct:
In drivers that manage their own queues of IRPs and use driver-supplied spin locks to synchronize queue access, the driver routines do not need to acquire the cancel spin lock before calling IoSetCancelRoutine.
KeAcquireSpinLock(&devExtension->QueueLock, &oldIrql);
IoSetCancelRoutine(Irp, CsampCancelIrp);
if (Irp->Cancel &&
IoSetCancelRoutine(Irp, NULL)) {
//
// The IRP has already been canceled, but the I/O
// Manager has not yet called the Cancel routine.
status = STATUS_CANCELLED;
//
// Complete the IRP later, after releasing the lock.
//
} else {
IoMarkIrpPending(Irp);
InsertTailList(&devExtension->PendingIrpQueue,
&Irp->Tail.Overlay.ListEntry);
status = STATUS_PENDING;
}
KeReleaseSpinLock(&devExtension->QueueLock, oldIrql);
In this context the check of Irp->Cancel is safe because it's done after a cancel routine is established. To understand why there's some important bits of information to know about the I/O Manager's cancel processing:
Grab the cancel spinlock
Set Irp->Cancel to TRUE
Clear the cancel routine with IoSetCancelRoutine(NULL)
Call the cancel routine if there was one
Once Irp->Cancel is set to TRUE it never resets to FALSE
So in your dispatch entry point you do the following:
Call IoSetCancelRoutine to set your cancel routine
Check to see if Irp->Cancel is TRUE. If not you're good to go
If Irp->Cancel is TRUE it means the IRP was canceled either before or after you set your cancel routine. So, you call IoSetCancelRoutine(NULL) to interlock yourself against the I/O Manager's cancel processing. If it returns NULL you lost the race, if it returns your cancel routine you "won" the race and have to cancel the IRP yourself.
Thanks Scott. In my dispatch entry routine, if Irp->Cancel is FALSE, can I conditionally either complete the IRP because the desired data is available OR pend the IRP because the desired data is NOT available?
The only time you have to worry about that is when you queue an irp for deferred processing.
If you use cancel safe queues then the required operations are correctly performed for you.
You need to follow the processing outlined in that document. Or just use a cancel safe queue and stop worrying about this (the CSQ package was added to the XP DDK to solve this exact confusion)
Thanks Mark, Scott, and Tim. I would like to learn and use cancel safe queues but my modifications need to be done soon. Plus, this driver code I've inherited has a lot of other problems exposed by driver verifier that thankfully I've been able to fix. I think I'm starting to get how the I/O Manager and a driver race when it comes to cancelation.
This is a bit of an exaggeration, but the Irp->Cancel flag is basically just a suggestion. It says that some entity above you would like to have the IRP cancelled. You are not, in the most literal sense, required to do anything about it, although to be a good neighbor, you should complete it with a canceled status when it is convenient to do so. But you don't have to take immediate action. If you miss the signal, it's no biggie, you'll catch it later.
Thank you Tim. I was also thinking Irp->Cancel was just a suggestion because dispatch routines that do not set IRP cancel routines simply ignore Irp->Cancel.