In the WDM book, I see the way to send an asynchronous IRP to a lower
driver is to do an IoAcquireRemoveLock(), install a completion
routine, do the IoCallDriver(), and do an IoReleaseRemoveLock() in the
completion routine. The remove lock is meant to prevent a PNP remove
from being forwarded to the lower driver while it's processing the
IRP, but won't the lower driver still be blocked in its call to
IoCompleteRequest() when the completion routine releases the remove
lock? That is, couldn't the other driver then be removed before its
completely done with the IRP?
I can think of two cases:
If the lower driver is calling IoCompleteRequest() from a worker
thread or a DPC, then it should be expected that thread or DPC will be
allowed to finish before the driver is removed. This case should be
okay.
If the lower driver is calling IoCompleteRequest() from its
dispatch routine, won't the completion routine remove the lock before
the dispatch routine returns so the lower driver could be removed
before the next few instructions that return from its dispatch
routine? This case seems to require the same extra reference solution
the WDM book mentions that initially confused me for a few days. I
think the remove lock needs to be acquired twice and released in both
the completion routine and after the call to IoCallDriver(), to make
sure the lower driver can't be removed until they're both finished
(whichever finishes first). As an alternative, I guess an extra
reference would work too.
(Of course, this alternative makes me wonder what are the advantages
of using a remove lock vs. taking extra references. They can both be
used to keep a device object in memory, but I guess the remove lock
can be used to start failing new IRPs at a certain point in the
removal process, so the removal goes faster. It doesn't actually
prevent new IRPs from being received by a dispatch routine. It just
allows them to be quickly completed with error status.)